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.