Build and Test Swift Packages using Github Actions CI
By: Przemek Jablonski
Tags: Apple, Github Actions, Ci, iOS, Swift

Build and Test Swift Packages using Github Actions CI

That’s how Github will build your app. Or maybe with xcodebuild. | Unsplash

Okay, so in the last article of the series we created our first Swift Package, and deployed it to the Github.com. Since our code is already being hosted there, how about checking out the Github’s integrated CI service, GitHub Actions? Sounds convenient, so let’s check out what it offers to us.

Let’s take the Earendil — Swift Package we have created last time — and teach Github Actions CI how to properly build and test it. We will explore available features of this service along the way too. Go! 🚀

Make Github Actions work for you totally free 💰

Last time I mentioned creating reusable Swift Package which was independent from your app business logic. This was for a reason — if your Package doesn’t hold any sensitive information, then I think it’s totally fine to set it’s repository visibility to Public .

By doing so you are enabling special open-source billing plan for your Package repository CI. This plan gives you unlimited resources, high parallelisation count and no build minutes limit, all completely free 🤑. Stonks!

Of course, Github Actions are available for private repos as well, and they even have very generous free minutes threshold for them. But if we can use it for free — why not? 🧐

Launch the default Swift job in Github Actions

Let’s dip out toes in by creating first CI job for the Package, which will run each time there’s PR created for master/main branch. For starters, let’s use one of the default job templates, called Swift. Open the repository webpage on Github and open up the Actions tab at the top. You should see Swift template card at the front. Click Set up this workflow to get started.

Default Swift template card

You will be presented with the CI config editor. On the left you can see the actual .yml file, which dictates how your jobs should be run. Steps are already preconfigured from the Swift job template. On the right you can browse the Marketplace — a library of usable prebuilt scripts, created by Actions open-source community. We will cover this feature later on.

Config editor on the web, configuration is based on the Swift default template job

Clicking Start commit on the top right will a create a PR to master/main , triggering your first Github Actions build.

Let’s examine the default Swift config closer. It has some triggering rules, explained in the snippet below. It also consists of a single job named build , which has 3 steps — code checkout and then building and testing the Package. It uses the swift command to do so. Find the step-by-step details below:

Default config based on Swift template, commented

Not too complicated, right? It seems that Github Actions is all about adding actionable steps to the jobs and that’s it. Let’s check out how elastic and capable this system really is by expanding it’s scope.

Proper multiplatform testing suite

If you have followed the previous article, your Package should not only have a proper Unit Test suite, but it should be working cross-platform too. We should setup the CI so that it can run those tests against variety of  OSs, so we are sure that the Package is operational on every platform.

Let’s setup some cron first, so that our jobs are being triggered at least once daily. Since we can use the CI for unlimited amount of time for free (🤑), we can even schedule this to run every hour, but let’s pretend we are responsible adults here and just trigger this on each midnight passing.

Cron triggering all of the jobs daily at midnight

Also, we might want to switch from building using the swift command to the xcodebuild one, since it’s probably more widely known among iOS devs.

In order to make this change, we will need list of eligible destinations for the scheme we want to build and test. I am checking this for the Earendil scheme, since it’s the only one I have in my Package from the last tutorial.

Getting the valid destinations for your Package scheme

Depending on the output of this command, set up the testing on valid devices.

Running xcodebuild for one device from each OS

Step up your CI game with the Virtual Environments

Environment matrix for the Github Actions

Each time any job is triggered, Github Actions is spinning one of the virtual environments up to do the required job. Notice that there is actually a choice of macOS-based images there, like Catalina and Big Sur currently. I recommend taking advantage of that and creating a second job, which will test the Package using older macOS version (and thus use older Xcode version installed there). We don’t want any surprises for the people stuck on older IDE versions, right? #corpolife

Changing the environment is done through the runs-on for each job, like so:

So, let’s create a second job called build-catalina and make it test the Package using the macOS Catalina image, Xcode 12.0 and iOS 14.0. We want this job to be launched alongside previously created build job, every time CI is being triggered.

The final product should look like below. Note the added build-catalina step at the bottom of the snippet. We will fix the slight code duplication next time.

Complete build config for testing on Big Sur and Catalina as well

And thus, our code is not only being built and tested against different target OSs, but it also uses various macOS environments to do so. That’s very easy and very powerful! 🧠

Decide on optional platform restrictions

So, as you can tell, Github Actions offers really diverse testing capabilities for Swift Packages. However, this huge array of testing destinations may lead you to discover that some of them may actually not work as intended — especially destinations with older OS versions.

That’s not necessarily a bad thing — if your Swift Package depends on the APIs introduced only recently, then I think it’s justified to introduce platform restrictions. You can’t expect Package containing SwiftUI extensions to run on iOS 10, after all.

If you have found lower bounds of platform availability for your Package, it might be reasonable to revisit Package.swift file and put those requirements there. Myself, I tend to write a lot using SwiftUI and Combine APIs, so my platforms restrictions usually look like below.

Platform requirements for SwiftUI and Combine without having “ @available(iOS 13.0, *)" everywhere

And that’s it, you should be sorted out with the CI for your Package 🐙. You can check out the final result here.