エンジニアリングガイド

クラウドMac CI向けXcode Scheme共有と実行入口の検証

クラウドMac CI向けXcode Scheme共有と実行入口の検証

チームがクラウドMacへプロジェクトをチェックアウトした後、見落とされやすい問題はコンパイルエラーではありません。パイプラインが想定したSchemeを見つけられない、あるいは同名のSchemeがリモート環境では別のTargetを実行してしまうことです。ローカルのXcodeは開発者のディレクトリにあるユーザー設定を読み込みますが、クリーンなCIワークスペースが認識できるのはリポジトリ内のファイルだけです。ビルドの実行入口を安定させるには、Schemeもスクリプトや依存関係のロックファイルと同様に、バージョン管理と検証の対象にする必要があります。

SchemeをCIインターフェースとして扱う

パイプラインから利用できるSchemeでは、少なくとも4つの項目を定義する必要があります。ビルドするTarget、使用するBuild Configuration、実行するTest Bundle、そしてArchive時に使用する構成です。Xcodeのメニューに名前が表示されているだけでは、これらの情報がコミット済みとは限りません。

まず、共有ファイルが存在するか確認します。

find . \( -path "*/xcshareddata/xcschemes/*.xcscheme" \
  -o -path "*/xcuserdata/*/xcschemes/*.xcscheme" \) -print

xcuserdata内のSchemeはユーザー設定であり、CIの実行入口にはできません。XcodeのManage SchemesでSharedを有効にし、次のいずれかのパスにファイルが作成されたことを確認します。

App.xcodeproj/xcshareddata/xcschemes/App.xcscheme
App.xcworkspace/xcshareddata/xcschemes/App.xcscheme

続いて、git statusgit check-ignore -vを使用し、ファイルが追跡対象になっていること、広すぎる*.xcuser*xcshareddataの除外ルールに誤って一致していないことを確認します。

「ローカルで見える」とは、現在のユーザーディレクトリに設定があるというだけです。「CIで利用できる」ことは、クリーンなチェックアウト先で実行したコマンドの結果によって証明する必要があります。

Workspace、Scheme、構成を固定する

パイプラインでXcodeによるプロジェクトの自動選択に依存してはいけません。CocoaPods、集約プロジェクト、ローカルPackageが存在する場合、.xcodeproj.xcworkspaceでは開いた結果が異なることがあります。3つの実行入口の値をCI変数として明示します。

WORKSPACE="App.xcworkspace"
SCHEME="App"
CONFIGURATION="Release"

xcodebuild \
  -workspace "$WORKSPACE" \
  -scheme "$SCHEME" \
  -configuration "$CONFIGURATION" \
  -list

-listの出力には、想定したSchemeが含まれている必要があります。続いてJSON出力を機械的に検証し、ログの目視確認だけに依存しないようにします。

xcodebuild -workspace "$WORKSPACE" -list -json > xcode-list.json
python3 - <<'PY'
import json
data = json.load(open("xcode-list.json"))
schemes = data.get("workspace", {}).get("schemes", [])
if "App" not in schemes:
    raise SystemExit("Required Xcode scheme is missing")
PY

リポジトリに開発、ステージング、本番用の実行入口を併存させる場合は、App-DevApp-StagingApp-Releaseのように用途が分かる名前を付けます。スクリプトで一覧の先頭にあるSchemeを選択してはいけません。依存関係を追加すると、順序が変わる可能性があります。

Build、Test、Archiveを個別に検証する

Schemeを共有できても、「見える」という問題が解決しただけで、各アクションが正しいことまでは証明できません。検証は3段階に分けることを推奨します。

解決後のビルド設定を確認する

まず最終的な設定を出力し、PRODUCT_BUNDLE_IDENTIFIERCONFIGURATIONSDKROOTSUPPORTED_PLATFORMS、成果物の出力先を重点的に確認します。

xcodebuild \
  -workspace "$WORKSPACE" \
  -scheme "$SCHEME" \
  -configuration "$CONFIGURATION" \
  -showBuildSettings > build-settings.txt

grep -E "PRODUCT_BUNDLE_IDENTIFIER|CONFIGURATION =|SDKROOT|SUPPORTED_PLATFORMS" \
  build-settings.txt

ここで確認できるのは、Scheme、プロジェクト設定、コマンドライン引数を統合した後の結果です。project.pbxprojを直接調べるより、実際の実行内容に近い情報を得られます。

テストの実行入口を検証する

テスト工程では、先にテスト可能な成果物をビルドしてからテストを実行します。これにより、「コンパイルの実行入口が壊れている」場合と「テスト実行が失敗している」場合を切り分けられます。

xcodebuild \
  -workspace "$WORKSPACE" \
  -scheme "$SCHEME" \
  -destination "platform=iOS Simulator,name=iPhone 16" \
  build-for-testing

シミュレータ名には、現在のマシンでxcrun simctl list devices availableを実行して得られる名前を使用します。個人のコンピュータにしか存在しないデバイスを固定値として指定してはいけません。チームでランタイムを固定する必要がある場合は、Xcodeのバージョンと対象OSのバージョンも併せて記録します。

アーカイブ処理を個別に確認する

Archiveでは、RunやTestとは異なる構成が使われることがあります。まず.xcscheme内のArchiveActionで使用される構成を確認し、その後、管理されたジョブ内で実際にアーカイブを実行します。

xcodebuild \
  -workspace "$WORKSPACE" \
  -scheme "$SCHEME" \
  -configuration Release \
  -destination "generic/platform=iOS" \
  -archivePath "$PWD/artifacts/App.xcarchive" \
  archive

通常のbuildが成功しても、Archiveの検証を省略してはいけません。リソース処理、スクリプトフェーズ、アーカイブ設定の問題は、この段階で初めて表面化することがよくあります。

ローカル状態を排除して再検証する

最も確実な確認方法はDerivedDataを削除することではなく、新しいディレクトリへ再度クローンすることです。これにより、未コミットのScheme、ローカルのシンボリックリンク、未追跡の設定、暗黙の依存関係をまとめて検出できます。

ROOT="$(mktemp -d)"
git clone --no-local . "$ROOT/repo"
cd "$ROOT/repo"
git submodule update --init --recursive
xcodebuild -workspace App.xcworkspace -scheme App -list

プロジェクトが生成処理に依存する場合は、生成コマンドをビルド前に配置し、生成結果が安定していることも確認します。手順書に「最初にXcodeでプロジェクトを一度開く」と記載してはいけません。この種の手順は無人ジョブに組み込めず、不足しているファイルを見えにくくします。

クリーン環境での検証では、少なくとも次の項目を確認します。

確認項目 合格基準
Schemeの可視性 xcodebuild -list -jsonが指定した名前を返す
ファイルの配置と管理 Schemeがxcshareddataにあり、Gitで追跡されている
ビルド構成 コマンドラインで解決された結果がパイプラインの想定と一致する
テストの実行入口 build-for-testingでテスト成果物を生成できる
アーカイブの実行入口 汎用iOSターゲットで.xcarchiveを生成できる
非対話実行 Xcodeを開く必要がなく、個人のディレクトリも参照しない

実行入口のドリフトをマージ前に阻止する

SchemeはXMLファイルですが、マージ競合は通常のテキスト競合として処理されがちです。その結果、名前は残っていてもTestableやBuildActionEntryが失われることがあります。マージリクエストに軽量なチェックを追加し、共有ファイルの存在確認、xcuserdataのコミット禁止、xcodebuild -list -jsonの実行を行います。

SchemeのPre-actionsとPost-actionsにも注意が必要です。スクリプトが絶対パス、対話型のShell設定、特定の開発者のマシンにしか存在しないツールを参照していると、リモート実行は依然として失敗します。スクリプトではSRCROOTなどのビルド変数を基準にファイルを特定し、依存関係が不足している場合は明示的に終了させます。

最終的な合格基準は単純です。空のディレクトリへリポジトリを取得し、ドキュメントに記載されたXcodeバージョンとコマンドだけを使用して、Schemeの列挙、設定の解決、テスト成果物のビルド、アーカイブの完了まで実行できることです。この状態になって初めて、G-Mini上のクラウドMacは、特定の開発者のコンピュータに偶然残っていた状態ではなく、リポジトリで定義された実行入口を使用していると言えます。

よくある質問

ローカルでは動くSchemeをクラウドMac CIが見つけられないのはなぜですか?

Schemeがxcuserdataにだけ保存されている可能性があります。xcshareddata/xcschemesへ共有Schemeを書き出し、そのファイルをGitへ追加する必要があります。

Schemeを共有すればArchiveも成功すると考えてよいですか?

いいえ。共有は可視性だけを解決します。Release構成、Archive対象、事前アクションを確認し、クリーンなチェックアウトから実際のArchiveを少なくとも一度実行してください。

専有物理ノード

次のビルドキューに備えるクラウドMac

2種類のM4構成と5つのノードを比較し、実際の作業サイクルに合わせて日単位、週単位、月単位、または四半期単位でレンタルできます。

クラウドMacプランを選ぶ