一次圖示槽位遺漏,往往要到完整封存接近尾聲時才會暴露。對雲端 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、必要圖示缺漏與重複名稱等確定問題設置門檻。
將下一次建置交給獨享的 Apple Silicon 物理節點
依照任務規模選擇三檔設定與五個節點,運算資源與儲存空間不與其他租戶共用。所有節點全年 365 天正常運作,實際可用狀態以控制台即時回傳為準。