工程记录

云端 Mac 上修复 Xcode 构建数据库损坏

云端 Mac 上修复 Xcode 构建数据库损坏

远程构建节点连续运行数天后,Xcode 偶尔会在项目代码没有变化时突然失败:日志出现 build.db 无法打开、磁盘映像格式错误或数据库损坏,而重新执行仍停在同一阶段。此时直接删除整个 DerivedData 虽然省事,却会同时丢掉索引、模块缓存和可复用产物,让后续构建变慢,也会抹掉最有价值的故障现场。

在 NUMACS 云端 Mac 上处理这类问题时,更稳妥的顺序是:固定工程入口,保全日志,确认没有构建进程占用,再检查数据库完整性,最后只重建目标工程的 XCBuildData。

先确认故障确实来自构建数据库

不要只凭 Xcode 界面的最后一行判断。先从相同目录、相同 scheme 和相同配置执行命令行构建,并把完整输出保存下来:

set -o pipefail
mkdir -p "$HOME/build-evidence"

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Debug \
  -destination 'generic/platform=iOS Simulator' \
  build 2>&1 | tee "$HOME/build-evidence/xcodebuild.log"

status=${PIPESTATUS[0]}
echo "$status" > "$HOME/build-evidence/exit-code.txt"
exit "$status"

重点搜索 build.dbdatabasemalformeddisk imageXCBuildData。如果日志明确指向源码编译错误、依赖解析失败或磁盘空间不足,应先处理对应问题,不能把所有失败都归因于数据库。

修复前至少保留完整日志、退出码和报错时间。清理动作一旦完成,原始数据库状态通常无法复现。

找到当前工程实际使用的 DerivedData

同一台云端 Mac 可能保留多个同名工程的 DerivedData。按目录修改时间猜测容易删错,应该让 xcodebuild 返回当前构建设置,再从 BUILD_DIR 推导工程根目录。

BUILD_DIR=$(
  xcodebuild \
    -workspace App.xcworkspace \
    -scheme App \
    -configuration Debug \
    -showBuildSettings |
  awk -F ' = ' '$1 ~ /^[[:space:]]*BUILD_DIR$/ {print $2; exit}'
)

DERIVED_ROOT=$(dirname "$(dirname "$BUILD_DIR")")
XCBD="$DERIVED_ROOT/Build/Intermediates.noindex/XCBuildData"

printf 'DerivedData: %s
XCBuildData: %s
' \
  "$DERIVED_ROOT" "$XCBD"
test -d "$XCBD"

若流水线使用了 -derivedDataPath,应直接复用该显式路径,不要再扫描用户目录。这样脚本在交互式会话和无人值守任务中会得到相同结果。

记录待处理文件

清理前记录数据库及相关文件的大小和修改时间:

find "$XCBD" -maxdepth 1 -type f \
  -exec stat -f '%Sm %z %N' -t '%Y-%m-%dT%H:%M:%S%z' {} \; \
  > "$HOME/build-evidence/xcbuilddata-files.txt"

这份清单能回答数据库是否在失败前刚被改写,也便于比较重复故障是否落在同一路径。

排除进程占用并只读检查 build.db

数据库被活跃构建占用时,不应直接删除。先检查相关进程和文件句柄:

pgrep -alf 'Xcode|xcodebuild|XCBBuildService' || true
lsof "$XCBD/build.db" || true

如果仍有属于当前任务的构建,应先通过流水线的正常取消机制停止,并等待子进程退出。不要对整台机器上的同名进程执行无差别终止,因为其他工作目录可能仍在运行有效任务。

确认无人占用后,以只读模式检查 SQLite 数据库:

sqlite3 "file:$XCBD/build.db?mode=ro" \
  'PRAGMA quick_check;'

结果为 ok 时,数据库结构未发现明显损坏,应回到日志继续检查权限、空间和工程配置。命令返回非零、无法读取,或输出完整性错误时,才进入重建步骤。

检查结果 下一步
lsof 显示活跃构建 正常停止任务并重新检查
quick_check 返回 ok 保留数据库,调查其他错误
无占用且完整性检查失败 重建目标 XCBuildData
路径不存在 核对 workspace、scheme 与 DerivedData 参数

最小化重建 XCBuildData

先把损坏目录移动到证据目录,而不是立即永久删除。移动应发生在同一文件系统内,以减少复制时间:

STAMP=$(date '+%Y%m%d-%H%M%S')
QUARANTINE="$HOME/build-evidence/XCBuildData-$STAMP"

mv "$XCBD" "$QUARANTINE"
mkdir -p "$(dirname "$XCBD")"

随后使用同一组构建参数重新执行。Xcode 会创建新的 XCBuildData 和 build.db,而 DerivedData 中其他目录仍被保留。

set -o pipefail

xcodebuild \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Debug \
  -destination 'generic/platform=iOS Simulator' \
  build 2>&1 | tee "$HOME/build-evidence/recovery-build.log"

如果最小重建仍失败,不要立刻扩大删除范围。先比较新旧日志,确认错误是否仍指向数据库。若错误已经转为依赖、权限或源码问题,说明数据库故障已解除,后续应按新错误处理。

用两次构建完成验收并预防复发

一次成功不足以证明状态稳定。第一次构建负责生成新数据库,第二次增量构建负责验证数据库能够再次读取和更新。两次都应固定 workspace、scheme、configuration、destination 和 DerivedData 路径,并分别保存日志与退出码。

验收时检查以下项目:

  1. 新的 build.db 已生成,且只读 quick_check 返回 ok
  2. 干净构建与增量构建都正常结束。
  3. 日志不再出现数据库格式或 XCBuildData 读取错误。
  4. 构建产物来自预期配置和目标目录。
  5. 失败现场在确认恢复前没有被删除。

预防重点不是定时清空缓存,而是让每个工作目录拥有明确的 DerivedData 路径,并确保任务取消时能等待构建子进程退出。执行节点还应持续检查可用磁盘空间,避免数据库写入过程中因空间耗尽留下不完整文件。

当同一路径反复损坏时,应记录触发前的异常退出、磁盘状态和 Xcode 版本,而不是把清理命令做成每次构建的固定前置步骤。缓存每次都被重置,会掩盖真正的问题,也失去增量构建价值。

最终可把流程收敛为四个动作:保存证据、确认路径、只读检查、最小重建。只有这些步骤仍无法恢复时,才考虑清理更大的 DerivedData 范围。这样既能缩短故障恢复时间,也能保留足够信息定位复发原因。

常见问题

build.db 报错时可以直接删除整个 DerivedData 吗?

不建议作为第一步。先停止相关构建并保留日志,再只删除目标工程的 XCBuildData;这样通常能重建构建数据库,同时保留其他可复用内容。

如何区分数据库损坏和仍有进程占用?

先用 lsof 检查 build.db 是否被 xcodebuild 或 Xcode 占用,再以只读方式执行 SQLite quick_check。存在活跃进程时先处理进程;无占用且检查返回错误时再按损坏处理。

怎样确认修复后不是偶然构建成功?

使用固定 workspace、scheme、configuration 和 DerivedData 路径连续执行一次干净构建与一次增量构建,并保存退出码、耗时和结果包作为验收证据。

NUMACS 云端 Mac

把构建任务放到独享物理工作站

两档 Apple Silicon 配置均为独享物理机、非虚拟机,可按天、周、月或季度租用,并覆盖新加坡、日本东京、韩国首尔与香港节点。

选择设备并下单