Automate iOS deployment process by CI/CD using Gitlab CI and Fastlane
When we develop iOS apps, we usually manage the app publication process using the Xcode Organizer. Then we sign, test, build, archive, submit, change versions, submit new builds again and again to the TestFlight or AppStore.
If we generate our builds daily, this process is tedious and tiring. So, in this article I am going to show you how to implement Gitlab CI in your iOS project and then we will see how to automatically upload builds on TestFlight.
What is CI/CD?
Continuous Integration and Continuous Delivery (CI/CD) for iOS enable us to improve our build deploys. We’re able to release updates at any time in a sustainable way without the hurdle of doing it manually every time. We don’t need to run all our tests when we add new code or take a trial and error approach before pushing a commit to our repository.
- GitLab comes with CI built-in for all projects, for free.
- Fastlane helps you to automate the most time-consuming beta distribution steps including incrementing the build version, code signing, building and uploading the app, and setting a change-log.
Continuous integration with Gitlab
You will need an iOS app to configure our Continuous Integration and Delivery pipeline. Then, you will need to configure a new repository.
Before jumping into the implementation, let us first understand the workflow of Gitlab CI.
- You make changes to your copy of the codebase and push a commit to GitLab.
- GitLab recognizes that the codebase has changed.
- GitLab triggers a build with the GitLab Runner you set up on your Mac for the project.
- The GitLab Runner runs through the build and test process you specified in
.gitlab-ci.yml
. - The GitLab Runner reports its results back to GitLab.
- GitLab shows you the results of the build.
For implementation, we have these basic requirements:
- Gitlab: as our CI(Continuous Integration) platform
- Xcode 11 or above with Swift 5.1 (or above) as our development tools.
- XCTestCases to create and run unit tests and UI tests.
Assumptions & Prerequisites
We are assuming that you have the Gitlab repository of your iOS app already setup. Also, these things are mandatory in your Xcode project:
- Make sure to include Unit Tests and Include UI Tests in the project.
- Have a gitlab repo setup for your project.
- Make sure that your project scheme is Shared.
Note: To share a scheme in Xcode, choose Product > Scheme > Manage Schemes.
By sharing project scheme, GitLab CI gets context it needs to build and test your project.
Now, open Terminal and navigate to the folder you created for your iOS project.
It’s convenient to add a standard .gitignore
file. For a Swift project, enter:
$ curl -o .gitignore https://www.gitignore.io/api/swift
The curl
command conveniently downloads the contents of the page at the given gitignore.io URL into a file named .gitignore
.
Install XCPretty
Install xcpretty for test case reports. When Xcode builds and tests your project, xcpretty will transform the output into something more readable format.
Installation:
Open Terminal and enter the following command:$ sudo gem install xcpretty
OR
$ gem install xcpretty --user-install
Try using any one of the above, in case the other one throws some permission error (which I personally faced).
Installing & Registering Gitlab Runner
The GitLab Runner is a service that’s installed on your Mac, which runs the build and test process that you set up in a configuration file(.gitlab-ci.yml).
Installation
Download the binary for your system:
sudo curl --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-darwin-amd64
Give it permissions to execute:
sudo chmod +x /usr/local/bin/gitlab-runner
Register
To register a Runner under macOS run the following command:
$ gitlab-runner register
Enter your GitLab instance URL:
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com )
https://serveraddress.com/gitlab/
Enter the token you obtained to register the Runner: (You will obtain this token once you logged in to the gitlab into your account setting pipeline section. If this token is not correct then it won’t register runner to GitLab)
Please enter the gitlab-ci token for
this
runner
xxxxxxxxxxxxxxxxxx (Use your GitLab token)
Enter a description for the Runner, you can change this later in GitLab’s
Please enter the gitlab-ci description for
this
runner
ios-runner
Enter the tags associated with the Runner, you can change this later in GitLab’s UI:
Please enter the gitlab-ci tags for
this
runner (comma separated):
ios-tag, version-1.0
Enter the Runner executor: (There are given number of executors, We have used shell since its won’t have any dependancy)
Please enter the executor: ssh, docker+machine, docker-ssh+machine, kubernetes, docker, parallels, virtualbox, docker-ssh, shell:
shell
How to verify if Runner properly registered
- Goto below Path <Home>/Users/<user>/gitlab-runner/config.toml
- .gitlab-runner directory will be created. This is hidden directory
- In that directory there is “config.toml”
- This config file will list down all the runners installed on the Mac machine
- You can increase the runner limit from this file. output_limit = 20000
Commands-
To run gitlab runner use: gitlab-runner run
To verify that your gitlab runner is running or not, enter the below command:gitlab-runner verify
Check the runner status. If it’s not running, Please use below commands to run the runner.gitlab-runner stop
gitlab-runner restart
PIPELINE IMPLEMENTATION
Create a .gitlab-ci.yml in your project root directory with given build stages Build, Test, Archive, Release.
Build: This stage will build the your project.
Test: This stage will run the Unit and UI test cases of your project and display on your GitLab console/terminal.
Archive: This stage will generate and IPA archive file, and you can also upload it on Gitlab.
Release: There are 2 methods to release the builds:
- This stage will upload the archive file on Gitlab(or in the local project directory) from there you can distribute the build to testers and stakeholders.
- Other method is to deploy the build on the TestFlight using Fastlane.
The last thing to do is to configure .yml file. To do so, open your .gitlab-ci.yml file with the text editor and enter the following:
stages:
- build
- archive
build_project:
stage: build
script:
- xcodebuild clean -workspace ProjectName.xcworkspace -scheme SchemeName | xcpretty
- xcodebuild test -workspace ProjectName.xcworkspace -scheme SchemeName -destination 'platform=iOS Simulator,name=iPhone 12 Pro,OS=15.2' | xcpretty -s
tags:
- ios-tag
- version-1.0archive_build:
dependencies: []
stage: archive
artifacts:
paths:
- build/ProjectName.ipa
script:
- fastlane beta
tags:
- ios-tag
- version-1.0
only:
- master
Save this file in your Xcode project folder as .gitlab-ci.yml
, and don't forget the period at the beginning of the file name!
Let’s go through the file with some detail:
- The file first describes the
stages
available to eachjob
. For simplicity, we have one stage (build
) and one job (build_project
). - The file then provides the settings for each
job
. Thebuild_project
job runs two scripts: one to clean the Xcode project, and then another to build and test it. You can probably skip the cleaning script to save time, unless you want to be sure that you're building from a clean state. - Under
tags
, add the tags you created when you registered the GitLab Runner. - The
fastlane beta
basically triggers the fastlane script present in the fastlane/fastfile and uploads the build on Testflight. If you want to only archive the build, then usefastlane gym
instead offastlane beta
. We will look into this later in this article only.
There are also some things to look out for:
- Make sure to replace all references to
ProjectName
with the name of your Xcode project; if you're using a different scheme than the default, then make sure you pass in the properSchemeName
too (the default is the same as theProjectName
). - In the
xcodebuild test
command, notice the-destination
option is set to launch an iPhone 12 Pro image running iOS 15.2 in the Simulator; if you want to run a different device (iPad, for example), you'll need to change this.
There’s a simple tool for “linting” (i.e., validating) your .gitlab-ci.yml
in GitLab. From your GitLab project page, click on CI/CD > Pipelines in the sidebar, then in the upper-right corner, click on CI lint:
Paste the contents of your .gitlab-ci.yml
file into the text box and click on Validate.
You should see something like:
Status: syntax is correct
This won’t tell you if your project name or the Simulator chosen is correct, so be sure to double-check these settings.
The .gitlab-ci.yml
file is extremely customizable. You can limit jobs to run on success or failure, or depending on branches or tags, etc.—read through the documentation to get a feeling for just how flexible and powerful it is.
How to trigger builds
To trigger a build, all you have to do is push a commit on GitLab. And Wohoo!! you have triggered a pipeline. You can see piplelines on the Sidebar menu in CICD -> Pipelines section on Gitlab dashboard.
If you click on the pipeline, it will show some logs like this in green if it is passed and successfully done.
We have finally configured CI/CD workflow with GitLab CI.
Other salient points
- This workflow should work for any kind of Xcode project, including tvOS, watchOS, and macOS. Just be sure to specify the appropriate Simulator device in your
.gitlab-ci.yml
file. - If you want to push a commit but don’t want to trigger a CI build, simply add
[ci skip]
to your commit message. - If the user that installed the GitLab runner isn’t logged in, the runner won’t run. So, if builds seem to be pending for a long time, you may want to check on this!
- If you’re working on a team, or if your project is public, you may want to install the GitLab Runner on a dedicated build machine. It can otherwise be very distracting to be using your machine and have Simulator launch unexpectedly to run a test suite.
CONTINUOUS DEPLOYMENT USING FASTLANE
In this article, we will archive the build using fastlane and then later deploy it on Testflight.
Installing Fastlane in Mac
Fastlane setup is the crucial step in the whole process since it is the backbone of the deployment process. To install Fastlane in your system, please go through the below steps: Install the latest Xcode command line tools:
sudo gem install fastlane -NV
Install fastlane using rubygems or homebrew:
# Using RubyGems
sudo gem install fastlane -NV# Alternatively using Homebrew
brew cask install fastlane
To start using fastlane in your project, you’ll need to run fastlane init from your project directory in command line.
$ fastlane init
Fastlane will ask you for some basic configuration and then create a folder called fastlane in your project which will contain mainly two files:
1. fastlane/Appfile
This file is straightforward, so you just want to check to make sure that the Apple ID and app ID that you set up earlier are correct.
app_identifier("APP IDENTIFIER") # The bundle identifier of your app
apple_id("APPLE ID") # Your Apple email address
2. fastlane/gymfile
gym builds and packages iOS apps for you. It takes care of all the heavy lifting and makes it super easy to generate a signed ipa
or app
file .
To create a gym file in your project, you’ll need to run this command from your project directory in command line.
$ fastlane gym init
Since you might want to automatically trigger a new build but don’t want to specify all the parameters every time, you can store your defaults in a so called Gymfile
.
Run fastlane gym init
to create a new configuration file. Example:
scheme("SchemeName")sdk("iphoneos15.2")clean(true)output_directory("./build") # store the ipa in this folder
output_name("AppName") # the name of the ipa file
3. fastlane/fastfile
The fastfile defines the build steps. Since we’re using a lot of the built-in capability of fastlane this is really straightforward. We create a single lane which increments build number, gets certificates, builds, and uploads the new build to TestFlight. Of course, you may want to split these out into different jobs depending on your use case. Each of these steps, get_certificates, get_provisioning_profile, match, gym, and upload_to_testflight are pre-bundled actions already included with fastlane.
Code Signing approach:
- Using fastlane match
The concept of match is described in the codesigning guide.
With match you store your private keys and certificates in a git repo to sync them across machines. This makes it easy to onboard new team-members and set up new Mac machines. This approach is secure and uses technology you already use.
Getting started with match requires you to revoke your existing certificates.
If you don’t want to revoke your existing certificates, but still want an automated setup, cert and sigh are for you.
- cert will make sure you have a valid certificate and its private key installed on the local machine
- sigh will make sure you have a valid provisioning profile installed locally, that matches the installed certificate
get_certificates and get_provisioning_profile are actions associated with the cert and sigh approach to code signing; if you’re using match, you may need to update this by commenting get_certificates and get_provisioning_profile.
There are some special environment variables we need to set to avoid the jobs failing due to 2FA from Apple that were made mandatory recently.FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD
and FASTLANE_SESSION
In order to authenticate against the App Store for the TestFlight upload, fastlane must be able to authenticate. In order to do this, you need to create an app-specific password to be used by CI. You can read more about this process in this documentation.
FASTLANE_USER
and FASTLANE_PASSWORD
Finally your fastfile should look like this:
default_platform(:ios)before_all do
ENV['FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD']= 'xxxx-xxxx-xxxx-xxxx'
endplatform :ios do
desc "Push a new beta build to TestFlight"
lane :beta do
increment_build_number(build_number: latest_testflight_build_number + 1,
xcodeproj: "ProjectName.xcodeproj")
build_app(workspace: "ProjectName.xcworkspace", scheme: "SchemeName", include_bitcode: true)
upload_to_testflight
end
end
Note: I have added the App_Specific_Password as we are having 2fa enabled in our our developer account which was creating problem for fastlane to authenticate with AppStore. So, adding this resolves our problem.
If you want to add an App Specific password in your app, you can read the doc here. There’s so much more there in Fastlane so, if you want to explore more about it, you can visit here.
Now, add the below code to your existing .gitlab-ci.yml file for triggering the job to upload build on the Tesflight when you commit the code to master branch.
- You have to add a new stage in our existing .yml file
- Add Job description in the .yml file.
- Only defines the branch name on which you want the job to be triggered.
Your final .gitlab-ci.yml file would look like this:
The last thing to do is to configure .yml file. To do so, open your .gitlab-ci.yml file with the text editor and enter the following:
stages:
- build
- test_flightbuild_project:
stage: build
script:
- xcodebuild clean -workspace ProjectName.xcworkspace -scheme SchemeName | xcpretty
- xcodebuild test -workspace ProjectName.xcworkspace -scheme SchemeName -destination 'platform=iOS Simulator,name=iPhone 12 Pro,OS=15.2' | xcpretty -s
tags:
- ios-tag
- version-1.0test_flight_build:
dependencies: []
stage: test_flight
artifacts:
paths:
- fastlane/logs
script:
- fastlane beta
tags:
- ios-tag
- version-1.0
only:
- master
Save this file in your Xcode project folder and push the code on gitlab and your job will be triggered, and hence your project will be built successfully.
If you have added the
fastlane gym
instead offastlane beta
command in the .gitlab-ci.yml file, and you have also created the gym file in the folder, then you just have to commit the code and your pipeline will start running and you can see the artifact on gitlab. And if you are using thefastlane beta
command, then it will upload the build on the TestFlight.
If you have only archived the build and not uploaded it on testflight by using the fastlane gym
command in yml file, then you can see your archived file uploaded on the Gitlab from here:
You can also view the archived file in the system(in which gitlab runner is running). For that you just have to open the pipeline and view the logs. It will show you the path where the archived is being stored.
More documents related to Fastlane can be found here.
Conclusion
Ah!! Kudos to you!!! We have finally configured our own CI/CD workflow with GitLab CI/CD and Fastlane as our primary tools.
Thanks for reading this article. Hope it proves to be helpful for you for configuring a robust CI/CD workflow for automating your iOS app deployment process.