クラウドMacでXcodeアーカイブの書き出し失敗を調べる

クラウドMacでXcodeアーカイブの書き出し失敗を調べる

アーカイブ作成タスクは成功しているのに、パイプラインが -exportArchive の段階で終了することがあります。この種の障害では、末尾の「export failed」というメッセージだけを見て判断すると原因を見誤りやすくなります。実際の原因としては、書き出し方法と署名情報の不一致、アーカイブ内の権限の変化、CI が別の Xcode を使用していることなどが考えられます。何度も再実行するのではなく、同じ xcarchive を保持したうえで、アーカイブ作成、書き出し、診断を個別に再現できる工程へ分離するのが有効です。

まず失敗時の状態を保存する

失敗後すぐに作業ディレクトリを削除したり、元のアーカイブを上書きしたりしないでください。少なくとも xcarchive、実際に使用した ExportOptions.plist、標準出力の全文、Xcode のパスとバージョンを保存します。RunnerVM の専有物理ノードはこのような状態の保持に適していますが、パイプライン側でも診断ファイルをジョブの成果物ディレクトリへ明示的にコピーする必要があります。

set -euo pipefail

RUN_DIR="$PWD/export-diagnostics"
ARCHIVE_PATH="$PWD/build/App.xcarchive"

mkdir -p "$RUN_DIR"
xcode-select -p > "$RUN_DIR/xcode-path.txt"
xcodebuild -version > "$RUN_DIR/xcode-version.txt"
sw_vers > "$RUN_DIR/macos-version.txt"
cp ExportOptions.plist "$RUN_DIR/ExportOptions.plist"
ditto "$ARCHIVE_PATH" "$RUN_DIR/App.xcarchive"

同時に、アーカイブファイルのチェックサムも記録します。後から誰かがアーカイブを差し替えた場合、調査対象が変わったことをすぐに検出できます。

ditto -c -k --keepParent "$ARCHIVE_PATH" "$RUN_DIR/App.xcarchive.zip"
shasum -a 256 "$RUN_DIR/App.xcarchive.zip" > "$RUN_DIR/checksums.txt"

書き出しの失敗は、アーカイブが無効であることを意味しません。まずアーカイブが完全かどうかを確認し、その後で問題がアーカイブ作成工程と配布工程のどちらにあるかを判断します。

アーカイブが書き出し可能な状態か確認する

xcarchive は実体としてはディレクトリです。最初にメタデータ、アプリケーションバンドル、実行ファイルが存在することを確認し、次にアーカイブに記録されたアプリケーション識別子とバージョンを読み取ります。プロジェクト名からパスを組み立てるのではなく、Info.plist から取得してください。

INFO="$ARCHIVE_PATH/Info.plist"
APP_RELATIVE=$(/usr/libexec/PlistBuddy -c \
  "Print :ApplicationProperties:ApplicationPath" "$INFO")
APP_PATH="$ARCHIVE_PATH/Products/$APP_RELATIVE"

plutil -lint "$INFO"
test -d "$APP_PATH"
test -f "$APP_PATH/Info.plist"

plutil -p "$INFO"
plutil -p "$APP_PATH/Info.plist"
codesign --verify --deep --strict --verbose=2 "$APP_PATH"

特に ApplicationPathCFBundleIdentifier、バージョン番号、署名検証の結果を照合します。アプリケーションバンドル自体が存在しない場合や、すでに codesign --verify が失敗している場合は、書き出しオプションを調整し続けるのではなく、archive コマンド、ビルド工程のスクリプト、成果物のコピー処理を調査すべきです。

アプリの権限とプロビジョニングプロファイルを比較する

書き出し工程では、署名の対応関係が再評価されます。アプリケーションに実際に設定されている権限と、埋め込まれたプロビジョニングプロファイルの権限をそれぞれ書き出し、構造として比較できます。

codesign -d --entitlements "$RUN_DIR/app-entitlements.plist" "$APP_PATH"

PROFILE="$APP_PATH/embedded.mobileprovision"
security cms -D -i "$PROFILE" > "$RUN_DIR/profile.plist"

plutil -extract Entitlements xml1 \
  -o "$RUN_DIR/profile-entitlements.plist" \
  "$RUN_DIR/profile.plist"

plutil -p "$RUN_DIR/app-entitlements.plist"
plutil -p "$RUN_DIR/profile-entitlements.plist"

ファイル内のテキストの並び順だけを比較してはいけません。アプリケーションが使用する権限をプロビジョニングプロファイルが許可しているか、アプリケーション識別子、チーム識別子、環境に依存する権限が一致しているかを確認します。メインアプリケーションが通っても、埋め込まれたすべてのコンポーネントを書き出せるとは限らないため、拡張ターゲットも一つずつ確認する必要があります。

ExportOptions.plistを検証する

まず plutil -lint ExportOptions.plist を実行して形式上のエラーを除外し、次に現在の Xcode のヘルプ出力で利用可能なキーと値を確認します。Xcode のバージョンによって method の値が変更されたり、古い記述方法が非推奨になったりするため、過去のジョブから設定をコピーしてそのまま実行しないでください。

最小構成では、現在の配布フローで本当に必要なフィールドだけを残せます。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>method</key>
  <string>development</string>
  <key>signingStyle</key>
  <string>manual</string>
  <key>stripSwiftSymbols</key>
  <true/>
</dict>
</plist>

ここでの method は開発用の書き出し例にすぎず、ほかの配布方法へそのまま適用することはできません。まず次のコマンドを実行し、現在のツールチェーンが出力する説明を基準にしてください。

plutil -lint ExportOptions.plist
xcodebuild -help > export-help.txt
grep -A 120 "Available keys for -exportOptionsPlist" export-help.txt

手動署名を使用する場合は、設定内のマッピングがメインアプリケーションとすべての拡張を網羅しているかも確認します。マッピングの不足、識別子の誤り、署名方式の混在は、多くの場合、書き出し工程まで進んで初めて表面化します。

書き出しだけを再実行して完全なログを保存する

アーカイブが有効であることを確認したら、固定した絶対パスを使用して書き出しだけを実行します。失敗時にスクリプトが一時ディレクトリを即座に削除しないようにしてください。

set +e

xcodebuild -exportArchive \
  -archivePath "$ARCHIVE_PATH" \
  -exportPath "$RUN_DIR/exported" \
  -exportOptionsPlist "$RUN_DIR/ExportOptions.plist" \
  2>&1 | tee "$RUN_DIR/export.log"

STATUS=${PIPESTATUS[0]}
set -e

printf '%s
' "$STATUS" > "$RUN_DIR/export-status.txt"
exit "$STATUS"

ログでは、末尾の要約ではなく、最初に現れる具体的なエラーを探します。署名、プロビジョニングプロファイル、権限、書き出し方法に関する語句で一次絞り込みができます。

grep -Ein \
  "error:|provision|entitlement|certificate|signing|export" \
  "$RUN_DIR/export.log" > "$RUN_DIR/export-errors.txt" || true

xcodebuild の出力に配布ログバンドルのパスが示されることがあります。その場合は、該当ディレクトリを丸ごとアーカイブしてください。通常、この中には IDEDistribution の各工程で行われた判定が含まれており、パイプラインに残った数行のエラーよりも根本原因に近い情報を得られます。

修正後の受け入れチェックリストを作る

修正後は終了コードだけで成功を判断しないでください。少なくとも、書き出し先ディレクトリに対象ファイルが存在すること、署名検証に通ること、アプリケーション識別子とバージョンがアーカイブと一致することを確認し、最終的な ExportOptions.plist と Xcode のバージョンを一緒に保存します。

ジョブの末尾に、次の確認項目を組み込むことを推奨します。

  1. 圧縮したアーカイブのチェックサムが変わっていない。
  2. 現在の xcode-select -p が想定するツールチェーンと一致している。
  3. メインアプリケーションとすべての拡張が署名検証に通っている。
  4. アプリケーションの権限がプロビジョニングプロファイルの許可範囲を超えていない。
  5. 書き出し設定が plutil の検証に通り、キーと値が現在の Xcode でサポートされている。
  6. 書き出しログ、配布ログバンドル、最終成果物が同じ一式として保存されている。
  7. 修正では一つの変数だけを変更し、元のアーカイブで繰り返し検証できる。

このように処理すれば、アーカイブの書き出しは原因を説明しにくいブラックボックスではなくなります。ツールチェーンのずれ、設定ミス、署名関係の異常、アーカイブの破損を明確に切り分けられるようになり、多くの修正を再ビルドせずに検証できます。

よくある質問

アーカイブ成功後に書き出しだけ失敗した場合、すぐ再作成すべきですか?

再作成の前に元のxcarchiveを保存してください。メタデータ、アプリ本体、埋め込みプロファイル、署名権限を確認すると、書き出し段階だけの問題を切り分けられます。

ExportOptions.plistは異なるXcodeバージョンで共用できますか?

無条件の共用は避けます。選択中のXcodeが出力するxcodebuildのヘルプでmethodなどの有効値を確認し、plistの構文も毎回検証してください。

Runner M4

次のビルドを専用の物理Macで実行

利用期間に合わせてクラウドMacノードを選び、コンソールから注文、接続情報、サポート依頼を管理できます。

プランを選んで注文