1. Make
Make是一款自动化编译工具,在执行编译任务时会解析在Makefile文件中定义的编译规则,然后依据编译规则对源文件进行编译。
1.1 为什么要使用它?
在编译小项目时,我们可以使用手动的编译每一个源文件,但是在编译大项目时(比如Android),如果再使用gcc手动编译,那么工作量是不能想象的。这时就需要使用自动化编译工具。使用make还有一个好处,避免不必要的重新编译,当修改了部分源文件时,它可以监测到哪些文件被修改了,然后只重新编译被修改的源文件。
1.2 编译规则
Makefile中定义的编译规则包括有源文件、编译的目标、模块之间的依赖关系以及编译条件等。
make的基础规则非常简单,但是经过扩展修饰后,就可以编译各种庞大的工程。基础编译规则如下:
- TARGET:编译目标,通常是最后需要生成的文件名或者是中间文件名,比如编译C时的和。
- PREREQUISITES:先决条件,也就是编译的目标文件所依赖的文件。
- COMMAND:编译目标时需要执行的命令。
注意:COMMOD前必须有一个制表符[TAB]
1.3 示例
main.c
utils.h
utils.c
Makefile
编写完源码以及Makefile后,执行make命令,它会解析Makefile文件。首先make会读取到第一条规则,查看目标是否有依赖,如果有依赖就先生成它的依赖目标,然后在生成自身。当然,如果对应的依赖也有自己的依赖,那么会先去生成依赖的依赖,这就形成了一个树形结构。最后,完成整个项目的编译。
在执行make 命令时,也可以指定编译目标,比如, 这样就只会执行utils.o所在规则对应的命令,将utils.c编译成utils.o。
注意到它虽然也是目标但是没有为它生成目标文件,这种叫做伪目标,在执行 make 时可以指定它来执行对应的的命令。
2. Android源码编译流程
对Make做了简单了解后,现在来说一下Android源码的编译流程。
Android源码的编译步骤分为3部,每一步都做了什么?
2.1 执行envsetup.sh
envsetup.sh中定义了很多变量和函数,执行envsetup.sh之后,就可以在当前终端中使用这些函数和变量。
- hmm:列出函数及其介绍
- lunch: 选择要编译的目标产品和版本
- croot: 切换到源码根目录
- m: 整编源码
- mm: 编译当前目录下的所有模块(不会编译它们的依赖)
- mmm: 编译指定目录下所有的模块(不会编译它们的依赖)
- mma: 编译当前目录下所有的模块以及它们所依赖的模块
- mmma: 编译指定目录下所有的模块以及它们所依赖的模块
- provision: 将设备所有需要的分区刷入,选项将传递给fastboot
- cgrep: 在C/C++文件中搜索指定关键字
- ggrep: 在gradle文件中搜索指定关键字
- Jgrep: 在java文件中搜索指定关键字
- resgrep: 在资源xml文件中搜索指定关键字
- mangrep: 在AndroidManifest.xml文件中搜索指定关键字
- mgrep:在Makefiles和android.mk文件中搜索指定关键字
- sepgrep: 在sepolicy文件中搜索指定关键字
- sgrep: 在所有本地文件中搜索指定关键字
- godir: 切换到包含某个文件的目录下
- cproj: 向上切换到最近包含Android.mk的目录下
- findmakefile: 打印当前目录所在工程的Android.mk的文件路径
- getsdcardpath: 获取Sd卡路径
- getscreenshotpath: 获取屏幕截图的路径
- getlastscreenshot: 获取最后一张截图,导出到当前目录下
- getbugreports: 将bug报告从设备上导出到本地,bug报告存放于目录/sdcard/bugreports
- gettop: 获取Android源码根目录
- pid: pid processname 查看某个可执行程序对应的进程id
- key_back: 模拟按返回键
- key_home: 模拟按Home键envsetup.sh
- key_menu: 模拟按菜单键
2.2 lunch
在print_lunch_menu函数中首先会获取COMMON_LUNCH_CHOICES变量,然后输出所有product
COMMON_LUNCH_CHOICES定义在AndroidProducts.mk中
device/qcom/qssi/AndroidProducts.mk
2.3 make
执行make命令后,会解析在源码根目录中的Makefile,可以看到只是包含了main.mk
在main.mk中包含了和。
config.mk中定义了一系列编译需要用到的变量,比如常用的CLEAR_VARS、BUILD_PACKAGE。这些变量记录着对应脚本的路径。每一个.mk完成一个基本功能,比如,CLEAR_VARS对应的build/make/core/clear_vars.mk是清除编译的临时变量,BUILD_PACKAGE对应的build/make/core/package.mk是编译APK。
而definitions.mk中用define也定义了一系列变量,比如常用的my-dir、all-subdir-makefiles、all-subdir-java-files等,这些编译主要用于处理目录相关的功能,比如my-dir是获取当前目录路径,all-subdir-makefiles调用所有子目录的android.mk。
在上面提到make在解析Makefile时,会形成一个树型结构。那么对于Android来说,这个树型结构的根是谁呢?在中可以看到,定义的默认target为,而又依赖。这里定义的只是一个空的规则,就相当于先占一个位置,后面有真正的定义。
build/make/core/main.mk
根据TARGET_BUILD_APPS值的不同,这里分成了单编和整编。
对于编译整个系统来说,droid_targets依赖droidcore和dist_files。
先看一下droid_core。通过droidcore可以生成分区镜像以及各种程序包,modules_to_install这个变量描述了系统需要安装的模块,比如framework、Settings。
dist_files的作用是在out目录下创建dist文件夹,用于存储多种分发包。
3. Android.mk
上面提到在执行make时,会定义很多变量以及函数,我们在Android.mk中就可以使用这些函数和变量。
Android.mk用于在构建模块时,向构建系统描述源文件以及使用到的共享库。构建的模块可以是静态库、共享库、可执行文件、jar或apk。
3.1 示例
3.1.1 编译动态jar
HelloWorld.java
Andoird.mk
执行进行编译
3.1.2 编译APK
APK源码放在附件MaterialMe.tar.gz中
Android.mk
3.2 常用函数和变量
常用获取源文件的方法
编译目标类型
变量及其含义
Android.mk其实还是Makefile文件,那么Android.mk自然也支持定义变量以及使用条件语句
3.3 静态库动态库的区别及使用场景
动态库编译后会放在;而静态库编译后会放在
引入了静态jar的模块实际上是把jar包里用到class都包含到引用的模块里了,会增大APK占用的空间;而共享jar在系统中仅有一份,使用到时,由系统加载提供。
什么时候使用动态jar,什么时候又使用静态jar呢?
一般打包系统库的时候,都会打包成动态jar,比如services.jar,framework.jar等。
在引用第三方库时,会打包成静态jar。
4. Android.bp
4.1 构建系统简介
Ninja
Ninja 是一个专注于速度的小型构建系统,它被设计为尽可能快地运行构建。Ninja与其他构建系统的区别在于,它的输入文件被设计为由更高级的构建系统生成;其他构建系统基于高级语言,而Ninja基于汇编。
ninja的构建文件虽然是可读的(human-readable),但是编写不是特别方便,因此就需要一些ninja构建文件的生成器。这些生成器就是一些元构建系统(meta-build system),例如Blueprint、CMake等等。
Blueprint
Blueprint 是一个元构建系统,该系统读取Blueprint文件来描述需要构建的模块,并生成一个Ninja清单来描述需要运行的命令及其依赖项。 Blueprint是ninja构建文件的生成器。android编译系统soong集成了Blueprint,Blueprint可将我们编写的android.bp解析生成一个ninja构建文件。
Kati
kati就是一个转换工具,它可以将Makefile和.mk文件转换为ninja。
Soong
在android 6.0版本之前,编译android源码采用的是基于make的编译系统,由于make在编译时表现出效率不够高、增量编译速度慢等问题,Google在android 7.0版本引进了编译速度更快的soong来替代make。
Soong集成了Ninja, 而Ninja专注于速度,没有条件或流程控制语句,也不支持逻辑运算。但它允许以其它语言如来维护这些复杂的编译流程和逻辑。例如,我们可以继续采用makefile, 或者采用go语言来维护编译流程和逻辑。上面已经提到了Ninja,Blueprint, kati等等好几种工具,为了完整、快速的构建一个android系统,就需要一个“管家”来协调这些工具。这个选择转换工具、选择解析框架、解析维护构建逻辑的“管家”就是soong。
androidmk
soong中还集成了一个非常有用的工具androidmk。androidmk可以将android.mk转换成android.bp。注意:androidmk工具可以转换变量,模块,注释和某些条件,但是自定义的Makefile规则,复杂的条件语句或其它的额外的include语句,必须手动转换。
4.2 services的Android.bp
Android.bp的格式:
开头为模块类型,比如的意思就是将次模块编译成动态jar。后面跟着的是一个类似Json的结构,它定义着模块的属性。每个模块都必须有一个 name属性,也就是模块的名称。
services模块
可以看到属性的值并不是文件路径,它是引用的一个filegroup
另外在Android.bp中还可以定义默认模块,默认模块可用于在多个模块中重复使用相同的属性。定义方法是将模块类型写为xxx_defaults,比如。引用时,可以给其他模块添加defaults属性,比如。默认模块还有cc_defaults, java_defaults, doc_defaults, stub_defaults等。
services/wifi的Android.bp中就使用了这个默认模块
4.3 模块类型
在build/soong/androidmk/cmd/androidmk/android.go可以看到Android.bp支持的模块类型以及预编译类型
4.4 常用属性
4.5 与Android.mk的差异
- Android.bp支持单行和多行注释
- Android.bp也可以定义变量,通过赋值,变量定义后,它的值是不可变的,除了使用改变值,但也仅限在使用之间。
- Android.mk文件通常可以包含多个同名模块(例如,用于库的静态(static)和共享(shared)版本,用于不同主机(host)的版本,用于不同设备(device)版本);Android.bp中的每一个模块都需要唯一的,但是单个模块可以构建为多个变体。
将不同变体添加到中,这样为arm平台构建时,将构建generic.cpp和arm.cpp。 在为x86平台构建时,将构建generic.cpp和x86.cpp。
4.6 示例
MaterialMe的Android.bp