建置機已產出 Example.app,發布工作也成功產生 DMG。不過,交付前仍須釐清三件事:掛載磁碟映像檔後,裡面是否有完整的 App 與「應用程式」捷徑;複製出來的 App 簽章是否有效;使用者能否透過圖形介面拖放安裝。以下將這三項分成可重複執行的檢查。請將範例中的 Example.app 換成實際產出項目的名稱。
先確定封裝範圍
輸入必須是最終版本的 App 套件。先在建置工作中固定產出路徑,確認 App 執行檔存在,並完成預定的簽署流程;不要把「製作 DMG」當成修補 App 內容的機會。修改資源、執行檔或內嵌元件,都可能使原有簽章失效。
檢查目錄時請使用 test -d,不要只檢查是否有同名檔案。也要確認輸出路徑沒有舊 DMG:讓工作明確失敗,比默默覆寫上一次的發布產出更容易追查。如果管線會重試,應由管線先清理自己的輸出目錄,而不是讓封裝腳本刪除來源不明的檔案。
映像檔完整、App 簽章有效,以及系統信任該 App,是三種不同的結論。單獨通過其中一項,不能取代另外兩項。
製作含安裝捷徑的映像檔
以下腳本會在暫存目錄中組裝卷宗內容:複製已簽署的 App,並建立指向 /Applications 的符號連結。App 套件使用 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"
如果 App 執行檔不叫 Example,也要調整檢查路徑。卷宗名稱只是使用者掛載後看到的名稱,不決定 App 的安裝路徑。將暫存組裝目錄與最終輸出目錄分開,既方便結束時清理,也能避免意外把舊映像檔裝進新映像檔。
哪些內容不該放入卷宗
先查看目錄清單,確認組裝區只有 App 套件和安裝捷徑。建置日誌、簽署材料、除錯檔案及專案儲存庫都不應隨 DMG 交付。腳本中的 stage 是唯一的輸入目錄;日後若要加入說明檔,也應明確加入並檢查,而不是直接對整個工作區執行 hdiutil create。
重新掛載,檢查實際交付內容
不要只讀取來源目錄;請開啟剛產生的 DMG。先以 hdiutil verify 檢查映像檔,再以唯讀方式掛載、核對卷宗內的路徑,接著將 App 複製到獨立目錄並驗證簽章:
#!/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,依一般使用者的順序操作:
- 確認卷宗中只有預期的 App 與「應用程式」捷徑,名稱沒有被截斷或造成混淆。
- 將 App 拖入「應用程式」,確認複製完成;不要以直接從唯讀卷宗執行來取代安裝測試。
- 從複製後的 App 啟動,檢查第一個視窗及必要的檔案存取流程。
- 卸載卷宗,再次啟動已安裝的 App,排除程式仍依賴卷宗內資源的情況。
執行這組檢查前,先確認測試位置沒有同名的舊 App,否則開啟的可能是上次安裝的版本。清理測試副本前也應核對路徑,以免誤刪日常使用的 App。若無法使用圖形工作階段,請將發布狀態標記為「命令列檢查通過、圖形驗收未完成」,不要把兩者合併視為成功。
將失敗定位到正確的環節
如果 hdiutil verify 失敗,先保留原始 DMG 與產生時的日誌,檢查產出項目是否在寫入完成前就被上傳或讀取。如果卷宗內缺少檔案,請回頭檢查組裝目錄與 ditto 的輸入路徑。如果簽章驗證失敗,則檢查簽署完成後是否仍有腳本修改 App 套件,並從最終版 App 重新製作映像檔。反覆局部修補現有 DMG,會讓交付內容難以追溯。
若命令列檢查全部通過,唯獨拖放後無法啟動,請優先確認實際啟動路徑、App 簽章,以及外部分發所需的系統信任檢查。最後記錄 App 建置識別碼、DMG 檔案校驗和、自動檢查結果與圖形驗收結論,讓下次交付時能判斷問題出在建置、封裝,還是安裝環節。
常見問題
hdiutil verify 通過就代表 App 能正常開啟嗎?
不代表。它只驗證映像;仍須檢查 App 簽章,並在圖形工作階段測試複製後的啟動。
應該在製作 DMG 前完成 App 簽章嗎?
應該。先簽署並驗證最終版本的 App,再製作 DMG;若修改 App,必須重新打包與驗收。
需要一台獨享雲端 Mac 嗎?
選擇機型、節點和計費週期。交付的是獨享實體機,實際可用狀態以控制台即時回報為準。