Pages

Tuesday, January 3, 2012

Setting up Automated Builds with TeamCity, MonoTouch and TestFlight


We have recently entered the world of iOS development with MonoTouch and one of the first things I wanted to do was get a build process and Continuous Integration (CI) server up and running.

We already had CI setup for our existing Windows product which is integral to our development process so it was one of the first things we wanted to get working.

I might write a more complete post of our build process another time but for the moment here are the details on how I set it up.

I'm going to assume you have some knowledge of TeamCity, although even if you don't have any  its pretty easy to use and worth learning.  I'm also if you use another build server it should be easy enough to apply the information here to it.

Tools used

  • TeamCity.  We had an existing TeamCity server running on Windows that we wanted to reuse.
  • Mac Mini. A "clean" non-development one to be used as a TeamCity build agent for the existing TeamCity server.
  • MonoTouch and MonoDevelop. Lots of reasons why we choose the Mono tools. I'll maybe write a full blog post on that another time.
  • TestFlight for automatic distribution of builds to beta testers

Setting up the Mac as a Build Agent.

First thing you need to do is set up your Mac as  TeamCity build agent.  There is a guide in the TeamCity documentation for how to do this here.  My basic steps where:

  • On the Mac install the agent from the on the Agents section of TeamCity using the Java Web Start installer.
  • Accept all the defaults during the install.
  • Follow this section of the TeamCity guide on how to start the build agent at Mac start up.
Note I couldn't get the last step to work by copying to the Launch Daemons folder as there was an error in some Java wrapper.  I copied the file to the /Library/LaunchAgents folder instead.  The downside to this is the agent will not start until the user logs in.  Although the Apple code signing stuff needs a user to be logged in anyway, so no big deal.

Once you TeamyCity server can see the new build agent authorise it via the web interface as normal.


Setting up the build process

The build process I initially set up consisted of 3 steps (excluding getting source from the VCS):
  1. Update the App Bundle version
  2. Build a class library project and a MonoTouch App project
  3. Upload the App Bundle to TestFlight

Update the App Bundle version

The version number of your app is stored in the info.plist file of your MonoTouch project.  I couldn't find a way to auto increment this in MonoDevelop so I had to move to an external tool.  I prefer this approach as I don't need to complicate my build process by checking in the latest version number once its been bumped up.  I let TeamCity track the next version number with its built in functionality.

The Apple Development tools comes with a handy little utility called PlistBuddy.  If you run the following command line it will update you App Bundle version.

/usr/libexec/PlistBuddy -c "Set :CFBundleVersion %build.number%" Info.plist

In TeamCity I added a Command Line build step with that command.

Update build number step

You can use TeamCity's auto increment version number functionality to increase the version number each time.

Build the Projects

Next step was to build the class library and MonoTouch App projects.

For this I used the mdtool command line utility which comes with MonoDevelop.  mdtool only lets you build one project at a time so you need to invoke it once for each project.  Luckily TeamCity will let us do this in the one command line build step by changing the type to script.


My script looked like this (I've split it over multiple lines to make it easier to read, in practice you would need to remove the line breaks):


/Applications/MonoDevelop.app/Contents/MacOS/mdtool build 
  "--project:YourProjectOne"
  "--configuration:%AppBuildType%" 
  "%teamcity.build.checkoutDir%/YourSolution.sln"

/Applications/MonoDevelop.app/Contents/MacOS/mdtool build 
  "--project:YourProjectTwo" 
  "--configuration:%AppBuildType" 
  "%teamcity.build.checkoutDir%/YourSolution.sln"

Upload the builds to TestFlight

For this you need to do an Ad-Hoc distribution build.  This in itself could fill a few blog posts.

These are the guides I followed:

MonoTouch Ad Hoc Distribution Tutorial
Building for Distribution

Its worth getting these working and testing them out in MonoDevelop first before moving to TeamCity.  MonoDevelop has an upload to TestFlight command built in so you can make sure you are setup correctly before automating.

TestFlight (once you have signed up) has an easy to use upload API.  You can upload your build to it by using the built in curl utility. Its also an idea to zip up you .dSYM file and upload it at the same time to get better crash reports.

Again I added another Command Line build step and changed the run option to "Custom Script".  My script zips up the dSYM file and them uploads the App to TestFlight (again I've split it over multiple lines):


zip -r -b . MyApp.app.dSYM.zip MyApp.app.dSYM

curl https://testflightapp.com/api/builds.json  
  -F file=@MyApp.ipa  
  -F dsym=@MyApp.app.dSYM.zip 
  -F api_token=MyAPIToken 
  -F team_token=MyTeamToken 
  -F notes='Built from TeamCity. %env.TEAMCITY_BUILDCONF_NAME%' 
  -F notify=True  
  -F distribution_lists='%TestFlightDistList%'



Final notes

In reality I actually have one build template and 2 configurations that are linked to it.
The base template just gets the files from the VCS and builds the projects.  My continuous integration configuration is based on this with no changes.

I have another Ad-Hoc build configuration also based on this template that adds the two extra build steps for incrementing the build number and uploading to test flight (with the build projects step inbetween the two of them).

At the moment the build process is just about manageable in TeamCity.  If it got any bigger I would definitely move the build steps out to something like Make or MSBuild\xBuild.