云端 Mac 上如何排查 Xcode 归档导出失败

云端 Mac 上如何排查 Xcode 归档导出失败

归档任务显示成功,流水线却在 -exportArchive 阶段退出,这类故障最容易被最后一行“export failed”误导。真正原因可能是导出方式与签名材料不匹配、归档内权限发生变化,或 CI 调用了另一套 Xcode。有效的处理方式不是反复重跑,而是保留同一份 xcarchive,把归档、导出和诊断拆成可单独复现的步骤。

先冻结失败现场

失败后不要立即清理工作目录,也不要覆盖原归档。至少保存 xcarchive、实际使用的 ExportOptions.plist、完整标准输出、Xcode 路径和版本。RunnerVM 上的独享物理节点适合保留这类现场,但流水线仍应主动把诊断文件复制到任务产物目录。

set -euo pipefail

RUN_DIR="$PWD/export-diagnostics"
ARCHIVE_PATH="$PWD/build/App.xcarchive"

mkdir -p "$RUN_DIR"
xcode-select -p > "$RUN_DIR/xcode-path.txt"
xcodebuild -version > "$RUN_DIR/xcode-version.txt"
sw_vers > "$RUN_DIR/macos-version.txt"
cp ExportOptions.plist "$RUN_DIR/ExportOptions.plist"
ditto "$ARCHIVE_PATH" "$RUN_DIR/App.xcarchive"

同时记录归档文件的校验值。后续若有人替换归档,就能立即发现调查对象已经变化。

ditto -c -k --keepParent "$ARCHIVE_PATH" "$RUN_DIR/App.xcarchive.zip"
shasum -a 256 "$RUN_DIR/App.xcarchive.zip" > "$RUN_DIR/checksums.txt"

导出失败不等于归档无效。先证明归档是否完整,再判断问题属于归档阶段还是分发阶段。

检查归档是否具备导出条件

xcarchive 本质上是目录。先确认元数据、应用包和可执行文件都存在,再读取归档声明的应用标识与版本。路径不要凭项目名拼接,应从 Info.plist 获取。

INFO="$ARCHIVE_PATH/Info.plist"
APP_RELATIVE=$(/usr/libexec/PlistBuddy -c \
  "Print :ApplicationProperties:ApplicationPath" "$INFO")
APP_PATH="$ARCHIVE_PATH/Products/$APP_RELATIVE"

plutil -lint "$INFO"
test -d "$APP_PATH"
test -f "$APP_PATH/Info.plist"

plutil -p "$INFO"
plutil -p "$APP_PATH/Info.plist"
codesign --verify --deep --strict --verbose=2 "$APP_PATH"

重点核对 ApplicationPathCFBundleIdentifier、版本号和签名校验结果。若应用包根本不存在,或 codesign --verify 已失败,问题应回到 archive 命令、构建阶段脚本或产物复制流程,而不是继续调整导出选项。

对比应用权限与描述文件

导出阶段会重新评估签名关系。可以分别导出应用实际权限和内嵌描述文件,再做结构化比较。

codesign -d --entitlements "$RUN_DIR/app-entitlements.plist" "$APP_PATH"

PROFILE="$APP_PATH/embedded.mobileprovision"
security cms -D -i "$PROFILE" > "$RUN_DIR/profile.plist"

plutil -extract Entitlements xml1 \
  -o "$RUN_DIR/profile-entitlements.plist" \
  "$RUN_DIR/profile.plist"

plutil -p "$RUN_DIR/app-entitlements.plist"
plutil -p "$RUN_DIR/profile-entitlements.plist"

不要只比较文件文本顺序。应关注应用使用的权限是否被描述文件允许,以及应用标识、团队标识和环境相关权限是否一致。扩展目标也要逐个检查,因为主应用通过并不代表所有嵌入组件都能导出。

验证 ExportOptions.plist

先执行 plutil -lint ExportOptions.plist 排除格式错误,再用当前 Xcode 的帮助输出确认可用键值。不同版本可能调整 method 的取值或弃用旧写法,因此不要从历史任务复制配置后直接运行。

一个最小配置可以只保留当前分发流程真正需要的字段:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>method</key>
  <string>development</string>
  <key>signingStyle</key>
  <string>manual</string>
  <key>stripSwiftSymbols</key>
  <true/>
</dict>
</plist>

这里的 method 只是开发导出的示例,不能直接套用到其他分发目标。先运行以下命令,以当前工具链给出的说明为准:

plutil -lint ExportOptions.plist
xcodebuild -help > export-help.txt
grep -A 120 "Available keys for -exportOptionsPlist" export-help.txt

如果使用手动签名,还应检查配置中的映射是否覆盖主应用和每个扩展。映射缺失、标识写错或签名方式混用,通常会在导出阶段才暴露。

单独重放导出并保留完整日志

归档确定有效后,用固定的绝对路径单独执行导出。不要让脚本在失败时立刻删除临时目录。

set +e

xcodebuild -exportArchive \
  -archivePath "$ARCHIVE_PATH" \
  -exportPath "$RUN_DIR/exported" \
  -exportOptionsPlist "$RUN_DIR/ExportOptions.plist" \
  2>&1 | tee "$RUN_DIR/export.log"

STATUS=${PIPESTATUS[0]}
set -e

printf '%s
' "$STATUS" > "$RUN_DIR/export-status.txt"
exit "$STATUS"

日志中应先找最早出现的具体错误,而不是末尾汇总。可按签名、描述文件、权限和导出方法进行初筛:

grep -Ein \
  "error:|provision|entitlement|certificate|signing|export" \
  "$RUN_DIR/export.log" > "$RUN_DIR/export-errors.txt" || true

xcodebuild 有时会在输出中给出分发日志包路径,应把该目录完整归档。它通常包含 IDEDistribution 流程各阶段的判断,比流水线截取的几行错误更接近根因。

建立修复后的验收清单

修复后不要只以退出码为准。至少确认导出目录存在目标文件、签名校验通过、应用标识和版本与归档一致,并将最终 ExportOptions.plist 与 Xcode 版本一起留档。

建议把以下检查固化为任务尾部清单:

  1. 归档压缩包校验值未变化。
  2. 当前 xcode-select -p 与预期工具链一致。
  3. 主应用和所有扩展均通过签名验证。
  4. 应用权限没有超出描述文件允许范围。
  5. 导出配置通过 plutil 检查,键值受当前 Xcode 支持。
  6. 导出日志、分发日志包和最终产物被同批保存。
  7. 修复只改变一个变量,并能在原归档上重复验证。

这样处理后,归档导出不再是一次难以解释的黑盒操作。团队可以明确区分工具链漂移、配置错误、签名关系异常和归档损坏,并在不重新构建的情况下验证多数修复。

常见问题

归档成功但导出失败,是否应该立即重新归档?

不应该。先保留原始 xcarchive,并核对其 Info.plist、应用包、描述文件和签名权限;很多导出错误来自导出参数或签名材料,重新归档会覆盖现场。

ExportOptions.plist 可以在不同 Xcode 版本之间直接复用吗?

不建议无条件复用。先用当前选中的 Xcode 查看 xcodebuild 帮助,确认 method 等键值仍受支持,再对配置文件做语法和必填项检查。

Runner M4

让下一次构建运行在独享物理 Mac 上

按任务周期选择云端 Mac 节点,在控制台管理订单、连接信息与支持工单。

选择方案并下单