iOS Continuous Integration and Delivery with Fastlane

Ira Revkina
arconsis
Published in
7 min readJul 7, 2021

Setting up a build for an application is always tricky. The developer has to download all the signing certificates, archive the application, do a lot of things, click some buttons and check the marks. To avoid these rather boring steps, Fastlane helps us by doing it all automatically. In this tutorial, I’m going to tell what Fastlane is and how you can use Fastlane to build your app!

What is CI/CD?

Continuous Integration works by pushing small code chunks to your application’s codebase hosted in a Git repository, and after every push, runs a pipeline of scripts to build, test, and validate the code changes before merging them into the main / develop / beta branch.

Continuous Delivery is next step for deploying your application to a production environment. In our case, the build must be submitted to Testflight / App Store, after which the new version can be distributed.

These methodologies allow us to detect bugs early in the development cycle and make life a lot easier for developers.

What is Fastlane?

Fastlane is a tool to automate build and publish processes for mobile apps (iOS, Android, Flutter), which also includes generating screenshots, running Unit / UI tests, connecting to Crashlytics, generating a Change Log, and many other useful things.

To install fastlane, open terminal and use one of these commands:

$ gem install fastlane 
# or via brew
$ brew cask install fastlane

After that navigate to your project directory via terminal to set the project as the working directory, and enter:

$ fastlane init

After some output, fastlane will ask: “What would you like to use fastlane for?”:

Input 4. The 4th option was chosen to commence manual setup. Installed Fastlane will generate a basic configuration and fetch existing metadata from iTunes Connect if any. In the your project folder, you’ll see: Gemfile, which includes the fastlane gem as a project dependency and a ./fastlane directory.

You will end up with a fastlane folder that looks like this:

There are 2 files: Appfile and Fastfile. The Fastfile is more interesting for developers, it contains all necessary information to distribute the application. We will see in more detail in next sections.

1. Appfile

The Appfile is a handy configuration file that stores information that fastlane needs to set up your app. It is commonly used to store variable properties such as your Apple ID and App ID, and any other identifying information. Then need to setup the bundle identifier of your App and your Apple ID.

Open Appfile. Remove the hash # symbol to uncomment the lines, then replace apple_id with your actual Apple ID username. The next step is to replace your app’s bundle id (in this example, the bundle id — “com.arconsis.ci-app”). So, the file will look like:

# The bundle identifier of your app
app_identifier("com.arconsis.ci-app")
# Your Apple email address
apple_id("Your Apple email address")
# To select a team for App Store Connect use
# itc_team_id("App Store Connect Team ID")
# team_id("Developer Portal Team ID")

The itc_team_id and the team_id are only needed if your Apple ID is integrated into more than one team on the Apple Developers Portal and iTunesConnect.

2. Fastfile

Fastfile manages the lanes to be created to invoke fastlane actions. A lot of the built-in fastlane features can be used.

Fastlane brings the following set of tools:

  • deliver uploads screenshots, metadata and your apps to the App Store
  • snapshot automates taking localized screenshots of your iOS app on every device
  • sigh can fully manage all aspects of provisioning profiles
  • produce set a new iOS apps on Developer Portal and/or iTunes Connect
  • cert automatically creates and maintains iOS code-signing certificates
  • scan runs tests on Xcode simulator or any connected devices on your local environment
  • gym builds and packages your iOS apps

These actions are organized into lanes. For example, a lane for App Store deployment and another lane for TestFlight distribution can be configured. Lanes can consist of the following individual actions:

  1. Building your project
  2. Incrementing build number
  3. Running unit tests
  4. Sending your .ipa to TestFlight
  5. Sending a Slack message to your team

Lanes can be thought of as functions that group related tasks, even invoke lanes methods from another lane to further decouple and reuse lanes.

Below is an example with actions for the beta lane:

lane :beta do
# Actions
increment_build_number
build_app
upload_to_testflight
end

Let’s start by giving the beta lane a meaningful name. Rename the beta to build_and_upload_to_testflight. Now it is clear that the result of executing this script will be a fresh assembly loaded into TestFlight.

default_platform(:ios)platform :ios do
desc "Build, create a signed IPA, and upload to TestFlight"
lane :build_and_upload_to_testflight do
# Actions
end
end

The first step is to create certificates, this needs 2 actions:

  • cert automatically creates and maintains iOS code-signing certificates. A new certificate will be generated only when needed.
  • sigh creates, renews, downloads and repairs provisioning profiles.
lane :build_and_upload_to_testflight do
cert
sigh(force: true)
...
end

force: true will ensure that the provisioning profile is re-generated on every run. This will cause sigh to always use the correct signing certificate that is installed on the local machine.

After that, the build number should be incremented.

lane :build_and_upload_to_testflight do
...
increment_build_number

# or if you need to define the path to a specific directory
increment_build_number(
xcodeproj: "./path/to/YourApp.xcodeproj"
)
...
end

Moreover, the build number can be updated using the latest TestFlight build number. latest_testflight_build_number is an integer representation of the last build number loaded into TestFlight.

lane :build_and_upload_to_testflight do
...
# Get new version code
version_number = getTestFlightVersionCode
increment_build_number(
build_number: version_number # set a specific number
)
...
end
def getTestFlightVersionCode
# Read versionCode from TestFlight
# Latest version code
testFlightVersionCodes = "#{latest_testflight_build_number}"

# New version code
newVersionCode = testFlightVersionCodes.next
newVersionCode
end

The next step is to build the application and upload it. The export_method is used to export an archive. Valid values are: app-store, validation, ad-hoc, package, enterprise, development, developer-id and mac-application. Set the correct export method, if the app is uploaded to the App Store or TestFlight, the app-store should be chosen:

lane :build_and_upload_to_testflight do
...
# Need to create .ipa and upload
build_ios_app(export_method: 'app-store')

# Upload new binary to App Store Connect for TestFlight beta testing
upload_to_testflight
end

upload_to_testflight method has many parameters that can be customised. Visit this link for details — upload to TestFlight actions.

3. .gitlab-ci.yml

The last thing to do is to configure your build and test settings. It’s time to add a config file to run jobs in GitLab pipelines. The following steps should be done: open TextEdit, paste the contents of the below snippet into your editor and save this file in the root folder of the repository as .gitlab-ci.yml:

before_script:
# Actions which should be completed before main script
- bundle install
build_and_upload_app:
script:
# run our lane for uploading
- bundle exec fastlane build_and_upload_to_testflight

The file provides actions for each job. The specified before_script as the name suggests is executed before the job. The build_and_upload_app triggers our upload lane in fastlane to build the app and upload it to TestFlight. The .gitlab-ci.yml file is highly customizable. Executing tasks on success or failure, or depending on branches, tags, etc can be restricted.

However, Gitlab CI is not the only way, Jenkins or Bitrise can be used.

Jenkins is an open-source continuous integration environment. You can define jobs in Jenkins that can perform tasks such as running a scheduled build of software and backing up data.

Bitrise is a continuous integration platform that is both powerful and easy to use. This is a hosted CI/CD solution where you can run your fastlane with the same commands you would use locally.It’s great for working with certificates and profiles, offers a good set of workflow options, and is fairly easy to configure.

Start final script

The script can be run manually from terminal — bundle exec fastlane build_and_upload_to_testflight or set up a CI Runner that will script and update the application under certain conditions, for example, every time the developer or master branch is updated. Follow the latest instructions to set up GitLab Runner on macOS.

Conclusion

We’ve finally configured the CI/CD workflow with GitLab and Fastlane as the main tools. Fastlane also supports TestFlight submission and a ton of integrations. Your lanes can be customized to provide real-time feedback on Slack, interact with Jira boards, etc.

Thanks for reading this article! I also want to thank Jonas Stubenrauch who assisted in writing this article. Hopefully this is helpful for you when setting up a CI/CD workflow to automate the iOS application deployment process.

--

--