1. 检查是否带有参数,即位置参数$1是否等于空。如果不等于空的话,就表明带有参数,并且该参数是用来指定要编译的设备型号及其编译类型的。如果等于空的话,那么就调用另外一个函数print_lunch_menu来显示Lunch菜单项,并且通过调用read函数来等待用户输入。无论通过何种方式,最终变量answer的值就保存了用户所指定的备型号及其编译类型。
2. 对变量answer的值的合法性进行检查。如果等于空的话,就将它设置为默认值“full-eng”。如果不等于空的话,就分为三种情况考虑。第一种情况是值为数字,那么就需要确保该数字的大小不能超过Lunch菜单项的个数。在这种情况下,会将输入的数字索引到数组LUNCH_MENU_CHOICES中去,以便获得一个用来表示设备型号及其编译类型的文本。第二种情况是非数字文本,那么就需要确保该文本符合<product>-<variant>的形式,其中<product>表示设备型号,而<variant>表示编译类型 。第三种情况是除了前面两种情况之外的所有情况,这是非法的。经过合法性检查后,变量selection代表了用户所指定的备型号及其编译类型,如果它的值是非法的,即它的值等于空,那么函数lunch就不往下执行了。
3. 接下来是解析变量selection的值,也就是通过sed命令将它的<product>和<variant>值提取出来,并且分别保存在变量product和variant中。提取出来的product和variant值有可能是不合法的,因此需要进一步通过调用函数check_product和check_variant来检查。一旦检查失败,也就是函数check_product和check_variant的返回值$?等于非0,那么函数lunch就不往下执行了。
4. 通过以上合法性检查之后,就将变量product和variant的值保存在环境变量TARGET_PRODUCT和TARGET_BUILD_VARIANT中。此外,另外一个环境变量TARGET_BUILD_TYPE的值会被设置为"release",表示此次编译是一个release版本的编译。另外,前面还有一个环境变量TARGET_BUILD_APPS,它的值被函数lunch设置为空,用来表示此次编译是对整个系统进行编译。如果环境变量TARGET_BUILD_APPS的值不等于空,那么就表示此次编译是只对某些APP模块进行编译,而这些APP模块就是由环境变量TARGET_BUILD_APPS来指定的。
5. 调用函数set_stuff_for_environment来配置环境,例如设置Java SDK路径和交叉编译工具路径等。
6. 调用函数printfconfig来显示已经配置好的编译环境参数。
在上述执行过程中,函数check_product、check_variant和printconfig是比较关键的,因此接下来我们就继续分析它们的实现。
函数check_product定义在文件build/envsetup.sh中,它的实现如下所示:
- # check to see if the supplied product is one we can build
- function check_product()
- {
- T=$(gettop)
- if [ ! "$T" ]; then
- echo "Couldn't locate the top of the tree. Try setting TOP." >&2
- return
- fi
- CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core \
- TARGET_PRODUCT=$1 \
- TARGET_BUILD_VARIANT= \
- TARGET_BUILD_TYPE= \
- TARGET_BUILD_APPS= \
- get_build_var TARGET_DEVICE > /dev/null
- # hide successful answers, but allow the errors to show
- }
接下来函数check_product设置几个环境变量,其中最重要的是前面三个CALLED_FROM_SETUP、BUILD_SYSTEM和TARGET_PRODUCT。环境变量CALLED_FROM_SETUP的值等于true表示接下来执行的make命令是用来初始化Android编译环境的。环境变量BUILD_SYSTEM用来指定Android编译系统的核心目录,它的值被设置为build/core。环境变量TARGET_PRODUCT用来表示要检查的产品名称(也就是我们前面说的设备型号),它的值被设置为$1,即函数check_product的调用参数。
最后函数check_product调用函数get_build_var来检查由环境变量TARGET_PRODUCT指定的产品名称是否合法,注意,它的调用参数为TARGET_DEVICE。
函数get_build_var定义在文件build/envsetup.sh中,它的实现如下所示:
- # Get the exact value of a build variable.
- function get_build_var()
- {
- T=$(gettop)
- if [ ! "$T" ]; then
- echo "Couldn't locate the top of the tree. Try setting TOP." >&2
- return
- fi
- CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core \
- make --no-print-directory -C "$T" -f build/core/config.mk dumpvar-$1
- }
文件build/core/config.mk的内容比较多,这里我们只关注与产品名称合法性检查相关的逻辑,这些逻辑也基本上涵盖了Android编译系统初始化的逻辑,如下所示:
- ......
- # ---------------------------------------------------------------
- # Define most of the global variables. These are the ones that
- # are specific to the user's build configuration.
- include $(BUILD_SYSTEM)/envsetup.mk //定义下文的TARGET_DEVICE
- # Boards may be defined under $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)
- # or under vendor/*/$(TARGET_DEVICE). Search in both places, but
- # make sure only one exists.
- # Real boards should always be associated with an OEM vendor.
- board_config_mk := \
- $(strip $(wildcard \
- $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)/BoardConfig.mk \ //加载TARGET_DEVICE目录下的BoardConfig.mk
- device/*/$(TARGET_DEVICE)/BoardConfig.mk \
- vendor/*/$(TARGET_DEVICE)/BoardConfig.mk \
- ))
- ifeq ($(board_config_mk),)
- $(error No config file found for TARGET_DEVICE $(TARGET_DEVICE))
- endif
- ifneq ($(words $(board_config_mk)),1)
- $(error Multiple board config files for TARGET_DEVICE $(TARGET_DEVICE): $(board_config_mk))
- endif
- include $(board_config_mk)
- ......
- include $(BUILD_SYSTEM)/dumpvar.mk
接下来我们就通过进入到build/core/envsetup.mk文件来分析变量TARGET_DEVICE的值是如何确定的:
- # Read the product specs so we an get TARGET_DEVICE and other
- # variables that we need in order to locate the output files.
- include $(BUILD_SYSTEM)/product_config.mk
文件build/core/product_config.mk的内容很多,这里我们只关注变量TARGET_DEVICE设置相关的逻辑,如下所示:
- ......
- 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)//仅仅是加载(SRC_TARGET_DIR)/product下的AndroidProduct.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) //加载所有AndroidProducts.mk,分析见下面
- endif
- # 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)) //rk3288_box.mk
- all_product_makefiles := $(strip $(all_product_makefiles))
- ifneq (,$(filter product-graph dump-products, $(MAKECMDGOALS)))
- # Import all product makefiles.
- $(call import-products, $(all_product_makefiles)) //例如提取rk3288_box.mk
- else
- # Import just the current product.
- ifndef current_product_makefile
- $(error Cannot 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
- ......
- # 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))//是一个mk文件
- 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 :=
- # Find the device that this product maps to.
- TARGET_DEVICE := $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_DEVICE)
- //如下所示:
- //PRODUCTS.build/target/product/full.mk.PRODUCT_NAME := full
- //PRODUCTS.build/target/product/full.mk.PRODUCT_DEVICE := generic
- //PRODUCTS.build/target/product/full.mk.PRODUCT_BRAND := Android
- //PRODUCTS.build/target/product/full.mk.PRODUCT_MODEL := Full Android on Emulator
- ......
1. 检查环境变量TARGET_BUILD_APPS的值是否等于空。如果不等于空,那么就说明此次编译不是针对整个系统,因此只要将核心的产品相关的Makefile文件加载进来就行了,否则的话,就要将所有与产品相关的Makefile文件加载进来的。核心产品Makefile文件在$(SRC_TARGET_DIR)/product/AndroidProducts.mk文件中指定,也就是在build/target/product/AndroidProducts.mk文件,通过调用函数get-product-makefiles可以获得。所有与产品相关的Makefile文件可以通过另外一个函数get-all-product-makefiles获得。无论如何,最终获得的产品Makefie文件列表保存在变量all_product_configs中。
2. 遍历变量all_product_configs所描述的产品Makefile列表,并且在这些Makefile文件中,找到名称与环境变量TARGET_PRODUCT的值相同的文件,保存在另外一个变量current_product_makefile中,作为需要为当前指定的产品所加载的Makefile文件列表。在这个过程当中,上一步找到的所有的产品Makefile文件也会保存在变量all_product_makefiles中。注意,环境变量TARGET_PRODUCT的值是在我们执行lunch命令的时候设置并且传递进来的。
3. 如果指定的make目标等于product-graph或者dump-products,那么就将所有的产品相关的Makefile文件加载进来,否则的话,只加载与目标产品相关的Makefile文件。从前面的分析可以知道,此时的make目标为dumpvar-TARGET_DEVICE,因此接下来只会加载与目标产品,即$(TARGET_PRODUCT)相关的Makefile文件,这是通过调用另外一个函数import-products实现的。
4. 调用函数resolve-short-product-name解析环境变量TARGET_PRODUCT的值,将它变成一个Makefile文件路径。并且保存在变量INTERNAL_PRODUCT中。这里要求变量INTERNAL_PRODUCT和current_product_makefile的值相等,否则的话,就说明用户指定了一个非法的产品名称。
5. 找到一个名称为PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_DEVICE的变量,并且将它的值保存另外一个变量TARGET_DEVICE中。变量PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_DEVICE是在加载产品Makefile文件的过程中定义的,用来描述当前指定的产品的名称。
上述过程主要涉及到了get-all-product-makefiles、import-products和resolve-short-product-name三个关键函数,理解它们的执行过程对理解Android编译系统的初始化过程很有帮助,接下来我们分别分析它们的实现。
函数get-all-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也是定义在文件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
函数get-product-makefiles也是定义在文件build/core/product.mk中,如下所示:(与get-all-product-makefiles区别是加载的范围不一样)
- #
- # 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
例如,在build/target/product/AndroidProducts.mk文件中,变量PRODUCT_MAKEFILES的值如下所示:
- # Unbundled apps will be built with the most generic product config.
- ifneq ($(TARGET_BUILD_APPS),)
- PRODUCT_MAKEFILES := \
- $(LOCAL_DIR)/full.mk \
- $(LOCAL_DIR)/full_x86.mk \
- $(LOCAL_DIR)/full_mips.mk
- else
- PRODUCT_MAKEFILES := \
- $(LOCAL_DIR)/core.mk \
- $(LOCAL_DIR)/generic.mk \
- $(LOCAL_DIR)/generic_x86.mk \
- $(LOCAL_DIR)/generic_mips.mk \
- $(LOCAL_DIR)/full.mk \
- $(LOCAL_DIR)/full_x86.mk \
- $(LOCAL_DIR)/full_mips.mk \
- $(LOCAL_DIR)/vbox_x86.mk \
- $(LOCAL_DIR)/sdk.mk \
- $(LOCAL_DIR)/sdk_x86.mk \
- $(LOCAL_DIR)/sdk_mips.mk \
- $(LOCAL_DIR)/large_emu_hw.mk
- endif