CMake for the Impatient

Me

Steve Love


@IAmSteveLove@mastodon.social
@stevelove.bsky.social
https://moderncsharp.blogspot.com/


Also Me

For you?

eggs

Photo by Nik on Unsplash

For me?

vs proppages

The Plan

  • Show some simple examples

  • Introduce some important concepts

  • Hopefully:

    • help you recognise the general structure of a CMake file

    • help you get started from scratch

Stop this

cmake_minimum_required(VERSION 3.28)
project(heating_system)
set(CMAKE_CXX_STANDARD 20)

add_executable(perf_triggers bench.cpp)

target_include_directories(perf_triggers PRIVATE ../full)

include(FetchContent)

FetchContent_Declare(
        googlebenchmark
        GIT_REPOSITORY https://github.com/google/benchmark.git
        GIT_TAG v1.9.2
)
FetchContent_MakeAvailable(googlebenchmark)

target_link_libraries(perf_triggers PRIVATE benchmark::benchmark_main)

Turning into this

cmake_minimum_required(VERSION 3.28)
project(heating_system)
set(SOMETHING_STANDARD 20)

gobble_dee_gook(nonsense blah.cpp)

marmalade_something_apples(garbage_patch BABOON something)

include(WibbleFish)

SomeNonsense_Bananas(
        googlebenchmark
        PET_HAMSTER https://github.com/google/benchmark.git
        PEA_SOUP v1.9.2
)
WibbleFish_MarmaladeJam(googlebenchmark)

apples_blah_something(blah BLAH something::something)

A simple example

main.cpp
#include <iostream>

int main()
{
    std::cout << "Hello CMake!\n";
}
CMakeLists.txt
cmake_minimum_required(VERSION 3.28)
project(hello)
add_executable(world main.cpp)

Prove it!

Generate the build config in bin
Scaffolds from CMakeLists.txt
Rerun when CMakeLists.txt changes

cmake -B bin

Build the code
Performs an incremental build
Detects changes to target’s files

cmake --build bin

Run the code

bin\Debug\world.exe

And Ninja Is?

  • Build system (like make)

  • Independent of tool chain

  • Fast

    • No rules!

    • Tell it what to do explicitly

  • Configuration by non-humans only

Headline Positives

  • Common build configuration

    • independent of the actual tools

    • switch on command line (-G <generator>)

    • a consistent process

  • Build process can be version controlled

    • multi-platform, multi-target

    • build process is part of the code

  • CI Server Friendly

    • build agent environment can differ

Do we really need another build system?

Just use make?

  • Multi-file projects

    • Compilation time

    • Dependency Management

      • Compile time

      • Link time

    • Avoiding unnecessary recompilation

Further warts

Proliferation of:

  • platforms

  • tools

  • configurations

  • environments

  • etc.

So. make

  • Different dialects

  • dependencies and rules

  • autotools

  • Precompiled headers

So, CMake

  • Cross-platform build tooling for C++ (and others)

  • De-facto standard format

  • A "meta-build"

  • Makefile maker for [m]any platform[s]

    • but more than that

But…​

CMake is very powerful. With great power comes

document storage

Simple stuff is really simple

main.cpp
#include <iostream>
#include "message.h"

int main()
{
    using namespace std;
    cout << "Hello " << message << "!\n";
}
message.h
#pragma once

#include <string>

const std::string message = "CMake";

Dependency inference

cmake_minimum_required(VERSION 3.28)
project(hello_custom)
add_executable(custom_message main.cpp)

Time for a demo?

Not in the box

#include <iostream>
#include <magic_enum/magic_enum.hpp>

enum class Suit
{
    Hearts, Diamonds,
    Spades, Clubs
};

int main()
{
    for (auto const suit : magic_enum::enum_names<Suit>()) {
        std::cout << suit << std::endl;
    }
}

← Non-standard system include

cmake_minimum_required(VERSION 3.28)
project(local3rdparty)
add_executable(cards main.cpp)

target_include_directories(cards SYSTEM PUBLIC
        "../../../lib/magic_enum/include"
        # ...
)

Full path specified

I want library X

target_include_directories(bounce SYSTEM PRIVATE
        "../../../../lib/SFML-3.0.0/include")

target_link_directories(bounce PRIVATE
        "../../../../lib/SFML-3.0.0/lib")

target_link_libraries(bounce
        sfml-graphics-d
        sfml-window-d
        sfml-system-d
)

← full paths for headers and libraries

← linked names are platform agnostic

…​ With Release Build

target_link_libraries(bounce-d
        debug sfml-graphics-d
        debug sfml-window-d
        debug sfml-system-d
        optimized sfml-graphics
        optimized sfml-window
        optimized sfml-system
)

A better way

cmake_minimum_required(VERSION 3.28)
project(bounce-f)

set(CMAKE_PREFIX_PATH "../../../../lib/SFML-3.0.0")

set(SFML_STATIC_LIBRARIES ON) # <- SFML defined
find_package(SFML 3 COMPONENTS Graphics Window System)

add_executable(bounce-f ../simple/main.cpp)

target_link_libraries(bounce-f PRIVATE SFML::Graphics)

← hard-coded path can be passed on cmake command-line

A stand-alone executable

Dependencies

I want to test!

By Artifact

cmake_minimum_required(VERSION 3.28)
project(gtests)

set(CMAKE_CXX_STANDARD 26)

include(FetchContent)
FetchContent_Declare(
        googletest
        URL https://github.com/google/googletest/archive/refs/tags/v1.17.0.zip
)
FetchContent_MakeAvailable(googletest)

add_executable(gtests tests.cpp)
target_link_libraries(gtests gtest_main)

By Git Reference

cmake_minimum_required(VERSION 3.28)
project(catchtests)

set(CMAKE_CXX_STANDARD 26)

Include(FetchContent)
FetchContent_Declare(
        Catch2
        GIT_REPOSITORY https://github.com/catchorg/Catch2.git
        GIT_TAG        v3.7.1
)
FetchContent_MakeAvailable(Catch2)

add_executable(catchtests tests.cpp)
target_link_libraries(catchtests PRIVATE Catch2::Catch2WithMain)
  • - note how slow to build

Realistically speaking

  • multiple targets

  • dependencies between targets

  • e.g. tests

Physical vs. Logical

Flat file

cmake_minimum_required(VERSION 3.28)
project(heating_system)

set(CMAKE_INCLUDE_CURRENT_DIR ON)
link_directories(AFTER ../externals/lib)

add_library(heating_triggers
        heating_triggers/thermostat.cpp
)
target_include_directories(heating_triggers PRIVATE
        ../externals/include    # 3rd party includes
)

add_executable(heating
        heating_controller/main.cpp
)
target_include_directories(heating PRIVATE
        ../externals/include    # 3rd party includes
)
target_link_libraries(heating
        acme-sensor
        acme-activator
        heating_triggers
)
include(FetchContent)
FetchContent_Declare(
    googletest URL
    https://github.com/google/googletest/
            archive/refs/tags/v1.17.0.zip
)
FetchContent_MakeAvailable(googletest)

add_executable(heating_tests
        tests/test_thermostat.cpp
        tests/stubs/activator.cpp
        tests/stubs/sensor.cpp
)
target_include_directories(heating_tests PRIVATE
        tests/stubs    # local includes
)
target_link_libraries(heating_tests
        gtest_main
        heating_triggers
)

Nested Projects

Top-levelheating_triggerstests
cmake_minimum_required(VERSION 3.28)
project(heating_system)

set(CMAKE_INCLUDE_CURRENT_DIR ON)
link_directories(AFTER ../externals/lib)

add_executable(heating
        heating_controller/main.cpp
)
target_include_directories(heating PRIVATE
        ../externals/include    # 3rd party includes
)
target_link_libraries(heating
        acme-sensor
        acme-activator
        heating_triggers
)

add_subdirectory(heating_triggers)
add_subdirectory(tests)
cmake_minimum_required(VERSION 3.28)
project(heating_system)

set(CMAKE_INCLUDE_CURRENT_DIR ON)

include_directories(
        AFTER
        ../../externals/include
)
add_library(heating_triggers
        thermostat.cpp
)
cmake_minimum_required(VERSION 3.28)
project(test_heating_system)

include(FetchContent)

FetchContent_Declare(
    googletest URL
    https://github.com/google/googletest/
            archive/refs/tags/v1.17.0.zip
)
FetchContent_MakeAvailable(googletest)

include_directories(heating_tests BEFORE stubs)

add_executable(heating_tests
        test_thermostat.cpp
        stubs/sensor.cpp
        stubs/activator.cpp
        ../heating_triggers/thermostat.cpp
)

target_link_libraries(heating_tests gtest_main)

A word about tooling

cmake-gui

frankly, don’t bother (unless you already know cmake)

VS2022

is popular, and free (with restrictions)

Clion

rocks (and free for non-commercial use)

Thank you!