构建日志突然出现 No space left on device,但节点上到底是项目产物、DerivedData、Simulator 还是归档在持续写盘,暂时不能靠猜。
最快解法:先停止继续写盘并保留现场,再按工作区、DerivedData、Simulator runtime、归档和包管理缓存逐层确认;只删除已停止任务产生、可重新生成且没有并行任务使用的数据。
这篇内容适合三类人:正在处理 Xcode 构建中断的值班开发者,需要低风险恢复顺序;维护共享远程 Mac Runner 的 DevOps 工程师,需要区分缓存、模拟器数据和发布资产;负责节点容量与交付验收的平台工程师,需要判断清理、重建还是扩容。
值班开发者:先冻结写盘现场
Xcode No space left on device 不等于“删除几个缓存就能恢复”。如果构建、测试、归档或上传任务仍在运行,继续清理可能造成目录被重新创建、文件写入失败边界变化,甚至破坏正在生成的发布资产。
先暂停流水线调度,禁止同一节点接收新任务;如果远程 Mac 上还有交互式 Xcode、测试进程或上传进程,也要先记录进程归属和任务编号。Apple 的命令行工具文档明确说明,xcodebuild、simctl 等工具依赖当前选中的 Xcode 开发目录,因此排查前还要确认执行环境没有切换到另一套 Xcode。可参考 Apple 的 Xcode 命令行工具说明。
使用实际执行账户登录后,先保存基础证据:
whoami
xcode-select -p
df -h /
df -i /
du -xhd 1 "$HOME" 2>/dev/null | sort -h
ps aux | grep -E 'xcodebuild|simctl|Simulator|swift|clang' | grep -v grep
这里同时检查可用空间和 inode,是因为“空间不足”与“文件节点耗尽”可能表现为相同的写盘错误;目录统计则用于判断异常增长发生在用户目录、临时目录还是共享工作区。路径中的 <WORKSPACE>、<PROJECT>、<RUN_ID> 和 <ACCOUNT> 必须替换为现场值,不能直接复制成生产命令。
记录第一条有效错误、失败任务的提交号、scheme、目的地、构建参数,以及清理前的 df 和目录快照。不要一开始删除整个开发目录、重建节点或执行来源不明的批量 rm 命令;归档、签名材料和诊断证据一旦被删,后续很难证明失败原因。
值班开发者:恢复单次构建工作区
第一责任是恢复一条可复测的构建链路,而不是把节点清到“看起来很空”。
先检查以下对象的所有权和状态:
<WORKSPACE>中未提交的修改、生成文件和测试报告;<RUN_ID>对应的临时构建目录;- 同一仓库的重复克隆、失败重试目录和旧任务产物;
- 当前任务仍在使用的输出目录;
- 需要交付给发布或测试负责人的
.xcarchive、导出包和日志。
出现磁盘不足时,第一批应当处理哪些内容?
优先处理已经停止的任务所产生、能够由同一提交和同一参数重新生成的临时产物;源代码、未提交改动、测试证据、发布归档和正在上传的文件不能按“缓存”处理。Apple 对 Mac 存储管理的建议也把“确认占用来源、移动或删除不再需要的文件”放在前面,而不是无差别清空系统目录,可参考 Apple Support 的 Mac 存储空间管理说明。
清理后必须用原提交和原构建参数复测:
xcodebuild \
-workspace "<WORKSPACE>.xcworkspace" \
-scheme "<SCHEME>" \
-destination 'generic/platform=iOS' \
-derivedDataPath "<NEW_DERIVED_DATA_PATH>" \
clean build
如果空间刚释放就再次下降,或者同一任务仍不断生成大型产物,应停止重复重试,把目录快照、进程列表和任务参数交给构建维护者。连续重跑只会扩大损坏范围,也会让“哪一个任务真正耗尽空间”变得更难判断。
构建维护者:区分 DerivedData 与依赖缓存
DerivedData 通常包含编译中间产物、索引、模块缓存和构建输出,但实际位置可能被 -derivedDataPath、执行账户或流水线参数改变。不能因为目录名称熟悉,就假定所有内容都可以直接删除。
在远程 Mac CI 中,DerivedData 何时适合清理?
只有在对应任务已经停止、目录没有被并行构建使用、其中没有需要保留的诊断或交付文件时,才可以删除指定项目的可再生产物。删除前先查当前任务参数和目录更新时间,再选择项目级清理;不要在共享 Runner 上直接删除整个用户级开发目录。
du -xhd 1 "<DERIVED_DATA_ROOT>" 2>/dev/null | sort -h
find "<DERIVED_DATA_ROOT>" -maxdepth 2 -type d -name 'Build' -print
lsof +D "<TARGET_DERIVED_DATA>" 2>/dev/null | head -50
依赖缓存要单独判断。Swift Package、Homebrew 和其他工具缓存可能被多个项目复用,也可能因版本变化而失效;删除后,下一次构建可能重新解析依赖、重新下载资源或重新编译模块。复核时至少分成两次:
- 清理已确认无任务使用的项目级缓存;
- 用原提交执行一次干净构建;
- 再执行一次相同参数的复用构建;
- 对比两次的输出完整性、依赖解析状态和构建日志;
- 确认流水线不再因空间、权限或缺失依赖失败。
Apple 文档说明,xcodebuild 可用于自动化构建和持续集成;命令行工具的活动开发目录必须与流水线预期一致,可参考 Apple 的 Command-line tools 文档。
⚠️ 不要把社区流传的“全盘清缓存”命令当成远程 Mac CI 的通用修复方案。它们往往没有处理并行任务、账户隔离、签名资产和回滚入口,清理成功也不代表流水线恢复。
测试负责人:处理 iOS Simulator 占用
Simulator 占用至少要拆成两类:已安装的 Simulator runtime,以及基于 runtime 创建的模拟设备实例与测试生成数据。前者影响可测试的平台版本,后者可能包含设备状态、应用数据、日志和测试残留;两者不能使用同一条删除规则。
iOS Simulator 占用过大时,怎样降低误删风险?
先列出当前安装的 runtime、设备状态和测试矩阵,再移除明确不再使用的版本或已废弃的设备实例。优先使用 Xcode 的 Components、Device Hub 或受支持的 simctl 操作,不要直接修改受系统保护的组件目录。Apple 的 Xcode 组件管理文档说明,Xcode 可以在设置中的 Components 页面查看已安装组件,并显示移除组件后可回收的空间。
xcrun simctl list runtimes
xcrun simctl list devices
xcrun simctl list devices unavailable
xcrun simctl help
清理前要确认:
- 当前测试矩阵是否仍要求目标 runtime;
- 是否有并行测试正在使用目标设备;
- 设备中的测试数据是否属于失败诊断证据;
- 删除后能否重新创建同版本、同架构的测试设备;
- 远程连接断开后,Simulator 是否仍能被流水线正确启动。
Apple 也提醒,Simulator 不能完全复现真实设备的性能和硬件特性,因此清理后的验收不能只看模拟器能否启动,还要重新执行目标测试,并在需要时安排实体设备验证,可参考 Apple 的模拟设备运行说明 和 Device Hub 管理文档。
发布负责人:保护归档和签名资产
归档属于交付证据,不能套用 DerivedData 的自动清理策略。发布负责人应先盘点 .xcarchive、导出包、dSYM、上传中间产物和发布日志,再依据发布状态与团队留存要求决定迁移、保留或删除。
Apple 将 archive 定义为包含应用构建结果和调试信息的 bundle;归档后还可以在 Organizer 中进行验证和分发,可参考 Apple 的应用归档与分发流程。对于已经分发的构建,Apple 明确要求保留对应的 Xcode archive,并保留与构建 UUID 匹配的 dSYM,否则后续崩溃分析可能缺少符号信息,详见 Apple 的调试信息与归档保留说明。
发布资产检查可以使用:
find "<ARCHIVE_ROOT>" -type d -name '*.xcarchive' -print
find "<EXPORT_ROOT>" -type f \( -name '*.ipa' -o -name '*.app' -o -name '*.dSYM' \) -print
以下内容通常必须保留或先迁移:
- 已上传但尚未完成处理的归档;
- 与已发布版本对应的
.xcarchive和dSYM; - 尚未交付给测试、审计或客户的导出包;
- 正在使用的钥匙串、证书、描述文件及其访问权限。
钥匙串、签名证书和描述文件不是释放空间的对象。清理后要重新执行归档、导出或上传链路,而不是只验证 xcodebuild 返回成功;发布负责人还应确认导出包可被下游系统接收,签名身份和配置没有被误删。
平台负责人:清理、隔离还是扩容
当单次构建已经恢复,平台负责人要回答的不是“还能删什么”,而是“正常工作负载为什么会超过节点的可用容量”。
可以用下面的验收清单建立交接记录:
- [ ] 已暂停新任务,并记录首个有效
No space left on device错误; - [ ] 已保存
df -h、df -i、目录占用、进程和流水线参数; - [ ] 已区分源代码、可再生产物、测试数据、归档和签名资产;
- [ ] 删除前已确认目标目录没有并行任务使用;
- [ ] 已用同一提交和同一构建参数完成干净构建;
- [ ] 已完成第二次复用构建,确认缓存逻辑没有被清理破坏;
- [ ] 已验证目标 Simulator runtime、设备启动和测试执行;
- [ ] 已验证归档、导出或上传链路;
- [ ] 已记录节点重启后的 Xcode 选择、SSH、Runner 服务和任务恢复状态;
- [ ] 已为工作区、缓存、Simulator 和归档分别指定所有者与保留规则。
怎样让共享 Mac Runner 不再反复写满?
把清理时机绑定到任务状态,而不是绑定到人工感觉:任务结束后处理临时工作区,构建失败后保留必要日志,发布完成后按留存策略迁移归档,节点重启前执行可观察的回收与验收。缓存要按项目、版本和复用价值管理,Simulator 要按测试矩阵保留,归档则要按发布记录保存。
如果正常的构建、测试和归档保留范围仍超过节点容量,反复执行全盘删除不是长期方案。更稳妥的选择通常是拆分开发与发布节点、把测试矩阵分配到不同节点、缩短闲置任务的保留周期,或更换具备更合适存储配置的远程 Mac;相关节点选择可以先参考 Mac mini 云算力方案说明 和 硅谷区域远程 Mac 节点方案,再用真实项目完成验收。
远程 Mac CI 的复测顺序
完成清理后,建议按照由低风险到高风险的顺序恢复:
- 确认 SSH 或网页控制台能够稳定登录,执行账户和
xcode-select -p正确; - 检查工作区和依赖状态,确认没有未交接的源文件或发布资产;
- 使用原提交执行一次干净构建;
- 重新启动目标 iOS Simulator,执行最小测试集;
- 执行完整测试矩阵,观察测试数据和日志是否正常写入;
- 创建新的 archive,检查导出包、符号文件和签名;
- 验证 Runner 服务、节点重启和下一次任务领取;
- 将清理对象、保留对象、失败边界和后续容量动作写入交接记录。
归档失败后,怎样判断继续清理还是调整节点容量?
如果失败原因是一次性残留、停止任务产物或明确失效的可再生产缓存,可以先按归属清理并复测;如果归档、Simulator 和正常缓存本身已经构成稳定的容量需求,清理只能延后下一次失败,应转向节点隔离、保留策略或扩容。判断依据应是复测后的真实增长来源,而不是某次手工删除后暂时出现的空闲空间。
当远程 Mac CI 仍反复耗尽容量时,当前方案的缺点通常已经很具体:共享节点会让不同项目争抢工作区和缓存,人工清理会增加误删归档的风险,单一节点还可能把开发、测试、发布故障绑在一起。此时,与其继续维护一套不可预测的临时环境,不如使用真实项目去验收 NodeMini 的远程 Mac 节点、存储余量、Simulator 测试、归档交付和重启恢复能力,再决定是拆分任务、调整租赁周期,还是选择更适合持续运行的配置。