A sensible way to increment bundle version (CFBundleVersion) in Xcode

tl;dr: Automatically set your app’s bundle version based on the number of git commits up to that point. Skip to the code!

The Problem
When I started working with Feathr over a year ago, one of the first issues I ran into was what to use as the bundle version (what I often refer to as the ‘build number’) in our apps. Originally, we were ignoring it until we had to change it – whenever it was time to submit a new version of an app, the new build number had to be higher than the previous submission. Pretty soon this got annoying, and I had the feeling we weren’t using the field to its full potential.

Research + Tactics
With a bit of Googling, I found a few ways people were automating changing the bundle version. Some people were auto-incrementing the field every time a build was run, but I might build my project a hundred times in a day if I’m trying to work out a small UI issue! That would quickly put my build number into the thousands, and if I continued to work on a project for any length of time it could become unnecessarily large. Plus, that number in itself would be useless to me, and I like my version numbers to mean something. Some SVN users put the current repository revision number as the build number… now that was something! The build number would point to something discrete – a specific revision of code – and wouldn’t change unnecessarily. If someone was having a problem with the app, all the developer needs is the build number – easily obtainable in tools like Crashlytics and easy to show somewhere in-app – to know the exact revision that the user is working with. This is a big win both during testing and when live apps are having issues.

The Snags
Alright, that is it, that is what I want to do. BUT WAIT… we use git for our source control. I won’t go into why I think git is superior to SVN (plenty of others have argued both sides of that), but the salient issue is that git, unlike SVN, does not give perfect integer revision numbers that increment on each commit. Instead, git hashes the commit data and uses that as a unique identifier of the commit. That means one commit could be 8d844d8dce7fbd0fe5b2bf8232ff1b82c8244648 and the next f1728db451a8a409215fa450d90d145ee77f9a4c, which is incomprehensible AND unusable as a bundle version.

The Compromise
Why not make a virtual “revision number” based on the number of commits that exist on a certain branch? Since I base all my work on a ‘development’ branch, I can simply have git count the number of commits on development and use that as my build number. I’m anchoring it to a specific branch so that the revision number is consistent: even if I check out another branch – to work on a new feature, for example – the number always points to the same commit and won’t differ between branches. This of course requires the branch used for counting to be long-lasting, and it’s only really useful if it’s where you distribute most of your builds from.

The Paradox (+ the fix)
One more problem: if you change the build number every time you add a commit (and you’re tracking the file that holds said build number in git), you’ll infinitely recurse into an update build number, commit updated-build-number, update build number again, commit again loop of horror. You could simply .gitignore the info.plist file, but that means you have to remove it from your repo (otherwise, modifications show even if it is .gitignored). To get around this (thanks to a suggestion I saw on StackOverflow1) we can leave the source-controlled info.plist file alone, and update the bundle version directly in the target build directory’s info.plist file. This means that the value of CFBundleVersion seen in Xcode is not used anywhere, since it is not overwritten until after info.plist is copied to the target build directory.

The Code

Usage

  1. Put this file in your repository (somewhere like /scripts/)
  2. In Xcode, create a Run Script build phase AFTER the Copy Bundle Resources phase
  3. In the Run Script, call path/to/update_build_number.sh [branch] where [branch] is the branch you want the count based on. If not supplied, master is used.

Explanation
If no branch is supplied (via command-line argument) then the script defaults to ‘master’. It uses git rev-list [branch] --count to get the number of commits on [branch], BUT it subtracts out the number of commits behind said branch that you are currently (with git rev-list HEAD..[branch] --count). This is so that if you aren’t at the tip of the branch (e.g. you’re checked out a few commits behind to find a problem), the build number will still be accurate to what you’re currently running. Also, instead of updating the info.plist file in your source directory, it modifies the info.plist in the target build directory; this way, you don’t have to check-in a constantly-modifying info.plist.

Wait a minute…
I know what you’re thinking: “Hey! You implied we’d be able to get back to a given build number’s code easily, but I can’t just git checkout [build number] to get to the [build number]th commit!”. This is true, however… There’s A Script For That™2. Just do nth-commit.sh build_number branch. Enjoy!


  1. SO answer that suggests editing the info.plist after it is copied to the target build directory 

  2. Modified from this gist 

Use SVN 1.7 in XCode 4.3+

tl;dr: Skip to the good part!

A while back, Subversion 1.7 was released. It was a major upgrade, and included an entirely new way of internally keeping track of local working copies. Unfortunately, the svn tools in XCode 4 are not compatible with the new format, and thus could not read the status of (or commit) code on the server.

Luckily, someone figured out a way to ‘patch’ Apple’s default SVN files with the new version. I first saw this on StackOverflow, and used it successfully for a few months waiting patiently for XCode to be updated.

Soon enough, there was a new version of XCode! And it was available through the Mac AppStore! And I had to spend a ridiculous amount of time working on my Hackintosh to get it upgraded to 10.7.3 so I could use the new version! …But that’s a story for another time.

After installing 10.7.3 and downloading XCode 4.3.1 from the Mac AppStore, I started it and immediately went to the repositories section, expecting to see my working copies listed and ready to be updated and committed. Unfortunately, what I got instead was a bunch of errors and “Cannot connect to repository” messages. I realized that the AppStore verson of XCode included it’s own copies of the SVN binaries, and wasn’t even using the ones that I had patched earlier. After looking a bit further, I realized that the new version of XCode had the SVN binaries packaged inside XCode.app. So how could those be updated to the new version?

The Good Part (AKA How To Upgrade to SVN 1.7 in XCode 4.3.x)

The good news is, upgrading SVN inside the XCode.app package isn’t too different from upgrading the old way.

This assumes you already have SVN 1.7 installed to /opt/subversion. You can get it from WANdisco (scroll down to the Vanilla Subversion section).

First, open Terminal and get an elevated shell using {shell}sudo -s{/shell} and typing in your password.
[shell]
tommylaptop:~ Tommy$ sudo -s
Password:
[/shell]

Then, cd to inside the XCode.app package, to where the SVN binaries are.
[shell]bash-3.2# cd /Applications/Xcode.app/Contents/Developer/usr/bin/[/shell]

Make a backup directory and move the old SVN files into it
[shell]
bash-3.2# mkdir bup
bash-3.2# mv svn* bup/
[/shell]

Lastly, symbolically link the new files into the package:
[shell]bash-3.2# ln -s /opt/subversion/bin/svn* ./[/shell]

That’s it! Note that this doesn’t seem to work 100% of the time (I still get random error messages sometimes), but it’s still nice to see the working copy status next to each file in the project, and be able to commit/revert from inside XCode. Also note that an update to XCode could break this – hopefully, by upgrading the SVN components *cough* – but the same procedure will probably work to restore SVN 1.7 functionality if the next update fails to deliver.