同一份 Xcode 封存檔交給發佈流程後,最終送到不同裝置上的內容並不完全相同。App Thinning 會依裝置能力選擇可執行檔切片與資源變體。日常建置若只驗證封存成功,很容易漏掉資源標籤寫錯、無關架構混入、內嵌框架發生變動,或容量報告根本沒有產生。更穩妥的做法,是在雲端 Mac 上將「封存」與「裝置變體驗收」拆成兩個獨立階段。
本文使用現有專案與可供發佈的封存設定,不變更簽章體系,也不要求每次都重新編譯。目標是從一份 .xcarchive 匯出 App Thinning 結果、產生結構清單,並讓 CI 在出現明確異常時傳回非零結束碼。
先固定驗收輸入
開始驗收前,先記錄 Xcode 路徑、專案版本、建置編號與封存檔位置。不要讓工作依賴互動式 Shell 的預設工具鏈。雲端 Mac 可能同時安裝多個 Xcode 版本,CI 應明確選擇目前管線已核准的版本。
set -euo pipefail
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
ARCHIVE_PATH="$PWD/output/App.xcarchive"
EXPORT_DIR="$PWD/output/thinning"
REPORT_DIR="$PWD/output/evidence"
xcodebuild -version
test -d "$ARCHIVE_PATH"
rm -rf "$EXPORT_DIR" "$REPORT_DIR"
mkdir -p "$EXPORT_DIR" "$REPORT_DIR"
接著讀取封存檔中的應用程式資訊,確認 CI 沒有誤用上一個工作遺留的產出。
INFO="$ARCHIVE_PATH/Info.plist"
/usr/libexec/PlistBuddy -c "Print :ApplicationProperties:CFBundleIdentifier" "$INFO"
/usr/libexec/PlistBuddy -c "Print :ApplicationProperties:CFBundleShortVersionString" "$INFO"
/usr/libexec/PlistBuddy -c "Print :ApplicationProperties:CFBundleVersion" "$INFO"
建議將工作目錄綁定至工作編號,並在工作開始時建立空目錄。不要使用全域固定的 output 目錄,讓多個並行工作共用封存檔。
App Thinning 驗收的輸入必須是已通過一般封存檢查的單一
.xcarchive。如果輸入本身無法確定,後續再仔細比較裝置變體也沒有意義。
匯出所有相容的裝置變體
準備獨立的 ExportOptions.plist。method 應與團隊目前使用的 Xcode 和既有發佈流程一致;以下設定的重點,是將 thinning 設為匯出所有相容變體。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store-connect</string>
<key>thinning</key>
<string><thin-for-all-variants></string>
<key>stripSwiftSymbols</key>
<true/>
</dict>
</plist>
執行匯出時,將結果與記錄都放進目前工作的目錄:
xcodebuild \
-exportArchive \
-archivePath "$ARCHIVE_PATH" \
-exportPath "$EXPORT_DIR" \
-exportOptionsPlist "$PWD/ExportOptions.plist" \
2>&1 | tee "$REPORT_DIR/export.log"
find "$EXPORT_DIR" -maxdepth 3 -type f -print \
| LC_ALL=C sort \
> "$REPORT_DIR/export-files.txt"
如果現有管線使用不同的匯出方式,不要為了照搬範例而變更。保留原本的 method,只在隔離分支中驗證 thinning 設定,並將實際使用的 plist 一併封存。
核對架構與內嵌內容
匯出成功不代表結果正確。先找出安裝套件並逐一解壓縮,再檢查主要可執行檔的架構。指令碼不應假設應用程式目錄名稱固定,而應從 Payload 中找出 .app。
mkdir -p "$REPORT_DIR/unpacked"
while IFS= read -r ipa; do
name="$(basename "$ipa" .ipa)"
dest="$REPORT_DIR/unpacked/$name"
mkdir -p "$dest"
ditto -x -k "$ipa" "$dest"
app="$(find "$dest/Payload" -maxdepth 1 -name '*.app' -type d | head -n 1)"
test -n "$app"
executable="$(/usr/libexec/PlistBuddy \
-c 'Print :CFBundleExecutable' "$app/Info.plist")"
printf '%s: ' "$name"
lipo -archs "$app/$executable"
find "$app/Frameworks" -maxdepth 1 -type d -name '*.framework' 2>/dev/null \
| sed "s#^$app/##" \
| LC_ALL=C sort \
> "$REPORT_DIR/$name-frameworks.txt"
done < <(find "$EXPORT_DIR" -type f -name '*.ipa' | LC_ALL=C sort)
對目前的 iOS 裝置發佈結果而言,主要應用程式若包含模擬器架構,應直接判定失敗。框架清單則適合與儲存庫中的核准基準比較。新增框架不一定有問題,但必須經過程式碼審查,避免除錯元件或暫時性相依項目進入交付套件。
不要比較整個安裝套件的雜湊值
不同裝置變體原本就可能包含不同的資源與可執行檔切片,簽章資料也會造成二進位差異。若直接要求所有 IPA 的 SHA-256 完全相同,會將正常的 App Thinning 誤判為失敗。
更有效的比較項目包括:
- 主要可執行檔允許的架構;
Frameworks與PlugIns的相對路徑清單;- 必須存在的本地化內容、字型與資料檔案;
- 不應進入成品的除錯設定、測試資源與原始碼檔案;
- 每個變體解壓縮後的容量,以及相對於歷史基準的變化。
將資源與容量轉化為驗收證據
先確認匯出流程確實產生容量報告,再建立每個變體的檔案清單。檔案大小會受資源裁剪影響,因此應分別為各個變體保存基準。
SIZE_REPORT="$(find "$EXPORT_DIR" -type f \
-name 'App Thinning Size Report.txt' | head -n 1)"
test -n "$SIZE_REPORT"
cp "$SIZE_REPORT" "$REPORT_DIR/"
for app in "$REPORT_DIR"/unpacked/*/Payload/*.app; do
variant="$(basename "$(dirname "$(dirname "$app")")")"
(
cd "$app"
find . -type f -print0 \
| xargs -0 stat -f '%z %N' \
| LC_ALL=C sort
) > "$REPORT_DIR/$variant-files-and-sizes.txt"
done
容量門檻不要只設定一個永遠不變的絕對數值。建議同時保留兩種判斷方式:團隊核准的硬性上限,以及相對於最近穩定基準的成長幅度。硬性上限可阻止明顯失控的容量膨脹,相對基準則用來捕捉單次提交造成的異常成長。
對於資源目錄,也應檢查業務關鍵檔案是否存在。例如離線範本、首屏字型或特定語言資源,不能只靠「應用程式可以啟動」來證明內容完整。將這些路徑維護成儲存庫內的文字清單,逐項執行 test -e,會比在指令碼中散落大量條件更容易審查。
在 CI 中設定明確的失敗條件
最終指令碼至少應阻擋以下情況:未匯出任何 IPA、缺少容量報告、主要應用程式包含未允許的架構、關鍵資源缺失、框架集合出現未經核准的變化,以及變體容量超過團隊上限。
同時保存以下證據:
- Xcode 版本與
DEVELOPER_DIR; - 封存檔的套件識別碼、版本與建置編號;
- 實際使用的
ExportOptions.plist; - 完整的匯出記錄;
- 每個變體的架構、框架與檔案清單;
- App Thinning 容量報告。
發生失敗時,不要立即刪除匯出目錄。應先將證據打包上傳,再由工作的結束掛鉤清理工作區。如果在 PingMini 的雲端 Mac 上並行執行,應讓每個工作使用獨立目錄,並在控制台確認目前可選的設定;驗收邏輯本身不應依賴某個固定節點。
App Thinning 的價值不只在於縮小下載容量,更在於它會改變最終交付內容。只有將裝置變體視為正式建置產出進行檢查,才能在資源標籤、架構或相依項目發生變化時,於發佈前取得可重現、可定位的失敗結果。
常見問題
App Thinning 驗收應檢查封存檔還是匯出的安裝套件?
兩者都要檢查。封存檔用來確認共同輸入正確,匯出套件則用來驗證各裝置變體實際包含的架構、資源與內嵌內容。
不同裝置變體的雜湊值不同就代表建置異常嗎?
不代表。執行檔切片、裝置資源與簽章資料可能不同,應比較檔案清單、允許的架構、必要資源與非預期新增項目。
哪些 App Thinning 異常適合直接阻擋 CI?
容量報告缺失、匯出目錄為空、出現未允許架構、必要資源缺少、內嵌框架集合漂移或變體超過團隊設定的容量上限,都應直接阻擋。
準備下一次建置所需的獨享物理 Mac
從三種固定配置中選擇適合開發、持續整合或本地模型測試的雲端 Mac,並依照任務週期完成設定。