Перейти к содержимому

Системы сборки: CMake & Package Management

Основы Master CMake, современные целевые CMake, FetchContent для зависимостей, менеджеры пакетов vcpkg и Conan и флаги компиляторов для безопасных, оптимизированных сборок.

Почему важно строить системы

Поскольку проекты C++ выходят за рамки одного файла, вам нужна система для управления компиляцией, ссылками, зависимостями и различиями платформы. Ручно работающий g++ *.cpp не масштабируется: вам нужны дополнительные сборки (рекомпилировать только измененные файлы), обнаружение библиотеки, кроссплатформенная поддержка и воспроизводимые сборки. CMake** стал де-факто стандартным генератором систем сборки для C++. Он не компилирует сам код — он генерирует собственные файлы сборки (Makefiles, файлы Ninja, проекты Visual Studio) для вашей платформы.

CMake Основы

Файл CMakeLists.txt описывает структуру вашего проекта. Современный CMake (3.12+) использует подход target-based, где все прикрепляется к целям (исполняемым файлам или библиотекам):

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

Строительство с помощью CMake

CMake использует двухэтапный процесс: configure (генерировать файлы сборки) и build (компиляция). Всегда используйте внешние сборки, чтобы сохранить дерево источника чистым:

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: Управление зависимостью

Модуль FetchContent от CMake загружает и создает зависимости во время настройки, что позволяет легко извлекать библиотеки без ручной настройки:

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 и Конан

Для более крупных проектов со многими зависимостями выделенные менеджеры пакетов C++ обеспечивают лучшее разрешение зависимостей и кэширование:

vcpkg (Microsoft) — интегрируется непосредственно с CMake через файл инструментальной цепи. Установите пакеты глобально или в «проявленном режиме» с файлом vcpkg.json:

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

Использование: cmake -B build -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake

Conan — использует conanfile.txt или conanfile.py для объявления зависимостей. Поддерживает несколько конфигураций сборки и бинарное кэширование:

[requires]
fmt/10.2.1
spdlog/1.13.0

[generators]
CMakeDeps
CMakeToolchain

Оба менеджера пакетов поддерживают хранилища тысяч библиотек C++ с проверенными, предварительно построенными двоичными файлами.

Лучшая практика

Следуйте этим правилам для поддержания CMake:

1. Всегда используйте целевые команды : target_include_directories, target_compile_options, target_link_libraries — никогда include_directories или add_definitions (загрязнение окружающей среды).
2. Понять PRIVATE/PUBLIC/INTERFACE: PRIVATE = только для этой цели, PUBLIC = для этой цели и ее потребителей, INTERFACE = только для потребителей.
3. Установите стандарт C++ для каждой цели или всего проекта, никогда не используя сырые флаги, такие как -std=c++20.
4. Включить максимальные предупреждения: -Wall -Wextra -Wpedantic (GCC/Clang) или /W4 (MSVC). Рассмотрим -Werror / /WX в CI.
5. Используйте cmake --preset для воспроизводимых сборок (CMakePresets.json).
6. Никогда не используйте траектории жесткого кода — используйте find_package, FetchContent или менеджеры пакетов.

Подводный камень

Использование file(GLOB ...) для сбора источников: file(GLOB SOURCES src/*.cpp) НЕ повторяется при добавлении или удалении файлов. Новые файлы не будут скомпилированы, пока вы не перенастроите их вручную. Всегда перечислите исходные файлы явно.

Забыв CMAKE_CXX_EXTENSIONS OFF: Без этого GCC использует -std=gnu++20 вместо -std=c++20, обеспечивая нестандартные расширения, которые нарушают переносимость.

Смешивание add_subdirectory и FetchContent: Оба вводят внешние проекты, но они по-разному взаимодействуют с кэшем сборки. Выберите один подход к зависимости.

Не устанавливайте CMAKE_EXPORT_COMPILE_COMMANDS ON Это создает compile_commands.json, который необходим для clang-tidy, clangd (LSP) и многих интеграций IDE. Всегда позволяйте.

Ключевые выводы
  • CMake - это стандартный генератор систем C++ - изучите его целевой API
  • Использование команд target_* (не глобального масштаба) с видимостью PRIVATE/PUBLIC/INTERFACE
  • FetchContent загружает и создает зависимости во время настройки - идеально подходит для небольших наборов зависимостей
  • vcpkg (режим манифеста) и Conan обрабатывают большие графы зависимостей с бинарным кэшированием
  • Всегда включайте предупреждения (-Wall -Wextra -Wpedantic) и используйте сборки извне
  • Создание compile_commands.json для инструментов IDE и статического анализа

Тест — проверьте свои знания

(15 XP)

1. Чем занимается `target_link_libraries(myapp PRIVATE mathlib)`?

2. Почему `file(GLOB SOURCES src/*.cpp)` считается плохой практикой в CMake?

3. Что такое `CMAKE_CXX_EXTENSIONS OFF`?