5、CMake生成器与构建:Unix Makefiles、Ninja、Visual Studio生成器、cmake --build命令
说到CMake,很多人第一反应就是“生成Makefile的工具”。嗯,这个说法其实只对了一半。CMake真正的核心能力,是作为一个跨平台的构建系统生成器。它不直接编译代码,而是根据你的项目描述,生成不同平台、不同构建工具能识别的“脚本”。
我个人习惯把CMake比作一个翻译官。你跟它说中文(CMakeLists.txt),它帮你翻译成英语(Makefile)、日语(Ninja build文件)或者法语(Visual Studio解决方案)。今天我们就来聊聊这位翻译官背后的几个“方言”——也就是生成器。
一句话总结:生成器决定了CMake输出什么类型的构建文件。选对了生成器,你的编译速度、调试体验、跨平台能力都会有质的提升。
5.1 什么是生成器?
生成器(Generator)是CMake的一个核心概念。它负责把CMakeLists.txt中描述的构建目标、依赖关系、编译选项等,转换成特定构建工具能理解的格式。
你可以通过 cmake --help 命令查看当前系统支持哪些生成器。在我的机器上,输出大概是这样的:
* Unix Makefiles = 生成标准的 UNIX 风格 Makefile
Ninja = 生成 Ninja 构建系统的 build.ninja 文件
Visual Studio 17 2022 = 生成 Visual Studio 2022 项目文件
Visual Studio 16 2019 = 生成 Visual Studio 2019 项目文件
Xcode = 生成 Xcode 项目文件
带星号的那个是默认生成器。如果你不指定,CMake就会用它。
小技巧:在Linux/macOS上默认是Unix Makefiles,在Windows上默认是Visual Studio(如果安装了的话)。但你可以随时通过 -G 参数切换。
5.2 三大主流生成器详解
5.2.1 Unix Makefiles
这是最经典、最通用的生成器。几乎所有的Linux发行版和macOS都自带make工具。它的工作原理很简单:CMake生成一个Makefile,然后你运行 make 来编译。
优点很明显:零依赖。你不需要额外安装任何构建工具。缺点嘛……说实话,编译速度一般。尤其是大型项目,Makefile的并行度不够理想。我曾经在一个百万行级别的C++项目中,用Makefile编译一次要等20分钟。后来切到Ninja,直接缩短到8分钟。
# 使用 Unix Makefiles 生成器
cmake -G "Unix Makefiles" -B build
cmake --build build # 等价于 cd build && make
注意:Unix Makefiles 在Windows上也能用(如果你装了MinGW或MSYS2),但我不建议这么做。Windows上还是老老实实用Visual Studio生成器吧。
5.2.2 Ninja
Ninja是我个人最偏爱的生成器。它的设计哲学就是“快”。Ninja的构建文件(build.ninja)比Makefile简洁得多,而且天生支持高并发编译。
为什么Ninja快?说白了,它没有Makefile那些复杂的隐式规则和递归调用。Ninja的构建规则是显式的、扁平的。你想想看,一个大型项目可能有几千个源文件,Makefile需要处理各种隐式依赖推导,而Ninja直接告诉你“这个.o文件依赖这个.cpp和这个.h”,一步到位。
# 使用 Ninja 生成器
cmake -G Ninja -B build
cmake --build build # 等价于 cd build && ninja
我在项目中遇到过一个问题:用Makefile编译时,有时候改了头文件,make检测不到变化,导致编译结果不一致。切到Ninja后,这种问题再也没出现过。Ninja的依赖追踪非常精准。
我的建议:如果你在Linux/macOS上做开发,优先考虑Ninja。只需要 apt install ninja-build 或 brew install ninja 就能装上。这点代价换来编译速度翻倍,绝对值。
5.2.3 Visual Studio 生成器
Windows上的王者。Visual Studio生成器会生成 .sln 解决方案文件和一堆 .vcxproj 项目文件。你可以在Visual Studio IDE中打开、编辑、调试,体验非常丝滑。
Visual Studio生成器有几个变种:
| 生成器名称 | 对应Visual Studio版本 | 架构 |
|---|---|---|
| Visual Studio 17 2022 | VS 2022 | x86/x64/ARM |
| Visual Studio 16 2019 | VS 2019 | x86/x64/ARM |
| Visual Studio 15 2017 | VS 2017 | x86/x64/ARM |
你可以通过 -A 参数指定目标架构:
# 生成64位项目
cmake -G "Visual Studio 17 2022" -A x64 -B build
# 生成ARM架构项目(比如给Surface Pro X用)
cmake -G "Visual Studio 17 2022" -A ARM64 -B build
避坑指南:我曾经在Windows上直接用 cmake -G "Visual Studio 17 2022" 不指定架构,结果默认生成了32位项目。链接第三方64位库时各种报错。后来养成了习惯:在Windows上永远显式指定 -A 参数。
5.3 cmake --build:统一构建命令
你有没有觉得,每次切换生成器,构建命令都不一样?用Makefile时敲 make,用Ninja时敲 ninja,用VS时还得打开IDE点按钮。太麻烦了。
CMake提供了一个统一的构建命令:cmake --build。它会自动识别你用的是哪个生成器,然后调用对应的构建工具。
# 不管用哪个生成器,构建命令都一样
cmake -G Ninja -B build
cmake --build build # 自动调用 ninja
cmake -G "Unix Makefiles" -B build
cmake --build build # 自动调用 make
cmake -G "Visual Studio 17 2022" -A x64 -B build
cmake --build build # 自动调用 MSBuild
这个命令还支持一些常用参数:
--target:指定构建哪个目标(比如只编译某个库)--config:指定构建配置(Debug/Release)-j:指定并行编译的线程数
# 只编译 mylib 这个库,Release模式,8线程并行
cmake --build build --target mylib --config Release -j 8
核心习惯:我建议你在所有项目中使用 cmake --build 而不是直接调用构建工具。这样你的构建脚本可以做到“一次编写,到处运行”。CI/CD流水线里尤其好用。
5.4 如何选择生成器?
这个问题没有标准答案。我根据自己的经验,给你一个参考:
- Linux/macOS 日常开发:Ninja。速度快,依赖追踪准。
- Linux/macOS 服务器/CI:Unix Makefiles。因为服务器上不一定装了Ninja,Makefile是保底方案。
- Windows 开发:Visual Studio生成器。IDE调试体验无可替代。
- Windows CI:Ninja(如果装了)或Visual Studio生成器。Ninja在Windows上同样很快。
- 跨平台项目:在CMakeLists.txt中不要写死生成器,让用户自己选择。你只需要确保代码能兼容所有生成器。
5.5 知识体系图
下面这张图展示了生成器在整个CMake构建流程中的位置:
从图中可以看到,CMakeLists.txt经过 cmake -G 命令,被翻译成不同生成器的构建文件。最后统一通过 cmake --build 触发编译。这个流程清晰、统一,也是CMake能成为跨平台构建标准的原因。
5.6 实战建议
最后,给你几个我在实际项目中总结出来的建议:
- 不要手动修改生成的构建文件。Makefile、build.ninja、.vcxproj 都是自动生成的。你改了,下次CMake重新生成时会被覆盖。所有修改都应该在CMakeLists.txt中完成。
- 在CI中固定生成器版本。比如在Linux CI上明确指定
-G Ninja,避免因为默认生成器不同导致构建行为不一致。 - 善用
cmake --build的并行参数。在CI机器上,可以用-j $(nproc)自动获取CPU核心数,最大化编译速度。 - Windows上注意架构匹配。如果你在64位系统上开发,一定要用
-A x64。否则链接第三方64位库时会遇到莫名其妙的错误。
个人经验:我曾经维护过一个项目,开发者在Windows上用Ninja生成器,结果调试时无法附加进程(因为Ninja生成的PDB文件路径有问题)。后来统一改用Visual Studio生成器,调试体验才恢复正常。所以,开发环境用VS生成器,CI环境用Ninja,这个组合我用了好几年,一直很稳。
好了,关于生成器的内容就聊到这里。记住一句话:生成器是CMake的“方言”,选对方言,沟通才顺畅。