Building a MAUI iOS app with Azure DevOps Pipelines
Learn how to build and sign your MAUI iOS app with Azure DevOps Pipelines and ouput an ipa file.
Setup
First off create yourself a new blank .yaml file if you don't already have one.
Triggers
You can trigger your pipeline based on changes to a branch if you like. For this example we'll trigger it manually.
Yamltrigger: none
Build stage
Now lets add our build stage and configure it to use the lastest macOS image (at the time of writing macOS-13). You can also use 'macOS-latest' but confusingly it might not be the latest version.
Yamltrigger: none
stages:
- stage: Build
displayName: Build stage
jobs:
- job: Build
displayName: Build
pool:
vmImage: 'macOS-13'
Configure MAUI
We need to install the MAUI workload first before we build so lets add a task for it.
Yamltrigger: none
stages:
- stage: Build
displayName: Build stage
jobs:
- job: Build
displayName: Build
pool:
vmImage: 'macOS-13'
steps:
- task: CmdLine@2
displayName: 'Install Latest .NET MAUI Workload'
inputs:
script: 'dotnet workload install maui'
(Optional) Select xcode version
If you want to build with a specific version of xcode you can use xcode-select. In this example I'm selecting xcode 15.2
Yaml- task: CmdLine@2
displayName: 'Use Xcode 15.2'
inputs:
script: 'sudo xcode-select -s /Applications/Xcode_15.2.app'
Install certificates and provisioning profiles
In order to sign your app you'll need an apple developer certificate and a provisioning profile. Upload your .p12 and .mobileprovision to Pipelines > Library > Secure files on DevOps.
Install the certificate
I've used pipeline variables for the filename and password but the values can be hardcoded instead
Yaml- task: InstallAppleCertificate@2
displayName: 'Install apple certificate'
inputs:
certSecureFile: '$(certificateFilename)'
certPwd: '$(certificatePassword)'
keychain: 'temp'
Install the provisioning profile
I've used a pipeline variable for the filename but the value can be hardcoded instead
Yaml- task: InstallAppleProvisioningProfile@1
displayName: 'Install app store provisioning profile'
inputs:
provisioningProfileLocation: 'secureFiles'
provProfileSecureFile: '$(appStoreProvisioningProfileFilename)'
Build the project
The next step is to build and sign your project. We'll use the dotnet publish command to achieve that. Don't change the values of CodesignKey CodesignProvision as they are output by the previous two steps. I've also set working directory as I'm using a multi-project solution, if you are using single project them you shouldn't need to.
Yaml- task: CmdLine@2
displayName: 'Build project'
inputs:
script: 'dotnet publish -f net8.0-ios -c Release -p:ArchiveOnBuild=true -p:RuntimeIdentifier=ios-arm64 -p:CodesignKey="$(APPLE_CERTIFICATE_SIGNING_IDENTITY)" -p:CodesignProvision="$(APPLE_PROV_PROFILE_NAME)"'
workingDirectory: './Source/MyProject.iOS/'
Publish build output
Finally the last thing to do is publish the ipa file that is output by the build. Note that the source folder will differ depending on your project name but dotnet publish always outputs to 'bin/Release/net8.0-ios/{RuntimeIdentifier}/publish'
Yaml- task: CopyFiles@2
displayName: 'Copy build output to staging'
inputs:
SourceFolder: '$(Agent.BuildDirectory)/s/Source/MyProject.iOS/bin/Release/net8.0-ios/ios-arm64/publish'
Contents: '**/*.ipa'
TargetFolder: '$(ArtifactStagingDirectory)'
flattenFolders: true
- publish: $(ArtifactStagingDirectory)
artifact: drop