Bitrise Fastlane Integration for iOS Apps

feat. Sergey Laschuk & Ruslan Krohalev

When it comes to automated testing and build deployment of web and mobile apps, there are several well-established services of continuous integration. However, it’s Bitrise that got a lot of publicity in 2017 and stands all the good chances to gain more traction in 2018.

What is Bitrise

Bitrise is a continuous integration platform focusing mainly on mobile development. Continuous integration (CI) being a commonly accepted practice of merging developer build copies to a shared mainline on a specific schedule. In other words, it is a workflow for version control and copies deployment without losing track of the development process.

Bitrise allows creating multiple workflows consisting of build steps. One app can have multiple workflows defined for it, as well as the webhooks to specify which workflow is selected for which trigger (webhook). The triggers mapped to workflows initiate builds with predefined workflows.

Build steps are programmed to perform a wide variety of functions implemented by command line scripts. All the virtual machine build steps are logged to keep the information of every step of the workflow.

Our projects with Bitrise

So far, we’ve used Bitrise on two major mobile development projects. Bitrise allows Jira integration. Which is a great option for us as all the features to be implemented, time tracking and estimations are run by Jira in our company.  

Build automation

One of them being an iOS app already up and running on App Store. With new builds produced every single day, Bitrise automates the master build changes shipment to App Store testing. The workflow is also configured to only deliver a new build once there are changes implemented. The product owner thus receives only the information on the significant builds shipped.

Testing automation

New features get tested automatically and no build is shipped if bugs are found. One of the latest Bitrise features allows running functional user tests on a device emulator. If any of these unit tests fail, the notifications go directly to a developer.

For one of our iOS apps, all the messages are collected from the commits on the project GitHub repository every day. By the end of the week or sprint or whatever landmark, the product owner (client) comes up with a description of the fixes and new features added to the build. All the features and fixes are listed in the files that come with the commits. The assembly of these descriptions is automated by Bitrise.

All in all, each upload to TestFlight takes up to 1 hour. But because it is done by the Bitrise virtual machine, it costs nothing for us.

What is Fastlane

Fastlane is a continuous delivery tool for iOS and Android. Fastlane is a ruby-written tool and can be installed by means of ruby gems. Like Bitrise, Fastlane has build steps, called Actions. Each action is a task that has to be executed to reach some kind of result.

All Fastlane actions can be collected into single entities, called Lanes. Lanes are like workflows in Bitrise. When a lane is run in Fastlane, each action in the lane gets executed with the build failing if any of the actions does not execute properly. The lanes are defined in the files called Fastlife.

With all the parallels, Bitrise and Fastlane are not conflicting and repeating each other, but as we found out, Fastlane can significantly supplement the Bitrise involvement in a mobile development project.  

So in our situation, Bitrise assembles builds from various steps and workflows and utilizes a script that collects messages and forms a build description and Fastlane is used to automate two subfeatures:

  • Automated build description pull.
  • Automated icon version badge update.

Bitrise Fastlane integration

Assuming you have an iOS project with all the app IDs and provisions on iTunes Connect and developer.apple.com, this is how you integrate Fastlane to Bitrise workflow:

  1. Install Fastlane.
  2. Navigate to the project directory:

    cd <your project directory>
  3. Initialize Fastlane configuration for your project:

    fastlane init
  4. Enter your Apple ID and password to verify that your app with a proper app ID exists on iTunes Connect.
  5. Specify your team if you are a member of several teams on developer.apple.com and iTunesConnect.
  6. Note: have your iTunes Connect team ID at hand, as you will need it later*.
  7. Navigate to ./fastlane/Appfile to check if the file contains the correct information (pp_identifier, apple_id, team_id). *Enter itc_team_id.
  8. Navigate to ./fastlane/Fastlane and apply the following changes:
    1. Leave fastlane_version as specified by fastlane init.
    2. Same applies to default_platform. For an iOS project, it has to be default_platform :ios.
    3. Leave the before_all block empty, as all the dependency management is lifted by Bitrise.
    4. Remove all the lane blocks except for just one that we are going to need.
    5. Empty the after_all and error blocks, as all the additional steps are going to be handled by Bitrise.
  9. Configure your lane.
  10. Specify the provision profiles in the “gym” action (see example below).
  11. Use environment variables through ENV[‘VAR_NAME’] (see example below).
  12. The example of Fastfile:
fastlane_version "2.66.2"
default_platform :ios
platform :ios do
    before_all do
    end
    desc "Submit a new Beta Build to Apple TestFlight"
    lane :beta do
        # Add a badge to app icon. This will actually overwrite images, so be careful.
        # More info: https://github.com/HazAT/fastlane-plugin-badge & https://github.com/HazAT/badge
        add_badge(
            alpha: true,
            shield: "v#{ENV['XPI_VERSION']}-#{ENV['XPI_BUILD']}-grey"
        )
        #build app
        gym(
            export_method: "app-store",
            export_options: {
                provisioningProfiles: {
                    "app.bundle.id" => "Your Provision Profile Name",
                }
            },
        scheme: "YourSchemeName")
        # Upload build to testflight & set field "what's new"
        pilot(
            changelog: ENV['WHATS_NEW_MESSAGE']
        )
    end
    after_all do |lane|
        # This block is called, only if the executed lane was successful
    end
    error do |lane, exception|
    end
    
end

13. To upload the project to App Store, use the Deliverfile of the deliver tool of Fastlane. However, as we are only uploading it to TestFlight, this file is irrelevant.

14. Use the plugins (if any) via gem. For this, install it:

sudo gem install bundler

15. Create Gemfile in your project directory with the specific contents. In the example below, we are adding the “badge” plugin.

source "https://rubygems.org"
gem "fastlane"
gem "fastlane-plugin-badge", git: "https://github.com/HazAT/fastlane-plugin-badge"

16. Install/update gems:

sudo bundle update

17. Push new files to the Git repository.

18. In your Bitrise workflow, set the following parameters:

    • Activate SSH key (RSA private key)– to use your credentials to access Git.
    • Git Clone Repository – to pull a branch.
    • Certificate and profile installer – a crucial step for provision profiles. See the Code Signing tab in the workflow editor on Bitrise. Upload the App Store and development provision profiles, development, and distribution certificates.
    • Run CocoaPods install – to install, update, and run CocoaPods (if your project uses them) and if you are not committing the pod files into your repository.
    • Brew install if your project swiftlint to check the formatting of the code. Specify swiftlint as “Name of the formula to install/upgrade” and select yes for “Upgrade formula if previously installed”.
    • Set Xcode Project Build Number – to increment the build number inside Info.plist. A nice solution is to add the Build Number field to $BITRISE_BUILD_NUMBER. This will sync up the build numbers in TestFlight with the ones on Bitrise.
    • Xcode Project Info – to extract the version and build number from Info.plist to environment variables.
    • Xcode Test for iOS – to ensure that unit tests are passing for a new build.
    • fastlane – to run the fastlane. Specify the lane that has to be running. In the example above, it’s beta. Check the Working directory parameter and make sure it is correct.
    • Script ‘push build number changes’ is a script to push a change of the build number back into Git:
git commit ./MetroApp/Metro/**/Info.plist -m "[version update] [ci skip]"
git push origin "$BITRISE_GIT_BRANCH"
  • Git tag – to add a tag to the current branch of the new release.  Tag to set on current commit is the tag string itself. For our example, it would be exampleapp-testflight-$XPI_VERSION-$XPI_BUILD.

19. Go to the Secrets tab in the workflow editor and add the FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD and FASTLANE_PASSWORD variables. Specify their values as the password for the Apple account that you entered in the Appfile as apple_id.

20. Make sure you uploaded the proper provision profiles and certificates to the Code signing tab of the workflow editor. Before running the tool mentioned on that page, make sure you’ve uploaded a build to TestFlight manually at least once.

This integration allows our iOS app builds to assemble in 45-50 minutes with most of the time spent on processing the build on App Store and TestFlight.

One of the most convenient features we’ve utilized so far is the workflow interactions allowing integration with GitHub, Jira, or some other progress tracking tools. Implemented tasks get tested within the project branch before the merge. We filter tests and merge the master build after that, which means the task can be closed.

Another option is to pass the task to a test engineer for the build to be merged to staging. Every new merge is a new build. After all the work in staging, a manager knows which of these builds is the most appropriate, and can be selected for the release. This is not exactly automation yet, but the Bitrise Fastlane combo is a great way to ship application builds.

A useful Bitrise script

This script collects commit messages and exports them to Jira tickets. In this script, the branches have a prefix equal to that of the Jira tickets (for example MET-66). The script exports a message to the Bitrise environment variable called WHATS_NEW_MESSAGE:

#!/usr/bin/env bash

# fail if any commands fails
set -e
#debug log
set -x

# get previous tag
PREVIOUS_TAG=$( git describe --abbrev=0 --match "*metro*" )

# get commit messages from previous tag and extract only uniqu task identifiers from them
# this will leave something like this:
# MET-1
# MET-3
# MET-89
TASK_IDS=$( git log --pretty=oneline "$PREVIOUS_TAG"..."$BITRISE_GIT_BRANCH" | sed 's/m/M/g' | sed 's/e/E/g' | sed 's/t/T/g' | grep -E '.*(MET-[0-9]+).*' | sed -E 's/.*(MET-[0-9]+).*/\1/' | sort | uniq )

# check, that there are new feature-branches merged
if [ $TASK_IDS -ge 4 ]
then
    echo "Found no new merges. Exciting..."
    exit 1
fi

# transform task ids into Jira links
MESSAGE=$( echo "$TASK_IDS" | sed 's|\(.*\)|http://j.shakuro.com/browse/\1  (\1)|' )

echo "Processed changelog:"
echo "$MESSAGE"

# export message as a global variable into Bitrise environment
envman add --key WHATS_NEW_MESSAGE --value "$MESSAGE"