Mobile CICD experience: one fastlane standard for many mobile applications

Mobile CICD experience: one fastlane standard for many mobile applications
I'd like to talk about continuous integration and mobile app delivery with fastlane. How do we implement CI/CD on all mobile applications, how did we go about it and what happened as a result.

The network already has enough material on the tool, which we lacked so much at the start, so I will deliberately not describe the tool in detail, but will only refer to what we had then:

The article consists of two parts:

  • The background of the emergence of mobile CI / CD in the company
  • Technical solution for rolling out CI/CD to N-applications

The first part is more nostalgia for the old days, and the second is an experience that you can apply at home.

That's how it happened historically

Year 2015

We just started developing mobile applications, then we still didn’t know anything about continuous integration, about DevOps and other trendy things. Each update of the application was rolled out by the developer himself from his machine. And if for Android it's easy enough - collected, signed .apk and threw it into the Google Developer Console, then for iOS, the then distribution tool through Xcode left us gorgeous evenings - attempts to download the archive often ended in errors and had to be tried again. It turned out that the most pumped developer several times a month does not write code, but is engaged in the release of the application.

Year 2016

We grew up, there were already thoughts on how to free developers from a whole day for the release, and a second application appeared, which only pushed us more towards automation. In the same year, we first installed Jenkins and wrote a bunch of ugly scripts, very similar to those that fastlane shows in their documentation.

$ xcodebuild clean archive -archivePath build/MyApp 
    -scheme MyApp

$ xcodebuild -exportArchive 
                        -exportFormat ipa 
                        -archivePath "build/MyApp.xcarchive" 
                        -exportPath "build/MyApp.ipa" 
                        -exportProvisioningProfile "ProvisioningProfileName"

$ cd /Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework/Versions/A/Support/

$ ./altool —upload-app 
-f {abs path to your project}/build/{release scheme}.ipa  
-u "[email protected]" 
-p "PASS_APPLE_ID"

Unfortunately, only our developers knew about how these scripts work and why this endless bundle of keys was needed, and when something broke again, they got “splendid evenings” for parsing the logs.

Year 2017

This year we learned that there is such a thing as fastlane. There was not as much information as it is now - how to start, how to use. Yes, and the tool itself was still damp then: constant errors only disappointed us, and it was hard to believe in the magical automation that they promised.

However, the main utilities included in the fastlane core are − gym и pilot, we managed to start.

Our scripts have improved a bit.

$ fastlane gym  —-workspace "Example.xcworkspace" 
                --scheme "AppName" 
                —-buildlog_path "/tmp" 
                -—clean

Ennobled, if only because not all the parameters necessary for xcodebuild, you need to specify - gym will independently understand where and what lies. And for finer tuning, you can specify the same keys as in xcodebuild, only the naming of the keys is clearer.

This time, thanks to gym and the built-in xcpretty formatter, the build logs are much more legible. This began to save time on fixing broken builds, and sometimes the release team could figure it out on their own.

Unfortunately, measurements on assembly speed xcodebuild и gym we did not, but we will believe the documentation - up to 30% acceleration.

One process for all applications

Year 2018 and present

By 2018, the process of building and rolling out applications completely moved to Jenkins, the developers stopped releasing from their machines, only the release team had the right to release.

We already wanted to tighten up the launch of tests and static analysis, and our scripts grew and grew. Grow and change along with our applications. At that time, there were about 10 applications. Given that we have two platforms, this is about 20 “living” scripts.

Every time we wanted to add a new step to a script, we had to copy-paste the pieces into all shell scripts. Perhaps it was possible to work more carefully, but often such changes ended in typos, which already turned into evenings of release teams to fix scripts and find out which of the smart people added this command and what it actually does. In general, it cannot be said that the scripts for building for one platform were at least somewhat similar. Although they certainly did the same thing.

In order to start a process for a new application, it was necessary to spend a day to pick up a “fresh” version of these scripts, debug it and say that “yes, it works”.

In the summer of 2018, we once again looked towards the still developing fastlane.

Task #1: Summarize all script steps and rewrite them in Fastfile

When we started, our scripts looked like a footcloth of all the steps and crutches in one shell script in Jenkins. We have not switched to pipeline and division by stage yet.

We looked at what we have and identified 4 steps that fit the description of our CI / CD:

  • build - install dependencies, build archive,
  • test - launching developer unit tests, calculating coverage,
  • sonar - launch all linters and send reports to SonarQube,
  • deploy - sending an artifact to alpha (TestFlight).

And if you don’t go into details, omit the keys used for actions, you get this Fastfile:

default_platform(:ios)

platform :ios do
  before_all do
    unlock
  end

  desc "Build stage"
  lane :build do
    match
    prepare_build
    gym
  end

  desc "Prepare build stage: carthage and cocoapods"
  lane :prepare_build do
    pathCartfile = ""
    Dir.chdir("..") do
      pathCartfile = File.join(Dir.pwd, "/Cartfile")
    end
    if File.exist?(pathCartfile)
      carthage
    end
    pathPodfile = ""
    Dir.chdir("..") do
      pathPodfile = File.join(Dir.pwd, "/Podfile")
    end
    if File.exist?(pathPodfile)
      cocoapods
    end
  end

  desc "Test stage"
  lane :test do
    scan
    xcov
  end

  desc "Sonar stage (after run test!)"
  lane :run_sonar do
    slather
    lizard
    swiftlint
    sonar
  end

  desc "Deploy to testflight stage"
  lane :deploy do
    pilot
  end

  desc "Unlock keychain"
  private_lane :unlock do
    pass = ENV['KEYCHAIN_PASSWORD']
    unlock_keychain(
      password: pass
    )
  end
end

In fact, our first Fastfile turned out to be monstrous, considering some crutches that we still needed, and the number of parameters that we substituted:

lane :build do
carthage(
  command: "update",
  use_binaries: false,
  platform: "ios",
  cache_builds: true)
cocoapods(
  clean: true,
    podfile: "./Podfile",
    use_bundle_exec: false)

gym(
  workspace: "MyApp.xcworkspace",
  configuration: "Release",
  scheme: "MyApp",
  clean: true,
  output_directory: "/build",
  output_name: "my-app.ipa")
end 

lane :deploy do
 pilot(
  username: "[email protected]",
  app_identifier: "com.example.app",
  dev_portal_team_id: "TEAM_ID_NUMBER_DEV",
  team_id: "ITS_TEAM_ID")
end

In the example above, only part of the parameters that we need to specify: these are the build parameters - scheme, configuration, Provision Profile names, as well as distribution parameters - Apple ID of the developer account, password, application ID, and so on. As a first approximation, we put all these keys in special files - Gymfile, Matchfile и Appfile.

Now in Jenkins you can call short commands that do not “blur” your eyes and are well read by the eye:

# fastlane ios <lane_name>

$ fastlane ios build
$ fastlane ios test
$ fastlane ios run_sonar
$ fastlane ios deploy

Wow, we are great

What did you get? Clear commands for each step. Well-groomed scripts neatly organized into fastlane files. Rejoiced, we ran to the developers with a request to add everything we needed to our repositories.

But we realized in time that we would face the same difficulties - we would still have 20 build scripts that would somehow begin to live their lives, it would be more difficult to edit them, since the scripts would move to the repositories, and we don’t have access there. And, in general, to solve our pain in this way will not work.

Mobile CICD experience: one fastlane standard for many mobile applications

Task #2: get a single Fastfile for N-applications

Now it seems that solving the problem is not so difficult - set the variables, and let's go. Yes, that is how the problem was solved. But at the moment when we screwed it up, we had neither expertise in fastlane itself, nor in Ruby, in which fastlane is written, nor useful examples on the network - everyone who wrote about fastlane then was limited to an example for one application for one developer.

Fastlane knows about environment variables, and we have already tried this by setting the password from the Keychain:

ENV['KEYCHAIN_PASSWORD']

Looking at our scripts, we've highlighted the common parts:

#for build, test and deploy
APPLICATION_SCHEME_NAME=appScheme
APPLICATION_PROJECT_NAME=app.xcodeproj
APPLICATION_WORKSPACE_NAME=app.xcworkspace
APPLICATION_NAME=appName

OUTPUT_IPA_NAME=appName.ipa

#app info
APP_BUNDLE_IDENTIFIER=com.example.appName
[email protected]
TEAM_ID=ABCD1234
FASTLANE_ITC_TEAM_ID=123456789

Now, in order to start using these keys in fastlane's files, it was necessary to figure out how to deliver them there. Fastlane has a solution for this: loading variables via dotenv. The documentation says that if it is important for you to load keys for different purposes, spawn several configuration files in the fastlane directory .env, .env.default, .env.development.

And then we decided to use this library a little differently. Let's put in the developers' repository not the fastlane scripts and its meta information, but the unique keys of this application in the file .env.appName.

Sami Fastfile, Appfile, Matchfile и Gymfile, we hid in a separate repository. An additional file with password keys from other services was also hidden there - .env.
An example can be seen here.

Mobile CICD experience: one fastlane standard for many mobile applications

On CI, the call has not changed much, a configuration key for a specific application has been added:

# fastlane ios <lane_name> --env appName

$ fastlane ios build --env appName
$ fastlane ios test --env appName
$ fastlane ios run_sonar --env appName
$ fastlane ios deploy --env appName

Before running the commands, we load our script repository. Doesn't look so pretty:

git clone [email protected]/FastlaneCICD.git fastlane_temp

cp ./fastlane_temp/fastlane/* ./fastlane/
cp ./fastlane_temp/fastlane/.env fastlane/.env

Left this solution for now although Fastlane has a solution to download Fastfile via action import_from_git, but it only works for Fastfile, it doesn't work for other files. If you want “just really beautiful”, you can write your own action.

A similar set was made for Android applications and ReactNative, the files are in the same repository, but in different branches iOS, android и react_native.

When the release team wants to add some new step, the changes in the script are fixed via MR in git, there is no more need to look for the culprits of broken scripts, and in general - to break it now, you have to try.

Now it's all right

We used to spend time maintaining all the scripts, updating them and fixing all the consequences of updates. It was very frustrating when the causes of errors and release downtime were simple typos that are so hard to keep track of in a hodgepodge of shell script. Now such errors are reduced to a minimum. Changes are rolled immediately to all applications. And it takes 15 minutes to start a new application into the process - set up a template pipeline on CI and add keys to the developer's repository.

It seems that the item with Fastfile for Android and the signature of applications remained unlit, if the article is interesting, I will write a sequel. I will be glad to your questions or suggestions “how would you solve this problem” in the comments or in Telegram bashkirova.

Source: habr.com

Add a comment