I’ve started dabbling with Github Actions this week. Even though I’m a team of one — when it comes to code anyway — I know myself well enough to know I shouldn’t be trusted. I decided I should set up a server of some sort to build Peek‑a‑View when I commit new code to ensure I didn’t accidentally break anything.
Tangentially, this week I also split out some code that’s shared between
Vignette and Peek‑a‑View into its own library. This new common library
that Vignette and Peek‑a‑View will share lives as a private repository on
Github. Since it’s mostly
extensions and other small objects, I’m
working on getting decent unit test coverage on it. Since this
project lives on Github, like all my projects do, I thougth I’d use
a Github Action to build and test this shared project every
time I add code to it.
As it turns out, for Swift packages, this is extremely easy to do using Github actions. The default action works out of the box. To add an action:
- Go to your repo on Github on the web
- Click the
- Click the
- Find the
Swiftworkflow and click
Set up this workflow
- Customize it if required, and then click
Start committo commit this new
Easy peasy, and now I will get an email if I ever break my own build.
The obvious next step was to try to get Peek‑a‑View building using Github actions. Even though there aren’t any unit tests there yet, it would still be nice to have independent verification that my builds are working.
Unfortunately, thanks to the use of a couple of private Github repositories, that’s far easier said than done. I found some really hacky ways of doing it — in short, using a Github Personal access token — but that would require me to expose what is effectively a password in my own repo. Yes, a private repo, but still; that path seems undesireable.
That got me to thinking: what if i didn’t rely on Github for this? Especially since running builds on Github’s servers is not free if you do too many of them. If only there was a way to do these builds locally, so they’re free, and I can take some more shortcuts with regard to authentication.
Introduced in 2013, Xcode added a new feature: Bots. Xcode Bots are basically ways of performing continuous integration locally, on a server of your own control. Thankfully, I have a Mac mini for exactly these sorts of reasons.
Like most Apple documentation these days, the documentation leaves a lot to be desired. As it turns out though, it’s not hard to install. On the machine you want to serve as your server:
- Open Xcode
- Open Xcode’s preferences
- Select the
Server & Botstab
- Unlock using the 🔒 at the bottom-left
- Flip the switch in the upper-right and follow the prompts
Then, on the machine you use to develop:
- Open your project/workspace
- Follow the prompts; you should find that your server is auto-discovered using Bonjour
This worked really well and quickly for Peek‑a‑View. So far so good.
Figuring it can’t hurt to have a little redundancy in my life, I decided to try to repeat the process for my shared library. And then I immediately hit a wall.
Bots and Swift Packages
The shared library was created as a SPM package using Xcode 11. Through some
sort of magic, when I open the folder the package is in using Xcode, it seems
to create a sort of anonymous project/workspace for me to use to build and test
the package. There is no
xcodeproj on the filesystem — at least, not
one that I’ve seen.
So I opened up this phantom project, and tried to add a Bot for it the same way that I did for Peek‑a‑View. When the Bot attempted to build it, I kept getting errors about how it couldn’t find a project or workspace.
After some fumbling about, it occurred to me that there isn’t a project nor workspace checked into Github, and the first thing the Bot does is pull down the source from Github. Annoying as it was, the error was correct: there wasn’t a project nor workspace. Unfortunately, the Bot isn’t capable of the same magic incantation Xcode is for Swift Packages; it needs a file on the filesystem to load.
SPM packages don’t generally have projects/workspaces, so I wasn’t sure what
to do. Then I had an
On the command line, I could have SPM create a project for me. When I am in the root of my package:
swift package generate-xcodeproj
This drops a file on the file system. My shared library is called
thus the above will drop
Macma.xcodeproj right where I’d expect it.
I then closed the copy of the phantom
Macma project, and opened the one
that I just created. Using this project — the one created by the
swift package command — I created a Bot. That was an improvement,
but I wasn’t out of the woods yet.
The good news is that the Bot knew which project to look for, but the bad news is that it still isn’t there, because it’s not checked into Github. Now what?
Easy mode would be to just check in that new
Magma.xcodeproj file into Github,
but that felt redundant and wasteful. Perhaps there was another approach?
I quickly realized that I needed to have the Bot generate its own
every time it did a run (an
Integration in Bot parlance). That’s easy enough: I
just needed to add a trigger.
Back in Xcode, in the
Macma.xcodeproj project, I edited my bot. The final tab
in that dialog is
Triggers. There, I added a new
Pre-Intergration Script, which
Prepare Project. The contents of that trigger are as follows:
#!/bin/sh cd ./Macma swift package generate-xcodeproj --enable-code-coverage
Now, every time the trigger is run, before the build/test process begins, Swift
Package Manager will re-create the
xcodeproj dynamically. By the time the
build/test starts, it’s there and waiting.
I certainly could check this into Github and make things easier, but I rather like having it dynamically created every time, ensuring the repo remains “pure”.
Now I have Xcode bots running for both Peek‑a‑View and Macma. If I want to, I could even set up an iPad with this snazzy Xcode Bots status page:
In a perfect world, I’d prefer to have a Github action for this, to further independently verify all is kosher, but I’m really pleased to have this new tool in my [local] arsenal.