CI / CD integration for multiple environments with Jenkins and Fastlane. Part 1

On the eve of the start of the course “iOS Developer. Basic” traditionally prepared an interesting translation for you, and we also invite you to sign up for a free webinar, in which our experts will tell you in detail about the course program, as well as answer your questions.


Incorporating Continuous Integration (CI) and Continuous Delivery (CD) technologies into the development process is undeniably the only way to track the relevance of code changes and identify integration errors at the earliest stages. It is also the path to debugged builds that are almost immediately available for testing and ready to ship to production even after significant code changes.

By integrating CI / CD into their day-to-day work, developers can achieve two important goals: first, the ability to run test suites on a server other than the production computer so that the engineer can continue to develop new functionality without distractions, and second, the ability to send builds to your customer or QA engineer to demonstrate / test / analyze a new feature, even if they cannot build and run the project on their own computers.

But what happens when we have multiple staging environments and tests need to run on different servers or features need to be tested in different environments? This is where Jenkins and Fastlane come to the rescue – tools that allow you to automate the process for various configurations, and this is what this article focuses on.

There are many great articles and tutorials out there that detail how to use Jenkins and Fastlane to set up CI for a mobile project. However, I want to focus on how we can set up the appropriate scripts to work with multiple environments.

Jenkins and Fastlane

To understand how we can integrate CI into a project, we must understand how Jenkins and Fastlane work. In short, Jenkins is an open source continuous integration server written in Java and one of the most widely used tools for managing continuous integration builds. Jenkins provides the ability to create pipelines (Pipelines), which in fact are life cycles of processes called tasks (Jobs) which include building, documenting, testing, packaging, staging, deploying, static analysis, and more. Tasks are linked together in a sequence that forms a pipeline, and this is where Fastlane comes into play.

Fastlane is an open source tool used to automate the deployment and distribution of mobile projects (iOS and Android), offering a wide range of automation features across the mobile app lifecycle, such as packaging, code signing, distribution of assemblies, and more. Fastlane allows you to create tasks called Lanes, which in essence are scripts, that is, a series of commands called Actions, which describe the workflow that we want to automate.

Our goal

So what we want to achieve here is to automate the distribution process of our iOS app by automating the upload of our app to Testflightfrom where stakeholders (customer, quality assurance engineer, salesperson who wants to demo the app, and so on) can log in and download it effortlessly. Moreover, we want to go one step further and implement the ability to automatically load builds into Testflight at the click of a button:

a) for different branches for different features

b) for multiple configurations to suit different environments

Will this be our ultimate goal?

Prerequisites

For this article, we assume that Jenkins and Fastlane have already been installed and configured. There are many guides that detail how to configure. Additionally, Jenkins will need to install some plugins to allow Jenkins to run from the Github webhooks and run the job. These are plugins Github, Xcode, SCM (Source Control Management), which will be used to check our project from Github, and Credentials Plugin in order to bind credentials for environment variables.

Let’s start

First, we need to create a new pipeline in Jenkins. This pipeline will be described by a script we have created that loads our application into Testflight.

From the main Jenkins dashboard menu (below) we select the first option => New Item (Pipeline) and create a Jenkins Job named “Upload to Testflight”.

Next, select the parameter Configure in the left menu and continue adding the configuration of the newly created pipeline. Let’s start by adding a little description.

In field Definition below, in the Pipeline section, select the “Pipeline Script from SCM” parameter. This parameter tells Jenkins to get the pipeline from the source code management (SCM) system, which will be our cloned Git repository.

We add a parameter – the branch that we want to build and upload to Testflight every time, and also add a link to the github repository, providing also the github credential parameters.

Finally, we define a script that will describe the entire pipeline process.

Click the Save button and voila! We managed to create our own task in Jenkins. We are now ready to start writing our script!

We will mainly use the approach Scripted Pipeline… Our script will consist of several stages, called stages, which describe what our pipeline is going to do. Ultimately we want to end up loading into Testflight, but before loading we want to make sure our unit tests pass. Thus, the various phases of an automation script should include the following stages:

  1. Repository checkout

  2. Installing dependencies

  3. Resetting simulators

  4. Running tests

  5. Build assembly

  6. Loading into Testflight

  7. Cleaning

After we have written our script, we need to select the option Build with Parameters from the pipeline menu we just created and specify the branch we want to build:

After we press the button Buildand the pipeline is successfully launched, we will see the following in the Jenkins Stage view:

which will mean that we have successfully uploaded our application to Testflight!

Failure at one of these stages will automatically mean that all work will end with an error, and we will see the corresponding stage highlighted in red. In this case, we can go to Console Output in the task menu, view the logs and determine the cause of the failure and, of course, rerun the task after fixing the problem.

Script

In our script, which we named MyScript.groovy, we will define a function called deploy(), within which we intend to implement the above stages as follows:

1. Checkout the repository

We checkout our repository using the command checkout an SCM plugin that will launch a project check using the configuration parameters we specified in the Jenkins Pipeline.

stage('Checkout') {
    checkout scm
}

2. Installing dependencies

stage('Install dependencies') {
      sh 'gem install bundler'
      sh 'bundle update'
      sh 'bundle exec pod repo update'
      sh 'bundle exec pod install'
}

Our project uses CocoaPods as its dependency manager, so we need to run pod installto load the project dependencies. To execute this command and all other shell commands, we use Bundler, which is a tool for managing the gem of a Ruby application. After completing the first 2 commands, we manage to install Bundler, and then Bundler loads all the gems specified in the project Gemfile. This is the stage at which we can specify a set of tools that our application may need, for example Fastlane, which we will use next, or if we need a linter or Danger, this is the place to define their ruby ​​gems. We carry on and run pod repo updateto always have the latest versions of the pod, and finally we run pod updateto load the project dependencies.

3. Reset simulators

stage('Reset Simulators') {
   sh 'bundle exec fastlane snapshot resetsimulators --force'
}

This step is optional, but necessary if we want to run unit tests in the next stage without worrying about the potentially stale state of the simulator that could cause unit testing to fail.

This is the first time Fastlane appears in the frame. As I mentioned earlier, Fastlane uses Lanes and already has a large number of prepared lanes, but we can create our own custom lanes inside a file called Fastfile. Here we are using a prepared lane that does exactly what we want.

The command above will delete and re-create all iOS simulators, which might be considered overwhelming, but sometimes “desperate times call for desperate measures.”

4. Running tests

stage('Run Tests') {
   sh 'bundle exec fastlane test'
}

At this point, we run unit tests for our project. Now test is a custom lane that we implemented inside our Fastfile, and has the following implementation:

lane :test do
    scan(
        clean: true,
        devices: ["iPhone X"],
        workspace: "ourproject.xcworkspace",
        scheme: "productionscheme",
        codecoverage: true,
        outputdirectory: "./testoutput",
        outputtypes: "html,junit"
    )
    slather(
        coberturaxml: true,
        proj: "ourproject.xcodeproj",
        workspace: "ourproject.xcworkspace",
        outputdirectory: "./testoutput",
        scheme: "productionscheme",
        jenkins: true,
        ignore: [arrayofdocstoignore]
    )
end

We use two main Fastlane actions here: scan and slather

Scan is used to actually run unit tests and can be configured with several parameters such as workspace (workspace), scheme (schema), codecoverage and, most importantly, devices (devices) where we can specify the simulator in which we want to run our unit tests.

Slather is used to collect code coverage when executing unit tests, and also takes several arguments, such as the target directory for the coverage report, and an array of documents to ignore when collecting code coverage (ignore).

This is where Fastlane magic happens ✨. By defining this lane with just two actions, we can easily run our unit tests inside Jenkins. Please note that all of them, obviously, must be successfully completed, otherwise this stage and the whole task will end in failure.


This concludes the first part of the translation, but in the coming days we will publish the continuation of the material.

And if you are interested in the course, we suggest signing up for free webinar by link

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *