diff --git a/articles/20200911-platform-version.md b/articles/20200911-platform-version.md index 5fd116705590d8a5b48662aef841f68517b04de1..beec3338b86af9613dd67a0b7c0e9d98757c90a4 100644 --- a/articles/20200911-platform-version.md +++ b/articles/20200911-platform-version.md @@ -3,6 +3,10 @@ 文章标题:**AOSP 的版本管理** +- 作者:汪辰 +- 联系方式: / + +文章大纲 - [1. 参考](#1-参考) @@ -18,18 +22,33 @@ # 2. AOSP 的版本管理 -AOSP (Android Open Source Project),也就是我们常说的 Android(安卓),从 2005 开始到现在已经十五个年头,整个代码仓库已经变成了一个巨兽。那么这么多年来, Google 是如何管理这个项目的呢,特别是对海量代码的版本管理又是怎样的呢,今天整理了一下,希望能够说说清楚。 +AOSP (Android Open Source Project),也就是我们常说的 Android(安卓),从 2005 开始 +到现在已经十五个年头,整个代码仓库已经变成了一个巨兽。那么这么多年来, Google 是如何管理 +这个项目的呢,特别是对海量代码的版本管理又是怎样的呢,今天整理了一下,希望能够说说清楚。 -首先我们来看一张思维导图,后面的总结都将围绕这张图展开,大家看完后再回来回顾一下这张图,印象就更深了。 +首先我们来看一张思维导图,后面的总结都将围绕这张图展开,大家看完后再回来回顾一下这张图, +印象就更深了。 ![](./diagrams/20200911-platform-version/android-version.png) -Android 的开发版本每一个大版本我们称之为一个 **Platform**。围绕 Platform 的概念,Google 提出了以下 **Version**、**Codename** 和 **API level** 三个和版本管理相关的概念: -- **Version**: 每个 **Platform** 有一个版本号 **Version**,这个 **Version** 的值是唯一的,我们就叫它 “版本号” 好了(后面直接用英文表达感觉更准确,其他关键字也是一样),可以作为关键字区分每个不同的 **Platform**。**Version** 的表达采用点分格式,完整的格式包括三段 `X.Y.Z`,但常常用不了那么多,所以经常简写为 `X` 或者 `X.Y`。 **Version** 的值是从 1.0 开始,依次递增,目前正式发布的已经达到 11。 -- **Codename**: 同时针对不同的 **Platform**, Google 为了方便人记忆,又会赋予一个独立的 **Codename**,这些 **Codename** 的灵感源自美味的点心,譬如Gingerbread/Honeycomb,就好比人的绰号。注意 **Codename** 和 **Version** 是一对多的概念,譬如同样叫 Oreo 对应的 **Version** 有 8.0.0 和 8.1.0。 -- **API level**: 和 **Version** 对应的是 **API level**(有的 **Version** 还有对应的 **NDK release ID**,差不多是一个概念),所谓 API 就是每个 **Version** 的 Android Platform 的 framework 提供出来供上层的应用编程时调用的一套接口,所以 API 的版本(官方叫 level)是对应每一个 **Version** 都是唯一的。 - -有关 **Platform**、**Codename**、**Vesion** 和 **API level** 之间概念的历史信息,可以参考官网的总结表格如下 (由于图片较大,只截取了最新的一部分),大家结合上面的总结再自己看一下: +Android 的开发版本每一个大版本我们称之为一个 **Platform**。围绕 Platform 的概念, +Google 提出了以下 **Version**、**Codename** 和 **API level** 三个和版本管理相关的概念: +- **Version**: 每个 **Platform** 有一个版本号 **Version**,这个 **Version** 的值是 + 唯一的,我们就叫它 “版本号” 好了(后面直接用英文表达感觉更准确,其他关键字也是一样), + 可以作为关键字区分每个不同的 **Platform**。**Version** 的表达采用点分格式,完整的格 + 式包括三段 `X.Y.Z`,但常常用不了那么多,所以经常简写为 `X` 或者 `X.Y`。 + **Version** 的值是从 1.0 开始,依次递增,目前正式发布的已经达到 11。 +- **Codename**: 同时针对不同的 **Platform**, Google 为了方便人记忆,又会赋予一个独 + 立的 **Codename**,这些 **Codename** 的灵感源自美味的点心,譬如Gingerbread/Honeycomb, + 就好比人的绰号。注意 **Codename** 和 **Version** 是一对多的概念,譬如同样叫 Oreo 对 + 应的 **Version** 有 8.0.0 和 8.1.0。 +- **API level**: 和 **Version** 对应的是 **API level**(有的 **Version** 还有对应的 + **NDK release ID**,差不多是一个概念),所谓 API 就是每个 **Version** 的 Android + Platform 的 framework 提供出来供上层的应用编程时调用的一套接口,所以 API 的版本 + (官方叫 level)是对应每一个 **Version** 都是唯一的。 + +有关 **Platform**、**Codename**、**Vesion** 和 **API level** 之间概念的历史信息,可 +以参考官网的总结表格如下 (由于图片较大,只截取了最新的一部分),大家结合上面的总结再自己看一下: ![](./diagrams/20200911-platform-version/android-codename.png) @@ -42,13 +61,18 @@ Android 的开发版本每一个大版本我们称之为一个 **Platform**。 (图片来源:https://en.wikipedia.org/wiki/Android_version_history) 每个 **Platform** 开发和发布周期中的源码管理上还会衍生出更细的版本管理策略。这涉及两个概念: -- **tag**, 或者叫 “标签”,这个使用过 git 的同学应该是熟悉的,Google 在 AOSP 的代码树中会给不同版本的代码采用打 tag 的方式进行标记,tag 的格式是 `android-X.Y.Z_rN`,其中 X、Y、Z 和 N 是变量: +- **tag**, 或者叫 “标签”,这个使用过 git 的同学应该是熟悉的,Google 在 AOSP 的代码树 + 中会给不同版本的代码采用打 tag 的方式进行标记,tag 的格式是 `android-X.Y.Z_rN`,其 + 中 X、Y、Z 和 N 是变量: - X.Y.Z 是上面介绍的 **Platform** 对应的 **Version** - N 是标识同一个 **Version** 下的二级小版本号,从 1 开始。 tag 的一个例子:`android-10.0.0_r39`。 -- **“构建(Build)”** 的概念。Android 开发团队针对某些 **tag** 标记的版本测试稳定后会发布比较正式的构建版本。针对这些构建版本会赋予一个唯一的 Build Number 进行标识。从 Android 8.0.0 (Oreo) 开始以后的版本中,每个 **Build** 的命名格式为 `PVBB.YYMMDD.bbb[.Cn]`,其中: +- **“构建(Build)”** 的概念。Android 开发团队针对某些 **tag** 标记的版本测试稳定后会 + 发布比较正式的构建版本。针对这些构建版本会赋予一个唯一的 Build Number 进行标识。 + 从 Android 8.0.0 (Oreo) 开始以后的版本中,每个 **Build** 的命名格式为 `PVBB.YYMMDD.bbb[.Cn]`, + 其中: - P 表示平台版本代号的第一个字母,例如 O 表示 Oreo。 - V 表示支持的行业。按照惯例,P 表示主要平台分支。 - BB 是由字母和数字组成的代码,Google 可通过该代码识别 build 所属的确切代码分支。 @@ -56,7 +80,8 @@ Android 的开发版本每一个大版本我们称之为一个 **Platform**。 - bbb 表示具有相同 YYMMDD 的不同版本,从 001 开始。 - Cn 是可选的字母数字,表示基于 PVBB.YYMMDD.bbb 这个构建的一次小修补,从 A1 开始。 - **Build ID** 的一个例子:`QQ3A.200605.002.A1`,大家可以逐一对号入座。注意:早期 Android 版本采用另一种较短的 build ID 代码(例如 FRF85B),这里就不罗嗦了。 + **Build ID** 的一个例子:`QQ3A.200605.002.A1`,大家可以逐一对号入座。注意:早期 + Android 版本采用另一种较短的 build ID 代码(例如 FRF85B),这里就不罗嗦了。 官网有关 **Build** 的总结表格如下,表格太长,我只是摘取了前面的部分条目: diff --git a/articles/20200915-android-linux-version.md b/articles/20200915-android-linux-version.md index fdc4363d4b57c81b4b2c38874648cdcdd8bed094..69d4ab6362d230ef94f99c32a19a46da5d7f9d2d 100644 --- a/articles/20200915-android-linux-version.md +++ b/articles/20200915-android-linux-version.md @@ -2,6 +2,11 @@ 文章标题:**AOSP 内核的版本管理** +- 作者:汪辰 +- 联系方式: / + +文章大纲 + - [1. 参考](#1-参考) @@ -12,29 +17,47 @@ - [4.2. Android Platform 的内核版本管理](#42-android-platform-的内核版本管理) - [4.2.1. “Launch Kenel”](#421-launch-kenel) - [4.2.2. “Feature Kernel”](#422-feature-kernel) -- [TBD](#tbd) +- [5. TBD](#5-tbd) # 1. 参考 -- https://www.kernel.org/ -- https://source.android.google.cn/devices/architecture/kernel +- +- # 2. 前言 -我们知道 AOSP(Android Open Source Project,和我们常说的 Android 是一个意思)所使用的操作系统内核就是 Linux。但 AOSP 使用的 Linux 内核是在原生 Linux 内核上加上了一些自己的东西,包括一些 Android 社区特有的特性(短期内还看不到可以被 Linux 接纳,以至于还未合并到 Linux upstream 的补丁程序)。所以严格地说 Android 的内核和 Linux 还不是一回事,本文中我们叫它 **AOSP 内核** 以示区别,而 Linux 则称之为 **Linux 内核**。 +我们知道 AOSP(Android Open Source Project,和我们常说的 Android 是一个意思)所使用 +的操作系统内核就是 Linux。但 AOSP 使用的 Linux 内核是在原生 Linux 内核上加上了一些自己 +的东西,包括一些 Android 社区特有的特性(短期内还看不到可以被 Linux 接纳,以至于还未合 +并到 Linux upstream 的补丁程序)。所以严格地说 Android 的内核和 Linux 还不是一回事, +本文中我们叫它 **AOSP 内核** 以示区别,而 Linux 则称之为 **Linux 内核**。 -由于 **Linux 内核** 一直在演进, **Android 内核** 自身也在进化,所以如何尽可能用上 upstream Linux 内核的新特性,管理好 **Linux 内核** 与 **AOSP 内核** 之间的关系对于 Android 系统的发展来说一直是一件头等重要的事情。最近我整理了一下这方面的思路,总结在这里,供大家参考,有些地方也是加入了自己的一些理解,有什么说得不到位的地方,欢迎大家拍砖指正。 +由于 **Linux 内核** 一直在演进, **Android 内核** 自身也在进化,所以如何尽可能用上 +upstream Linux 内核的新特性,管理好 **Linux 内核** 与 **AOSP 内核** 之间的关系对于 +Android 系统的发展来说一直是一件头等重要的事情。最近我整理了一下这方面的思路,总结在这 +里,供大家参考,有些地方也是加入了自己的一些理解,有什么说得不到位的地方,欢迎大家拍砖指正。 # 3. **Linux 内核** 的版本管理 首先熟悉一下 **Linux 内核** 的版本管理模式。 -在版本发布管理模式上,**Linux 内核** 与标准的 AOSP 是很不相同的。从 2003 年 12 月往后,也就是 从 `2.6` 内核版本发布往后,内核开发者社区从之前维护单独的一个开发分支和另一个稳定分支的模型迁移到只维护一个 “稳定” 分支模型。在此模式下,每 2-3 个月就会发布一次新版本,出现这种变化的原因在于:`2.6` 版本内核之前的版本周期非常长(将近 3 年),且同时维护两个不同的代码库难度太高。 - -内核版本的编号从 `2.6.x` 开始,其中 x 是一个数字,会在每次发布新版本时递增(除了表示此版本比上一内核版本更新之外,该数字的值不具有任何意义)。内核版本发展到现在已经进入 5 系列(最新的是 `5.8.x`)。在众多内核版本中,分为 mainline 即主线版本,stable 稳定版本和 longterm 长期维护版本。其中 mainline 版本由 Linus 本人维护,每两到三个月会做一次升级,而且是两位数字版本号,不涉及第三位数字,譬如 `5.1`、`5.2`、`5.3` ......。stable 版本是针对 mainline 版本你的小版本更新,譬如对应 `5.1` 的会有 `5.1.1`、`5.1.2`、`5.1.3` ......。某一些 stable 版本会被指定为 longterm 版本(也称为 LTS),从而拥有更长的维护周期,直白了说就是其第三位数字会会变得很大。 +在版本发布管理模式上,**Linux 内核** 与标准的 AOSP 是很不相同的。从 2003 年 12 月往后, +也就是 从 `2.6` 内核版本发布往后,内核开发者社区从之前维护单独的一个开发分支和另一个稳定 +分支的模型迁移到只维护一个 “稳定” 分支模型。在此模式下,每 2-3 个月就会发布一次新版本, +出现这种变化的原因在于:`2.6` 版本内核之前的版本周期非常长(将近 3 年),且同时维护两个 +不同的代码库难度太高。 + +内核版本的编号从 `2.6.x` 开始,其中 x 是一个数字,会在每次发布新版本时递增(除了表示此 +版本比上一内核版本更新之外,该数字的值不具有任何意义)。内核版本发展到现在已经进入 5 系 +列(最新的是 `5.8.x`)。在众多内核版本中,分为 mainline 即主线版本,stable 稳定版本 +和 longterm 长期维护版本。其中 mainline 版本由 Linus 本人维护,每两到三个月会做一次升 +级,而且是两位数字版本号,不涉及第三位数字,譬如 `5.1`、`5.2`、`5.3` ......。stable +版本是针对 mainline 版本你的小版本更新,譬如对应 `5.1` 的会有 `5.1.1`、`5.1.2`、`5.1.3` ......。 +某一些 stable 版本会被指定为 longterm 版本(也称为 LTS),从而拥有更长的维护周期,直白 +了说就是其第三位数字会会变得很大。 下图是 Linux 的版本计划(by 2020/9) @@ -52,23 +75,48 @@ ## 4.1. AOSP 通用内核 -针对 Andorid 内核的管理, Google 定义了一个所谓 “AOSP 通用内核 ( AOSP common kernels,简称 ACKs)” 的概念。 ACKs 版本基于 **Linux 内核** 开发(术语上称之为 downstream,形象地说可以认为 **Linux 内核** 的发布处于上游,而 ACKs 随着 Linux 内核版本的升级而升级,就好像处在流水线的下游),ACKs 在 **Linux 内核** 的版本基础上包含了许多与 Android 社区相关但尚未合并到 LTS 的补丁程序,可以简单地分成以下几大类: +针对 Andorid 内核的管理, Google 定义了一个所谓 “AOSP 通用内核 ( AOSP common kernels, +简称 ACKs)” 的概念。 ACKs 版本基于 **Linux 内核** 开发(术语上称之为 downstream,形 +象地说可以认为 **Linux 内核** 的发布处于上游,而 ACKs 随着 Linux 内核版本的升级而升级, +就好像处在流水线的下游),ACKs 在 **Linux 内核** 的版本基础上包含了许多与 Android 社 +区相关但尚未合并到 LTS 的补丁程序,可以简单地分成以下几大类: - Android 需要,存在于更新的 Linux mainline 版本上,但尚未合入当前 LTS 版本的内容。 - Android 需要,但未被 Linux mainline 接受的功能(例如 Energy Aware Scheduling/EAS)。 - Vendor(供应商)/ OEM(原始设备制造商)特有的功能(例如 sdcardfs)。 -**AOSP common kernels** 的发布网站地址在 。我截了个图,感兴趣大家可以看一下。 +**AOSP common kernels** 的发布网站地址在 。 +我截了个图,感兴趣大家可以看一下。 ![](./diagrams/20200915-android-linux-version/acks-website.png) -图上左侧 Branches 列表的第一个 `android-mainline` 是 Android 的主要开发分支。每当 Linus Torvalds 发布 Linux 的主线版本以及候选版本时,其 Linux 主线代码就会被合并到 `android-mainline` 中。 - -在 2019 年之前,**AOSP common kernels** 的构建方法是追踪最新的 LTS 内核版本,每次一个 Linux LTS 版本发布后, Google 就基于该最新的 LTS 版本添加特定于 Android 的补丁。但这么做的一个问题是 Linux LTS 版本发布的周期比较长(大概一年发布一次),由于跨度较大,每次基于 LTS 版本进行移植和添加 Android 的补丁冲突可能会比较大,容易引入混乱。所以从 2019 年开始 Google 修改了该流程,目前的做法是 `android-mainline` 持续保持对 Linux 的 mainline 版本的追踪,每次有正式发布,或者 RC 发布都同步升级这个 `android-mainline` 分支,基本思想就是将原来的一年一次升级分散到多个小步辐升级过程,再加上持续的每日构建和测试,避免了以前构建方式下执行 “前向移植(forward port)” 带来的压力。新的流程减轻了这些压力,保证了内核的高质量。当然代价是需要小心地维护 `android-mainline`,将维护的工作量分散到平时的工作中。 - -在以上持续更新 `android-mainline` 的基础上,当 Linux 宣布某个 mainline 版本成为一个新的 LTS 版本时,Google 将相对应地从 `android-mainline` 上拉一个新的对应该 LTS 的 **AOSP common kernel** 分支。因为 Google 和它的合作伙伴一直在积极维护这个 `android-mainline` 分支,所以在其基础上拉出来的分支代码都不会有太大的质量问题,这种迁移是无缝的。 - -此外,一旦某个 LTS 版本有小版本升级,我们仍然需要积极地将其修改合入对应的 **AOSP common kernels** 版本。例如,我们已经基于 Linux 的 `4.19` 建立了 **AOSP common kernels** 分支版本 `android-4.19-q`,当 Linux 对 `4.19.x` 进行更新,升级到 `4.19.y` 时,我们需要将对应的修改也相应合并到 `android-4.19-q` 中,从而保持 `android-4.19-q` 这个 **AOSP common kernel** 分支与最新的 `4.19.y` LTS Linux 内核版本同步同时又包含了特定于 Android 的补丁修改。 +图上左侧 Branches 列表的第一个 `android-mainline` 是 Android 的主要开发分支。每当 +Linus Torvalds 发布 Linux 的主线版本以及候选版本时,其 Linux 主线代码就会被合并到 +`android-mainline` 中。 + +在 2019 年之前,**AOSP common kernels** 的构建方法是追踪最新的 LTS 内核版本,每次一个 + Linux LTS 版本发布后, Google 就基于该最新的 LTS 版本添加特定于 Android 的补丁。但这 +么做的一个问题是 Linux LTS 版本发布的周期比较长(大概一年发布一次),由于跨度较大,每次 +基于 LTS 版本进行移植和添加 Android 的补丁冲突可能会比较大,容易引入混乱。所以从 +2019 年开始 Google 修改了该流程,目前的做法是 `android-mainline` 持续保持对 Linux 的 +mainline 版本的追踪,每次有正式发布,或者 RC 发布都同步升级这个 `android-mainline` 分 +支,基本思想就是将原来的一年一次升级分散到多个小步辐升级过程,再加上持续的每日构建和测试, +避免了以前构建方式下执行 “前向移植(forward port)” 带来的压力。新的流程减轻了这些压力, +保证了内核的高质量。当然代价是需要小心地维护 `android-mainline`,将维护的工作量分散到 +平时的工作中。 + +在以上持续更新 `android-mainline` 的基础上,当 Linux 宣布某个 mainline 版本成为一个新 +的 LTS 版本时,Google 将相对应地从 `android-mainline` 上拉一个新的对应该 LTS +的 **AOSP common kernel** 分支。因为 Google 和它的合作伙伴一直在积极维护这个 +`android-mainline` 分支,所以在其基础上拉出来的分支代码都不会有太大的质量问题,这种迁 +移是无缝的。 + +此外,一旦某个 LTS 版本有小版本升级,我们仍然需要积极地将其修改合入对应的 +**AOSP common kernels** 版本。例如,我们已经基于 Linux 的 `4.19` 建立了 +**AOSP common kernels** 分支版本 `android-4.19-q`,当 Linux 对 `4.19.x` 进行更新, +升级到 `4.19.y` 时,我们需要将对应的修改也相应合并到 `android-4.19-q` 中,从而保持 +`android-4.19-q` 这个 **AOSP common kernel** 分支与最新的 `4.19.y` LTS Linux 内核 +版本同步同时又包含了特定于 Android 的补丁修改。 ## 4.2. Android Platform 的内核版本管理 @@ -78,31 +126,90 @@ (图片来源:AOSP 官网) -整个 AOSP 项目从层次上来看,最下面的是 **AOSP 内核**,其上部分除了 HAL 之外都可以认为是 AOSP 的 Platform 部分。这部分的代码,即 Android Platform 的 Version 版本发布由 Google 定义(有关 AOSP Platform 版本的相关介绍请参考我写的 [另一篇总结](./20200911-platform-version.md))。结合 Google 自身的 Android Platform 的版本发布策略,Google 定义了与之相对应的最新的 Android 内核版本管理方法,总结在下面这张表格中。我们可以结合这张表继续深入理解一下 Google 对 Android 的内核版本的维护和管理思想。 +整个 AOSP 项目从层次上来看,最下面的是 **AOSP 内核**,其上部分除了 HAL 之外都可以认为 +是 AOSP 的 Platform 部分。这部分的代码,即 Android Platform 的 Version 版本发布由 +Google 定义(有关 AOSP Platform 版本的相关介绍请参考我写的 [另一篇总结](./20200911-platform-version.md))。 +结合 Google 自身的 Android Platform 的版本发布策略,Google 定义了与之相对应的最新的 +Android 内核版本管理方法,总结在下面这张表格中。我们可以结合这张表继续深入理解一下 +Google 对 Android 的内核版本的维护和管理思想。 ![](./diagrams/20200915-android-linux-version/android-kernels.png) (图片来源:https://source.android.com/devices/architecture/kernel/android-common#feature-and-launch-kernels) -这张表格的第一列是 **“Android platform release”**, 这个无需多言,基本上 Google 的策略是保持一年更新升级一个 Platform 的版本。过早的版本暂不予考虑。 +这张表格的第一列是 **“Android platform release”**, 这个无需多言,基本上 Google 的策 +略是保持一年更新升级一个 Platform 的版本。过早的版本暂不予考虑。 ### 4.2.1. “Launch Kenel” -这张表格的第二列叫 **“Launch Kenel”**。具体是什么意思呢,原来 Google 规定,每个 Android Platform 固定支持三个 **AOSP 内核** 版本用于发布(launching)新设备,这些 **AOSP 内核** 版本称之为 “Launch Kernel”。譬如,表格中 Android 11 支持的三个 **Launch Kernels** 版本分别是 `android-4.14-stable`、`android-4.19-stable` 和 `android11-5.4`。这些版本实际上就是上文我们介绍的从 `android-mainline` 分支上拉出来的对应各个 Linux LTS 版本的稳定分支。也就是说针对每个 Platform 发布,只支持最近的三个 Linux 内核 LTS 版本的稳定分支,而且这三个版本可以认为是每个 Platform 版本支持的 “老”、“中”、“青” 三代,还是以 Android 11 为例,其支持的 “老” 一代版本是 `android-4.14-stable`,恰好对应 Android 10 支持的 “中” 一代版本 `android-4.14-q`,以此类推。由于目前 Linux 的 LTS 分支差不多一年发布一个,而 Android 的 Platform 也是一年升级一个版本,所以对应每个 Android 的 Platform 版本支持的 LTS 内核版本也会吐故纳新,去掉一个最老的,加入一个新的,如此循环往复。Google 这么设计的考虑也是为了设备的兼容性和升级。假设有一款手机 Foo 采用 Android 10 platform,内核采用 `android-4.14-q`。当 Android 升级到 11 后,由于 Android 11 所支持的 “Launch Kernel” 中是宣称支持 `android-4.14-stable` 的,所以 Google 的兼容性测试会保证该手机即使不升级内核(更重要的是这也意味着不需要升级 Vendor/OEM 的驱动等),只升级 Platform 版本到 11 也是可以运行的,当然 11 中那些需要新内核补丁才可以工作的特性将无法工作,前提是该款手机不需要这些特定的新特性。 - -另外针对 **“Launch Kernel”**,我们会看到从 Android 11 开始,**“Launch Kernels”** 的命名发生了变化,其支持的三款 **“Launch Kernels”** 分别叫做 `android-4.14-stable`、`android-4.19-stable` 和 `android11-5.4`,其命名习惯发生了修改。 - -首先我们发现形如 `android-4.14-p` 这样的分支名称被替换为`android-4.14-stable`。`android-4.14-p` 这样的命名遵循的是 Codename 的缩写命名形式,其末尾的 `p` 其实就是 Pie 的首字母,所以这种命名方式也称为 “甜品(dessert)” 内核版本号。但考虑到从 10 开始对外 Google 不再使用甜品的绰号,所以先是采用 “stable” 代替了末尾的字母。 - -其次我们还看到形如 `android11-5.4` 这样的新规则。这种新的版本规则 Google 称之为 KMI kernel 分支。这和 Google 的新发明有关。所谓 KMI 全称叫 “Kernel Module Interface”,指的是 Linux 内核中提供给 “模块(Module)” 的编程接口 API。譬如 Vendor/OEM 会根据自己设备的特色为设备编写很多外设驱动模块,这些模块很可能并没有进入内核主线,或者在构建时和内核是独立编译安装的,那么维持模块和内核之间 API 的一致性就变得非常重要。如果 KMI 不一致很可能会导致系统升级时发生兼容性问题。为了解决 Android 版本的碎片化问题,从 Android 11 开始,Google 提出 **GKI(Generic Kernel Image)** 计划,这个具体可以参考 Andorid 官网有关 GKI 的说明:。简而言之就是通过统一内核的公共部分,并为内核模块提供稳定的内核模块接口(KMI),从而达到可以独立更新模块和内核的目的。与之对应的新的内核命令规则就叫 “KMI 内核”,具体形式为 `-`,通过结合 Linux 内核版本号和 Android 的 Platform Version 号来唯一决定。譬如表格中的 `android11-5.4` 表达的意思就是基于 Linux 5.4 LTS 版本,并且是支持 Android Platform 11 及以上版本。 +这张表格的第二列叫 **“Launch Kenel”**。具体是什么意思呢,原来 Google 规定,每个 +Android Platform 固定支持三个 **AOSP 内核** 版本用于发布(launching)新设备,这些 +**AOSP 内核** 版本称之为 “Launch Kernel”。譬如,表格中 Android 11 支持的三个 +**Launch Kernels** 版本分别是 `android-4.14-stable`、`android-4.19-stable` 和 +`android11-5.4`。这些版本实际上就是上文我们介绍的从 `android-mainline` 分支上拉出来的 +对应各个 Linux LTS 版本的稳定分支。也就是说针对每个 Platform 发布,只支持最近的三个 +Linux 内核 LTS 版本的稳定分支,而且这三个版本可以认为是每个 Platform 版本支持的 +“老”、“中”、“青” 三代,还是以 Android 11 为例,其支持的 “老” 一代版本是 `android-4.14-stable`, +恰好对应 Android 10 支持的 “中” 一代版本 `android-4.14-q`,以此类推。由于目前 Linux +的 LTS 分支差不多一年发布一个,而 Android 的 Platform 也是一年升级一个版本,所以对应每个 +Android 的 Platform 版本支持的 LTS 内核版本也会吐故纳新,去掉一个最老的,加入一个新的, +如此循环往复。Google 这么设计的考虑也是为了设备的兼容性和升级。假设有一款手机 Foo 采用 +Android 10 platform,内核采用 `android-4.14-q`。当 Android 升级到 11 后,由于 +Android 11 所支持的 “Launch Kernel” 中是宣称支持 `android-4.14-stable` 的,所以 +Google 的兼容性测试会保证该手机即使不升级内核(更重要的是这也意味着不需要升级 Vendor/OEM +的驱动等),只升级 Platform 版本到 11 也是可以运行的,当然 11 中那些需要新内核补丁才可 +以工作的特性将无法工作,前提是该款手机不需要这些特定的新特性。 + +另外针对 **“Launch Kernel”**,我们会看到从 Android 11 开始,**“Launch Kernels”** +的命名发生了变化,其支持的三款 **“Launch Kernels”** 分别叫做 `android-4.14-stable`、 +`android-4.19-stable` 和 `android11-5.4`,其命名习惯发生了修改。 + +首先我们发现形如 `android-4.14-p` 这样的分支名称被替换为`android-4.14-stable`。 +`android-4.14-p` 这样的命名遵循的是 Codename 的缩写命名形式,其末尾的 `p` 其实就是 +Pie 的首字母,所以这种命名方式也称为 “甜品(dessert)” 内核版本号。但考虑到从 10 开始 +对外 Google 不再使用甜品的绰号,所以先是采用 “stable” 代替了末尾的字母。 + +其次我们还看到形如 `android11-5.4` 这样的新规则。这种新的版本规则 Google 称之为 +KMI kernel 分支。这和 Google 的新发明有关。所谓 KMI 全称叫 “Kernel Module Interface”, +指的是 Linux 内核中提供给 “模块(Module)” 的编程接口 API。譬如 Vendor/OEM 会根据自己 +设备的特色为设备编写很多外设驱动模块,这些模块很可能并没有进入内核主线,或者在构建时和内 +核是独立编译安装的,那么维持模块和内核之间 API 的一致性就变得非常重要。如果 KMI 不一致很 +可能会导致系统升级时发生兼容性问题。为了解决 Android 版本的碎片化问题,从 Android 11 +开始,Google 提出 **GKI(Generic Kernel Image)** 计划,这个具体可以参考 Andorid +官网有关 GKI 的说明:。 +简而言之就是通过统一内核的公共部分,并为内核模块提供稳定的内核模块接口(KMI),从而达到 +可以独立更新模块和内核的目的。与之对应的新的内核命令规则就叫 “KMI 内核”,具体形式为 +`-`,通过结合 Linux 内核版本号和 Android 的 +Platform Version 号来唯一决定。譬如表格中的 `android11-5.4` 表达的意思就是基于 +Linux 5.4 LTS 版本,并且是支持 Android Platform 11 及以上版本。 ### 4.2.2. “Feature Kernel” -表格的第二列叫 **“Feature Kernel”**。我们知道,有时候为了支持 Android 的某个 Platform 的新的 “特性(Feature)”,可能会涉及到需要修改 Linux 内核(当然这种情况是比较少的,但也不能排除),那么添加了这些补丁的 AOSP 内核自然就叫 **“Feature Kernel”** 了。在 Android 11 之前我们看到基本上每个 “Launch Kernel” 都打上了 Platform 相关的补丁,所以针对每个 Platform 版本,三个 “Launch Kernel” 本身就是 “Feature Kernel”。这样的好处是延长了老设备的维护周期。还是以上面的 Foo 设备举例,其发布时采用内核版本 `android-4.14-q`,假设 Android Platform 从 10 升级到 11 后新增支持了一个特性 A,而且 A 这个特性是需要内核新增支持的,那么如果 Foo 这款手机也希望使用特性 A,那么它还是有内核可用的,当然除了升级内核到 `android-4.14-stable`,Platfrom 自然也需要升级到 11。但从 Google 的角度来看,Foo 生命周期的延长导致 4.14 内核系列的维护生命周期也变长了,这增加了碎片化的风险。所以从 Android 12(S)开始,为了控制和限制需要支持的稳定的 KMI 的个数,避免碎片化,Google 计划缩减每个 Platform 对应支持的 **“Feature Kernel”** 个数,从三个减为两个。从表格上来看,以 Android S 即 Platform version 为 12 的那行为例,“Feature Kernel” 不再支持 `android-4.19-stable`,后果就是如果 12 中引入了一个新特性 B,而且该特性需要内核补丁支持的话,已经发货的手机如果采用的是 `android-4.19-stable` 及以前的内核,那么必须强制升级内核到 `android12-5.4`,这也意味着 Vendor/OEM 维护的驱动等也需要相应适配到更新的 Linux 内核 5.4。 - - -# TBD +表格的第二列叫 **“Feature Kernel”**。我们知道,有时候为了支持 Android 的某个 Platform +的新的 “特性(Feature)”,可能会涉及到需要修改 Linux 内核(当然这种情况是比较少的,但也 +不能排除),那么添加了这些补丁的 AOSP 内核自然就叫 **“Feature Kernel”** 了。在 +Android 11 之前我们看到基本上每个 “Launch Kernel” 都打上了 Platform 相关的补丁,所以 +针对每个 Platform 版本,三个 “Launch Kernel” 本身就是 “Feature Kernel”。这样的好处是 +延长了老设备的维护周期。还是以上面的 Foo 设备举例,其发布时采用内核版本 `android-4.14-q`, +假设 Android Platform 从 10 升级到 11 后新增支持了一个特性 A,而且 A 这个特性是需要内 +核新增支持的,那么如果 Foo 这款手机也希望使用特性 A,那么它还是有内核可用的,当然除了升 +级内核到 `android-4.14-stable`,Platfrom 自然也需要升级到 11。但从 Google 的角度来看, +Foo 生命周期的延长导致 4.14 内核系列的维护生命周期也变长了,这增加了碎片化的风险。所以 +从 Android 12(S)开始,为了控制和限制需要支持的稳定的 KMI 的个数,避免碎片化,Google +计划缩减每个 Platform 对应支持的 **“Feature Kernel”** 个数,从三个减为两个。从表格上 +来看,以 Android S 即 Platform version 为 12 的那行为例,“Feature Kernel” 不再支持 +`android-4.19-stable`,后果就是如果 12 中引入了一个新特性 B,而且该特性需要内核补丁支 +持的话,已经发货的手机如果采用的是 `android-4.19-stable` 及以前的内核,那么必须强制升 +级内核到 `android12-5.4`,这也意味着 Vendor/OEM 维护的驱动等也需要相应适配到更新的 +Linux 内核 5.4。 + + +# 5. TBD 补充 内核生命周期部分, https://source.android.com/devices/architecture/kernel/android-common -> When entering the frozen phase, the branch is git-tagged with the KMI version string containing the KMI generation number. For example, when android11-5.4 was frozen, it was tagged with the KMI version string 5.4-android11-0 where the trailing 0 is the KMI generation number. +> When entering the frozen phase, the branch is git-tagged with the KMI version +> string containing the KMI generation number. For example, when android11-5.4 +> was frozen, it was tagged with the KMI version string 5.4-android11-0 where +> the trailing 0 is the KMI generation number. + diff --git a/articles/20200929-build-riscv-android-kernel.md b/articles/20200929-build-riscv-android-kernel.md index 333e984a074507bb74c81d8d47b2f98ede37754d..62db5dd3f7e88585aec9987976b2a7b875eb1ded 100644 --- a/articles/20200929-build-riscv-android-kernel.md +++ b/articles/20200929-build-riscv-android-kernel.md @@ -2,6 +2,11 @@ 文章标题:**编译一个 RISC-V 的 Android 内核** +- 作者:汪辰 +- 联系方式: / + +文章大纲 + - [1. 前言](#1-前言) @@ -13,17 +18,27 @@ # 1. 前言 -今天尝试了一下将 Android 的内核交叉编译运行在 RISC-V 平台上,笔记小结如下,如果有什么补充欢迎留言。 -Google 官方提供了编译内核的指导,参考 。但这个 build 过程是针对 Google 的官方编译,也就是说其操作使用的是 LLVM/Clang 编译器,而且只支持 ARM/X86/... 几个有限的平台。而我这里计划是要将其 port 到 RISC-V 上,所以直接使用是不行的。研究了一下打算按下面的步骤来做。 +今天尝试了一下将 Android 的内核交叉编译运行在 RISC-V 平台上,笔记小结如下,如果有什么补 +充欢迎留言。 +Google 官方提供了编译内核的指导,参考 。 +但这个 build 过程是针对 Google 的官方编译,也就是说其操作使用的是 LLVM/Clang 编译器, +而且只支持 ARM/X86/... 几个有限的平台。而我这里计划是要将其 port 到 RISC-V 上,所以直 +接使用是不行的。研究了一下打算按下面的步骤来做。 -有关针对 RISC-V 的 GNU toolchain(注:这里暂不使用 LLVM/Clang,使用 LLVM/Clang 放到后续尝试),QEMU 和文件系统等的制作方法请参考过去我在知乎上总结的文章:[汪辰:在 QEMU 上运行 RISC-V 64 位版本的 Linux](https://zhuanlan.zhihu.com/p/258394849)。本文要做的事情实际上就是把其中制作内核部分的操作换掉,因为这次我们要编译一个 Andorid 的 Linux 内核。 +有关针对 RISC-V 的 GNU toolchain(注:这里暂不使用 LLVM/Clang,使用 LLVM/Clang 放到 +后续尝试),QEMU 和文件系统等的制作方法请参考过去我在知乎上总结的文章: +[汪辰:在 QEMU 上运行 RISC-V 64 位版本的 Linux](https://zhuanlan.zhihu.com/p/258394849)。 +本文要做的事情实际上就是把其中制作内核部分的操作换掉,因为这次我们要编译一个 Andorid 的 Linux 内核。 # 2. 下载 Android 内核源码 -Andorid 的官方内核源码下载请访问 ,如果这个访问不了,可以访问 github: ,或者尝试国内的一些 mirror,譬如 +Andorid 的官方内核源码下载请访问 , +如果这个访问不了,可以访问 github: , +或者尝试国内的一些 mirror,譬如 [Tsinghua Open Source Mirror](https://mirrors.tuna.tsinghua.edu.cn/help/AOSP)。 -clone 下来后切换到 android-5.4-stable 分支(commit:bb168ca1805b33289aae802a940551dec93a24d3),这个分支也是撰写本文时 AOSP 官方维护的最新的 LTS 稳定分支,值得信赖。 +clone 下来后切换到 android-5.4-stable 分支(commit:bb168ca1805b33289aae802a940551dec93a24d3), +这个分支也是撰写本文时 AOSP 官方维护的最新的 LTS 稳定分支,值得信赖。 总结命令如下: ``` @@ -32,7 +47,13 @@ $ cd common $ git checkout android-5.4-stable ``` -根据我在另一篇文章: [AOSP 内核的版本管理](./20200915-android-linux-version.md) 中的介绍,我们知道,`android-mainline` 是 Android 的主要开发分支,Google 在这个主分支上密切跟踪 upstream Linux 的 mainline。当 Linux 宣布某个 mainline 版本成为一个新的 LTS 版本时,Google 将相对应地以 `android-mainline` 分支为基础拉一个 AOSP common kernel 分支并积极维护这个分支,在这里就是 `android-5.4-stable`,这个分支基于最新的 LTS 5.4.x 并包含有所有特定于 Android 的补丁。查看内核源码根目录下的 Makefile 文件,我们可以知道这个分支目前基于的是 5.4.x LTS 的小版本 5.4.61,应该是比较新的了。 +根据我在另一篇文章: [AOSP 内核的版本管理](./20200915-android-linux-version.md) 中的 +介绍,我们知道,`android-mainline` 是 Android 的主要开发分支,Google 在这个主分支上密 +切跟踪 upstream Linux 的 mainline。当 Linux 宣布某个 mainline 版本成为一个新的 LTS +版本时,Google 将相对应地以 `android-mainline` 分支为基础拉一个 AOSP common kernel +分支并积极维护这个分支,在这里就是 `android-5.4-stable`,这个分支基于最新的 LTS 5.4.x +并包含有所有特定于 Android 的补丁。查看内核源码根目录下的 Makefile 文件,我们可以知道这 +个分支目前基于的是 5.4.x LTS 的小版本 5.4.61,应该是比较新的了。 ``` $ cat Makefile | head @@ -50,9 +71,12 @@ NAME = Kleptomaniac Octopus # 3. 下载 Android 内核配置 -但光是源码中含有 Android 的补丁还不够,Google 对 Android 的内核还有自己特定的配置,所以我们还需要把这些配置加上。 +但光是源码中含有 Android 的补丁还不够,Google 对 Android 的内核还有自己特定的配置, +所以我们还需要把这些配置加上。 -我们知道对于一款设备或者一个平台,内核中一般都提供了它的缺省配置,譬如我们在前面配置 RISC-V 的内核时就是使用了它的缺省配置,当时我们配置操作命令如下,参考:[《QEMU 上运行 RISC-V 64 位版本的 Linux》](https://zhuanlan.zhihu.com/p/258394849)。 +我们知道对于一款设备或者一个平台,内核中一般都提供了它的缺省配置,譬如我们在前面配置 +RISC-V 的内核时就是使用了它的缺省配置,当时我们配置操作命令如下, +参考:[《QEMU 上运行 RISC-V 64 位版本的 Linux》](https://zhuanlan.zhihu.com/p/258394849)。 ``` $ make ARCH=riscv CROSS_COMPILE=riscv64-unknown-linux-gnu- defconfig @@ -67,9 +91,15 @@ total 8 -rw-rw-r-- 1 u u 2102 9月 18 12:13 rv32_defconfig ``` -如果要支持特定的硬件或软件功能(譬如这里要专门针对 Android),则我们可以为 defconfig 文件打补丁,这些补丁文件我们术语上称之为配置 “片段(fragments)”。这些 fragments 文件的格式与 defconfig 相同,但通常要小得多,因为它们仅包含为支持特定的硬件或软件功能/行为所需的内核配置项。我们可以使用内核提供的合并脚本(即内核源码树中 `scripts/kconfig/merge_config.sh` 将这些 fragments 文件与平台的 defconfig 文件进行合并。 +如果要支持特定的硬件或软件功能(譬如这里要专门针对 Android),则我们可以为 defconfig +文件打补丁,这些补丁文件我们术语上称之为配置 “片段(fragments)”。这些 fragments 文件 +的格式与 defconfig 相同,但通常要小得多,因为它们仅包含为支持特定的硬件或软件功能/行为 +所需的内核配置项。我们可以使用内核提供的合并脚本(即内核源码树中 `scripts/kconfig/merge_config.sh` +将这些 fragments 文件与平台的 defconfig 文件进行合并。 -Android 内核的 fragments 配置专门维护在一个独立的仓库中,官网地址是:。github 和国内 mirror 上似乎还没看到比较官方的 mirror,有知道的同学请留言,谢了先。 +Android 内核的 fragments 配置专门维护在一个独立的仓库中,官网地址是: +。github 和国内 mirror 上似乎还没 +看到比较官方的 mirror,有知道的同学请留言,谢了先。 下载这个仓库: ``` @@ -82,10 +112,17 @@ $ cd configs - `android-base.config`: 这是 Android 的基本配置,必须要应用。 - `android-recommended.config`: 这些配置属于对 Android 功能增强,推荐使用但不强制使用。 -- `android-base-conditional.xml`:在 Android P(包括 Android P)以下的版本中,特定于体系结构的内核配置要求包含在特定于体系结构的基本配置 fragments 中,例如 `android-base-arm64.config`。但在 Android P 之后的版本中,特定于体系结构的基本配置 fragments 被删除,一些可选的内核配置要求存储在 `android-base-conditional.xml` 文件中,注意这里变成了 xml 的格式,也就是说不能直接使用 merge 脚本和 defconfig 合并,不知道 Google 为何这么做,也许是为了强迫大家不要图省事,不得不从 xml 文件中手动挑选合适的配置项进行添加。 +- `android-base-conditional.xml`:在 Android P(包括 Android P)以下的版本中,特定于 + 体系结构的内核配置要求包含在特定于体系结构的基本配置 fragments 中,例如 + `android-base-arm64.config`。但在 Android P 之后的版本中,特定于体系结构的基本配置 + fragments 被删除,一些可选的内核配置要求存储在 `android-base-conditional.xml` 文件 + 中,注意这里变成了 xml 的格式,也就是说不能直接使用 merge 脚本和 defconfig 合并, + 不知道 Google 为何这么做,也许是为了强迫大家不要图省事,不得不从 xml 文件中手动挑选合 + 适的配置项进行添加。 - `non_debuggable.config`:其他用户自己的配置项。 -这个 configs 仓库也包含了很多个分支,针对一个正式的 Andorid 版本有对应的发布分支,譬如最新的 Android 11 其发布分支就是 `android11-release`(commit:2f1dcde47942beb27201574a0c09bbf4182b79e6) +这个 configs 仓库也包含了很多个分支,针对一个正式的 Andorid 版本有对应的发布分支,譬如 +最新的 Android 11 其发布分支就是 `android11-release`(commit:2f1dcde47942beb27201574a0c09bbf4182b79e6) ``` $ git checkout android11-release @@ -104,7 +141,8 @@ drwxrwxr-x 5 u u 4096 9月 18 10:45 r drwxrwxr-x 2 u u 4096 9月 18 10:45 tools ``` -切换到这个分支后我们看到一些 o、p、q、r 这样的目录,这些就是对应着 Android platform version:8、9、10、11。进去看一下 +切换到这个分支后我们看到一些 o、p、q、r 这样的目录,这些就是对应着 Android platform version:8、9、10、11。 +进去看一下 ``` $ cd p @@ -128,8 +166,10 @@ drwxrwxr-x 2 u u 4096 9月 18 10:45 android-4.19 drwxrwxr-x 2 u u 4096 9月 18 10:45 android-5.4 ``` -每个 platform 下对应三个内核版本,回忆一下 [《AOSP 内核的版本管理》](./20200915-android-linux-version.md) 中 章节 “Android Platform 的内核版本管理” 的介绍会进一步加深 Google 对 Android 内核版本的管理方式。 -configs 仓库的 master 分支上维护着下一个 AOSP 的 Platform 版本(这里是 12/S)会支持的内核版本。切换到 master 看一下: +每个 platform 下对应三个内核版本,回忆一下 [《AOSP 内核的版本管理》](./20200915-android-linux-version.md) +中章节 “Android Platform 的内核版本管理” 的介绍会进一步加深 Google 对 Android 内核版本的管理方式。 +configs 仓库的 master 分支上维护着下一个 AOSP 的 Platform 版本(这里是 12/S)会支持的 +内核版本。切换到 master 看一下: ``` $ git checkout master @@ -141,11 +181,16 @@ drwxrwxr-x 2 u u 4096 9月 24 16:02 android-5.4 ...... ``` -看上去好像不对,但注意按照 google 的定义,4.14 肯定不会在 S 中被支持,而不确定的 5.x 要到 2020 年底才会揭晓,所以现在只是还未确定,这个不矛盾。 +看上去好像不对,但注意按照 google 的定义,4.14 肯定不会在 S 中被支持,而不确定的 5.x +要到 2020 年底才会揭晓,所以现在只是还未确定,这个不矛盾。 # 4. 编译 Android 内核 -理解了这些概念后我们可以开始干活了。关键是要把 Android 的配置 fragments 合并到缺省的针对 RISC-V 平台的配置下去。假设 common 仓库目录和 configs 仓库目录和 riscv64-linux 目录(有关 `riscv64-linux` 目录同样参考 [《QEMU 上运行 RISC-V 64 位版本的 Linux》](https://zhuanlan.zhihu.com/p/258394849) 一文)关系如下: +理解了这些概念后我们可以开始干活了。关键是要把 Android 的配置 fragments 合并到缺省的针 +对 RISC-V 平台的配置下去。假设 common 仓库目录和 configs 仓库目录和 riscv64-linux 目 +录(有关 `riscv64-linux` 目录同样参考 +[《QEMU 上运行 RISC-V 64 位版本的 Linux》](https://zhuanlan.zhihu.com/p/258394849) +一文)关系如下: ``` $ tree -L 2 @@ -169,7 +214,9 @@ $ cd configs $ git checkout android11-release ``` -然后进入 common 仓库,先清理一下,然后确保切换到 `android-5.4-stable` 分支 再执行合并配置操作,完成后直接编译即可,注意运行 `merge_config.sh` 脚本文件后会自动在内核源码根目录下生成 `.config` 文件。注意我这里只合并了 base 配置。 +然后进入 common 仓库,先清理一下,然后确保切换到 `android-5.4-stable` 分支 再执行合并 +配置操作,完成后直接编译即可,注意运行 `merge_config.sh` 脚本文件后会自动在内核源码根 +目录下生成 `.config` 文件。注意我这里只合并了 base 配置。 ``` $ cd common @@ -186,7 +233,10 @@ QEMU 中启动后检查内核版本信息: Linux (none) 5.4.61-00012-gbb168ca1805b #1 SMP PREEMPT Fri Sep 18 12:17:20 CST 2020 riscv64 GNU/Linux ``` -可以看到内核已经变成 5.4.61,而且注意和 defconfig 编译出的内核的一个区别,就是上述信息中出现了一个 `PREEMPT`,这说明内核的内核态抢占被打开了,这体现了 Android 对内核的特殊要求,作为主要应用于移动通讯设备的 Linux 版本,为了保证一些实时应用,这个选项是必须的。具体也可以查看 Android 的 android-base.config 配置 fragment 文件,中间的确有一行: +可以看到内核已经变成 5.4.61,而且注意和 defconfig 编译出的内核的一个区别,就是上述信息 +中出现了一个 `PREEMPT`,这说明内核的内核态抢占被打开了,这体现了 Android 对内核的特殊 +要求,作为主要应用于移动通讯设备的 Linux 版本,为了保证一些实时应用,这个选项是必须的。 +具体也可以查看 Android 的 android-base.config 配置 fragment 文件,中间的确有一行: ``` ...... diff --git a/articles/20201120-first-rv-android-mini-system.md b/articles/20201120-first-rv-android-mini-system.md index fe3f0c71d606590ebb6ac584981f52745711beec..d4f0ca18087ce56b389425d5bbc4e5f0cd66a42a 100644 --- a/articles/20201120-first-rv-android-mini-system.md +++ b/articles/20201120-first-rv-android-mini-system.md @@ -2,6 +2,11 @@ 文章标题:**第一个 RISC-V 上的“Android 最小系统”** +- 作者:汪辰 +- 联系方式: / + +文章大纲 + - [1. 需求分析](#1-需求分析) @@ -16,7 +21,8 @@ -经过这段时间以来的埋头苦干,目前我们已经可以在 RISC-V 的 QEMU 上启动一个 Android 的 “最小系统”。先来张图,有照为证。 +经过这段时间以来的埋头苦干,目前我们已经可以在 RISC-V 的 QEMU 上启动一个 Android 的 +“最小系统”。先来张图,有照为证。 ![](./diagrams/20201120-first-rv-android-mini-system/1.jpg) @@ -24,43 +30,89 @@ # 1. 需求分析 -我们的项目全称是 AOSP for RISC-V,目前所有源码开源在 github 上: (注:与本文工作对应的代码仓库已经备份到 ,具体参考 [项目 README 文件](https://gitee.com/aosp-riscv/working-group/blob/master/README_zh.md) 中 2021-01-xx 的日志记录)。说白了就是希望乘着 RISC-V 这阵春风将 Android 也移植到 RISC-V 上去。当然这个终极目标很大。 +我们的项目全称是 AOSP for RISC-V,目前所有源码开源在 github 上: +(注:与本文工作对应的代码仓库已经备份到 , +具体参考 [项目 README 文件](https://gitee.com/aosp-riscv/working-group/blob/master/README_zh.md) +中 2021-01-xx 的日志记录)。说白了就是希望乘着 RISC-V 这阵春风将 Android 也移植到 RISC-V 上去。当然这个终极目标很大。 -但目前我们还有一个小目标,用一句话描述就是:`基于 RISC-V 平台,实现在 QEMU 上跑起来 Android 的 kernel 部分,并将 Android 的 Shell 运行起来。` +但目前我们还有一个小目标,用一句话描述就是:`基于 RISC-V 平台,实现在 QEMU 上跑起来 +Android 的 kernel 部分,并将 Android 的 Shell 运行起来。` -基于以上目标,具体分析来说,也就是要实现一个最小的 Android 系统,这里的 **“最小系统”** 的含义就是所谓的 “bootable unix-style command line operating system”。传统意义上一个完整的 “最小系统” 如下图的左侧所描述,从下往上,最下面是硬件(注意:硬件不算我们的 “最小系统” 的一部分)。硬件上运行的第一层软件是操作系统内核 (Operating System Kernel),OS kernel 上是 [C 库 (C Library)](https://en.wikipedia.org/wiki/C_standard_library),基于 C Library 我们就可以搭建一个最小的文件系统,一个可以满足我们基本操作的文件系统本质上来说就是一堆命令行工具,这些命令行工具里至少要包含一个 init, 用于配合内核启动基本的登录 Shell,以及一个 Shell 用于和用户交互,用来调用其他的工具程序。有了这个 **“最小系统”**,我们的大目标就有了一个基础。 +基于以上目标,具体分析来说,也就是要实现一个最小的 Android 系统,这里的 **“最小系统”** +的含义就是所谓的 “bootable unix-style command line operating system”。传统意义上一 +个完整的 “最小系统” 如下图的左侧所描述,从下往上,最下面是硬件(注意:硬件不算我们的 +“最小系统” 的一部分)。硬件上运行的第一层软件是操作系统内核 (Operating System Kernel), +OS kernel 上是 [C 库 (C Library)](https://en.wikipedia.org/wiki/C_standard_library), +基于 C Library 我们就可以搭建一个最小的文件系统,一个可以满足我们基本操作的文件系统本质 +上来说就是一堆命令行工具,这些命令行工具里至少要包含一个 init, 用于配合内核启动基本的 +登录 Shell,以及一个 Shell 用于和用户交互,用来调用其他的工具程序。有了这个 **“最小系统”**, +我们的大目标就有了一个基础。 ![](./diagrams/20201120-first-rv-android-mini-system/2.jpg) 通过对 AOSP 的调研,我大致归纳了我们需要实现的工作如下: - 硬件部分:这里我们先采用 QEMU for RISC-V 来模拟。 -- OS Kernel:Android 的内核采用的就是 Linux,当然打上了自己的一些补丁。有关如何在 RISC-V 的 QEMU 上运行一个 Android 的内核,我们在另外一篇文章 [《编译一个 RISC-V 的 Android 内核》](./20200929-build-riscv-android-kernel.md) 中有过介绍 -- C Library:Android 有自己的 C 库,就是 bionic。它与 GNU C库(glibc)的不同之处在于,它是为内存和处理器能力较低的并且运行 Linux 的设备设计的。它基于 BSD 许可发布,而不是像 glibc 那样使用 GNU 公共许可证。 -- Rootfs:Android 有自己复杂的文件系统组织,作为我们实验的目标,我们需要的是一个最精简的最小文件系统,暂时没有必要将 Android 的完整系统移植过来,所以我选择了 [toybox](https://en.wikipedia.org/wiki/Toybox) 来实现我们的各种命令行工具。同学们可能会问为啥不用更著名的 [busybox](https://zh.wikipedia.org/wiki/BusyBox),原因还是和软件许可证有关,busybox 是 GPL,而 toybox 是 BSD,自然更符合 Android 的胃口,所以最后被包含在 AOSP 的源码树里的就是 toybox,而不是 busybox。另外需要提一下,由于 toybox 实现得十分简陋,我基于的 AOSP 版本中 toybox 自带的 shell 还无法正常工作,好在 Android 已经有了自己官方的 Shell,就是 [mksh](http://www.mirbsd.org/mksh.htm),所以对于 Shell 我们就直接用 mksh。 +- OS Kernel:Android 的内核采用的就是 Linux,当然打上了自己的一些补丁。有关如何在 RISC-V + 的 QEMU 上运行一个 Android 的内核,我们在另外一篇文章 + [《编译一个 RISC-V 的 Android 内核》](./20200929-build-riscv-android-kernel.md) + 中有过介绍 +- C Library:Android 有自己的 C 库,就是 bionic。它与 GNU C库(glibc)的不同之处在于, + 它是为内存和处理器能力较低的并且运行 Linux 的设备设计的。它基于 BSD 许可发布,而不是 + 像 glibc 那样使用 GNU 公共许可证。 +- Rootfs:Android 有自己复杂的文件系统组织,作为我们实验的目标,我们需要的是一个最精简 + 的最小文件系统,暂时没有必要将 Android 的完整系统移植过来,所以我选择了 + [toybox](https://en.wikipedia.org/wiki/Toybox) 来实现我们的各种命令行工具。同学们 + 可能会问为啥不用更著名的 [busybox](https://zh.wikipedia.org/wiki/BusyBox),原因还 + 是和软件许可证有关,busybox 是 GPL,而 toybox 是 BSD,自然更符合 Android 的胃口, + 所以最后被包含在 AOSP 的源码树里的就是 toybox,而不是 busybox。另外需要提一下,由于 + toybox 实现得十分简陋,我基于的 AOSP 版本中 toybox 自带的 shell 还无法正常工作, + 好在 Android 已经有了自己官方的 Shell,就是 [mksh](http://www.mirbsd.org/mksh.htm), + 所以对于 Shell 我们就直接用 mksh。 # 2. 移植工作介绍 -上面谈了整体移植工作需要做哪些内容。在具体的实现上其实还有很多的细节,由于我们的最终目标(将 AOSP 整体移植到 RISC-V 上)还远未实现,所以我先就我目前已经实现的内容简单梳理了一下,作为阶段总结记录在这里备忘: +上面谈了整体移植工作需要做哪些内容。在具体的实现上其实还有很多的细节,由于我们的最终目标 +(将 AOSP 整体移植到 RISC-V 上)还远未实现,所以我先就我目前已经实现的内容简单梳理了一 +下,作为阶段总结记录在这里备忘: - 运行平台(硬件):目前暂时采用 QEMU - AOSP 版本:基于 `android-10.0.0_r39` 的 tag -- 编译环境:AOSP 的编译已经完整迁移到 LLVM/CLANG,但在链接部分仍然使用的是 GNU tools。由于 AOSP 自带的 Prebuild 工具链不支持 RISC-V,所以我采用自己制作的 LLVM/Clang 和 GNU-tools。有关 LLVM/Clang,可以参考我的另一篇文章 [《制作一个针对 RISC-V 的 LLVM/Clang 编译器》](https://zhuanlan.zhihu.com/p/263550372);有关 GNU-tools 的制作,参考另一篇文章 [《在 QEMU 上运行 RISC-V 64 位版本的 Linux》](https://zhuanlan.zhihu.com/p/258394849)。 -- 内核的移植,参考我的另一篇文章 [《编译一个 RISC-V 的 Android 内核》](./20200929-build-riscv-android-kernel.md),我采用的内核版本是 common 仓库的 `android-5.4-stable` 加上 configs 仓库的 `android11-release`。 -- BIONIC 库的移植,如前所述,基于 `android-10.0.0_r39` 的 tag,考虑到第一期的需求,只实现 libc 的静态库,未实现 libc 的动态库以及 libm/libdl/libstdc++/linker。也就是说下面的 toybox 和 mksh 这些可执行程序都是静态链接的。libc 是 bionic 的最主要部分,组成内容相当庞杂,其中的主要组件以及组件之间的依赖关系简单总结如下图: +- 编译环境:AOSP 的编译已经完整迁移到 LLVM/CLANG,但在链接部分仍然使用的是 GNU tools。 + 由于 AOSP 自带的 Prebuild 工具链不支持 RISC-V,所以我采用自己制作的 LLVM/Clang 和 + GNU-tools。有关 LLVM/Clang,可以参考我的另一篇文章 + [《制作一个针对 RISC-V 的 LLVM/Clang 编译器》](https://zhuanlan.zhihu.com/p/263550372); + 有关 GNU-tools 的制作,参考另一篇文章 + [《在 QEMU 上运行 RISC-V 64 位版本的 Linux》](https://zhuanlan.zhihu.com/p/258394849)。 +- 内核的移植,参考我的另一篇文章 [《编译一个 RISC-V 的 Android 内核》](./20200929-build-riscv-android-kernel.md), + 我采用的内核版本是 common 仓库的 `android-5.4-stable` 加上 configs 仓库的 `android11-release`。 +- BIONIC 库的移植,如前所述,基于 `android-10.0.0_r39` 的 tag,考虑到第一期的需求, + 只实现 libc 的静态库,未实现 libc 的动态库以及 libm/libdl/libstdc++/linker。也就是 + 说下面的 toybox 和 mksh 这些可执行程序都是静态链接的。libc 是 bionic 的最主要部分, + 组成内容相当庞杂,其中的主要组件以及组件之间的依赖关系简单总结如下图: ![](./diagrams/20201120-first-rv-android-mini-system/3.jpg) -- toybox:如前所述,基于 `android-10.0.0_r39` 的 tag,但做了相当多的裁剪。因为 Android 中的 toybox 引入了许多 Android 特有的特性,比如 SELinux 和加密等等,为了不牵扯太多的精力,我在移植时 disable 这些功能,只保留了基本的一些常用功能。 +- toybox:如前所述,基于 `android-10.0.0_r39` 的 tag,但做了相当多的裁剪。因为 + Android 中的 toybox 引入了许多 Android 特有的特性,比如 SELinux 和加密等等,为了不 + 牵扯太多的精力,我在移植时 disable 这些功能,只保留了基本的一些常用功能。 - mksh:Shell 部分看上去比较单纯,所以直接拿来,只要确保编译没有问题即可。 -- 整个移植过程中还有一块很大的工作是涉及构建系统的搭建。我没有使用 AOSP 自带原生的 Soong 系统,因为在前期的预研过程中我发现在现有的 AOSP 构建系统上(即传统的 lunch + m 操作)要加入一个新的 ARCH 支持并确保将修改局限在自己关心的 module 上是一件非常不容易的事情。AOSP 的构建系统过于复杂和成熟,但对后来者并不友好。为了降低风险,先期聚焦重点关注部分,我选择了采用 make 重写了需要先期移植的模块(bionic/toybox/mksh)。当然后面还是要找机会去动 AOSP 的 Soong,这个留在后面再做吧。 +- 整个移植过程中还有一块很大的工作是涉及构建系统的搭建。我没有使用 AOSP 自带原生的 + Soong 系统,因为在前期的预研过程中我发现在现有的 AOSP 构建系统上(即传统的 lunch + m 操作) + 要加入一个新的 ARCH 支持并确保将修改局限在自己关心的 module 上是一件非常不容易的事情。 + AOSP 的构建系统过于复杂和成熟,但对后来者并不友好。为了降低风险,先期聚焦重点关注部分, + 我选择了采用 make 重写了需要先期移植的模块(bionic/toybox/mksh)。当然后面还是要找机 + 会去动 AOSP 的 Soong,这个留在后面再做吧。 # 3. 实验步骤 -经过这段时间的工作,已经初步完成了以上小目标,至少可以在 QEMU 上启动一个上面我们定义的 **“最小 Android 系统”**。相关移植修改已经公开开源在 github。如果有同学希望尝试,可以参考以下步骤操作。也欢迎大家测试,提 PR,或者直接参与我们的 AOSP 移植 RISC-V 的开源项目。 +经过这段时间的工作,已经初步完成了以上小目标,至少可以在 QEMU 上启动一个上面我们定义的 +**“最小 Android 系统”**。相关移植修改已经公开开源在 github。如果有同学希望尝试,可以参 +考以下步骤操作。也欢迎大家测试,提 PR,或者直接参与我们的 AOSP 移植 RISC-V 的开源项目。 ## 3.1. 构建 GNU 工具链 -参考 [在 QEMU 上运行 RISC-V 64 位版本的 Linux](https://zhuanlan.zhihu.com/p/258394849) 的 “1. 制作交叉工具链 riscv-gnu-toolchain” 章节。 +参考 [在 QEMU 上运行 RISC-V 64 位版本的 Linux](https://zhuanlan.zhihu.com/p/258394849) +的 “1. 制作交叉工具链 riscv-gnu-toolchain” 章节。 完成后将 GCC 的安装目录,譬如 `/opt/riscv64/bin` 导出到环境变量 PATH 中。 @@ -72,7 +124,8 @@ ## 3.3. 构建 QEMU -参考 [《在 QEMU 上运行 RISC-V 64 位版本的 Linux》](https://zhuanlan.zhihu.com/p/258394849) 的 “2. 编译 QEMU” 章节。 +参考 [《在 QEMU 上运行 RISC-V 64 位版本的 Linux》](https://zhuanlan.zhihu.com/p/258394849) +的 “2. 编译 QEMU” 章节。 编译完成后将 QEMU 的安装目录 譬如 `/opt/qemu/bin` 导出到环境变量 PATH 中。 @@ -99,7 +152,9 @@ git submodule update --progress make ``` -如果要制作根文件系统的 img,可以运行 `mkrootfs.sh` 这个脚本,注意在运行前可以检查一下 `mkrootfs.sh` 这个脚本,自行调整并确保 `PATH_QEMU` 这个变量指向正确的 QEMU 安装目录,譬如前文 3.3 章节中的 `/opt/qemu/bin`。 +如果要制作根文件系统的 img,可以运行 `mkrootfs.sh` 这个脚本,注意在运行前可以检查一下 +`mkrootfs.sh` 这个脚本,自行调整并确保 `PATH_QEMU` 这个变量指向正确的 QEMU 安装目录, +譬如前文 3.3 章节中的 `/opt/qemu/bin`。 然后以 sudo 权限执行 `mkrootfs.sh` ``` @@ -139,7 +194,9 @@ out ## 3.6. 运行 -参考 [在 QEMU 上运行 RISC-V 64 位版本的 Linux](https://zhuanlan.zhihu.com/p/258394849) 一文中的章节 “5. 启动运行” 执行如下命令,记得将内核 Image 和 rootfs.img 的路径设置为自己机器上的正确位置,我这里的例子如下: +参考 [在 QEMU 上运行 RISC-V 64 位版本的 Linux](https://zhuanlan.zhihu.com/p/258394849) +一文中的章节 “5. 启动运行” 执行如下命令,记得将内核 Image 和 rootfs.img 的路径设置为 +自己机器上的正确位置,我这里的例子如下: ``` $ qemu-system-riscv64 -M virt -m 256M -nographic -kernel ../../../aosp-kernel/common/arch/riscv/boot/Image -drive file=out/rootfs.img,format=raw,id=hd0 -device virtio-blk-device,drive=hd0 -append "root=/dev/vda rw console=ttyS0" @@ -149,8 +206,11 @@ $ qemu-system-riscv64 -M virt -m 256M -nographic -kernel ../../../aosp-kernel/co ![](./diagrams/20201120-first-rv-android-mini-system/4.jpg) -最后注意一下由于还未实现关机命令,所以退出时只能采用 `Ctrl+A X`,也就是同时按下 Ctrl 和 A 键,同时松开再按下 X 键即可强制退出 QEMU。 +最后注意一下由于还未实现关机命令,所以退出时只能采用 `Ctrl+A X`,也就是同时按下 Ctrl +和 A 键,同时松开再按下 X 键即可强制退出 QEMU。 -目前这个系统能提供的功能也就这么多了,虽然还很不完善,但毕竟已经可以运行起来了,而且提请大家注意的是它绝对是一个 **“货真价实,血统纯正”** 的 **“Android 最小系统”**,更何况它还是一个运行在 **RISC-V** 体系架构平台上的 Android! +目前这个系统能提供的功能也就这么多了,虽然还很不完善,但毕竟已经可以运行起来了,而且提请 +大家注意的是它绝对是一个 **“货真价实,血统纯正”** 的 **“Android 最小系统”**,更何况它 +还是一个运行在 **RISC-V** 体系架构平台上的 Android! diff --git a/articles/20201215-opensrc-on-gitee.md b/articles/20201215-opensrc-on-gitee.md index 28a7ec57d4fd222068b97b8d8329ddae49a9ee43..c433681d1a040c40c3d21918bce8b72b172fc820 100644 --- a/articles/20201215-opensrc-on-gitee.md +++ b/articles/20201215-opensrc-on-gitee.md @@ -2,19 +2,38 @@ 文章标题:**AOSP-RISCV 的开源仓库在 Gitee 上新建了镜像** -前阵子在知乎上给大家介绍了我们在移植 AOSP 到 RISC-V 上的第一步: [第一个 RISC-V 上的“Android 最小系统”](./20201120-first-rv-android-mini-system.md)。 +- 作者:汪辰 +- 联系方式: / + +文章大纲 + + + + + +前阵子在知乎上给大家介绍了我们在移植 AOSP 到 RISC-V 上的第一步: +[第一个 RISC-V 上的“Android 最小系统”](./20201120-first-rv-android-mini-system.md)。 ​ -目前所有的工作成果都是开源在 Github 上的,移植改动涉及的子仓库达到 9 个,所有源码下载下来达到 537M+,由于 Github 的访问速度较慢,这么多东西要全部下载下来会非常的慢。 +目前所有的工作成果都是开源在 Github 上的,移植改动涉及的子仓库达到 9 个,所有源码下载下 +来达到 537M+,由于 Github 的访问速度较慢,这么多东西要全部下载下来会非常的慢。 -为了方便国内的小伙伴访问我们的开源仓库,最近我们在国内知名的开源托管仓库平台 Gitee 上也建立了相应的镜像: +为了方便国内的小伙伴访问我们的开源仓库,最近我们在国内知名的开源托管仓库平台 Gitee 上也 +建立了相应的镜像: -注意:由于 2021 年 1 月 平头哥的 [开源工作](https://github.com/T-head-Semi/aosp-riscv),我们于 2021 年初暂停了 PLCT lab 的相关 AOSP 移植工作。所有原 下的代码仓库(除了 working-group)都备份到 Gitee 的 aosp-riscv-bionic-porting 组织下,具体网址是:。 +注意:由于 2021 年 1 月 平头哥的 [开源工作](https://github.com/T-head-Semi/aosp-riscv), +我们于 2021 年初暂停了 PLCT lab 的相关 AOSP 移植工作。所有原 +和 下的代码仓库(除了 working-group)都备份到 Gitee +的 aosp-riscv-bionic-porting 组织下,具体网址是:。 -大家如果对我们 2021 年 1 月份之前的工作(具体参考 [《第一个 RISC-V 上的“Android 最小系统”》](./20201120-first-rv-android-mini-system.md) 这篇文章的介绍)感兴趣的话。相关的代码可以参考上面提到的 aosp-riscv-bionic-porting。 +大家如果对我们 2021 年 1 月份之前的工作(具体参考 +[《第一个 RISC-V 上的“Android 最小系统”》](./20201120-first-rv-android-mini-system.md) +这篇文章的介绍)感兴趣的话。相关的代码可以参考上面提到的 aosp-riscv-bionic-porting。 -原有的 将用于我们新阶段的 AOSP for RISC-V 的开发工作, +原有的 将用于我 +们新阶段的 AOSP for RISC-V 的开发工作, -另外,所有 AOSP for RISC-V 的开源工作状态我们都会定期更新在 github/gitee 上,大家可以在线访问,以 github 的网页为例: +另外,所有 AOSP for RISC-V 的开源工作状态我们都会定期更新在 github/gitee 上,大家可以 +在线访问,以 github 的网页为例: 中文版: ​ diff --git a/articles/20201230-android-build-sum.md b/articles/20201230-android-build-sum.md index fd4249c3370347147fb3c7b2ee5c4e77436535ac..c1afd86061735d699a32daa13be90b839a40bfb5 100644 --- a/articles/20201230-android-build-sum.md +++ b/articles/20201230-android-build-sum.md @@ -2,6 +2,11 @@ 文章标题:**AOSP Build 背后涉及的相关知识汇总** +- 作者:汪辰 +- 联系方式: / + +文章大纲 + - [1. AOSP 构造系统的演进](#1-aosp-构造系统的演进) @@ -22,66 +27,100 @@ are read by [Kati], and generate a ninja file instead of being executed directly. That's combined with a ninja file read by Soong so that the build graph of the two systems can be combined and run as one. -查了一下,历史大致如下:Android 6.0 (包括 6.0)之前采用传统的纯 Makefile 编译。从 7.0 开始引入 Soong 希望加速 Make,但由于改动太大,所以到目前为止 Soong 并没有全部替代 make,一直在改进中,直到 10.0 依然还是一个混杂的系统。但 Soong 是主流,相信未来一定会全面代替 make。 +查了一下,历史大致如下:Android 6.0 (包括 6.0)之前采用传统的纯 Makefile 编译。从 +7.0 开始引入 Soong 希望加速 Make,但由于改动太大,所以到目前为止 Soong 并没有全部替代 +make,一直在改进中,直到 10.0 依然还是一个混杂的系统。但 Soong 是主流,相信未来一定会 +全面代替 make。 Soong 引入后的编译过程大致下图所示,后面有机会再详细分析具体的细节: ![](./diagrams/20201230-android-build-sum/aosp_build_procedure.png) -- Soong 的自举(bootstrap)。这个步骤会编译 Soong 的核心组件,从源码开始,无到有创建并搭建好 Soong 子系统。 -- 目前已经迁移到 Soong 框架的模块都会在自己的模块目录下提供一个叫做 `Android.bp` 文件,Soong 会扫描并收集所有的 Android.bp,从而生成 `out/soong/build.ninja` 文件,这部分的 bp 文件语法处理涉及 Soong 中的 Blueprint 子模块。 -- 目前还没有迁移到 Soong 框架的模块依然按照老规矩提供 `Android.mk` 文件,目前由 Kati 程序负责收集所有的 `Android.mk` 并生成 `out/build-.ninja` 文件。 +- Soong 的自举(bootstrap)。这个步骤会编译 Soong 的核心组件,从源码开始,无到有创建并 + 搭建好 Soong 子系统。 +- 目前已经迁移到 Soong 框架的模块都会在自己的模块目录下提供一个叫做 `Android.bp` 文件, + Soong 会扫描并收集所有的 Android.bp,从而生成 `out/soong/build.ninja` 文件,这部分 + 的 bp 文件语法处理涉及 Soong 中的 Blueprint 子模块。 +- 目前还没有迁移到 Soong 框架的模块依然按照老规矩提供 `Android.mk` 文件,目前由 Kati + 程序负责收集所有的 `Android.mk` 并生成 `out/build-.ninja` 文件。 - Soong 负责将两个 ninja 文件组合成一个 `out/combined-.ninja` 文件。 -- 以最终的 `out/combined-.ninja` 作为输入执行 Ninja,调用实际的编译工具,譬如 gcc 等执行实际的编译操作,完成最终的编译。 +- 以最终的 `out/combined-.ninja` 作为输入执行 Ninja,调用实际的编译工具, + 譬如 gcc 等执行实际的编译操作,完成最终的编译。 # 2. 涉及相关技术简介 ## 2.1. Ninja -Ninja (这个词来源于日语的 “忍者”,是不是感觉够 “狠”;-))是一个致力于速度的小型编译驱动(类似于 Make);它去除了 Make 中对编译过程的复杂的条件判断处理,只保留了对构建步骤的描述。 +Ninja (这个词来源于日语的 “忍者”,是不是感觉够 “狠”;-))是一个致力于速度的小型编译驱动 +(类似于 Make);它去除了 Make 中对编译过程的复杂的条件判断处理,只保留了对构建步骤的描述。 它具备两个主要特点: -1、简单,因为只保留了非常基本的对 build 操作的描述,所以执行 ninja 所花费的时间可以压缩到极致。但这也意味着对于复杂的项目,ninja 必须依赖于其他 “前端” 程序,一般是采用像 python 或者 go 等高级语言构造的更高一层的处理工具完成对项目编译步骤的前期处理,生成 build.ninja 这样的构建步骤描述文件供 ninja 完成最终的编译操作; +1、简单,因为只保留了非常基本的对 build 操作的描述,所以执行 ninja 所花费的时间可以压缩 +到极致。但这也意味着对于复杂的项目,ninja 必须依赖于其他 “前端” 程序,一般是采用像 +python 或者 go 等高级语言构造的更高一层的处理工具完成对项目编译步骤的前期处理,生成 +build.ninja 这样的构建步骤描述文件供 ninja 完成最终的编译操作; -2、快速。它的设计目标就是为了更快的编译,所以在功能实现上除了简单,如果和快速的目标有冲突则优先考虑快速。 +2、快速。它的设计目标就是为了更快的编译,所以在功能实现上除了简单,如果和快速的目标有冲突 +则优先考虑快速。 -Ninja 核心是由 C/C++ 编写的,同时有一部分辅助功能由 python 和 shell 实现。由于其开源性,所以可以利用 ninja 的开源代码进行各种个性化的编译定制。Ninja 的代码仓库地址:。 +Ninja 核心是由 C/C++ 编写的,同时有一部分辅助功能由 python 和 shell 实现。由于其开源性, +所以可以利用 ninja 的开源代码进行各种个性化的编译定制。Ninja 的代码仓库地址:。 -Android 引入 Ninja 的主要目的就是为了加快编译速度。作为在具体生成 ninja 文件的前端,Google 自创了 Soong 框架,下一章节介绍。 +Android 引入 Ninja 的主要目的就是为了加快编译速度。作为在具体生成 ninja 文件的前端, +Google 自创了 Soong 框架,下一章节介绍。 更详细的对 ninja 的介绍,以后有空再总结吧。 ## 2.2. Soong 和 Blueprint -Soong 是 Google 采用 Go 语言搭建的一个项目构建体系,和 ninja 一起用于替代原先的基于 make 的一套构建体系。Blueprint 和 Soong 都是由 Golang 写的项目。 从 Android Nougat 开始,`prebuilts/go/` 目录下新增了 Golang 所需的运行环境,在编译时使用。 +Soong 是 Google 采用 Go 语言搭建的一个项目构建体系,和 ninja 一起用于替代原先的基于 +make 的一套构建体系。Blueprint 和 Soong 都是由 Golang 写的项目。 从 Android Nougat +开始,`prebuilts/go/` 目录下新增了 Golang 所需的运行环境,在编译时使用。 Soong 的源码在 AOSP 源码目录 `build/soong` 下。 有关 Soong 的介绍可以阅读 AOSP 源码的`build/soong/README.md`。 -> The Soong build system was introduced in Android 7.0 (Nougat) to replace Make. It leverages the Kati GNU Make clone tool and Ninja build system component to speed up builds of Android. +> The Soong build system was introduced in Android 7.0 (Nougat) to replace Make. +> It leverages the Kati GNU Make clone tool and Ninja build system component to +> speed up builds of Android. -就和基于 make 我们需要写 Makefile(在 AOSP 里是 Android.mk) 一样,基于 Soong 我们需要为每个模块编写 Android.bp 文件来描述组件中各个编译目标(以 module 为单位)之间的依赖关系和组成。之所以后缀名为 `.bp` 是因为 `Android.bp` 采用了一种叫做 Blueprint 语法,这种语法格式类似于 [Bazel BUILD files](https://docs.bazel.build/versions/master/be/overview.html)。 +就和基于 make 我们需要写 Makefile(在 AOSP 里是 Android.mk) 一样,基于 Soong 我们需 +要为每个模块编写 Android.bp 文件来描述组件中各个编译目标(以 module 为单位)之间的依赖 +关系和组成。之所以后缀名为 `.bp` 是因为 `Android.bp` 采用了一种叫做 Blueprint 语法, +这种语法格式类似于 [Bazel BUILD files](https://docs.bazel.build/versions/master/be/overview.html)。 -对 BP 语法格式的解析由一个独立的 Blueprint 模块完成,Blueprint 的源码在 AOSP 源码的 `build/blueprint` 目录下。该模块相对比较独立,可以单独编译、使用。任何实际的应用系统都可以在 Blueprint 的基础上定制自己语法。而在 Android 系统上,这个实际的应用系统就是 Soong。所以说 Soong 是 Android 强相关的,而 Blueprint 实际上相对独立一些,我们也可以基于 Blueprint 模块开发自己的应用系统。 +对 BP 语法格式的解析由一个独立的 Blueprint 模块完成,Blueprint 的源码在 AOSP 源码的 +`build/blueprint` 目录下。该模块相对比较独立,可以单独编译、使用。任何实际的应用系统都 +可以在 Blueprint 的基础上定制自己语法。而在 Android 系统上,这个实际的应用系统就是 Soong。 +所以说 Soong 是 Android 强相关的,而 Blueprint 实际上相对独立一些,我们也可以基于 +Blueprint 模块开发自己的应用系统。 -因为 Android 在 Blueprint 的基础上定制了 自己的 Soong 系统。所以针对 AOSP,其自身定义了一套 Android 私有的 BP 语法,可以参考 AOSP 官方文档 [“Android.bp file format”](https://source.android.google.cn/setup/build#androidbp_file_format) 了解详细内容。 +因为 Android 在 Blueprint 的基础上定制了 自己的 Soong 系统。所以针对 AOSP,其自身定义 +了一套 Android 私有的 BP 语法,可以参考 AOSP 官方文档 +[“Android.bp file format”](https://source.android.google.cn/setup/build#androidbp_file_format) +了解详细内容。 ## 2.3. Kati -其代码在 AOSP 源码的 `build/kati` 目录下。如果相对 Kati 有更多了解,可以阅读 AOSP 源码下的 `build/kati/README.md` 和 `build/kati/INTERNALS.md` 文件。 +其代码在 AOSP 源码的 `build/kati` 目录下。如果相对 Kati 有更多了解,可以阅读 AOSP 源 +码下的 `build/kati/README.md` 和 `build/kati/INTERNALS.md` 文件。 大概的情况是 Kati 出现得比 Soong 要早,当初担负了加速 AOSP 编译的重任。 > kati is an experimental GNU make clone. > The main goal of this tool is to speed-up incremental build of Android. -随着 Soong 的出现以及成熟,Kati 的角色也发生了转变,目前退化为仅用于处理遗留的基于 make 的 `.mk` 文件,将其转换 ninja 文件。 +随着 Soong 的出现以及成熟,Kati 的角色也发生了转变,目前退化为仅用于处理遗留的基于 +make 的 `.mk` 文件,将其转换 ninja 文件。 -> Currently, kati does not offer a faster build by itself. It instead converts your Makefile to a ninja file. +> Currently, kati does not offer a faster build by itself. It instead converts +> your Makefile to a ninja file. -注意当前的 Kati 程序是由 C++ 编写的,而非 Google 官方御用的 Go。大致的历史是早期的 Kati 的确是用 Go 编写的,但因为 Go 的某些 “低效” 原因,开发团队最后还是选择了 C++ :),所以目前的 Kati 有时候又叫 CKati。 +注意当前的 Kati 程序是由 C++ 编写的,而非 Google 官方御用的 Go。大致的历史是早期的 +Kati 的确是用 Go 编写的,但因为 Go 的某些 “低效” 原因,开发团队最后还是选择了 C++ :), +所以目前的 Kati 有时候又叫 CKati。 后面我们主要关心 Soong,对 Kati 的了解就到此为止吧。 diff --git a/articles/20210111-soong-process.md b/articles/20210111-soong-process.md index 68a1b0aea64f6fac739dd39d77aab13bc7408e52..0792e4d9f29024b252cd5a66c3138eb6599b1337 100644 --- a/articles/20210111-soong-process.md +++ b/articles/20210111-soong-process.md @@ -2,6 +2,11 @@ 文章标题:**AOSP Soong 创建过程详解** +- 作者:汪辰 +- 联系方式: / + +文章大纲 + - [1. 前言](#1-前言) @@ -19,11 +24,15 @@ # 1. 前言 -前阵子总结了一下 AOSP Build 背后涉及的相关知识,具体可以参考这篇知乎文章 [《AOSP Build 背后涉及的相关知识汇总》](./20201230-android-build-sum.md)。当时总结了 AOSP 引入 Soong 后的编译过程,参考如下图所示: +前阵子总结了一下 AOSP Build 背后涉及的相关知识,具体可以参考这篇知乎文章 +[《AOSP Build 背后涉及的相关知识汇总》](./20201230-android-build-sum.md)。当时总结了 +AOSP 引入 Soong 后的编译过程,参考如下图所示: ![](./diagrams/20210111-soong-process/aosp-build-procedure-overview.jpg) -这个流程图是一个非常 high-level 的描述,特别是对 Soong 是如何将输入的各个模块的 Android.bp 文件转化为 `out/soong/build.ninja` 只是一笔带过了。由于移植的需要,最近还是仔细又看了一下,这篇文章就来把这个过程的细节再扒拉扒拉,顺便记着备忘。 +这个流程图是一个非常 high-level 的描述,特别是对 Soong 是如何将输入的各个模块的 +Android.bp 文件转化为 `out/soong/build.ninja` 只是一笔带过了。由于移植的需要,最近还 +是仔细又看了一下,这篇文章就来把这个过程的细节再扒拉扒拉,顺便记着备忘。 首先提醒大家一下,今天这篇文章关注的是下图中用红色方框圈起来的部分。 @@ -31,11 +40,17 @@ # 2. 两个入口,一个结局 -大家现在应该已经知道,当前 AOSP 的编译框架(截止我目前实验的 Android 10,貌似 11 也还没有完全清理干净)还是保留了 make 和 soong 两套,后续的 roadmap 会逐渐用 soong 方式替换掉剩余的 make,这样就完全统一为 Soong 了。但至少现在 AOSP 依然存在两套构造的入口,基于 make 的和基于 Soong 的。从后面的分析我们可以看出,在目前最新版本的 AOSP 中,缺省情况下,make 流程内部已经被悄悄替换为 soong 了。 +大家现在应该已经知道,当前 AOSP 的编译框架(截止我目前实验的 Android 10,貌似 11 也还 +没有完全清理干净)还是保留了 make 和 soong 两套,后续的 roadmap 会逐渐用 soong 方式替 +换掉剩余的 make,这样就完全统一为 Soong 了。但至少现在 AOSP 依然存在两套构造的入口,基 +于 make 的和基于 Soong 的。从后面的分析我们可以看出,在目前最新版本的 AOSP 中,缺省情况 +下,make 流程内部已经被悄悄替换为 soong 了。 ## 2.1. 入口之一:make -我们先简单看一下基于 make 的构造流程。所谓 make 流程,指在操作上输入 `make` 方式的操作模式。当我们在 AOSP 总目录下 make 时,默认寻找当前路径下的 Makefile。看一下这个文件,发现没有什么特别的。 +我们先简单看一下基于 make 的构造流程。所谓 make 流程,指在操作上输入 `make` 方式的操作 +模式。当我们在 AOSP 总目录下 make 时,默认寻找当前路径下的 Makefile。看一下这个文件,发 +现没有什么特别的。 ``` $ cat Makefile @@ -64,13 +79,20 @@ $(sort $(MAKECMDGOALS)) : run_soong_ui endif # KATI -关注一下这里加黑加粗的 **`ifndef ... else ... endif`**。默认版本下 `KATI` 没有定义,所以走的就是 **`ifndef ... else`** 之间的这段流程。 +关注一下这里加黑加粗的 **`ifndef ... else ... endif`**。默认版本下 `KATI` 没有定义, +所以走的就是 **`ifndef ... else`** 之间的这段流程。 -其中 `MAKECMDGOALS` 这个变量是 make 执行时后面的参数赋值,以上写法的效果就是无论我们以任何方式执行 make 的时候都会执行 `run_soong_ui` 这个伪目标(.PHONY),而 `run_soong_ui` 这个伪目标的 action 本质上就是在调用 `build/soong/soong_ui.bash`。这个脚本干了什么我们稍后分析,但从脚本的名字上你是不是感受到了一丝丝 soong 的气息了呢?是的,正是这个脚本实现了 android 从 Make 切换为 Soong,后面就跟传统的 make 没有丁点关系了。有关 `run_soong_ui` 我们在分析正宗的基于 soong 的构造流程中再来看。 +其中 `MAKECMDGOALS` 这个变量是 make 执行时后面的参数赋值,以上写法的效果就是无论我们以 +任何方式执行 make 的时候都会执行 `run_soong_ui` 这个伪目标(.PHONY),而 `run_soong_ui` +这个伪目标的 action 本质上就是在调用 `build/soong/soong_ui.bash`。这个脚本干了什么我 +们稍后分析,但从脚本的名字上你是不是感受到了一丝丝 soong 的气息了呢?是的,正是这个脚本 +实现了 android 从 Make 切换为 Soong,后面就跟传统的 make 没有丁点关系了。有关 `run_soong_ui` +我们在分析正宗的基于 soong 的构造流程中再来看。 ## 2.2. 入口之二:m -我们重点来看看标准的基于 soong 构建过程,这个只要是编译过 Android 的同学再熟悉不过的了,标准的构造操作步骤分三步: +我们重点来看看标准的基于 soong 构建过程,这个只要是编译过 Android 的同学再熟悉不过的了, +标准的构造操作步骤分三步: ``` $ source build/envsetup.sh @@ -78,8 +100,11 @@ $ lunch [-] $ m [] [] [=...] ``` -- 第一步执行 `. build/envsetup.sh`。`build/envsetup.sh` 实际只是一个符号链接,指向 `build/make/envsetup.sh`,这个脚本定义了用于编译的很多命令,包括标准构建流程中的 `m`/`mm`/`mmm` 等,所以 source 这个脚本相当于导入了这些命令函数,为第三步做准备。 -- 第二步暂略不表,大致的行为是通过菜单选择定义了一些最基本的环境变量,执行后会打印出来,也是为第三步 m 做准备。 +- 第一步执行 `. build/envsetup.sh`。`build/envsetup.sh` 实际只是一个符号链接,指向 + `build/make/envsetup.sh`,这个脚本定义了用于编译的很多命令,包括标准构建流程中的 + `m`/`mm`/`mmm` 等,所以 source 这个脚本相当于导入了这些命令函数,为第三步做准备。 +- 第二步暂略不表,大致的行为是通过菜单选择定义了一些最基本的环境变量,执行后会打印出来, + 也是为第三步 m 做准备。 - 第三步执行构造,以执行 `m` 为例,我们来看看定义在 `build/make/envsetup.sh` 中的这个函数:
@@ -95,11 +120,15 @@ function m()
 }
 
-是不是很熟悉,这个函数的核心同样是调用了 `build/soong/soong_ui.bash` 这个脚本。所以说所谓的 “殊途同归”,现在我们知道执行 make 和 m 最后都是同样的结果。而这个 `build/soong/soong_ui.bash` 脚本正是进入 Soong 世界的统一入口。下面我们就不再区分 make 和 m,重点来看看 Soong 的构造过程。 +是不是很熟悉,这个函数的核心同样是调用了 `build/soong/soong_ui.bash` 这个脚本。所以说 +所谓的 “殊途同归”,现在我们知道执行 make 和 m 最后都是同样的结果。而这个 +`build/soong/soong_ui.bash` 脚本正是进入 Soong 世界的统一入口。下面我们就不再区分 +make 和 m,重点来看看 Soong 的构造过程。 # 3. 站在 Soong 的门前并踹上一脚 -严格地说,进入 `build/soong/soong_ui.bash` 的时候 Soong 这个东西除了一堆源码,还没有被创建出来,而 `build/soong/soong_ui.bash` 的主要作用就是先把这个门(UI)给做出来。 +严格地说,进入 `build/soong/soong_ui.bash` 的时候 Soong 这个东西除了一堆源码,还没有 +被创建出来,而 `build/soong/soong_ui.bash` 的主要作用就是先把这个门(UI)给做出来。 ![](./diagrams/20210111-soong-process/phase-0.jpg) @@ -122,21 +151,38 @@ cd ${TOP} - 设置 GOROOT 环境变量,指向 prebuild 的 go 编译工具链 - 定义一些函数,特别地 `soong_build_go()` 这个函数会在第二步中被调用。 - - 导入 `${TOP}/build/blueprint/microfactory/microfactory.bash` 这个脚本,这个脚本中定义了其他辅助函数,特别注意 `build_go()` 这个函数,这个函数会被 `soong_build_go()` 调用执行实际的动作,结果是会根据参数编译 go 源码生成相应的程序,这里简单举个例子理解一下:`build_go soong_ui android/soong/cmd/soong_ui` 就是根据 AOSP 源码树目录 `soong/cmd/soong_ui` 的 package 生成一个可执行程序叫 soong_ui。 - -- 第二步:`soong_build_go soong_ui android/soong/cmd/soong_ui`。其作用是调用 `soong_build_go` 函数。这个函数有两个参数,从第一步的分析可以知道,`soong_build_go` 实际上是一个对 `build_go()` 函数的调用封装,所以以上语句等价于 `build_go soong_ui android/soong/cmd/soong_ui`。第一参数 `soong_ui` 是指定了编译生成的可执行程序的名字, `soong_ui` 是一个用 go 语言写的程序,也是 Soong 的实际执行程序。在第二个参数告诉 `soong_build_go` 函数,`soong_ui` 程序的源码在哪里,这里制定了其源码路径 `android/soong/cmd/soong_ui`(实际对应的位置是 `build/soong/cmd/soong_ui`) - -- 第三步:就是在前述步骤的基础上通过 exec 命令创建进程执行上一步生成的 `soong_ui`, 并接受所有参数并执行,这一步相当于等价替换了原来传统意义上的 `make $@`。举个例子,假如我们调用了 `m libart`,最终等价于调用 `out/soong_ui --make-mode libart`,注意这里的 `--make-mode` 是在 `m` 函数里引入的。 - -总而言之,`build/soong/soong_ui.bash` 的最终效果就是帮助我们制作出了一个叫做 `soong_ui` 的应用程序,放在 `out` 下。然后一脚将门踹开,执行这个程序,从而进入 Soong 的世界。 + - 导入 `${TOP}/build/blueprint/microfactory/microfactory.bash` 这个脚本,这个脚 + 本中定义了其他辅助函数,特别注意 `build_go()` 这个函数,这个函数会被 `soong_build_go()` + 调用执行实际的动作,结果是会根据参数编译 go 源码生成相应的程序,这里简单举个例子理解 + 一下:`build_go soong_ui android/soong/cmd/soong_ui` 就是根据 AOSP 源码树目录 + `soong/cmd/soong_ui` 的 package 生成一个可执行程序叫 soong_ui。 + +- 第二步:`soong_build_go soong_ui android/soong/cmd/soong_ui`。其作用是调用 + `soong_build_go` 函数。这个函数有两个参数,从第一步的分析可以知道,`soong_build_go` + 实际上是一个对 `build_go()` 函数的调用封装,所以以上语句等价于 + `build_go soong_ui android/soong/cmd/soong_ui`。第一参数 `soong_ui` 是指定了编译 + 生成的可执行程序的名字, `soong_ui` 是一个用 go 语言写的程序,也是 Soong 的实际执行 + 程序。在第二个参数告诉 `soong_build_go` 函数,`soong_ui` 程序的源码在哪里,这里制定 + 了其源码路径 `android/soong/cmd/soong_ui`(实际对应的位置是 `build/soong/cmd/soong_ui`) + +- 第三步:就是在前述步骤的基础上通过 exec 命令创建进程执行上一步生成的 `soong_ui`, 并 + 接受所有参数并执行,这一步相当于等价替换了原来传统意义上的 `make $@`。举个例子,假如 + 我们调用了 `m libart`,最终等价于调用 `out/soong_ui --make-mode libart`,注意这里 + 的 `--make-mode` 是在 `m` 函数里引入的。 + +总而言之,`build/soong/soong_ui.bash` 的最终效果就是帮助我们制作出了一个叫做 `soong_ui` +的应用程序,放在 `out` 下。然后一脚将门踹开,执行这个程序,从而进入 Soong 的世界。 ![](./diagrams/20210111-soong-process/phase-1.jpg) -到这里,我们可以顺便体会一下 `soong_ui` 是个啥意思,Google 官方的说法,就是 “soong native UI” 的缩写,我猜 `UI` 应该就是 user interface 的缩写吧,表示这是面向 Soong 用户的操作接口。 +到这里,我们可以顺便体会一下 `soong_ui` 是个啥意思,Google 官方的说法,就是 +“soong native UI” 的缩写,我猜 `UI` 应该就是 user interface 的缩写吧,表示这是面向 +Soong 用户的操作接口。 # 4. 欢迎进入 Soong 的世界 -我们来看看 `soong_ui` 这个程序都干了些什么。Soong 子系统都是用 go 语言写的,其主文件是 `build/soong/cmd/soong_ui/main.go` +我们来看看 `soong_ui` 这个程序都干了些什么。Soong 子系统都是用 go 语言写的,其主文件是 +`build/soong/cmd/soong_ui/main.go` 在 Android 10 中直接使用 `soong_ui` 命令编译 AOSP 时,必需要带下面这三个参数中的一个: @@ -144,7 +190,9 @@ cd ${TOP} - `--dumpvar-mode` - `--make-mode` -没带这些参数就会报错 `The 'soong' native UI is not yet available.`,更要命的是如果什么参数都不带,会直接导致程序崩溃:`panic: runtime error: index out of range`,这绝对是个 bug,还没看 11 里有没有修正。 +没带这些参数就会报错 `The 'soong' native UI is not yet available.`,更要命的是如果什 +么参数都不带,会直接导致程序崩溃:`panic: runtime error: index out of range`,这绝对 +是个 bug,还没看 11 里有没有修正。 其中 - `--dumpvars-mode` 和 `--dumpvar-mode` 用于 `dump the values of one or more legacy make variables` @@ -153,9 +201,11 @@ cd ${TOP} ./out/soong_ui --dumpvar-mode TARGET_PRODUCT aosp_arm ``` -- `--make-mode` 参数告诉 soong_ui,是正儿八经要开始编译。也就是说 `soong_ui --make-mode` 可以替代原来的 make, 所以后面还可以带一些参数选项。这些参数可能都是为了兼容 make 的习惯。 +- `--make-mode` 参数告诉 soong_ui,是正儿八经要开始编译。也就是说 `soong_ui --make-mode` +- 可以替代原来的 make, 所以后面还可以带一些参数选项。这些参数可能都是为了兼容 make 的习惯。 -关键是看 `soong/cmd/soong_ui/main.go` 中的 main 函数。代码不多,为方便解释我就直接在关键代码周围加注释了(加粗加黑标出)。 +关键是看 `soong/cmd/soong_ui/main.go` 中的 main 函数。代码不多,为方便解释我就直接在 +关键代码周围加注释了(加粗加黑标出)。
 func main() {
@@ -213,15 +263,21 @@ func main() {
 }
 
-进入 `build.Build()` 之前的 Soong 系统状态如下,和前面相比也就是多了一些数据文件,譬如 `out/.module_paths/Android.bp.list`。 +进入 `build.Build()` 之前的 Soong 系统状态如下,和前面相比也就是多了一些数据文件,譬如 +`out/.module_paths/Android.bp.list`。 ![](./diagrams/20210111-soong-process/phase-2.jpg) -看到这里读者可能会疑问,难道赫赫有名的 Soong 只是这么一个 `soong_ui` 么?答案当然不是,如果打个不恰当的比喻,`soong_ui` 只是 Soong 的大门,也就是说流程走到这里,我们进了 Soong 的大门,但会发现 Soong 的世界里除了一个大门(`out/soong_ui`)目前啥也没有。而 `build.Build()` 函数要做的事情就是从无到有将整个 Soong 系统构建起来。 +看到这里读者可能会疑问,难道赫赫有名的 Soong 只是这么一个 `soong_ui` 么?答案当然不是, +如果打个不恰当的比喻,`soong_ui` 只是 Soong 的大门,也就是说流程走到这里,我们进了 +Soong 的大门,但会发现 Soong 的世界里除了一个大门(`out/soong_ui`)目前啥也没有。而 +`build.Build()` 函数要做的事情就是从无到有将整个 Soong 系统构建起来。 # 5. 一步,两步,从 0 搭建一个 Soong -最后一步就是执行 `build.Build()` 这个核心函数, 这个函数定义在 `soong\ui\build\build.go`, 略去所有辅助的步骤,只保留核心的步骤,大家会发现和我们最早描述的下图是一样的,这里再贴一下,方便大家对照着理解。 +最后一步就是执行 `build.Build()` 这个核心函数, 这个函数定义在 `soong\ui\build\build.go`, +略去所有辅助的步骤,只保留核心的步骤,大家会发现和我们最早描述的下图是一样的,这里再贴一 +下,方便大家对照着理解。
 func Build(ctx Context, config Config, what int) {
@@ -245,9 +301,12 @@ func Build(ctx Context, config Config, what int) {
 }
 
-对 Build 这个核心函数的分析来看,其实最重要的是 `runSoong()`, 这个函数为最终最终 `runNinja()` 生成了 build.ninja 文件。`runSoong()`这个函数定义在 `soong\ui\build\soong.go`,下面我们来重点看一下这个函数。 +对 Build 这个核心函数的分析来看,其实最重要的是 `runSoong()`, 这个函数为最终最终 +`runNinja()` 生成了 build.ninja 文件。`runSoong()`这个函数定义在 `soong\ui\build\soong.go`, +下面我们来重点看一下这个函数。 -函数不算长,我这里不想直接贴代码,我们可以重点关注这个函数中的类似 `ctx.BeginTrace(XXX, "YYY")` 的地方,基本上就是在标注重点步骤。 +函数不算长,我这里不想直接贴代码,我们可以重点关注这个函数中的类似 `ctx.BeginTrace(XXX, "YYY")` +的地方,基本上就是在标注重点步骤。
 func runSoong(ctx Context, config Config) {
@@ -264,11 +323,21 @@ func runSoong(ctx Context, config Config) {
 }
 
-我这里一共标注了五行比较重要的代码,可以分成两个阶段,前四行属于第一阶段,Soong 术语上称为 minibootstrap;第五行属于第二阶段,Soong 术语上称为 bootstrap。之所以叫 bootstrap,意思就是从无到有创建 Soong。 +我这里一共标注了五行比较重要的代码,可以分成两个阶段,前四行属于第一阶段,Soong 术语上称 +为 minibootstrap;第五行属于第二阶段,Soong 术语上称为 bootstrap。之所以叫 bootstrap, +意思就是从无到有创建 Soong。 -我这里一共标注了五行比较重要的代码,之间的关系具体可以参考 AOSP 源码树中 Blueprint 的文档 `build/blueprint/bootstrap/doc.go` 的 "The Bootstrapping Process" 章节。这里的五个步骤可以对应到 Blueprint 官方定义的 **bootstrap** 和 **primary** 两个阶段(顺便说一下第三个称之为 **main** 阶段,不在本文的介绍范围内)。其中代码中的前四行属于第一阶段,在 Soong 系统里其术语又称之为 **minibootstrap**;第五行属于第二阶段,Soong 术语上称为 **bootstrap**。注意这里稍微有些混淆,我们后面主要以 Soong 的术语为主,必要时会对应看一下 Blueprint 的术语。这里我们可以回忆一下,Blueprint 和 Soong 的关系,Blueprint 是一个公共模块, Soong 利用 Blueprint 搭建了 AOSP 的自动化构建系统。 +我这里一共标注了五行比较重要的代码,之间的关系具体可以参考 AOSP 源码树中 Blueprint 的 +文档 `build/blueprint/bootstrap/doc.go` 的 "The Bootstrapping Process" 章节。这里 +的五个步骤可以对应到 Blueprint 官方定义的 **bootstrap** 和 **primary** 两个阶段(顺 +便说一下第三个称之为 **main** 阶段,不在本文的介绍范围内)。其中代码中的前四行属于第一 +阶段,在 Soong 系统里其术语又称之为 **minibootstrap**;第五行属于第二阶段,Soong 术语 +上称为 **bootstrap**。注意这里稍微有些混淆,我们后面主要以 Soong 的术语为主,必要时会 +对应看一下 Blueprint 的术语。这里我们可以回忆一下,Blueprint 和 Soong 的关系,Blueprint +是一个公共模块, Soong 利用 Blueprint 搭建了 AOSP 的自动化构建系统。 -之所以叫 bootstrap,意思就是从无到有创建 Soong。下面我们就来仔细看看 minibootstrap 和 bootstrap 这两个阶段分别做了些什么。 +之所以叫 bootstrap,意思就是从无到有创建 Soong。下面我们就来仔细看看 minibootstrap 和 +bootstrap 这两个阶段分别做了些什么。 ## 5.1. 第一阶段 minibootstrap(对应 Blureprint 定义的 Bootstrap stage) @@ -276,23 +345,30 @@ func runSoong(ctx Context, config Config) { - 第一步 **`ctx.BeginTrace(metrics.RunSoong, "blueprint bootstrap")`**: - 执行 `build/blueprint/bootstrap.bash -t` 这个脚本,创建 `out/soong/.minibootstrap/` 目录并在这个目录下创建一系列文件,其中最重要的是 `out/soong/.minibootstrap/build.ninja` 这个文件。这个文件很关键,是构造下一个阶段 bootstrap 的 ninja build 文件。此时的 Soong 系统状态如下: + 执行 `build/blueprint/bootstrap.bash -t` 这个脚本,创建 `out/soong/.minibootstrap/` + 目录并在这个目录下创建一系列文件,其中最重要的是 `out/soong/.minibootstrap/build.ninja` + 这个文件。这个文件很关键,是构造下一个阶段 bootstrap 的 ninja build 文件。此时的 + Soong 系统状态如下: ![](./diagrams/20210111-soong-process/phase-3.jpg) - 第二步 **`ctx.BeginTrace(metrics.RunSoong, "minibp")`**: - 利用 blueprint 的 microfactory 创建 minibp 这个可执行程序,minibp 的源码在 `build/blueprint/bootstrap/minibp`,这个应用程序也是用于构造第二阶段 bootstrap 的工具。 + 利用 blueprint 的 microfactory 创建 minibp 这个可执行程序,minibp 的源码在 + `build/blueprint/bootstrap/minibp`,这个应用程序也是用于构造第二阶段 bootstrap 的工具。 - 第三步 **`ctx.BeginTrace(metrics.RunSoong, "bpglob")`**: - 同上第二步,也是利用 blueprint 的 microfactory 创建另一个可执行程序 bpglob,其源码在 `build/blueprint/bootstrap/bpglob`。第二步和第三步做完后的 Soong 系统状态如下: + 同上第二步,也是利用 blueprint 的 microfactory 创建另一个可执行程序 bpglob,其源码 + 在 `build/blueprint/bootstrap/bpglob`。第二步和第三步做完后的 Soong 系统状态如下: ![](./diagrams/20210111-soong-process/phase-4.jpg) - 第四步:**`ninja("minibootstrap", ".minibootstrap/build.ninja")`**: - 调用 ninja,根据上面第一步生成的 `out/soong/.minibootstrap/build.ninja`,驱动第二步和第三步生成的 minibp/bpglob 这些程序构造第二阶段 bootstrap。最终的结果是生成了另一个 ninja 的 build 文件 `out/soong/.bootstrap/build.ninja` + 调用 ninja,根据上面第一步生成的 `out/soong/.minibootstrap/build.ninja`,驱动第二 + 步和第三步生成的 minibp/bpglob 这些程序构造第二阶段 bootstrap。最终的结果是生成了另 + 一个 ninja 的 build 文件 `out/soong/.bootstrap/build.ninja` 对照 `verbose.log`,输出了这么一段命令,可供参考: @@ -314,7 +390,11 @@ $ ls ./out/soong/.bootstrap/bin gotestmain gotestrunner loadplugins soong_build soong_env ``` -注意其中的 `soong_build` 这个程序,按照官方的定义(具体参考文档 `build/blueprint/bootstrap/doc.go`),它就是整个 bootstrapping 流程 (包括我们这里给大家介绍的 minibootstrap 和 bootstrap 两个阶段) 最后生成的所谓 "primary builder"。它含有针对整个 AOSP 的工程的完整的构造逻辑,在 bootstrap 的最后一步会调用 `soong_build` 这个程序最终创建生成了针对整个 AOSP 项目的 ninja 的 build 文件 `out/soong/build.ninja`。 +注意其中的 `soong_build` 这个程序,按照官方的定义(具体参考文档 `build/blueprint/bootstrap/doc.go`), +它就是整个 bootstrapping 流程 (包括我们这里给大家介绍的 minibootstrap 和 bootstrap +两个阶段) 最后生成的所谓 "primary builder"。它含有针对整个 AOSP 的工程的完整的构造逻辑, +在 bootstrap 的最后一步会调用 `soong_build` 这个程序最终创建生成了针对整个 AOSP 项目的 +ninja 的 build 文件 `out/soong/build.ninja`。 参考 `verbose.log` 中最后一行可以对应一下: @@ -322,13 +402,17 @@ gotestmain gotestrunner loadplugins soong_build soong_env [117/117] out/soong/.bootstrap/bin/soong_build -t -l out/.module_paths/Android.bp.list -b out/soong -n out -d out/soong/build.ninja.d -globFile out/soong/.bootstrap/build-globs.ninja -o out/soong/build.ninja Android.bp ``` -`out/soong/.bootstrap/bin/soong_build` 会逐个扫描前期记录在 `out/.module_paths/Android.bp.list` 中的 Android.bp 文件,所以这包含了整个 AOSP 项目的所有模块,所以这里生成的 `out/soong/build.ninja` 这个文件超级巨大,谨慎打开! +`out/soong/.bootstrap/bin/soong_build` 会逐个扫描前期记录在 +`out/.module_paths/Android.bp.list` 中的 Android.bp 文件,所以这包含了整个 AOSP 项 +目的所有模块,所以这里生成的 `out/soong/build.ninja` 这个文件超级巨大,谨慎打开! ![](./diagrams/20210111-soong-process/phase-6.jpg) # 6. 总结 -前面啰里啰唆讲了太多,这里直接回放一下整个过程吧,直观感受一下 AOSP 是如何从无到有构建了自己的构造系统 Soong。当然这个远远不是结局,接下来等待你的将是真正漫长的 AOSP 编译过程,对应 Blueprint 的定义就是所谓的 **main stage**!enjoy it ;) +前面啰里啰唆讲了太多,这里直接回放一下整个过程吧,直观感受一下 AOSP 是如何从无到有构建了 +自己的构造系统 Soong。当然这个远远不是结局,接下来等待你的将是真正漫长的 AOSP 编译过程, +对应 Blueprint 的定义就是所谓的 **main stage**!enjoy it ;) - 初始状态 diff --git a/articles/20211026-lunch.md b/articles/20211026-lunch.md index 71cff6b1d689d96cb78cfc8ef0b597d4a8dd272c..b465667f44d928cfa891dedaac294bb4f52a7f24 100644 --- a/articles/20211026-lunch.md +++ b/articles/20211026-lunch.md @@ -2,6 +2,11 @@ **envsetup.sh 中的一些重要函数分析** +- 作者:汪辰 +- 联系方式: / + +文章大纲 + - [1. `lunch()` 函数分析](#1-lunch-函数分析) @@ -129,7 +134,8 @@ function lunch() - TARGET_BUILD_VARIANT - TARGET_PLATFORM_VERSION - TARGET_BUILD_TYPE -- 设置 build 过程中需要用到的环境变量(`set_stuff_for_environment()`),其中主要是通过 `setpaths()` 设置一些编译工具的路径 +- 设置 build 过程中需要用到的环境变量(`set_stuff_for_environment()`),其中主要是通 + 过 `setpaths()` 设置一些编译工具的路径 - 输出主要的 config 信息(`printconfig()`) 一个输出的示例,参考 T-head 的 aosp_riscv64-eng 实现 @@ -180,7 +186,8 @@ function print_lunch_menu() # 3. `get_build_var` 函数分析 -`get_build_var()` 这个函数会编译生成 soong_ui 这个小程序,然后执行该程序,带入 `--dumpvar-mode` 选项以及 $1 +`get_build_var()` 这个函数会编译生成 soong_ui 这个小程序,然后执行该程序,带入 +`--dumpvar-mode` 选项以及 $1
 function get_build_var()
@@ -201,7 +208,8 @@ function get_build_var()
 }
 
-所以我们知道在 envsetup.sh 中有很多形如执行 `get_build_var AAA` 的代码, 相当于执行 `./out/soong_ui --dumpvar-mode AAA` +所以我们知道在 envsetup.sh 中有很多形如执行 `get_build_var AAA` 的代码, 相当于执行 +`./out/soong_ui --dumpvar-mode AAA` # 4. `set_stuff_for_environment()` 函数分析 @@ -221,7 +229,9 @@ function set_stuff_for_environment() `set_stuff_for_environment()` 中最关键的函数是 `setpaths()`,下面有一章节专门看一下。 -其他代码,譬如 `set_sequence_number()` 的工作只是导出了 `BUILD_ENV_SEQUENCE_NUMBER` 这个环境变量。其他的工作是导出了 `ANDROID_BUILD_TOP`、`GCC_COLORS` 和 `ASAN_OPTIONS` 这三个环境变量 +其他代码,譬如 `set_sequence_number()` 的工作只是导出了 `BUILD_ENV_SEQUENCE_NUMBER` +这个环境变量。其他的工作是导出了 `ANDROID_BUILD_TOP`、`GCC_COLORS` 和 `ASAN_OPTIONS` +这三个环境变量 # 5. `setpaths()` 函数分析 @@ -372,7 +382,8 @@ function setpaths() - TARGET_GCC_VERSION - ANDROID_TOOLCHAIN:这个是形如 - ANDROID_TOOLCHAIN_2ND_ARCH -- ANDROID_DEV_SCRIPTS: 形如 `$T/development/scripts:$T/prebuilts/devtools/tools:$T/external/selinux/prebuilts/bin`, 如果是在 Linux 上编译还会补上 `:$T/prebuilts/misc/linux-x86/dtc:$T/prebuilts/misc/` +- ANDROID_DEV_SCRIPTS: 形如 `$T/development/scripts:$T/prebuilts/devtools/tools:$T/external/selinux/prebuilts/bin`, + 如果是在 Linux 上编译还会补上 `:$T/prebuilts/misc/linux-x86/dtc:$T/prebuilts/misc/` - ANDROID_EMULATOR_PREBUILTS - ANDROID_BUILD_PATHS: 这个路径是一个所有和 build 有关的路径的全集,包括: - ANDROID_BUILD_PATHS diff --git a/articles/20211102-codeanalysis-soong_ui.md b/articles/20211102-codeanalysis-soong_ui.md index 629c48ec8acb67483b8b9758d24767f305f4a411..809b4a5a2df166db0fe8cd9f52dbdebbae2de984 100644 --- a/articles/20211102-codeanalysis-soong_ui.md +++ b/articles/20211102-codeanalysis-soong_ui.md @@ -2,6 +2,11 @@ **代码走读:对 soong_ui 的深入理解** +- 作者:汪辰 +- 联系方式: / + +文章大纲 + - [1. 构建系统对 soong_ui 的封装](#1-构建系统对-soong_ui-的封装) @@ -19,9 +24,15 @@ # 1. 构建系统对 soong_ui 的封装 -`soong_ui` 这个程序可以认为是 Google 在替代原先基于 make 的构建系统而引入的一个非常重要的程序,整个构建可以说就是由这个程序驱动完成的。但代码中我们很难看到直接调用 `soong_ui` 这个程序的地方,更多的我们看到的是形如在 `envsetup.sh` 脚本文件中诸如 `$T/build/soong/soong_ui.bash ...` 这样的调用,这个脚本正是对 `soong_ui` 程序的封装调用,以这个脚本函数为入口, Google 将原来的以 make 为核心的框架改造为以 Soong 为核心的构建框架。 +`soong_ui` 这个程序可以认为是 Google 在替代原先基于 make 的构建系统而引入的一个非常重 +要的程序,整个构建可以说就是由这个程序驱动完成的。但代码中我们很难看到直接调用 `soong_ui` +这个程序的地方,更多的我们看到的是形如在 `envsetup.sh` 脚本文件中诸如 +`$T/build/soong/soong_ui.bash ...` 这样的调用,这个脚本正是对 `soong_ui` 程序的封装 +调用,以这个脚本函数为入口, Google 将原来的以 make 为核心的框架改造为以 Soong 为核心 +的构建框架。 -我们可以认为 Soong 的入口封装在 `build/soong/soong_ui.bash` 这个脚本中,下面我们来看看这个脚本的核心处理逻辑,主要包括以下三步: +我们可以认为 Soong 的入口封装在 `build/soong/soong_ui.bash` 这个脚本中,下面我们来看 +看这个脚本的核心处理逻辑,主要包括以下三步:
 // 第一步
@@ -44,8 +55,14 @@ source ${TOP}/build/soong/scripts/microfactory.bash
 这个被导入的脚本主要做了以下几件事情:
 
 - 设置 GOROOT 环境变量,指向 prebuild 的 go 编译工具链
-- 定义 `getoutdir()` 和 `soong_build_go()` 这两个函数。`getoutdir()` 的作用很简单,就是用于 `Find the output directory`;`soong_build_go()` 实际上是一个对 `build_go()` 函数的调用封装。`soong_build_go()` 在第二步会用到。
-- 导入 `${TOP}/build/blueprint/microfactory/microfactory.bash` 这个脚本,这个脚本中定义了 `build_go()` 这个函数,这个函数的中会调用 go 的命令,根据调用的参数生成相应的程序,其中第一个参数用于指定生成的程序的名字,第二个参数用于指定源码的路径,还有第三个参数可以用于指定额外的编译参数。举个例子:`build_go soong_ui android/soong/cmd/soong_ui` 就是根据 AOSP 源码树目录 `soong/cmd/soong_ui` 的 package 生成一个可执行程序叫 `soong_ui`。
+- 定义 `getoutdir()` 和 `soong_build_go()` 这两个函数。`getoutdir()` 的作用很简单,
+  就是用于 `Find the output directory`;`soong_build_go()` 实际上是一个对 `build_go()` 
+  函数的调用封装。`soong_build_go()` 在第二步会用到。
+- 导入 `${TOP}/build/blueprint/microfactory/microfactory.bash` 这个脚本,这个脚本
+  中定义了 `build_go()` 这个函数,这个函数的中会调用 go 的命令,根据调用的参数生成相应
+  的程序,其中第一个参数用于指定生成的程序的名字,第二个参数用于指定源码的路径,还有第三
+  个参数可以用于指定额外的编译参数。举个例子:`build_go soong_ui android/soong/cmd/soong_ui` 
+  就是根据 AOSP 源码树目录 `soong/cmd/soong_ui` 的 package 生成一个可执行程序叫 `soong_ui`。
 
 ## 1.2. 第二步:构建
 
@@ -53,11 +70,18 @@ source ${TOP}/build/soong/scripts/microfactory.bash
 soong_build_go soong_ui android/soong/cmd/soong_ui
 ```
 
-其作用是调用 `soong_build_go` 函数。这个函数有两个参数,从第一步的分析可以知道,`soong_build_go` 实际上是一个对 `build_go()` 函数的调用封装,所以以上语句等价于 `build_go soong_ui android/soong/cmd/soong_ui`。第一参数 `soong_ui` 是指定了编译生成的可执行程序的名字, `soong_ui` 是一个用 go 语言写的程序,也是 Soong 的实际执行程序。在第二个参数告诉 `soong_build_go` 函数,`soong_ui` 程序的源码在哪里,这里制定了其源码路径  `android/soong/cmd/soong_ui`(实际对应的位置是 `build/soong/cmd/soong_ui`)
+其作用是调用 `soong_build_go` 函数。这个函数有两个参数,从第一步的分析可以知道,
+`soong_build_go` 实际上是一个对 `build_go()` 函数的调用封装,所以以上语句等价于 
+`build_go soong_ui android/soong/cmd/soong_ui`。第一参数 `soong_ui` 是指定了编译生
+成的可执行程序的名字, `soong_ui` 是一个用 go 语言写的程序,也是 Soong 的实际执行程序。
+在第二个参数告诉 `soong_build_go` 函数,`soong_ui` 程序的源码在哪里,这里制定了其源码
+路径  `android/soong/cmd/soong_ui`(实际对应的位置是 `build/soong/cmd/soong_ui`)
 
-综上所述,`build/soong/soong_ui.bash` 的第二步的效果就是帮助我们把 `soong_ui` 制作出来,制作好的 `soong_ui` 路径在 `out/soong_ui` 下。
+综上所述,`build/soong/soong_ui.bash` 的第二步的效果就是帮助我们把 `soong_ui` 制作出
+来,制作好的 `soong_ui` 路径在 `out/soong_ui` 下。
 
-p.s.: `soong_ui` 是 “soong native UI” 的缩写,这是一个运行在 host 上的可执行程序,即 Soong 的总入口。
+p.s.: `soong_ui` 是 “soong native UI” 的缩写,这是一个运行在 host 上的可执行程序,
+即 Soong 的总入口。
 
 ## 1.3. 第三步:执行
 
@@ -70,7 +94,9 @@ exec "$(getoutdir)/soong_ui" "$@"
 
 # 2. `soong_ui` 程序分析
 
-`soong_ui` 的主文件是 `build/soong/cmd/soong_ui/main.go` 这个文件可以认为只是 `soong_ui` 作为一个命令行程序的入口,但这个程序的内容绝对不止这一个文件。从其 `soong/cmd/soong_ui/Android.bp` 文件来看:
+`soong_ui` 的主文件是 `build/soong/cmd/soong_ui/main.go` 这个文件可以认为只是 `soong_ui` 
+作为一个命令行程序的入口,但这个程序的内容绝对不止这一个文件。从其 `soong/cmd/soong_ui/Android.bp` 
+文件来看:
 
 ```
 blueprint_go_binary {
@@ -90,9 +116,13 @@ blueprint_go_binary {
 编译这个 soong_ui 会涉及到以下几个依赖的 module:
 
 - `soong/ui/build`:soong_ui 的主逻辑
-- `soong/ui/logger`:Package logger implements a logging package designed for command line utilities.  It uses the standard 'log' package and function, but splits output between stderr and a rotating log file.
-- `soong/ui/terminal`:Package terminal provides a set of interfaces that can be used to interact with the terminal
-- `soong/ui/tracer`:This package implements a trace file writer, whose files can be opened in chrome://tracing.
+- `soong/ui/logger`:Package logger implements a logging package designed for 
+  command line utilities.  It uses the standard 'log' package and function, but 
+  splits output between stderr and a rotating log file.
+- `soong/ui/terminal`:Package terminal provides a set of interfaces that can be 
+  used to interact with the terminal
+- `soong/ui/tracer`:This package implements a trace file writer, whose files 
+  can be opened in chrome://tracing.
 
 
 ## 2.1. `soong_ui` 的 main 函数。
@@ -195,7 +225,8 @@ func main() {
 ./out/soong_ui --dumpvar-mode TARGET_PRODUCT
 ```
 
-`--make-mode` 参数告诉 soong_ui,是正儿八经要开始编译。也就是说 `soong_ui --make-mode` 可以替代原来的 make, 所以后面还可以带一些参数选项。这些参数可能都是为了兼容 make 的习惯。
+`--make-mode` 参数告诉 soong_ui,是正儿八经要开始编译。也就是说 `soong_ui --make-mode` 
+可以替代原来的 make, 所以后面还可以带一些参数选项。这些参数可能都是为了兼容 make 的习惯。
 
 ### 2.2.1. `soong_ui` 的 "--dumpvar-mode" 和 "--dumpvars-mode" 参数
 
@@ -223,15 +254,22 @@ human-readable config banner from the beginning of the build.
 
 ```
 
-这两个函数差不多,区别仅在于 dump 的 var 的个数多少。内部核心都是调用的 `build.DumpMakeVars()`, 具体的代码实现在 `./build/soong/ui/build/dumpvars.go`
+这两个函数差不多,区别仅在于 dump 的 var 的个数多少。内部核心都是调用的 `build.DumpMakeVars()`, 
+具体的代码实现在 `./build/soong/ui/build/dumpvars.go`
 
-而 `build.DumpMakeVars()` 内部最终封装的 `build.dumpMakeVars()`, 注意对于 "--make-mode" 内部如果要 BuildProductConfig 也会调用 `build.dumpMakeVars()` 这个函数。
+而 `build.DumpMakeVars()` 内部最终封装的 `build.dumpMakeVars()`, 注意对于 "--make-mode" 
+内部如果要 BuildProductConfig 也会调用 `build.dumpMakeVars()` 这个函数。
 
-`build.dumpMakeVars()` 这个函数就非常有趣了,看它的代码实际上是用命令行的方式去执行一个叫做 `build/make/core/config.mk` 的脚本。
+`build.dumpMakeVars()` 这个函数就非常有趣了,看它的代码实际上是用命令行的方式去执行一
+个叫做 `build/make/core/config.mk` 的脚本。
 
-这个脚本是从 Android 原先的 make 系统里遗留下来的,从该文件的最前面注释上来看,原先的 Android 的 build 系统中,top-level Makefile 会包含这个 config.mk 文件,这个文件根据 platform 的不同以及一些 configration 的不同设置了一些 standard variables,这些变量 `are not specific to what is being built`。
+这个脚本是从 Android 原先的 make 系统里遗留下来的,从该文件的最前面注释上来看,原先的 
+Android 的 build 系统中,top-level Makefile 会包含这个 config.mk 文件,这个文件根据 
+platform 的不同以及一些 configration 的不同设置了一些 standard variables,这些变量 
+`are not specific to what is being built`。
 
-这个 config.mk 会 include 大量的其他 mk 文件,这些文件存放在 BUILD_SYSTEM(`./build/make/common`) 和  BUILD_SYSTEM(`./build/make/core`) 下
+这个 config.mk 会 include 大量的其他 mk 文件,这些文件存放在 BUILD_SYSTEM(`./build/make/common`) 
+和  BUILD_SYSTEM(`./build/make/core`) 下
 
 注意在这个 config.mk 的最后 include 了这么两个文件
 ```
@@ -241,7 +279,9 @@ include $(BUILD_SYSTEM)/soong_config.mk
 endif
 ```
 
-其中 `soong_config.mk` 里将大量 Soong 需要的,但原先定义在 mk 文件中的变量打印输出到 `out/soong/soong.variables` 这个文件中,这是一个 json 格式的文件,这也是我们所谓的 dump Make Vars 的含义。dump 出来后我们就可以随时使用了。生成的 jason 语法格式为:
+其中 `soong_config.mk` 里将大量 Soong 需要的,但原先定义在 mk 文件中的变量打印输出到 
+`out/soong/soong.variables` 这个文件中,这是一个 json 格式的文件,这也是我们所谓的 
+dump Make Vars 的含义。dump 出来后我们就可以随时使用了。生成的 jason 语法格式为:
 
 ```
 $(call add_json_str,  BuildId,                           $(BUILD_ID))
@@ -260,11 +300,14 @@ $(call add_json_list, ProductResourceOverlays,           $(PRODUCT_PACKAGE_OVERL
 "ProductResourceOverlays": ["device/generic/goldfish/overlay"],
 ```
 
-这个地方对于理解 Android 中从 make 到 Soong 的转换非常重要,看上去 Android 的思路还是先保留了原先 Make 的一套核心的 setup 逻辑,然后导出为 soong variables 供新的 Soong 使用,完成了转换。
+这个地方对于理解 Android 中从 make 到 Soong 的转换非常重要,看上去 Android 的思路还是
+先保留了原先 Make 的一套核心的 setup 逻辑,然后导出为 soong variables 供新的 Soong 
+使用,完成了转换。
 
 ### 2.2.2. `soong_ui` 的 "--make-mode" 参数
 
-现在来看 `build.Build()` 这个核心函数, 源码在 `./soong/ui/build/build.go`, 略去所有辅助的步骤,只保留核心的步骤
+现在来看 `build.Build()` 这个核心函数, 源码在 `./soong/ui/build/build.go`, 略去所有
+辅助的步骤,只保留核心的步骤
 
 
 func Build(ctx Context, config Config, what int) {
@@ -306,7 +349,9 @@ func Build(ctx Context, config Config, what int) {
 }
 
-对 Build 这个核心函数的分析来看,其实最重要的是 `runSoong()`, 这个函数最终生成了 `./out/soong/build.ninja`, `runNinja()` 啥的都是以这个最终的 ninja 文件作为输入,在这个基础上执行编译构建的工作。 +对 Build 这个核心函数的分析来看,其实最重要的是 `runSoong()`, 这个函数最终生成了 +`./out/soong/build.ninja`, `runNinja()` 啥的都是以这个最终的 ninja 文件作为输入,在 +这个基础上执行编译构建的工作。 ## 2.3. `build.runSoong()` diff --git a/articles/template.md b/articles/template.md new file mode 100644 index 0000000000000000000000000000000000000000..edb76c30859c6e7265a7a023d255f8de3b88f45e --- /dev/null +++ b/articles/template.md @@ -0,0 +1,63 @@ +文章标题:**这里填写文章标题** + +- 作者:这里填写作者姓名 +- 联系方式:这里填写作者联系方式,譬如 > + +文章大纲 + + +- [1. 一级标题](#1-一级标题) + - [1.1. 二级标题](#11-二级标题) + - [1.1.1. 三级标题](#111-三级标题) +- [2. 一级标题](#2-一级标题) + - [2.1. 二级标题](#21-二级标题) + - [2.1.1. 三级标题](#211-三级标题) + + + +# 1. 一级标题 + +一些小小的要求: + +- **有关文件名**: + + 文件后缀以 `.md` 结尾,文件名全部为英文或者阿拉伯数字或者分隔符 "`-`"。 + 具体格式例子:“20200911-platform-version”,前面 8 个数字为文件创建时的时间戳,如果 + 后期文章内容或者标题有修改但该时间戳保持不变。 + +- **有关文章撰写**: + + 正文 Markdown 语法采用 Github 习惯方法就好,具体参考 + + + 额外要求: + - 正文中英文字符和中文字符之间建议加空格,譬如:“`中文 English 中文 English`” + - 一行不要太长,80 个英文字符左右就主动换行吧。 + +- **有关贴图**: + + 例子:`![](./diagrams/20200911-platform-version/android-version.png)` + + 每篇文章在 diagrams 目录下新建一个和文章文件同名(不带 `.md`)的目录,本篇文章相关图 + 片文件放在该目录下即可。注意版权问题。 + + +## 1.1. 二级标题 + +正文 + +### 1.1.1. 三级标题 + +正文 + +# 2. 一级标题 + +正文 + +## 2.1. 二级标题 + +正文 + +### 2.1.1. 三级标题 + +正文