团队已经有一条能工作的 iOS 流水线,但每次增加依赖、切换 Xcode 或调整签名后,都会重新争论一个问题:继续使用托管构建,还是把任务放到自管云端 Mac。不要先比较某次构建快了多少秒。真正影响长期成本的是环境能否复现、缓存是否可控、失败现场能否保留,以及工程师要承担多少运行工作。
先把工作负载分成三类
先统计最近两周的任务,而不是凭印象选平台。建议按合并检查、归档发布和临时诊断三类记录。合并检查通常生命周期短,输入明确,失败后可以重跑;归档发布涉及签名材料、产物留存和严格版本固定;临时诊断则要求工程师进入同一环境复现问题。
至少记录以下数据:
| 项目 | 需要记录的内容 | 判断目的 |
|---|---|---|
| 工具链 | macOS、Xcode、Ruby、包管理器版本 | 判断环境固定难度 |
| 输入 | 提交号、锁文件、构建参数 | 判断任务能否复现 |
| 状态 | 排队、执行、上传、清理耗时 | 找出真正瓶颈 |
| 现场 | 日志、result bundle、归档产物 | 判断故障取证能力 |
| 缓存 | 路径、大小、命中条件、失效方式 | 判断长期收益 |
单次成功不代表流程稳定。至少使用同一提交连续执行三次,再用一个只修改业务源码、不修改依赖的提交复测缓存行为。
建立统一的环境指纹
两种方式要使用同一份环境检查脚本。不要只输出 xcodebuild -version,还要记录系统版本、当前开发者目录、SDK、Ruby 和包管理器状态。指纹文件应作为每次任务的普通产物保存,失败时先比较它,再检查业务代码。
#!/bin/zsh
set -euo pipefail
mkdir -p artifacts
{
sw_vers
uname -m
xcodebuild -version
xcode-select -p
xcrun --sdk iphoneos --show-sdk-version
ruby --version
git --version
} > artifacts/environment.txt
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-derivedDataPath "$PWD/.build/DerivedData" \
-resultBundlePath "$PWD/artifacts/App.xcresult" \
build
DEVELOPER_DIR 不应指向未经核对的路径。自管环境可以保留多个 Xcode,但流水线必须显式选择;托管环境则应在任务开始时验证实际版本,不匹配就尽早退出。这样可以避免任务运行到归档阶段才暴露 SDK 差异。
分别验证缓存、签名与并发
缓存不要整目录搬运
缓存键至少应包含 Xcode 主版本、架构和依赖锁文件摘要。SwiftPM、CocoaPods 与构建中间目录应分开管理,因为它们的失效条件不同。不要把整个用户目录打包恢复,这会把旧权限、临时文件和不可见配置一起带入新任务。
自管云端 Mac 适合需要持续保留大型依赖缓存的流水线,但必须给每个仓库独立工作目录,并定期验证缓存删除后仍能完整构建。托管构建的缓存生命周期通常由平台控制,工程上应把缓存视为优化,而不是成功条件。
签名材料只在任务期间可见
签名材料应由受控存储在任务开始时注入,完成后立即移除临时副本。日志中不得打印密码、密钥内容或完整文件路径。无论采用哪种方式,都要验证失败分支也执行清理,而不只是成功分支。
并发测试则应从两条任务开始,观察它们是否共享 DerivedData、模拟器、Keychain 或固定端口。只要存在共享可写状态,并发数增加就可能把偶发故障放大。
用故障演练测试可恢复性
不要等真实发布失败才验证恢复流程。可以设计四个无破坏性的演练:故意指定不存在的 scheme、删除一个依赖缓存、让测试用例返回失败、在归档前终止任务。每次都检查退出码、日志末尾、result bundle、临时签名材料和工作目录状态。
托管构建应重点确认日志是否足够解释失败、环境是否还能重现,以及重新执行时输入是否完全一致。自管云端 Mac 还要确认残留进程、挂载目录和端口不会污染下一次任务。建议每个任务使用唯一目录,并用退出钩子回收子进程。
workdir="$(mktemp -d "$PWD/.job.XXXXXX")"
cleanup() {
jobs -p | xargs -r kill 2>/dev/null || true
rm -rf "$workdir"
}
trap cleanup EXIT INT TERM
清理脚本必须先在测试目录验证,避免宽泛通配符触及其他任务。需要保留的日志和构建产物应先复制到独立的 artifacts 目录,再执行回收。
按控制权而不是口号做决定
如果项目使用常规依赖、任务短、失败后允许直接重跑,且团队不希望维护机器状态,托管构建通常更合适。如果需要固定多个 Xcode 版本、长期复用缓存、运行特殊工具、进入现场诊断,或让长任务持续执行,自管云端 Mac 更容易建立清晰边界。
两者也可以拆分使用:合并请求执行轻量测试,稳定分支在自管环境完成归档与深度诊断。关键是两边共用同一套环境指纹、锁文件、构建入口和产物命名,避免形成两条互不兼容的流水线。
最终验收不看最快一次,而看多次运行的成功率、环境差异数量、缓存删除后的可恢复性、失败证据完整度和人工介入步骤。把这些结果写入仓库中的决策记录,并约定在 Xcode 主版本、依赖体系或任务规模变化后重新评估,选型才不会变成永久假设。
常见问题
托管构建一定比自管云端 Mac 更省事吗?
不一定。标准化项目通常能减少维护工作;需要固定 Xcode、小众工具、长时间缓存或深入现场取证时,自管环境往往更直接。
团队可以同时使用两种构建方式吗?
可以。常见做法是让托管构建承担合并检查,把需要稳定缓存、复杂签名或完整日志的归档任务放在自管云端 Mac。
选型前最重要的验证是什么?
用同一提交、锁文件和构建命令各跑多次,比较成功率、环境差异、缓存命中、日志完整度以及失败后的恢复时间。
需要一台由单一订单独享的物理 Mac mini?
核对 M4 与 M4 Pro 两档配置、四种租用周期和 5 个可选节点,再按工作流选择设备。