上一篇文章解释了envsetup.sh的主要用途,其中是把LUNCH_MENU_CHOICES数组填充一些套餐combo,在lunch命令里这个数组会被使用到。
让我们看下lunch命令在envsetup.sh的位置:530-624行
function lunch() { local answer if [ "$1" ] ; then answer=$1 else print_lunch_menu echo -n "Which would you like? [full-eng] " read answer fi local selection= if [ -z "$answer" ] then selection=full-eng elif [ "$answer" = "simulator" ] then selection=simulator elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$") then if [ $answer -le ${#LUNCH_MENU_CHOICES[@]} ] then selection=${LUNCH_MENU_CHOICES[$(($answer-$_arrayoffset))]} fi elif (echo -n $answer | grep -q -e "^[^\-][^\-]*-[^\-][^\-]*$") then selection=$answer fi if [ -z "$selection" ] then echo echo "Invalid lunch combo: $answer" return 1 fi export TARGET_BUILD_APPS= # special case the simulator if [ "$selection" = "simulator" ] then export TARGET_PRODUCT=sim export TARGET_BUILD_VARIANT=eng export TARGET_SIMULATOR=true export TARGET_BUILD_TYPE=debug else local product=$(echo -n $selection | sed -e "s/-.*$//") check_product $product if [ $? -ne 0 ] then # if we can't find a product, try to grab it off the CM github T=$(gettop) pushd $T > /dev/null build/tools/roomservice.py $product popd > /dev/null check_product $product fi if [ $? -ne 0 ] then echo echo "** Don't have a product spec for: '$product'" echo "** Do you have the right repo manifest?" product= fi local variant=$(echo -n $selection | sed -e "s/^[^\-]*-//") check_variant $variant if [ $? -ne 0 ] then echo echo "** Invalid variant: '$variant'" echo "** Must be one of ${VARIANT_CHOICES[@]}" variant= fi if [ -z "$product" -o -z "$variant" ] then echo return 1 fi export TARGET_PRODUCT=$product export TARGET_BUILD_VARIANT=$variant export TARGET_SIMULATOR=false export TARGET_BUILD_TYPE=release fi # !simulator echo set_stuff_for_environment printconfig }
在shell里执行lunch时可以传入参数,如lunch 86或lunch cyanogen_sholest-eng,$1展开就是86或cyanogen_sholest-eng,也就是我们传入的参数。local answer if [ "$1" ] ; then answer=$1 else print_lunch_menu echo -n "Which would you like? [full-eng] " read answer fi
这段代码是为本地变量 answer赋值,这里判断用户是否传入了参数,如是则直接赋值给本地变量answer,answer的作用域仅仅在lunch函数中。否则调用print_lunch_menu方法,打印LUNCH_MENU_CHOICES的内容,让用户选择。457-485行:
function print_lunch_menu() { local uname=$(uname) echo echo "You're building on" $uname if [ "$(uname)" = "Darwin" ] ; then echo " (ohai, koush!)" fi echo if [ "z${CM_DEVICES_ONLY}" != "z" ]; then echo "Breakfast menu... pick a combo:" else echo "Lunch menu... pick a combo:" fi local i=1 local choice for choice in ${LUNCH_MENU_CHOICES[@]} do echo " $i. $choice" i=$(($i+1)) done if [ "z${CM_DEVICES_ONLY}" != "z" ]; then echo "... and don't forget the bacon!" fi echo }
answer变量被赋值后,接着看:local selection= if [ -z "$answer" ] then selection=full-eng elif [ "$answer" = "simulator" ] then selection=simulator elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$") then if [ $answer -le ${#LUNCH_MENU_CHOICES[@]} ] then selection=${LUNCH_MENU_CHOICES[$(($answer-$_arrayoffset))]} fi elif (echo -n $answer | grep -q -e "^[^\-][^\-]*-[^\-][^\-]*$") then selection=$answer fi if [ -z "$selection" ] then echo echo "Invalid lunch combo: $answer" return 1 fi
这段代码是为本地变量selection赋值,先检查answer变量,如其长度是为0则赋值full-eng,等于“simulator”则赋值simulator,如是数字则作为索引获取LUNCH_MENU_CHOICES相应位置的值,如是字符串则直接赋answer的值。最后再次检验变量selection的长度是否为0,如是则立即停止执行。
export TARGET_BUILD_APPS= # special case the simulator if [ "$selection" = "simulator" ] then export TARGET_PRODUCT=sim export TARGET_BUILD_VARIANT=eng export TARGET_SIMULATOR=true export TARGET_BUILD_TYPE=debug else local product=$(echo -n $selection | sed -e "s/-.*$//") check_product $product if [ $? -ne 0 ] then # if we can't find a product, try to grab it off the CM github T=$(gettop) pushd $T > /dev/null build/tools/roomservice.py $product popd > /dev/null check_product $product fi if [ $? -ne 0 ] then echo echo "** Don't have a product spec for: '$product'" echo "** Do you have the right repo manifest?" product= fi local variant=$(echo -n $selection | sed -e "s/^[^\-]*-//") check_variant $variant if [ $? -ne 0 ] then echo echo "** Invalid variant: '$variant'" echo "** Must be one of ${VARIANT_CHOICES[@]}" variant= fi if [ -z "$product" -o -z "$variant" ] then echo return 1 fi export TARGET_PRODUCT=$product export TARGET_BUILD_VARIANT=$variant export TARGET_SIMULATOR=false export TARGET_BUILD_TYPE=release fi # !simulator
其实这段代码的目的是为1.TARGET_PRODUCT
2.TARGET_BUILD_VARIANT
3.TARGET_SIMULATOR
4.TARGET_BUILD_TYPE
5.TARGET_BUILD_APPS
这5个变量赋值并导出,这5个变量在执行完lunch后,执行make时起了重要的作用,也正是由于他们这么重要,所以必须对生成的过程进行检查。
如selection不等于simulator,首先取selection变量'-'符号的左侧字符串赋值给product,然后以变量product作为参数调用check_product。
# 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 if (echo -n $1 | grep -q -e "^cyanogen_") ; then CM_BUILD=$(echo -n $1 | sed -e 's/^cyanogen_//g') else CM_BUILD= fi export CM_BUILD CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core \ TARGET_PRODUCT=$1 TARGET_BUILD_VARIANT= \ TARGET_SIMULATOR= TARGET_BUILD_TYPE= \ TARGET_BUILD_APPS= \ get_build_var TARGET_DEVICE > /dev/null # hide successful answers, but allow the errors to show }
检查$1是否以"cyanogen_"开头,如是则赋值CM_BUILD为"cyanogen_"后的字符串,否则为空。例如$1是cyanogen_sholest,CM_BUILD则为"sholest"。最后导出CM_BUILD。然后将一些变量赋值,以“TARGET_DEVICE”作为参数执行get_build_var,意思是看TARGET_DEVICE变量是否被赋值了,如被赋值了,则该product可以被build。# 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 }这里最后执行了make,以build/core/config.mk作为Makefile文件输入,目标为dumpvar-TARGET_DEVICE。对make不熟悉的同学可以打开(http://www.gnu.org/software/make/manual/make.html)参考。
打开build/core/config.mk,搜索整个文件发现与dumpvar相关的只有153行:
include $(BUILD_SYSTEM)/dumpvar.mk
其中重要的一段:dumpvar_goals := \ $(strip $(patsubst dumpvar-%,%,$(filter dumpvar-%,$(MAKECMDGOALS)))) ifdef dumpvar_goals ifneq ($(words $(dumpvar_goals)),1) $(error Only one "dumpvar-" goal allowed. Saw "$(MAKECMDGOALS)") endif # If the goal is of the form "dumpvar-abs-VARNAME", then # treat VARNAME as a path and return the absolute path to it. absolute_dumpvar := $(strip $(filter abs-%,$(dumpvar_goals))) ifdef absolute_dumpvar dumpvar_goals := $(patsubst abs-%,%,$(dumpvar_goals)) DUMPVAR_VALUE := $(PWD)/$($(dumpvar_goals)) dumpvar_target := dumpvar-abs-$(dumpvar_goals) else DUMPVAR_VALUE := $($(dumpvar_goals)) dumpvar_target := dumpvar-$(dumpvar_goals) endif .PHONY: $(dumpvar_target) $(dumpvar_target): @echo $(DUMPVAR_VALUE) endif # dumpvar_goalsMAKECMDGOALS是执行make时传入的目标列表,这里我们只有“dumpvar-TARGET_DEVICE”一个目标。filter函数过滤返回满足“dumpvar-%”通配符的字符串,所以这里filter返回“dumpvar-TARGET_DEVICE”。
patsubst函数将filter函数返回数组中的元素符合"dumpvar-%",替换为"%"并返回。
strip函数大家都懂的了~于是最后dumpvar_goals变量的值为"TARGET_DEVICE",也就是其实我们是想测试该变量是否已经被赋值了。
words函数返回dumpvar_goals中有多少个元素,如果不等于1则抛出错误。
下一个if判断里面,我们的要取的变量不是abs-开头的,也就是不是要取绝对路径,所以分支到else,dumpvar_goals第一次展开后为"TARGET_DEVICE",第二次展开意思是取TARGET_DEVICE这个变量的值,并赋给DUMPVAR_VALUE。
下面看下TARGET_DEVICE被赋值的过程:
在config.mk中:
# Define most of the global variables. These are the ones that # are specific to the user's build configuration. include $(BUILD_SYSTEM)/envsetup.mk
在envsetup.mk中:
# Read the product specs so we can get TARGET_DEVICE and other # variables that we need in order to locate the output files. include $(BUILD_SYSTEM)/product_config.mk在product_config.mk中:
# Include the product definitions. # We need to do this to translate TARGET_PRODUCT into its # underlying TARGET_DEVICE before we start defining any rules. # include $(BUILD_SYSTEM)/node_fns.mk include $(BUILD_SYSTEM)/product.mk include $(BUILD_SYSTEM)/device.mk ifneq ($(strip $(TARGET_BUILD_APPS)),) # An unbundled app build needs only the core product makefiles. $(call import-products,$(call get-product-makefiles,\ $(SRC_TARGET_DIR)/product/AndroidProducts.mk)) else ifneq ($(CM_BUILD),) $(call import-products, vendor/cyanogen/products/cyanogen_$(CM_BUILD).mk) else # Read in all of the product definitions specified by the AndroidProducts.mk # files in the tree. # #TODO: when we start allowing direct pointers to product files, # guarantee that they're in this list. $(call import-products, $(get-all-product-makefiles)) endif # TARGET_BUILD_APPS $(check-all-products) #$(dump-products) #$(error done) # 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)) #$(error TARGET_PRODUCT $(TARGET_PRODUCT) --> $(INTERNAL_PRODUCT)) # Find the device that this product maps to. TARGET_DEVICE := $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_DEVICE)
这里因为TARGET_BUILD_APPS为空且CM_BUILD不为空,所以执行$(call import-products, vendor/cyanogen/products/cyanogen_$(CM_BUILD).mk)看一下import-products方法:# $(1): product makefile list == vendor/cyanogen/products/cyanogen_sholest.mk # #TODO: check to make sure that products have all the necessary vars defined define import-products $(call import-nodes,PRODUCTS,$(1),$(_product_var_list)) endef
再次跳到import-nodes方法:# # $(1): output list variable name, like "PRODUCTS" or "DEVICES" == PRODUCTS # $(2): list of makefiles representing nodes to import == vendor/cyanogen/products/cyanogen_sholest.mk # $(3): list of node variable names == PRODUCT_SPECIFIC_DEFINES PRODUCT_BUILD_PROP_OVERRIDES PRODUCT_NAME ... # define import-nodes $(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)) \ $(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))),) \ ) \ ,) endef