Adding GitLab CI Support
I recently made a post detailing several continuous integration systems and the one I ultimately decided on: GitLab CI. Today, I’m pulling another one of my projects over to the new service, and I thought I’d put together a play-by-play on what exactly I’m doing to build and deploy this project with GitLab CI.
About the Project
The project in question is Scrawl Notes for iOS. It’s a relatively simple app with relatively simple requirements. It has a single dependency1, which is managed by Carthage. There are two test targets (both UI and unit tests)2, and I’d like to run them both whenever I push to my GitLab server. Finally, I deploy this app to beta testers via Apple’s TestFlight, so I’d like to automatically upload builds there when I push to my
Getting all of this set up on another project was made way easier with Fastlane, so I’m going to be using that here again. Scrawl Notes isn’t using Fastlane yet, so adding that will be the first step.
$ fastlane setup
As part of the setup process, Fastlane asks me for my Apple ID. I have a separate Apple developer account just for my continuous integration systems. This is mainly so that if it goes completely haywire, at least it’s limited to only my work, and not my employer’s or any of our clients’.
After setup completes, Fastlane has created a couple of files for me:
$ git status On branch 1.2-release Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: fastlane/Appfile new file: fastlane/Deliverfile new file: fastlane/Fastfile …
The main one I care about here is the
Fastfile, which describes all the tasks I want Fastlane to run. The default generated file has a ton of stuff in it, and I don’t want all that right now. Here’s what I’ve got instead:
This file describes two lanes. The first is
test, which simply runs all the tests in the “Scrawl” target (my main app target). The other is
beta, which does quite a bit more:
- Downloads my signing certificate and provisioning profile from the Apple Developer portal.
- Disables Xcode’s “automatic signing” feature.
- Edits the project file to add the signing credentials downloaded earlier.
- Updates the build number to the number of Git commits that have been made so far.
- Builds the app.
- Uploads the built products to iTunes Connect, but does not automatically submit it for TestFlight release.
Unfortunately, this is where I hit a few snags. Something I forgot to mention about the project above is that while it’s a simple app project… the app isn’t the only target. Embedded within the Scrawl Notes app are
three3 two app extensions: a Today widget and a Siri intent. It was these that gave me some problems; they weren’t getting signed with the appropriate provisioning profiles for each. Ultimately, I added two extra lines for each extension target:
This did the trick. I’m not sure if it’s the most appropriate way to handle this case, but it seemed like the most straightforward.
At this point, I have a working Fastlane setup with two commands to run:
fastlane test will run my tests and
fastlane beta will build the app and upload it to TestFlight. However, that’s only available locally; GitLab won’t run these commands for me when I push to my repository.
In order to get GitLab to run CI commands when I push, I have to include a file named
.gitlab-ci.yml in the root of my repository. Here’s what that file looks like:
This defines two CI stages:
beta. Those names should look familiar: they’re the same as the Fastlane lanes we defined above. They have the same behaviors:
test runs the tests, and
beta uploads to TestFlight.
We specify a few behaviors for each stage:
artifactsis the list of files we want GitLab to keep around from each build. We keep any screenshots taken during the build, the logs of the build, and (when applicable) the results of the test runs.
scriptis what command we want run. We simply run our Fastlane commands, exactly as we would from the command line.
tagsdefines what GitLab “runner” we want to have run our commands.
xcodeis my Mac that does all my iOS builds. If I wanted to, I could have several different machines4 all tagged
xcode, and GitLab CI would pick the first available free runner for me.
onlyis specified on the
betastage. This tells GitLab CI I only want this stage to be run which a push is made to the
masterbranch. It doesn’t make sense for me to upload builds for unfinished feature branches.
One quick commit and push later, and my build is running! Followed quickly by not running and a big red X indicating a build failure. Good thing we kept those logs!
▸ CloudCoordinator.swift:8:8: no such module 'Majima' ▸ import Majima
…oops. Remember how I said this project had only one dependency, and that it was managed by Carthage? I’ve forgotten to run Carthage! Fortunately, this is easy to fix. Let’s add a single line to each
Just like that, we’ve turned that red X into a green checkmark!
There you have it: a blow-by-blow recap of setting up an iOS project for GitLab CI, complete with stupid mistakes and code signing issues! I’ve posted my final
gitlab-ci.yml as Gists on GitHub, so go take a look if you want to copy them for your own usage.