Android編譯系統分析之幾個關鍵點(一)
已開通新的部落格,後續文字都會發到新部落格
Android 編譯系統解析系列文件
解析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: # <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整個編譯環境中的
################### 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
的一個簡單的封裝
####################
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
接下來要對掃描出的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
的列表
取得current_makefile(當前lunch機型的配置檔案)
###################
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變數
###################
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是第一個入棧的
- 清除所有的PRODUCT_開頭的變數的值
- include meizu_m86.mk
meizu_m86.mk
是整個機型的配置入口,此處開始處理 - 將處理過的檔案新增到_included變數中
- 將第三步include進來的檔案中的PRODUCT_開頭的變數使用
copy-var-list
函式新增 (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
函式就是繼承的一個典型的應用,我們先來看看它是怎麼用的,然後再往下看具體的解析過程
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
的內容也很簡單
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
函式之前,我們都會得到相如以下型別的變數:
_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
這個函式做了以下幾件事:
- _eiv_tv 將_product_var_list中的所有變數都添加了公有字首+私有字首
- _eiv_i 表示當前正在處理的makefile檔案的繼承關係makefile列表,是通過過濾@inherit這個字首拿到的,顯而易見,這個是從倒數第二深的makefile檔案起作用,因為最深層次的makefile變數中不包含繼承關係
- 使用
uniq-word
來確保只繼承了一次,這種繼承的檢查發生在上層與緊挨著的下層之間 - 展開@inherit表示的變數,其實也就是使用比他深一層次的makefile檔案的對應的PRODUCT_XXX變數替換@inherit這個識別符號
- 檢查是否迴圈繼承(自己繼承了自己)
我們接著看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
的值,這裡就簡單的展開結束,因此我們最後得到就是所有繼承變數的綜合起來的內容
檢查所有的product
最後還剩下一點簡單的程式碼,是用來解析出一個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檔案的關鍵點的解析已經完成.