CI for Open Source iOS Applications, Part 1: Setup
This guide will walk you through setting up a Jenkins continuous integration server for your open source iOS project. By the end of the guide, you will be able to add an image to your README indicating the build status of your repository's master branch.
Step 1: Provisioning a CI server
Jenkins on the Cloud
In order to provide developers with feedback on the state of the build, you will need a publicly available Jenkins instance. Normally, this would mean hosting an instance on EC2, setting up permissions to prevent anonymous users from messing with configurations, and other hassles. You can avoid these headaches by using a PaaS such as CloudBees (note that I'm not affiliated with the service in any way, other than being a extremely satisfied user), which gets you up and running with Jenkins after a simple registration process. This guide will cover setup using CloudBees.
Free OSS Account
CloudBees Jenkins instances are private by default. In order to get a publicly available one, you'll need to register for something called a FOSS account.
After registering, you can check out your Jenkins instance at {{ your_domain }}.ci.cloudbees.com
.
Set up a Slave
The Jenkins instance hosted on CloudBees will show other developers the state of the build, but the actual execution has to be on an OS X machine with Xcode installed. In other words, we'll need a slave to run our test suite. You can set up a slave by following CloudBees' instructions on customer provided slaves.
In summary, you'll have to add your RSA key to your CloudBees settings, then download the jenkins-cli.jar file and use it to connect to your Jenkins instance, like so:
java -jar jenkins-cli.jar -s https://yourdomainname.ci.cloudbees.com -i ~/.ssh/id_rsa customer-managed-slave -fsroot ~/.jenkins/cloudbees -labels xcode_4_4 -executors 1 -name modocache-macbookpro-2009
The labels parameter is a tag associated with your machine (more on that in the next section).
Once you have your slave connected, you should be able to confirm it is connected at https://yourdomainname.ci.cloudbees.com/computer/ . After making sure it is connected, continue to the next step.
Get a Job!
The next set of instructions will cover the necessary steps in setting up a job on Jenkins. Readers familiar with Jenkins might find this part a bit boring.
Navigate to your Jenkins dashboard and click New Job from the menu on the right
Enter your job name (preferably the name of the repo) and select the Build a free-style software project option. This option is the most open-ended, and best suits our purposes, as all our job will do is execute a shell script.
After being redirected to your job page, click Configure from the menu on the left.
Enable the Restrict where this project can be run option and set Label Expression to
xcode_4_4
(we want to make sure we are testing on the right environment, in this case an environment with Xcode 4.4 installed, which we will labelxcode_4_4
)Set Source Code Management to git, and specify the repository URL as the git read-only URL for your repository. Make sure to set Branches to build to the branch you will be adding your test script to. This guide will assume the branch name to be feature/jenkins-ci.
Add a build step of the Execute shell variety, with the value ./Scripts/jenkinsBuild.sh $BUILD_NUMBER (we will write this script in the next step)
Enable Archive the artifacts as a post-build action, setting Files to archive to
dist/*
(our Jenkins script will package app binaries at this location)
Basically, all we need Jenkins to do is run a shell script we will create in our project. The next step is actually writing the script.
Step 2: Automating the Build
The Build Script
Our build script will build and package all schemes defined in the project, then run integration and unit tests. Here is a template you can use as a starting point for creating your own. Make sure you create it at the same path as the one you specified in your Jenkins configuration (in our case, Scripts/jenkinsBuild.sh) It assumes you are using CocoaPods for dependency management, as well as KIF and Kiwi for your integration and unit tests, so let's set those up.
Integration Tests with KIF
- Set up integration tests as a separate application target, exactly as outlined in the documentation.
- Make sure the target name specified in the runintegrationtests function of the build script matches the name of your integration test target
Unit Tests with Kiwi
Running iOS unit tests via the command line is made significantly more complicated by the fact that xcodebuild
does not take a test
argument. Thankfully, Peter Jihoon Kim has written an expertly crafted post on the matter. Follow his instructions and you should be golden. Again, make sure the test target name matches the one being used in rununittests in the build script.
Other Items of Note
xcodebuild
can only build schemes that are "shared". You can share schemes via the Manage Schemes tab in Xcode:
The build script creates a build and dist directory in the project root directory. Be sure to add these to your .gitignore.
Step 3: Build, baby, build!
Testing Locally
Test out your automated test suite by entering the root of your project and running ./Scripts/buildJenkins.sh 0. If everything goes well, you should see a ton of build output, followed by the output from your integration tests, followed by your unit tests.
(Tweet or email me if you run into any issues, preferably with details on how you solved those issues, and I will add them here.)
The script will exit with a status code of 0 if everything goes well, and a non-zero value otherwise. Jenkins will use this value to mark the build as pass/fail.
Executing the Build on a Jenkins Slave
Once everything works locally, push to the branch you specified in your Jenkins configuration. Make the slave you set up is still connected and click Build Now on your project. The build will now execute on your machine.
Jenkins will queue jobs to run on your machine. These will run in order once you or another slave connects. The idea is to have many machines dedicated to this task, in order to make sure tests are run as soon as they are enqueued.
Step 4: Adding the embeddable-build-status Plugin
Jenkins has tons of plugins. Only a subset of these are available on the Free OSS plan on CloudBees, but that subset includes embeddable-build-status. This plugin adds a route to your Jenkins instance, which you can access for an image displaying the current build status.
In order to add this plugin, navigate to Manage Jenkins > Manage Plugins > Available on your Jenkins instance, check the embeddable-build-status plugin, and click Download now and install after restart.
Your Jenkins instance will be unavailable during restart (any connected slaves will be disconnected as well), but after a minute or so you should be able to see a link to Embeddable Build Status in the sidebar on your job page. Add this URL to you project's README on Github, and voila! You now have an image displaying the build status of your project.
You can set up polling or a post-commit hook in order to make sure this status updates every time a change is pushed to your repository, allowing all developers to know immediately when something's funky with the build. Now that's continuous integration!
In Conclusion
I hope you've enjoyed this guide. Let me know if you run into any issues that you cannot solve yourself, or if you'd like to suggest a change to this post.
Also, stay tuned for Part 2: Pimping Out Your CI Server!