CI/CD in iOS development

Kresimir Bakovic
Azikus

--

Your app is ready for internal or external testing, and you want to publish it as soon as possible to your testers, right? Every iOS developer knows what the next steps are → running the tests and creating a new build. This part of the development process is repetitive and time-consuming, so in this blog post we will go through it and check how it can be shortened and made more robust. A time-saving and proper solution for this is to set up CI/CD on your project. Let‘s cover basic definitions so we can move on to more important how-to sections. 🙂

Continuous integration (CI) is a DevOps software development practice where developers regularly merge their code changes into a central repository, after which automated builds and tests are run.

Continuous delivery (CD) is a software development practice where code changes are automatically prepared for a release to production. With continuous delivery, every code change is built, tested, and then pushed to a non-production testing or staging environment.

CI/CD flows

If you want to set up CI/CD flow for your application, you can do it in multiple ways, but implementations can be separated into two main groups:
Drag-and-drop implementations → offered by some of the providers (Bitrise), are easier to set up but usually cost more
Custom script implementations → setup process is a little bit more complicated, but it‘s usually cheaper (GitHub, Bitbucket)

In later sections, I will go through one example that requires writing of your own script for CI/CD flow. Also, I will make a short comparison between the providers mentioned above.

GitHub Actions

GitHub is for sure one of the most popular code managing systems out there. In 2018 GitHub introduced their platform-native automation tool called GitHub Actions. GitHub Actions is designed to help simplify workflows with flexible automation and offer easy-to-use CI/CD capabilities built by developers for developers. When it comes to pricing, there are several things to mention. First of all, GitHub actions is free for standard GitHub-hosted runners in public repositories and for self-hosted runners. On the other side, if you have a private repository, you will get free minutes based on your product. One image is worth 1000 words, so please take a look at the free tiers on the next image.

Image 1. Free storage and minutes for private repositories

At first sight, this looks very promising and cheap, but like always, there is a catch. For macOS runners (sure, we need those), the minutes from the previous image are multiplied by 0.1😔 This means that we can only use GitHub Free for 200 minutes per month. If you are using Windows Runner, your minutes will be multiplied by 0.5 (still way better than macOS). All multipliers are shown in the next image.

Image 2. Minute multipliers

The great thing about GitHub Actions is that it responds to web-hook events. It means that you can trigger desired workflows based on events defined in your repository. In my opinion, GitHub is nice and easy to use, and you can have everything (code, project boards, CD flow) in one place. I will list some of GitHub‘s pros and cons in the next couple of bullet points.

Pros

  • ability to have everything in one place
  • webhook events
  • supports self-hosted runners
  • unlimited running minutes for public repositories

Cons

  • minutes for macOS runners are multiplied by 0.1
  • it takes some time to configure more complicated workflows

CI/CD flow using GitHub Actions

In this section, two workflows that will cover the CI and CD parts of the development process will be explained. The first workflow will be designed for executing the tests on each pull request to the dev branch. The second workflow will be responsible for building the application (on the chosen branch) and uploading it to TestFlight.

Maybe we should take one step back and describe what a workflow is? Workflow is a configurable, automated process made up of one or more jobs. Jobs are some actions that are executed one after another, and you define their order of execution inside the YAML file. More information can be found on Github‘s page.

In order to start writing the scripts for the workflows, you need to create .github file in your project‘s folder. Files that start with a dot are hidden by default, so you can press SHIFT+COMMAND+DOT in order to see them. Inside .github folder, create a folder called workflows, where you will define all workflows that will be visible on the GitHub page of your repository. Those workflows will be later triggered by some action or manually by someone from your team (this will be described later on).

Setting up CI flow

This workflow will be triggered on each PR to dev branch (or manually), and it will run tests to ensure that the code that needs to be merged is passing all the tests and isn‘t breaking existing implementations and functionalities. This script, called testing_workflow.yaml, will be placed in the previously mentioned workflows folder. The script will not be explained in detail because it has meaningful and descriptive comments. Content of testing_workflow.yaml:

Code block 1. testing_workflow.yaml

Setting up CD flow

This workflow will be triggered manually by the developer. You can choose the branch from which you want to upload your build to TestFlight. In order to make this work, we need to set up a couple of things first.

  • Create a new workflow named build_export_workflow.yaml and put it inside the workflows folder
  • Make a scripts folder within .github folder
  • Create certs folder inside .github folder

Now we have all the folders needed, and we can start adding scripts and certificates to them. In folder scripts, add the set_certificates.sh script:

Code block 2. set_certificates.sh

We will run this script from our workflow to decrypt our encrypted files, import the certificate to the new keychain, and set up the provisioning profile. In order for our build and export workflow to run on a remote machine, we need to configure some things:

  • Encrypt a distribution certificate and provisioning profile
  • Create an app-specific password for your Apple ID
  • Configure Encrypted secrets in GitHub.

Let‘s start with the encryption of the distribution certificate and provisioning profile. Go to the profiles section on your Apple developer account and create a provisioning profile for your iOS application. Download that profile and save it somewhere where you can find it. 😂

Now open your keychain and press on My certificates tab. There, you should be able to find the distribution certificate that you set for your provisioning profile in your Apple developer account. Press on that certificate, and its private key should appear below it. Right-click on that key and select Export. Enter a password when prompted to protect it, and write the password down — we will need it later.

The next step is to encrypt the exported certificate and profile. For encryption and decryption, we’ll use a tool called gpg; it uses one-way cryptographic algorithms to provide military-grade encryption.
Open Terminal and type brew install gpg to get it set up on your machine. Then, for each file, run gpg — symmetric — cipher-algo AES256 your_file> and enter a passphrase. Please write the passphrases down, as we will use them later for the file decryption.
Please rename both of these encrypted files like this: app.mobileprovision.gpg and app.p12.gpg. After that, place those files in the certs folder (we previously created it).

In order to create an App-specific password, please visit this page.
After a successful login, go to the App-specific password section. There, you can generate App-specific password needed for GitHub Actions. Write down the generated password because it will be used later on.

GitHub Actions secrets are next on the list. Secrets let you store sensitive information right in your GitHub repository. Navigate to Settings in your repository, then Secrets → Actions. We need to add four items that we created earlier:

  • APP_SPECIFIC_PASS → Apple ID specific password (one that you generated in the previous step)
  • DECRYPTION_PASS_CERTIFICATE → certificate encryption passphrase
  • DECRYPTION_PASS_PROFILE → profile encryption passphrase
  • KEYCHAIN_EXPORT → password for exporting your .p12 file from the keychain

The next step is to add actual workflow code to the existing build_export_workflow.yaml file like this:

Code block 3. build_export_workflow.yaml

The last step is to add a Fastlane folder to the root of your project and add a Fastfile file to it. The content of the Fastfile is explained and listed below:

Code block 4. Fastfile

In the next image you can see contents of workflows folder mentioned earlier.

Image 3. Contents of worflows folder

How to run workflows?

The CI flow that we created and explained in the sections above will be automatically run when some of the developers make PRs to the base branch (dev in our example).
If you want to run the newly created CD workflow, go to the GitHub page of your project and open the actions tab. On the right side, you will see all available workflows. Press on created build_export_workflow and on the right side, tap on the Run workflow button. Select the branch on which you want to run the workflow from the drop-down, and that’s it; pressing the green button will start the selected workflow, and when it’s finished, your app will be available for testing on TestFlight. 🚀

Bitrise

If you want the easiest solution for your CI/CD flow, Bitrise is the option that you are looking for. A nice looking UI and drag-and-drop configuration of the workflow ensure quick setup. Bitrise offers a wide range of integrations with popular tools and frameworks like Xcode, Cocoapods, Carthage, and Fastlane, which allow developers to build and test their iOS apps on virtual machines (VMs) or real devices. Bitrise also offers automatic code signing, which simplifies the process of signing your app with your Apple Developer account. It offers a powerful workflow editor that allows you to create custom workflows that define the steps of your CI/CD process. You can add steps to build, test, and deploy your app, and even add custom scripts to automate specific tasks. You can also define triggers for your workflows, such as triggering a build when a new code change is pushed to your repository. Bitrise makes it easy to deploy your iOS app to Apple’s App Store or other distribution channels, such as TestFlight. It also supports multiple distribution channels and offers a range of integrations with popular deployment tools like Fastlane and HockeyApp.

Image 4. Bitrise steps

Here you can find more info about Bitrise setup.

Pricing comparison

In this section, I am going to compare pricing options for the providers mentioned in the text above (GitHub, Bitrise, Bitbucket).

I think that the best overview of the prices can be shown in a chart, so I made one below. I am going to compare the Free plan for all of the providers, and you can find more info about other plans on the links at the bottom of this section.

Image 5. Free minutes for macOS runners

On the chart above, you can see that GitHub gives the most minutes in the Free plan (200 minutes). In second place is Bitrise with 150 minutes, and last is Bitbucket with just 50 minutes in the free tier. More info about the prices for all available tiers can be found on the pages of the mentioned providers: GitHub, Bitrise, and Bitbucket.

What is Apple providing in terms of CI/CD?

Xcode Server is a continuous integration tool for iOS apps, built by Apple. It comes bundled in the Xcode app, beginning with Xcode 9. It automatically checks out your project, builds the app, runs tests, archives the app for distribution, and lets you know if anything went wrong.
Xcode Server runs locally on a Mac. It is designed to be installed and run on a dedicated machine, such as a Mac mini, which can be used as a build and test server. When you set up Xcode Server, you can configure it to connect to your source code repository (such as GitHub or Bitbucket) and automatically build and test your code whenever changes are committed to the repository.

However, it’s worth noting that you can also use Xcode Server to distribute your app to testers or users over the internet, through methods such as TestFlight or in-house distribution. In this case, the app would be hosted on a web server or cloud service, rather than running directly on Xcode Server.

Overall, Xcode Server is primarily designed for local use, to automate the build, test, and deployment processes of your app development workflow.

Image 6. Working graph of Xcode server

Setting up the Xcode server is not a difficult process that can be completed quickly by following Apple’s documentation. In the next couple of bullet points, I am going to list some of the pros and cons of using Xcode Server for your CI/CD flow:

Pros

  • Provides a comprehensive set of tools for building, testing, and deploying iOS and macOS apps
  • Easy to set up and use, with a user-friendly interface
  • Offers detailed reporting and analysis of build and test results, with automatic notifications
  • Provides tools for automating distribution and deployment to a variety of platforms, including the App Store, TestFlight, and in-house distribution

Cons

  • Requires a dedicated Mac machine to run Xcode Server, which can be expensive and take up physical space
  • There may be issues with compatibility between Xcode Server and other tools in your development workflow
  • Some features, such as automated testing, may require additional setup and configuration outside of Xcode Server
  • The build and test process can be slow, especially for large or complex projects, which can impact developer productivity

Conclusion

So what‘s the best way to set up the CI/CD flow? This answer isn‘t and can‘t be a one-way street. The correct answer is determined by the technology stack you are using as well as your personal preferences. The true efficacy of your CI/CD solution can be quantified in the number of minutes saved from manually debugging and building, thus highlighting the value of an automated process.

It‘s worth mentioning that projects that require builds every month, or even less often, are not actually great candidates for setting up your CI/CD environment. The real need and power of CI/CD setup can be seen on projects that require new builds every couple of days or even more frequently. In the beginning, Bitrise can be the easiest option to setup, but later on, I suggested using your own machine (runner).

With that said, we have come to the end of this blog. Feel free to share it with your colleagues and let the knowledge spread. If you have any questions, do not hesitate to post them in the comments section. 🏁

Krešimir is a valuable member of our iOS team.
At Azikus, we design and develop top notch mobile and web apps.
We are a bunch of experienced developers and designers who like to share knowledge, always staying up to date with the latest and newest technologies.
To find out more about what we do, feel free to check out our
website.

--

--