1.引言
项目中有新增编译device需求,新增device就势必要了解Android的初步编译配置框架,所以就花了些时间初步探索了下Android12.0 source lunch配置流程,为新增编译device需求开发做铺垫。Android通用编译指令以RK3568为例:source build/envsetup.sh; lunch rk3568_s-userdebug ; make -j8;RK平台自己封装了build.sh脚本, 所以make指令可用build.sh脚本替换处理。本文主要讲述source与lunch的解析配置流程
(1)source build/envsetup.sh
(2)lunch rk3588-userdebug
2. source流程
执行build目录下的envsetup.sh,给后续加载相关环境指令,比如我们可以使用hmm来看系统下支持哪些指令,如图1所示:
Run "m help" for help with the build system itself.
Invoke ". build/envsetup.sh" from your shell to add the following functions to your environment:
- lunch: lunch <product_name>-<build_variant>
Selects <product_name> as the product to build, and <build_variant> as the variant to
build, and stores those selections in the environment to be read by subsequent
invocations of 'm' etc.
//…...
- outmod: Gets the location of a module's installed outputs with a certain extension.
- dirmods: Gets the modules defined in a given directory.
- installmod: Adb installs a module's built APK.
- refreshmod: Refresh list of modules for allmod/gomod/pathmod/outmod/installmod.
- syswrite: Remount partitions (e.g. system.img) as writable, rebooting if necessary.
Environment options:
- SANITIZE_HOST: Set to 'address' to use ASAN for all host modules.
- ANDROID_QUIET_BUILD: set to 'true' to display only the essential messages.
Look at the source to view more functions. The complete list is:
//….
图1 hmm显示
其实在Android10.0之前envsetup.sh脚本中还有一个重要的作用,就是在device以及vendor目录下进行深度为4检索vendorsetup.sh,vendorsetup.sh中调用add_lunch_combo添加对应device到lunch 的choices中去,但是Android10.0开始后这个链路废除掉,换用环境变量COMMON_LUNCH_CHOICES来进行device的添加。
envsetup.sh中没有过多的流程需要再分析,Android的编译配置主要在lunch阶段,也是本文着重要介绍的章节。
3 lunch流程
接下来就开始进行lunch阶段的配置流程分析, lunch的主逻辑流程如下图2所示:
function lunch()
{
local answer
if [[ $# -gt 1 ]]; then
echo "usage: lunch [target]" >&2
return 1
fi
if [ "$1" ]; then
answer=$1
else
print_lunch_menu
echo -n "Which would you like? [aosp_arm-eng] "
read answer
fi
local selection=
if [ -z "$answer" ]
then
selection=aosp_arm-eng
elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")
then
local choices=($(TARGET_BUILD_APPS= get_build_var COMMON_LUNCH_CHOICES))
if [ $answer -le ${#choices[@]} ]
then
# array in zsh starts from 1 instead of 0.
if [ -n "$ZSH_VERSION" ]
then
selection=${choices[$(($answer))]}
else
selection=${choices[$(($answer-1))]}
fi
fi
else
selection=$answer
fi
export TARGET_BUILD_APPS=
local product variant_and_version variant version
product=${selection%%-*} # Trim everything after first dash
variant_and_version=${selection#*-} # Trim everything up to first dash
if [ "$variant_and_version" != "$selection" ]; then
variant=${variant_and_version%%-*}
if [ "$variant" != "$variant_and_version" ]; then
version=${variant_and_version#*-}
fi
fi
if [ -z "$product" ]
then
echo
echo "Invalid lunch combo: $selection"
return 1
fi
TARGET_PRODUCT=$product \
TARGET_BUILD_VARIANT=$variant \
TARGET_PLATFORM_VERSION=$version \
build_build_var_cache
if [ $? -ne 0 ]
then
return 1
fi
export TARGET_PRODUCT=$(get_build_var TARGET_PRODUCT)
export TARGET_BUILD_VARIANT=$(get_build_var TARGET_BUILD_VARIANT)
if [ -n "$version" ]; then
export TARGET_PLATFORM_VERSION=$(get_build_var TARGET_PLATFORM_VERSION)
else
unset TARGET_PLATFORM_VERSION
fi
export TARGET_BUILD_TYPE=release
[[ -n "${ANDROID_QUIET_BUILD:-}" ]] || echo
set_stuff_for_environment
[[ -n "${ANDROID_QUIET_BUILD:-}" ]] || printconfig
destroy_build_var_cache
}
图2 lunch实现
这里如果我们没有选择特定device时, lunch方法中会调用 print_lunch_menu 来打印当前Android环境下有哪些device, print_lunch_menu实现如下图3所示:
function print_lunch_menu()
{
local uname=$(uname)
local choices
choices=$(TARGET_BUILD_APPS= TARGET_PRODUCT= TARGET_BUILD_VARIANT= get_build_var COMMON_LUNCH_CHOICES 2>/dev/null)
local ret=$?
echo
echo "You're building on" $uname
echo
if [ $ret -ne 0 ]
then
echo "Warning: Cannot display lunch menu."
echo
echo "Note: You can invoke lunch with an explicit target:"
echo
echo " usage: lunch [target]" >&2
echo
return
fi
echo "Lunch menu... pick a combo:"
local i=1
local choice
for choice in $(echo $choices)
do
echo " $i. $choice"
i=$(($i+1))
done
echo
}
图3 print_lunch_menu
print_lunch_menu实现,使用 get_build_var 获取编译环境下的COMMON_LUNCH_CHOICES变量,将这个变量作为choices 数组供开发者进行选择,当我们选择对应数字后,就会映射找到对应数字中的具体device, 之后进行对应解析流程。
在lunch实现中会分段截取到product 和variant并分别赋值给TARGET_PRODUCT和TARGET_BUILD_VARIANT,并最终调用build_build_var_cache, 这个方法也是本文分析的入口。
为便于阅读路线清晰,这里进行分段章节开始逐步分析build_build_var_cache内部的具体配置线, 主要分为8个子阶段如下
(1).go语言转入mk语言阶段
(2).envsetup.mk加载阶段
(3).product_config.mk加载阶段1
(4).product_config.mk加载阶段2
(5).product_config.mk加载阶段3
(6).product_config.mk加载阶段4
(7).product_config.mk加载阶段5
(8).board_config.mk加载阶段
3.1 go语言转入mk语言阶段
承接上文,入口为build_build_var_cache, 实现如下图4所示:
文件路径:
build/envsetup.sh
function build_build_var_cache()
{
local T=$(gettop)
# Grep out the variable names from the script.
cached_vars=(`cat $T/build/envsetup.sh | tr '()' ' ' | awk '{for(i=1;i<=NF;i++) if($i~/get_build_var/) print $(i+1)}' | sort -u | tr '\n' ' '`)
cached_abs_vars=(`cat $T/build/envsetup.sh | tr '()' ' ' | awk '{for(i=1;i<=NF;i++) if($i~/get_abs_build_var/) print $(i+1)}' | sort -u | tr '\n' ' '`)
# Call the build system to dump the "<val>=<value>" pairs as a shell script.
build_dicts_script=`\builtin cd $T; build/soong/soong_ui.bash --dumpvars-mode \
--vars="${cached_vars[*]}" \
--abs-vars="${cached_abs_vars[*]}" \
--var-prefix=var_cache_ \
--abs-var-prefix=abs_var_cache_`
//....
//....
}
图4 build_build_var_cache
调用build/soong/soong_ui.bash,如下图5所示:
文件路径:
build/soong/soong_ui.bash
//....
//....
export ORIGINAL_PWD=${PWD}
export TOP=$(gettop)
source ${TOP}/build/soong/scripts/microfactory.bash
soong_build_go soong_ui android/soong/cmd/soong_ui
cd ${TOP}
exec "$(getoutdir)/soong_ui" "$@"
图5 soong_ui.bash实现
其中执行exec "$(getoutdir)/soong_ui" "$@", soong_ui的源码实现是在build/song/cmd/soong_ui中,执行时即会进入main.go方法中,如图6所示:
文件路径:
build/soong/cmd/soong_ui/main.go
func main() {
shared.ReexecWithDelveMaybe(os.Getenv("SOONG_UI_DELVE"), shared.ResolveDelveBinary())
buildStarted := time.Now()
c, args, err := getCommand(os.Args)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing `soong` args: %s.\n", err)
os.Exit(1)
}
//.....
//..…
build.FindSources(buildCtx, config, f)
//…..
}
图6 main实现
关键方法是调用 getCommand并同时传入携带进来的参数, getCommand方法实现如下图7所示:
func getCommand(args []string) (*command, []string, error) {
if len(args) < 2 {
return nil, nil, fmt.Errorf("Too few arguments: %q", args)
}
for _, c := range commands {
if c.flag == args[1] {
return &c, args[2:], nil
}
}
// command not found
return nil, nil, fmt.Errorf("Command not found: %q", args)
}
图7 getCommand实现
getCommand方法是遍历 commands数组,匹配args[1]后,执行对应方法,这里args1就是最早进入go语言环境时传入的那个参数,也就是—dumpvars-mode, 我们需要看下commands定义,定义如下图8所示:
var commands []command = []command{
//.....
//.....
{
flag: "--dumpvar-mode",
description: "print the value of the legacy make variable VAR to stdout",
simpleOutput: true,
logsPrefix: "dumpvars-",
config: dumpVarConfig,
stdio: customStdio,
run: dumpVar,
}, {
flag: "--dumpvars-mode",
description: "dump the values of one or more legacy make variables, in shell syntax",
simpleOutput: true,
logsPrefix: "dumpvars-",
config: dumpVarConfig,
stdio: customStdio,
run: dumpVars,
},
//.....
//.....
}
图8 commands定义
参数匹配到--dumpvars-mode时,执行相应方法 dumpVars, 如图9所示:
func dumpVars(ctx build.Context, config build.Config, args []string, _ string) {
//.....
/......
varData, err := build.DumpMakeVars(ctx, config, nil, allVars)
if err != nil {
ctx.Fatal(err)
}
//.....
//.....
}
图9 dumpVars方法
进一步调用 build.DumpMakeVars 方法, 追踪该方法:关键部分如下图10所示:
文件路径:
build/soong/ui/dumpvars.go
func DumpMakeVars(ctx Context, config Config, goals, vars []string) (map[string]string, error) {
//.......
ret, err = dumpMakeVars(ctx, config, goals, makeVars, false, tmpDir)
if err != nil {
return ret, err
}
//.......
return ret, nil
}
图10 DumpMakeVars方法实现
进一步调用 dumpMakeVars, 具体实现如下图11所示:
func dumpMakeVars(ctx Context, config Config, goals, vars []string, write_soong_vars bool, tmpDir string) (map[string]string, error) {
ctx.BeginTrace(metrics.RunKati, "dumpvars")
defer ctx.EndTrace()
cmd := Command(ctx, config, "dumpvars",
config.PrebuiltBuildTool("ckati"),
"-f", "build/make/core/config.mk",
"--color_warnings",
"--kati_stats",
"dump-many-vars",
"MAKECMDGOALS="+strings.Join(goals, " "))
cmd.Environment.Set("CALLED_FROM_SETUP", "true")
if write_soong_vars {
cmd.Environment.Set("WRITE_SOONG_VARIABLES", "true")
}
cmd.Environment.Set("DUMP_MANY_VARS", strings.Join(vars, " "))
if tmpDir != "" {
cmd.Environment.Set("TMPDIR", tmpDir)
}
//......
//......
}
图11 dumpMakeVars方法
主要逻辑是include config.mk,从这里开始总算从go语言转入常规makefile语言了
3.2 envsetup.mk加载阶段
承接上文,从config.mk文件包含逻辑开始追踪:如下图12所示:
文件路径:
build/make/core/config.mk
include $(BUILD_SYSTEM)/envsetup.mk
图12 envsetup.mk加载
envsetup.mk关键代码如图13所示:
文件路径:
build/make/core/envsetup.mk
//....
include $(BUILD_SYSTEM)/product_config.mk
//....
include $(BUILD_SYSTEM)/board_config.mk
//.…
图13 mk文件包含
envsetup.mk包含product_config.mk和board_config.mk product_config.mk是关键配置阶段,针对性分为5个阶段,当product_config.mk流程分析完成后,再往后分析board_config.mk
3.3 product_config.mk加载阶段1
product_config.mk流程阶段1, 关键性代码如图14所示:
all_product_configs := $(get-all-product-makefiles)
图14 product_config.mk 入口
all_product_configs 在此刻被赋值, 通过方法 get-all-product-makefiles获取, 追踪get-all-product-makefiles实现,如图15所示:
文件路径:
build/make/core/product.mk
define get-all-product-makefiles
$(call get-product-makefiles,$(_find-android-products-files))
endef
图15 get-all-product-makefiles实现
可以看到进一步调用方法 get-product-makefiles, 参数则是_find-android-products-files方法获取到,所以这里我们需要分别看这两个方法, get-product-makefiles方法实现如图16所示:
define get-product-makefiles
$(sort \
$(eval _COMMON_LUNCH_CHOICES :=) \
$(foreach f,$(1), \
$(eval PRODUCT_MAKEFILES :=) \
$(eval COMMON_LUNCH_CHOICES :=) \
$(eval LOCAL_DIR := $(patsubst %/,%,$(dir $(f)))) \
$(eval include $(f)) \
$(call _validate-common-lunch-choices,$(COMMON_LUNCH_CHOICES),$(PRODUCT_MAKEFILES)) \
$(eval _COMMON_LUNCH_CHOICES += $(COMMON_LUNCH_CHOICES)) \
$(PRODUCT_MAKEFILES) \
) \
$(eval PRODUCT_MAKEFILES :=) \
$(eval LOCAL_DIR :=) \
$(eval COMMON_LUNCH_CHOICES := $(sort $(_COMMON_LUNCH_CHOICES))) \
$(eval _COMMON_LUNCH_CHOICES :=) \
)
endef
图16 get-product-makefiles
遍历$1 即_find-android-products-files方法的返回, 解析PRODUCT_MAKEFILES变量获取到具体产品.mk的具体路径,那$1是什么呢,我们要看_find-android-products-files实现,如图17所示:
define _find-android-products-files
$(file <$(OUT_DIR)/.module_paths/AndroidProducts.mk.list) \
$(SRC_TARGET_DIR)/product/AndroidProducts.mk
endef
图17 _find-android-products-files
依据 AndroidProducts.mk.list获取到其中的AndroidProducts.mk文件,这个list是又在哪里生成的呢, 抛出问题1:AndroidProducts.mk.list来源?
追踪这个来源我们就需要回到图6中main.go中的后续逻辑。其中有个关键方法:
build.FindSources(buildCtx, config, f), 这个方法的实现如图18所示:
文件路径:
build/soong/ui/build/finder.go
func FindSources(ctx Context, config Config, f *finder.Finder) {
//.....
//.....
// Only consider AndroidProducts.mk in device/, vendor/ and product/, recursively in these directories.
androidProductsMks := f.FindNamedAt("device", "AndroidProducts.mk")
androidProductsMks = append(androidProductsMks, f.FindNamedAt("vendor", "AndroidProducts.mk")...)
androidProductsMks = append(androidProductsMks, f.FindNamedAt("product", "AndroidProducts.mk")...)
err = dumpListToFile(ctx, config, androidProductsMks, filepath.Join(dumpDir, "AndroidProducts.mk.list"))
if err != nil {
ctx.Fatalf("Could not export product list: %v", err)
}
}
图18 FindSources
可以看到 FindSources里面的实现逻辑是遍历SDK目录下的device vendor product目录并查找到里面的AndroidProduct.mk并追踪添加到AndroidProducts.mk.list里, 此处回答问题1:AndroidProducts.mk.list来源。
所以 get-product-makefiles方法中获取到AndroidProduct.mk后,解析里面的PRODUCT_MAKEFILES变量进而得到产品mk列表,这里为便于方便说明我们给出Rk3568的AndroidProduct.mk内容,如图19所示:
PRODUCT_MAKEFILES := \
$(LOCAL_DIR)/rk3566_sgo/rk3566_sgo.mk \
$(LOCAL_DIR)/rk3566_32bit/rk3566_32bit.mk \
$(LOCAL_DIR)/rk3566_r/rk3566_r.mk \
$(LOCAL_DIR)/rk3566_s/rk3566_s.mk \
$(LOCAL_DIR)/rk3568_s/rk3568_s.mk \
$(LOCAL_DIR)/rk3568_s_atompi_ca1/rk3568_s_atompi_ca1.mk \
$(LOCAL_DIR)/rk3566_eink/rk3566_eink.mk \
$(LOCAL_DIR)/rk3566_einkw6/rk3566_einkw6.mk
COMMON_LUNCH_CHOICES := \
rk3566_32bit-userdebug \
rk3566_32bit-user \
rk3566_sgo-userdebug \
rk3566_sgo-user \
rk3566_r-userdebug \
rk3566_r-user \
rk3566_s-userdebug \
rk3566_s-user \
rk3568_s-userdebug \
rk3568_s-user \
rk3568_s_atompi_ca1-userdebug \
rk3568_s_atompi_ca1-user \
rk3566_eink-userdebug \
rk3566_eink-user \
rk3566_einkw6-userdebug \
rk3566_einkw6-user
图19 AndroidProduct.mk文件
阶段1目的就是读取所有AndroidProduct.mk中PRODUCT_MAKEFILES定义的产品mk,最终赋值给all_product_configs 。
这个值打印出来的话会相对很多,因为是SDK里面所有的PRODUCT mk文件,没有进行过滤,当然也包含rk3568_s.mk, 精简格式如下图19所示:
aosp_cf_arm64_auto:device/google/cuttlefish/vsoc_arm64/auto/aosp_cf.mk aosp_cf_arm64_only_phone:device/google/cuttlefish/vsoc_arm64_only/phone/aosp_cf.mk
//.....
图19 all_product_configs
接下来转入product_config.mk阶段2
3.4 product_config.mk加载阶段2
获取到all_product_configs后,接下来就是要进行过滤得到我们自己lunch device所选择的产品mk, 关键代码如图20所示:
# 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 := $(call _decode-product-name,$(f)))\
$(eval _cpm_word1 := $(word 1,$(_cpm_words)))\
$(eval _cpm_word2 := $(word 2,$(_cpm_words)))\
$(eval all_product_makefiles += $(_cpm_word2))\
$(eval all_named_products += $(_cpm_word1))\
$(if $(filter $(TARGET_PRODUCT),$(_cpm_word1)),\
$(eval current_product_makefile += $(_cpm_word2)),))
_cpm_words :=
_cpm_word1 :=
_cpm_word2 :=
current_product_makefile := $(strip $(current_product_makefile))
all_product_makefiles := $(strip $(all_product_makefiles))
图20 current_product_makefile获取
主要逻辑是依据 TARGET_PRODUCT变量进行过滤,这个变量在最早的envsetup.sh被赋值为相应值,这里的话以rk3568为例, TARGET_PRODUCT值就是rk3568_s, 将过滤出来的产品mk赋值给current_product_makefile。
阶段二的目的就是得到产品自己的mk文件。此时的current_product_makefile值就是device/rockchip/rk356x/rk3568_s/rk3568_s.mk
接下来转入product_config.mk阶段3
3.5 product_config.mk加载阶段3
阶段3关键代码如下图21所示:
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
图21 import-products做临时变量
current_product_makefile如果没有正确解析到,则error报错停止处理,正确解析到后,会调用 import-products, 其中current_product_makefile作为参数传入, 实现如下图22所示:
文件路径:
build/make/core/product.mk
define import-products
$(call import-nodes,PRODUCTS,$(1),$(_product_var_list),$(_product_single_value_vars))
endef
图22 import-products实现
进一步调用import-nodes方法,PRODUCTS为固定字符,$(1)是我们自己产品mk文件,_product_var_list和_product_single_value_vars)则是类似PRODUCT_NAME,PRODUCT_MODEL等类似字段,看下import-nodes实现, 如下图23所示:
文件路径:
build/make/core/node_fns.mk
define import-nodes
$(call dump-phase-start,$(1),$(2),$(3),$(4),build/make/core/node_fns.mk) \
$(if \
$(foreach _in,$(2), \
$(eval _node_import_context := _nic.$(1).[[$(_in)]]) \
$(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),$(4)) \
$(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))),) \
) \
,) \
$(call dump-phase-end,build/make/core/node_fns.mk)
endef
图23 import-nodes方法实现
这个过程就是在解析对应实际产品的.mk中所定义的PRODUCT_NAME以及PRODUCT_DEVICE等等其他变量,之后实现变量转移,这个临时变量就是通过move-var-list逻辑组合出来的, 临时变量的结构名为类似如下:以PRODUCTS为前缀,当前产品.mk路径为紧邻, _product_var_list 中的变量为后缀的临时编译变量,结果如下:
PRODUCTS.device/rockchip/rk356x/rk3568_s/rk3568_s.mk.PRODUCT_NAME 得到多个这个组合出来的编译临时变量体。
阶段三的主要目的就是生成这种多个类似的临时编译变量体。
这里可能读者会有疑问, 生成这个临时编译变量体有啥作用呢,其实这个就是用来给后续的编译框架中其他逻辑用来进行解析用的,包括product_config.mk后续逻辑也有解析的地方,我们可以继续往下看。转入product_config.mk阶段4
3.6 product_config.mk加载阶段4
阶段4关键代码逻辑如下图24所示:
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
图24 阶段4入口
调用 resolve-short-product-name方法, 实现如图25所示:
define resolve-short-product-name
$(strip $(call _resolve-short-product-name,$(1)))
endef
图25 resolve-short-product-name
进一步调用_resolve-short-product-name方法,具体实现如图26所示:
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
图26 _resolve-short-product-name实现
这个里面就有用到阶段3中产生的临时编译变量体,就是这个表达式, $(PRODUCTS.$(p).PRODUCT_NAME),这里的逻辑就是解析出来产品.mk中定义的PRODUCT_NAME变量是否和TARGET_PRODUCT一致,也就是一致性检查,这里我们给出rk3568s.mk中的具体内容,如下图27所示:
DEVICE_PACKAGE_OVERLAYS += $(LOCAL_PATH)/../overlay
PRODUCT_CHARACTERISTICS := tablet
PRODUCT_NAME := rk3568_s
PRODUCT_DEVICE := rk3568_s
图27 rk3568s.mk内容
可以看到和lunch的device是一致的,所以在编译框架中就强制要求了产品mk中的PRODUCT_NAME要和lunch 的device保持一致,否则就会在里直接报错停止。逻辑执行完后,阶段4最终是将产品的mk赋值到INTERNAL_PRODUCT。
阶段4的目的就是进行匹配检查以及将产品mk赋值给INTERNAL_PRODUCT
这里读者会有疑问,问题2产品的mk不是已经在 current_product_makefile里了吗, 为何要重复赋值给INTERNAL_PRODUCT, 那我们继续往下分析
接下来转入product_config.mk阶段5
3.7 product_config.mk加载阶段5
阶段5的关键逻辑如下如28所示:
TARGET_DEVICE := $(PRODUCT_DEVICE)
图28 TARGET_DEVICE赋值
如果是在之前老的Android系列比如7.1版本或者9.0版本时,这里就会使用之前编译生成的临时变量体+INTERNAL_PRODUCT变量共同实现完成TARGET_DEVICE赋值,比如使用这种结构(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_DEVICE), 但是Android12.0的话则有些许变化, 直接赋值 PRODUCT_DEVICE, 那 PRODUCT_DEVICE赋值来源在哪里
文件路径:
build/make/core/product-graph.mk
关键实现如图29所示:
PRODUCT_DEVICE=$(call get-product-var,$(1),PRODUCT_DEVICE)
图29 PRODUCT_DEVICE赋值逻辑
get-product-var实现逻辑则如图30所示
get-product-var = $(PRODUCTS.$(strip $(1)).$(2))
图30 get-product-var
这里就又解析我们之前编译生成的临时编译变量体, $2就是PRODUCT_DEVICE),即TARGET_DEVICE来源于产品.mk中PRODUCT_DEVICE。
针对问题2,其实在整个编译框架中进行检索就会发现,编译框架中访问产品的mk的方式就是直接使用的INTERNAL_PRODUCT, 在编译框架中的其他mk文件中比比皆是,其实current_product_makefile的作用在编译框架中仅是用来做合规性检查的临时变量,这也就是问题2的解答。
至此product_config.mk阶段5也已经走完。
阶段5的目的就是进行解析获取TARGET_DEVICE.
product_config.mk的流程就基本走完了。
3.8 board_config.mk加载
承接上文进行收尾工作, product_config.mk包含完成后,后续进行board_config.mk文件的包含,关键内容如下图31所示:
board_config_mk := \
$(strip $(sort $(wildcard \
$(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)/BoardConfig.mk \
$(shell test -d device && find -L device -maxdepth 4 -path '*/$(TARGET_DEVICE)/BoardConfig.mk') \
$(shell test -d vendor && find -L vendor -maxdepth 4 -path '*/$(TARGET_DEVICE)/BoardConfig.mk') \
)))
图31 board_config.mk
依据product_config.mk阶段5解析出来的TARGET_DEVICE包含相应产品的BoardConfig.mk。至此整个主体source lunch流程就已走完。
4.总结
本章节中讲解的内容是以AndroidProduct.mk rk3568s.mk BoardConfig.mk为主线.TARGET_PRODUCT以及TARGET_DEVICE这两个变量赋值为辅线进行的分析,之所以采用这种方式,主要是能够相对清晰的看到编译配置的流程,另外TARGET_PRODUCT或者TARGET_DEVICE也经常被开发拿来做不同产品的编译区分依据。以一个简单框图为例如图32所示:
图32 流程主框图
配合本章对应的新增编译device需求则在另外一篇 Android12.0 需求开发篇之新增编译device, 读者读完本章有主线后可继续阅读新增编译device篇,相信会更为清晰的理解到。如有不当,欢迎指正,至此本章结束。