引言:施工队长的“双语手册”
在我们的CMake学习旅程中,这位“施工队长”一直用一套现代化的指挥体系(cmake 命令族)来统筹全局。但很多从传统构建系统迁移过来的开发者,或是跟随老教程学习的同学,脑海里已经牢牢刻下了 make 和 ninja 的肌肉记忆。
这一节附录就像一本双语对照手册:左边是CMake风格的“标准普通话”,右边是Make/Ninja风格的“地方方言”。理解它们之间的映射关系,不仅能让你在老项目和新项目之间游刃有余,更能深刻理解CMake作为元构建系统(Meta-Build System)的定位——它不负责直接砌砖,而是负责生成砌砖的流水线图纸。
配置阶段:从“画图纸”到“下料单”
在CMake的世界里,配置(Configure)是一个独立的、显式的阶段。而在传统Make项目中,配置往往隐含在第一次编译之前,或者通过独立的 ./configure 脚本完成。
传统Make/Ninja项目的配置
如果你拿到一个纯手写Makefile的项目,通常没有独立的“配置”命令。开发者需要手动编辑Makefile来修改编译器路径、头文件目录或宏定义。对于使用Autotools的项目,流程则是:
# Autotools风格
./configure --prefix=/usr/local --enable-feature-x
make
sudo make install
对于Ninja,由于Ninja本身不生成构建规则,它通常由GN、Meson或CMake生成。因此纯Ninja项目极少需要手动配置。
CMake的配置命令
CMake将配置阶段显式化,并且与构建目录强绑定。现代CMake推荐 out-of-source 配置:
# 使用默认生成器(Linux/Unix下通常是Unix Makefiles)
cmake -B build -S .
# 显式指定Make生成器
cmake -B build -S . -G "Unix Makefiles"
# 显式指定Ninja生成器
cmake -B build -S . -G Ninja
# 传递配置选项(相当于./configure的参数)
cmake -B build -S . -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/opt/myapp
核心差异总结:
./configure直接修改构建规则脚本;cmake -B生成全新的构建规则文件(Makefile或build.ninja)。- CMake的配置选项以
-D变量形式传递;Autotools以--flag形式传递。 - CMake配置后可重复执行(增量更新配置),而重新运行
./configure通常需要先make distclean。
构建阶段:直接喊“砌砖” vs 通过“工头”传话
配置完成后,真正的编译链接工作交由底层的Make或Ninja执行。CMake提供了一层抽象,让你不必关心底层到底是Make还是Ninja。
直接调用底层工具
当你进入构建目录后,可以直接调用生成好的工具:
# 进入构建目录
cd build
# 使用Make(会自动读取目录下的Makefile)
make -j$(nproc)
# 使用Ninja(会自动读取目录下的build.ninja)
ninja -j$(nproc)
这里的 -j$(nproc) 表示使用全部CPU核心并行编译。Make和Ninja都支持并行,但Ninja的并行调度通常更高效。
通过CMake抽象层调用
CMake推荐的做法是不直接调用 make 或 ninja,而是使用 cmake --build:
# 不进入构建目录也可执行
cmake --build build
# 指定并行任务数(跨生成器通用)
cmake --build build --parallel 8
# 指定构建目标(相当于 make target_name)
cmake --build build --target my_executable
# 传递底层生成器的原生参数(注意后面的 --)
cmake --build build --parallel 4 -- -k 0 # 传给make:遇到错误继续编译
cmake --build build -- -v # 传给ninja:显示完整命令
核心差异总结:
- 跨平台一致性:
cmake --build build在Windows(Visual Studio)、Linux(Make/Ninja)、macOS(Xcode)上都有效;而make在Windows上通常不存在。 - 目录无关性:
cmake --build build可以在源码根目录执行,无需cd build。 - 参数透传: 需要通过
--分隔符将原生参数传递给底层的Make或Ninja。
常见构建命令速查
| 操作意图 | Make风格 | Ninja风格 | CMake抽象风格(推荐) |
|---|---|---|---|
| 编译整个项目 | make |
ninja |
cmake --build build |
| 并行编译(8线程) | make -j8 |
ninja -j8 |
cmake --build build --parallel 8 |
| 编译特定目标 | make my_target |
ninja my_target |
cmake --build build --target my_target |
| 显示执行的完整命令 | make VERBOSE=1 |
ninja -v |
cmake --build build --verbose |
| 清理构建产物 | make clean |
ninja -t clean |
cmake --build build --target clean |
安装阶段:交钥匙的不同仪式
构建完成后,需要将头文件、库文件、可执行文件复制到系统目录或指定前缀路径。这个阶段在对照表中差异尤为明显。
直接调用底层工具安装
对于由CMake生成的Makefile或build.ninja,传统做法是在构建目录内执行:
cd build
# Make风格
sudo make install
# Ninja风格
sudo ninja install
# 指定安装前缀(仅部分生成器支持,CMake 3.15之前需重新配置)
sudo make install DESTDIR=/tmp/staging
sudo ninja install
注意:Make支持 DESTDIR 环境变量来重定向安装根目录,Ninja也支持,但这本质上是底层生成器的行为,不是CMake的行为。
通过CMake抽象层安装
CMake 3.15+ 引入了 cmake --install,这是现代CMake推荐的安装方式:
# 基本用法(安装到 CMAKE_INSTALL_PREFIX 指定的位置)
cmake --install build
# 指定安装前缀(无需重新配置,直接覆盖)
cmake --install build --prefix /opt/myapp
# 仅安装特定组件
cmake --install build --component Runtime
# 指定临时安装根目录(常用于打包)
cmake --install build --prefix /usr --default-directory-permissions u=rwx,g=rx
核心差异总结:
- 无需进入构建目录:
cmake --install build可以在任意位置执行,而make install必须在build目录内。 - 前缀覆盖:
--prefix参数在安装时动态覆盖,不需要像传统方式那样重新运行cmake -DCMAKE_INSTALL_PREFIX=...配置。 - 跨平台:
cmake --install在Windows上可以直接运行,不需要管理员权限的提升提示(配合合适的--prefix),而make install在Windows上往往不可行。 - 组件化安装: CMake原生支持按组件安装,Make层面的
make install需要Makefile本身实现类似逻辑,否则只能全量安装。
其他常用操作对照
除了配置、构建、安装这三大阶段,日常开发中还有几个高频操作值得并列对比。
运行测试
- Make风格: 项目需自行定义
make test目标(通常调用ctest)。 - Ninja风格:
ninja test(同样依赖项目定义)。 - CMake风格:
ctest --test-dir build或cmake --build build --target test。推荐前者,因为CTest能更好地管理测试标签、并行度和输出格式。
清理与重新构建
# 清理构建产物(保留配置)
make clean # 在build目录内
ninja -t clean # 在build目录内
cmake --build build --target clean
# 彻底删除构建目录(最干净)
rm -rf build/
编辑缓存(高级)
如果你需要修改配置变量:
- 传统方式: 手动编辑
Makefile或config.h。 - CMake方式: 使用
ccmake build(终端图形界面)或cmake-gui,或者直接修改build/CMakeCache.txt后重新运行cmake -B build。
选择哲学:何时用抽象,何时用原生?
看到这里,你可能会有疑问:既然CMake提供了 --build 和 --install 这样的抽象命令,为什么还有人直接去敲 ninja 或 make?
推荐用 cmake --build 的场景
- 你在编写跨平台脚本(如CI/CD的YAML文件),希望同一套命令在Linux、macOS、Windows上都有效。
- 你不确定队友使用什么生成器(有人用Make,有人用Ninja,有人用Visual Studio)。
- 你需要在源码根目录一键执行,不想记当前在哪个子目录。
推荐直接用 make/ninja 的场景
- 你在本地开发,已经明确知道生成器是Ninja,并且需要频繁使用Ninja特有的高级功能(如
ninja -t compdb生成编译数据库)。 - 你需要利用Make特有的功能,如
make -n(只打印命令不执行)进行深度调试。 - 项目极其庞大,你需要Ninja的极致性能,并且希望直接使用
ninja -d stats等诊断工具。
结语:统一指挥,因地制宜
CMake与Make/Ninja的关系,就像一位统筹全局的工程总监与一线施工班组的关系。CMake负责画总图、排工期、协调物料(cmake -B);Make和Ninja则是具体的施工班组,按照图纸砌砖抹灰(make/ninja)。
cmake --build 和 cmake --install 是工程总监的直接调度热线——无论现场是Make班组还是Ninja班组,一个电话就能统一指挥。而直接敲 make 或 ninja,则是你走到工地现场,直接对班组长喊话。
作为现代C++开发者,建议你养成使用 cmake --build 和 cmake --install 的习惯,它们是你行走江湖的“普通话”;同时保留阅读和理解原生Make/Ninja命令的能力,因为那是你与遗留项目和老派工程师交流的“方言”。两者兼备,方能游刃有余。


没有回复内容