cmake
CMake
用途
引导完成以目标为核心的现代 CMake(C/C++ 项目):源外构建、依赖管理、生成器选择,以及与 CI 和 IDE 的集成。
触发场景
- "如何为项目编写 CMakeLists.txt?"
- "如何用 CMake 添加外部库?"
- "CMake 找不到我的包/库"
- "如何在 CMake 中启用 Sanitizer?"
- "如何用 CMake 进行交叉编译?"
- "如何使用 CMake Presets?"
工作流程
1. 现代 CMake 原则
- 定义目标,而非变量。使用
target_*命令。 - 使用
PUBLIC/PRIVATE/INTERFACE控制属性传播。 - 不使用
include_directories()或link_libraries()(遗留 API)。 - 最低 CMake 版本:大多数特性需要
cmake_minimum_required(VERSION 3.20)。
2. 最小项目
cmake_minimum_required(VERSION 3.20)
project(MyApp VERSION 1.0 LANGUAGES C CXX)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_executable(myapp
src/main.c
src/utils.c
)
target_include_directories(myapp PRIVATE include)
target_compile_options(myapp PRIVATE -Wall -Wextra)
3. 静态库与共享库
# 静态库
add_library(mylib STATIC lib/foo.c lib/bar.c)
target_include_directories(mylib
PUBLIC include # 消费者获得此 include 路径
PRIVATE src # 仅 mylib 自身可见
)
# 共享库
add_library(myshared SHARED lib/foo.c)
set_target_properties(myshared PROPERTIES
VERSION 1.0.0
SOVERSION 1
)
# 将可执行文件链接到库
add_executable(myapp src/main.c)
target_link_libraries(myapp PRIVATE mylib)
4. 配置与构建
# 源外构建(始终应这样做)
cmake -S . -B build
cmake --build build
# 指定生成器
cmake -S . -B build -G Ninja
cmake --build build -- -j$(nproc)
# Debug 构建
cmake -S . -B build-debug -DCMAKE_BUILD_TYPE=Debug
cmake --build build-debug
# Release 构建
cmake -S . -B build-release -DCMAKE_BUILD_TYPE=Release
cmake --build build-release
# 安装
cmake --install build --prefix /usr/local
构建类型:Debug、Release、RelWithDebInfo、MinSizeRel。
5. 外部依赖
find_package(系统已安装的库)
find_package(OpenSSL REQUIRED)
target_link_libraries(myapp PRIVATE OpenSSL::SSL OpenSSL::Crypto)
find_package(Threads REQUIRED)
target_link_libraries(myapp PRIVATE Threads::Threads)
find_package(ZLIB REQUIRED)
target_link_libraries(myapp PRIVATE ZLIB::ZLIB)
FetchContent(下载并构建依赖)
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG v1.14.0
)
FetchContent_MakeAvailable(googletest)
add_executable(mytest test/test_foo.cpp)
target_link_libraries(mytest PRIVATE GTest::gtest_main mylib)
pkg-config 回退
find_package(PkgConfig REQUIRED)
pkg_check_modules(LIBFOO REQUIRED libfoo>=1.2)
target_link_libraries(myapp PRIVATE ${LIBFOO_LIBRARIES})
target_include_directories(myapp PRIVATE ${LIBFOO_INCLUDE_DIRS})
6. 按构建配置设置编译选项
target_compile_options(myapp PRIVATE
$<$<CONFIG:Debug>:-g -Og -fsanitize=address>
$<$<CONFIG:Release>:-O2 -DNDEBUG>
$<$<CXX_COMPILER_ID:GNU>:-fanalyzer>
$<$<CXX_COMPILER_ID:Clang>:-Weverything>
)
target_link_options(myapp PRIVATE
$<$<CONFIG:Debug>:-fsanitize=address>
)
生成器表达式:$<条件:值> 在构建时求值。
7. 启用 Sanitizer
option(ENABLE_ASAN "启用 AddressSanitizer" OFF)
if(ENABLE_ASAN)
target_compile_options(myapp PRIVATE -fsanitize=address -fno-omit-frame-pointer -g -O1)
target_link_options(myapp PRIVATE -fsanitize=address)
endif()
构建命令:cmake -DENABLE_ASAN=ON -S . -B build-asan && cmake --build build-asan
8. 交叉编译工具链文件
# toolchain-aarch64.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++)
set(CMAKE_SYSROOT /opt/aarch64-sysroot)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
cmake -S . -B build-arm -DCMAKE_TOOLCHAIN_FILE=toolchain-aarch64.cmake
9. CMake Presets(CMake 3.20+)
{
"version": 6,
"configurePresets": [
{
"name": "release",
"displayName": "Release",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/release",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "debug",
"displayName": "Debug",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build/debug",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"ENABLE_ASAN": "ON"
}
}
],
"buildPresets": [
{ "name": "release", "configurePreset": "release" },
{ "name": "debug", "configurePreset": "debug" }
]
}
cmake --preset release
cmake --build --preset release
10. 常见错误
| 错误 | 原因 | 解决方法 |
|---|---|---|
Could not find package Foo |
包未安装或前缀路径有误 | 安装开发包;设置 CMAKE_PREFIX_PATH |
No CMAKE_CXX_COMPILER |
找不到 C++ 编译器 | 安装 g++/clang++;检查 PATH |
target_link_libraries called with wrong number of arguments |
缺少 PUBLIC/PRIVATE/INTERFACE |
添加关键字 |
Cannot find source file |
拼写错误或相对路径有误 | 检查相对于 CMakeLists.txt 的路径 |
generator expression 错误 |
$<> 语法有误 |
查阅 CMake 文档确认表达式名称 |
完整 CMakeLists.txt 模板见 references/templates.md。
相关技能
- 使用
skills/build-systems/ninja了解 Ninja 生成器的详细信息 - 使用
skills/build-systems/make了解 Make 生成器 - 使用
skills/compilers/cross-gcc配置交叉编译工具链 - 使用
skills/runtimes/sanitizers了解 Sanitizer 集成的详细信息
More from killvxk/low-level-dev-skills-zh
binutils
GNU binutils 二进制操作与分析技能。适用场景:使用 ar 管理静态库、使用 strip 或 objcopy 处理二进制文件、使用 addr2line 将地址转换为源码位置、使用 strings 提取文本、或使用 c++filt 对 C++ 名称进行反混淆。触发条件:涉及 ar、strip、objcopy、addr2line、strings、c++filt、ranlib 或二进制后处理任务的查询。
1ebpf
Linux 可观测性和网络的 eBPF 技能。适用场景:使用 libbpf 或 bpftrace 编写 eBPF 程序、挂载 kprobe/tracepoint/XDP 钩子、调试验证器错误、使用 eBPF map,或实现跨内核版本的 CO-RE 可移植性。触发条件:查询 eBPF、bpftool、bpftrace、XDP 程序、libbpf、验证器错误、eBPF map 或使用 BPF 进行内核追踪相关问题。
1clang
C/C++ 项目的 Clang/LLVM 编译器技能。适用场景:使用 clang 或 clang++ 进行诊断、sanitizer 插桩、优化备注、通过 clang-tidy 进行静态分析、通过 lld 实现 LTO,或从 GCC 迁移到 Clang。触发条件:涉及 clang 标志、clang-tidy、clang-format、更好的错误信息、Apple/FreeBSD 工具链或 LLVM 特定优化的查询。涵盖标志选择、诊断调优及与 LLVM 工具的集成。
1gcc
C/C++ 项目的 GCC 编译器技能。适用场景:选择优化级别、警告标志、调试构建、LTO、sanitizer 插桩,或诊断 GCC 编译错误。涵盖调试与发布构建的标志选择、ABI 问题、预处理器宏、配置引导优化(PGO)及与构建系统的集成。触发条件:涉及 gcc 标志、编译错误、性能调优、警告抑制或跨标准编译的查询。
1cpp-modules
现代 C++ 项目的 C++20 模块技能。适用场景:使用命名模块、模块分区、头文件单元、CMake MODULE_SOURCES、Clang -fmodules-ts、BMI 缓存问题,或从头文件迁移到模块。触发条件:涉及 C++20 模块、import 语句、模块接口单元、头文件单元或 BMI 文件的查询。
1binary-hardening
二进制加固技能,用于安全加固的 C/C++ 构建。适用场景:启用 RELRO、PIE、栈金丝雀、FORTIFY_SOURCE、CFI sanitizers、影子栈,或 seccomp-bpf 系统调用过滤。涵盖 checksec 分析、用于加固构建的编译器和链接器标志,以及 NSA/CISA 推荐的缓解措施。触发条件:查询涉及二进制加固、checksec、RELRO、PIE、栈金丝雀、FORTIFY_SOURCE、CFI、影子栈,或 seccomp。
1