2026-04-30 iOS CI 迁移:从 Intel x86_64 到 Apple Silicon ARM64 与 租用云 Mac 上的 xcodebuild(香港 / 东京 / 首尔 / 新加坡 / 美国)
苹果仍交付 Intel 版 Xcode,但 2026 年大部分移动团队不愿再为只存在于构建机里的 x86_64 切片付双倍成本。你的队列如果还指着老旧 Intel 小机,而线上产物已是 arm64,你就在资助重复的依赖图和更慢冷的 xcodebuild test 启动。本文是写给发布与基础架构同学的迁移简讯:先用 lipo 和 file 做可审计的二进制清单,把 ARCHS 和 ONLY_ACTIVE_ARCH 写成组织级策略,清掉从未提供 Apple Silicon 切片的厂商 Pod,在双车道下观察够久的抖动,再把默认可调度队列切到 ARM,同时为极少数遗留 CLI 保留断路。它与 远程 Archive 模式、并行 xcodebuild、多版本 Xcode 矩阵、Swift 6 严格并发 互补:并发负载下,并发与架构迁移会同时放大隐藏问题。香港、东京、首尔、新加坡、美国机房的 Mac mini M4 裸金属在 MacXCode 上为同一类策略提供可比的执行环境。下面我们会按章节展开:先解释为什么“在我 M3 笔记本上能过”在 CI 里不算数;再给一套可在 Jira 里原样粘贴的割接表;用行式指标表替代英文原稿里的示例数字结构,但保留可比维度;并反复强调 DerivedData 与签名的老话题——请同步阅读 DerivedData 隔离 与 钥匙串与描述文件 CI。在自托管 GitHub Actions runner 上,标签与队列命名要诚实;名字写 macos-intel 实际跑 arm64 会在迁移后制造幻觉级事故。
快拍:为什么“本机绿”对 CI 不够
个人笔记本会掩盖问题:有温缓存、有 Rosetta 的透明、有人肉耐心。CI 应在几分钟内把矛盾摊开。若出现下面任一条,你多半在迁移中而非已完成:链报里还冒 undefined symbols for architecture arm64,而 lipo -archs 仍对某厂商框架显示 x86_64;YAML 里目的地还停留在 Intel 时代的写法;自托管 runner 的 label 说 Intel,shell 其实已是 arm64。把 2026-04-30 的本文当作跨职能对齐材料:iOS 负责人、SRE、财务(容量标签)和合规都要读同一版。用 MacXCode 在香港或新加坡的节点时,也别忘了制品与 Git 的地理归属:东京绿、美东红往往来自清单脚本在不同区域没对齐,而不是代码突然变坏。
- 链接与架构类报错仍指向多架构 vendored 产物。
- 模拟器目标字符串与新 Xcode 组合不匹配。
- 自托管系统上的架构标签与真实
uname -m不一致。
xcodebuild build 的墙钟若能相对 Intel 基线降至少三成多 值得在复盘里大写高亮;双队列对比至少观察十四个自然日,再谈下线 Intel 容量。具体百分比请用你们自己的直方图替换宣传口径。
迁移与日常 CI 的交叉点还包括 UI 测试分片、Archive 与单元测试的并行度。若你在阅读 并行 xcodebuild 时只看了队列深度而没看内存峰值,很容易在 16GB 统一内存的机型上三轨 Archive 同时撞墙。与静态分析/隐私门禁相关的流水线如果也在同一组主机上,记得把“架构迁移”与“隐私清单/Required Reason 审计”当成两条独立项目线排期。更多远程签名与多仓库 farm 的取舍见 iOS 构建 farm 与远程签名优化 的讨论框架。
二进制清单:先证明仍是 x86_64,再骂 Swift
遍历 Carthage/Build、Pods/、ThirdParty/ 下的 .framework、.a 与预编译的 XCFramework。给每张迁移单都附同一段可重复脚本,团队内部只维护一份。可以用 find 与 xargs 组合在清单阶段批量询问架构;真正写进产线的脚本要放进唯一仓库,香港与美国节点跑同一版本。发现厂商仍只发 Intel 切片,走商务与工程两条线,而不是在工程里用 EXCLUDED_ARCHS=arm64 这种长期毒药“瞒天过海”。在共享 自托管 runner 的宿主机上,更要把清单输出 diff 到基础设施仓库,让区域间漂移有迹可循。
find . -name "*.framework" -o -name "*.a" -o -name "*.xcframework" 2>/dev/null | while read -r f; do echo "=== $f ==="; lipo -info "$f" 2>/dev/null || file "$f"; done | head -n 200
这段刻意截断输出,是提醒你先在小仓验证,再把限制放宽。对于 SwiftPM 的二进制 target,要核对锁文件里引用的平台三元组。若 Swift 6 并发 的警告在 ARM 上更吵,别把它误当成架构没切完——那往往是 Sendable/actor 问题,和 slice 没对齐要分开排障。与此同时,多 Xcode 并存 会让同一套 project 在两条工具链上解读出不同构建设置,迁移窗口里务必 pin DEVELOPER_DIR 并在交接邮件里写死。
多车道:ARM 主道、Intel 断路与“混合”烟测
迁移期最忌讳假装两条队列等价。用下表先口头对齐再改调度。
| 车道 | 负责 | 不负责 |
|---|---|---|
| ARM64 主道 | 默认 PR 检查、模拟器测试、面向 TestFlight 的 Archive | 无 arm64 的古老 CLI,除非在沙箱里 |
| Intel 断路 | 仍待厂商更新的二进制、一次性对比 | 长期产线签名,避免把生产证书绑死在过渡车道 |
| 烟测/混合 | 脚本化 file/lipo 确认需要的 fat 切片 |
让 iOS app 主目标在不知情下依赖 Rosetta |
xcodebuild 与构建设置:值得写成组策略
把下表落到共享 xcconfig 或 Fastlane 环境,吵一次、处处继承。与 xcode-select/DEVELOPER_DIR 绑定,避免“同一条 main 在两条 Xcode 上解释不同”的罗生门。
| 项 | 建议的 ARM64 CI 取值 | 注记 |
|---|---|---|
ARCHS |
显式 arm64 |
对主 App 与扩展同时检查,别只改其一。 |
ONLY_ACTIVE_ARCH |
与 Debug/CI 内环策略统一 | Release 风格 CI 常与内环不同,易制造 sim/device 差异。 |
| 模拟器排除项 | 谨慎清空历史 Intel 排异 | 从 Intel 时代 copy 的排除有时会误伤 arm64 模拟器构建。 |
CocoaPods、SwiftPM 与和 ARM64 作对的依赖图
使用 CocoaPods 的团队应在 arm64 宿主机上 重新跑 install,不要从 Intel 机器 rsync 一整个 Pods/。SwiftPM 团队要核实二进制产物是否真包含 arm64-apple-ios 等所需切片;若厂商滞后,临时改源码依赖或锁 revision,有利息。Rosetta 陷阱:若 uname -m 是 arm64,而 file $(which node) 报 x86_64,你挂在同一台自托管机上的 OpenClaw 与前端检查脚本会在翻译模式下烧 CPU。配合 并行任务 规划 Node 与 Xcode 的亲和性。若 远程 Archive 的带宽或签名窗口是瓶颈,别把架构问题与上载问题混在一个工单里关。
双车道:队列、内存、磁盘与 DerivedData
ARM 机器在多数场景更快,但三路重 Archive 加多路 UI 测 仍可能把 16GB 统一内存打满。为迁移期设置“仅编译”与“含 Archive”不同标签,和 每作业独立 DerivedData/TMPDIR/xcresult 的约定一起执行,否则实验互踢缓存。磁盘方面 ARM 并不会自动让 DerivedData 更瘦,配合 模拟器、运行时与归档清理 的 janitor,才有把握把 NVMe 留给高价值任务。
八步割接:可贴进 Jira 描述
- 割接周末前 72 小时 尽量冻结大版本依赖升。
- 在各区域宿主机上跑同版清单脚本,输出 diff 进基设施仓库。
- 对所有 scheme(含 extension) 应用
xcconfig策略。 - 在 arm64 干净检出上重跑 Pods/SwiftPM 解析与预编译阶段。
- 开双队列,以同一提交为种子对比墙钟、flake、制品尺寸。
- 将默认 PR 路由到 ARM,Intel 车道只读保留。
- 再观察 14 天;回滚标签而不是在慌乱中轮换生产密钥,除非已确认泄露。
- 为 Intel 容量做下线或只保留专项作业并加账单标签,避免与移动产线争预算。
指标:怎么叫“在租用的 M4 上算成功”
下表是维度-导向,不像英文例里的样例分钟数。请用 Grafana/Buildkite 的原始直方图替换本表“示例”列,避免用幻灯片纸面数字开复盘。
| 指标维度 | 在表里写什么 | 用 ARM 车道对比时要注意 |
|---|---|---|
| 冷干净构建 p50 / p95 | 同提交、同缓存策略下的墙钟 | 确认 DEVELOPER_DIR 与 ONLY_ACTIVE_ARCH 一致 |
| 模拟器 UI 测 p95 | 目的地字符串与运行时已对齐后 | 和并发 xctest 分片强相关,见 并行 |
| 每周能耗/时长的归一化代理 | 以 Intel 基线为 1.00 | 要同时看空闲瓦数与吞吐,别只看峰值时钟 |
常见问题
| 问题 | 务实回答(2026-04-30) |
|---|---|
| 为了让 CI 快,要整体跑在 Rosetta 下吗? | 对原生 iOS 编译不应如此;Rosetta 留给周边 CLI 即可。 |
| 架构切了,签名身份要分架构吗? | 身份本身不随架构而变,但 profile 与 ent 要与实际二进一致;大改图后重导。 |
与签名/钥匙串的自动化更细的清单见 CI 上的钥匙圈与描述文件。架构问题常常会放大 profile 不匹配。若用 远程编译与 Archive,确保迁移前后上传窗口与 并发策略 一致。自托管侧参考 GitHub runner 的标签与权限边界。
为什么裸金属 Mac mini M4 仍值得下迁移的注
ARM 的价值不只是更高的标称频率,而是少一层虚拟化对工具链的说谎:租用的 Mac mini M4 配 1–2 TB NVMe 在 MacXCode 多区节点上,让你能并排放双车道、多份 Xcode.app,还能扛住 DerivedData 峰值,而工程师在别的时区睡觉。把可逆的、可度量的灰度与透明 定价 配在一起,你更容易说服再加第二台而不是让第一台超载。要人工点 FDA/权限时,可以短期开 VNC,但日常应留在能 grep 的 SSH 里。与 数据隔离、并发模型 一起读,会显著降低迁移周期间的连环工单。