開發任務筆記

雲端 Mac CI 檔案時間戳漂移排查實作

雲端 Mac CI 檔案時間戳漂移排查實作

程式碼沒有變更,雲端 Mac 上的第二次 Xcode 建置卻仍編譯大量目標;同一個提交換到新的工作區後,又恢復正常。這類問題不一定源自 DerivedData,也可能是原始碼、產生的檔案或快取產物帶有錯誤的修改時間。僅清除快取可以暫時消除症狀,但如果同步與還原流程沒有改變,時間戳漂移仍會再次進入工作區。

先確認是否為時間戳問題

先固定提交、Scheme、Xcode 路徑與建置參數,連續執行兩次相同的建置。第一次允許產生完整的中間產物,第二次則觀察哪些任務仍在執行。若同一批 Swift、資源或指令碼階段反覆執行,再比較乾淨工作區與重複使用工作區的結果。

排查時不要只看建置總時間。應重點記錄重複執行的輸入路徑、指令碼輸出與產生目錄,並檢查建置開始前是否有工具改寫檔案。常見跡象包括:

增量建置依賴的不只是檔案內容。輸入與輸出的時間關係一旦失真,建置系統就可能持續將已完成的任務判定為需要重做。

掃描未來時間與異常目錄

先檢查系統時間與時區,再掃描修改時間比目前時間晚五分鐘以上的檔案。五分鐘的容許範圍可以避開極短暫的擷取誤差,同時足以找出明顯的時間漂移。

#!/bin/zsh
set -euo pipefail

root="${1:-$PWD}"
limit=$(( $(date +%s) + 300 ))

find "$root" -type f -print0 |
while IFS= read -r -d '' file; do
  modified=$(stat -f '%m' "$file")
  if (( modified > limit )); then
    printf '%s\t%s\n' \
      "$(date -r "$modified" '+%Y-%m-%d %H:%M:%S %z')" \
      "$file"
  fi
done

掃描範圍至少應涵蓋原始碼、專案檔案、指令碼、資源、程式碼產生目錄,以及還原後的快取。不要一開始就掃描整個使用者目錄,否則套件管理器快取、日誌與系統檔案會產生大量雜訊。對掃描命中的檔案繼續執行 stat -x 檔案路徑,核對修改時間、變更時間與所屬磁碟區。

如果異常集中在單一目錄,通常可以直接將範圍縮小到某個下載、解壓縮、同步或程式碼產生步驟;如果異常散布在整個儲存庫,則應檢查工作區的複製方式,以及任務開始前執行的初始化指令碼。

依來源判斷修復方式

不同來源需要採取不同的處理方式,不能一律使用 touch 掩蓋問題。

現象 常見來源 建議處理
少量原始碼時間位於未來 壓縮檔或同步來源異常 重新取得檔案,並核對來源機器的時間
產生的檔案每輪都更新時間 產生器無條件覆寫 僅在內容變更後進行原子替換
快取目錄的時間關係混亂 還原時保留了錯誤的中繼資料 捨棄該批快取並重建快取鍵
整個工作區的時間接近複製時刻 複製參數改變了中繼資料 統一取出方式,不要混用多種同步策略
輸出早於輸入且長期不變 快取封裝內部時間錯誤 重新產生並驗證快取清單

對於由 Git 管理的原始碼,最穩妥的修復方式通常是先確認沒有未提交的變更,再重新取出受影響的目錄,而不是重寫整個儲存庫的時間。對於產生的檔案,應讓產生器先寫入暫存檔,比較內容後再替換:

generate_config > Config.generated.swift.tmp
if ! cmp -s Config.generated.swift.tmp Config.generated.swift; then
  mv Config.generated.swift.tmp Config.generated.swift
else
  rm Config.generated.swift.tmp
fi

如此一來,內容未變更時,原始檔案的修改時間也不會改變,依賴該檔案的編譯任務便不會被無意義地喚起。

檢查快取與同步邊界

還原快取前,應先明確定義哪些目錄可以跨任務重複使用。建置產物、模組快取與套件管理器快取的生命週期不同,不應封裝成一個無法追蹤的大型檔案。快取清單至少應記錄快取鍵、Xcode 版本、架構、產生命令與檔案數量;還原後先執行時間戳預檢,再允許建置進入主要階段。

同步工具也必須明確設定是否保留修改時間。保留時間有利於增量判斷,但來源機器的時間若有錯誤,也會原樣傳播;不保留時間則可能讓所有檔案看起來都像剛被修改。關鍵不在於固定選擇某個參數,而是讓團隊只採用一套經過驗證的策略,並將該策略寫入任務指令碼。

若程式碼透過壓縮檔交接,可先解壓縮至隔離目錄,完成未來時間掃描、檔案數量核對與 Git 狀態檢查後,再以原子方式切換至正式工作區。不要直接覆寫仍含有舊中間產物的目錄。

將時間基準納入任務入口

最終應將檢查安排在相依套件解析與 Xcode 建置之前。發現帶有未來時間的檔案時應立即失敗,並輸出有限數量的路徑;不要自動修改後繼續建置,因為自動修正會抹除故障證據。

建議為每次任務保留下列記錄:

  1. date 與 systemsetup -gettimezone 的輸出;
  2. 目前提交、Xcode 路徑與 SDK 資訊;
  3. 帶有未來時間的檔案數量及前幾筆路徑;
  4. 快取鍵與還原結果;
  5. 第一次建置與第二次建置的任務差異。

修復後,使用同一個提交連續執行三輪:第一輪建立快取,第二輪驗證增量效果,第三輪確認結果並非偶然。若第二、三輪仍重複執行相同任務,就沿著該任務的輸入檔案繼續追查,而不是再次進行全量清理。時間戳治理的目標不是讓所有檔案擁有相同時間,而是讓原始碼、產生物與快取之間維持可解釋、可複核的先後關係。

常見問題

找到未來時間檔案後,可以直接對整個儲存庫執行 touch 嗎?

不建議。這會同時改寫所有檔案的修改時間,通常會觸發大範圍重新編譯。應先確認來源,再只修復異常檔案或重新取出受影響目錄。

為什麼乾淨建置正常,增量建置卻持續變慢?

增量建置會比較既有輸出與相依狀態。未來時間輸入、系統時間回撥或帶有錯誤中繼資料的快取,都可能讓相依項目每次被判定為過期。

獨享開發環境

為下一項開發任務設定雲端 Mac

選擇 Oak M4、Oak M4 Plus 或 Oak M4 Pro,並依團隊所在地從五個實體節點中設定租用方案。

設定雲端 Mac