CMake 入门与多目录项目
用现代 CMake(target 级命令)描述最小项目与多目录工程,理解配置/生成/构建三阶段与 out-of-source 构建
- 来源
- 补充
- 纠错
- 待确认
标记说明:【来源】来自上传资料 · 【补充】课程新编 · 【纠错】按勘误表修正 · 【更新】过时内容已现代化 · 【待确认】无法可靠还原
完成标准(本章)
- 📖 已阅读:滚动 ≥ 80% 且有效阅读 ≥ 180 秒
- ✏️ 已练习:小练习正确率 ≥ 60%
- 📝 已通过测验:分数 ≥ 60 分
- 🛠️ 已掌握还需完成实践任务
1.1.5 CMake 入门与多目录项目
本章来源:配置/生成/构建三阶段、CMakeLists 基本结构与多目录 add_subdirectory 来自《第一阶段讲义》5.x 节【来源】,经重新组织表述;-D 变量拼写笔误已【纠错】(err-7.1-23:-DMAKE_* → -DCMAKE_*);原资料示例全部以截图嵌入无法还原【待确认】——本章示例代码全部重写并本机实测【补充】(cmake 4.2.3,约 40% 新编);现代 target 级写法为资料未覆盖的补充内容。平台适用性 universal。
① 学习目标
- 说出 CMake 解决的"跨平台构建描述"问题,以及它与 Makefile 的关系;
- 讲清 配置(configure)→ 生成(generate)→ 构建(build)三阶段各自做什么;
- 写出最小项目的 CMakeLists.txt(cmake_minimum_required / project / add_executable)并完成一次 out-of-source 构建;
- 使用 target 级命令(target_include_directories / target_link_libraries)与 PRIVATE / PUBLIC / INTERFACE 表达依赖;
- 独立完成多目录工程(add_subdirectory + add_library + 链接);
- 用 -DCMAKE_BUILD_TYPE 切换 Debug/Release,并说出常见配置与链接错误各发生在哪一阶段。
② 前置知识
- 必选:1.1.4 Makefile(构建规则与增量编译概念——CMake 是把"写规则"再抽象一层);
- 建议:1.1.1 GCC 四阶段(理解编译与链接错误的阶段归属,本章诊断示例直接复用)。
③ 核心概念【来源】
| 概念 | 说明 |
|---|---|
| CMake 解决什么问题 | Makefile 直接描述"怎么编译",但换平台(Linux/Windows/macOS、不同生成器)要重写;CMake 用 CMakeLists.txt 描述"工程长什么样",由它生成对应平台的构建脚本 |
| 三阶段 | ① 配置:读取 CMakeLists.txt 与 -D 选项,检测编译器/平台;② 生成:产出构建脚本(Unix Makefiles / MinGW Makefiles / Ninja / Visual Studio 工程);③ 构建:执行生成的脚本(make/ninja/MSBuild)产出可执行文件 |
| cmake_minimum_required | 声明所需最低 CMake 版本(如 cmake_minimum_required(VERSION 3.16)),避免用低版本打开高版本工程时语义不一致;不锁定陈旧版本,写当前稳定可用的下限即可 |
| project | project(名称 C):命名工程并声明语言,同时定义 CMAKE_PROJECT_NAME 等变量 |
| add_executable / add_library | 声明可执行目标 / 库目标(STATIC/SHARED);目标(target)是现代 CMake 的核心对象 |
| target_include_directories | 给某个目标加头文件搜索路径(替代全局 include_directories 旧式写法) |
| target_link_libraries | 给某个目标链接库;同时"传递"库的 PUBLIC 使用要求(头文件路径、宏、依赖) |
| PRIVATE / PUBLIC / INTERFACE | 使用要求的传播方向:PRIVATE 只自己用;INTERFACE 只给下游用(纯头文件库);PUBLIC 两者都要(库本身和下游都需要) |
| out-of-source build | cmake -S 源目录 -B build:构建产物全部进 build/,源码目录零污染;是社区标准实践(in-source 已不推荐) |
| -DCMAKE_BUILD_TYPE | 配置单配置生成器(Makefiles/Ninja)的构建类型:Debug(-g 调试)/ Release(-O3 -DNDEBUG)/ RelWithDebInfo |
| cmake --build | 统一构建入口:cmake --build build(自动调用底层 make/ninja/MSBuild,跨平台一致) |
| 生成器差异 | Unix Makefiles / MinGW Makefiles / Ninja / Visual Studio:同一份 CMakeLists 可生成任一种;VS 是"多配置"生成器(Debug/Release 在构建时选,-DCMAKE_BUILD_TYPE 不适用) |
④ 通俗解释【补充】
- CMake 是"菜单翻译":你写一张中文菜单(CMakeLists.txt),翻译官(cmake)照着客人国籍生成法语/英语/日语菜单(Makefiles/Ninja/VS 工程),厨房(编译器)只看得懂自己的语言;
- Makefile 是手写的"后厨操作手册":换个厨房要重写;CMake 是"点菜系统":菜单不变,翻译成哪个厨房的操作手册由生成器决定;
- target 级命令是"按桌配餐":target_include_directories 只给这桌(目标)上佐料,别的桌不受影响——旧式全局命令像"全店泼佐料",一桌口味变了全店遭殃;
- PRIVATE/PUBLIC/INTERFACE 是"秘方是否外传":PRIVATE=自己后厨用;PUBLIC=自己用也写进菜单给客人看;INTERFACE=自己不炒菜、纯卖秘方(头文件库)。
⑤ 示例代码
代码示例与验证记录
- examples/ex1-minimal/CMakeLists.txt最小项目(cmake_minimum_required/project/add_executable + out-of-source 构建)✓ 已实测(cmake 4.2.3 / MinGW Makefiles / gcc 15.2.0 / Windows 11, 2026-08-15)编译:
在 ex1-minimal 目录内:cmake -S . -B build -G "MinGW Makefiles" && cmake --build build适用环境:LinuxWindows(MinGW)macOS展开预期输出(实测)
-- Configuring done (1.6s) -- Generating done (0.0s) -- Build files have been written to: <目录>/build [ 50%] Building C object CMakeFiles/app.dir/src/main.c.obj [100%] Linking C executable app.exe [100%] Built target app cmake demo: 3+4=7 [100%] Built target app (第二遍 cmake --build:无重编译,增量生效)
差异说明:配置耗时随机器不同;第二遍构建只显示 Built target(CMake 生成的 Makefile 同样按时间戳做增量编译)
完整源码见 /code 代码示例页
- examples/ex2-multidir/CMakeLists.txt多目录工程(add_subdirectory + add_library + PUBLIC 头文件传播 + PRIVATE 链接)✓ 已实测(cmake 4.2.3 / MinGW Makefiles / gcc 15.2.0 / Windows 11, 2026-08-15)编译:
在 ex2-multidir 目录内:cmake -S . -B build -G "MinGW Makefiles" && cmake --build build适用环境:LinuxWindows(MinGW)macOS展开预期输出(实测)
-- Configuring done (1.5s) -- Build files have been written to: <目录>/build [ 25%] Building C object lib/CMakeFiles/mymath.dir/mymath.c.obj [ 50%] Linking C static library libmymath.a [ 75%] Building C object CMakeFiles/app.dir/src/main.c.obj [100%] Linking C executable app.exe [100%] Built target app square(7) = 49
差异说明:注意 app 源码里直接
完整源码见 /code 代码示例页
- examples/ex3-cmake-diagnostics.txt配置与链接错误诊断(diagnostic:漏 target_link_libraries 的两种真实报错)诊断示例(预期报错/警告)✓ 已实测(cmake 4.2.3 / MinGW Makefiles / gcc 15.2.0 / Windows 11, 2026-08-15)编译:
见文件内说明(实验基于 ex2-multidir 修改后真实执行)适用环境:LinuxWindows(MinGW)macOS展开预期输出(实测)
—— 诊断 A(漏 target_link_libraries,编译期)—— src/main.c:2:10: fatal error: mymath.h: No such file or directory mingw32-make[2]: *** [CMakeFiles\app.dir\build.make:78: ...main.c.obj] Error 1 —— 诊断 B(include 已给但库未链接,链接期)—— .../ld.exe: CMakeFiles\app.dir/objects.a(main.c.obj):main.c:(.text+0x13): undefined reference to `square' collect2.exe: error: ld returned 1 exit status
差异说明:两条均为 cmake 4.2.3 实测真实文本(路径与行号随环境不同);诊断文本在不同编译器/生成器下可能措辞不同,稳定核心是 No such file or directory 与 undefined reference 两行
完整源码见 /code 代码示例页
⑥ 编译与运行方法
本章命令为 CMake 命令(本机实测:cmake 4.2.3 + MinGW Makefiles 生成器 + gcc 15.2.0 / Windows 11):
cmake -S . -B build -G "MinGW Makefiles" # 配置+生成(Windows/MinGW;Linux 可省略 -G)
cmake --build build # 构建
build\app.exe # 运行(Linux: ./build/app)- ex1/ex2 的真实输出(配置完成、[50%]/[100%] 构建进度、运行结果、第二遍构建的增量行为)见示例卡折叠区;
- 多目录 ex2 的关键点是 add_subdirectory(lib) 后,lib 目标通过
target_link_libraries(app PRIVATE mymath)自动获得 mymath 的 PUBLIC 头文件路径; - Debug/Release:
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release(注意前缀是 CMAKE_,见勘误 err-7.1-23);多配置生成器(VS)改为构建时选配置。
⑦ 常见错误
| 症状 | 原因 | 解决 |
|---|---|---|
【纠错】写了 -DMAKE_INSTALL_PREFIX / -DMAKE_BUILD_TYPE | 前缀少字母(资料总结处笔误) | 写全 -DCMAKE_INSTALL_PREFIX / -DCMAKE_BUILD_TYPE;cmake 对未知变量静默忽略(不报错但不生效),极难排查 |
fatal error: mymath.h: No such file or directory | 漏了 target_link_libraries:库的 PUBLIC 头文件路径只随"链接关系"传播 | 补 target_link_libraries(app PRIVATE mymath)(本机实测诊断 A) |
undefined reference to 'square' | 头文件找得到但没链接库(或链接顺序/库名错) | 检查 target_link_libraries 与库目标名(本机实测诊断 B) |
| 改了 CMakeLists 不生效 | 直接调 make 而不是 cmake --build;或没重新配置 | 重新 cmake -S . -B build 再构建;统一用 cmake --build |
| build 目录里到处是产物、源码目录被污染 | 用了 in-source 构建(cmake .) | 一律 -S 源 -B build out-of-source 构建 |
cmake --build 提示找不到生成器输出 | build 目录不是本机生成器生成的 | 用同一生成器重新配置(如 MinGW Makefiles) |
⑧ 小练习
小练习
学习自测:提交后才显示答案与解析(前端判分,不作为正式考试)ex-1-1-5-1.cmake -S . -B build 这个命令完成的是哪两个阶段?(单选)
◌ 未作答ex-1-1-5-2.mymath 库的头文件路径想让链接它的目标自动获得,应该用哪种传播方式?(单选)
◌ 未作答ex-1-1-5-3.构建时报 undefined reference to 'square',但头文件能找到,最可能的原因是?(单选)
◌ 未作答
⑨ 章节测验
章节测验
⑩ 实战任务
实践任务
为多文件项目同时提供 CMake 与 Makefile 两套构建
把自己的多文件小项目(如 1.1.4 的 ex1 双文件工程,或自建含一个静态库的三文件工程)改造成 CMake 构建:顶层 CMakeLists + add_subdirectory 子目录,使用 target 级命令与 PRIVATE/PUBLIC, 完成 out-of-source 构建并跑通;再与已有的 Makefile 构建对比说明各自优劣。
输入与输出
无程序输入输出。交付物:① CMakeLists.txt(顶层+子目录);② cmake --build 构建与运行成功记录; ③ 一段"CMake vs Makefile 各自优劣"的 100 字以上小结。
功能要求
- 工程至少含一个静态库目标(add_library)与一个可执行目标(add_executable)
- 使用 target_include_directories / target_link_libraries(不用全局旧式命令)
- 使用 out-of-source 构建(cmake -S . -B build)
- 分别以 Debug 与 Release 各构建一次并运行(-DCMAKE_BUILD_TYPE,前缀写全 CMAKE_)
- 写出 CMake 与 Makefile 的优劣对比小结
限制条件
- 实验在专用目录进行,不污染课程仓库
- 版本使用当前稳定版 cmake,不锁定陈旧版本
验收步骤(自检清单 0/5)
验收标准
- cmake --build build 无错误且程序输出正确(验收步骤 3)
- 两种构建类型均构建成功(验收步骤 4)
- 小结提到"描述 vs 规则"与"生成器/跨平台"两个要点(验收步骤 5)
常见失败原因
- 漏 target_link_libraries → undefined reference(诊断 B)
- 头文件找不到 → PUBLIC 传播缺失(诊断 A)
- 用 in-source 构建污染了源码目录
- -D 前缀拼成 MAKE_ 导致类型不生效且无报错(err-7.1-23)
可选扩展
- 用 Ninja 生成器构建一次并对比构建速度(cmake -G Ninja)
- 给库目标加 INTERFACE 头文件库做一次纯头文件模块实验
完成必要清单后才能计入"已完成实践"(学习状态自动推导,不提供一键完成)
⑪ 面试问题
面试问题
CMake 和 Makefile 是什么关系?各自解决什么问题?高频C/C++ · medium
要点:Makefile 直接描述"怎么编译";CMake 用 CMakeLists.txt 描述"工程长什么样",再生成 Makefile/Ninja/VS 工程等构建脚本。
Makefile 是构建规则(目标/依赖/命令),与平台和工具强绑定——Windows 的 nmake、Linux 的 make、 生成器的语法都不同。CMake 是更高一层的工程描述:CMakeLists.txt 声明目标、源文件、依赖与使用要求, 由 cmake 按生成器(-G)产出对应构建脚本(Unix Makefiles/MinGW Makefiles/Ninja/Visual Studio)。 分层收益:换平台/换生成器不改工程描述;同一份 CMakeLists 可同时维护 Makefile 与 VS 工程两种构建。
追问:- 追问:既然 CMake 生成 Makefile,为什么不直接用 Makefile?(跨平台描述与生成器抽象、自动依赖分析、模块与测试生态)
评分要点:- 描述 vs 规则的层次关系
- 生成器抽象与跨平台
target_link_libraries 里的 PRIVATE、PUBLIC、INTERFACE 分别是什么意思?高频C/C++ · medium
要点:控制"使用要求"的传播方向:PRIVATE 只自己用;PUBLIC 自己用且传给下游;INTERFACE 只传给下游(自己不使用)。
现代 CMake 的依赖围绕 target 展开:一个库的"使用要求"包括头文件路径、编译宏、链接的库。 PRIVATE:这些要求只影响本目标编译(内部实现细节,下游不可见);PUBLIC:本目标编译需要, 且任何链接本目标的下游也自动获得(如头文件路径随链接关系传播);INTERFACE:本目标不产生 编译单元(纯头文件库/仅传递配置),要求只给下游。举例:mymath 的头文件路径必须 PUBLIC, 否则 app 链接 mymath 后依然 #include 不到 mymath.h。
追问:- 追问:为什么现代 CMake 推荐 target 级命令而不用全局 include_directories?(避免目录级污染、依赖显式化)
评分要点:- 三种传播方向定义准确
- 能举头文件路径的例子
一个工程如何在同一个源码目录下同时维护 Debug 与 Release 两个构建?C/C++ · medium
要点:out-of-source 构建:同一份源码分别 cmake -S . -B build-debug -DCMAKE_BUILD_TYPE=Debug 与 -B build-release -DCMAKE_BUILD_TYPE=Release,两套产物互不干扰。
out-of-source 构建把构建产物与源码分离,因此同一源码可以生成任意多个构建目录,每个目录一份 独立的 CMake 缓存与产物。单配置生成器(Makefiles/Ninja)通过配置时的 -DCMAKE_BUILD_TYPE 区分; 多配置生成器(Visual Studio/Xcode)则在构建或打开 IDE 时选择配置。切回 in-source 会污染源码目录 且无法共存多配置——这是社区默认 out-of-source 的原因。
追问:- 追问:多配置生成器下 -DCMAKE_BUILD_TYPE 还有效吗?(无效,构建时选配置;可用 CMAKE_CONFIGURATION_TYPES 控制可选集合)
评分要点:- out-of-source 与多构建目录
- 单/多配置生成器差异
⑫ 延伸阅读
- 《Professional CMake》(书名,只引名称:target 级命令与 PRIVATE/PUBLIC/INTERFACE 的权威讲解);
- CMake 官方文档:cmake.org/documentation(cmake-buildsystem(7) 手册讲 target 与使用要求);
- 本地手册:
cmake --help/cmake --help-command target_link_libraries; - 下一章预告:1.1.6 GDB 调试实战——构建出 Debug 版程序后,用 GDB 定位崩溃。
迁移训练(migration training)
把本章技能迁移到 Linux 与 macOS:
| 环节 | Windows(MinGW) | Linux / macOS |
|---|---|---|
| 生成器 | MinGW Makefiles(本机实测) | Unix Makefiles(默认)/ Ninja |
| 命令 | 一致(cmake -S . -B build && cmake --build build) | 一致 |
| 产物 | app.exe | build/app(无后缀) |
| 多配置 | 不适用(单配置生成器) | 不适用;VS/Xcode 多配置构建时选 |
| 交叉编译 | 不涉及 | -DCMAKE_TOOLCHAIN_FILE=xxx.cmake(7.2 展开) |
不变的:CMakeLists.txt 内容、target 级命令、三阶段、PRIVATE/PUBLIC/INTERFACE 语义;要改的:生成器名与产物后缀(平台差异由 cmake 吸收,这正是它的价值)。
内容来源映射
| 内容部分 | 资料 | 位置 | 标记 | 说明 |
|---|---|---|---|---|
| 配置/生成/构建三阶段、CMakeLists 基本结构与命令、多目录 add_subdirectory、Debug/Release 配置 | 第一阶段讲义 | 5.x 节 | 【来源】 | 正文在原资料基础上重新组织表述,未大段复制原文 |
| -D 变量拼写(总结处 -DMAKE_* 笔误) | 第一阶段讲义 | 5.x 节(PAGE 143) | 【纠错】 | 见勘误 err-7.1-23;正文一律使用 -DCMAKE_ 前缀 |
| 原资料 CMake 示例 | 第一阶段讲义 | 5.x 节 | 【待确认】 | 全部示例以截图嵌入、无法还原 → 本章示例代码全部重写并本机实测(cmake 4.2.3) |
| 现代 target 级写法(target_include_directories/target_link_libraries 与 PRIVATE/PUBLIC/INTERFACE)、生成器差异、实验记录 | 无 | 【补充】 | 原资料以全局 include_directories 等旧式写法为主;本章按现代推荐重构,比例约 40% | |
| 小练习 / 章节测验 / 实践任务 / 面试问题 / 延伸阅读 | 无 | 【补充】 | 原资料该章无成体系练习,全部新编 |
本章勘误与更新记录(【纠错】/【更新】)
err-7.1-23 · 技术错误 · 出处 PAGE 143
原文:资料总结处把 -D 变量写成 -DMAKE_INSTALL_PREFIX / -DMAKE_BUILD_TYPE
正确:正确拼写为 -DCMAKE_INSTALL_PREFIX / -DCMAKE_BUILD_TYPE(前缀是 CMAKE_ 不是 MAKE_;资料正文页写法正确,总结处笔误)
原因:第一步报告 7.1 第 23 条:少字母会导致 cmake 生成时静默忽略该变量(不报错但不生效),是危险的笔误