解析Xcode项目文件中的project.pbxproj:Object-C实现

解析project.pbxproj文件
本文介绍了Xcode项目的配置文件project.pbxproj的基本结构和内容组织方式,包括UUID的使用和配置信息的存储机制。同时提供了多种解析此文件的方法,如使用plutil工具、Swift语言库、C#实现的XUPorter等。

project.pbxproj介绍

        project.pbxproj 文件被包含于 Xcode 工程文件 *.xcodeproj 之中,存储着 Xcode 工程的各项配置参数。它本质上是一种旧风格的 Property List 文件,历史可追溯到 NeXT 的 OpenStep。其可读性不如 xml 和 json,苹果却一直沿用至今,作为一家以创新闻名的公司可能这里剩下的就是情怀吧。

       project.pbxproj 使用 UUID 作为交叉引用的索引,保证每个配置信息对象的唯一性。因为 UUID 根据机器硬件和时间戳生成,避免了多人在同一时间段操作修改工程文件带来的问题。也就是说工程中每项配置对象都有个唯一的 UUID,然后其他配置对象想引用某个配置对象直接使用它的 UUID 即可。这就跟我们编程时使用指针指向某个对象的地址一样,其他对象的属性想引用它,只需要给属性传个指针地址就行了。

        可以把整个文件的内容想象成一个字典,字典中的 Key 按照字典序来排列。字典的第一层级总共有 5 个键值对,Key 分别为:archiveVersionclassesobjectVersionobjectsrootObject。其中重要的 Key 是 objectsrootObject

        所有的配置对象都放在 objects 对应的 Value 中,包括跟对象(rootObject)。objects 对应的 Value 也是一个字典,Key 都为 UUID,Value 依然是个字典。可以将 rootObject 的值(是一个 UUID)作为 Key 在objects 对应的字典中找到根对象。这个根对象的 isa 属性为 PBXProjectisa = PBXProject)。读懂 project.pbxproj 的最好方式就是顺着rootObject 的各个属性对应的 UUID 在 objects 中找到对应的对象,然后一层层看下去。这样整个文件的配置信息存放方式就慢慢摸清了。

objects 中的键值对被分成了若干个 section,虽然 section 的顺序是 Xcode 私有 API 钦定的,但每个 section 内部的键值对会根据 Key 的字典序排列。

        每个对象内部的属性(也是键值对)会把 isa 排在最前面,其余的按照字典序排列。

        数组内部的顺序完全按照元素内容的字典序排列。

        关于project.pbxproj文件的详细格式介绍: Xcode Project File Format


如何解析project.pbxproj文件呢

1. 直接使用MAC提供的property plist文件解析工具

plutil工具:        plutil(1) Mac OS X Manual Page

plutil 工具提供了处理 Property list 文件的能力。 比如将 Property list 文件转成 XML 格式:

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

-convert 选项可以传入的参数有: xml1, binary1 和 json。

2.  Swift语言解析

pbxprojHelper--Xcode工程文件助手

3. XUPorter开源库

 Unity编译iOS工程的自动化配置(XUPorter)    XUPorter里会去解析project.pbxproj文件,他是使用C#实现的。

4. 其他一些脚本语言实现的开源库

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 工程

目前没找到有效的Object-C实现的三方库,如果谁有找到的话,可以告知一下


Object-C解析project.pbxproj


参考:

Xcode Project File Format

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

iOS 开发 xcode中的project.pbxproj--深入剖析 

xcode XCBuildConfiguration自动配置


Xcode16中创建的Framework项目添加Podfile并执行pod install首次能够成功,后续清理再次执行pod install会报错:### Error ``` RuntimeError - [Xcodeproj] Type checking error: got `PBXHeadersBuildPhase` for attribute: Attribute `buildPhase` (type: `to_one`, classes: `["PBXSourcesBuildPhase", "PBXCopyFilesBuildPhase"]`, owner class: `PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet`) - 63B91FF92E862DCC00404248 <Nanaimo::Dictionary {"isa"=>"PBXHeadersBuildPhase", "buildActionMask"=>"2147483647", "runOnlyForDeploymentPostprocessing"=>"0", "files"=>[]}> /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project/object_attributes.rb:142:in `validate_value' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project/object_attributes.rb:359:in `block in has_one' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project/object_attributes.rb:104:in `set_value' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project/object.rb:291:in `block in configure_with_plist' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project/object.rb:287:in `each' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project/object.rb:287:in `configure_with_plist' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project.rb:272:in `new_from_plist' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project/object.rb:350:in `object_with_uuid' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project/object.rb:300:in `block (2 levels) in configure_with_plist' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project/object.rb:299:in `each' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project/object.rb:299:in `block in configure_with_plist' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project/object.rb:296:in `each' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project/object.rb:296:in `configure_with_plist' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project.rb:272:in `new_from_plist' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project/object.rb:350:in `object_with_uuid' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project/object.rb:300:in `block (2 levels) in configure_with_plist' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project/object.rb:299:in `each' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project/object.rb:299:in `block in configure_with_plist' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project/object.rb:296:in `each' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project/object.rb:296:in `configure_with_plist' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project.rb:272:in `new_from_plist' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project/object.rb:350:in `object_with_uuid' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project/object.rb:290:in `block in configure_with_plist' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project/object.rb:287:in `each' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project/object.rb:287:in `configure_with_plist' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project.rb:272:in `new_from_plist' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project.rb:213:in `initialize_from_file' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.27.0/lib/xcodeproj/project.rb:113:in `open' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/cocoapods-deintegrate-1.0.5/lib/cocoapods/command/deintegrate.rb:40:in `validate!' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/claide-1.1.0/lib/claide/command.rb:333:in `run' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/cocoapods-1.16.2/lib/cocoapods/command.rb:52:in `run' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/cocoapods-1.16.2/bin/pod:55:in `<top (required)>' /Users/huang/.rbenv/versions/3.3.0/bin/pod:25:in `load' /Users/huang/.rbenv/versions/3.3.0/bin/pod:25:in `<main>' ``` ――― TEMPLATE END ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― [!] Oh no, an error occurred. Search for existing GitHub issues similar to yours: https://github.com/CocoaPods/CocoaPods/search?q=%5BXcodeproj%5D+Type+checking+error%3A+got+%60PBXHeadersBuildPhase%60+for+attribute%3A+Attribute+%60buildPhase%60+%28type%3A+%60to_one%60%2C+classes%3A+%60%5B%22PBXSourcesBuildPhase%22%2C+%22PBXCopyFilesBuildPhase%22%5D%60%2C+owner+class%3A+%60PBXFileSystemSynchronizedGroupBuildPhaseMembershipExceptionSet%60%29+-+63B91FF92E862DCC00404248+%3CNanaimo%3A%3ADictionary+%7B%22isa%22%3D%3E%22PBXHeadersBuildPhase%22%2C+%22buildActionMask%22%3D%3E%222147483647%22%2C+%22runOnlyForDeploymentPostprocessing%22%3D%3E%220%22%2C+%22files%22%3D%3E%5B%5D%7D%3E&type=Issues If none exists, create a ticket, with the template displayed above, on: https://github.com/CocoaPods/CocoaPods/issues/new Be sure to first read the contributing guide for details on how to properly submit a ticket: https://github.com/CocoaPods/CocoaPods/blob/master/CONTRIBUTING.md Don't forget to anonymize any private data! Looking for related issues on cocoapods/cocoapods... Searching for inspections failed: undefined method `map' for nil
最新发布
10-11
pod install报错:Analyzing dependencies Inspecting targets to integrate CDN: trunk Relative path: CocoaPods-version.yml exists! Returning local because checking is only performed in repo update ――― MARKDOWN TEMPLATE ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― ### Command ``` /Users/huang/.rbenv/versions/3.3.0/bin/pod install --verbose ``` ### Report * What did you do? * What did you expect to happen? * What happened instead? ### Stack ``` CocoaPods : 1.15.2 Ruby : ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [x86_64-darwin23] RubyGems : 3.5.3 Host : macOS 15.0.1 (24A348) Xcode : 16.0 (16A242d) Git : git version 2.39.5 (Apple Git-154) Ruby lib dir : /Users/huang/.rbenv/versions/3.3.0/lib Repositories : trunk - CDN - https://cdn.cocoapods.org/ ``` ### Plugins ``` cocoapods-deintegrate : 1.0.5 cocoapods-plugins : 1.0.0 cocoapods-search : 1.0.1 cocoapods-trunk : 1.6.0 cocoapods-try : 1.2.0 ``` ### Podfile ```ruby # Uncomment this line to define a global platform for your project # platform :ios, '14.0' target 'HXGPSLibrary' do use_frameworks! # 添加GZIP pod 'GZIP', '~> 1.3.0' target 'HXGPSLibraryTests' do inherit! :search_paths end end ``` ### Error ``` RuntimeError - `PBXGroup` attempted to initialize an object with unknown ISA `PBXFileSystemSynchronizedRootGroup` from attributes: `{"isa"=>"PBXFileSystemSynchronizedRootGroup", "exceptions"=>["63B920102E862DCC00404248"], "path"=>"HXGPSLibrary", "sourceTree"=>"<group>"}` If this ISA was generated by Xcode please file an issue: https://github.com/CocoaPods/Xcodeproj/issues/new /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.0/lib/xcodeproj/project/object.rb:359:in `rescue in object_with_uuid' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.0/lib/xcodeproj/project/object.rb:349:in `object_with_uuid' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.0/lib/xcodeproj/project/object.rb:300:in `block (2 levels) in configure_with_plist' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.0/lib/xcodeproj/project/object.rb:299:in `each' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.0/lib/xcodeproj/project/object.rb:299:in `block in configure_with_plist' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.0/lib/xcodeproj/project/object.rb:296:in `each' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.0/lib/xcodeproj/project/object.rb:296:in `configure_with_plist' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.0/lib/xcodeproj/project.rb:272:in `new_from_plist' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.0/lib/xcodeproj/project/object.rb:350:in `object_with_uuid' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.0/lib/xcodeproj/project/object.rb:290:in `block in configure_with_plist' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.0/lib/xcodeproj/project/object.rb:287:in `each' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.0/lib/xcodeproj/project/object.rb:287:in `configure_with_plist' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.0/lib/xcodeproj/project.rb:272:in `new_from_plist' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.0/lib/xcodeproj/project.rb:213:in `initialize_from_file' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/xcodeproj-1.25.0/lib/xcodeproj/project.rb:113:in `open' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/installer/analyzer.rb:1194:in `block (2 levels) in inspect_targets_to_integrate' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/installer/analyzer.rb:1193:in `each' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/installer/analyzer.rb:1193:in `block in inspect_targets_to_integrate' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/user_interface.rb:64:in `section' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/installer/analyzer.rb:1188:in `inspect_targets_to_integrate' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/installer/analyzer.rb:107:in `analyze' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/installer.rb:422:in `analyze' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/installer.rb:244:in `block in resolve_dependencies' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/user_interface.rb:64:in `section' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/installer.rb:243:in `resolve_dependencies' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/installer.rb:162:in `install!' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/command/install.rb:52:in `run' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/claide-1.1.0/lib/claide/command.rb:334:in `run' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/lib/cocoapods/command.rb:52:in `run' /Users/huang/.rbenv/versions/3.3.0/lib/ruby/gems/3.3.0/gems/cocoapods-1.15.2/bin/pod:55:in `<top (required)>' /Users/huang/.rbenv/versions/3.3.0/bin/pod:25:in `load' /Users/huang/.rbenv/versions/3.3.0/bin/pod:25:in `<main>' ``` ――― TEMPLATE END ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― [!] Oh no, an error occurred. Search for existing GitHub issues similar to yours: https://github.com/CocoaPods/CocoaPods/search?q=%60PBXGroup%60+attempted+to+initialize+an+object+with+unknown+ISA+%60PBXFileSystemSynchronizedRootGroup%60+from+attributes%3A+%60%7B%22isa%22%3D%3E%22PBXFileSystemSynchronizedRootGroup%22%2C+%22exceptions%22%3D%3E%5B%2263B920102E862DCC00404248%22%5D%2C+%22path%22%3D%3E%22HXGPSLibrary%22%2C+%22sourceTree%22%3D%3E%22%3Cgroup%3E%22%7D%60%0AIf+this+ISA+was+generated+by+Xcode+please+file+an+issue%3A+https%3A%2F%2Fgithub.com%2FCocoaPods%2FXcodeproj%2Fissues%2Fnew&type=Issues If none exists, create a ticket, with the template displayed above, on: https://github.com/CocoaPods/CocoaPods/issues/new Be sure to first read the contributing guide for details on how to properly submit a ticket: https://github.com/CocoaPods/CocoaPods/blob/master/CONTRIBUTING.md Don't forget to anonymize any private data! Looking for related issues on cocoapods/cocoapods... Searching for inspections failed: undefined method `map' for nil
09-27
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值