工程指南

雲端 Mac CI 的 Xcode Scheme 共享與入口驗收

雲端 Mac CI 的 Xcode Scheme 共享與入口驗收

團隊將工程檢出至雲端 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 statusgit 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-DevApp-StagingApp-Release。不要讓指令碼依照清單順序選取第一個 Scheme;新增相依套件後,順序可能改變。

逐項驗收 Build、Test 與 Archive

成功共享只解決了「看得見」的問題,尚未證明各項動作正確。建議將驗收拆成三層。

檢查解析後的建置設定

先匯出最終設定,重點核對 PRODUCT_BUNDLE_IDENTIFIERCONFIGURATIONSDKROOTSUPPORTED_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 配置與五個節點,依實際工作週期選擇日租、週租、月租或季租。

選擇雲端 Mac 方案