云端 Mac 的 Xcode 磁盘容量监控与安全清理

CI/CD 实践 ·约 7 分钟阅读

云端 Mac 的 Xcode 磁盘容量监控与安全清理

一台长期承担归档和自动化测试的云端 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 图形界面与命令行完成开发或自动化任务。

立即租用云端 Mac