Android12.0 框架探索篇之source lunch编译配置原理

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脚本替换处理。本文主要讲述sourcelunch的解析配置流程

(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.shvendorsetup.sh中调用add_lunch_combo添加对应devicelunch 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环境下有哪些deviceprint_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_PRODUCTTARGET_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.mkboard_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列表,这里为便于方便说明我们给出Rk3568AndroidProduct.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_NAMEPRODUCT_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来源于产品.mkPRODUCT_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篇,相信会更为清晰的理解到。如有不当,欢迎指正,至此本章结束。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

洋仔518

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值