Android编译系统分析之lunch分析
Android 编译系统解析系列文档
编译系统入口envsetup.sh解析
解析lunch的执行过程以及make执行过程中include文件的顺序
关注一些make执行过程中的几个关键点
对一些独特的语法结构进行解析
编译一个android Project,我们需要使用到makefile文件,通过makefile文件的规则我们来构建整个Project的编译过程,那么在make之前,首先我们会执行以下命令:
1 | source build/envsetup.sh |
envsetup.sh脚本
我们先来看一下source build/envsetup.sh
做了什么?
定义函数
在envsetup.sh
中定义了很多函数,函数列表大致如下:
1 | function hmm() |
这些中有我们熟悉的m,mm,mmm,lunch,add_lunch_combo,mma,croot,print_lunch_menu,findmakefile,jgrep
等等,
envsetup.sh
做的第一个工作就是定义了许多函数,方便开发者在编译的过程中调用
添加编译参数
定义这些函数之后,在脚本的最后,envsetup.sh
会遍历源码下device
以及vendor
目录,查找vendorsetup.sh
,找到之后将这些文件include(source vendorsetup.sh)
到当前位置,而这些vendorsetup.sh
中都是使用函数add_lunch_combo
定义了一些需要编译的Project
1 | function axxxxxx() |
add_lunch_combo
实现很简单,就是将其所带的参数添加到LUNCH_MENU_CHOICES
数组中
因为前边使用for
循环遍历了device
和vendor
下的vendorsetup.sh
文件,所以各个project的编译选项最后都通过add_lunch_combo被添加进了编译系统中
因此如果要想让你新加的project被android编译系统检测到,你需要在vendorsetup.sh
中使用add_lunch_combo()
添加编译参数,至于编译参数的详细格式,我们后边会有讲到
至此,source build/envsetup.sh
的工作就完成了,我们接下来看看lunch
详解lunch
lunch的使用方法
lunch的使用方法
带参数使用方法
lunch 后边可以直接带参,例如lunch meizu_m76-eng
,这个meizu_m76-eng
参数就是之前add_lunch_combo
添加的参数,像这样的在vendorsetup.sh
中定义的类似的参数我们可以直接指定不带参数使用方法
如果后边不带参数,直接使用lunch
,会在屏幕上打印出一个列表,列出了之前扫描到的所有vendorsetup.sh
中设置的project,然后选择即可
不管带不带参数,lunch都会读取project的名称,然后检查project的命名是否符合xxxx-xxx_xxxx这样的格式
然后提取出其中的product 和 varient 检查是否合法
检查project的编译参数格式是否符合xxxx-xxxx_xxx,是通过一个正则表达式来验证的
1 | (echo -n $answer | grep -q -e "^[^\-][^\-]*-[^\-][^\-]*$") |
包括后边对product 以及varient的提取也是使用sed的正则表达,检查提取出来的变量值是否合法,使用函数check_product()
和check_varient()
检查product是否合法
我们先来看check_product
1 | function check_product() |
在解析envsetup.sh
时,经常会遇到gettop()
函数,这个函数的主要作用就是取得源码的根目录的位置,具体实现内容就是依次向上查找包含 build/core/envsetup.mk 文件的目录,然后使用pwd获取当前目录即可
在check_product()
函数中,TARGET_PRODUCT
将被赋值为之前提取出的product ,例如meizu_m76
接着通过 get_build_var()
来获取一些变量的值
注意这里传入的是 TARGET_DEVICE
这个变量,这个时候变量的内容为空
接下来是get_build_var函数
1 | function get_build_var() |
定义了两个变量的值,
1 | CALLED_FROM_SETUP=true |
然后使用了command make命令传入一个指定的文件 build/core/config.mk
,带了参数 dumpvar-TARGET_DEVICE
这里需要注意一下,command命令后边跟着的是指内置的shell命令,而不是系统命令,你可以在envsetup.sh脚本的最后找到android封装过的make函数,这里调用的其实就是那个封装过的函数,函数中增加了编译校验以及时间戳的标记方便用户查看
另外,这里也是bash脚本与makefile文件交互的位置,从这一函数开始,我们接下来的主要工作就需要用到makefile文件了,所以我们暂时先跳过这部分,接着来看bash脚本相关的内容
检查variant是否合法
检查完product 之后,需要check_variant,这个比较简单,只需要查找是否在已经预定义好的VARIANT_CHOICES
数组中即可
1 | function check_variant() |
设置环境变量
然后还有最后的几步工作要做
1 | export TARGET_PRODUCT=$product |
将刚刚获取并且检查过的变量export,然后设置环境变量,并打印一些字段的值
我们注意到这里也使用到了get_build_var()
函数,而这次传入的参数是report_config
,这个具体执行过程我们在最后一起分析,至此,我们导入环境变量,并lunch product_name 的整个过程就结束了
其他注意事项
另外还有一点需要注意,envsetup.sh中导出了一些环境变量,因为envsetup.sh大多数都是local,所以为了之后操作的方便,使用export导出了如下这些环境变量
1 | ANDROID_BUILD_PATHS=$(get_build_var ANDROID_BUILD_PATHS):$ANDROID_TOOLCHAIN:$ANDROID_TOOLCHAIN_2ND_ARCH:$ANDROID_KERNEL_TOOLCHAIN_PATH$ANDROID_DEV_SCRIPTS: |
进入makefile的世界
之前我们在分析envsetup.sh脚本的时候遇到了get_build_var()
函数,这个函数指定了一个makefile文件,并传入dumpvar-$1
参数,我们可以很轻易的找到对于dumpvar-$1
类似参数出现的地方只有一个,就是dumpvar.mk文件,而我们在config.mk的尾行找到这样一句include dumpvar.mk
所以我们直接来关注dumpvar.mk文件
dumpvar.mk文件的作用是打印出一些基本的变量
我们来看看具体干了什么
1 | dumpvar_goals := \ |
这个dumpvar_goals的目标就是前边的传进来的,经过替换
dumpvar_goals := TARGET_DEVICE
1 | absolute_dumpvar := $(strip $(filter abs-%,$(dumpvar_goals))) |
如果之前追加的变量是dumpvar-abs-VARNAME
, 那就返回一个参数,我们直接lunch的时候,传入一个dumpvar-VARNAME
,因此直接来看11,12行的代码
那么
DUMPVAR_VALUE := $(TARGET_DEVICE)
dumpvar_target := dumpvar-TARGET_DEVICE
那么这个TARGET_DEVICE
的值是从哪里来的呢? 我们前边只是知道了product的值(meizu_m76),它和device的值有什么关系吗?
我们来看看相关makefile的include的关系
1 | config.mk ( |
通过以上这个include的顺序图,我们可以看到lunch的过程中,make构建规则依赖的就是include 这些文件而组成的一个大的Makefile文件
而AndroidProduct.mk的相关的定义是在product_config.mk中的这句:
all_product_configs := $(get-all-product-makefiles)
取所有的AndroidProduct.mk中定义的product_makefile
1 | define _find-android-products-files |
其中AndroidProduct.mk查找的范围为 device 和 vendor 目录下6级深度以内以及 SRC_TARGET_DIR
下的所有文件
SRC_TARGET_DIR := $(TOPDIR)build/core/target
翻译一下也就是build/core/target/
目录中的AndroidProduct.mk
那么最后all_product_configs
的值就是所有xxxxx_product.mk
的集合
注意:这里有一个关键点TARGET_BUILD_APPS
如果构建的是APP的话,我们可以只需要加载核心product makefiles就可以,在这里我们是编译系统,所以这个值为空
1 | ifneq ($(strip $(TARGET_BUILD_APPS)),) |
这里有对PRODUCT_MAKEFILES
的格式的定义,如果product_name
和文件名称一样,那么可以省略
例如:product_name
为meizu_m76
,如果product的makefile文件为meizu_m76.mk,
那么product_name这个前缀就可以省略不写
1 | Format of PRODUCT_MAKEFILES: |
搞清楚了这一点,我们来看一下如何找到current_product_makefile
1 | current_product_makefile := |
根据定义,我们可以使用简写,那么_cpm_word2
为空,我们只需将与TARGET_PRODUCT
相等的makefile加进去就可以了,
这里使用的是+=, 表示可以定义多个product_makefile,current_product_makefile
则是我们定义的TARGET_PRODUCT
过滤出来的文件,all_product_makefiles
则是之前找到的全部的product文件
product_config.mk
文件还定义了我们之前一直苦苦寻找的TARGET_DEVICE
1 | # Find the device that this product maps to. |
这个INTERNAL_PRODUCT
值通过在product.mk中定义的resolve-short-product-name
函数,返回product所对应的makefile的路径
INTERNAL_PRODUCT := $(call resolve-short-product-name, $(TARGET_PRODUCT))
google使用INTERNAL_PRODUCT
这个变量的作用是为了区分例如PRODUCT_COPY_FILES
这些变量在不同地方定义无法区分的问题,通过使用这样的定义就可以区分不同文件的相同的定义了
这里TARGET_DEVICE
的值是product makefile中的PRODUCT_DEVICE
字段,如果是meizu_m76 我们定义的就是m76
知道了TARGET_DEVICE
的值,我们继续回到dumpvar.mk中看看之后做了什么
1 |
|
定义了一个target 就是dumpvar-TARGET_DEVICE
,然后打印了TARGET_DEVICE
变量的值
这样就check_product完毕(打印了这个值就是执行成功,如果执行失败就会直接报错,也就是检查失败)
在dumpvar.mk文件的末尾,打印解析出来的一些product的字段
Android编译系统分析之lunch分析
http://www.0xforee.top/2015/12/01/android-build-system-lunch/