For the past couple of weeks I’ve been researching, setting up and comparing Continuous Integration systems for an Xcode iOS Development project.
This article is about this journey and a subjective comparison between three major CI/CD solutions; Jenkins, CircleCI and Bitrise.
What is Continuous Integration (CI)
There are different types of releases a software developers must do when new features are being developed or bugs are fixed. Each release is mostly the same software with a difference of release frequency and/or target audience.
The main types of releases of a software product typically are as follows:
- internal testing release (~daily)
- company-wide internal release (~every week)
- public beta release (~once every two weeks)
- production release (~once a month)
For each release there’s a bunch of repetitive tasks that are needed to be done, to get a build out to the audience. These steps are usually as follows:
- pull the latest code from a repository
- update third party libraries
- increase build and version number
- run unit tests (possibly on different screen sizes)
- run UI tests (on different devices)
- compile app
- publish to target audience
It doesn’t seem much at first to get a build out, but someone has to do it and the time eventually adds up. Especially for a bigger software product with many stakeholders.
This is where Continuous Integration comes to play. CI is a software product release automation system.
It’s a software that has been given a mission to release other software.
Surely there’s plenty more maintenance tasks that a good CI setup can do. Pretty much any repetitive task in software lifecycle could be delegated to a CI bot.
Mission to find a suitable solution for iOS development
So recently I got a task from a client to find the best CI solution for iOS app development project. We had just published the first version of the app and now needed to automate aforementioned build/release steps. To get things interesting I had an ideal solution in my head. An ideal CI solution wouldn’t interfere with our project settings, its folder structure nor with work of other developers working on the project.
Just to note here, we were using Git Flow for source control strategy.
As the company used Jenkins server for their web related releases, then I started my research from there.
Jenkins is a universal, open source CI platform. It’s probably the most popular one because it’s free and it’s reliable. Another good (and not so good) thing with Jenkins is that it’s highly configurable. One can pretty much automate any software task with this tool, but as it’s highly configurable, one can create a huge mess with it as well.
As it’s been around forever, there’s a myriad of plugins/extensions to Jenkins to help you out. Plugins are a pieces of user interface that help you set up pipelines. A pipeline is one group of automated tasks.
I managed to install Jenkins server on my computer quite easily. You’ll surely need to know your way around MacOS Terminal to do it, but it’s not rocket science.
When installed I followed a tutorial to set up a pipeline using various plugins. It took a bit of time to set things up, as the Xcode plugin has many complex settings and options. The fact that error messages and option descriptions are rather cryptic doesn’t help much either.
However, after a few days I got it set up. This meant that when a Pull Request was merged to develop branch in our Github repository, then a pipeline was ran on Jenkins.
The pipeline went through all the steps to automatically test and release a version of iOS app to App Store from my machine. It was working like magic.
There were a few problems though.
1. Complicated pipelines
First, the various plugins that I had to install to do this were complex. This meant, when someone other than me, would later need to change something, it would need a lot of time to dig through the setup.
Also installed plugins often executed commands that might not be necessary every time.
This problem could probably be solved by using as few plugins as possible by writing the necessary/possible steps as Shell commands. That in turn would make steps easier to follow, but the command structure would still need a fair bit of concentration to be comprehended by other people than me.
2. iOS needs Xcode to compile
Second, iOS apps need a mac machine running Xcode to compile a build. Clients Jenkins server was not a MacOS machine.
The way other Jenkins users have handled this problem is having a dedicated Mac Mini machine connected to network running in the corner of the office somewhere. But this machine would need constant software updates, maintenance and care, so it was a no-go at this point.
Alternatively there are also “mac in a cloud” kind of services out there that would do the trick, but they cost money and they too need maintenance and care from us.
So considering everything, while Jenkins can be an option, it comes with too much of a burden for iOS development.
I fell in love with this service the minute I registered with them. It’s a CI solution integrated with Github. It can run CI pipelines on pull requests and will show the outcome right there in Github.
Circle CI will add a configurable .yml file to our repo that defines the tasks needed to test/release builds. Everything can be configured via this config file right then and there and developers don’t need to log in or install anything… Or so it seemed.
While the above might be the case if you’re releasing web apps, for complicated steps needed for iOS build release, we need an extra bit of system added to the project setup; Fastlane.
Fastlane itself is not a bad thing. It’s a nice little app development automation tool. It can help with many iOS development tasks. Especially if you work with a team. It’ll help to get provisioning profiles and certificates right for the whole team. It’ll also set up a project automagically when configured correctly. It will even work as a CI if you just need to test/release apps from your development machine.
So to get stuff working using CircleCI we end up with two new folders (CircleCI and Fastlane) and various files in our repository. Also The project needs a slight modification in its setup to make codesign work better. For certificate and provisioning profile management it’s recommended to set up a separate repo.
All these mean disturbance for the other developers working on a project and a bit too much hassle setting it all up. Also we have a problem with other developers understanding how it all works when I’m not around.
While it’s not a showstopper, but to compile on a Mac machine in CircleCI costs money to use.
As much as I would want it, CircleCI was not a perfect match for this project needs. It can work nicely as a CI for Xcode project when set up from a get go. But when we have a working team carrying out everyday development task, it’ll be a bit of a disturbance to include into a project underway.
Bitrise was the next CI option that I found appealing. It’s a CI platform especially dedicated to Mobile projects.
Bitrise pipelines are called workflows. A workflow is a node based chain.
A node is a configurable action.
There are plenty of useful nodes to choose from. Then there are triggers, that decide when to run a workflow.
Finally all this witchcraft boils down to one .yml configuration file that you can edit as text if you feel courageous enough.
Bitrise is especially proficient with confusing Apple certificate and provisioning profile generation. When in Jenkins and in CircleCI I had to generate them myself. Bitrise has a command line app for this. Just copy-paste a line into a Terminal on your development machine while on project directory and it’ll prompt you for a few questions about your project and app scheme. Finally it’ll generate all the required certificates and profiles for you.
You can then copy-paste those into the workflow and you’re done.
Same thing with connecting to Github repository. Just push a button and it’ll create SSH keys to pull your most recent project updates from the desired branch.
I had slight difficulties setting up App Store publishing node, but it turned out I was using a wrong node for that.
When I ran into trouble like the latter I had a discussion group supporting me troubleshooting my problems and even a “let’s chat” screen in the corner of my browser window (that I didn’t need to use).
Compared to Jenkins and Circle CI taking days going through documentation and setting things up bit by bit, with Bitrise I had a full blown working pipeline set up in ~4 hours. This pipe is pulling recent project from Github, executing pod install, running unit and UI tests, increasing build number, compiling and codesigning an App Store release .ipa and uploading it to our appstore connect account. In addition, I can make it to submit it for review and do several other automations if I wanted to.
Talking about magic then this is pretty close to it in my book.
While Jenkins is nice and configurable, open source and free, it really is not an “out of the box” solution for iOS Development.
Circle CI is something that has done a lot right with Github integration and one config file premise. Unfortunately it can’t do iOS deployment automation without a fight.
Needless to say, my vote goes for Bitrise, which does everything right. I convinced my client to choose it as well and we’ve been distributing builds with it to everyone since then.
This article ended up as a praise for Bitrise, but rightfully so. I would also like to note that other than using it myself, I’m in no way affiliated with them. Also note that there are other possibly great CI/CD solutions that I didn’t try. Please do write comments below if you’re using some that you think is worth an inspection.
If this article helped you make a decision towards Bitrise, then I would be glad if you’d use the referral link below to register with them.