构建机已经产出 Example.app,发布任务也成功生成了 DMG。交付前仍要回答三个问题:映像挂载后是否有完整的应用和“应用程序”入口,复制出来的应用签名是否有效,用户能否在图形界面完成拖拽安装。下面把这三件事拆成可重复的检查;示例中的 Example.app 请替换为实际产物名称。
先确定打包边界
输入必须是最终版本的应用包。先在构建任务里固定产物路径,确认应用可执行文件存在,并完成预定的签名流程;不要把“制作 DMG”当成修补应用内容的机会。修改资源、可执行文件或内嵌组件都可能使原有签名失效。
检查目录时使用 test -d,不要只检查同名文件。还要确认输出路径尚无旧 DMG:让任务明确失败,比静默覆盖上一次发布产物更容易追查。若流水线会重试,应先由流水线清理自己的输出目录,而不是让打包脚本删除未知文件。
映像完整、应用签名有效、系统信任该应用,是三种不同的结论。单独通过其中一项,不能代替另外两项。
制作带安装入口的映像
下面的脚本在临时目录装配卷内容:复制已签名应用,并建立指向 /Applications 的符号链接。ditto 用于复制应用包;不要在复制后再编辑包内文件。
#!/bin/bash
set -euo pipefail
APP="$PWD/build/Release/Example.app"
OUT="$PWD/dist/Example.dmg"
test -d "$APP"
test -f "$APP/Contents/MacOS/Example"
mkdir -p "$(dirname "$OUT")"
test ! -e "$OUT"
work="$(mktemp -d "${TMPDIR:-/tmp}/dmg-build.XXXXXX")"
trap 'rm -rf "$work"' EXIT
stage="$work/stage"
mkdir -p "$stage"
ditto "$APP" "$stage/Example.app"
ln -s /Applications "$stage/Applications"
hdiutil create -volname "Example" \
-srcfolder "$stage" -fs HFS+ -format UDZO "$OUT"
如果应用的可执行文件名不叫 Example,要同时调整检查路径。卷名只是用户挂载后看到的名称,不决定应用安装路径。临时装配目录与最终输出目录分开,既方便退出时清理,也能避免把旧映像意外装进新映像。
哪些内容不该进入卷
先用目录清单确认装配区只有应用包和安装入口。构建日志、签名材料、调试文件与项目仓库不应随 DMG 交付。脚本中的 stage 是唯一输入目录;后续若增加说明文件,也应明确加入并检查,而不是直接对整个工作区运行 hdiutil create。
重新挂载,检查实际交付内容
不要只读取源目录;要打开刚生成的 DMG。hdiutil verify 检查映像,随后以只读方式挂载,核对卷内路径,再将应用复制到独立目录并验证签名:
#!/bin/bash
set -euo pipefail
OUT="$PWD/dist/Example.dmg"
work="$(mktemp -d "${TMPDIR:-/tmp}/dmg-check.XXXXXX")"
mountpoint="$work/mount"
attached=0
cleanup() {
if [ "$attached" -eq 1 ]; then
hdiutil detach "$mountpoint" >/dev/null || true
fi
rm -rf "$work"
}
trap cleanup EXIT
mkdir -p "$mountpoint" "$work/installed"
hdiutil verify "$OUT"
hdiutil attach -readonly -nobrowse \
-mountpoint "$mountpoint" "$OUT" >/dev/null
attached=1
test -d "$mountpoint/Example.app"
test -L "$mountpoint/Applications"
test "$(readlink "$mountpoint/Applications")" = /Applications
ditto "$mountpoint/Example.app" "$work/installed/Example.app"
codesign --verify --deep --strict \
"$work/installed/Example.app"
脚本遇到失败会返回非零状态,适合接入发布任务。卸载失败则需要人工检查是否有进程仍占用该卷;不要用强制卸载掩盖占用。codesign --verify 检查复制后的包是否仍符合签名要求,但不等于系统已信任它。采用面向外部分发的签名与公证流程时,还须按该流程单独验证系统评估结果。
用图形会话走一遍用户路径
命令行能检查路径和签名,却不能证明图标布局清楚、拖拽目标可见,或首次启动没有异常。使用已登录的 macOS 图形会话打开 DMG,按普通用户的顺序操作:
- 确认卷中只有预期的应用与“应用程序”入口,名称没有截断或混淆。
- 将应用拖入“应用程序”,确认复制完成;不要直接从只读卷运行来代替安装测试。
- 从复制后的应用启动,检查首个窗口和必要的文件访问流程。
- 卸载卷,再次启动已安装应用,排除程序仍依赖卷内资源的情况。
执行这组检查时,先确认测试位置不存在同名旧应用,否则可能打开的是上一次安装的版本。清理测试副本前也应核对路径,避免误删日常使用的应用。图形会话不可用时,将发布标记为“命令行检查通过、图形验收未完成”,不要把两者合并成成功。
把失败定位到正确的一层
hdiutil verify 失败,先保留原始 DMG 与生成日志,检查产物是否在写入完成前被上传或读取。卷内少文件,回到装配目录和 ditto 输入路径排查。签名验证失败,则检查签名完成后是否仍有脚本修改应用包,并从最终应用重新制作映像;反复对现有 DMG 做局部修补会让交付物难以追溯。
若命令行检查都通过,唯独拖拽后无法启动,优先确认实际启动路径、应用签名及外部分发所需的系统信任检查。最后记录应用构建标识、DMG 文件校验和、自动检查结果与图形验收结论,让下一次交付能够判断问题出在构建、封装,还是安装环节。
常见问题
hdiutil verify 通过,是否说明应用可以正常打开?
不能。它检查映像本身;还需检查卷内文件、应用签名,并在图形会话中验证复制后的启动与安装体验。
可以在打包后修改 DMG 内的应用再重新签名吗?
不要这样安排流程。先完成应用签名并验证,再制作 DMG;需要改动应用时,从更新后的应用重新打包和验收。
需要一台独享的云端 Mac?
选择机型、节点和计费周期。交付的是独享物理机,实际可用性以控制台实时返回为准。