Building iOS apps for Over-The-Air AdHoc distribution

I’ve written about building iOS applications with Hudson Jenkins, but until recently there hasn’t been a convenient way of getting those applications to your testers. Of course the most important part of your build output will be the app bundle you send to Apple’s iTunes Connect web interface, but throughout your development cycle you’ll want to test your app.  Sure you could build and deploy a debug build straight to your own personal device, but you get the most benefit from having other people beta test your app.

With recent releases of Xcode and the iOS SDK, Apple improved their AdHoc distribution support with two main enhancements:

  1. Mobile provisioning files can now be embedded in the App’s IPA itself, meaning you don’t have to maintain and update separate .mobileprovision files separately;
  2. A specially-formated manifest Plist file can be created that, when linked to properly, allows test devices to install new versions of your AdHoc app without needing to plug into a computer to sync the app using iTunes.

These improvements are huge, but require some changes to your build scripts and your Continuous Integration environment.  I’d like to show you how to do this in your own installations, and show you some options for how to distribute your apps to your testers.

Building IPAs properly

In the past, if you wanted to create an AdHoc distributable binary, you could either zip up the MyAppName.app bundle directory and mobile provisioning files separately, or you could attempt to create an IPA manually to fake-out iTunes. Getting this right was difficult however since the contents of the app directory are signed, and any changes (e.g. trying to include an embedded mobileprovision or iTunesArtwork file) would break the codesign.

With recent iOS SDK releases Apple added a “Build And Archive” menu that builds an IPA and archives it for later use. This is fairly nifty if you are a one-man shop and don’t practice Continuous Integration, but for the rest of us this just won’t cut it. However, some adventurous people dug through Xcode’s command-line build output to see just what was happening when the “Build And Archive” command was generating the IPA, and here’s what they found:

If you run that on the command-line, lo’ and behold, you can generate properly-signed IPAs to your heart’s content. But there’s more! The interesting thing about this command is that it takes both the codesign name and certificate file as arguments while generating your IPA.  There’s a subtlety to this that many people haven’t stumbled upon.  Here it goes:

No matter what provisioning profile you use within your Xcode project file to build your apps, you can re-sign them later in your Continuous Integration environment!

This is a powerful point that you can really take advantage of while setting up your build scripts. As mobile provisioning files change, you only need to ensure you perform the final IPA creation with the proper profile.

Adding a statement above to your build script is fairly straight-forward. Here is a snippet of code I use in my own build scripts:

I then place some simple configuration into the “build.config” file, that looks like so:

This is used by the script to determine, at runtime and on a per-artifact basis, which codesign and mobile provision file should be used.  This means that if your provisioning profile changes (if you add or remove devices from an AdHoc profile, for example) you only have to check the new provisioning file into your SCM, submit to Jenkins, and wait for a new build. (Note: The settings starting with “OTA” will be used in the next section)

The great thing I discovered is that Apple’s iTunes Connect web interface accepts properly-signed IPA files as your final app submission, as long as they’re signed with your App Store mobile provisioning profile.  This means if you build an IPA for both AdHoc and App Store configurations, that’s all you need.

Over-The-Air Distribution

Once you have an IPA generated with an embedded mobile provision profile, you can start distributing your apps to beta testers much easier. In my experience it’s difficult to find beta testers who will diligently put your app through its paces thoroughly enough to truly test your application’s limits. It makes matters even harder when you require users to: a) download an IPA, b) maybe download a new mobile provision profile, c) drag them into iTunes (in the right order), and d) sync their phone.  You’ll be lucky at that point if they actually have the time to launch your app.

There are a few 3rd-party solutions (such as TestFlight or Hockey) that handle the nitty-gritty of getting Over-The-Air installs working to manage your beta testers and to push updates out to them, but most of the features they provide can be built yourself simply enough just by adding a little bit of creativity to your Jenkins build scripts. I’m going to work backwards, since that might be easier to follow along with.

Send email notifications to your testers

Sample "Build Success" email that beta testers receive

The screenshot to the right shows a sample email that my beta testers and I receive when a new build is successfully compiled. I use the Jenkins Email-ext plugin to customize my build result emails, and enable HTML output so that I can include custom links in it.  Using this plugin allows you to easily send separate messages on success, failure, etc, and provides a number of variables/tags that can provide additional context to your testers. For example, I use the “$CHANGES_SINCE_LAST_SUCCESS” tag to show my testers what’s changed since the last build, using my SCM commit log messages.

Apple uses a special URL scheme to indicate that an app install should be performed.  In the email screenshot, you’ll see that I have a link titled “Install beta build #32″. This is link to the following URL:

itms-services://?action=download-manifest&url=http://url/to/app-manifest.plist

Clicking an itms-services:// URL results in this prompt

Clicking that link from an iOS device will tell the OS to download the application update manifest and, if it’s properly formulated, will prompt the user if they want to install the app.

Assuming the manifest is valid and the user taps “Install”, it behaves exactly as if the user purchased the app from the App Store. The device switches back to the home screen, and begins downloading the application.

Note: If your user’s device UDID isn’t included in the embedded mobile provision profile, the user will get a vague error about being unable to install the application.

Generating the update manifest

All of this assumes you’ve generated your update manifest to begin with. The format is pretty verbose, and has a few hard requirements for the information you need to supply. But honestly these are files you should have available to begin with. They include:

  • Your company name
  • The name of the application
  • The version number that you want to report for this app
  • Your 57×57 app icon
  • Your 512×512 app icon

There is some other information you can optionally supply, but that is the minimum you need. All of those files need to be available on a web server the user will have access to. So if your Jenkins server is password protected, you might want to archive those files elsewhere.  For my installation I use the Jenkins SCP plugin to archive the relevant files to a public “Beta” section of my website and reference the files from my app manifest.

I generate my app manifest from my build scripts, which looks something like this:

mkdir $OUTPUT/$fileprefix
cp $WORKSPACE/$OTASmallIcon $OUTPUT/$fileprefix/Icon-57.png
cp $WORKSPACE/$OTALargeIcon $OUTPUT/$fileprefix/Icon-512.png

info_plist=$(ls *Info.plist | sed -e 's/\.plist//')
bundle_version=$(defaults read $WORKSPACE/$info_plist CFBundleShortVersionString)
bundle_id=$(defaults read $WORKSPACE/$info_plist CFBundleIdentifier)

cat << EOF > $OUTPUT/$otaname
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>items</key>
<array>
<dict>
<key>assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>$OTAURL/$ipaname</string>
</dict>
<dict>
<key>kind</key>
<string>display-image</string>
<key>needs-shine</key>
<true/>
<key>url</key>
<string>$OTAURL/output/$fileprefix/Icon-57.png</string>
</dict>
<dict>
<key>kind</key>
<string>full-size-image</string>
<key>needs-shine</key>
<true/>
<key>url</key>
<string>$OTAURL/output/$fileprefix/Icon-512.png</string>
</dict>
</array>
<key>metadata</key>
<dict>
<key>bundle-identifier</key>
<string>$bundle_id</string>
<key>bundle-version</key>
<string>$bundle_version #$BUILD_NUMBER</string>
<key>kind</key>
<string>software</string>
<key>title</key>
<string>$OTATitle</string>
<key>subtitle</key>
<string>$OTASubtitle</string>
</dict>
</dict>
</array>
</dict>
</plist>
EOF
[/code]

The contents of the "output" directory are subsequently SCP'd to my beta server, and this manifest file is referenced in my Jenkins build success email.

The last step is to maintain an email mailing list of your beta testers and configure your Jenkins build job to notify that list when a successful build is finished. Your testers are then notified the moment any new changes are available, and with a single tap they can download and update to the latest build from anywhere. And your testers can see, based on your SCM changelog, what features may have changed and areas where they might need to focus their test efforts.

If you have any questions about this technique, please don't hesitate to leave comments below. I use this for my personal iOS application builds, as well as at work, and we've had great success with it, especially since it doesn't require the installation or configuration of any extra 3rd-party software.

Tags:

About Michael Nachbaur

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

5 Responses to “Building iOS apps for Over-The-Air AdHoc distribution”

  1. redsolo March 25, 2011 12:41 am
    #

    Very nice post. Ive built iOS apps using Jenkins but it has always been tedious to get the latest version to them, but this solves it! Together with the Build pipeline plugin it is possible to add a manual step before sending the email so the testers wont get spammed. Thanks for sharing!

  2. Thomas Lo March 30, 2011 5:06 am
    #

    Hi Mike, very nice article. I was manually constructing the ipa with previous versions of xcode, but xcode4 will not perform any codesigning from commandline builds for me anymore. I got it to work by manually calling codesign with the right certificate, but your method is much better as the provisioning profile is also embedded. Do you know if the PackageApplication tool also uses the correct entitlements? Using codesign, I could specify the entitlements file, but there is nothing about that in the PackageApplication tool manual.
    My guess is, that it is working, as the app would hang on the splashscreen, when the entitlements were not correct.

    • Michael Nachbaur March 30, 2011 3:14 pm
      #

      As far as I can tell the entitlements step happens at compile time, so that part relies on correct build settings in your project. So fortunately, if you build your AdHoc distribution build and then use the PackageApplication IPA generation technique I describe above, everything should Just Work™.

  3. Yixiang May 15, 2011 10:32 pm
    #

    It doesn’t seem to work properly. I just can’t get the ipa installed on to my iphone. It fails with a message “Unable to Download” during the installation process. Does anybody else experience the same issue?

    I am running xcode 4.0.2 with ios sdk 4.3.

    • Michael Nachbaur May 16, 2011 6:50 am
      #

      It sounds like your manifest XML file isn’t formatted correctly, or is missing something important. Some of those fields are required, and the only error message you’ll get back is “Unable to Download”.