chromium中的GN构建系统

阅读最新的chromium源码,发现项目的构建系统已经从GYP全面切换到GN了。在软件开发中,经常有人忠告:不要重复造轮子。但谷歌可不管这个,造的轮子一个接一个,谁叫人家牛呢?chromiumi项目为啥要折腾构建系统呢?因为谷歌chrome浏览器追求一个字:快。不仅浏览器的速度要快,构建系统也要追求快。

构建系统简介

在探讨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。

Chromium中的构建系统

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

最近再看chromium的源码,发现里面熟悉的GYP文件都不见了,取而代之的是GN文件(以gn和gni为文件名后缀),瞬时感觉一夜回到解放前。然而稍微研究了一个GN文件,还是那些熟悉的模块、依赖、条件等等元素,和GYP差别不大,而且总体上比原来的GYP文件更清晰。

GN构建系统

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

  • 可读性更好,更容易编写和维护。
  • 速度更快,谷歌官方给的数据是20倍的速度提升。
  • 修改GN文件后,执行ninja构建时会自动更新Ninja构建文件。以前用GYP的时候就有过修改了GYP,而忘记使用gyp命令重新生成Ninja构建文件的尴尬。
  • 更简单的模块依赖,提供了public_deps, data_deps等,在GYP中,只有一种目标依赖,导致依赖关系错综复杂,容易引入不必要的模块依赖。
  • 提供了更好的工具查询模块依赖图谱。这在GYP构建系统中是一个噩梦,要查一个目标依赖哪些模块或者一个模块被哪些目标依赖几乎是不可能的。
  • 更好的调试支持。为了打印GYP中的变量值,我以前还专门写过一篇博文<<如何打印gyp构建系统中的变量值>>,在GN中,只需要一条print语句就可以解决。

快速入门

运行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

我在chromium源码下运行,参数如此之多,要翻好几屏,所以不需要记住所有的参数,只需知道几个比较常用的参数:

is_component_build = true
is_debug = false

前面一个参数决定是否分动态库build,现在chrome for android包含了build出了几十个so,就是这么来的,好处是节约修改代码后的构建时间。后面一个参数决定是build Debug版本还是Release版本。

从编写代码到build

  1. 编写代码,比如代码文件为test/hello_world.cc
  2. 编写GN文件,比如放在和上面代码同一目录下。

    executable("hello_world") {
    sources = [
     "hello_world.cc",
    ]
    }
  3. 打开源码根目录下的BUILD.gn,加入对上述目标的依赖:

    group("root") {
    deps = [
     ...
     "//url",
     "//test:hello_world",
    ]
    }

    这里//代表源码根目录。

  4. 构建

    gn gen out/Default
    ninja -C out/Default hello_world
    out/Default/hello_world

调试

  1. 打印

    static_library("hello") {
    ...
    print(configs)
    }

    可以打印这个目标的配置信息。

  2. 查看目标信息

    gn desc out/Default //test:hello_world

    这会列出很多详细的信息,包括配置参数、目标依赖,通过更复杂的命令参数,你还可以查看某个宏定义是在哪个目标定义,目标依赖的树结构,比如:

    gn desc out/Default //base:base_i18n deps --tree

    更多参数的说明可以使用gn help desc查看。

总结

因为还没有编写复杂的GN文件,所以对GN的优缺点还体会不深,不过对于GYP的几个深有感受的痛点:
- 多层嵌套,导致GYP文件难以阅读和修改,想想chromium下的build/common.gypi这个文件有多恐怖
- 打印支持
- 对于复杂的依赖缺少有效的手段去定位和排查

这在GN上得到了完美的解决,就冲这这一点,也要为谷歌点赞。虽然是重复发明轮子,但轮子比原来的好用啊!

chromium中的GN构建系统_第1张图片

参考文档

  1. What is GN?
  2. GN Quick Start guide

你可能感兴趣的:(1.Chromium研究)