Pipeline Templates: Complete Azure Pipelines Example for a Uno Project for iOS, Android and Windows

My last post was a bit of a long one as it covered a bunch of steps for setting up the bits and pieces required for signing an application for different platforms. In this post I just wanted to provide a complete example that shows a single multi-stage (6 in total) Azure Pipelines pipeline for building a Uno application for iOS, Android and Windows (UWP) and releasing them to App Center.

Secure Files

In my last post I showed how to create and populate Secure Files in Azure Pipelines. Any certificate or provisioning profile you need to use in your pipeline should be added to the Secure Files section of the Library in Azure Pipeline. My list of Secure Files looks like this:

Here we can see that we have the signing certificates for iOS and Windows, and the keystore for Android. Then we have two iOS provisioning profiles, one for my XF application and the other for my Uno application.

Variable Groups

I’ve extracted most of the variables I use in my pipeline into one of two variable groups:

The Common Build Variables are those variables that can be reused across multiple projects.

The Inspector Uno Build Variables are those variables that are specific to this project. For example it includes the AppCenter ids for the iOS, Android and Windows applications. It also includes the iOS provisioning profile which is specifically tied to this application.

Pipeline

Here’s the entire pipeline:

resources:
  repositories:
    - repository: builttoroam_templates
      type: github
      name: builttoroam/pipeline_templates
      ref: refs/tags/v0.5.0
      endpoint: github_connection
  
variables:
  - group: 'Common Build Variables'
  - group: 'Inspector Uno Build Variables'
  - name: ios_enabled
    value: 'true'
  - name: windows_enabled
    value: 'true'
  - name: android_enabled
    value: 'true'
 
stages:
- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and whether it's enabled
    stage_name: 'Build_Android'
    build_android_enabled: $(android_enabled)
    # Version information
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    # Signing information
    secure_file_keystore_filename: '$(android_keystore_filename)'
    keystore_alias: '$(android_keystore_alias)'
    keystore_password: '$(android_keystore_password)'
    # Solution to build
    solution_filename: $(solution_file)
    solution_build_configuration: $(solution_build_config)
    # Output information
    artifact_folder: $(artifact_android_folder)
    application_package: $(android_application_package)

- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and dependencies
    stage_name: 'Deploy_Android'
    depends_on: 'Build_Android'
    deploy_appcenter_enabled: $(android_enabled)
    environment_name: $(appcenter_environment)
    # Build artifacts
    artifact_folder: $(artifact_android_folder)
    application_package: $(android_application_package)
    # Signing information (for Android repack to APK)
    secure_file_keystore_filename: '$(android_keystore_filename)'
    keystore_alias: '$(android_keystore_alias)'
    keystore_password: '$(android_keystore_password)'
    # Deployment to AppCenter
    appcenter_service_connection: $(appcenter_service_connection)
    appcenter_organisation: $(appcenter_organisation)
    appcenter_applicationid: $(appcenter_android_appid)


- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and whether it's enabled
    stage_name: 'Build_Windows'
    build_windows_enabled: $(windows_enabled)
    # Version information
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    # Signing information
    windows_cert_securefiles_filename: '$(windows_signing_certificate_securefiles_filename)'
    windows_cert_password: '$(windows_signing_certificate_password)'
    # Solution to build
    solution_filename: $(solution_file)
    solution_build_configuration: $(solution_build_config)
    # Output information
    artifact_folder: $(artifact_windows_folder)
    application_package: $(windows_application_package)

- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and dependencies
    stage_name: 'Deploy_Windows'
    depends_on: 'Build_Windows'
    deploy_appcenter_enabled: $(windows_enabled)
    environment_name: $(appcenter_environment)
    # Build artifacts
    artifact_folder: $(artifact_windows_folder)
    application_package: $(windows_application_package)
    # Deployment to AppCenter
    appcenter_service_connection: $(appcenter_service_connection)
    appcenter_organisation: $(appcenter_organisation)
    appcenter_applicationid: $(appcenter_windows_appid)

- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and whether it's enabled
    stage_name: 'Build_iOS' 
    build_ios_enabled: $(ios_enabled)
    # Version information
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    # Solution to build
    solution_filename: $(solution_file)
    solution_build_configuration: $(solution_build_config)
    # Signing information
    ios_plist_filename: 'src/Apps/DotNet/Uno/InspectorUno/InspectorUno/InspectorUno.iOS/Info.plist'
    ios_cert_password: '$(ios_signing_certificate_password)'
    ios_cert_securefiles_filename: '$(ios_signing_certificate_securefiles_filename)'
    ios_provisioning_profile_securefiles_filename: '$(ios_provisioning_profile_securefiles_filename)'
    # Output information
    artifact_folder: $(artifact_ios_folder)
    application_package: $(ios_application_package)

- template:  azure/mobile/[email protected]_templates
  parameters:
    # Stage name and dependencies
    stage_name: 'Deploy_iOS'
    depends_on: 'Build_iOS'
    deploy_appcenter_enabled: $(ios_enabled)
    environment_name: $(appcenter_environment)
    # Build artifacts
    artifact_folder: $(artifact_ios_folder)
    application_package: $(ios_application_package)
    # Deployment to AppCenter
    appcenter_service_connection: $(appcenter_service_connection)
    appcenter_organisation: $(appcenter_organisation)
    appcenter_applicationid: $(appcenter_ios_appid)


Pipeline Templates v0.5.0

Most of the v0.5.0 release has been tidying things up, reducing the number of required parameters by making the template smarter and increasing consistency across the templates

Breaking Changes:

  • build-xamarin-android.yml – changed build_platform to solution_target_platform parameter
  • build-xamarin-windows.yml – changed build_platform to solution_target_platform parameter
  • build-xamarin-windows.yml – changed windows_appxupload_name parameter to windows_upload_name to reflect support for msix

Other Changes:

  • build-xamarin-[iOS/android/windows].yml – added depends_on parameter so that the stages can be ordered
  • build-xamarin-[iOS/android/windows].yml – artifact_name, artifact_folder and application_package are no longer required and have default values
  • build-xamarin-android.yml – supports building either aab or apk based on the application_package parameter
  • build-xamarin-windows.yml – windows_upload_name parameter isn’t required as it has a default value based on the bundle name
  • deploy-appcenter.yml – added conditions to all steps so that pipeline doesn’t break if build stage doesn’t generate any output
  • All template – documentation added to parameters and steps

Pipeline Templates: Building and Deploying Uno Apps for iOS, Android and Windows

In my previous posts covering the Pipeline Templates I’ve discussed building a Xamarin.Forms apps for iOS, Android and Windows (UWP) and subsequently deploying them to AppCenter. In this post we’re going to look at doing the same with a Uno application. Given that Uno is built on top of the core Xamarin functionality, the process for both build and deploy should be fairly similar. Rather than just stepping through using the template, I’m going to cover creating a new Uno project and setting up the corresponding multi-stage pipeline to build and deploy to AppCenter.

Also, I’ve pushed v0.3.0 of the pipeline templates, which I’ll summarise at the end of this post.

Creating a Uno Application

Let’s get into it and start by creating a Uno application. Before proceeding, make sure you grab the latest Uno extension for Visual Studio that includes the project templates. From the Create a new project dialog, enter “uno” into the search box and then select the Cross-Platform App (Uno Platform) template.

Next, enter a name and location for your new application

When you hit the Create button, Visual Studio will generate a new solution with five projects, representing the head projects for iOS, Android, Windows (UWP) and Web (WASM), along with a shared project.

After creating the application, you should go through each platform and check that it builds and runs. The WASM project will take a while to build and run the first time as it needs to download some components first – don’t be alarmed it nothing appears to happen for a long time.

Build Configuration

One thing I do like to tidy up for my applications is the build configurations. This is often overlooked by developers and then wonder why they have to wait around for say the Android project to build, when they have the UWP project selected as their start up project. Here are the Release configurations I have set for the different platforms

Release Configuration with Any CPU
Release Configuration with iPhone
Release Configuration with x64 (x86 and ARM platforms are the same except the Platform for InspectorUno.UWP is x86 and ARM respectively)

Building Uno Applications

We’ll go through each Uno application separately and I’ll endeavor to highlight typical pain points that developers face. If you’re using the pipeline_templates and you run into issues, message me on Twitter and I’ll help you out. Where there are common frustrations, I’ll work to improve the templates to make them easier to use.

Let’s start by creating a new pipeline in Azure DevOps. I’m not going to step through the pipeline wizard but for what we want to do, just select the appropriate source code respository and an empty YAML file. When you get to the YAML editor, enter the following.

trigger: none
resources:
  repositories:
    - repository: builttoroam_templates
      type: github
      name: builttoroam/pipeline_templates
      ref: refs/tags/v0.3.0
      endpoint: github_connection
  
variables:
  - group: 'Uno Build Variables'

There are three parts to this inital YAML. Firstly, we’re disabling the CI trigger whilst we’re configuring the build. As we’ll be committing changes to both the build pipeline and source code, it’s as well to only have builds triggered when you’re ready, otherwise you’ll be continually cancelling builds.

Next, the resources section is where we pull in the pipeline templates. In this case we’re targeting the v0.3.0 release to ensure the templates don’t change and break our builds in the future.

Lastly, we’re pulling in the Uno Build Variables variable group. You can pick whatever name you want for this group but it needs to match the name of the variable group defined under the Library tab on Azure DevOps.

Android – Build

Starting with Android we’re going to add stages to the YAML file (note you only need to include the stages element once and then add the individual stages by referencing the corresponding template). Here’s the Android build

stages:
- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Build_Android'
    build_android: $(android_enabled)
    solution_filename: 'src/Apps/DotNet/Inspector.Uno.sln'
    solution_build_configuration: 'Release'
    build_number: '$(Build.BuildId)'
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    artifact_name: 'inspector-build'
    artifact_folder: 'Android_output'
    application_package: 'Inspector-Uno-Android.aab'
    secure_file_keystore_filename: '$(android_keystore_filename)'
    keystore_alias: '$(android_keystore_alias)'
    keystore_password: '$(android_keystore_password)'

There are a few things you need to have setup for this build to work:

  • solution_filename – make sure the path to your solution file is correct, relevant to the root of your code repository.
  • $(version_prefix) – make sure the version_prefix variable exists in your variable group and has the format X.Y (eg 1.0)
  • $(android_keystore_filename) – make sure the android_keystore_filename variable exists in your variable group and that it’s value matches the Secure file name of the keystore file uploaded to the Secure files under the Library in Azure DevOps.
  • $(android_keystore_alias) – make sure the android_keystore_alias variable exists in your variable group and that it is the the alias that you specified when creating the keystore. This variable should be marked as private by clicking the lock beside it in the portal – this hides it both in the variable group editor in the portal and in the log files.
  • $(android_keystore_password) – make sure the android_keystore_password variable exists in your variable group and that it is the password you specified when creating the keystore. Again this should be marked as private.

Frustratingly, the Android build didn’t just work for a couple of reasons:

  • Firstly, there was an issue where one of the images has a file name that isn’t supported by the version of Visual Studio and/or Android tooling on the windows-latest build agent, resulting in the following error:
    2020-02-08T06:04:55.7036355Z ##[error]src\Apps\DotNet\Uno\InspectorUno\InspectorUno\InspectorUno.Shared\Assets\Square44x44Logo.targetsize-24_altform-unplated.png(0,0): Error APT0003: Invalid file name: It must contain only [^a-zA-Z0-9_.]+.
    I simply excluded the file Square44x44Logo.targetsize-24_altform-unplated.png from the shared project
  • Secondly, the build agent include the Android NDK but doesn’t specify the appropriate environment variables (eg ANDROID_NDK_HOME), resulting in the following error:
    2020-02-07T12:56:19.5428557Z ##[error]C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Xamarin\Android\Xamarin.Android.Common.targets(2843,3): Error XA5101: Could not locate the Android NDK. Please make sure the Android NDK is installed in the Android SDK Manager, or if using a custom NDK path, please ensure the $(AndroidNdkDirectory) MSBuild property is set to the custom path.
    The solution here was for me to update the build-xamarin-android.yml to include these environment variables. You shouldn’t run into this error if you’re using v0.3.0 of the pipeline templates.

Android – Deploy

Here’s the YAML for deploying the Android application to AppCenter

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Deploy_Android'
    depends_on: 'Build_Android'
    environment_name: 'Inspector-Alpha'
    artifact_name: 'inspector-build'
    artifact_folder: 'Android_output'
    application_package: 'Inspector-Uno-Android.aab'
    appcenter_service_connection: 'AppCenterInspectorCI'
    appcenter_organisation: 'thenickrandolph'
    appcenter_applicationid: 'Inspector-Uno-Alpha'
    appcenter_release_notes: 'Release from deploy pipeline'
    secure_file_keystore_filename: '$(android_keystore_filename)'
    keystore_alias: '$(android_keystore_alias)'
    keystore_password: '$(android_keystore_password)'

Again there are a few things you need to check here but most importantly for this to work you need to setup an application in AppCenter. I created a new application

After creating the application, look at the url that appears in the browser. The piece highlighted in green is the value you need to set for the appcenter_organisation and the yellow is for the appcenter_applicationid.

You also need to make sure you setup a Service Connection between Azure DevOps and AppCenter (in this case it was given the name ‘AppCenterInspectorCI’ and assigned to the appcenter_service_connection property)

The other properties you need to check are:

  • depends_on – make sure this matches the stage_name property set on the android build stage
  • artifact_name, artifact_folder and application_package – make sure these match the corresponding value on the android build stage
  • secure_file_keystore_filename, keystore_alias and keystore_password – these should be set to the same as on the android build stage and are required so that the release process can extract and sign a fat apk from the aab file.

iOS – Build

Before we get into building your iOS application, let’s make sure you have both a signing certificate and a provisioning profile ready to go. If you have both of these, you can jump over the next two sections. These instructions can be stepped through on Windows, without the need for a Mac!

certificate

  • Install OpenSSL (Download from https://slproweb.com/products/Win32OpenSSL.html)
  • Open command prompt
  • Add OpenSSL to path (alternatively you can permanently add it to your Path environment variable if you want to have it accessible across all command prompts)
    set PATH=%PATH%;C:\Program Files\OpenSSL-Win64\bin\
  • Generate the signing request
    openssl genrsa -out ios_distribution.key 2048
    openssl req -new -key ios_distribution.key -out CertificateSigningRequest.certSigningRequest
  • Sign into the Apple Developer portal and go to Certificates (https://developer.apple.com/account/resources/certificates)
  • Click the + button to start the process of adding a new certificate
  • Select Apple Distribution from the list of certificate types and then Continue
  • Use the Choose File link to select the CertificateSigningRequest.certSigningRequest file you created in the previous steps, and then Continue
  • Important – Make sure you click the Download button to download your file. It’ll download as a file called distribution.cer unless you choose to rename it.
  • Move the distribution.cer file to the same folder that you’ve been working in
  • Complete the certificate creation from the command prompt (making sure you change PASSWORD to be a password of your choice)
    openssl x509 -in distribution.cer -inform DER -out ios_distribution.pem -outform PEM
    openssl pkcs12 -export -inkey ios_distribution.key -in ios_distribution.pem -out ios_distribution.p12 -passout pass:PASSWORD

With these steps completed you have a certificate file, ios_distribution.p12, which you can upload as a Secure File to Azure DevOps.

You’ll also need the signing identity, which we can get directly from the ios_distribution.pem that was generated along the way.
openssl x509 -text -noout -in ios_distribution.pem

Application Identifier

Before you can create a provisioning profile you need to setup an identity for your application in the Apple Developer portal. Go to the Identities tab and click on the + button to register a new application.

Select App IDs and then Continue

Enter both a Description and a Bundle ID. You may also need to select an App ID Prefix if you belong to a team. Next click Continue, followed by the Register button to complete the process of registering your application’s identity.

Note: Currently you will need to update the Info.plist file of your iOS application to set the CFBundleIdentifier to match the Bundle ID specified when creating the App ID.

Provisioning Profile

To create the provisioning profile, click on the Profiles tab in the Apple Developer portal. Click the + button to start the process of creating a new provisioning profile.

Select the Ad Hoc option under Distribution and then Continue.

Select the App ID that you previously created, then Continue

Select the certificate that needs to be used to sign the application, then Continue

Select the devices that will be able to install the application, then Continue

Give the provisioning profile a name and then click Generate.

Download the provisioning profile and add it as a Secure File in the Library in Azure DevOps. You’ll also need the provisioning profile id, which you can extract from the provisioning profile using any text editor. Open the provisioning profile and look for the UUID key; the string value immediately after it is the profile id.

Ok, we’re now ready for the build stage

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Build_iOS' 
    build_ios: true
    solution_filename: 'src/Apps/DotNet/Inspector.Uno.sln'
    solution_build_configuration: 'Release'
    ios_cert_password: '$(ios_signing_certificate_password)'
    ios_cert_securefiles_filename: '$(ios_signing_certificate_securefiles_filename)'
    ios_provisioning_profile_securefiles_filename: '$(ios_provisioning_profile_securefiles_filename)'
    build_number: '$(Build.BuildId)'
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    ios_signing_identity: '$(ios_signing_identity)'
    ios_provisioning_profile_id: '$(ios_provisioning_profile_id)'
    artifact_name: 'inspector-build'
    artifact_folder: 'iOS_output'
    application_package: 'Inspector-Uno-iOS.ipa'

iOS – Deploy

The deploy stage is relatively simple

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Deploy_iOS'
    depends_on: 'Build_iOS'
    environment_name: 'Inspector-Alpha'
    artifact_name: 'inspector-build'
    artifact_folder: 'iOS_output'
    application_package: 'Inspector-Uno-iOS.ipa'
    appcenter_service_connection: 'AppCenterInspectorCI'
    appcenter_organisation: 'thenickrandolph'
    appcenter_applicationid: 'Inspector-Uno-iOS-Alpha'
    appcenter_release_notes: 'Release from deploy pipeline'

Windows – Build

The build stage for Windows (UWP) is

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Build_Windows'
    build_windows: true
    solution_filename: 'src/Apps/DotNet/Inspector.Uno.sln'
    solution_build_configuration: 'Release'
    uwpBuildPlatform: 'x86'
    build_number: '$(Build.BuildId)'
    full_version_number: '$(version_prefix).$(Build.BuildId)'
    windows_cert_securefiles_filename: '$(windows_signing_certificate_securefiles_filename)'
    windows_cert_password: '$(windows_signing_certificate_password)'
    artifact_name: 'inspector-build'
    artifact_folder: 'Windows_output'
    application_package: 'Inspector-Uno-Windows.msixbundle'
    windows_appxupload_name: 'Inspector-Uno-Windows.msixupload'

In order to build your Windows application you’re going to need a signing certificate. Microsoft has good documentation for creating the certificate which I would encourage you to follow. The important thing is to upload the certificate to the Secure files section under the Library tab on Azure DevOps and the corresponding password as a private variable. Damien Aicheh has a good tutorial on uploading certificates to Azure DevOps – don’t worry about the section on using the certificates as the Windows build template already does this for you.

Note: You also don’t need to worry about updating your package.appxmanifest so that the Publisher matches the Subject on the signing certificate. The Windows build template takes care of this too.

There are a couple of other parameters you’ll want to check off

  • uwpBuildPlatform – in this YAML this is only set to x86 for efficiency. If you’re planning to deploy your application to a range of devices, you should set this to x86|x64|ARM (this is the default value on the template so you can omit this property if you want to build for all three platforms)
  • $(windows_signing_certificate_securefiles_filename) – make sure the windows_signing_certificate_securefiles_filename variable exists in your variable group and that it’s value matches the Secure file name of the certificate (.pfx) file uploaded to the Secure files under the Library in Azure DevOps.
  • $(windows_signing_certificate_password) – make sure the windows_signing_certificate_password variable exists in your variable group and that it is password you specified when creating the certificate. This should be marked as private.

Note that the application package that we’re specifying here is an msixbundle. The Windows build pipeline has been upgraded to support both appx and msix bundles.

Windows – Deploy

The deploy stage for Windows (UWP) is

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Deploy_Windows'
    depends_on: 'Build_Windows'
    environment_name: 'Inspector-Alpha'
    artifact_name: 'inspector-build'
    artifact_folder: 'Windows_output'
    application_package: 'Inspector-Uno-Windows.msixbundle'
    appcenter_service_connection: 'AppCenterInspectorCI'
    appcenter_organisation: 'thenickrandolph'
    appcenter_applicationid: 'Inspector-Uno-UWP-Alpha'
    appcenter_release_notes: 'Release from deploy pipeline'

Pipeline Templates v0.3.0

As promised, here’s a quick summary of the changes in v0.3.0. Technically this should have been a major version increment as there were some breaking changes but since we’re not at v1 yet, I’ve just pushed a minor update.

Breaking Changes:

  • build-xamarin-android.yml – change android_bundle_name parameter to application_package (for consistency with deploy-appcenter.yml)
  • build-xamarin-ios.yml – change ios_ipa_name parameter to application_package (for consistency with deploy-appcenter.yml)
  • build-xamarin-windows.yml – change windows_bundle_name parameter to application_package (for consistency with deploy-appcenter.yml)

Other Changes:

  • build-xamarin-android.yml – added environment variables (ANDROID_NDK_HOME, ANDROID_NDK_PATH, AndroidNdkDirectory) so that the build process can locate the NDK. This is required for Android builds that enable AOT compilation.
  • build-xamarin-android.yml: android_manifest_filename property no longer required – template will search for AndroidManifest.xml
  • build-xamarin-windows.yml: windows_package_manifest_filename property no longer required – template will search for *.appxmanifest
  • build-xamarin-ios.yml: ios_plist_filename property no longer required – template will search for Info.plist
  • build-xamarin-windows.yml: application_package supports both appxbundle and msixbundle file types. You need to make sure the filename matches the type of bundle your application is setup to create.

Deploy Xamarin.Forms Apps to App Center from a Azure Multi-Stage Pipeline using Templates and Environments that Require Manual Approval

Wow, that title’s a mouthful, and I didn’t add in there that I’ve just pushed v0.2.0 release of the Pipeline Templates repository. In this post we’re going to add stages to a YAML based Azure DevOps pipeline in order to deploy a Xamarin.Forms application to AppCenter for testing. We’ll also be using on the of the latest features of the Azure DevOps YAML based pipelines, Environments, to insert a manual approval gate into our multi-stage pipeline.

Pipeline Templates v0.2.0

Before we get into talking about releasing apps to AppCenter I just wanted to reiterate that there is a new release of the Pipeline Templates repository with the following changes:

  • Added a new parameter, stage_name, to iOS, Android and Windows build templates. It has a default value, which matches the value previously specified on the stage element in the template, so won’t break any existing builds. This parameter can be set by the calling pipeline so that the stage is given a known name, which can then be referenced by other stages in the dependsOn element.
  • Added deployappcenter.yml template that can be used to deploy iOS, Android and Windows apps to AppCenter. For Android, if the application_package parameter is an .aab file, the calling pipeline will also need to supply keystore information. AppCenter doesn’t support .aab files, so the pipeline uses bundletool to generate and sign a fat apk, which is submitted to AppCenter.

Deploy to AppCenter

With a classic pipelines in Azure DevOps, you can setup separate build and release pipelines. However, YAML pipelines don’t differentiate between build and release pipelines; instead you can split a single pipeline into multiple stages (as we demonstrated in the previous post when we used different templates to create different stages in the build process).

To deploy apps to AppCenter we could simply create a template, similar to what we did with the build templates, that includes the tasks necessary to deploy to AppCenter, and then add new stages to our pipeline for each app we want to deploy. However, this would limit our ability to take advantage of some of the deployment specific features that are available in Azure DevOps. For this reason, the AppCenter template makes use of a deployment job in order to do the steps necessary to release an app to AppCenter.

Using the AppCenter Template

The following YAML pipeline provides an example of using the new AppCenter deployment template to deploy Windows (UWP), Android and iOS applications to AppCenter. Note that the ref element for the repository resource has been updated to point to the v0.2.0 tag.

resources:
  repositories:
    - repository: builttoroam_templates
      type: github
      name: builttoroam/pipeline_templates
      ref: refs/tags/v0.2.0
      endpoint: github_connection
  
variables:
  - group: 'Inspector XF Build Variables'
  
stages:
## Build stages excluded for brevity
- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Deploy_Windows'
    depends_on: 'Build_Windows'
    environment_name: 'Inspector-Alpha'
    artifact_name: 'inspector-build'
    artifact_folder: 'Windows_output'
    application_package: 'Inspector-XF-Windows.appxbundle'
    appcenter_service_connection: 'AppCenterInspectorCI'
    appcenter_organisation: 'thenickrandolph'
    appcenter_applicationid: 'Inspector-XF-UWP'
    appcenter_release_notes: 'Release from deploy pipeline'

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Deploy_Android'
    depends_on: 'Build_Android'
    environment_name: 'Inspector-Alpha'
    artifact_name: 'inspector-build'
    artifact_folder: 'Android_output'
    application_package: 'Inspector-XF-Android.aab'
    appcenter_service_connection: 'AppCenterInspectorCI'
    appcenter_organisation: 'thenickrandolph'
    appcenter_applicationid: 'Inspector-XF-Android-Alpha'
    appcenter_release_notes: 'Release from deploy pipeline'
    secure_file_keystore_filename: '$(android_keystore_filename)'
    keystore_alias: '$(android_keystore_alias)'
    keystore_password: '$(android_keystore_password)'

- template:  azure/mobile/[email protected]_templates
  parameters:
    stage_name: 'Deploy_iOS'
    depends_on: 'Build_iOS'
    environment_name: 'Inspector-Alpha'
    artifact_name: 'inspector-build'
    artifact_folder: 'iOS_output'
    application_package: 'Inspector-XF-iOS.ipa'
    appcenter_service_connection: 'AppCenterInspectorCI'
    appcenter_organisation: 'thenickrandolph'
    appcenter_applicationid: 'Inspector-XF-iOS-Alpha'
    appcenter_release_notes: 'Release from deploy pipeline'

There are a couple of prerequisites that need to be setup in order for the deploy stages to work:

  • Each deploy stage specifies a depends_on property. This needs to correlate to the stage_name property specified on the corresponding build stage.
  • The artifact name, artifact folder and application_package properties need to match to the values used in the corresponding build stage.
  • A Service Connection needs to be established between Azure DevOps and AppCenter (in this case it was given the name ‘AppCenterInspectorCI’)
  • An application needs to be registered in AppCenter for each target platform. Each deploy stage needs the organisation (ie username or org_name) and applicationid (app_identifier). See the documentation on the AppCenterDistribute task for more information on how to find these values for your AppCenter apps.

Manual Approval with Environments

I mentioned earlier that using a deployment job would allow us to take advantage of deployment specific features. This is an area that’s currently under development and we’d expect to see more features lighting up in this area over time.

One feature that’s available today is the ability to add manual approval requirement to a deployment job. However, unlike in the classic pipeline where you’d create a manual approval requirement directly on the release pipeline, on a YAML pipeline you actually need to associated the deployment job with an environment and then add a manual approval on the environment.

You may have noticed that each of the deploy stages in the example YAML specified the environment_name property. This defines which environment you’re going to be deploying to. At this stage the only thing you can use this for in terms of deploying a mobile application is to require manual approval for the stage to continue. Let’s step through creating the environment and the approval requirement and you’ll see what I mean.

Create an Azure Pipelines Environment

Under the Pipelines tab, select Environments and then click the Create environment button in the center of the screen.

Next, provide a name for your environment and click Create. At this stage we don’t need to define any resources, so you can leave the default selection of “None”. The name that you specify for your environment has to match what you use as the environment_name property on the template.

Adding Manual Approval to the Environment

In order to add a manual approval requirement to an environment, simply open the environment (if you’ve just created the environment you’re already there). From the drop-down menu in the top-right corner, select Approvals and checks.

Next, click the + button in the top right corner.

Select Approvals, and then click Next

In the Approvals flyout you can specify a list of users and/or groups that need to approve a release to the environment. If you specify multiple users, each user needs to approve the release. If you specify a group, only one person in the group needs to approve the release.

In the Approvals flyout you can also specify a timeout; if the deployment isn’t approved for an environment within the timeout, the pipeline will fail.

Note: Currently there are no emails, or other notifications, sent to approvers. If you limit the timeout, once the specified period has elapsed if the environment hasn’t been approved, the pipeline fail and a notification will be sent out. The stage in the pipeline that failed can then be manually run and again, approval for the environment will be required.

Running the Build and Deploy Pipeline

Now when we run the pipeline, what we see in the portal is three different rows (one for each platform) with two stages (a build and deploy stage).

We’ve set the dependsOn element on each of the build stages to an empty array (ie []) meaning that they have no dependencies (btw the default is that stages will be done in the sequence that they appear in the YAML file, unless you indicate there should be no dependencies). Depending on how many build agents you have at your disposal the build stages may run in sequence, or in parallel.

Eventually, when each of the build stages completes, the corresponding deploy stage will light up indicating that it’s waiting for a check to be passed. There’s also a message box inserted into the interface to draw attention to the required approval.

Once all three of the build stages are complete, there are 3 approvals required; one for each of the deploy stages. The way we’ve structured the pipeline you have to approve the deployment for each platform.

If you wanted to require only a single approval for all three platforms you could inject an additional stage that was dependent on all three build stages. The approval would be required for this single stage, and then each of the subsequent deploy stages would be permitted to continue without further checks. The downside of this would be that you would have to wait for all builds to fail before you could deploy the app to any platform.

Summary

In this post we’ve looked at using a pipeline template for deploying Xamarin.Forms applications to AppCenter for testing across Windows (UWP), iOS and Android. In actual fact, and what I didn’t point out earlier, the deploy template can be used for any iOS, Android or Windows (UWP) app, not just a Xamarin.Forms application.

I also walked through setting up an environment so that you could add a manual approval step to the deployment process. Whilst the YAML pipelines don’t yet have all the features of the classic release pipeline, you can see from the way that the components connect and the UI that’s built in the portal that the foundations have been laid for future features to be built on.