Upcoming and OnDemand Webinars View full list

Automating TestFlight Builds with CircleCI – Part 2

Chris Downie

In Part 1, we set up two Fastlane commands:

  1. bump to increment the build number and
  2. submit_to_testflight to upload the build to our TestFlight group

Let’s configure CircleCI to run these commands for us when new features are delivered to our master branch.

Initial CircleCI Setup

Let’s tell Circle CI that we want it to run some projects for us. Click on CircleCI’s Add Projects tab. Find your repository in the list and click Set Up Project. Follow the initial setup steps on that page and then click Start Building.

Bump the build number

In config.yaml, add a new job to your job list:

  bump-build:
    macos:
      xcode: "10.2.0"
    environment:
      # Silences warning from Fastlane
      - LC_ALL: "en_US.UTF-8"
      - LANG: "en_US.UTF-8"
    steps:
      - checkout
      # Install fastlane and its dependencies
      - run:
          name: install Brewfile
          command: brew bundle
      - run:
          name: Installing gem dependencies
          command: bundle install
      # Securely connect to GitHub
      - add_ssh_keys:
          fingerprints:
            - "SO:ME:FI:NG:ER:PR:IN:T"
      # Call the bump lane
      - run:
          name: Bump the build number and commit
          command: bundle exec fastlane bump

The interesting part is add_ssh_keys. Deployment keys let CircleCI access your code. However, while you can set a service deployment key in CircleCI, it only gives read access. The fastlane bump command needs write access to push the version bump commit to master. Therefore, we need to generate and register a user-deploy key with write access by following these instructions. Paste the key’s fingerprint here.

Notably:

  • That key should be added to the project repository’s settings in GitHub
  • The fingerprint in this step should match.

Submit to TestFlight

Let’s add the job for submitting to TestFlight:

submit_to_testflight:
    macos:
      xcode: "10.2.0"
    environment:
      # Silences warning from Fastlane
      - LC_ALL: "en_US.UTF-8"
      - LANG: "en_US.UTF-8"
    steps:
      - checkout
      # Install fastlane and its dependencies
      - run:
          name: install Brewfile
          command: brew bundle
      - run:
          name: Installing gem dependencies
          command: bundle install
      # Securely connect to GitHub
      - add_ssh_keys:
          fingerprints:
            - "SO:ME:FI:NG:ER:PR:IN:T"
      # Call the submit lane
      - run:
          name: Deploy new build to TestFlight
          command: bundle exec fastlane submit_to_testflight

This looks very similar to the step for making the build. In fact, the only differences are:

  1. We install a different SSH key
  2. Our final run command is now fastlane submit_to_testflight

This SSH key is the one Circle CI will use to download your credentials from the repo that fastlane match uses to store them. If you attempt to reuse your key from earlier, GitHub will reject it. Instead, you’ll need to do the same key generation process again, and add the fingerprint for the SSH key associated with the credentials repo here.

Build the workflow

Now that we’ve got two jobs, let’s put them together into a workflow: 

#Defines work-flows for the jobs specified above
workflows:
  version: 2
  build-test-lint:
    jobs:
      - build-and-test:
          filters:
            tags:
              only: /.*/
      - bump-build:
          requires:
            - build-and-test
          filters:
            branches:
              only: master
            tags:
              only: /.*/
      - deploy-beta:
          requires:
            - build-and-test
          filters:
            branches:
              ignore: /.*/
            tags:
              only: /^builds.*/

That’s a lot. Let’s look at each job in that workflow individually.

- build-and-test:
    filters:
      tags:
        only: /.*/

This builds and tests any commit to any branch. See CircleCI’s excellent guide on how to configure automated tests.

The filter here is odd. Why would we filter to a regex that matches everything? This is how CircleCI knows it should trigger this workflow for either a tag match or a branch match. It looks superfluous right now, but adding the “only run it for every tag” filter means we can specify “only run it for specific tags” in later, dependent jobs.

- bump-build:
    requires:
      - build-and-test
    filters:
      branches:
        only: master
      tags:
        only: /.*/

This runs our fastlane bump job. This will only run on the master branch, and will run with any specified tag. And, it’ll only run after build-and-test successfully completes.

Just like before, the “filter to only all of the tags” looks superfluous, but it’s not. Now that our parent task has started filtering for tags, if any job doesn’t specify a tag filter, then all tag filters will be ignored.

And now, the grand finale of CI config:

- deploy-to-testflight:
    requires:
      - build-and-test
    filters:
      branches:
        ignore: /.*/
      tags:
        only: /^builds.*/

Finally, our deploy task ignores branches and only triggers if it sees a tag that starts with builds. You can probably make the tag regex more specific, but this is good enough for me.

Both of these filters values are OR’d together. If we specified branches: only: master with this filter tag, then it would run for every commit on master and also any commits that start with builds. Ignoring the branch entirely and having builds tags only added to master is the only way to achieve the desired effect.

Your automated process should now look like this:

Configure the CircleCI Environment

You’re still not done! There’s some state on your local machine that you need to go into CircleCI and add as environment variables. Specifically:

  • MATCH_PASSWORD – This is the password you use to decrypt your fastlane credentials.
  • FASTLANE_PASSWORD – This is the Apple ID password of the account you use to submit builds to Apple.

If the shared build account follows fastlane’s recommendation and doesn’t have two-factor authentication enabled, then you’re done. Otherwise, you’ll need these additional environment variables:

  • FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD – This can be generated in Apple ID Settings, underneath Security > Generate Password.
  • FASTLANE_SESSION – A session token that’s sufficent for two-factor authentication. To generate, type the following command in the terminal, using the build account’s email address: fastlane spaceauth -u user@email.com

The FASTLANE_SESSION expires after a month. You will need to update this environment variable monthly in order to keep the build process automatic. Unfortunately, that’s the best fastlane can do.

Hope for the best

That’s it! Make your commits, get them reviewed and merged to master, and about 20-30 minutes later you should get a notification that you’ve got a new build to download from TestFlight.

If you’re anxiously watching the workflow process as this goes on, you should see each of the five jobs:

  1. Test your commit with build-and-test.
  2. Increment the build number with bump-build and tag the commit.
  3. Test the build tag commit with build-and-test
  4. Re-run bump-build with no effect on the tag commit.
  5. Deploy with deploy-to-testflight.

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project