Ninja文件的基本语法

Ninja是一种类似GNU make的编译系统。 就像make有Makefile,它也有自己的编译配置文件。 相对来说,Ninja文件没有分支、循环的流程控制,本质上就是纯粹的配置文件,所以要比Makefile简单得多。

本文介绍Ninja文件的独特语法。

基本

概念

概念 (非官方)译名 解释
edge 即build语句,指定目标(输出)、规则与输入,是编译过程拓扑图中的一条边(edge)。
target 目标 编译过程需要产生的目标,由build语句指定。
output 输出 build语句的前半段,是target的另一种称呼。
input 输入 build语句的后半段,用来产生output的文件或目标,另一种称呼是依赖。
rule 规则 通过指定command与一些内置变量,决定如何从输入产生输出。
pool 一组rule或edge,通过指定其depth,可以控制并行上限。
scope 作用域 变量的作用范围,有rule与build语句的块级,也有文件级别。rule也有scope。

关键字

关键字 作用
build 定义一个edge。
rule 定义一个rule。
pool 定义一个pool。
default 指定默认的一个或多个target。
include 添加一个ninja文件到当前scope。
subninja 添加一个ninja文件,其scope与当前文件不同。
phony 一个内置的特殊规则,指定非文件的target。

变量

变量有两种,内置变量与自定义变量。 二者都可以通过var = str的方式定义,通过$var${var}的方式引用。 变量类型只有一种,那就是字符串。

顶层的内置变量有两个:

内置变量 作用
builddir 指定一些文件的输出目录,如.ninja_log.ninja_deps等。
ninja_required_version 指定ninja命令的最低版本。

通过以下代码,可以在build.ninja中指定两个变量,影响编译过程。

builddir = /tmp/
ninja_required_version = 1.7

rule

rule name
    command = echo ${in} > ${out}
    var = str

形如以上的,就是rule代码块。 通常,一个rule就是通过${in}输入的目标列表,生成${out}的输出目标列表。

目标一般都是文件。 例外是,有一个内置的特殊规则phony,可以指定非文件目标。

除了可以自定义变量以外,包括command在内,rule还有以下内置变量:

内置变量 作用
command 定义一个规则所必备的变量,指定实际执行的命令。
description command的说明,会替代command在无-v时打印。
generator 指定后,这条rule生成的文件不会被默认清理。
in 空格分割的input列表。
in_newline 换行符分割的input列表。
out 空格分割的output列表。
depfile 指定一个Makefile文件作为额外的显式依赖。
deps 指定gcc或msvc方式的依赖处理。
msvc_deps_prefix deps=msvc情况下,指定需要去除的msvc输出前缀。
restat 在command执行结束后,如果output时间戳不变,则当作未执行。
rspfile, rspfile_content 同时指定,在执行command前,把rspfile_content写入rspfile文件,执行成功后删除。

build edge

build foo: phony bar
    var = str

形如以上的,就是build代码块,也是编译过程中的一个edge。 其中,foo就是output,bar就是input,:后面第一个位置的phony就是rule,var就是自定义变量。 在build块中,也可以对rule块的变量进行扩展(复写)。

最复杂的build代码块形式,莫过于:

build output0 output1 | output2 output3: rule_name $
        input0 input1 $
        | input2 input3 $
        || input4 input5
    var0 = str0
    var1 = str1

其中,行末的$是转义字符,并未真正换行; output0output1是显示(explicit)输出,会出现在${out}列表中; 出现在|后面的output2output3是隐式(implicit)输出,不会出现在${out}列表中; rule_name是规则名称; input0input1是显示依赖(输入),会出现在${in}列表中; 出现在|后面的input2input3是隐式依赖,不会出现在${in}列表中; 出现在||后面的input4input5是隐式order-only依赖,不会出现在${in}列表中。

所谓order-only依赖,如字面意思,就是指仅仅是顺序需要的依赖。 在首次编译时,和其它依赖的表现相同; 再次编译时,如果input4input5有缺失,Ninja会更新它们,但不会执行这条edge,更新output0output1。 order-only的依赖,不是真的依赖,而是可有可无、只是需要在当前edge之前执行而已。

pool

pool的意义,在于限制一些非常消耗硬件资源的edge同时执行。

pool example
    depth = 2

rule echo_var
    command = echo ${var} >> ${out}
    pool = example

build a: echo_var
    var = a

build b: echo_var
    var = b

build c: echo_var
    var = c

以上代码,通过pool = example,在rule或build代码块中指定对应的edge所属的pool为example。 由于exampledepth = 2,所以a、b、c三个target最多只有2个可以同时生成。

目前,Ninja只有一个内置的pool,名为console。 这个pool的depth等于1,只能同时执行1个edge。 它的特点是,可以直接访问stdin、stdout、stderr三个特殊stream。

总结

相对Makefile来说,Ninja的内容非常的精简。 本文虽然简短,但绝大部分内容都已覆盖。 Ninja的设计哲学,就是用最精简的配置,执行复杂的编译过程,以此提高增量编译效率和准确度。

虽然介绍了语法,但手写build.ninja仍然是不推荐的。 可以在《List of generators producing ninja build files》中选取合适的工具,来生成Ninja文件。

参考


相关笔记