美文网首页
Android 编译系统--02:编译环境初始化

Android 编译系统--02:编译环境初始化

作者: DarcyZhou | 来源:发表于2023-12-03 08:28 被阅读0次

    本文转载自:编译环境初始化-Android10.0编译系统(二)

    本文基于Android 10.0源码分析

    1.概述

      上一节针对Android编译系统做了一个笼统的说明,这一节针对编译环境初始化做一下详细的展示。

    2.编译环境初始化

    (1)初始化命令

    source build/envsetup.sh
    

    envsetup.sh主要做了下面几个事情:

    编译系统2-1.PNG

    (2)envsetup.sh构建代码

      ...
    
      validate_current_shell
    
      source_vendorsetup
    
      addcompletions
    

    2.1 hmm查看支持接口

      输入hmm可以看到envsetup支持的一些接口。

    命令 说明
    lunch lunch <product_name>-<build_variant>选择<product_name>作为要构建的产品,<build_variant>作为要构建的变体,并将这些选择存储在环境中,以便后续调用“m”等读取。
    tapas 交互方式:tapas [<App1> <App2> ...] [arm|x86|mips|arm64|x86_64|mips64][eng|userdebug|user]
    croot 将目录更改到树的顶部或其子目录
    m 编译整个源码,可以不用切换到根目录
    mm 编译当前目录下的源码,不包含他们的依赖模块
    mmm 编译指定目录下的所有模块,不包含他们的依赖模块 例如:mmm dir/:target1,target2.
    mma 编译当前目录下的源码,包含他们的依赖模块
    mmma 编译指定目录下的所模块,包含他们的依赖模块
    provision 具有所有必需分区的闪存设备。选项将传递给fastboot
    cgrep 对系统本地所有的C/C++ 文件执行grep命令
    ggrep 对系统本地所有的Gradle文件执行grep命令
    jgrep 对系统本地所有的Java文件执行grep命令
    resgrep 对系统本地所有的res目录下的xml文件执行grep命令
    mangrep 对系统本地所有的AndroidManifest.xml文件执行grep命令
    mgrep 对系统本地所有的Makefiles文件执行grep命令
    sepgrep 对系统本地所有的sepolicy文件执行grep命令
    sgrep 对系统本地所有的source文件执行grep命令
    godir 根据godir后的参数文件名在整个目录下查找,并且切换目录
    allmod 列出所有模块
    gomod 转到包含模块的目录
    pathmod 获取包含模块的目录
    refreshmod 刷新allmod/gomod的模块列表

    2.2 validate_current_shell

      确定当前的shell环境,建立shell命令。

    function validate_current_shell() {
        local current_sh="$(ps -o command -p $$)"
        case "$current_sh" in
            *bash*)
                function check_type() { type -t "$1"; }
                ;;
            *zsh*)
                function check_type() { type "$1"; }
                enable_zsh_completion ;;
            *)
                echo -e "WARNING: Only bash and zsh are supported.\nUse of other shell would lead to erroneous results."
                ;;
        esac
    }
    

    2.3 source_vendorsetup

      从device/vendor/product等目录遍历搜索vendorsetup.sh,并source进来。

    function source_vendorsetup() {
        allowed=
        for f in $(find -L device vendor product -maxdepth 4 -name 'allowed-vendorsetup_sh-files' 2>/dev/null | sort); do
            if [ -n "$allowed" ]; then
                echo "More than one 'allowed_vendorsetup_sh-files' file found, not including any vendorsetup.sh files:"
                echo "  $allowed"
                echo "  $f"
                return
            fi
            allowed="$f"
        done
    
        allowed_files=
        [ -n "$allowed" ] && allowed_files=$(cat "$allowed")
        for dir in device vendor product; do
            for f in $(test -d $dir && \
                find -L $dir -maxdepth 4 -name 'vendorsetup.sh' 2>/dev/null | sort); do
    
                if [[ -z "$allowed" || "$allowed_files" =~ $f ]]; then
                    echo "including $f"; . "$f"
                else
                    echo "ignoring $f, not in $allowed"
                fi
            done
        done
    }
    

    (1)简单的实例

    1. 建立一个目录:/vendor/ingres/build

    2. 创建一个vendorsetup.sh

    3. 写一个log:echo "vendor build test."

    4. 执行source build/envsetup.sh

    (2)source后打印

    including vendor/ingres/build/vendorsetup.sh
    vendor build test.
    

    2.4 addcompletions

    function addcompletions()
    {
        local T dir f
    
        # Keep us from trying to run in something that's neither bash nor zsh.
        # 检测shell版本字符串BASH_VERSION或ZSH_VERSION长度为0时,返回
        if [ -z "$BASH_VERSION" -a -z "$ZSH_VERSION" ]; then
            return
        fi
    
        # Keep us from trying to run in bash that's too old.
        # 检测bash主版本低于3时返回
        if [ -n "$BASH_VERSION" -a ${BASH_VERSINFO[0]} -lt 3 ]; then
            return
        fi
    
        # 指定bash文件目录并检查是否存在
        local completion_files=(
          system/core/adb/adb.bash
          system/core/fastboot/fastboot.bash
          tools/asuite/asuite.sh
        )
        # Completion can be disabled selectively to allow users to use non-standard completion.
        # e.g.
        # ENVSETUP_NO_COMPLETION=adb # -> disable adb completion
        # ENVSETUP_NO_COMPLETION=adb:bit # -> disable adb and bit completion
            #*.bash文件列表,并将这些*.bash文件包含进来
        for f in ${completion_files[*]}; do
            if [ -f "$f" ] && should_add_completion "$f"; then
                # 对*.bash文件执行'.'操作
                . $f
            fi
        done
    
        if should_add_completion bit ; then
            complete -C "bit --tab" bit
        fi
        if [ -z "$ZSH_VERSION" ]; then
            # Doesn't work in zsh.
            complete -o nospace -F _croot croot
        fi
        complete -F _lunch lunch   # _lunch命令提供lunch命令的补全操作
    
        complete -F _complete_android_module_names gomod
        complete -F _complete_android_module_names m
    }
    

    3.lunch aosp_arm_eng

    3.1 lunch说明

      环境变量初始化完成后,我们需要选择一个编译目标。lunch主要作用是根据用户输入或者选择的产品名来设置与具体产品相关的环境变量。如果你不知道想要编译的目标是什么,直接执行一个lunch命令,会列出所有的目标,直接回车,会默认使用aosp_arm-eng这个目标。

    编译系统2-2.png

    (1)执行命令:lunch 1, 可以看到配置的一些环境变量

    编译系统2-3.png

    (2)环境变量的含义

    名称 说明
    PLATFORM_VERSION_CODENAME=REL 表示平台版本的名称
    PLATFORM_VERSION=10 Android平台的版本号
    TARGET_PRODUCT=aosp_arm 所编译的产品名称
    TARGET_BUILD_VARIANT=userdebug 所编译产品的类型
    TARGET_BUILD_TYPE=release 编译的类型,debug和release
    TARGET_ARCH=arm 表示编译目标的CPU架构
    TARGET_ARCH_VARIANT=armv7-a-neon 表示编译目标的CPU架构版本
    TARGET_CPU_VARIANT=generic 表示编译目标的CPU代号
    HOST_ARCH=x86_64 表示编译平台的架构
    HOST_2ND_ARCH=x86 表示编译平台的第二CPU架构
    HOST_OS=linux 表示编译平台的操作系统
    HOST_OS_EXTRA=Linux-4.15.0-112-generic-x86_64-Ubuntu-16.04.6-LTS 编译系统之外的额外信息
    HOST_CROSS_OS=windows
    HOST_CROSS_ARCH=x86
    HOST_CROSS_2ND_ARCH=x86_64
    HOST_BUILD_TYPE=release 编译类型
    BUILD_ID=QQ1D.200205.002 BUILD_ID会出现在版本信息中,可以利用
    OUT_DIR=out 编译结果输出的路径

    (3)lunch aosp_arm-eng结束后,后创建一个out文件夹,生成一些中间文件

    编译系统2-4.png

    3.2 lunch()

      lunch命令用来设置TARGET_PRODUCT、TARGET_BUILD_VARIANT、TARGET_PLATFORM_VERSION、TARGET_BUILD_TYPE、TARGET_BUILD_APPS等环境变量。

      lunch操作流程如下:

    1. 获取lunch操作的参数,如果参数不为空,参数则为指定要编译的设备型号和编译类型;如果参数为空,会调用print_lunch_menu来显示Lunch菜单项,读取用户的输入,存入answer;

    2. 如果answer为空,即之前在lunch菜单用,用户只敲了一个回车。会将默认选项改为aosp_arm-eng,结果存入selection;

    3. 如果lunch操作得到的输入是数字,则将数字转换为LUNCH_MENU_CHOICES中的字符串,结果存入selection;

    4. 解析selection的值,得到product = aosp_arm 和variant = eng, 把他们分别保存到TARGET_PRODUCT 和 TARGET_BUILD_VARIANT 中;

    5. 根据前面的设置,调用build_build_var_cache来更新编译环境相关变量;

    6. export 编译选项TARGET_PRODUCT, TARGET_BUILD_VARIANT和TARGET_BUILD_TYPE三元组;

    7. 调用set_stuff_for_environment来设置其他环境变量,如PROMPT_COMMAND,编译toolchain和tools相关的路径等;

    8. 调用printconfig来输出当前的设置选项。

    function lunch()
    {
        local answer
        # 获取lunch操作的参数
        if [ "$1" ] ; then
            answer=$1
        else
            # lunch操作不带参数,则先显示lunch menu,然后读取用户输入
            print_lunch_menu
            echo -n "Which would you like? [aosp_arm-eng] "
            read answer
        fi
    
        local selection=
        # lunch操作得到的结果为空(例如用户直接在lunch要求输入时回车的情况)
        # 则将选项默认为"aosp_arm-eng"
        if [ -z "$answer" ]
        then
            selection=aosp_arm-eng
        # lunch操作得到的输入是数字,则将数字转换为LUNCH_MENU_CHOICES中的字符串
        elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")
        then
            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
            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和TARGET_BUILD_VARIANT
        TARGET_PRODUCT=$product \
        TARGET_BUILD_VARIANT=$variant \
        TARGET_PLATFORM_VERSION=$version \
    
        # 根据前面的设置,更新编译环境相关变量
        build_build_var_cache #参考[3.2.1]
        if [ $? -ne 0 ]
        then
            return 1
        fi
    
        # export编译选项TARGET_PRODUCT, TARGET_BUILD_VARIANT和TARGET_BUILD_TYPE三元组
        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 # 设置其他环境变量,如PROMPT_COMMAND,编译toolchain和tools相关的路径等
        printconfig        # 输出当前的设置选项
        destroy_build_var_cache
    }
    

    3.2.1 build_build_var_cache()

      根据前面的设置,更新编译环境相关变量,主要通过执行 "build/soong/soong_ui.bash --dumpvars-mode" 完成,最终执行的是 "./out/soog_ui --dumpvars-mode"。

    function build_build_var_cache()
    {
        local T=$(gettop)
        # Grep out the variable names from the script.
        cached_vars=(`cat $T/build/envsetup.sh | tr '()' '  ' | awk '{for(i=1;i<=NF;i++) if($i~/get_build_var/) print $(i+1)}' | sort -u | tr '\n' ' '`)
        cached_abs_vars=(`cat $T/build/envsetup.sh | tr '()' '  ' | awk '{for(i=1;i<=NF;i++) if($i~/get_abs_build_var/) print $(i+1)}' | sort -u | tr '\n' ' '`)
        # Call the build system to dump the "<val>=<value>" pairs as a shell script.
        build_dicts_script=`\builtin cd $T; build/soong/soong_ui.bash --dumpvars-mode \
                            --vars="${cached_vars[*]}" \
                            --abs-vars="${cached_abs_vars[*]}" \
                            --var-prefix=var_cache_ \
                            --abs-var-prefix=abs_var_cache_`
        local ret=$?
        if [ $ret -ne 0 ]
        then
            unset build_dicts_script
            return $ret
        fi
        # Execute the script to store the "<val>=<value>" pairs as shell variables.
        eval "$build_dicts_script"
        ret=$?
        unset build_dicts_script
        if [ $ret -ne 0 ]
        then
            return $ret
        fi
        BUILD_VAR_CACHE_READY="true"
    }
    

    soong_ui由build/soong/cmd/soong_ui/main.go编译生成。

    # build/soong/cmd/soong_ui/main.go
    func main() {
    ...
        if os.Args[1] == "--dumpvar-mode" {
            dumpVar(buildCtx, config, os.Args[2:])
        } else if os.Args[1] == "--dumpvars-mode" {
            dumpVars(buildCtx, config, os.Args[2:])
        } else {
            ...
        }
    ...
    }
    
    # build/soong/cmd/soong_ui/main.go
    func dumpVars(ctx build.Context, config build.Config, args []string) {
           varData, err := build.DumpMakeVars(ctx, config, nil, allVars)
    }
    

    最后调用到了ckati执行-f build/make/core/config.mk。

    # /build/soong/ui/build/dumpvars.go
    func dumpMakeVars(ctx Context, config Config, goals, vars []string, write_soong_vars bool) (map[string]string, error) {
        ctx.BeginTrace(metrics.RunKati, "dumpvars")
        defer ctx.EndTrace()
    
        cmd := Command(ctx, config, "dumpvars",
            config.PrebuiltBuildTool("ckati"),
            "-f", "build/make/core/config.mk",
            "--color_warnings",
            "--kati_stats",
            "dump-many-vars",
            "MAKECMDGOALS="+strings.Join(goals, " "))
        cmd.Environment.Set("CALLED_FROM_SETUP", "true")
        if write_soong_vars {
            cmd.Environment.Set("WRITE_SOONG_VARIABLES", "true")
        }
        cmd.Environment.Set("DUMP_MANY_VARS", strings.Join(vars, " "))
        cmd.Sandbox = dumpvarsSandbox
        output := bytes.Buffer{}
        cmd.Stdout = &output
        pipe, err := cmd.StderrPipe()
        if err != nil {
            ctx.Fatalln("Error getting output pipe for ckati:", err)
        }
        cmd.StartOrFatal()
        // TODO: error out when Stderr contains any content
        status.KatiReader(ctx.Status.StartTool(), pipe)
        cmd.WaitOrFatal()
    
        ret := make(map[string]string, len(vars))
        ...
    
        return ret, nil
    }
    

    下面我们单独研究一下config.mk。

    4.config.mk

    编译系统2-5.png

    config.mk首先加载了build/make/common中的core.mk、math.mk、strings.mk、json.mk用来配置一些shell环境、math函数、string和json的一些支持函数。最主要的操作还是加载build/make/core中的envsetup.mk和dumpvar.mk。

    # build/make/core/config.mk
    ...
    
    #配置两个目录的变量,供之后的mk使用
    BUILD_SYSTEM :=$= build/make/core
    BUILD_SYSTEM_COMMON :=$= build/make/common
    
    #加载core.mk, 只使用ANDROID_BUILD_SHELL来包装bash。
    include $(BUILD_SYSTEM_COMMON)/core.mk
    
    #设置make中使用的有效数学函数。
    include $(BUILD_SYSTEM_COMMON)/math.mk
    
    include $(BUILD_SYSTEM_COMMON)/strings.mk
    
    include $(BUILD_SYSTEM_COMMON)/json.mk
    
    # 避免硬件解码路径被覆盖的调用pathmap.mk建立硬解映射
    include $(BUILD_SYSTEM)/pathmap.mk
    
    # 允许项目定义自己的全局可用变量
    include $(BUILD_SYSTEM)/project_definitions.mk
    
    # ###############################################################
    # Build system internal files
    # ###############################################################
    # 构建系统内部文件(写Android.mk时会调用include头文件,也就是这些makefile文件)
    
    BUILD_COMBOS:= $(BUILD_SYSTEM)/combo
    
    CLEAR_VARS:= $(BUILD_SYSTEM)/clear_vars.mk
    BUILD_HOST_STATIC_LIBRARY:= $(BUILD_SYSTEM)/host_static_library.mk
    BUILD_HOST_SHARED_LIBRARY:= $(BUILD_SYSTEM)/host_shared_library.mk
    BUILD_STATIC_LIBRARY:= $(BUILD_SYSTEM)/static_library.mk
    
    ...
    
    BUILD_NOTICE_FILE := $(BUILD_SYSTEM)/notice_files.mk
    BUILD_HOST_DALVIK_JAVA_LIBRARY := $(BUILD_SYSTEM)/host_dalvik_java_library.mk
    BUILD_HOST_DALVIK_STATIC_JAVA_LIBRARY := $(BUILD_SYSTEM)/host_dalvik_static_java_library.mk
    
    BUILD_HOST_TEST_CONFIG := $(BUILD_SYSTEM)/host_test_config.mk
    BUILD_TARGET_TEST_CONFIG := $(BUILD_SYSTEM)/target_test_config.mk
    
    #定义大多数全局变量。这些是特定于用户的构建配置的。
    include $(BUILD_SYSTEM)/envsetup.mk
    
    #构建系统为在哪里找到内核公开了几个变量
    #(1)TARGET_DEVICE_KERNEL_HEADERS是为当前正在构建的设备自动创建的。
    #它被设置为$(TARGET_DEVICE_DIR)/kernel headers,
    #例如DEVICE/samsung/tuna/kernel headers。此目录不是由任何人显式设置的,生成系统总是添加此子目录。
    TARGET_DEVICE_KERNEL_HEADERS := $(strip $(wildcard $(TARGET_DEVICE_DIR)/kernel-headers))
    
    #(2)TARGET_BOARD_KERNEL_HEADERS由BoardConfig.mk允许包含其他目录的文件。
    #如果有一些常见的地方为一组设备保留了一些报头,那么这很有用。
    #例如,device/<vendor>/common/kernel头可以包含一些<vendor>设备的头。
    TARGET_BOARD_KERNEL_HEADERS := $(strip $(wildcard $(TARGET_BOARD_KERNEL_HEADERS)))
    TARGET_BOARD_KERNEL_HEADERS := $(patsubst %/,%,$(TARGET_BOARD_KERNEL_HEADERS))
    $(call validate-kernel-headers,$(TARGET_BOARD_KERNEL_HEADERS))
    
    #(3)TARGET_PRODUCT_KERNEL_头由产品继承图生成。
    #这允许体系结构产品为使用该体系结构的设备提供报头。
    TARGET_PRODUCT_KERNEL_HEADERS := $(strip $(wildcard $(PRODUCT_VENDOR_KERNEL_HEADERS)))
    TARGET_PRODUCT_KERNEL_HEADERS := $(patsubst %/,%,$(TARGET_PRODUCT_KERNEL_HEADERS))
    $(call validate-kernel-headers,$(TARGET_PRODUCT_KERNEL_HEADERS))
    
    # 选择一个Java编译器
    include $(BUILD_SYSTEM)/combo/javac.mk
    
    # A list of SEPolicy versions, besides PLATFORM_SEPOLICY_VERSION, that the framework supports.
    #框架支持的SEPolicy版本列表,除了PLATFORM_SEPOLICY_VERSION
    PLATFORM_SEPOLICY_COMPAT_VERSIONS := \
        26.0 \
        27.0 \
        28.0 \
    
    ifeq ($(CALLED_FROM_SETUP),true)
    include $(BUILD_SYSTEM)/ninja_config.mk
    include $(BUILD_SYSTEM)/soong_config.mk
    endif
    
    #加载dumpvar.mk,用来生成make目标
    include $(BUILD_SYSTEM)/dumpvar.mk
    

    4.1 build/make/core/envsetup.mk

      envsetup.mk主要加载product_config.mk和board_config.mk,用来得到TARGET_DEVICE和其他变量。

    ...
    #设置host和target编译链相关的变量
    include $(BUILD_SYSTEM)/combo/select.mk
    #(1)阅读产品规格,这样我们就可以得到TARGET_DEVICE和其他变量,我们需要找到输出文件
    include $(BUILD_SYSTEM)/product_config.mk
    
    include $(BUILD_SYSTEM)/board_config.mk
    ...
    

    4.2 build/make/core/product_config.mk

      阅读产品规格,这样我们就可以得到TARGET_DEVICE和其他变量,我们需要找到输出文件。

    ...
    # ---------------------------------------------------------------
    # Include the product definitions.
    # We need to do this to translate TARGET_PRODUCT into its
    # underlying TARGET_DEVICE before we start defining any rules.
    #
    include $(BUILD_SYSTEM)/node_fns.mk
    include $(BUILD_SYSTEM)/product.mk
    include $(BUILD_SYSTEM)/device.mk
    
    ...
    
    #############################################################################
    # Sanity check and assign default values
    TARGET_DEVICE := $(PRODUCT_DEVICE)
    ...
    

    4.3 build/make/core/board_config.mk

      板级可以在$(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)下定义,也可以在vendor/*/$(TARGET_DEVICE)下定义。 在这两个地方搜索,但要确保只存在一个。真正的板级应始终与OEM vendor相关联。

    ...
    # Boards may be defined under $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)
    # or under vendor/*/$(TARGET_DEVICE).  Search in both places, but
    # make sure only one exists.
    # Real boards should always be associated with an OEM vendor.
    ifdef TARGET_DEVICE_DIR
      ifneq ($(origin TARGET_DEVICE_DIR),command line)
        $(error TARGET_DEVICE_DIR may not be set manually)
      endif
      board_config_mk := $(TARGET_DEVICE_DIR)/BoardConfig.mk
    else
      board_config_mk := \
        $(strip $(sort $(wildcard \
          $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)/BoardConfig.mk \
          $(shell test -d device && find -L device -maxdepth 4 -path '*/$(TARGET_DEVICE)/BoardConfig.mk') \
          $(shell test -d vendor && find -L 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
      TARGET_DEVICE_DIR := $(patsubst %/,%,$(dir $(board_config_mk)))
      .KATI_READONLY := TARGET_DEVICE_DIR
    endif
    include $(board_config_mk)
    ...
    

    5.总结

      至此,envsetup.sh 和lunch()的初始化流程基本上理清了,主要就是加载了环境变量,并选择了编译目标,后面只要执行一下make就能够进行启动编译,下一节让我们一起看看敲下make后到底发生了什么。

    相关文章

      网友评论

          本文标题:Android 编译系统--02:编译环境初始化

          本文链接:https://www.haomeiwen.com/subject/ctyxidtx.html