已开通新的博客,后续文字都会发到新博客
http://www.0xfree.top
Android 编译系统解析系列文档
编译系统入口envsetup.sh解析
解析lunch的执行过程以及make执行过程中include文件的顺序
关注一些make执行过程中的几个关键点
对一些独特的语法结构进行解析
我们首先来看看今天的主角,以下这段代码就是解析AndroidProducts.mk以及其内容的关键代码
###################
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:
# :
# or just 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文件是如何被加载到Android整个编译环境中的
###################
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-
这个变量默认为空,也就是编译整个系统,我们可以在加载环境变量之后通过使用printconfig命令来查看我们是否设置过TARGET_BUILD_APPS
变量
get-all-product-makefiles
函数定义在build/core/product.mk文件中,是get-product-makefiles
的一个简单的封装
####################
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的集合
####################
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文件,从这里我们可以看出,这里的得到的最后结果带有相对于源码根目录的相对路径,类似以下格式:
device/htc/flounder/AndroidProducts.mk
device/meizu/m86/AndroidProducts.mk
device/samsung/avl7420/AndroidProducts.mk
....
注:
SRC_TARGET_DIR=build/target
接下来要对扫描出的AndroidProducts.mk文件进行处理
####################
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文件中定义的格式像下边这样:
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
的列表
###################
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
从前边的代码我们可以知道all_product_configs是代表device以及vendor所有的AndroidProducts.mk文件中变量PRODUCT_MAKEFILES
的值的集合,这个PRODUCT_MAKEFILES
值包括两种情况
:
也就是在上边代码中then-1做出判断,我们一般都是使用的第二种情况,所以我们就解析一下else的情况,else主要做了两步处理
all_product_makefiles
变量中通过以上两步,我们可以得到一个包含全部device,vendor下的product_makefile文件的变量all_product_makefiles
以及当前我们需要编译的product_makefile的变量current_product_makeifle
以上的示例也提醒我们,AndroidProducts.mk文件内容中指向的product_makefile名称必须标准
###################
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
干了什么
####################
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开头的一系列的变量的枚举
####################
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
这个函数干了什么
####################
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变量,我们都会使用公有前缀+私有前缀作为前缀来区分
我们继续来看
####################
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
来实际导入
####################
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
是整个机型的配置入口,此处开始处理copy-var-list
函数添加 ( 1 ) . (1). (1).(2)前缀,这里就是公有前缀+私有前缀公有前缀+私有前缀+inherited
变量中_import-nodes-inner
来循环获取上一步得到的继承列表,将所有层次的继承的文j件都获取到,最后得到一个解除@inherit前缀的包含所有继承层次的makefile文件列表_expand_-inherited-values
展开上一步得到的这些文件中_product_var_list中变量的值这里第1步到第7部,以及加上前边的_import-nodes-inner
一起构成了递归,我们在递归展开这些变量的最后一步时,会调用_expand-inherited-values
来从最深层次的继承一直展开到最浅层次的继承,也就是第一层继承,要明白最深层次与之后浅层次的makefile中变量的关系,是最深覆盖最浅?还是最浅覆盖最深?或者二者叠加?我们就需要看_expand-inherited-values
的具体内容
在看最后一个函数的内容之前,我们还需要了解一个知识点,在第5步的时候,出现了一个新的概念inherit,也就是继承,我们经常会在product_makefile中看到inherit-product
函数就是继承的一个典型的应用,我们先来看看它是怎么用的,然后再往下看具体的解析过程
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)
这个函数主要做了以下几件事:
@inherit xxxmakefile
的前缀,这个xxxmakfile就是传入的$(1)变量值PRODUCTS.当前makefile.INHERITS_FROM
这个变量来存放当前makefile的继承列表inherit-product
函数继承的makefile,都会被加到以上变量中PRODUCTS.当前makefile.INHERITS_FORM :=$(sort $(PRODUCT.当前makefile.INHERITS_FROM) last_makefile )
了解了这个背景知识之后,我们可以得出以下几点:
_import-node
第三步有一个include product_makefile文件的操作,这里我们分析的inherit-product
函数就在这个include的操作中被调用import-node
函数中,因此include的makefile也会被加入到_include_stack中inherit-product
函数中还提供了一个INHERIT_FROM的变量,这个变量的相关用法,我们可以在build/core/tasks/product-graph.mk
见到由以上内容得知,在这里我们只需要明白调用inherit-product
函数只是添加了@inherit:
这个前缀就行,当然从这里我们也可以看出,如果一个makefile文件中inherit两次同一个makefile,也会被在这里去重
我们继续向下来看是如何解析加入@inherit前缀的这些变量
_get-inherited-nodes
的内容也很简单
define get-inherited-nodes
$(sort \
$(subst $(INHERIT_TAG),, \
$(filter $(INHERIT_TAG)%, \
$(foreach v,$(2),$($(1).$(v))) \
)))
endef
这个函数有点复杂,我们在这里说明一下:
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
函数之前,我们都会得到相如以下类型的变量:
_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
函数之前的那种类型的变量
接下来我们来实际解析这个函数
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
这个函数做了以下几件事:
uniq-word
来确保只继承了一次,这种继承的检查发生在上层与紧挨着的下层之间我们接着看uniq-word
这个函数的实现
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
的值,这里就简单的展开结束,因此我们最后得到就是所有继承变量的综合起来的内容
最后还剩下一点简单的代码,是用来解析出一个short-name后边来使用
# 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
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
,我们直接来看
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文件的关键点的解析已经完成.