Android 编译系统分析
在编译android project时,我们需要使用到makefile文件,通过makefile文件的规则来构建整个project
source build/envsetup.sh
lunch project_name
make -jx
envsetup.sh脚本
先看下source build/envsetup.sh做了哪些工作?
定义函数
在build/envsetup.sh里定义了很多函数
function hmm() //显示帮助信息,列出所有支持的命令,比如lunch、mm、mmm等
function get_abs_build_var() //获取编译时一些变量的绝对路径(比如BUILD_SYSTEM)
function get_build_var() //获取编译时一些变量的路径
function check_product() //检查产品是否支持编译
function check_variant() //检查变量variant是否有效,只能是user、userdebug、eng
function printconfig() //打印配置信息
function choosecombo() //选择编译类型,release和debug中的一个
function add_lunch_combo() //增加lunch命令的选择项
function print_lunch_menu() //打印lunch命令可选择的项目列表
function lunch() //lunch命令的执行逻辑
function gettop //获取Android源码根目录
function m() //m命令的执行逻辑
function findmakefile() //查找当前目录下Android.mk文件路径
function mm() //mm命令的执行逻辑
function mmm() //mmm命令的执行逻辑
function mma()
function mmma()
function croot() //进入Android源码根目录
function ggrep()
function jgrep()
function cgrep()
function resgrep()
function mangrep()
function sepgrep()
function getprebuilt
function smoketest()
function runtest()
function godir()
function make()
envsetup.sh做的第一个工作就是定义了许多函数,方便在编译过程中调用
add_lunch_combo函数被废弃
所谓的lunch-combo指的是大家每次lunch时,打印出来的,可供选择的lunch选项。
旧版本的Android,使用此函数来添加lunch-combo
新版本的Android,执行此函数时会提醒add_lunch_combo is obsolete,告知此命令已经废弃。
并提醒开发者Use COMMON_LUNCH_CHOICES in your AndroidProducts.mk instead.
采用COMMON_LUNCH_CHOICES变量,在AndroidProducts.mk文件中声明lunch-combo
function add_lunch_combo()
{
if [ -n "$ZSH_VERSION" ]; then
echo -n "${funcfiletrace[1]}: "
else
echo -n "${BASH_SOURCE[1]}:${BASH_LINENO[0]}: "
fi
echo "add_lunch_combo is obsolete. Use COMMON_LUNCH_CHOICES in your AndroidProducts.mk instead."
}
lunch
function lunch()
function lunch()
{
# 定义局部变量`answer`来存放用户通过`lunch`命令传进来的参数(即`lunch-combo`,可以是字符串或数字)
local answer
if [ "$1" ] ; then
answer=$1
else # 如果用户没有没有传进来任何值,则打印出`lunch-combo`列表,提示用户输入
print_lunch_menu
echo -n "Which would you like? [aosp_arm-eng] "
read answer
fi
# 定义局部变量`selection`,用来存放`lunch-combo`字符串
local selection=
if [ -z "$answer" ] # 如果`answer`为空,则把`selection`设置为默认的`aosp_arm-eng`
then
selection=aosp_arm-eng
elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")
then # 如果`answer`是数字,则把`selection`设置为对应的`lunch-combo`字符串
local choices=($(TARGET_BUILD_APPS= get_build_var COMMON_LUNCH_CHOICES))
if [ $answer -le ${#choices[@]} ]
then
# array in zsh starts from 1 instead of 0.
if [ -n "$ZSH_VERSION" ]
then
selection=${choices[$(($answer))]}
else
selection=${choices[$(($answer-1))]}
fi
fi
else # 如果`answer`是字符串,则把`selection`赋值为`answer`
selection=$answer
fi
export TARGET_BUILD_APPS=
local product variant_and_version variant version
product=${selection%%-*} # Trim everything after first dash
variant_and_version=${selection#*-} # Trim everything up to first dash
if [ "$variant_and_version" != "$selection" ]; then
variant=${variant_and_version%%-*}
if [ "$variant" != "$variant_and_version" ]; then
version=${variant_and_version#*-}
fi
fi
if [ -z "$product" ]
then
echo
echo "Invalid lunch combo: $selection"
return 1
fi
TARGET_PRODUCT=$product \
TARGET_BUILD_VARIANT=$variant \
TARGET_PLATFORM_VERSION=$version \
build_build_var_cache
if [ $? -ne 0 ]
then
return 1
fi
export TARGET_PRODUCT=$(get_build_var TARGET_PRODUCT)
export TARGET_BUILD_VARIANT=$(get_build_var TARGET_BUILD_VARIANT)
if [ -n "$version" ]; then
export TARGET_PLATFORM_VERSION=$(get_build_var TARGET_PLATFORM_VERSION)
else
unset TARGET_PLATFORM_VERSION
fi
export TARGET_BUILD_TYPE=release
echo
set_stuff_for_environment
printconfig
destroy_build_var_cache
}
命令格式:
lunch <product_name>-<build_variant>
如果lunch没有没有指定product,会调用print_lunch_menu打印所有lunch-combo
print_lunch_menu函数
打印出全部的lunch-combo
具体是通过调用get_build_var函数来获取编译变量COMMON_LUNCH_CHOICES
function print_lunch_menu()
{
local uname=$(uname)
echo
echo "You're building on" $uname
echo
echo "Lunch menu... pick a combo:"
local i=1
local choice
for choice in $(TARGET_BUILD_APPS= get_build_var COMMON_LUNCH_CHOICES)
do
echo " $i. $choice"
i=$(($i+1))
done
echo
}
lunch函数里会根据选择的lunch-combo赋值
TARGET_PRODUCT=$product
TARGET_BUILD_VARIANT=$variant
如何去获取COMMON_LUNCH_CHOICES变量?
添加lunch-combo
新的Android版本,通过在AndroidProducts.mk中定义COMMON_LUNCH_CHOICES
AndroidProducts.mk中定义了lunch-combo和lunch-combo对应的PRODUCT_MAKEFILES Makefile文件。
COMMON_LUNCH_CHOICES中定义的产品名需要和PRODUCT_MAKEFILES中定义的文件名(不考虑路径和.mk后缀)一样。
如果不一样,需要在定义PRODUCT_MAKEFILES采用<product_name>:<path_to_the_product_makefile>格式
例:在/deivce/qcom/AndroidProducts.mk中添加
PRODUCT_MAKEFILES := \
$(LOCAL_DIR)/aosp_marlin.mk
COMMON_LUNCH_CHOICES := \
aosp_marlin-user \
aosp_marlin-eng \
aosp_marlin-userdebug
aosp_marlin就是product_name
userdebug 就是build_variant
AndroidProducts.mk如何被加载
从lunch到AndroidProducts.mk.list
- 当用户执行lunch命令时会调用get_build_var,build_build_var_cache等shell函数
- 这些shell函数又会调用build/soong/soong_ui.bash --dumpvar-mode
- soong_ui.bash走到/build/soong/cmd/soong_ui/main.go中的main函数中
- main函数中调用build.FindSources(buildCtx, config, f)
- FindSources代码逻辑如下
FindSources
// FindSources searches for source files known to <f> and writes them to the filesystem for
// use later.
func FindSources(ctx Context, config Config, f *finder.Finder) {
// note that dumpDir in FindSources may be different than dumpDir in NewSourceFinder
// if a caller such as multiproduct_kati wants to share one Finder among several builds
dumpDir := config.FileListDir()
os.MkdirAll(dumpDir, 0777)
androidMks := f.FindFirstNamedAt(".", "Android.mk")
err := dumpListToFile(androidMks, filepath.Join(dumpDir, "Android.mk.list"))
if err != nil {
ctx.Fatalf("Could not export module list: %v", err)
}
androidProductsMks := f.FindNamedAt("device", "AndroidProducts.mk")
androidProductsMks = append(androidProductsMks, f.FindNamedAt("vendor", "AndroidProducts.mk")...)
androidProductsMks = append(androidProductsMks, f.FindNamedAt("product", "AndroidProducts.mk")...)
err = dumpListToFile(androidProductsMks, filepath.Join(dumpDir, "AndroidProducts.mk.list"))
if err != nil {
ctx.Fatalf("Could not export product list: %v", err)
}
cleanSpecs := f.FindFirstNamedAt(".", "CleanSpec.mk")
err = dumpListToFile(cleanSpecs, filepath.Join(dumpDir, "CleanSpec.mk.list"))
if err != nil {
ctx.Fatalf("Could not export module list: %v", err)
}
owners := f.FindNamedAt(".", "OWNERS")
err = dumpListToFile(owners, filepath.Join(dumpDir, "OWNERS.list"))
if err != nil {
ctx.Fatalf("Could not find OWNERS: %v", err)
}
testMappings := f.FindNamedAt(".", "TEST_MAPPING")
err = dumpListToFile(testMappings, filepath.Join(dumpDir, "TEST_MAPPING.list"))
if err != nil {
ctx.Fatalf("Could not find TEST_MAPPING: %v", err)
}
androidBps := f.FindNamedAt(".", "Android.bp")
androidBps = append(androidBps, f.FindNamedAt("build/blueprint", "Blueprints")...)
if len(androidBps) == 0 {
ctx.Fatalf("No Android.bp found")
}
err = dumpListToFile(androidBps, filepath.Join(dumpDir, "Android.bp.list"))
if err != nil {
ctx.Fatalf("Could not find modules: %v", err)
}
}
在device、vendor、product目录中查找AndroidProducts.mk文件。
并将所有的名为AndroidProducts.mk文件路径记录在AndroidProducts.mk.list中
备注:这函数的作用不止如此,还会查找Android.mk、Android.bp、CleanSpec.mk等文件。
AndroidProducts.mk.list调用关系
AndroidProducts.mk.list会在makefile宏函数_find-android-products-files中被调用
_find-android-products-files宏函数
/build/make/core/product.mk文件中定义了该宏函数
#
# Returns the list of all AndroidProducts.mk files.
# $(call ) isn't necessary.
#
define _find-android-products-files
$(file <$(OUT_DIR)/.module_paths/AndroidProducts.mk.list) \
$(SRC_TARGET_DIR)/product/AndroidProducts.mk
endef
$(_find-android-products-files)显示了所有AndroidProducts.mk的路径信息
get-all-product-makefiles宏函数
$(_find-android-products-files)被get-all-product-makefiles调用
#
# Returns the sorted concatenation of all PRODUCT_MAKEFILES
# variables set in all AndroidProducts.mk files.
# $(call ) isn't necessary.
#
define get-all-product-makefiles
$(call get-product-makefiles,$(_find-android-products-files))
endef
get-all-product-makefiles中,$(_find-android-products-files)作为参数,交给get-product-makefiles去处理
get-product-makefiles
#
# Returns the sorted concatenation of PRODUCT_MAKEFILES
# variables set in the given AndroidProducts.mk files.
# $(1): the list of AndroidProducts.mk files.
#
# As a side-effect, COMMON_LUNCH_CHOICES will be set to a
# union of all of the COMMON_LUNCH_CHOICES definitions within
# each AndroidProducts.mk file.
#
define get-product-makefiles
$(sort \
$(eval _COMMON_LUNCH_CHOICES :=) \
$(foreach f,$(1), \
$(eval PRODUCT_MAKEFILES :=) \
$(eval COMMON_LUNCH_CHOICES :=) \
$(eval LOCAL_DIR := $(patsubst %/,%,$(dir $(f)))) \
$(eval include $(f)) \
$(call _validate-common-lunch-choices,$(COMMON_LUNCH_CHOICES),$(PRODUCT_MAKEFILES)) \
$(eval _COMMON_LUNCH_CHOICES += $(COMMON_LUNCH_CHOICES)) \
$(PRODUCT_MAKEFILES) \
) \
$(eval PRODUCT_MAKEFILES :=) \
$(eval LOCAL_DIR :=) \
$(eval COMMON_LUNCH_CHOICES := $(sort $(_COMMON_LUNCH_CHOICES))) \
$(eval _COMMON_LUNCH_CHOICES :=) \
)
endef
通过foreach循环遍历$(_find-android-products-files)
然后依次include AndroidProducts.mk,然后通过_validate-common-lunch-choices宏函数,判断其是否有效
最终结果
- 函数返回所有AndroidProducts.mk中定义的PRODUCT_MAKEFILES的值(全都囊括)
- 更新COMMON_LUNCH_CHOICES变量的值,将所有AndroidProducts.mk中定义的COMMON_LUNCH_CHOICES的值都囊括
make编译命令
如何编译特定的product
之前的lunch函数定义了TARGET_PRODUCT
AndroidProducts.mk中定义的PRODUCT_MAKEFILES对应的product.mk中定义了
1.PRODUCT_NAME
2.PRODUCT_DEVICE
3.PRODUCT_BRAND
4.PRODUCT_MODEL
...
-
AndroidProducts.mk 关联TARGET_PRODUCT
上述流程通过current_product_makefile 找到当前匹配的AndroidProduct.mk文件。 -
BoardConfig.mk怎么被自动加载?关联TARGET_DEVICE
上述流程确定了TARGET_DEVICE,后续在build/make/core/board_config.mk中查找device相关的boardconfig.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') \
))
扫描device以及vendor目录下4层深度以内的BoardConfig.mk文件,指定给board_config_mk 。
核心配置文件
AndroidProducts.mk
BoardConfig.mk
vendorsetup.sh // 通过AndroidProduct.mk引入,可去掉
product.mk //名称自定义 如aosp_marlin.mk
BoardConfig.mk
1.目的:产品涉及的硬件相关配置,例如架构、分区等等
product.mk
1.目的:产品涉及的软件相关配置,例如集成哪些软件(app等等)、软件涉及配置文件等等。
2.实现:
以下信息必加:
PRODUCT_NAME := TARGET_PRODUCT
PRODUCT_DEVICE := TARGET_PRODUCT
PRODUCT_BRAND := qti
PRODUCT_MODEL := Kona for arm64
(PRODUCT_NAME和PRODUCT_DEVICE保持一致,同时,跟产品名一样)
kernel defconfig及dts匹配
以SM8250为例:
/device/qcom/kernelscripts 编译kernel的脚本目录