最初に押さえるポイント
- Archive ≠ Build:Archive は Release + Any iOS Device 固定で
.xcarchiveを生成。Debug で通っていても Archive は落ちる。 - CI の詰まりは 4 段——Scheme/設定、コンパイルキャッシュ、署名とキーチェーン、exportArchive。ログで段を特定してから手を入れる。
xcodebuild archiveはメニュー Archive と等価だが、CI では-scheme、-configuration Release、-destination 'generic/platform=iOS'を明示必須。- 「動かない」の多くはキーチェーン待ち、プロビジョニング取得待ち、メモリ swap——xcodebuild 自体が死んでいるわけではない。
- クラウド Mac + 持続 DerivedData + 専用キーチェーンは、ホステッド Runner の「毎回コールド Archive」より桁違いに安定しやすい。
1. Archive と Build の違い
CI 失敗を「xcodebuild が落ちた」と片付けるチームは多いですが、⌘B Build と Product → Archive は別ルートです。
| 観点 | Build(⌘B) | Archive |
|---|---|---|
| 典型 Configuration | Debug(日常開発) | Release(App Store / TestFlight) |
| Destination | シミュレータ or 接続中の実機 | Any iOS Device (arm64) |
| 成果物 | DerivedData 内の .app |
.xcarchive + IPA エクスポート可 |
| 署名 | 開発証明書で通ることも | 配布証明書 + プロビジョニング必須 |
| 最適化 | 低く、速い | フル最適化でコンパイル時間が大幅増 |
だから「ローカル 3 分で通るのに CI Archive が 20 分で失敗」は矛盾しません。ローカルが Debug + シミュレータ、CI が Release + 実機アーキ + フル署名チェーン、というケースが典型です。比較対象を揃えてから最適化を議論してください。コンパイル遅延そのものは xcodebuild が CI でローカルの 2〜3 倍遅い理由 を参照。
2. メニューから CLI:Product → Archive の等価操作
Xcode GUI で Product → Archive を押したとき、裏側ではだいたい次が走ります。
- Shared Scheme を選択し、Configuration は Release;
- Destination を Any iOS Device に(シミュレータ不可);
xcodebuild archiveを実行し、ARCHIVE_PATHに出力;- (任意)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
漏れやすい 3 点:
-destination 'generic/platform=iOS':省略するとシミュレータが選ばれ、Archive 失敗や挙動ブレの原因に。-archivePath:書き込み可能なパス必須。ホステッド Runner の一時ディレクトリ権限で「コンパイル成功→書き込み失敗」が起きる。- Scheme は Shared で Git 管理:ローカルだけの Scheme は CI checkout 後に
scheme not foundになる。
公式:Apple — Building your app、Distributing 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 分がち archive 8–25 分 無言ハング多発 plist 設定ミス多発
CI が「ここで止まった」と言うとき、まずログ最終行がどの段かを確認。CompileSwift ならコンパイル、CodeSign なら署名、exportArchive ならエクスポート設定。段を混ぜてキャッシュ全消しを繰り返すと時間だけ溶けます。
4. 段階 1:Resolve & Compile(CI が遅い理由)
Archive 前の依存解決とコンパイルは、ローカルは「温まった」状態、CI は「冷えた」状態になりがちです。
- CocoaPods / SPM:CI で毎回
pod installするとInstalling …だけで 10 分。Archive 前に時間が消える。→ Flutter iOS CI のキャッシュ伝播。 - DerivedData コールド:Release フルビルド + キャッシュなしで Pod ネイティブコードを全部再コンパイル。自前 Runner で
DERIVED_DATA_PATHを SSD 固定にすると 2 回目 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 が空。
- Extension 署名漏れ:本体 App は通るが Notification Service Extension で落ち、Archive 全体失敗。
- ビルド番号衝突: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 にはその UI がない。
errSecInternalComponent、User interaction is not allowed:ログインキーチェーンの秘密鍵に 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 プロセスが秘密鍵に無操作で触れること。match ドキュメント:fastlane match。
7. 段階 4:Export IPA(exportArchive)
Archive は .xcarchive まで。TestFlight 配布には Export が別段。Organizer の Distribute App は次に相当します。
xcodebuild -exportArchive \
-archivePath "$RUNNER_TEMP/MyApp.xcarchive" \
-exportPath "$RUNNER_TEMP/export" \
-exportOptionsPlist ExportOptions.plist
ExportOptions.plist の method がエクスポート種別(app-store、ad-hoc、development 等)を決めます。CI で多い問題:
- method とプロファイル種別の不一致:development プロファイルなのに
app-store。 teamID/signingCertificate欠落:Manual 署名で export 段階初出エラー→ Archive 遅いと誤解。- upload を同じ step に混在:
altool/ Transporter のネット待ちが「Archive タイムアウト」に計上される。
archive、export、upload を 3 step に分け計測。アップロードは xcrun altool --upload-app 等で Archive から切り離す。
8. CI が止まる 7 類型早見表
| 症状 / ログキーワード | 段階 | 最初の一手 |
|---|---|---|
| 長時間ログ更新なし、CPU 低い | Sign | キーチェーン解錠確認;GUI 要 provisioning 自動更新を無効化 |
scheme 'Foo' not found |
前置 | Scheme を Shared にしてコミット;CI で -list 照合 |
No profiles for … were found |
Sign | Bundle ID・プロファイルのリポジトリ同梱 or match 取得成功を確認 |
未変更 Pod への大量 CompileC |
Compile | DerivedData 持続化;Job 内 clean 回避 |
pod install > 8 分 |
Resolve | ios/Pods キャッシュ;pod install --deployment |
| プラットフォームが Job を kill(スタックなし) | 全体 | timeout 延長;swap / ディスク満杯(ディスク対策) |
exportArchive failed + plist 関連 |
Export | export 単体実行;ExportOptions.plist の method 確認 |
オンコールではログを末尾から最初の error: を探し、上表と照合——flutter clean 全消しより 1 時間短いことが多いです。
9. Runbook:段階計測してから直す
- ローカルで Release Archive 再現:Product → Scheme → Edit Scheme → Archive を Release;Destination は Any iOS Device。ローカルで落ちるなら CI を回す意味が薄い。
- CI に 4 段タイマー:
pod install→archive→exportArchive→upload、各 step summary に記録。 - 生ログを残す:過度なログ整形は署名失敗の
CodeSign行を消す。 - Xcode バージョン固定:
xcode-select -sでパス固定;.xcode-versionや workflow のmacos-15と整合。 - 48 時間検収:同一 commit で Archive を 2 連続。2 回目が明らかに速ければキャッシュ命中。まだ遅いならメモリとディスク。
リリース週のビルド機増設は リモート Mac 発版スプリント短期レンタル と相性が良い。ホステッド Runner で Archive タイムアウトを硬く耐えるより、専有 Mac を足す方が早い場面も多い。
10. 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 専用と思い込む、archive と export を 1 つのブラックボックス step にする——この 3 つが大半です。
実行順:ローカル Archive 合格 → CI 4 段計測 → 専用キーチェーン + 持続キャッシュ → その後に増機・スペックアップ。Apple プラットフォームチームにとって、クラウド Mac 自前 Runner はホステッド cache 調整より、Archive が求める安定したウォームパスを作れる点で総時間を削りやすいです。
クラウド Mac で Archive 全工程を通す
M4 専有ベアメタルは xcodebuild archive、専用署名キーチェーン、持続 DerivedData に向いています。日払いで Archive 時間と安定性を検証し、問題なければ月額の常駐 Runner へ。