Recently I’ve been working on a little side project in the form of a C++ k-means clustering library. Development recently reached a point where the project was functionally complete, so I began to look for other areas to improve. One such improvement was adding Continuous Integration (CI) via Travis CI.
Travis CI is the de-facto standard for CI-as-a-service for Github projects. It automatically detects when new code is pushed to a Github repository (including on branches) and will execute whatever jobs are set up for that repository (builds, testing, static analysis, etc.). According to stats available publicly uptake of CI-as-a-service is relatively low for C++. This could be due to C++ projects not having CI, or it might just be that C++ projects (like Firefox and Dolphin) have their own infrastructure and so don’t need or want to rely on a third party service.
At this point I’m assuming you’re already sold on the idea of CI, because another entire blog post could be devoted to explaining why you should use CI and what you should do with it. This post focuses on the technical how question: specifically, with regards to building modern C++ code on Travis.
Basic Configuration
Travis is very easy to get set started with. Add a configuration file (.travis.yml
) to the repository with some information about the type of build(s) to run, and enable Travis CI on Github: under Integrations globally, and under Webhooks and services in the settings for each repository.
The basic .travis.yml for a C++ project looks something like this:
|
|
This is a good start. It gets us builds against GCC 4.6.3 and Clang 3.4 on Ubuntu 12.04 Precise. Travis uses these parameters to construct a “Build Matrix”. The build matrix is effectively a table of all of the possible combinations of build options specified in the configuration. In a simple configuration like this it consists only of different compilers and operating systems. We have specified one operating system and two compilers, so we get 1 * 2 = 2 different builds. In a more complex configuration we can specify different environments, build procedures and other variations to ramp up the number of configurations tested in each job. The Build Matrix in Travis CI is very powerful and flexible.
Travis CI Environment Limitations
Hold on, did I say GCC 4.6? Clang 3.4?
This won’t do. This won’t do at all.
GCC declared itself feature complete at 4.8.1 according to the C++11 feature matrix, but <regex>
was missing until GCC 4.9. So for modern C++ I would expect to be using GCC 4.9 at minimum. We get lucky with Clang; 3.4 has full C++11 support, and even feature-complete C++14 support. Great, so I can at least get a build going using Clang.
cmake .. && make
CMake Error at CMakeLists.txt:1 (cmake_minimum_required):
CMake 3.0 or higher is required. You are running version 2.8.7
CMake 2.8? I chose 3.0 for my project as a compromise between being on the bleeding edge and having to support an ancient toolset. The version offered by Travis’ build slaves is over 4 years old.
For other languages that Travis is popular with (e.g. Javascript, Ruby, Python) Travis (the company) backports the latest runtimes, and provides a range of the latest versions for user to choose from. For example, for Python they provide 2.6, 2.7, 3.2, 3.3, 3.4, 3.5 and a nightly python build. Even Rust projects get to build against its stable, beta and nightly channels with the latest compiler or range of specific versions. No such support is provided for building C and C++ code on Travis: all we get by default is the compilers shipped with the platform.
This project has only a single code dependency on OpenCV 2.x, which is thankfully available in the apt repositories for Ubuntu 12.04. Other projects may not be so fortunate with the libraries and kernels available on such a mature version of Ubuntu.
Building Modern C++ on Travis (C++11/C++14)
So is this the end of the road for building modern C++ projects on Travis? Fortunately it is not. Other people have already run into all of these same problems and provided solutions or hints which we can use to set up a working C++11/14 build.
One way of getting a more up-to-date toolchain is to opt into the “beta” Ubuntu 14.04 Trusty build environment. To use the Trusty build environment add the following to your .travis.yml
:
|
|
Unfortunately this is not as advantageous as it first appears. It gets us GCC 4.8.4 and Clang 3.5.0 and CMake 2.8.12. Again, the version of Clang available is sufficient for building modern C++ code, but the GCC and CMake versions are still a bit further behind than we would like. At this point I conceded the CMake version, since my CMakeLists files worked with CMake 2.8.
So how can we get even more up to date dependencies over what the platform offers? As the Travis build environment is Ubuntu based, is it possible to add specific external PPAs for up-to-date dependencies. In this case we need to add two PPAs for our compilers, one for LLVM and one for GCC. Unfortunately there is currently an open issue for adding the Trusty LLVM ppa to Travis’ whitelist (at the time of writing), so in order to use an up-to-date LLVM compiler I had to also revert to the Ubuntu 12.04 build environment.
|
|
Now, if this is added to the .travis.yml
and a build is kicked off Travis will still use the old compiler, because it is simply setting the compiler environment variables to g++
or clang++
respectively, which doesn’t pick up the newly install non-default versions. At first I toyed with reconfiguring the default with Ubuntu’s update-alternatives
tool, but there were a few problems around using this for the clang build. Instead I settled on an environment variable juggling technique suggested on StackOverflow.
I mentioned earlier that the build matrix can be used to generate a suite of different builds that are all triggered in the same job. Adding specific configurations can be done via the matrix.include
property in .travis.yml
. Each item in the sequence (denoted by -
) details a different build configuration. This configuration could specify a compiler/runtime, environment variables, build steps ppa sources, packages, etc. I configured the build matrix to be made up of 4 builds total, GCC 4.9 and 5.x (currently 5.2 from the ubuntu-toolchain-r-test
ppa) and Clang 3.6 and 3.7.
|
|
Conclusion
Building modern C++ projects on Travis is entirely possible, if a little tricky to set up. With the apt addon for Travis and a custom build matrix we can target multiple compilers for every build. Github integration means automatic builds on every branch and pull request. And best of all, it’s free for open source projects. If you have an open source project C++ project on Github I would suggest there is no reason not to make use of Travis.
The final configuration I ended up with for my project is available on github, and you can see the associated build on Travis.