I’ve worked with a bunch of different automated iOS build systems over the years at the various companies I’ve worked for and with my own apps. In the early days of the App Store, many of these were completely home grown. As the toolchain matured, I’ve dealt with Xcode bots as well as dedicated SaaS companies that provide build farms like Microsoft App Center and Bitrise. I’ve also had the horrible misfortune of being tasked with maintaining a dilapidated, Frankenstein of a Jenkins installation that talked to an underpowered Mac mini over a shoddy VPN connection.
What I’ve learned from all those setups is that as useful as they are, they’re generally a bitch to maintain once they reach even a moderate level of complexity. So I tend to shy away from them until there’s a real need.
Over the last few weeks at my current job, that need has presented itself in two ways.
- We added a watchOS target to an existing app already in the App Store. For reasons I don’t completely understand even after hours of debugging, it completely broke my co-worker’s ability to submit builds to App Store Connect. We did a fresh clone of the project, blew away Derived Data, deleted and reinstalled every certificate and provisioning profile. Nothing worked until it seemingly fixed itself about a week later for no apparent reason.
- For reasons I don’t want to (and can’t) really go into, we have about fifty WiFi networks broadcasting through our small office space. Many of them physically moving around at different times. That, plus what we think is a shitty Comcast modem, means the WiFi we actually connect to randomly fluctuates between passable, to mostly broken, to everyone just gives up and tethers to their phone. The result being that it can take multiple tries and multiple hours to successfully upload our 250MB .ipa to App Store Connect – if it even works at all.
The solution to these two problems? An automatic, repeatable way to produce builds located somewhere else with a good network connection.
Like at many companies, our executives don’t want to use a 3rd-party build service because they don’t want our source code in someone else’s control. So that meant we needed to build something ourselves. And while we may eventually pony up for a hosted Mac mini somewhere, for now during this just-get-it-working-phase, I decided to go the pragmatic route and setup a build system on my (mostly idle) iMac Pro at home that sits behind a very nice Comcast Business connection.
I’m not a DevOps expert. And I’m certainly not an expert when it comes to the thousands of arcane Xcode build settings. But over the years I’ve become very good at diagnosing code signing issues and scripting various bit and bobs together on macOS.
So I spent a couple nights piecing together a straight-forward, stupid-simple, build script that does exactly the minimum necessary to accomplish our goals. Those being 1) the ability to execute a reproducible build on-demand, and 2) automatically build, sign, and submit to Apple on every commit to a specific release branch.
For a manual build, you pass the script a JSON file containing various build settings, and it builds the project and then (optionally) submits to Apple.
For automatic builds, I’ve included a sample
.plist that checks for new commits every minute and, if any are found, kicks off the build process.
Oh, and as the build progresses through all the various steps, the script can optionally update you with its progress in the Slack channel of your choosing. Even better – and I’m quite proud of this – if an error occurs, it will post the full
stderr log files as Slack attachments so the whole team can immediately debug and see what went wrong without having to SSH into a remote build server.
I think the whole setup is really great. It suits our needs perfectly. I make no claims about it being the right choice for your situation, or that I even did anything remotely unique / interesting when I pieced it together. There are a thousand build scripts out there; this one happens to be mine. I guess what I’m saying is please don’t make fun of my shell scripting abilities.
Check out the README for more details and usage instructions.