一個 iOS 專案在開發分支上或許能正常編譯,Archive 中卻可能悄悄多出動態 Framework、指向開發機目錄的絕對連結,或暴露原本僅供內部使用的全域符號。只審查專案檔案很難涵蓋這些變化,因為真正交付的是連結完成後的 Mach-O。更穩妥的做法,是讓雲端 Mac CI 直接檢查封存產物,並將依賴與符號變化轉換成必須審核的差異。
先定義要攔截的變化
這類門禁不應判斷某個函式庫「好或不好」,而應回答三個可驗證的問題:
- 主程式與內嵌 Framework 實際依賴了哪些項目?
@rpath指向的自帶函式庫是否確實存在於 App 套件內?- 新增的全域符號是否屬於預期介面?
基準並非永久白名單,而是一次經過人工確認的產物快照。工具鏈升級、依賴更新或介面調整後可以更新基準,但必須與程式碼變更一併審查。
建議將檢查安排在 Release Archive 之後。Debug 建置可能包含診斷函式庫,最佳化結果也可能不同,因此無法代表最終交付邊界。首次導入時只產生報告,不要立即阻擋流程;待團隊確認基準內容後,再讓差異觸發失敗狀態。
從 Archive 擷取檢查對象
先建立獨立封存,避免重複使用開發者本機的歷史產物:
rm -rf out/App.xcarchive
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-destination 'generic/platform=iOS' \
-archivePath "$PWD/out/App.xcarchive" \
clean archive
如果簽章由後續的獨立工作處理,可依專案實際情況設定 CODE_SIGNING_ALLOWED=NO。不要機械式地將這個覆寫項加入所有專案;若專案包含特定能力或自訂建置階段,應先驗證未簽章的封存是否完整。
封存中的主程式通常位於 Products/Applications。檢查範圍至少應包含 App 的主要可執行檔,以及 Frameworks 目錄內每個 Framework 的可執行檔。擴充功能目標也應分別納入,因為它們各自擁有獨立的依賴邊界。
| 對象 | 主要檢查 | 常見異常 |
|---|---|---|
| App 主程式 | 系統函式庫、自帶 Framework、全域符號 | 非系統絕對路徑 |
| 動態 Framework | 次級依賴、安裝名稱、公開符號 | 依賴未隨套件交付 |
| App Extension | 獨立的依賴與符號基準 | 錯誤沿用主程式假設 |
產生依賴與符號清單
下列指令碼會讀取主程式與內嵌 Framework,分別輸出經穩定排序的依賴與全域符號。排序可避免工具輸出順序變動而產生無意義的差異。
set -euo pipefail
archive="${1:?archive path required}"
app=$(find "$archive/Products/Applications" -maxdepth 1 -name '*.app' -print -quit)
test -n "$app"
mkdir -p audit/current
main=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$app/Info.plist")
printf '%s
' "$app/$main" > audit/current/binaries.txt
if test -d "$app/Frameworks"; then
while IFS= read -r framework; do
executable=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleExecutable' "$framework/Info.plist")
printf '%s
' "$framework/$executable"
done < <(find "$app/Frameworks" -maxdepth 1 -name '*.framework' -type d | sort)
fi >> audit/current/binaries.txt
: > audit/current/dependencies.txt
: > audit/current/symbols.txt
while IFS= read -r binary; do
name=${binary#"$app/"}
otool -L "$binary" |
sed '1d' |
awk -v n="$name" '{$1=$1; print n " " $1}' \
>> audit/current/dependencies.txt
xcrun nm -gU "$binary" |
awk -v n="$name" 'NF {print n " " $NF}' \
>> audit/current/symbols.txt
done < audit/current/binaries.txt
LC_ALL=C sort -u -o audit/current/dependencies.txt audit/current/dependencies.txt
LC_ALL=C sort -u -o audit/current/symbols.txt audit/current/symbols.txt
提交基準時只保留相對物件名稱,不要記錄雲端 Mac 的工作目錄。接著由管線執行:
diff -u audit/baseline/dependencies.txt audit/current/dependencies.txt
diff -u audit/baseline/symbols.txt audit/current/symbols.txt
將報告轉換成可靠門禁
只進行全文差異比對會較為敏感。更實用的策略是先執行硬性規則,再檢查基準變化。依賴通常可以合理地以 /System/Library/Frameworks/、/usr/lib/ 或受控的 @rpath/ 開頭;其他絕對路徑應直接判定失敗。對於 @rpath/Example.framework/Example,指令碼還應確認套件內確實存在對應的 Framework,不能只因前綴正確就放行。
符號變化則適合交由人工審核。Swift 名稱經過修飾後可能非常長,不要根據字串長度判斷風險。應重點檢查突然出現的大量全域符號、除錯輔助介面、測試進入點,以及原本應隱藏卻可跨模組存取的 C 或 Objective-C 符號。
降低誤報
固定 Xcode 版本與 Release 組態,並確保每次比較的都是同一個 scheme。若專案包含多個擴充功能,應為每個二進位檔保存獨立基準,而不是將所有結果混合成單一集合。工具鏈升級導致系統符號變化時,應在升級合併請求中更新基準,避免日後無法說明差異來源。
失敗後從產物反向追查
出現新依賴時,先使用 otool -L 確認是哪個二進位檔將其引入,再檢查目標的連結設定、條件式編譯與依賴嵌入階段。若 @rpath 目標缺失,應優先確認 Framework 是否只參與連結,卻未複製到套件內。出現非預期符號時,可使用 nm -gU 定位物件,再檢查可見性宣告、橋接程式碼與連結器匯出設定。
失敗後不要直接覆寫基準。正確順序是先確認變更來源、判斷是否影響交付邊界、修正或核准變化,最後才重新產生基準。以這種方式建立的門禁不會依賴某台雲端 Mac 的暫時狀態,也能為每次發布保留可供複核的二進位邊界紀錄。
常見問題
為什麼不能只檢查專案中的依賴宣告?
專案宣告不等於最終產物。條件編譯、連結參數與建置腳本都可能改變 Archive,因此門禁應直接檢查成品。
匯出符號改變時都必須阻擋發佈嗎?
不必一律阻擋。CI 應先要求審查;若變更來自已確認的工具鏈升級或公共介面調整,再更新並提交基準檔。
符號清單能取代敏感資料掃描嗎?
不能。符號清單主要揭露介面與連結邊界變化,敏感資料掃描仍須涵蓋原始碼、設定、資源與建置日誌。
獨享實體節點
將開發與建置任務交給獨享雲端 Mac
依任務選擇機型、節點區域與租用週期。每個實例都對應獨享實體機,採用非虛擬化環境。