工程指南

遠端雲端 Mac 的 Xcode 建置快取調校實戰

遠端雲端 Mac 的 Xcode 建置快取調校實戰

距離發布只剩兩天時臨時增租一台遠端 Mac,拉取程式碼只花了幾分鐘,第一次用 Xcode 建置卻遲遲無法完成;第二次雖然快了不少,但切換分支後,又遇到舊資源殘留、索引異常或相依套件重新解析。問題通常不在於機器效能不足,而是 DerivedData、Swift 套件下載內容與建置結果全都混在預設目錄裡,不僅難以確認快取是否命中,也無法在租期結束前選擇性匯出。

先建立可比較的建置基準

調校快取前,必須先固定各項變因。請記錄 xcodebuild -version、macOS 版本、提交雜湊、Scheme、建置設定與目標平台。測試期間不要同時升級相依套件、切換 Xcode 或修改編譯參數,否則兩次建置所花的時間便無法直接比較。

至少執行以下三組測試:

  1. 刪除專案專用的 DerivedData 後執行冷建置。
  2. 不修改程式碼,以相同命令執行暖建置。
  3. 修改一個一般 Swift 檔案,再執行增量建置。

每次都要保存開始時間、結束時間、結束代碼與結果套件。圖形介面的進度列適合用來觀察狀態,卻不適合精確比較;自動化記錄應統一透過 xcodebuild 完成。

快取的目的不是讓某一次建置看起來特別快,而是讓相同輸入能得到可預期的耗時與結果。

依照用途拆分快取目錄

建議在工作目錄下建立四類路徑,分別存放原始碼、DerivedData、Swift 套件下載內容,以及結果與記錄。不要讓多個專案共用同一個 DerivedData,也不要讓 Debug 與 Release 互相覆寫。專案數量較多時,還可以加入 Xcode 主要版本與提交分支識別資訊。

目錄 保存內容 建議策略
Source Git 工作樹 各專案獨立
DerivedData 索引與中間產物 依版本、Scheme、設定隔離
SourcePackages Swift 套件下載與簽出內容 鎖定檔未變更時重複使用
Results 結果套件與記錄 依建置時間封存

Swift 套件目錄可以重複使用,但前提是相依套件鎖定檔完全一致。若鎖定檔有所變更,應先讓解析程序正常完成,不要直接把舊的簽出目錄複製到新專案。DerivedData 對工具鏈更為敏感;切換 Xcode 後應建立新目錄,而不是覆寫原目錄後繼續使用。

使用固定命令執行建置

以下指令碼會明確指定所有關鍵路徑。請將工作區、Scheme 與目標平台替換成專案實際使用的值。結果套件路徑在執行前必須不存在,因此指令碼會先刪除同名目錄。

#!/bin/bash
set -euo pipefail

ROOT="${HOME}/BuildWorkspace"
SCHEME="Application"
CONFIGURATION="Release"
DERIVED="${ROOT}/DerivedData/${SCHEME}-${CONFIGURATION}"
PACKAGES="${ROOT}/SourcePackages"
RESULT="${ROOT}/Results/${SCHEME}.xcresult"
LOG="${ROOT}/Results/${SCHEME}.log"

mkdir -p "${DERIVED}" "${PACKAGES}" "${ROOT}/Results"
rm -rf "${RESULT}"

xcodebuild \
  -workspace "${ROOT}/Source/Application.xcworkspace" \
  -scheme "${SCHEME}" \
  -configuration "${CONFIGURATION}" \
  -destination "generic/platform=iOS" \
  -derivedDataPath "${DERIVED}" \
  -clonedSourcePackagesDirPath "${PACKAGES}" \
  -resultBundlePath "${RESULT}" \
  build | tee "${LOG}"

請先在終端機執行 xcodebuild -list,確認工作區確實公開了目標 Scheme。若專案使用的是工程檔而不是工作區,請將 -workspace 改成 -project,不要同時傳入兩者。

為快取目錄產生穩定識別碼

快取鍵至少應包含 Xcode 版本、相依套件鎖定檔摘要、Scheme 與建置設定。也可以加入分支名稱,但不能只使用分支名稱,因為同一分支中的相依套件狀態仍可能改變。

XCODE_KEY="$(xcodebuild -version | shasum -a 256 | cut -c1-12)"
PACKAGE_KEY="$(shasum -a 256 Package.resolved | cut -c1-12)"
printf '%s-%s-%s-%s
' "${XCODE_KEY}" "${PACKAGE_KEY}" "${SCHEME}" "${CONFIGURATION}"

若工作區中有多個 Package.resolved,應明確選擇實際參與建置的檔案。找不到鎖定檔時,不要產生固定的空白鍵值,否則不同的相依套件狀態會被寫入同一目錄。

分階段找出不必要的重新編譯

如果暖建置依然很慢,應先確認時間究竟耗在哪個階段。若相依套件解析反覆執行,請檢查鎖定檔是否被指令碼重寫,以及套件目錄權限是否正確;若編譯階段一再執行,請檢查編譯條件、自動產生的檔案與建置階段指令碼;若連結階段出現異常,則應確認版本資訊或嵌入內容是否每次都被改動。

最常見的問題,是指令碼階段未宣告輸入與輸出。只要指令碼每次都改寫產生檔案的時間戳記,下游目標就會被判定為需要重新建置。解決方式是讓指令碼僅在內容實際變更時替換檔案,並在 Xcode 建置階段中填入準確的輸入與輸出路徑。

不要把「清空所有快取」當成固定操作。這項操作只能確認快取是否受到污染,卻無法解釋污染來源。請先單獨移走專案的 DerivedData;若問題仍然存在,再處理 Swift 套件目錄。如此既能保留診斷證據,也能避免每次都重新下載全部相依套件。

在租期結束前保留真正有用的內容

匯出前,請先停止仍在執行的建置,確認記錄已完整寫入,再封裝相依套件鎖定檔、必要的 Swift 套件下載內容、結果套件與專案指令碼。原始碼應以程式碼儲存庫中已提交的內容為準;工作樹裡尚未提交的修改必須另外檢查。

不建議長期保存完整的 DerivedData。其容量通常很大,內含索引、目的檔,以及與特定工具鏈綁定的中間產物。下次改用不同版本的 Xcode 時,重新產生通常更可靠。敏感憑證、私密金鑰、權杖與暫存環境檔案都不應混入快取封存檔;請先清除 Shell 歷史記錄、暫存鑰匙圈與專案產生的憑證檔案,再核對匯出套件的內容清單。

最後請保留一份簡短記錄,列出工具鏈版本、快取鍵、最後一次成功建置的提交、建置命令、已匯出的路徑,以及明確未匯出的路徑。下一台雲端 Mac 依照這份記錄還原環境,會比直接複製整個家目錄更容易重現。

常見問題

不同 Xcode 版本可以共用 DerivedData 嗎?

不建議。至少應依 Xcode 版本、專案、Scheme 與建置設定分開目錄;切換工具鏈後重新產生 DerivedData,可避免舊索引及中間產物影響結果。

租期結束前最值得匯出的快取是什麼?

優先匯出依賴鎖定檔、可重用的 Swift 套件下載、結果封裝與診斷日誌。物件檔及索引高度依賴工具鏈,通常在新機器重新產生更可靠。

清除全部快取可以解決建置變慢嗎?

它適合用來確認快取污染,但不應成為日常做法。先分別量測依賴解析、編譯、連結與測試時間,再只清理對應階段的目錄。

ArmMacs 雲端 Mac

依專案週期使用獨享實體節點

選擇固定晶片、記憶體、儲存空間、租期與在售節點;庫存可用時即可進入交付流程。

選擇機型並訂購