Android Build 系统可以使用Android.mk文件通过m/mm/mmm/make等命令进行编译,make命令还可制作各种系统镜像文件,例如system.img、boot.img和recovery.img等。这一切都得益于Android编译系统,它为我们处理了各种依赖关系,以及提供各种有用工具。
Android Build系统中最主要的处理逻辑都在Make文件中,而其他脚本文件只是起到一些辅助作用。Make命令在执行的时候,默认会在当前目录找到一个Makefile文件,然后根据Makefile文件中的指令来对代码进行编译。也就是说,make命令执行的是Makefile文件中的指令。
Android Build系统中Make文件可以分为三类:
- 第一类是Build系统核心文件,此类文件定义了整个Build系统框架,而其他所有Make文件都是在这个框架的基础上编写出来的。Build系统核心文件全部位于/build/core/
- 第二类是针对某个产品(一个产品可能是某个型号的手机或者平板电脑)的Make文件,这些文件通常位于/device目录下,该目录又以公司以及产品名分为两级目录/device/公司/产品,/device下还包含一些芯片平台相关的Make文件。
- 第三类是针对某个模块的Make文件。整个系统中,包含大量的模块,每个模块都有一个专门的Make文件,这类文件的名称统一为“Android.mk”,该文件中定义了如何编译当前模块。Android Build系统会在这个源码树种扫描名称为“Android.mk”的文件并根据其中的内容执行模块编译。
编译Android系统
编译Android系统三部曲:
- source build/envsetup.sh
- lunch full-eng
- make -j24
这三行命令的说明如下:
第一行命令“source build/envsetup.sh”引入了 build/envsetup.sh
脚本。该脚本的作用是初始化编译环境,并引入一些辅助的 Shell 函数,这其中就包括第二步使用 lunch 函数。
除此之外,该文件中还定义了其他一些常用的函数,它们如表 1 所示:
build/envsetup.sh 中定义的常用函数
名称 | 说明 |
---|---|
croot | 切换到源码树的根目录 |
m | 在源码树的根目录执行 make |
mm | Build 当前目录下的模块 |
mmm | Build 指定目录下的模块 |
cgrep | 在所有 C/C++ 文件上执行 grep |
jgrep | 在所有 Java 文件上执行 grep |
resgrep | 在所有 res/*.xml 文件上执行 grep |
godir | 转到包含某个文件的目录路径 |
printconfig | 显示当前 Build 的配置信息 |
add_lunch_combo | 在 lunch 函数的菜单中添加一个条目 |
第二行命令“lunch full-eng”是调用 lunch 函数,并指定参数为“full-eng”。lunch 函数的参数用来指定此次编译的目标设备以及编译类型。在这里,这两个值分别是“full”和“eng”。“full”是 Android 源码中已经定义好的一种产品,是为模拟器而设置的,而编译类型会影响最终系统中包含的模块,关于编译类型可以参考alps/build/core/build-system.html 说明。
如果调用 lunch 函数的时候没有指定参数,那么该函数将输出列表以供选择,该列表类似图 3 中的内容(列表的内容会根据当前 Build 系统中包含的产品配置而不同),此时可以通过输入编号或者名称进行选择。
第三行命令“make -j24”才真正开始执行编译。make 的参数“-j”指定了同时编译的 Job 数量,这是个整数,该值通常是编译主机 CPU 支持的并发线程总数的 1 倍或 2 倍(例如:在一个 4 核,每个核支持两个线程的 CPU 上,可以使用 make -j8 或 make -j16)。
Build 结果的目录结构
- /out/host/:该目录下包含了针对主机的 Android 开发工具的产物。即 SDK 中的各种工具,例如:emulator,adb,aapt 等
- /out/target/common/:该目录下包含了针对设备的共通的编译产物,主要是 Java 应用代码和 Java 库
- /out/target/product/<product_name>/:包含了针对特定设备的编译结果以及平台相关的 C/C++ 库和二进制文件。其中,<product_name>是具体目标设备的名称
Make文件说明
整个 Build 系统的入口文件是源码树根目录下名称为“Makefile”的文件,当在源代码根目录上调用 make 命令时,make 命令首先将读取该文件。
Makefile 文件的内容只有一行:“include build/core/main.mk
”。该行代码的作用很明显:包含 build/core/main.mk 文件。在 main.mk 文件中又会包含其他的文件,其他文件中又会包含更多的文件,这样就引入了整个 Build 系统。
这些 Make 文件间的包含关系是相当复杂的,图 3 描述了这种关系,该图中黄色标记的文件(且除了 $
开头的文件)都位于 build/core/ 目录下。
主要的 Make 文件的说明
文件名 | 说明 |
---|---|
main.mk | 最主要的 Make 文件,该文件中首先将对编译环境进行检查,同时引入其他的 Make 文件。另外,该文件中还定义了几个最主要的 Make 目标,例如 droid,sdk,等(参见后文“Make 目标说明”)。 |
help.mk | 包含了名称为 help 的 Make 目标的定义,该目标将列出主要的 Make 目标及其说明。 |
pathmap.mk | 将许多头文件的路径通过名值对的方式定义为映射表,并提供 include-path-for 函数来获取。例如,通过 $(call include-path-for, frameworks-native) 便可以获取到 framework 本地代码需要的头文件路径。 |
envsetup.mk | 配置 Build 系统需要的环境变量,例如:TARGET_PRODUCT,TARGET_BUILD_VARIANT,HOST_OS,HOST_ARCH 等。 当前编译的主机平台信息(例如操作系统,CPU 类型等信息)就是在这个文件中确定的。 另外,该文件中还指定了各种编译结果的输出路径。 |
combo/select.mk | 根据当前编译器的平台选择平台相关的 Make 文件。 |
dumpvar.mk | 在 Build 开始之前,显示此次 Build 的配置信息。 |
config.mk | 整个 Build 系统的配置文件,最重要的 Make 文件之一。该文件中主要包含以下内容:
|
definitions.mk | 最重要的 Make 文件之一,在其中定义了大量的函数。这些函数都是 Build 系统的其他文件将用到的。例如:my-dir,all-subdir-makefiles,find-subdir-files,sign-package 等,关于这些函数的说明请参见每个函数的代码注释。 |
distdir.mk | 针对 dist 目标的定义。dist 目标用来拷贝文件到指定路径。 |
dex_preopt.mk | 针对启动 jar 包的预先优化。 |
pdk_config.mk | 顾名思义,针对 pdk(Platform Developement Kit)的配置文件。 |
${ONE_SHOT_MAKEFILE} | ONE_SHOT_MAKEFILE 是一个变量,当使用“mm”编译某个目录下的模块时,此变量的值即为当前指定路径下的 Make 文件的路径。 |
${subdir_makefiles} | 各个模块的 Android.mk 文件的集合,这个集合是通过 Python 脚本扫描得到的。 |
post_clean.mk | 在前一次 Build 的基础上检查当前 Build 的配置,并执行必要清理工作。 |
legacy_prebuilts.mk | 该文件中只定义了 GRANDFATHERED_ALL_PREBUILT 变量。 |
Makefile | 被 main.mk 包含,该文件中的内容是辅助 main.mk 的一些额外内容 |
Android编译系统变化
1.1 编译系统变化
从Android 7.0开始,Android的编译系统发生了变化,之前依赖Make文件组织编译系统,从7.0开始逐步引入Kati soong(optional为正式使用,需要USE_SOONG=true开启),将Android.mk文件转化成ninja文件,使用ninja文件对编译系统进行管理。
从Android 8.0开始,Android引入了Android.bp文件来替换之前的Android.mk文件,与Android.mk不同的是,Android.bp是纯粹的配置文件,不包含分支、循环等流程控制,也不能做算数、逻辑运算。 与此同时,ninja文件也是如此。 这就产生了一些新的问题与需求——在Android项目上进行选择编译、解析配置、转换成ninja等——Soong应运而生。Soong其实就相当于Makefile编译系统的核心,即build/make/core/
下面的内容。 它负责提供Android.bp的含义定义与解析,并将之转换为ninja文件。。
此外,Soong还会编译产生一个androidmk
命令,可以手动把Android.mk转换成Android.bp。 这只对无选择、循环等复杂流程控制的Android.mk生效。
Blueprint和Soong都是由Go语言写的项目。 从Android Nougat开始,prebuilts/go/
目录下新增了Go语言所需的运行环境,在编译时使用。
Android.bp以及相关支持,从Android Nougat开始加入,从Android Oreo(8.0)开始默认开启。 如果需要在Android Nougat的版本使用,需要在执行编译时添加变量。
make 'USE_SOONG=true'
1.2 代码路径变化
Kati的位置是在build/kati中
Soong的位置在build/soong
1.3 工具链关系
Android.mk、Android.bp、Soong、Buleprint、Ninja,它们之间到底有什么关系? 以下用简单的方式表达这几个概念之间的作用关系。
Android.bp --> Blueprint --> Soong --> Ninja
Makefile or Android.mk --> kati --> Ninja
(Android.mk --> Soong --> Blueprint --> Android.bp)
Blueprint是生成、解析Android.bp的工具,是Soong的一部分。 Soong则是专为Android编译而设计的工具,Blueprint只是解析文件的形式,而Soong则解释内容的含义。
Android.mk可以通过Soong提供的androidmk
转换成Android.bp,但仅限简单配置。 目前Oreo的编译流程中,仍然是使用kati来做的转换。
现存的Android.mk、既有的Android.bp,都会分别被转换成Ninja。 从Android.mk与其它Makefile,会生成out/build-<product_name>.ninja
文件。 而从Android.bp,则会生成out/soong/build.ninja
。 此外,还会生成一个较小的out/combined-<product_name>.ninja
文件,负责把二者组合起来,作为执行入口。
最终,Ninja文件才是真正直接控制源码编译的工具。在编译过程中,将所有的Android.bp文件收集成out/soong/build.ninja.d,并以此为基础生成out/soong/build.ninja规则
总结为:在原先Android 6.0纯Makefile编译的传统流程前,8.0版本新增了四个步骤:
- Soong的自举(bootstrap)。这个步骤会编译Soong的核心组件。
- 收集Android.bp并生成
out/soong/build.ninja
文件。 - 收集Android.mk并生成
out/build-<product>.ninja
与out/combined-<product>.ninja
文件。 - 执行Ninja文件,进行编译。这个
combined-*.ninja
文件,就是真正的执行入口。
后面的步骤就是自底向上编译Android所有模块,与以前相同。
参考:
https://www.ibm.com/developerworks/cn/opensource/os-cn-android-build/index.html
http://www.cnblogs.com/mr-raptor/archive/2012/06/07/2540359.html