Have you ever tried to setup a continuous integration server for iOS? From my personal experience it is not easy. You have to organize a Mac and install software and plugins. You have to manage user accounts and provide security. You have to grant access to the repositories and configure all build steps and certificates. During the project lifetime you have to keep the server healthy and up-to-date.
In the end you will spend a lot of time maintaining the server – time you wanted to save in the first place. But if your project is hosted on GitHub there is hope: Travis CI – continuous integration as a service. That means they are taking care of all the hosting aspects. In the Ruby world they are well-known already. Since April 2013 they support iOS and Mac as well.
In this article I want to show you how to setup your project for Travis. This does not only include building and running unit tests, but also shipping the app to all your test devices. For demonstration purposes I put an example project on GitHub. At the end of this article I will give some tips on how to tackle problems with Travis.
Please note that this is a more up-to-date version of my original article on objc.io from November 2013. Feel free to comment on this article if you have questions.
Update 2014/04/07: I corrected some sloppy mistakes I made in the code snippets. This was mostly affecting the encryption part. A big thanks to my colleague Jalda who was pointing them out.
Update 2014/04/19: Added hint regarding encryption of strings that contain special characters. Thanks to Jann Driessen for pointing it out.
What I really like about Travis is how well it is embedded in the GitHub web UI. One example is pull requests. Travis will run the build for each request. The pull request on GitHub will look like this if everything is ok:
In case the build doesn’t succeed the page will be colored accordingly on GitHub:
Link Travis and GitHub
Once signed in you have to enable your project for Travis. Navigate to the profile page which lists all your GitHub projects. Please note, when you create a new repository later use the
Sync now button. Travis only updates the list occasionally.
Enable your project now by using the switch.
Minimal Project Configuration
Travis CI needs some basic information about your project. Create a file called
.travis.yml in the project root with the following content:
Travis builds run in a virtual machine environment. They are pre-configured with ruby, homebrew, cocoapods and some default build scripts. The above configuration should be enough to build your project.
The pre-installed build script analyzes your Xcode project and builds each target. The build succeeds if everything compiled without error and the tests didn’t break. Push your changes to GitHub and see if the build succeeds.
While this is really easy to setup it might not work for your project. There is not much documentation on how to configure the default build behaviour. I had for example code signing issues because it didn’t use the
iphonesimulator SDK. If that minimal solution doesn’t suite you let’s have a look on how to use Travis with a custom build command.
Custom Build Commands
Travis builds your project from the command line. Therefore the first step is to make your project compile locally. As part of the Xcode Command Line Tools Apple ships
Open your terminal and type:
This should list all possible arguments for
xcodebuild. If it fails make sure the Command Line Tools are properly installed. This is how a typical build command would look like:
iphonesimulator SDK is set in order to avoid signing issues. This is necessary until we include the certificates later. By setting
ONLY_ACTIVE_ARCH=NO we make sure that we can build for the simulator architecture. You can set additional attributes (e.g.
man xcodebuild to read the documentation.
CocoaPods projects you have to specify the
Schemes get created automatically with XCode but this won’t happen on the server. Make sure to declare the schemes as
shared and add them to the repository. Otherwise it will work locally but not on Travis CI.
.travis.yml for our example project would look like this now:
1 2 3
Please note that the rvm environment is explicitly set to
1.9.3 because the new default environment
2.0.0 does not have
CocoaPods pre-installed. You could also add
before_install: gem install cocoapods instead.
Usually for testing you would use a command like this (note the
xcodebuild doesn’t properly support test targets and application tests for iOS. There are attempts to fix this problems but I suggest using Xctool instead.
Xctool is a command line tool from Facebook to make building and testing easier. The output is less verbose than with
xcodebuild. It can create a nicely structured and colored output instead. It also fixes the problems with running logic and application tests.
Travis comes with xctool pre-installed. To test it locally, install it with homebrew:
The usage is really simple as it takes exactly the same arguments as
Once we get these commands working locally it’s time to put them in our
1 2 3 4 5
What we looked at so far is enough to use Travis for library projects. We can ensure that it compiles properly and tests pass. But for iOS apps we want to test on a physical device. That means we have to distribute it to all our test devices. Of course we want to do this automatically using Travis. As a first step we have to sign our app with an apple distribution certificate.
In order to sign our app on Travis we have to create all necessary certificates and profiles. As every iOS-Developer knows, this might be the most difficult step ;–). Afterwards we will write some scripts that do the signing on the server.
Certificates and Profiles
1. Apple Worldwide Developer Relations Certification Authority
Download it from the Apple page or export it from your Keychain. Save it in your project under
2. iPhone Distribution Certificate + Private Key
Create a new distribution certificate if you don’t have one already. Therefore go to your Apple Developer Account and follow the steps to create a new certificate for Production (
App Store and Ad Hoc). Make sure to download and install the certificate. Afterwards you should find it in your Keychain with a private key attached to it. Open the application
Keychain Access on you Mac:
Export the certificate into
scripts/certs/dist.cer by right-clicking the item and choose
Export.... Then export the private key and save it into
scripts/certs/dist.p12. Enter a password of your choice.
As Travis needs to know this password we have to store it somewhere. Of course we don’t want to save it as plain text. Therefore we can make use of the Travis secure environment variables. Open terminal and navigate to your project folder that contains the
.travis.yml. First let’s install the Travis gem by running
gem install travis. Afterwards you can add the password by executing:
This will add an encrypted environment variable called
KEY_PASSWORD to your
.travis.yml. It can then be used in any script executed by Travis CI.
3. iOS Provisioning Profile (Distribution)
If you don’t have already create a new distribution profile. Based on your account you can either create an Ad Hoc or In House profile (
Provisioning Profiles >
Ad Hoc or
In House). Download and save it under
As we have to access this profile from Travis we have to store the name as global environment variable. Therefore add it to the
.travis.yml global environment variables section. If the file name would be
TravisExample_Ad_Hoc.mobileprovision add this:
1 2 3 4 5
There are 2 more environment variables declared. The
APP_NAME (line 3) is usually the same name as your main target. The
DEVELOPER_NAME (line 4) is what you see when checking the XCode
Build Settings of your main target under
Code Signing Identity >
Release. Search for the
Ad Hoc or
In House profile of your app. Take the text part that is in black letters. Depending on your setup it may or may not include a code in brackets.
Encrypt certificates and profiles
If your GitHub project is public you might want to encrypt your certificates and profiles if they contain sensitive data. If you have a private repository you can move on to the next section.
First we have to come up with a password that encrypts all our files (the secret). In our example let’s choose “foo”. But you should come up with a more secure password for your project ;–). Please note that you have to escape (some) special characters as they will be interpreted as bash statement otherwise. See the official docs for more information on this matter.
On the command line encrypt all 3 sensitive files using the password and
1 2 3
This will create encrypted versions of our files with the ending
.enc. You can now remove or ignore the original files. At least make sure not to commit them, otherwise they would show up on GitHub. If you accedentially commited or pushed them already get some help.
Now that our files are encrypted we need to tell Travis to decrypt them again. For that Travis needs the secret. We use the same approach that we used already for the
Lastly, we have to tell Travis which files to decrypt. Therefore add the following commands to the
before-script phase in the
1 2 3 4
With that your files on GitHub will be secured, while Travis can still read and use them. There is only one security issue that you have to be aware of: You could (accidentally) expose a decrypted environment variable in the Travis build log. Note however that decryption is disabled for pull requests.
Now we have to make sure that the certificates get imported in the keychain on the Travis server. Therefore we should add a new file
add-key.sh in the
1 2 3 4 5 6 7
Here we create a new temporary keychain called
ios-build that will contain all the certificates. Note that we use the
$KEY_PASSWORD here to import the private key. As a last step the mobile provisioning profile is copied into the
After creating this file make sure to give it executable rights. On the command line type
chmod a+x scripts/add-key.sh. You have to do this for the following scripts as well.
Now that all certificates and profiles are imported we can sign our application. Please note that we have to build the app before we can sign it. As we need to know where the build is stored on disk I recommend specifying the output folder by declaring
SYMROOT in the build command. Also we should create a release version by setting the sdk to
iphoneos and the configuration to
If you run this command you should find the app binary in the
build/Release-iphoneos folder afterwards. Now we can sign it and create the
IPA file. Therefore create a new script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
Line 2-9 are quite important. You don’t want to create a new release while working on a feature branch. The same is true for pull requests. Builds for pull requests wouldn’t work anyway as secured environment variables are disabled.
In line 14 we do the actual signing. This results in two new files in the
TravisExample.app.dsym. The first one contains the app which is ready to be delivered to your phone. The
dsym file allows contains debug information of your binary. This is important for logging crashes on the devices. We will use both files later when we distribute the app.
The last script will remove the temporary keychain again and delete the mobile provisioning profile. It is not really necessary but helps when testing locally.
1 2 3
As a last step we have to tell Travis when to execute these 3 scripts. The keys should be added before the app is built and the signing and cleanup should happen afterwards. Add/replace the following steps in your
1 2 3 4 5 6 7 8
With that being done we can push everything to GitHub and wait for Travis to sign our app. We can validate if it worked by investigating the Travis console on the project page. If everything works fine we can have a look on how to distribute the signed app to our testers.
Distributing the App
There are two well-known services that help you with distributing your app: Testflight and Hockeyapp. Choose whatever is more sufficient for your needs. Personally I prefer Hockeyapp but I’ll show how to integrate both services.
We will extend our existing shell script
sign-and-build.sh for that. Let’s create some release notes first:
Note that we use a global environment variable set by Travis here (
Create a Testflight account and setup your app. In order to use the Testflight API you need to get the api_token and team_token first. Again we have to make sure to encrypt them. On the command line execute:
Now we can call the API accordingly. Add this to the
1 2 3 4 5 6 7
Make sure NOT to use the verbose flag (
-v) as this would expose your decrypted tokens!
Sign up for a Hockeyapp account and create a new App. Afterwards grab the App ID from the overview page. Next we have to generate an API token. Go to this page and create a new API token. If you want to automatically distribute new versions to all testers choose the ‘Full Access’ version.
Encrypt both tokens:
Then call their API from the
1 2 3 4 5 6 7 8
Using Travis over the last month wasn’t always flaw-less. It’s important to know how to approach issues with your build without having direct access to the build environment.
As of writing this article there are no VM images available for download. If your build doesn’t work anymore, first try to reproduce the problem locally. Run the exact same build commands that Travis executes locally:
For debugging the shell scripts you have to define the environment variables first. What I did for this is create a new shell script that sets all the environment variables. This script is added to the
.gitignore file because you don’t want it exposed to the public. For the example project my
config.sh looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
In order the expose these environment variables execute this (be sure
config.sh has executable rights):
echo $APP_NAME to check if it worked. If it did, you can run any of your shell scripts locally without modifications.
If you get different build results locally you might have different versions of your libraries and gems installed. Try to imitate the exact same setup as on the Travis VM. They have a list of their installed software versions online. You can also figure out the exact versions of all gems and libraries by putting debug information into your travis config:
1 2 3 4
After you installed the exact same versions locally re-run the build.
If you still don’t get the same results try to do a clean checkout into a new directory. Also make sure all caches are cleared. As Travis sets up a new virtual machine for each build it doesn’t have to deal with cache problems – but your local test environment might has to.
Once you can reproduce the exact same behaviour as on the server you can start to investigate what the problem is. It really depends then on your concrete scenario how to approach it. Usually google is a great help in figuring out what could be the cause of your problem.
If after all the problem seems to affect other project on Travis as well it might be an issue with the Travis environment itself. I saw this happening several times (especially in the beginning). In this case try to contact their support. My experience is that they react super fast.
There are some limitations when using Travis CI compared to other solutions on the market. As Travis runs from a pre-configured VM you have to install custom dependencies for every build. That costs additional time. They put effort in providing caching mechanisms lately, though.
To some extend you rely on the setup that Travis provides. For example you have to deal with the currently installed version of Xcode. If you use a newer version than Travis CI you probably won’t be able to run your build on the server. It would be helpful if there were different VM’s setup for each major Xcode version.
For complex projects you might want to split up your build jobs into compiling the app, running integration tests and so forth. This way you get the build artifacts faster, without having to wait for all tests beeing processed. There is no direct support for dependent builds so far.
When pushing your project to GitHub Travis get’s triggered instantly. But builds usually won’t start right away. They will be put in a global language-specific build queue. The Pro version allows to have more builds beeing executed concurrently, though.
Travis CI provides you with a fully functional continuous integration environment that builds, tests and distributes your iOS apps. For open source projects this service is even free. Community projects benefit from the great GitHub integration. You might have seen buttons like this already:
Even for commercial project their support for private GitHub repositories with Travis Pro opens up an easy and fast way to use continuous integration.
If you didn’t try Travis yet, go and do it now. It’s awesome!