source /build/envsetup.sh和lunch)

提醒:想要研究安卓编译系统,必须对bash shell和GUN make非常熟悉,不然会看的云里雾里,没有这个背景的可以先补充知识。

1.source ./build/envsetup.sh
主要是加载device/vendor目录下面的vendorsetup.sh文件

2.lunch
source /build/envsetup.sh和lunch)_第1张图片
(本图来自罗升阳博客,已经比较老了,现在的安卓源码将board_config_mk放在了envsetup.mk)
上面的图很好的阐释了lunch命令执行的主要流程
总结起来,android编译系统初始化以后主要完成下面三件事情

  1. 将device/vendor目录下面的vendorsetup.sh加载到当前shell当中,这样我们执行lunch的时候就会有产品的选项
  2. 将lunch/mm/mmm等加载到了当前shell,当前shell可以执行上述函数
  3. lunch命令执行后会弹出选项供我们选择,lunch函数根据选择设定了TARGET_PRODUCT/TARGET_BUILD_VARIANT/TARGET_BUILD_TYPE和TARGET_BUILD_APPS等环境变量。

    上面三条可以通过执行lunch函数来验证,看看lunch的输出结果就好了,示例如下:

PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=4.2
TARGET_PRODUCT=full
TARGET_BUILD_VARIANT=eng
TARGET_BUILD_TYPE=release
TARGET_BUILD_APPS=
TARGET_ARCH=arm
TARGET_ARCH_VARIANT=armv7-a
HOST_ARCH=x86
HOST_OS=linux
HOST_OS_EXTRA=Linux-3.8.0-31-generic-x86_64-with-Ubuntu-13.04-raring
HOST_BUILD_TYPE=release
BUILD_ID=JOP40C
OUT_DIR=out

接下来我们将对上面两个步骤展开详细的论述。

build/envsetup.sh加载过程

这个文件是一个脚本文件,里面定义了各种函数如mm等,函数的部分我们稍后分析,先来介绍其另外一个重要的功能,加载vendorsetup.sh焦恩文件,相关代码如下(build/envsetup.sh):

for f in `test -d device && find -L device -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null` \
         `test -d vendor && find -L vendor -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null`
do
    echo "including $f"
    . $f
done
unset f

上面代码很简单,在device/vendor里面寻找vendorsetup.sh文件,如此一来,我们的产品就会出现在了lunch命令弹出的选项当中。

lunch命令执行过程

lunch函数的实现代码如下:

function lunch()
{
    local answer
    //if/else主要判定lunch是否带参数,如果带参数就直接赋值给
    //answer,否则,打印选项供选择
    if [ "$1" ] ; then
        answer=$1
    else
        print_lunch_menu
        echo -n "Which would you like? [full-eng] "
        read answer
    fi
    //selection保存了选择的选项内容
    local selection=

    if [ -z "$answer" ]
    then
        selection=full-eng
    elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")
    then
        if [ $answer -le ${#LUNCH_MENU_CHOICES[@]} ]
        then
            selection=${LUNCH_MENU_CHOICES[$(($answer-1))]}
        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=
    //假如selection=XM-eng-32,product=XM
    local product=$(echo -n $selection | sed -e "s/-.*$//")
    check_product $product
    if [ $? -ne 0 ]
    then
        echo
        echo "** Don't have a product spec for: '$product'"
        echo "** Do you have the right repo manifest?"
        product=
    fi
    //按照上面的假设,variant=eng
    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_BUILD_TYPE=release

    echo

    set_stuff_for_environment
    printconfig
}

lunch命令的功能总结起来很清楚了,根据用户提供的选项执行,check_product和check_variant函数,并最终打印环境变量,这里重点需要分析的是check_product函数,实现如下:

function check_product()
{
    #此时TARGET_DEVICE为空
    T=$(gettop)
    if [ ! "$T" ]; then
        echo "Couldn't locate the top of the tree.  Try setting TOP." >&2
        return
    fi
        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
}

逻辑很明显,使用TARGET_DEVICE参数调用get_build_var函数,这个函数实现代码如下:

function get_build_var()
{
    T=$(gettop)
    if [ ! "$T" ]; then
        echo "Couldn't locate the top of the tree.  Try setting TOP." >&2
        return
    fi
    (\cd $T; CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core \
      command make --no-print-directory -f build/core/config.mk dumpvar-$1)
}

逻辑也很简单,就是执行一个make指令,目标时dumpvar-TARGET_DEVICE,makefile是build/core/config.mk,好,现在我们终于进入了极为重要的一个文件了。主要代码如下:

include $(BUILD_SYSTEM)/envsetup.mk
...
include $(BUILD_SYSTEM/dumpvar.mk

所有的惊喜都在build/core/envsetup.mk中,核心代码如下:

include $(BUILD_SYSTEM)/product_config.mk
...
board_config_mk := \
    $(strip $(wildcard \
        $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)/BoardConfig.mk \
        $(shell test -d device && find device -maxdepth 4 -path '*/$(TARGET_DEVICE)/BoardConfig.mk') \
        $(shell test -d vendor && find vendor -maxdepth 4 -path '*/$(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
#将目标BoardConfig.mk文件载入进来
include $(board_config_mk)

主要是完成了两件事,一个是加载product_config.mk,一个是加载board_config_mk。前者来确定了TARGET_DEVICE及其与目标产品相关的变量值,后者加载BoardConfig.mk文件,所以我们先来分析前者,核心代码如下:

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.
all_product_configs := $(call get-product-makefiles,\
    $(SRC_TARGET_DIR)/product/AndroidProducts.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)
endif
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))
#当lunch选择了m13时current_product_makefile=device/amlogic/MagicBox_M13/MagicBox_M13.mk

all_product_makefiles := $(strip $(all_product_makefiles))
#$(shell echo $(all_product_makefiles) >> debai.txt)


#这里执行的分支是$(call import-products,$(current_product_makefile))
ifneq (,$(filter product-graph dump-products, $(MAKECMDGOALS)))
# Import all product makefiles.
$(call import-products, $(all_product_makefiles))
else
# Import just the current product.
ifndef current_product_makefile
$(error Can not 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

# Sanity check
$(check-all-products)

ifneq ($(filter dump-products, $(MAKECMDGOALS)),)
$(dump-products)
$(error done)
endif

# 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))
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 :=
...
TARGET_DEVICE := $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_DEVICE)

简单的介绍下上面的逻辑:

  1. 检查TARGET_BUILD_APPS是否为空,从lunch打印的结果可以看到为空,所以直接调用到get-all-product-makefiles函数。all_product_configs保存AndroidProducts.mkPRODUCT_MAKEFILES变量的内容,即产品定义文件。
  2. 遍历all_product_configs所描述的问价列表,并在这些Makefile文件中,找到名称和环境变量TARGET_PRODUCT值相同的文件,保存在current_product_makefile,注意TARGET_PRODCUT是执行lunch的时候设定的。
  3. 调用resolve-short-product-name解析TARGET_PRODUCT的值,将它变成一个makefile文件路。并保存在变量INTERNAL_PRODUCT中,这里要求INTERNAL_PRODUCT和current_product_makefile的值相当,否则,就说说明用户指定了非法产品名称。
  4. 找到名称PRODUCTS. (INTERNALPRODUCT).PRODUCTDEVICETARGETDEVICEPRODUCTS. (INTERNAL_PRODUCT).PRODUCT_DEVICE是调用resolve-short-product-name时设定的。

上面过程主要涉及了get-all-product-makefiles、import-products和resolve-shrot-product-name按个关键函数,我们接下来分析后两个的实现。
import-products实现在文件build/core/product.mk当中:

define import-products  
$(call import-nodes,PRODUCTS,$(1),$(_product_var_list))  
endef

它调用import-nodes来加载参数 1PRODUCTS (_product_var_list),其中变量_product_var_list也定义在prodcut.mk中:

_product_var_list := \  
    PRODUCT_NAME \  
    PRODUCT_MODEL \  
    PRODUCT_LOCALES \  
    PRODUCT_AAPT_CONFIG \  
    PRODUCT_AAPT_PREF_CONFIG \  
    PRODUCT_PACKAGES \  
    PRODUCT_PACKAGES_DEBUG \  
    PRODUCT_PACKAGES_ENG \  
    PRODUCT_PACKAGES_TESTS \  
    PRODUCT_DEVICE \  
    PRODUCT_MANUFACTURER \  
    PRODUCT_BRAND \  
    PRODUCT_PROPERTY_OVERRIDES \  
    PRODUCT_DEFAULT_PROPERTY_OVERRIDES \  
    PRODUCT_CHARACTERISTICS \  
    PRODUCT_COPY_FILES \  
    PRODUCT_OTA_PUBLIC_KEYS \  
    PRODUCT_EXTRA_RECOVERY_KEYS \  
    PRODUCT_PACKAGE_OVERLAYS \  
    DEVICE_PACKAGE_OVERLAYS \  
    PRODUCT_TAGS \  
    PRODUCT_SDK_ADDON_NAME \  
    PRODUCT_SDK_ADDON_COPY_FILES \  
    PRODUCT_SDK_ADDON_COPY_MODULES \  
    PRODUCT_SDK_ADDON_DOC_MODULES \  
    PRODUCT_DEFAULT_WIFI_CHANNELS \  
    PRODUCT_DEFAULT_DEV_CERTIFICATE \  
    PRODUCT_RESTRICT_VENDOR_FILES \  
    PRODUCT_VENDOR_KERNEL_HEADERS \  
    PRODUCT_FACTORY_RAMDISK_MODULES \  
    PRODUCT_FACTORY_BUNDLE_MODULES 

它描述了产品定义文件中定义的各种变量。
函数import-nodes定义在build/core/node-fns.mk中,如下:

#  
# $(1): output list variable name, like "PRODUCTS" or "DEVICES"  
# $(2): list of makefiles representing nodes to import  
# $(3): list of node variable names  
#  
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

这个函数主要做了三件事:

  1. 调用_import-nodes-inner将$2描述的产品定义文件加载进来
  2. 调用函数move-var-list将定义在前面所加载的产品定义文件里面的由参数$3指定的变量的值分别拷贝到另外一组独立变量当中(理解这句话很重要呀,因为这个设定TARGET_DEVICE的值密切相关的)
  3. 将参数 2 1描述的变量中,也就是保存在PRODUCTS中。

上面的2过程需要额外介绍一下,由于加载的每一个产品定义文件都会定义相同的变量名,为了区分这些变量,需要加一些前缀,例如,假设加载了build/target/product/full.mk这个产品定义文件,它里面的定义的变量如下:

# Overrides  
PRODUCT_NAME := full  
PRODUCT_DEVICE := generic  
PRODUCT_BRAND := Android  
PRODUCT_MODEL := Full Android on Emulator

当调用了move-var-list对它进行解析以后,就会得到如下变量:

`
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 Emulato

正是由于调用了move-var-list,我们才可以在build/core/product_config.mk中通过PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_DEVICE来设置TARGET_DEVICE的值。

现在回到build/core/config.mk文件中,接下来我们看BoardConfig.mk文件的加载过程。当前要加载的BoardConfig.mk文件由变量TARGET_DEVIEC来确定的。例如,假设我们执行lunch命令时,输入了full-eng,那么build/target/product/full.mk就会被加载,并且我们得到了TARGET_DEVICE的值是generic,接下来加载的BoardConfig.mk文件就会在build/target/board/generic目录中找到。

你可能感兴趣的:(Android开发)