Github actions + Fastlane + iOS = ❤
By: Tj Gillis
Tags: Fastlane, iOS, Github

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 --verbose

That’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-latest

Following 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@v1

Next, 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 generate

Now 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 --verbose

Our 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

)

end

Above 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 --verbose

Here 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"],

)



end

Our 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 --verbose content_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.