• Build it, Test it, Deliver it! Complete iOS Guide on Continuous Delivery with fastlane and Jenkins
  • Original post by S.T.Huang
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: talisk
  • Proofreader: ALVINYEH, Rydensun

IOS /macOS is really fun. You can gain knowledge in many fields! You’ll probably know about graphics techniques like Bezier or 3D transformations. You also need to understand how to use databases and design efficient architectures. In addition, you should be familiar with the memory management of embedded systems (especially those in the MRC era). All of this makes iOS/macOS development so diverse and challenging.

In this article, we’re going to learn something else you might want to know: Continuous delivery (CD). Continuous delivery is a software approach that helps you deliver products reliably at any time. Continuous delivery (CD) often carries the term continuous integration (CI). CI is also a software engineering technique. This means that the system constantly merges the developer’s work into the mainline. CI and CD are useful not only for large teams, but also for single-person teams. If you’re the only developer on a one-man team, CDS may mean more to you, because every application developer can’t avoid delivery. Therefore, this article focuses on how to build a CD system for your application. Fortunately, all of these techniques can also be used to build CI systems.

Imagine we’re developing an iOS app called Brewer, and our workflow looks something like this:

First, we develop. Then the QA team helped us manually test the application. After QA approved the build test, we released our app (submitted to the AppStore for review). At different stages, we have different environments. During development, we created the application in a test environment, ready for normal testing. While the QA team is testing, we prepare a production application, which may be dedicated to QA testing on a weekly basis. Finally, we submit a production application. Thus, the final build may not have a clear timeline.

Let’s take a closer look at the deliverables. You may find that there is a lot of duplication in our efforts to build the test application. This is what the CD system can help you with. Specifically, our CD system requires:

  1. Build the application in different environments (test/production).
  2. Sign the application based on the environment we choose.
  3. Export the application and send it to a distribution platform, such as Crashlytics and TestFlight.
  4. Build the application according to a specific schedule.

The profile

Here’s what we’ll do in this article:

  • Setup your project: How to setup your project to support switching between different environments.
  • Manual code signing: How to handle certificates and configuration files manually.
  • Isolated Environment: How to use Bundler to isolate the system environment.
  • Build with FastLane: How to build and export applications using Fastlane.
  • Jenkins will be at your service tonight: How Jenkins can help you plan your assignments.

Before you start, you might want to look at these:

  • What is the fastlane
  • What is the Jenkins
  • What is Code signing

If you’re a busy guy or girl, don’t worry, I’ve got the Brewer app and some sample scripts for you in the public warehouse!

  • koromiko/Brewer: Brewer – We brew beer every night! github.com

So, let’s get started!

Set up your project

We usually connect to the development server or test server during developer testing. We also need to connect to the production server when we send the app to the QA team for testing, or to the AppStore. Switching servers by editing code is probably not a good idea. Here we use the build configuration and compiler flags in Xcode. We won’t go into the details of the configuration. If you are interested in this setup, check out this great post by Yuri Chukhlib:

  • Easily manage the environment in Swift projects medium.com

In our Brewer project, we have three build options:

  • Staging
  • Production
  • Release

Each maps to a specific Bundle identity:

We let the code know which environment we’re using by setting up flags.

So we can write it like this:

#if PROD
  print(" the brew beerinThe Production ").#elseif STG
  print(" the brew beerinThe Staging ").#endif
Copy the code

Now we can switch between test and production environments using build options without changing the code! 🎉

Manual code signing

This is the red button that every iOS/macOS developer knows well. We start each project by unchecking this box. But why is it so notorious? You probably know that it downloads certificates and configuration files and empowers them into your projects and systems. If any files are missing, it will make a new file for you. This should not be a problem for single-person project teams. But if you’re on a large team, you might accidentally refresh the original certificate and then cause the build system to stop working because the certificate is invalid. For us, it’s a black box that hides too much information.

So in our Brewer project, we wanted to do this manually, and we had three application ids in our configuration:

  • works.sth.brewer.staging
  • works.sth.brewer.production
  • works.sth.brewer

We will focus on the first two configurations in this article, and now we will prepare:

  • Certificate: an Ad Hoc, App Store distribution Certificate in. P12 format.
  • Provisioning Profiles: two application identification of Ad Hoc distributed configuration files, works. STH. Brewer. The staging and works. STH. Brewer. Production.

Note that we need the certificate file in P12 format because we want it to work on different machines, only.p12 format contains the private key of the certificate. See this article to learn how to convert.cer (DEM format) files to.p12 (P12 format) files.

Our certificate signature file is now in the directory:

These files are used by the CD system, so put the folder on the CD machine. Please do not place these files in your project and do not submit them to your project repository. Hosting code signing files in a different private repository is possible. If you want to follow up on security discussions, check out Match — Fastlane Docs.

Build 🚀 with Fastlane

Fastlane is a tool that automates development and publishing workflows. For example, it can build applications from a script, run unit tests, and upload binaries to Crashlytics. You don’t have to do this step by step manually.

In this project, we will use Fastlane to accomplish two tasks:

  • Build and release the application of the test environment.
  • Build and release production applications.

The difference between these two approaches is only in configuration. Common tasks include:

  • Sign with certificates and configuration files
  • Build and export the application
  • Upload your app to Crashlytics (or other distribution platform)

With the task defined, we can now start writing fastlane scripts. We will use fastlane Swift to write our scripts in our project. Fastlane for Swift is still in beta and runs fine, except for:

  • It doesn’t support plug-ins yet
  • It cannot catch exceptions

But scripting in Swift makes it easier for developers to read and maintain. And you can easily convert Swift scripts to Ruby scripts. So let’s try it!

First, initialize our project (remember Bundler?). :

bundler exec fastlane init swift
Copy the code

You can then find the script in Fastlane/fastfile.swift. In the script, there is a FastFile class. This is our main procedure. Each method with a Lane suffix in this class is a Lane. We can add predefined actions to lane and execute lane with commands:

bundle exec fastlane <lane name>.
Copy the code

Let’s fill in some code:

class Fastfile: LaneFile {
    func developerReleaseLane() {
        desc("Create a developer release")
	package(config: Staging())
	crashlytics
    }

    func qaReleaseLane() {
        desc("Create a qa release")
        package(config: Production())
        crashlytics
    }
}
Copy the code

We create two lanes for the task: developerRelease and qaRelease. Both tasks do the same thing: build the package with the specified configuration and upload the exported IPA to Crashlytics.

Both lanes have a package method. The declaration of the package() method looks like this:

func package(config: Configuration) {
}
Copy the code

Parameter is an object that complies with the Configuration protocol. Configuration is defined as follows:

protocol Configuration {
    /// file name of the certificate 
    var certificate: String { get } 

    /// file name of the provisioning profile
    var provisioningProfile: String { get } 

    /// configuration name in xcode project
    var buildConfiguration: String { get }

    /// the app id for this configuration
    var appIdentifier: String { get }

    /// export methods, such as "ad-doc" or "appstore"
    var exportMethod: String { get }
}
Copy the code

Then we create two structures that follow this protocol:

struct Staging: Configuration { 
    var certificate = "ios_distribution"
    var provisioningProfile = "Brewer_Staging"
    var buildConfiguration = "Staging"
    var appIdentifier = "works.sth.brewer.staging"
    var exportMethod = "ad-hoc"
}

struct Production: Configuration { 
    var certificate = "ios_distribution"
    var provisioningProfile = "Brewer_Production"
    var buildConfiguration = "Production"
    var appIdentifier = "works.sth.brewer.production"
    var exportMethod = "ad-hoc"
}
Copy the code

Using this protocol, we were able to ensure that each configuration had the required Settings. Whenever we have a new configuration, we do not need to write the details of the package.

So, how does package(config:) look? To say he loves you, he needs to import the certificate from the file system. Keeping in mind our code signing folder, we use the importCertificate Action to achieve our goal.

importCertificate(
    keychainName: environmentVariable(get: "KEYCHAIN_NAME"),
    keychainPassword: environmentVariable(get: "KEYCHAIN_PASSWORD"),
    certificatePath: "\(ProjectSetting.codeSigningPath)/\(config.certificate).p12",
    certificatePassword: ProjectSetting.certificatePassword
)
Copy the code

KeychainName is the name of your keystring. The default name is “Login”. KeychainPassword is the password to your keychain, which Fastlane uses to unlock your keychain. Since we commit Fastfile.swift to the repository to ensure that the delivery code is consistent across each machine, it is not a good idea to write the password as a string in Fastfile.swift. Therefore, we use environment variables to replace string literals. In the system, we store environment variables in this way:

exportKEYCHAIN_NAME KEYCHAIN_NAME = "";exportYOUR_PASSWORD KEYCHAIN_PASSWORD = "";Copy the code

In Fastfile, we use environmentVariable(get:) to get the value of the environmentVariable. By using environment variables, we can significantly improve security by avoiding passwords in our code.

Back to importCertificate(), certificatePath is the path to your.p12 certificate file. We create an enumeration called ProjectSetting to identify the shared project Settings. Here we also use environment variables to pass the password.

enum ProjectSetting {
    static let codeSigningPath = environmentVariable(get: "CODESIGNING_PATH")
    static let certificatePassword = environmentVariable(get: "CERTIFICATE_PASSWORD")}Copy the code

After importing the certificate, we will set up the configuration file. We use updateProjectProvisioning:

updateProjectProvisioning(
    xcodeproj: ProjectSetting.project,
    profile: "\(ProjectSetting.codeSigningPath)/\(config.provisioningProfile).mobileprovision",
    targetFilter: "^\(ProjectSetting.target)$",
    buildConfiguration: config.buildConfiguration
)
Copy the code

This operation gets the configuration file, imports the configuration file, and modifies your project Settings in the specified configuration. The configuration file parameter is the path to the configuration file. The target filter uses regular expression symbols to find the target we want to modify. Please note that updateProjectProvisioning not modify your project file, so if you want to run it on the local computer, please be careful. The CD task is irrelevant because the CD system does not make any changes to the code base.

Okay, we’re done with the code signing part! The following sections will be very simple, please be patient!

Let’s now build the application:

buildApp(
    workspace: ProjectSetting.workspace,
    scheme: ProjectSetting.scheme,
    clean: true,
    outputDirectory: ". /",
    outputName: "\(ProjectSetting.productName).ipa",
    configuration: config.buildConfiguration,
    silent: true.exportMethod: config.exportMethod,
    exportOptions: [
        "signingStyle": "manual"."provisioningProfiles": [config.appIdentifier: config.provisioningProfile] ],
    sdk: ProjectSetting.sdk
)
Copy the code

BuildApp helps you build and export projects. It calls XcodeBuild underneath. With the exception of exportOptions, every parameter is straightforward. Let’s see what it looks like:

exportOptions: [
    "signingStyle": "manual"."provisioningProfiles": [config.appIdentifier: config.provisioningProfile] ]
Copy the code

Unlike the other arguments, it is a dictionary. SigningStyle is how you want your code to be signed, and we put manual in there. ProvisioningProfiles is also a dictionary. This is the mapping between the application ID and the corresponding configuration file. Finally, we’re done with the Fastlane setup! Now you can execute directly:

bundle exec fastlane qaRelease
Copy the code

Or this:

bundle exec fastlane developerRelease
Copy the code

To release test builds with the proper configuration!

Jenkins will be at your service tonight

Jenkins is an automated server that helps you perform CI and CD tasks. It runs a Web GUI interface and is easy to customize, making it a good choice for agile teams. Jenkins’ rules for our project are shown below:

Jenkins gets the latest code for the project and runs tasks for you on a regular basis. In the execution section of the script, we can see that Jenkins actually performs the tasks we did in the previous sections. But now we don’t have to do it ourselves, Jenkins has done it seamlessly for you!

Starting with the nightly build job, let’s start creating a Jenkins task. First, we create a “custom project” and go to its “Configuration” page. The first thing we need to configure is the source control (SCM) section:

The Repository URL is the address of the project source code. If your repository is private, you need to add the Credentials to gain access to the repository. You can set target Branches in Branches to build, usually this is your default branch.

Then, next we can see the Builder Trigger section. In this section, we can determine what triggers the build job. Based on our workflow, we want it to start every weekend night.

We then check the Poll SCM, which means Jenkins regularly polls the designated warehouse. The following should be written in the calendar text area:

H 0 * * 0–4
Copy the code

What does that mean? Let’s start with the official note:

This field follows cron’s syntax (with subtle differences). Specifically, each line contains five fields separated by TAB or space: MINUTE HOUR DOM MONTH DOW MINUTE Number of minutes in an HOUR (0-59) HOUR HOUR of a day (0-23) DOM Day in a MONTH (1-31) MONTH (1-12) DOW Day in a week (0-7) Where 0 and 7 are Sunday.

It consists of five parts

  • minutes
  • hours
  • The date of
  • in
  • weeks

The field can be a number. We can also use * for all numbers. We use “H” to represent a hash, automatically selecting “some” number.

So we would write:

H 0 * * 0–4
Copy the code

The mission will take place Sunday through Thursday, midnight to one o ‘clock every night.

Last, but not least, check out the Build section. Here’s what we want Jenkins to implement:

export LC_ALL=en_US.UTF-8;
export LANG=en_US.UTF-8;

exportCODESIGNING_PATH = "/ path/to/cert";exportCERTIFICATE_PASSWORD = "XXX";exportKEYCHAIN_NAME = XXXXXXXX ";"exportKEYCHAIN_PASSWORD= "XXXXXXXXXXXXXX" bundle install - Path Vendor /bundler bundleexec fastlane developerRelease
Copy the code

The first six lines set the environment variables we described earlier. Line 7 installs dependencies, including Fastlane. Then the last line executes a lane named “developerRelease.” In summary, this task will create and upload a developerRelease every weeknight. This is our first nightly build! 🚀

You can check the build status by clicking on the build number in the menu on the side of the Jenkins project page:

review

Together we learned how to create CD systems with Fastlane and Jenkins. We learned how to manage code signatures manually. We automatically created a run task for us. We also looked at how to switch configurations without changing the code. Finally, we set up a CD system to build the application every night.

Although many iOS and macOS applications are created by one-man teams, automating the delivery process is still an efficient improvement. By automating the process, we can reduce the risk of configuration errors, avoid blocking with expired code signatures, and reduce wait times for build uploads.

The workflow described in this article may not be exactly the same as yours, but it is important to master each team’s own workflow and pace. So you must create your own CD system to meet the needs of your team. By using these techniques as building blocks, you can build your own custom, better CD system!


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.