聊聊 Xcode 項目文件中的 project.pbxproj

project.pbxproj 文件被包含於 Xcode 工程文件 *.xcodeproj 之中,存儲着 Xcode 工程的各項配置參數。它本質上是一種舊風格的 Property List 文件,歷史可追溯到 NeXT 的 OpenStep。其可讀性不如 xml 和 json,蘋果卻一直沿用至今,做爲一家以創新聞名的公司可能這裏剩下的就是情懷吧。html

本文談了下 project.pbxproj 的知識,並總結了一些操做工程文件的優秀輪子,並在最後給出了本身的解決方案 pbxprojHelper (https://github.com/yulingtianxia/pbxprojHelper)。node

 

Property List 的歷史git

 

想了解 project.pbxproj 文件格式,就須要先了解 Property List。github

 

Property List 有不少種表現方式,最古老的格式就是以前提到的 NeXTSTEP 所使用的格式。那時還算是可讀性很強的,仍須要手動編輯。與 json 最明顯的差異是:數組用小括號括起來並用逗號隔開元素;字典用大括號括起來並用分號隔開鍵值對,鍵值之間用等號鏈接;二進制數據用尖括號 括起來:編程

 

數組:json

 

( "1", "2", "3" )數組

 

字典:xcode

 

{app

    "key" = "value";工具

    ...

}

 

這也是 project.pbxproj 文件中所使用的格式。

 

後來出現的 GNUstep 沿用了 NeXTSTEP 格式,並添加了對 NSValue 和 NSDate 對象的支持。到了蘋果的 Mac OS X 10.0 推出了新的 XML 格式,舊的 NeXTSTEP 被廢棄,只支持讀不支持寫。這也是爲何使用 plutil 命令或者 Cocoa 的 NSPropertyListSerialization 寫入 OpenStep 格式時會報錯:Property list format kCFProperty ListOpenStepFormat not supported for writing

 

由於 XML 語法囉嗦很佔空間,蘋果在 Mac OS X 10.2 又推出了一種新格式,將 Property List 存儲於二進制文件中。雖然在 Mac OS X 10.7 JSON 格式出現了,可是跟 Property List 不兼容。

 

因而乎 Property List 在蘋果家族的歷史上存在三種格式:OpenStep,XML 和 Binary。除了 OpenStep 被廢棄不支持寫入之外,其他格式都提供 API 支持讀寫。

 

操做 Property List 的途徑

 

Unix 的 plutil 工具提供了處理 Property list 文件的能力。 好比將 Property list 文件轉成 XML 格式:

 

plutil -convert xml1 -s -r -o project.pbxproj.xml project.pbxproj

 

-convert 選項能夠傳入的參數有: xml1, binary1 和 json。

 

固然 Cocoa 的 NSPropertyListSerialization 也提供了相似的功能,更面向對象。其實 plutil 和 NSPropertyListSerialization 底層都是調用 CoreFoundation 的CFPropertyList 相關的 API,因此功能相似。

 

使用 NSPropertyListSerialization 讀入 project.pbxproj 文件時,字典中鍵值對的順序會跟文件中原始的順序不一致。這是由於字典爲了實現快速查找會將 key 按序存儲(好比字典序或用紅黑樹排序)。用 plutil 命令將 project.pbxproj 文件轉成 xml 或 json 也會如此。

 

此外,plutil 命令也支持對某個 keypath 的增、刪、改操做。NSPropertyListSerialization 就更不用說了,在程序中隨意搞。

 

以前提到過不支持 OpenStep 寫入的問題,因此即使咱們能在內存中操做 project.pbxproj 文件,依然不能直接保存。若是本身動手寫一個 OpenStep 格式生成程序,依然沒法準確還原字典中鍵值對的順序。更況且 project.pbxproj 文件中還插入了大量加強 human-readable 的註釋,這些註釋的生成是有特殊邏輯的,這個在後面會講。

 

簡要解析 project.pbxproj 文件

 

既然表面上沒法將修改過的工程文件數據還原爲 OpenStep 格式,Xcode 又是如何『開掛』作到的呢?這就得從 project.pbxproj 文件內容提及了。

 

內容規則

 

project.pbxproj 使用 UUID 做爲交叉引用的索引,保證每一個配置信息對象的惟一性。由於 UUID 根據機器硬件和時間戳生成,避免了多人在同一時間段操做修改工程文件帶來的問題。也就是說工程中每項配置對象都有個惟一的 UUID,而後其餘配置對象想引用某個配置對象直接使用它的 UUID 便可。這就跟咱們編程時使用指針指向某個對象的地址同樣,其餘對象的屬性想引用它,只須要給屬性傳個指針地址就好了。

 

能夠把整個文件的內容想象成一個字典,字典中的 Key 按照字典序來排列。字典的第一層級總共有 5 個鍵值對,Key 分別爲:archiveVersion,classes,objectVersion,objects 和 rootObject。其中重要的 Key 是 objects 和 rootObject。

 

全部的配置對象都放在 objects 對應的 Value 中,包括跟對象(rootObject)。 objects 對應的 Value 也是一個字典,Key 都爲 UUID,Value 依然是個字典。能夠將 rootObject 的值(是一個 UUID)做爲 Key 在 objects 對應的字典中找到根對象。這個根對象的 isa 屬性爲 PBXProject(isa = PBXProject)。讀懂 project.pbxproj 的最好方式就是順着 rootObject 的各個屬性對應的 UUID 在 objects 中找到對應的對象,而後一層層看下去。這樣整個文件的配置信息存放方式就慢慢摸清了。

 

objects 中的鍵值對被分紅了若干個 section,雖然 section 的順序是 Xcode 私有 API 欽定的,但每一個 section 內部的鍵值對會根據 Key 的字典序排列。

 

每一個對象內部的屬性(也是鍵值對)會把 isa 排在最前面,其他的按照字典序排列。

 

數組內部的順序徹底按照元素內容的字典序排列。

 

下面是 objects 中 PBXNativeTarget section 的一個對象,感覺一下格式:

 

/* Begin PBXNativeTarget section */

A450185D1D9D68D60002869D /* projectTest */ = {

isa = PBXNativeTarget;

buildConfigurationList = A45018751D9D68D60002869D /* Build configuration list for PBXNativeTarget "projectTest" */;

buildPhases = (

A450185A1D9D68D60002869D /* Sources */,

A450185B1D9D68D60002869D /* Frameworks */,

A450185C1D9D68D60002869D /* Resources */,

);

buildRules = (

);

dependencies = (

);

name = projectTest;

productName = projectTest;

productReference = A450185E1D9D68D60002869D /* projectTest.app */;

productType = "com.apple.product-type.application";

};

/* End PBXNativeTarget section */

 

能夠根據 A45018751D9D68D60002869D 找到對應的 buildConfigurationList 對象的內容,因此說 project.pbxproj 使用 UUID 做爲交叉引用的索引。經過這種關係,能夠遞歸構建一張有向圖,每一個對象都是一個節點。

 

內容類型

 

在 Xcode 中能看見全部的公共配置信息都存在於 project.pbxproj 中。主要包含跟文件相關的 BuildFile,Group 和 FileReference;跟編譯相關的 BuildPhase 和 Build Configuration(List);以及一些列 Target 和 TargetDependency。

 

objects 的鍵值對根據內容類型被分紅了若干個 section,採用註釋的方式分節也使得可讀性更強。section 的數量跟工程有關,尤爲是每一個工程的 BuildPhase 和 Target 差異都很大。下面列出了一個section 列表(非完整):

 

PBXBuildFile

PBXBuildPhase

PBXAppleScriptBuildPhase

PBXCopyFilesBuildPhase

PBXFrameworksBuildPhase

PBXHeadersBuildPhase

PBXResourcesBuildPhase

PBXShellScriptBuildPhase

PBXSourcesBuildPhase

PBXContainerItemProxy

PBXFileElement

PBXFileReference

PBXGroup

PBXVariantGroup

PBXTarget

PBXAggregateTarget

PBXLegacyTarget

PBXNativeTarget

PBXProject

PBXTargetDependency

XCBuildConfiguration

XCConfigurationList

 

每一個 section 中的對象類型都是相同的,對象的類型是靠 isa 的值區分的。對象內部的屬性類型以及含義能夠參照這篇文章提供的對照表:Xcode Project File Format(http://www.monobjc.net/xcode-project-file-format.html)

 

操做 project.pbxproj 文件

 

我收集了一些能夠操做 project.pbxproj 文件的優秀輪子,原理大都是用 plutil 轉成 json 或 xml 後進行處理,不只功能很是侷限,且都沒法完美還原爲 OpenStep 格式的內容:

 

  • Xcodeproj CocoaPods 寫的 Ruby 解析庫,用於修改引入 CocoaPods 的工程文件並保存爲 XML 格式。CocoaPods 自己是很強大的,還能夠用來操做 Xcode workspaces (.xcworkspace), configuration files (.xcconfig) 和 Xcode Scheme files (.xcscheme).

  • mod-pbxproj 強大的 Python 解析庫,支持必定的修改操做,可輸出 OpenStep 格式,可是順序和註釋內容沒法完美還原,有些雞肋。

  • xUnique 用 Python 寫的統一多設備生成的 UUID 的工具,主要用途是統一工程在多設備上生成的 UUID,避免工程文件衝突。

  • pbxplorer Ruby 寫的解析庫。

  • node-xcode Cordova 基於它管理 Xcode 工程

 

不過 Xcode 能夠打開 XML 格式的 project.pbxproj,一旦在 Xcode 界面上修改工程配置就會從新將 project.pbxproj 轉成 OpenStep 風格。解鈴還須繫鈴人,通過多番對比以後發現最終仍是 Xcode 本身才能將 XML 完美還原成原來的 OpenStep 格式,且 diff 對比毫無差錯。緣由很簡單,Xcode 使用的私有 API 的導出結果是個黑盒,外界不管怎麼猜都會有瑕疵。因此仍是導出爲 XML 後手動在 Xcode 界面中觸發下吧。既然這樣的話,若是可以簡單高效地生成出 XML 文件做爲工程文件就行了。基於此想法我開發了一款叫作 pbxprojHelper 的 Mac App:

操做簡單粗暴:

 

  • 選擇一個工程文件而後內容會自動解析在下面的 Outline 列表中,Filter 輸入框便於過濾查看內容。

  • 單擊 Outline 列表中的文字便可複製內容到剪貼板,雙擊複製整個keypath!

  • 對 project.pbxproj 文件的增刪改操做都配置在 json 文件中,每次想對工程進行修改只需選擇對應的 json 配置文件而後點擊 「Apply」 便可完成寫入替換哦!

  • 不當心誤操做的話還能夠點 「Revert」 回滾到上個版本哦!

  • 什麼?懶得寫 json 配置文件?下面這個附帶的 json 配置生成器能夠幫你直接生成一個哦!使用 ⇧⌘0 快捷鍵便可召喚此神器!選擇兩個工程文件和 json 保存路徑後輕輕一點 「Generate」 就搞定咯:

     

因此處理工程文件的正確姿式是:

 

  • 拷貝出一份原始的 project.pbxproj 文件

  • 在 Xcode 界面上修改工程配置,好比修改編譯選項,使用本身的證書等

  • 使用 pbxprojHelper 的 JSON Configuration Generator 來對比修改後的工程文件和原始的工程文件,自動生成 JSON 配置文件

  • 之後想要在工程文件上施加本身的修改時,只須要應用以前生成好的 JSON 配置文件便可

 

pbxprojHelper 的優點在於能夠自由地增刪改查任意屬性,原生 UI 下降了使用門檻。功能強大的同時人性化的設計使得更快捷瀏覽工程文件中的內容。無需寫任何代碼便可一鍵配置本身想要的工程文件

 

此外還提供了命令行工具 pbxproj, 它具備 pbxprojHelper.app 具備的大部分功能:

 

Usage: pbxproj [command_option] file

Command options are (-convert is the default):

-compare modified_file -o path          compare modified property list file with property list file and generate a json result at the given path

-apply json_file                        apply a json file on property list file

-revert                                 revert property list file to latest backup

-convert                                rewrite property list files in xml format

 

可使用 pbxproj 搭配 DevToolsCore 私有 framework 來完成修改工程文件並轉化成 OpenStep 格式的一條龍自動化程序。

 

你能夠在 GitHub 上下載最新的 Release 版。或者在 App Store 中下載:https://itunes.apple.com/cn/app/pbxprojhelper/id1160801848?mt=12

 

本項目徹底手擼,沒依賴上面提到的任何輪子😃。但因爲使用 Swift 3 來開發,因此最低只能支持 macOS 10.12 系統。

 

想了解更多信息請查看 GitHub 主頁:https://github.com/yulingtianxia/pbxprojHelper

 

Reference

 

https://en.wikipedia.org/wiki/Property_list

http://www.monobjc.net/xcode-project-file-format.html

http://stackoverflow.com/questions/1452707/library-to-read-write-pbxproj-xcodeproj-files

https://github.com/CocoaPods/Xcodeproj/issues/52

相關文章
相關標籤/搜索