When writing software there are a few problem areas that are relatively well under control, like editing source files, (with syntax highlighting, autocompletion), version control and building software, i.e. executing the compiler and linker with some given options on a given set of files with given dependencies, and redoing this only for the files necessary when something changed.
But one area that seems to be inherently complex (in particular in the C/C++ ecosystem) is configuring a build, i.e. to specifying the options for compiler and linker, finding libraries, figuring out the dependencies. This is particularly true when portability is an issue, i.e. this should work for different operating systems, tool chains and versions of libraries.
Traditionally this was dealt with by autotools, which outputs a generated makefile. This is relatively portable, but also a entangled mess in many respects. A more modern approach is CMake, which can also serve a wider range of build systems (even IDEs like Eclipse). CMake has gotten a lot of traction in the recent years and many big projects are nowadays based on CMake.
Although there is documentation and a wiki, I found it difficult to find a decent high-level overview over the basic concepts and abstractions of CMake. Most tutorials and explanations focus a lot on small practical examples.
The original intent of this post was to write such an overview, but in the process of researching, I dug up some useful resources:
- CMakes own high level overview (which was surprisingly well hidden)
- CMake guide for the Ogre3d Engine
- John Lamps big multi part Cmake Tutorial
If you are getting started with CMake, these links are a great starting point.
I was a bit surprised that some of the really useful features were very hard to discover. For this reason, I will throw in a short description of a few underused and underdocumented features, that I use often.
I like to structure large codebases into maximally independent, loosely coupled chunks of functionality and only tie them together as late as possible. In the build system I express these chunks as static library targets located in adjacent folders on the file system, and a top level CMakeLists that ties them together using add_subdirectory. Even though the libraries are not always reusable in a strict sense, this gives structure and makes it harder to introduce coupling. It also speeds up compilation in some cases. A bit of care has to be exercised to make sure that the CMakefiles for the libraries remain composable with add_subdirectory and do not introduce strong dependencies.
Code that is genuinely reusable can be put into a separate git repository and included as a submodule.
One feature that is very useful for this approach are transitive usage requirements. If for building a library A certain include paths are required or linking against another library (B) is necessary, then CMake can be told to transitively use these when A is linked into another target. This turns library targets into easy to use building blocks.
However this feature seems to be not widely known or used. The typical tutorials for CMake do not cover it and the documentation is well hidden. I had used CMake extensively for quite a while before I found out about it, and none of my colleagues knew about it.
Using it is relatively simple. Instead of using include_directories or link_libraries to specify the include directories and libraries to link for all targets in this CMakeLists.txt, one uses target_include_directories and target_link_libraries to specify them for a particular target. There are keywords to control the precise way how these usage requirements are propagated.
However, I do not understand, why target_link_libraries seems to be well known and widely used, while link_libraries is not used a lot, while include_directories is much more popular than target_include_directories.
Building documentation for C/C++ is the job of Doxygen, I found a this page describes how it can be integrated in the CMake build system. This is perfect for html output but for tags files (which one wants to use in the approach described earlier) this can be problematic for very big projects, as this makes doxygen run everytime. Doxygen does its own caching and tags file generation is fast, so it probably is not a problem. An alternative that can take into account file changes can be realised like this.