Mastering Github Actions for iOS
I have spent last few weeks exploring Github’s new CI service, Github Actions. I managed to set up my workflows for both my iOS apps, macOS apps and multiplatform Swift Packages. I have to admit, I really like it! It’s quick, versatile, cheap and just really fun to work with 🐙.
However, I feel like Github Actions are not really designed for the iOS audience — you start to see your workflows become more bloated and messy quite early on as you expand their capabilities.
But does it really have to be this way? No! After extensive docs study and experimentation I can teach you some cool tips and tricks to master your CI workflows. Let’s make your jobs supercharged with features and more elegant at the same time! 💪
Use matrix for job parametrisation
In previous articles, we have established that we would like to perform CI workflows against variety of platforms, OS versions, macOS images and Xcode versions. It would be great if we could have one parametrised job template to cover all of those, instead of copy-pasting the same one over and over again, applying minor changes to it each time, right?
Enter the matrix. Matrix is a nice and elegant way to define a group of parameters, which can be then used inside of your job steps. For example, if matrix contains a list of
schemes to build as a parameter, then those values are usable in your
xcodebuild steps later down the pipeline.
And not only that — if you group mutliple parameters in a matrix, then each time CI is triggered, it creates multiple instances of the same job, instead of just single one. The idea is to create as many job instances with parameters switched all around so that all of the parameter combinations are fulfilled. Let the examples do the explanation:
Straightforward matrix example
Below you can find code snippet of adding matrix to the
build-and-test job, containing just one
xcodebuild step. This matrix consists of three schemes and two xcodebuild commands to perform.
So now, each time CI is triggered, Github Actions will create 6 jobs instead of one— each serving one combination of parameters. There will be job for building
Core scheme, another for testing
Core scheme, then another for building
UI, another for testing
UI and so on.
Full multiplatform matrix example
Now that you know matrix works, I would like to share with you my opus magnum of Github Actions as of now. This gist below is a curated snippet of fully parametrised, no-maintenance, matrix-based job I came up with, which uses
xcodebuild to build and test my multiplatform Swift Packages. No flimsy destination strings, iPhone names, mac IDs, or whatever — just Actions stressing my Package out against ridiculous amounts of variables. 🐙
This example uses matrix consisting of multiple variables worth testing on, such as different
macos environment versions,
schemes to build,
platforms to test against etc.
Make Open-Source Actions work for you
As you may have noticed from the examples above, those contain some prebuilt Actions from Github Marketplace (mainly
xcodebuild). Those are free, open-source scripts created by the ever-growing Github Actions community. At first, browsing the Marketplace and eyeing out iOS stuff seemed to me a bit redundant — it felt like I would give up some of the control of my build process, so I tried to configure every nook-and-cranny on my own.
However, now I would highly recommend taking advantage of them though. I’ve understood that the best CIs are those which are always reliable and 100% maintenance-free — if third-party solutions get you closer to this ideal, then it’s just unwise to not use them!
I mean, those are built by people smarter than you anyway 🤭
One of the Actions I tend to use often is
maxim-lobanov/setup-xcode. This seems to be a bit silly, since we all know how easy switching to given Xcode actually is (
sudo xcode-select -s PATH_TO_XCODE.app). But remember when we’ve talked about perfect CI being maintenance-free? You, as a dev, should only care about which Xcode version to run your job on — let the open-source community take care of the mundane and fragile stuff, like figuring out the path to the
Xcode.app for your builds. One less thing to worry about!
Turbocharged xcodebuild command
Another one, which is frankly quite insane, is custom
mxcl/xcodebuild. This Action redefines the inputs for your standard
xcodebuild, making them readable faster and also expands the command’s base capabilities overall.
Moreover, as you can read in the docs, this Action not only is made to be continuously resilient (never failing), but also houses tons of fail-safe mechanisms, so that below usage will always trigger code testing properly:
Even though, if you think about it, it doesn’t have all of the necessary data to do so — destination, scheme, platform and others are all inferred for your convenience. It’s even smart enough to replace
test command with
build for watchOS builds under Xcode <12.5. And that’s just one example. Haven’t thought of that yourself, did you? 🧐
Conditionally excluding matrix job combinations
You should be wary of certain edge cases when designing your job parametrisation matrix. If your matrix is anything like mine, then you are probably parametrising
macOS versions to use in your jobs, like so:
xcode: ['13.0', '12.5.1', '12.4', '12.0.1']
macos: ['macos-11', 'macos-10.15']
This looks good at a first glance, but if you think about it, at least one of the jobs are destined to fail — there’s no
xcode '13.0' available on
Catalina and no
xcode '12.0.1' available on
Big Sur environment and so on.
The solution is the keyword
exclude available in Github Actions syntax. Using it you can escape non-favourable job scenarios created by the matrix parameters. You can also go the other way around with the keyword
Trigger builds manually from the web
This is a pet-peeve of mine actually — how many times you had to create dummy commits in your PRs so that the CI jobs would trigger (especially when you are setting up the workflow for the first time)? You can actually create a button in your repos web interface, which will trigger the CI whenever you need to do so. No more clumsy commit history!
Cool thing is that it’s done right in the config file as below, with the keyword
workflow_dispatch. No web code needed. You can even add additional input parameters to be available, but a plain button with branch selector will do too.
Diagnostic ideas before building and testing
I feel like I have spent too much of my professional career figuring out “why this CI build failed?” and creating makeshift diagnostic tools to help me debug the issues on the fly. This is why I’d recommend coming out with as many diagnostic steps as possible, to be fired before you actually build, test or release anything, each time. Better to have a think about it now, then to frantically try to think of something when your jobs actually start to fail miserably. Right?
Have a go at my little cheatsheet below and feel free to incorporate some of those ideas to your own Github Actions jobs.
Hope that was helpful! Share your favourite tricks for Github Actions below!