How to use CMake

This is just a reminder for some syntax/parameter details on how to invoke CMake (i.e. the actual cmake program), mainly from the CLI, to configure/generate/build a project – it’s not a tutorial on how to write a CMakeLists.txt file!

By the way: I just discovered that I had written a similar (but much simpler and shorter) predecessor article a long time ago (late 2017, never updated). I integrated a few bits of it here, before I removed it from the site (and set an alias for the old URL).

General CMake usage

CMake is a collection of open source, cross-platform tools designed to build, test, and package software.

It’s a “meta-build tool”: It configures and generates a buildsystem for a given project, but requires then other, native tools (like Microsoft Visual Studio (Community Edition) on Windows) to make use of that buildsystem; the same principle applies for areas like testing and packaging)

Workflow

After CMake has configured and generated the buildsystem, one could then either simply use e.g. Visual Studio for the rest of the process…
Or let CMake trigger the next steps, since it also acts as a platform-/tool-agnostic wrapper for invoking the native tools indirectly (see below).

An usual workflow goes like this: Configure & Generate ๐Ÿกข Build [ ๐Ÿกข Install ]

These are the three main steps when working with cmake:

  1. Configure and Generate the project’s buildsystem files.
    • This requires a CMakeLists.txt file, which contains the instructions on how to create a buildsystem – but the explanation of writing such a file is too extensive and thus out of scope for this post!
    • Process that CMakeLists.txt file with CMake; that will result in the creation of a buildsystem (e.g. *.sln, *.vcxproj, etc. files for a Visual Studio build).
  2. Build the project (with native tools), from the previously generated buildsystem files.
  3. Install (optionally) on the current machine the artifacts that were built by the previous step (i.e. executables, libraries, etc.).

Additionally, one can also prepare and run tests with ctest or create setup packages (installers and source packages in a variety of formats) with cpack; but these topics are too much to cover here.

Example

A basic filesystem structure for one of my projects normally looks something like this:

C:\ProjectDir\
   โ”œโ”€โ”€ ChangeLog.txt
   โ”œโ”€โ”€ ReadMe.md
   โ”œโ”€โ”€ LICENSE.txt
   โ”œโ”€โ”€ ...
   โ”‚
   โ”œโ”€โ”€ build                    โ† The directory for "cmake -B"
   โ”‚   โ•ฐโ”€โ”€ ...
   โ”‚
   โ•ฐโ”€โ”€ src                      โ† The directory for "cmake -S"
       โ”œโ”€โ”€ CMakeLists.txt       โ† The top-level (primary) CMakeLists.txt file
       โ”œโ”€โ”€ CMakePresets.json    โ† Optional: The presets for this project (must be on the same level)
       โ”œโ”€โ”€ ...
       โ”‚
       โ”œโ”€โ”€ Application
       โ”‚   โ”œโ”€โ”€ CMakeLists.txt   โ† Will be included by the primary CMakeLists.txt
       โ”‚   โ”œโ”€โ”€ *.cpp, *.h, ...
       โ”‚   โ•ฐโ”€โ”€ ...
       โ”‚
       โ”œโ”€โ”€ Other Stuff
       โ”‚   โ”œโ”€โ”€ CMakeLists.txt
       โ”‚   โ”œโ”€โ”€ *.cpp, *.h, ...
       .   โ•ฐโ”€โ”€ ...

Assuming that is the layout, it then could go on like this:

  1. Open a command prompt and go to your project folder.
  2. Go to the directory where the primary CMakeLists.txt file is (src)
  3. Call cmake to configure and generate the project by specifying where the input sources are (this directory: src), where the output buildsystem should be put (build) and for which tool (a version of 64-bit Visual Studio).
  4. Let CMake trigger the actual build process (in this example: for all targets and in Release mode).

Tip: These steps can be simplified more by using presets (which I’ll explain later).

C:\>               cd C:\ProjectDir
C:\ProjectDir>     cd src
C:\ProjectDir\src> cmake -S . -B ..\build -G "Visual Studio 17 2022" -A x64
C:\ProjectDir\src> cmake --build ..\build --target ALL_BUILD --config Release

Configure & Generate

Nowadays, one can set up define a preset (see below for details), which would shorten the command line to something like this:

C:\ProjectDir\src> cmake --preset <name_of_a_CONFIG_preset>

But if you want (or need) to provide command line arguments directly and explictly, here’s the gist:

cmake -D DEF=Value -S <dir> -B <dir> -G "<GeneratorName>" -A <Arch> -T <ToolsetName>
       โ”‚            โ”‚        โ”‚        โ”‚                    โ”‚         โ”‚
       [optional]   โ”‚        โ”‚        [optional]           [optional][optional]
       โ”‚            โ”‚        โ”‚        โ”‚                    โ”‚         โ”‚
       โ”‚            โ”‚        โ”‚        โ”‚                    โ”‚         โ•ฐโ”€ The toolset that the generator should use
       โ”‚            โ”‚        โ”‚        โ”‚                    โ”‚            (Not supported by all generators)
       โ”‚            โ”‚        โ”‚        โ”‚                    โ”‚
       โ”‚            โ”‚        โ”‚        โ”‚                    โ•ฐโ”€ The target architecture/platform
       โ”‚            โ”‚        โ”‚        โ”‚                       (Not supported by all generators)
       โ”‚            โ”‚        โ”‚        โ”‚
       โ”‚            โ”‚        โ”‚        โ•ฐโ”€ For which native build tool should CMake generate the files?
       โ”‚            โ”‚        โ”‚           If not specified, CMake will check some possibilties or try a default value
       โ”‚            โ”‚        โ”‚
       โ”‚            โ”‚        โ•ฐโ”€ Build directory for the artifacts (i.e. destination directory for the output)
       โ”‚            โ”‚           If the directory doesn't exist, it will be created
       โ”‚            โ”‚
       โ”‚            โ•ฐโ”€ Source/input directory with a CMakeLists.txt file
       โ”‚
       โ•ฐโ”€ Definition(s)/Option(s)
          Optional, but if used, must come first!

How to specify a build directory (-B)

In short: It’s completely up to you, where the generated CMake buildsystem should be saved.

But a (strong) recommendation/convention is not to use the source directory (where the CMakeLists.txt and/or the source code files reside):
(My CMakeLists.txt template even checks and prevents this at the beginning.)

To maintain a pristine source tree, perform an out-of-source build, by using a separate, dedicated build tree.
An in-source build (in which the build tree is placed in the same directory as the source tree) is supported, but discouraged.
CMake manual

And don’t bother to put the build directory under version control: All it contains is stuff that is temporary, mutable and automatically generated by the build process!

How to set a generator (-G), architecture/platform (-A) or toolset (-T)

Each version of CMake offers a specific list of generators (-G).

Not all generators support the architecture/platform (-A) or toolset (-T) parameter.

How to set options

CMake relies on cached variables1 (and as you know: Caching is one of the three hard problems in software development), so keep that in mind if things go awry.
If in doubt: Delete the CMakeCache.txt file and/or the CMakeFiles directory (both located in the build directory) — or simply remove the whole build directory and start anew.

These variables (options) can be defined and provided in multiple ways for a project:

Where How
On the command line: Single option: cmake -D <var>=<val> ...
Multiple options: cmake -D <var>[:<type>]=<val>, -D <var>=<val>, ...
The type is optional here(!)
In a preset JSON file: Can be an object (with certain fields) or a key/value pair (representing null/boolean/string):
"cacheVariables": { "VarX": { "type":"BOOL", "value":"ON" } }
"cacheVariables": { "VarY": "some value" }
In a CMakeLists.txt file: via set() or option()

How to list the available options

If you don’t know or don’t remember all the available variables and options, one can List the (optionally Advanced) cached variables, together with (optional) Help text on the command line:

> cmake -S . -B ..\_build -L
> cmake -S . -B ..\_build -LAH

Or one can use the CMake-GUI (Wizard), which offers tooltips, dropdown lists etc.:

> cmake-gui

Build

After the configuration & generation is done, you can…

Let’s stay with CMake to build a CMake-generated project binary tree…

Nowadays, one can define a lot of options in a preset (see below for details), which shortens the command:

C:\ProjectDir\src> cmake --build --preset <name_of_a_BUILD_preset>

But if you want (or need) to provide command line arguments directly and explictly, here’s the gist:

cmake --build <dir> --target <TargetName> --config <ConfigName> --parallel <Number>
        โ”‚             โ”‚                     โ”‚                     โ”‚
        โ”‚             [optional]            [optional]            [optional]
        โ”‚             โ”‚                     โ”‚                     โ”‚
        โ”‚             โ”‚                     โ”‚                     โ•ฐโ”€ Concurrent processes to use
        โ”‚             โ”‚                     โ”‚                        If <Number> is omitted, the build tool's default is used
        โ”‚             โ”‚                     โ”‚
        โ”‚             โ”‚                     โ•ฐโ”€ Supported by multi-configuration tools (like MSVC; e.g. 'Release' or 'Debug')
        โ”‚             โ”‚
        โ”‚             โ•ฐโ”€ To build <TargetName> instead of the default target
        โ”‚                Multiple targets may be given, separated by spaces
        โ”‚
        โ•ฐโ”€ The destination (i.e. the build directory, which was '-B' in the configure & generate step)
           Mandatory and must come first!

Additional parameters for clean builds

--fresh
Added in version 3.24
Perform a fresh configuration of the build tree.
This removes any existing CMakeCache.txt file and the associated CMakeFiles directory and re-creates them from scratch.
--clean-first
First build the target clean, then build anew (to clean without re-building, use --target 'clean' instead.
Note that the clean target does not get rid of CMakeCache.txt and CMakeFiles!)

What Targets are available for a project?

Tip: I wrote my own CMake helper script to collect all targets in all (sub)directories of a project (print_all_targets.cmake), since CMake doesn’t yet have a way to do this (it only looks in a specific directory, but targets can be spread over many subdirectories).


Install

CMake also provides a separate method to install a built project (instead of the above mentioned way to build the target INSTALL).
Here’s a simple example; more options can be found in the install section of the manual:

cmake --install <dir> --prefix <path>
        โ”‚               โ”‚
        โ”‚               [optional]
        โ”‚               โ”‚
        โ”‚               โ•ฐโ”€ Overrides the already configured option CMAKE_INSTALL_PREFIX,
        โ”‚                  to define a different installation destination path.
        โ”‚
        โ•ฐโ”€ Path of the build directory.
           Mandatory and must come first!

CMake Presets

Presets (available since CMake 3.20) are a very helpful feature for sharing common settings to configure a project via a JSON file.

This is just a brief reminder on the topic; for more details on how to create a preset, read the section in the manual.

CMake knows two files for this feature and will search for both in the current working directory or in the top-level source directory (specified with the -S option):

What Presets are available for a project?

To list the available presets:

C:\ProjectDir\src> cmake --list-presets <type>

Where type can be:

Tip: My convention is to always have a configure and build preset named “default” (or a variation of that).
That way I don’t have to think twice which preset to choose when I switch between projects.


  1. CMake Cache: “The first time CMake is run on a project, it produces a CMakeCache.txt file in the top directory of the build tree. CMake uses this file to store a set of global cache variables, whose values persist across multiple runs within a project build tree. […]” ↩︎