團隊將工程檢出至雲端 Mac 後,最容易被低估的故障往往不是編譯錯誤,而是流水線根本找不到預期的 Scheme,或同名 Scheme 在遠端環境執行了不同的目標。本機 Xcode 會讀取開發者目錄中的使用者設定,乾淨的 CI 工作區卻只認得版本庫內的檔案。要讓建置入口維持穩定,Scheme 必須像指令碼與相依套件鎖定檔一樣納入版本控制與驗收。
先將 Scheme 視為 CI 介面
可供流水線呼叫的 Scheme 至少定義四件事:要建置哪些 Target、使用哪一套 Build Configuration、執行哪些 Test Bundle,以及 Archive 時採用什麼設定。只在 Xcode 選單中看見名稱,不代表這些資訊已經提交。
先檢查共享檔案是否存在:
find . \( -path "*/xcshareddata/xcschemes/*.xcscheme" \
-o -path "*/xcuserdata/*/xcschemes/*.xcscheme" \) -print
位於 xcuserdata 的 Scheme 屬於使用者設定,不能作為 CI 入口。應在 Xcode 的 Manage Schemes 中啟用 Shared,再確認檔案出現在以下任一路徑:
App.xcodeproj/xcshareddata/xcschemes/App.xcscheme
App.xcworkspace/xcshareddata/xcschemes/App.xcscheme
接著使用 git status 與 git check-ignore -v,確認檔案已被追蹤,且未遭過於寬泛的 *.xcuser* 或 xcshareddata 忽略規則誤傷。
「本機可見」只代表目前的使用者目錄中存在設定;「CI 可用」則必須由乾淨檢出目錄中的命令列結果證明。
固定 Workspace、Scheme 與設定
流水線不應依賴 Xcode 自動選擇工程。若使用 CocoaPods、聚合工程或本機 Package,開啟 .xcodeproj 與 .xcworkspace 得到的結果可能不同。應將三個入口值明確寫入 CI 變數:
WORKSPACE="App.xcworkspace"
SCHEME="App"
CONFIGURATION="Release"
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-configuration "$CONFIGURATION" \
-list
-list 的輸出應包含預期的 Scheme。接著以 JSON 輸出進行機器驗證,避免只靠人工閱讀日誌:
xcodebuild -workspace "$WORKSPACE" -list -json > xcode-list.json
python3 - <<'PY'
import json
data = json.load(open("xcode-list.json"))
schemes = data.get("workspace", {}).get("schemes", [])
if "App" not in schemes:
raise SystemExit("Required Xcode scheme is missing")
PY
若版本庫同時保留開發、預發布與正式環境入口,名稱應清楚表達用途,例如 App-Dev、App-Staging、App-Release。不要讓指令碼依照清單順序選取第一個 Scheme;新增相依套件後,順序可能改變。
逐項驗收 Build、Test 與 Archive
成功共享只解決了「看得見」的問題,尚未證明各項動作正確。建議將驗收拆成三層。
檢查解析後的建置設定
先匯出最終設定,重點核對 PRODUCT_BUNDLE_IDENTIFIER、CONFIGURATION、SDKROOT、SUPPORTED_PLATFORMS 與產出目錄:
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-configuration "$CONFIGURATION" \
-showBuildSettings > build-settings.txt
grep -E "PRODUCT_BUNDLE_IDENTIFIER|CONFIGURATION =|SDKROOT|SUPPORTED_PLATFORMS" \
build-settings.txt
這裡讀取的是 Scheme、工程設定與命令列參數合併後的結果,比直接檢查 project.pbxproj 更接近實際執行狀態。
驗證測試入口
測試階段應先建置可測試產物,再執行測試,如此可區分「編譯入口損壞」與「測試執行失敗」:
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-destination "platform=iOS Simulator,name=iPhone 16" \
build-for-testing
模擬器名稱必須取自目前機器的 xcrun simctl list devices available,不要將只存在於個人電腦上的裝置寫死。若團隊需要固定執行環境,應同時記錄 Xcode 版本與目標 OS 版本。
單獨檢查封存動作
Archive 可能採用與 Run、Test 不同的設定。先確認 .xcscheme 中 ArchiveAction 的設定,再於受控工作中執行實際封存:
xcodebuild \
-workspace "$WORKSPACE" \
-scheme "$SCHEME" \
-configuration Release \
-destination "generic/platform=iOS" \
-archivePath "$PWD/artifacts/App.xcarchive" \
archive
不要以一般 build 成功取代 Archive 驗收。資源處理、指令碼階段與封存設定,往往只會在此時暴露問題。
清除本機狀態後再次驗證
最可靠的檢查方式不是刪除 DerivedData,而是在新目錄中重新複製版本庫。這樣可同時發現未提交的 Scheme、本機符號連結、未追蹤設定與隱含相依項目:
ROOT="$(mktemp -d)"
git clone --no-local . "$ROOT/repo"
cd "$ROOT/repo"
git submodule update --init --recursive
xcodebuild -workspace App.xcworkspace -scheme App -list
若工程依賴產生步驟,應在建置前執行產生命令,並檢查產出結果是否穩定。不要將「先用 Xcode 開啟一次工程」寫進操作手冊;這類步驟無法納入無人值守工作,也會掩蓋檔案缺漏。
乾淨環境驗證至少應涵蓋:
| 檢查項目 | 通過標準 |
|---|---|
| Scheme 可見性 | xcodebuild -list -json 傳回指定名稱 |
| 檔案歸屬 | Scheme 位於 xcshareddata,且已由 Git 追蹤 |
| 建置設定 | 命令列解析結果與流水線預期一致 |
| 測試入口 | build-for-testing 能產生可測試產物 |
| 封存入口 | 通用 iOS 目標可產生 .xcarchive |
| 無互動執行 | 不需要開啟 Xcode 或讀取個人目錄 |
在合併前阻止入口漂移
Scheme 是 XML 檔案,合併衝突經常被當成一般文字衝突處理,結果可能保留名稱,卻遺失 Testable 或 BuildActionEntry。可在合併請求中加入輕量檢查:驗證共享檔案存在、禁止提交 xcuserdata,並執行 xcodebuild -list -json。
此外也要檢查 Scheme 的 Pre-actions 與 Post-actions。若指令碼引用絕對路徑、互動式 Shell 設定,或只存在於某位開發者機器上的工具,遠端執行仍會失敗。指令碼應透過 SRCROOT 等建置變數定位檔案,並在缺少相依項目時明確結束執行。
最終驗收標準很簡單:從空目錄取得版本庫,只提供文件中聲明的 Xcode 版本與命令,就能列舉 Scheme、解析設定、建置測試產物並完成封存。達到這一點後,G-Mini 上的雲端 Mac 才是在執行版本庫所定義的入口,而不是重現某台開發者電腦上的偶然狀態。
常見問題
為什麼本機 Xcode 可以執行,雲端 Mac CI 卻找不到 Scheme?
Scheme 多半仍放在 xcuserdata,而不是 xcshareddata/xcschemes。CI 使用乾淨的檢出目錄,不會取得個別開發者的 Xcode 使用者設定。
共享 Scheme 之後還要驗證 Archive 嗎?
需要。共享只代表 CI 看得到 Scheme,不代表 Release 組態、封存目標與前置腳本正確。應在乾淨檢出環境完成設定檢查與至少一次實際封存。
為下一個建置佇列準備雲端 Mac
比較兩種 M4 配置與五個節點,依實際工作週期選擇日租、週租、月租或季租。