CI/CD Using Fastlane and Jenkins-iOS

Sumit Kumar
8 min readJan 31, 2022

--

Continuous Integration(CI/CD or CICD)

If you haven't read the previous part for this, hereby sharing that.
I described there, the setup process and creation of fast-lane script to facilitate Continuous Delivery (CD).

In this article, I will be guiding through what is Continuous Integration (CI) and how to blend this with CD to accomplish CI & CD for iOS.

What is CI/CD

CI/CD bridges the gaps between development and operation activities and teams by enforcing automation in the building, testing, and deployment of applications.
Depending on your source control/version control strategy, code changes for a bug fix or new feature need to be merged/committed into a branch in the source code repository.
For more details refer to this article.

Continuous Integration(CI/CD or CICD) workflow

Above flowchart gives a high level overview of how CI/CD flow works and what all checks/steps it goes through.

The flowchart shows the continuous integration till artifacts created and uploaded and later deployed manually (generally most preferred approach as to safely get changes into production), however this can be configured as per need and deployment can also be done automatically.

Introduction

In order to achieve CI/CD, we will be using Jenkins(CI Server) + Fastlane(CD).

What is Jenkins

Jenkins is an automation server that enables to build, test and deploy the software which supports almost all language and source code repositories.
This is one of the most popular DevOps tools and contains almost all plugins to connect all tools/systems available for SDLC to create the pipeline.
Being open-source in nature and due to its versatility —makes it a favorable choice for developers for CI/CD.

Jenkins installation

Follow this link for Jenkins installation and setup
https://www.knowledgehut.com/blog/devops/install-jenkins-on-mac

Jenkins Plugins installation

In order to use any system/tools, its respective plugin needs to be installed, in case that is not installed as part of the default plugins list.
For example, listing few:

  1. Github/ Bitbucket Plugin
  2. Blue ocean (Enhance pipeline UI)
  3. Cobertura/ Sonarqube etc.
Manage Plugins Installations

Getting Started With Jenkins Pipeline

Once Jenkins is installed, we need to create a job(New Item) from the sidebar. There are different styles of pipeline that Jenkins offers.
However, in this article, we will use the Multi-branch pipeline approach.

Multi-branch pipeline

New Jenkins job created automatically as per new PR request or new branch pushed and cleans up the jobs as in branches are merged.

Pipeline Steps

Each time a commit is done below listed activities/checks/steps would be executed.
1. Checkout working branch
2. Build the project
3. Execute Unit Tests, linters, code coverage in parallel
4. Execute UI tests using simulators
5. Build, test, deploy using fastlane scripts
6. Publish post build report summary

Since UI testing/artifacts creation, requires simulator, Xcode etc with hardware dependencies — hence, this pipeline requires to be executed in a dedicated machine. Jenkins server can be hosted in any cloud space/private space, however a slave node is used to execute the pipeline either in the same machine or a dedicated machine.

All the pipeline stages, steps, flow defined in a Jenkinsfile, including the identity of slave node. Jenkinsfile reside inside the source code, and by default jenkins pipeline uses a file named Jenkinsfile in the root directory, we will learn details in the later half how to add and define pipeline script inside.

Assuming till this point you have basic Jenkins setup and installed the basic plugins like Git,Blueocean etc.
Now lets jump in to create the pipeline, Fun begins ;)

Step 1: Create Multibranch pipeline Job

Jenkins Dashboard -> New Item

Multibranch Pipeline

Step 2: Configure the job

Configure the SCM/Git inside the job

SCM Configuration

Once SCM config is saved, jenkins server would do automatic scan to discover the available branches as per the filter we applied via a regular expression.

Automatic Scan report

Note: ‘Jenkinsfile’ found — because i have jenkinsfile already added to repo, else it will show missing Jenkinsfile, which we will learn more in Step4.

Step 3: Configure webhooks to receive changes

Jenkins Dashboard -> Manage Jenkins -> Configure System -> Github Server
Credentials — You can create your own personal access token in your account GitHub settings, add that as a secret text kind of credentials in jenkins.

Go to Github repo settings -> Webhooks -> Add webhook

Adding webhook to github

Note: If needed, you can use https://ngrok.com/ for generating Public URL for webhook relay. Else for learning and development you use manual trigger or periodic scan option from Jenkins.

Step 4: Add Jenkinsfile

Go to project root on the system.

touch Jenkinsfile

push the created file to remote.

Go to Step2 and scan again, should show `jenkinsfile` found.

Step5: Configure Jenkinsfile

This file is written in Groovy and defines the Pipeline Stages configuration. Two types of syntax can be used to declare the stages: Declarative Pipeline and Scripted Pipeline. Both can be mixed in the same file.
We will be using Declarative style, i personally prefer this because of its simplicity and readability.

Lets go through and understand what each of them meant for.

Pipeline Sections

pipeline {agent { label 'mac-mini-slave' }parameters {
// the default choice for commit-triggered builds is the first item in the choices list
choice(name: 'buildVariant', choices: ['Debug_Scan_Only', 'Debug_TestFlight', 'Release_AppStore_TestFlight'], description: 'The variants to build')
}
environment {LC_ALL = 'en_US.UTF-8'
APP_NAME = 'AdManagerTest'
BUILD_NAME = 'AdManagerTest'
APP_TARGET = 'AdManagerTest'
APP_PROJECT = 'AdManagerTest.xcodeproj'
APP_WORKSPACE = 'AdManagerTest.xcworkspace'
APP_TEST_SCHEME = 'AdManagerTest'
PUBLISH_TO_CHANNEL = 'teams'
}stages {
...
}
}

agent — defines the slave node
parameters — A build parameter allows us to pass data into our Jenkins jobs. Using build parameters, we can pass any data we want: build variant, secrets, host-names and ports, and so on.
environment — A key-value pair to define any stage or steps specific dependency. Here we will inject project specific configs via environment variables to create build accordingly. Env variables are global to the pipeline.

pipeline {agent { label 'mac-mini-slave' }
parameters {..}
environment {...}
stages {
//<< Git SCM Checkout >>
stage('Git Checkout') {
steps {
checkout scm
}
}
stage('Update Env with Build Variant') {
steps {
script {
env.BUILD_VARIANT = params.buildVariant
// Conditionally define a build variant 'impact'
if (BUILD_VARIANT == 'Debug_TestFlight') {
echo "Debug_TestFlight"
} else if (BUILD_VARIANT == 'Release_AppStore_TestFlight') {
echo "Release_AppStore_TestFlight"
}
}
}
}
stage('Git - Fetch Version/Commits') {
steps {
script {
//Shell commands
env.GIT_COMMIT_MSG = sh(returnStdout: true, script: '''
git log -1 --pretty=%B ${GIT_COMMIT}
''').trim()
def DATE_TIME = sh(returnStdout: true, script: '''
date +%Y.%m.%d-%H:%M:%S
''').trim()
....
}
}
}
stage('Unit Test cases') {
}
stage('Quality checks - Report') {
parallel {
stage('Linting') { ... }
stage('Code Coverage') { ... }
}
stage('Commit File changes') { ... }
stage('Build') { ... }
stage('Generating Artifacts') { ... }
stage('Post Build -- Actions') { ... }
}post {success {
office365ConnectorSend color: '#86BC25', status: currentBuild.result, webhookUrl: "${ env.WEBHOOK_URL }",
message: "Test Successful: ${JOB_NAME} - ${currentBuild.displayName}<br>Pipeline duration: ${currentBuild.durationString.replace(' and counting', '')}"
}
unstable {
office365ConnectorSend color: '#FFE933', status: currentBuild.result, webhookUrl: "${ env.WEBHOOK_URL }",
message: "Successfully Build but Unstable. Unstable means test failure, code violation, push to remote failed etc. : ${JOB_NAME} - ${currentBuild.displayName}<br>Pipeline duration: ${currentBuild.durationString.replace(' and counting', '')}"
}
failure {
office365ConnectorSend color: '#ff0000', status: currentBuild.result, webhookUrl: "${ env.WEBHOOK_URL }",
message: "Build Failed: ${JOB_NAME} - ${currentBuild.displayName}<br>Pipeline duration: ${currentBuild.durationString.replace(' and counting', '')}"}always {
echo "Build completed with status: ${currentBuild.result}"
}
}

stages — Its a block for executing certain task, this where it contains the bulk of work described by pipeline. Structure is listed as above for defining the stage.
steps— defines a series of one or more steps to be executed in a given stage directive
script — this block is usually used to define complex code, for example conditional checks, shell commands.
parallel — block of action required to be executed in parallel, this is useful to reduce pipeline total time.
post — this block gets executed when all stages are performed, usually used to send notification about build/pipeline status, archive artifacts, test reports etc.

Step6: Changes to fastlane script

As per the variables getting injected from pipeline environment block, the values hardcoded in `Fastfile`should be reading from environment variable.
For example:

// Fastfile as per we created in Series 1
app_name = "FastlaneSample"
scheme = "FastlaneSample-Test"
// Fastfile as per updated in this article
app_name = ENV["APP_NAME"]
scheme = ENV["APP_SCHEME"]
& so on....

Refer sample project for going over complete file(Fastfile + Jenkinsfile) in details.

Step6: Commit & Push these changes to remote

Commit all changes to Jenkinsfile/fastfile and others to remote.
Final outcome would look like this.
Hola!! Our Pipeline is a Success , Soothing to eyes ; )

Pipeline view (via Blue Ocean)
Notification send to Teams

Security

In live project, you should move the confidential environment variables to be injected/defined via your CI server rather then hard-coding on script files.
For example — APi key’s, session secrets, certificate details etc.

Conclusion

Congratulations! You have completed setting up the automated CI/CD process, it will automatically scan, build, validate and deploy all the repository branches.
Note: Well, there is no one shoe fits all here and requires update/alteration as per custom need and ecosystem. However, this solution will give a standard generalized template to execute any iOS mobile solutions.

Part of these series we were able to learn:

— Setup jenkins & fastlane
— CD with fastlane
— CI/CD with Jenkins + Fastlane + Github Server
— How to add webhook relay
— How to write Fastfile and Jenkinsfile
— Complete Integration process

Thanks for reading the article, hope this helps!!!

— — — — — — Happy Deploying! ! — — — — — — —

Any further queries/suggestions please leave a comment below or reach out to me on sumit16.kumar@gmail.com

--

--

Sumit Kumar

Mobile Digital Solution Expert | Devops | ML/AI Enthusiast