限時優惠

Xcode Product → Archive 全流程解析:為什麼你的 CI 總是卡在這裡

CI Xcode Archive · iOS 發版
2026-06-15 約 14 分鐘閱讀

本機點一下 Product → Archive,Organizer 裡就能匯出 IPA;CI 跑 xcodebuild archive 卻常在同一階段掛 20 分鐘、逾時、或靜默失敗。差距不在「會不會用 Xcode」,而在你是否把 Archive 當成一條可觀測的流水線,而不是一條黑盒指令。

本文按階段拆解:Build → Archive → Sign → Export 各自做什麼、xcodebuild 如何等價重現選單操作,以及 CI 上七類高頻卡點的症狀—動作對照表。

本文要點

  1. Archive ≠ Build:Archive 固定走 Release + Any iOS Device,並產生 .xcarchive;本機 Debug 編過不代表 Archive 能過。
  2. CI 卡點集中在四段:Scheme/設定編譯快取簽章與鑰匙圈exportArchive——日誌裡要先定位段,再動手。
  3. xcodebuild archive 與選單 Archive 等價,但 CI 必須明確指定 -scheme-configuration Release-destination 'generic/platform=iOS'
  4. 「卡住不動」多半是等鑰匙圈授權等網路拉描述檔、或記憶體 swap——不是 xcodebuild 當機了。
  5. 自託管雲 Mac + 持久 DerivedData/專用鑰匙圈,往往比託管 Runner「每次冷 Archive」穩定一個數量級。
Xcode Archive 與 CI 流水線示意
本機 Archive 有 GUI 回饋;CI 裡同一條鏈必須靠日誌分段,否則只會看到「Job 跑了 40 分鐘」。

1. Archive 和 Build 到底差在哪

很多團隊把 CI 失敗概括成「xcodebuild 掛了」,其實 Cmd+B BuildProduct → Archive 是兩條路徑:

維度 Build(⌘B) Archive
典型 Configuration Debug(本機開發) Release(上架/TestFlight)
Destination 模擬器或已連接實機 Any iOS Device (arm64)
產物 .app 在 DerivedData .xcarchive + 可匯出 IPA
簽章要求 開發簽可編過部分目標 分發憑證 + 描述檔必須齊全
最佳化等級 低,編得快 全量最佳化,編譯時間明顯更長

因此「本機 3 分鐘能編過」與「CI Archive 20 分鐘還失敗」並不矛盾:你本機可能在跑 Debug + 模擬器,CI 在跑 Release + 實機架構 + 完整簽章鏈。先對齊比較對象,再談最佳化。編譯慢本身可參考 xcodebuild 在 CI 為什麼比本機慢 2–3 倍

2. 從選單到命令列:Product → Archive 的等價操作

在 Xcode GUI 裡點 Product → Archive,底層大致等價於:

  1. 選中 Shared Scheme,Configuration 為 Release
  2. Destination 切到 Any iOS Device(不能是模擬器);
  3. 執行 xcodebuild archive,把產物寫入 ARCHIVE_PATH
  4. (可選)在 Organizer 裡 Distribute App → 對應 xcodebuild -exportArchive

CI 裡常見最小 Archive 命令如下(路徑按專案替換):

xcodebuild archive \
  -workspace MyApp.xcworkspace \
  -scheme MyApp \
  -configuration Release \
  -destination 'generic/platform=iOS' \
  -archivePath "$RUNNER_TEMP/MyApp.xcarchive" \
  CODE_SIGN_STYLE=Manual \
  DEVELOPMENT_TEAM=XXXXXXXXXX \
  | tee archive.log

三個容易漏的參數:

  • -destination 'generic/platform=iOS':不寫時 xcodebuild 可能預設模擬器,Archive 直接失敗或行為詭異。
  • -archivePath:必須可寫;託管 Runner 上暫存目錄權限問題會導致「編完落盤失敗」。
  • Scheme 必須 Shared 且提交到 Git:CI checkout 後本機未共享的 Scheme 不存在,報 scheme not found

官方參考:Apple — Building your appDistributing your app

3. 四階段流水線(一圖讀懂)

┌──────────────┐    ┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│ 1. Resolve   │ →  │ 2. Archive   │ →  │ 3. Sign      │ →  │ 4. Export    │
│ SPM/Pods     │    │ Release 全量編 │    │ 憑證/描述檔   │    │ IPA / Upload │
│ DerivedData  │    │ → .xcarchive │    │ 鑰匙圈存取     │    │ TestFlight   │
└──────────────┘    └──────────────┘    └──────────────┘    └──────────────┘
     pod install          xcodebuild           codesign           exportArchive
     常占 5–15 min        archive 8–25 min     靜默掛起高發        plist 配錯高發

CI「卡在這裡」時,先問:日誌最後一行停在哪個階段? 停在 CompileSwift 是編譯;停在 CodeSign 是簽章;停在 exportArchive 是匯出設定。混談只會反覆清快取。

4. 階段 1:Resolve & Compile(為什麼 CI 更慢)

Archive 之前的依賴解析與編譯,本機往往「熱」、CI 往往「冷」:

  • CocoaPods / SPM:CI 每次 pod install 重裝,日誌一片 Installing …,還沒進 Archive 就吃掉 10 分鐘。見 Flutter iOS CI 快取傳播鏈
  • DerivedData 冷啟動:Release 全量編 + 無快取時,所有 Pod 原生程式碼重編。自託管 Runner 把 DERIVED_DATA_PATH 固定在 SSD 上,第二次 Archive 常能砍半時間。
  • 記憶體壓力:Archive 峰值記憶體高於 Debug;16GB 機器並行多 Job 會 swap,表現為「CPU 不高但一直不動」。見 Runner 記憶體與 swap 治理

判斷法:若日誌在大量 CompileC / SwiftCompile,且針對你沒改過的第三方函式庫,優先修快取,別先動簽章。

5. 階段 2:Archive(產生 .xcarchive)

xcodebuild archive 成功後,應在 -archivePath 看到目錄結構:

MyApp.xcarchive/
  Info.plist
  Products/Applications/MyApp.app
  dSYMs/...

常見失敗模式:

  • Scheme 的 Archive 動作未勾選目標:本機用別的 Scheme 能編,CI 指定的 Scheme Archive 為空。
  • 多 Target / Extension 漏配:主 App 過了,Notification Service Extension 簽章失敗,整包 Archive 失敗。
  • Build number / 版本衝突:CI 未遞增 CFBundleVersion,上傳階段才爆,但有人誤以為是 Archive 慢。

驗收:Archive 結束後立刻 ls -la "$ARCHIVE_PATH"plutil -p "$ARCHIVE_PATH/Info.plist",確認 ApplicationProperties 存在——別等 export 失敗再回頭查 Archive 是否完整。

6. 階段 3:Code Sign(CI 第一大坑)

本機 Archive 時,Xcode 可能彈出鑰匙圈授權;你點「永遠允許」,流水線就通了。無人值守 CI 沒有這一步,於是:

  • errSecInternalComponentUser interaction is not allowed:私鑰在登入鑰匙圈,CI 使用者無 GUI 授權。
  • 描述檔過期 / Bundle ID 不匹配:日誌停在 CodeSign,提示 Provisioning profile … doesn't match
  • 誤開 -allowProvisioningUpdates:需要 Apple ID 互動登入,Headless 環境會一直等。
  • 鑰匙圈未解鎖:Job 開頭缺 security unlock-keychain / set-key-partition-list

雲 Mac/自託管 Runner 上的推薦做法:為 CI 單獨建鑰匙圈檔案(非登入鑰匙圈),匯入分發憑證 + 私鑰,Job 開頭解鎖並設為預設。完整鏈見 雲 Mac iOS CI:codesign 與鑰匙圈邊界

# Job 開頭示意(口令走 CI Secret)
KEYCHAIN=$RUNNER_TEMP/ci-signing.keychain-db
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
security import dist.p12 -k "$KEYCHAIN" -P "$P12_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"
security list-keychains -s "$KEYCHAIN" login.keychain-db

若用 fastlane match 或 Xcode Cloud 管理憑證,原則不變:CI 程序必須能無互動存取私鑰,而不是「憑證在機器上 somewhere」。match 文件見 fastlane match

7. 階段 4:Export IPA(exportArchive)

Archive 只產生 .xcarchive;上架 TestFlight 還需要 Export。選單裡的 Distribute App 對應:

xcodebuild -exportArchive \
  -archivePath "$RUNNER_TEMP/MyApp.xcarchive" \
  -exportPath "$RUNNER_TEMP/export" \
  -exportOptionsPlist ExportOptions.plist

ExportOptions.plistmethod 決定匯出類型(app-storead-hocdevelopment 等)。CI 高發問題:

  • method 與描述檔類型不一致:用 development 描述檔卻填 app-store
  • 漏配 teamID / signingCertificate:Manual 簽章下 export 階段才報錯,誤以為 Archive 慢。
  • 上傳與匯出混在同一 stepaltool / Transporter 網路慢,被算進「Archive 逾時」。

建議把 archiveexportupload 拆成三個 step 並分別計時;上傳可用 xcrun altool --upload-appxcrun notarytool(macOS 分發),與 Archive 解耦。

8. CI 卡住的七類根因對照表

症狀 / 日誌關鍵字 階段 首選動作
長時間無新日誌,CPU 低 Sign 查鑰匙圈是否解鎖;停用需 GUI 的 provisioning 自動更新
scheme 'Foo' not found 前置 Scheme 勾選 Shared 並提交;CI 用 -list 核對
No profiles for … were found Sign 核對 Bundle ID、描述檔是否入倉或 match 拉取成功
大量 CompileC 針對未改 Pod Compile 持久化 DerivedData;避免 Job 內 clean
pod install > 8 分鐘 Resolve 快取 ios/Podspod install --deployment
Job 被平台 kill(無錯誤堆疊) 全域 調高 timeout;查 swap/磁碟滿(磁碟治理
exportArchive failed + plist 相關 Export 單獨跑 export;核對 ExportOptions.plist 的 method

值班時把日誌從後往前找第一個 error:,再對照上表——比全盤 flutter clean 省一小時。

9. 排障 Runbook:先分段打點,再對症修

  1. 本機複現 Release Archive:Product → Scheme → Edit Scheme → Archive 用 Release;Destination 選 Any iOS Device。本機不過,CI 不必跑。
  2. CI 加四段計時pod installarchiveexportArchiveupload,每段寫入 step summary。
  3. 保留完整日誌:慎用過度安靜的 log 美化器;簽章失敗需要原始 CodeSign 行。
  4. 固定 Xcode 版本:用 xcode-selectsudo xcode-select -s 鎖路徑;與 .xcode-version 或 workflow macos-15 對齊。
  5. 48 小時驗收:同一 commit 連續 Archive 兩次,第二次應明顯變快(快取命中);仍慢則查記憶體與磁碟。

發版衝刺、臨時加建置機場景,可配合 遠端 Mac 發版衝刺短租,避免在託管 Runner 上硬扛 Archive 逾時。

10. 可複製的 CI 命令範本(GitHub Actions 示意)

jobs:
  archive-ios:
    runs-on: [self-hosted, macOS, ios]  # 或 macos-14 託管機
    timeout-minutes: 60
    steps:
      - uses: actions/checkout@v4

      - name: Install pods
        run: pod install --deployment
        working-directory: ios

      - name: Archive
        run: |
          set -o pipefail
          xcodebuild archive \
            -workspace ios/MyApp.xcworkspace \
            -scheme MyApp \
            -configuration Release \
            -destination 'generic/platform=iOS' \
            -archivePath "$RUNNER_TEMP/MyApp.xcarchive" \
            | tee archive.log
        env:
          DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer

      - name: Export IPA
        run: |
          xcodebuild -exportArchive \
            -archivePath "$RUNNER_TEMP/MyApp.xcarchive" \
            -exportPath "$RUNNER_TEMP/export" \
            -exportOptionsPlist ios/ExportOptions.plist

自託管 Runner 標籤路由、launchd 保活與 CI 使用者隔離,見 Mac mini 自託管 Runner 搭建Flutter + GitHub Actions 架構總覽

11. 結論與下一步

Product → Archive 不是「多編一會兒」,而是 Release 全量建置 + 封存 + 分發級簽章 + 可選匯出 的完整鏈條。CI 卡在這裡,很少是 Xcode 壞了,更多是:把 Debug 經驗套在 Release 上、把簽章當成本機 GUI 專屬、把 export 和 archive 混成一個黑盒 step

可執行的下一步:本機先 Archive 過關 → CI 四段打點 → 專用鑰匙圈 + 持久快取 → 再考慮加機器或升規。對 Apple 平台團隊,雲 Mac 自託管 Runner 往往比反覆調託管機 cache 更省總時間——因為 Archive 要的是穩定的熱路徑,不是一次性最快的晶片。

用雲 Mac 跑通 Archive 全鏈路

獨占 M4 裸金屬,適合 xcodebuild archive、專用簽章鑰匙圈與持久 DerivedData。日租驗證 Archive 耗時與穩定性,滿意再升月租常駐 Runner。

配置租 Mac 方案 · 查看 M4 規格 · 開通驗收清單