Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Free Learning
Arrow right icon
Modern CMake for C++
Modern CMake for C++

Modern CMake for C++: Effortlessly build cutting-edge C++ code and deliver high-quality solutions , Second Edition

eBook
£20.98 £29.99
Paperback
£25.99 £37.99
Subscription
Free Trial
Renews at £16.99p/m

What do you get with Print?

Product feature icon Instant access to your digital eBook copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning
OR
Modal Close icon
Payment Processing...
tick Completed

Shipping Address

Billing Address

Shipping Methods
Table of content icon View table of contents Preview book icon Preview Book

Modern CMake for C++

First Steps with CMake

There is something magical about software creation. We’re not only creating a working mechanism that gets brought to life but we’re also often authoring the very idea behind the functionality of the solution.

To cast our ideas into existence, we work in the following loop: design, code, and test. We invent changes, we phrase them in a language that the compiler understands, and we check whether they work as intended. To create proper, high-quality software from our source code, we need to meticulously execute repetitive, error-prone tasks: invoking the correct commands, checking the syntax, linking binary files, running tests, reporting issues, and more.

It takes great effort to remember each step every single time. Instead, we want to stay focused on the actual coding and delegate everything else to automated tooling. Ideally, this process would start with a single button, right after we have changed our code. It would be smart, fast, extensible, and work in the same way across different OSs and environments. It would be supported by multiple Integrated Development Environments (IDEs). Going even further, we could streamline this process into Continuous Integration (CI) pipelines that build and test our software every time a change is submitted to a shared repository.

CMake is the answer to many such needs; however, it requires a bit of work to configure and use correctly. CMake isn’t the source of the complexity; that stems from the subject that we’re dealing with here. Don’t worry, we will go through this whole learning process very methodically. Before you know it, you will become a software-building guru.

I know you’re eager to rush off to start writing your own CMake projects, and this is exactly what we will be doing for most of this book. But since you’ll be creating your projects primarily for users (yourself included), it’s important for you to understand their perspective first.

So, let’s start with just that: becoming a CMake power user. We’ll go through a few basics: what this tool is, how it works in principle, and how to install it. Then, we’ll do a deep dive into the command line and modes of operation. Finally, we’ll wrap up with the purposes of different files in a project, and we’ll explain how to use CMake without creating projects at all.

In this chapter, we’re going to cover the following main topics:

  • Understanding the basics
  • Installing CMake on different platforms
  • Mastering the command line
  • Navigating project files
  • Discovering scripts and modules

Technical requirements

You can find the code files that are present in this chapter on GitHub at https://github.com/PacktPublishing/Modern-CMake-for-Cpp-2E/tree/main/examples/ch01.

To build the examples provided in this book, always execute all the recommended commands:

cmake -B <build tree> -S <source tree>
cmake --build <build tree>

Be sure to replace the placeholders <build tree> and <source tree> with the appropriate paths. As you will learn in this chapter, build tree is the path of your output directory, and source tree is the path at which your source code is located.

To build C++ programs, you also need a compiler appropriate for your platform. If you’re familiar with Docker, you can use a fully tooled image introduced in the Installing CMake on different platforms section. If you’d rather set up CMake manually, we’ll explain the installation in the same section.

Understanding the basics

The compilation of C++ source code appears to be a fairly straightforward process. Let’s start with the classic Hello World example.

The following code is found in ch01/01-hello/hello.cpp, Hello world in the C++ language:

#include <iostream>
int main() {
  std::cout << "Hello World!" << std::endl;
  return 0;
}

To produce an executable, we of course need a C++ compiler. CMake doesn’t come with one, so you’ll need to pick and install one on your own. Popular choices include:

  • Microsoft Visual C++ compiler
  • The GNU compiler collection
  • Clang/LLVM

Most readers are familiar with a compiler, as it is indispensable when learning C++, so we won’t go into picking one and installation. Examples in this book will use GNU GCC as it is a well-established, open-source software compiler available for free across many platforms.

Assuming that we have our compiler already installed, running it is similar for most vendors and systems. We should call it with the filename as an argument:

$ g++ hello.cpp -o hello

Our code is correct, so the compiler will silently produce an executable binary file that our machine can understand. We can run it by calling its name:

$ ./hello
Hello World!

Running one command to build your program is simple enough; however, as our projects grow, you will quickly understand that keeping everything in a single file is simply not possible. Clean code practices recommend that source code files should be kept small and in well-organized structures. The manual compilation of every file can be a tiresome and fragile process. There must be a better way.

What is CMake?

Let’s say we automate building by writing a script that goes through our project tree and compiles everything. To avoid any unnecessary compilations, our script will detect whether the source has been modified since the last time we ran the script. Now, we’d like a convenient way to manage arguments that are passed to the compiler for each file – preferably, we’d like to do that based on configurable criteria. Additionally, our script should know how to link all of the compiled files into a single binary file or, even better, build whole solutions that can be reused and incorporated as modules into bigger projects.

Building software is a very versatile process and can span multiple different aspects:

  • Compiling executables and libraries
  • Managing dependencies
  • Testing
  • Installing
  • Packaging
  • Producing documentation
  • Testing some more

It would take a very long time to come up with a truly modular and powerful C++ building utility that is fit for every purpose. And it did. Bill Hoffman at Kitware implemented the first versions of CMake over 20 years ago. As you might have already guessed, it was very successful. Today, it has a lot of features and extensive support from the community. CMake is being actively developed and has become the industry standard for C and C++ programmers.

The problem of building code in an automated way is much older than CMake, so naturally, there are plenty of options out there: GNU Make, Autotools, SCons, Ninja, Premake, and more. But why does CMake have the upper hand?

There are a couple of things about CMake that I find (granted, subjectively) important:

  • It stays focused on supporting modern compilers and toolchains.
  • CMake is truly cross-platform – it supports building for Windows, Linux, macOS, and Cygwin.
  • It generates project files for popular IDEs: Microsoft Visual Studio, Xcode, and Eclipse CDT. Additionally, it is a project model for others, like CLion.
  • CMake operates on just the right level of abstraction – it allows you to group files in reusable targets and projects.
  • There are tons of projects that are built with CMake and offer an easy way to plug them into your project.
  • CMake views testing, packaging, and installing as an inherent part of the build process.
  • Old, unused features get deprecated to keep CMake lean.

CMake provides a unified, streamlined experience across the board. It doesn’t matter whether you’re building your software in an IDE or directly from the command line; what’s really important is that it takes care of post-build stages as well.

Your CI/CD pipeline can easily use the same CMake configuration and build projects using a single standard even if all of the preceding environments differ.

How does it work?

You might be under the impression that CMake is a tool that reads source code on one end and produces binaries on the other – while that’s true in principle, it’s not the full picture.

CMake can’t build anything on its own – it relies on other tools in the system to perform the actual compilation, linking, and other tasks. You can think of it as the orchestrator of your building process: it knows what steps need to be done, what the end goal is, and how to find the right workers and materials for the job.

This process has three stages:

  • Configuration
  • Generation
  • Building

Let’s explore them in some detail.

The configuration stage

This stage is about reading project details stored in a directory, called the source tree, and preparing an output directory or build tree for the generation stage.

CMake starts by checking whether the project was configured before and reads cached configuration variables from a CMakeCache.txt file. On a first run, this is not the case, so it creates an empty build tree and collects all of the details about the environment it is working in: for example, what the architecture is, what compilers are available, and what linkers and archivers are installed. Additionally, it checks whether a simple test program can be compiled correctly.

Next, the CMakeLists.txt project configuration file is parsed and executed (yes, CMake projects are configured with CMake’s coding language). This file is the bare minimum of a CMake project (source files can be added later). It tells CMake about the project structure, its targets, and its dependencies (libraries and other CMake packages).

During this process, CMake stores collected information in the build tree, such as system details, project configurations, logs, and temp files, which are used for the next step. Specifically, a CMakeCache.txt file is created to store more stable information (such as paths to compilers and other tools), which saves time when the whole build sequence is executed again.

The generation stage

After reading the project configuration, CMake will generate a buildsystem for the exact environment it is working in. Buildsystems are simply cut-to-size configuration files for other build tools (for example, Makefiles for GNU Make or Ninja and IDE project files for Visual Studio). During this stage, CMake can still apply some final touches to the build configuration by evaluating generator expressions.

The generation stage is executed automatically after the configuration stage. For this reason, this book and other resources sometimes refer to both of these stages interchangeably when mentioning the “configuration” or “generation” of a buildsystem. To explicitly run just the configuration stage, you can use the cmake-gui utility.

The building stage

To produce the final artifacts specified in our project (like executables and libraries), CMake has to run the appropriate build tool. This can be invoked directly, through an IDE, or using the appropriate CMake command. In turn, these build tools will execute steps to produce target artifacts with compilers, linkers, static and dynamic analysis tools, test frameworks, reporting tools, and anything else you can think of.

The beauty of this solution lies in the ability to produce buildsystems on demand for every platform with a single configuration (that is, the same project files):

Figure 1.1: The stages of CMake

Do you remember our hello.cpp application from the Understanding the basics section? It is really easy to build it with CMake. All we need is the following CMakeLists.txt file in the same directory as our source.

ch01/01-hello/CMakeLists.txt

cmake_minimum_required(VERSION 3.26)
project(Hello)
add_executable(Hello hello.cpp)

After creating this file, execute the following commands in the same directory:

cmake -B <build tree>
cmake --build <build tree>

Note that <build tree> is a placeholder that should be replaced with a path to a temporary directory that will hold generated files.

Here is the output from an Ubuntu system running in Docker (Docker is a virtual machine that can run within other systems; we’ll discuss it in the Installing CMake on different platforms section). The first command generates a buildsystem:

~/examples/ch01/01-hello# cmake -B ~/build_tree
-- The C compiler identification is GNU 11.3.0
-- The CXX compiler identification is GNU 11.3.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (1.0s)
-- Generating done (0.1s)
-- Build files have been written to: /root/build_tree

The second command actually builds the project:

~/examples/ch01/01-hello# cmake --build ~/build_tree
Scanning dependencies of target Hello
[ 50%] Building CXX object CMakeFiles/Hello.dir/hello.cpp.o
[100%] Linking CXX executable Hello
[100%] Built target Hello

All that’s left is to run the compiled program:

~/examples/ch01/01-hello# ~/build_tree/Hello
Hello World!

Here, we have generated a buildsystem that is stored in the build tree directory. Following this, we executed the build stage and produced a final binary that we were able to run.

Now you know what the result looks like, I’m sure you will be full of questions: what are the prerequisites to this process? What do these commands mean? Why do we need two of them? How do I write my own project files? Don’t worry – these questions will be answered in the following sections.

This book will provide you with the most important information that is relevant to the current version of CMake (at the time of writing, this is 3.26). To provide you with the best advice, I have explicitly avoided any deprecated and no longer recommended features and I highly recommend using, at the very least, CMake version 3.15, which is considered the modern CMake. If you require more information, you can find the latest, complete documentation online at https://cmake.org/cmake/help/.

Installing CMake on different platforms

CMake is a cross-platform, open-source software written in C++. That means you can, of course, compile it yourself; however, the most likely scenario is that you won’t have to. This is because precompiled binaries are available for you to download from the official web page at https://cmake.org/download/.

Unix-based systems provide ready-to-install packages directly from the command line.

Remember that CMake doesn’t come with compilers. If your system doesn’t have them installed yet, you’ll need to provide them before using CMake. Make sure to add the paths to their executables to the PATH environment variable so that CMake can find them.

To avoid facing tooling and dependency problems while learning from this book, I recommend practicing by following the first installation method: Docker. In a real-world scenario, you will of course want to use a native version, unless you’re working in a virtualized environment to begin with.

Let’s go through some different environments in which CMake can be used.

Docker

Docker (https://www.docker.com/) is a cross-platform tool that provides OS-level virtualization, allowing applications to be shipped in well-defined packages called containers. These are self-sufficient bundles that contain a piece of software with all of the libraries, dependencies, and tools required to run it. Docker executes its containers in lightweight environments that are isolated one from another.

This concept makes it extremely convenient to share whole toolchains that are necessary for a given process, configured and ready to go. I can’t stress enough how easy things become when you don’t need to worry about minuscule environmental differences.

The Docker platform has a public repository of container images, https://registry.hub.docker.com/, that provides millions of ready-to-use images.

For your convenience, I have published two Docker repositories:

  • swidzinski/cmake2:base: An Ubuntu-based image that contains the curated tools and dependencies that are necessary to build with CMake
  • swidzinski/cmake2:examples: An image based on the preceding toolchain with all of the projects and examples from this book

The first option is for readers who simply want a clean-slate image ready to build their own projects, and the second option is for hands-on practice with examples as we go through the chapters.

You can install Docker by following the instructions from its official documentation (please refer to docs.docker.com/get-docker). Then, execute the following commands in your terminal to download the image and start the container:

$ docker pull swidzinski/cmake2:examples
$ docker run -it swidzinski/cmake2:examples
root@b55e271a85b2:root@b55e271a85b2:#

Note that examples are available in the directories matching this format:

devuser/examples/examples/ch<N>/<M>-<title>

Here, <N> and <M> are zero-padded chapter and example numbers, respectively (like 01, 08, and 12).

Windows

Installing in Windows is straightforward – simply download the version for 32 or 64 bits from the official website. You can also pick a portable ZIP or MSI package for Windows Installer, which will add the CMake bin directory to the PATH environment variable (Figure 1.2) so that you can use it in any directory without any such errors:

cmake is not recognized as an internal or external command, operable program, or batch file.

If you select the ZIP package, you will have to do it manually. The MSI installer comes with a convenient GUI:

Figure 1.2: The installation wizard can set up the PATH environment variable for you

As I mentioned earlier, this is open-source software, so it is possible to build CMake yourself. However, on Windows, you will have to get a binary copy of CMake on your system first. This scenario is used by CMake contributors to generate newer versions.

The Windows platform is no different from others, and it also requires a build tool that can finalize the build process started by CMake. A popular choice here is the Visual Studio IDE, which comes bundled with a C++ compiler. The Community edition is available for free from Microsoft’s website: https://visualstudio.microsoft.com/downloads/.

Linux

Installing CMake on Linux follows the same process as with any other popular package: call your package manager from the command line. Package repositories are usually kept up to date with fairly recent versions of CMake, but usually not the latest. If you’re fine with this and using a distribution like Debian or Ubuntu, it is simplest to just install the appropriate package:

$ sudo apt-get install cmake

For a Red Hat distribution, use the following command:

$ yum install cmake

Note that when installing a package, your package manager will fetch the latest available version in the repository configured for your OS. In many cases, package repositories don’t provide the latest version but, rather, a stable one that has been proven over time to work reliably. Pick according to your needs, but be aware that older versions won’t have all the features described in this book.

To get the latest version, reference the download section of the official CMake website. If you know the current version number, you can use one of the following commands.

The command for Linux x86_64 is:

$ VER=3.26.0 && wget https://github.com/Kitware/CMake/releases/download/v$VER/cmake-$VER-linux-x86_64.sh && chmod +x cmake-$VER-linux-x86_64.sh && ./cmake-$VER-linux-x86_64.sh

The command for Linux AArch64 is:

$ VER=3.26.0 && wget https://github.com/Kitware/CMake/releases/download/v$VER/cmake-$VER-Linux-aarch64.sh && chmod +x cmake-$VER-Linux-aarch64.sh && ./cmake-$VER-Linux-aarch64.sh

Alternatively, check out the Building from the source section to learn how to compile CMake on your platform yourself.

macOS

This platform is also strongly supported by CMake developers. The most popular choice of installation is through MacPorts with the following command:

$ sudo port install cmake

Do note that at the time of writing, the latest version available in MacPorts was 3.24.4. To get the latest version, install the cmake-devel package:

$ sudo port install cmake-devel

Alternatively, you can use the Homebrew package manager:

$ brew install cmake

macOS package managers will cover all necessary steps, but be mindful that you might not get the latest version unless you’re building from the source.

Building from the source

If you’re using another platform, or just want to experience the latest builds that haven’t been promoted to a release (or adopted by your favorite package repository), download the source from the official website and compile it yourself:

$ wget https://github.com/Kitware/CMake/releases/download/v3.26.0/cmake-3.26.0.tar.gz
$ tar xzf cmake-3.26.0.tar.gz
$ cd cmake-3.26.0
$ ./bootstrap
$ make
$ make install

Building from the source is relatively slow and requires more steps. However, there is no other way to have the freedom of picking any version of CMake. This is especially useful when packages that are available in repositories of your operating system are stale: the older the version of the system, the fewer updates it gets.

Now that we have installed CMake, let’s learn how to use it!

Mastering the command line

The majority of this book will teach you how to prepare CMake projects for your users. To cater to their needs, we need to thoroughly understand how users interact with CMake in different scenarios. This will allow you to test your project files and ensure they’re working correctly.

CMake is a family of tools and consists of five executables:

  • cmake: The main executable that configures, generates, and builds projects
  • ctest: The test driver program used to run and report test results
  • cpack: The packaging program used to generate installers and source packages
  • cmake-gui: The graphical wrapper around cmake
  • ccmake: The console-based GUI wrapper around cmake

Additionally, Kitware, the company behind CMake, offers a separate tool called CDash to provide advanced oversight over the health of our projects’ builds.

CMake command line

The cmake is the main binary of the CMake suite, and provides a few modes of operation (also sometimes called actions):

  • Generating a project buildsystem
  • Building a project
  • Installing a project
  • Running a script
  • Running a command-line tool
  • Running a workflow preset
  • Getting help

Let’s see how they work.

Generating a project buildsystem

The first step required to build our project is to generate a buildsystem. Here are three forms of command to execute the CMake generating a project buildsystem action:

cmake [<options>] -S <source tree> -B <build tree>
cmake [<options>] <source tree>
cmake [<options>] <build tree>

We’ll discuss available <options> in the upcoming sections. Right now, let’s focus on choosing the right form of the command. One important feature of CMake is the support for out-of-source builds or the support for storing build artifacts in a directory different from the source tree. This is a preferred approach to keep the source directory clean from any build-related files and avoid polluting the Version Control Systems (VCSs) with accidental files or ignore directives.

This is why the first form of command is the most practical. It allows us to specify the paths to the source tree and the produced buildsystem specified with -S and -B, respectively:

cmake -S ./project -B ./build

CMake will read the project files from the ./project directory and generate a buildsystem in the ./build directory (creating it beforehand if needed).

We can skip one of the arguments and cmake will “guess” that we intended to use the current directory for it. Note that skipping both will produce an in-source build and store the build artifacts along with source files, which we don’t want.

BE EXPLICIT WHEN RUNNING CMAKE

Do not use the second or third form of the cmake <directory> command, because they can produce a messy in-source build. In Chapter 4, Setting Up Your First CMake Project, we’ll learn how to prevent users from doing that.

As hinted in the syntax snippet, the same command behaves differently if a previous build already exists in <directory>: it will use the cached path to the sources and rebuild from there. Since we often invoke the same commands from the Terminal command history, we might get into trouble here; before using this form, always check whether your shell is currently working in the right directory.

Examples

Generate the build tree in the current directory using the source from one directory up:

cmake -S ..

Generate the build tree in the ./build directory using the source from the current directory:

cmake -B build

Choosing a generator

As discussed earlier, you can specify a few options during the generation stage. Selecting and configuring a generator decides which build tool from our system will be used for building in the subsequent Building a project section, what build files will look like, and what the structure of the build tree will be.

So, should you care? Luckily, the answer is often “no.” CMake does support multiple native buildsystems on many platforms; however, unless you have installed a few generators at the same time, CMake will correctly select one for you. This can be overridden by the CMAKE_GENERATOR environment variable or by specifying the generator directly on the command line, like so:

cmake -G <generator name> -S <source tree> -B <build tree>

Some generators (such as Visual Studio) support a more in-depth specification of a toolset (compiler) and platform (compiler or SDK). Additionally, CMake will scan environment variables that override the defaults: CMAKE_GENERATOR_TOOLSET and CMAKE_GENERATOR_PLATFORM. Alternatively, the values can be specified directly in the command line:

cmake -G <generator name>
      -T <toolset spec>
      -A <platform name>
      -S <source tree> -B <build tree>

Windows users usually want to generate a buildsystem for their preferred IDE. On Linux and macOS, it’s very common to use the Unix Makefiles or Ninja generators.

To check which generators are available on your system, use the following command:

cmake --help

At the end of the help printout, you will get a full list of generators, like this one produced on Windows 10 (some output was truncated for readability):

The following generators are available on this platform:

Visual Studio 17 2022       
Visual Studio 16 2019       
Visual Studio 15 2017 [arch]
Visual Studio 14 2015 [arch]
Visual Studio 12 2013 [arch]
Visual Studio 11 2012 [arch]
Visual Studio 9 2008 [arch] 
Borland Makefiles           
NMake Makefiles             
NMake Makefiles JOM         
MSYS Makefiles              
MinGW Makefiles             
Green Hills MULTI           
Unix Makefiles              
Ninja                       
Ninja Multi-Config          
Watcom WMake                
CodeBlocks - MinGW Makefiles
CodeBlocks - NMake Makefiles
CodeBlocks - NMake Makefiles JOM
CodeBlocks - Ninja          
CodeBlocks - Unix Makefiles 
CodeLite - MinGW Makefiles  
CodeLite - NMake Makefiles  
CodeLite - Ninja            
CodeLite - Unix Makefiles   
Eclipse CDT4 - NMake Makefiles
Eclipse CDT4 - MinGW Makefiles
Eclipse CDT4 - Ninja        
Eclipse CDT4 - Unix Makefiles
Kate - MinGW Makefiles      
Kate - NMake Makefiles      
Kate - Ninja                
Kate - Unix Makefiles       
Sublime Text 2 - MinGW Makefiles
Sublime Text 2 - NMake Makefiles
Sublime Text 2 - Ninja      
Sublime Text 2 - Unix Makefiles

As you can see, CMake supports a lot of different generator flavors and IDEs.

Managing the project cache

CMake queries the system for all kinds of information during the configuration stage. Because these operations can take a bit of time, the collected information is cached in the CMakeCache.txt file in the build tree directory. There are a few command-line options that allow you to manage the behavior of the cache more conveniently.

The first option at our disposal is the ability to prepopulate cached information:

cmake -C <initial cache script> -S <source tree> -B <build tree>

We can provide a path to the CMake listfile, which (only) contains a list of set() commands to specify variables that will be used to initialize an empty build tree. We’ll discuss writing the listfiles in the next chapter.

The initialization and modification of existing cache variables can be done in another way (for instance, when creating a file is a bit much to only set a few variables). You can set them directly in a command line, as follows:

cmake -D <var>[:<type>]=<value> -S <source tree> -B <build tree>

The :<type> section is optional (it is used by GUIs) and it accepts the following types: BOOL, FILEPATH, PATH, STRING or INTERNAL. If you omit the type, CMake will check if the variable exists in the CMakeCache.txt file and use its type; otherwise, it will be set to UNINITIALIZED.

One particularly important variable that we’ll often set through the command line specifies the build type (CMAKE_BUILD_TYPE). Most CMake projects will use it on numerous occasions to decide things such as the verbosity of diagnostic messages, the presence of debugging information, and the level of optimization for created artifacts.

For single-configuration generators (such as GNU Make and Ninja), you should specify the build type during the configuration phase and generate a separate build tree for each type of config. Values used here are Debug, Release, MinSizeRel, or RelWithDebInfo. Missing this information may have undefined effects on projects that rely on it for configuration.

Here’s an example:

cmake -S . -B ../build -D CMAKE_BUILD_TYPE=Release

Note that multi-configuration generators are configured during the build stage.

For diagnostic purposes, we can also list cache variables with the -L option:

cmake -L -S <source tree> -B <build tree>

Sometimes, project authors may provide insightful help messages with variables – to print them, add the H modifier:

cmake -LH -S <source tree> -B <build tree>
cmake -LAH -S <source tree> -B <build tree>

Surprisingly, custom variables that are added manually with the -D option won’t be visible in this printout unless you specify one of the supported types.

The removal of one or more variables can be done with the following option:

cmake -U <globbing_expr> -S <source tree> -B <build tree>

Here, the globbing expression supports the * (wildcard) and ? (any character) symbols. Be careful when using these, as it is easy to erase more variables than intended.

Both the -U and -D options can be repeated multiple times.

Debugging and tracing

The cmake command can be run with a multitude of options that allow you to peek under the hood. To get general information about variables, commands, macros, and other settings, run the following:

cmake --system-information [file]

The optional file argument allows you to store the output in a file. Running it in the build tree directory will print additional information about the cache variables and build messages from the log files.

In our projects, we’ll be using message() commands to report details of the build process. CMake filters the log output of these based on the current log level (by default, this is STATUS). The following line specifies the log level that we’re interested in:

cmake --log-level=<level>

Here, level can be any of the following: ERROR, WARNING, NOTICE, STATUS, VERBOSE, DEBUG, or TRACE. You can specify this setting permanently in the CMAKE_MESSAGE_LOG_LEVEL cache variable.

Another interesting option allows you to display log context with each message() call. To debug very complex projects, the CMAKE_MESSAGE_CONTEXT variable can be used like a stack. Whenever your code enters an interesting context, you can name it descriptively. By doing this, our messages will be decorated with the current CMAKE_MESSAGE_CONTEXT variable, like so:

[some.context.example] Debug message.

The option to enable this kind of log output is as follows:

cmake --log-context <source tree>

We’ll discuss naming contexts and logging commands in more detail in Chapter 2, The CMake Language.

If all else fails and we need to use the big guns, there is always trace mode, which will print every executed command with its filename, the line number it is called from, and a list of passed arguments. You can enable it as follows:

cmake --trace

As you can imagine, it’s not recommended for everyday use, as the output is very long.

Configuring presets

There are many, many options that users can specify to generate a build tree from your project. When dealing with the build tree path, generator, cache, and environmental variable, it’s easy to get confused or miss something. Developers can simplify how users interact with their projects and provide a CMakePresets.json file that specifies some defaults.

To list all of the available presets, execute the following:

cmake --list-presets

You can use one of the available presets as follows:

cmake --preset=<preset> -S <source> -B <build tree>

To learn more, please refer to the Navigating the project files section of this chapter and Chapter 16, Writing CMake Presets.

Cleaning the build tree

Every now and then, we might need to erase generated files. This may be due to some changes in the environment that were made between builds, or just to ensure that we are working on a clean slate. We can go ahead and delete the build tree directory manually, or just add the --fresh parameter to the command line:

cmake --fresh -S <source tree> -B <build tree>

CMake will then erase CMakeCache.txt and CMakeFiles/ in a system-agnostic way and generate the buildsystem from scratch.

Building a project

After generating our build tree, we’re ready for the building a project action. Not only does CMake know how to generate input files for many different builders but it can also run them for us providing appropriate arguments, as required by our project.

AVOID CALLING MAKE DIRECTLY

Many online sources recommend running GNU Make directly after the generation stage by calling the make command directly. Because GNU Make is a default generator for Linux and macOS, this recommendation can work. However, use the method described in this section instead, as it is generator-independent and is officially supported across all platforms. As a result, you won’t need to worry about the exact environment of every user of your application.

The syntax of build mode is:

cmake --build <build tree> [<options>] [-- <build-tool-options>]

In the majority of cases, it is enough to simply provide the bare minimum to get a successful build:

cmake --build <build tree>

The only required argument is the path to the generated build tree. This is the same path that was passed with the -B argument in the generation stage.

CMake allows you to specify key build parameters that work for every builder. If you need to provide special arguments to your chosen native builder, pass them at the end of the command after the -- token:

cmake --build <build tree> -- <build tool options>

Let’s see what other options are available.

Running parallel builds

By default, many build tools will use multiple concurrent processes to leverage modern processors and compile your sources in parallel. Builders know the structure of project dependencies, so they can simultaneously process steps that have their dependencies met to save users’ time.

You might want to override that setting if you’d like to build faster on a multi-core machine (or to force a single-threaded build for debugging).

Simply specify the number of jobs with either of the following options:

cmake --build <build tree> --parallel [<number of jobs>]
cmake --build <build tree> -j [<number of jobs>]

The alternative is to set it with the CMAKE_BUILD_PARALLEL_LEVEL environment variable. The command-line option will override this variable.

Selecting targets to build and clean

Every project is made up of one or more parts, called targets (we’ll discuss these in the second part of the book). Usually, we’ll want to build all available targets; however, on occasion, we might be interested in skipping some or explicitly building a target that was deliberately excluded from normal builds. We can do this as follows:

cmake --build <build tree> --target <target1> --target <target2> …

We can specify multiple targets to build by repeating the –target argument. Also, there’s a shorthand version, -t <target>, that can be used instead.

Cleaning the build tree

One special target that isn’t normally built is called clean. Building it has the special effect of removing all artifacts from the build directory, so everything can be created from scratch later. You can start this process like this:

cmake --build <build tree> -t clean

Additionally, CMake offers a convenient alias if you’d like to clean first and then implement a normal build:

cmake --build <build tree> --clean-first

This action is different from cleaning mentioned in the Cleaning the build tree section, as it only affects target artifacts and nothing else (like the cache).

Configuring the build type for multi-configuration generators

So, we already know a bit about generators: they come in different shapes and sizes. Some of them offer the ability to build both Debug and Release build types in a single build tree. Generators that support this feature include Ninja Multi-Config, Xcode, and Visual Studio. Every other generator is a single-configuration generator, and they require a separate build tree for every config type we want to build.

Select Debug, Release, MinSizeRel, or RelWithDebInfo and specify it as follows:

cmake --build <build tree> --config <cfg>

Otherwise, CMake will use Debug as the default.

Debugging the build process

When things go bad, the first thing we should do is check the output messages. However, veteran developers know that printing all the details all the time is confusing, so they often hide them by default. When we need to peek under the hood, we can ask for far more detailed logs by telling CMake to be verbose:

cmake --build <build tree> --verbose
cmake --build <build tree> -v

The same effect can be achieved by setting the CMAKE_VERBOSE_MAKEFILE cached variable.

Installing a project

When artifacts are built, users can install them on the system. Usually, this means copying files into the correct directories, installing libraries, or running some custom installation logic from a CMake script.

The syntax of installation mode is:

cmake --install <build tree> [<options>]

As with other modes of operation, CMake requires a path to a generated build tree:

cmake --install <build tree>

The install action also has plenty of additional options. Let’s see what they can do.

Choosing the installation directory

We can prepend the installation path with a prefix of our choice (for example, when we have limited write access to some directories). The /usr/local path that is prefixed with /home/user becomes /home/user/usr/local.

The signature for this option is as follows:

cmake --install <build tree> --install-prefix <prefix>

If you use CMake 3.21 or older, you’ll have to use a less explicit option:

cmake --install <build tree> --prefix <prefix>

Note that this won’t work on Windows, as paths on this platform usually start with the drive letter.

Installation for multi-configuration generators

Just like in the build stage, we can specify which build type we want to use for our installation (for more details, please refer to the Building a project section). The available types include Debug, Release, MinSizeRel, and RelWithDebInfo. The signature is as follows:

cmake --install <build tree> --config <cfg>

Selecting components to install

As a developer, you might choose to split your project into components that can be installed independently. We’ll discuss the concept of components in further detail in Chapter 14, Installing and Packaging. For now, let’s just assume they represent sets of artifacts that don’t need to be used in every case. This might be something like application, docs, and extra-tools.

To install a single component, use the following option:

cmake --install <build tree> --component <component>

Setting file permissions

If the installation is performed on a Unix-like platform, you can specify default permissions for the installed directories with the following option, using the format of u=rwx,g=rx,o=rx:

cmake --install <build tree>
      --default-directory-permissions <permissions>

Debugging the installation process

Similarly to the build stage, we can also choose to view a detailed output of the installation stage. To do this, use any of the following:

cmake --install <build tree> --verbose
cmake --install <build tree> -v

The same effect can be achieved if the VERBOSE environment variable is set.

Running a script

CMake projects are configured using CMake’s custom language. It’s cross-platform and quite powerful. Since it’s already there, why not make it available for other tasks? Sure enough, CMake can run standalone scripts (more on that in the Discovering scripts and modules section), like so:

cmake [{-D <var>=<value>}...] -P <cmake script file>
      [-- <unparsed options>...]

Running such a script won’t run any configuration or generate stages, and it won’t affect the cache.

There are two ways you can pass values to this script:

  • Through variables defined with the -D option
  • Through arguments that can be passed after a -- token

CMake will create CMAKE_ARGV<n> variables for all arguments passed to the script with the latter (including the -- token).

Running a command-line tool

On rare occasions, we might need to run a single command in a platform-independent way – perhaps copy a file or compute a checksum. Not all platforms were created equal, so not all commands are available in every system (or they have been named differently).

CMake offers a mode in which most common commands can be executed in the same way across platforms. Its syntax is:

cmake -E <command> [<options>]

As the use of this particular mode is fairly limited, we won’t cover it in depth. However, if you’re interested in the details, I recommend calling cmake -E to list all the available commands. To simply get a glimpse of what’s on offer, CMake 3.26 supports the following commands: capabilities, cat, chdir, compare_files, copy, copy_directory, copy_directory_if_different, copy_if_different, echo, echo_append, env, environment, make_directory, md5sum, sha1sum, sha224sum, sha256sum, sha384sum, sha512sum, remove, remove_directory, rename, rm, sleep, tar, time, touch, touch_nocreate, create_symlink, create_hardlink, true, and false.

If a command you’d like to use is missing or you need a more complex behavior, consider wrapping it in a script and running it in -P mode.

Running a workflow preset

We mentioned in the How does it work? section that building with CMake has three stages: configure, generate, and build. Additionally, we can also run automated tests and even create redistributable packages with CMake. Usually, users need to manually execute every such step separately by calling the appropriate cmake action through the command line. However, advanced projects can specify workflow presets that bundle multiple steps into a single action that can be executed with just one command. For now, we’ll only mention that users can get the list of available presets by running:

cmake ––workflow --list-presets

They can execute a workflow preset with:

cmake --workflow --preset <name>

This will be explained in depth in Chapter 16, Writing CMake Presets.

Getting help

It isn’t a surprise that CMake offers extensive help that is accessible through its command line. The syntax of help mode is:

cmake --help

This will print the list of the possible topics to dive deeper into and explain which parameters need to be added to the command to get more help.

CTest command line

Automated testing is very important in order to produce and maintain high-quality code. The CMake suite comes with a dedicated command-line tool for this purpose called CTest. It is provided to standardize the way tests are run and reported. As a CMake user, you don’t need to know the details of testing this particular project: what framework is used or how to run it. CTest provides a convenient interface to list, filter, shuffle, retry, and timebox test runs.

To run tests for a built project, we just need to call ctest in the generated build tree:

$ ctest
Test project /tmp/build
Guessing configuration Debug
    Start 1: SystemInformationNew
1/1 Test #1: SystemInformationNew .........   Passed 3.19 sec
100% tests passed, 0 tests failed out of 1
Total Test time (real) =   3.24 sec

We devoted an entire chapter to this subject: Chapter 11, Testing Frameworks.

CPack command line

After we have built and tested our amazing software, we are ready to share it with the world. The rare few power users are completely fine with the source code. However, the vast majority of the world uses precompiled binaries for convenience and time-saving reasons.

CMake doesn’t leave you stranded here; it comes with batteries included. CPack is a tool that will create redistributable packages for various platforms: compressed archives, executable installers, wizards, NuGet packages, macOS bundles, DMG packages, RPMs, and more.

CPack works in a very similar way to CMake: it is configured with the CMake language and has many package generators to pick from (not to be confused with CMake buildsystem generators). We’ll go through all the details in Chapter 14, Installing and Packaging, as this tool is meant to be used by mature CMake projects.

CMake GUI

CMake for Windows comes with a GUI version to configure the building process of previously prepared projects. For Unix-like platforms, there is a version built with Qt libraries. Ubuntu provides it in the cmake-qt-gui package.

To access the CMake GUI, run the cmake-gui executable:

Figure 1.3: The CMake GUI – the configuring stage for a buildsystem using a generator for Visual Studio 2019

The GUI application is a convenience for users of your application: it can be useful for those who aren’t familiar with the command line and would prefer a graphical interface.

USE COMMAND-LINE TOOLS INSTEAD

I would definitely recommend the GUI to end users, but for programmers like you, I suggest avoiding any manual blocking steps that require clicking on forms every time you build your programs. This is especially advantageous in mature projects, where entire builds can be fully executed without any user interaction.

CCMake command line

The ccmake executable is an interactive text user interface for CMake on Unix-like platforms (it’s unavailable for Windows unless explicitly built). I’m mentioning it here so you know what it is when you see it (Figure 1.4, but as with the GUI, developers will benefit more from editing the CMakeCache.txt file directly.

Figure 1.4: The configuring stage in ccmake

Having this out of the way, we have concluded the basic introduction to the command line of the CMake suite. It’s time to discover the structure of a typical CMake project.

Navigating project directories and files

Quite a lot of files and directories make up CMake projects. Let’s get a general idea of what each one does so we can start tinkering with them. There are several general categories:

  • Of course, we’ll have project files that we, as developers, prepare and change as our project grows.
  • There will be files that CMake generates for itself, and even though they will contain CMake language commands, they aren’t meant for developers to edit. Any manual changes made there will be overwritten by CMake.
  • Some files are meant for advanced users (as in: not project developers) to customize how CMake builds the project to their individual needs.
  • Finally, there are some temporary files that provide valuable information in specific contexts.

This section will also suggest which files you can put in the ignore file of your Version Control System (VCS).

The source tree

This is the directory where your project will live (it is also called the project root). It contains all of the C++ sources and CMake project files.

Here are the most important takeaways from this directory:

  • It requires a CMakeLists.txt configuration file.
  • The path to this directory is given by the user with a -S argument of the cmake command when generating a buildsystem.
  • Avoid hardcoding any absolute paths to the source tree in your CMake code – users of your software will store the project in another path.

It’s a good idea to initialize a repository in this directory, perhaps using a VCS like Git.

The build tree

CMake creates this directory in a path specified by the user. It will store the buildsystem and everything that gets created during the build: the artifacts of the project, the transient configuration, the cache, the build logs, and the output of your native build tool (like GNU Make). Alternative names for this directory include build root and binary tree.

Key things to remember:

  • Your build configuration (buildsystem) and build artifacts will be created here (such as binary files, executables, and libraries, along with object files and archives used for final linking).
  • CMake recommends that this directory be placed outside the source tree directory (a practice known as out-of-source builds). This way, we can prevent the pollution of our project (in-source builds).
  • It is specified with -B to the cmake command when generating a buildsystem.
  • This directory isn’t meant as a final destination for generated files. Rather, it’s recommended that your projects include an installation stage that copies the final artifacts where they should be in the system and removes all temporary files used for building.

Don’t add this directory to your VCS – every user picks one for themselves. If you have a good reason to do an in-source build, make sure to add this directory to the VCS ignore file (like .gitignore).

Listfiles

Files that contain the CMake language are called listfiles and can be included one in another by calling include() and find_package(), or indirectly with add_subdirectory(). CMake doesn’t enforce any naming rules for these files but, by convention, they have a .cmake extension.

Project file

CMake projects are configured with a CMakeLists.txt listfile (notice that due to historical reasons, this file has an unconventional extension). This file is required at the top of the source tree of every project and is the first to be executed in the configuration stage.

A top-level CMakeLists.txt should contain at least two commands:

  • cmake_minimum_required(VERSION <x.xx>): Sets an expected version of CMake and tells CMake how to handle legacy behaviors with policies
  • project(<name> <OPTIONS>): Names the project (the provided name will be stored in the PROJECT_NAME variable) and specifies the options to configure it (more on this in Chapter 2, The CMake Language)

As your software grows, you might want to partition it into smaller units that can be configured and reasoned about separately. CMake supports this through the notion of subdirectories with their own CMakeLists.txt files. Your project structure might look similar to the following example:

myProject/CMakeLists.txt
myProject/api/CMakeLists.txt
myProject/api/api.h
myProject/api/api.cpp

A very simple top-level CMakeLists.txt file can then be used to bring it all together:

cmake_minimum_required(VERSION 3.26)
project(app)
message("Top level CMakeLists.txt")
add_subdirectory(api)

The main aspects of the project are covered in the top-level file: managing the dependencies, stating the requirements, and detecting the environment. We also have an add_subdirectory(api) command to include another CMakeListst.txt file from the api subdirectory to perform steps that are specific to the API part of our application.

Cache file

Cache variables will be generated from the listfiles and stored in CMakeCache.txt when the configure stage is run for the first time. This file resides in the root of the build tree and has a fairly simple format (some lines removed for brevity):

# This is the CMakeCache file.
# For build in directory: /root/build tree
# It was generated by CMake: /usr/local/bin/cmake
# The syntax for the file is as follows:
# KEY:TYPE=VALUE
# KEY is the name of a variable in the cache.
# TYPE is a hint to GUIs for the type of VALUE, DO NOT EDIT
  #TYPE!.
# VALUE is the current value for the KEY.
########################
# EXTERNAL cache entries
########################
# Flags used by the CXX compiler during DEBUG builds.
CMAKE_CXX_FLAGS_DEBUG:STRING=/MDd /Zi /Ob0 /Od /RTC1
# ... more variables here ...
########################
# INTERNAL cache entries
########################
# Minor version of cmake used to create the current loaded
  cache
CMAKE_CACHE_MINOR_VERSION:INTERNAL=19
# ... more variables here ...

As you can see from the header comments, this format is pretty self-explanatory. Cache entries in the EXTERNAL section are meant for users to modify, while the INTERNAL section is managed by CMake.

Here are a couple of key takeaways to bear in mind:

  • You can manage this file manually, by calling cmake (see Options for caching in the Mastering the command line section of this chapter), or through ccmake or cmake-gui.
  • You can reset the project to its default configuration by deleting this file; it will be regenerated from the listfiles.

Cache variables can be read and written from the listfiles. Sometimes, variable reference evaluation is a bit complicated; we will cover that in more detail in Chapter 2, The CMake Language.

Package definition file

A big part of the CMake ecosystem is the external packages that projects can depend on. They provide libraries and tools in a seamless, cross-platform way. Package authors that want to provide CMake support will ship it with a CMake package configuration file.

We’ll learn how to write those files in Chapter 14, Installing and Packaging. Meanwhile, here’s a few interesting details to bear in mind:

  • Config-files (original spelling) contain information regarding how to use the library binaries, headers, and helper tools. Sometimes, they expose CMake macros and functions that can be used in your project.
  • Config-files are named <PackageName>-config.cmake or <PackageName>Config.cmake.
  • Use the find_package() command to include packages.

If a specific version of the package is required, CMake will check this against the associated <PackageName>-config-version.cmake or <PackageName>ConfigVersion.cmake.

If a vendor doesn’t provide a config file for the package, sometimes, the configuration is bundled with the CMake itself or can be provided in the project with Find-module (original spelling).

Generated files

Many files are generated in the build tree by the cmake executable in the generation stage. As such, they shouldn’t be edited manually. CMake uses them as a configuration for the cmake install action, CTest, and CPack.

Files that you may encounter are:

  • cmake_install.cmake
  • CTestTestfile.cmake
  • CPackConfig.cmake

If you’re implementing an in-source build, it’s probably a good idea to add them to the VCS ignore file.

JSON and YAML files

Other formats used by CMake are JavaScript Object Notation (JSON) and Yet Another Markup Language (YAML). These files are introduced as an interface to communicate with external tools (like IDEs) or to provide configuration that can be easily generated and parsed.

Preset files

The advanced configuration of the projects can become a relatively busy task when we need to be specific about things such as cache variables, chosen generators, the path of the build tree, and more – especially when we have more than one way of building our project. This is where the presets come in – instead of manually configuring these values through the command line, we can just provide a file that stores all the details and ship it with the project. Since CMake 3.25, presets also allow us to configure workflows, which tie stages (configure, build, test, and package) into a named list of steps to execute.

As mentioned in the Mastering the command line section of this chapter, users can choose presets through the GUI or use the command --list-presets and select a preset for the buildsystem with the --preset=<preset> option.

Presets are stored in two files:

  • CMakePresets.json: This is meant for project authors to provide official presets.
  • CMakeUserPresets.json: This is dedicated to users who want to customize the project configuration to their liking (you can add it to your VCS ignore file).

Presets are not required in projects and only become useful in advanced scenarios. See Chapter 16, Writing CMake Presets, for details.

File-based API

CMake 3.14 introduced an API that allows external tools to query the buildsystem information: paths to generated files, cache entries, toolchains, and such. We only mention this very advanced topic to avoid confusion if you come across a file-based API phrase in the documentation. The name suggests how it works: a JSON file with a query has to be placed in a special path inside the build tree. CMake will read this file during the buildsystem generation and write a response to another file, so it can be parsed by external applications.

The file-based API was introduced to replace a deprecated mechanism called server mode (or cmake-server), which was finally removed in CMake 3.26.

Configure log

Since version 3.26, CMake will provide a structured log file for really advanced debugging of the configure stage at:

<build tree>/CMakeFiles/CMakeConfigureLog.yaml

It’s one of these features that you don’t normally need to pay attention to – until you do.

Ignoring files in Git

There are many VCSs; one of the most popular out there is Git. Whenever we start a new project, it is good to make sure that we only add the necessary files to the repository. Project hygiene is easier to maintain if we specify unwanted files in the .gitignore file. For example, we might exclude files that are generated, user-specific, or temporary.

Git will automatically skip them when forming new commits. Here’s the file that I use in my projects:

ch01/01-hello/.gitignore

CMakeUserPresets.json
# If in-source builds are used, exclude their output like so:
build_debug/
build_release/
# Generated and user files
**/CMakeCache.txt
**/CMakeUserPresets.json
**/CTestTestfile.cmake
**/CPackConfig.cmake
**/cmake_install.cmake
**/install_manifest.txt
**/compile_commands.json

Now you hold a map to the sea of project files. Some files are very important and you will use them all the time – others, not so much. While it might seem like a waste to learn about them, it can be invaluable to know where not to look for answers. In any case, one last question for this chapter remains: what other self-contained units can you create with CMake?

Discovering scripts and modules

CMake is primarily focused on projects built to produce artifacts that get consumed by other systems (such as CI/CD pipelines and test platforms, or deployed to machines or stored in artifact repositories). However, there are two other concepts in CMake that use its language: scripts and modules. Let’s explain what they are and how they differ.

Scripts

CMake offers a platform-agnostic programming language, which comes with many useful commands. Scripts written in it can be bundled with a bigger project or be completely independent.

Think of it as a consistent way to do cross-platform work. Normally, to perform a task, you would have to create a separate Bash script for Linux and separate batch files or PowerShell scripts for Windows, and so on. CMake abstracts this away so you can have one file that works fine on all platforms. Sure, you could use external tools such as Python, Perl, or Ruby scripts, but that’s an added dependency and will increase the complexity of your C/C++ projects. So why introduce another language, when most of the time, you can get the job done with something far simpler? Use CMake!

We have already learned from the Mastering the command line section that we can execute scripts using the -P option: cmake -P script.cmake.

But what are the actual requirements for the script file that we want to use? Not that big: the script can be as complex as you like, or just an empty file. It is still recommended to call the cmake_minimum_required() command at the beginning of every script though. This command tells CMake which policies should be applied to subsequent commands in this project (more in Chapter 4, Setting Up Your First CMake Project).

Here’s an example of a simple script:

ch01/02-script/script.cmake

# An example of a script
cmake_minimum_required(VERSION 3.26.0)
message("Hello world")
file(WRITE Hello.txt "I am writing to a file")

When running scripts, CMake won’t execute any of the usual stages (such as configuration or generation), and it won’t use the cache, since there is no concept of source tree or build tree in scripts. This means that project-specific CMake commands are not available/usable in scripting mode. That’s all. Happy scripting!

Utility modules

CMake projects can use external modules to enhance their functionality. Modules are written in the CMake language and contain macro definitions, variables, and commands that perform all kinds of functions. They range from quite complex scripts (like those provided by CPack and CTest) to fairly simple ones, such as AddFileDependencies or TestBigEndian.

The CMake distribution comes packed with over 80 different utility modules. If that’s not enough, you can download more from the internet by browsing curated lists, such as the one found at https://github.com/onqtam/awesome-cmake, or write your own module from scratch.

To use a utility module, we need to call an include(<MODULE>) command. Here’s a simple project showing this in action:

ch01/03-module/CMakeLists.txt

cmake_minimum_required(VERSION 3.26.0)
project(ModuleExample)
include (TestBigEndian)
test_big_endian(IS_BIG_ENDIAN)
if(IS_BIG_ENDIAN)
message("BIG_ENDIAN")
else()
message("LITTLE_ENDIAN")
endif()

We’ll learn what modules are available as they become relevant to the subject at hand. If you’re curious, a full list of bundled modules can be found at https://cmake.org/cmake/help/latest/manual/cmake-modules.7.html.

Find-modules

In the Package definition File section, I mentioned that CMake has a mechanism to find files belonging to external dependencies that don’t support CMake and don’t provide a CMake package config-file. That’s what find-modules are for. CMake provides over 150 find-modules that are able to locate those packages if they are installed in the system. As was the case with utility modules, there are plenty more find-modules available online. As a last resort, you can always write your own.

You can use them by calling the find_package() command and providing the name of the package in question. Such a find-module will then play a little game of hide and seek and check all known locations of the software it is looking for. If the files are found, variables with their path will be defined (as specified in that module’s manual). Now, CMake can build against that dependency.

For example, the FindCURL module searches for a popular Client URL library and defines the following variables: CURL_FOUND, CURL_INCLUDE_DIRS, CURL_LIBRARIES, and CURL_VERSION_STRING.

We will cover find-modules in more depth in Chapter 9, Managing Dependencies in CMake.

Summary

Now you understand what CMake is and how it works; you learned about the key components of the CMake tool family and how it is installed on a variety of systems. Like a true power user, you know all the ways in which to run CMake through the command line: buildsystem generation, building a project, installing, running scripts, command-line tools, and printing help. You are aware of the CTest, CPack, and GUI applications. This will help you to create projects with the right perspective for users and other developers. Additionally, you learned what makes up a project: directories, listfiles, configs, presets, and helper files, along with what to ignore in your VCS. Finally, you took a sneak peek at other non-project files: standalone scripts and two kinds of modules – utility modules and find-modules.

In the next chapter, we will learn how to use the CMake programming language. This will allow you to write your own listfiles and will open the door to your first script, project, and module.

Further reading

For more information, you can refer to the following resources:

Leave a review!

Enjoying this book? Help readers like you by leaving an Amazon review. Scan the QR code below to get a free eBook of your choice.

Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Get to grips with CMake and take your C++ development skills to enterprise standards
  • Use hands-on exercises and self-assessment questions to lock-in your learning
  • Understand how to build in an array of quality checks and tests for robust code

Description

Modern CMake for C++ isn't just another reference book, or a repackaging of the documentation, but a blueprint to bridging the gap between learning C++ and being able to use it in a professional setting. It's an end-to-end guide to the automation of complex tasks, including building, testing, and packaging software. This second edition is significantly rewritten, restructured and refreshed with latest additions to CMake, such as support of C++20 Modules. In this book, you'll not only learn how to use the CMake language in CMake projects but also discover how to make those projects maintainable, elegant, and clean. As you progress, you'll dive into the structure of source directories, building targets, and packages, all while learning how to compile and link executables and libraries. You'll also gain a deeper understanding of how those processes work and how to optimize builds in CMake for the best results. You'll discover how to use external dependencies in your project – third-party libraries, testing frameworks, program analysis tools, and documentation generators. Finally, you'll gain profi ciency in exporting, installing, and packaging for internal and external purposes. By the end of this book, you'll be able to use CMake confi dently at a professional level.

Who is this book for?

The book is for build engineers and software developers with knowledge of C/C++ programming who are looking to learn CMake to automate the process of building small and large software solutions. If you’re just getting started with CMake, a long-time GNU Make user, or simply looking to brush up on the latest best practices, this book is for you.

What you will learn

  • Understand best practices to build ++ code
  • Gain practical knowledge of the CMake language
  • Guarantee code quality with tests and static and dynamic analysis
  • Discover how to manage, discover, download, and link dependencies with CMake
  • Build solutions that can be reused and maintained in the long term
  • Understand how to optimize build artifacts and the build process
  • Program modern CMake and manage your build processes
  • Acquire expertise in complex subjects such as CMake presets
Estimated delivery fee Deliver to Great Britain

Standard delivery 1 - 4 business days

£4.95

Premium delivery 1 - 4 business days

£7.95
(Includes tracking information)

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date : May 28, 2024
Length: 502 pages
Edition : 2nd
Language : English
ISBN-13 : 9781805121800
Category :
Languages :
Tools :

What do you get with Print?

Product feature icon Instant access to your digital eBook copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Product feature icon AI Assistant (beta) to help accelerate your learning
OR
Modal Close icon
Payment Processing...
tick Completed

Shipping Address

Billing Address

Shipping Methods
Estimated delivery fee Deliver to Great Britain

Standard delivery 1 - 4 business days

£4.95

Premium delivery 1 - 4 business days

£7.95
(Includes tracking information)

Product Details

Publication date : May 28, 2024
Length: 502 pages
Edition : 2nd
Language : English
ISBN-13 : 9781805121800
Category :
Languages :
Tools :

Packt Subscriptions

See our plans and pricing
Modal Close icon
£16.99 billed monthly
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Simple pricing, no contract
£169.99 billed annually
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just £5 each
Feature tick icon Exclusive print discounts
£234.99 billed in 18 months
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just £5 each
Feature tick icon Exclusive print discounts

Frequently bought together


Stars icon
Total £ 86.97 117.97 31.00 saved
Modern C++ Programming Cookbook
£28.99 £41.99
Asynchronous Programming in Rust
£31.99 £37.99
Modern CMake for C++
£25.99 £37.99
Total £ 86.97 117.97 31.00 saved Stars icon
Banner background image

Table of Contents

18 Chapters
First Steps with CMake Chevron down icon Chevron up icon
The CMake Language Chevron down icon Chevron up icon
Using CMake in Popular IDEs Chevron down icon Chevron up icon
Setting Up Your First CMake Project Chevron down icon Chevron up icon
Working with Targets Chevron down icon Chevron up icon
Using Generator Expressions Chevron down icon Chevron up icon
Compiling C++ Sources with CMake Chevron down icon Chevron up icon
Linking Executables and Libraries Chevron down icon Chevron up icon
Managing Dependencies in CMake Chevron down icon Chevron up icon
Using the C++20 Modules Chevron down icon Chevron up icon
Testing Frameworks Chevron down icon Chevron up icon
Program Analysis Tools Chevron down icon Chevron up icon
Generating Documentation Chevron down icon Chevron up icon
Installing and Packaging Chevron down icon Chevron up icon
Creating Your Professional Project Chevron down icon Chevron up icon
Writing CMake Presets Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon
Index Chevron down icon Chevron up icon

Customer reviews

Top Reviews
Rating distribution
Full star icon Full star icon Full star icon Full star icon Half star icon 4.6
(11 Ratings)
5 star 72.7%
4 star 18.2%
3 star 9.1%
2 star 0%
1 star 0%
Filter icon Filter
Top Reviews

Filter reviews by




Austin Bachurski Jun 03, 2024
Full star icon Full star icon Full star icon Full star icon Full star icon 5
Prior to going through this book, I had no idea that CMake is all but a programming language unto itself. This book goes over a ton of information with lots of examples on things like testing. How to use test frameworks with CMake. How to use analysis tools for both performance and safety using CMake. Generating documentation with CMake. I didn't even know CMake could do these things, but this book has an entire section for each of these topics. It's quite a lot to take in at once, but it's going to be a great reference to have on the shelf to come back to when I have questions. Highly recommended if you're finding CMake confusing.
Amazon Verified review Amazon
Felix Bytow Jun 21, 2024
Full star icon Full star icon Full star icon Full star icon Full star icon 5
Disclaimer: I received a free review copy.But I liked it so much, I actually ordered a physical copy as well.I'm using CMake for many years already to build my C and C++ projects.Over the years a lot of things changed. A lot of things became easier with CMake,but CMake also became more powerful.So I was thrilled, when I saw this book. Reading through it, I found it to be a rather complete manualto everything CMake has to offer. There was a lot of information about functionality,that I had either only heard about, or didn't even know exists.I think the book handles both pretty well:As a beginner, reading it from the start, will give a good introduction of what CMake is, what it does, how it integrates with other tools and what best practices are.For experienced users it can act as a reference, whenever you find yourself in a situation, where you are unsure how to do something.
Amazon Verified review Amazon
Amazon Customer Jul 15, 2024
Full star icon Full star icon Full star icon Full star icon Full star icon 5
I enjoyed the read having recently picked up CMake. This book is well structured and comprehensive, giving practical examples of how to get started. Each chapter is broken down into bite size chunks that are easy to follow and grasp.
Amazon Verified review Amazon
Neil Jul 03, 2024
Full star icon Full star icon Full star icon Full star icon Full star icon 5
Disclosure: I was provided an early review copy at no expense but these opinions are my own.I've used CMake for several years and know enough to generally make it do what I need it to do. That being said, there's always more to learn. This book is a fantastic resource for a number of reasons.1. It starts from an introductory level with very few assumptions of your current knowledge.2. There are a number of side-notes and tips for best practices that can help provide context for deeper understanding.3. It goes beyond introductory tutorials and explains deeper concepts before ending with a solid summary chapter project.By building on a foundation of basics piece by piece all the way to more complicated topics -- with chapters explaining concepts that I didn't know even after using CMake for years -- I anticipate that this book would be a solid roadmap for beginners to learn how to start effectively using CMake in their projects and for the proficient to at least learn something new.
Amazon Verified review Amazon
Y. Arazi Sep 13, 2024
Full star icon Full star icon Full star icon Full star icon Full star icon 5
I recently read Modern CMake for C++, Second Edition and was thoroughly impressed. Despite considering myself highly technical and knowledgeable in CMake, I still learned a plethora of new information. The book covers a wide range of topics, from debugging a CMake project, understanding the grammar, targets, and package management like FetchContent, to using CMake in advanced IDEs.One of the standout aspects of this book is its guidance on properly setting up a project. It emphasizes good practices, what to focus on when building a project, the hierarchy, and various gotchas to avoid. The book even delves into the linking models of C and C++ and how to handle them correctly in CMake.This book is a must-read for every developer using CMake. By following the rules and best practices outlined, it will make your project healthier. Regardless of your experience level, you are bound to pick up new skills. The format and organization of this book are simply fabulous, making it highly recommended.
Amazon Verified review Amazon
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

What is the delivery time and cost of print book? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela
What is custom duty/charge? Chevron down icon Chevron up icon

Customs duty are charges levied on goods when they cross international borders. It is a tax that is imposed on imported goods. These duties are charged by special authorities and bodies created by local governments and are meant to protect local industries, economies, and businesses.

Do I have to pay customs charges for the print book order? Chevron down icon Chevron up icon

The orders shipped to the countries that are listed under EU27 will not bear custom charges. They are paid by Packt as part of the order.

List of EU27 countries: www.gov.uk/eu-eea:

A custom duty or localized taxes may be applicable on the shipment and would be charged by the recipient country outside of the EU27 which should be paid by the customer and these duties are not included in the shipping charges been charged on the order.

How do I know my custom duty charges? Chevron down icon Chevron up icon

The amount of duty payable varies greatly depending on the imported goods, the country of origin and several other factors like the total invoice amount or dimensions like weight, and other such criteria applicable in your country.

For example:

  • If you live in Mexico, and the declared value of your ordered items is over $ 50, for you to receive a package, you will have to pay additional import tax of 19% which will be $ 9.50 to the courier service.
  • Whereas if you live in Turkey, and the declared value of your ordered items is over € 22, for you to receive a package, you will have to pay additional import tax of 18% which will be € 3.96 to the courier service.
How can I cancel my order? Chevron down icon Chevron up icon

Cancellation Policy for Published Printed Books:

You can cancel any order within 1 hour of placing the order. Simply contact [email protected] with your order details or payment transaction id. If your order has already started the shipment process, we will do our best to stop it. However, if it is already on the way to you then when you receive it, you can contact us at [email protected] using the returns and refund process.

Please understand that Packt Publishing cannot provide refunds or cancel any order except for the cases described in our Return Policy (i.e. Packt Publishing agrees to replace your printed book because it arrives damaged or material defect in book), Packt Publishing will not accept returns.

What is your returns and refunds policy? Chevron down icon Chevron up icon

Return Policy:

We want you to be happy with your purchase from Packtpub.com. We will not hassle you with returning print books to us. If the print book you receive from us is incorrect, damaged, doesn't work or is unacceptably late, please contact Customer Relations Team on [email protected] with the order number and issue details as explained below:

  1. If you ordered (eBook, Video or Print Book) incorrectly or accidentally, please contact Customer Relations Team on [email protected] within one hour of placing the order and we will replace/refund you the item cost.
  2. Sadly, if your eBook or Video file is faulty or a fault occurs during the eBook or Video being made available to you, i.e. during download then you should contact Customer Relations Team within 14 days of purchase on [email protected] who will be able to resolve this issue for you.
  3. You will have a choice of replacement or refund of the problem items.(damaged, defective or incorrect)
  4. Once Customer Care Team confirms that you will be refunded, you should receive the refund within 10 to 12 working days.
  5. If you are only requesting a refund of one book from a multiple order, then we will refund you the appropriate single item.
  6. Where the items were shipped under a free shipping offer, there will be no shipping costs to refund.

On the off chance your printed book arrives damaged, with book material defect, contact our Customer Relation Team on [email protected] within 14 days of receipt of the book with appropriate evidence of damage and we will work with you to secure a replacement copy, if necessary. Please note that each printed book you order from us is individually made by Packt's professional book-printing partner which is on a print-on-demand basis.

What tax is charged? Chevron down icon Chevron up icon

Currently, no tax is charged on the purchase of any print book (subject to change based on the laws and regulations). A localized VAT fee is charged only to our European and UK customers on eBooks, Video and subscriptions that they buy. GST is charged to Indian customers for eBooks and video purchases.

What payment methods can I use? Chevron down icon Chevron up icon

You can pay with the following card types:

  1. Visa Debit
  2. Visa Credit
  3. MasterCard
  4. PayPal
What is the delivery time and cost of print books? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela