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的脚本目录
本文作者:caseyzz
本文链接:https://www.cnblogs.com/caseyzq/p/17124317.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步