53. 附录E:CMake与Make/Ninja命令对照

引言:施工队长的“双语手册”

在我们的CMake学习旅程中,这位“施工队长”一直用一套现代化的指挥体系(cmake 命令族)来统筹全局。但很多从传统构建系统迁移过来的开发者,或是跟随老教程学习的同学,脑海里已经牢牢刻下了 makeninja 的肌肉记忆。

这一节附录就像一本双语对照手册:左边是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 生成全新的构建规则文件(Makefilebuild.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推荐的做法是不直接调用 makeninja,而是使用 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 buildcmake --build build --target test。推荐前者,因为CTest能更好地管理测试标签、并行度和输出格式。

清理与重新构建

# 清理构建产物(保留配置)
make clean          # 在build目录内
ninja -t clean      # 在build目录内
cmake --build build --target clean

# 彻底删除构建目录(最干净)
rm -rf build/

编辑缓存(高级)

如果你需要修改配置变量:

  • 传统方式: 手动编辑 Makefileconfig.h
  • CMake方式: 使用 ccmake build(终端图形界面)或 cmake-gui,或者直接修改 build/CMakeCache.txt 后重新运行 cmake -B build

选择哲学:何时用抽象,何时用原生?

看到这里,你可能会有疑问:既然CMake提供了 --build--install 这样的抽象命令,为什么还有人直接去敲 ninjamake

推荐用 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 --buildcmake --install 是工程总监的直接调度热线——无论现场是Make班组还是Ninja班组,一个电话就能统一指挥。而直接敲 makeninja,则是你走到工地现场,直接对班组长喊话。

作为现代C++开发者,建议你养成使用 cmake --buildcmake --install 的习惯,它们是你行走江湖的“普通话”;同时保留阅读和理解原生Make/Ninja命令的能力,因为那是你与遗留项目和老派工程师交流的“方言”。两者兼备,方能游刃有余。

请登录后发表评论

    没有回复内容

正在唤醒异次元光景……