Build Your Xamarin App For iOS Once, Deploy It Twice – Both To Your Testers And To The AppStore.
The ordinary process of building and shipping an (iOS) app often includes at least two builds: The first for your internal tests, the second for the public (AppStore). That's somewhat less than perfect and we can do better.
How Apple Thinks Deployment Should Be Done
That's the way Apple wants us to work. It's better than it has been previously, but at the end of the day Apple is still forcing you through their review process even for your beta builds you want to distribute via TestFlight (at least for what they call external testers).
It may be a good fit for most setups anyway, but if you want to deliver your beta-bits internally without involving Apple, you need an alternative way.
Get Some More Control
If you want to have more control over the internal process, you still have two additional options:
- Know all devices your app is being installed on for test-purposes, which is kinda annoying because you have to recreate your provisioning profile with every change in your device setup. And you are limited to a maximum of 100 device registrations per year.
- Get into Apple's enterprise program for 299 USD a year. This enables you to deploy your apps internally almost without limitations. At least you don't have to know every device in advance, which is a huge advantage and provides peace of mind. If it's legally totally accurate ... that's something you better discuss with your lawyer ;-).
The Problem Of Different Certificates
At the end you still have the problem of having two builds for each target: because you can't distribute an .ipa signed with a certificate for the AppStore to your internal beta-testers. And you can't upload an .ipa signed with an enterprise or development certificate to the AppStore.
I've seen some ways people work with that situation, the most common appears to be to have two different builds at different points in time.
Usually internal test builds are triggered whenever something needs to be tested or a new milestone is hit. Maybe there's even something equivalent to a staging environment where only bugs are being fixed and no new features are added (compare git-flow).
But whenever a new version is ready for the AppStore, a new build is being made – now with the correct provisioning profile and certificate for publishing on iTunes Connect.
The Problem Of Different Builds For Testing and Production
Now you may have a solution that feels good – you have figured out all the crap around provisioning and signing Apple forces you through. Your process may even be automated. But you still have two builds. Why's that bad?
Because you want to to ship the bits to the AppStore you have exhaustively tested internally.
Let that sink for a moment.
So what could possibly go wrong with having two different builds? The main point: Your production build could differ from your test build. That is really bad.
- Someone could quickly add some well-meant but breaking changes to your master branch which then are included in the production build but were not in the latest test bits.
- The same applies for manually or even automatically resolved merge-conflicts. (Which, to be honest, would be a flaw in your overall process if they occur.)
- Your configuration would propably differ, producing different behavior.
- Your build tools could use different versions, introducing odd behavior or even bugs. Not completely unlikely in the team of Apple and Xamarin.
Solution: Build Once, Deploy Twice
- SOLUTION_PATH – The fully qualified path to your *.sln.
- BUILD_DIR – A custom output directory you want your artefacts to be stored in.
- APP_STORE_DIR – Usually the */bin/iPhone/AppStore/YourApp.app folder.
- INTERNAL_PROVISIONING_PROFILE_PATH – The fully qualified path to your .mobileprovision file for your internal build.
- INTERNAL_CERT_NAME – The name of your cert. If you don't know it, find it in the key chain application of OS X.
Make sure the right AppStore certificate is installed on your build machine and Xcode knows about the right provisioning profile.
1. Build Your AppStore Build
Take a look at step 3 over there to see how you can configure your *.plist automatically.
Let's start. First build the .ipa ...
/Applications/Xamarin Studio.app/Contents/MacOS/mdtool -v build "--configuration:AppStore|iPhone" "$SOLUTION_PATH"
... then pack it.
xcrun -sdk iphoneos PackageApplication -v "$APP_STORE_DIR" -o "$BUILD_DIR/AppAppStore.ipa"
2. Build Your Internal Build
Now take everything produced in the previous step and adjust it to the needs of your internal build. Start with all the configurations again, for example:
/usr/libexec/PlistBuddy -c "Set :CFBundleIdentifier xyz.yourapp.com" "$APP_STORE_DIR/Info.plist"
Then re-sign your AppStore build:
rm -rf "$APP_STORE_DIR/_CodeSignature/" cp "$INTERNAL_PROVISIONING_PROFILE_PATH" "$APP_STORE_DIR/embedded.mobileprovision" codesign -f -s "$INTERNAL_CERT_NAME" "$APP_STORE_DIR" --entitlements "./configs/Entitlements.plist"
And pack it:
xcrun -sdk iphoneos PackageApplication -v "$APP_STORE_DIR" -o "$BUILD_DIR/AppInternal.ipa"
Now you know how to build both AppStore.ipa and AppInternal.ipa in one single step. Those packages only differ in the signature and the embedded provisioning profile, but the application itself is absolutely the same in every single bit.
That will give every stakeholder some peace of mind, because it makes sure there is nothing shipped to your users that has not been gone through your test-process.
What do you think? Drop me a line and let me know!