工程文章

云端 Mac 用 actool 预检 iOS 资源目录

云端 Mac 用 actool 预检 iOS 资源目录

一次图标槽位遗漏,往往要等到完整归档接近结束才暴露。对云端 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、重复名称等确定性问题设置失败门槛。

OwnAMac 云端 Mac

把下一次构建放到独享 Apple Silicon 物理节点

按任务规模选择三档配置与五个节点,计算和存储不与其他租户共享。所有节点全年 365 天正常运行,实际可用状态以控制台实时返回为准。

选择配置并订购