同一台雲端 Mac 持續執行 React Native 建置時,最難追查的往往不是編譯錯誤,而是「程式碼明明已修改,產出物卻像完全沒變」。在本機重新執行一切正常,持續整合任務卻偶爾仍引用舊模組;刪除整個儲存庫後,問題暫時消失,幾天後又再次出現。根本原因通常是四層狀態沒有妥善分離:Git 工作區、Watchman 監聽、Metro 轉換快取,以及 Xcode DerivedData。
先確認污染發生在哪一層
遇到異常時,不要立即刪除所有快取。全面清理會掩蓋故障來源,也會拖慢後續建置。先用一項最小變更判斷問題邊界,例如修改只會進入開發套件的字串,再分別檢查檔案內容、Metro 模組圖與最終應用程式產出物。
建立任務證據
每次任務至少應記錄提交雜湊、工作區絕對路徑、Node 與 Xcode 版本、啟動 Metro 的命令,以及 DerivedData 路徑。也應保留下列命令的輸出:
set -euo pipefail
printf 'commit=%s\n' "$(git rev-parse HEAD)"
printf 'workspace=%s\n' "$PWD"
node --version
xcodebuild -version
watchman watch-list || true
find "$PWD" -maxdepth 2 -name metro.config.js -print
如果提交與原始檔都正確,但 JavaScript 套件仍是舊版,應優先檢查 Metro。若封存檔中的原生程式碼沒有更新,則檢查 DerivedData、建置設定,以及實際開啟的工作區。若 Watchman 回報錯誤或監聽到上層目錄,應先處理監聽邊界。
清理是一種修復動作,不是診斷結論。先確認哪一層失效,再刪除該層的狀態。
為每個任務配置獨立工作區
平行任務不可在同一個簽出目錄中切換分支,也不要把多個任務放在同一個由 Watchman 監聽的上層目錄中。建議讓路徑包含管線編號與任務編號,例如 /Users/ci/work/4821/ios-release。任務結束後,整個目錄便可獨立封存或刪除。
工作區必須是實際目錄,而不是目標不斷變更的符號連結。某些指令碼會用固定路徑指向「目前建置」,但 Watchman 與工具鏈可能保留先前解析出的舊路徑。結果是命令看似已進入新目錄,監聽卻仍落在舊的簽出目錄。
JOB_ROOT="/Users/ci/work/${PIPELINE_ID}/${JOB_ID}"
WORKSPACE="${JOB_ROOT}/repo"
DERIVED_DATA="${JOB_ROOT}/DerivedData"
METRO_CACHE="${JOB_ROOT}/metro-cache"
mkdir -p "$WORKSPACE" "$DERIVED_DATA" "$METRO_CACHE"
export TMPDIR="${JOB_ROOT}/tmp/"
mkdir -p "$TMPDIR"
目錄名稱只使用穩定的 ASCII 字元,避免空格與動態符號連結。每個任務各自使用一個根目錄,也能讓磁碟用量與清理責任更容易稽核。
限縮 Watchman 的監聽範圍
進入工作區後執行 watchman watch-project "$WORKSPACE",並確認傳回的 watch 是否正是預期的專案根目錄。如果監聽根目錄被提升到共用的上層目錄,通常表示儲存庫邊界不明確,或上層目錄中存在會影響辨識結果的設定。
在儲存庫根目錄放置 .watchmanconfig,排除不會參與 JavaScript 相依性圖的大型目錄:
{
"ignore_dirs": [
".git",
"DerivedData",
"build",
"artifacts"
]
}
修改此檔案後,應重新建立目前專案的監聽。不要在執行平行任務的機器上習慣性執行 watchman watch-del-all,因為它會一併刪除其他任務的監聽。較安全的做法是讓每個工作區都成為獨立的監聽根目錄,並在任務結束時只執行:
watchman watch-del "$WORKSPACE" || true
如果此命令提示目標不是監聽根目錄,應先查看 watchman watch-list,不要猜測路徑後再批次刪除。
明確管理 Metro 與 Xcode 快取
Metro 快取應與任務或程式碼基準綁定,不能依賴系統暫存目錄中來源不明的歷史檔案。啟動指令碼必須將快取位置作為明確參數傳入;具體參數名稱可能隨 Metro 版本而異,應以專案鎖定版本的說明輸出為準。無論使用命令參數還是專案設定,最終目標都是讓快取存放在 ${METRO_CACHE},而不是由多個任務共用預設目錄。
--reset-cache 只應在確認轉換快取損壞後,用於單次復原。如果每個任務都需要重設快取,代表隔離方案尚未完成。Xcode 也應比照辦理,將 DerivedData 固定存放在任務目錄中:
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Release \
-derivedDataPath "$DERIVED_DATA" \
build
Pods、Swift 套件與 JavaScript 相依項目可以使用經過驗證的下載快取,但「下載快取」與「建置狀態」必須分開。前者可以依鎖定檔鍵值重複使用,後者則應由單一任務獨占。還原快取後,至少要核對鎖定檔雜湊,不能只憑分支名稱判定命中。
以分層清理取代一鍵重設
發生異常時,應從影響最小的層級開始處理。先終止目前的 Metro 程序,刪除該任務的 Metro 快取並重新啟動;如果仍有異常,再移除目前工作區的 Watchman 監聽並重新註冊。只有原生編譯結果不一致時,才刪除該任務的 DerivedData。最後才考慮重新簽出儲存庫。
清理前,請確認沒有任何程序仍占用相關目錄:
pgrep -af "$WORKSPACE" || true
lsof +D "$JOB_ROOT" 2>/dev/null | head -n 50 || true
任務結束指令碼還應驗證工作區路徑位於預期的根目錄下,避免變數為空時誤刪使用者目錄。可以使用 case 對路徑前綴執行允許清單檢查,再執行 rm -rf -- "$JOB_ROOT"。
驗收時,連續執行兩次相同提交。第二次可以重複使用下載快取,但工作區、Metro 狀態與 DerivedData 仍須維持任務層級的邊界。如果兩次產出物不同,應先比較產生的檔案、環境變數與建置日誌,不要立即擴大清理範圍。這樣建立的流程不只能修復一次性的快取問題,也能留下足夠證據,說明下一次偏差的來源。
常見問題
每次建置都需要使用 Metro 的 reset-cache 嗎?
不需要。一般任務應使用獨立快取目錄;只有確認模組圖或轉換快取損壞時,才針對該任務執行重設。
多個 React Native 任務可以共用 Watchman 監聽根目錄嗎?
不建議。並行任務應使用互不巢狀的工作區,分別註冊監聽根目錄,結束時只移除自己建立的監聽。
為下一次 Xcode 建置選擇持續在線的雲端 Mac
確認晶片、記憶體、儲存空間、計費週期及節點區域後,即可進入設定流程。實際可用狀態以控制台即時回傳為準。