
There is a lot to cover here so we will jump right in by looking at what our ideal build process would be:
- xcodegen project¹
- fastlane build/sign
- upload to hockey app²
Straightforward, eh?
Let’s see what this looks like when we put this into an action workflow.
name: CI
on: [push]
jobs:
build:
runs-on: macOS-latest
steps:
- uses: actions/checkout@v1
- name: Install and run xcodegen
run: |
brew install xcodegen
xcodegen generate
- name: Run fastlane setup
env:
APPLE_ACCOUNT: ${{ secrets.APPLE_ACCOUNT }}
TEAM_ID: ${{ secrets.TEAM_ID }}
run: |
fastlane setup --verbose
- name: Run fastlane build
env:
MATCH_REPO: ${{ secrets.MATCH_REPO }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASS }}
SIGNING_ID: ${{ secrets.SIGNING_ID }}
APPLE_ACCOUNT: ${{ secrets.APPLE_ACCOUNT }}
TEAM_ID: ${{ secrets.TEAM_ID }}
run: |
fastlane compile --verbose
- name: Run fastlane deploy
env:
HOCKEY_TOKEN: ${{ secrets.HOCKEY_TOKEN}}
APPLE_ACCOUNT: ${{ secrets.APPLE_ACCOUNT }}
TEAM_ID: ${{ secrets.TEAM_ID }}
run: fastlane deploy --verboseThat’s a clean build script. Let’s step through it and see what’s happening here.
name: CI
on: [push]Here we have a name and an on field. Name is exactly what it sounds like. This is the name of your workflow. The next field is more interesting. This is an array of different webhooks you can listen to for when to run the job. For now, we’re just interested in push but here are the docs if your case requires others.
jobs:
build:
runs-on: macOS-latestFollowing this, we have jobs and runs-on. Jobs are what they sound like. A unit of work. Here, our job is called build. Within that build job we can choose what OS we want to work on by setting runs-on. See here for the different platforms but in our case, we’re attempting to build for iOS so macOS it is.
steps:
- uses: actions/checkout@v1Next, we have our steps. The first step is mandatory if you want to actually check out the code from your repo. You can read more about it here but essentially without it you aren’t able to perform any other tasks, which would make this exercise rather pointless.
- name: Install and run xcodegen
run: |
brew install xcodegen
xcodegen generateNow we’re starting to see our code performing what we described at the top as our ideal process.
This here is a step named “Install and run xcodegen”. Luckily, the macOS image comes pre-installed with homebrew so we will use that to install xcodegen and generate a project with it.
- name: Run fastlane setup
env:
APPLE_ACCOUNT: ${{ secrets.APPLE_ACCOUNT }}
TEAM_ID: ${{ secrets.TEAM_ID }}
run: |
fastlane setup --verboseOur next step, “Run fastlane setup”, is another element that is concisely named. But this is where things get tricky, at least for me. For match and fastlane to work how I wanted, I required access to the keychain. This is not something that we seem to have permissions to when using actions.
Thankfully, there is a fastlane action (this is not the same as a Github action) that will create a keychain and set it as the default, which makes up the content of our setup_project lane.
lane :setup do
setup_project
end
private_lane :setup_project do
create_keychain(
name: "actiontest_keychain",
password: "meow",
default_keychain: true,
unlock: true,
timeout: 3600,
lock_when_sleeps: false
)
endAbove we use create_keychain to generate a new keychain that we’ve called actiontest_keychain, with a password of meow.
Now that we have our keychain made, let’s download our certs and build the app.
- name: Run fastlane build
env:
MATCH_REPO: ${{ secrets.MATCH_REPO }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASS }}
SIGNING_ID: ${{ secrets.SIGNING_ID }}
APPLE_ACCOUNT: ${{ secrets.APPLE_ACCOUNT }}
TEAM_ID: ${{ secrets.TEAM_ID }}
run: |
fastlane compile --verboseHere you see an example of environment vars and secrets. Secrets are in your GitHub repo settings and allow for removing passwords and other sensitive info from source code. Very useful. But Fastlane does not understand how to access secrets, so we need to assign it to an environment var so we can ensure access.
private_lane :build do
match(
type: "appstore",
readonly: is_ci,
keychain_name: "actiontest_keychain",
keychain_password: "meow"
)
update_project_provisioning(
xcodeproj: ENV["XCODE_PROJ"],
profile: ENV["sigh_com.redspace.actionstest_appstore_profile-path"],
target_filter: "actionstest",
build_configuration: "Release",
code_signing_identity: "iPhone Distribution: REDspace Inc."
)
build_app(
scheme: "actionstest",
project: ENV["XCODE_PROJ"],
)
endOur build lane is simple for how much trouble it gave me.
First, we run our fastlane match action. The special part of this match is that we are telling it what keychain to add the certs to. Another thing to keep in mind is at the moment it is difficult to check out repos on another host (such as Stash) with ssl. Your best bet would be to use http check out.
Since we’re not using automatic code signing, we must run the update_project_provisioning fastlane action with our profile, target, config, and signing identity. In terminal match will print out a list of environment variables that it has created and within them you’ll find an environment variable with your profile already set.
Finally, call build_app.
After that’s completed, we have one final step, which is to upload to hockey app.
- name: Run fastlane deploy
env:
HOCKEY_TOKEN: ${{ secrets.HOCKEY_TOKEN}}
APPLE_ACCOUNT: ${{ secrets.APPLE_ACCOUNT }}
TEAM_ID: ${{ secrets.TEAM_ID }}
run: fastlane deploy --verbosecontent_copy
private_lane :hockey_upload do
hockey(
api_token: ENV["HOCKEY_TOKEN"],
create_status: "2",
ipa: "actionstest.ipa",
notes: ENV["RELEASENOTES"]
)
end
No secret sauce here.
Congratulations! You’ve created an action capable of generating, signing, building, and deploying your iOS project. Yay!
If you’d like to see this is as completed project we have made an example available on our GitHub.

