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

  1. 当用户执行lunch命令时会调用get_build_var,build_build_var_cache等shell函数
  2. 这些shell函数又会调用build/soong/soong_ui.bash --dumpvar-mode
  3. soong_ui.bash走到/build/soong/cmd/soong_ui/main.go中的main函数中
  4. main函数中调用build.FindSources(buildCtx, config, f)
  5. 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
...

  1. AndroidProducts.mk 关联TARGET_PRODUCT
    上述流程通过current_product_makefile 找到当前匹配的AndroidProduct.mk文件。

  2. 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的脚本目录

posted @ 2023-02-15 18:51  caseyzz  阅读(764)  评论(0编辑  收藏  举报