The Problem
Engineering teams these days find it troublesome to build, test and deploy their mobile application changes locally without having to maintain the tools required for it. There is a lot of maintenance involved as you need to keep track of what versions of these tools have been installed to avoid compatibility issues when youβre building the application bundles, especially hybrid apps built on React Native.
The Cure
fastlane, an automation tool that aids in handling all of the tedious tasks so you don't have to. It's by far, the easiest way to build and release your mobile apps.
fastlane can easily:
- Distribute beta builds to your testers
- Publish a new release to the app store in seconds
- Reliably code sign your application - alleviates all of your headaches
- Reliably maintain your provisioning profiles and application certificates in a Git repository
How it works
All actions in fastlane are written into lanes. Defining lanes are easy. Think of them as functions in any programming language of your choosing. You define all of your actions within that lane.
An example of a lane is as follows:
lane :beta do
increment_build_number
build_app
upload_to_testflight
end
So when it comes to executing that lane, all you do is run fastlane beta
in your terminal.
Installation & Setup
In this article, we will look at setting up a fastlane script to build, sign and deploy an iOS application to Testflight.
Pre-requisites
As with most projects, you need to perform the initial project setup to support building your application, such as:
- Installing your project dependencies
- Install XCode and Android Studio
- Install Java SDK
- Setup git repository for Android and iOS certificates - will be explained later in the article
fastlane folder structure
In ideal cases, you would have an Android application project and an iOS application project, both hosted in the same code repository as your project
projectFolder/
app/
scripts/
ios/
fastlane/
....
android/
fastlane/
....
package.json
.gitignore
...
In this case, you will want to initialise the fastlane folder within the respective android
and ios
project sub-directories.
Setup
- Install fastlane
- via Homebrew (
brew install fastlane
) - via RubyGems (
sudo gem install fastlane
)
- via Homebrew (
- Navigate your terminal to your respective
android
andios
project directory and runfastlane init
- You should have a folder structure that is similar to the one below:
fastlane/
Fastfile
Appfile
The most interesting file is fastlane/Fastfile
, which contains all the information that is needed to distribute your app.
- Inside your
Fastfile
, you can start writing lanes:
lane :my_lane do
# Whatever actions you like go in here.
end
You can start adding in actions into your lanes. fastlane actions can be found here.
Copy the following file structure to your
Fastfile
default_platform(:ios)
def beta(arg1, arg2)
# You may use Ruby functions to write custom actions for your app deployment
end
platform :ios do
desc "Building the IPA file only"
lane :build_ios_app_ipa do
app_identifier = "com.appbundle.id"
beta("AppSchemeName", app_identifier)
end
end
default_platform(:ios)
- Initialise your Fastfile file with a default platformplatform :ios do
- Add all actions under a platform
From 5 to 6, it will inform fastlane that this particular Fastfile is purely for iOS operations. So instead of running the command fastlane <lane_name>
, you will actually run fastlane ios <lane_name>
. Therefore, anything parked under platform :ios do
will be executed when lanes are invoked in your terminal.
If you are well-versed in Ruby, you may write your own Ruby functions to help you write custom actions that you require for further flexibility, especially when it comes to building several apps with different environments across your organisation.
fastlane will identify them as an action regardless due to the fact that fastlane is written in Ruby.
In this article, we will follow writing the actions using a Ruby function. This is so we can promote action re-usability across other lanes deployments.
Action Steps
Before we start writing our functionality in the lanes, we first list down our action steps:
-
Setup API Key for App Store Connect (
app_store_connect_api_key
) - This will allow fastlane to connect to your App Store to perform other actions that requires user authentication -
Setup CI (
setup_ci
) - This will setup a temporary keychain to work on your CI pipeline of your choice -
Create and sync provisioning profiles and certificates (
match
) - This will help us maintain our provisioning profiles and certificates across your teams -
Update code signing settings (
update_code_signing_settings
) - This is to update the code signing identities to match your profile name and app bundle identifier -
Increment your app build number (
increment_build_number
) - This will automate your application build number by retrieving the latest Testflight build number and incrementing the value -
Build the app (
build_app
) - This will build the app for us and generate a binary (IPA) file -
Upload your binary to Testflight (
upload_to_testflight
) - This will automate the process of uploading the binary file to Testflight and informing your testers accordingly
Steps
Step 1: Setup your App Store Connect API key
- Visit the following page. It will provide you a step-by-step process in generating an API key
- Once you have generated a key, take note of:
- Issuer ID
- Key ID
- Download the generated API key - A
.p8
file - Store it in a secure location in which you can easily access them. Avoid storing them in the same repository as anyone in your organisation will have access to the company's Apple account.
- In this article, we are storing them in another Git repository with limited read and write scopes to specific engineers within our organisation.
Step 2: Setup the fastlane script
- Setup your App Store API Key to generate a hash used for JWT authorization
def setup_api_key()
sh "if [ -d \"appstoreapi\" ]; then echo 'Folder exist, executing next step'; else git clone #{ENV['APPSTORE_API_GIT_URL']} appstoreapi; fi"
app_store_connect_api_key(
key_id: ENV['APPSTORE_KEY_ID'],
issuer_id: ENV['APPSTORE_ISSUER_ID'],
key_filepath: Dir.pwd + "/appstoreapi/AuthKey_xxx.p8",
)
end
In the snippet above, I am cloning a Git repository which contains my App Store Connect API key, followed by utilising the app_store_connect_api_key
action from fastlane. It takes in several parameters, however, there are 3 vital parameters:
-
key_id
- The Key ID from which you took note when you generated the API key -
issuer_id
- The Issuer ID from which you took note when you generated the API key -
key_filepath
- The file path to your.p8
file
This step will generate a hash that will be used to authenticate the App Store using JWT.
I would highly recommend storing sensitive credentials or URLs and access them from an Environment Variable (.env
) file in the same project folder - fastlane/.env
Once you have prepared the file, you can easily access any Environment Variable by simply passing in ENV['ENV_NAME']
into the Fastfile.
2. Define a Ruby function with 2 arguments
def beta(scheme, bundle_id)
end
Within this function, we specify the action steps we mentioned above
def beta(scheme, bundle_id)
setup_api_key() # Import the setup_api_key function
setup_ci() # Uses fastlane action to create a temporary keychain access on a CI pipeline
end
3. Utilise the match
action from fastlane
match is a fastlane action that allows you to easily sync your certificates and provisioning profiles. It takes a new approach to iOS and macOS code signing, where you share one code signing identity across your engineering team to simplify the setup and prevent code signing issues. The foundation of match was built using the implementation of codesigning.guide concept.
You can store your code signing identities in:
- Git repository
- Google Cloud
- Amazon S3 bucket
In this article, we have chosen to store it a Git repository. However, with the case of storing in a Git repository, you would need to provide a form of basic authorization in order for match to access and clone your repository.
Whichever Git provider you choose (GitHub, Bitbucket, Gitlab or Azure DevOps), you would need to setup a Personal Access Token (PAT), which fastlane will use to clone repository and sync your code signing identities.
In your Fastfile, you will need to encode your PAT with a Base64 encryption
authorization_token_str = ENV['GITHUB_TOKEN']
basic_authorization_token = Base64.strict_encode64(authorization_token_str)
The basic_authorization_token
variable will be used in setting up the match implementation below.
match(
git_url: "<git_url>",
git_basic_authorization: basic_authorization_token,
readonly: true,
type: "appstore",
app_identifier: [
bundle_id
])
From the snippet above, this is a very simple implementation. Take note on the readonly
parameter. This is crucial in creating and syncing your profiles and certs with your Apple Developer account. If you set readonly
to false
, you will allow match to automatically sync and create new profiles and certs, should it deem your existing certs and profiles expired or corrupted. Once it has provisioned the profiles, you can set readonly
to true
. This is to avoid egde cases where it might accidentally create new certs and profiles.
4. Update code signing identities
This step is mainly used to update the Xcode settings on a CI pipeline due to its default behaviour of selecting a default cert and profile from the Mac OS agent.
update_code_signing_settings(
use_automatic_signing: false,
path: "../ios/AppName.xcodeproj",
code_sign_identity: "iPhone Distribution",
profile_name: "match AppStore com.appbundle.id",
bundle_identifier: "com.appbundle.id"
)
From the above snippet, you can retrieve your profile_name
and bundle_identifier
from your Apple Developer account or taking note of them from the match step once it has been executed.
5. Increment Application build number
We simply increment the number by accessing your latest testflight build number and incrementing the value with 1.
increment_build_number({
build_number: latest_testflight_build_number + 1
})
puts "BUILD_NUMBER: #{lane_context[SharedValues::BUILD_NUMBER]}"
The puts
command will print out the Build number in the console, which is shared across the lane context.
You may have other app build versioning models than the one mentioned in this article. Increment the build number however you see fit to your project needs.
6. Build the application
This step will involve building your application to generate a binary (APK) file.
build_app(
workspace: "../ios/AppName.xcworkspace",
export_xcargs: "-allowProvisioningUpdates",
scheme: scheme,
clean: true,
silent: true,
sdk: "iphoneos"
)
puts "IPA: #{lane_context[SharedValues::IPA_OUTPUT_PATH]}"
The build_app
action is provided by fastlane.
The puts
command will print out the file path location of the IPA file in which you can use to manually upload to Testflight.
7. Upload the binary file to Testflight
This step will involve uploading your binary file to your Testflight account.
app_identifier = "com.appbunde.id"
upload_to_testflight(app_identifier: app_identifier)
upload_to_testflight
is an action provided by fastlane.
Putting them all together
The final lane should look something like this:
desc "Build and push a new build to TestFlight"
lane :release_build do
app_identifier = "com.appbundle.id"
beta("AppSchemeName", app_identifier) # The custom function we wrote earlier
upload_to_testflight(app_identifier: app_identifier)
end
When you execute fastlane ios release_build
in your terminal, it will operate based on the aforementioned steps above.
As you can see, we utilised a Ruby function to group all common operations under the same roof so is to ensure:
- Code re-usability
- Consistency
- Clean code
Integration with CI/CD tools
With the fastlane scripts, you can easily integrate it with any CI/CD tools of your choosing:
- GitHub Actions
- Gitlab CI
- Azure Devops
- Bitbucket Pipelines
When setting up your pre-requisite steps in your pipeline YAML file, you can simply include a bash script that executes your fastlane script
cd ios/android folder
fastlane ios/android release_build
The command above will execute your fastlane script by following the aforementioned steps above.
Conclusion
fastlane is a powerful tool that helps streamline your build process across your organisation. It can be used along side your existing CI/CD pipelines or as a standalone pipeline script to be used on your local machines or custom pipeline tools such as Buildkite. It would require additional steps such as, cloning the repository and checking out branches, however, all readily available from fastlane as actions.
Spend some time reading through the documentation as it contains a lot of actions out of the box that will prove useful for your engineering teams (match, pilot, cert, sigh, appium, xctool, supply, and many more) . Furthermore, they provide an extensive list of plugins for third-party integrations such as Firebase, App Center, Yarn, Android Versioning, Dropbox, Slack Upload, S3, Bugsnag and many more. You can supercharge your fastlane scripts by coupling it with a CI/CD tool, such as GitHub Actions, Bitbucket Pipelines or Azure DevOps. The sky's the limit!
Top comments (2)
Your clear explanation of the challenges faced by engineering teams in managing mobile app builds locally resonated with me. The way you introduced fastlane as a powerful solution for automating tasks like beta distribution, code signing, and maintaining provisioning profiles is commendable. π
Question for you: How do you handle versioning and compatibility issues when deploying hybrid apps built on React Native? Iβd love to hear more about your experiences! π
It would be good if you share GitHub code.
Any GitHub link for all this code?