Wednesday, November 03, 2010

Automating build versioning in XCode for iOS with SVN and Dropbox

I am working on an iOS project at the minute that is heavily dependant on a test team actually getting the latest version of the app onto a variety of devices and tapping away to verify the latest feature*. At certain times in the sprint as defects come in are fixed quickly and a new version put out it was becoming very difficult to keep track of the builds and who had what.

To make matters worse the way a tester would get a new build was for them to walk up to a developer and ask for the latest stable version. The developer would then need to stop what he was doing, grab the relevant version from svn, plug the test device into their machine and hit build. This was horrible for workflow and productivity.

This week sees the first real run with my automagical build system and so far its working great. Basically it uses the "build and archive" function to share an installable build to a dedicated build station running iTunes. All the devices are synced to this version of iTunes and the binary gets to the machine by a network folder - we happen to use Dropbox for this.

This works OK but there are a couple of gotchas. In order for iTunes to recognise the incoming build as an update the version number must have changed. This meant that the developers needed to keep the minor version number rolling along and we resorted to naming the binary with a number including the SVN revision and the date and time. I didn't like this much so came up with a better way of numbering the versions.

Modifying a script I found here I added a bit that pulls the last subversion revision from svnversion (including the Modified flag if appropriate) and creates a build version number like M.M.M.svn.m for example. is the third build from a modified SVN revision 1923 of release 2.0.1.

Here's the script:

 PROJECT_NAME=$(basename "${PROJECTMAIN}")  
 echo -e "starting build number script for ${PROJECTMAIN}"  
 # find the plist file  
 if [ -f "${PROJECTMAIN}/Resources/${PROJECT_NAME}-Info.plist" ]  
 elif [ -f "${PROJECTMAIN}/resources/${PROJECT_NAME}-Info.plist" ]  
 elif [ -f "${PROJECTMAIN}/${PROJECT_NAME}-Info.plist" ]  
   echo -e "Can't find the plist: ${PROJECT_NAME}-Info.plist"  
   exit 1  
 # try and get the build version from the plist  
 buildVersion=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" "${buildPlist}" 2>/dev/null)  
 if [ "${buildVersion}" = "" ]  
   echo -e "\"${buildPlist}\" does not contain key: \"CFBundleVersion\""  
   exit 1  
 echo -e "current version number == ${buildVersion}"  
 # get the subversion revision  
 svnVersion=$(svnversion . | perl -p -e "s/([\d]*:)([\d+[M|S]*).*/\$2/")  
 echo -e "svn version = ${svnVersion}"  
 # construct a new build number  
 set $buildVersion   
 if [ ${4} != ${svnVersion} ]  
 buildNumber=$(($MINOR_VERSION + 1))  
 echo -e "new version number: ${buildNewVersion}"  
 # write it back to the plist  
 /usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${buildNewVersion}" "${buildPlist}"  

Just place it in your project directory and tell XCode to run it as a the first phase of the build by adding a new "Run Script" build phase. Then getInfo on the new phase and set shell: /bin/bash and script: ./path/to/script

Now when the build and archive runs I can grab the build version from the results window and add it to the resolve comment in Jira so the tester knows which build to grab from the dropbox. All they need to do is go to the build station, click the build in the dock stack (hooray for stacks and instant context opens) which will replace the version in the iTunes library with that build. Then they just plug in the device and it updates.

Next step will be to subscribe all the devices to a twitter feed using some iOS client that does push notifications on @mentions so that I can tell a device to get a new build from the station.