š How to automatize the iOS app release process with CircleCI
Although the idea of releasing a new version of our app with new features and solving a bunch of bugs sounds happy, the reality is that the manual process is cumbersome and time-consuming, making this process not a happy one.
To relieve this pain, Iāve automated this process with CircleCI. It hasnāt neither been an easy path because of the bad documentation and lack of working examples. Sometimes, I had the feeling that some parts of the docs were outdated and not maintained.
On the following article, you will find the process I followed to automatize the iOS app release process including the release notes, and links to all the documentation and examples I used.
š Define App Store Connect account settings & CircleCI environment variables
In first place, we need to gather all the related identifiers stuff to be able to connect with the App Store Connect API.
Create the Appfile
We need to create the Appfile containing the bundle identifier or app id and our Apple account email:
your_project/ios/fastlane/Appfile
app_identifier("com.bla.bla") # The bundle identifier of your app
apple_id("bla@mail.com") # Your Apple email address
Create the release settings
At the beginning of the Fastfile, weāll define the app id, the provisioning profile and team id to be used on the release lane. This is very useful because if we have other lanes such as Adhoc, we can define different configuration objects to use depending on the lane:
your_project/ios/fastlane/Fastfile
APP_ID = "com.bla.bla"
PROVISIONING_PROFILE_APPSTORE = "match AppStore com.bla.bla"
TEAM_ID = "your_team_id"
settings_to_override_release = {
:BUNDLE_IDENTIFIER => APP_ID,
:PROVISIONING_PROFILE_SPECIFIER => PROVISIONING_PROFILE_APPSTORE,
:DEVELOPMENT_TEAM => TEAM_ID,
}
Create the api key and add it to CircleCI environment variables
First, we need to get the key_id, issuer_id and key_content from our App Store Connect account. We can get them from here:
Second, we should create CircleCI environment variables for these values by going to your project > āļø Project Settings > Environment Variables > Add environment variable.
Finally, by using app_store_connect_api_key command on our Fastfile, we will read the api key values previously defined as environment variables, and thus, we will be able to identify ourselves on the App Store Connect API to access and send our project data.
your_project/ios/fastlane/Fastfile
desc "Export Release IPA & upload Release to App Store"
lane :release_ipa do
api_key = app_store_connect_api_key(
key_id: $APP_STORE_CONNECT_API_KEY_KEY_ID,
issuer_id: $APP_STORE_CONNECT_API_KEY_ISSUER_ID,
key_content: $APP_STORE_CONNECT_API_KEY_KEY
)
end
Doing it like this, itās the recommended way to avoid the 2 authentication factor which is prompted when doing the login by using user and password.
š Define the Fastfile Release lane
We need to create a new lane to specify the needed steps in order to fulfill successfully a new release to the App Store Connect.
Create release_ipa lane
Letās go back to our Fastfile, on this file weāll define all our desired lanes related with iOS processes such as exporting adhoc IPA, exporting release IPA or creating a new release and uploading the release IPA to the App store.
This article will cover creating a lane for launching a new release on the App store, besides the IPA generation and upload to the new release.
ā Increment build number
Weāll continue the development of our release_ipa lane by adding the call to increment_build_number:
your_project/ios/fastlane/Fastfile
desc "Export Release IPA & upload Release to App Store"
lane :release_ipa do
api_key = app_store_connect_api_key(
key_id: $APP_STORE_CONNECT_API_KEY_KEY_ID,
issuer_id: $APP_STORE_CONNECT_API_KEY_ISSUER_ID,
key_content: $APP_STORE_CONNECT_API_KEY_KEY
)
increment_build_number(
build_number: app_store_build_number(
api_key: api_key,
initial_build_number: 0,
version: get_version_number(xcodeproj: "your_project.xcodeproj"),
live: false
) + 1,
)
end
This piece of code will be very useful when uploading several builds for Testflight users. Automatically, it assigns the next build version taking into account if an IPA already exists on the new release.
Take into account that this call needs to include the api_key credentials, this is not well documented on the offical docs. More information on app_store_build_number.
š match command
The match call is where we define the type of permissions / provisioning profiles applied to the App Store, on this case, the type keyword assigned to Release mode is appstore
.
your_project/ios/fastlane/Fastfile
desc "Export Release IPA & upload Release to App Store"
lane :release_ipa do
api_key = app_store_connect_api_key(
key_id: $APP_STORE_CONNECT_API_KEY_KEY_ID,
issuer_id: $APP_STORE_CONNECT_API_KEY_ISSUER_ID,
key_content: $APP_STORE_CONNECT_API_KEY_KEY
)
increment_build_number(
build_number: app_store_build_number(
api_key: api_key,
initial_build_number: 0,
version: get_version_number(xcodeproj: "your_project.xcodeproj"),
live: false
) + 1,
)
match(
app_identifier: APP_ID,
readonly: is_ci,
type:"appstore"
)
end
š build app command
The build_app call builds the IPA with the desired configuration. On the next code snippet, you can see how to set the Release configuration:
your_project/ios/fastlane/Fastfile
desc "Export Release IPA & upload Release to App Store"
lane :release_ipa do
api_key = app_store_connect_api_key(
key_id: $APP_STORE_CONNECT_API_KEY_KEY_ID,
issuer_id: $APP_STORE_CONNECT_API_KEY_ISSUER_ID,
key_content: $APP_STORE_CONNECT_API_KEY_KEY
)
increment_build_number(
build_number: app_store_build_number(
api_key: api_key,
initial_build_number: 0,
version: get_version_number(xcodeproj: "your_project.xcodeproj"),
live: false
) + 1,
)
match(
app_identifier: APP_ID,
readonly: is_ci,
type:"appstore"
)
build_app(
scheme:"your_project_name",
export_method:"app-store",
skip_profile_detection:true,
configuration: "Release",
workspace: "your_project_name.xcworkspace",
xcargs: settings_to_override_release,
export_options: {
provisioningProfiles: {
APP_ID => PROVISIONING_PROFILE_APPSTORE
},
installerSigningCertificate: "your_installer_signing_certificate_name"
}
)
end
šµ deliver command
The deliver call creates a new release on the App Store and then uploads the IPA binary generated on the build_app step:
your_project/ios/fastlane/Fastfile
APP_ID = "com.bla.bla"
PROVISIONING_PROFILE_APPSTORE = "match AppStore com.bla.bla"
TEAM_ID = "your_team_id"
settings_to_override_release = {
:BUNDLE_IDENTIFIER => APP_ID,
:PROVISIONING_PROFILE_SPECIFIER => PROVISIONING_PROFILE_APPSTORE,
:DEVELOPMENT_TEAM => TEAM_ID,
}
default_platform(:ios)
platform :ios do
before_all do
setup_circle_ci
end
desc "Export Release IPA & upload Release to App Store"
lane :release_ipa do
api_key = app_store_connect_api_key(
key_id: $APP_STORE_CONNECT_API_KEY_KEY_ID,
issuer_id: $APP_STORE_CONNECT_API_KEY_ISSUER_ID,
key_content: $APP_STORE_CONNECT_API_KEY_KEY
)
increment_build_number(
build_number: app_store_build_number(
api_key: api_key,
initial_build_number: 0,
version: get_version_number(xcodeproj: "your_project.xcodeproj"),
live: false
) + 1,
)
match(
app_identifier: APP_ID,
readonly: is_ci,
type:"appstore"
)
build_app(
scheme:"your_project_name",
export_method:"app-store",
skip_profile_detection:true,
configuration: "Release",
workspace: "your_project_name.xcworkspace",
xcargs: settings_to_override_release,
export_options: {
provisioningProfiles: {
APP_ID => PROVISIONING_PROFILE_APPSTORE
},
installerSigningCertificate: "your_installer_signing_certificate_name"
}
)
deliver(
api_key: api_key,
submit_for_review: false,
force: true,
precheck_include_in_app_purchases: false
)
end
end
This step also needs to include the api_key credentials, it isnāt clearly documented on the official docs.
āļø If we set submit_for_review: true
, the new release will be sent to review automatically, so we donāt need to click on it manually from Apple Store Connect website.
Extra tip: If you donāt have in-app purchases, set precheck_include_in_app_purchases: false
, otherwise the workflow crashes on circleCI even though the IPA has been successfully sent to App Store.
Precheck cannot check In-app purchases with the App Store Connect API Key Ā· Issue #18250 Ā· fastlane/fastlane
Now, our Fastfile is finished š
š¾ Define the Release job on config.yml
Once our lane is finished, we need to create a job that calls this lane among other processes to have our flow ready.
Create the .env file
On the config.yml, weāll create the ios-release job.
On this job, weāll need a step to create the .env file containing the previously defined App Store Connect related keys on CircleCI environment variables:
- run:
name: "Create .env file"
command: echo -e "APP_STORE_CONNECT_API_KEY_ISSUER_ID=${APP_STORE_CONNECT_API_KEY_ISSUER_ID}\nAPP_STORE_CONNECT_API_KEY_KEY=${APP_STORE_CONNECT_API_KEY_KEY}\nAPP_STORE_CONNECT_API_KEY_KEY_ID=${APP_STORE_CONNECT_API_KEY_KEY_ID}" > .env
If you already had this step created, just add the new environment variables.
Call to the release_ipa lane
On the config.yml, weāll add a new step to call our previously created release_ipa lane:
- run:
command: rm -rf Pods && pod install && bundle exec fastlane release_ipa --verbose
no_output_timeout: 30m
working_directory: ios
The config.yml release job skeleton will be something like this:
ios-release:
macos:
xcode: '13.3.0'
working_directory: ~/your-working-directory
# use a --login shell so our "set Ruby version" command gets picked up for later steps
shell: /bin/bash --login -o pipefail
steps:
- checkout
- run:
name: "Create .env file"
command: echo -e "APP_STORE_CONNECT_API_KEY_ISSUER_ID=${APP_STORE_CONNECT_API_KEY_ISSUER_ID}\nAPP_STORE_CONNECT_API_KEY_KEY=${APP_STORE_CONNECT_API_KEY_KEY}\nAPP_STORE_CONNECT_API_KEY_KEY_ID=${APP_STORE_CONNECT_API_KEY_KEY_ID}" > .env
(...)
- run:
command: rm -rf Pods && pod install && bundle exec fastlane release_ipa --verbose
no_output_timeout: 30m
working_directory: ios
- store_artifacts:
path: ios/your_project.ipa
š¹ Define the Release workflow on config.yml
Weāre going to define two jobs on the config.yml file:
- request-ipa-and-prepare-release: In order to be able to decide when we want to create a release, this job will give us the control to decide on which branch we want to trigger a new iOS release.
- ios-release: When clicking on the thumb up from request-ipa-and-prepare-release job, the iOS release process will be triggered, so a new release will be created on App Store Connect and the new generated IPA will be uploaded.
The workflows skeleton would be something like this:
workflows:
version: 2
ios:
jobs:
- request-ipa-and-prepare-release:
type: approval
filters:
<<: *filters-node
- ios-release:
requires:
- request-ipa-and-prepare-release
filters:
<<: *filters-node
And this is how it would look on circleCI:
š Upload Release Notes automatically
Itās very easy to automatize the upload of the release notes for each new iOS release, so we donāt need to go to the App Store Connect and fill it for each language. Here is the unique step to follow:
- Create a folder for each one of your app supported languages on the following location:
ios/fastlane/metadata/en-US/release_notes.txt
ios/fastlane/metadata/es-ES/release_notes.txt
ios/fastlane/metadata/fr-FR/release_notes.txt
On the release_notes.txt files, add the text as you would do it: No extra steps nor extra flags needed.
This is automatically detected by the deliver command so thatās why thereās no need of extra configuration to be added.
Congratulations, youāve made it! Now your iOS release process is automated! š„³
š„¹ When trying the new release process youāll be aware that the IPA appears INSTANTLY on App Store Connect portal when doing it through circleCI, unlike when we did it manually from Xcode which took about 20 minutes to appear.
š Bugs found during the process
CircleCI shows an error related with iTMSTransporter: An exception has occurred: issuerId is required
error
This has been solved by adding the following env variable to CircleCI: ITMSTRANSPORTER_FORCE_ITMS_PACKAGE_UPLOAD = true
And updating fastlane version to 2.210.1 on Gemfile.lock:
// on the Terminal, go to your project
cd ios
bundle update
// after these steps, commit and push the Gemfile.lock
On the next article, weāll learn to how to automatize the Android app release process on Google Play Store and CircleCI š¤
š Docs
Documentation & extra tutorials
- Fastlane official guide about Apple Store deployment
- How to build the perfect fastline pipeline for iOS
- How to use fastlane to deploy iOS app fast
- Build and deploy your iOS app to Testflight with Github Actions
- Set environment variables on CI
- Lanes
- App Store Connect API
- Authenticating with Apple Services
Examples
- circleci-demo-ios/config.yml at master Ā· CircleCI-Public/circleci-demo-ios
- circleci-demo-ios/Fastfile at master Ā· CircleCI-Public/circleci-demo-ios
- https://webcache.googleusercontent.com/search?q=cache:DFydATxAjUEJ:https://circleci.com/docs/ios-tutorial&cd=2&hl=en&ct=clnk&gl=es