DevOpsiOS Development

Fastlane Integration for Continuous Delivery in an iOS Project

By November 15, 2016 No Comments

This is the 2nd part of our series of “Continuous Integration and Delivery for iOS using Jenkins and Fastlane”. Check below links for the 1st and 3rd Parts

Fastlane is a suite of tools that simplifies the process of deploying an app by automating tasks like generating screenshots, code signing, and releasing the apps to app stores. Custom build tasks called ‘lanes’ can be created using fastlane, which work in a sequence to perform tasks like running unit tests, static analysis, code coverage, building the .ipa or .apk files etc. Fastlane allows developers to deploy an app to the stores with the single push of a button, easing  the process of releasing frequent smaller updates.

Fastlane, when combined with the pipeline job configured in Jenkins using Groovy Script, makes the development and deployment process smooth.

Fastlane Installation on Mac machine:

We can install the Fastlane with a simple command. Open the terminal and type in


sudo gem install fastlane --verbose

It’s going to take some time, as it needs to download lots of files.
Note: Fastlane requires Ruby 2.0.0 or higher version. Make sure it is already installed on the system.

Steps to initialize Fastlane in the iOS project:

1. First, we need to setup a new ‘private’ Git repository, which holds our certificates and provisioning profiles for development and distribution of the application. Team members should be given exclusive access to this repository.

Fastlane 1

2. Once we have our private Git repository setup, we need to execute the below command in the terminal. This will check for any existence of public key within ~/.ssh/ folder

cat ~/.ssh/id_rsa.pub

Skip the below command if public key is already available. If there is no public key available, create a public and private key pair for accessing certificates through SSH. Use the below command in terminal to create the pair

ssh-keygen -t rsa -C "GitLabEmailId@company.com”

(replace the email id with your Git email id. If it asks for any file name, hit enter multiple times.)

3. Skip this step if you already have a public key.Execute the below command and copy the entire public key (including your mail id)

cat ~/.ssh/id_rsa.pub

4. Login to your GitLab account and go to your profile settings. Here, you will have a space to store your SSH keys. Add the public key copied from the terminal and give the SSH key a name and click on ‘Add Key’.

Fastlane 4

5. Come back to your iOS Project and open it in your Xcode. We need to make some changes in project ‘schemes’, so that the entire team share  single scheme instead of each member having their own set of schemes on their developer machines.

Click on the main scheme present on top left and then click on ‘New Scheme’

Fastlane 5

 

 

We will also create separate schemes for Unit Tests and UI Tests targets that can run independently on a CI Server or a development machine. In new scheme window, select your Unit Test target from ‘Target’ drop down and create a new scheme with any name.

Fastlane 5-2

Fastlane 5-3

Click on ‘OK’ to create the new scheme. Follow the same procedure to create another scheme for UI Tests target.

We now have following three schemes ready to be shared with the team:

  1. Main scheme for building and deploying apps
  2. Unit Test scheme for running Unit tests target
  3. UI Test scheme for running the UI tests target

Fastlane 5-4

6. Let’s share these schemes with our development team.

Click on ‘Manage Schemes…’, refer to the  above figure and make sure all the schemes you have created are selected in the ‘Shared’ column.

Fastlane 6

7. Now, we need to initialize fastlane within our project. Open terminal and change directory to your project folder using ‘cd’ command, for example:

Fastlane 7

8. Once you are in the project folder, type the below command to initialize Fastlane.

fastlane init

During the setup process, it may ask for following

  • Apple ID
  • If there are multiple teams, select the team to which the app belongs.
  • Would you like to create your app on iTunes Connect and the Developer Portal? (y/n): Select ‘y’ and follow the instructions to create App ID’s in both Developer portal and iTunes connect.
  • The scheme name of your app.

9. Now, a ‘fastlane’ folder gets created in your project directory where you will see certain files like below

In your project directory, you can find the ‘fastlane’ folder as shown here –

Fastlane 9

Creating App IDs in Developer Portal and iTunes Connect

Skip this step if you created App IDs during Fastlane initialization.

10. Making sure you are still in the project folder, type in the below command in the terminal.

produce --app_name YourAppName

11. Initialize ‘match’ command to create or get certificates and provisioning profiles from developer portal.

match init

It will ask you to enter the private Git repository, which we created earlier in the first step .

If it asks, create a passphrase for the Git repository. This is to ensure everything is encrypted in Git repository. The passphrase should be shared with the development team.

Alternatively, you can even set the environment variable in ‘Fastfile’, so that the development team do not have to enter it manually time and again.

Fastlane 11

Environment variable that can be used in Fastfile to set passphrase:

ENV[“MATCH_PASSWORD”] = “YourPassPhrase”

12. You will now have one more file added to the ‘fastlane’ folder called ‘Matchfile’.

Open ‘MatchFile’ in text editor and remove everything except the ‘git_url’ and ‘app_identifier’ lines. hange the app identifier to the one that your project has. Save the file.

Fastlane 12

13. Run the below commands based on your requirements (one command at a time and not all commands are mandatory it depends on the requirement)

Based on your requirement, run the below commands one by one. Remember, not all the commands are mandatory.

To create development provisioning profile and development certificate:

match development

To create adhoc provisioning profile and distribution certificate:

match adhoc

To create Appstore provisioning profile and distribution certificate:

match appstore

14. After executing above commands in the terminal, you would see something like this

For Development configuration:

Fastlane 14-1

For App store configuration:

Fastlane 14-2

We now have the environment variables. These environment variable will be updated whenever the match command runs. We can use these environment variables within our project without worrying about the provisioning profiles.

Open your project in Xcode and go to ‘Build Settings’. In the ‘Code Signing’ section, click on the arrow beside ‘Provisioning Profiles’ and enter respective Environment variables in the ‘Debug’ and ‘Release’ configurations.

Fastlane 14-3

Environment variables should be in the following form: $(sigh_com.enlume.PasswordManager_development)

Installation and configuration of Fastlane is complete. We are now ready to write custom lanes.

Writing custom lanes in Fastlane to do tasks in Fastfile

As discussed earlier, we can write different lanes for unit testing, UI testing, testflight deployment or app store deployment etc. Once you are ready with the lanes in Fastfile, you can push the entire code to your project Git repository.

Fastlane tutorials references (for writing lanes and different action commands in fastlane)
https://github.com/fastlane/fastlane
https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Actions.md
https://realm.io/news/felix-krause-fastlane-automating-ios-tasks/
http://lyxit.se/blog/ios-developers-toolbox-fastlane/
https://labs.kunstmaan.be/blog/ios-continuous-delivery-with-jenkins-and-fastlane

Sample Fastfile with lanes:


# If you want to automatically update fastlane when a new version is available:
# update_fastlane
# This is the minimum version number required.
# Update this, if you use features of a newer version
fastlane_version "1.95.0"
default_platform :ios
platform :ios do
before_all do
if is_ci?
setup_jenkins(result_bundle: false)
ENV["KEYCHAIN_NAME"] = "Jenkins.keychain"
ENV["KEYCHAIN_PASSWORD"] = "ENLUME123456"
ENV["MATCH_KEYCHAIN_NAME"] = "Jenkins.keychain"
end
ENV["MATCH_PASSWORD"] = "ENLUME123456" # Passphrase for private certificates git repo to encrypt and decrypt
ENV["FASTLANE_XCODE_LIST_TIMEOUT"] = "60" # 60 seconds Time out for Xcodebuild list command execution
end
############## Private Lanes ################
desc "Generates/Refresh Certificates and Provisioning Profiles (readonly mode)"
private_lane :matchsync do
match(type:"development",app_identifier:"com.enlume.PasswordManager",readonly:true,force_for_new_devices:true)
match(type:"appstore",app_identifier:"com.enlume.PasswordManager",readonly:true,force_for_new_devices:true)
end
############## Public Lanes ################

desc "Runs Unit Tests"
lane :unittest do
# Scan command is used for running Test Cases/ Test Targets of the Project
scan(
workspace:"PasswordManager.xcworkspace",
configuration:"Debug",
scheme:"PasswordManagerTests",
output_types: "junit,json-compilation-database",
code_coverage:true,
derived_data_path: "./derivedData",
output_directory: "./output",
skip_build: true,
use_clang_report_name:true
)
end

desc "Runs UI Tests"
lane :uitest do
# Scan command is used for running Test Cases/ Test Targets of the Project
scan(
workspace:"PasswordManager.xcworkspace",
configuration:"Debug",
scheme:"PasswordManagerUITests",
output_types: "junit",
code_coverage:true,
derived_data_path: "./derivedData",
output_directory: "./output",
skip_build: true
)
end

desc "Generates Code Coverage Report"
lane:coverage do
slather(
workspace:"PasswordManager.xcworkspace",
scheme:"PasswordManagerTests",
proj: "PasswordManager.xcodeproj",
#cobertura_xml:true,
html:true,
jenkins:true,
build_directory: "./derivedData",
binary_basename: "PasswordManager",
output_directory: "./output/code_coverage",
ignore: ["../*","Pods/*","*/View/*","*/Custom Views/*","*/App Delegate/*"] # These eliminates header files, Pods dependency classes, View Controller classes, Custom Views classes. So that only ViewModel classes can be tested for coverage
)
end

desc "OCLint Static Code Analyzer"
lane :analyze do
sh 'ulimit -n 6666' # make sure we can open enough number of files at a time (oclint is throwing errors in some cases, so it is mandatory)
oclint(
compile_commands: './output/compile_commands.json', # The JSON compilation database, use xctool reporter 'json-compilation-database'
select_regex: /.*/, # Select all files matching this regex
exclude_regex: /Pods/, # Exclude all files matching this regex
report_type: 'pmd', # The type of the report (default: html)
max_priority_1: 10, # The max allowed number of priority 1 violations
max_priority_2: 100, # The max allowed number of priority 2 violations
max_priority_3: 1000, # The max allowed number of priority 3 violations
thresholds: [ # Override the default behavior of rules
'LONG_LINE=200',
'LONG_METHOD=200'
],
list_enabled_rules: true, # List enabled rules
enable_clang_static_analyzer: false, # Enable Clang Static Analyzer, and integrate results into OCLint report
enable_global_analysis: false, # Compile every source, and analyze across global contexts (depends on the number of source files, could result in high memory load)
allow_duplicated_violations: true # Allow duplicated violations in the OCLint report
)
end

desc "Generate .ipa nd xcarchive files for release"
desc "This will also make sure the profile is up to date"
lane :release_package do
matchsync
gym(
workspace:"PasswordManager.xcworkspace",
clean: true,
configuration: "Release",
scheme:"PasswordManager",
output_directory:"./output",
output_name:"PasswordManager.ipa",
archive_path: "./output/PasswordManager",
#derived_data_path: "./derivedData",
use_legacy_build_api:false,
include_bitcode:false,
include_symbols:false,
export_method: "app-store"
)
end

desc "Generate .ipa and xcarchive files for debug"
desc "This will also make sure the profile is up to date"
lane :debug_package do
matchsync
gym(
workspace:"PasswordManager.xcworkspace",
clean: true,
configuration: "Debug",
scheme:"PasswordManager",
output_directory:"./output",
output_name:"PasswordManager.ipa",
archive_path: "./output/PasswordManager",
derived_data_path: "./derivedData",
use_legacy_build_api:false,
include_bitcode:false,
include_symbols:false,
export_method: "development"
)
end

desc "Upload to testflight"
lane :upload_to_itc do
pilot(
ipa: "./output/PasswordManager.ipa",
skip_submission: false,
skip_waiting_for_build_processing: true,
testers_file_path: "./fastlane/testers.csv"
)
end

desc "Appium Functional Tests"
lane :appium do
appium(
app_path: "./output/PasswordManager.ipa",
spec_path: "./spec/test.rb",
invoke_appium_server: true,
platform: "iOS",
caps: {
platformVersion: "9.3",
deviceName: "iPhone 5s",
launchTimeout: 600000,
udid: "f67376487648809b914d50ddb128400fbe654a"
}
)
end
end

Note:

  • While running any of the above commands, if you are prompted for the Developer account / Apple ID password, please enter them. It is a one-time process for every new machine and it saves the password in keychain.
  • Every team member should follow steps 2,3, & 4 in order to authenticate themselves with their Git account and to access projects from their Git account (same applies for CI server as well).
  • During execution of any of the ‘match’ commands initially, if it prompts you to create a passphrase for the private Git repository (certificates and provisioning profile repository), create it and include environment variable for the passphrase in the ‘Fastfile’. See below screenshot to know where to add MATCH_PASSWORD environment variable

Fastlane screen

Related Post

Related Post

Leave a Reply