Skip to content

CMake Build System Guide

Patrick Heyer edited this page Nov 1, 2024 · 1 revision

Contents

  1. Before We Begin
  2. CMake Is A Build System Generator
  3. The Gist Of Working With CMake
  4. Important Differences Between Generators
  5. Suggested Reading

Before We Begin

One of the best resources around modern CMake is An Introduction To Modern CMake. It should be considered "required reading" when it comes to working with CMake and CMake-based projects.

CMake Is A Build System Generator

This is an important distinction: CMake itself never builds a project. Instead it uses a high-level description of the project with all its requirements and properties to generate an actual build system based on one of its many supported "generators".

It is these generators that take the abstract description of the project and convert it into targets, project files, compiler command lines and more. CMake itself supports this process by running code to identify the current environment, available build systems and their compiler features, as well as available dependencies used by a project.

The Gist Of Working With CMake

Working with a CMake project usually boils down to the same basic steps:

  1. Configure the project
  2. Generate the build system
  3. Build the project
  4. Install or fix up the generated binaries for distribution

CMake always performs Step 1 and 2 in combination, first taking all the developer's custom variables (if any were provided) to run through the CMakeLists.txt script files to build a configuration of the project. This configuration will build a "graph" of all the targets defined in the project and imported via dependencies, which is then used by the second step to generate the actual build system.

Step 3 either involves invoking the build system manually (by opening the IDE project or pointing tools like make or ninja to the generated build files) or using CMake itself to trigger a build, which has the benefit that a developer does not need to care about the generator used (CMake can identify the generator and call the corresponding build system automatically).

The exact scope of the fourth step differs vastly between projects and thus cannot be explained here in full. Usually the "install" step takes care of fixing up library load paths (replacing absolute paths that were only valid within the build tree with relative paths) and assembling all files related to the project in a central location.

Important Differences Between Generators

By default OBS Studio requires the use of specific generators on different platforms: Visual Studio and Xcode on Windows and macOS respectively, Ninja or GNU Make on Linux and Linux-like platforms.

The important difference between those generators is that GNU Make and Ninja are "single-config" generators, whereas Visual Studio and Xcode are "multi-config".

The "config" is the CMake-specific build configuration used to generate the build system. CMake by default supports four configurations: "Debug", "RelWithDebInfo", "Release", and "MinSizeRel", which each have different built-in defaults for compiler command lines and other settings.

A Ninja or GNU Make build system will have one of these configurations (or a custom one implemented by a developer) "locked in", that is the Makefile will invoke the compiler directly with the arguments associated with that config. If a different config is desired, a separate build system will need to be generated by CMake (or the current one needs to be replaced). Thus the configuration is known even before the build system is generated (and available in a CMake variable).

When generating a Visual Studio or Xcode project however, this configuration is not known during generation, because both IDEs allow the developer to switch between configurations at any point and build/run/debug a project with any available configuration. Thus there is no "current" configuration available to the CMake script code (because the generator will just create setups for all of them) and instead "Generator Expressions" need to be used that are evaluated by the build system generators on the fly.

Read also CMake's own documentation about build configurations.

Tip

Use CMAKE_C_FLAGS_DEBUG to explicitly set compiler flags for "Debug" configuration instead of setting different values to CMAKE_C_FLAGS based on CMAKE_BUILD_TYPE. Many CMake variables have such a _<CONFIG> suffix (as seen in CMake's built-in variable documentation) that will then allow generators to pick the values they need to generate proper build systems for single-target as well as multi-target generators.

Suggested Reading

Due to CMake's scope this guide can only scratch the surface of many topics, thus it's advised to look up more detailed information about each of the topics touched upon in the actual documentation. Additional information, particularly regarding "modern" CMake practices is available online as well.

(The CMake tutorial is highly suggested as it walks through the core concepts of how CMake works, encompassing all aspects mentioned in this guide as well).

Official CMake Documentation

CMake Best Practices and Modern CMake