编译Android源码(3) ---- lunch命令的分析

上一篇文章解释了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_goals
MAKECMDGOALS是执行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


你可能感兴趣的:(编译Android源码(3) ---- lunch命令的分析)