← 返回技术博客

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 季 cache 失效的根因是编译器与 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;Fastlane 签名见 TestFlight CI

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 支持矩阵 与站内 Fastlane 文。
  • 磁盘满: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 限时优惠