Xcode 的 project.pbxproj 檔案採用文字格式儲存專案結構,但 Xcode 在新增檔案或修改設定時,不保證項目的插入順序一致。多人協作時,即使修改不同的檔案或 target,也可能因為項目順序差異而產生 merge conflict。這些衝突往往與實際變更無關,純粹是格式問題。
我的解決方案
將 project.pbxproj 中的各個區塊按固定規則排序,確保相同內容產生相同的檔案結構。配合 git pre-commit hook,每次提交前自動排序,團隊成員的專案檔就能維持一致的順序,大幅降低無意義的衝突。
我開發了一個腳本工具來執行這個任務,也已經在多個專案上跑了好幾年,GitHub repo 放在這裡:https://github.com/chiahsien/sort-Xcode-project-file
雖然目前推崇使用 Swift Package Manager 進行模組化,Xcode 16 也引入了 buildable folders 功能來減少專案檔變更,甚至也有 Tuist 或 Xcode Gen 這類的工具來生成專案檔,但這些新技術主要針對新專案或願意大幅重構的專案。對於已經開發多年、結構複雜的舊專案,貿然改用 SPM 模組化或轉換成 folder references 風險過高。此時,這個排序工具仍是最務實的選擇,能以最小成本解決 merge conflict 問題。
排序範圍
此工具排序以下 array 結構:
children— group 內的檔案與 subgroup(目錄排在檔案前面)files— build phase 的檔案列表buildConfigurations— build configuration 列表targets— 專案 target 列表packageProductDependencies— Swift Package product dependencypackageReferences— Swift Package reference
安全性
這些 array 是宣告性內容,Xcode 透過 24 字元的十六進位 ID 參照物件,而非依賴位置。排序不影響建置行為或專案結構,僅改變檔案內的呈現順序。
不排序的區塊:PBXFrameworksBuildPhase section 的 framework 連結順序會影響符號解析,因此保持原始順序。buildPhases array 決定編譯順序,同樣不進行排序。
警告:
雖然這個工具已經在多個不同專案執行很長一段時間了,我還是強烈建議在修改之前先做好備份,才不會出現難以挽回的錯誤!
與原版的差異
本版本是 WebKit 專案的 fork,新增以下功能:
- Natural sorting:數字部分按數值比較,
file2.m排在file10.m前面,符合人類直覺 - Case-insensitive 選項:提供
--case-insensitive參數支援不分大小寫排序,預設仍為 case-sensitive 以保持原始行為 - 目錄優先排序:
childrenarray 中目錄排在檔案前面,符合檔案系統慣例 - 自動去除重複:移除重複的項目 reference
- 擴充排序範圍:包含所有
childrenarray、filesarray、targets列表、packageProductDependencies與packageReferences
使用方法
基本呼叫
perl sort-Xcode-project-file.pl path/to/Project.xcodeproj
腳本會自動找到 project.pbxproj 並就地排序。
選項
# 使用 case-insensitive sorting
perl sort-Xcode-project-file.pl --case-insensitive Project.xcodeproj
# 抑制 warning 訊息
perl sort-Xcode-project-file.pl --no-warnings Project.xcodeproj
# 顯示說明
perl sort-Xcode-project-file.pl --help
Git Hook 整合
請參考 repo 的 README