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 內的檔案與 subgroupbuildConfigurations— build configuration 列表targets— 專案 target 列表packageProductDependencies— Swift Package product dependencypackageReferences— Swift Package referencefiles— build phase 的檔案列表
Section 區塊
PBXFileReference— 檔案 referencePBXBuildFile— build filePBXGroup— group 定義PBXVariantGroup— 本地化檔案 groupPBXReferenceProxy— 外部專案 referencePBXContainerItemProxy— container proxyPBXTargetDependency— target dependencyXCBuildConfiguration— build configurationXCConfigurationList— configuration list
安全性
這些區塊是宣告性內容,Xcode 透過 24 字元的十六進位 ID 參照物件,而非依賴位置。排序不影響建置行為或專案結構,僅改變檔案內的呈現順序。
不排序的區塊:PBXFrameworksBuildPhase 的 framework 連結順序會影響符號解析,因此保持原始順序。buildPhases array 決定編譯順序,同樣不進行排序。
警告:
雖然這個工具已經在多個不同專案執行很長一段時間了,我還是強烈建議在修改之前先做好備份,才不會出現難以挽回的錯誤!
與原版的差異
本版本是 WebKit 專案的 fork,新增以下功能:
- Natural sorting:數字部分按數值比較,
file2.m排在file10.m前面,符合人類直覺 - Case-insensitive 選項:提供
--case-insensitive參數支援不分大小寫排序,預設仍為 case-sensitive 以保持原始行為 - 更多區塊排序:原版僅排序部分 section,本版涵蓋所有安全的宣告性區塊
- 自動去除重複:移除重複的項目 reference
- 擴充排序範圍:包含
mainGroup的頂層目錄與檔案、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