How to automate your iPhone app builds with Hudson

As any iPhone application developer who’s released at least a single app to the App Store will tell you, releasing your app is a terrible pain in the…well, it’s not a fun experience.  After your second or third app you start to get the hang of things, but there’s still pain and suffering involved.  Managing certificates, getting settings configured properly, and iterating between development, AdHoc beta builds, and the final App Store release builds, all make the process seem tediously manual and prone to human error.

In professional software development shops, you would use a Continuous Integration server to monitor your source control repository, check out changes as they’re submitted, compile, test and package up builds, and notify developers of the build’s health via emails and a web-based “Dashboard”.  I missed having this while developing my PhoneGap-based iPhone applications, so I decided to once and for all bring good development practices to my iPhone work.

Why do I need to configure automated builds anyway?

I get this a lot from people when I’m trying to convince them of the need for automated builds.  I personally find it hard to imagine people getting by without them in a single-developer project, let alone when multiple developers contribute to a project.

Monitoring the health of an application

Lets face it, we’re human, and we make mistakes.  It’s alright to break code from time to time, but what really sucks is when you find out far too late.  Did your recent changes accidentally eliminate your Entitlements.plist file, thus breaking distribution or release builds?  Do you have a file or library you forgot to check in, meaning when you delete the project from your working directory all those changes will just vanish?

Instead of having to remember to check each of those things manually (which, lets face it, you’ll forget at least half of the things you’re supposed to do inevitably), why not have an automated system tell you every time you make a change?  And if you’re in a multi-developer project, you’ll be able to see who broke the build and what change specifically broke it.

Always be ready for distributing your application

Many times in the natural course of development you’ll break code.  You’ve gotta break something in order to improve it.  But sometimes someone (your wife, a client, a beta tester) will want to try out your application before you have an opportunity to finish off your recent changes.  Instead of spending ages back-tracking your work to get your application to compile, why not rely on your automated build system to keep archives of previously successful builds?

Release what you test

Since you want to test an application before you release it to the App Store, you’ll probably create an Ad-Hoc distribution build to give to friends, family, or official beta testers before you bundle your application up to send to the App Store.  Maybe your testers will find bugs, maybe they won’t.  But at the end of the day that compiled app bundle you just created isn’t actually what you submit to Apple.  You need to compile a completely different app bundle with very different files stored in a Zip file, and if you’re not careful you could potentially be releasing something different than what you tested.

Why not have your automated build system create both your Ad-Hoc distribution build as well as an App Store release build every time?  That way you’re not only always ready to release something to the App Store, but you can be guaranteed that you’re submitting to Apple the exact code that your testers evaluated.

More benefits than I can list

If you’re really serious about best practices, you’ll probably want to write unit tests for your code and have those run after your code has been compiled, but before your build is packaged and archived.  Just because your code compiles doesn’t mean that it will behave correctly.  And lets face it, if you have a lot of tests, you’ll never wait for all of them to run throughout the course of your work.  So by running your tests as a prerequisite to a build succeeding, you’re guaranteed that you’ve got a safety net.

There’s plenty of other best practices that having an automated build system can help with, so what I’m discussing here will just cover the tip of the iceberg.  If I’ve convinced you that automating your builds, read on.

Getting started with Hudson

After evaluating a few solutions, I ended up settling on Hudson for my build server.  It’s Open Source, is built in Java so it runs almost anywhere, and it supports Slave build agents which is a must-have for compiling iPhone applications.  You can most likely use anything you want, though the amount of work you’ll need to do to make it work will vary.

Your first step will be to download and run Hudson itself.  It’s a self-contained Jar file, so the installation procedure is very simple.  You might want to run it inside its own user account on your computer, but you can decide where and how you want it run.  I have a Linux server that runs my email, websites and contains my source code repository, so I decided to run Hudson there.  I use Hudson to build not only iPhone apps, but my Perl-based webapps and its dependencies, so having it in a central location makes life easier for me.

After you get it up and running, you might want to install a few plugins to help you connect to your source code repository, or to enable you to do different styles of notification (for instance, my build sends me an instant message on GTalk when I break a build).  Play with its configuration options and see what it can provide.

Create your build job

Hudson: New JobOf course for your first step you’ll actually need to build your project is to create a build job.  Go to the dashboard and click “New Job”.  Give it a name, and tell it that you want to create a “Freestyle software project”.  This means it will simply run a shell script that describes how your build should be done.

Tell the job how to find your source code by pointing it at the proper source code control repository.  It has support for Git, Subversion, Perforce and even CVS.  Most of the defaults for which branch to build from should be fine, but you’re the best judge of what options to pass to the job.

Once you give it information about where to find your source code, you need to tell it what to do with it.  Under the “Build” section, click on “Add build step” and select “Execute shell”.  This will allow you to enter the name of a script to execute.  Just type in:

Hudson will check your code out into a directory where it will be built, and the $WORKSPACE variable will be set to the full path where that directory exists.  There are other variables it defines for you, which are described on the job’s configure page.  By telling the job to run the above command, it will run the “build.sh” script that you’ll add to your project’s source code repository.  This way the way you build your project can be versioned alongside your actual code.

Create your build script

Of course you actually need a script that will call all the necessary commands to build your application.  Luckily Apple provides a command-line tool called “xcodebuild” which is used to build applications.  In fact, this is the exact command that XCode calls when you click the “Build” button in the GUI.
This is the full version of my build script, which might not work well for you.  This is almost a drop-in script for PhoneGap apps though, and supports packaging up Release and Distribution builds automatically.  Along with this build script (which can be reused for different iPhone projects), you create a per-project build configuration file that looks like so:

This way you can specify the specific certificates each build uses, and which builds you want to create and archive.  The build script will package up the certificate for Distribution builds (that way your beta testers can add the .mobileprovision file to iTunes) and will package up the app’s icon file with the Release build (so the zip file can just be submitted as-is to Apple).

One of the steps in the build script above is to specify a custom build version number at build-time, based on the Hudson build number.  This means, as a developer, you don’t have to manually increment version numbers in order for your test application to install through iTunes. I followed these instructions on auto-incrementing XCode build version numbers, and altered them to suit my build script.  All the guesswork and human-error is avoided.

Slave and remote builds

Screenshot of Hudson's Add Slave screen

Screenshot of Hudson's Add Slave screen

One of the limitations of building iPhone applications is that they must be compiled on a Mac with the XCode developer tools installed.  This means in my environment, where Hudson is running on a Linux server, I have to perform my builds on a remote computer.  Luckily at home I have an iMac running in a DMZ of my home firewall, both of which are always running.  So for my builds I configure my iMac to be a slave build server.  I enter its hostname, username and password, and Hudson automatically SSH’s into my home computer, SFTPs the appropriate Jar files down, and spawns the processes remotely.

At that point you can configure a job to use a specific build machine, or you can tag a set of machines with certain keywords and can bind a job to those tags.

Publishing build artifacts

One of the configuration options I specify within my Hudson jobs is I tell it to publish my successful build artifacts (the packaged Zip files for my application releases) over SCP to my web site into a password-protected directory.  From here I can give my beta testers a password to log into my website and to download the latest build, simplifying the beta test cycle.  I just point my testers at a URL and let them test to their heart’s content.  This is especially useful since a lot of mail servers block iPhone app bundles, thinking they’re malware or spam of some kind.

Automatically start builds from your version control system

The final step needed before you can fully automate your builds is to tie Hudson into your version control system.  The way you tie scripts into your version control system depends on what you use, but for Git, you simply create the following script somewhere in your user’s account and invoke it from your post-receive hook.

Next Steps

As I improve my build infrastructure more, I have plans to support pushing releases straight to Apple’s iTunes Connect interface for me.  My plan is to run a “Parameterized Build”, where I can supply options to a build, indicating that I would like to issue a full release.

I also have plans to improve my testing infrastructure, and would even like to try to run tests of my application by interactively controlling the iPhone Simulator.  If anyone has suggestions or experience on doing this, please let me know.

Tags:

About Michael Nachbaur

iOS app developer, livin' the dream. Working from wherever I find myself; Hawaii, Santa Monica, Vancouver, and elsewhere.

22 Responses to “How to automate your iPhone app builds with Hudson”

  1. Umair Aziz October 14, 2009 12:19 am
    #

    Thanks…very helpful. I’ve been looking for automating iphone builds myself and its not been a pleasant experience. We use Cruise Control for building our java, .net apps. The fact that you’ve got something working gives me hope that I may be able to do this as well.

  2. Vickram October 14, 2009 6:45 pm
    #

    Excellent article. Thanks for taking the time to write it!

  3. Rasheed Abdul-Aziz October 16, 2009 10:39 pm
    #

    Looks like Mike Nachbaur has gone a fair way further than I have. I’ll still complete my article series when we get to the app store steps, but I might be borrowing plenty from Mike’s article :)

  4. Rasheed Abdul-Aziz October 16, 2009 10:40 pm
    #

    That was meant to be a trackback to my link post on our company blog: http://devblog.visfleet.com/

  5. grigory October 21, 2009 6:38 pm
    #

    Hi there!

    Thank you for taking your time to write this post. I’ve been looking at automating dev process, and this is quite helpful.

    I was wondering if you got anywhere with this: “I have plans to support pushing releases straight to Apple’s iTunes Connect interface for me”?

    • Michael Nachbaur October 23, 2009 2:00 pm
      #

      Grigory, not yet. I’ve been finding that my builds are so stable these days, that submitting a revision to an app through the iTunes Connect website is easy enough for me to do as long as I maintain a list of descriptions, keywords, etc in my Git repository. That way it’s easy for me to copy/paste my previous values, and can revision changes to my keywords.

  6. James October 21, 2009 11:47 pm
    #

    I’m having trouble getting the .mobileprovision files to copy in. I am not a Unix shell programmer, so bare with me. The only place I see that the files would be copied in is in this line

    cp “$cert” “$PROFILE_HOME”, but that’s backwards and missing the actual file name, isn’t it?

    Thanks,

    • Michael Nachbaur October 23, 2009 2:08 pm
      #

      James, the key part of the cert handling in this script is the build.config file. This lists the build types (Distribution, Release, etc) that you want to build and package up. There’s other configuration statements in the build.config that says which provision file goes along with each build type. You’ll want to look in your xcodeproj file to see what cert a particular build wants to use (e.g. the UUID specified), and copy the file from “~/Library/MobileDevice/Mobile Provisions/.mobileprovision” to your build directory.

      What I do is I check those certificates into git, so the certs themselves are versioned. If I change the cert (for instance, to add additional beta testers) then I delete the old cert and add the new one into git.

  7. Chris December 16, 2009 4:05 am
    #

    Michael, is it possible to email me a copy of your build script? I’m trying to do something very similar with TeamCity but GitHub seems to be down!

  8. Marc December 16, 2009 5:09 am
    #

    interesting article, I made a similar general-purpose build script here in this blogpost:
    http://iphonedev.makerlab.org/2009/12/packaging-script-for-iphone-ad-hoc-distribution-builds/

    good idea to do a clean and a || failed in the build process!

    you should add iTunesArtwork to your build process as well as make an .ipa archive for easier installation!

  9. Marcus January 11, 2010 12:47 pm
    #

    Excellent article. I’ve made a few adjustments for my setup but it works! The Certs are the key. Hudson ROCKS BTW!!

  10. James July 8, 2010 3:01 pm
    #

    I used this with TeamCity and it works like a charm(with a few changes of course). Thank for the sharing it.

  11. Mike July 22, 2010 6:50 am
    #

    I am unable to get the macbook setup as a slave, I keep getting connection refused. Any thoughts?

    07/22/10 08:53:21] [SSH] Opening SSH connection to 10.1.6.248:22.
    java.io.IOException: There was a problem while connecting to 10.1.6.248:22
    at com.trilead.ssh2.Connection.connect(Connection.java:755)
    at com.trilead.ssh2.Connection.connect(Connection.java:546)
    at hudson.plugins.sshslaves.SSHLauncher.openConnection(SSHLauncher.java:422)
    at hudson.plugins.sshslaves.SSHLauncher.launch(SSHLauncher.java:154)
    at hudson.slaves.SlaveComputer$1.call(SlaveComputer.java:180)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
    at java.util.concurrent.FutureTask.run(FutureTask.java:138)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
    at java.lang.Thread.run(Thread.java:619)
    Caused by: java.net.ConnectException: Connection refused
    at java.net.PlainSocketImpl.socketConnect(Native Method)
    at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333)
    at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195)
    at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:182)
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366)
    at java.net.Socket.connect(Socket.java:525)
    at com.trilead.ssh2.transport.TransportManager.establishConnection(TransportManager.java:342)
    at com.trilead.ssh2.transport.TransportManager.initialize(TransportManager.java:450)
    at com.trilead.ssh2.Connection.connect(Connection.java:699)
    … 9 more
    [07/22/10 08:53:21] [SSH] Connection closed.

  12. Chris August 12, 2010 12:17 pm
    #

    I have a question about the following line in your build script:

    xcodebuild -configuration $config -sdk $sdk || failed build;

    How exactly does the last part ‘|| failed build’ work and why are you using it? I basically ask, because I have a rake build script for my iphone project and have configured a job in Hudson to run this script. However, if I break my build by putting in a bad line of code that doesn’t compile, I see in Hudson’s console output that there was a build failure spit out by Rake, but Hudson never detects it and says that the build was successful. Thoughts?

    Any ideas on how ot fix this would be greatly appreciated.

    • Michael Nachbaur August 18, 2010 5:15 pm
      #

      Hudson will detect a failure when the build script exits with a status code of anything but “0″. So the “|| failed” function call logs an error message and spits out an exit code

      So if you want your Rake script to fail, you have to exit from that with a status code too.

Trackbacks/Pingbacks

  1. Rhonabwy » Hudson – a lot of things just done “right” - October 21, 2009

    [...] doing CI with python code actually. And there’s folks who are building all sorts of things including iPhone apps with [...]

  2. wonderful world of programming » Blog Archive » Link roundup: Dead fish in the Hudson - October 27, 2009

    [...] Nachbaur writes about automating iPhone app builds with Hudson. Of course this idea is particularly appealing to me since I love teh Hudson (even when it gives me [...]

  3. Blog harvest: Metaprogramming in Ruby,Hudson builds IPhone apps, Git workflow, Podcasting Equipment and Marketing « Schneide Blog - November 4, 2009

    [...] How to automate your IPhone app builds with Hudson – Another domain in which the popular CI Hudson helps: building your IPhone apps. [...]

  4. Cocoa, Hudson CI and Xcode - go green | manicwave.com - March 1, 2010

    [...] up more than a few times getting my apps to build in Hudson.  There are more than a few pages on the web that illustrate Cocoa/Hudson [...]

  5. Continuous Integration with Hudson - June 16, 2010

    [...] How to automate your iPhone app builds with Hudson by Michael Nachbaur. [...]

  6. Building iPhone apps with Hudson, Part 2 | Web Developer's Life in Beta - June 18, 2010

    [...] already posted before on how to set up Hudson to compile and build iPhone applications, but I just had a “OMG I <3 Hudson!” moment just now, and felt I had to share [...]

  7. hudson | designoMatt - July 13, 2010

    [...] great article on automating your build processes with hudson. [...]