在雲端 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 的說明輸出確認可用鍵值。不同版本可能調整 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,檢查其中的中繼資料、App 套件、描述檔與簽署權限;重新封存可能覆蓋定位匯出錯誤所需的現場。

ExportOptions.plist 能直接跨 Xcode 版本使用嗎?

不建議直接沿用。應先查看目前所選 Xcode 的 xcodebuild 說明,確認 method 等值仍受支援,並在每次匯出前驗證 plist 語法。

Runner M4

讓下一次建置在獨享物理 Mac 上執行

依任務週期選擇雲端 Mac 節點,並在控制台管理訂單、連線資訊與支援工單。

選擇方案並下單