Skip to content

OxfordRSE/ModernCMakeCourse

Repository files navigation

Oxford RSE: Modern CMake

The exercises that accompany the Modern CMake course from Oxford RSE.

The course material

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.

Prerequisites

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-dev

You 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.

How the checkpoints work

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_executable

The 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.

The checkpoints

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_STANDARD and 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 the PRIVATE / PUBLIC / INTERFACE visibility 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=ON

Fetches 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 in exe/main.cpp)
  • Enabling testing with include(CTest) and the BUILD_TESTING option
  • 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 default

Makes the library installable, so that a completely separate project can use it.

Concepts introduced:

  • A namespaced ALIAS target
  • $<BUILD_INTERFACE:...> and $<INSTALL_INTERFACE:...> include directories
  • Declaring public headers with FILE_SET HEADERS
  • install(TARGETS ... EXPORT ...) and install(EXPORT ... NAMESPACE ...)
  • Generating a package config with configure_package_config_file and write_basic_package_version_file, so find_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 ./stage

A 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/consumer

Continuous integration

The 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.

About

An introductory course in CMake from Oxford RSE

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages