提醒:想要研究安卓编译系统,必须对bash shell和GUN make非常熟悉,不然会看的云里雾里,没有这个背景的可以先补充知识。
1.source ./build/envsetup.sh
主要是加载device/vendor目录下面的vendorsetup.sh文件
2.lunch
(本图来自罗升阳博客,已经比较老了,现在的安卓源码将board_config_mk放在了envsetup.mk)
上面的图很好的阐释了lunch命令执行的主要流程
总结起来,android编译系统初始化以后主要完成下面三件事情
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
接下来我们将对上面两个步骤展开详细的论述。
这个文件是一个脚本文件,里面定义了各种函数如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函数的实现代码如下:
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)
简单的介绍下上面的逻辑:
上面过程主要涉及了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来加载参数 1指定的产品定义文件,并指定另外两个参数PRODUCTS和 (_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
这个函数主要做了三件事:
上面的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目录中找到。