Better Ad-Hoc Builds on the iPhone
Ad-Hoc builds - what a pain. Seriously, Apple has made them much harder than they should be. And that’s after they have been much improved over the past couple of years. But they are very nice to have — especially if you are doing work with clients or if you want to send out builds to your army of testers.
This post is basically what I’ve found that helps in making consistently working Ad-Hoc builds. The consistent part is key. I have had Ad-Hoc builds that work some of the time before, and trust me, nothing is more frustrating than putting together a build, sending off an email, and getting a response saying “doesn’t work for me”.
If you look around on the net, you’ll find a number of posts on this subject — I used Craig Hockenberry’s great post to get my initial projects up and running with an Ad-Hoc build. If you setup your project this way, you’ll get a app bundle under build/Ad-Hoc-iphoneos that you can use to send to your users. But the app bundle is not the best way to distribute the apps — any change, accidental or not, by the receiving end will cause the app to fail to install.
Turns out that the best way to distribute an Ad-Hoc app is the same way that Apple stores apps in your iTunes folder — as an IPA file. Don’t let the name fool you — IPA files are really just zipped up copies of the app bundle, with a little more structure around them to allow for a custom icon in iTunes. Since creating this manually can be a pain, I found this post that explains how you can create an aggregate target in your Xcode project to automatically do that. I’ve made a couple of changes on my end — first, is that I rename the original target from Foo to Foo.app. The aggregate target is then called Foo. Once I set the target to the aggregate target, I never change it back to the original target. It’s not necessary if you have the if statement checking for the Ad Hoc build at the beginning of the script.
At this point, when you click build for your Ad Hoc target, you’ll get an IPA file. This is almost perfect. But there are a few remaining issues. One is that if you forget to do a clean project and you have removed files since the last time you built the Ad Hoc build, you will end up with an IPA file that will fail to install. This is because there are now resources in the package that have been signed. The other issue is that you should be saving the dSYM file locally on your machine for any build that you send out for testing. If you don’t do this, any crash reports you get will not be able to be symbolized (see here for more info).
You can solve both of these easily with yet another script. Since you can’t tell your aggregate target to automatically force a build clean when doing Ad Hoc builds (at least, not that I know of — please let me know if there is below), you need to fall back to an external shell script. As a bonus, you can add additional steps to your build process. For example, you can automatically upload the build to an external server so that it can be accessed by your testers. You could add a step to add the new build version to your bug tracking software.
I’m not going to show the script that I use here, but I will show you a simplified script that cleans the project, does an Ad Hoc build and copies the dSYM file to another directory. You will need to customize this for your own needs as necessary. There is also no error checking here — you are on your own for that as well.
#!/bin/bash
PROJECT=Foo
STAGING_DIR=~/staging
rm -rf build
xcodebuild -project $PROJECT.xcodeproj -target "$PROJECT" -configuration "Ad Hoc" -sdk iphoneos3.1
cd build/Ad\ Hoc-iphoneos
cp -R $PROJECT.app.dSYM $STAGING_DIR
cp $PROJECT.ipa $STAGING_DIR
You will probably want to make sure that you rename the IPA and dSYM file to include some kind of build number, version, or build time. Otherwise, you will overwrite your previous version each time you build an Ad Hoc build.
I hope this helps someone out there. Feel free to leave comments/questions/suggestions below.