CI/CD for Mobile Apps


Build and deployment automation can be an intimidating topic, but it's also an important skill to have as a developer. My team recently started a new project/repo, so we’ve been busy setting up our deployment pipeline. Since my team doesn’t have the administrative privileges to configure our build jobs in the TeamCity web UI, we’ve been configuring our build jobs using DSL (domain-specific language) scripts written in Kotlin. The scripts are written in Kotlin because Kotlin is developed by the company that also developed TeamCity. A lot of this was new to me, so I wanted to write a post consolidating key concepts.
Build steps
Build steps form the crux of the whole CI/CD process. We want to automate the workflow for deploying our app to the App Store as much as possible. To do that, we must automate the steps (i.e. build steps) leading up to deployment, such as:
Running UI/end-to-end tests on the latest version of the app we want to deploy.
Uploading our app to TestFlight.
Submitting our app to the App Store for review.
We’ll configure these build steps using Kotlin files, which I’ll cover more in the next section.
Fastlane and TeamCity
We use the following tools for build and deployment automation:
Fastlane: used to automate deployment for iOS and Android apps
TeamCity: used to manage automated deployment processes. It provides a web UI for checking build progress, build logs, etc.
We configure our build steps in the following files:
Multiple Kotlin files, which contain the DSL scripts that directly apply the build settings to TeamCity.
1 Fastfile
1 Makefile
The Kotlin files reside in a few levels of directories within the .teamcity directory, the Fastfile resides in a fastlane directory, and the Makefile resides in the root directory, as pictured below.
Here’s a more detailed breakdown of each file’s purpose, listed in the order that I chose to configure them:
Fastfile
The Fastfile, written in Ruby, is the first file that you configure because it contains the commands to run the various build steps. More specifically, it contains lanes that you define, which contain one or more built-in Fastlane actions that you specify. You can also call lanes within lanes, which is useful if you want to abstract a group of Fastlane actions into their own lane.
Example of a lane:
desc 'Release iOS app in App Store' # lane description specified by me
lane :release do # lane name, “release,” specified by me
precheck # built–in fastlane action (function)
deliver( # built–in fastlane action with arguments below
submit_for_review: true,
automatic_release: false,
force: true,
metadata_path: "./metadata/ios"
)
echo(
message: "Submitted app to App Store for review"
)
end
Kotlin files
The Kotlin file contains a script that essentially translates your Fastfile lanes into build steps and transmits these steps to TeamCity. Each lane roughly correlates to one build step, and you’ll create a Kotlin file for each build step. For example, you may have a IosAppStoreRelease.kt file that represents the build step for submitting my app to the app store. Inside the file, you specify something like this:
steps {
exec {
path = "make"
arguments = "release–app–store"
}
}
Makefile
The Makefile is the last file that you configure because it serves as a sort of bridge between Fastlane (Fastfile) and TeamCity (Kotlin files). The Makefile contains a list of “rules”, and each rule corresponds to a build step (which corresponds to an individual Kotlin file).
Example of a rule:
release-app-store: # submit app to Apple for review
rbenv install -s 2.4.1
rbenv local 2.4.1
rbenv rehash
rbenv exec gem install bundler
rbenv exec bundle install
PATH=~/.rbenv/shims:"$(PATH)"
rbenv exec fastlane ios release
Each rule is composed of a target and commands. The target in the example above is release-app-store
, which maps to the release-app-store
argument specified in the IosAppStoreRelease.kt file. The target tells TeamCity what commands should be run for the build step. The last command, rbenv exec fastlane ios release
, runs the fastlane lane called release
(which in my case is nested within another lane called ios
), which I defined in the Fastfile (lane :release do...
).
Putting it all together, the cross referencing of commands and configuration settings between files follows this general flow, or call hierarchy, if you will:
Subscribe to my newsletter
Read articles from Christine Chang directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Christine Chang
Christine Chang
Passionate about: Defensive Programming • Effective Engineering Documentation • Emotion-Centered Design and Microcopy