CMakeLists.txt 应从最小可运行骨架入手,首行必须为 cmake_minimum_required(VERSION 3.10),project() 紧随其后并指定 LANGUAGES,add_executable 后需配 target_include_directories,target_link_libraries 要按 PRIVATE/PUBLIC/INTERFACE 明确依赖传播范围。

直接上手写,别从“教程”开始——CMakeLists.txt 的语法不是靠背出来的,是靠改错、看报错、再改错练出来的。最短路径是:先抄一个能跑的最小骨架,然后按需加东西,每次只动一行,立刻 cmake .. && make 验证。
怎么写第一行 cmake_minimum_required
这行必须是文件第一行,否则 CMake 直接报错退出。版本选 3.10 是当前最稳妥的下限:太低(比如 2.8)缺现代特性;太高(比如 3.25)可能在 CI 或旧机器上失败。
- 写成
cmake_minimum_required(VERSION 3.10)就够,括号里不能有空格(VERSION3.10是常见拼错) - 别用
cmake_minimum_required(VERSION ${CMAKE_VERSION})这种自引用,变量此时还没定义 - 如果项目用了
find_package(Threads REQUIRED)等模块,3.10足够;但若要用FetchContent或generator expressions,建议升到3.14或更高
project() 为什么总出错?
project() 必须紧跟在 cmake_minimum_required 后面,且它会重置很多内部变量(比如 CMAKE_CXX_STANDARD 会被清空),所以设置标准版本的 set() 必须放在这行之后。
- 常见错误:
project(MyApp)没写语言,结果 C++ 文件被当 C 编译,报error: 'std::string' was not declared in this scope - 正确写法:
project(MyApp LANGUAGES CXX)(只用 C++)或project(MyApp LANGUAGES C CXX)(混用) - 别在
project()里写VERSION 1.0后又手动设set(PROJECT_VERSION "1.0"),重复且易不同步
add_executable 和 target_include_directories 怎么配对用?
源文件能编译过去,不代表头文件能找到。很多初学者卡在 #include "xxx.h" 找不到,本质是没告诉目标该去哪搜头文件。
-
add_executable(myapp main.cpp)只管编译,不管头文件路径 - 必须配
target_include_directories(myapp PUBLIC include),其中PUBLIC表示:这个路径既给myapp用,也透传给依赖它的其他目标(比如测试 target) - 如果头文件在子目录(如
include/utils/xxx.h),路径写include就够,不要写成include/utils——#include "utils/xxx.h"才对 - 别用过时的
include_directories(include),它作用于全局,容易污染其他 target
target_link_libraries 的 PRIVATE/PUBLIC/INTERFACE 到底怎么选?
这不是风格问题,是链接可见性的硬约束。选错会导致下游 target 编译失败或链接失败,而且报错信息往往不直接指向这里。
-
PRIVATE:只影响当前 target,下游看不到(适合内部实现依赖,比如log4cpp) -
PUBLIC:当前 target + 所有依赖它的 target 都能用(适合你导出的头文件里用了某个库的类型,比如std::shared_ptr来自stdc++,但一般不用显式写) -
INTERFACE:只影响下游,当前 target 不链接(适合纯头文件库,比如fmt或range-v3) - 常见坑:
add_library(mylib STATIC ...)用PRIVATE链了pthread,结果add_executable(app ...)链mylib时却报undefined reference to 'pthread_create'—— 因为pthread没透传,得改成PUBLIC或在 app 里单独链一次
真正难的不是记住命令,而是理解每个命令背后的作用域和传播规则。CMake 不是脚本语言,它是声明式构建描述——你写的不是“怎么做”,而是“要什么”,中间怎么连、怎么传、怎么隔离,全靠这几个关键词控制。改一行就重新 cmake,比读十页文档管用。


















