← 返回技術部落格

Xcode 26 Beta 測試成本暴漲?
一套 Runner 隔離方案解決

遠端 Mac mini 上 Xcode 26 beta CI 與 self-hosted runner 雙機隔離架構
生產釘 Xcode 16、beta 走專用 Runner——WWDC 後 iOS CI 的正確做法是拆磁碟,不是全員升 beta。

結論:WWDC 26 之後要在 CI 裡試 Xcode 26 beta,正確做法不是把 main 的 workflow 改成 macos-latest 然後祈禱——而是在遠端 Mac 上拆兩條 Runner 車道:正式環境繼續釘 Xcode 16 穩定版,beta 走獨立 label 加獨立 DerivedData 目錄。

三條硬規則:① 正式 job 禁止呼叫 sudo xcode-select;② beta 與正式的 -derivedDataPath 必須物理隔離;③ iOS 27 Simulator runtime 只在 beta Runner 預裝一次,別讓每個 job 重複下載。

本文邊界:不談「為什麼 WWDC 後 CI 變慢」(見 WWDC26 iOS CI 變慢文),只給 48 小時可執行的 Runbook

6 月 10 日那篇我們說過:WWDC 後 iOS CI 的震感來自快取清零、SDK 變胖、託管 Runner 壅塞。一週過去,Slack 裡的新問題變成實操向的——「beta 裝在哪台 Mac?」「一台機器能跑兩個 Xcode 嗎?」「main 紅了誰負責?」這篇就是答案:用遠端 Mac mini 當 self-hosted runner,把 beta 驗證與 App Store 發版徹底分流。若你還沒上 self-hosted,先讀 iOS CI 加速指南;節點與月租對比見 六地 Runner TCO

1)先排除三個誤讀:一台 Mac 混跑 ≠ 省錢

WWDC 後最常見的錯誤決策,是在唯一一台 CI Mac 上同時裝 Xcode 16 與 26 beta,然後靠 workflow 裡的 xcode-select -s 切換。看起來省了一台月租,實際代價是:

  • 競態:兩個 job 並行時,後執行的 xcode-select 會改掉全域路徑,正式 Archive 可能意外用到 beta 編譯器。
  • 快取污染:共用 ~/Library/Developer/Xcode/DerivedData 時,Swift module 版本混編,報錯像「幽靈 bug」——本機重現不了,CI 隨機紅。
  • 磁碟爭搶:兩套 Xcode 加兩套 Simulator runtime 輕鬆占 80GB 以上;16GB 記憶體機器在 beta 全量編譯時 swap,拖慢所有 job。
決策表一句話: 團隊日更 main、要保 TestFlight 節奏 → 正式 Runner 與 beta Runner 必須隔離。只有「每月發版、beta 僅 nightly」的小團隊,才勉強考慮單機分時複用——且仍要分 DerivedData 目錄。

2)雙 Runner 架構:label 池與 workflow 分流

推薦的最小可行架構如下——兩台遠端 Mac mini M4(或一台正式環境加一台按日租的 beta 試錯機):

RunnerGitHub LabelXcode 版本負責 job
正式機 Aself-hosted, macos, ios-prodXcode 16.4(釘死)main Archive、TestFlight、發版 tag
Beta 機 Bself-hosted, macos, xcode26-betaXcode 26 betaios-27-* 分支、 nightly 適配、API 探測

workflow 側用 runs-on 硬分流,禁止 beta 分支觸發走 ios-prod label:

.github/workflows/ios-prod.yml — 正式 lane
name: iOS Production CI
on:
  push:
    branches: [main, release/*]
jobs:
  archive:
    runs-on: [self-hosted, macos, ios-prod]
    env:
      DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer
      DERIVED_DATA: /var/ci/deriveddata/prod
    steps:
      - uses: actions/checkout@v4
      - name: Build & Archive
        run: |
          xcodebuild -scheme MyApp -configuration Release \
            -derivedDataPath "$DERIVED_DATA" \
            -archivePath build/MyApp.xcarchive archive
.github/workflows/ios-beta.yml — beta lane
name: iOS 27 Beta Adapter
on:
  push:
    branches: [ios-27-*, feature/siri-ai-*]
  schedule:
    - cron: '0 2 * * *'   # nightly,不阻塞 merge
jobs:
  beta-build:
    runs-on: [self-hosted, macos, xcode26-beta]
    continue-on-error: true   # beta 紅不擋 main
    env:
      DEVELOPER_DIR: /Applications/Xcode_26_beta.app/Contents/Developer
      DERIVED_DATA: /var/ci/deriveddata/beta
    steps:
      - uses: actions/checkout@v4
      - run: xcodebuild -scheme MyApp -sdk iphonesimulator build \
          -derivedDataPath "$DERIVED_DATA"

註冊 Runner 與 label 命名規範見 GitHub self-hosted runner 文件。要點:label 即契約——誰改 label 誰背鍋。

3)遠端 Mac 上安裝 Xcode 26 beta 與多版本共存

裸金屬遠端 Mac上,你可以把兩個 Xcode 並排裝在 /Applications,用目錄名區分版本,避免 beta 覆蓋穩定版:

  1. Apple Developer Downloads 拉取 Xcode 26 beta(.xip),SSH 登入遠端 Mac 後解壓到 /Applications/Xcode_26_beta.app
  2. 正式機只接受 DEVELOPER_DIR 環境變數,workflow 裡寫絕對路徑;禁止 job 內 sudo xcode-select -switch
  3. 首次安裝後手動跑一次 xcodebuild -runFirstLaunch 與 license accept——這些步驟不要放進每次 CI job。
  4. iOS 27 Simulator runtime 在 beta 機預裝:打開 Xcode → Settings → Platforms 一次裝完;workflow 裡刪除 -downloadPlatform 步驟。

若團隊本機開發也要混版本,可參考 告別 Xcode 卡頓 裡的混合工作流:本機寫 Swift,重編譯丟雲端 M4——beta 適配同樣適用,別把 beta 索引和編譯壓在 16GB 筆電上

4)DerivedData / Pods / SPM 三路快取隔離

WWDC 季快取失效的根因是編譯器與 module 格式變了,不是「快取壞了」。隔離策略如下:

表:正式 vs beta Runner 快取目錄(建議固定路徑,跨 job 持久)
快取類型正式路徑Beta 路徑備註
DerivedData/var/ci/deriveddata/prod/var/ci/deriveddata/betaworkflow 寫死 -derivedDataPath
CocoaPods/var/ci/cocoapods/prod/var/ci/cocoapods/betaCP_HOME_DIR--deployment
SPM/var/ci/spm/prod/var/ci/spm/betaclonedSourcePackagesDirPath
ModuleCache隨 DerivedData 父目錄獨立 beta 樹切勿共用預設 ~/Library

actions/cache 在 WWDC 季往往不夠:Xcode 大版本切換後 cache key 全廢,上傳數 GB DerivedData 的網路時間常比本機磁碟更虧。self-hosted 的價值正是磁碟跨 job 存活——第二次 beta build 才會從 20 分鐘以上回落到 10 分鐘檔。Flutter 三快取細節見 Flutter iOS CI;簽名與 TestFlight 上傳見 iOS CI 加速指南 的 Signing 章節。

Runner 初始化腳本(僅需執行一次)
sudo mkdir -p /var/ci/{deriveddata,cocoapods,spm}/{prod,beta}
sudo chown -R $(whoami) /var/ci
# 磁碟監控:DerivedData 膨脹時按 lane 清理,勿 rm -rf 整盤
du -sh /var/ci/deriveddata/*

5)典型踩坑:beta 崩潰、SDK 變胖、鑰匙串串台

我們在客戶現場見過這些高頻事故——每條都有明確預防:

  • beta 紅一片阻塞 merge:把 beta workflow 設為 continue-on-error: true,且不要作為 required status check。beta 不穩定是預期,不是工程師失職。
  • 正式誤用 beta SDK:Archive 日誌裡檢查 DTXcodeDEVELOPER_DIR;在 prod job 開頭加斷言:test "$DEVELOPER_DIR" = "/Applications/Xcode_16.4.app/Contents/Developer"
  • 鑰匙圈 / Match 證書串台:兩台 Runner 各用獨立 login keychain;Fastlane match 的 git_url 可共用,但匯入證書的 keychain 密碼按機隔離。詳見 Apple 的 Xcode 支援矩陣
  • 磁碟滿:beta DerivedData 加雙 Simulator runtime 增長極快;建議 beta 機 512GB SSD,每週 cron 清理 beta 樹下超過 14 天未存取的 subfolder。
  • 並行 Archive OOM:M4 16GB 同時跑兩個 Archive 會 swap;正式機 concurrency 設為 1,beta 機可 1–2 視工程而定。

6)樣本:拆 Runner 前後同一倉庫 P50(非 SLA)

以下為 Swift/UIKit 加 CocoaPods 中等工程、main 正式 job 在 WWDC 後首週的對比——未拆 Runner(全員升 beta)vs 拆雙 Runner(正式仍 Xcode 16):

指標未拆分(main 升 beta)雙 Runner 隔離
main Archive P50~42 min~11 min(與 WWDC 前持平)
beta 適配 job P50(與 prod 搶同一台)~22 min(首週全量,第二週 ~12 min)
main 因 beta 誤傷失敗率高(編譯器/快取混用)接近 0
所需 Mac 數量1 台(看似省)2 台 M4 或 1 正式 + beta 日租

數字因倉庫而異,請用自家 workflow 跑 48 小時 A/B。核心不是「快多少」,而是main 不再為 beta 買單

7)48 小時落地清單

  1. Day 0 上午:鎖定正式 Xcode 版本,改 prod workflow 寫死 DEVELOPER_DIR;main 上禁止合併任何「升級 Xcode 26」的 CI 改動。
  2. Day 0 下午:開一台遠端 Mac mini(或 48h 日租)註冊為 xcode26-beta Runner;裝 Xcode 26 beta 加 iOS 27 runtime。
  3. Day 1:建立 /var/ci/... 快取目錄;跑通 beta workflow 首次全量 build;確認 prod job 未受影響。
  4. Day 2:把 beta workflow 改為 nightly 加 ios-27-* 分支觸發;從 GitHub branch protection 移除 beta check 的 required 標記。
  5. 驗收:連跑 3 次 main Archive < 15 min(視工程);beta job 失敗不 Slack @channel。

不確定第二台是否值得月租?先用日租 beta 機跑完 iOS 27 適配 spike,再決定是否並席——TCO 模板見 MacBook Pro vs 雲端 Mac 決策。工具鏈為何只能 macOS 獨占,背景見 Xcode 工具鏈獨占文

8)常見疑問(15 條精選)

1. 一台遠端 Mac 能裝兩個 Xcode 嗎? 能。用不同 .app 目錄名並存;CI 上用 DEVELOPER_DIR 指定,別依賴全域 xcode-select

2. beta Runner 必須 24GB 嗎? 單 scheme 中等工程 16GB M4 通常夠;多 target 並行 Archive 建議 24GB。

3. 正式能否偶爾跑 beta job? 不建議。即使分時複用,也至少分 DerivedData 目錄;最好物理分機。

4. 託管 macos-latest 能代替 beta Runner 嗎? 不能。ephemeral 磁碟每次冷啟動,WWDC 季 bootstrap 可占 15 分鐘以上,且無持久快取。

5. actions/cache 夠用嗎? 大版本切換後 key 失效;大 DerivedData 上傳下載不如本機磁碟。

6. beta 不穩定導致 CI 紅一片? 預期之內;beta lane 設 optional,勿阻塞 main merge。

7. 如何確認 prod 沒用 beta 編譯器? 查 Archive 日誌 DTXcode 或在 job 開頭斷言 DEVELOPER_DIR

8. Simulator runtime 每個 job 都要下嗎? 不要。beta 機預裝一次,workflow 刪掉 download 步驟。

9. 兩台 Runner 能不同地區嗎? 可以。正式選離團隊近的節點;beta 可與 prod 同區降低 ops 複雜度。

10. OpenClaw 能管 beta Runner 嗎? 可以 webhook 觸發,但本文不展開 Gateway;CI 註冊見 OpenClaw CI Runner FAQ

11. 日租 beta 機夠嗎? 適配 spike 夠;若 nightly 長期跑,月租更省。

12. DerivedData 多大要清理? 單 lane 超 40GB 建議按 scheme 清理;正式 lane 謹慎,優先刪 beta。

13. Swift 6 並發檢查更嚴怎麼辦? 在 beta lane 單獨開 strict 標誌,prod 保持現有設定,分階段遷移。

14. 和 WWDC 文什麼關係? 那篇講「為什麼慢」;這篇講「怎麼拆 Runner 落地」。

15. 48h 內必須兩台嗎? 若 main 暫不碰 beta,一台 prod 即可;beta 適配可等日租機到位再開 lane。

Beta 可以折騰,正式環境必須穩

WWDC 後最划算的配置,往往是一台月租正式 Runner 加一台按需 beta 機:正式 Mac mini M4 7×24 保 TestFlight 節奏,beta 用日租試跑 iOS 27 適配,試完再決定是否並席。Nuvcloud 裸金屬獨享磁碟,DerivedData 跨 job 存活;M4 16GB 對絕大多數 iOS Archive 夠用,24GB 留給多 scheme 並行。低功耗、無風扇,適合長期無人值守 CI。

若你正在 WWDC 後規劃Xcode 26 beta CI,又不想拿 main 當試驗場, Nuvcloud 雲端 Mac mini M4 是成本最低的拆分起點—— 立即了解套餐方案 ,48 小時把 beta / 正式 Runner 拆清楚。

LIMITED 限時優惠