简介
Bazel Google开源的,是一款与 Make、Maven 和 Gradle 类似的开源构建和测试工具。 它使用人类可读的高级构建语言。Bazel 支持多种语言的项目,可为多个平台构建输出。Bazel支持任意大小的构建目标,并支持跨多个代码库和大量用户的大型代码库。是Google主推的一种构建工具。
Googl的bazel历史和计划
Timeline
上面这张图主要讲述了 Bazel 和 AOSP 的发展时间线。这里解读一下,基本上对 Bazel 和 AOSP 的构建相关的历史可以有一个大致的了解。
大家可能知道,与世界上 99.99% 的公司不同,Google 将几乎整个公司所有项目(据说不包括 Chrome 和 Android),数十亿行代码都放在一个代码仓库中,这个仓库我们就叫它 monorepo。从上面得图上可见,这个 monorepo 项目的时间大致起始于 2003 年。
而 Android 项目,即我们本文中的 AOSP,在 Google 公司内部代码仓库的建立起始于 2005 年,也正是在这一年,Google 花了五千万美金收购了 Andy Rubin 创建的 Android Inc.。和所有很多其他软件项目一样,早期的 Android 的构建系统也是基于 Make 建立的,那还是在有点遥远的 2006 年,到了 2015 年的时候随着 Android 代码体积的迅速膨胀,Google 发现 Make 已经不堪大用,于是发起了 Soong 项目来替代 Make。当然整个替代过程比我们想象的要复杂的多,有关这些内容,下文还会谈到,也可以参考我们以前发布的另外一篇介绍 Soong 的文章 "AOSP Build 背后涉及的相关知识汇总"。
而那时 Bazel 项目还没有开始,直到 2007 年,才开始有了创建一个公司内部的构建系统(也就是 Blaze)的想法。Blaze 作为 Google 公司内部使用构建工具,目前已经发展成为 Google 的整个 momorepo 生态链中不可或缺,乃至最重要的一环。因为整个 Google 的 monorepo 代码库有几百万个代码文件,几十 TB 的数据,加上大家是代码级依赖,如果按照传统的开发和编译方法,即使只 check out 直接和间接依赖的代码,其数据规模和编译时间也不可接受。而 Blaze 配合 CitC 可以做到开发时只需 check out 需要修改的代码,编译时将所有依赖进行分布式编译,编译上万 C++ 文件也只需几分钟。Blaze还有一个好处就是为全公司提供了整齐划一的编译环境,使得编译器、三方库版本等都不再是问题。到了 2015 年的时候,Google 在 Blaze 的基础上发布了其开源移植版本,并命名为 Bazel。
时间到了 2019 年,Google 又发起了一个将 Android 的 Build 系统迁移到 Bazel 的计划。在此不得不对 Google 工程师的文化翘起我的大拇指,敢于创新,追求卓越已经深深地根植在这家公司的文化精髓中。但由于 Android 系统构建的复杂性,此迁移尚处于早期阶段。从 AOSP 12 开始,会发现 AOSP 的源码仓库下出现了一些和 bazel 有关的内容,这引起了我的好奇心,找了些资料来看了一下,于是总结了这篇文章与大家分享。
before
看一下没有加入 Bazel 支持之前 AOSP 项目的构建系统组成和构建的大致过程。如下图所示:
这张图上内容分成三部分,其中灰色的模块是 AOSP 构建系统的组成部分,我们可以发现包含了 Kati,Soong 和 ninja 好几个东东,还是比较复杂的。黄色的可以认为是整个构建过程中各个构建组件的输出和输入:
-
最左边的内容指得是对 “产品配置信息” 的处理。所谓产品,就是形如我们在执行
lunch aosp_arm-eng
时指定的aosp_arm-eng
就是一个 product。这个 product 定义了我们要构建的系统 image 中具体的组成内容。定义的内容目前是按照 Makefile 的语法写在一系列Product.mk
文件中,然后通过 Kati 系统处理后输出生成一系列的配置信息(图上的 Config Vars),以 Make 变量和环境变量的形式保存下来并作为中间部分的输入。Kati 系统是 Google 为了提高构建速度,自己采用 C++ 开发的用来代替 Make 的一套构建系统,但是后来又给 Soong 替代了,只可惜一直到现在为止 Soong 都没有能够完全替换 Kati。所以就成了目前这个尴尬的局面。 -
中间部分体现的是对 “模块构建动作(action)” 的处理。总体上就是输入构建软件模块的描述,输出是供 ninja 读取的
.ninja
文件。而.ninja
文件中的内容可以理解为就是实际的构建动作(action)描述,简单的打个比方就好像我们自己在命令行中输入的那些形如gcc ... -o a.out xxx.c
这些命令,这些命令驱动了 ninja 完成最终的编译链接动作。在中间部分的处理上存在两套并行的处理系统。一套是 Soong,这是 Google 采用自家的 Go lang 编写的第二代构建系统(如果把 Kati 看成是第一代的话)。其输入的描述信息采用的是遵循 Blueprint 语法的定义,和上面我们介绍的 Bazel 系统的 BUILD 文件中的语法非常类似。这些 Blueprint 格式的描述存放在 AOSP 源码树下各个模块的子目录下的Android.bp
文件中,每一个Android.bp
描述了本模块构建的规则。和 Soong 并行的另外一套系统就是 Kati,它承接了剩下的那些还没有完全转为采用Android.bp
描述的模块,这些没有完成的模块仍然采用 Makefile 语法描述的.mk
文件来描述自身的构建规则,然后作为输入被 Kati 读取,输出供 ninja 使用的.ninja
文件。同时在 Soong 和 Kati 以上的转化过程中还会参考左边产品配置信息构建产生的 Config vars 信息,决定哪些模块需要处理,哪些不需要。 -
最右边指的就是最终的 “实际构建动作的执行”,这部分在目前 AOSP 上在第一代 Kati 系统时已经采用了 ninja 并一直延续到现在。ninja 读入 Soong 和 Kati 生成的
.ninja
描述文件,驱动实际的工具链程序完成最终模块的构架和系统 Image 的组装。
after
在 Google Open Source Live "Bazel day" 中 Google 希望的第三代基于 Bazel 的 AOSP 构建系统完成后整个构建过程是变成如下情况,见下图所示:
也就是说上一节中涉及的 “产品配置信息” 的定义信息(见图中左边下部标注为 “product config” )和涉及 “模块构建动作(action)”(见图中左边上部标注为 “code” )统一采用 BUILD 文件进行描述和定义。由 Bazel 统一处理后,并由 Bazel 负责驱动实现上一节中的第三部分 “实际构建动作的执行”(ninjia 也不再需要了)。整个构建过程全部统一由 Bazel 完成。
而且根据 Bazel 的文档说明,现有 AOSP 中的自动化测试部分的驱动组件也由 Bazel 系统承接,整个替换是全方位的,更详细的针对 Android buid system component 执行 Bazel 替换的对比,可以看 android.googlesource.com
now
虽然理想很丰满,但现实却是骨感的。完整的替换过程相当的复杂,不仅仅在于现有 Android 构建系统的复杂性,也在于 Android 系统本身就是一个巨型复杂系统。
Google 的计划是逐步完成这个过程的替换,就像当初从 Kati 到 Soong 的转变一样。替换的方案还是从上面提到的三个方向入手:
- 对 “产品配置信息” 的处理
- 对 “模块构建动作(action)” 的处理
- 最终的 “实际构建动作的执行”
而且为了保证替换不会影响现有用户的使用体验,包括构建速度和使用习惯。
大致的替换方案如下图所示,我根据我的而理解解释一下,如果有什么不对的地方请大家不吝指出。其中注意下图中增加啊的绿色部分就是为实现 Bazel 替换引入的新组件。
-
在对 “产品配置信息” 的处理上,Google 会引入两个新的工具
convert
和product_config
。convert
负责将含有产品配置信息处理的.mk
文件转换为以 Starlark 语法描述的替代文件,在图上标识为后缀为.bzl
的 文件,我理解就是对应 图(4) 中标识为(product config)的 BUILD 文件。而product_config
则负责将.bzl
文件进一步转化为 Config vars。我猜想分成两步的方式可能是为了在演变成最终的 图 (4) 后,.bzl
文件可以作为包含 product config 信息的 BUILD 文件保留下来。 -
在对 “模块构建动作(action)” 的处理上,Google 会新开发一个叫做 bp2build 的工具,负责将现有系统中的
Android.bp
文件转化为 BUILD 文件。而为了兼容目前在该阶段生成.ninja
文件的需要,Bazel 系统还要改造一下,能够输入 BUILD 文件,输出.ninja
文件(目前的 Bazel,读入 BUILD 就直接驱动构建了,并没有 ninja 的介入)。从图上来看,会存在一段时间,AOSP 构建系统中将同时存在三套处理系统。是不是很复杂 :(。 -
在对最终的 “实际构建动作的执行”上,同样需要改动 Bazel,使其能够像 ninja 一样读入
.ninja
文件,即基于.ninja
文件驱动实际的构建动作。也就是说在过渡阶段,AOSP 的构建系统将同时支持两种构建模式:一种是 legacy mode with ninja;另一种是 the new mode with Bazel。 -
Bazel for AOSP 进展状态
仍然是基于 Google Open Source Live "Bazel day" , 注意视频时间标注为 2021 年 4 月 2 日 。所以不能说是最新的,而是截至本文完成前一年的状态,我看了一下大致和我们移植 AOSP 12 的 aosp-riscv 采用的 tag:
android-12.0.0_r3
差不多的时间。从当时的进展来看,只能说 “任重而道远” 啊!不多说了,大家可以对照上面的介绍估计一下 Google 的进度。最近的状态我们还是要等 13 的代码放出来再看。也希望本文的介绍能对大家了解 Bazel for AOSP 有所帮助。
优劣势
优势
官方说明:
-
高级构建语言。Bazel 使用人类可读的抽象语言,在较高的语义级别描述项目的构建属性。与其他工具不同,Bazel 的运作涉及库、二进制文件、脚本和数据集的概念,可以避免将单个调用编写到编译器和链接器等工具的复杂性。
-
Bazel 既快速又可靠。Bazel 会缓存之前完成的所有工作,并跟踪文件内容和 build 命令的更改。这样,Bazel 就会知道何时需要重新构建应用,并且只会重新构建。为了进一步加快构建速度,您可以将项目设置为以高度并行的方式进行构建。
-
Bazel 是多平台的。Bazel 可在 Linux、macOS 和 Windows 上运行。Bazel 可以从同一项目为多个平台(包括桌面设备、服务器和移动设备)构建二进制文件和可部署软件包。
-
Bazel 支持扩缩。在处理包含 10 万个以上源文件的构建时,Bazel 可保持敏捷性。它可以处理数以万计的多个代码库和用户群。
-
Bazel 可扩展。它支持许多语言,您可以扩展 Bazel,以支持任何其他语言或框架。
其它人总结:
-
很方便地获取第三方依赖;
-
构建多语言的软件系统,One Tool,All Languages;
-
优秀的缓存和依赖计算,编译速度非常优异;
-
DevOps部署云构建,可极大复用宝贵的计算资源;
快(Fast)
Bazel的构建过程很快,它集合了之前构建系统的加速的一些常见做法。包括:
1.增量编译。只重新编译必须的部分,即通过依赖分析,只编译修改过的部分及其影响的路径。
2. 并行编译。将没有依赖的部分进行并行执行,可以通过--jobs来指定并行流的个数,一般可以是你机器CPU的个数。遇到大项目马力全开时,Bazel能把你机器的CPU各个核都吃满。
3. 本地缓存+远程缓存。Bazel将构建过程视为函数式的,只要输入给定,那么输出就是一定的。而不会随着构建环境的不同而改变(当然这需要做一些限制),这样就可以分布式的缓存/复用不同模块,这点对于超大项目的速度提升极为明显。
4. 远程构建。默认情况下,Bazel 会在本地机器上执行构建和测试。通过 Bazel 构建的远程执行,您可以将构建和测试操作分散到多个机器(例如数据中心)。
可伸缩(scalable)
Bazel号称无论什么量级的项目都可以应对,无论是超大型单体项目monorepo、还是超多库的分布式项目multirepo。Bazel还可以很方便的集成CD/CI ,并在云端利用分布式环境进行构建。
它使用沙箱机制进行编译,即将所有编译依赖隔绝在一个沙箱中,比如编译golang项目时,不会依赖你本机的GOPATH,从而做到同样源码、跨环境编译、输出相同,即构建的确定性。
跨语言(multi-language)
如果一个项目不同模块使用不同的语言,利用Bazel可以使用一致的风格来管理项目外部依赖和内部依赖。典型的项目如 Ray。该项目使用C++构建Ray的核心调度组件、通过Python/Java来提供多语言的API,并将上述所有模块用单个repo进行管理。如此组织使其项目整合相当困难,但Bazel在此处理的游刃有余,大家可以去该repo一探究竟。
可扩展(extensible)
Bazel使用的语法是基于Python裁剪而成的一门语言:Startlark。其表达能力强大,往小了说,可以使用户自定义一些rules(类似一般语言中的函数)对构建逻辑进行复用;往大了说,可以支持第三方编写适配新的语言或平台的rules集,比如rules go。 Bazel并不原生支持构建golang工程,但通过引入rules go ,就能以比较一致的风格来管理golang工程。
劣势
-
隔离未遵循程惯例:从策略上看,Bazel从其他遗留库获取代码,这是必须的,否则Bazel自己完蛋了;其他库要想从Bazel获取,用户也必须使用Bazel,用户真的被强绑定了。
-
依赖于JVM。
本文属于如下文章中的子章节
bazel学习系列章节汇总_m0_74043383的博客-CSDN博客