tl;dr: Automatically set your app’s bundle version based on the number of git commits up to that point. Skip to the code!
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.
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.
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.
- Put this file in your repository (somewhere like
- In Xcode, create a Run Script build phase AFTER the Copy Bundle Resources phase
- In the Run Script, call
[branch]is the branch you want the count based on. If not supplied,
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!