C++ is more active than ever, with the C++17 standard ready, a widely support on C++14 from major compilers and C++20 planning on the way there is a interesting future for the standard.
Modern C++ is great, some people are even calling it a new language, but is not only the language what is evolving the tool-chain is getter better, so doing continuous integration for cross platform projects is simple and effective.
I decide to do a simple project using some of the C++14 features and following the C++ Core Guidelines whenever its possible. The result is available in this repository.
I set some goals to doing this project:
- Project is organized with a logical structure
- Need to be a small C++14 project, but nothing really complicate.
- Will have at least two modules, a library and a program that uses it.
- Modern Unit Tests.
- It should use some third party software.
- CI will be triggered per commit and build using;
- GCC & CLang on Linux
- XCode on OSX
- Visual Studio on Windows
The initial project structure
/lib
/src
/include
/test
/app
/src
/test
/third_party
Nothing complicated, and easy to manage, with a clear meaning of each folder.
The simple program
This program will output when run:
[2017-07-01 11:09:22.766] [console] [info] [main] doing some calculation
[2017-07-01 11:09:22.768] [console] [info] [main] 1 + 2 * 5 - 3 / 4 = 3
This example use a couple of classes defined in the library, Calc and Logger to display a simple calculation.
Doing Unit Tests
I decide to use the wonderful Catch for doing the unit test, as an example:
We are not going into the detail in the implementation of the classes or the test, the repository has all the details about it, lest focus now on the CI.
Build and test in all platform
We are going to use CMake and CTest for creating our build so we start with the main CMakeLists.txt on the root of the project.
First we just set some settings, as that we are going to we require C++14, we will find the Thread library for any tool-chain, we include our directories, but we exlude for the third party other build targets that our dependencies could bring, and finally we set that this CMake project will have tests.
Preparing the third party software
The third part software that we are going to use is:
The third party software is cloned as submodules, so we use their original project structure.
Now we prepare a new CMakeLists.txt under the /third_party folder:
The most import part here is that we export two variables that will have the corresponding directories to add to the include paths for our targets and we push them to the parent scope so we could use them. Additionally for Catch we include a custom function that will allow to auto discover tests.
Bulding the library
For creating our library we add a new CMakeLists.txt under the /lib folder:
Here we set the desired include directories and we built a list of sources and header files, them we create the library and export a couple of variable so we could use them in our application, finally we add the test directory so we build the test for this library.
Testing the library
No we create a new CMakeLists.txt under the /lib/test directory:
We include the test sources and we link the test executable with our Library and the Threads library, finally we out discover the catch tests using the previously imported function in the third party modules.
Building the application
Building the application is now quite simple with a new CMakeLists.txt under the /app directory:
We just include the sources for the app, link with the libraries and include the test folder.
Doing a simple test on the application
For the application itself we are just going to running and check that end successfully, so we create a new CMakeLists.txt under the /app/test directory:
Here we simply run the application and we use the CTest command add_test to run it, if the application fails this test will fail.
CMake will handle it
With this CMake will create our targets and link them together so if we change our lib their test will be build, so the application. If we run the test all required target will be build include the library and the application.
Using CMake to create a project for our tool-chain
To generate the projects, auto discovering everything, including what compiler we are going to use we could just:
If you like to set a implicit compiler set the variable CXX=${COMPILER}, for example COMPILER could be gcc, clang and so on.
Auto detect in Windows usually generate a Visual Studio project since msbuild require it, but in OSX does not generate and XCode project, since is not required for compiling using XCode clang.
Specify build type debug/release
Specify architecture
Generate different project types
Build the project
From the Build folder
Run tests
From the Build folder
This will run all our test and given stats about how long will take, which one fail and so on.
Adding Travis CI
No that we have our project ready we could building in travis for Linux and OSX. We will add this .travis.yml to our project:
This will use a buld matrix to generate the project, build it and do the test for Linux (clang38 / gcc6) and OSX (XCode 8.3 clang) for Debug and Release targets.
Adding Appveyor
Finally we will use Appveyor for Visual Studio on Windows builds adding a appveyor.yml to our project:
In this case we are going to build using Visual Studio 2015 and 2017 with Win32 and x64 architecture and Debug and Release targets.
Summary
So probably this is a more complex setup that initially anticipated but when is done working with it is really simple.
We could add new files just creating them in the right folders, work with our favorite IDE, run our tests and push to our git to get our CI reports.
Some IDE as CLion have a runner for Catch that allow us to run individual test with a couple of clicks, however we could do the same just filtering test by tags in our favorite IDE adding to the arguments of our test application any of the Catch supported command line parameters.
In fact we could even use this jenkins pluging to get our CMake / CTest build, test and reported. But I’ll leave that for other day.
Anyway I think this is something that I’ve really enjoy to learn and I’m sure that will continue to use in future C++ projects.
references
- https://cmake.org/
- https://docs.travis-ci.com/user/languages/cpp/
- https://www.appveyor.com/docs/lang/cpp/
- https://github.com/isocpp/CppCoreGuidelines
- https://github.com/philsquared/Catch
- https://github.com/gabime/spdlog
- https://github.com/cognitivewaves/CMake-VisualStudio-Example
- http://derekmolloy.ie/hello-world-introductions-to-cmake/
- https://cmake.org/Wiki/CMake/Testing_With_CTest
- https://www.appveyor.com/docs/lang/cpp/
- https://docs.travis-ci.com/user/languages/cpp/
- https://github.com/philsquared/Catch/blob/master/docs/build-systems.md
- https://stackoverflow.com/questions/14446495/cmake-project-structure-with-unit-tests