构建工具(九):Ninja——构建界的闪电侠

1、make 不够快吗?

用了几年 make 后,你有没有发现一个现象:

make -j8

你开了 8 个并行线程,CPU 跑满了,但 make 还是要在启动时「卡」几秒钟才真正开始编译。

这几秒钟里,make 在干什么?

它在构建依赖图——从 Makefile 里解析谁依赖谁、谁先编译谁后编译。Makefile 是文本格式,make 每次启动都要从头解析一遍。项目越大,这个解析时间越长。

而且 make 对依赖图的推理也偏保守——即使 A 和 B 之间没有先后依赖,make 也常常把它们串行执行,因为没有「全局视野」。

Ninja 就是冲这两个痛点来的:更快的启动、更激进的并行

2、Ninja 的设计哲学

Ninja 的 Slogan 很实在:「致力于让你编译得更快」

它的设计思路很简单:

  • 构建文件不是给人写的,是给程序生成的。Ninja 的 .ninja 文件格式极其精简,几乎没有语法糖
  • 代码量很小,解析快到几乎可以忽略。你用 make 时那几秒钟的「发呆」,在 Ninja 里变成了毫秒级
  • 并行判断更精准,能最大程度利用多核 CPU

Ninja 的定位不是替代 CMake,而是替代 make。你可以把 Ninja 理解为「让构建启动更快、执行更快」的后端引擎。

CMakeLists.txt → cmake -G Ninja → build.ninja → ninja 执行

3、用 CMake 生成 Ninja 构建文件

几乎不需要额外学习。你只需要在 cmake 时换个生成器:

mkdir build && cd build
cmake .. -G Ninja
ninja

cmake .. && make 完全一样的使用方式。CMakeLists.txt 一行都不用改。

如果你还没装 Ninja:

# Ubuntu / Debian
sudo apt install ninja-build

# macOS
brew install ninja

跑起来之后你会立刻感受到区别——Ninja 几乎没有任何停滞,直接就开始编译了。

4、build.ninja 长什么样

虽然你一般不需要直接写 .ninja 文件,但看一眼它的结构有助于理解为什么它这么快:

# 变量
cflags = -Wall -g

# 规则(rule):定义一种「如何编译」
rule cc
  command = gcc $cflags -c $in -o $out
  description = CC $out

# 构建(build):描述「用什么规则、从什么编出什么」
build main.o: cc main.c
build utils.o: cc utils.c

# 链接规则
rule link
  command = gcc $in -o $out
  description = LINK $out

build myapp: link main.o utils.o

# 默认目标
default myapp

规则(rule)定义了编译器命令模板,构建(build)声明了具体的编译任务和依赖关系。格式极度精简,没有任何花哨的东西——所以解析极快。

5、Ninja vs Make:速度对比

以一个中等规模的 C 项目(约 500 个源文件)为例,纯增量编译(什么都不改):

# make
time make -j8
# real 0m3.2s

# ninja
time ninja
# real 0m0.3s

Ninja 的增量编译(no-op build)比 make 快了近 10 倍。这 3 秒的差异,在你频繁改代码-编译-测试的循环里累积起来,一天能省很多时间。

全量编译的速度差距没那么大,但 Ninja 在并行调度上的优势仍然能让它略快一些。如果使用 ccache 等缓存工具,Ninja 的 no-op 速度优势会更明显。

6、Ninja 的局限性

Ninja 虽然快,但不是万能的:

  • 不适合手工编写.ninja 文件极其枯燥。你永远不会想手写它
  • 需要搭配高级构建系统:Ninja 只是一个执行引擎,你仍然需要 CMake(或 GN、Meson)来生成构建文件
  • 没有条件判断、函数等高级特性:这些都是生成器(CMake)的职责,Ninja 只管执行

可以这么理解:make 既是构建描述语言,又是构建执行引擎。Ninja 把这两个角色分开了——你只需要用 CMake 描述构建逻辑,Ninja 负责执行,各司其职。

现代大型 C++ 项目(Chromium、LLVM)几乎都用 Ninja 做构建后端。

7、什么时候用 Ninja

场景 推荐
小项目(< 10 个源文件) make 够了,没必要上 Ninja
中型项目(10-100 个源文件) Ninja 的启动速度有感知,但差异不大
大型项目(100+ 源文件) 强烈推荐 Ninja,省的时间非常可观
持续集成(CI) 必须 Ninja,CI 环境每次都是全新构建
配合 CMake 使用 几乎零成本切换,cmake -G Ninja 一步到位

如果你已经在用 CMake,切到 Ninja 几乎零成本——改一行生成器参数的事,换来更快的构建速度,何乐而不为。


下一篇文章,我们来认识 Google 出品的重量级构建系统——Bazel。它号称能处理成千上万个源文件的 monorepo 项目,背后是怎样的设计哲学?

每天前进一小步,就是一个新的高度!

作者:唐明

出处:/post/build-09-ninja

版权:本站使用"CC BY 4.0"创作共享协议,转载请在文章明显位置注明作者及出处。

关注微信公众号

DevOps持续交付公众号ID:devopscd