Adding Continuous Integration to a Swift project
Continuous Integration (or CI for short) is one of those buzz words that have become really popular lately. It’s the idea that instead of having long-lived feature branches that you use to work on a new feature, you continuously integrate your new code into the project’s main branch (which is usually
develop if you work with Git). Every time you do so you build the project and run its tests, to make sure everything is in order before merging the change in.
This week, let’s take a look at how we can use CI to establish a smooth and nice workflow for our team working on a Swift project.
CI is done on a server that is separate from any team member’s working machine. This adds a layer of “quality assurance” (no more “It works on my machine!” arguments) and makes sure that the project builds on a “clean” setup — which has the added benefit of making on-boarding new team members easier.
The most common options for CI solutions
These are the most commonly used solutions for running a CI server that’s used for Swift projects today:
- Travis CI, which is a cloud-based solution that gives you a VM to build your code in. You pick your environment and then you run your build commands using tools like
swift build, or
fastlane. It’s free for open source projects, and there’s also several paid tiers for closed source ones.
- Circle CI, very similar to Travis in that they also offer a cloud-based VM for you to build in, and give you access to a command line where you can run your build tools. However, it’s not free for open source projects (unless you’re only building on Linux) — but they do offer a 2 week free trial.
- Bitrise, which takes a slightly different approach than both Travis and Circle, in that they also offer a web-based UI to set up CI pipelines, as well as automated setups when adding a new project. Bitrise is free for both open source projects and private ones, as long as they take less than 10 minutes to build.
- Jenkins, if you’re the kind of person who likes to build things yourself (no shame in that, it can make for an interesting weekend project 😀), Jenkins is usually the way to go. It’s basically a package you install on your own hardware (typically a Mac Mini in someone’s closet), which gives you a CI server you can configure to your heart’s content.
- Xcode Server/Bots is Apple’s official way to do CI for Swift & Objective-C projects. It does provide some sweet integration with Xcode, and has a very nice UI, but I’ve yet to hear anyone deploy it successfully on any significantly complex project 😅 Please let me know if you’ve heard otherwise though! 👍
Picking the right solution
So, which solution is the best one? Like most tech decisions, it comes down to applying a healthy mix between requirements and personal taste.
The good news is that almost all of the above mentioned solutions have a free tier, so before fully investing in any given solution — you can always try a few of them out to see which one fits your project best.
But, I do want to provide a quick guide to setting some of these solutions up - so for the purpose of this post I’m going to focus on Travis and Bitrise. They are interesting to compare, because they take very different approaches, and are easy enough to setup that I can recommend them to anyone, irregardless of how much you like to tinker with these type of things.
So, let’s dive in! 🚀
Like mentioned above, Travis essentially gives you a command line to run your build tools on. Configuration is done through a
.travis.yml file that you put in your project’s repository, and once you sign up for Travis and activate it for your project, it will automatically pick up any such
.yml file and run your CI based on it.
Here’s an example of a simple
.travis.yml file that uses
xcodebuild (which is the command line interface to Xcode’s build system) to build and test an iOS project:
language: objective-c osx_image: xcode9 script: - xcodebuild clean test -project MyApp.xcodeproj -scheme MyApp -destination "platform=iOS Simulator,name=iPhone 7,OS=10.3" CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO ONLY_ACTIVE_ARCH=NO -quiet
The above command is a bit of a mouthful, so let’s break it down:
xcodebuildto first clean the project (you often want to start from a clean slate in CI, to avoid false positives and flakiness) and then build it and run its tests.
-project MyApp.xcodeprojis where we supply the path to our Xcode project (if you’re using CocoaPods, you’d use the project’s Workspace instead, and supply it using
-scheme MyAppis which scheme you want to build and test. Make sure you share the scheme by ticking the “Shared” box in the “Manage schemes” dialog in Xcode, to make the scheme accessible to CI.
-destinationargument and the string that follows it tells
xcodebuildwhich platform we want to run on. In this case we pick an iPhone 7 simulator running iOS 10.3.
CODE_SIGNING_REQUIRED=NOallows us to override the code signing settings that we have set up in Xcode. This is very useful, since it enables our CI server to build & test without code signing, while still not modifying our Xcode project.
ONLY_ACTIVE_ARCH=NOmakes sure that we build for all architectures, not only for the iOS simulator. This is to make sure we don’t have any architectural-specific build errors in our project.
-quietreduces the otherwise super-verbose output of
xcodebuildto only include warnings and errors. This is very nice for when you need to dig into the output of any build log for debugging, so that you don’t have to go through thousands of lines of output.
There are of course alternatives to using
xcodebuild directly, like using
fastlane (both of which come pre-installed on any Travis macOS VM). In the case of
fastlane you can simply execute a given lane that you have defined in your
Fastfile, making your
.travis.yml look something like this:
language: objective-c osx_image: xcode9 script: - fastlane MyTestLane
Even though Travis requires you to set up things manually, and it can be a bit unstable and slow sometimes (especially for open source projects, but hey, it’s free 😅), I really like it. It is widely used in the community, which means that there’s tons of help to be found in case you can’t figure something out.
Now, let’s take a look at Bitrise, which I should disclose is a frequent sponsor of Swift by Sundell. However, I’ve personally used Bitrise since long before they became a sponsor, and this article wasn’t paid for by them in any way.
Like mentioned in the overview above, Bitrise can automatically infer recommended build settings when adding a new project, rather than requiring you to set up configuration files in your repo. Most developers (including myself) will probably be very skeptical to this approach at first, we know how hard inferring things like this correctly can be, so you might think that it’ll be extremely flaky and error prone.
But it turns out, Bitrise works amazingly well. From my own personal experience with the platform, Bitrise is the most stable CI platform that I’ve used so far.
For iOS apps, there’s not much of a tutorial needed in order to get started with Bitrise. Their UI will walk you through setting things up and will ask you for any information required. It “just works”. But, sometimes you do need/want to dive in and customize things with your own build scripts. When I set up Splash with Bitrise, I did find myself in need of doing such customization, since it uses the Swift Package Manager as its build system.
So, here’s how to easily set up a Swift Package Manager-based project using Bitrise. Within the workflow editor for the project in question, simply add a Do anything with Script step, with the following content:
#!/usr/bin/env bash # fail if any commands fails set -e # debug log set -x swift test
And that’s it! 🎉
As you can probably tell from reading the above, I’ve become quite a big fan of Bitrise. I especially like how easy it is to setup and manage, especially when dealing with many projects like I do — both for commercial projects and when doing open source. Eventually I’ll probably move all of my projects over to it, but for now I’m also using Travis quite a lot and am quite happy with that too.
But I do encourage you to find out for yourself. Try, experiment, and share your findings! 👩🔬👨🔬 I’m not using CI for all of my projects, but for any that at least has more people than just me working on it, I definitely think it’s worth the effort setting it up. Having that additional check to make sure that your project remains in a buildable and testable state is super valuable, especially when working in a team.
Do you have any favorite way of doing CI that I didn’t mention in this post, or any other questions, comments or feedback? I’d love to hear from you! 👍 Feel free to contact me on Twitter or email.
Thanks for reading! 🚀