了解一下,Android 10 Build系统

源起

因工作原因不得不重新抄起Android源代码开始看。这次就直接上Android 10.0了。当把代码导入Source Insight后,感慨万千。我一度觉得对TA的熟悉简直超过对自己的身体...毕竟,摸了有9年了...。不过好在Android还在快速发展。例如,仅仅是编译出一个可供模拟器加载的镜像,我就在这个过程中发现了不少新鲜的东西。所以,我想后面可能需要来一系列短小精悍“了解一下,Android 10 XXX”这样的文章来弥补心中的缺憾。

消极苦痛和积极苦痛

先来一碗不那么毒的鸡汤,至少对我还是有点效果。最近读到一本书,提到王国维里的一段话。

王国维认为,人心一个最基本的特点便是无时无刻不在运动。你可以想东,也可以想西,但完全不去想任何事,很难做到。心只有充分活动起来才能获得快感,一旦无所事事,就会陷入苦痛——这是一种“消极苦痛”,即百无聊赖,混吃等死。与之相对的是“积极苦痛”,比如你夙兴夜寐,努力上进,却不为领导赏识,遭到同事忌恨。虽说也很难受,但内心一直都在剧烈运动,顺应天性,故依然包含着快乐的元素。相比之下,“消极苦痛”因违背心性,更加难以忍受。人们为了免除此苦,在工作之余的闲暇时间发明了种种“消遣”,从而产生了种种嗜好。清代词人项鸿祚在解释自己为何填词时说:“不为无益之事,何以遣有涯之生?”

王国维这段话出自他的《人间嗜好之研究》,此书的作者将其转化成了现代汉语。其实,上面这段话核心意思就是——人呐,不要闲死。闲死是最难受的。看来,我之前说“宁愿闲得蛋疼也不要乱动”是违反人性的......。既然如此,那我们就Android 10搞起来,积极得痛苦一段时间。

Android 10 Build系统简介

Android 10的Build系统是有名字的,它叫Soong。Soong之前,Android的编译系统也有名字,不过比较土,就叫Make——意思是基于Makefile文件的编译系统。Soong其实也不是陌生人,大概在Android 7就崭露头角了。

Soong的宏伟目标是干掉Make。但直到Android 10,这个看起来比较容易的小目标也还未完全实现。为什么呢?

  1. 写Makefile文件的语言属于Domain Specific 的Languange(领域语言),它是图灵完备的。所以,Makefile可以写得巨复杂。

  2. AOSP本身是一个非常庞大和复杂的系统。这就导致AOSP里有着成千上万的Makefile文件。各种依赖,组合等等。

OMG...所以,Soong想把Make很快的干掉也不是一件容易的事情。

....继续来看。

先看Android 10源码中的build目录,现在是这个样子:

其中有两个比较重要的内容需要了解:

  • 原来的make那套东西,放在make目录下。为了对使用者屏蔽切换编译系统的差异,envsetup.sh等依然还在。

  • 增加了blueprintkatisoong三个目录。这三位(还得加上一个ninja)一起组成了AOSP的新的Soong编译系统。下图是Soong编译系统中的几位重要成员。

对Android系统的使用者来说:

  • 使用方法和以前一样。先引入build/envsetup,sh,然后执行m/mm/mmm等命令。只不过现在的m/mm/mmm命令将调用Soong中的对应工具。

  • Soong将把.bp文件交给blueprint工具集来处理。blueprint工具集是用go语言写的。官方文档对它的描述是:a meta-build system。输入为.bp文件。输出为.ninja文件

  • Soong把.mk/Makefile文件交给kati/ckati工具来处理。kati是go语言写的,而ckati是c++写的。kati官方文档对它的描述是:kati is an experimental GNU make clone。也就是说,kati是对等make命令的。只不过kati并不执行具体的编译工作,而是生成ninja文件。

  • ninja是一个神奇的东西,它是忍者桃太郎的意思。ninja本身不是编译器,它是去调用具体编译器的工具。真正的编译工作还是由编译器来完成,比如gcc,clang,java等。ninja读取的是.ninja结尾的编译配置文件,然后调起对应的编译器。

好了,上面是整个Soong编译系统的Big picture。现在我们需要对其中一些小问题做一些了解。

ninja是什么

ninja是忍者桃太郎。它最初来自谷歌chrome团队。因为工程师发现原来使用的make系统比较慢。所以考虑要优化优化。题外话,我对谷歌这个工程师文化还是蛮敬佩的,很难想象国内哪个公司还能允许一个团队去优化一个运行得好好的编译系统,而且当时肯定不知道最终能优化成什么样.....

Anyway,ninja的思想还是极具远见的。Makefile文件中如果写了太多if/else,这个管理起来(包含修改)的复杂度就大幅提升。我其实在自己工作中也发现了这个问题。比如,现在Android APP编译的gradle文件也比较复杂了。因为不同渠道,debug/release的原因,if/else这样的代码语句也出现在了gradle文件里。我曾经考虑过能不能有个更高层级的工具,它能生成专门用于比如debug版本的gradle文件。这样,在debug版本里的gradle文件就不需要if/else来区分了。

没想到我的这个想法和ninja的思路是一致的。总结来说,ninja的输入文件(.ninja)是没有if/else这样的分支逻辑。它包含的就是单纯的编译规则。至于debug/release这样的区分,则由更高层级的工具来编写,比如用调试更为方便的python、go等语言编写。

所以,.ninja文件号称编译系统里的汇编文件,既然是汇编文件,自然也不希望你去修改.ninja文件,而是拜托大家去调整生成.ninja文件的地方。比如上图中的blueprint或者kati。所以,ninja是一个比较底层的东西,应该把它嵌入到一个大的编译系统中去。然后由更高层次的工具生成ninja文件并交给ninja去处理。这就是ninja的逻辑。其官网地址是:https://ninja-build.org

.bp和blueprint是什么

Soong系统使用.bp作为编译配置文件,替代了之前的Makefile文件。相比复杂的Makefile,.bp是一个类似json一样的文件。bp就是blueprint的缩写,待会我们会介绍blueprint。

首先,.bp从样子上看就清爽很多,连gradle都不能与之相比。下面是某个bp文件的样子。

.bp文件的内容很明显是借鉴了Google内部的Bazel编译系统。不过,Android中的.bp没有完全照搬bazel,而是省略了对分支逻辑的支持。

另外,bp写起来是比较简单,但是由于正式的文档很稀少,要写对了也不是一件容易的事情。比如,上面的tidy_checks什么意思?可以取哪些值?人民群众急需一个正式而严密的文档!!!。

Android中的.bp文件由谁处理呢?答案是blueprint。blueprint读取.bp然后转成.ninja文件。blueprint本身是一个比较复杂的工具,它是由go语言写的。这里不打算介绍太多,牢记我们的目标是“了解一下”,其官网地址是https://github.com/google/blueprint

kati/ckati是什么

Android 10还未将所有.mk文件转成.bp文件。所以,还需要一个工具用于将.mk/Makefile文件转换成.ninja。这就冒出来了新的工具kati。kati的文档较少。要了解它的话,源码中build/kati下的README.md和INTERNALS.md是最好的材料,尤其是INTERNALS.md,更是无情得打了go的一点点脸

根据上面的文档,kati才是肩负干掉make系统重任的家伙。只不过现在变成soong了,那么kati就降格为将Makefile文件转成.ninja的工具。正是在kati的文档里,我才意识到Make是一个多么复杂的东西。

kati最开始用go写,然后发现速度不行,最后改为C++。所以有两个版本,katickati。为何go版本的kati速度不行呢?作者说,主要是GC慢了。AOSP编译系统大概包含了100万个变量并且不会释放.......。

当然,打脸的事情一定是无图无真相:

m命令的执行

当我们source build/envsetup.sh后,就可以通过m、mm或mmm来编译系统或者某个模块了。这个用法倒是和以前一样,只不过内部实现切换到了soong。这里简单看看envsetup.sh里是怎么和soong勾搭上的。主要了解下m命令即可,来看下图。

m是envsetup.sh里定义的一个函数,它将执行脚本build/soong/soong_ui.bash。而这个soong_ui.bash的核心就是下面这个图:

最后执行的是soong_ui命令。而这个soong_ui的源码位于build/soong/cmd/soong_ui/main.go中。

另外,

  • 以前编译系统的时候需要执行lunch。现在其实不需要lunch了。只要使用m PRODUCT-aosp_x86_64-eng就可以编译aosp_x86_64-eng这个设备的eng系统。

  • 以前要编译某个模块,比如make libart。现在只需要使用m libart即可。好像是方便一点了。

小提示,大家不妨用vscode,配置好go环境,然后调试下这个main.go,切身体验下soong......

其他的内容

envsetup.sh提供了更多的命令,hmm执行下即可获取全部信息。如下图:

比如,

  • allmod可以获得编译目标下的所有模块

  • 比如,pathmod libart将获取libart所在的源码目录。答案是art/runtime。

allmod、pathmod等命令其实是解析了out/module-info.json这个文件——通过调用python的相关工具。从这一点可以看出,AOSP编译系统其实俨然是一个各种综合技术高度聚集的地方,绝非以前写写Makefile就行。

另外,AOSP 10中有很多编译的小秘密隐藏在一些偷偷生成的文件里,比如下图out/.module_paths目录:

编译中遇到的所有mk文件、bp文件信息等都存到对应的.list里。当然,除了这个.moduel_paths之外,还有更多的秘密等待大家去发现。

后续的安排

AOSP 10源码撸了大概五天,发现其中有一些需要了解的知识,比如APEX、ART等。接下来会对这些东西做一系列的“了解”。在此也欢迎大家提供一些目标,好让我们的"了解Android 10”系列飞得更远一点。

最后的最后

  • 我期望的结果不是朋友们从我的书、文章、博客后学会了什么知识,干成了什么,而应该是说,神农,我可是踩在你的肩膀上的喔。

  • 关于学习方面的问题,我已经讨论完了。后面这个公众号将对一些基础的技术,新技术做一些学习和分享。也欢迎你的投稿。不过,正如我在公众号“联系方式”里说的那样——郑渊洁在童话大王《智齿》里有一句话令我印象深刻,大意是“我有权保持沉默,但你说的每一句话都可能成为我灵感的源泉”。所以,影响不是单向的,很可能我从你那学到的东西更多。

神农和朋友们的杂文集

长按识别二维码关注我们

猜你喜欢

转载自blog.csdn.net/Innost/article/details/103286516