Photo by Ant Rozetsky

How to build and sign your iOS application using Azure DevOps

Discover continious integration for your iOS application.

Posted by Damien Aicheh on 12/05/2019 · 12 mins

When developing a mobile application you need to distribute a package on a regular basis, for example when you want to update your application on the App Store.

Quick reminder

To do that for an iOS application you need to follow a few steps:

  • Restore the Pods of your project
  • Choose the right environment
  • Build your application
  • Sign it using the right Apple Certificate
  • Generate an .ipa
  • Upload it to the Store of your choise

Of course, you can do all these steps manually but:

  • It will be repetitive
  • It’s time-consuming and time is money
  • You will probably make some errors

In this tutorial I m going to show you how to automatically build and sign your iOS application. So let’s get started!

Introducing Azure DevOps

The idea is to automate this process using Azure DevOps, the interest of it is to be able to:

  • Access it from everywhere
  • Avoid to be in charge of server maintenance
  • Spend less money and improve the quality of your delivery

By the end of this tutorial we will have something called a pipeline which will allow you to generate your ipa with just one click.

Build your own pipeline

First of all let’s go to Azure DevOps!

Inside the Pipelines tab create a New pipeline and follow the 4 steps to get the source code from the correct repository. Azure DevOps will automatically create a new azure-pipelines.yml at the root of your project folder. This file will define all the task we need and it will be interpreted by Azure DevOps.

Global configurations

We will compile our iOS application using the current latest version of mac available on Azure DevOps: 10.14. This will allows us to compile for iOS 13.1.

Let’s define the version of the mac agent on top of the azure-pipelines.yml file like this:

  vmImage: 'macos-10.14'

Create your variables group

Like all jobs, we will need multiple variables like folder path, environnement variables and passwords. So to manage all of it let’s create a variables group. If you are not familiar with this you can look at my previous tutorial about it.

For this tutorial, the variable group will be called ios-pipeline, load it by adding it just below the agent definition like this:

  - group: ios-pipeline

Import your certificates

To sign your application you will need one provisioning profile and an access to an iOS distribution certificate.

There are a few types of Apple certificates:

  • The App Store certificate which just allow you to distribute your application through the official Store.
  • The Ad Hoc certificate which allow you to install the application only for a list of devices that are register inside your Apple Account.
  • The In-House certificate is only available with an Apple Enterprise account, which allow you to distribute your application to your employees only.

With that in mind, choose the right one depending on your needs.

With your certificate and provisioning profile ready, you now need to upload them inside the Secure File. To do that go to Pipelines > Library > Secure files tab. For this tutorial my provisioning profile will be called My_Demo_Application_AdHoc_Distribution.mobileprovision. With your Distribution certificate ready you need to export the public key of it. To do it, just add it to your Keychain on your mac and then, right clic on it and export it as a .p12 file, then you can upload it in the Secure File section. Don’t forget to add the associated password to the variable group.

At this point your variable groups should looks like this:

Variable groups

With that done, we can add two tasks to our azure-pipelines.yml:

First, install the Apple certificate previously uploaded we will sign the ipa with it:

- task: InstallAppleCertificate@2
   certSecureFile: '$(p12FileName)'
   certPwd: '$(p12Password)'
   keychain: 'temp'
   deleteCert: true  

Then add a new task to install the provisioning profile associated to your application like this:

- task: InstallAppleProvisioningProfile@1
   provisioningProfileLocation: 'secureFiles'
   provProfileSecureFile: '$(provisioningProfile)'
   removeProfile: true

With these tasks the mac agent is ready to build and sign your project.

Build and sign

It’s time to build our application. Let’s start by restoring the Cocoapods packages for our project by adding this task:

- task: CocoaPods@0
    forceRepoUpdate: false

If you do not have any Pods in your project just skip this task.

Before adding the Xcode@5 build task add two variables: configuration and iphoneos define them like this:

  - group: ios-pipeline
  - name: configuration
    value: 'Release'
  - name: sdk
    value: 'iphoneos'

We compile our application in Release mode and we use the iphoneos sdk. Next step, add the build task:

- task: Xcode@5
    actions: 'build'
    scheme: 'iOSPipeline'
    sdk: '$(sdk)'
    configuration: '$(configuration)'
    xcWorkspacePath: '**/iOSPipeline.xcworkspace'
    xcodeVersion: 'default'
    packageApp: true
    signingOption: 'manual'
    provisioningProfileUuid: '$(APPLE_PROV_PROFILE_UUID)'

Let’s explain what the Xcode@5 task do. We choose a Release configuration and the iphoneos sdk as previously declared in the variables and we enabled the generation of a package for the app: an ipa.

The compiler also needs to know which scheme to use inside your project to build your application. If you do not know what scheme you have inside your project, open a terminal on your mac and launch this line if you have a workspace file .xcworkspace:

xcodebuild -workspace /path/to/your/.xcworkspace -list

Or this one if you just have a project file .xcodeproj:

xcodebuild -project /path/to/your/ -list

This will list all the schemes available on your project and you will just have to pick the correct one. If you don’t setup this value and you use a .xcworkspace you will have this kind of error:

xcodebuild: error: If you specify a workspace then you must also specify a scheme. Use -list to see the schemes in this workspace

The $(APPLE_CERTIFICATE_SIGNING_IDENTITY) variable correspond to the Install Apple Certificate task we previously did and is automatically set. The $(APPLE_PROV_PROFILE_UUID) variable is automaitically set by the the Install Apple Provisioning Profile task and is linked to the provisioning profile we previously did.

Now if you run your job for the first time, you will get an error message that ask you to give access to your variable groups and secure files. Accept it by clicking on Authorize resources, restart the build and you are good to go.

If you are using some Pods you will probably have an error like this:

error: Pods-ProjectName does not support provisioning profiles, but provisioning profile TheNameOfYourProfile has been manually specified.

This is because you are trying to sign the Pods but you do not want to do that, you just need to sign your application code. To fix this issue just open your Podfile and add the lines below to specify that you do not need to sign the Pods.

post_install do |installer|
        installer.pods_project.build_configurations.each do |config|
            config.build_settings['CODE_SIGNING_REQUIRED'] = "NO"
            config.build_settings['CODE_SIGNING_ALLOWED'] = "NO"

Generate the artifact

We have our ipa builded and signed, it’s time to download it. We first need to copy the ipa from the build sources directory into the artifact directories with a this first task:

- task: CopyFiles@2
    contents: '**/*.ipa'
    targetFolder: '$(build.artifactStagingDirectory)'
    overWrite: true

Final step we publish the ipa so it can be available to download by clicking on the Artifacts button.

- task: PublishBuildArtifacts@1
    pathtoPublish: '$(build.artifactStagingDirectory)/output/$(sdk)/$(configuration)' 
    artifactName: 'drop' 
    publishLocation: 'Container'

If everything is going well you will see something like this:

Final Pipeline


You will find full source code in this Github repository.

Happy coding!

You liked this tutorial? Leave a star in the associated Github repository!

Do not hesitate to follow me on to not miss my next tutorial!