Android Build System[一]
来源:互联网 发布:博采网络好不好 编辑:程序博客网 时间:2024/06/01 16:09
更多干货,请关注微信公众号: tmac_lover
1. 写在最开始
Android Build System是AOSP(Android Open Source Project)中既重要又复杂的一部分,且涉及的知识点非常多,比如shell script, Makefile, python, aapt等等。但若从事Android ROM开发工作,需要通过修改编译系统来实现系统的定制,所以适当了解AOSP编译系统,对我们的工作会有非常大的帮助。
Android Build System非常庞大和复杂,一篇文章不能介绍清楚,所以后续我通过一系列的文章来讲解,并用示例和原理相结合的方式来帮助大家深入理解编译系统。Android的编译是基于linux的,并且使用make命令编译,需要有一些基本的Makefile知识,文章中会涉及到一些基本的Makefile知识,但若先对Makefile有一定的了解会有助于大家理解后续文章。
2. 建立环境
这里的建立环境并不是讲如何配置你的电脑,而是我们在编译源码之前常执行的几步操作。
$ source build/envsetup.sh$ lunch
执行完这两步之后,我们就可以使用lunch命令或者mm, mmm之类的命令编译某个模块。
我知道有些朋友肯定发现了,如果不运行source build/envsetup.sh这条命令,或者已经在一个console里运行过这条命令,但是在一个新开的console里,仍然是无法编译或者运行lunch和mm等命令。
本文以剖析这两条命令来作为Android Build System系列的开端。
3. source build/envsetup.sh
source是一条标准的linux命令,相当于.命令,这条命令和
$ ./build/envsetup.sh
功能差不多。只不过source后加的脚本不需要有可执行权限。但是其结果也就是执行一遍build/envsetup.sh这个shell脚本,而envsetup.sh脚本里定义了很多函数,执行完这个脚本之后,就可以在当前console里直接像使用linux命令一样执行这些函数,比如lunch, mm, mmm等(使用hmm可以看到所有可用命令)。所以lunch, mmm它们实质上是一个shell函数,也就相当于shell脚本。我们可以在build/envsetup.sh里找到这些命令的实现原理。
当然envsetup.sh不仅仅只是现实了lunch, mmm等这些命令,它还有一个重要的功能就是找到所有当前源码支持的平板编译选项,并放到一个数组中去,也就是后面使用lunch命令时列出来的那些选项。先来看看是如何实现的:
[build/envsetup.sh]
... ...unset LUNCH_MENU_CHOICESfunction add_lunch_combo(){ local new_combo=$1 local c for c in ${LUNCH_MENU_CHOICES[@]} ; do if [ "$new_combo" = "$c" ] ; then return fi done LUNCH_MENU_CHOICES=(${LUNCH_MENU_CHOICES[@]} $new_combo)}add_lunch_combo aosp_arm-engadd_lunch_combo aosp_arm64-engadd_lunch_combo aosp_mips-engadd_lunch_combo aosp_mips64-engadd_lunch_combo aosp_x86-engadd_lunch_combo aosp_x86_64-eng... ...for f in `test -d device && find -L device -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort` \ `test -d vendor && find -L vendor -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort`do echo "including $f" . $fdone
这里做了下面几件事:
* 使用add_lunch_combo函数添加了几个默认的平台编译选项,比如: aosp_arm-eng …
* 在源码的device和vendor目录下找到名为vendorsetup.sh的脚本,并依次执行它们,需要注意的是,vendorsetup.sh文件所在目录层级不能超过4级
* device或者vendor目录下的vendorsetup.sh里其实也是通过add_lunch_combo xxx添加平台编译选项
上面代码中有add_lunch_combo的实现,它将所 有add_lunch_combo后的参数全部保存到LUNCH_MENU_CHOICES这个数组中去(${LUNCH_MENU_CHOICES[@]}是取数组的所有元素的意思),后面lunch命令直接使用。
4. lunch
上面说过,lunch命令实质上也是envsetup.sh里的一个函数,以我的代码选择”aosp_mangosteen-userdebug”为例,看看它的实现:
[build/envsetup.sh]
function lunch(){ local answer // 如果lunch命令后接有参数,赋值给answer if [ "$1" ] ; then answer=$1 else // 否则将所有平台编译选项打印出来,并等待输入 print_lunch_menu read answer fi local selection= if [ -z "$answer" ] // 如果answer的值是0,设置aosp_arm-eng为默认 then selection=aosp_arm-eng elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$") // answer是数字的情况,从LUNCH_MENU_CHOICES数组中选择 then if [ $answer -le ${#LUNCH_MENU_CHOICES[@]} ] then selection=${LUNCH_MENU_CHOICES[$(($answer-1))]} fi elif (echo -n $answer | grep -q -e "^[^\-][^\-]*-[^\-][^\-]*$") // answer也可以直接是名字,但要符合规则 then selection=$answer fi export TARGET_BUILD_APPS= // 结果的前半部分aosp_mangosteen作为产品名,通过check_product函数检测它是否可用 local product=$(echo -n $selection | sed -e "s/-.*$//") check_product $product // 结果的后半部分userdebug作为编译变量,通过check_variant函数检测它是否可用 // variant必须严格的是user userdebug eng这几个中的一个,否则lunch结束 local variant=$(echo -n $selection | sed -e "s/^[^\-]*-//") check_variant $variant if [ -z "$product" -o -z "$variant" ] then echo return 1 fi // export几个重要的环境变量,后面make使用 export TARGET_PRODUCT=$product export TARGET_BUILD_VARIANT=$variant export TARGET_BUILD_TYPE=release echo // 设置一些重要的环境变量 set_stuff_for_environment // lunch完成结束后,打印所有和平台相关重要的环境变量 printconfig}
从上面lunch函数的代码注释可以看到,lunch实际上就是确定编译平台和编译选项,并且设置了一些关键环境变量。下面来详细讲下lunch中几个关键的地方。
4.1 check_product
小小的check_product函数,后面引入了一大堆makefile,并且执行这个函数过程中,会设置相当多你眼熟的环境变量,并且会检查lunch选择的平台是否可用,来看一看它是如何实现的吧。
check_product设置一些变量后,调用get_build_var函数,所以check_product $product相当于:
TARGET_PRODUCT=$1 \TARGET_BUILD_VARIANT= \TARGET_BUILD_TYPE= \TARGET_BUILD_APPS= \CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core \make -f build/core/config.mk dumpvar-TARGET_DEVICE > /dev/null
直接点,就是以build/core/config.mk作为makefile执行make命令,目标是dumpvar-TARGET_DEVICE。这里TARGET_PRODUCT就是lunch时选择的名字里’-‘前的部分,即aosp_mangosteen。
先看看build/core/config.mk里对其它makefile文件的依赖关系:
- 这里只列出了部分makefile包含关系,这些makefile里同时还定义了非常多的变量,比如BUILD_STATIC_LIBRARY, TARGET_OUT_JAVA_LIBRARIES等等,感兴趣可以自己去看一下
- 图中board_config_mk的定义如下:
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') \ ))
也就是$(TARGET_DEVICE)目录下的BoardConfig.mk文件。从图中可以看到,TARGET_DEVICE变量的值是在build/core/product_config.mk里确定的,在这个文件中有一段很重要的代码来确定TARGET_DEVICE:
... ...// 找到所有的AndroidProducts.mk文件all_product_configs := $(get-all-product-makefiles)current_product_makefile :=all_product_makefiles :=$(foreach f, $(all_product_configs),\ $(eval _cpm_words := $(subst :,$(space),$(f)))\ $(eval _cpm_word1 := $(word 1,$(_cpm_words)))\ $(eval _cpm_word2 := $(word 2,$(_cpm_words)))\ $(if $(_cpm_word2),\ $(eval all_product_makefiles += $(_cpm_word2))\ $(if $(filter $(TARGET_PRODUCT),$(_cpm_word1)),\ $(eval current_product_makefile += $(_cpm_word2)),),\ $(eval all_product_makefiles += $(f))\ $(if $(filter $(TARGET_PRODUCT),$(basename $(notdir $(f)))),\ $(eval current_product_makefile += $(f)),)))_cpm_words :=_cpm_word1 :=_cpm_word2 :=current_product_makefile := $(strip $(current_product_makefile))all_product_makefiles := $(strip $(all_product_makefiles))... ...ifneq (,$(filter product-graph dump-products, $(MAKECMDGOALS)))...else ifndef current_product_makefile error endif ifneq (1,$(words $(current_product_makefile))) error endif// MAKECMDGOALS = dumpvar-TARGET_DEVICE, 所以走这个分支$(call import-products, $(current_product_makefile))endifINTERNAL_PRODUCT := $(call resolve-short-product-name, $(TARGET_PRODUCT))ifneq ($(current_product_makefile),$(INTERNAL_PRODUCT)) errorendifTARGET_DEVICE := $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_DEVICE)
这段代码如果根据传入的TARGET_PRODUCT = aosp_mangosteen来查找它需要的makefile文件,如果能够找到,然后就设置TARGET_DEVICE;否则,直接报错,结束lunch。
- 函数get-all-product-makefiles定义在build/core/product.mk里,它的作用是返回device, vendor, build/target目录下所有AndroidProducts.mk文件中PRODUCT_MAKEFILES变量的值的列表
- all_product_makefiles的值是all_product_configs里所有.mk文件路径的列表
- current_product_makefile是根据TARGET_PRODUCT = aosp_mangosteen来找到的目标.mk路径,文件名它必须与TARGET_PRODUCT的值相同,在我们这里它的值是device/mstar/mangosteen/aosp_mangosteen.mk
- MAKECMDGOALS是make定义的一个全局变量,它的值是make命令指定的目标,在这里MAKECMDGOALS = dumpvar-TARGET_DEVICE
- 函数import-products定义在build/core/product.mk里, 它的作用是把current_product_makefile的值赋给PRODUCTS,并且将current_product_makefile代表的.mk文件加载进来, 并将它里的变量值保存到变量PRODUCTS.$(current_product_makefile).xxx中,
xxx就是.mk文件中每个变量名字,比如 PRODUCTS.\$(INTERNAL_PRODUCT).PRODUCT_DEVICE = PRODUCT_DEVICE = mangosteen - 函数resolve-short-product-name定义在build/core/product.mk里,它将TARGET_PRODUCT的值和PRODUCTS列表里的.mk文件中PRODUCT_NAME的值一一对比,返回和TARGET_PRODUCT值相同PRODUCT_NAME变量所在的那个.mk文件的路径
- 根据上面的分析TARGET_DEVICE的值,其实就是device/mstar/mangosteen/aosp_mangosteen.mk文件中PRODUCT_DEVICE的值,它的值mangosteen
- 如果上面的正常步骤中有任何一项没找到或者匹配不成功,则lunch就以失败结束
再回头看看那条make语句: make -f build/core/config.mk dumpvar-TARGET_DEVICE > /dev/null, 结合build/core/dumpvar.mk中下面这段看:
dumpvar_goals := \ $(strip $(patsubst dumpvar-%,%,$(filter dumpvar-%,$(MAKECMDGOALS))))ifdef dumpvar_goals absolute_dumpvar := $(strip $(filter abs-%,$(dumpvar_goals))) ifdef absolute_dumpvar ... else DUMPVAR_VALUE := $($(dumpvar_goals)) dumpvar_target := dumpvar-$(dumpvar_goals) endif.PHONY: $(dumpvar_target)$(dumpvar_target): @echo $(DUMPVAR_VALUE)
- 上一段有说过,MAKECMDGOALS在这里就是dumpvar-TARGET_DEVICE
- 这段代码中,dumpvar_goals = TARGET_DEVICE;DUMPVAR_VALUE = $(TARGET_DEVICE) = mangosteen; dumpvar_target = dumpvar-mangosteen
- 所以make -f build/core/config.mk dumpvar-TARGET_DEVICE > /dev/null最终就是一句”echo $(DUMPVAR_VALUE)”, 因为”> /dev/null”将make输出的正确信息重定向到/dev/null, 所以正常情况下,在console里看不到任何输出
到这里check_product函数就执行完了。
4.2 set_stuff_for_environment
函数set_stuff_for_environment里有一个比较重要的函数setpaths(),它的作用是往PATH环境变量中添加一些路径,让我们可以在执行过lunch的console里使用Android源码里的一些工具。
function setpaths() { ... ... export ANDROID_BUILD_PATHS=$(get_build_var ANDROID_BUILD_PATHS):$ANDROID_TOOLCHAIN:$ANDROID_TOOLCHAIN_2ND_ARCH:$ANDROID_DEV_SCRIPTS: export PATH=$ANDROID_BUILD_PATHS$PATH ... ...}
可以使用echo $ANDROID_BUILD_PATHS 命令查看新增了哪些目录。
4.3 printconfig
printconfig函数的核心是调用”get_build_var report_config”, 和check_product函数流程类似,在build/core/dumpvar.mk里可以看到:
ifneq ($(PRINT_BUILD_CONFIG),)HOST_OS_EXTRA:=$(shell python -c "import platform; print(platform.platform())")$(info ============================================)$(info PLATFORM_VERSION_CODENAME=$(PLATFORM_VERSION_CODENAME))$(info PLATFORM_VERSION=$(PLATFORM_VERSION))$(info TARGET_PRODUCT=$(TARGET_PRODUCT))$(info TARGET_BUILD_VARIANT=$(TARGET_BUILD_VARIANT))$(info TARGET_BUILD_TYPE=$(TARGET_BUILD_TYPE))$(info TARGET_BUILD_APPS=$(TARGET_BUILD_APPS))$(info TARGET_ARCH=$(TARGET_ARCH))$(info TARGET_ARCH_VARIANT=$(TARGET_ARCH_VARIANT))$(info TARGET_CPU_VARIANT=$(TARGET_CPU_VARIANT))$(info TARGET_2ND_ARCH=$(TARGET_2ND_ARCH))$(info TARGET_2ND_ARCH_VARIANT=$(TARGET_2ND_ARCH_VARIANT))$(info TARGET_2ND_CPU_VARIANT=$(TARGET_2ND_CPU_VARIANT))$(info HOST_ARCH=$(HOST_ARCH))$(info HOST_OS=$(HOST_OS))$(info HOST_OS_EXTRA=$(HOST_OS_EXTRA))$(info HOST_BUILD_TYPE=$(HOST_BUILD_TYPE))$(info BUILD_ID=$(BUILD_ID))$(info OUT_DIR=$(OUT_DIR))$(info ============================================)endif
有没有发现,这就是lunch命令执行成功之后,console里打印出来的一串信息。
到这里lunch命令就执行完了,总得来说,lunch就是根据我们的输入,找到对应平台的所有相关makefile, 并根据makefile里定义的变量值,设置编译需要的变量的值;同时也添加一些源码中的目录到PATH中,方便使用源码目录中的一些命令。
5. mmm命令
这里顺带再简单介绍下mmm命令,如果你看懂了lunch命令的原理后,mmm也就很容易了。我们都知道使用mmm可以编译单个模块,比如:mmm packages/apps/Launcher3
function mmm() { ... ... for DIR in $DIRS ; do MODULES=`echo $DIR | sed -n -e 's/.*:\(.*$\)/\1/p' | sed 's/,/ /'` if [ "$MODULES" = "" ]; then MODULES=all_modules fi ... ... done ONE_SHOT_MAKEFILE="$MAKEFILE" $DRV make -C $T -f build/core/main.mk $DASH_ARGS $MODULES $ARGS ... ...}
- mmm先从参数中分离出需要编译模块的目录,mmm可以指定多个目录下的模块进行编译,比如:mmm packages/apps/Launcher3 packages/apps/Launcher2
- 如果Android.mk中有多个module, 可以通过命令指定编译某一个module, 使用:和目录分开, 例如: mmm packages/apps/Launcher3:Launcher3; 若不指定,则默认编译Android.mk中所有的module
- 可以看到mmm最终是定义了一个变量ONE_SHOT_MAKEFILE,然后使用make -C
T−fbuild/core/main.mk DASH_ARGSMODULES ARGS,还是一个make命令。注意make后加了-CT,因为我们可能在packages/apps/下使用mmm.编译,而make−f指定的makefile文件是源码根目录下的相对路径,所以−C T是必要的
6. 总结
本文以介绍lunch和mmm命令作为编译系统的开篇,特别是lunch命令,虽然它没有编译任何文件,但是它仍然使用了make命令,并且加载了一大堆的makefile,后面编译都以lunch为基础,并使用了lunch设置的很多变量。
- Android Build System[一]
- Android Build System详解<一>
- Android build system
- Android Build System
- Android build system
- Android Build System
- Android build System
- Android build system note
- android build system links
- Android build system note
- Android build system note
- Android build system note
- Android build system note
- Android build System
- Android Build System
- Android build system note
- Android快速Build system
- Android Build System
- bzoj 3889: [Usaco2015 Jan]Cow Routing SPFA
- Python搭建开发环境
- Win10+VS2013+PCL1.8 环境配置
- Hook学习(二):使用场景
- lua语句
- Android Build System[一]
- Swift的初认识
- 关于背景全屏展示
- struts2知识点总结
- Android 一键退出程序最简单的方法 finishAffinity()
- VUE的MVVM框架解析
- C++ primer 第十章 对象和类
- linux常用命令之文件处理命令
- String、StringBuffer、与StringBuilder的区别