Setting up Xamarin.iOS builds in Visual Studio Team Services (formerly Visual Studio Online) through MacinCloud, and a bonus (HockeyApp)
A few weeks ago we needed to setup CI/CD builds in HealthClinic.biz private repo (here is the public one), hosted in Visual Studio Team Services (VSTS from now on), for the Patients app made with Xamarin.iOS. Since the suite includes a Cordoba app for iOS as well, such was being built through MacinCloud, so the agent pool was already configured (if not, here can be found how to).
Nowadays, VSTS offers a Xamarin.iOS build step where, if the agent pools support such capability, wraps everything needed to generate the final APP/IPA (1st for Simulator, OK for CI; 2nd for device, needed for CD) just selecting a Visual Studio Solution to build. The issue is, what happens with all the other steps needed prior to this point? And, which are those?
One of the things to solve prior building a Xamarin.iOS app is its NuGet packages. Not just NuGet, but Xamarin Components as well, since Patients has dependencies on both places.
Starting with NuGet, although there is a specific build task for such, we have opted for a command line one (every step apart from Xamarin.iOS build is a command line task indeed), with the following:
- Tool: mono
- Arguments: exe restore src/MyHealth.Client.Core/packages.config -PackagesDirectory packages
Instead of restoring the whole solution through the built in task for NuGet, we have added a different step for the iOS project:
- Tool: mono
- Arguments: exe restore src/MyHealth.Client.iOS/packages.config -PackagesDirectory packages
HealthClinic.biz offers multiple solutions to see the whole suite: just the native apps, just the Xamarin ones, the web API, etc. But to fit perfectly for our build, we just want to restore packages for the Core and iOS projects.
In a similar way to NuGet, we also want to restore Xamarin Components. Patients for iOS makes use of a calendar control which’s supplied from this repository, so it needs to be restored as well as some other components. Xamarin provides a command-line tool for that, which’s served as a ZIP file on a fixed URL, to facilitate its use on automated tasks. The steps needed are the following:
- Download Xamarin Components command-line tool
- Tool: curl
- Arguments: https://components.xamarin.com/submit/xpkg -o xpkg.zip
- Unzip Xamarin Components command-line tool
- Tool: unzip
- Arguments: -o xpkg.zip
- * Remove Xamarin Components’ previous auth. cookie to avoid asking for user input
- Tool: rm
- Arguments: ../../../../.xamarin-credentials
- Log into Xamarin Components
- Tool: mono
- Arguments: xamarin-component.exe login $(xamarinuser) -p $(xamarinpassword)
- Restore Xamarin Components for whole solution
- Tool: mono
- Arguments: xamarin-component.exe restore 04_Demos_NativeXamarinApps.sln
All of the five steps are simple and self-explanatories but, third one, may have retained your attention. Why is it needed to remove any cookie? And why is there that ugly hardcoded path? xamarin-component.exe stores somewhat like a cookie within $HOME/.xamarin-credentials. If such isn’t removed prior to a next authentication, the tool asks for permission, handling user input, which’s not possible in a build scenario like ours. We’ve looked for workarounds on this, even doing a trick with env. vars, but didn’t end up working in MacinCloud. Maybe with a little bit more time we could improve this, it’d be nice to hear your feedback. Even Xamarin could add a flag to avoid user interaction, so there’d be no need to workaround this. 😉
Finally, the last step is the iOS build it-self. VSTS provides a step type for this, Xamarin.iOS, so no need to go further on it. It’s just worth mentioning if you’re just interested in CI (i.e. knowing whether current source code just builds fine), enable the checkbox Build for iOS Simulator. It won’t generate the IPA, but APP, which’s the minimum needed to achieve what you want, with lower building times.
Having our CI build working, the natural step is to go through a CD one, and publish the IPA into HockeyApp, so your customers will be able to download such into their iPhones and iPads with just a few taps.
The first thing to solve is to configure MacinCloud agents to have every signing requirement for our app. In order to generate the IPA, the agents need 1) the provisioning profile which matches our app’s bundle id and 2) the private key to digitally sign.
1) can be downloaded from your Apple’s Member Center account, just navigate to the desired provisioning profile and download it; but 2), understanding you have access to a Mac which’s already able to build and sign the app by it-self, requires a small detail: the private key MacinCloud needs isn’t just the private key being exported from Keychain Access, but the certificate paired with it which, in the end, has the private key along with it as well. A picture is worth thousand words:
You must right click in the highlighted row (if you’d like Distribution one, otherwise Developer) and choose Export “iPhone Distribution: Bla Bla Bla”… The resulting P12, along with the password used to export it, will be sent to MacinCloud.
MacinCloud current wizard allows to upload 1) a P12 and its password pair OR 2) a Provisioning Profile (please note the OR: there’s no need to supply both at the same time).
From my perspective, there are a few tweaks which could improve the UX on this wizard:
- Currently, when Save is pressed, the wizard closes and a “Success” alert appears. It’d be great to know more info on such success, something like:
- “Success: the certificate “iPhone Distribution: Bla Bla Bla”, along with its private key, was successfully registered”, or
- “Success: the Provisioning Profile Bla Bla Bla was successfully added”
- It’d be awesome to have a small panel where one could see which certificates and profiles are already present on the machine. It’d have saved me lots of attempts to configure the build machine
Once the build machine is set up, by only changing the Xamarin.iOS build step to not Build for iOS Simulator, it’ll consequently generate the IPA for the device. Such can be uploaded to HockeyApp using a last step:
- Deploy to HockeyApp using curl
- NOTE: There’s an existing HockeyApp task, build it was freezing the build when I tried two weeks ago :-(, so I had to go manually
- Tool: curl
- Arguments: -F “status=2” -F “notify=1” -F “notes=Automatically published from VSTS build.” -F “notes_type=0” -F “ipa=@src/MyHealth.Client.iOS/bin/iPhone/Ad-Hoc/MyHealthClientiOS-1.0.ipa” -F “dsym=@src/MyHealth.Client.iOS/bin/iPhone/Ad-Hoc/MyHealthClientiOS.app.dSYM.zip” -H “X-HockeyAppToken: $(HockeyAppToken)” https://rink.hockeyapp.net/api/2/apps/$(AppId)/app_versions/upload
Even dSYMs can be sent as well with just one more step before above one:
- Zip dSYMs
- Tool: zip
- Arguments: -r src/MyHealth.Client.iOS/bin/iPhone/Ad-Hoc/MyHealthClientiOS.app.dSYM.zip src/MyHealth.Client.iOS/bin/iPhone/Ad-Hoc/MyHealthClientiOS.app.dSYM