一次图标槽位遗漏,往往要等到完整归档接近结束才暴露。对云端 Mac 上的 iOS 流水线来说,这意味着依赖解析、源码编译和测试已经消耗了时间,最终却因为 Assets.xcassets 内的一处元数据错误失败。更直接的做法,是把 Xcode 内部调用的 actool 拆成独立预检,让资源问题在流水线前段结束。
先界定 actool 预检能解决什么
actool 负责读取 Asset Catalog,并按平台、部署目标和设备类型编译资源。单独执行它,可以发现无效的 Contents.json、缺少文件的图片槽位、无法识别的属性、重复名称,以及 AppIcon 与目标平台不匹配等问题。
它不能替代完整构建。源码引用了不存在的资源名、不同 target 使用不同 Asset Catalog、构建设置覆盖了 AppIcon 名称等问题,仍可能只在 xcodebuild 阶段出现。合理的流水线顺序是:
| 阶段 | 检查内容 | 失败成本 |
|---|---|---|
| JSON 语法检查 | Contents.json 是否可解析 |
最低 |
| actool 预检 | 资源结构与平台参数 | 较低 |
| Xcode 构建 | target、源码引用与链接 | 较高 |
| 测试与归档 | 运行行为及交付产物 | 最高 |
预检的目标不是模拟整个 Xcode,而是尽早拦截结果确定、修复路径明确的资源错误。
固定工具链与输入参数
先确认当前任务实际调用的 Xcode。不要假设交互式终端与 CI 进程使用相同的开发者目录。
xcode-select -p
xcodebuild -version
xcrun --find actool
xcrun actool --version
若团队在节点上保留多个 Xcode 版本,应在任务入口明确设置 DEVELOPER_DIR,并把 xcodebuild -version 写入构建日志。脚本中的平台、最低系统版本、设备类型和 AppIcon 名称,也要与项目构建设置保持一致。否则预检通过只代表另一组参数能够编译,不能证明真实 target 可用。
在执行 actool 前,再对目录中的 JSON 做一次轻量检查:
find App/Assets.xcassets -name Contents.json -print0 |
while IFS= read -r -d '' file; do
plutil -lint "$file"
done
这一步能把合并冲突残留、尾部多余字符和损坏文件准确定位到具体路径。
编写可在 CI 直接执行的脚本
下面的脚本把输出固定在 .ci-artifacts/actool,失败时保留完整诊断,便于 CI 收集为附件。目录、部署目标和 AppIcon 名称应通过参数或项目配置传入,不要复制多份脚本分别维护。
#!/bin/bash
set -euo pipefail
CATALOG="${1:-App/Assets.xcassets}"
DEPLOYMENT_TARGET="${DEPLOYMENT_TARGET:-16.0}"
APP_ICON_NAME="${APP_ICON_NAME:-AppIcon}"
OUT=".ci-artifacts/actool"
rm -rf "$OUT"
mkdir -p "$OUT/compiled"
export LANG=en_US.UTF-8
if ! xcrun actool "$CATALOG" \
--compile "$OUT/compiled" \
--platform iphoneos \
--minimum-deployment-target "$DEPLOYMENT_TARGET" \
--target-device iphone \
--app-icon "$APP_ICON_NAME" \
--output-partial-info-plist "$OUT/partial-info.plist" \
--notices --warnings --errors \
>"$OUT/actool.log" 2>&1; then
cat "$OUT/actool.log"
exit 1
fi
cat "$OUT/actool.log"
plutil -lint "$OUT/partial-info.plist"
使用 set -euo pipefail 可以避免命令失败后流水线继续运行。日志先写文件再输出,是为了同时保留退出码和诊断内容;直接把命令接到复杂管道后,容易误取最后一个命令的状态。
处理警告与常见误判
不要立即把所有警告升级为失败
旧工程可能积累未分配设备外观、未使用图片集或命名提示。第一轮接入时应保存日志并建立基线,再挑选确定性问题阻断。直接对所有包含 warning: 的行返回失败,通常会让团队先关闭检查,而不是修复资源。
建议立即阻断无效 JSON、资源文件缺失、AppIcon 必要槽位缺失和重复名称;对未使用资源等提示先记录数量,设定清理期限后再收紧规则。
区分模拟器与真机平台
只用 iphonesimulator 预检,不代表 iphoneos 一定通过。两者可能在设备要求、图标规则和产物形式上产生不同诊断。若流水线同时构建两类目标,应分别执行两次,并使用独立输出目录,避免后一次覆盖前一次结果。
包含 iPad 目标时,还要增加对应的 --target-device ipad。不要为了让检查通过而只声明 iphone,设备集合必须来自 target 的真实配置。
接入流水线并验收结果
把预检放在依赖安装之后没有必要,因为 actool 通常只依赖 Xcode 和仓库内资源。更合适的位置是代码检出完成后、依赖解析前。这样即使包管理服务暂时缓慢,资源错误仍能快速反馈。
首次上线可准备三个故障样本:破坏一份 Contents.json、临时移走一个被引用的图标文件、把 AppIcon 名称改成不存在的值。确认任务均以非零状态结束,日志包含具体目录,而且 .ci-artifacts/actool 被系统保存。随后恢复文件,再验证预检与完整构建都通过。
如果仓库包含多个应用 target,应维护“target、资源目录、平台、AppIcon 名称”的清单并逐项循环,而不是把所有目录一次性交给同一条命令。这样失败信息能直接对应负责模块,也不会因某个 target 的参数掩盖另一个 target 的问题。
最终保留两层门禁:actool 负责快速发现资源目录的确定性错误,完整 Xcode 构建负责验证 target 集成与最终产物。两者职责清晰后,资源问题会更早暴露,失败日志也更接近实际修复位置。
常见问题
actool 预检能替代完整的 Xcode 构建吗?
不能。它适合提前发现资源目录结构、AppIcon 和编译参数问题,最终仍需执行与发布配置一致的 Xcode 构建。
actool 警告是否应该全部导致 CI 失败?
不建议一开始全部阻断。先保存并分类现有警告,再只对缺失图标、无效 JSON、重复名称等确定性问题设置失败门槛。
把下一次构建放到独享 Apple Silicon 物理节点
按任务规模选择三档配置与五个节点,计算和存储不与其他租户共享。所有节点全年 365 天正常运行,实际可用状态以控制台实时返回为准。