App Clipを開発用MacのSchemeから起動できても、成果物が正しいとは限りません。特に見落とされやすいのは、ビルドの失敗ではなく、Clipがホストアーカイブに含まれていない、親子の識別子が一致していない、または呼び出しURLが理想的な経路しかカバーしていないといった問題です。こうしたチェックをクラウドMac上の継続的インテグレーションに組み込むことで、すべてのコミットを同一のXcode、シミュレータ、検証スクリプトで確認できます。
検証可能な成果物を先に定義する
「Build Succeeded」だけを合格条件にしてはいけません。完全な検証では、少なくともホストの.xcarchive、App Clip単体のビルドログ、解析済みのEntitlementsファイル、呼び出しシナリオの記録を生成する必要があります。テストURLはSchemeの個人設定に分散させず、リポジトリの設定で管理することを推奨します。
| チェック層 | 入力 | 合格条件 |
|---|---|---|
| ビルド | App Clip Scheme | シミュレータ向けターゲットをビルドできる |
| 埋め込み | ホストアーカイブ | AppClips配下にClipが存在する |
| 関連付け | 両アプリの識別子とEntitlements | 親子関係が一致している |
| 呼び出し | 基本URL、パラメータ付きURL、不正URL | ルーティングとフォールバックが想定どおりに動作する |
Clipを単体で起動して確認できるのは、ビジネスロジック上のエントリーポイントです。ホストをアーカイブして埋め込み関係を確認して初めて、配布可能な構造を検証できます。この2つは相互に代替できません。
ビルドディレクトリを固定する
クラウド上のジョブでは専用のDerivedDataを使用し、前回の成果物によってコピー工程のエラーが隠れないようにします。最初にApp Clipのシミュレータ向けバージョンをビルドし、その後でホストをアーカイブします。Scheme名を環境変数で渡せば、同じスクリプトを複数のブランチで再利用できます。
set -euo pipefail
ROOT="$PWD"
OUT="$ROOT/.ci-artifacts"
DERIVED="$OUT/DerivedData"
ARCHIVE="$OUT/HostApp.xcarchive"
rm -rf "$OUT"
mkdir -p "$OUT"
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$CLIP_SCHEME" \
-sdk iphonesimulator \
-destination "$SIMULATOR_DESTINATION" \
-derivedDataPath "$DERIVED" \
clean build | tee "$OUT/app-clip-build.log"
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$HOST_SCHEME" \
-destination "generic/platform=iOS" \
-archivePath "$ARCHIVE" \
archive | tee "$OUT/host-archive.log"
SIMULATOR_DESTINATIONには、パイプラインにインストール済みのデバイスタイプとOSバージョンを固定で指定できます。Xcodeをアップグレードした後は、まずベースライン用ジョブを更新し、通常の機能コミットによってテストランタイムまで変更されないようにしてください。
ホストとClipのバンドル関係を確認する
埋め込まれた成果物を特定する
アーカイブが完了したら、Products/Applicationsからホストの.appを探し、そのAppClipsディレクトリに想定したターゲットがちょうど1つ存在することを確認します。アプリのファイル名を固定値として扱わず、拡張子で検索して結果数を確認してください。
HOST_APP="$(find "$ARCHIVE/Products/Applications" -maxdepth 1 -name '*.app' -print -quit)"
CLIP_APP="$(find "$HOST_APP/AppClips" -maxdepth 1 -name '*.app' -print -quit)"
test -n "$HOST_APP"
test -n "$CLIP_APP"
HOST_ID="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$HOST_APP/Info.plist")"
CLIP_ID="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$CLIP_APP/Info.plist")"
printf '%s
' "$HOST_ID" > "$OUT/host-bundle-id.txt"
printf '%s
' "$CLIP_ID" > "$OUT/clip-bundle-id.txt"
AppClipsディレクトリが存在しない場合は、まずホストTargetの埋め込みフェーズ、Clip Targetが現在のアーカイブSchemeに含まれているか、そしてDebugで有効な設定がRelease構成でも適用されているかを確認します。
最終的なEntitlementsを比較する
プロジェクトファイル上の宣言が、署名済み成果物の最終的な内容と一致するとは限りません。codesign -d --entitlements :-を使用してホストとClipのEntitlementsをそれぞれ書き出し、plutilで安定した形式に変換します。ゲートでは、Clipの親アプリ関連付け値が現在のホストを指していることに加え、継承すべきでない機能が制限されていることも確認する必要があります。
codesign -d --entitlements :- "$HOST_APP" > "$OUT/host-entitlements.plist"
codesign -d --entitlements :- "$CLIP_APP" > "$OUT/clip-entitlements.plist"
plutil -lint "$OUT/host-entitlements.plist"
plutil -lint "$OUT/clip-entitlements.plist"
Xcodeのバージョン変更によってキーの順序が変わる可能性があるため、ファイル全体をテキストとして比較するだけでは不十分です。安定している必要があるキーを抽出して個別に比較し、失敗後に確認できるよう、実際の値をビルド成果物へ保存してください。
3種類のURLでルーティングを検証する
呼び出しテストでは、少なくとも基本エントリーポイント、正しいパラメータ、不正な入力をカバーします。たとえば、基本エントリーポイントでは既定の軽量シーンが開き、リソース番号付きURLでは指定されたコンテンツへ移動し、パラメータが欠落または不正な場合は空白画面にとどまらず、安全なページへ戻る必要があります。
URLをテスト入力として扱う
App Clip Schemeで_XCAppClipURLを使用する方法は手動デバッグには適していますが、継続的インテグレーションを開発者のローカルScheme状態に依存させるべきではありません。より堅牢な方法は、ルート解析処理がURLを受け取る設計にし、単体テストで入力を直接網羅したうえで、アプリのライフサイクルを確認するシミュレータのスモークテストを1件残すことです。
func testInvocationRoutes() throws {
let base = try XCTUnwrap(URL(string: invocationBaseURL))
XCTAssertEqual(router.route(for: base), .home)
let item = try XCTUnwrap(URL(string: invocationItemURL))
XCTAssertEqual(router.route(for: item), .item(id: "42"))
let malformed = try XCTUnwrap(URL(string: invocationMalformedURL))
XCTAssertEqual(router.route(for: malformed), .fallback)
}
これにより、ドメイン名の解決、パスのマッチング、クエリパラメータの検証、画面の起動を分離できます。ルーティングの単体テストで分岐を網羅し、シミュレータのジョブでは実際のプロセスが起動して初期画面を表示できることだけを確認すれば、失敗原因をより直接的に特定できます。
ゲートを厳格化し、失敗時の情報を残す
ゲートが失敗した場合は、少なくともxcodebuildログ、2つのInfo.plist、書き出したEntitlementsファイル、ホストとClipの識別子、失敗したテストケース名をアーカイブします。ログにアクセストークン、一時的な認証情報、ローカルパス内の機密情報が含まれる場合は、アップロード前にマスキングしてください。
チェック順序は、専用ビルドディレクトリの消去、Clipのビルド、ホストのアーカイブ、埋め込みの確認、重要なEntitlementsの比較、ルーティングテストの実行、シミュレータのスモークテストという順に固定することを推奨します。前段の構造チェックに失敗した時点で直ちに停止し、不正なアーカイブに対してシミュレータ時間を浪費しないようにします。
GPUMini上のジョブでも、xcodebuild -version、選択したSDK、シミュレータランタイムを明示的に記録してください。長期的に保守すべきなのは一度の成功を示すスクリーンショットではなく、入力が明確で、成果物を追跡でき、失敗後にすばやく再現できる一連のチェック項目です。
よくある質問
App Clipターゲットだけをビルドすれば十分ですか?
十分ではありません。コンパイルは確認できますが、ホストアーカイブ内の配置、識別子の関係、最終的な権限設定は別途検査が必要です。
呼び出しURLは一つだけ検証すればよいですか?
基本パス、パラメータ付きパス、不正な入力を用意し、ルーティング、値の解析、安全なフォールバックをそれぞれ確認します。
ローカル起動に成功してもアーカイブ検査が必要なのはなぜですか?
ローカルのSchemeはApp Clipを直接起動できるため、ホストへのコピー処理や識別子、権限のずれを見逃す可能性があるからです。
開発やビルド作業に使うクラウドMacを選ぶ
2種類のM4構成、4種類のレンタル期間、販売中の4つのノードを比較し、用途に合わせてデプロイします。