ninja和gn

引言

官方文档:

  1. What is GN?
  2. GN快速入门指南
  3. ninja 教程

1 构建系统简介

在探讨chromium的最新GN构建系统之前,回顾一下软件开发中的构建系统。构建系统的需求是随着软件规模的增大而提出的。如果只是做软件编程训练,通常代码量比较小,编写的源代码只有几个文件。比如你编写了一段代码放入helloworld.c文件中,要编译这段代码,只需要执行以下命令:gcc helloworld.c
当软件规模逐渐增加,这时可能有几十个源代码文件,而且有了模块划分,有的要编译成静态库,有的要编译成动态库,最后链接成可执行代码,这时命令行方式就捉襟见肘,需要一个构建系统。常见的构建系统有GNU Make。需要注意的是,构建系统并不是取代gcc这样的工具链,而是定义编译规则,最终还是会调用工具链编译代码。

当软件规模进一步扩大,特别是有多平台支持需求的时候,编写GNU Makefile将是一件繁琐和乏味的事情,而且极容易出错。这时就出现了生成Makefile的工具,比如cmake、AutoMake等等,这种构建系统称作元构建系统(meta build system)。在Linux上软件仓库的概念还没有普及的时候,通常我们安装软件的步骤是:

  • ./configure
  • make
  • make install

第一步就是调用AutoTool工具,根据系统环境(Linux的版本众多,软件安装情况也不一样),生成GNU Makefile。

2 Chromium中的构建系统

几年前的chromium开源项目采用的是GYP(Generate Your Projects)构建系统,这也是一种元构建系统。软件工程师根据GYP规则编写构建工程文件(通常以gyp, gypi为后缀),GYP工具根据gyp文件生成GNU Makefile。接着chromium项目又整出了Ninja构建系统,但这个Ninja并不是用来取代GYP的,而是取代GNU make的,据谷歌官方的说法是速度有了好几倍的提升。对于我们开发者而言,不需要去深入了解Ninja或GNU Makefile这样构建系统,因为这只是一种中间输出,所以ninja的出现,与我们关系不大,原来怎么写gyp,现在还是怎么写,只是构建命令稍微做了改变。GN文件相当于gyp文件的下一代,和GYP差别不大,但是总体上比原来的GYP文件更清晰。

3 GN构建系统

GN是一种元构建系统,生成Ninja构建文件(Ninja build files),相较GYP而言,具有如下优点:

  1. 可读性更好,更容易编写和维护。
  2. 速度更快,谷歌官方给的数据是20倍的速度提升。
  3. 修改GN文件后,执行ninja构建时会自动更新Ninja构建文件。
  4. 更简单的模块依赖,提供了public_deps, data_deps等,在GYP中,只有一种目标依赖,导致依赖关系错综复杂,容易引入不必要的模块依赖。
  5. 提供了更好的工具查询模块依赖图谱。这在GYP构建系统中是一个噩梦,要查一个目标依赖哪些模块或者一个模块被哪些目标依赖几乎是不可能的。
  6. 更好的调试支持。在GN中,只需要一条print语句就可以解决。

4 快速入门

运行GN
从命令行运行gn,这实际上是depot_tools下的一个脚本,所以需要确保depot_tools路径包含在环境变量$PATH中。

配置一个构建
在GYP中,有两个特定的目录Debug和Release目录,分别用于生成Debug版本和Release版本。在GN中,采用了更灵活的方式,你随便指定一个目录,比如为了测试,定义一个test输出目录,可以采用如下的命令:

gn gen out/test
那要是我要分别构建Debug版本和Release版本怎么办?GN通过传递参数来解决。也就是说,现在光通过输出目录是无法确定到底是Debug版本和Release版本,而要取决于传递的构建参数。

传递构建参数
将上面的命令稍微修改一下,即可设置构建参数:

gn args out/test
可以使用下面的命令列出可用的构建参数和它们的缺省值:

gn args --list out/test

gn+ninja编译示例

  • 在已有的chrome代码目录中,添加自己的目录,ex: tools/gn/tutorial;
  • 在上述目录中新建代码文件hello_world.cc如下:
  • 新建gn文件BUILD.gn,内容如下:
  • 在根目录(.gn文件所在路径)中找到BUILD.gn,新增如下group:
  • 根目录执行:gn gen out/gn_hello ;(生成hello_world.cc编译所需的ninja文件,该文件位于out/gn_hello下,名为build.ninja)
  • 根目录执行编译: ninja -C out/gn_hello hello_world;
  • 编译结果产出在 out/gn_hello下,示例构建的是可执行文件,可以直接执行,如图:

5 GN命令

gn args <out_dir>: Display or configure arguments declared by the build.
gn check <out_dir>: Check header dependencies.
gn clean <out_dir>: Cleans the output directory.
gn desc <out_dir> <target_name> [<what to display>]: Show lots of insightful information about a target or config.
gn format [--dump-tree] [--in-place] [--stdin] BUILD.gn: Format .gn file.
gn gen <out_dir>: Generate ninja files.
gn help: Does what you think.
gn ls <build dir> [<label_pattern>]*: List matching targets.
gn path <out_dir> <target_one> <target_two>: Find paths between two targets.
gn refs <out_dir> (<label_pattern>|<file>)*: Find stuff referencing a target or file.

6 GN语法

参考文档 GN language and operation.

注:想要看懂.gn文件,务必看上面的文档!

7 整体构建流程

  1. 在当前目录中查找.gn 文件,然后沿着目录树向上走,直到找到.gn文件所在目录为止。将此目录设置为“source root”并解析此文件以查找构建配置文件的名称。
  2. 执行构建配置文件(这是默认的工具链)。
  3. 将BUILD.gn文件加载到根目录中。
  4. 递归加载BUILD.gn其他目录以解析所有当前依赖项。如果在指定位置找不到BUILD文件,GN将查看tools/gn/secondary中的相应位置。
  5. 解决目标的依赖关系后,将.ninja文件写入磁盘。
  6. 解决所有目标后,写出根build.ninja文件。

8 Ninja 构建系统

先去看博文《Ninja 构建系统》。

一些好用的ninja命令:

  1. 在浏览器中查看编译Target的编译依赖图(此命令会启动一个web server服务): ninja -t browse --port=8000 --no-browser mytarget
  • 命令行查看Target的编译过程: ninja -t query mytarget
  • 通过工具导出一个.png图片显示编译流程(未实际测试,缺少工具): ninja -t graph mytarget | dot -Tpng -ograph.png

9 使用积累

  1. gn中的is_component_build和编译target类型component的联系:chromium.googlesource.com

编辑于 2020-04-28 15:24