Archive for Versioning

Versioning your application with the Mercurial changeset hash

A user of MacHg (Marko Käning) wanted to be able to see the changeset hash in the About MacHg box (see here). I had been meaning to do this at some stage and, well, given the request, now seemed as good a time as any… It turns out it was quite easy to configure XCode to create the appropriate information while building MacHg.

(In my workflow I release versions of MacHg every so often, and I want users who get the source code off of bitbucket and build it for themselves to be able to easily tell me something like “In such and such a version of MacHg I am seeing XYZ happen.”)

So now MacHg shows the following sort of thing when you look in the About MacHg box (note the grayed c0d2f1df774d hash code):

ok so how did I go about making this work? It happens in two steps:

  1. As part of the build process, get the hash key automatically and insert it into the applications info.plist.
  2. Inside your application extract the stored value from the hash key and display it in the about box

Steps to Automatically set the BuildHashKey when building your Application

  1. Open your XCode project.
  2. Under targets select your application.
  3. Right click on the target and choose Add -> New Build Phase -> New Run Script Build Phase, as in:
  4. Change the shell to be /usr/bin/python instead of /bin/sh.
  5. Then in the script, copy and paste the following — I actually did this in bash first but decided to do it properly again in python so that I will be able to read it again in six months time if need be, python being that much more readable :

import os, subprocess, re, sys

targetBuildDir = os.getenv("TARGET_BUILD_DIR") getChangeset = subprocess.Popen('hg parent --template "{node|short}" --cwd ' + targetBuildDir, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)

if (getChangeset.stderr.read() != ""): print "Error in obtaining current changeset of the Mercurial repository" sys.exit(0) # if you want the build to fail here since the changeset is malformed change this to sys.exit(1)

changeset = getChangeset.stdout.read() if (not re.search("^[0-9a-f]{12}$", changeset)): print "Current changeset of the Mercurial repository is malformed" sys.exit(0) # if you want the build to fail here since the changeset is malformed change this to sys.exit(1)

infoPlist = os.path.join(targetBuildDir, "MacHg.app/Contents/Info.plist") if not os.path.exists(infoPlist): print "Cannot locate MacHg.app/Contents/Info.plist" sys.exit(1) # if you want the build to not fail here change this to sys.exit(0)

result = subprocess.Popen('/usr/libexec/PlistBuddy -c "Delete BuildHashKey" ' + infoPlist, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) result = subprocess.Popen('/usr/libexec/PlistBuddy -c "Add BuildHashKey string '+ changeset + '" ' + infoPlist, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)

print "MacHg BuildHashKey set to " + changeset

Of course you need to change MacHg.app to the name of your application. Also you might need to specify the full path to hg (ie /usr/local/bin/hg) in the line:
getChangeset = subprocess.Popen('hg parent --template "{node|short}" --cwd ' + targetBuildDir, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
Also you might want to have the build fail if the changeset is not available, since currently the script above just keeps going.  (I want users who download and compile MacHg to not get lots of critical errors if things are not set up just right.)

Using the BuildHasKey in your Application

Then back in your application you can have a method which will get the hash key from the info.plist:

- (NSString) macHgBuildHashKeyString
{
    NSString key = [[NSBundle mainBundle] objectForInfoDictionaryKey: @"BuildHashKey"];
    return key ? key : @"";
}
Then it’s just a matter of styling it and viola, you have the ingredients for versioning your application with the Mercurial changeset hash key. Actually, after doing this I found out that over at Cocoa is My Girlfriend he had done something similar but in bash.

Leave a Comment