运维 / 审计

Xcode 27 与 Xcode 26 怎么共存?2026 年远程 Mac CI 配置

MacHTML Lab2026.09.01 约7分钟阅读
Xcode 27 与 Xcode 26 怎么共存?2026 年远程 Mac CI 配置

截至 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 期间具体版本会持续变化,后续回滚需要知道原来保留的是哪一个构建。

第二步:给每条任务建立只读预检

预检必须发生在 xcodebuildxcrun 或签名动作之前。下面的脚本使用虚构路径和任务名称,你需要替换成自己的 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_DIR
  • xcodebuild -version
  • xcode-select --print-path
  • xcrun --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 主版本或流水线标识。你不需要机械地复制所有签名资产;更重要的是确认两套工具链在相同、受控的签名身份边界内都能完成任务。

建议采用两轮验证:

  1. 干净构建:删除当前任务的 DerivedData 和归档目录,验证工具链本身。
  2. 重复构建:保留允许复用的缓存,再次构建,确认缓存不会改变 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 控制台快速管理节点,配合远程桌面完成配置、排错与版本验收。 无需采购和维护本地硬件,按需开通即可开始构建,让你的团队更快投入多版本发布流程。

租用云端 Mac mini
Apple Silicon 云端 Mac