DEV Community

Cover image for How to build a MAUI app in Azure Devops
stfnilsson for Charlie Foxtrot

Posted on

How to build a MAUI app in Azure Devops

- and how to publish it to Test Flight and Google Play Console

You have created a .NET MAUI app but you have been building it on your laptop and now want to know how to send it to Testers or to the Store.

Environment

This blog post is covering building via Azure Devops but there are many other options, like Github.

In Azure Devops you can setup your own build agent by following this guide: https://learn.microsoft.com/sv-se/azure/devops/pipelines/agents/osx-agent?view=azure-devops

You can also use a hosted agent. Here are some different alternatives: https://learn.microsoft.com/sv-se/azure/devops/pipelines/agents/hosted?view=azure-devops&tabs=yaml#software

For building a Maui app for iOS you need a Mac. In this build pipeline 'macos-14' is used.

pool:
  vmImage: 'macos-14'
Enter fullscreen mode Exit fullscreen mode

Global settings

To share settings between build pipelines or to just abstract away the settings you can use Library in Azure Devops.

In this example the variable group "Svetto" is used.

Image description

You define the variable group in your build pipeline like this:

variables:
  - group: Svetto
Enter fullscreen mode Exit fullscreen mode

This is all the setting which is used to build both for iOS and Android, the name of the files is also specified as variables.

For signing the Android app:

  • AndroidKeyStoreAlias
  • AndroidKeyStoreFile
  • AndroidKeyStorePassword

For signing the iOS app:

  • AppleCertificate
  • AppleSigningIdentity
  • iOSCertPassword
  • ProvisioningProfile

The version (major.minor)

  • ApplicationDisplayVersion

Image description

The files needed to sign the app are:

  • .p12 which is the Apple Certificate

  • .mobileprovision which is the Apple Provision Profile

  • .keystore which is used to sign the Android app

Image description

Version number

A version number contains of three digits {major.minor.build}.

The major and the minor are specified in the project file:
<ApplicationDisplayVersion>2.0</ApplicationDisplayVersion>

To make the build number unique build/runner in devops is often used but it's also possible to use the date like this:

<ApplicationVersion>$([System.DateTime]::Now.ToString('yyyyMMddHH'))</ApplicationVersion>

Preparing

Select Xcode version

If you want to use a specific version of Xcode you can use this inline script, here it's specified that Xcode 16 should be used.

- task: CmdLine@2
  displayName: 'Selects a specific version of Xcode'
  inputs:
    script: 'sudo xcode-select -switch /Applications/Xcode_16.app/Contents/Developer'
Enter fullscreen mode Exit fullscreen mode

Install latest .net MAUI

- task: CmdLine@2
  displayName: 'Install Latest .NET MAUI Workload '
  inputs:
    script: 'dotnet workload install maui'
Enter fullscreen mode Exit fullscreen mode

You can adjust the --source to target specific versions of .NET MAUI workloads, and you can learn more about changing sources in the .NET MAUI Wiki (https://github.com/dotnet/maui/wiki/#install-net-6-with-net-maui).

Install apple certificate and provision profile

For iOS, you need a signing certificate and a provisioning profile. This guide walks you through how to obtain these files:(https://learn.microsoft.com/sv-se/azure/devops/pipelines/apps/mobile/app-signing?view=azure-devops&tabs=yaml#apple)

- task: InstallAppleCertificate@2
  inputs:
    certSecureFile: '$(AppleCertificate)'
    certPwd: '$(iOSCertPassword)'
    keychain: 'temp'
Enter fullscreen mode Exit fullscreen mode
- task: InstallAppleProvisioningProfile@1
  displayName: 'Install app store provisioning profile'
  inputs:
    provisioningProfileLocation: 'secureFiles'
    provProfileSecureFile: '$(ProvisioningProfile)'
Enter fullscreen mode Exit fullscreen mode

Add Android keystore key

For Android, you need keystore file and value of keystore password and keystore alias. This guide walks you through how to obtain these files: (https://learn.microsoft.com/sv-se/azure/devops/pipelines/apps/mobile/app-signing?view=azure-devops&tabs=yaml#sign-your-android-app)

- task: DownloadSecureFile@1
  name: keystore
  inputs:
    secureFile: '$(AndroidKeyStoreFile)'
Enter fullscreen mode Exit fullscreen mode

Building

Build iOS

In .NET 8, the dotnet publish command defaults to the Release configuration. Therefore, the build configuration can be omitted from the command line. In addition, the dotnet publish command also defaults to the ios-arm64 RuntimeIdentifier. The RuntimeIdentifier can also be omitted from the command line.

.NET MAUI apps produce executables for each Target Framework—these are native app packages with all dependencies/resources bundled in.

- task: DotNetCoreCLI@2
  displayName: 'dotnet publish iOS'
  inputs:
    command: 'publish'
    publishWebProjects: false
    projects: '**/Svetto.sln'
    arguments: '-f net8.0-ios -c Release -p:ApplicationDisplayVersion=$(ApplicationDisplayVersion) -p:ArchiveOnBuild=true -p:EnableAssemblyILStripping=false -p:RuntimeIdentifier=ios-arm64'
    zipAfterPublish: false
Enter fullscreen mode Exit fullscreen mode

Copy ipa to stage folder

- task: CopyFiles@2
  displayName: 'Copy iOS artifact'
  inputs:
    Contents: '**/*.ipa'
    TargetFolder: '$(Build.ArtifactStagingDirectory)'
    CleanTargetFolder: true
    OverWrite: true
    flattenFolders: true
Enter fullscreen mode Exit fullscreen mode

Build Android

- task: DotNetCoreCLI@2
  displayName: 'dotnet publish android'
  inputs:
    command: 'publish'
    publishWebProjects: false
    projects: '**/Svetto.sln'
    arguments: '-f net8.0-android -c Release -p:ApplicationDisplayVersion=$(ApplicationDisplayVersion) -p:AndroidKeyStore=true -p:AndroidSigningKeyStore=$(keystore.secureFilePath) -p:AndroidSigningKeyPass=$(AndroidKeyStorePassword) -p:AndroidSigningStorePass=$(AndroidKeyStorePassword) -p:AndroidSigningKeyAlias=$(AndroidKeyStoreAlias)'
    zipAfterPublish: false
Enter fullscreen mode Exit fullscreen mode

Copy aab to stage folder

- task: CopyFiles@2
  displayName: 'Copy android artifact'
  inputs:
    Contents: |
      **/*Signed.aab
    TargetFolder: '$(Build.ArtifactStagingDirectory)'
    OverWrite: true
    flattenFolders: true
Enter fullscreen mode Exit fullscreen mode

Publishing

Publish staged files, connect them to the build task

If you want to attach the ipa/abb to the build task you can use this task to publish the artifact to the task, as a zipfile called 'drop'.

- task: PublishBuildArtifacts@1
  displayName: 'Publish Artifact: drop'
  inputs:
    PathtoPublish: '$(build.artifactstagingdirectory)'
Enter fullscreen mode Exit fullscreen mode

Publish to Apple TestFlight

Prerequisites

  • In order to automate the release of app updates to the App Store, you need to have manually released at least one version of the app beforehand.
  • The tasks install and use fastlane tools. fastlane requires Ruby 2.0.0 or above and recommends having the latest Xcode command line tools installed on the MacOS computer.

Read more about the extension here:
https://github.com/microsoft/app-store-vsts-extension/tree/master

- task: AppStoreRelease@1
  displayName: 'Publish to Test Flight'
  inputs:
    serviceEndpoint: 'AppleTestFlight'
    releaseTrack: 'TestFlight'
    appIdentifier: 'xxxxxxxx'
    appType: 'iOS'
    appSpecificId: 'xxxxxxxx'
    shouldSkipWaitingForProcessing: true
    isTwoFactorAuth: true
Enter fullscreen mode Exit fullscreen mode

Publish to Google Play Console

To publish a .NET MAUI Android app for Google Play distribution requires that your app package format is AAB, which is the default package format for release builds. To verify that your app's package format is set correctly:

In Visual Studio's Solution Explorer right-click on your .NET MAUI app project and select Properties. Then, navigate to the Android > Options tab and ensure that the value of the Release field is set to bundle:

Image description

The first time an AAB is submitted to Google Play, it must be manually uploaded through the Google Play Console. This enables Google Play to match the signature of the key on all future bundles to the original key used for the first version of the app. In order to upload the app through the Google Play Console, it must first be built and signed in Visual Studio.

- task: GooglePlayRelease@4
  inputs:
    serviceConnection: 'GooglePlayConsole'
    applicationId: 'xxxxxxxx'
    action: 'SingleBundle'
    bundleFile: '$(build.artifactstagingdirectory)/*.aab'
    track: 'internal'
    isDraftRelease: true
Enter fullscreen mode Exit fullscreen mode

Conclusion

Next year AppCenter will be deprecated so MAUI developers need to find other ways to share their app to testers and users. Publishing them to the test stores (TestFlight/Google Play Console) is the recommended way.

I hope this guide will help you. Even if it doesn't cover all details about certificates, configuration and setup in the stores.

Good luck!

Top comments (0)