Building and Deploying apps using VSTS and HockeyApp - Part 1 : iOS

If you are like me and you need a process for continuous integration and deployment for mobile apps on iOS, Android and Windows then this series of posts might help.

I tend to use a slightly custom version of Gitflow for new projects. Typically I’d have 3 permanent branches as follows:

  • develop (alpha)
  • beta
  • master (production)

The idea:

Developers will create feature branches of develop. They work on this feature branch as long as they need to. They can also rebase onto develop when they need to get latest changes from develop in their branch. After they complete the feature, the developers do a final rebase onto develop to put their changes on top of develop (for easy and linear merging back into develop). They push changes to the origin (i.e VSTS git repo) and send a pull request or just an email to the code reviewer.

The reviewer will either tell the developer to fix something or merge (Fast forward) into develop. After a successful merge the feature branch is deleted from origin by reviewer and an email is sent to dev team to remove that branch if someone has it checked out locally. The dev team will also need do a git fetch and prune to clean up the deleted branches.

Finally once a week some team member will merge develop with beta that triggers a build and deployment to the HockeyApp for internal beta testing.

Creating a project in VSTS

If you are new to VSTS and don’t know how to create a new project, follow this link to create the project in Visual studio team services.

Handy Tip: To create a new project.

New project SC

Add name of the project, we’d use process template as Scrum’ & version control as GIT.

Setting up Build Agents

By default VSTS comes with 2 built-in agents.

  • Default
  • Hosted

Build agents are the work horses of a build system and they compile the platform code into executables like ipa, apk etc. To build ios apps you need a build agent that can run on OSX and has all the correct tooling installed (XCode etc.)

I use the default agent for building iOS applications using a service called macincloud.com. As the name suggests it is an online service that is pre-configured with all required tooling to build Xamarin.iOS apps. It is a paid service, but worth every cent as it integrates well with VSTS. Follow the instruction here to setup macinclod with VSTS account. Upon successful setup this will start showing up as the Default agent in the VSTS under settings > Agent Queues.

Handy Tip: Settings is the gear icon in the top right corner of the screen.


Agent Pool Default

Agent Pool Hosted

The green tick next to the agents indicate they the agent is available for builds.

Important: Make sure to add yourself as an Administrator in the Roles tab next to Agents for both agents.

Agent Roles

The next step is to get the code solution into VSTS Git repository. Follow this link for steps on how to do this. Once the code is committed and pushed to origin, it will be visible under the CODE menu. The next step is to switch over the BUILD tab and start adding some build definitions.

Creating the iOS Build definition

Develop Branch

  • Click the + New Definition” button to get started.
  • Select the Xamarin.iOS template to get started.
  • Create New Build Definition screen.
    • Repo source = Project Team Project
    • Default branch = Develop
    • Tick Continuous integration (build whenever this branch is updated)
    • Default Agent Queue = Default
    • Select Folder = \
  • Delete existing build steps to start with a clean slate.

Before we add any build steps, we need to configure a few things.

  • Repository tab - Clean > true
  • Variables tab
    • Name: BuildPlatform
    • Value: any cpu

PCL Build

Before adding the first build step, it is worthwhile noting that the solution needs to be split into 3 separate sub solutions. This makes it easy for VSTS to build the 3 platforms separately on different hosts (Default and Hosted). We’ll still need the original solution, this is what the developers will open locally when they are working with Visual studio and it will load all projects.

  1. ios solution - Built on the Default agent and only builds the ios project.
  2. droid solution - Built on Hosted agent.
  3. windows solution - Built on Hosted agent.

Click the + Add Build step button to get started

Step # 1: Nuget Installer
Field Name Field Value
Path to solution Path to ios only solution
Path to Nuget.config [Custom Nuget File].config

A custom nuget file could be needed if you need multiple sources outside of Nuget to fetch some private libraries. In our case we had some private enterprise libs that our code depends on. Below is a sample of such a file. Place this file at the same level as the solution in your file explorer.

<?xml version="1.0" encoding="utf-8"?>
    <add key="enabled" value="True" />
    <add key="automatic" value="True" />
    <clear />
    <add key="nuget.org" value="https://www.nuget.org/api/v2" />

    <!-- change this to your project feed. -->
    <add key="MyGetFeed" value="https://www.myget.org/path/guid/api/v2" /> 
  <disabledPackageSources />
    <add key="All" value="(Aggregate source)" />
Field Name Field Value
Installation Type restore
Disable Local Cache true
Step # 2: Build Xamarin.iOS
Field Name Field Value
Solution iosonly.sln
Configuration Release
Create App Package true
Build for iOS simulator true

Beta Branch

Beta steps

Step # 1: Nuget Installer

Same as Develop branch

Step # 2: Version assemblies using /Info.plist
Field Name Field Value
Source Path Path to the iOS csproj
File Pattern /info.plist
Build Regex Pattern \d+

Advanced tab

Field Name Field Value
Build Regex Group Index 0
Regex Replace Pattern <key>CFBundleVersion(.|\n)*<string>\d+(?=<\/string>)
Prefix for Replacements <key>CFBundleVersion</key><string>
Fail If no Target Match Found true
Step # 3: Print Info.plist
Field Name Field Value
Tool cat
Arguments Solution Folder/Project Folder/Info.plist
Step # 4: Build Xamarin.iOS
Field Name Field Value
Solution iosonly.sln
Configuration $(BuildConfiguration).This is a build variable. Change this to release from any cpu’
Create App Package true
Build for iOS Simulator false

Signing & Provisioning

Note: I typically create a folder called Build in the iOS project that contains all my mobileprovision and p12 certificates. This folder is referenced by this build step to compile iOS apps on macincloud.

Field Name Field Value
Override Using File Contents
P12 Certificate File Path to the p12 file in the solution build folder.
P12 Password Your Password or use Secure Build Variable (click the lock icon next to the variable to make it secure) and reference here
Provisioning Profile File Path to your mobileprovision file.

Note: Typically you’d create 2 separate profiles. One for internal distribution within the organization (I typically call it HockeyApp profile) and one for putting the app on the store (I typically call it the store profile). For beta builds we’ll use the HockeyApp profile as we want to deploy to Hockey after a successful build.

Field Name Field Value
Remove Profile After Build true

To learn more about p12 and mobile provisioning profile, google create p12 & mobileprovision files for iPhone”

Step # 5: Deploy to HockeyApp

Follow this tutorial to setup HockeyApp with VSTS

  • if everything is setup correctly the Hockey connection will start appearing in the HockeyApp Configuration dropdown. If not, click the manage button to fix any issues.
Field Name Field Value
AppId Guid from HockeyApp project. If project does not exist, create one in hockey to get this ID.

Hockey App Id

Field Name Field Value
Binary File Path Path To Ios project/**/*.ipa
Symbols File Path Path To Ios project/**/*.dSYM
Publish true

Master Branch

ios master

Step # 1: Nuget restore

Same as the Beta branch

Step # 2: Update Hockey ID

In the Appdelegate’s FinishedLaunching method of our iOS project, we read the HockeyId from a file called HockeyID.txt. This file lives in the build directory of the project as well. In order to change this to a different id, we use this build step to replace the contents of this file before the build is started. This shell script only has a single line of code:

sed -i "" 's/<Old HockeyID here>/<New HockeyId here>/g' HockeyID.txt

The configuration for this step is as follows:

Field Name Field Value
Script Path Path to .sh script in the Build directory of the project

Advanced Tab

Field Name Field Value
Specify Working Directory true
Woking Directory Path to ios Build folder.
Step # 3: Version Info.plist

Same as beta branch

Step # 4: Print Info.plist

Same as beta branch

Step # 5: Build Xamarin.iOS

Same as beta branch, except the provisioning profile changes to the Apple store profile instead of the Hockey profile.

Step # 6: Copy Artifacts
Field Name Field Value
Source Folder Path to ios project folder
Contents **/*.ipa
Target Folder $(build.artifactstagingdirectory)
Advanced > Clear Target Folder true
Step # 7: Publish Artifacts
Field Name Field Value
Path to Publish $(build.artifactstagingdirectory)
Artifact Name AppStore-Build
Artifact Type Server

Lastly, an optional build definition can also be created for the ios-store-hockey configuration. This is useful in scenarios when you have to fix a bug in some existing production code in the master branch. In this definition all the build steps are exactly the same as the beta ios build definition with the only exception of a different app id in the Deploy to hockey step.

For further information on provisioning profiles checkout this link on Xamarin’s website.

Up next How I diagnosed High CPU usage using Windbg Building and Deploying apps using VSTS and HockeyApp - Part 2 : Android
Latest posts Refactor react code to use state store instead of multiple useState hooks Notes on Python Threat Modelling - Using Microsoft STRIDE Model WCAG - Notes Flutter CI/CD with Azure Devops & Firebase - iOS - Part 1 Flutter CI/CD with Azure Devops & Firebase - Android - Part 2 How to samples with AWS CDK A hashicorp packer project to provision an AWS AMI with node, pm2 & mongodb Some notes on Zeebe (A scalable process orchestrator) Docker-Compose in AWS ECS with EFS volume mounts Domain Driven Design Core Principles Apple Push Notifications With Amazon SNS AWS VPC Notes Building and Deploying apps using VSTS and HockeyApp - Part 3 : Windows Phone Building and Deploying apps using VSTS and HockeyApp - Part 2 : Android Building and Deploying apps using VSTS and HockeyApp - Part 1 : iOS How I diagnosed High CPU usage using Windbg WCF service NETBIOS name resolution woes The troublesome Git-Svn Marriage GTD (Getting things done) — A simplified view Javascript Refresher Sharing common connection strings between projects A simple image carousel prototype using Asp.net webforms and SignalR Simple logging with NLog Application logger SVN Externals — Share common assembly code between solutions Simple async in .net 2.0 & Winforms Clean sources Plus Console 2 — A tabbed console window