iOS Continuous Deployment with Fastlane and Ansible — Part 1

Shashikant Jagtap
XCBlog
Published in
7 min readJun 23, 2017

Note : This post has been published on official Moonpig Engineering blog. Read here

At my previous role at Photobox Group, I have enjoyed working on DevOps practices for iOS apps by involving in setting up Continuous Integration servers for iOS deployments and provisioning them with Ansible. We used self-hosted CI solution using mac Mini servers with TeamCity. In this two part post, we will see how we achieved Continuous Delivery of iOS apps using combination of Fastlane and Ansible.

Continuous Delivery (CD) and DevOps practices accelerates the delivery of new features to the end users. The fast-paced world of iOS app development may benefit from the DevOps practices to release an app quickly and easily to the production.

In the iOS world, releasing is difficult. It involves complicated steps like code signing and dealing with Apple development and distribution certificates. It can be an error-prone and time consuming process. A manual release is done by building, testing, and archiving an iOS app from Xcode locally. The archived .ipa file then needs to be uploaded to iTunes Connect. Ideally we would want to automate this process using Continuous Integration (CI) which pushes every commit or merge to the main branch to Apple’s beta testing service TestFlight without any manual intervention. In this post, we’ll explain how we achieved Continuous Deployment using the build automation tool Fastlane.

Fastlane offers the most common business benefits of CD, but also offers benefits to other engineering teams including:

  • Eliminate DIY for DevOps and focus on building native iOS features.
  • New build is uploaded automatically to iTunes Connect after every merge.
  • Automates repetitive tasks of upgrade or setting up new CI server machine.
  • Manage all the Infrastructure from source code.
  • Developer computers and CI server have the same configuration.

Challenges

Previously, releasing our app was a mission; deployment was a day long marathon. We followed traditional, time consuming, manual releases from local machines. It was time consuming to build, test, archive and upload an iOS app from Xcode. The most painful part of release involved downloading correct provisioning profiles associated with distribution certificates. It was even more painful to repeat the process if we found an issue in the build uploaded to iTunes Connect or in production.

In order to streamline our release process, we had to overcome the following challenges:

  • Automate the process of analysing, building, testing, archiving and uploading the iOS app to iTunes Connect
  • Setup a Continuous Integration Server to use automated builds
  • Automate the process of resetting the CI server setup whenever needed (for example, when the Xcode version changes, or when any of the Apple APIs change)
  • Drive the iOS CI infrastructure from code a.k.a Infrastructure as Code
  • Make sure the software configuration on an iOS developer machine and CI server are the same. Say No to It Works on My Machine

We solved these problems using a combination of Fastlane, TeamCity and Ansible tools. The code snippets are for the reference purpose only so Let’s start..

Build Automation with Fastlane

Apple command line developer tools like xcodebuild are a powerful way to script anything we want to automate. However, commands we were writing could be very lengthy and cumbersome. Fastlane is a wrapper around Apple command line tools to make the build automation easier. There is a collection of Fastlane tools available to automate various iOS development tasks, e.g Scan is used for running tests, Gym is used for building an app, Pilot is used to upload an app to TestFlight.

Automating Swift Version Checking

The first step in our automated build process is to make sure that we start the CI build in a clean state using correct software versions e.g. Swift and Ruby. Fastlane provides a before_all step which runs before all other lanes. You can read more about advance Fastlane to configure this step. We wrote custom Fastlane actions to check the version of Swift check_swift_version and to check Swift Toolchain check_swift_toolchain. It’s pretty straightforward to write a custom Fastlane action. Our before_all step looked like this:

before_all do |lane,options|
ensure_git_status_clean
fastlane_version "2.20.0"
check_swift_version(version: "Apple Swift version 3.1")
check_swift_toolchain
clear_derived_data
end

This ensures that every build starts with a clean state and a correct version of tools; there are no leftovers from the previous build.

Automating XCTest

We used the XCTest Framework by Apple to write unit and UI tests. Fastlane made it easy to configure the XCTest by wrapping all the options from xcodebuild in Fastlane’s Scan tool. Xcode has new features like build-for-testing and test-without-building which means we build once and test multiple times using the .xctestrun file. We have a Scanfile where all the common configurations like scheme, workspace and build configuration are stored. Our example lane looks like this:

lane :build_app_for_testing do
scan(
build_for_testing: true,
)
end
desc "Run Unit Tests without building and generate code coverage"
lane :run_test_without_building do
scan(
test_without_building: true,
)
xcov(
scheme: "our_test_scheme",
derived_data_path: "./build",
output_directory: "./build/reports/coverage/",
skip_slack: true
)
end

Automating Fabric Deployments

We use Fabric for testing debug or adhoc builds internally within the team. We don’t always need to test using Fabric or Crashlytics so we only make a build when needed. The build will be pushed to Crashlytics when a developer puts #fabric in the commit message. There is a fastlane action to push builds to Crashlytics, we just need to provide our API_TOKEN and GROUP. Our lane looks like this:

lane :distribute_to_crashlytics do
commit_message = last_git_commit[:message]
if commit_message.include? "#fabric"
increment_build_number(build_number: build_number) if is_ci?
gym(
export_method: "ad-hoc",
configuration: "AdHoc",
)
crashlytics(groups: "photobox-pr-reviewers", notes: commit_message)
else
puts "=== Skipping the Crashlytics build for now. ====="
end
end

Automating TestFlight Deployments

Apple’s TestFlight is great way of beta testing an iOS application. We use TestFlight to test release candidates to be shipped to the App Store. The process of automating the TestFlight build involves various things:

  • Ensuring Xcode Automated Signing is disabled
  • Getting the current version and build number from the Info.plist file
  • Incrementing the build number for the specific version
  • Uploading the build to TestFlight with the correct scheme, provisioning profiles and certificates
  • Committing back the build version bump to the main source repository
  • Creating and Push Tagging on Github
  • Creating Github Upload assets (.ipa) to Github Release
  • Sending a Slack notification that a new build is uploaded to TestFlight with release notes

It’s a lot to do! Fortunately, Fastlane provides actions or plugins for each of these tasks. We get the current version of the app by using the get_version_number_from_plist plugin and current build number by using the Fastlane action latest_testflight_build_number. We use the increment_build_number action to increment the build number for the version and build our app with a ‘release’ configuration. We then upload it to TestFlight using Pilot. One thing to note is that the increment_build_number action changes the Info.plist file which we need to commit back to the source code for future build creation. In order to commit this file, we use the commit_version_bump action. Gym can now be used to build the app. Once the build is successfully uploaded we generate a Slack message to the team.

Our sample TestFlight lane looks like this :

lane :distribute_to_testflight do |options|
scheme = "our_app"
version = get_version_number_from_plist(xcodeproj: "our_project.xcodeproj", target: 'our_target', build_configuration_name: 'Release')
current_build_number = latest_testflight_build_number(version: version)
increment_build_number(
build_number: current_build_number + 1
)
testflight_build = current_build_number + 1
gym(
scheme: scheme,
export_method: "app-store",
configuration: "Release",
)

pilot(
skip_submission: false,
distribute_external: false,
skip_waiting_for_build_processing: true
)
slack_message(message: ":airplane: The new build is uploaded to testflight: version #{version} & Build number : #{testflight_build}", success: true, payload: {"Build Date" => Time.new.to_s,
"Built by" => " iOS CI Server",})
add_git_tag(
tag: "#{version}-#{testflight_build}"
)
push_to_git_remote(
remote: "origin",
local_branch: "develop",
remote_branch: "develop",
tags: true
)
end

Automating Releases on Github

As part of our code review process, an engineer has to create a feature branch on GitHub and create a pull request against the ‘develop’ branch (main branch) once the feature is built. The pull request has to go though our SwiftLint rules and code review process. Once the pull request is merged to the main branch it will trigger our automated tests. If all the tests pass the build will be uploaded to TestFlight with the incremented build number.

This means that every merge to the main branch goes to TestFlight, and any build can then be promoted for release. Once we select a release candidate build, assets such as the .ipa or .dSYM are uploaded to Github, storing artifacts from previous releases and change logs for reference. We use the Fastlane action set_github_releases to automate this process.

With this process we have almost achieved a fully automated CD process for iOS apps.

Next Challenge

Continuous Delivery isn’t possible without Continuous Integration. All the above build automation scripts are automatically triggered from our TeamCity CI server.

In the next post, we will discuss what challenges we faced while maintaining the CI servers, and how we used Ansible to solve configuration management issues and speedy setup of iOS CI Server. Continue reading part 2

Like this post from XCBlog By XCTEQ ? You may also like some of our services like guest blogging or Mobile DevOps(CI/CD) or Test Automation. Chekout our services, open source projects on Github or Follow us on Twitter , Facebook, Youtube , LinkedIn. Download Our XCBlog iOS App to read the blogs offline.

XCTEQ Limited: Mobile DevOps, CI/CD and Automation

XCTEQ is a company specialised in Mobile DevOps, CI/CD, Mobile, AI/ML based Test Automation Checkout XCTEQ products and services at http://www.xcteq.co.uk or write to us on info@xcteq.co.uk..

--

--

Shashikant Jagtap
XCBlog

All the posts published on this channel before I joined Apple. Thanks for being a reader of XCBlog. Web: shashikantjatap.net, xcteq.co.uk