modern cmake核心是“描述目标”而非配置编译器:用add_executable/add_library创建目标,再以target_include_directories、target_link_libraries等target_*命令精准配置其依赖与属性,避免全局设置污染和作用域失控。

从“能跑通”开始,别碰变量全局设置
刚上手就写 set(CMAKE_CXX_FLAGS) 或手动拼 include_directories(),90% 的人会在三天内放弃。Modern CMake 的核心不是“怎么配置编译器”,而是“怎么描述目标”。先确保你能用三行命令生成可执行文件,再谈其他。
-
cmake_minimum_required(VERSION 3.15)—— 版本别低于 3.15,否则target_compile_features等关键能力缺失 -
project(MyApp LANGUAGES CXX)—— 明确语言,避免隐式 C 模式干扰 C++ 编译行为 -
add_executable(myapp main.cpp)—— 所有源文件列在括号里,空格分隔,不要换行或逗号
构建必须用 out-of-source:在项目根目录下 mkdir build && cd build && cmake .. && cmake --build .。别在源码目录里直接 cmake .,否则生成的中间文件会污染源树,后续 git clean -fdx 都救不回来。
加库时只认准三个 target_* 命令
一旦要拆出 .cpp 文件做库,立刻停用所有 include_directories()、link_directories()、target_link_libraries(... LINK_PUBLIC) 这类过时写法。它们破坏依赖传递性,且无法跨生成器(比如 Ninja 和 VS)一致工作。
-
add_library(mylib STATIC src/math.cpp)—— 库名不能和可执行目标重名,否则cmake不报错但链接失败 -
target_include_directories(mylib PUBLIC ${CMAKE_SOURCE_DIR}/include)——PUBLIC表示头路径对使用者可见;PRIVATE仅限库内部用;INTERFACE只提供头不带实现 -
target_link_libraries(myapp PRIVATE mylib)——myapp依赖mylib,但不把mylib的依赖暴露出去,除非你明确需要传递依赖
常见错误:target_link_libraries(myapp mylib) 缺少作用域关键字,CMake 3.20+ 会警告,3.24+ 默认拒绝解析;更隐蔽的问题是,如果 mylib 用了 fmt,而你没设 PUBLIC,myapp 就没法 #include <fmt></fmt>。
子目录管理只靠 add_subdirectory,别拼路径
大型项目必然分 src/、lib/、tests/,但千万别在顶层 CMakeLists.txt 里写 add_subdirectory(../lib) 或 add_subdirectory(./lib)。相对路径在不同构建方式下行为不稳定,尤其配合 cmake -S / -B 时极易失效。
- 每个子目录必须自带
CMakeLists.txt,且只负责自己目录下的目标 - 父目录统一用
add_subdirectory(lib)—— 路径是相对于当前CMakeLists.txt的,不是相对于源码根或构建目录 - 子目录中访问路径一律用
${CMAKE_CURRENT_SOURCE_DIR},而不是${CMAKE_SOURCE_DIR}/lib,后者硬编码会断掉模块复用能力
一个典型坑:你在 lib/math/CMakeLists.txt 里写了 target_include_directories(math PUBLIC ${CMAKE_SOURCE_DIR}/include),结果把它挪到另一个项目当 submodule 用,${CMAKE_SOURCE_DIR} 指向新项目的根,头文件路径就错了。改用 ${CMAKE_CURRENT_SOURCE_DIR}/../include 或更稳妥的 ${CMAKE_CURRENT_LIST_DIR}/../include 才可靠。
第三方库优先用 find_package,别自己写头/库路径
系统级库(如 Threads、ZLIB)或已安装的库(如 OpenCV、Boost),直接 find_package(OpenCV REQUIRED) 即可。它自动处理头文件位置、链接选项、版本检查——比你手写 include_directories(/usr/include/opencv4) 安全十倍。
- 查不到?先确认库是否真装了:
pkg-config --modversion opencv4或dpkg -L libopencv-dev(Ubuntu) - 想指定版本?写成
find_package(OpenCV 4.5 REQUIRED),CMake 会拒绝匹配 4.4.x - 找不到 Config 模式文件(xxxConfig.cmake)?加
PATHS或设CMAKE_PREFIX_PATH环境变量,而不是改CMAKE_MODULE_PATH
最易被忽略的一点:find_package 成功后,它导出的是 OpenCV_LIBS 这类变量,但 Modern CMake 要求你用 target_link_libraries(myapp PRIVATE ${OpenCV_LIBS}) —— 更推荐直接链接 imported target:target_link_libraries(myapp PRIVATE OpenCV::opencv_core),这才是类型安全、可传递、支持跨平台的写法。











