A lot has been said about CMake as undoubtedly CMake is a great tool. If you search “CMake” you will definitely come across this sentence: “CMake is not a build system but rather it is a build system generator”. This blog post is entirely about what CMake generators are, why CMake supports so many of them, and when will you need each of them in your development. But before everything, a refresher on CMake build process is handy:
The configure step in the above diagram is only relevant in GUI builds. While working with CMake command line the generate command internally handles configure step. But what does the generate step do? Read on.
CMake Generator
What is a CMake Generator? As can be seen from the above diagram, native build tools are responsible for the actual compilation of source files. These build tools expect their input to be in a particular form, for example, a Makefile. A CMake Generator is responsible for creating this input for the native build tool. Exactly one CMake Generator is involved in the creation of a build output during the Generate step.
Types of Generators
Broadly there are two different kinds of CMake Generators.
Command Line Build Tool Generators
These are generators that support command line build tools. CMake is invoked from a command line whose environment is already configured for the chosen compiler and build tool.
Make is a well-known Unix utility and Makefiles are its input. Here’s a deep dive on how CMake won over Make. There are non-standard Makefiles supported by other programs targeting other platforms. Some of the non-standard ones supported by CMake are:
- Borland Makefiles
- MSYS Makefiles
- MinGW Makefiles
- NMake Makefiles
- NMake Makefiles JOM
- Watcom WMake
The tools that are used in the actual compilation are different for each of these different Makefiles. For example, in Borland Makefile the compiler used for compiling a C++ file is called bcc32. For NMake, which is the make tool bundled with Microsoft Visual Studio, the compiler used will be the Microsoft C++ compiler named cl.
Although make has been in existence for a long time now, the complexities of maintaining Makefiles led to the development of other build systems – one of the most successful being Ninja. The unique selling proposition of Ninja over make is the support for parallel builds. In Ninja, builds are always run in parallel whereas make depends on flags supported by the underlying compiler to achieve this. Since Ninja is a modern tool, the assumption here is multi-core CPUs which can all be used during a build thereby improving throughput. Ninja makes sure that the command output is always buffered. This makes it easy to understand what failed during a parallel compilation. (If you want to further reduce compilation time, you can check out the incredibuild solution – we are not limited to one system, the build is concurrently distributed across idle CPUs in your local network or even the cloud thereby making the build incredibly fast).
GUI Build Tool Generators
In an automated nightly build setup where human intervention is minimal, command line build tool generators are the obvious choice. Whereas when a developer is trying to debug a program using an integrated development environment, he would appreciate how easy is it to get from a set of source files and the CMakeLists.txt file, his familiar development environment. CMake supports the following IDEs:
- Visual Studio (from version 6 to the latest version 16)
- Xcode
- CodeBlocks
- CodeLite
- Eclipse CDT
- Kate
- Sublime Text 2
CMake Generator Expressions
In the world of compilers an expression is something that needs to be evaluated. For example, consider the following C++ line involving three variables a, b and c:
a = b + c;
This is an assignment statement that involves an expression b + c on the right-hand side of the assignment. The expression b + c will be evaluated and its value will be assigned to the variable a.
CMake has a language to write CMakeLists.txt (and also to write scripts and modules). Of course, mere mortals like you or me do not need to understand the CMake language to work with CMake. Generator expressions are – when used in a CMakeLists.txt file or elsewhere – something that stands out and hinders our understanding with our limited knowledge of CMake language. Truth be told, I am not a big fan of generator expressions myself and I believe readability should always win over terseness. So, if you see something along the lines:
$<abc:xyz>
This basically means if generator expression abc evaluates to true, then the value of the generator expression is xyz, otherwise it is an empty string. Let us take an example to make this clear. Save the following as CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(TestingGenerators)
message(STATUS "This message can be seen during generation")
add_custom_target(print ${CMAKE_COMMAND} -E echo $<0:hello> $<0:world>)
Let us first generate using the command:
cmake -S. -BBuild -G "Ninja"
Now let us build the custom target:
cmake --build . --target print
You shouldn’t be getting any output. Now change the above file to:
add_custom_target(print ${CMAKE_COMMAND} -E echo $<1:hello> $<1:world>)
You should be getting “hello world” printed on the command line. 1 evaluates to true so there was some output whereas 0 evaluates to false hence there was no output. This is the bare minimum that can be explained through a general blog post. If you are interested in generator expressions, I recommend reading the CMake documentation.
Final note
CMake has some very powerful features that are used sparingly. In this post, we looked into CMake generators and CMake generator expressions. It should whet your appetite and if you feel adventurous, take you on an amazing journey through the nooks and corners of CMake. Bon Voyage sailor!