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