Swift 6 の strict concurrency は、手元の Xcode では通っても CI だけが揺れるとチームの信頼を削ります。原因はしばしば業務ロジックではなく、ビルド設定の揺らぎ、DerivedData の競合、シミュレータ指定の差分、警告をゲートにしない運用です。本稿はプラットフォーム担当者向けに、七つの痛みで典型失敗を先に列挙し、対照表で実行環境の選択肢を整理し、六段階の Runbook と受け入れ観点まで一気通貫でまとめます。併読として SwiftPM/CocoaPods と DerivedData、再現性のあるビルド指紋、セルフホスト Runner とキャッシュ分離 を挙げます。
並行性の診断はタイミングに依存しやすいため、環境差分があると失敗が散らばります。次の七条は、専用ノード導入やプロファイル固定を検討するトリガーです。
ローカルと CI で Swift と Xcode の組み合わせが一致しない:swift --version と xcodebuild -version をログに残していないと、Sendable 診断の有無すらブレます。
strict 相当のチェックをリリース直前だけ有効化する:日常ビルドは寛容、本番だけ厳格だと差分が一度に噴き出し、ロールバック判断が遅れます。
並列 xcodebuild が同一 DerivedData を共有する:競合で増分コンパイルが壊れ、並行性エラーと無関係なクリーン要求が増えます。
警告を許容したまま CI を緑にする:SWIFT_STRICT_CONCURRENCY を上げた際に初めて致命的になるパターンが残ります。
アクター境界を考えず UI テストと単体テストを同一ジョブへ混在させる:Simulator の負荷とビルドフラグが干渉し、フレークの原因を誤認します。
xcode-select や TOOLCHAINS の前提が Runbook に無い:誰かが GUI で Xcode を切り替えるだけで夜間ジョブが別コンパイラになります。
Linux だけで swift build し、iOS ターゲットの厳格診断を後回しにする:サーバ用ターゲットと UIKit 系ターゲットでは診断集合が異なり、問題を先送りします。
共通項は「コンパイラの事実を CI の入口で固定していない」ことです。専用リモート Mac は、その事実を SSH 越しにインフラとして固定しやすい点が強みです。
目的は「同じ診断集合を毎回得る」ことです。並行ジョブ数やキャッシュ方針が変わるほど、厳格モードの結果はブレます。
| 観点 | Xcode Cloud | ホスト型 macOS Runner | 専用リモート Mac(SSH) |
|---|---|---|---|
| ツールチェーンの固定 | マネージド更新に追従しやすい | イメージタグ依存、隣接テナントで変動し得る | xcode-select とディスクレイアウトを契約化しやすい |
| 並行性診断の再現性 | ワークフロー設計次第で高い | 共有キャッシュと CPU 争いでブレやすい | DerivedData をジョブ単位で分離しやすい |
| xcodebuild パラメータの自由度 | カスタムは勝手が制限される場合がある | ランナー管理者ポリシーに依存 | -destination や -derivedDataPath を固定しやすい |
| 典型的な失敗 | キューとクォータ、非同期ステップ境界 | クリーン環境での初回のみ再現する依存抜け | 省電力設定やディスク枯渇など運用抜け |
| 向くチーム | App Store 近接フローを標準化したい場合 | 短時間スモークを大量に回したい場合 | monorepo と厳格ゲートを長時間安定運用したい場合 |
「厳格並行性はコードの話に見えますが、CI ではコンパイラとキャッシュの物理配置の話でもあります。」
料金と帯域の前提を揃えるには レンタル SLA と課金 も参照し、ノードに何を期待するかを財務と共有してください。
各ステップはログに証跡を残すことが目的です。ブランチ保護ルールと合わせ、入口で失敗させます。
採用 Xcode を文書化し xcode-select -p を記録する:複数バージョンが同居するなら CI ユーザーごとに切り替え手順を固定します。
入口で xcodebuild -version、xcodebuild -showsdks、swift --version を必ず出力する:期待値と diff したら即中断します。
ビルド設定で SWIFT_STRICT_CONCURRENCY をリポジトリ全体で揃える:モジュール単位で段階昇格するならマトリクスを変更票に残します。
xcodebuild に -scheme、固定の -destination、ジョブ単位の -derivedDataPath を渡す:必要なら -clonedSourcePackagesDirPath も分離します。
テスト階層を分離する:単体はホスト/シミュレータの軽い宛先、UI と性能は別スロット。並行上限をノード契約に書きます。
受け入れチェックリストに署名する:ログ断片、採用コンパイラ版、厳格レベル、クリーン再現手順を添付し、リリースオーナーが確認します。
#!/usr/bin/env bash set -euo pipefail xcodebuild -version xcodebuild -showsdks swift --version export DERIVED_DATA="$(pwd)/.derivedData/$CI_JOB_ID" xcodebuild -scheme "App" \ -destination 'platform=iOS Simulator,name=iPhone 16' \ -derivedDataPath "$DERIVED_DATA" \ build
ヒント:サードパーティ依存に寛容モードが残る場合は、自前コードから順に complete へ寄せるロードマップを README に書き、ゲートを段階化します。
夜間バッチと昼間の対話セッションを同一ユーザーで混ぜないと、ツールチェーン切り替えが人為ミスで入りやすくなります。
モジュールの並行性チェックは、プロジェクト設定とコンパイラフラグの組み合わせで決まります。ターゲットごとに期待値が違う場合、マトリクスを表形式で公開し、レビュー時に差分を見える化します。
注意:語句や既定値の詳細は利用中の Xcode/Swift のリリースノートを正とし、本稿は運用手順の枠組みに限ります。法務観点は社内ポリシーに従ってください。
MainActor 境界とデータレース警告は、Simulator の負荷やビルド並列度でも表面化しやすいため、性能テストと機能テストのログを分離してください。
次の項目を満たしたら初回ロールアウト完了とみなせます。
xcodebuild -version と swift --version を成果物ストアへ保存しました。SWIFT_STRICT_CONCURRENCY が表形式で文書化され、未設定ターゲットがありません。-derivedDataPath がジョブ ID など一意キーに結び付いています。共有 Runner だけではツールチェーンとディスク競合を同時に抑え込みにくく、Swift 6 の厳格ゲートはフレークに見えがちです。NodeMini の専用 Mac Mini クラウドレンタルは、SSH 越しにプロファイルを固定し、ビルドを長期サービスとして運用する前提に合います。仕様と価格は レンタル料金のご案内 で確認し、接続と受け入れは ヘルプセンター で進めてください。
運用が回り始めたら、厳格モード違反のトレンドを週次で数え、依存ライブラリの追従計画と結び付けると改善が継続します。
診断はコンパイラ版とキャッシュ配置に強く依存するため、占有ノードでレイアウトを固定した方が再現性が出ます。料金の比較は レンタル料金のご案内 をご覧ください。
scheme、destination、derivedDataPath を固定し、ビルド設定で strict concurrency レベルを揃えます。手順の詳細は ヘルプセンター を参照してください。
Xcode のビルド番号、xcode-select、必要なら TOOLCHAINS、そしてジョブ先頭で実行するコマンド列を Runbook に明記します。契約プラン確認は レンタル料金のご案内 から行えます。