Building with CMake

CMake is the de facto standard way to build C++ projects. You describe your targets — executables and libraries, their source files, options and dependencies — in a CMakeLists.txt, and CMake generates the actual build files for your platform and tools: Makefiles, Ninja, Visual Studio or Xcode projects.

A minimal project

For the inventory program from the previous lesson:

TXT CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(inventory LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

add_executable(inventory main.cpp product.cpp)
target_compile_options(inventory PRIVATE -Wall -Wextra -pedantic)
$
cmake -S . -B build                 # configure: generate build files into ./build
cmake --build build                 # compile and link
./build/inventory

The build folder holds everything generated, keeping your source tree clean — add it to .gitignore. After editing code, only cmake --build build is needed; CMake re-runs the configure step itself when CMakeLists.txt changes.

Targets and properties

Modern CMake is organised around targets. Settings are attached to a target with target_* commands, and PUBLIC/PRIVATE decides whether they also apply to targets that use it:

TXT CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(shop LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# A library with the reusable code
add_library(inventory src/product.cpp src/order.cpp)
target_include_directories(inventory PUBLIC include)        # users get the include path too
target_compile_options(inventory PRIVATE -Wall -Wextra)     # only for building the library

# The program, linking the library
add_executable(shop src/main.cpp)
target_link_libraries(shop PRIVATE inventory)

# Tests
enable_testing()
add_executable(inventory_tests tests/product_tests.cpp)
target_link_libraries(inventory_tests PRIVATE inventory)
add_test(NAME inventory_tests COMMAND inventory_tests)

With tests registered, ctest --test-dir build runs them all.

Build types

$
cmake -S . -B build-debug -DCMAKE_BUILD_TYPE=Debug          # -g, no optimisation
cmake -S . -B build-release -DCMAKE_BUILD_TYPE=Release      # -O3, no debug info
cmake -S . -B build -G Ninja                                # use the fast Ninja generator

Multi-configuration generators (Visual Studio, Xcode) choose the type at build time instead: cmake --build build --config Release.

Using libraries

Installed libraries are found with find_package; others can be downloaded at configure time with FetchContent:

TXT CMakeLists.txt
include(FetchContent)
FetchContent_Declare(
    fmt
    GIT_REPOSITORY https://github.com/fmtlib/fmt.git
    GIT_TAG 11.1.4
)
FetchContent_MakeAvailable(fmt)

target_link_libraries(shop PRIVATE fmt::fmt)

Package managers such as vcpkg and Conan integrate with CMake to install and version third-party libraries for every platform.

Sanitizers in a build

TXT CMakeLists.txt
option(ENABLE_SANITIZERS "Build with AddressSanitizer and UBSan" OFF)
if(ENABLE_SANITIZERS)
    target_compile_options(shop PRIVATE -fsanitize=address,undefined -fno-omit-frame-pointer)
    target_link_options(shop PRIVATE -fsanitize=address,undefined)
endif()
$
cmake -S . -B build-asan -DENABLE_SANITIZERS=ON

IDE support

CLion, Visual Studio, VS Code (with the CMake Tools extension) and Qt Creator open a folder containing CMakeLists.txt directly and build, run and debug from it. CMake can also export compile_commands.json (-DCMAKE_EXPORT_COMPILE_COMMANDS=ON), which tools like clangd and clang-tidy use to understand your project.

Other build systems

  • Meson — clean syntax, fast, popular in Linux projects.
  • Bazel — for very large, multi-language codebases.
  • Plain Makefiles — fine for small Unix-only projects (see the C course's make lesson).

Try it yourself

Create a folder with this program and a CMakeLists.txt containing add_executable(hello main.cpp), then configure and build it with CMake.

Try it yourself · main.cpp
Edit the code, copy it into main.cpp, then compile and run it with c++ -std=c++20 -Wall main.cpp -o main && ./main. How to set up a C++ compiler
Output
Built with CMake!