工程文章

雲端 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 建置嗎?

不可以。它適合提早發現資產目錄與參數錯誤,最終仍須以實際交付所用的 scheme、SDK 與組態完成 Xcode 建置。

actool 的所有警告都應該讓 CI 失敗嗎?

不建議一開始全部阻擋。先保存並分類既有警告,再針對無效 JSON、必要圖示缺漏與重複名稱等確定問題設置門檻。

OwnAMac 雲端 Mac

將下一次建置交給獨享的 Apple Silicon 物理節點

依照任務規模選擇三檔設定與五個節點,運算資源與儲存空間不與其他租戶共用。所有節點全年 365 天正常運作,實際可用狀態以控制台即時回傳為準。

選擇設定並訂購