2026年獨佔遠端 Mac iOS/macOS CI:Xcode 26 Compilation Cache 與 sccache/ccache 遠端編譯快取怎麼選?——顯式模組、SPM 盲區、併發 Job 目錄隔離與 SSH 無頭可複現決策矩陣 + FAQ
在獨佔遠端 Mac(SSH 無頭)上跑 iOS/macOS CI 時,「編譯變快」往往被混成一件事:Xcode 內建的 Compilation Cache、Clang 顯式模組產物、DerivedData 分層,以及跨機器共享的 sccache/ccache。它們快取的對象、失效條件與併發隔離方式完全不同——疊錯層只會得到「快取命中率很好看、可複現性卻很差」的報表。
先把三層快取畫清楚:別用同一個鍵管所有「快」
層 A — Xcode 26 Compilation Cache(Apple 工具鏈內建):面向 Clang/Swift 前端在同一工具鏈版本下的編譯單元複用,通常與顯式模組(Explicit Modules)和模組圖穩定性強相關。它解決的是「同一台機器、同一套 SDK 下重複編同一 TU」的分鐘成本,而不是天然跨 Xcode 小版本或跨不同 DEVELOPER_DIR 的遠端共享。
層 B — DerivedData / 模組快取目錄:工程級索引、預編譯模組、Swift 增量狀態。對獨佔 Runner 友好,但對併發 Job必須做目錄隔離,否則會出現 A job 寫入、B job 讀到半寫入索引的詭異失敗。
層 C — sccache / ccache(編譯器包裝器 + 遠端物件儲存):在更底層攔截 clang / swiftc 呼叫,把預處理後的編譯命令雜湊映射到共享儲存(S3、MinIO、自建 NAS)。優勢是跨 Runner、跨分支可共享;代價是鍵設計、沙箱路徑與 Apple 專有參數一旦漏進雜湊就會大面積未命中或錯誤命中。
評審口訣:Compilation Cache 管「工具鏈內」;DerivedData 管「工程內」;sccache/ccache 管「組織內跨機」。三層可以並存,但必須有明確的失效策略與目錄邊界,否則 Ephemeral Runner 與持久 Runner 的取捨會被快取污染掩蓋——持久池與 Ephemeral 池的邊界可參考:2026年獨佔遠端 Mac 自託管 CI:持久 Runner 與 Ephemeral Runner 池怎麼選?。
Xcode 26 Compilation Cache:什麼時候「預設開」就夠?
在 Xcode 26 世代,Compilation Cache 的價值在中大型 ObjC/Swift 混編、顯式模組已穩定、且 CI 節點長期固定同一 xcodebuild -version 時最明顯:乾淨建置後的第二次全量編通常能明顯縮短前端時間。它並不替代 -derivedDataPath 治理,也不解決 SwiftPM 依賴圖在鎖檔漂移時的重解析成本。
建議預設開啟 Compilation Cache 的情境:
- 獨佔 Mac 上持久 Runner或「每 job 獨立 DerivedData 但共享唯讀 Compilation Cache 種子層」
- 已啟用或計畫啟用顯式模組,且 CI 與本地開發使用同一套
SWIFT_ENABLE_EXPLICIT_MODULES/ 建置設定 - 發布流水線對可複現性要求高於「跨 PR 共享最後一層快取」
應謹慎或配合隔離的情境:同一台 Mac 上多 job 併發且共用使用者主目錄;Xcode 小版本滾動升級週(Compilation Cache 與模組索引需一併失效);需要向稽核方證明「建置僅由提交 SHA + 鎖檔決定」的合規流水線——此時要把 Cache 目錄納入版本化失效清單,而不是「永不過期」。
顯式模組(Explicit Modules):Compilation Cache 的前置條件
顯式模組把「誰先編、誰依賴誰」從隱式掃描改成可宣告的模組圖。對 CI 的意義是:快取鍵更穩定、並行度更可預測,Compilation Cache 的命中也更「講理」。若本地關、CI 開(或反過來),你會看到典型的「CI 綠、筆電紅」或相反——這不是 flaky,是模組圖不一致。
落地檢查清單(可寫進 Runbook):
- 在
xcodebuild與 Xcode GUI 使用同一xcconfig或 Build Settings 匯出 - 升級 Xcode 時同時 bump「模組快取 / Compilation Cache」失效版本號
- 併發 job 使用獨立
-derivedDataPath,可共享唯讀 SDK 與唯讀種子層,但勿共享可寫模組快取根目錄
SPM 盲區:為什麼「開了快取還是慢」
Swift Package Manager 的解析、檢出與二進位製品快取,和 Clang Compilation Cache 不是一條管線。常見盲區包括:
- 鎖檔未提交或 CI 未校驗:
Package.resolved漂移導致每次 job 重新解析依賴圖 - 全域
~/Library/Caches/org.swift.swiftpm被多 job 爭用:表現為偶發解析失敗或極慢的首分鐘 - 二進位依賴 + 不同 Xcode:製品與 SDK 不匹配時,Compilation Cache 無法救場
- 把 SPM 目錄打進 sccache 鍵卻未固定路徑:SSH 無頭節點上 home 路徑不一致造成偽命中
SPM 側的策略應單獨成文:2026年獨佔遠端 Mac iOS/macOS CI:SwiftPM 的 Package.resolved 到底該不該提交?——可復現建置、快取預熱、CI 鎖版本漂移與 SSH 流水線決策矩陣 + FAQ。Compilation Cache 與 sccache 都應建立在「SPM 圖已鎖」之後,否則只是在快取雜訊。
sccache vs ccache:遠端 Mac CI 上的現實取捨
| 維度 | ccache | sccache |
|---|---|---|
| 遠端後端 | 需外掛或自建同步 | 原生 S3/Azure/GCS 等 |
| Rust/LLVM 生態工具鏈 | 成熟 | 成熟 |
| Swift/Apple 專有參數 | 需仔細調雜湊 | 需仔細調雜湊 |
| 多租戶隔離 | 前綴 + 桶策略 | 前綴 + 桶策略 |
| 維運複雜度 | 低(單機) | 中(物件儲存 + IAM) |
2026 年獨佔遠端 Mac 上的經驗法則:僅一台持久 Runner、團隊很小——Compilation Cache + 本地 DerivedData 分層往往已夠;多台 Mac mini 池、分支爆炸、需要跨機共享 C/C++/Rust 輔助目標——優先評估 sccache 遠端桶,並把 Xcode 主路徑留給 Apple 內建快取,避免雙重包裝 swiftc 引入難以診斷的命中錯誤。
ccache 仍適合「單機 + 少量原生依賴」或已有 ccache 維運經驗的團隊;若你已在用物件儲存做 Bazel 遠端快取,sccache 與桶策略、IAM、生命週期規則可以複用同一套 FinOps 科目。
併發 Job 目錄隔離:SSH 無頭上的硬約束
獨佔 Mac 並不等於「可以共用一個 DerivedData」。推薦最小隔離模型:
DERIVED_DATA_ROOT/$CI_JOB_ID— 每 job 可寫COMPILATION_CACHE_ROOT/v$XCODE_BUILD— 多 job 唯讀掛載或每 job 拷貝種子SCCACHE_BUCKET_PREFIX/$REPO/$XCODE_BUILD/$BRANCH_POLICY— 組織級共享,發布分支與 PR 分支分桶- 環境變數
TMPDIR、HOME對 CI 使用者做job 級覆寫,避免寫入全域鑰匙圈或全域 SPM 快取
SSH 無頭排障時,應能在不登入 GUI 的情況下匯出:xcodebuild -showBuildSettings、實際 -derivedDataPath、Compilation Cache 路徑、sccache --show-stats。把「路徑」寫進日誌,比只報「快取命中率 82%」更能定位未命中原因。若你正在校準 SSH 無頭工作站與 Runner 帳號邊界,可一併對照 如何透過 SSH 將遠端 Mac mini 打造為命令列工作站。
可複現決策矩陣(寫入 Runbook)
| 你的現狀 | Compilation Cache | sccache/ccache |
|---|---|---|
| 單台獨佔 Mac,持久 Runner,Xcode 版本固定 | ✓ 首選 | 可選 |
| 多台 Mac mini 池,PR 併發高 | ✓ 每節點 | ✓ 遠端桶 |
| Ephemeral job,強合規「零跨 job 狀態」 | ✓ 唯讀種子 | △ 慎共享寫 |
| 大量 SwiftPM + 鎖檔不穩定 | △ 先鎖 SPM | △ 先鎖 SPM |
| 含 Rust/C++ 輔助目標的多儲存庫 | ✓ Apple 目標 | ✓ 非 Apple 目標 |
| 每週滾動升級 Xcode | ✓ + 版本失效 | ✓ 桶前綴含版本 |
混合策略(2026 常見):預設開啟 Xcode 26 Compilation Cache + 每 job 獨立 DerivedData;對原生輔助庫啟用 sccache 遠端桶;SPM 用鎖檔與專用快取目錄預熱;發布 job 在流水線裡顯式執行「快取失效」步驟而非依賴隱式過期。
FAQ
Q1:Compilation Cache 和清理 DerivedData 衝突嗎?
不必然衝突。DerivedData 清理主要影響工程索引與增量狀態;Compilation Cache 若在獨立路徑,可保留為唯讀種子層。若你「一鍵 rm -rf DerivedData」把模組快取與 Compilation Cache 根目錄一起刪掉,第二次建置仍會冷啟動——Runbook 裡應寫明刪什麼、留什麼。
Q2:sccache 包一層 xcodebuild 安全嗎?
不建議把 xcodebuild 整體交給 sccache。更穩的做法是:僅對 CMake/Bazel/Rust 等顯式呼叫 clang/swiftc 的子目標啟用包裝;Apple 主目標交給 Xcode 內建快取與正確的 DerivedData 隔離。
Q3:命中率很高但結果不可複現怎麼辦?
先停遠端共享,固定 Package.resolved 與 Xcode 版本,在 SSH 上做一次乾淨建置 + 帶快取建置的 diff。常見原因是雜湊未包含 SDK 路徑、巨集定義或分支名,導致錯誤命中。發布流水線應能一鍵停用 sccache(SCCACHE_DIRECT=1 或卸載包裝)。
Q4:併發兩個 job 必須兩台 Mac 嗎?
不必須,但可寫目錄必須隔離。一台 M4 Mac mini 上跑兩個 job 時,更常見的問題是 I/O 爭用與 Simulator 爭用,而不是「不能共享 Compilation Cache 唯讀層」。用佇列限制併發度往往比盲目加機更省錢。
Q5:怎麼向管理層解釋「還要買物件儲存」?
把 sccache 桶成本與「少一台常駐 Runner 分鐘」放在同一 FinOps 表:遠端快取降低的是跨機重複編譯,Compilation Cache 降低的是單機重複前端。兩者疊加不當會增加排障人力——矩陣的意義是讓花錢買來可觀測的命中,而不是買來 mystery green。
在 Mac mini 上把三層快取跑順
Compilation Cache、sccache 與 DerivedData 策略最終都要落在一台能 7×24 編譯、SSH 可登入排障的 Apple Silicon 真機上。Mac mini M4 的低待機功耗(約 4W 量級)與靜音設計,適合在桌邊或機房堆疊多台組成獨佔池;macOS 與 Xcode 的原生對齊,減少了「筆電上快、CI 上慢」的工具鏈溫差。
統一記憶體架構讓中大型 iOS 工程在「編譯 + 單測 + Simulator」並行時更少觸頂;Gatekeeper、SIP 與 FileVault 則為無人值守節點提供了可稽核的預設安全姿態。當你把顯式模組、SPM 鎖檔與 job 級目錄隔離寫進 Runbook,Mac mini 是讓這些策略穩定兌現的硬體基線,而不是另一層需要猜測的黑盒。
如果你正準備把 Xcode 26 的 Compilation Cache 與遠端 sccache 桶同時上線,並希望用可登入、低噪音的小主機撐起整條獨佔流水線,Mac mini M4 仍是 2026 年性價比極高的起點——先把快取分層在真機上驗收,再擴展 Runner 副本數。