2026年 Mac 雲 CI:SPM 依賴鎖定與 Package.resolved 決策矩陣——何時強制 -disableAutomaticPackageResolution(含 FAQ)
在 Mac 雲共享建置池上跑 iOS/macOS CI 的團隊,常遇到「筆記本能編、雲上隨機缺包或版本漂移」:根因往往不是 Xcode 版本,而是 Swift Package Manager 在無人值守環境下自動改寫了依賴圖。本文面向 2026 年要把 Mac 雲當成可控 VPS 的平臺工程與 iOS 開發者,先拆四類痛點,再給 workspace / xcodebuild / 多 Job 共享節點三列決策矩陣,接著給出五步 Runbook、並行解析參數表與三條可引用指標;讀完你能判斷何時必須把 Package.resolved 納入門禁、何時加 -disableAutomaticPackageResolution,並把失敗按「解析 / 磁碟 / 企業出口」分流。
1. 痛點拆解:lockfile 缺失、自動解析、共享 runner 與出口誤判
Apple 官方在 2026 年仍強調:使用 Swift Package 的 CI 應把 Package.resolved 提交進版本庫,並在 xcodebuild 路徑上通過 -disableAutomaticPackageResolution 強制使用鎖定版本。把 Mac 雲當成「另一臺 Linux runner」而忽略 lockfile,會把依賴不確定性轉嫁給鏈接階段,表現為 flaky 的 missing package product。
- lockfile 未納入門禁:開發者本地 Xcode 自動升級了傳遞依賴,但未提交
Package.resolved;雲上第一次 resolve 成功、第二次因緩存汙染或分支切換失敗,團隊誤以為是「Mac 雲不穩定」。 - 建置階段仍允許自動解析:Archive Job 與 PR Job 共用同一 scheme,卻未在
xcodebuild加-disableAutomaticPackageResolution,導致夜間 main 與白天 PR 使用不同依賴圖,迴歸無法 bisect。 - 多 Job 共享同一 DerivedData / SPM 緩存根:A 項目的 resolve 寫入全局緩存,B 項目鏈接階段讀到半成品模塊;日誌裡只有模糊的 link error,排障會浪費數小時。延伸閱讀:建置佇列與 DerivedData/SPM 磁碟治理。
- 把 SPM 慢誤判為編譯慢:企業 HTTPS 解密、DNS 分流或 IPv6 路徑差異會讓元數據拉取間歇 403;若未單獨保存 resolve 日誌,會與磁盤滿、併發 stampede 混淆。對照:企業防火牆與 Git/SPM 出口。
2. 決策矩陣:workspace、xcodebuild 直連、多 Job 共享 Mac 雲節點
下列矩陣回答三個實操問題:是否提交 lockfile、是否在建置命令禁用自動解析、是否拆獨立 resolve Job。與「只在本地能編」的臨時方案不同,Mac 雲 CI 的價值在於把依賴圖變成可審計契約。
| 場景 | 提交 Package.resolved | -disableAutomaticPackageResolution | 獨立 resolve Job |
|---|---|---|---|
| 單倉庫 · xcodebuild Archive(推薦默認) | 必須(PR 門禁 + main 保護) | 必須(archive / test 均加) | 建議:先 resolve 探針再 archive,日誌分倉 |
| 多 scheme 並行 · 同一 Mac 節點 | 必須;禁止多分支共用一個未版本化的 lockfile | 必須;併為每併發槽位設獨立 -derivedDataPath |
必須:resolve 與 compile 分隊列,避免疊峰 IO |
| 純 SwiftPM CLI(swift build / test) | 必須;用 swift package resolve --force-resolved-versions 對齊 |
不適用 xcodebuild 標誌;用 CLI 嚴格模式代替 | 可選;私包多時用獨立 Job 預熱 SSH agent |
| 實驗分支 · 允許升級依賴 | 僅在合併前更新 lockfile;禁止直接進 main | 實驗 Job 可臨時關閉,但不得與 release 共 runner 緩存 | 必須隔離目錄與 Unix 用戶,防交叉汙染 |
若你使用 Xcode 的 workspace 集成多個 app / extension,lockfile 可能位於 .xcworkspace/xcshareddata/swiftpm/Package.resolved 或工程旁的 Package.resolved;CI 門禁應覆蓋實際被 Xcode 讀取的路徑,而不是假設只有根目錄一份文件。
3. 並行解析參數表:何時加速、何時不該開
在 M4 類 Mac 雲節點上,適度並行可縮短 resolve 尾延遲;但與 Archive 疊峰時會放大 NVMe 與出口爭用。下列參數需在resolve Job中設置,而非在 Archive 高峰硬開。
| 變量 / 標誌 | 建議起點(單節點 8–12 併發槽) | 不應開啟的情況 |
|---|---|---|
SWIFT_PACKAGE_MANAGER_PARALLEL_FETCH_LIMIT |
8–12(與 CPU 核數、出口帶寬聯調) | 磁盤可用空間 < 15% 或隊列深度已告警 |
XCODE_PACKAGE_RESOLVE_PARALLELISM=YES |
resolve 探針 Job 開啟 | 與 Archive 同機同時跑滿時 |
-scmProvider system |
私包走系統 Git + SSH agent 時加在 xcodebuild | 未配置 known_hosts / agent 時(會先表現為 auth 失敗) |
-disableAutomaticPackageResolution |
所有 compile / archive / test 的 xcodebuild | 僅當你故意在隔離 Job 中刷新 lockfile 且未進入 release 線 |
4. 五步 Runbook:從門禁到三次驗收
- 把 lockfile 納入版本門禁:PR 必須包含
Package.resolved變更說明;若 Xcode 本地提示升級依賴,要求開發者顯式提交 lockfile,禁止「只在本地 resolve」。 - resolve 探針 Job(Mac 雲 SSH 用戶下):在 archive 前執行
xcodebuild -resolvePackageDependencies -scheme YourApp -destination 'generic/platform=iOS',保存完整日誌;失敗則直接 fail,不進入 compile。 - 固定路徑的 archive:為每個併發槽位設置
DERIVED_DATA_PATH與-derivedDataPath,並在 archive 命令加-disableAutomaticPackageResolution;私包場景按需加-scmProvider system。 - 出口與代理一次性驗收:對照企業出口文,在節點上夜間跑一次「大依賴拉取」演練,確認 SPM 元數據域名與 Git 走同一策略,避免「clone 正常、resolve 隨機 403」。
- 三次建置驗收:固定同一 commit 連續跑三次,記錄 resolve 耗時 P95、compile 與 link 佔比;若僅第一次 resolve 慢、後兩次仍慢,查磁碟與緩存根;若三次依賴版本不一致,查 lockfile 門禁與自動解析標誌。
export DERIVED_DATA_PATH=/Volumes/ci/slot${JOB_SLOT}/DerivedData
xcodebuild -resolvePackageDependencies -scheme App -derivedDataPath "$DERIVED_DATA_PATH" -destination 'generic/platform=iOS'
xcodebuild -scheme App -configuration Release -derivedDataPath "$DERIVED_DATA_PATH" \
-disableAutomaticPackageResolution -scmProvider system \
-destination 'generic/platform=iOS' archive
5. 三條可引用指標:resolve 佔比、lockfile 漂移、解析失敗聚類
- resolve 佔端到端耗時比例:在 45+ 包工程中,若 resolve 持續高於端到端約 20% 且與代碼變更無關,優先查出口與並行度,而不是加 CPU。
- lockfile 漂移次數:統計「main 上建置成功但 lockfile 未變」的次數;若每週 > 2 次,說明仍有 Job 在自動解析,應強制加標誌或拆 Job。
- 解析失敗 vs 鏈接失敗聚類:日誌含
missing package product、checksum、failed downloading歸入解析類;含linker command failed且 resolve 已成功歸入編譯/磁盤類——避免用「重試 archive」掩蓋依賴問題。
6. 與磁盤水位、冷/溫節點的銜接
SPM 緩存與 DerivedData 共用 NVMe 時,「解析成功但鏈接失敗」常來自磁盤水位或併發 stampede,而非業務代碼。把 resolve 放在溫節點或低併發隊列、把 Archive 放在常駐低併發槽,與 冷啟動 vs 溫節點 的策略一致:依賴解析是 IO 與出口敏感型任務,不適合與 Archive 在無閘門情況下疊峰。
若團隊已實踐「PR 走彈性、Nightly 走常駐 Mac 雲」,本文補齊第二層:在 Mac 雲內部把 SPM 鎖定義成與簽名鏈同級的契約;否則彈性池只會放大依賴漂移,而不是吸收尖峰。
7. FAQ
問:Xcode 會刪掉 Package.resolved,怎麼辦? 在 CI 中用門禁檢測 lockfile 是否存在;本地用「提交前 resolve + diff lockfile」流程。若出現 missing package product,先 git checkout -- Package.resolved 再 resolve,而不是在 archive 裡反覆重試。
問:多分支共用一個 Mac 雲 runner 會衝突嗎? 會。應為每個項目或槽位使用獨立 Unix 用戶與 DERIVED_DATA_PATH,禁止多 repo 共寫同一 SPM 緩存根。
問:能否只在 Linux 上 resolve、Mac 上只編譯? 不能可靠替代:iOS 鏈路的包圖與 Xcode 集成解析強相關,且簽名與 SDK 只能在 macOS 上完成;Linux 前置最多做元數據鏡像,不能作為 lockfile 真源。
8. 結論
在 Mac 雲 CI 上,Package.resolved 不是「可選優化」,而是把 Swift 依賴從個人筆記本習慣升級為可復現契約的最低門檻;-disableAutomaticPackageResolution 則是防止無人值守建置偷偷改寫依賴圖的保險絲。
僅依賴開發者本機偶爾 resolve、或把 SPM 緩存扔在多臺共享 runner 的同一目錄裡,短期看似省事,長期會把版本漂移與 flaky 鏈接轉嫁成排障黑洞;完全指望託管 macOS Runner 的默認緩存策略,又很難按項目定製 lockfile 門禁與出口白名單。對要把 iOS 交付做成穩定產能、又希望繼續用 SSH 管理「像 VPS 一樣」的 Mac 節點的團隊,租賃 VPSMAC 的 Apple Silicon Mac 雲主機,用獨佔或低併發槽位承載 resolve + Archive 重鏈路,並把 lockfile 與路徑契約寫進 Runbook,通常比繼續在共享緩存根上碰運氣更接近問題本質。