DEV Community

Cover image for iOS CI/CD Evolution: From Bitrise to GitHub Actions Migration Study
Tomasz Pierzchała for Brainhub

Posted on • Updated on • Originally published at brainhub.eu

iOS CI/CD Evolution: From Bitrise to GitHub Actions Migration Study

Background

Some time ago we had a client that asked us to migrate his whole mobile CI/CD flow from Bitrise to GitHub actions. The project was a React Native, iOS-targeted application.

After finding the client’s motivations behind the decision (like having all pipelines in one place and reducing their list of vendors) and quick research, since it was our first time setting up mobile pipelines in GitHub Actions we accepted the challenge. What are the outcomes? Was it a good decision?

The workflow

The client’s pipeline workflow was pretty standard.

On each push to the repository or when the PR was opened we were first linting the code, and then testing the JS part.

Additionally on the master branch E2E Detox tests were executed. We also had the possibility to run the manual workflows for E2E and deployment.

On push / PR Manual
1. Linting the code
2. Testing JS
1. Building the app for testing
2. E2E Detox testing
1. Building the app for release
2. Code signing
3. Deployment to Testflight

Apple to oranges?

Both of the providers support YAML configurations where you can specify the workflows.

But Bitrise also supports the UI workflow editor. I heard the opinions that UI editors are for amateurs only, providing basic functionalities only but seriously the editor speeds up the configuration process significantly.

Let’s configure our repo to support both CI/CD providers. How can it be done?

In GitHub Actions, we need to create a new workflow file in ./github/workflows directory. It could look like this:

name: Shared / E2E (iOS)

on:
  workflow_dispatch:
  push:

jobs:
  build:
    name: E2E (iOS)
    runs-on: macos-11
    steps:
      - uses: actions/checkout@v1

      - name: Install Node
        uses: actions/setup-node@v1
        with:
        node-version: '16'

      - name: Install NPM Dependencies
        run: npm install

      - name: Cache Pods
        id: cache-pods
        uses: actions/cache@v2
        with:
        path: ios/Pods
        key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
        restore-keys: |
        ${{ runner.os }}-pods-

      - name: Install Pod Dependencies
        if: steps.cache-pods.outputs.cache-hit != 'true'
        run: cd ./ios && pod install && cd ..

      - name: Install Detox Dependencies
        run: |
        brew tap wix/brew
        brew install applesimutils

      - name: Run Detox Build
        run: npm run e2e:ios:build

      - name: Run Detox Test(s)
        run: bash ${{ github.workspace }}/e2e/.ci-scripts/run-e2e-ios.sh
Enter fullscreen mode Exit fullscreen mode

And voilà we have a GitHub Action workflow that can be run either on push to all branches or manually.

Now let’s see how it looks in Bitrise. Since it is another service first we need to connect it with the repo. It has a wizard that leads you through the whole process. Just a couple of dialogs / settings and you have a primary but working pipeline that we can customize.

You can utilize over 200 add-ons that are used as workflow steps, with integrations for Apple app building and deployment included. This is important because it allows configuring these steps in a much more accessible way than in GitHub.

The wizard can also automatically connect with GitHub API so that the pipeline starts on push and reports the status back.

Bitrise UI wizard

Bitrise UI workflow editor

The workflow is also available as a YAML configuration:
Bitrise YAML workflow editor

The crucial difference between the providers is related to their targets. GitHub Actions is a generic solution that is totally unopinionated, there are multiple shared actions available in the marketplace and you can configure it as you wish however there’s no recipe that you can quickly adapt to your project.

Bitrise however is mobile-focused, and opinionated, providing the recipes that should speed up the configuration.

My subjective feeling is that Bitrise solutions are significantly better documented, you have the aforementioned recipes, and so on. On the other hand, GitHub Actions as iOS CI is rather a niche - for instance, Detox docs don’t even say a thing about GitHub Actions configurations, whereas different providers are described. However it’s doable, we have successfully set it up in our client’s project.

Code signing

Each time setting up the pipelines for iOS development the most problematic part to me is the code signing and Testflight part.

How to store the certificate? How to sign the code? These are the questions you need to address on your own when using GH Actions.

Bitrise provides its own add-ons that can do it for you, apparently after some configuration that is usually well described in the documentation.

Returning to GH Actions, we decided to automate the process using Fastlane. For those who are not familiar with this - it is an app automation tool that allows deployment-related tasks to be performed automatically by using so-called lanes.

Sample Fastlane’s lane for building the iOS app:

private_lane :build do |options|
  app_identifier = options[:app_identifier]
  scheme = options[:scheme]

  disable_automatic_code_signing(
    team_id: team_id
  )

  get_certificates(
    keychain_path: keychain_path
  )
  get_provisioning_profile
  update_project_provisioning(
    code_signing_identity: "iPhone Distribution"
  )

  increment_build_number({
    build_number: latest_testflight_build_number(app_identifier: app_identifier) + 1
  })
  increment_version_number(
    version_number: version_number
  )

  build_app(
    scheme: scheme,
    workspace: "MyProject.xcworkspace",
    configuration: "Release",
    export_method: "app-store",
    export_team_id: team_id,
  )
end
Enter fullscreen mode Exit fullscreen mode

We used Fastlane’s cert and sigh approach that handles loading the certificates/provisioning profiles and signing the app. But first, we needed to import the certificate to the keychain which is a bit complicated if you don’t work with Mac pipelines often. A sample script might look like this:

- name: Installing the certificate and provisioning profile
  env:
  KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
  CERTIFICATE_BASE64: ${{ secrets.CERTIFICATE_BASE64 }}
  run: |
  # create variables
  CERT_PATH=$RUNNER_TEMP/build_certificate.p12

  # import certificate and provisioning profile from secrets
  echo -n "$CERTIFICATE_BASE64" | base64 --decode --output $CERT_PATH

  # create temporary keychain
  security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
  security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
  security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH

  # import certificate to keychain
  security import $CERT_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
  security list-keychain -d user -s $KEYCHAIN_PATH
Enter fullscreen mode Exit fullscreen mode

There is also an alternative Fastlane’s approach, called match that stores the certificates and provisioning profiles in a dedicated repository, it might be GitHub or another provider.

But we decided to stick with the cert and sigh approach.

How it is done in Bitrise? Well, you just need to upload the certificates and adjust a few settings:
Bitrise Code Signing certificates upload

Bitrise Code Signing settings

Pricing

A penny saved is a penny earned, so let’s compare the pricing plans.

GitHub Actions pricing strategy is based on the free minutes assigned to the account and as-you-go payments when the free limit is reached.

At first glance, you might get the impression that you get 2000 or even 3000 free minutes but beware of the operating system multiplier. Yes, you get 2000/3000 minutes as long as it’s a Linux build. If you’re going to use Mac it reduces 10 times. (But When building Android on Linux this pricing might be very generous).

Each minute beyond the free limit cost at least $0.08 for a 3-core machine and the price is the same, no matter how many minutes you spend.

GitHub pricing:

Free Pro / Team
Mac compute-minutes included 200 300
Cost of additional minutes $0.08 (3 cores) or $0.32 (12 cores) $0.08 (3 cores) or $0.32 (12 cores)
Concurrency up to 5 concurrent Mac jobs up to 5 concurrent Mac jobs

For further details check the GitHub pricing page.

Bitrise offers 300 compute minutes for free. Unlike Github the additional minutes don’t have a fixed price - the more you consume the less you pay. The first Team tier includes 500 minutes, $0.07 each, and the last tier includes 50 000, $0.0351 each.

Bitrise pricing:

Hobby (Free) Team (different Tiers)
Mac compute-minutes included 300 500 - 50 0000
Cost of minute N/A $0.0351 (the biggest Tier, paid yearly) - $0.07 (the smallest Tier, paid monthly)
Concurrency up to 5 concurrent Mac jobs up to 10 concurrent Mac jobs

For further details check the Bitrise pricing page.

Conclusion

GitHub Actions is definitely a stable and robust CI solution backed by the tech giant. I enjoy working with it on our projects where we’re operating on Linux and doing common, well-known tasks. But React Native iOS pipeline is a different story, though - it is a bit challenging to set up and that’s not bad at all - we, software developers, like challenges, isn’t it? But the fact that in Bitrise the pipeline can be set up in the UI wizard with the guidance of recipes on production-ready add-ons and in most cases for a lower price makes it a no-brainer decision.

There might be some cases where GitHub Actions’ unopinionated and low-level traits may shine however in most cases I would suggest using Bitrise, especially if you’re working on an MVP project where deadlines and budgets are pretty tight.

Top comments (0)