同一個提交在開發機上能順利編譯,換到雲端 Mac 的新任務後,卻突然解析出不同的依賴,或在執行 pod install 後悄悄改寫 Podfile.lock。這類問題往往不在 Xcode 本身,而是 CocoaPods 工具版本、快取與安裝模式未被視為建置輸入來管理。正確的目標不是「每次都能安裝完成」,而是同一組輸入必然產生相同的依賴圖,並在發生變化時立即停止。
先定義可重現安裝的輸入
CocoaPods 的結果至少受五項輸入影響:Podfile、Podfile.lock、CocoaPods 版本、Ruby 與 Bundler 環境,以及本機開發 Pod 所指向的原始碼。即使已提交鎖定檔,只要執行器仍可使用任意 CocoaPods 版本,就可能出現專案產生格式或安裝行為上的差異。
儲存庫應提交 .ruby-version、Gemfile、Gemfile.lock、Podfile 與 Podfile.lock。請在 Gemfile 中明確指定 CocoaPods 版本,不要依賴機器全域安裝的最新版本:
source ENV.fetch("BUNDLE_GEM_SOURCE")
ruby "3.3.4"
gem "cocoapods", "1.15.2"
CI 入口一律使用 bundle exec pod。如此一來,日誌中顯示的版本就會與鎖定的環境一致,也能避免某個互動式 Shell 剛好優先找到全域命令。
Podfile.lock鎖定的是依賴解析結果,不會替你鎖定用來解析依賴與產生專案的 CocoaPods 程式。
將安裝命令改為嚴格模式
日常建置不得執行 pod update。這個命令會在版本限制範圍內重新解析依賴,適合由人工發起的升級任務,不適合用來驗證既有提交。CI 可以採用以下安裝入口:
#!/bin/bash
set -euo pipefail
export LANG=en_US.UTF-8
export COCOAPODS_DISABLE_STATS=true
export CP_HOME_DIR="${RUNNER_TEMP}/cocoapods-home"
bundle config set path "${RUNNER_TEMP}/bundle"
bundle check || bundle install
bundle exec pod install --deployment --clean-install
git diff --exit-code -- Podfile.lock
--deployment 會在鎖定檔需要變更時失敗;--clean-install 則能降低舊有安裝狀態掩蓋問題的機率。最後的 git diff 是第二道關卡:即使某段指令碼在安裝過程中改寫了鎖定檔,任務也不能繼續進入編譯階段。
如果儲存庫包含多個工作區,應為每個工作區配置獨立的安裝目錄與日誌檔案,不要讓並行任務共用同一個 Pods 目錄。
隔離快取,但不要盲目停用快取
快取本身不是問題,界線不清才是。雲端 Mac 上常見的污染來源包括多個儲存庫共用 CocoaPods 下載快取,或兩個任務同時讀寫同一個 Bundler 目錄。建議以「鎖定檔摘要 + 工具版本」建立快取鍵,並且只在安裝成功後才寫入快取。
| 快取物件 | 建議快取鍵 | 失效條件 |
|---|---|---|
| Bundler 安裝目錄 | Gemfile.lock 摘要 + Ruby 版本 | 任一輸入發生變化 |
| CocoaPods 下載快取 | Podfile.lock 摘要 + CocoaPods 版本 | 鎖定檔或工具發生變化 |
| Pods 目錄 | Podfile.lock 摘要 + 專案設定摘要 | Podfile、指令碼或設定發生變化 |
不要快取 Pods/Manifest.lock 後再用它覆蓋新的安裝結果。它記錄的是目前沙箱中實際安裝的狀態,應由本次安裝產生。若還原快取後出現異常,請先在隔離目錄中執行一次不使用快取的安裝;只有在無快取結果正常時,才能將問題歸因於快取。
在編譯前驗證依賴圖
安裝成功不代表依賴圖正確。至少要檢查 Podfile.lock 與 Pods/Manifest.lock 是否一致,並確認沒有尚未提交的變更:
test -f Podfile.lock
test -f Pods/Manifest.lock
cmp -s Podfile.lock Pods/Manifest.lock || {
echo "CocoaPods sandbox differs from Podfile.lock" >&2
exit 1
}
git status --porcelain -- Podfile Podfile.lock Gemfile Gemfile.lock
最後一個命令只要有輸出就應判定為失敗,而不是僅顯示警告。對於使用 :path 的本機 Pod,還必須額外記錄目標目錄的提交編號或內容摘要。否則,即使鎖定檔未變,目錄內容仍可能變動,可重現性依然不存在。
為升級建立獨立通道
依賴升級任務可以對指定目標執行 pod update SomePod,但必須輸出三類差異:鎖定檔中的版本變更、SPEC CHECKSUMS 變更,以及新增或刪除的傳遞依賴。升級提交只能包含依賴變更,不要同時混入業務程式碼,這樣在需要回復時才能直接還原上一份鎖定檔。
保留失敗現場並依序排查
失敗後不要立刻刪除所有快取。請先保留 CocoaPods 版本、Ruby 版本、鎖定檔摘要、安裝日誌與 git status,再判斷問題位於哪一層。
- 若
bundle check失敗,先核對 Ruby 與Gemfile.lock平台。 - 若嚴格安裝要求修改鎖定檔,請檢查是否有人變更了
Podfile,卻漏交對應的鎖定檔。 - 若只有還原快取的任務失敗,請在獨立目錄中執行無快取安裝並比較結果。
- 若
Manifest.lock不一致,請檢查安裝是否曾被中斷,或多個任務是否共用Pods。 - 若本機 Pod 內容發生漂移,請為原始碼目錄增加提交編號或摘要驗證。
穩定的 CocoaPods 流程最終應該相當平淡:輸入未變時,安裝結果不變;輸入發生變化時,差異進入程式碼審查;快取損壞時,只會影響速度,不會改變依賴圖。將這些檢查放在 Xcode 編譯之前,通常能以數十秒內的失敗,取代數十分鐘後才出現的模糊錯誤。
常見問題
CI 應該執行 pod install 還是 pod update?
一般 CI 只執行帶有 --deployment 的 pod install。pod update 應放在獨立的依賴更新工作中,並審查 Podfile.lock 的變更。
已提交 Podfile.lock,為什麼結果仍可能不同?
常見原因包括 Ruby 或 CocoaPods 版本不同、可變動的本機 Pod、損壞的快取,以及安裝前修改 Podfile 的腳本。
Pods 目錄需要提交到 Git 嗎?
通常不需要。建議提交 Podfile、Podfile.lock、Gemfile 與 Gemfile.lock,讓 CI 從固定輸入重新取得依賴。
依任務週期選擇 LemonVM 雲端 Mac
Lemon M4 與 Lemon M4 Pro 覆蓋新加坡、東京、首爾、香港及美國西部,支援日租、週租、月租與季租。