cmake最佳实践包括:1. 用include_guard(global)防止重复包含;2. 用interface库绑定编译选项替代全局cmake_cxx_flags;3. 用configure_file生成配置头文件;4. 用add_subdirectory组织多目标项目。

用 include_guard 防止多次 include 同一个 CMake 模块
重复 include 一个 .cmake 文件(比如 CompileOptions.cmake)会导致变量重定义、函数重复声明,甚至 target_compile_options 被叠加两次——轻则警告,重则编译失败。
include_guard 是 CMake 3.10+ 的标准解法,它像 C++ 的 #pragma once 一样,在文件被第二次包含时直接跳过后续内容:
include_guard(GLOBAL)
常见误用点:
- 把
include_guard放在文件末尾或函数体内 —— 它必须是文件里第一个有效命令 - 在
function()内部调用include_guard—— 不合法,CMake 会报错 - 用
DIRECTORY却期望跨子目录去重 —— 它只对当前目录及子目录生效,父目录仍可再 include
用 target_compile_options + INTERFACE 库替代全局 set(CMAKE_CXX_FLAGS)
全局设置 CMAKE_CXX_FLAGS 看似省事,实则破坏模块边界:所有 target 都被迫继承同一套选项,无法为测试目标关掉 -Werror,也无法为嵌入式模块单独加 -mcpu=armv7。
正确做法是把编译选项“绑定到目标”,再通过 INTERFACE 库复用:
add_library(common_options INTERFACE) target_compile_options(common_options INTERFACE -Wall -Wextra) target_compile_features(common_options INTERFACE cxx_std_17) <h1>其他 target 只需链接它</h1><p>add_executable(my_app main.cpp) target_link_libraries(my_app PRIVATE common_options)</p>
好处:
- 选项只影响显式链接的 target,不污染构建树其他部分
- 不同平台可为同一
INTERFACE库设置不同属性:target_compile_options(common_options INTERFACE $:-std=c++17>) - Clion 和 VS 等 IDE 能正确解析并高亮这些选项
用 configure_file 把硬编码路径/宏转成生成式配置
手动在多个 CMakeLists.txt 里写 include_directories(${CMAKE_SOURCE_DIR}/include) 或 add_definitions(-DVERSION=\"1.2.3\"),本质是复制粘贴,版本一改全得手动搜替换。
换成模板文件 + configure_file:
- 新建
config.h.in:#define APP_VERSION "@PROJECT_VERSION@"
- 在根
CMakeLists.txt中:configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) - 再用
target_include_directories(my_target PRIVATE $>)让源码能#include "config.h"
关键细节:
-
@VAR@语法只认 CMake 变量,且变量必须在configure_file执行前已定义(如project(... VERSION 1.2.3)自动设PROJECT_VERSION) - 生成的
config.h在build/目录下,不是source/,避免污染源码树 - 如果要导出给下游项目用,得配合
install(EXPORT ...)和find_package
子目录用 add_subdirectory 而非手写 add_executable 列表
当项目有多个可执行目标(如 server、client、test_runner),把它们全堆在根 CMakeLists.txt 里,很快就会变成维护噩梦。每次增删目标都要改根文件,还容易漏掉 target_link_libraries 或 target_include_directories。
更健壮的结构是每个子目录独立管理自身目标:
-
src/server/CMakeLists.txt:add_executable(server server.cpp) target_link_libraries(server PRIVATE core_lib)
-
src/client/CMakeLists.txt:add_executable(client client.cpp) target_link_libraries(client PRIVATE core_lib)
- 根
CMakeLists.txt只做一件事:add_subdirectory(src/server) add_subdirectory(src/client)
这样做的实际收益:
- Clion 右上角运行配置下拉菜单自动列出所有
add_executable目标,无需手动切换 - 某个子目录临时禁用?注释掉一行
add_subdirectory就行,不影响其他模块构建 - 子目录可独立测试:
cd build/src/server && make server
最常被忽略的是 add_subdirectory 的路径必须是相对于当前 CMakeLists.txt 的,不能用绝对路径或 ../ 跨级引用 —— CMake 会静默失败,目标根本不会注册进构建系统。











