Android编译系统分析之几个关键点(一)

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值包括两种情况

  1. <product_name>:<path_to_the_product_makefile>
  2. <path_to_the_product_makefile>

也就是在上边代码中==then-1==做出判断,我们一般都是使用的第二种情况,所以我们就解析一下else的情况,else主要做了两步处理

  1. 将所有的product_makefile文件加入到all_product_makefiles变量中
  2. 通过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的方式:

  1. MAKECMDGOALS中如果包含dump-products,那么执行$(dump-products)命令打印所有的PRODUCT_XXXX变量,具体规则定义位于build/core/product.mk文件
  2. 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_FILESPRODUCT_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函数主要做了以下几件事:

  1. 将找到的最新文件入栈,此时栈应该是空的,也就是说meizu_m86.mk是第一个入栈的
  2. 清除所有的PRODUCT_开头的变量的值
  3. include meizu_m86.mk meizu_m86.mk是整个机型的配置入口,此处开始处理
  4. 将处理过的文件添加到_included变量中
  5. 将第三步include进来的文件中的PRODUCT_开头的变量使用copy-var-list函数添加$(1).$(2)前缀,这里就是公有前缀+私有前缀
  6. 取得带有@inherit前缀的变量,然后去掉@inherit前缀,然后排序去重,获得继承第一层的makefile文件的列表,记录到公有前缀+私有前缀+inherited变量中
  7. 通过_import-nodes-inner来循环获取上一步得到的继承列表,将所有层次的继承的文j件都获取到,最后得到一个解除@inherit前缀的包含所有继承层次的makefile文件列表
  8. _expand_-inherited-values展开上一步得到的这些文件中_product_var_list中变量的值
  9. 清空继承列表与_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)

这个函数主要做了以下几件事:

  1. 为_product_var_list中所有变量挨个加上@inherit xxxmakefile的前缀,这个xxxmakfile就是传入的$(1)变量值
  2. 组织当前正被处理(include)的文件内容的继承列表,具体是这样的:
    • 我们用PRODUCTS.当前makefile.INHERITS_FROM这个变量来存放当前makefile的继承列表
    • 然后这个文件中的每一个使用inherit-product函数继承的makefile,都会被加到以上变量中
      这一步的具体过程就是以下:
      PRODUCTS.当前makefile.INHERITS_FORM :=$(sort $(PRODUCT.当前makefile.INHERITS_FROM) last_makefile )
  3. 将当前正在处理(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
  1. 将_product_var_list中的变量挨个取出,这里的变量已经添加了前缀,因此要使用前缀取出,也就是类似_nic.PRODUCTS.[[device/meizu/m86/meizu_m86.mk]].device/meizu/m86/meizu_m86.mk所代表的product文件来取出var变量的值
  2. 过滤出带有@inherit前缀的变量值
  3. 将前缀去掉,然后排序去重,最后得到一份继承的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-innerget-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

这个函数做了以下几件事:

  1. _eiv_tv 将_product_var_list中的所有变量都添加了公有前缀+私有前缀
  2. _eiv_i 表示当前正在处理的makefile文件的继承关系makefile列表,是通过过滤@inherit这个前缀拿到的,显而易见,这个是从倒数第二深的makefile文件起作用,因为最深层次的makefile变量中不包含继承关系
  3. 使用uniq-word来确保只继承了一次,这种继承的检查发生在上层与紧挨着的下层之间
  4. 展开@inherit表示的变量,其实也就是使用比他深一层次的makefile文件的对应的PRODUCT_XXX变量替换@inherit这个标识符
  5. 检查是否循环继承(自己继承了自己)

我们接着看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-innerget-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文件的关键点的解析已经完成.

Android编译系统分析之几个关键点(一)

http://www.0xforee.top/2015/12/22/android-build-system-keypoint-first/

作者

0xforee

发布于

2015-12-22

更新于

2015-12-22

许可协议


欢迎关注我的公众号 0xforee,第一时间获取更多有价值的思考

评论