chromium
的编译过程中用到了GYP,GN和Ninja这三个构建工具,GYP
是一个在不同平台构建项目的工具,GN
是GYP
的升级版,Ninja
是一个小型追求速度的构建系统。
GYP
GYP
是Generate Your Projects
的缩写,GYP
的目的是为了支持更大的项目编译在不同的平台,比如Mac
,Windows
,Linux
,它可以生成Xcode工程,Visual Studio工程,Ninja编译文件和Mackefiles。
GYP结构
GYP
的输入是.gyp
和.gypi
文件,.gypi
文件是用于.gyp
文件include使用的。.gyp
文件就是符合特定格式的json
文件。
先来看一个chromium
中缩减的.gyp
文件:
1 | { |
上面指定下面几个属性的值:
variables
: 定义可能被修改或者用于文件其它地方的变量。
includes
: 需要包含进来的有.gypi
后缀的文件。target_defaults
: 默认设置,应用于文件中的所有target。targets
: 指定该文件生成的target列表。conditions
: 指定不同的条件,修改文件中的变量。
下面来看构建一个简单的可执行文件的target:
1 | { |
target_name
: 唯一的来表示工程名称。type
: 文件类型,这里是executable
。msvs_guid
: 用于生成Visual Studio solution
文件的GUID
值。dependencies
: 该target所依赖的其它target。defines
: 宏定义,用于-D
or/D
。include_dirs
: 包含头文件的文件夹,用于-I
or/I
。sources
: 该target的源文件列表。conditions
: 一些条件设置。
再来看一个简单的库的target:
1 | { |
大部分和可执行文件的target是一样的,有些不一样的:
type
: 类型要设置为<(library)
direct_dependent_settings
: 这些设置会应用到直接依赖于这个target的target,也就是在dependencies
中指定了该target的。
export_dependent_settings
: 导出列target的direct_dependent_settings
设置到目标target。
GYP 实例
下面来看几个简单的例子:
1 | { |
生成一个可执行文件foo
,参与编译的源文件有independent.cc
,specific_win.cc
,可以通过指定后缀_linux
,_mac
,_posix
,_win
来指定某个文件在指定的平台才参与编译,比如上面specific_win.cc
只在Windows平台参与编译。
也可以指定conditions
,在不同的平台,加上不同的条件:
1 | { |
上面表示:如果不是linux平台,就不包含源文件linux_specific.cc
。
再来看下依赖的使用:
1 | { |
dependencies
可以指定依赖,该文件的其它target,或者其它文件的某个target。
或者指定编译参数:
1 | { |
target之间的相互依赖:
1 | { |
foo
依赖libbar
。也可以是其它文件的libbar
,那就要写成'../bar/bar.gyp:libbar',
。
而且foo
会加上编译选项-DDEFINE_TO_USE_LIBBAR -Iinclude/libbar
。
还支持Mac OS X bundles
1 | { |
GN
GN(Generate Ninja)是chromium project用来取代GYP的新工具,由于GN是用C++
编写,比起用 python
写的GYP快了很多,GN新的DSL的语法也被认为是比较好写以及维护的。
GN的使用
在source project的根目录新增一个.gn
,内容如下:
1 | buildconfig = "//build/BUILDCONFIG.gn" |
.gn
所在的目录会被GN工具认定是project的source root,.gn
的内容醉基本就是用buildconfig
来指定build config
的位置,其中//build/BUILDCONFIG.gn
用来指定相对于source root的路径。
建立build/NUILDCONFIG.gn
根据前面的设定,需要在build/
下再新增一个BUILDCONFIG.gn
,内容如下:
1 | set_default_toolchain("//build/toolchains:gcc") |
第一行指定要使用的toolchain
,参数给的是一个label,//build/toolchains:gcc
指的是build/toolchains/BUILD.gn
里面定义的gcc
toolchain。第三行则是设定编译C++时会用到的命令行参数。
建立build/toolchains/BUILD.gn
因为GN没有内建的toolchain
规则,toolchain里的各种tool例如 cc
,cxx
,link
等必须自己指定:
1 | toolchain("gcc") { |
注意下之前提到的cflags_cc
是怎么被tool cxx使用的。
建立BUILD.gn
最后在source root新增一个BUILD.gn
,内容如下:
1 | executable("hello") { |
指定hello执行程序由main.cpp编译。
编译
先用gn gen
指定在out/
目录里面生成ninja。
1 | gn gen out |
再执行ninja来build code
1 | ninja -C out |
以后所有修改gn设定的话,也不用重新执行gn,ninja会自动更新设置:
Ninja
Ninja是一个追求速度的构建系统,相比别的构建系统,Ninja的特点是快和简洁,仅保留最少的特性来提高编译速度。Ninja使用build.ninja文件来定义构建规则,和Makefile里的元编程不同,build.ninja几乎是完全静态的,动态生成依赖其他工具,如gyp或者CMake。
build.niinja
build.niinja相当于ninja的makefile,一个简单的build.ninja
文件如下,分为rule
和dependency
两部分。
1 | # part rull |
ninja命令的使用如下:
1 | # compile |
其它细节
phony
: 可以创建其他target的别名。如上面的build all: phony || app.exe
。
default
: 如果没有在命令行中指定target,可以使用default来指定默认的target。
pools
: 为了支持并发作业,Ninja还支持pool的机制,和用-j
并行模式一样。
Ninja构建日志保存在构建过程的根目录或.ninja文件中 builddir
变量对应的目录的.ninja_log文件中。
Make与Ninja对比
Ninja的定位非常清晰,就是达到更快的构建速度。
ninja的设计是对于make的缺陷的考虑,认为make有下面几点造成编译速度过慢:
- 隐式规则,make包含很多默认
- 变量计算,比如编译参与应该如何计算出来
- 依赖对象计算
ninja认为描述文件应该是这样的:
- 依赖必须显式写明(为了方便可以产生依赖描述文件)
- 没有任何变量计算
- 没有默认规则,没有任何默认值
针对这点所以基本上可以认为ninja就是make的最最精简版。
ninja相对于make增加了下面这些功能:
- 如果构建命令发生变化,那么这个构建也会重新执行。
- 所依赖的目录在构建之前都已经创建了,如果不是这样的话,我们执行命令之前都要去生成目录。
- 每条构建规则,除了执行命令之外,还允许有一个描述,真正执行打印这个描述而不是实际执行命令。
- 每条规则的输出都是buffered的,也就是说并行编译,输入内容不会被搅和在一起。
构建工具太多了,我个人觉得make主要偏大众化一点,可以进行各种隐式推导,比较灵活,每一条命令执行都有输出。
而Ninja主要的设计目的是为了像chromium
这种大型项目,能够显著的提高编译速度,一方面它去掉了各种计算和推导,把一些耗时的需要计算的东西去掉了,只留下简单重要的部分,所以如果自己去写build.ninja
文件的话比较繁琐,所以都是依赖于其它构建工具生成的,另一方面它每次输出只输出一个描述,而不是真正的命令执行输出,真正的命令执行再后台运行,只有警告和报错信息才会显示出来,这也提高了它的速度。
Make vs Ninja Performance Comparison这篇文章对Make接Ninja进行测试对比。