Xcode 26 build database locked:2026 远程构建怎么修?

Xcode 26 官方要求运行在 macOS Sequoia 15.6 或更高版本上;如果远程 Mac 已满足系统条件,却出现 build database locked,优先怀疑构建任务或路径冲突,而不是先重装 Xcode。(developer.apple.com)

症状:两个 Job 几乎同时执行 xcodebuild,日志末尾出现 build database locked,但最后一行不足以说明真正的占用者。
最快解法:先停止共享同一构建目录的并发任务,再为每个 Job 分配独立的 DerivedData;确认没有残留进程后仍失败,才清理受影响项目的构建数据库,并用单任务 Archive 复验。

这篇文章适合通过 SSH 在远程 Mac 上运行 xcodebuild、断线或重试后遇到数据库锁定的独立开发者,也适合允许多个 Job 同时构建同一项目的小型团队。
如果你负责一台 Mac 上的 Build、Test、Archive 调度,下面的重点是建立证据链,而不是收集一串“万能清缓存”命令。

先把失败现场保存下来:不要只看最后一行

假设脱敏后的工作目录为 /Users/runner/work/App,两个任务分别在 10:14:0210:14:05 启动。你需要保留完整命令、当前目录、Scheme、工作区文件、DerivedData 路径,以及第一个有效错误,而不是只复制:

error: build database is locked

建议把现场整理成下面这种记录:

Job A
start: 10:14:02
cwd: /Users/runner/work/App
command: xcodebuild -workspace Redacted.xcworkspace \
  -scheme RedactedApp \
  -configuration Release build \
  -derivedDataPath /Users/runner/build/shared-derived

Job B
start: 10:14:05
cwd: /Users/runner/work/App
command: xcodebuild -workspace Redacted.xcworkspace \
  -scheme RedactedApp \
  -configuration Release test \
  -derivedDataPath /Users/runner/build/shared-derived

同时标明是哪个进程发起了 Build、Test、Archive,是否由 Run Script、发布脚本或依赖项目再次调用 xcodebuild。Xcode 的构建系统会根据 Scheme、Target Dependencies 和构建设置生成任务图,并在依赖允许时并行执行;因此“看到并行”本身不是错误,真正危险的是多个独立 Job 共享同一个可写目录。(developer.apple.com)

⚠️ 不要一开始执行 rm -rf ~/Library/Developer/Xcode/DerivedData/*。这会抹掉其他项目的诊断线索,也可能让你无法判断问题究竟来自并发、路径还是异常中断。

第一步:确认是不是两个进程正在争用同一数据库

在远程 Mac 上重新登录后,先列出相关进程:

pgrep -alf 'xcodebuild|xcbuild|xctest|swiftc'

再查看进程启动时间、父进程和完整参数:

ps -axo pid,ppid,lstart,command | \
  grep -E 'xcodebuild|xcbuild|xctest' | grep -v grep

你要找的不是“有没有 Xcode 进程”,而是以下关系:

  • 同一项目、同一 Scheme,两个 Job 同时运行;
  • 一个旧的 xcodebuild 仍由 SSH 会话、Runner 或后台脚本托管;
  • CI 重试已启动新任务,但旧任务没有收到取消信号;
  • 一个 Archive 正在写入,另一个 Test 或 Build 使用了相同的中间目录;
  • 不同系统用户分别启动了任务,却把输出指向同一路径。

不要直接使用会终止所有 Xcode 相关任务的命令。先根据 PPID 和启动时间判断哪个 Job 应保留,再让无效任务正常退出;只有在明确确认进程已失去控制、且不会再写入 xcresultxcarchive 或上传目录时,才按父子关系终止。

终止顺序也有实际影响:正在运行的 Test 可能只留下不完整的 .xcresult,Archive 可能没有完成 Info.plist 或 dSYM 写入,上传脚本则可能已经拿到一个不完整文件。保留任务应继续观察日志,失效任务先尝试发送正常终止信号,等待其子进程退出后再处理目录。

第二步:核对 DerivedData、OBJROOT、SYMROOT 是否真的分开

很多 CI 配置表面上使用了不同的 Job 名称,实际却解析到相同路径。-derivedDataPath 只解决显式传入的 DerivedData;项目或脚本仍可能通过 OBJROOTSYMROOTCONFIGURATION_BUILD_DIR 或自定义变量写入共享位置。Apple 的 Build Settings 文档将 OBJROOT 定义为中间文件路径,并将 SYMROOTCONFIGURATION_BUILD_DIR 用于构建产物位置。(developer.apple.com)

先让 xcodebuild 输出最终解析后的设置:

xcodebuild \
  -workspace Redacted.xcworkspace \
  -scheme RedactedApp \
  -showBuildSettings \
  | egrep 'DERIVED|OBJROOT|SYMROOT|CONFIGURATION_BUILD_DIR|ARCHIVE_PATH'

如果使用 .xcconfig、Shell 变量或 CI 模板,还要检查:

env | egrep 'BUILD|DERIVED|OBJROOT|SYMROOT|ARCHIVE|RESULT'
grep -R --line-number -E 'derivedDataPath|OBJROOT|SYMROOT|archivePath|resultBundlePath' .

每个 Job 应至少具备以下隔离:

JOB_ROOT="/Users/runner/build/${CI_JOB_ID}"
DERIVED="$JOB_ROOT/DerivedData"
RESULT="$JOB_ROOT/Results/App.xcresult"
ARCHIVE="$JOB_ROOT/Archives/App.xcarchive"

mkdir -p "$JOB_ROOT" "$DERIVED" "$JOB_ROOT/Results" "$JOB_ROOT/Archives"

xcodebuild \
  -workspace Redacted.xcworkspace \
  -scheme RedactedApp \
  -configuration Release \
  build \
  -derivedDataPath "$DERIVED" \
  -resultBundlePath "$RESULT"

源码工作区可以只读共享,最终 Artifacts 也可以由流水线统一收集,但中间产物不应共享。DerivedData、对象文件、模块缓存和构建数据库属于可重新生成内容;xcresultxcarchive、dSYM、签名后的导出包和上传日志则应按 Job 单独保存。

第三步:排除 Run Script 或依赖项目触发的嵌套构建

如果进程列表只显示一个主 xcodebuild,仍可能存在嵌套构建。常见来源包括:

  • Run Script 内部再次执行 xcodebuild build
  • 发布脚本为了生成另一个产品,复用了主 Job 的 DerivedData;
  • 跨项目依赖没有被声明为 Target Dependency,而是由脚本手工触发;
  • 包构建工具或自定义生成器启动了第二个构建命令;
  • 脚本没有声明输入、输出,导致 Xcode 每次都运行并重复写入文件。

Apple 要求 Run Script 提供输入和输出文件信息,以便构建系统安排任务顺序;缺少这些声明时,脚本可能每次构建都执行,并引入不正确的构建行为。(developer.apple.com)

你可以在项目文件和脚本中搜索:

grep -R --line-number -E 'xcodebuild|xcrun|archivePath|derivedDataPath' \
  --exclude-dir=.git .

修改前先写清楚三个边界:

  1. xcodebuild 负责哪些 Target 和产品;
  2. 脚本只能读取哪些输入,生成哪些输出;
  3. 如果子构建失败,主任务如何回退、日志保存在哪里。

不要把“关闭并行构建”作为默认修复。Scheme 的 Dependency Order 本身支持按照依赖关系并行构建,Manual Order 反而会让任务串行化并降低资源利用率。(developer.apple.com) 只有当日志证明项目的依赖声明错误、脚本输出无法准确描述,且短期必须降低风险时,才可临时限制并行;长期仍应修正调用边界。

没有活跃进程后,怎样逐级清理残留状态?

只有在确认相关 xcodebuild、测试进程和嵌套脚本均已退出后,才进入清理阶段。可以用下面的检查确认目标路径是否仍被打开:

lsof +D "/Users/runner/build/失效Job/DerivedData" 2>/dev/null

清理范围应从小到大:

  1. 只删除失效 Job 的临时 DerivedData;
  2. 仍失败时,删除该项目专属的构建数据库或项目构建目录;
  3. 重新创建 Job 工作区,并重复同一条 xcodebuild 命令;
  4. 最后才考虑用户级缓存,而且必须先保存诊断资料。

保留这些内容:

  • 源码和提交版本;
  • 证书、Provisioning Profile 等签名资产;
  • 已完成的 xcarchive
  • dSYM;
  • .xcresult
  • 完整标准输出、错误输出和进程时间线。

删除 DerivedData 不等于删除已经独立保存的 Archive 或签名资产,但如果你的 Archive 仍放在 DerivedData 下面,或者脚本用临时目录保存 dSYM,清理就可能带来不可逆损失。Archive 是包含调试信息的构建包,Apple 的分发流程要求先创建 Archive,再使用 xcodebuild exportArchive 导出分发包。(developer.apple.com)

独立构建路径的 CI 写法,应该通过什么标准?

不要只验证“命令返回成功”。两个并行 Job 应分别生成自己的结果包,并且在删除其中一个 Job 的临时目录后,另一个 Job 的结果仍可读取。

测试阶段可以显式输出独立的 .xcresult

xcodebuild \
  -workspace Redacted.xcworkspace \
  -scheme RedactedApp \
  test \
  -derivedDataPath "$DERIVED" \
  -resultBundlePath "$RESULT"

Apple 文档说明,xcodebuild test 会生成包含测试会话、覆盖率和日志的 .xcresult;将它放在 Job 专属目录,才能在并行和失败重试时追踪真实结果。(developer.apple.com)

Archive 阶段则使用独立的归档路径:

xcodebuild \
  -workspace Redacted.xcworkspace \
  -scheme RedactedApp \
  -configuration Release \
  -destination "generic/platform=iOS" \
  archive \
  -derivedDataPath "$DERIVED" \
  -archivePath "$ARCHIVE"

验证 Archive 时,不要把“生成了目录”当成通过。至少检查归档结构、产品文件、签名信息、dSYM 和日志是否完整,再进入导出或上传。对于持续集成,还应固定 Package.resolved,并按 Apple 的 CI 建议考虑使用 -disableAutomaticPackageResolution,避免构建过程中出现与数据库锁定无关的依赖变化。(developer.apple.com)

独立路径与共享路径,哪种方案适合你的远程 Mac?

决策维度 多个 Job 共用路径 每个 Job 独立路径
DerivedData ❌ 容易互相写入构建数据库和中间文件 ✅ 通过 Job ID 隔离
并行 Build / Test ❌ 锁冲突和结果覆盖风险高 ✅ 可并发,问题更容易归属
磁盘占用 ✅ 可能较低,但依赖共享缓存 ⚠️ 需要为每个任务预留临时空间
失败重试 ❌ 旧状态容易污染新任务 ✅ 可删除单个 Job 后重跑
Archive 与 dSYM ⚠️ 容易被脚本覆盖或误清理 ✅ 可按版本和 Job 独立留存
适用场景 单任务、严格串行且有锁机制 CI 并行、SSH 断线恢复、多人共用主机

如果你只有一台远程 Mac,路径隔离仍然是最低要求;但它不能替代进程控制。主机需要能识别取消的 Job、结束孤儿进程,并在重启后重新创建目录。Apple 对构建设置的解析还存在层级继承,命令行传入的设置优先级最高,因此排查时应以 xcodebuild -showBuildSettings 的最终值为准,而不是只看 Xcode 图形界面的某个字段。(developer.apple.com)

验收阶段 执行动作 通过标准 未通过时的动作
单任务 Build 使用独立 -derivedDataPath 执行 Build 无锁错误,日志完整 检查路径解析和残留进程
并行 Test 启动两个不同 Job,各自输出 .xcresult 两个结果包均可打开且不互相覆盖 检查共享的 OBJROOT、脚本和测试目录
真实 Archive 只保留一个 Archive 任务 xcarchive、dSYM 和签名信息完整 回退到项目专属清理,不清全局缓存
SSH 断线重连 断开会话后重新登录并检查进程 Job 状态可判断,日志仍持续保存 增加进程托管和取消逻辑
主机重启恢复 重启后重新创建工作目录并重跑 新 Job 不读取失效 Job 的中间目录 拆分 Runner 或更换独立构建环境

FAQ:几个容易误判的锁定场景

Xcode 26 build database locked 通常是哪里发生冲突?

最常见的排查方向不是缓存本身,而是两个 xcodebuild 进程同时写入同一个 DerivedDataOBJROOT 或 Build Products 目录。SSH 断线后旧任务仍在后台运行、CI 重试未取消,以及脚本再次启动构建,也会造成相同现象。

两个 xcodebuild 任务能不能共用同一个 DerivedData?

不建议把并行 Job 的 DerivedData 设为同一路径。即使源码和 Scheme 相同,两个任务仍可能同时更新构建数据库、中间文件和模块缓存。正确做法是按 Job ID 分配独立的 -derivedDataPath,同时隔离 xcresultxcarchive

SSH 断线后怎样确认旧的 Xcode 构建还在运行?

重新登录远程 Mac 后,先用 pgreppslsof 查看 xcodebuild、测试进程及其父子关系,不要直接执行会杀掉所有 Xcode 任务的命令。确认旧任务属于已取消或已失效的 Job 后,再按进程树逐级终止。

CI 并行构建怎样设置独立的 derivedDataPath?

在流水线变量中生成每个 Job 唯一的工作目录,例如 $RUNNER_TEMP/ios-build/$CI_JOB_ID,并在每次 Build、Test、Archive 命令中显式传入 -derivedDataPath。Archive、xcresult 和日志也应使用独立文件名,避免只隔离中间目录。

删除 DerivedData 会不会影响 Archive 和签名文件?

删除受影响 Job 的 DerivedData 通常不会直接删除源码、证书、Provisioning Profile 或已经保存到其他位置的 xcarchive,但它会移除中间产物。清理前必须确认签名资产、Archive、dSYM 和 xcresult 已独立留存,并用同一命令重新验证。

结论:把锁错误当成调度问题处理

Xcode 26 build database locked 的可靠修复顺序是:保存完整失败现场,确认并发进程,核对最终解析路径,排除隐藏嵌套构建,确认没有残留任务后再局部清理,最后依次通过单任务 Build、并行 Test 和真实 Archive。

如果你需要继续完善远程 Mac 的持续构建流程,可以先参考 远程 Mac 使用场景,再结合 Xcode 安装与远程环境排查文章检查系统版本、权限和开发工具链。对于需要长期运行 SSH 构建、断线恢复和独立工作目录的团队,也可以查看 KVMFLUX 的 Mac 方案与套餐

你现有环境如果能完成“并行 Job、SSH 断线重连、主机重启恢复”三项测试,就不必因为一次锁错误立即更换方案;但如果它无法提供独立工作目录、完整进程控制或稳定常驻运行,继续把所有 Build、Test 和 Archive 挤在同一台临时主机上,维护成本会持续转化为失败重试和人工清理。此时,使用 KVMFLUX 的远程 Mac 作为专用构建环境,至少能把主机独占、持续在线和路径隔离纳入部署设计;是否租用,仍应以你的并发量、长期运行时间和是否需要物理设备接口来决定。

用 KVMFLUX 打造稳定的远程构建节点

租用专属 Mac mini M4,避免共享环境中的并发任务、残留进程和构建目录相互锁定。 通过 SSH 或 VNC 连接独享物理设备,按需运行 Build、Test 与 Archive,减少远程构建排障时间。 按日、周、月或季灵活租用,最低 $19.3/天,不必为偶发构建任务提前采购硬件。 选择距离团队更近的节点,付款后几分钟内获取连接凭证,尽快恢复稳定交付。

Mac Mini M4 · 16GB / 256GB
按天$19.3 /天
按周$52.2 /周
按月$96.7 /月
按季$263 /季