雲端 Mac 建置產物打包:用 ditto 保留中繼資料

CI/CD 實踐 ·約 8 分鐘閱讀

雲端 Mac 建置產物打包:用 ditto 保留中繼資料

雲端 Mac 建置順利完成,產物下載到交付機後卻出現指令碼無法執行、符號連結變成一般檔案,或應用程式套件驗證失敗。這類問題通常不在編譯階段,而是發生於最後一次壓縮與解壓縮。macOS 檔案除了內容與路徑,還可能帶有延伸屬性、存取控制清單、資源分支及符號連結關係。若交付流程只確認「壓縮檔可以開啟」,這些變化就會一路進入發布階段。

先定義需要完整保留的產物

不要直接壓縮整個工作區。應先建立只包含交付內容的暫存目錄,並明確界定哪些中繼資料屬於產物契約的一部分。

常見產物包括應用程式套件、框架、動態函式庫、命令列工具、設定範本及符號檔。快取目錄、測試暫存檔、版本控制目錄與本機日誌都不應混入。使用暫存目錄也能避免封裝期間,來源目錄持續被建置工作修改。

set -euo pipefail

ROOT="$PWD"
SOURCE="$ROOT/build/Release"
STAGE="$ROOT/artifacts/stage"
ARCHIVE="$ROOT/artifacts/release.zip"

rm -rf "$STAGE"
mkdir -p "$STAGE"
ditto "$SOURCE/MyProduct.app" "$STAGE/MyProduct.app"
ditto "$SOURCE/MyFramework.framework" "$STAGE/MyFramework.framework"

第一輪檢查應記錄一般檔案、目錄與符號連結的數量。符號連結在不同工具之間傳遞時尤其容易被展開,進而造成封存檔大小改變,或使相對路徑失效。

find "$STAGE" -type f | wc -l
find "$STAGE" -type d | wc -l
find "$STAGE" -type l -print

打包前盤點 macOS 中繼資料

使用 ls -le@ 檢視權限、ACL 與延伸屬性,再用 xattr 展開關鍵物件的屬性內容。不要把所有不熟悉的屬性都視為無用資料。

ls -le@ "$STAGE"
xattr -lr "$STAGE" > "$ROOT/artifacts/xattr-before.txt"
find "$STAGE" -type l -exec sh -c '
  for item do
    printf "%s -> %s\n" "$item" "$(readlink "$item")"
  done
' sh {} + > "$ROOT/artifacts/symlinks-before.txt"

不要把 xattr -cr 當成通用修復方式。遞迴清除雖然可能暫時排除某些表面錯誤,卻也會一併刪除鑑識資訊。正確順序是先盤點並判斷來源,再針對已確認不需要的個別屬性進行最小範圍處理。

還需要檢查可執行檔的權限位元。指令碼在儲存庫中具備執行權限,不代表複製到暫存目錄後仍會保持不變。可使用 find "$STAGE" -type f -perm -111 建立基準;若某個指令碼必須可執行,應將權限要求寫入驗收指令碼,而不是依賴人工記憶。

使用 ditto 建立可交付的壓縮檔

ditto 是 macOS 內建工具,適合處理應用程式套件與框架目錄。建立 ZIP 時啟用資源與延伸屬性保留,並保留暫存目錄這一層,可讓接收方取得穩定的頂層結構。

rm -f "$ARCHIVE"
ditto -c -k \
  --sequesterRsrc \
  --keepParent \
  "$STAGE" \
  "$ARCHIVE"

shasum -a 256 "$ARCHIVE" > "$ARCHIVE.sha256"
du -h "$ARCHIVE"

封存檔名稱應固定,內部頂層目錄也應固定。不要將時間戳記、機器名稱或暫存工作區的絕對路徑寫入目錄名稱,否則下游指令碼只能猜測進入點。若管線需要唯一識別碼,可將提交編號存放於外層的產物記錄中,而不要改變壓縮檔內部配置。

對來源不受控的檔案,還應在其進入暫存目錄前,檢查檔名是否含有換行字元、不可見字元或異常的 Unicode 組合。一般空格可以安全處理,但指令碼必須始終以雙引號包住變數,避免路徑被 Shell 拆分。

在獨立目錄解壓縮並比較

驗收時不可覆蓋解壓縮到原目錄,否則缺少的檔案可能被舊檔案掩蓋。每次都應使用全新目錄,並由 ditto 負責解壓縮。

VERIFY="$ROOT/artifacts/verify"

rm -rf "$VERIFY"
mkdir -p "$VERIFY"
ditto -x -k "$ARCHIVE" "$VERIFY"

test -d "$VERIFY/stage/MyProduct.app"
test -d "$VERIFY/stage/MyFramework.framework"

find "$VERIFY/stage" -type l -exec sh -c '
  for item do
    printf "%s -> %s\n" "$item" "$(readlink "$item")"
  done
' sh {} + > "$ROOT/artifacts/symlinks-after.txt"

diff -u \
  "$ROOT/artifacts/symlinks-before.txt" \
  "$ROOT/artifacts/symlinks-after.txt"

檔案內容驗證應分別針對打包前與解壓縮後的目錄產生清單。路徑可能包含空格,因此不要使用未加引號的迴圈。以下 Python 程式片段會依相對路徑排序,並為一般檔案計算 SHA-256。

from hashlib import sha256
from pathlib import Path
import sys

root = Path(sys.argv[1])

for path in sorted(root.rglob("*"), key=lambda p: str(p.relative_to(root))):
    if path.is_file() and not path.is_symlink():
        digest = sha256(path.read_bytes()).hexdigest()
        print(digest, path.relative_to(root))

分別對 stage 與 verify/stage 執行,再使用 diff 比較輸出。這樣可以同時發現內容變更、檔案遺漏及非預期新增。

將結構與簽章檢查設為發布門禁

如果產物包含已簽章的應用程式套件或框架,應在解壓縮後再次執行簽章驗證。只在壓縮前驗證一次並不足夠,因為問題可能由封存工具引入。

codesign --verify --deep --strict \
  "$VERIFY/stage/MyProduct.app"

codesign -d --verbose=4 \
  "$VERIFY/stage/MyProduct.app" 2>&1 |
  sed -n '1,20p'

--deep 適合用於最終完整性檢查,但不能取代逐層定位。驗證失敗時,應先確認問題出在主應用程式、內嵌框架,還是輔助可執行檔,再針對具體路徑執行 codesign --verify --strict --verbose=4。同時比較打包前後的 xattr 輸出,以確認變化是否由封存步驟造成。

建議將驗收失敗條件固定為:頂層目錄不一致、檔案清單不同、符號連結目標改變、必要的執行權限位元遺失、關鍵延伸屬性異常,或簽章驗證失敗。只要出現任何一項,就應停止發布,而不是重新壓縮後碰運氣。

常見陷阱與交付清單

最常見的錯誤是混用多種工具:先由指令碼建立一般 ZIP,再由上傳程式自動解壓縮並重新壓縮。處理鏈越長,中繼資料語意就越難追蹤。應指定唯一的打包工具,並將原始壓縮檔直接交給產物儲存系統。

第二類錯誤是建置與打包同時進行。編譯器仍在寫入檔案時,清單與壓縮檔可能取自不同時間點。正確做法是等待建置程序成功結束,將結果複製到唯讀暫存目錄,再開始封存。

第三類錯誤是只在同一台機器上測試。至少應刪除舊的驗收目錄,重新解壓縮,並從壓縮檔內的路徑啟動檢查;條件允許時,再由獨立工作執行同一套指令碼。BerryVM 上的長期管線可以將盤點檔案、壓縮檔雜湊值與驗收日誌作為同一批次的交付記錄,但日誌不得包含憑證或未經遮蔽處理的環境變數。

最終清單應保持簡短:暫存目錄已凍結、符號連結已記錄、延伸屬性已盤點、壓縮檔雜湊值已產生、獨立解壓縮已完成、檔案清單一致、執行權限位元符合預期,且適用的簽章檢查已通過。完成這些步驟後,打包就不再是建置結束後隨手執行的動作,而是一道可重複執行、並能準確判定失敗的工程門禁。

常見問題

為什麼不能只用 zip -r 打包 macOS 建置產物?

一般 ZIP 流程可能忽略資源分支、延伸屬性、ACL 或符號連結語意。產物若包含應用程式套件、框架或工具目錄,應優先使用 ditto 並驗收解包結果。

打包前可以直接執行 xattr -cr 嗎?

不建議對整個目錄遞迴清除。應先列出屬性並確認用途,只針對已判定不需要的項目做最小範圍處理,以免刪除交付或診斷所需資訊。

只比對壓縮檔的 SHA-256 是否足夠?

不夠。雜湊只能證明壓縮檔未被改動,仍需在獨立目錄解包後檢查結構、符號連結、屬性,以及適用時的程式碼簽章。

獨享實體 Mac

將下一項開發任務放到雲端 Mac 上

選擇 Berry M4 或 Berry M4 Pro,確認五個節點、租用期間與附加選項後提交訂單。

選擇設定並訂購