工程檔案

雲端 Mac 的 Xcode 靜態分析基線與增量門禁

雲端 Mac 的 Xcode 靜態分析基線與增量門禁

團隊將 xcodebuild analyze 整合至雲端 Mac 後,最常見的結果並不是立即達成零缺陷,而是一次出現數十條歷史診斷。若直接將警告總數設為失敗條件,管線會長期維持紅燈;若只將日誌保留為附件,又不會有人持續查看。更具可行性的做法,是先建立經過審閱的基線,再讓每次合併只對新增問題負責。

先固定分析工作的輸入

靜態分析取決於專案設定、編譯條件,以及實際參與編譯的原始碼檔案。分析前應固定 Xcode 選擇、工作區、Scheme、設定與目標平台,並確保 CI 使用與封存工作相同的相依性鎖定檔。不要使用開發者的個人 Scheme,也不要在指令碼中自動選擇「最新」工具鏈。

分析工作應使用獨立目錄,避免與編譯、測試工作同時改寫 DerivedData:

set -o pipefail

ROOT="$PWD"
OUT="$ROOT/ci-artifacts/analyze"
DERIVED="$ROOT/.derived/analyze"
RESULT="$OUT/AppAnalyze.xcresult"

rm -rf "$OUT" "$DERIVED"
mkdir -p "$OUT" "$DERIVED"

xcodebuild analyze \
  -workspace App.xcworkspace \
  -scheme App \
  -configuration Debug \
  -destination 'generic/platform=iOS' \
  -derivedDataPath "$DERIVED" \
  -resultBundlePath "$RESULT" \
  CODE_SIGNING_ALLOWED=NO \
  | tee "$OUT/xcodebuild.log"

CODE_SIGNING_ALLOWED=NO 只適用於不需要簽署即可完成的分析目標。若專案指令碼明確讀取與簽署相關的建置設定,應先修正指令碼的輸入邊界,而不是盲目加入更多覆寫參數。

基線不是「這些問題可以忽略」的清單,而是遷移期間對已知事實的快照。新增問題仍應立即導致失敗,舊問題則需要指定負責人與清理計畫。

保留完整結果,而不只是計算 warning 數量

直接執行 grep -c warning: 很方便,但不適合作為長期門禁。日誌中的路徑、行號與格式可能改變,同一筆診斷也可能重複輸出。CI 應完整保留 .xcresult 與原始日誌,再從結構化結果產生一份可供比較的診斷清單。

不同 Xcode 版本的 xcresulttool 子命令可能有所調整,因此應先記錄說明資訊,並在節點映像更新時重新檢查解析指令碼:

xcrun xcresulttool help > "$OUT/xcresulttool-help.txt"
xcrun xcresulttool get \
  --legacy \
  --path "$RESULT" \
  --format json > "$OUT/xcresult.json"

如果目前的工具鏈不接受 --legacy,應依該版本的說明資訊調整命令,並將解析器變更與工具鏈升級放在同一個合併請求中。不要在指令碼失敗時退回日誌計數後繼續放行,否則門禁會在不知不覺間失效。

建議為每筆診斷保留以下欄位:

欄位 用途
規則或問題類型 區分空指標、資源洩漏等類別
儲存庫相對路徑 避免將節點工作目錄寫入基線
函式或符號名稱 行號變更後仍可協助定位
正規化訊息 移除暫存目錄與不穩定數字
嚴重程度 支援依風險設定處理策略

使用穩定指紋建立可審閱的基線

診斷指紋不應包含絕對路徑、DerivedData 路徑或單獨的行號。較穩妥的組合是「問題類型 + 儲存庫相對路徑 + 符號名稱 + 正規化訊息」。行號可以保留供顯示使用,但不應納入主鍵,否則只要在檔案頂端新增一行,所有舊問題都會被判定為新增。

正規化時避免過度合併

可以移除工作目錄前綴、暫存 UUID 與連續空白,但不要刪除變數名稱、呼叫名稱或資源類型。若將同一檔案中的兩個不同缺陷壓縮成相同指紋,後來出現的問題就會被舊基線遮蔽。

基線檔案建議採用排序後的 JSON Lines 格式,每行一筆記錄,並納入版本控制:

{"fingerprint":"sha256:…","type":"AnalyzeWarning","path":"Sources/Cache.swift","symbol":"load()","message":"Potential leak of an object"}

首次建立基線時,應由程式碼擁有者逐筆確認。確認內容至少包括:診斷確實來自目前的主分支、暫時無法立即修正、已有追蹤事項,而且沒有將解析失敗或重複記錄寫入基線。

在合併請求中只比較集合差異

每次工作產生 current.jsonl 後,以指紋集合與 baseline.jsonl 比較。current - baseline 是新增問題,應使工作失敗;baseline - current 是已經消失的問題,應提示維護者刪除對應的基線項目,而不是繼續保留無效記錄。

門禁輸出應簡短且可直接採取行動。針對每個新增問題,輸出類型、相對路徑、行號、符號與訊息,並附上完整 xcresult 成品的位置。主控台日誌只顯示摘要,避免數千行分析軌跡掩蓋真正的差異。

區分三種失敗情況

分析指令碼至少要區分以下狀態:

  1. xcodebuild analyze 本身失敗,例如缺少相依項目或 Scheme 無法使用;
  2. 結果解析失敗,例如工具鏈升級後 JSON 結構發生變化;
  3. 分析成功,但發現基線以外的新診斷。

前兩種屬於基礎設施或設定錯誤,不能顯示為「零新增缺陷」。只有分析與解析都成功,而且集合差異為空時,工作才可以通過。

讓基線持續縮小

如果基線長期不變,最終會成為另一份無人維護的忽略清單。可以按模組指定負責人,在修改相關檔案時一併修正附近的診斷;也可以設定每次迭代清理少量高風險項目。修正後必須同步刪除基線記錄,下一次執行會驗證問題是否真的消失。

日常檢查可依下列順序執行:

  • 確認工具鏈、Scheme、設定與目標平台沒有漂移;
  • 確認分析目錄與其他工作隔離;
  • 確認 xcresult、日誌與正規化清單均已產生;
  • 抽查指紋中沒有絕對路徑或暫存識別碼;
  • 新增診斷阻擋合併,消失的診斷則提示縮減基線;
  • 工具鏈升級時,先在獨立分支重建並審閱差異。

在 OakVM 上執行這類工作時,關鍵不在於選擇更激進的閾值,而是讓同一實體節點上的工具鏈、指令碼與結果格式保持可追蹤。只要解析失敗不會被當成成功,而且基線變更必須經過審閱,靜態分析就能從一份容易被忽略的冗長日誌,轉變為穩定的增量品質門禁。

常見問題

為什麼不直接要求 Xcode 靜態分析零警告?

既有專案可能累積大量已知警告,直接要求清零會使門禁長期失敗。先固定基線並阻擋新增缺陷,再依模組逐步移除基線項目,較容易持續執行。

分析工作應保存哪些產物?

至少保存完整 xcresult、建置日誌、正規化後的診斷清單,以及本次比較使用的基線檔案,才能在失敗時重現判定並檢查上下文。

OAKVM BUILD NODE

在獨享實體節點上執行下一次建置

選擇 OakVM M4 或 OakVM M4 Pro,按日、週、月或季租用非虛擬化的雲端 Mac。節點的實際可用狀態以控制台即時回傳結果為準。

選擇設定並租用