Thomas Bandt

Automate All The Things: Distributing Xamarin iOS Apps To The AppStore And TestFlight Without Pain

Everyone who has already built and shipped an iOS app to the AppStore knows the pain. That awful feeling when dealing with provisioning profiles, development and distribution certificates.

Published on Friday, 27 November 2015

I can't free you from that kind of experience, but here's a way that allows you to only go through it once.

The Manual Way Of Distributing An iOS App

Shipping a Xamarin iOS app to Apple follows usually those manual steps:

  1. Create an app id.
  2. Create a distribution certificate.
  3. Create a distribution provisioning profile.
  4. Install both the certificate and the provisioning profile on your development machine.
  5. Build your Xamarin app with Xamarin Studio or Visual Studio with the "AppStore | iPhone" build configuration and archive it.
  6. Create an iTunes Connect entry for your app (via their website).
  7. Export your app (.ipa) from the Xcode Organizer.
  8. Upload your app (.ipa) with the Application Loader desktop application to iTunes connect.
  9. From there: Publish your app to your testers via TestFlight or submit it to the review process of Apple.

I think it's fair to say that those steps are hard to remember if you're not doing it every week, and it evidentially is error-prone. So setting up a new development machine once in a while can drive you crazy, and so can taking over the distribution task from a colleague who's normally doing this job but is not available at your important deadline.

That cries for automation.

The Process Of Automatic Distribution

Prerequisites: A Build Server With Xcode And Xamarin

Obviously everything has to be executed on a Mac, so it won't work on your average Windows box or on a Linux server. I highly recommend you to use a dedicated machine.

You need to install Xcode and its Command Line Tools, as well as Xamarin. The latter needs to run with an account that runs on the business plan, as you you need the "business feature" of headless builds to run all the stuff from the command line.

Everything described below can easily be run via simple shell scripts, but if you're running a fully featured build server like TeamCity or Jenkins anyway, go for it. I prefer it that way, but I won't go into any product specific details here because that would go beyond the scope of this post.

Step 1: Set Up The Certificate And The Provisioning Profile

Yes, you need to do that – but hopefully the last time for a long, long time. If you've everything prepared, install the certificate (by double-clicking the *.p12) and the provisioning profile (likewise by double-clicking).

Protip:

When you've created a new certificate by the guidance of the Apple Website, don't only download it to your machine, but directly export the private key as a *.p12 file afterwards from the Keychain Access tool. Remember the password of that file, so you can import the certificate on any machine you need in the future. Otherwise you would always have to create a new certificat when setting up a new computer.

Step 2: Make A Clean Checkout Of Your Sources

Make sure you're working on a clean and fresh working directory with the latest sources of your project (and maybe a specific branch). See this thread for an example for Git.

Step 3: Set Up All Your *.plist

You will need to change some things in your Info.plist file that differ from your development configuration. Those things usually are CFBundleIdentifier, CFBundleVersion, and CFBundleShortVersionString. There's a nice tool around to help you with those changes, it's called PlistBuddy.

It's also the right point in the process for setting up a custom Root.plist, for example for hiding some development options from the iOS Settings app. Simply replace the one in your Settings.bundle directory within your app's root directory with another one used only for distribution.

Sample:

# Replacing the Root.plist
rm src/MyAppRoot/Settings.bundle/Root.plist
cp deployment/Root.Distribution.plist src/MyAppRoot/Settings.bundle/Root.plist

# Setting up the Info.plist
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier com.mydomain.myapp" src/MyAppRoot/Info.plist
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion 2.5.0" src/MyAppRoot/Info.plist
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString 2.5.0" src/MyAppRoot/Info.plist

Step 4: Do All The .NET Things

You may restore your Nuget packages, if you're using Nuget [sic] and you may run Nunit and execute other steps necessary to correctly build your project as well.

Step 5: Build Your App

Building your Xamarin app via the command line is easy:

/Applications/Xamarin Studio.app/Contents/MacOS/mdtool
  -v build '--configuration:AppStore|iPhone' src/MyApp.sln

This will result in a bunch of files in src/MyAppRoot/bin/iPhone/AppStore.

Step 6: Create The IPA

None of the built files will be sufficient for distribution, so you need to create and sign an *.ipa file:

xcrun -sdk iphoneos PackageApplication
  -v src/MyAppRoot/bin/iPhone/AppStore/MyApp.app
  -o src/MyAppRoot/bin/iPhone/AppStore/MyApp.ipa

If you want to check everything went right, which means all your *.plist adjustments were applied correctly and the *.ipa is signed with your distribution certificate, you can make use of nomad:

ipa info MyApp.ipa

Step 7: Ship It To iTunes Connect

Last but not least you have to deploy your *.ipa to Apple, so you can publish it to your TestFlight users or submit it to the review process.

This can be done by the command line as well, all you need is this script and an user account with a Admin or Technical role, so it is allowed to upload builds.

Conclusion: Build It, Ship It, Forget It

Dealing with all the Apple stuff sometimes is hard, doing it again and again is a waste of lifetime. Automating this process is definitely worth the time, so you can do something more joyful with the time you gain.

What do you think? Drop me a line and let me know!