YAML 没改,但同一个 xcode-27 标签的宿主系统已经从 macOS 26 切到了 macOS 27,构建结果当然不能再按“只升级 Xcode”处理。

最快解法:本周先保留生产流水线,把 xcode-27 当作预览环境;同时用相同 Xcode、固定 macOS 版本的远程 Mac 建立对照节点,等编译、测试、签名和回滚证据齐全后,再决定迁移或继续双轨。 GitHub 已确认这次变化从 2026 年 9 月 10 日开始生效,xcode-27xcode-27-xlarge 目前仍是 Public Preview。(GitHub Changelog:Xcode 27 Runner 镜像变更)

这篇清单适合维护 xcode-27 工作流的开发者、负责 Apple 平台 CI 的 DevOps 工程师,以及需要判断预览 Runner 是否具备生产准入条件的移动研发平台负责人。

最后更新于 2026 年 9 月 14 日,数据核实自 GitHub Changelog、GitHub-hosted Runners 文档、runner-images 镜像清单,以及 Apple 的 Xcode 系统要求和发行说明。

迁移前:固定标签与固定环境,先不要混为一谈

这次迁移最容易犯的错误,是看到 YAML 仍然写着:

runs-on: xcode-27

就以为执行环境没有实质变化。实际上,GitHub 的公告明确说明,xcode-27 过去运行在 macOS 26,现在运行在 macOS 27;标签、架构和 Xcode 主版本看起来相同,宿主系统却已经改变。该镜像仍只面向 arm64 macOS Runner,并保持 Public Preview 状态。(GitHub 官方变更公告)

Apple 的系统要求页面显示,Xcode 27 RC 支持 macOS Tahoe 26.6 或更高版本,并提供 iOS 27、macOS 27 等 SDK。这个信息只能说明 Xcode 与宿主系统存在支持关系,不能证明 GitHub 的预览镜像已经适合你的正式发布链路。(Apple Xcode 系统要求)

本周第一项动作:从最近一次成功的生产任务保存完整基线。 至少包括以下内容:

  • 工作流运行编号、提交 SHA 和触发方式;
  • Runner 镜像版本与操作系统版本;
  • Xcode 版本、Build 号和 xcode-select 指向;
  • CPU 架构,以及关键工具的架构;
  • Package.resolvedPodfile.lockGemfile.lock 或其他依赖锁文件;
  • xcodebuild 参数、Scheme、Workspace、Deployment Target;
  • 构建产物、测试结果包和签名验证日志;
  • 当前发布流水线的证书、Profile、钥匙串处理方式。

GitHub 的镜像仓库会公开镜像软件清单和镜像版本,工作流日志则记录任务实际使用的 Runner。你应把这两类信息一起保存,而不是只截取 YAML 文件。镜像清单还明确说明,Xcode 27 作为预览版本会按最新可用 macOS 镜像提供,工具与模拟器运行时可能随镜像更新而变化。(Xcode 27 镜像软件清单)

一个变量一次改变

迁移对照时,不要同时更新依赖、证书、构建脚本和 Xcode 配置。推荐分成两条实验路径:

  • 路径 A: 同一提交、同一锁文件、同一签名策略,只把 runs-on 从旧 Runner 改为 xcode-27
  • 路径 B: 同一提交、同一 Xcode 主版本,放到固定 macOS 版本的远程 Mac 对照节点;
  • 禁止操作: 在第一次失败后直接删除缓存、重装全部依赖、替换证书,再用“构建成功”证明迁移完成。

如果你的团队已经在做 GitHub Actions macOS 镜像漂移排查,这次应额外把“宿主系统变化”单独列为事件类型,不要归入普通依赖安装失败。

第一个小时:从日志确认到底是哪一层发生了变化

第一次运行不要直接执行完整 Archive。先让任务输出环境指纹,把标签解析成可以比较的证据。

可以在工作流开头加入类似检查:

- name: Print CI environment
  run: |
    sw_vers
    uname -a
    uname -m
    xcode-select -p
    xcodebuild -version
    xcrun --sdk macosx --show-sdk-version
    xcrun simctl list runtimes

你需要看到的不是“任务完成”,而是以下几层是否符合预期:

  1. sw_vers 是否显示 macOS 27;
  2. uname -m 是否显示 arm64
  3. xcode-select -p 是否指向预期的 Xcode 27;
  4. xcodebuild -version 的版本与 Build 号是否匹配;
  5. SDK 与 Simulator runtime 是否来自预期代际;
  6. 镜像版本是否能与 runner-images 清单对应。

GitHub 文档列出的标准 arm64 macOS Runner 是 3 个 M1 CPU、7 GB 内存和 14 GB SSDxcode-27 也在该类 Public Preview 标签中。大型 Runner 的规格和标签另有定义,不能因为都写着 arm64 就假设资源相同。(GitHub-hosted Runner 官方文档)

标签没有改变但系统发生变化,通常是因为镜像维护方更新了该标签所指向的宿主环境。 GitHub 在 2026 年 9 月 10 日更新了 xcode-27 镜像的 macOS 基础系统,而不是要求用户改用新的工作流标签。也就是说,YAML 入口保持不变,但镜像内部的系统边界发生了变化。这个变化已经由 GitHub Changelog 确认,不能再按社区传闻处理。

接下来执行一个最小编译任务:

set -o pipefail

xcodebuild \
  -workspace "<WORKSPACE>.xcworkspace" \
  -scheme "<SCHEME>" \
  -configuration Debug \
  -destination 'generic/platform=iOS' \
  CODE_SIGNING_ALLOWED=NO \
  build | tee xcodebuild-minimal.log

这个步骤的目的,是先排除签名和发布系统。如果最小编译都失败,应立即停止生产迁移,保留完整日志、环境指纹和结果包。不要先运行清理脚本,也不要把失败归因到项目代码。

定位 CI 失败时,先看问题能否在无签名、无 Simulator 的最小编译中复现;如果能复现,再比较旧 Runner 与 xcode-27 的 OS、架构、Developer Directory 和 SDK。只有当最小任务通过后,才进入测试、归档和签名阶段。

第一小时之后:脚本、依赖与架构假设逐项拆开

宿主系统切换带来的问题,往往不是 Xcode 命令本身报错,而是项目周边脚本默认了旧环境。优先检查这些位置:

Shell 与路径

搜索以下硬编码内容:

  • /usr/local/bin/opt/homebrew/bin
  • /Applications/Xcode_*.app
  • 旧版 Simulator runtime 名称;
  • 固定的临时目录、钥匙串路径或系统工具路径;
  • 依赖 macOS 小版本的 sw_vers 判断分支;
  • 假设默认 Shell 行为不变的脚本。

Apple Silicon 环境下,Homebrew、Ruby 原生扩展、Node 原生模块和自编译工具可能出现架构不一致。不要因为 brew install 成功就认为二进制链路正常,应该直接检查:

file "$(which <TOOL>)"
file "<PATH_TO_BINARY>"
otool -hv "<PATH_TO_BINARY>" | head -n 12

依赖与社区 Action

GitHub 文档特别提醒,GitHub 提供的 Action 与 arm64 Runner 兼容,但社区 Action 不一定兼容,部分 Action 需要在运行时自行安装。这个边界会影响缓存、代码签名、模拟器管理、报告上传和第三方发布工具。(GitHub Runner 架构与兼容性说明)

检查顺序建议如下:

  1. 读取锁文件,确认依赖没有被隐式升级;
  2. 检查原生插件和二进制是否包含 arm64;
  3. 查看 Homebrew 软件的实际安装路径;
  4. 确认社区 Action 是否声明支持 arm64;
  5. 最后才考虑清缓存或重新安装。

如果发现 Intel-only 工具暂时无法处理,不要在生产任务中加入 Rosetta 或混合架构作为临时修复。先建立隔离清单,记录工具名称、调用步骤、失败日志和替代入口,再放到固定远程 Mac 节点复测。

区分 Xcode 工具链变化与 macOS 宿主变化时,采用“三组对照”:相同项目与相同 Xcode 对比不同 macOS;相同 macOS 对比不同 Xcode;最后再与旧生产 Runner 比较。只在第一组失败,优先检查宿主系统、权限、路径和系统工具;只在第二组失败,优先检查编译器、SDK、Simulator 和 Xcode 行为变化。

完整构建:从无签名编译走到 Simulator 与结果包

通过最小编译后,再按以下顺序扩大测试范围:

里程碑一:无签名构建

验证 Debug 和 Release 两种配置,但暂时关闭签名。记录:

  • 编译器错误与警告数量;
  • 依赖解析是否重新下载;
  • 自定义脚本返回码;
  • 构建目录与缓存命中情况;
  • 产物是否能被后续步骤读取。

不要只比较总耗时。一次构建变快,可能只是缓存命中;一次构建变慢,也可能是依赖重新解析。你需要保存命令、日志和缓存状态,才能判断结果是否可复现。

里程碑二:单元测试与 Simulator

然后执行单元测试,再执行 Simulator 测试。重点观察:

  • 目标 Simulator runtime 是否真的存在;
  • 测试目标能否启动;
  • 并行测试时日志是否延迟;
  • 测试进程是否被系统权限或架构问题中断;
  • xcresult 是否仍能被现有报告脚本解析。

Apple 的 Xcode 27 发行说明曾记录并行测试场景下 stdoutstderr 可能明显延迟,这类行为会影响日志采集和失败定位,因此不能只看最终退出码。(Apple Xcode 27 发行说明)

结果包检查可以单独执行:

xcrun xcresulttool get \
  --path "<RESULT_BUNDLE>.xcresult" \
  --format json > result.json

如果现有报告工具依赖旧版 JSON 字段、路径或日志顺序,应把它视为迁移阻断项,而不是“报告暂时缺失”。CI 能否准确告诉你测试为什么失败,本身就是生产准入条件。

里程碑三:完整 Archive

最后才运行 Archive。完整构建必须同时覆盖:

  • 无签名构建;
  • 单元测试;
  • Simulator 测试;
  • Archive;
  • xcresult 解析;
  • 产物完整性检查;
  • 构建日志上传。

官方镜像清单显示,Xcode 27 镜像包含 iOS 27、macOS 27、watchOS 27、tvOS 27 等 SDK 与对应 Simulator runtime,但实际可用版本仍应以任务日志和当前镜像清单为准。不要只根据标签名称猜测运行时是否存在。

发布验证:预览 Runner 与固定远程 Mac 分开承担风险

签名是迁移中最不适合“一次失败就重建”的部分。首次候选发布应使用非生产凭据、测试 Bundle Identifier 和受控应用,验证以下链路:

  1. 临时钥匙串创建与解锁;
  2. 证书导入;
  3. Provisioning Profile 匹配;
  4. Archive;
  5. Export;
  6. codesignspctl 验证;
  7. 产物上传到测试交付入口;
  8. 失败时删除临时凭据并恢复原状态。

GitHub 文档指出,arm64 macOS Runner 没有固定 UUID/UDID;如果你的签名流程依赖固定设备标识,不能直接套用 Intel Runner 的假设。(GitHub 自托管 Runner 与 Apple 平台说明)

任何证书删除、钥匙串重建、Profile 覆盖或签名配置替换,都必须在变更记录中写清影响范围和恢复入口。预览 Runner 的第一次试跑不应直接接管正式发布。

决策工具:什么时候保留、双轨或迁移

观察结果 推荐动作 生产处理
最小编译失败,或失败只能在 macOS 27 复现 保留旧流水线 xcode-27 仅用于隔离测试
编译通过,但 Simulator、结果包或签名链路不稳定 双轨运行 关键发布任务继续走旧环境
相同提交在两套环境中产物、测试和签名结果一致 分阶段迁移 先扩大非发布任务,再迁移发布任务
预览 Runner 出现无法解释的镜像漂移 固定对照节点 暂停依赖预览标签的生产任务
团队需要长期固定 macOS 与 Xcode 组合 使用固定远程 Mac 将其作为受控自托管节点或人工复核节点

预览 Runner 是否适合正式发布,不能只看它能否被工作流选中。当前它仍是 Public Preview,GitHub 对预览和 Beta 镜像的支持边界、稳定性与服务承诺不能等同于稳定生产环境。除非你已经完成产物可复现、签名验证、故障取证和回滚演练,否则不建议让它成为唯一发布入口。

上线首周:用连续证据决定迁移,而不是看一次成功

上线首周至少连续观察以下指标:

  • 同一提交重复构建是否得到一致产物;
  • 编译、测试和 Archive 是否都能通过;
  • Simulator runtime 是否发生漂移;
  • 失败能否从日志定位到具体层;
  • 签名失败是否有可用回滚路径;
  • 旧流水线是否仍能在需要时立即接管;
  • 镜像公告、软件清单或预览状态是否发生变化。

保留 Xcode 27 的 macOS 26 对照环境时,不要依赖 xcode-27 标签,因为该标签现在指向 macOS 27。你需要固定一台安装相同 Xcode 版本、但运行受控 macOS 26 的远程 Mac,保存系统版本、Xcode Build 号、架构、依赖锁文件和任务日志。完成对照后,再把它接入有限范围的 GitHub Actions 自托管 Runner 或作为人工复核节点。

VMSPIN 的 远程 Mac Xcode 开发环境验收可以作为这类对照节点的落地入口:先固定环境组合,再复制最小编译、Simulator 测试和签名验证,不要一开始就把正式发布凭据放进去。若你需要比较不同租赁周期与方案,可查看 VMSPIN 的远程 Mac 方案说明

建议把旧流水线至少保留到以下事件发生之后再重新评审:

  • GitHub 明确更新 xcode-27 的预览状态;
  • runner-images 清单出现新的 Xcode 或 macOS 版本;
  • Apple 发布新的 Xcode 27 构建或系统状态;
  • 关键依赖完成 arm64 兼容验证;
  • 团队完成一次签名失败后的回滚演练。

GitHub 的自托管 Runner 文档说明,任务会按标签和 Runner 状态路由;如果没有匹配且在线的节点,任务会排队等待。固定远程 Mac 因此不仅要能编译,还要验证重启后能否恢复、Runner 服务能否重新上线、工作目录和凭据是否按预期处理。

如果你当前依赖的是托管预览 Runner,主要问题是宿主系统随镜像变化、社区 Action 的架构兼容性不确定,以及发布失败后缺少固定现场。继续使用它适合快速验证新 SDK;但对需要稳定 Xcode、可复现 macOS、可保留日志和明确恢复入口的团队,固定远程 Mac 更容易建立真正的对照证据。

因此,先用 VMSPIN 租用一台固定组合的远程 Mac 复制关键工作流,再决定是否把生产任务从旧流水线迁出:你得到的不是一次“构建成功”的截图,而是相同 Xcode、不同 macOS 下可重复的编译、测试、签名和重启恢复记录。