Uncrustifying Objective-C Source in Xcode 4

K&R. Allman. BSD. Crazy Whitesmiths. No, it’s not a nu-punk-alt-country rock band, but a selection of common (well, sort of) code indent styles. Much blood has been spilled arguing over the merits of each.

I’m not brave enough to venture into that battlefield, but I can at least point the weary foot soldier towards the tent of General Uncrustify and his Marvellous Xcode Contraption.

Xcode Scripting

Previous versions of Xcode allowed fairly powerful scripting. Sadly that feature has gone. It has been replaced by Behaviors instead. While they aren’t quite as flexible, they can be used to run a shell script from wherever your project has been saved.

First download the wrapper script I created for uncrustify. You can either save the script from your browser by visiting the gist page, or you can clone the gist with git:

$ git clone git://gist.github.com/1641451.git nah_xcode_uncrustify

If you download the script from your browser, don’t forget to make it executable:

$ chmod a+x nah_xcode_uncrustify.rb

Have a quick look over the script to make sure I’m not doing anything evil, like tarring all your source and emailing it to my swiss github account. Next, make sure you have uncrustify installed. If you have the nifty package manager Homebrew, then installing is as simple as:

$ brew install uncrustify

If you have another package manager installed such as MacPorts, then you can probably use that to install uncrustify. Otherwise you’ll have to build it from source and put the executable in your /usr/bin folder. See https://github.com/bengardner/uncrustify for more details.

Now add a new Behavior to Xcode. From the Xcode menu, select ”Edit Behaviors…”:

Click the + icon in the list to add a new Behavior, give it a name and scroll to the ”Run” script check box. Tick the check box and then select ”Choose script…” from the drop down list next to it:

Navigate to wherever you have the nah_xcode_uncrustify.rb script and select it.

Optionally, you can assign a keyboard shortcut to the Behavior by double clicking on the command symbol next to the Behavior name. In the screen above I’ve assigned alt-shift-f to the Behavior.

Now for a test run. Open an Objective-C project. Make sure you have it safely under version control. This is very important because uncrustify has been set to run without creating a backup of the source files. If you don’t have your source under version control and you hate what it does to your files, then there’s no going back.

Save all the open files by pressing alt-command-s (the script modifies the files on disk, you need to save first) and be sure to stage any new source file changes or whatever the equivalent is in the VCS you are using. Now run uncrustify either by selecting the Behavior you just created from the Behaviors menu, or using the keyboard shortcut you assigned.

Depending on the size of the project it may take a few moments to run. When it’s finished, you should see that the source controlled files are marked as modified and any open ones should reload with nice uncrusted spacing.

If you’re really eager to have formatted code, you can edit the ”Build starts” Behavior in Xcode and tell it to run nah_xcode_uncrustify.rb before each build.

I Hate Your Uncrusting Config, How Do I Fix It?

The first time the script is run, it creates a file called .nah_xcode_uncrustify.cfg in your home folder. To change or tweak how nah_xcode_uncrustify.rb tells uncrustify to format your source, simply edit that config file.

NOTE: the config file starts with a . indicating it is hidden, so by default Finder won’t show it. You can either open it from Terminal or else set Finder to show hidden files (how to).

The config file is fairly well documented. If it all goes hideously wrong during editing, you can always delete .nah_xcode_uncrustify.cfg from your home folder then re-reun the uncrustifying Behavior. A new default config file will be generated.

UniversalIndentGUI

If tweaking text config files isn’t your thing, you can use a great utility called UniversalIndentGUI to modify the config file. It allows you to play with values and preview the changes on the currently loaded source file.

To use it, simply download the OS X version from the UniversalIndentGUI homepage, chuck it in your Applications folder, and then when you run it, select uncrustify as the code formatter:

You can use the ”Live Indent Preview” check box to toggle between the original file and the settings you’ve selected.

If you want to use the default config from the script as a starting point, you have to tell uncrustify to modify it a bit first. The following command should do the trick:

$ uncrustify -c ~/.nah_xcode_uncrustify.cfg --universalindent > \
    ~/uigui_nah_xcode_uncrustify.ini

That will create or overwrite a file in your home directory called uigui_nah_xcode_uncrustify.ini. Import that into UniversalIndentGUI and fiddle with it as much as you want.

Whether you start from scratch or tweak the default config created by the script, you will have to save it to your home folder as a file called .nah_xcode_uncrustify.cfg. When you do, the app will ask you if you want to save a file starting with . (you do) and if you want to replace the existing .nah_xcode_uncrustify.cfg (which you also want to do).

As with manual editing, if it all goes wrong you can delete .nah_xcode_uncrustify.cfg from your home folder then re-reun the uncrustifying Behavior to recreate the default config.

Script Miscellanea

nah_xcode_uncrustify.rb has peculiarities.

It only tries to format C, C++, Objective-C, Objective-C++ and header files (*.h).

What the script considers to be the main source folder depends on the name of the project. If the project is called Bob, then the main source folder is assumed to be called Bob, and is inside the same folder containing Bob.xcodeproj. This allows you to place third party submodules outside of that folder, preventing the script from reformatting them. This is especially important for git users as modifying git submodule folders can cause issues for other people trying to checkout the code.

The script tries to be clever about the language type of header files. Objective-C, Objective-C++, C and C++ all happily use *.h files as header files. The script will search for a source file with the same name as the header file and use that extension as the language setting for the header. At the moment, it searches the main source folder for the associated source file. If it can’t find one it treats it as an Objective-C header file on the assumption that it’s probably a protocol.

The default formatting for C and C++ files may be a bit shonky and inconsistent compared to Objective-C files. I spend most of my time in Objective-C land these days, so didn’t have many test files handy.

Comments