The exercises that accompany the Modern CMake course from Oxford RSE.
This repository on its own is just a series of small C++ projects. The written material that explains each one is here:
https://train.rse.ox.ac.uk/material/HPCu/technology_and_tooling/cmake
Work through the material, and use the checkpoints in this repository as you go. Each section of the material has a challenge that points at the checkpoint it belongs to.
The course assumes a Linux-like environment. On Windows, the simplest route is
WSL2 with Ubuntu. On a
recent Ubuntu (24.04 LTS or newer), everything you need comes from apt:
sudo apt update
sudo apt install build-essential cmake ninja-build git \
libeigen3-dev libboost-program-options-devYou need CMake 3.24 or newer: check with cmake --version. Ubuntu 22.04
ships 3.22, which is too old, so upgrade from
apt.kitware.com if necessary.
Later checkpoints download {fmt} and
Catch2 for you with FetchContent, so you
will want an internet connection when you reach them. There is nothing to install.
Each checkpoint is a self-contained CMake project that builds on the one before, and each is configured, built and run the same way:
cd checkpoint_0
cmake -S . -B build_dir
cmake --build build_dir
./build_dir/main_executableThe executable moves as the project grows: it is build_dir/main_executable in
Checkpoint 0, build_dir/src/main_executable in Checkpoint 1, and
build_dir/exe/main_executable from Checkpoint 2 onwards.
Checkpoints 3 and 4 are deliberately incomplete. Their CMakeLists.txt
files have comment blocks marking the lines you need to write, so they will not
build until you have done the exercise. The solution to each appears in the
following checkpoint, and in the course material.
A single CMakeLists.txt and a single main.cpp.
Concepts introduced:
-
Setting a minimum CMake version, and a policy range, with
cmake_minimum_required -
Defining the project name, version and languages with
project -
Setting variables with
set -
Requesting a C++ standard with
CMAKE_CXX_STANDARDand friends -
Defining an executable with
add_executable -
The configure-build-run cycle, out of tree:
cmake -S . -B build_dir cmake --build build_dir ./build_dir/main_executable -
Choosing a generator, and a build type:
cmake --fresh -S . -B build_dir -G Ninja -DCMAKE_BUILD_TYPE=Release cmake --build build_dir --target main_executable
The project grows: a top-level CMakeLists.txt plus one in src/, and the
functionality moves into functionality.hpp and functionality.cpp.
Concepts introduced:
- Processing a subdirectory with
add_subdirectory - Keeping build logic in the directory it belongs to
- Lists, and dereferencing variables with
${...} - Printing information with
message
The functionality is separated from the executable into a library, which the
executable links. There are now three CMakeLists.txt files: the top level,
src/ and exe/.
Concepts introduced:
- Defining a library with
add_library - Carrying include directories on the target with
target_include_directories CMAKE_CURRENT_SOURCE_DIR- Linking with
target_link_libraries, and thePRIVATE/PUBLIC/INTERFACEvisibility keywords
Adds a dependency: Eigen. The library
cmake_course_lib gains some linear algebra, and CMake has to find Eigen.
This checkpoint is an exercise: src/CMakeLists.txt is missing the lines
that find and link Eigen.
Concepts introduced:
- Finding a dependency in config mode with
find_package - Linking a namespaced target (
Eigen3::Eigen)
In config mode, find_package searches for an <PackageName>Config.cmake file
shipped by the library itself, which tells CMake everything it needs to know.
Adds another dependency: Boost.Program_options. This time it is the executable that needs it.
This checkpoint is an exercise: exe/CMakeLists.txt is missing the lines
that find and link Boost.
Concepts introduced:
- Requesting only the components of a package that you need
- Component targets, of the form
Boost::program_options
Modern Boost (1.70 and later) ships its own BoostConfig.cmake, so it is found
in config mode too. CMake's bundled FindBoost module was deprecated in CMake
3.30. Module mode, which uses a Find<PackageName>.cmake script, still matters
for the many libraries that do not ship a CMake config.
Adds a configurable set of compiler warnings.
Concepts introduced:
- Pulling in CMake code with
include - An interface "library" whose only job is to carry options to other targets
- Defining a function with
function - A configure-time switch with
option - Conditionals, and identifying the compiler with
CMAKE_CXX_COMPILER_ID
Try it:
cmake --fresh -S . -B build_dir -DWARNINGS_AS_ERRORS=ONFetches a dependency, and adds tests.
Concepts introduced:
- Recording build configurations in a
CMakePresets.json - Downloading a dependency with
FetchContent, pinning it for reproducibility ({fmt}, used inexe/main.cpp) - Enabling testing with
include(CTest)and theBUILD_TESTINGoption - Unit tests with Catch2, registered
individually via
catch_discover_tests
Configure, build and test with the presets:
cmake --list-presets
cmake --preset default
cmake --build --preset default
ctest --preset defaultMakes the library installable, so that a completely separate project can use it.
Concepts introduced:
- A namespaced
ALIAStarget $<BUILD_INTERFACE:...>and$<INSTALL_INTERFACE:...>include directories- Declaring public headers with
FILE_SET HEADERS install(TARGETS ... EXPORT ...)andinstall(EXPORT ... NAMESPACE ...)- Generating a package config with
configure_package_config_fileandwrite_basic_package_version_file, sofind_package(CMakeCourse)works - Building an archive with CPack
Install it into a staging prefix, which Checkpoint 8 then consumes:
cmake --preset default
cmake --build --preset default
cmake --install build_dir --prefix ./stageA small, separate project that consumes the library installed by Checkpoint 7. It knows nothing about Checkpoint 7's source tree: it finds the package, and links the namespaced target.
Concepts introduced:
find_package(CMakeCourse CONFIG REQUIRED)against an installed package- Telling CMake where to look with
CMAKE_PREFIX_PATH
cmake -S . -B build_dir -DCMAKE_PREFIX_PATH=/absolute/path/to/checkpoint_7/stage
cmake --build build_dir
./build_dir/consumerThe checkpoints workflow configures,
builds and runs every checkpoint on Ubuntu, using the same commands the course
material asks you to run. Checkpoints 3 and 4 are patched with the solutions
from .github/solutions/ first, since they do not build
as they ship.