宝塔服务器面板,一键全能部署及管理,送你10850元礼包,点我领取

大家好,关于团队网站安卓源码分享很多朋友都还不太明白,不过没关系,因为今天小编就来为大家分享关于团队 平台的知识点,相信应该可以解决大家的一些困惑和问题,如果碰巧可以解决您的问题,还望关注下本站哦,希望对各位有所帮助!

本文由AppInfra-Build团队出品。作者:兰军健、丁德高、谢然

本文是从构建系统对比的角度出发,深度的对比了Gradle与Bazel两大构建系统的设计理念及优劣势,并结合Android构建的表现进行了详细的分析

背景

目前字节Android的一些超大型项目均从原来的多仓二进制的研发模式切换到了Monorepo全源码,并在研发效能方面取得了较大的收益。关于Monorepo全源码模式下的一些技术挑战及解决方案后续会有文章单独阐述。在全源码改造的过程中,作为build方向的基建团队,我们也不断克服了很多Gradle生态的问题与挑战,对构建系统方面有了更进一步的理解。

提到超大型仓库的Monorepo,就不得不提到Bazel构建系统。Google内部使用Bazel作为超大仓(TB级别)的构建系统,足以证明Bazel构建系统优异的性能表现。目前字节内服务端、iOS端均采用Bazel作为构建系统来演进Monorepo,业界关于Bazel的原理文章也很多,但大部分都围绕在工程改造方面或者在单一阐述Bazel的设计。这里就有几个问题:

单一的阐述一个构建系统,没有横向的比较是不太客观的,也不太容易理解核心的理念及技术很多人有这个疑问:既然Bazel这么出色,为什么在JVM领域没有形成大的生态,为什么Android不使用Bazel

本篇文章我们将尽量通俗易懂的阐述下Bazel与Gradle的设计异同及其在Android构建方面的表现,希望能解决这些疑惑。

构建系统核心概念

什么是构建系统

官方回答:“用来从源代码生成用户可以使用的目标(targets)的自动化工具。目标可以包括库、可执行文件、或者生成的脚本”。

叫的上名字的构建系统很多,如CMake、Maven、Gradle、Bazel等。

其中Bazel和Gradle的生态尤为庞大。如果给各自贴个标签,那么:

Bazel:应用于Google大型项目,倾向于支持多语言,以性能著称。Gradle:JVM体系最好用的构建系统之一,广泛应用于Java和Android项目,同样支持多语言。

一个成熟的构建系统的核心要素大概包括以下几个维度:

核心调度机制:构建系统的“发动机”,调度能力的设计一定程度上决定了构建系统的上限。

构建规则DSL:任何构建系统都有一套自己的DSL供开发者使用,用来定义要构建规则和目标。Gradle的DSL语言是Groovy和kotlin,特点是更灵活强大,但灵活性也给其生态带来了巨大的副作用;而Bazel使用Starlark作为DSL,可以理解为一个阉割版的python,虽然限制了DSL部分能力,反而实现了系统的可控性。这一点整体上来讲Bazel显得更有远见一些。

缓存系统:缓存系统设计的好坏和核心调度机制同等重要,决定了构建系统的上限,一个性能好的构建系统一定在缓存方面有着优雅的设计。二者在缓存方面均下足了功夫,从缓存的角度来看无法评价二者的优劣。

依赖管理系统:一个复杂的巨型工程可能具有很复杂的依赖关系,因此一个易用灵活高性能的依赖管理系统至关重要。下文中会对二者在这一方面进行对比。

扩展能力:Bazel和Gradle都声称支持多语言,扩展性强,最直观的体现就是插件系统。Gradle可以通过自定义插件实现任意能力的扩展,比如Android构建的过程就是Google开发了一套AndroidGradlePlugin运行在Gradle上完成的;Bazel对应于的体系则被称为rules,Bazel也是通过提供rules_android来完成Android构建。从扩展性的角度来看,二者均非常出色。

rules_android:https://github.com/bazelbuild/rules_android

从上面的几个要素分析看,Bazel与Gradle均具备了大型构建系统的核心要素,接下来我们逐步深入,从更细的维度来进行下对比,在进入之前先来看看两者在构建流程方面的差异,方便大家了解下文中的一些概念。

构建流程

当我们执行一条构建命令时,构建系统基本上都会经历三个阶段来完成构建。

Load阶段:进行初始化构建系统,加载配置文件,找到入口TargetorTask。针对Bazel而言即为加载WORKSPACE和BUILD文件,找到需要执行的Target;针对Gradle即为加载settings.gradle及build.gradle文件,找到目标Task。

Analysis阶段:解析脚本及规则进行依赖解析,完成依赖的下载及ActionorTask的注册,形成待执行的DAG(有向无环图)。Bazel的执行单元称为Action,Gradle的执行单元称为Task,为了方便描述,下文统称为Task。注意这里表述的不够严谨,对于Bazel来说,这个DAG更复杂一些,为了方便理解,暂且这么认为。

Execution阶段:按照构建系统的调度策略执行对应的Task,得到构建结果。这个阶段也是Bazel和Gradle差异最大的地方。

深度对比

终于来到了本篇文章的核心部分,我们接下来从理念并发能力增量机制配置阶段差异其他核心能力等五个方面来进行一下阐述。为了避免枯燥,尽量做到通俗易懂。

理念

Gradle在2007年就进行了开源,当初的对标目标是Maven,众所周知,Maven服务于Java生态,早年的Gradle性能也是非常糟糕,这个印象可能至今都没有很好的扭转过来。

Bazel最早诞生于Google内部,为了应对Google内部超大型多语言仓库的瓶颈与挑战而开发,于2015年开源。

二者的设计理念和自我定位有着较大区别:

理念解释BazelArtifact-basedbuildsystem产物驱动型只声明需要什么,依赖什么产物;Bazel会自动依赖产物来关联相关的action,这些action是否并发执行也仅取决于产物的依赖情况。举个例子:定义ActionX和ActionY,ActionXinput中依赖了一个A.jar,ActionY会产出A.jar产物,则ActionX会自动隐式依赖output中含有A.jar的ActionY。GradleTask-basedbuildsystem任务驱动型Gradle构建系统的视角为Task,Task内部可以定义任意的逻辑与能力;比如TaskX有个input为A.jar,A.jar由TaskY产生,需要显示的声明TaskXdependsonTaskY。

从表格中可以感受到Bazel的“产物驱动”的模式自动化程度貌似更高一些。其通过“产物依赖”建立Action自动隐式依赖的形式能带来诸多好处:

构建系统层面有更多的信息与控制权;在构建系统层面非常容易拿到A.jar相关的Action信息,比如A.jar变了,应该执行什么,不应该执行什么的粒度就可以做的更细。而task驱动型相对来讲就会有些吃力。理念的差异其实一定程度也会影响到构建系统的并发能力;产物驱动型的构建系统的并发能力一定程度上会更高。

这里理解起来可能还是有点抽象,下面的章节中会不断地在示例中阐述这种里面层面的差异。接下来先来看看并发能力方面的差异。

并发能力

并发能力是衡量一个构建系统的核心指标,我们通过一个最小化的场景来对比一下。

如图所示,假设有三个任务T1、T2和T3。T2和T3从直观感受上并没有依赖关系,一般情况下完全可以并发执行。为什么说是一般情况下呢?如果T2、T3操作的了同一个文件,那此时并发就可能出现问题。

对于Bazel而言应对很轻松,产物驱动型的理念和设计能感知到T2,T3是否操作了同一个文件,从而决定是否完全并发。

对于Gradle而言就比较麻烦了。在Gradle任务驱动型机制下,对文件修改的感知能力不如Bazel,当出现T2,T3都属于同一个module时,无法准确判断T2,T3是否存在overlap,因为它的机制下,同一个module下的所有Task执行期间会持有一种相同的锁来保证正确性。那是不是只能串行呢?其实也不然。Gradle提供了一种称为WorkerAPI的机制来弥补这个缺陷,基本思想为既然无法整体判断,那就进行拆分,保证资源共享的部分依然串行,将耗时的大头部分扔到后台线程池去执行,执行完通知进行资源释放即可,从而间接的实现了此种场景的并发。感兴趣的同学可以看之前我们发表过的Gradle调度机制的文章。

WorkerAPI:https://docs.gradle.org/current/userguide/worker_api.html

Gradle调度机制:组件发布效率提升15倍是怎么做到的——基于Gradle调度机制深度研究与优化

总结

Bazel的并发性能更好,而Gradle也并没有其他Task驱动型的构建系统的那么不堪,通过一种不太优雅的机制弥补了这一缺陷,但是这种机制引入了大量的wait-notifyAll的唤醒行为。单纯从并发性能上看,Bazel更加强悍,但二者的差距并没有外界认为的那么大。

大致了解了并发能力的差异后,我们以一个增量编译的场景为契机来了解下二者在Execution阶段的差异。

快速增量的秘密

Bazel有一个极其震撼的特性及效果:在多个大型的Bazel工程中,当无任何代码修改的情况下,Bazel能够在1s内执行完毕。这个对于研究Gradle的人来讲太震撼了,Gradle在未执行任何修改的情况下肯定是做不到这个效果的。接下来我们看看两个构建系统是如何实现快速增量编译的,为了方便阐述,我们就以改动少量代码的场景来进行说明。

假设已经进行了一次全量编译,也就意味着有了一张全量的DAG,此时改动少量代码,假设改动影响到的是T5,毫无疑问,T5是肯定会执行的。

上文中讲到,Gradle是一个高度灵活的构建系统,此外其还是一个单进程的构建系统,Task间没有严格的进程级别的隔离机制,导致Task间可能存在访问关系,所以无法提前判断到底应该执行哪些Task。

因此Gradle会在执行阶段进行全量判断,也就是会遍历每个Task是否需要执行。如图中的T2、T4及T6的部分,直观的感受是大概率和T5并没有任何关系,但依然需要做一次判断。

所以Gradle要想提升增量构建的性能,必然需要在判断是否需要执行的逻辑上做足功夫,确保每个节点的判断迅速完成,否则无法应对大型工程的构建。Gradle的应对法宝为:

Daemon进程虚拟文件系统(VirtualFileSystem,后文简称vfs)远程缓存

首先采用Daemon进程来进行全局的内存缓存,然后对于每个task的输入输出变更应用了vfs监控来快速进行检测,同时对于每个Task还用远程缓存进行兜底,来保证快速并发检测多个节点。绝大部分的节点的检测均控制在10ms左右完成,性能方面已经比较出色。以抖音Android为例,一次构建大概需要14000个Task,全部判断完成大概需要10s左右,这个时间针对于增量构建所需的任务来讲,并不算长。

再来看看Bazel的视角如何做到快速的增量编译。与Gradle相比,相同点是均采用Daemon进程+vfs进行快速的变更检测,不同点在于Bazel的DAG设计。依然是产物驱动型的理念带来的优势,使得Bazel建立的DAG基本更细的颗粒度。几乎可以认为任何关系均可以从该DAG中获取。

对示例中的流程来讲:

我们修改了一部分代码,全局的vfs能够快速感知变更的文件A.java,假设修改的是A.java文件。利用全局的DAG索引,通过T5=graphNodeMap.get(&34;),直接获取该文件从属于节点T5依然利用DAG索引,通过递归调用getReverseDeps依次找到依赖T5的T3和依赖T3的T1,并将它们标记为脏节点。其余的T2、T4和T6则被判定为无需执行。实际标记的过程远比描述的复杂,这里只进行理念的阐述。调度器将只会执行剪枝后的DAG,即T5-T3-T1,规模迅速缩小

总结

Bazel通过全局的DAG索引保证增量过程中总是执行“最小DAG”,执行规模不随工程规模的扩大而线性增长,这个特性也很大程度上决定了Bazel能够应对超大仓的挑战;

而Gradle可能会随着工程规模增量效率出现劣化,但其增量性能依然比较出色,面对抖音Android的Monorepo,单次构建超过14000个Task,依然可以在5-10s内完成所有增量Task判断,也算表现不俗。

无论是并发或者是Exectution阶段的增量效果,虽然Bazel更好,但实际上并没有很大的区别。不过接下来我们要介绍的配置阶段可能差别就比较大了。

配置阶段的巨大差异

如果单纯从性能角度对比,analysis阶段或者叫configuration阶段二者的性能差异是最明显的。Bazel几乎处于吊打Gradle的地步。注意观察图中绿色框的部分,这个是设计上的核心差距。

从上图可以得知二者都有DAG来指导编译流程的执行,但在生命周期和效率上有较大区别。

Gradle的DAG只为执行阶段服务,configuration阶段仅仅是为了生成待执行的DAG;Bazel的DAG是真正的全生命周期,覆盖了Analysis阶段。这就意味着上一节提到的”剪枝的DAG“的优势复用到了Analysis阶段,换句话说全生命周期的各个阶段均能享受到同样的缓存能力、增量能力。

Gradle的configuration阶段可以认为是整个Gradle构建系统最不尽如人意的设计。在业界很多人不敢做Android全源码很大程度上也是因为担心configuration过程时间就炸了。这里应该算是Gradle的一个设计缺陷,在早期过于考虑灵活性,动态的groovy语言加上过于开放的API,导致Configuration阶段难以做高质量的缓存及更高程度的并发。后来官方意识到这个问题后,采用了一种及其激进的缓存方案,称为Configurationcache。原理很暴力,“既然大部分场景不涉及配置改动,直接将整个DAG进行序列化缓存,如果不改动配置,就直接反序列化回来”。这里面涉及两个问题:

改了配置的场景,依然龟速没改配置的场景,跳过了过多步骤,导致改造成本非常高,对于很多已存在的大型项目来讲均有较大的挑战

虽然官方在非常努力的演进这个feature,已经横跨N个版本,但无论怎么样都像是一种“亡羊补牢”的打补丁的方案。

再来看看Bazel,Bazel在设计之初进行了充分的思考,在性能方面做足了功课。在Bazel的世界里,一切都可以简单的抽象成一个函数模型:输入x通过一个函数得到y,并且要保存下来所有的依赖关系,比如可以轻松的通过x查询到y的状态。基于这个模型设计好DAG和缓存,就能实现全生命周期的覆盖,就无所谓区分Analysis和Execution阶段了,二者均可以享受增量缓存和“DAG剪枝”的效果了。

Bazel先进的设计理念将全生命周期一体化抽象,在Analysis阶段确实要比Gradle出色太多,或者说在这个层面二者就不是一个level上的选手。

其他核心能力

分布式编译

对于分布式编译能力,两套系统出现了分歧,分布式编译一直是Bazel的一个核心“卖点”,而Gradle没有分布式能力且官方未来也没计划跟进。

由于Java编译本身就比较轻量,加上没有头文件加持,很难做到单文件粒度的编译,意味着分布式编译不见得就能带来很大的收益。这么看来分布式编译能力在JVM体系下貌似并不算刚需,而Android编译瓶颈在于长尾效应非常严重(如下图),这也是为什么在Google内部构建一个AndroidRelease包依然很慢的核心原因,这个慢当前来看并不取决于构建系统自身的性能。

分布式编译固然能吸引眼球,高大上,但不一定能解决问题,Gradle没有分布式编译能力好像并没有影响什么。但其对C系编译的作用还是很大的。

依赖管理能力

Gradle几乎全部继承了Maven在依赖管理方面的优势并进行了极致的优化,这对于复杂项目和超大型项目而言至关重要。而Bazel在依赖管理能力就显得有点不入流了,这和背景有一定的关系,因为Bazel诞生于google的超大仓,不需要远程依赖,甚至不需要版本决议。开源后发现玩不转,补了个rules_jvm_external来管理外部依赖,感受上大概率抄袭了Gradle的一些东西,但能力依然很弱。最近在做的bzlmod可能会好一些,这块显然是Bazel的短板,被Gradle甩了几条街。

值得说明的一点是:依赖解析的过程很大程度上会影响Analysis阶段的耗时,这也是为什么看似Bazel的设计更优秀,但实际上我们测试的结果显示,在全量编译及修改代码的场景,Bazel在Analysis阶段也没有想象中的那么快,并没有完全发挥出设计优势。

以上是针对Bazel与Gradle构建系统层面的对比,整体下来确实是Bazel的设计更加优秀,但为什么Bazel在Android方面或者Java领域规模很小呢,接下来我们再从Android构建的角度来简单的描述下。

Android构建

详细对比完Gradle和Bazel在多个维度的差异,这章节会围绕Android构建,从构建性能生态两方面来陈述下。

构建性能

首先我们需要明确的是一个构建任务是否能高效完成,并不完全由构建系统决定。并不是说“Bazel比Gradle设计的更出色,用Bazel构建Android就比Gradle要快”。Android构建过程相对复杂,需要如下几个基础能力配合完成。

AndroidGradlePlugin:简称AGP,由Android官方团队维护开发,投入力度较大,虽然性能方面还有很多提升空间,但功能完整性很高。对应的Bazel体系则为rules_android,Bazel的rules_android功能层面极其粗糙,由开源社区维护,近两年的活跃度很低,相较于AGP来讲,rules_android无论从性能和完善度方面相较于AGP均有较大的差距。AGP和rules_android对于构建体验的重要性甚至超过构建系统自身的性能

KotlinGradlePlugin:简称KGP,kotlin官方维护开发,更新也较频繁,针对kotlin编译做了比较多的优化。对应到Bazel体系则为rules_kotlin。rules_kotlin由社区维护,从能力和稳定性来讲全面弱于官方KGP。

至于java编译部分,Gradle就更狠了,Gradle内置了java插件,并且在Gradle框架层面进行了较为极致的优化。针对java编译,Gradle其实是“不惧”Bazel的。这里其实依赖一个设计理念的区别。

Gradle是有增量Task的概念的,而Bazel没有增量Action的设计。换言之,Gradle从设计上是支持自定义增量Task,构建系统层面感知变更,Task实现者来实现增量Task,虽然写好增量Task并不是件容易的事,但这对Gradle体系极为重要。Bazel并没有类似的机制,它的最小的缓存单元就是Action,Bazel的理念是“定义的越细,性能就越好”。

举个例子来描述下Gradle细粒度增量编译能力

└──java\n└──com\n├──A.java\n├──B.java\n├──C.java\n└──util\n├──D.java\n├──E.java\n└──F.java\n

在大型项目中,一个模块具备十几个文件夹、数百个类是一件很常见的场景,从设计的角度来看也是合理的。Gradle针对java编译做了非常极致的优化,如上图中,如果D变了,可以做到只编译D。而Bazel体系下,构建单元完全是由BUILD文件来确定。换句话说,如果类似Gradle只在模块的根目录下配置BUILD文件,则会同时编译所有文件,推荐的解决办法是在util文件夹下配置单独的BUILD文件,这样D、E、F会被看做一个独立的执行单元,修改D文件,就会变为编译D、E、F。其实这是一种理念的差异。当在符合各自构建系统的理念的使用姿势下,均可以达到良好的构建性能。但Bazel的理念和使用姿势在JVM领域显得有一些苛刻和难以接受。

所以对于java构建来讲,在增量阶段,Gradle的性能是极其突出的,绝大部分场景是要快于Bazel的。

Benchmark

文章到此处还未出现数据层面的对比。因为针对Android项目,在业界确实找不到相对公平比对的benchmark项目,甚至想改造出一个双系统进行跑通的中等工程都很困难。注意,我们希望用的是真实项目,demo级别的对比或者脚本生成的工程对比其实并没有较大的意义。为此,我们进行了一个真实项目的改造选取了飞书Docs项目约80个模块搭建了Gradle和Bazel的双构建系统以对比二者的差异。

实验环境:

飞书Docs项目80个moduleBazel版本:6.2.1Gradle版本:6.7.1|AGP版本:4.1.0Gradle与Bazel同等配置粒度,粒度较粗,从Bazel的理念上来看,Bazel略吃亏Gradle方面没有去掉Debug阶段耗时的Transform环节,Bazel原生并没有支持此能力,从这方面看,对Gradle略不公平

增量编译场景结果如下:

GradleBazelexplanationNoChange20s0.222s验证了上文提到的Bazel设计方面的特性。VFS+DAG剪枝底层模块ABIChange35s95.8sBazel不具备增量Action的能力,级联变更较多时性能很差。Gradle的增量Task发挥了巨大作用底层模块NonABIChange35s18.5sBazel模块间同样具有“编译避免”的能力,加上analysis阶段的优势,整体耗时较短。上层模块ABIChange30s31.5s与底层模块ABIChange类似上层模块NonABIChange30s16.6s与底层模块NonABIChange类似

可以看出在增量编译场景下,由于Bazel配置阶段的巨大优势使它在NoChangeNonABIChange场景下大幅领先Gradle,然而在ABIChange场景下,Gradle细粒度的增量能力与更完善的编译避免能力发挥了作用,反杀了Bazel。

全量编译场景结果如下:

GradleBazel有缓存42s31.2s无缓存120s969.078s(优化后248s)

而在全量编译场景下,Bazel的表现只能用惨不忍睹来形容了,我们团队也针对这一难以置信的数据表现进行了细致分析,并做了一些优化,优化后的时长降低到了248s。其主要原因是很多工作量小,数量极大的Action没有以Worker(https://bazel.build/remote/persistent?hl=en)的方式执行,而导致大量进程创建开销(Worker可以使多个Action复用同一个常驻进程来执行,避免每次执行都创建新进程),比如从AAR中提取产物,以及安卓资源处理方面的一些缺陷,如AAPT2没有使用Daemon模式,冗余的Link调用等等,这也印证了rules_android确实还不够完善,我们也将一些通用优化向Bazel官方提了PR,其优化方案也得到了官方的认可:

https://github.com/Bazelbuild/Bazel/pull/18496https://github.com/Bazelbuild/Bazel/pull/18573https://github.com/Bazelbuild/rules_jvm_external/pull/911

生态

在研发阶段最影响用户体验的两个环节是IDE和构建系统相关的工具链体系。目前Android开发唯一的IDE即AndroidStudio也是由GoogleAndroid官方团队维护的。从官方信息来看,AndroidStudio与AGP越来越倾向于强耦合,虽然理论上AndroidStudio和Gradle之间并不存在强耦合的关系,任何构建系统都可以通过自行实现IDE扩展来支持开发的能力,但不可否认的是AndroidStudio与Gradle系统的适配是官方进行开箱即用的,Bazel的AS支持只能由Bazel团队及社区完成,对于新特性的支持显然是要滞后一大截,甚至是否能对齐都有待商榷,稳定性存疑。

从生态上看,Bazel的Android生态与Gradle对比极其弱小,并未得到官方的强力支持,加上Gradle和Bazel体系玩法相差巨大,想低成本改造绝非易事。好在Bazel对于Android也有进一步的规划,在2023年的BazelRoadmap里表明,AndroidRules将由Starlark重写,迁移出Bazel源码,由Bazel官方和社区一起维护AndroidRules。不过想追平现有的Android工具链生态,只能说任重而道远。

2023年BazelRoadmap:https://bazel.build/about/roadmap?hl=zh-cn#bazel_ecosystem_tooling

我们能做什么

构建系统的性能决定了构建性能的上限,生态体系决定了下限。长远来看,朝着Monorepo的演进,Bazel的上限更高,但就目前及中短期来看,受限于生态及工具链匮乏,它的下限也很低。Buildinfra团队对Bazel从源码及工具链层面进行了较为深入的研究。目前Android层面通过在Gradle场景的优化,还未触达Gradle体系的上限,但不代表未来不会,所以我们会对Bazel进行适当的长期的投入,并对社区做出一定的贡献。

与此同时,我们是少有的能同时深入两个超大型构建系统的团队,完全可以借鉴Bazel的优异设计来反哺当前Gradle生态。能够达到横向迁移的目的,也能体现当下的价值,我们在研究Bazel的过程中确实受到了不少的启发,并已经迁移到了gradle上。

举个例子:

前文提到Bazel对于增量构建来讲,DAG剪枝的能力极其诱人,这也是能做到增量编译速度不随工程规模劣化的原因,注意这里的劣化指的是不引入过多的自身损耗。比如DAG的节点有1000个,修改一行只需要执行100个,其余的不用参与构建。当节点扩展到10000个时,同样的修改,也只有100个需要执行。而gradle体系则不然,如果工程规模很大,那Task的个数就会线性增长,比如1000个时,修改一行代码,真正需要重新运行的task同样为100个,但要进行990个Task是否能复用缓存的判断。虽然他的判断极其高效,但这个数量级会随工程规模线性增长,当10000个task时,就会进行9900个task的判断。这也是为什么我们不看好Gradle能成为巨型仓库的构建系统的原因。

但是Bazel的这个能力太诱人了,那我们有没有办法在Gradle上也进行一次task的剪枝呢,为此我们团队做了一套剪枝的方案,对于抖音全源码工程来讲,几乎砍掉了90%的task的判断。这完全来源于Bazel的启发。

总结

本文首先从构建系统设计和理念的角度对Bazel和Gradle进行了深度的对比,然后围绕Android构建方面的表现从性能和生态两个角度进行了阐述,最后讲述了一下我们的研究思路与优化的能力。

总的来说:

对于超大型或者巨型工程来讲,Bazel确实是独一无二的选择,优秀的理念和设计上限更高。但这个超大型如何定义呢,以我们在Monorepo的实践来看,大概量级是在抖音Android现有规模的2-3倍左右,注意这里指的是单体app规模。现有规模Gradle经过优化的表现依然有着较大的承载空间。Bazel在当前Android构建领域不够完善且缺乏官方支持,长期来看,是否能让生态成长起来还有较大的不确定性,毕竟不是所有的项目都是超大型项目,对于中小型项目无论从易用性、性能和生态的角度都几乎处于被吊打的状态。

对于Bazel构建系统,我们会进行持续关注与投入,后续为大家带来更多Android视角的看法与思考!近期我们也会将字节大型Android项目在Monorepo全源码模式改造过程中的一些经验和沉淀分享给大家,敬请期待!

作者:AppInfra-Build

来源:微信公众号:字节跳动终端技术

出处:https://mp.weixin.qq.com/s/4AI7H428oSc4fWgcK3KOpQ

好了,本文到此结束,如果可以帮助到大家,还望关注本站哦!