ninja代码解析01
ninja代码不多,主要分成两个部分,libninja + libninja-re2c
有些unix平台支持browse的,还会带上brows.py的代码, 跟libninja-re2c一起到browse.o里去,最后跟libninja一起合成ninja的二进制
理论上unix下的ninja binary里是直接有一个python代码的字符串文件的,它是通过inlie.sh把一个browse.py脚本直接转成了二进制的字符串数组丢给了”build/browse_py.h”里的kBrowsePy
1
ninja -t browse
所以上面的命令其实跑的是一个python代码, 通过pipe + fork + dup2 + execvp + python -
从stdin读输入的py脚本,然后让他跑起来,第一次看到觉得非常的骚
libninja-re2c主要是用来解析ninja文件,比如里面主要就两个部分,第一个是lexer,第二个是depfile_parse
主要的功能逻辑其实都在libninja里,这里先大体看下ninja的设计逻辑语法,对后面理解代码很有帮助
语法
ninja的语法专注在编译,所以其实非常的简单,一开始的设计就不是让人手写的,希望用工具生成。
- edge,一条边,就是一个编译语句,会有输入,输出,规则,变量等
- target,一般是编译的输出,build语句的输出端
- output,跟target差不多
- input,build output的依赖输入
- rule,规则,在edge的build中指定的规则,指定了这个edge是怎么从输入产生输出的
- scope,作用域,变量的作用范围
关键字
- build, 定义edge
- rule,定义规则
- pool,定义pool,这个主要是为了并行度,在rule下指定pool,这个使用这个rule的edge的并行度就依赖这个pool的规则
- default,指定默认的一个或多个target
- include,添加ninja file到当前scope
- subninja,添加一个ninja文件,其scope跟当前的不一样
- phony,比较特殊rule,指定非文件target
变量
全局或者scope的,都直接用var = value的形式,然后用$var的形式引用
当然有内置的变量,in/out/builddir/ninja_required_version等
rule的内置一些变量值得说一下
rule cc
command = gcc -c $in -o $out -MMD -MF $out.d
description = CC $out
depfile = $out.d
deps = gcc
pool = console
- cc就是rule的name
- command就是编译的命令,$in是输入,$out是输出
- description是编译的描述, 代替command没-v的时候的输出
- generator, bool, 这个rule的生成文件不会背默认清理
- in_newline: 换行符分割的输入
- in,空格分隔的输入
- depfile,依赖文件,这个文件会记录这个输出的依赖,如果依赖改变了,输出就会重新编译
- deps,依赖类型,gcc/msvc等
- msvc_deps_prefile,在deps = msvc情况下,指定需要去除的msvc输出前缀。
- restat,在command执行结束后,如果output时间戳不变,则当作未执行
- rspfile_content,rspfile,同时指定,在执行command前,把rspfile_content写入rspfile文件,执行成功后删除。
- pool指定pool,console是预定义的,这玩意的并行度只有1,比较特殊的是可以stdin, stdout, stderr的重定向
增量编译
- 输入输出时间戳
- ninja_deps + ninja_log,维护一个构建数据库了
- depfile,配合编译器依赖文件
depfile主要是依赖文件,它解决了自动发现和跟踪依赖关系的问题,特别是在C/C++项目中的头文件依赖。这玩意要依赖编译器生成的
- gcc = -MMD -MF $out.d
- msvc = /showIncludes /F$out.d
depfile的主要作用是记录一个输出文件的完整依赖关系,尤其是那些在构建开始前可能无法确定的依赖。以C/C++为例,源文件中可能包含许多头文件,这些头文件又可能包含其他头文件,形成复杂的依赖网络。
这种在编译下就不用手动维护依赖关系了,增量构建的效率非常高(直接编译器发现,非常准)
简单的例子
cxx = g++
rule cc
command = ${cxx} -c $in -o $out
description = ${cxx} $out
build output0 output1 | output2 output3: rule_name $
input0 input1 $ # 显示依赖
| input2 input3 $ # 隐式依赖
|| input4 input5 # order-only依赖 可有可无
var0 = str0
var1 = str1
- order-only依赖的意思就是编译顺序,先input4 input5编译完了,再编译output0 output1
- 显示依赖就是显示的依赖,如果input0 input1改变了,output0 output1就会重新编译
- 隐式依赖就是不显示的依赖,如果input2 input3改变了,output0 output1(隐式输出)也会重新编译
- $是转移,没有换行
隐式依赖的特点, 暂时没感觉有什么用
重建触发:如果隐式依赖(如示例中的input2和input3)发生变化,相关的输出文件(output0和output1以及隐式输出output2和output3)将被重新构建。
不作为命令行参数:与显式依赖不同,隐式依赖通常不会直接传递给构建命令。
隐式输出:在示例中,output2和output3是隐式输出。它们会被构建规则生成,但可能不是主要目标。隐式输出同样会根据依赖变化而重新构建