Skip to content

CMake 现代实践

现代 CMake(3.x)以 target 为中心,通过 target_* 命令管理依赖和属性,告别全局变量的混乱时代。

核心概念:Target

cmake
# 现代 CMake 的核心:一切皆 target
# target 有三种可见性:
# PRIVATE   — 只影响当前 target
# PUBLIC    — 影响当前 target 和依赖它的 target
# INTERFACE — 只影响依赖它的 target(不影响自身)

add_library(mylib STATIC src/lib.cpp)

# 头文件路径
target_include_directories(mylib
    PUBLIC  include/      # 使用者也能看到
    PRIVATE src/internal/ # 只有 mylib 自己用
)

# 编译选项
target_compile_options(mylib PRIVATE -Wall -Wextra)

# 链接库
target_link_libraries(mylib
    PUBLIC  fmt::fmt      # 使用者也需要链接
    PRIVATE spdlog::spdlog # 只有 mylib 需要
)

# 编译定义
target_compile_definitions(mylib PRIVATE MYLIB_INTERNAL=1)
target_compile_definitions(mylib PUBLIC MYLIB_VERSION=2)

查找与使用依赖

cmake
# 方式 1:find_package(系统安装或 vcpkg)
find_package(fmt REQUIRED)
find_package(spdlog REQUIRED)
find_package(nlohmann_json REQUIRED)
find_package(GTest REQUIRED)

target_link_libraries(myapp PRIVATE
    fmt::fmt
    spdlog::spdlog
    nlohmann_json::nlohmann_json
    GTest::gtest_main
)

# 方式 2:FetchContent(自动下载,适合小依赖)
include(FetchContent)

FetchContent_Declare(
    CLI11
    GIT_REPOSITORY https://github.com/CLIUtils/CLI11.git
    GIT_TAG        v2.4.1
)
FetchContent_MakeAvailable(CLI11)
target_link_libraries(myapp PRIVATE CLI11::CLI11)

# 方式 3:vcpkg 工具链(推荐)
# cmake -DCMAKE_TOOLCHAIN_FILE=/path/to/vcpkg/scripts/buildsystems/vcpkg.cmake ..

多目标项目结构

cmake
# 根 CMakeLists.txt
cmake_minimum_required(VERSION 3.25)
project(MyProject VERSION 1.2.3)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

# 全局编译选项(通过 INTERFACE 库传递)
add_library(project_warnings INTERFACE)
target_compile_options(project_warnings INTERFACE
    $<$<CXX_COMPILER_ID:GNU,Clang>:-Wall;-Wextra;-Wpedantic>
    $<$<CXX_COMPILER_ID:MSVC>:/W4>
)

add_library(project_options INTERFACE)
target_compile_features(project_options INTERFACE cxx_std_20)

# 子目录
add_subdirectory(src)
add_subdirectory(tests)
add_subdirectory(benchmarks)

# 安装规则
install(TARGETS mylib myapp
    RUNTIME DESTINATION bin
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib
)
install(DIRECTORY include/ DESTINATION include)
cmake
# src/CMakeLists.txt
add_library(mylib STATIC
    engine.cpp
    parser.cpp
    utils.cpp
)
target_include_directories(mylib PUBLIC ${PROJECT_SOURCE_DIR}/include)
target_link_libraries(mylib
    PUBLIC  project_options
    PRIVATE project_warnings fmt::fmt
)

add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE mylib project_warnings)

生成器表达式

cmake
# 生成器表达式:在生成阶段求值(不是配置阶段)
# 语法:$<condition:value>

# 根据构建类型设置选项
target_compile_options(mylib PRIVATE
    $<$<CONFIG:Debug>:-g;-O0;-DDEBUG>
    $<$<CONFIG:Release>:-O3;-DNDEBUG>
)

# 根据编译器设置选项
target_compile_options(mylib PRIVATE
    $<$<CXX_COMPILER_ID:GNU>:-fstack-protector-strong>
    $<$<CXX_COMPILER_ID:Clang>:-fsanitize=address>
    $<$<CXX_COMPILER_ID:MSVC>:/GS>
)

# 区分构建时和安装时的头文件路径
target_include_directories(mylib PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
    $<INSTALL_INTERFACE:include>
)

版本管理

cmake
# 从 CMake 版本号生成头文件
configure_file(
    ${CMAKE_CURRENT_SOURCE_DIR}/include/version.h.in
    ${CMAKE_CURRENT_BINARY_DIR}/include/version.h
)
target_include_directories(mylib PUBLIC ${CMAKE_CURRENT_BINARY_DIR}/include)
cpp
// include/version.h.in
#pragma once
#define MYPROJECT_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define MYPROJECT_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define MYPROJECT_VERSION_PATCH @PROJECT_VERSION_PATCH@
#define MYPROJECT_VERSION "@PROJECT_VERSION@"

常用 CMake 命令速查

bash
# 配置
cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=vcpkg/scripts/buildsystems/vcpkg.cmake

# 构建
cmake --build build --parallel 8

# 安装
cmake --install build --prefix /usr/local

# 运行测试
ctest --test-dir build --output-on-failure --parallel 4

# 打包
cpack --config build/CPackConfig.cmake

# 查看所有 target
cmake --build build --target help

# 详细构建输出
cmake --build build --verbose

关键认知

现代 CMake 的核心原则:target_* 命令,不用全局变量PUBLIC/PRIVATE/INTERFACE 控制属性传播。compile_commands.jsonCMAKE_EXPORT_COMPILE_COMMANDS=ON)是 IDE 和工具链的基础,必须生成。

系统学习 C++ 生态,深入底层架构