Zum Inhalt springen

Build-Systeme: CMake & Paketverwaltung

Beherrsche die CMake-Grundlagen, modernes target-basiertes CMake, FetchContent für Abhängigkeiten, die Paketmanager vcpkg und Conan sowie Compiler-Flags für sichere, optimierte Builds.

Warum Build-Systeme wichtig sind

Sobald ein C++-Projekt über eine einzelne Datei hinauswächst, brauchst du ein Build-System, das Kompilierung, Linken, Abhängigkeiten und Plattformunterschiede verwaltet. Von Hand g++ *.cpp aufzurufen skaliert nicht: Du brauchst inkrementelle Builds (nur geänderte Dateien neu übersetzen), Bibliothekserkennung, plattformübergreifende Unterstützung und reproduzierbare Builds. CMake hat sich als De-facto-Standard unter den Build-System-Generatoren für C++ durchgesetzt. Es kompiliert selbst keinen Code — es erzeugt native Build-Dateien (Makefiles, Ninja-Dateien, Visual-Studio-Projekte) für deine Plattform.

CMake-Grundlagen

Eine CMakeLists.txt-Datei beschreibt die Struktur deines Projekts. Modernes CMake (ab 3.12) verfolgt einen target-basierten Ansatz, bei dem alles an Targets (ausführbare Dateien oder Bibliotheken) hängt:

CMakeLists.txt
# CMakeLists.txt — minimum viable project
cmake_minimum_required(VERSION 3.20)
project(MyApp VERSION 1.0.0 LANGUAGES CXX)

# Set C++ standard for the entire project
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)  # use -std=c++20, not -std=gnu++20

# Create an executable target
add_executable(myapp
    src/main.cpp
    src/engine.cpp
    src/utils.cpp
)

# Add include directories for this target
target_include_directories(myapp PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/include
)

# Create a library target
add_library(mathlib STATIC
    src/math/vector.cpp
    src/math/matrix.cpp
)

target_include_directories(mathlib PUBLIC
    ${CMAKE_CURRENT_SOURCE_DIR}/include/math
)

# Link the library to the executable
target_link_libraries(myapp PRIVATE mathlib)

# Compiler warnings — essential for quality code
target_compile_options(myapp PRIVATE
    $<$<CXX_COMPILER_ID:GNU,Clang>:-Wall -Wextra -Wpedantic -Werror>
    $<$<CXX_COMPILER_ID:MSVC>:/W4 /WX>
)

Bauen mit CMake

CMake arbeitet in zwei Schritten: configure (Build-Dateien erzeugen) und build (kompilieren). Verwende immer Out-of-Source-Builds, damit der Quellbaum sauber bleibt:

terminal
# Configure — generate build files in a 'build' directory
cmake -B build -DCMAKE_BUILD_TYPE=Release

# Build — compile the project
cmake --build build --parallel $(nproc)

# Install (optional)
cmake --install build --prefix /usr/local

# Common build types:
# Debug          — -O0 -g (full debug symbols, no optimization)
# Release        — -O3 -DNDEBUG (max optimization, no asserts)
# RelWithDebInfo — -O2 -g -DNDEBUG (optimized with debug info)
# MinSizeRel     — -Os -DNDEBUG (optimize for size)

# Run with sanitizers (add to CMakeLists.txt or command line)
cmake -B build-asan -DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer"

FetchContent: Abhängigkeiten verwalten

Das CMake-Modul FetchContent lädt Abhängigkeiten zur Konfigurationszeit herunter und baut sie, sodass du Bibliotheken ohne manuelle Einrichtung einbinden kannst:

CMakeLists.txt
cmake_minimum_required(VERSION 3.20)
project(MyApp LANGUAGES CXX)

include(FetchContent)

# Fetch GoogleTest
FetchContent_Declare(
    googletest
    GIT_REPOSITORY https://github.com/google/googletest.git
    GIT_TAG        v1.14.0
)

# Fetch fmt (formatting library)
FetchContent_Declare(
    fmt
    GIT_REPOSITORY https://github.com/fmtlib/fmt.git
    GIT_TAG        10.2.1
)

# Download and make available
FetchContent_MakeAvailable(googletest fmt)

add_executable(myapp src/main.cpp)
target_link_libraries(myapp PRIVATE fmt::fmt)

# Test executable
enable_testing()
add_executable(tests tests/test_main.cpp)
target_link_libraries(tests PRIVATE GTest::gtest_main)
add_test(NAME unit_tests COMMAND tests)

vcpkg und Conan

Bei größeren Projekten mit vielen Abhängigkeiten bieten dedizierte C++-Paketmanager bessere Abhängigkeitsauflösung und besseres Caching:

vcpkg (Microsoft) — integriert sich über eine Toolchain-Datei direkt in CMake. Installiere Pakete global oder im "Manifest-Modus" mit einer vcpkg.json-Datei:

{
    "dependencies": ["fmt", "spdlog", "nlohmann-json", "catch2"]
}

Verwendung mit: cmake -B build -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake

Conan — deklariert Abhängigkeiten über eine conanfile.txt oder conanfile.py. Unterstützt mehrere Build-Konfigurationen und Binary-Caching:

[requires]
fmt/10.2.1
spdlog/1.13.0

[generators]
CMakeDeps
CMakeToolchain

Beide Paketmanager pflegen Repositories mit Tausenden von C++-Bibliotheken samt getesteter, vorgebauter Binaries.

Best Practice

Befolge diese Regeln für wartbares CMake:

1. Verwende immer target-basierte Befehle: target_include_directories, target_compile_options, target_link_libraries — niemals include_directories oder add_definitions (die den globalen Scope verschmutzen).
2. Verstehe PRIVATE/PUBLIC/INTERFACE: PRIVATE = nur für dieses Target, PUBLIC = für dieses Target und seine Konsumenten, INTERFACE = nur für Konsumenten.
3. Lege den C++-Standard pro Target oder projektweit fest, niemals über rohe Flags wie -std=c++20.
4. Aktiviere maximale Warnungen: -Wall -Wextra -Wpedantic (GCC/Clang) oder /W4 (MSVC). Erwäge -Werror / /WX in der CI.
5. Nutze cmake --preset für reproduzierbare Builds (CMakePresets.json).
6. Verdrahte niemals Pfade fest — verwende find_package, FetchContent oder Paketmanager.

Stolperfalle

file(GLOB ...) zum Einsammeln von Quelldateien: file(GLOB SOURCES src/*.cpp) läuft NICHT erneut, wenn Dateien hinzugefügt oder entfernt werden. Neue Dateien werden erst kompiliert, wenn du manuell neu konfigurierst. Liste Quelldateien immer explizit auf.

CMAKE_CXX_EXTENSIONS OFF vergessen: Ohne diese Einstellung verwendet GCC -std=gnu++20 statt -std=c++20 und aktiviert damit nicht standardkonforme Erweiterungen, die die Portabilität kaputtmachen.

add_subdirectory und FetchContent vermischen: Beide binden externe Projekte ein, gehen aber unterschiedlich mit dem Build-Cache um. Entscheide dich pro Abhängigkeit für einen Ansatz.

CMAKE_EXPORT_COMPILE_COMMANDS ON nicht gesetzt: Diese Einstellung erzeugt compile_commands.json, das clang-tidy, clangd (LSP) und viele IDE-Integrationen benötigen. Aktiviere sie immer.

Wichtigste Erkenntnisse
  • CMake ist der Standard-Build-System-Generator für C++ — lerne seine target-basierte API
  • Verwende target_*-Befehle (nicht den globalen Scope) mit der Sichtbarkeit PRIVATE/PUBLIC/INTERFACE
  • FetchContent lädt Abhängigkeiten zur Konfigurationszeit herunter und baut sie — ideal für kleine Abhängigkeitsmengen
  • vcpkg (Manifest-Modus) und Conan bewältigen größere Abhängigkeitsgraphen mit Binary-Caching
  • Aktiviere immer Warnungen (-Wall -Wextra -Wpedantic) und verwende Out-of-Source-Builds
  • Erzeuge compile_commands.json für IDE- und Static-Analysis-Tooling

Quiz — Teste dein Wissen

(15 XP)

1. Was bewirkt `target_link_libraries(myapp PRIVATE mathlib)`?

2. Warum gilt `file(GLOB SOURCES src/*.cpp)` in CMake als schlechte Praxis?

3. Wozu dient `CMAKE_CXX_EXTENSIONS OFF`?