ZergXcode
ZergXcode is a tool and library for performing automated modifications to Xcode project files. It can be used to work around Xcode’s limitations, such as
- the cumbersome UI for assigning files to the main target vs the test target
(
zerg-xcode retarget
) - the impossibility to easily drop other people’s code in your project
(
zerg-xcode import
) - a problem that annoys you enough to learn the API and write a ruby script
User Instructions
If you’re on OSX Leopard, you already have most of the infrastructure in place. Install the gem by typing the following in Terminal.
sudo gem install zerg_xcode
Stay in Terminal and take a look at the available commands.
zerg-xcode help
Then start playing with your code project.
zerg-xcode help
zerg-xcode help ls
zerg-xcode ls ProjectName
You can learn all about importing projects in this blog post
ZergSupport is an example of a project that you can import. You can read more about it in this blog post
Developer Notes
The rest of the file is useful if you’re considering tweaking the code. Here are a few reasons to use this library:
- ruby and rubygems come pre-installed on every Mac
- full test coverage, so it’s high quality and easy to refactor
- MIT license, so your boss will not hate you
- all commands are plug-ins, so all the infrastructure is in place
Getting started.
It’s recommended to start by playing with the API in
lib/zerg_xcode/shortcuts.rb
moonstone:ZergSupport victor$ zerg-xcode irb ZergSupport
>> $p
*snip*
>> $p.attrs # shows the names of all the attributes of the Xcode object
=> ["isa", "buildConfigurationList", "hasScannedForEncodings", "targets", "projectDirPath", "compatibilityVersion", "projectRoot", "mainGroup"]
>> $p['targets'].first.attrs # navigates a list
=> ["name", "isa", "productType", "buildConfigurationList", "productReference", "productName", "buildRules", "dependencies", "buildPhases"]
>> $p['targets'].first['name'] # inspects attributes
=> "ZergSupport"
>> $p['targets'].map { |target| target['name'] } # more attribute inspection
=> ["ZergSupport", "ZergTestSupport", "ZergSupportTests"]
>> $p.all_files.length # call method in PBXProject
=> 116
You can up load your favorite Xcode project in irb, and understand the object graph. Once you feel you have a decent grasp, you can start experimenting with changing the graph, as shown below.
moonstone:ZergSupport victor$ zerg-xcode irb ZergSupport
>> $p['targets'][2]['name']
=> "ZergSupportTests"
>> $p['targets'][2]['name'] = 'ZergSupportTestz'
=> "ZergSupportTestz"
>> $p.save! # the project remembers where it was loaded from
=> 54809
>> quit # now load the project in Xcode and see the change
Plug-ins Extend the Command Set
The set of commands accepted by the zerg-xcode
tool can be extended
by adding a plug-in, which is nothing but a ruby file that exists in
lib/zerg_xcode/plugins
. An easy example to get you started can be
found at lib/zerg_xcode/plugins/ls.rb
. The code in there should
help you if you want to write your own command-line tool as well.
Plug-ins must implement the methods run(args)
(called when the
command is executed) and help
(called when the user needs help on
your plug-in), which must return a Hash with the keys :short
and
:long
.
If you write a plug-in that seems even remotely useful, please don’t be shy and send a Pull Request on Github.
Encoding and Decoding Files
The code is in lib/zerg_xcode/file_format
. You can safely ignore it
unless you’re handling a new file format, or you want to make the decoding
better.
A .pbxproj file is decoded by the chain Lexer → Parser → Archiver.unarchive into an object graph. An object graph is encoded back into a .pbxproj by the chain Archiver.archive → Encoder.
The decoding process discards comments and the order of objects in the graph, so an encoded .pbxproj will not be the same as the original. However, Xcode will happily open an encoded file, and that’s all that matters.
Xcode Object Graph
Objects in the Xcode object graph are represented by XcodeObject instances. The
code for XcodeObject is in lib/zerg_xcode/objects/xcode_object.rb
.
Xcode objects use the ‘isa’ attribute to indicate their class.
XcodeObject.new
implements isa-based polymorphism as follows: if
ZergXcode::Objects
has a class with the same name as the ‘isa’
property, that class is instantiated instead of XcodeObject.new
.
So, you can add magic methods to Xcode objects by implementing them in
XcodeObject subclasses contained in the ZergXcode::Objects
module.
By convention, these classes are implemented in
lib/zerg_xcode/objects
.
XcodeObject stores the attributes of the original object, and also keeps track of subtle meta-data from the original objects, such as the object version and archive identifier. This is done so modified Xcode files resemble the originals as much as possible.
Testing
The test
directory contains a parallel tree to
lib/zerg_xcode
. Coverage must remain excellent, because this is a
tool that operates on developers’s hard work, which may not be protected by
version control.