AI Summary 146.20563.2 Ninja构建系统摘要 2026-10-10
Ninja构建系统 Ninja是Google 程序员推出的注重速度的构建工具 在Unix/Linux 通过make/makefile 构建编译 Ninja 将编译任务并行组织 提高构建速度

Ninja构建系统

Ninja构建系统

ninja 美 [ˈnɪndʒə],英 ['nɪndʒə] n. 忍者(受过日本传统打斗和轻功训练的人)

官网

ninja-build.org

Github

github.com/ninja-build/ninja

ninja简单介绍

ninja是一种构建工具,用来调用各种工具(代码生成器、编译器、链接器等)来编译大型项目

与cmake/make等工具不同,ninja在设计之初就不是给人写的,只是用于作为其它程序生成的目标。

ninja的设计哲学就是避免任何模糊的内容

比如makefile里面可能经常会用dir/*.cpp来表示源文件

但是这需要查询文件系统才能得到具体内容,是一个很慢而且很不确定的操作

ninja把这些模糊的操作都交给元构建系统(meta-build system,例如cmake),只留下真正需要编译的命令。

因此,本质上我们可以认为ninja就是一条一条地列出了具体要执行的命令,然后执行即可。

ninja会分析这一系列命令之间的依赖关系,并根据这个依赖关系实现两个重要特点:

并行编译

没有依赖的命令可以并行执行。ninja默认使用的并行数为CPU数量,一般不用手动设置并行数,除非想限制ninja使用的CPU数量(例如有其他任务在运行,只让ninja用一半CPU)

增量编译

根据文件的时间戳进行分析,如果某个文件的时间戳发生了改变,则依赖于这个文件的命令以及其他依赖于这个命令的命令都会被重新执行,以此达到增量编译的效果(比如修改了一个文件之后重新编译)

ninja 安装方式

ninja体量很小

三种安装方式

系统级安装

Ubuntu 的

apt-get install ninja-build

MacOS的

brew install ninja

安装后ninja就是一个系统命令,类似ls/cat等命令

用户级安装

conda都可以安装ninja

conda install ninja

通过pip

pip install ninja

自定义安装

在GitHub下载页面下载安装包并解压即可

或者下载源码自己编译

ninja 构建配置文件build.ninja

例子

有两个头文件a1.h和a2.h

分别定义了一个变量a

但是有不同的值

// a1.h

int a = 1;

// a2.h

int a = 2;

源文件a.cpp

里根据宏的不同来包含这两个文件中的某一个

// a.cpp

#include<iostream>

using namespace ::std;

#ifdef USE_A1

#include "a1.h"

#endif

#ifdef USE_A2

#include "a2.h"

#endif

int main()

{

cout << "hello, world from " << a << "!\n";

}

一份

build.ninja文件

包含ninja的构建配置(注意最后的换行,是必要的)

# build.ninja

cxxflags = -DUSE_A1

rule compile

description = compile with $cxxflags

command = g++ -MM -MF out in && g++ in -o $out

depfile = $out.d

build compile_with_default_cxxflags: compile a.cpp

build compile_with_shadow_cxxflags: compile a.cpp

cxxflags = -DUSE_A2

default compile_with_default_cxxflags

配置里包含了内容

注释(comment)

build.ninja里用#开头的行表示注释

变量(variable)

cxxflags = -DUSE_A1

定义了一个变量 cxxflags

其值为-DUSE_A1

变量的值在后面的代码中都可以用

引用方式为 $cxxflags

-DUSE_A1

就是在编译之前定义名为 USE_A1 的宏

规则(rule)

语法为rule 加上规则的名字

定义了一个名为 compile 的规则

规则内有默认变量out

$in 表示输入文件的列表

$out 表示输出文件名

规则的文字描述(description)

用于在编译过程中展示并告诉用户目前在干什么

规则的具体命令(command)

写明具体的编译命令

规则的依赖项记录文件(depfile)

用于增量编译,详见后面的解释

构建(build)

语法为build加上构建的文件名,加上冒号:以及规则名、输入文件列表

本质上

build rule

in和rule

然后运行它的command命令

构建里的局部变量

在build下面的任意变量

在本次构建命令中覆盖全局的变量

build compile_with_shadow_cxxflags 里定义的cxxflags会覆盖本次compile命令用到的cxxflags

默认构建目标(default)。可以存在多行default,最后的默认构建目标就是它们的并集。如果没有任何default,则默认构建全部的build条目。

有了 build.ninja 通过ninja或者ninja compile_with_default_cxxflags来构建默认的代码,得到可执行文件compile_with_default_cxxflags(执行后输出hello, world from 1!);也可以通过ninja compile_with_shadow_cxxflags得到可执行文件compile_with_shadow_cxxflags(执行后输出hello, world from 2!)。我们也可以修改a1.h或者a2.h,观察ninja是否会重新编译。

理解ninja的增量编译

build.ninja 大部分内容简单易懂,唯一难以理解的就是用于增量编译的

depfile = $out.d

$out是ninja的默认变量

表示构建的目标文件名

所以$out.d就是目标文件名加上.d 比如 build compile_with_default_cxxflags

depfile 就是 compile_with_default_cxxflags.d

然后需要理解增量构建的目标:

build compile_with_default_cxxflags 构建条目中

只传入了 a.cpp 这个文件

但是实际上代码里面包含了 a1.h 或 a2.h

如何能够让ninja知道增量构建的时候需要检查a1.h或者a2.h

这就需要编译器的支持了

g++ 的 -MM -MF out.d中

下次ninja就会通过这个文件的内容来判断具体需要检查发生改变的文件

例如,compile_with_default_cxxflags.d文件里,就有以下内容

compile_with_default_cxxflags: a.cpp a1.h

下次ninja再build compile_with_default_cxxflags

的时候,就会检查这个文件,然后查看a.cpp与a1.h的修改时间,

如果任何一个文件被修改了,就重新编译

另外,如果compile_with_default_cxxflags文件不在了,ninja也会重新编译(这个很好理解)

ninja进阶用法

很多ninja.build里存在phony规则

这是一个内置规则,意思是“假冒的”,就是一个不存在规则的规则

rule phony

command = : out

其中冒号

:是unix系统里的一个命令 不管参数是什么,永远正常退出

因此,phony 规则不会做什么 但是会在输入和输出之间建立依赖关系

不带参数的ninja命令会构建文件里的default构建目标

也可以用ninja compile_with_shadow_cxxflags来手动构建某个目标

构建多个目标(比如PyTorch有上千个目标)的时候

ninja会展示一个进度条

进度条展现的内容就是rule下面的description字段的内容

ninja 高级用法一般都在ninja -t下面

例如

ninja -t clean

清理全部生成文件

ninja -t browse

打开一个网页浏览器查看文件之间的依赖图(默认查看的是名为all的依赖图)

通过浏览器界面,方便查看什么文件依赖于什么文件

打开

http://localhost:8000/?libtorch.so

就可以看到完整的libtorch.so对应的依赖

再也不用对着CMakeLists.txt瞎猜了

还可以通过ninja -t graph直接导出依赖图的dot graph文件

但是大型项目的依赖图一般都非常复杂,看起来不方便

ninja -t targets all

得到的是全部的构建目标

用于grep搜索想要的内容;

ninja -C /path/to/dir -f /path/to/file

让ninja切换到/path/to/dir去执行命令

并读取/path/to/file配置文件来执行

-f的默认参数是build.ninja

-C的默认参数为.,

也就是当前路径

一般来说

-f参数用得很少

-C参数用得比较多