https://blog.csdn.net/aggresss/article/details/53968884
在之前的实验中,我们使用几个简单的命令就可以编译出适用于模拟器qemu运行的Android客户端 : source ./build/envsetup.sh 然后 lunch 最后 make,等上一盏茶的功夫,镜像就编译出来了。对于经常下载源码自己编译的兄弟一定很好奇,AOSP编译方式在表面上和Automake和Cmake的方式都不相同。因为AOSP比Linux内核的编译过程要复杂,Google为它量身定做了一套编译系统,但底层还是基于Makefile的方式,这一期我们来分析一下AOSP编译环境的初始化过程的工作原理。
首先来分析 source ./build/envsetup.sh 的原理。脚本文件build/envsetup.sh被source到当前shell的过程中,会在vendor和device两个目录将厂商指定的vendorsetup.sh也source到当前shell当中,这样就可以获得厂商提供的产品配置信息。
此外,脚本文件build/envsetup.sh还提供了以下几个重要的命令来帮助我们编译Android源码,可以通过 hmm 命令来查看envsetup.sh加载的命令:
1. lunch
用来初始化编译环境,例如设置环境变量和指定目标产品型号。Lunch命令在执行的时候,主要做三件事情。
(1).第一件事情是设置
TARGET_PRODUCT、
TARGET_BUILD_VARIANT、
TARGET_BUILD_TYPE、
TARGET_BUILD_APPS
等环境变量,用来指定目标产品类型和编译类型。
(2).第二件事情是通过make命令执行build/core/config.mk脚本,并且通过加载另外一个脚本build/core/dumpvar.mk打印出当前的编译环境配置信息,
(3).第三件事情是通过已经加载build/core/config.mk脚本加载BoradConfig.mk和 build/core/envsetup.mk两个脚本来配置目标产品型号的相关信息。
2. m
相当于是在执行make命令。对整个Android源码进行编译。
3. mm
如果是在Android源码根目录下执行,那么就相当于是执行make命令对整个源码进行编译。如果是在Android源码根目录下的某一个子目录执行,那么就在会在从该子目录开始,一直往上一个目录直至到根目录,寻找是否存在一个Android.mk文件。如果存在的话,那么就通过make命令对该Android.mk文件描述的模块进行编译。
4. mmm
后面可以跟一个或者若干个目录。如果指定了多个目录,那么目录之间以空格分隔,并且每一个目录下都必须存在一个Android,mk文件。如果没有在目录后面通过冒号指定模块名称,那么在Android.mk文件中描述的所有模块都会被编译,否则只有指定的模块会被编译。如果需要同时指定多个模块,那么这些模块名称必须以逗号分隔。它的语法如下所示:
mmm
该命令会通过make命令来执行Android源码根目录下的Makefile文件,该Makefile文件又会将build/core/main.mk加载进来。文件build/core/main.mk在加载的过程中,还会加载以下几个主要的文件:
(1). build/core/config.mk
该文件根据lunch命令所配置的产品信息在build/target/board、vendor或者device目录中找到对应的BoradConfig.mk文件,以及通过加载build/core/product_config.mk文件在build/target/product、vendor或者device目录中找到对应的AndroidProducts.mk文件,来进一步对编译环境进行配置,以便接下来编译指定模块时可以获得必要的信息。
(2). build/core/definitions.mk
该文件定义了在编译过程需要调用到的各种自定义函数。
(3). 指定的Android.mk
这些指定的Android.mk环境是由mmm命令通过环境变量ONE_SHOT_MAKEFILE传递给build/core/main.mk文件使用的。这些Android.mk文件一般还会通过环境变量BUILD_PACKAGE、BUILD_JAVA_LIBRARY、BUILD_STATIC_JAVA_LIBRARY、BUILD_SHARED_LIBRARY、BUILD_STATIC_LIBRARY、BUILD_EXECUTABLE和BUILD_PREBUILT将build/core/package.mk、build/core/java_library.mk、build/core/static_java_library.mk、build/core/shared_library.mk、build/core/static_library.mk、build/core/executable.mk和build/core/prebuilt.mk等编译片段模板文件加载进来,来表示要编译是APK、Java库、Linux动态库/静态库/可执行文件或者预先编译好的文件等等。
(4). build/core/Makefile
该文件包含了用来制作system.img、ramdisk.img、boot.img和recovery.img等镜像文件的脚本。
下面我们来详细分析一下lunch的执行过程。
函数代码我就不贴了,请到./build/envsetup.sh中查找:
1. 检查是否带有参数,即位置参数$1是否等于空。如果不等于空的话,就表明带有参数,并且该参数是用来指定要编译的设备型号及其编译类型的。如果等于空的话,那么就调用另外一个函数print_lunch_menu来显示Lunch菜单项,并且通过调用read函数来等待用户输入。无论通过何种方式,最终变量answer的值就保存了用户所指定的备型号及其编译类型。
2. 对变量answer的值的合法性进行检查。如果等于空的话,就将它设置为默认值“full-eng”。如果不等于空的话,就分为三种情况考虑。第一种情况是值为数字,那么就需要确保该数字的大小不能超过Lunch菜单项的个数。在这种情况下,会将输入的数字索引到数组LUNCH_MENU_CHOICES中去,以便获得一个用来表示设备型号及其编译类型的文本。第二种情况是非数字文本,那么就需要确保该文本符合
3. 接下来是解析变量selection的值,也就是通过sed命令将它的
4. 通过以上合法性检查之后,就将变量product和variant的值保存在环境变量TARGET_PRODUCT和TARGET_BUILD_VARIANT中。此外,另外一个环境变量TARGET_BUILD_TYPE的值会被设置为"release",表示此次编译是一个release版本的编译。另外,前面还有一个环境变量TARGET_BUILD_APPS,它的值被函数lunch设置为空,用来表示此次编译是对整个系统进行编译。如果环境变量TARGET_BUILD_APPS的值不等于空,那么就表示此次编译是只对某些APP模块进行编译,而这些APP模块就是由环境变量TARGET_BUILD_APPS来指定的。
5. 调用函数set_stuff_for_environment来配置环境,例如设置Java SDK路径和交叉编译工具路径等。
6. 调用函数printfconfig来显示已经配置好的编译环境参数。
======================分割线========================
上面的逻辑流程已经很复杂,如果想深究可以查看继续跟踪其中的文件,我们这里来集中精力解决一个疑问:AOSP的编译系统是如何自定义编译固件的。也就是说如果我们想自定义一个硬件,都需要提供哪些格式的信息。
当我们执行 # make 时
这实际上等价于下面的完整命令 (具体参见 build/core/envsetup.mk )
# TARGET_ARCH=arm TARGET_PRODUCT=generic TARGET_BUILD_TYPE=release make
可见,默认情况下编译系统认为TARGET_PRODUCT 是generic 的
Android Makefile 的引用关系是这样的
Makefile -> build/core/main.mk -> build/core/config.mk -> build/core/envsetup.mk -> build/core/product_config.mk
在build/core/product_config.mk 中编译系统首先调用 build/core/product.mk中定义的函数get-all-product-makefiles ,来
遍历整个vendor和device 的子目录, 找到vendor和device下所有的 AndroidProducts.mk, 不同子目录下的AndroidProducts.mk 中定义了不同的 PRODUCT_NAME, PRODUCT_DEVICE 等信息,接着build/core/product_config.mk 会调用resolve-short-product-name 将TARGET_PRODUCT匹配的AndroidProducts.mk 中定义的 PRODUCT_DEVICE 赋值给TARGET_DEVICE。
有了这个TARGET_DEVICE, 再回到 build/core/config.mk,它再include build/core/envsetup.mk,通过这个文件加载BoardConfig.mk
# Boards may be defined under $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)
# or under vendor/*/$(TARGET_DEVICE). Search in both places, but
# make sure only one exists.
# Real boards should always be associated with an OEM vendor.
board_config_mk := \
$(strip $(wildcard \
$(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)/BoardConfig.mk \
$(shell test -d device && find device -maxdepth 4 -path '*/$(TARGET_DEVICE)/BoardConfig.mk') \
$(shell test -d vendor && find vendor -maxdepth 4 -path '*/$(TARGET_DEVICE)/BoardConfig.mk') \
))
而这个配置文件BoardConfig.mk 决定了目标系统编译属性,比如使用ALSA还是不是 GENERIC_AUDIO 等等
当然Android 的目标输出也是由TARGET_DEVICE决定,见build/core/envsetup.mk
TARGET_OUT_ROOT_release := $(OUT_DIR)/target
TARGET_OUT_ROOT_debug := $(DEBUG_OUT_DIR)/target
TARGET_OUT_ROOT := $(TARGET_OUT_ROOT_$(TARGET_BUILD_TYPE))
TARGET_PRODUCT_OUT_ROOT := $(TARGET_OUT_ROOT)/product
PRODUCT_OUT := $(TARGET_PRODUCT_OUT_ROOT)/$(TARGET_DEVICE)
再回到 build/core/main.mk, 编译系统接着做的一个件事情是,遍历所有字目录,找到所有Android.mk文件,并将这些Android.mk文件include 进来
#
# Typical build; include any Android.mk files we can find.
#
subdir_makefiles := /
$(shell build/tools/findleaves.py --prune=out --prune=.repo --prune=.git $(subdirs) Android.mk)
include $(subdir_makefiles)
我们再来看其中的
./build/target/board/Android.mk它引用了
include $(TARGET_DEVICE_DIR)/AndroidBoard.mk
由上面TARGET_DEVICE_DIR的定义,这下又进入了
vendor或者device 下TARGET_DEVICE指向的目录了,这个mk文件中定义了特定Product需要编译和安装app和script.
通过前面的分析我们大体总结了AOSP编译的内部流程,比较杂乱,我在网上找了几个路线图提供参考:
还有两篇篇参考文章:
Android编译系统环境初始化过程分析
http://blog.csdn.net/luoshengyang/article/details/18928789
Android编译过程详解
http://www.cnblogs.com/mr-raptor/archive/2012/06/08/2541571.html
经过前面的分析我们发现如果想要移植一个新的硬件到AOSP上面只要要定义下面这些文件:
vendorsetup.sh :被envsetup.sh搜索到并将参数加载到编译选项中;
AndroidProducts.mk :文件用于定义当前厂商所拥有的所有产品列表;
BoardConfig.mk :定义硬件信息
AndroidBoard.mk :定义对应硬件编译的脚本和程序
Android.mk :模块编译配置文件。
上面这些文件中Android.mk是AOSP编译的基石,有很多编译选项,下一期我们就通过分析Android.mk来分析AOSP的编译系统。