同一筆提交在開發機上可以順利通過,到了雲端 Mac 的持續建置環境,卻可能讓 swift-frontend 異常退出。重新執行偶爾會成功,清除快取後又改成另一個檔案失敗。此時最危險的做法,是連續修改原始碼、升級相依套件並刪除所有快取:現場條件同時遭到改變,真正觸發編譯器崩潰的因素也會隨之消失。更穩妥的方式,是先確認故障類型,再依照單一變因原則逐步縮小範圍。
先確認是否真的是編譯器崩潰
CompileSwiftSources 失敗並不等於編譯器崩潰。語法錯誤、型別推斷失敗或模組缺失,也會在這個階段傳回非零狀態。真正適合進入本文排查流程的跡象包括:
- 日誌明確指出
swift-frontend異常退出,或出現signal 11。 - 輸出包含
Stack dump、內部斷言失敗或編譯器呼叫堆疊。 ~/Library/Logs/DiagnosticReports/中出現時間相符的swift-frontend報告。- 相同的原始碼與命令可以多次觸發,而不是僅發生一次的遠端工作階段中斷。
先記錄工具鏈與主機資訊,不要只截取日誌最後十行:
mkdir -p diagnostics
{
date -u
sw_vers
uname -m
xcode-select -p
xcodebuild -version
xcrun swiftc --version
} | tee diagnostics/environment.txt
find "$HOME/Library/Logs/DiagnosticReports" \
-maxdepth 1 -type f -name 'swift-frontend*' \
-exec cp {} diagnostics/ \;
崩潰報告、完整建置命令與觸發問題的原始碼,必須來自同一次重現。把不同執行輪次的資料拼在一起,往往只會產生無法驗證的錯誤線索。
以隔離建置保存第一現場
為重現作業準備獨立的 DerivedData,並完整保存終端機輸出。不要一開始就刪除全域快取,因為快取是否參與觸發,本身就是需要驗證的變因。
set -o pipefail
rm -rf "$PWD/.diagnostics-derived-data"
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Debug \
-destination 'generic/platform=iOS Simulator' \
-derivedDataPath "$PWD/.diagnostics-derived-data" \
-jobs 1 \
build 2>&1 | tee diagnostics/build-single-job.log
此處將並行度降為一,只是為了判斷崩潰是否與平行編譯有關,不代表應長期採用這項設定。如果單一工作仍會穩定崩潰,請保留該份日誌;如果故障消失,則在原始碼與 DerivedData 固定不變的情況下,分別測試 -jobs 2 與日常使用的並行值。每組至少重複三次,並記錄結果是成功、一般編譯錯誤,還是編譯器崩潰,不能只寫「失敗」。
在 OVPS 雲端 Mac 上重現時,也應記錄實際選用的 Xcode 路徑。只比較介面顯示的版本名稱並不足夠;即使主版本相同,建置編號與 Swift 工具鏈仍可能不同。
建立單一變因對照矩陣
排查應從成本最低、破壞性最小的變因開始。每次只修改一項,並另外保存一份新日誌。
| 對照項目 | 基準值 | 變更值 | 要回答的問題 |
|---|---|---|---|
| 並行度 | -jobs 1 |
日常並行值 | 是否只在平行編譯時觸發 |
| DerivedData | 隔離目錄 | 新建空目錄 | 是否依賴既有的中間產物 |
| 編譯模式 | 目前專案值 | 暫時對照值 | 是否與增量編譯或整體模組編譯路徑有關 |
| 最佳化層級 | Debug 設定 | 專案允許的對照設定 | 是否只在最佳化階段觸發 |
| 工具鏈 | 目前鎖定版本 | 另一個已驗證版本 | 是否屬於特定工具鏈的回歸問題 |
不要同時切換 Xcode、更新相依套件並清除快取。若切換工具鏈後問題消失,只能說明故障與該工具鏈組合有關,不能直接證明新版本已經修復問題。還需要把失敗檔案、編譯模式與觸發次數記錄在同一張表中。
識別快取造成的假象
只有在空白 DerivedData 可以通過,而舊目錄會穩定失敗時,才進一步檢查模組快取與建置資料庫。先封存有問題的目錄,再進行定點清理。若直接大範圍刪除,就會失去比較舊產物與新產物的機會。
從失敗檔案縮減為最小重現案例
在完整日誌中找出失敗的 Swift 編譯工作。若問題可以脫離原專案重現,請將相關宣告複製到 Repro.swift,先以型別檢查進行驗證:
xcrun swiftc -typecheck Repro.swift 2>&1 | tee diagnostics/repro.log
縮減時應依相依關係反向處理:先刪除無關方法,再移除協定實作、泛型約束與屬性包裝器;每刪除一部分,就執行一次判定指令稿。不要只憑肉眼猜測某段語法「看起來很複雜」,編譯器崩潰經常是由兩個普通特性的組合所觸發。
#!/bin/zsh
set -o pipefail
output="$(mktemp)"
xcrun swiftc -typecheck Repro.swift >"$output" 2>&1
if grep -Eiq 'signal 11|segmentation fault|stack dump|swift-frontend.*failed' "$output"; then
cp "$output" diagnostics/latest-crash.log
rm -f "$output"
exit 0
fi
rm -f "$output"
exit 1
這個指令稿將「仍能觸發目標崩潰」定義為成功,方便搭配人工二分法或原始碼縮減工具。每次縮減後,還要確認錯誤特徵保持一致;如果內部斷言變成一般型別錯誤,就表示關鍵條件已經被刪除。
若單一檔案無法重現,就保留最小的模組邊界:一個精簡專案、必要的建置設定、鎖定的相依套件版本,以及一條執行命令。不要夾帶業務資料、存取憑證或無關資源。
建立可複查的交付套件
最終資料應讓另一台雲端 Mac 在沒有口頭說明的情況下完成驗證。建議目錄只包含:
README.md:預期現象、執行命令、重複次數與實際結果。environment.txt:系統架構、Xcode 建置編號與 Swift 版本。Repro.swift或精簡專案:只保留必要的原始碼。build.log:未經截斷的標準輸出與錯誤輸出。- 崩潰報告:與該次執行時間相符的診斷檔案。
check.sh:透過退出狀態明確表示是否重現目標崩潰。
交付前,請在新目錄中重新執行一次,確認指令稿不依賴原專案的絕對路徑、使用者目錄或殘留的模組快取。若問題只會在特定並行度下出現,應清楚寫明並行參數與重現機率;若只在某個工具鏈建置編號中出現,則同時記錄可以順利通過的對照版本。這樣留下的就不再只是一句「Swift 偶爾崩潰」,而是一組可以重複、比較並繼續用於修復的工程證據。
常見問題
CompileSwiftSources 失敗是否代表編譯器已經崩潰?
不代表。語法、型別與相依套件錯誤也會在同一階段失敗。只有 swift-frontend 異常結束、出現 signal、Stack dump 或對應診斷報告時,才應視為編譯器崩潰。
最小重現一定要包含完整 Xcode 專案嗎?
不一定。若單一 Swift 檔案配合 swiftc -typecheck 即可穩定觸發,提交該檔案與命令更清楚;依賴模組邊界或建置設定時,才需要精簡專案。
將下一次建置部署至獨享物理節點
從三種 Apple Silicon 配置與六個在售節點中選擇,實際可用狀態以控制台即時回傳為準。