Make a routine task less tedious through Xcode’s flexibility
Like a lot of iOS developers, we use TestFlight for our development build process on MapBox for iPad, our iPad app for sharing fast custom maps. TestFlight easily lets you manage teammates, their devices, app builds, and statistics around who is reading about, installing, and reporting issues with your app while it is in development.
One thing that can be tedious, however, is the process of creating an iOS app archive – the bundle that holds your built application, some metadata around the conditions under which it was built, and your .dSYM file, which contains data that is stripped from your shipping app for performance reasons but is used when debugging crashes during development. On top of that, you then need to create an .ipa file, which is the bundle that actually gets placed on iOS devices, and you need to upload it to TestFlight in order to send notifications about it out to your testing team.
Whew! Just writing about all of this process is tedious, let alone actually doing it. So I started working on a way to automate all of it with Xcode 4, allowing you to click Build & Archive in Xcode as normal, wait a while, and be presented with a web page on TestFlight containing your build that’s ready for your testing release notes.
The basic approach is as follows. Warning! Xcode-isms abound.
Create an Archive step Post-action in your main target’s scheme.
Use some shell trickery to find the latest built archive, since this doesn’t get passed to scripts.
Create an .ipa from the archive.
Re-codesign the .ipa for distribution.
Embed the proper provisioning profile that references your testing team’s devices.
Upload the .ipa to TestFlight using their upload API.
Open the build page in your browser.
Log everything along the way.
Keep in mind that this script will end up in your scheme, which you may or may not include in version control. Mine is located in (project).xcodeproj/xcuserdata/(username).xcuserdatad, and we exclude xcuserdata from version control.
The script ends up in a place like this: