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中,它的实现如下所示:


[plain]  view plain copy

1. # check to see if the supplied product is one we can build  
2. function check_product()  
3. {  
4.     T=$(gettop)  
5.     if [ ! "$T" ]; then  
6.         echo "Couldn't locate the top of the tree.  Try setting TOP." >&2  
7.         return  
8.     fi  
9.     CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core \  
10.         TARGET_PRODUCT=$1 \  
11.         TARGET_BUILD_VARIANT= \  
12.         TARGET_BUILD_TYPE= \  
13.         TARGET_BUILD_APPS= \  
14.         get_build_var TARGET_DEVICE > /dev/null  
15.     # hide successful answers, but allow the errors to show  
16. }

        函数gettop用来返回Android源代码工程的根目录。函数check_product需要在Android源代码工程根目录或者子目录下调用。否则的话,函数check_product就出错返回。

        接下来函数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中,它的实现如下所示:

[plain]  view plain copy

1. # Get the exact value of a build variable.  
2. function get_build_var()  
3. {  
4.     T=$(gettop)  
5.     if [ ! "$T" ]; then  
6.         echo "Couldn't locate the top of the tree.  Try setting TOP." >&2  
7.         return  
8.     fi  
9.     CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core \  
10.       make --no-print-directory -C "$T" -f build/core/config.mk dumpvar-$1  
11. }


        这里就可以看到,函数get_build_var实际上就是通过make命令在Android源代码工程根目录中执行build/core/config.mk文件,并且将make目标设置为dumpvar-$1,也就是dumpvar-TARGET_DEVICE。

        文件build/core/config.mk的内容比较多,这里我们只关注与产品名称合法性检查相关的逻辑,这些逻辑也基本上涵盖了Android编译系统初始化的逻辑,如下所示:


[plain]  view plain copy


1. ......  
2.   
3. # ---------------------------------------------------------------  
4. # Define most of the global variables.  These are the ones that  
5. # are specific to the user's build configuration.  
6.  include $(BUILD_SYSTEM)/envsetup.mk //定义下文的TARGET_DEVICE
7.   
8. # Boards may be defined under $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)  
9. # or under vendor/*/$(TARGET_DEVICE).  Search in both places, but  
10. # make sure only one exists.  
11. # Real boards should always be associated with an OEM vendor.  
12.  board_config_mk := \  
13.      $(strip $(wildcard \  
14.          $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)/BoardConfig.mk \  //加载TARGET_DEVICE目录下的BoardConfig.mk

15.          device/*/$(TARGET_DEVICE)/BoardConfig.mk \  
16.          vendor/*/$(TARGET_DEVICE)/BoardConfig.mk \  
17.     ))  
18. ifeq ($(board_config_mk),)  
19.   $(error No config file found for TARGET_DEVICE $(TARGET_DEVICE))  
20. endif  
21. ifneq ($(words $(board_config_mk)),1)  
22.   $(error Multiple board config files for TARGET_DEVICE $(TARGET_DEVICE): $(board_config_mk))  
23. endif  
24. include $(board_config_mk)  
25.   
26. ......  
27.   
28. include $(BUILD_SYSTEM)/dumpvar.mk

环境变量TARGET_DEVICE用来描述目标设备,它的值是在envsetup.mk文件加载的过程中确定的。一旦目标设备确定后,就可以在$(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)、device/*/$(TARGET_DEVICE)和vendor/*/$(TARGET_DEVICE)目录中找到对应的BoradConfig.mk文件。注意,变量SRC_TARGET_DIR的值等于build/target。最后,dumpvar.mk文件也是位于build/core目录中,它用来打印已经配置好的编译环境信息。

        接下来我们就通过进入到build/core/envsetup.mk文件来分析变量TARGET_DEVICE的值是如何确定的:

[plain]  view plain copy


1. # Read the product specs so we an get TARGET_DEVICE and other  
2. # variables that we need in order to locate the output files.  
3. include $(BUILD_SYSTEM)/product_config.mk


      它通过加载另外一个文件build/core/product_config.mk文件来确定变量TARGET_DEVICE以及其它与目标产品相关的变量的值。

       文件build/core/product_config.mk的内容很多,这里我们只关注变量TARGET_DEVICE设置相关的逻辑,如下所示:


[plain]  view plain copy


1. ......  
2.   
3. ifneq ($(strip $(TARGET_BUILD_APPS)),)  
4. # An unbundled app build needs only the core product makefiles.  
5. all_product_configs := $(call get-product-makefiles,\  
6.     $(SRC_TARGET_DIR)/product/AndroidProducts.mk)//仅仅是加载(SRC_TARGET_DIR)/product下的AndroidProduct.mk文件
7. else  
8. # Read in all of the product definitions specified by the AndroidProducts.mk  
9. # files in the tree.  
10.  all_product_configs := $(get-all-product-makefiles)  //加载所有AndroidProducts.mk,分析见下面
11. endif  
12.   
13. # all_product_configs consists items like:  
14. # <product_name>:<path_to_the_product_makefile>  
15. # or just <path_to_the_product_makefile> in case the product name is the  
16. # same as the base filename of the product config makefile.  
17. current_product_makefile :=  
18. all_product_makefiles :=  
19. $(foreach f, $(all_product_configs),\  
20.     $(eval _cpm_words := $(subst :,$(space),$(f)))\  
21.     $(eval _cpm_word1 := $(word 1,$(_cpm_words)))\  
22.     $(eval _cpm_word2 := $(word 2,$(_cpm_words)))\  
23.     $(if $(_cpm_word2),\  
24.         $(eval all_product_makefiles += $(_cpm_word2))\  
25.         $(if $(filter $(TARGET_PRODUCT),$(_cpm_word1)),\  
26.             $(eval current_product_makefile += $(_cpm_word2)),),\  
27.         $(eval all_product_makefiles += $(f))\  
28.         $(if $(filter $(TARGET_PRODUCT),$(basename $(notdir $(f)))),\  
29.             $(eval current_product_makefile += $(f)),)))  
30. _cpm_words :=  
31. _cpm_word1 :=  
32. _cpm_word2 :=  
33.  current_product_makefile := $(strip $(current_product_makefile)) //rk3288_box.mk 
34. all_product_makefiles := $(strip $(all_product_makefiles))  
35.   
36. ifneq (,$(filter product-graph dump-products, $(MAKECMDGOALS)))  
37. # Import all product makefiles.  
38. $(call import-products, $(all_product_makefiles)) //例如提取rk3288_box.mk
39. else  
40. # Import just the current product.  
41. ifndef current_product_makefile  
42. $(error Cannot locate config makefile for product "$(TARGET_PRODUCT)")  
43. endif  
44. ifneq (1,$(words $(current_product_makefile)))  
45. $(error Product "$(TARGET_PRODUCT)" ambiguous: matches $(current_product_makefile))  
46. endif  
47. $(call import-products, $(current_product_makefile))  
48. endif  # Import all or just the current product makefile  
49.   
50. ......  
51.   
52. # Convert a short name like "sooner" into the path to the product  
53. # file defining that product.  
54. #  
55. INTERNAL_PRODUCT := $(call resolve-short-product-name, $(TARGET_PRODUCT))//是一个mk文件 
56. ifneq ($(current_product_makefile),$(INTERNAL_PRODUCT))  
57. $(error PRODUCT_NAME inconsistent in $(current_product_makefile) and $(INTERNAL_PRODUCT))  
58. endif  
59. current_product_makefile :=  
60. all_product_makefiles :=  
61. all_product_configs :=  
62.   
63. # Find the device that this product maps to.  
64. TARGET_DEVICE := $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_DEVICE) 
65. //如下所示:
66. //PRODUCTS.build/target/product/full.mk.PRODUCT_NAME := full  
67. //PRODUCTS.build/target/product/full.mk.PRODUCT_DEVICE := generic  
68. //PRODUCTS.build/target/product/full.mk.PRODUCT_BRAND := Android  
69. //PRODUCTS.build/target/product/full.mk.PRODUCT_MODEL := Full Android on Emulator 
 70. ......

       上述代码的执行逻辑如下所示:

       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中,如下所示:


[plain]  view plain copy


1. #  
2. # Returns the sorted concatenation of all PRODUCT_MAKEFILES  
3. # variables set in all AndroidProducts.mk files.  
4. # $(call ) isn't necessary.  
5. #  
6. define get-all-product-makefiles  
7. $(call get-product-makefiles,$(_find-android-products-files))  
8. endef

       它首先是调用函数_find-android-products-files来找到Android源代码目录中定义的所有AndroidProducts.mk文件,然后再调用函数get-product-makefiles获得在这里AndroidProducts.mk文件里面定义的产品Makefile文件。

       函数_find-android-products-files也是定义在文件build/core/product.mk中,如下所示:

[plain]  view plain copy

1. #  
2. # Returns the list of all AndroidProducts.mk files.  
3. # $(call ) isn't necessary.  
4. #  
5. define _find-android-products-files  
6. $(shell test -d device && find device -maxdepth 6 -name AndroidProducts.mk) \  
7.   $(shell test -d vendor && find vendor -maxdepth 6 -name AndroidProducts.mk) \  
8.   $(SRC_TARGET_DIR)/product/AndroidProducts.mk  
9. endef


从这里就可以看出,Android源代码目录中定义的所有AndroidProducts.mk文件位于device、vendor或者build/target/product目录或者相应的子目录(最深是6层)中。

get-all-product-makefiles区别是加载的范围不一样)

[plain]  view plain copy


1. #  
2. # Returns the sorted concatenation of PRODUCT_MAKEFILES  
3. # variables set in the given AndroidProducts.mk files.  
4. # $(1): the list of AndroidProducts.mk files.  
5. #  
6. define get-product-makefiles  
7. $(sort \  
8.   $(foreach f,$(1), \  
9.     $(eval PRODUCT_MAKEFILES :=) \  
10.     $(eval LOCAL_DIR := $(patsubst %/,%,$(dir $(f)))) \  
11.     $(eval include $(f)) \  
12.     $(PRODUCT_MAKEFILES) \  
13.    ) \  
14.   $(eval PRODUCT_MAKEFILES :=) \  
15.   $(eval LOCAL_DIR :=) \  
16.  )  
17. endef

       这个函数实际上就是遍历参数$1所描述的AndroidProucts.mk文件列表,并且将定义在这些AndroidProucts.mk文件中的变量PRODUCT_MAKEFILES的值提取出来,形成一个列表返回给调用者。

       例如,在build/target/product/AndroidProducts.mk文件中,变量PRODUCT_MAKEFILES的值如下所示:

[plain]  view plain copy

1. # Unbundled apps will be built with the most generic product config.  
2. ifneq ($(TARGET_BUILD_APPS),)  
3. PRODUCT_MAKEFILES := \  
4.     $(LOCAL_DIR)/full.mk \  
5.     $(LOCAL_DIR)/full_x86.mk \  
6.     $(LOCAL_DIR)/full_mips.mk  
7. else  
8. PRODUCT_MAKEFILES := \  
9.     $(LOCAL_DIR)/core.mk \  
10.     $(LOCAL_DIR)/generic.mk \  
11.     $(LOCAL_DIR)/generic_x86.mk \  
12.     $(LOCAL_DIR)/generic_mips.mk \  
13.     $(LOCAL_DIR)/full.mk \  
14.     $(LOCAL_DIR)/full_x86.mk \  
15.     $(LOCAL_DIR)/full_mips.mk \  
16.     $(LOCAL_DIR)/vbox_x86.mk \  
17.     $(LOCAL_DIR)/sdk.mk \  
18.     $(LOCAL_DIR)/sdk_x86.mk \  
19.     $(LOCAL_DIR)/sdk_mips.mk \  
20.     $(LOCAL_DIR)/large_emu_hw.mk  
21. endif