截至 2026 年 9 月 1 日,Xcode 27 Beta 6 要求 macOS Tahoe 26.4 或更高版本,Xcode 26.6 要求 macOS Tahoe 26.2 或更高版本;Xcode 27 还只支持 Apple silicon Mac。(Apple Developer:Xcode 系统要求)
症状:流水线声明使用 Xcode 26,日志却显示调用了 Xcode 27 的 SDK。
最快解法:保留两个独立的 Xcode 应用目录,在每条任务中显式设置 DEVELOPER_DIR,并把工具链、SDK、组件、缓存和回滚结果写入构建证据。
本文最后更新于 2026 年 9 月 1 日,版本与系统要求核实自 Apple Developer 的 Xcode 27 Beta 文档、Xcode 26.6 文档、系统要求页及命令行工具配置文档。Beta 期间的版本号、已知问题和兼容范围仍可能变化。
谁该看这篇:
- 仍需用 Xcode 26 发布正式版本,同时验证 Xcode 27 项目的移动开发团队。
- 需要把不同仓库、分支或流水线路由到指定 Xcode 版本的 DevOps 工程师。
- 负责远程 Mac 节点升级、磁盘容量、签名环境和故障恢复的平台维护人员。
先分清:能安装两个 Xcode,不等于 CI 已经安全
一台兼容的 Apple silicon Mac 可以同时保留 Xcode 26 和 Xcode 27,但“两个应用都能打开”只能证明图形界面启动成功,不能证明 CI 使用了正确工具链。Apple 已明确说明,Xcode 27 Beta 只能安装并运行在 Apple silicon Mac 上;系统要求还列出了不同 Xcode 版本对应的 macOS 支持范围。(Apple Developer:Xcode 27 Release Notes)
你需要把问题拆成 4 个冲突域:
- 版本选择冲突:全局
xcode-select、当前 Shell、CI 子进程可能得到不同的 developer directory。 - 应用目录冲突:自动更新、覆盖安装或含糊的目录名,可能让脚本指向错误的应用。
- 状态污染:DerivedData、Swift Package 缓存、归档目录和日志混用,旧产物可能掩盖新工具链问题。
- 组件与权限冲突:模拟器运行时、平台支持、首次启动初始化和签名身份没有完成,节点表面在线,任务实际不可用。
Apple 的命令行工具配置文档说明,xcode-select --print-path 可以查看当前生效的 developer directory;而 DEVELOPER_DIR 可以只对当前命令临时覆盖默认选择,不需要修改全局设置。(Apple Developer:配置命令行工具)
版本选择对比:节点默认值,还是任务级 DEVELOPER_DIR
共享远程 Mac CI 节点上,建议把 Xcode 26 设为节点默认版本,或者保留一个经过验证的稳定默认值;正式链和测试链则分别在任务内部设置 DEVELOPER_DIR。这样即使两个任务并行运行,也不会因为某个任务执行了 sudo xcode-select --switch,改变另一个任务的工具链来源。
| 选择方式 | 影响范围 | 适合场景 | 主要风险 |
|---|---|---|---|
xcode-select --switch |
节点级,影响后续命令和用户环境 | 单用户节点、维护窗口、默认版本切换 | 并行任务互相覆盖 |
DEVELOPER_DIR |
当前命令或当前任务 | 共享节点、多仓库、正式链与 Beta 链并行 | 脚本漏传变量 |
| 固定脚本入口 | 由 CI 模板统一注入 | 团队规模较大、需要审计和回滚 | 模板更新需同步验证 |
| 独立节点 | 物理隔离工具链和状态 | 高并发、签名边界不同、Beta 风险较高 | 资源成本和维护工作增加 |
Apple 官方给出的使用边界也很明确:xcode-select --switch 用于改变默认 Xcode,DEVELOPER_DIR 用于在保留默认值的情况下临时选择另一套 Xcode。共享节点优先使用任务级变量,避免不同流水线相互改写全局状态。
决策条件:
- 若节点只有一条流水线、任务不并行,且你正在维护窗口内切换默认版本,则可以使用
xcode-select --switch。 - 若同一节点同时服务正式构建、验证构建和多个仓库,则优先使用任务级
DEVELOPER_DIR。 - 若 Xcode 27 Beta 需要不同签名权限、不同模拟器集合或高并发运行,则回退到独立远程 Mac 节点。
- 若宿主 macOS 不满足 Xcode 27 的官方要求,不要修改应用包、伪造系统版本或使用非官方方式强行部署。
第一步:固定应用目录,并先做宿主机兼容性检查
不要把两个应用都叫成含糊的 Xcode.app,也不要把 Beta 安装到会被自动更新覆盖的路径。可以使用类似下面的目录结构:
/Applications/CI/Xcode-26.6.app
/Applications/CI/Xcode-27-Beta.app
目录名称只是示例。你的关键目标是:路径可读、不会复用、不会被脚本中的模糊通配符选中。
安装前先记录 3 类信息:
uname -m
sw_vers -productVersion
ls -ld /Applications/CI/Xcode-*.app
在 Apple silicon 节点上,uname -m 应该能确认架构;系统版本则必须分别对照 Apple 的系统要求页。当前公开要求显示,Xcode 27 Beta 6 需要 macOS Tahoe 26.4 或更高版本,Xcode 26.6 需要 macOS Tahoe 26.2 或更高版本。版本不满足时,应更换宿主系统或节点,而不是绕过安装检查。
随后分别读取两个应用的版本和构建号:
/Applications/CI/Xcode-26.6.app/Contents/Developer/usr/bin/xcodebuild -version
/Applications/CI/Xcode-27-Beta.app/Contents/Developer/usr/bin/xcodebuild -version
把下载来源、文件校验结果、版本号和构建号存入节点变更记录。不要只记录“已安装 Xcode 27 Beta”,因为 Beta 期间具体版本会持续变化,后续回滚需要知道原来保留的是哪一个构建。
第二步:给每条任务建立只读预检
预检必须发生在 xcodebuild、xcrun 或签名动作之前。下面的脚本使用虚构路径和任务名称,你需要替换成自己的 CI 变量:
#!/bin/zsh
set -euo pipefail
export DEVELOPER_DIR="/Applications/CI/Xcode-26.6.app/Contents/Developer"
echo "DEVELOPER_DIR=$DEVELOPER_DIR"
xcode-select --print-path
xcodebuild -version
xcrun --find xcodebuild
xcrun --find clang
xcrun --sdk iphoneos --show-sdk-path
swiftc --version
这里的重点不是输出越多越好,而是确认“实际来源”。你至少要把以下字段写入构建日志:
DEVELOPER_DIRxcodebuild -versionxcode-select --print-pathxcrun --find xcodebuild- 目标 SDK 路径
- Swift 编译器版本
- 当前提交哈希和 Scheme 名称
如果正式链显示 Xcode 26,但 xcrun --sdk iphoneos --show-sdk-path 指向 Xcode 27 目录,应立即停止任务。不要继续生成归档,再用日志猜测原因。Apple 的构建设置文档将 SDKROOT 列为构建所使用的基础 SDK 路径,因此 SDK 来源必须纳入验收记录,而不是只看应用名称。(Apple Developer:Build Settings Reference)
任务级配置示例
export DEVELOPER_DIR="/Applications/CI/Xcode-27-Beta.app/Contents/Developer"
xcodebuild \
-workspace "DemoWorkspace.xcworkspace" \
-scheme "Demo-Validation" \
-destination "generic/platform=iOS" \
-derivedDataPath "$CI_WORKSPACE/DerivedData/xcode-27" \
clean build
正式链则使用另一条固定路径和独立的 DerivedData 目录。不要在共享脚本开头执行全局切换,再期待后续子进程永远继承正确状态。这个做法在串行任务中可能看起来正常,在并行任务中却容易产生来源不明的产物。
第三步:组件和模拟器要绑定到正确的 Xcode
“应用能打开”不代表节点具备完整构建能力。Xcode 的平台支持、模拟器运行时和首次启动初始化都可能缺失。Apple 文档指出,如果目标平台支持未安装,项目不能在相应设备或模拟器上构建运行;附加组件可以从 Xcode 设置或命令行安装。(Apple Developer:下载和安装附加 Xcode 组件)
每套 Xcode 都应分别执行初始化:
sudo xcode-select --switch "/Applications/CI/Xcode-26.6.app"
sudo xcodebuild -runFirstLaunch
sudo xcode-select --switch "/Applications/CI/Xcode-27-Beta.app"
sudo xcodebuild -runFirstLaunch
这里使用全局切换,是因为 Apple 的组件安装流程要求先选择目标 Xcode,再执行 xcodebuild -runFirstLaunch 或导入平台组件。组件安装完成后,CI 任务本身仍应使用 DEVELOPER_DIR,避免把维护动作和构建路由混在一起。
如果任务需要下载模拟器平台,可以让对应 Xcode 执行:
DEVELOPER_DIR="/Applications/CI/Xcode-27-Beta.app/Contents/Developer" \
xcodebuild -downloadPlatform iOS -exportPath "$CI_WORKSPACE/components"
验收时不要安装所有平台。归档节点通常只需要目标平台支持;单元测试节点可能不需要完整 UI 模拟器;UI 测试节点才需要与测试矩阵匹配的运行时。模拟器运行时是按操作系统版本和平台提供的组件,不是两个 Xcode 应用各自拥有一套完全独立的模拟器数据。Apple 的模拟器文档也说明,多个不同设备类型可以使用同一个 Simulator runtime。(Apple Developer:添加其他模拟器)
⚠️ 经验提醒:Xcode 27 Beta 的 Release Notes 记录过组件安装后模拟器设备不显示等已知问题。遇到这类现象,先按对应 Beta 文档验证重启或服务恢复步骤,不要把它误判为项目代码或签名配置错误。
第四步:隔离 DerivedData、依赖缓存和归档目录
多版本 Xcode 共享 DerivedData,最容易出现“第二次构建通过,干净构建失败”的假象。Xcode 26 文档将项目 DerivedData 位置列为 ~/Library/Developer/Xcode/DerivedData;在 CI 中更稳妥的做法是通过 -derivedDataPath 为不同工具链指定独立目录。(Apple Developer:Xcode 26 Release Notes)
建议至少隔离以下目录:
$CI_WORKSPACE/DerivedData/xcode-26
$CI_WORKSPACE/DerivedData/xcode-27
$CI_WORKSPACE/Archives/xcode-26
$CI_WORKSPACE/Archives/xcode-27
$CI_WORKSPACE/Logs/xcode-26
$CI_WORKSPACE/Logs/xcode-27
Swift Package 依赖缓存、构建缓存和归档目录也应带上 Xcode 主版本或流水线标识。你不需要机械地复制所有签名资产;更重要的是确认两套工具链在相同、受控的签名身份边界内都能完成任务。
建议采用两轮验证:
- 干净构建:删除当前任务的 DerivedData 和归档目录,验证工具链本身。
- 重复构建:保留允许复用的缓存,再次构建,确认缓存不会改变 SDK、签名或产物来源。
如果只有重复构建成功,干净构建失败,先修复环境,不要把缓存命中率当成稳定性。
第五步:签名验证要证明身份一致,而不是复制证书
正式链和 Beta 链可以使用相同的受控签名身份,但应分别验证:
codesign -dv --verbose=4 "Payload/Demo.app" 2>&1 | \
egrep "Identifier|Authority|TeamIdentifier"
xcrun --find codesign
xcrun --find security
你需要核对 Bundle Identifier、Team Identifier、签名主体和导出方式。不要在脚本中输出证书私钥、密码、令牌或完整凭据;日志只保留脱敏后的身份摘要。
Xcode 27 Beta 与 Xcode 26 在 SDK、编译器和平台组件上可能存在差异。Xcode 27 Beta 文档列出 Swift 6.4 以及 iOS 27 等 SDK,Xcode 26.6 文档列出 Swift 6.3 与 iOS 26.5 等 SDK。这个差异意味着,同一个 Scheme 即使源代码不变,也可能因为工具链不同而产生不同警告、链接结果或签名流程表现。
第六步:用验收矩阵决定是否继续共用节点
不要用“今天构建成功”作为上线标准。至少建立正式链、验证链和回滚链三列:
| 验收项目 | Xcode 26 正式链 | Xcode 27 验证链 | 回滚标准 |
|---|---|---|---|
| 版本与 SDK 识别 | 记录版本、构建号、SDK | 记录版本、构建号、SDK | 日志必须与任务声明一致 |
| 干净构建 | 通过 | 通过 | 失败则禁止发布 |
| 单元测试 | 通过 | 通过 | 失败则保留旧工具链 |
| 归档与导出 | 通过 | 按需验证 | 产物身份必须可追溯 |
| 签名检查 | 通过 | 通过 | 不接受来源不明签名 |
| 节点重启后复验 | 通过 | 通过 | 路径和组件不能丢失 |
| Beta 更新后复验 | 不适用 | 手动执行 | 失败立即切回 Xcode 26 |
节点重启后,重新执行版本预检、xcrun --find、SDK 路径检查和一次最小构建。然后模拟一次 Xcode 27 Beta 更新:保留旧应用目录,不覆盖原路径;更新失败时,让验证链继续指向旧 Beta 或直接暂停验证链。
组件可以在准备阶段下载并部署到节点,但实际部署仍需逐节点确认架构、系统版本和目标平台。若你的节点无法稳定恢复目录、环境变量和组件状态,就不要扩大 Xcode 27 的使用范围。
什么时候应该拆成两台远程 Mac
共用节点适合低并发、组件集合相近、签名边界一致的团队。下面几种情况更适合拆分:
- 正式构建不能被 Beta 更新、模拟器安装或初始化动作影响。
- 两条流水线需要不同的签名账号、钥匙串或权限范围。
- UI 测试运行时间较长,容易占满共享节点。
- 多个仓库同时运行,无法保证每个脚本都正确注入
DEVELOPER_DIR。 - Xcode 27 Beta 的已知问题会影响节点重启、设备发现或虚拟化相关任务。
如果只是暂时验证 Xcode 27 Beta,独立远程 Mac 往往比改造现有正式节点更容易回滚。你可以先按照远程 Mac 使用帮助确认 SSH、VNC 和控制台访问方式,再根据并发量查看 MacHTML 的远程 Mac 方案;不要在没有验收矩阵的情况下直接替换正式构建节点。
当前的本地 Windows/Linux 主机或普通 Linux 云服务器,在代码编辑、依赖下载和通用脚本执行上可能更便宜,但它们无法直接提供完整 macOS 工具链;虚拟 macOS 还会增加系统兼容、设备访问和性能波动等边界问题。若你需要的是临时验证 Xcode 27、保留 Xcode 26 正式链,或者为 CI 提供一台持续在线的真实 Apple silicon Mac,租赁 MacHTML 的远程 Mac 会比改造现有主机更容易控制版本、权限和回滚;但长期稳定的高负载任务、必须接入本地物理设备或需要自主管理硬件的团队,仍应评估自购 Mac 或专用节点。
完成验收矩阵后,再决定是继续共用一台节点、为 Xcode 27 准备独立远程 Mac,还是暂缓扩大 Beta 使用范围。需要临时算力或隔离测试环境时,可进一步查看 MacHTML 的方案与交付说明,重点核对租期、访问方式、root 权限和节点交付条件。
为多版本构建准备稳定的远程 Mac
使用 MacHTML 远程 Mac,将正式版与测试版开发环境分开运行,减少本地环境冲突。 按项目需求选择 Mac 租赁或算力节点,为持续集成、自动化构建和回归测试提供稳定资源。 通过 MacHTML 控制台快速管理节点,配合远程桌面完成配置、排错与版本验收。 无需采购和维护本地硬件,按需开通即可开始构建,让你的团队更快投入多版本发布流程。