一台长期承担归档和自动化测试的云端 Mac,最常见的故障未必来自代码。Xcode 会持续写入 DerivedData、归档、设备支持文件和模拟器数据;磁盘接近写满时,编译器可能报出无关紧要的写入错误,签名步骤也可能只留下不完整产物。可靠的处理方式不是定期清空所有目录,而是先量化占用、设置构建门禁,再按可恢复程度逐层清理。
先建立磁盘占用基线
先记录系统可用空间,再检查开发目录。df 回答文件系统还能写多少,du 用来定位是谁占用了空间。两者不能互相替代。
df -Pk /
for path in \
"$HOME/Library/Developer/Xcode/DerivedData" \
"$HOME/Library/Developer/Xcode/Archives" \
"$HOME/Library/Developer/CoreSimulator" \
"$HOME/Library/Developer/Xcode/iOS DeviceSupport"
do
if [ -e "$path" ]; then
du -sk "$path"
fi
done
建议在一次干净构建前、归档完成后和测试结束后各采样一次。三个数字能说明单次任务的增长量,也能避免把正常峰值误判为空间泄漏。若要继续定位 DerivedData 内的大目录,可执行:
du -sk "$HOME/Library/Developer/Xcode/DerivedData"/* 2>/dev/null \
| sort -nr \
| head -20
APFS 显示的可用空间可能受到可回收数据影响。构建门禁应读取
df的可用块,不要仅凭目录标称大小判断任务一定能完成。
给构建设置明确的容量门禁
容量阈值应来自实际峰值,而不是凭经验写死。先以 30GB 作为起点,记录完整归档过程中最低剩余空间;稳定运行数次后,把阈值调整为峰值增量的 1.5 倍,并额外覆盖导出产物。
下面的脚本可放在流水线最前面。空间不足时以状态码 75 退出,让调度器把任务标记为暂不可执行,而不是启动一个大概率失败的构建。
#!/bin/zsh
set -euo pipefail
minimum_kb=$((30 * 1024 * 1024))
available_kb=$(df -Pk / | awk 'NR == 2 {print $4}')
if (( available_kb < minimum_kb )); then
printf 'Insufficient disk capacity: %s KB available
' "$available_kb"
exit 75
fi
printf 'Disk capacity check passed: %s KB available
' "$available_kb"
阈值应按节点分别保存。不同 Xcode 版本、模拟器组合和项目规模会产生不同峰值,不应让小项目的结果替大型工作区背书。
把每次构建限制在独立目录
共享默认 DerivedData 的问题不只是容量,还包括无法判断目录属于哪个任务。为工作区指定独立路径,任务结束后就能精准删除,而不影响其他正在运行的构建。
job_root="$HOME/build-jobs/$BUILD_ID"
derived_data="$job_root/DerivedData"
archive_path="$job_root/artifacts/App.xcarchive"
mkdir -p "$job_root/artifacts"
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-derivedDataPath "$derived_data" \
-archivePath "$archive_path" \
archive
BUILD_ID 应由任务系统提供,并限制为字母、数字、点、下划线或连字符。清理前还要验证目标路径位于 build-jobs 之下,避免空变量把删除范围扩大到主目录。
按可恢复程度分层清理
清理顺序应从可重新生成的数据开始,最后才处理需要人工确认的归档。
| 层级 | 数据 | 建议动作 | 主要边界 |
|---|---|---|---|
| 1 | 已结束任务的 DerivedData | 按任务目录删除 | 确认没有构建进程占用 |
| 2 | 不可用的模拟器记录 | 使用 simctl 清理 |
不直接删除 Devices 目录 |
| 3 | 旧设备支持文件 | 按实际测试版本复核 | 保留仍需调试的系统版本 |
| 4 | xcarchive 与 dSYM | 人工核对后迁出 | 已发布版本必须可追溯 |
删除失效模拟器应交给系统工具处理:
xcrun simctl delete unavailable
不要直接清空 CoreSimulator/Devices。文件目录与模拟器服务状态不一致时,后续创建和启动设备会出现更难定位的问题。归档目录也不适合按“超过若干天”直接删除,因为 xcarchive 内可能保留发布版本对应的 dSYM。正确做法是先登记版本、构建号和迁出位置,再移除本地副本。
自动化清理时避开三个坑
第一,不要在构建进行中清理共享目录。即使某个文件看起来很旧,编译器仍可能通过索引或中间产物引用它。清理任务应取得节点级锁,或者只操作已经标记完成的独立工作目录。
第二,不要把“清理成功”等同于“空间已经释放”。删除后应再次执行 df -Pk /,确认可用块确实增长;如果没有变化,应继续检查仍被进程打开的文件,而不是重复执行删除命令。
第三,不要让脚本自行判断归档是否重要。无人值守任务只负责列出候选项,例如生成目录大小、最后修改时间和关联的构建编号;是否迁出或删除,应由发布记录决定。
用一次可重复构建完成验收
清理完成后,至少执行一次与正式任务相同的归档。验收时记录开始容量、最低容量、结束容量、归档路径和退出状态,并检查 .xcarchive 是否完整生成。若节点还承担模拟器测试,再启动测试矩阵中每个保留版本各一次,确认设备能正常创建、启动和关闭。
最终检查项可以保持简短:构建前门禁已通过;任务使用独立 DerivedData;退出后没有遗留编译进程;归档和 dSYM 已登记;simctl list 中没有异常设备;磁盘剩余量高于下一次任务阈值。只有这些条件同时满足,清理才算闭环。
常见问题
Xcode 构建节点至少应预留多少磁盘空间?
没有适用于所有项目的固定值。可先把 30GB 设为构建前最低阈值,再根据一次完整归档的峰值占用调整;大型多模块项目应预留最近峰值的至少 1.5 倍。
可以直接删除整个 DerivedData 目录吗?
可以,但必须确认没有构建进程正在使用它。更稳妥的做法是为每个工作区指定独立 DerivedDataPath,只删除已经结束的任务目录。
哪些 Xcode 文件不应自动删除?
未完成归档、尚未备份的 xcarchive、对应发布版本的 dSYM,以及仍被测试矩阵使用的模拟器设备都不应进入无人值守清理。
把可复现环境保留在同一台云端 Mac
选择固定机型、区域和租期,使用完整 macOS 图形界面与命令行完成开发或自动化任务。