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.
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.
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.
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?
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.
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.
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.
Of 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:
"$WORKSPACE/build/build.sh"
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.
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.
#!/bin/sh
function failed()
{
echo "Failed: $@" >&2
exit 1
}
set -ex
export OUTPUT=$WORKSPACE/output
rm -rf $OUTPUT
mkdir -p $OUTPUT
PROFILE_HOME=~/Library/MobileDevice/Provisioning\ Profiles/
KEYCHAIN=~/Library/Keychains/login.keychain
. "$WORKSPACE/build/build.config"
[ -d "$PROFILE_HOME" ] || mkdir -p "$PROFILE_HOME"
security unlock -p $PASSWORD
cd $WORKSPACE/iphone;
agvtool new-version -all $BUILD_NUMBER
for sdk in $SDKS; do
for config in $CONFIGURATIONS; do
provision=$(eval echo \$`echo Provision$config`)
cert="$WORKSPACE/build/$provision"
archive="$OUTPUT/$JOB_NAME-$BUILD_NUMBER-$config.zip";
[ -f "$cert" ] && cp "$cert" "$PROFILE_HOME"
cd $WORKSPACE/iphone
xcodebuild -configuration $config -sdk $sdk clean;
xcodebuild -configuration $config -sdk $sdk || failed build;
cd build/$config-iphoneos;
if [ $config == "Release" ]; then
zip -r -T -y "$archive" *.app* || failed zip
zip -j -T "$archive" "$WORKSPACE/iphone/icon.png" || failed icon
else
zip -r -T -y "$archive" *.app || failed zip
zip -j -T "$archive" "$cert" || failed cert
fi
done
done
SDKS="iphoneos3.0"
CONFIGURATIONS="Distribution Release"
ProvisionDistribution=<CERTIFICATE_UUID>.mobileprovision
ProvisionRelease=<CERTIFICATE_UUID>.mobileprovision
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.
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.
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.
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.
#!/bin/sh
USER=hudson
PASS=password
HUDSON_URL=http://$USER:$PASS@<my-build-server>:8080
JOB=$1
TOKEN=$2
CAUSE=$2
set -x
lwp-request -ds "$HUDSON_URL/job/$JOB/build?token=$TOKEN"
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.