Steps to automate deployment process for iOS applications to Test flight on any machine (p1)
Introduction
Are you tired of the manual and time-consuming process of deploying your iOS applications? Look no further. With the power of GitHub Actions, deploying iOS apps has never been easier. By automating the building, testing, and deployment tasks, you can save time and eliminate human error.
In this blog post, we will explore how to deploy iOS applications using GitHub Actions. I'll walk through the essential elements you need to prepare, such as setting up a build machine, managing certificates and provisioning profiles. Additionally, we'll dive into the concept of environment variables and how they can simplify the configuration process.
Preparation
A build machine with Installed Xcode
To build and package an iOS application, you need a dedicated build machine. Set up your correct version of Xcode. I would recommend to use Xcodes tool to manage multiples XCode versions on your build machine.
https://github.com/XcodesOrg/xcodes
Code signing certificate
I assume you know how to create/store/export a deployment certificate. Our certificate .p12
will be encoded to base64 string and stored as environment variables (please find detail in next segment). Then we create a bash script install_certificate.sh
to
- Create new keychain.
- Install the certificate to the keychain.
- Unlock the keychain to allow xcode access it to use the cert when exporting ipa.
Here is the content of the sript
#!/usr/bin/env sh
CERTIFICATE_P12=certificate.p12
# Decoding the base 64 cert and write it to a file
echo $CERT_BASE64 | base64 --decode > $CERTIFICATE_P12
# Creating new keychain with a password
security create-keychain -p $BUILD_KEY_CHAIN_PASSWORD $BUILD_KEY_CHAIN_NAME
# Setting the keychain as default
security list-keychain -s $BUILD_KEY_CHAIN_NAME
security default-keychain -s $BUILD_KEY_CHAIN_NAME
# Unlock the keychain
security unlock-keychain -p $BUILD_KEY_CHAIN_PASSWORD $BUILD_KEY_CHAIN_NAME
# start manipulate it
security set-keychain-settings $BUILD_KEY_CHAIN_NAME
# import the cert content, $2 is the certificate password
security import $CERTIFICATE_P12 -k $BUILD_KEY_CHAIN_NAME -P $CERT_PASSWORD -T /usr/bin/codesign;
# we allow codesign to use the keychain above
# codesign is the tool that xcode uses to sign our app
# https://stackoverflow.com/questions/39868578/security-codesign-in-sierra-keychain-ignores-access-control-settings-and-ui-p
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $BUILD_KEY_CHAIN_PASSWORD $BUILD_KEY_CHAIN_NAME
# Then just remove the file
rm -fr *.p12
This bash script will be called like
chmod +x install_profile.sh && ./install_certificate.sh
Provisioning profile
It's simpler with provisioning profile. We just need to write it to the file with correct name in a correct folder. Let make another bash script install_profile.sh
PROFILE_FILE=$1.mobileprovision
echo $PROVISIONING_BASE64 | base64 --decode > $PROFILE_FILE
cp ${PROFILE_FILE} "$HOME/Library/MobileDevice/Provisioning Profiles/$1.mobileprovision"
rm -fr *.mobileprovision
Nice, really easy to understand.
So your machine is ready to build because it has the cert & provisioning. From next steps, I will share what i achieve to automate the whole process.
Fastlane script
I will give you an example of the lane I made, please adjust depends on your team need.
desc "PROD Build and upload to AppStore if need"
lane :prod_appstore do |options|
$version = options[:version] || get_version_number(xcodeproj: ENV["PROJECT_IDENTIFIER"], target: ENV["BUILD_TARGET"])
$build = options[:build] || get_build_number(xcodeproj: ENV["PROJECT_IDENTIFIER"])
prepare(
plist: ENV["PLIST_FILE_NAME"],
version: $version,
build: $build,
team_id: ENV["APP_STORE_TEAM_ID"],
code_sign: ENV["CODE_SIGN_IDENTITY_NAME"],
provisioning: ENV["PROVISIONING_NAME"],
bundle_id: ENV["APP_BUNDLE_ID"],
xcode_version: options[:xcode_version]
)
gym(
scheme: scheme,
export_method: "app-store",
export_team_id: ENV["APP_STORE_TEAM_ID"],
export_options: {
provisioningProfiles: {
ENV["APP_BUNDLE_ID"] => ENV["PROVISIONING_NAME"]
},
manageAppVersionAndBuildNumber: false
},
codesigning_identity: ENV["CODE_SIGN_IDENTITY_NAME"],
clean: true,
output_directory: "./builds",
skip_build_archive: false,
output_name: "just-a-name.ipa"
)
verify_build(
provisioning_type: "distribution",
bundle_identifier: ENV["APP_BUNDLE_ID"],
team_identifier: ENV["APP_STORE_TEAM_ID"]
)
end
def prepare(plist:, version:, build:, team_id:, code_sign:, provisioning:, bundle_id:, xcode_version:)
puts "Configure Xcode and Project"
if xcode_version
xcversion(version: xcode_version)
end
ensure_git_status_clean
increment_version_number(version_number: version)
increment_build_number(build_number: build)
update_code_signing_settings(
use_automatic_signing: false,
targets: [ENV["BUILD_TARGET"]],
path: ENV["PROJECT_IDENTIFIER"],
team_id: team_id,
code_sign_identity: code_sign,
profile_name: provisioning,
)
update_app_identifier(
plist_path: plist,
app_identifier: bundle_id
)
update_project_team(
path: ENV["PROJECT_IDENTIFIER"],
targets: [ENV["BUILD_TARGET"]],
teamid: team_id
)
cocoapods(clean_install: true)
end
Fastlane upload to App Store
This is sample steps in fastlane.
api_key = app_store_connect_api_key(
key_id: ENV["APP_STORE_KEY_ID"],
issuer_id: ENV["APP_STORE_ISSUER_ID"],
key_content: ENV["APP_STORE_KEY"],
is_key_content_base64: true,
duration: 1200, # optional (maximum 1200)
in_house: false
)
upload_to_testflight(
ipa: "./builds/just-a-name.ipa",
skip_submission: true,
skip_waiting_for_build_processing: true,
api_key: api_key
)
end
I wrote details about some ways to upload in this blog
https://www.hapq.me/two-ways-to-upload-ipa-to-testflight-with-fastlane/
Environment Variables
This is the thing I missed for long time in my whole career. I found out these variables are live inside a terminal session. We can store it by running this command
export MY_FUTURE_USE_VARIABLE=Hello
then when need to use, add a dollar sign before the name
echo $MY_FUTURE_USE_VARIABLE
Really simple but i don't know why I missed this stuff.
In any CI/CD tools such as Gitlab, Jenkins, Github action... there're always predefined environment variables that tell you about the job, the pull request, the machine etc.... You can access it anywhere anytime you want.
Gitlab CI shared details of them: https://docs.gitlab.com/ee/ci/variables/predefined_variables.html
Github action & Gitlab CI
please wait for part 2 of this serie.