构建配置工具 + 构建执行工具 + 编译器
构建配置工具
检测操作系统:判断当前操作系统的环境,win,linux,mac方便调用工具链。
确定构建的文件:哪些文件哪些目录需要去编译,如果不想编译
确定引入的库:程序需要链接哪些库文件,在这里配置
构建配置工具有:
cmake(跨平台) 生成makefile或者build.ninja文件交给构建执行工具
xmake(跨平台) 自己配置,自己调用编译不需要构建执行工具
Makefile这个不是构建工具,这个只是我们自己编写的让make去执行的脚本。
构建执行工具(构建的后端)
1. 头文件依赖的“精确制导”(这是最要命的)
假设你修改了 global_defines.h 这个头文件。这个头文件被 3000 个 .cpp 文件引用了。
- 如果直接让 CMake 调用 gcc:CMake 不知道这个头文件影响了谁,它只能全部重新编译 3000 个文件。
- 中间层(Ninja)的做法:在第一次完全编译时,GCC 配合
-MMD参数生成了一个.d依赖文件。Ninja 读取这个.d文件,精确记录了“global_defines.h影响了哪 3000 个.o”。当你改了global_defines.h,Ninja 只重新编译这 3000 个,如果只改了某个不常用的.cpp,它可能只编译 1 个。这种“按需编译”的能力,是构建执行工具的核心命脉。
2. 作业调度器(Job Server)—— 避免“打架死锁”
GCC 本身不支持多核编译一个文件,但 Ninja 支持同时启动多个 GCC。 Ninja 内置了图论拓扑排序算法。例如:
- 必须先编译
core_a.cpp和core_b.cpp,才能链接出libcore.a。 - 有了
libcore.a,才能编译main.cpp。 Ninja 会先启动 16 个核心同时编译core_a和core_b,等它们全部完成后,再启动main.cpp的编译。 如果让 CMake 干这个活,CMake 得自己写一套复杂的线程锁和信号量机制(Semaphore),这相当于让设计师去当建筑工地的安全调度员,它根本干不来,强行写会极其臃肿且容易死锁。
3. 构建状态的“永久记忆”(.ninja_log)
Ninja 不仅是看文件时间,它还存储了每次编译命令的哈希值(Hash)。
- 假设你昨天编译时用的 GCC 版本是 10.0,今天你升级到了 11.0。
- 即使
.cpp文件没变,Ninja 检测到编译器的哈希值变了,它会自动重新编译所有文件,因为不同版本的 GCC 生成的机器码可能不一样。 - 如果 CMake 直接调用,它根本不会记录这些元数据,会导致你用了新编译器,程序却还在跑旧编译器的残留缓存,引发极其诡异的崩溃 Bug。
Make (GNU Make) NMake (微软) Ninja MSBuild (VS)
编译器
GCC (gcc/g++)
Clang
MSVC (cl.exe)