雲端 Mac 的 CocoaPods 可重現安裝與依賴漂移治理

CI/CD 實踐 ·約 6 分鐘閱讀

雲端 Mac 的 CocoaPods 可重現安裝與依賴漂移治理

同一個提交在開發機上能順利編譯,換到雲端 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,再判斷問題位於哪一層。

  1. 若 bundle check 失敗,先核對 Ruby 與 Gemfile.lock 平台。
  2. 若嚴格安裝要求修改鎖定檔,請檢查是否有人變更了 Podfile,卻漏交對應的鎖定檔。
  3. 若只有還原快取的任務失敗,請在獨立目錄中執行無快取安裝並比較結果。
  4. 若 Manifest.lock 不一致,請檢查安裝是否曾被中斷,或多個任務是否共用 Pods。
  5. 若本機 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 覆蓋新加坡、東京、首爾、香港及美國西部,支援日租、週租、月租與季租。

立即租用雲端 Mac