Android 编译系统解析系列文档
编译系统入口envsetup.sh解析
解析lunch的执行过程以及make执行过程中include文件的顺序
关注一些make执行过程中的几个关键点
对一些独特的语法结构进行解析
我们首先来看看今天的主角,以下这段代码就是解析AndroidProducts.mk以及其内容的关键代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 ################### build/core/product_config.mk ################### ifneq ($(strip $(TARGET_BUILD_APPS)),) # An unbundled app build needs only the core product makefiles. all_product_configs := $(call get-product-makefiles,\ $(SRC_TARGET_DIR)/product/AndroidProducts.mk) else # Read in all of the product definitions specified by the AndroidProducts.mk # files in the tree. all_product_configs := $(get-all-product-makefiles) endif # Find the product config makefile for the current product. # all_product_configs consists items like: # <product_name>:<path_to_the_product_makefile> # or just <path_to_the_product_makefile> in case the product name is the # same as the base filename of the product config makefile. 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))) # Import all product makefiles. $(call import-products, $(all_product_makefiles)) else # Import just the current product. ifndef current_product_makefile $(error Can not locate config makefile for product "$(TARGET_PRODUCT)") endif ifneq (1,$(words $(current_product_makefile))) $(error Product "$(TARGET_PRODUCT)" ambiguous: matches $(current_product_makefile)) endif $(call import-products, $(current_product_makefile)) endif # Import all or just the current product makefile # Sanity check $(check-all-products) ifneq ($(filter dump-products, $(MAKECMDGOALS)),) $(dump-products) $(error done) endif # Convert a short name like "sooner" into the path to the product # file defining that product. # INTERNAL_PRODUCT := $(call resolve-short-product-name, $(TARGET_PRODUCT)) ifneq ($(current_product_makefile),$(INTERNAL_PRODUCT)) $(error PRODUCT_NAME inconsistent in $(current_product_makefile) and $(INTERNAL_PRODUCT)) endif current_product_makefile := all_product_makefiles := all_product_configs :=
该加载哪里的AndroidProducts.mk文件? 我们将之前的代码拆着来看,首先看AndroidProducts.mk文件是如何被加载到Android整个编译环境中的
1 2 3 4 5 6 7 8 9 10 11 12 13 ################### build/core/product_config.mk ################### ifneq ($(strip $(TARGET_BUILD_APPS)),) # An unbundled app build needs only the core product makefiles. all_product_configs := $(call get-product-makefiles,\ $(SRC_TARGET_DIR)/product/AndroidProducts.mk) else # Read in all of the product definitions specified by the AndroidProducts.mk # files in the tree. all_product_configs := $(get-all-product-makefiles) endif
这里判断构建的目标是不是APP,对于独立APP的编译,只需要加载核心目录下(build/target/product)AndroidProducts.mk文件即可,如果是构建整个系统,那么需要加载所有的AndroidProducts.mk文件
TARGET_BUILD_APPS
这个变量可以通过tapas
命令指定(具体命令使用方式请参见envsetup.sh),也可以通过”APP-<appname>” 来指定
这个变量默认为空,也就是编译整个系统,我们可以在加载环境变量之后通过使用printconfig命令来查看我们是否设置过TARGET_BUILD_APPS
变量
取得编译系统中所有的AndroidProducts.mk get-all-product-makefiles
函数定义在build/core/product.mk文件中,是get-product-makefiles
的一个简单的封装
1 2 3 4 5 6 7 8 9 10 11 12 #################### build/core/product.mk #################### # # Returns the sorted concatenation of all PRODUCT_MAKEFILES # variables set in all AndroidProducts.mk files. # $(call ) isn't necessary. # define get-all-product-makefiles $(call get-product-makefiles,$(_find-android-products-files)) endef
其中的_find-android-products-files
函数返回的是整个编译系统中所有AndroidProducts.mk的集合
1 2 3 4 5 6 7 8 9 10 11 12 13 #################### build/core/product.mk #################### # # Returns the list of all AndroidProducts.mk files. # $(call ) isn't necessary. # define _find-android-products-files $(shell test -d device && find device -maxdepth 6 -name AndroidProducts.mk) \ $(shell test -d vendor && find vendor -maxdepth 6 -name AndroidProducts.mk) \ $(SRC_TARGET_DIR)/product/AndroidProducts.mk endef
如上:扫描device与vendor目录下6层深度的子目录下的所有AndroidProducts.mk文件,以及build/target/product/AndroidProducts.mk文件,从这里我们可以看出,这里的得到的最后结果带有相对于源码根目录的相对路径,类似以下格式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 device/htc/flounder/AndroidProducts.mk device/meizu/m86/AndroidProducts.mk device/samsung/avl7420/AndroidProducts.mk .... ``` > **注:**`SRC_TARGET_DIR=build/target` ### 处理AndroidProducts.mk 接下来要对扫描出的AndroidProducts.mk文件进行处理 ```makefile?linenums=41 #################### build/core/product.mk #################### # # Returns the sorted concatenation of PRODUCT_MAKEFILES # variables set in the given AndroidProducts.mk files. # $(1 ): the list of AndroidProducts.mk files. # define get-product-makefiles $(sort \ $(foreach f,$(1 ), \ $(eval PRODUCT_MAKEFILES :=) \ $(eval LOCAL_DIR := $(patsubst %/,%,$(dir $(f)))) \ $(eval include $(f)) \ $(PRODUCT_MAKEFILES) \ ) \ $(eval PRODUCT_MAKEFILES :=) \ $(eval LOCAL_DIR :=) \ ) endef
我们注意到以上代码有一行是对LOCAL_DIR
进行了定义,这个定义的原因是因为AndroidProducts.mk文件中定义的格式像下边这样:
1 2 3 ifeq ($(TARGET_PRODUCT) ,meizu_m86) PRODUCT_MAKEFILES := $(LOCAL_DIR) /meizu_m86.mk endif
我们还记得上边扫描得出的所有AndroidProducts.mk的集合是带有相对路径的,所以我们这里可以通过dir
获取路径,然后置换为下一行include对应AndroidProducts.mk中的LOCAL_DIR
,这样我们就得到了我们真正要加载的makefile文件,就是我们配置一个device需要用到的文件(例:meizu_m86.mk)
这样在将所有的AndroidProducts.mk文件中的内容解析完毕之后,我们就得到了一份使用sort排序并去重的product_makefile文件列表
注意: 这里我们并未区分我们要编译的product_makefile
,也就是说这是一个包含全部product_makefile
的列表
取得current_makefile(当前lunch机型的配置文件) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ################### build/core/product_config.mk ################### 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),\ #then-1 $(eval all_product_makefiles += $(_cpm_word2))\ $(if $(filter $(TARGET_PRODUCT),$(_cpm_word1)),\ #then-2 $(eval current_product_makefile += $(_cpm_word2)),),\ #else $(eval all_product_makefiles += $(f))\ $(if $(filter $(TARGET_PRODUCT),$(basename $(notdir $(f)))),\ #then-3 $(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))
关于makefile中IF的语法:$(if <condition>, <then-part>, <else-part> )
从前边的代码我们可以知道all_product_configs是代表device以及vendor所有的AndroidProducts.mk文件中变量PRODUCT_MAKEFILES
的值的集合,这个PRODUCT_MAKEFILES
值包括两种情况
<product_name>:<path_to_the_product_makefile>
<path_to_the_product_makefile>
也就是在上边代码中==then-1==做出判断,我们一般都是使用的第二种情况,所以我们就解析一下else的情况,else主要做了两步处理
将所有的product_makefile文件加入到all_product_makefiles
变量中
通过TARGET_PRODUCT来解析出对应的product_makefile文件
通过以上两步,我们可以得到一个包含全部device,vendor下的product_makefile文件的变量all_product_makefiles
以及当前我们需要编译的product_makefile的变量current_product_makeifle
以上的示例也提醒我们,AndroidProducts.mk文件内容中指向的product_makefile名称必须标准
导入PRODUCT变量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 ################### build/core/product_config.mk ################### ifneq (,$(filter product-graph dump-products, $(MAKECMDGOALS))) # Import all product makefiles. $(call import-products, $(all_product_makefiles)) else # Import just the current product. ifndef current_product_makefile $(error Can not locate config makefile for product "$(TARGET_PRODUCT)") endif ifneq (1,$(words $(current_product_makefile))) $(error Product "$(TARGET_PRODUCT)" ambiguous: matches $(current_product_makefile)) endif $(call import-products, $(current_product_makefile)) endif # Import all or just the current product makefile # Sanity check $(check-all-products) ifneq ($(filter dump-products, $(MAKECMDGOALS)),) $(dump-products) $(error done) endif # Convert a short name like "sooner" into the path to the product # file defining that product. # INTERNAL_PRODUCT := $(call resolve-short-product-name, $(TARGET_PRODUCT)) ifneq ($(current_product_makefile),$(INTERNAL_PRODUCT)) $(error PRODUCT_NAME inconsistent in $(current_product_makefile) and $(INTERNAL_PRODUCT)) endif
在上边所示的代码中,google也给出了调试product_makefile的方式:
MAKECMDGOALS
中如果包含dump-products
,那么执行$(dump-products)
命令打印所有的PRODUCT_XXXX
变量,具体规则定义位于build/core/product.mk
文件
MAKECMDGOALS
中如果包含product-graph
,那么google会在out目录生成一个pdf和svg文件,这两个文件内包含了所有的product_makefile文件之间的相互依赖关系,具体规则定义位于build/core/tasks/product-graph.mk
一般的编译来说,是调用import-products
导入当前的我们要编译的机型配置,也就是这个current_product_makefile
这个变量的值
注意 :current_product_makefile
这个值是唯一 的,否则会报错
重要说明: 对于各个目录下定义的PRODUCT_
开头的相同的变量都会在import-products
中得到处理(或者说展开),在处理完毕之后,对于各个变量的获取,我们都可以在如下格式的变量中获取到PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_XXXX
其中INTERNAL_PRODUCT
值为上边current_product_makefile
的值,类似device/meizu/m86/meizu_m86.mk
PRODUCT_XXXXX
表示PRODUCT_COPY_FILES
,PRODUCT_LOCALES
等变量
让我们接着来看看import-products
干了什么
1 2 3 4 5 6 7 #################### build/core/product.mk #################### define import-products $(call import-nodes,PRODUCTS,$(1),$(_product_var_list)) endef
我们记录一下传入的参数:$(1) = $(current_product_makefile)
实际调用import-nodes
导入传入的参数,这里的_product_var_list
是以PRODUCT开头的一系列的变量的枚举
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #################### build/core/product.mk #################### _product_var_list := \ PRODUCT_NAME \ PRODUCT_MODEL \ PRODUCT_LOCALES \ PRODUCT_AAPT_CONFIG \ PRODUCT_AAPT_PREF_CONFIG \ PRODUCT_AAPT_PREBUILT_DPI \ PRODUCT_PACKAGES \ PRODUCT_PACKAGES_DEBUG \ PRODUCT_PACKAGES_ENG \ PRODUCT_PACKAGES_TESTS \ PRODUCT_DEVICE \ PRODUCT_MANUFACTURER \ PRODUCT_BRAND \ PRODUCT_PROPERTY_OVERRIDES \ PRODUCT_DEFAULT_PROPERTY_OVERRIDES \ PRODUCT_CHARACTERISTICS \ PRODUCT_COPY_FILES \ PRODUCT_OTA_PUBLIC_KEYS \ PRODUCT_EXTRA_RECOVERY_KEYS \ PRODUCT_PACKAGE_OVERLAYS \ DEVICE_PACKAGE_OVERLAYS \ PRODUCT_TAGS \ ...... ``` 我们再来看看`import-nodes`这个函数干了什么 ```makefile?linenums=241 #################### build/core/node_fns.mk #################### # # $(1): output list variable name, like "PRODUCTS" or "DEVICES" # $(2): list of makefiles representing nodes to import # $(3): list of node variable names # define import-nodes $(if \ $(foreach _in,$(2), \ $(eval _node_import_context := >>==_nic.$(1).[[$(_in)]]==<<) \ # _node_import_context := _nic.PRODUCT.[[device/meizu/m86/meizu_m86.mk]]# $(if $(_include_stack),$(eval $(error ASSERTION FAILED: _include_stack \ should be empty here: $(_include_stack))),) \ $(eval _include_stack := ) \ $(call >>==_import-nodes-inner,$(_node_import_context),$(_in),$(3)==<<) \ $(call move-var-list,$(_node_import_context).$(_in),$(1).$(_in),$(3)) \ $(eval _node_import_context :=) \ $(eval $(1) := $($(1)) $(_in)) \ $(if $(_include_stack),$(eval $(error ASSERTION FAILED: _include_stack \ should be empty here: $(_include_stack))),) \ ) \ ,) endef
import-nodes
函数主要做了以下几件事:
以current_product_makefile
定义了一个变量,这个变量的作用其实就是用来标示唯一性的,在后边的代码中会将这个变量当前缀使用
使用_import-nodes-inner
函数做具体的解析工作
将上一步解析完毕的变量变换前缀
将解析过的current_product_makefile
都添加到PRODUCTS变量中
重要提示 move-var-list
用法很简单:$(call move-var-list,SRC,DST,A B)
:变更A和B的前缀SRC到DST
我们之前提到过,在所有的PRODUCT_XXXX变量都展开之后,也就是import-products current_product_makefile
之后,所有的PRODUCT_XXXX变量都会被集中到以PRODUCTS.$(INTERNAL_PRODUCT)
为前缀的对应的变量中,这个操作就是使用move-var-list
来完成的
下边是_import-nodes-innner
函数中将要使用到的变量的列表表示:
$(_node_import_context)
$(_in)
$(3)
$(2)
$(1)
_nic.PRODUCTS.[[device/meizu/m86/meizu_m86.mk]]
device/meizu/m86/meizu_m86.mk
$(_product_var_list)
device/meizu/m86/meizu_m86.mk
PRODUCTS
重要提示
对于$(_node_import_context)
所代表的内容为了方便叙述,我们定义为公有前缀 ,对于每次编译的目标,公有的前缀是唯一的
对于$(_in)
或者$(2)
中表示的内容,我们定义为私有前缀 ,对于同一编译目标不同makefile文件中的相同PRODUCT_XXX变量,我们都会使用公有前缀+私有前缀 作为前缀来区分
我们继续来看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #################### build/core/node_fns.mk #################### # # $(1): context prefix # $(2): list of makefiles representing nodes to import # $(3): list of node variable names # define _import-nodes-inner $(foreach _in,$(2), \ $(if $(wildcard $(_in)), \ $(if >>==$($(1).$(_in).seen==<<), \ $(eval ### "skipping already-imported $(_in)") \ , \ $(eval $(1).$(_in).seen := true) \ $(call >>==_import-node,$(1),$(strip $(_in)),$(3)==<<) \ ) \ , \ $(error $(1): "$(_in)" does not exist) \ ) \ ) endef
重要提示
wildcard
是一个通配符的关键字,这里用来判断$(_in)文件是否存在
这里我们看到有一个foreach循环,这个在第一次的时候因为参数之后current_product_makefile,所以不会用到,后边我们在用到继承性的时候,因为会有继承多个product的情况发生,所以需要foreach这个函数来遍历
这里会有一个变量($(1).$(_in).seen
)来标示文件的内容是否已经导入,以86为例,变量以及内容分别为
$(1)
$(_in)
$(3)
_nic.PRODUCTS.[[device/meizu/m86/meizu_m86.mk]]
device/meizu/m86/meizu_m86.mk
_product_var_list
很长很变态…..
_import-nodes-inner
函数只是来判断是否导入过文件,如果没有导入,使用_import_node
来实际导入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 #################### build/core/node_fns.mk #################### # # $(1): context prefix # $(2): makefile representing this node # $(3): list of node variable names # # _include_stack contains the list of included files, with the most recent files first. define _import-node $(eval _include_stack := $(2) $$(_include_stack)) $(call clear-var-list, $(3)) $(eval LOCAL_PATH := $(patsubst %/,%,$(dir $(2)))) $(eval MAKEFILE_LIST :=) $(eval >>==include $(2)==<<) $(eval _included := $(filter-out $(2),$(MAKEFILE_LIST))) $(eval MAKEFILE_LIST :=) $(eval LOCAL_PATH :=) $(call copy-var-list, $(1).$(2), $(3)) $(call clear-var-list, $(3)) $(eval $(1).$(2).inherited := \ $(call >>==get-inherited-nodes,$(1).$(2),$(3))==<<) $(call >>==_import-nodes-inner,$(1),$($(1).$(2).inherited),$(3)==<<) $(call >>==_expand-inherited-values,$(1),$(2),$(3)==<<) $(eval $(1).$(2).inherited :=) $(eval _include_stack := $(wordlist 2,9999,$$(_include_stack))) endef
知识点 MAKEFILE_LIST
: 这个变量内容包含,在环境变量中指定的,命令行中指定的,以及make指定makefile文件时,使用include包含进的文件,这三者所组成的列表
_import-node
函数主要做了以下几件事:
将找到的最新文件入栈,此时栈应该是空的,也就是说meizu_m86.mk是第一个入栈的
清除所有的PRODUCT_开头的变量的值
include meizu_m86.mk meizu_m86.mk
是整个机型的配置入口,此处开始处理
将处理过的文件添加到_included变量中
将第三步include进来的文件中的PRODUCT_开头的变量使用copy-var-list
函数添加$(1).$(2)前缀,这里就是公有前缀+私有前缀
取得带有@inherit前缀的变量,然后去掉@inherit前缀,然后排序去重,获得继承第一层的makefile文件的列表,记录到公有前缀+私有前缀+inherited
变量中
通过_import-nodes-inner
来循环获取上一步得到的继承列表,将所有层次的继承的文j件都获取到,最后得到一个解除@inherit前缀的包含所有继承层次的makefile文件列表
_expand_-inherited-values
展开上一步得到的这些文件中_product_var_list中变量的值
清空继承列表与_include_stack
这里第1步到第7部,以及加上前边的_import-nodes-inner
一起构成了递归,我们在递归展开这些变量的最后一步时,会调用_expand-inherited-values
来从最深层次的继承一直展开到最浅层次的继承,也就是第一层继承,要明白最深层次与之后浅层次的makefile中变量的关系,是最深覆盖最浅?还是最浅覆盖最深?或者二者叠加?我们就需要看_expand-inherited-values
的具体内容
什么是inherit? 在看最后一个函数的内容之前,我们还需要了解一个知识点,在第5步的时候,出现了一个新的概念inherit,也就是继承,我们经常会在product_makefile中看到inherit-product
函数就是继承的一个典型的应用,我们先来看看它是怎么用的,然后再往下看具体的解析过程
1 2 3 4 5 6 7 8 9 define inherit-product $(foreach v,$(_product_var_list), \ $(eval $(v) := $($(v)) $(INHERIT_TAG)$(strip $(1)))) \ $(eval inherit_var := \ PRODUCTS.$(strip $(word 1,$(_include_stack))).INHERITS_FROM) \ $(eval $(inherit_var) := $(sort $($(inherit_var)) $(strip $(1)))) \ $(eval inherit_var:=) \ $(eval ALL_PRODUCTS := $(sort $(ALL_PRODUCTS) $(word 1,$(_include_stack)))) endef
这个函数在build/core/product.mk中定义,后边的参数都是跟一个makefile名称 示例:$(call inherit-product, $(SRC_TARGET_DIR)/product/full_base.mk)
这个函数主要做了以下几件事:
为_product_var_list中所有变量 挨个加上@inherit xxxmakefile
的前缀,这个xxxmakfile就是传入的$(1)变量值
组织当前正被处理(include)的文件内容的继承列表,具体是这样的:
我们用PRODUCTS.当前makefile.INHERITS_FROM
这个变量来存放当前makefile的继承列表
然后这个文件中的每一个使用inherit-product
函数继承的makefile,都会被加到以上变量中 这一步的具体过程就是以下:PRODUCTS.当前makefile.INHERITS_FORM :=$(sort $(PRODUCT.当前makefile.INHERITS_FROM) last_makefile )
将当前正在处理(include)的makefile加入到变量ALL_PRODUCTS中
了解了这个背景知识之后,我们可以得出以下几点:
我们注意到之前分析的_import-node
第三步有一个include product_makefile文件的操作,这里我们分析的inherit-product
函数就在这个include的操作中被调用
include操作是发生在import-node
函数中,因此include的makefile也会被加入到_include_stack中
_product_var_list中的每个变量也都加入了带有@inherit前缀的所继承的xxxmakefile
inherit-product
函数中还提供了一个INHERIT_FROM的变量,这个变量的相关用法,我们可以在build/core/tasks/product-graph.mk
见到
由以上内容得知,在这里我们只需要明白调用inherit-product
函数只是添加了@inherit:
这个前缀就行,当然从这里我们也可以看出,如果一个makefile文件中inherit两次同一个makefile,也会被在这里去重
我们继续向下来看是如何解析加入@inherit前缀的这些变量
_get-inherited-nodes
的内容也很简单
1 2 3 4 5 6 7 define get-inherited-nodes $(sort \ $(subst $(INHERIT_TAG),, \ $(filter $(INHERIT_TAG)%, \ $(foreach v,$(2),$($(1).$(v))) \ ))) endef
将_product_var_list中的变量挨个取出,这里的变量已经添加了前缀,因此要使用前缀取出,也就是类似_nic.PRODUCTS.[[device/meizu/m86/meizu_m86.mk]].device/meizu/m86/meizu_m86.mk所代表的product文件来取出var变量的值
过滤出带有@inherit前缀的变量值
将前缀去掉,然后排序去重,最后得到一份继承的makefile的list
这个函数有点复杂,我们在这里说明一下:
for循环之后,在_product_var_list中的全部变量(PRODUCT_LOCALES,PRODUCT_FILES等)中带有@inheri前缀的内容都会被取出来,然后去掉前缀,排序去重,我们之前解析过使用@inherit前缀的函数inherit-product
,知道调用函数之后,所有的PRODUCT_XXX都会继承@inherite标识后边加入的makefile,因此,这一块的内容其实只是将我们之前include的makefile文件中所继承的(也就是调用inherit-product后的参数)所有makefile做了一个集合,你调用了几个inherit-product,也就有几个继承,这个函数的返回值最后是要记录在公共前缀+私有前缀+inherited
这个变量中的,来表示某个makefile文件的继承性的
经过以上3步之后,我们可以(filter过滤出了带@inherit前缀的变量,故变量原本的值没有在这个列表中)去除@inherit前缀的继承的makefile列表的集合,并且此集合是排序去重过的,这个集合被赋值给了$(1).$(2).inherited
也就是之前我们说到的以makefile绝对路径来区分的前缀,然后又会重复调用_import-nodes-inner
这个函数,这个函数我们已经解析过了,只是用来判断是否解析过传入的文件列表,实际将同样的参数传入了_import-node
这个函数来进行解析
总的来说_import-node
与_import-nodes-inner
与get-inherited-nodes
在不停的循环,将每次get-inherit-nodes
得到的去除了@inherit
的makefile重新放入循环中,然后解析出这个makefile文件的所有的_product_var_list
所对应的继承关系,将其中_product_var_list
中的变量的值都加上某一前缀,这个前缀就是_include_stack
栈中最近的一个makefile,因此不需要担心不同的makefile文件中的同一变量会互相覆盖,他们都会以不同的makefile作为前缀来标示
也就是在_expand-inherited-values
函数之前,我们都会得到相如以下类型的变量:
1 _nic.PRODUCTS.[[device/meizu/m86/meizu_m86.mk]] .last_makefile.PRODUCT_xxxx := aaa bbb @inherit xxxmakefile @inherit yyymakefile
展开继承的变量 我们来看最后一个函数_expand-inherited-values
,我们将传入_expand-inherited-values
的参数列举出来,方便后边查看
$(1)
$(2)
$(3)
_nic.PRODUCTS.[[device/meizu/m86/meizu_m86.mk]]
last_makefile
$(_product_var_list)
假设,我们这里的$(2),也就是last_makefile,是example.txt举例的最深层次的makefile文件,也就是build/target/product/embedded.mk
文件,这个文件内容中已经不包含继承关系,因此_get_inherited_nodes
返回的内容为空,_import-node-inner
也什么都不做,我们可以直接看_expand-inherited-values
仔细看传入的参数,其实就是传入_import_node
的三个参数,其实就是公有前缀,私有前缀,以及一个PRODUCT_xxx的列表,我们可以用这三个参数组成我们调用_expand-inherited-values
函数之前的那种类型的变量
接下来我们来实际解析这个函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 define _expand-inherited-values $(foreach v,$(3), \ $(eval ### "Shorthand for the name of the target variable") \ $(eval _eiv_tv := $(1).$(2).$(v)) \ $(eval ### "Get the list of nodes that this variable inherits") \ $(eval _eiv_i := \ $(sort \ $(patsubst $(INHERIT_TAG)%,%, \ $(filter $(INHERIT_TAG)%, $($(_eiv_tv)) \ )))) \ $(foreach i,$(_eiv_i), \ $(eval ### "Make sure that this inherit appears only once") \ $(eval $(_eiv_tv) := \ $(call uniq-word,$($(_eiv_tv)),$(INHERIT_TAG)$(i))) \ $(eval ### "Expand the inherit tag") \ $(eval $(_eiv_tv) := \ $(strip \ >>==$(patsubst $(INHERIT_TAG)$(i),$($(1).$(i).$(v)), \ $($(_eiv_tv)))==<<)) \ $(eval ### "Clear the child so DAGs don't create duplicate entries" ) \ $(eval $(1).$(i).$(v) :=) \ $(eval ### "If we just inherited ourselves, it's a cycle.") \ $(if $(filter $(INHERIT_TAG)$(2),$($(_eiv_tv))), \ $(warning Cycle detected between "$(2)" and "$(i)" for context "$(1)") \ $(error import of "$(2)" failed) \ ) \ ) \ ) \ $(eval _eiv_tv :=) \ $(eval _eiv_i :=) endef
这个函数做了以下几件事:
_eiv_tv 将_product_var_list中的所有变量都添加了公有前缀+私有前缀
_eiv_i 表示当前正在处理的makefile文件的继承关系makefile列表,是通过过滤@inherit这个前缀拿到的,显而易见,这个是从倒数第二深的makefile文件起作用,因为最深层次的makefile变量中不包含继承关系
使用uniq-word
来确保只继承了一次,这种继承的检查发生在上层与紧挨着的下层之间
展开@inherit表示的变量,其实也就是使用比他深一层次的makefile文件的对应的PRODUCT_XXX变量替换@inherit这个标识符
检查是否循环继承(自己继承了自己)
我们接着看uniq-word
这个函数的实现
1 2 3 4 5 6 7 8 9 10 11 define uniq-word $(strip \ $(if $(filter-out 0 1,$(words $(filter $(2),$(1)))), \ $(eval h := |||$(subst $(space),|||,$(strip $(1)))|||) \ $(eval h := $(subst |||$(strip $(2))|||,|||$(space)|||,$(h))) \ $(eval h := $(word 1,$(h)) $(2) $(wordlist 2,9999,$(h))) \ $(subst |||,$(space),$(h)) \ , \ $(1) \ )) endef
代码比较简单,读者可以根据前边的内容来分析这个函数的实际作用,不再赘述
我们继续往下看,还记得之前我们通过不停的调用_import-node
与_import-nodes-inner
与get-inherited-nodes
函数构建了所有有继承关系的makefile自己的变量的值的关系,所以我们在这里展开的时候,直接将@inherit:last_makefile
替换为PRODUCT.last_makefile.PRODUCT_xxx
的值,这里就简单的展开结束,因此我们最后得到就是所有继承变量的综合起来的内容
检查所有的product 最后还剩下一点简单的代码,是用来解析出一个short-name后边来使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # Sanity check $(check-all-products) ifneq ($(filter dump-products, $(MAKECMDGOALS)),) $(dump-products) $(error done) endif # Convert a short name like "sooner" into the path to the product # file defining that product. # INTERNAL_PRODUCT := $(call resolve-short-product-name, $(TARGET_PRODUCT)) ifneq ($(current_product_makefile),$(INTERNAL_PRODUCT)) $(error PRODUCT_NAME inconsistent in $(current_product_makefile) and $(INTERNAL_PRODUCT)) endif
首先,check-all-products
来检查全部products
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 define check-all-products $(if ,, \ $(eval _cap_names :=) \ $(foreach p,$(PRODUCTS), \ $(eval pn := $(strip $(PRODUCTS.$(p).PRODUCT_NAME))) \ $(if $(pn),,$(error $(p): PRODUCT_NAME must be defined.)) \ $(if $(filter $(pn),$(_cap_names)), \ $(error $(p): PRODUCT_NAME must be unique; "$(pn)" already used by $(strip \ $(foreach \ pp,$(PRODUCTS), $(if $(filter $(pn),$(PRODUCTS.$(pp).PRODUCT_NAME)), \ $(pp) \ ))) \ ) \ ) \ $(eval _cap_names += $(pn)) \ $(if $(call is-c-identifier,$(pn)),, \ $(error $(p): PRODUCT_NAME must be a valid C identifier, not "$(pn)") \ ) \ $(eval pb := $(strip $(PRODUCTS.$(p).PRODUCT_BRAND))) \ $(if $(pb),,$(error $(p): PRODUCT_BRAND must be defined.)) \ $(foreach cf,$(strip $(PRODUCTS.$(p).PRODUCT_COPY_FILES)), \ $(if $(filter 2 3,$(words $(subst :,$(space),$(cf)))),, \ $(error $(p): malformed COPY_FILE "$(cf)") \ ) \ ) \ ) \ ) endef
这里简单调用了resolve-short-product-name
的函数,然后将参数传入_resolve-short-product-name
,我们直接来看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 define _resolve-short-product-name $(eval pn := $(strip $(1))) $(eval p := \ $(foreach p,$(PRODUCTS), \ $(if $(filter $(pn),$(PRODUCTS.$(p).PRODUCT_NAME)), \ $(p) \ )) \ ) $(eval p := $(sort $(p))) $(if $(filter 1,$(words $(p))), \ $(p), \ $(if $(filter 0,$(words $(p))), \ $(error No matches for product "$(pn)"), \ $(error Product "$(pn)" ambiguous: matches $(p)) \ ) \ ) endef define resolve-short-product-name $(strip $(call _resolve-short-product-name,$(1))) endef
以上代码也很简单,就是针对对应的product_makefile来获取对应的PRODUCT_NAME,然后定义为短product_name
至此,我们关于AndroidProduct.mk文件的关键点的解析已经完成.