Ninja 手册#
Ninja 是另一种构建系统。它以文件(通常是源代码和输出可执行文件)之间的相互依赖关系为输入,并协调构建它们,速度很快。
Ninja 加入到众多构建系统中。它的独特目标是快速。它源于在 Chromium 浏览器项目上的工作,该项目有超过 30,000 个源文件,而其他构建系统(包括基于自定义非递归 Makefiles 构建的系统)在更改文件后需要十秒钟才能开始构建,而 Ninja 只需要不到一秒钟。
Ninja 哲学#
其他构建系统是高级语言,而 Ninja 旨在成为汇编器。
当构建系统需要做决策时,它们会变慢。在编辑-编译循环中,你希望它尽可能快——你希望构建系统能做最少的必要工作来确定立即需要构建的内容。
Ninja 包含描述任意依赖图所需的最基本功能。它缺乏语法,使得无法表达复杂的决策。
相反,Ninja 旨在与单独的程序一起使用,该程序生成其输入文件。生成器程序(如 autotools
项目中找到的 ./configure
)可以分析系统依赖关系,并尽可能提前做出决策,以便增量构建保持快速。超越 autotools
,即使是构建时决策,如“我应该使用哪些编译器标志?”或“我应该构建调试模式还是发布模式的二进制文件?”也应该属于 .ninja
文件生成器。
Ninja 设计目标#
非常快速(即即时)的增量构建,即使对于非常大的项目。
关于如何构建代码的政策非常少。不同的项目和更高级别的构建系统对代码应该如何构建有不同的看法;例如,构建对象应该与源代码一起存放,还是所有构建输出都应该放入一个单独的目录?是否存在一个“打包”规则,用于构建项目的可分发包?通过尝试允许两者都得到实现,而不是做出选择,来回避这些决策,即使这会导致更多的冗余。
正确获取依赖关系,并且在特别情况下,这些情况很难用 Makefile 正确处理(例如,输出需要隐式依赖用于生成它们的命令行;要构建 C 源代码,你需要使用 gcc 的
-M
标志来处理头文件依赖)。当便利性和速度发生冲突时,优先考虑速度。
一些明确不追求的目标:
手动编写构建文件时的便捷语法。你应该使用其他程序生成你的 ninja 文件。这是能够规避许多策略决策的方式。
内置规则。开箱即用,Ninja 没有针对例如编译 C 代码的规则。
构建时的定制。选项应该属于生成 ninja 文件的程序。
构建时决策能力,如条件语句或搜索路径。决策过程很慢。
简而言之,Ninja 比其他构建系统更快,因为它极其简单。在创建项目的 .ninja
文件时,你必须明确告诉 Ninja 要做什么。
对比参考#
Ninja 在精神和功能上最接近 Make,依赖于文件时间戳之间的简单依赖关系。
但根本上,Make 有很多特性:后缀规则、函数、内置规则,例如在构建源代码时搜索 RCS 文件。Make 的语言设计是为了让人编写。许多项目发现仅用 Make 就足以解决他们的构建问题。
相比之下,Ninja 几乎没有特性;仅包含那些确保构建正确性而将大部分复杂性转移给 ninja 输入文件生成的必要功能。Ninja 本身对大多数项目来说不太可能有用。
这里有一些 Ninja 为 Make 添加的功能。(这类功能通常可以通过更复杂的 Makefile 实现,但它们本身并不是 make 的一部分。)
Ninja 在构建时具有特殊的支持功能,可以自动发现额外的依赖关系,从而轻松地正确处理 C/C++代码的头部依赖。
一个构建边可以有多个输出。
输出会隐式地依赖于生成它们的命令行,这意味着更改例如编译标志会导致输出重新构建。
输出目录总是在运行依赖它们的命令之前隐式创建。
规则可以提供正在运行的命令的简短描述,所以在构建时你可以打印例如
CC foo.o
而不是长命令行。构建总是并行运行,默认基于系统拥有的 CPU 数量。未明确指定的构建依赖会导致构建错误。
命令输出总是被缓冲。这意味着并行运行的命令不会交错它们的输出,当命令失败时,我们可以打印它的失败输出在产生失败的完整命令行旁边。
使用 Ninja 构建你的项目#
Ninja 目前支持类 Unix 系统和 Windows 系统。它在 Linux 系统上经过了最多的测试(并且在该系统上性能最佳),但在 Mac OS X 和 FreeBSD 系统上也能正常运行。
如果你的项目规模较小,Ninja 的速度提升可能难以察觉。(然而,即使是对于小项目,Ninja 有限的语法有时也会迫使使用更简单的构建规则,从而实现更快的构建速度。)另一种说法是,如果你对项目的编辑-编译周期时间已经满意,那么 Ninja 将无法提供帮助。
除了 Ninja 本身,还有许多更用户友好或功能更丰富的构建系统。以下是一些推荐:Ninja 的作者认为 tup 构建系统对 Ninja 的设计有影响,并且认为 redo 的设计相当巧妙。
Ninja 构建系统的优势在于它与更智能的元构建系统协同使用。
运行 Ninja#
运行 ninja
。默认情况下,它会在当前目录中查找名为 build.ninja
的文件,并构建所有过时的目标。你可以通过命令行参数指定要构建哪些目标(文件)。
如果存在规则包含你在命令行中输入的源文件,还有特殊的语法 target^
用于指定该目标作为该规则的第一输出。例如,如果你指定目标为 foo.c^
,那么 foo.o
将会被构建(假设你在构建文件中包含这些目标)。
ninja -h
打印帮助输出。Ninja 的许多标志有意与 Make 的标志相匹配;例如 ninja -C build -j 20
会切换到 build 目录并并行运行 20 个构建命令。(注意 Ninja 默认就会并行运行命令,因此通常你不需要传递 -j
。)