Site Tools


wiki:cmake

cmake

What is CMake?

CMake is a build system generator. It does not compile anything itself; instead it reads a project description written in its own scripting language (CMakeLists.txt) and generates the native build files for whatever toolchain you actually want to use, Makefiles, Ninja files, Visual Studio projects, Xcode projects, and drives that generated build through a single uniform cmake --build interface.

The problem CMake solves is portability of the build itself. A hand-written Makefile encodes assumptions about compiler flags, library paths, and OS conventions that break the moment someone tries to build the same project on a different machine or platform. CMake pushes that platform-specific knowledge into the generation step: you describe targets, sources, and dependencies abstractly, and CMake works out how to turn that into a build recipe for the local environment.

In HPC and embedded work, CMake is the de facto standard because most numerical and systems libraries (Eigen, OpenCV, PCL, most CUDA and MPI-enabled projects) ship a CMakeLists.txt and expect to be consumed via find_package. Knowing CMake is less about writing elaborate build scripts and more about being able to read and extend someone else's.

Install

On Debian and Ubuntu:

sudo apt install cmake

On Fedora and RHEL:

sudo dnf install cmake

Distro-packaged CMake often lags upstream by a year or more, which matters if a project requires a recent feature (like FetchContent or C++20 module support). To get the latest release, use the Kitware APT repository or download a binary release directly:

wget https://github.com/Kitware/CMake/releases/download/vX.Y.Z/cmake-X.Y.Z-linux-x86_64.sh
chmod +x cmake-X.Y.Z-linux-x86_64.sh
sudo ./cmake-X.Y.Z-linux-x86_64.sh --prefix=/usr/local --skip-license

Practice

The canonical CMake workflow is out-of-source: never write generated files into the same directory as your sources.

mkdir build && cd build
cmake ..                  # generate the build system
cmake --build .            # invoke the underlying generator (make, ninja, ...)

A minimal CMakeLists.txt for a single executable looks like:

cmake_minimum_required(VERSION 3.20)
project(hello LANGUAGES C)
 
add_executable(hello main.c)

Running cmake .. from build/ produces Makefiles (or Ninja files, if -G Ninja is passed) in that directory, and cmake --build . runs the underlying build tool without you needing to know whether it's make or ninja under the hood.

Concepts

Targets

CMake's central abstraction is the target, not the file. add_executable and add_library create targets; everything else (target_link_libraries, target_include_directories, target_compile_options) attaches properties to a target rather than to a raw list of flags.

add_library(mathutils STATIC mathutils.c)
target_include_directories(mathutils PUBLIC include/)
 
add_executable(app main.c)
target_link_libraries(app PRIVATE mathutils)

The PUBLIC/PRIVATE/INTERFACE keywords control propagation: a PUBLIC include directory on mathutils is automatically inherited by anything that links against it, so app sees include/ without repeating the flag. This transitive propagation is the main reason modern CMake code reads so differently from an old-style Makefile.

find_package

find_package locates an already-installed dependency and, if it ships CMake config files (most modern libraries do), imports it as a target you can link against directly:

find_package(MPI REQUIRED)
target_link_libraries(app PRIVATE MPI::MPI_C)

MPI::MPI_C is an imported target that carries the correct include paths, compiler flags, and link libraries for the MPI implementation CMake found on the system, so the project doesn't need to know whether that's OpenMPI or MPICH.

FetchContent

FetchContent pulls a dependency's source at configure time and builds it as part of the current project, useful when a library either has no system package or you want a pinned version:

include(FetchContent)
FetchContent_Declare(
  googletest
  GIT_REPOSITORY https://github.com/google/googletest.git
  GIT_TAG        v1.14.0
)
FetchContent_MakeAvailable(googletest)
 
target_link_libraries(app_tests PRIVATE gtest_main)

Build types

CMake's CMAKE_BUILD_TYPE selects a predefined set of compiler flags: Debug (no optimisation, debug symbols), Release (optimised, no symbols), RelWithDebInfo (optimised, with symbols, useful when profiling with perf).

cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo ..

For HPC work, RelWithDebInfo is usually the right default: it keeps the optimisation that makes the timing meaningful while retaining the symbols that make perf and gdb output readable.

wiki/cmake.md · Last modified: by 127.0.0.1