Android脚本envsetup.sh逐行分析

编译Android的第一步是执行source build/envsetup.sh设置编译相关的环境,里面到底都做了什么呢?我们来看一看。

envsetup.sh的代码较长,共有1632行,但其内容较简单,只做了两件事:
1. 函数定义

定义可以在命令行直接调用的函数,方便编译和调试操作。一共定义了四种类型共75个可以在命令行调用的函数;
2. 生成编译配置列表

脚本通过查找并执行{device, vendor, product}目录下的vendorsetup.sh文件,搜集所有可能的编译配置生成配置列表,供编译时选择;

接下来基于这两个主题对文件envsetup.sh进行详细分析。

1. 函数定义

文件95%以上的代码都是在定义各种各样的函数,并将这些函数导入到shell环境中,使其可以直接在命令行调用。
这些函数总共分为4类,包括:
- 辅助函数类
- 编译环境设置类
- 代码搜索类
- adb调试类

通过以下命令获得envsetup.sh中所定义的函数总数:

ygu@guyongqiangx:src$ sed -n "/^[[:blank:]]*function /s/function \([a-z_]\w*\).*/\1/p" < build/envsetup.sh | wc -l
75

以下对所有定义的函数逐个进行分析。

1.1 辅助函数类

hmm

hmm函数输出envsetup.sh的帮助说明,提示执行. build/envsetup.sh后有哪些可以调用的操作。以前看到别人提过android编译的特殊命令,包括mmmmmm,这3个命令的差别我每次都要去问度娘,好了,现在直接运行hmm,什么都知道了。

function hmm() {
cat <<EOF
Invoke ". build/envsetup.sh" from your shell to add the following functions to your environment:
- lunch:     lunch -
- tapas:     tapas [  ...] [arm|x86|mips|armv5|arm64|x86_64|mips64] [eng|userdebug|user]
- croot:     Changes directory to the top of the tree.
- m:         Makes from the top of the tree.
- mm:        Builds all of the modules in the current directory, but not their dependencies.
- mmm:       Builds all of the modules in the supplied directories, but not their dependencies.
             To limit the modules being built use the syntax: mmm dir/:target1,target2.
- mma:       Builds all of the modules in the current directory, and their dependencies.
- mmma:      Builds all of the modules in the supplied directories, and their dependencies.
- provision: Flash device with all required partitions. Options will be passed on to fastboot.
- cgrep:     Greps on all local C/C++ files.
- ggrep:     Greps on all local Gradle files.
- jgrep:     Greps on all local Java files.
- resgrep:   Greps on all local res/*.xml files.
- mangrep:   Greps on all local AndroidManifest.xml files.
- mgrep:     Greps on all local Makefiles files.
- sepgrep:   Greps on all local sepolicy files.
- sgrep:     Greps on all local source files.
- godir:     Go to the directory containing a file.

Environment options:
- SANITIZE_HOST: Set to 'true' to use ASAN for all host modules. Note that
                 ASAN_OPTIONS=detect_leaks=0 will be set by default until the
                 build is leak-check clean.

Look at the source to view more functions. The complete list is:
EOF
    # 查找编译环境根目录
    T=$(gettop)
    local A
    A=""
    # 读取build/envsetup.sh文件,并通过sed操作获取其中定义的函数,并进行排序输出,存放到变量$A中
    for i in `cat $T/build/envsetup.sh | sed -n "/^[[:blank:]]*function /s/function \([a-z_]*\).*/\1/p" | sort | uniq`; do
      A="$A $i"
    done
    echo $A
}

直接在命令行调用hmm,先显示一堆help信息,随后列举本文件中所有定义的函数。

>

函数中命令”sed -n "/^[[:blank:]]*function /s/function \([a-z_]*\).*/\1/p"“用于生成函数列表。

但其操作有一个bug,用于匹配函数的正则表达式”function \([a-z_]*\).*“会漏掉函数”is64bit“。

将匹配模式从”function \([a-z_]*\).*“修改为”function \([a-z_]\w*\).*“可以匹配文件中的所有函数。

gettop

gettop函数从指定的$TOP目录或当前目录开始查找build/core/envsetup.mk文件,并将能找到该文件的目录返回给调用函数作为操作的根目录,详细注释如下:

function gettop
{
    local TOPFILE=build/core/envsetup.mk
    # 如果编译环境已经设置了$TOP,就检查$TOP/build/core/envsetup.mk文件是否存在
    if [ -n "$TOP" -a -f "$TOP/$TOPFILE" ] ; then
        # The following circumlocution ensures we remove symlinks from TOP.
        # 转到$TOP目录,通过命令`/bin/pwd`将$TOP目录指向的真实路径存放到PWD中
        (cd $TOP; PWD= /bin/pwd)
    else
        # 如果当前路径下能够找到build/core/envsetup.mk文件,
        # 则将当前目录的真实路径存放到PWD中
        if [ -f $TOPFILE ] ; then
            # The following circumlocution (repeated below as well) ensures
            # that we record the true directory name and not one that is
            # faked up with symlink names.
            PWD= /bin/pwd
        else
            # 如果当前目录下无法找到build/core/envsetup.mk文件,
            # 则不断返回到外层目录查找,直到到达根目录/为止

            # 保存查找操作前的路径
            local HERE=$PWD
            T=
            while [ \( ! \( -f $TOPFILE \) \) -a \( $PWD != "/" \) ]; do
                # 转到外层目录
                \cd ..
                # 将当前路径保存到T中
                T=`PWD= /bin/pwd -P`
            done
            # 查找完后恢复操作前的路径
            \cd $HERE
            # 如果目录T包含build/core/envsetup.mk,说明是T是编译的根目录
            if [ -f "$T/$TOPFILE" ]; then
                # 输出$T中保存的路径作为gettop的返回值
                echo $T
            fi
        fi
    fi
}

croot

croot命令切换到当前编译环境的根目录。

function croot()
{
    # 查找当前编译树的根目录
    T=$(gettop)
    if [ "$T" ]; then
        # 切换到编译环境的根目录
        \cd $(gettop)
    else
        echo "Couldn't locate the top of the tree.  Try setting TOP."
    fi
}

cproj

cproj命令用于切换到当前模块的编译目录下(含有Android.mk)

function cproj()
{
    TOPFILE=build/core/envsetup.mk
    # 保存操作前的路径
    local HERE=$PWD
    T=
    # 当前目录下build/core/envsetup.mk不存在(即当前目录不是编译根目录),
    # 并且当前目录不是系统根目录
    while [ \( ! \( -f $TOPFILE \) \) -a \( $PWD != "/" \) ]; do
        T=$PWD
        # 当前$T目录下存在文件Android.mk
        if [ -f "$T/Android.mk" ]; then
            # 转到$T目录
            \cd $T
            return
        fi
        # 转到外层目录
        \cd ..
    done
    # 恢复操作前的路径
    \cd $HERE
    echo "can't find Android.mk"
}

getprebuilt

getprebuilt返回ANDROID_PREBUILTS的路径

命令行直接调用getprebuilt

ygu@guyongqiangx:src$ getprebuilt
/android/src/prebuilt/linux-x86

实际上src目录下并不存在路径prebuilt/linux-x86

function getprebuilt
{
    # 通过函数get_abs_build_var返回ANDROID_PREBUILTS设置
    get_abs_build_var ANDROID_PREBUILTS
}

setpaths

setpaths

function setpaths()
{
    # 如果找不到编译的根目录,则退出设置
    T=$(gettop)
    if [ ! "$T" ]; then
        echo "Couldn't locate the top of the tree.  Try setting TOP."
        return
    fi

    ##################################################################
    #                                                                #
    #              Read me before you modify this code               #
    #                                                                #
    #   This function sets ANDROID_BUILD_PATHS to what it is adding  #
    #   to PATH, and the next time it is run, it removes that from   #
    #   PATH.  This is required so lunch can be run more than once   #
    #   and still have working paths.                                #
    #                                                                #
    ##################################################################

    # Note: on windows/cygwin, ANDROID_BUILD_PATHS will contain spaces
    # due to "C:\Program Files" being in the path.

    # 将$ANDROID_BUILD_PATHS和$ANDROID_PRE_BUILD_PATHS指定的路径添加到$PATH中,并export
    # out with the old
    if [ -n "$ANDROID_BUILD_PATHS" ] ; then
        export PATH=${PATH/$ANDROID_BUILD_PATHS/}
    fi
    if [ -n "$ANDROID_PRE_BUILD_PATHS" ] ; then
        export PATH=${PATH/$ANDROID_PRE_BUILD_PATHS/}
        # strip leading ':', if any
        # 说实话,我没搞懂这个是什么意思,求大神指点下
        export PATH=${PATH/:%/}
    fi

    # and in with the new
    # 设置ANDROID_PREBUILT和ANDROID_GCC_PREBUILTS相应的路径
    prebuiltdir=$(getprebuilt)
    gccprebuiltdir=$(get_abs_build_var ANDROID_GCC_PREBUILTS)

    # defined in core/config.mk
    # 设置TARGET_GCC_VERSION和2ND_TARGET_GCC_VERSION
    targetgccversion=$(get_build_var TARGET_GCC_VERSION)
    targetgccversion2=$(get_build_var 2ND_TARGET_GCC_VERSION)
    export TARGET_GCC_VERSION=$targetgccversion

    # The gcc toolchain does not exists for windows/cygwin. In this case, do not reference it.
    export ANDROID_TOOLCHAIN=
    export ANDROID_TOOLCHAIN_2ND_ARCH=
    # 根据get_build_var返回的TARGET_ARCH分别设置{x86, x86_64, arm, arm64,mips|mips64}体系结构对应的toolchaindir名称
    local ARCH=$(get_build_var TARGET_ARCH)
    case $ARCH in
        x86) toolchaindir=x86/x86_64-linux-android-$targetgccversion/bin
            ;;
        x86_64) toolchaindir=x86/x86_64-linux-android-$targetgccversion/bin
            ;;
        arm) toolchaindir=arm/arm-linux-androideabi-$targetgccversion/bin
            ;;
        arm64) toolchaindir=aarch64/aarch64-linux-android-$targetgccversion/bin;
               toolchaindir2=arm/arm-linux-androideabi-$targetgccversion2/bin
            ;;
        mips|mips64) toolchaindir=mips/mips64el-linux-android-$targetgccversion/bin
            ;;
        *)
            echo "Can't find toolchain for unknown architecture: $ARCH"
            toolchaindir=xxxxxxxxx
            ;;
    esac

    # 设置ANDROID_TOOLCHAIN和ANDROID_TOOLCHAIN_2ND_ARCH环境变量,用于指示toolchain路径
    if [ -d "$gccprebuiltdir/$toolchaindir" ]; then
        export ANDROID_TOOLCHAIN=$gccprebuiltdir/$toolchaindir
    fi

    if [ -d "$gccprebuiltdir/$toolchaindir2" ]; then
        export ANDROID_TOOLCHAIN_2ND_ARCH=$gccprebuiltdir/$toolchaindir2
    fi

    # 设置Android编译相关的路径变量ANDROID_BUILD_PATHS
    export ANDROID_DEV_SCRIPTS=$T/development/scripts:$T/prebuilts/devtools/tools:$T/external/selinux/prebuilts/bin
    export ANDROID_BUILD_PATHS=$(get_build_var ANDROID_BUILD_PATHS):$ANDROID_TOOLCHAIN:$ANDROID_TOOLCHAIN_2ND_ARCH:$ANDROID_DEV_SCRIPTS:

    # If prebuilts/android-emulator// exists, prepend it to our PATH
    # to ensure that the corresponding 'emulator' binaries are used.
    # 基于不同的系统设置ANDROID_EMULATOR_PREBUILTS变量
    case $(uname -s) in
        Darwin)
            ANDROID_EMULATOR_PREBUILTS=$T/prebuilts/android-emulator/darwin-x86_64
            ;;
        Linux)
            ANDROID_EMULATOR_PREBUILTS=$T/prebuilts/android-emulator/linux-x86_64
            ;;
        *)
            ANDROID_EMULATOR_PREBUILTS=
            ;;
    esac
    # 如果ANDROID_EMULATOR_PREBUILTS变量指定的目录存在,则将其添加到Android编译相关的路径变量ANDROID_BUILD_PATHS中
    if [ -n "$ANDROID_EMULATOR_PREBUILTS" -a -d "$ANDROID_EMULATOR_PREBUILTS" ]; then
        ANDROID_BUILD_PATHS=$ANDROID_BUILD_PATHS$ANDROID_EMULATOR_PREBUILTS:
        export ANDROID_EMULATOR_PREBUILTS
    fi

    # 将Android编译相关的路径ANDROID_BUILD_PATHS添加到PATH中
    export PATH=$ANDROID_BUILD_PATHS$PATH
    # 将development/python-packages添加到python运行的查找路径中
    export PYTHONPATH=$T/development/python-packages:$PYTHONPATH

    unset ANDROID_JAVA_TOOLCHAIN
    unset ANDROID_PRE_BUILD_PATHS
    # 如果设置了$JAVA_HOME,则将其加入到PATH变量中
    if [ -n "$JAVA_HOME" ]; then
        export ANDROID_JAVA_TOOLCHAIN=$JAVA_HOME/bin
        export ANDROID_PRE_BUILD_PATHS=$ANDROID_JAVA_TOOLCHAIN:
        export PATH=$ANDROID_PRE_BUILD_PATHS$PATH
    fi

    # 设置ANDROID_PRODUCT_OUT和OUT环境变量
    unset ANDROID_PRODUCT_OUT
    export ANDROID_PRODUCT_OUT=$(get_abs_build_var PRODUCT_OUT)
    export OUT=$ANDROID_PRODUCT_OUT

    # 设置HOST_OUT环境变量
    unset ANDROID_HOST_OUT
    export ANDROID_HOST_OUT=$(get_abs_build_var HOST_OUT)

    # needed for building linux on MacOS
    # TODO: fix the path
    #export HOST_EXTRACFLAGS="-I "$T/system/kernel_headers/host_include
}

set_java_home

set_java_home

# Force JAVA_HOME to point to java 1.7/1.8 if it isn't already set.
function set_java_home() {
    # Clear the existing JAVA_HOME value if we set it ourselves, so that
    # we can reset it later, depending on the version of java the build
    # system needs.
    #
    # If we don't do this, the JAVA_HOME value set by the first call to
    # build/envsetup.sh will persist forever.
    #
    # 如果已经设置$ANDROID_SET_JAVA_HOME变量,则清空JAVA_HOME
    if [ -n "$ANDROID_SET_JAVA_HOME" ]; then
      export JAVA_HOME=""
    fi

    # 如果JAVA_HOME没有设置
    if [ ! "$JAVA_HOME" ]; then
      # 如果已经设置了LEGACY_USE_JAVA7,说明强制指定使用JDK7
      if [ -n "$LEGACY_USE_JAVA7" ]; then
        echo Warning: Support for JDK 7 will be dropped. Switch to JDK 8.
        # 根据编译系统设置JDK7的JAVA_HOME
        case `uname -s` in
            Darwin) # Mac
                export JAVA_HOME=$(/usr/libexec/java_home -v 1.7)
                ;;
            *)
                # 默认设置为java-7-openjdk-amd64的路径
                export JAVA_HOME=/usr/lib/jvm/java-7-openjdk-amd64
                ;;
        esac
      # 如果没有设置LEGACY_USE_JAVA7,则使用JDK 8
      else
        # 根据系统设置JDK8的JAVA_HOME
        case `uname -s` in
            Darwin)
                export JAVA_HOME=$(/usr/libexec/java_home -v 1.8)
                ;;
            *)
                export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
                ;;
        esac
      fi

      # Keep track of the fact that we set JAVA_HOME ourselves, so that
      # we can change it on the next envsetup.sh, if required.
      #
      # 设置ANDROID_SET_JAVA_HOME作为已经设置了JAVA_HOME的标识
      export ANDROID_SET_JAVA_HOME=true
    fi
}

printconfig

printconfig输出当前的编译配置,如:

ygu@guyongqiangx:src$ printconfig
============================================
PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=7.1.1
TARGET_PRODUCT=bcm7252ssffdr4
TARGET_BUILD_VARIANT=userdebug
TARGET_BUILD_TYPE=release
TARGET_BUILD_APPS=
TARGET_ARCH=arm
TARGET_ARCH_VARIANT=armv7-a-neon
TARGET_CPU_VARIANT=cortex-a15
TARGET_2ND_ARCH=
TARGET_2ND_ARCH_VARIANT=
TARGET_2ND_CPU_VARIANT=
HOST_ARCH=x86_64
HOST_2ND_ARCH=x86
HOST_OS=linux
HOST_OS_EXTRA=Linux-4.2.0-42-generic-x86_64-with-Ubuntu-14.04-trusty
HOST_CROSS_OS=windows
HOST_CROSS_ARCH=x86
HOST_CROSS_2ND_ARCH=x86_64
HOST_BUILD_TYPE=release
BUILD_ID=NMF27D
OUT_DIR=out
============================================
function printconfig()
{
    # 检查编译根目录
    T=$(gettop)
    if [ ! "$T" ]; then
        echo "Couldn't locate the top of the tree.  Try setting TOP." >&2
        return
    fi
    # 调用get_build_var report_config来打印输出当前的编译配置
    get_build_var report_config
}

getdriver

getdriver在定义了 WITHSTATICANALYZER WITH_STATIC_ANALYZER没有定义,所以getdriver调用返回空

# Return driver for "make", if any (eg. static analyzer)
function getdriver()
{
    local T="$1"
    # 检查$WITH_STATIC_ANALYZER,如果设置为0,就将其内容清空
    test "$WITH_STATIC_ANALYZER" = "0" && unset WITH_STATIC_ANALYZER
    if [ -n "$WITH_STATIC_ANALYZER" ]; then
        # 好吧,将这一堆字符串返回,我也不知道干什么用,求大神指导下
        echo "\
$T/prebuilts/misc/linux-x86/analyzer/tools/scan-build/scan-build \
--use-analyzer $T/prebuilts/misc/linux-x86/analyzer/bin/analyzer \
--status-bugs \
--top=$T"
    fi
}

gettargetarch

gettargetarch函数返回编译目标系统的CPU架构,如arm

function gettargetarch
{
    # 通过get_build_var TARGET_ARCH返回cpu arch
    get_build_var TARGET_ARCH
}

godir

godir函数的用法为”Usage: godir ”,在编译路径下搜索匹配模式的目录,然后跳转到此目录。

ygu@guyongqiangx:src$ godir fugu
   [1] ./device/asus/fugu
   [2] ./device/asus/fugu-kernel
   [3] ./device/asus/fugu/bluetooth
   [4] ./device/asus/fugu/dumpstate
   [5] ./device/asus/fugu/factory-images
   [6] ./device/asus/fugu/kernel-headers/drm
   [7] ./device/asus/fugu/kernel-headers/drm/ttm
   [8] ./device/asus/fugu/kernel-headers/linux
   [9] ./device/asus/fugu/kernel-headers/linux/sound
   ...
Select one:

在上面的提示后输入1,命令行跳转到./device/asus/fugu目录下,实现了跟函数名一致的”go dir”的操作。

function godir () {
    # 如果godir的第一个参数$1为空,显示godir的用法
    if [[ -z "$1" ]]; then
        echo "Usage: godir "
        return
    fi
    # 变量T存放编译根目录路径
    T=$(gettop)
    # 根据是否设置$OUT_DIR,设置$FILELIST
    if [ ! "$OUT_DIR" = "" ]; then
        # 创建$OUT_DIR目录
        mkdir -p $OUT_DIR
        FILELIST=$OUT_DIR/filelist
    else
        FILELIST=$T/filelist
    fi
    # 如果$FILELIST文件不存在,则将find命令结果输出到filelist中
    if [[ ! -f $FILELIST ]]; then
        echo -n "Creating index..."
        # 
        # 使用find命令从编译的根目录下查找文件"-type f"(目录out和.repo除外),并将结果输出到$FILELIST文件中
        # out目录和.repo目录的排除选项分别为:
        # - "-wholename ./out -prune"
        # - "-wholename ./.repo -prune"
        # 关于find的"-wholename pattern"选项,其行为跟"-path pattern"基本一样,具体可以查看find的帮助信息
        # 因此filelist文件保存了除out和.repo目录外其余目录的完整文件名
        (\cd $T; find . -wholename ./out -prune -o -wholename ./.repo -prune -o -type f > $FILELIST)
        echo " Done"
        echo ""
    fi
    local lines
    # 根据传入godir的参数,在filelist中搜索,并用sed处理后将结果存放在lines中
    # 操作"sed -e 's/\/[^/]*$//'"仅保留完整文件名的路径部分
    lines=($(\grep "$1" $FILELIST | sed -e 's/\/[^/]*$//' | sort | uniq))
    # 检查lines中的结果,即filelist通过grep和sed操作后,是否还有匹配的目录
    if [[ ${#lines[@]} = 0 ]]; then
        echo "Not found"
        return
    fi
    local pathname
    local choice
    # 如果lines的结果多于1行,则对各行进行编号并输出
    if [[ ${#lines[@]} > 1 ]]; then
        while [[ -z "$pathname" ]]; do
            # 从1开始编号
            local index=1
            local line
            for line in ${lines[@]}; do
                # 对每行以类似以下的格式进行输出:
                # $ godir fugu
                #   [1] ./device/asus/fugu
                #   [2] ./device/asus/fugu-kernel
                #   [3] ./device/asus/fugu/bluetooth
                #   [4] ./device/asus/fugu/dumpstate
                # 
                printf "%6s %s\n" "[$index]" $line
                # 序号自增
                index=$(($index + 1))
            done
            echo
            # 提示输入序号
            echo -n "Select one: "
            unset choice
            # 读取输入序号
            read choice
            if [[ $choice -gt ${#lines[@]} || $choice -lt 1 ]]; then
                echo "Invalid choice"
                continue
            fi
            # 取得输入序号对应的目录
            pathname=${lines[$(($choice-1))]}
        done
    else
        # 如果符合匹配的路径只有一条,则直接将匹配的路径存放到pathname中
        pathname=${lines[0]}
    fi
    # 转到选择的目标路径
    \cd $T/$pathname
}

pez

pez函数的参数”$@”是一条可执行命令,通过执行结果来决定打印FAILUE和SUCCESS的颜色,失败打印红色的FAILURE,成功打印绿色的SUCCESS

# Print colored exit condition
function pez {
    # 执行参数命令#@
    "$@"
    # 获取$@命令的返回值
    local retval=$?
    # 检查返回值是否为0
    if [ $retval -ne 0 ]
    then
        # 输出红色的FAILURE
        echo $'\E'"[0;31mFAILURE\e[00m"
    else
        # 输出绿色的SUCCESS
        echo $'\E'"[0;32mSUCCESS\e[00m"
    fi
    # 将$@命令的执行结果返回给外层调用
    return $retval
}

findmakefile

findmakefile查找当前模块的Android.mk并输出文件的详细路径
以下是在目录cd device/asus/fugu/kernel-headers/linux/sound/内执行findmakefile的例子:

ygu@guyongqiangx:src$ cd device/asus/fugu/kernel-headers/linux/sound/
ygu@guyongqiangx:src/device/asus/fugu/kernel-headers/linux/sound$ findmakefile
/android/src/device/asus/fugu/Android.mk
ygu@guyongqiangx:src/device/asus/fugu/kernel-headers/linux/sound$ 

显然,会在当前目录下逐层往外查找Android.mk,找到后显示Android.mk的完整路径,显示完路径后仍然在当前目录下。

function findmakefile()
{
    TOPFILE=build/core/envsetup.mk
    local HERE=$PWD
    T=
    # 检查是否已经在编译的根目录或系统根目录了
    while [ \( ! \( -f $TOPFILE \) \) -a \( $PWD != "/" \) ]; do
        T=`PWD= /bin/pwd`
        # 如果目录下存在Android.mk
        if [ -f "$T/Android.mk" ]; then
            # 输出Android.mk的完整路径
            echo $T/Android.mk
            # 切换回当前执行findmakefile的目录
            \cd $HERE
            return
        fi
        # 转到上级目录
        \cd ..
    done
    # 切换回当前执行findmakefile的目录
    \cd $HERE
}

settitle

settitle根据板子设置,更细PROMPT_COMMAND设置
这里的PROMPT_COMMAND设置看起来好像没有什么用。
因此设置PROMPT_COMMAND有什么目的,我完全没弄清楚,求指点。
以下是关于PROMPT_COMMAND的两个链接:
http://www.tldp.org/HOWTO/Bash-Prompt-HOWTO/x264.html

https://unix.stackexchange.com/questions/27692/in-bash-why-is-prompt-command-set-to-something-invisible

function settitle()
{
    # 如果STAY_OFF_MY_LAWN为"",则根据arch, product, variant, apps更新环境的PROMPT_COMMAND
    if [ "$STAY_OFF_MY_LAWN" = "" ]; then
        local arch=$(gettargetarch)
        local product=$TARGET_PRODUCT
        local variant=$TARGET_BUILD_VARIANT
        local apps=$TARGET_BUILD_APPS
        if [ -z "$apps" ]; then
            export PROMPT_COMMAND="echo -ne \"\033]0;[${arch}-${product}-${variant}] ${USER}@${HOSTNAME}: ${PWD}\007\""
        else
            export PROMPT_COMMAND="echo -ne \"\033]0;[$arch $apps $variant] ${USER}@${HOSTNAME}: ${PWD}\007\""
        fi
    fi
}

set_sequence_number

set_sequence_number将BUILD_ENV_SEQUENCE_NUMBER设置为10
说实话,我也不知道这个BUILD_ENV_SEQUENCE_NUMBER是做什么用的

function set_sequence_number()
{
    export BUILD_ENV_SEQUENCE_NUMBER=10
}

set_stuff_for_environment

set_stuff_for_environment 调用前面定义的一系列函数设置title, java home, path, sequence number, android build top dir等环境变量

function set_stuff_for_environment()
{
    # 设置PROMPT_COMMAND
    settitle
    # 设置JAVA_HOME
    set_java_home
    # 将Android编译相关的toolchain和tools路径导入到PATH变量
    setpaths
    # 设置 BUILD_ENV_SEQUENCE_NUMBER
    set_sequence_number

    # 设置ANDROID_BUILD_TOP为编译根目录路径
    export ANDROID_BUILD_TOP=$(gettop)
    # With this environment variable new GCC can apply colors to warnings/errors
    # 通过注释看起来是设置GCC工具在error, warning, note, caret, lucus, quote情形下的颜色
    export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'
    # 看起来这个选项是给ASAN使用的,参考链接:http://www.freebuf.com/news/83811.html
    export ASAN_OPTIONS=detect_leaks=0
}

addcompletions

addcompletions命令将sdk/bash_completion目录下所有的*.bash文件通过’.’操作导入到当前环境中来

function addcompletions()
{
    local T dir f

    # Keep us from trying to run in something that isn't bash.
    # 检测shell版本字符串BASH_VERSION长度为0时,返回
    if [ -z "${BASH_VERSION}" ]; then
        return
    fi

    # Keep us from trying to run in bash that's too old.
    # 检测bash主版本低于3时返回
    if [ ${BASH_VERSINFO[0]} -lt 3 ]; then
        return
    fi

    # 指定dir目录并检查是否存在
    dir="sdk/bash_completion"
    if [ -d ${dir} ]; then
        # 获取sdk/bash_completion下的*.bash文件列表,并将这些*.bash文件包含进来
        for f in `/bin/ls ${dir}/[a-z]*.bash 2> /dev/null`; do
            echo "including $f"
            # 对*.bash文件执行'.'操作
            . $f
        done
    fi
}

1.2 编译环境设置类

build_build_var_cache

build_build_var_cache命令用于创建var_cache_xxx='yyy'abs_var_cache_xxx='yyy'的键值对,用于存储环境变量。
主要的键值对包括:
- var_cache_2ND_TARGET_GCC_VERSION
- var_cache_ANDROID_BUILD_PATHS
- var_cache_TARGET_ARCH
- var_cache_TARGET_DEVICE
- var_cache_TARGET_GCC_VERSION
- var_cache_print
- var_cache_report_config
- abs_var_cache_ANDROID_GCC_PREBUILTS
- abs_var_cache_ANDROID_PREBUILTS
- abs_var_cache_HOST_OUT
- abs_var_cache_PRODUCT_OUT
- abs_var_cache_print
键值对分析:
- var_cache_printabs_var_cache_print是因为提取脚本分析错误的原因,误以为’print’也是get_build_var/get_build_var需要提取的参数
- var_cache_report_config存放了编译配置完整的的report

# Get all the build variables needed by this script in a single call to the build system.
function build_build_var_cache()
{
    # 获取编译根目录
    T=$(gettop)
    # Grep out the variable names from the script.
    # 
    # 我读书少,不太了解awk的用法,看了以下操作,确实开了眼界,值得借鉴
    # 命令直接分析脚本提取参数,确保刚好是需要的参数,没有多一个,也没有少一个,哈哈
    #
    # 搜索envsetup.sh文件中所有调用get_build_var的地方,提取其调用参数,并存放到cached_vars中
    # 我尝试在命令行直接执行操作,其得到的结果如下:
    # ygu@guyongqiangx:src$ cat build/envsetup.sh | tr '()' '  ' | awk '{for(i=1;i<=NF;i++) if($i~/get_build_var/) print $(i+1)}'
    # print
    # 
    # TARGET_DEVICE
    # TARGET_GCC_VERSION
    # 2ND_TARGET_GCC_VERSION
    # TARGET_ARCH
    # ANDROID_BUILD_PATHS
    # report_config
    # TARGET_ARCH
    # 这里分析代码时把设置cached_vars的这行也包含在里面了,所以可以看到结果的第一行中有 print
    #
    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' ' '`

    # 搜索envsetup.sh文件中所有调用get_abs_build_var的地方,提取其调用参数,并存放到cached_abs_vars中
    # ygu@guyongqiangx:src$ cat build/envsetup.sh | tr '()' '  ' | awk '{for(i=1;i<=NF;i++) if($i~/get_abs_build_var/) print $(i+1)}'
    # print
    # 
    # ANDROID_GCC_PREBUILTS
    # PRODUCT_OUT
    # HOST_OUT
    # ANDROID_PREBUILTS
    # 这里跟提取get_build_var的参数一样,也包含了print一行
    # 
    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 "=" pairs as a shell script.
    #
    # 通过 command make --no-print-directory -f build/core/config.mk 命令来操作cached_vars和cached_abs_vars相关变量
    # 后续打算对build/core/config.mk进行分析,看看到底发生了什么,这里暂且略过。
    #
    build_dicts_script=`\cd $T; CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core \
                        command make --no-print-directory -f build/core/config.mk \
                        dump-many-vars \
                        DUMP_MANY_VARS="$cached_vars" \
                        DUMP_MANY_ABS_VARS="$cached_abs_vars" \
                        DUMP_VAR_PREFIX="var_cache_" \
                        DUMP_ABS_VAR_PREFIX="abs_var_cache_"`
    # 检查上一步的返回值,即上一行命令中make操作的执行结果                      
    local ret=$?
    if [ $ret -ne 0 ]
    then
        unset build_dicts_script
        return $ret
    fi
    # Excute the script to store the "=" pairs as shell variables.
    # 对$build_dicts_script内容进行求值处理,从注释看起来是建立一个"="的键值对
    # 通过在eval操作前“echo $build_dicts_script”输出发现,其格式是这样的(为便于阅读,已经经过换行处理):
    # ygu@guyongqiangx:src$ build_build_var_cache  
    # var_cache_2ND_TARGET_GCC_VERSION='' 
    # var_cache_ANDROID_BUILD_PATHS='/android/src/out/host/linux-x86/bin' 
    # var_cache_TARGET_ARCH='arm' 
    # var_cache_TARGET_DEVICE='bcm7252ssffdr4' 
    # var_cache_TARGET_GCC_VERSION='4.9' 
    # var_cache_print='' 
    # var_cache_report_config=` \
    #           echo '============================================'; \
    #           echo 'PLATFORM_VERSION_CODENAME=REL'; \
    #           echo 'PLATFORM_VERSION=7.1.1'; \
    #           echo 'TARGET_PRODUCT=bcm7252ssffdr4'; \
    #           echo 'TARGET_BUILD_VARIANT=userdebug'; \
    #           echo 'TARGET_BUILD_TYPE=release'; \
    #           echo 'TARGET_BUILD_APPS='; \
    #           echo 'TARGET_ARCH=arm'; \
    #           echo 'TARGET_ARCH_VARIANT=armv7-a-neon'; \
    #           echo 'TARGET_CPU_VARIANT=cortex-a15'; \
    #           echo 'TARGET_2ND_ARCH='; \
    #           echo 'TARGET_2ND_ARCH_VARIANT='; \
    #           echo 'TARGET_2ND_CPU_VARIANT='; \
    #           echo 'HOST_ARCH=x86_64'; \
    #           echo 'HOST_2ND_ARCH=x86'; \
    #           echo 'HOST_OS=linux'; \
    #           echo 'HOST_OS_EXTRA=Linux-4.2.0-42-generic-x86_64-with-Ubuntu-14.04-trusty'; \
    #           echo 'HOST_CROSS_OS=windows'; \
    #           echo 'HOST_CROSS_ARCH=x86'; \
    #           echo 'HOST_CROSS_2ND_ARCH=x86_64'; \
    #           echo 'HOST_BUILD_TYPE=release'; \
    #           echo 'BUILD_ID=NMF27D'; \
    #           echo 'OUT_DIR=out'; \
    #           echo '============================================';` 
    # abs_var_cache_ANDROID_GCC_PREBUILTS='/android/src/prebuilts/gcc/linux-x86' 
    # abs_var_cache_ANDROID_PREBUILTS='/android/src/prebuilt/linux-x86' 
    # abs_var_cache_HOST_OUT='/android/src/out/host/linux-x86' 
    # abs_var_cache_PRODUCT_OUT='/android/src/out/target/product/bcm7252ssffdr4' 
    # abs_var_cache_print=''
    #
    eval "$build_dicts_script"
    # 显然,执行eval求值的结果就是建立如下类型的两种键值对:
    # var_cache_xxx='yyy'
    # abs_var_cache_xxx='yyy'
    #
    # 保存eval的求值结果
    ret=$?
    unset build_dicts_script
    # 检查$build_dicts_scripts操作是否成功
    if [ $ret -ne 0 ]
    then
        return $ret
    fi
    # 设置CACHE_READY标志
    BUILD_VAR_CACHE_READY="true"
}

destroy_build_var_cache

destroy_build_var_cache命令清空所有通过build_build_var_cache函数建立的环境变量。

# Delete the build var cache, so that we can still call into the build system
# to get build variables not listed in this script.
function destroy_build_var_cache()
{
    # 取消CACHE_READY标志
    unset BUILD_VAR_CACHE_READY
    # 根据cached_vars列表取消相应变量var_cache_xxx的设置
    for v in $cached_vars; do
      unset var_cache_$v
    done
    # 清空cached_vars列表
    unset cached_vars
    # 根据cached_abs_vars列表取消相应变量abs_var_cache_xxx的设置
    for v in $cached_abs_vars; do
      unset abs_var_cache_$v
    done
    # 清空cached_abs_vars列表
    unset cached_abs_vars
}

get_abs_build_var

get_abs_build_var命令查找通过build_build_var_cache函数建立的键值对列表,输出其参数对应的键值
如,get_abs_build_var xxx,则返回变量abs_var_cache_xxx的值。
get_abs_build_var函数跟get_build_var函数操作一样,唯一不同的地方是前者获取abs_var_cache_xxx变量的值,后者获取var_cache_xxx变量的值。

# Get the value of a build variable as an absolute path.
function get_abs_build_var()
{
    # 如果$BUILD_VAR_CACHE_READY=true,直接返回前缀为abs_var_cache_的变量
    # 函数 getprebuilt中,调用get_abs_build_var ANDROID_PREBUILTS,直接返回$abs_var_cache_ANDROID_PREBUILTS
    if [ "$BUILD_VAR_CACHE_READY" = "true" ]
    then
        # 有意思,通过对echo输出的内容进行eval来设置
        eval echo \"\${abs_var_cache_$1}\"
    return
    fi

    # 查找编译根目录,如果没找到,则退出函数
    T=$(gettop)
    if [ ! "$T" ]; then
        echo "Couldn't locate the top of the tree.  Try setting TOP." >&2
        return
    fi
    # 切换到编译的根目录,执行命令"make --no-print-directory -f build/core/config.mk dumpvar-abs-$1"
    # 函数 getprebuilt中,调用get_abs_build_var ANDROID_PREBUILTS,执行命令:"make --no-print-directory -f build/core/config.mk dumpvar-abs-ANDROID_PREBUILTS"
    # 这里应该是通过make命令来重新生成`$abs_var_cache_ANDROID_PREBUILTS`变量
    (\cd $T; CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core \
      command make --no-print-directory -f build/core/config.mk dumpvar-abs-$1)
}

get_build_var

get_build_var命令超找通过build_build_var_cache函数建立的键值对列表,输出其参数对应的键值
如,get_build_var xxx,则返回变量var_cache_xxx的值。
get_build_var函数跟get_abs_build_var函数操作一样,唯一不同的地方是前者获取var_cache_xxx变量的值,后者获取abs_var_cache_xxx变量的值。

# Get the exact value of a build variable.
function get_build_var()
{
    # 如果$BUILD_VAR_CACHE_READY=true,直接返回前缀为var_cache_的变量
    if [ "$BUILD_VAR_CACHE_READY" = "true" ]
    then
        eval echo \"\${var_cache_$1}\"
    return
    fi

    # 查找编译根目录,如果没找到,则退出函数
    T=$(gettop)
    if [ ! "$T" ]; then
        echo "Couldn't locate the top of the tree.  Try setting TOP." >&2
        return
    fi
    # 切换到编译的根目录,执行命令"make --no-print-directory -f build/core/config.mk dumpvar-$1"
    # 来重新生成`$var_cache_ANDROID_PREBUILTS`变量
    (\cd $T; CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core \
      command make --no-print-directory -f build/core/config.mk dumpvar-$1)
}

check_product

check_product检查TARGET_DEVICE设置是否有效

# check to see if the supplied product is one we can build
function check_product()
{
    # 查找编译根目录,如果没找到,则退出函数
    T=$(gettop)
    if [ ! "$T" ]; then
        echo "Couldn't locate the top of the tree.  Try setting TOP." >&2
        return
    fi
        # 将参数1设置到TARGET_PRODUCT,其余相关参数置空
        TARGET_PRODUCT=$1 \
        TARGET_BUILD_VARIANT= \
        TARGET_BUILD_TYPE= \
        TARGET_BUILD_APPS= \
        # 通过get_build_var获取var_cache_TARGET_DEVICE变量,并将标准输出的内容重定向到/dev/null
        get_build_var TARGET_DEVICE > /dev/null
    # hide successful answers, but allow the errors to show
    # 这里根据注释说,错误信息会显示,并且check_product的返回值是get_build_var函数的调用结果
}

check_variant

check_variant 检查输入的选项是否(user userdebug eng)这三者之一,找到返回0,找不到返回1

VARIANT_CHOICES=(user userdebug eng)

# check to see if the supplied variant is valid
function check_variant()
{
    # 遍历数组VARIANT_CHOICES=(user userdebug eng)
    for v in ${VARIANT_CHOICES[@]}
    do
        # 如果匹配,返回0
        if [ "$v" = "$1" ]
        then
            return 0
        fi
    done
    # 没有找到,返回1
    return 1
}

choosetype

choosetype 根据传入选项或读取用户输入设置编译版本是release还是debug版

function choosetype()
{
    # 显示提示信息
    echo "Build type choices are:"
    echo "     1. release"
    echo "     2. debug"
    echo

    # 设置默认选项为"1. release"
    local DEFAULT_NUM DEFAULT_VALUE
    DEFAULT_NUM=1
    DEFAULT_VALUE=release

    export TARGET_BUILD_TYPE=
    local ANSWER
    # 检查是否已经设置TARGET_BUILD_TYPE,设置后其值不为空
    while [ -z $TARGET_BUILD_TYPE ]
    do
        # 提示默认选项为"[1]"
        echo -n "Which would you like? ["$DEFAULT_NUM"] "
        # 如果choosetype不带参数,则读取参数到ANSWER
        if [ -z "$1" ] ; then
            read ANSWER
        else
            # choosetype带参数的情况下,直接将参数保存到ANSWER
            echo $1
            ANSWER=$1
        fi
        # 根据ANSWER,设置TARGET_BUILD_TYPE
        case $ANSWER in
        "")
            export TARGET_BUILD_TYPE=$DEFAULT_VALUE
            ;;
        1)
            export TARGET_BUILD_TYPE=release
            ;;
        release)
            export TARGET_BUILD_TYPE=release
            ;;
        2)
            export TARGET_BUILD_TYPE=debug
            ;;
        debug)
            export TARGET_BUILD_TYPE=debug
            ;;
        *)
            echo
            echo "I didn't understand your response.  Please try again."
            echo
            ;;
        esac
        # 如果choosetype带有参数,则退出while循环,不用再提示设置
        if [ -n "$1" ] ; then
            break
        fi
    done

    # 设置编译环境相关变量var_cache_xxx和abs_var_cache_xxx
    build_build_var_cache
    # 设置其他环境变量,如PROMPT_COMMAND,编译toolchain和tools相关的路径等
    set_stuff_for_environment
    # 清空环境变量,不懂为什么刚设置了这些变量,这里又要取消?
    destroy_build_var_cache
}

chooseproduct

chooseproduct 根据预先设置的变量或读取用户输入设置TARGET_PRODUCT

#
# This function isn't really right:  It chooses a TARGET_PRODUCT
# based on the list of boards.  Usually, that gets you something
# that kinda works with a generic product, but really, you should
# pick a product by name.
#
# 注释里面提到一些trick,说需要通过name来选择product
#
function chooseproduct()
{
    # 检查TARGET_PRODUCT是否为空
    if [ "x$TARGET_PRODUCT" != x ] ; then
        # 已经设置TARGET_PRODUCT,则保存到default_value
        default_value=$TARGET_PRODUCT
    else
        # 没有设置TARGET_PRODUCT,则设置default_value为aosp_arm
        default_value=aosp_arm
    fi

    export TARGET_BUILD_APPS=
    export TARGET_PRODUCT=
    local ANSWER
    # 读取TARGET_PRODUCT设置
    while [ -z "$TARGET_PRODUCT" ]
    do
        # 如果chooseproduct有带参数,则将参数保存到ANSWER
        # 否则,在命令行读取用户输入数据,并保存到ANSWER
        echo -n "Which product would you like? [$default_value] "
        if [ -z "$1" ] ; then
            read ANSWER
        else
            echo $1
            ANSWER=$1
        fi

        # 如果 ANSWER 长度为0,即用户直接回车输入的情况
        if [ -z "$ANSWER" ] ; then
            export TARGET_PRODUCT=$default_value
        else
            # 调用check_product函数检查输入ANSWER
            if check_product $ANSWER
            then
                export TARGET_PRODUCT=$ANSWER
            else
                echo "** Not a valid product: $ANSWER"
            fi
        fi
        # 如果chooseproduct带了参数,则不再读取输入,跳出循环
        if [ -n "$1" ] ; then
            break
        fi
    done

    # 设置编译环境相关变量var_cache_xxx和abs_var_cache_xxx
    build_build_var_cache
    # 设置其他环境变量,如PROMPT_COMMAND,编译toolchain和tools相关的路径等
    set_stuff_for_environment
    # 清空环境变量,不懂为什么刚设置了这些变量,这里又要取消?
    destroy_build_var_cache
}

choosevariant

choosevariant 读取用户输入设置TARGET_BUILD_VARIANTuser,userdebugeng

function choosevariant()
{
    echo "Variant choices are:"
    local index=1
    local v
    # 从1开始,循环显示VARIANT_CHOICES数组的内容,效果如下:
    # $ choosevariant
    # Variant choices are:
    #      1. user
    #      2. userdebug
    #      3. eng
    # Which would you like? [eng]
    #
    for v in ${VARIANT_CHOICES[@]}
    do
        # The product name is the name of the directory containing
        # the makefile we found, above.
        echo "     $index. $v"
        index=$(($index+1))
    done

    local default_value=eng
    local ANSWER

    export TARGET_BUILD_VARIANT=
    # 交互读取TARGET_BUILD_VARIANT设置
    while [ -z "$TARGET_BUILD_VARIANT" ]
    do
        # 读取用户输入,默认为[eng]
        echo -n "Which would you like? [$default_value] "
        if [ -z "$1" ] ; then
            read ANSWER
        else
            # 如果choosevariant有带参数,则直接用保存参数
            echo $1
            ANSWER=$1
        fi

        # 如果 ANSWER 长度为0,即用户直接回车输入的情况
        if [ -z "$ANSWER" ] ; then
            export TARGET_BUILD_VARIANT=$default_value
        # 将ANSWER的数值转换为VARIANT_CHOICES数组中的字符串
        elif (echo -n $ANSWER | grep -q -e "^[0-9][0-9]*$") ; then
            if [ "$ANSWER" -le "${#VARIANT_CHOICES[@]}" ] ; then
                export TARGET_BUILD_VARIANT=${VARIANT_CHOICES[$(($ANSWER-1))]}
            fi
        else
            # 调用check_variant是否为有效值
            if check_variant $ANSWER
            then
                export TARGET_BUILD_VARIANT=$ANSWER
            else
                echo "** Not a valid variant: $ANSWER"
            fi
        fi
        # 如果choosevariant带了参数,则不再读取输入,跳出循环
        if [ -n "$1" ] ; then
            break
        fi
    done
}

choosecombo

choosecombo根据传入的3个参数,分别设置type(release, debug), product和variant(user, userdebug, eng)参数

function choosecombo()
{
    # 使用参数1设置type (release, debug)
    choosetype $1

    # 使用参数2设置product
    echo
    echo
    chooseproduct $2

    # 使用参数3设置variant (user, userdebug, eng)
    echo
    echo
    choosevariant $3

    echo
    # 设置编译环境相关变量var_cache_xxx和abs_var_cache_xxx
    build_build_var_cache
    # 设置其他环境变量,如PROMPT_COMMAND,编译toolchain和tools相关的路径等
    set_stuff_for_environment
    # 显示当前选择的编译配置
    printconfig
    # 清空环境变量,不懂为什么刚设置了这些变量,这里又要取消?
    destroy_build_var_cache
}

add_lunch_combo

add_lunch_combo将提供的编译选项参数添加到LUNCH_MENU_CHOICES列表中

function add_lunch_combo()
{
    local new_combo=$1
    local c
    for c in ${LUNCH_MENU_CHOICES[@]} ; do
        if [ "$new_combo" = "$c" ] ; then
            return
        fi
    done
    LUNCH_MENU_CHOICES=(${LUNCH_MENU_CHOICES[@]} $new_combo)
}

print_lunch_menu

print_lunch_menu打印编译选项列表LUNCH_MENU_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
    # 遍历数组LUNCH_MENU_CHOICES,以类似"1. aosp_arm-eng"的格式输出
    for choice in ${LUNCH_MENU_CHOICES[@]}
    do
        echo "     $i. $choice"
        i=$(($i+1))
    done

    echo
}

lunch

lunch操作根据传入参数选项设置TARGET_PRODUCT, TARGET_BUILD_VARIANTTARGET_BUILD_TYPE

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
        if [ $answer -le ${#LUNCH_MENU_CHOICES[@]} ]
        then
            selection=${LUNCH_MENU_CHOICES[$(($answer-1))]}
        fi
    # lunch操作得到的是字符串,直接将字符串保存到selection中      
    elif (echo -n $answer | grep -q -e "^[^\-][^\-]*-[^\-][^\-]*$")
    then
        selection=$answer
    fi

    # 检查selection的值是否正常
    # 例如选择了一个LUNCH_MENU_CHOICES中不存在的索引,则selection就为空
    if [ -z "$selection" ]
    then
        echo
        echo "Invalid lunch combo: $answer"
        return 1
    fi

    export TARGET_BUILD_APPS=

    #
    # 分离selection字符串,例如:aosp_fugu-userdebug
    #
    # 获取第一个'-'后的部分,这里即userdebug,保存到variant
    local variant=$(echo -n $selection | sed -e "s/^[^\-]*-//")
    # 检查variant是否合法,即是否选项(user, userdebug, eng)之一,如果不是,输出提示
    check_variant $variant
    if [ $? -ne 0 ]
    then
        echo
        echo "** Invalid variant: '$variant'"
        echo "** Must be one of ${VARIANT_CHOICES[@]}"
        variant=
    fi

    # 获取最后一个'-'前的部分,这里即aosp_fugu,保存到product
    local product=$(echo -n $selection | sed -e "s/-.*$//")

    # 设置TARGET_PRODUCT和TARGET_BUILD_VARIANT
    TARGET_PRODUCT=$product \
    TARGET_BUILD_VARIANT=$variant \
    # 根据前面的设置,更新编译环境相关变量
    build_build_var_cache
    if [ $? -ne 0 ]
    then
        echo
        echo "** Don't have a product spec for: '$product'"
        echo "** Do you have the right repo manifest?"
        product=
    fi

    # product或variant为空的情况下,退出函数
    if [ -z "$product" -o -z "$variant" ]
    then
        echo
        return 1
    fi

    # export 编译选项TARGET_PRODUCT, TARGET_BUILD_VARIANT和TARGET_BUILD_TYPE三元组
    export TARGET_PRODUCT=$product
    export TARGET_BUILD_VARIANT=$variant
    export TARGET_BUILD_TYPE=release

    echo

    # 设置其他环境变量,如PROMPT_COMMAND,编译toolchain和tools相关的路径等
    set_stuff_for_environment
    # 输出当前的设置选项
    printconfig
    # 清空环境变量,不懂为什么刚设置了这些变量,这里又要取消?
    destroy_build_var_cache
}

_lunch

_lunch命令提供lunch命令的补全操作

# Tab completion for lunch.
function _lunch()
{
    local cur prev opts
    COMPREPLY=()
    cur="${COMP_WORDS[COMP_CWORD]}"
    prev="${COMP_WORDS[COMP_CWORD-1]}"

    # 生成补全结果
    COMPREPLY=( $(compgen -W "${LUNCH_MENU_CHOICES[*]}" -- ${cur}) )
    return 0
}
# 指定遇到lunch命令时,用_lunch函数的输出的内容进行补全
complete -F _lunch lunch

m

可以在代码的任何一个目录里面执行m指令编译所有模块
其实很简单,就是用make的-C选项指定到代码的根目录。

function m()
{
    # 获取代码的根目录
    local T=$(gettop)
    # 编译无关,可以忽略此选项
    local DRV=$(getdriver $T)
    if [ "$T" ]; then
        # 直接转到代码的根目录进行编译
        $DRV make -C $T -f build/core/main.mk $@
    else
        echo "Couldn't locate the top of the tree.  Try setting TOP."
        return 1
    fi
}

mm

mm指令编译当前目录下的所有模块

function mm()
{
    # 获取代码根目录
    local T=$(gettop)
    local DRV=$(getdriver $T)
    # If we're sitting in the root of the build tree, just do a
    # normal make.
    # 如果在代码根目录下执行mm指令,转换为直接运行make指令
    if [ -f build/core/envsetup.mk -a -f Makefile ]; then
        $DRV make $@
    else
        # 超找当前目录对应模块的Android.mk文件
        # Find the closest Android.mk file.
        local M=$(findmakefile)
        local MODULES=
        local GET_INSTALL_PATH=
        local ARGS=
        # Remove the path to top as the makefilepath needs to be relative
        # 将查找到的Android.mk转换为针对代码根目录的相对路径
        local M=`echo $M|sed 's:'$T'/::'`
        # 好吧,没有找到代码的根目录
        if [ ! "$T" ]; then
            echo "Couldn't locate the top of the tree.  Try setting TOP."
            return 1
        # 好吧,竟然没有Android.mk文件
        elif [ ! "$M" ]; then
            echo "Couldn't locate a makefile from the current directory."
            return 1
        else
            # 逐个处理命令行参数
            for ARG in $@; do
                case $ARG in
                  GET-INSTALL-PATH) GET_INSTALL_PATH=$ARG;;
                esac
            done
            # 如果GET_INSTALL_PATH不为空,则
            if [ -n "$GET_INSTALL_PATH" ]; then
              MODULES=
              ARGS=GET-INSTALL-PATH
            # 编译所有模块
            else
              MODULES=all_modules
              ARGS=$@
            fi
            # 转到代码根目录开始编译指定的模块
            ONE_SHOT_MAKEFILE=$M $DRV make -C $T -f build/core/main.mk $MODULES $ARGS
        fi
    fi
}

mmm

mmm手动指定目录和,其下面编译的模块,格式如下:
mmm dir1,dir2,dir3,dir4/,...:[module1],[module2],[module3],[module4] -options

function mmm()
{
    # 获取代码根目录
    local T=$(gettop)
    local DRV=$(getdriver $T)
    # 在代码根目录存在的情况下,开始进行命令参数解析和编译
    if [ "$T" ]; then
        local MAKEFILE=
        local MODULES=
        local ARGS=
        local DIR TO_CHOP
        local GET_INSTALL_PATH=
        # 提取编译命令中的选项参数,即符号"-"后面接的参数
        local DASH_ARGS=$(echo "$@" | awk -v RS=" " -v ORS=" " '/^-.*$/')
        # 提取编译命令中目录和模块部分,即符号的"-"前的参数部分
        # 例如 "mmm dir1 dir2 dir3 dir4/:module1,module2,module3,module4"
        local DIRS=$(echo "$@" | awk -v RS=" " -v ORS=" " '/^[^-].*$/')
        for DIR in $DIRS ; do
            #
            # 编译命令类似 mmm dir1 dir2 dir3 dir4/:module1,module2,module3,module4
            #
            # 第一个sed命令获取$DIR中":"后面的module1~module4的部分
            # 第二个sed命令替换到各个目标间的','号,将其转换为一个包含编译目标的数组,即模块
            # 处理后变成 MODULES=module1 module2 module3 module4
            MODULES=`echo $DIR | sed -n -e 's/.*:\(.*$\)/\1/p' | sed 's/,/ /'`
            # 没有指定模块的话,默认为all_modules
            if [ "$MODULES" = "" ]; then
                MODULES=all_modules
            fi
            # 第一个sed命令提取$DIR的":"前的目录部分
            # 第二个sed命令忽略目录部分的"/"后缀
            # 处理后变成DIR=dir1 dir2 dir3 dir4
            DIR=`echo $DIR | sed -e 's/:.*//' -e 's:/$::'`
            # 如果处理后的文件夹得到的DIR下有Android.mk
            if [ -f $DIR/Android.mk ]; then
                # 计算代码根目录路径包含的字符数
                local TO_CHOP=`(\cd -P -- $T && pwd -P) | wc -c | tr -d ' '`
                # 代码根目录路径的字符数+1
                local TO_CHOP=`expr $TO_CHOP + 1`
                # 获取当前目录的绝对路径START
                local START=`PWD= /bin/pwd`
                # 获取当前目录相对于根目录的相对路径,并保存在MFILE中
                local MFILE=`echo $START | cut -c${TO_CHOP}-`
                # 构建Android.mk的相对路径
                if [ "$MFILE" = "" ] ; then
                    MFILE=$DIR/Android.mk
                else
                    MFILE=$MFILE/$DIR/Android.mk
                fi
                MAKEFILE="$MAKEFILE $MFILE"
            else
                # 如果处理后的文件夹下面没有Android.mk,说明其可能不是目录,而是某个命令,如showcommands
                case $DIR in
                  # 如果是showcommands, snode, dist或*=*的情况,将其作为真正编译命令的参数传递
                  showcommands | snod | dist | *=*) ARGS="$ARGS $DIR";;
                  GET-INSTALL-PATH) GET_INSTALL_PATH=$DIR;;
                  # 不是showcommands, snode, dist等的情况下,检查这个目录是否存在
                  *) if [ -d $DIR ]; then
                         # 目录存在,但Android.mk不存在,提示没有Android.mk文件
                         echo "No Android.mk in $DIR.";
                     else
                         echo "Couldn't locate the directory $DIR";
                     fi
                     return 1;;
                esac
            fi
        done
        # 将GET_INSTALL_PATH作为参数传入编译
        if [ -n "$GET_INSTALL_PATH" ]; then
          ARGS=$GET_INSTALL_PATH
          MODULES=
        fi
        # 将mmm命令“-”后的选项和模块参数以及其他参数传递给主makefile进行编译
        ONE_SHOT_MAKEFILE="$MAKEFILE" $DRV make -C $T -f build/core/main.mk $DASH_ARGS $MODULES $ARGS
    # 获取代码根目录是T返回空,说明没有找到代码根目录,有什么办法?那就显示错误信息并退出吧      
    else
        echo "Couldn't locate the top of the tree.  Try setting TOP."
        return 1
    fi
}

mma

mma 相当于mm执行相应目录下的all_modules参数

function mma()
{
  # 获取代码根目录
  local T=$(gettop)
  local DRV=$(getdriver $T)
  # 如果在代码根目录下执行mm指令,转换为直接运行make指令
  if [ -f build/core/envsetup.mk -a -f Makefile ]; then
    $DRV make $@
  else
    # 取得的代码根目录路径为空,那就报错退出
    if [ ! "$T" ]; then
      echo "Couldn't locate the top of the tree.  Try setting TOP."
      return 1
    fi
    # 将当前目录转换为相对于根目录的相对路径
    local MY_PWD=`PWD= /bin/pwd|sed 's:'$T'/::'`
    # 基于路径设置MODULES-IN-PATH参数
    local MODULES_IN_PATHS=MODULES-IN-$MY_PWD
    # Convert "/" to "-".
    MODULES_IN_PATHS=${MODULES_IN_PATHS//\//-}
    # 编译所有模块
    $DRV make -C $T -f build/core/main.mk $@ $MODULES_IN_PATHS
  fi
}

mmma

mmma 相当于mmm执行相应目录下的all_modules参数

function mmma()
{
  # 获取代码根目录
  local T=$(gettop)
  local DRV=$(getdriver $T)
  # 在代码根目录存在的情况下,开始进行命令参数解析和编译
  if [ "$T" ]; then
    # 提取编译命令中的选项参数,即符号"-"后面接的参数
    local DASH_ARGS=$(echo "$@" | awk -v RS=" " -v ORS=" " '/^-.*$/')
    # 提取编译命令中目录和模块部分,即符号的"-"前的参数部分
    # 例如 "mmm dir1 dir2 dir3 dir4/:module1,module2,module3,module4"
    local DIRS=$(echo "$@" | awk -v RS=" " -v ORS=" " '/^[^-].*$/')
    # 获取当前目录
    local MY_PWD=`PWD= /bin/pwd`
    # 将当前目录到代码根目录的相对路径保存到MY_PWD中
    if [ "$MY_PWD" = "$T" ]; then
      MY_PWD=
    else
      MY_PWD=`echo $MY_PWD|sed 's:'$T'/::'`
    fi
    local DIR=
    local MODULES_IN_PATHS=
    local ARGS=
    for DIR in $DIRS ; do
      # 检查DIR指定的参数是否为目录
      if [ -d $DIR ]; then
        # Remove the leading ./ and trailing / if any exists.
        DIR=${DIR#./}
        DIR=${DIR%/}
        # 生成DIR相对于代码根目录的完整相对路径
        if [ "$MY_PWD" != "" ]; then
          DIR=$MY_PWD/$DIR
        fi
        MODULES_IN_PATHS="$MODULES_IN_PATHS MODULES-IN-$DIR"
      else
        # 如果DIR对应的参数是showcommands, snod, dist等,将其转换为参数ARGS
        case $DIR in
          showcommands | snod | dist | *=*) ARGS="$ARGS $DIR";;
          *) echo "Couldn't find directory $DIR"; return 1;;
        esac
      fi
    done
    # Convert "/" to "-".
    MODULES_IN_PATHS=${MODULES_IN_PATHS//\//-}
    # 将mmm命令“-”后的选项和模块参数以及其他参数传递给主makefile进行编译
    $DRV make -C $T -f build/core/main.mk $DASH_ARGS $ARGS $MODULES_IN_PATHS
  else
    # 获取代码根目录是T返回空,说明没有找到代码根目录,有什么办法?那就显示错误信息并退出吧    
    echo "Couldn't locate the top of the tree.  Try setting TOP."
    return 1
  fi
}

get_make_command

get_make_commandmake命令转换为command make调用。
执行souce build/envsetup.sh后环境中有两个make

  • envsetup.sh脚本中定义的make
  • make系统的可执行文件make
    命令行运行make时,先执行shell环境中内置的make函数,然后make函数内部通过command命令调用可执行文件make
function get_make_command()
{
  echo command make
}

make

make

function make()
{
    # 获取开始时间
    local start_time=$(date +"%s")
    # 
    # 命令行运行`make`时,先执行`shell`环境中内置的`make`函数(本函数),
    # 然后通过函数的`command`命令调用可执行文件`make`进行真正的make操作
    # 这里相当于是重新定义shell命令行的函数将命令`make`拦截了
    #
    # 将make xxx转换为命令command make xxx执行
    $(get_make_command) "$@"
    # 获取可执行文件make的返回值
    local ret=$?
    # 获取执行make命令完成的时间
    local end_time=$(date +"%s")
    # 计算时间差,并转换为HH:MM:SS的格式
    local tdiff=$(($end_time-$start_time))
    local hours=$(($tdiff / 3600 ))
    local mins=$((($tdiff % 3600) / 60))
    local secs=$(($tdiff % 60))
    # 设置各种字体颜色
    local ncolors=$(tput colors 2>/dev/null)
    if [ -n "$ncolors" ] && [ $ncolors -ge 8 ]; then
        color_failed=$'\E'"[0;31m"
        color_success=$'\E'"[0;32m"
        color_reset=$'\E'"[00m"
    else
        color_failed=""
        color_success=""
        color_reset=""
    fi
    echo
    # 显示编译成功信息
    if [ $ret -eq 0 ] ; then
        echo -n "${color_success}#### make completed successfully "

    # 显示编译失败信息
    else
        echo -n "${color_failed}#### make failed to build some targets "
    fi
    # 显示make操作执行的时间
    if [ $hours -gt 0 ] ; then
        printf "(%02g:%02g:%02g (hh:mm:ss))" $hours $mins $secs
    elif [ $mins -gt 0 ] ; then
        printf "(%02g:%02g (mm:ss))" $mins $secs
    elif [ $secs -gt 0 ] ; then
        printf "(%s seconds)" $secs
    fi
    echo " ####${color_reset}"
    echo
    return $ret
}

tapas

tapas 以交互方式设置单个app编译的build环境变量,调用格式为:

tapas [ ...] [arm|x86|mips] [eng|userdebug|user]

话说tapas长这样,其实做底层的我一次都没有用过这个命令。

# Configures the build to build unbundled apps.
# Run tapas with one or more app names (from LOCAL_PACKAGE_NAME)
function tapas()
{
    # 获取参数里的arch相关变量(arm|x86|mips|armv5|arm64|x86_64|mips64)
    local arch="$(echo $* | xargs -n 1 echo | \grep -E '^(arm|x86|mips|armv5|arm64|x86_64|mips64)$' | xargs)"
    # 获取参数里variant相关变量(user|userdebug|eng)
    local variant="$(echo $* | xargs -n 1 echo | \grep -E '^(user|userdebug|eng)$' | xargs)"
    # 获取参数里分辨率DPI(`Dot Per Inch`,每英寸像素数)相关的参数
    local density="$(echo $* | xargs -n 1 echo | \grep -E '^(ldpi|mdpi|tvdpi|hdpi|xhdpi|xxhdpi|xxxhdpi|alldpi)$' | xargs)"
    # 使用`grep -v`过滤参数中arch, variant,density相关参数,然后将剩余参数指定为apps
    local apps="$(echo $* | xargs -n 1 echo | \grep -E -v '^(user|userdebug|eng|arm|x86|mips|armv5|arm64|x86_64|mips64|ldpi|mdpi|tvdpi|hdpi|xhdpi|xxhdpi|xxxhdpi|alldpi)$' | xargs)"

    #
    # 检查命令行是否设置了多个arch, variant和density参数,显然这类相关参数变量只能有唯一值
    #

    # 检查是否设置了多个arch参数,显示错误消息
    if [ $(echo $arch | wc -w) -gt 1 ]; then
        echo "tapas: Error: Multiple build archs supplied: $arch"
        return
    fi
    # 检查是否设置了多个variant参数,显示错误消息
    if [ $(echo $variant | wc -w) -gt 1 ]; then
        echo "tapas: Error: Multiple build variants supplied: $variant"
        return
    fi
    # 检查是否设置了多个density参数,显示错误消息
    if [ $(echo $density | wc -w) -gt 1 ]; then
        echo "tapas: Error: Multiple densities supplied: $density"
        return
    fi

    # 根据arch设置针对相应平台的默认的product
    local product=aosp_arm
    case $arch in
      x86)    product=aosp_x86;;
      mips)   product=aosp_mips;;
      armv5)  product=generic_armv5;;
      arm64)  product=aosp_arm64;;
      x86_64) product=aosp_x86_64;;
      mips64)  product=aosp_mips64;;
    esac
    # 没有指定variant,则默认设置为eng
    if [ -z "$variant" ]; then
        variant=eng
    fi
    # 没有指定app,则默认为all
    if [ -z "$apps" ]; then
        apps=all
    fi
    # 没有指定density参数,则默认设置为alldpi
    if [ -z "$density" ]; then
        density=alldpi
    fi

    # 根据以上的各参数设置编译环境变量TARGET_PRODUCT, TARGET_BUILD_{VARIANT, DENSITY, TYPE, APPS}
    export TARGET_PRODUCT=$product
    export TARGET_BUILD_VARIANT=$variant
    export TARGET_BUILD_DENSITY=$density
    export TARGET_BUILD_TYPE=release
    export TARGET_BUILD_APPS=$apps

    # 设置编译环境相关变量var_cache_xxx和abs_var_cache_xxx
    build_build_var_cache
    # 设置其他环境变量,如PROMPT_COMMAND,编译toolchain和tools相关的路径等
    set_stuff_for_environment
    # 显示当前选择的编译配置
    printconfig
    # 清空环境变量,不懂为什么刚设置了这些变量,这里又要取消?
    destroy_build_var_cache
}

1.3 代码搜索类

代码搜索类函数定义了各种xxxgrep函数,并导入到当前环境中,使其可以直接在命令行调用。
这些函数先用find命令在除.repo/.git/out的目录外搜索相应后名称的目录或文件,然后基于搜索结果调用grep进行模式查找。

  • sgrep,基于(c|h|cc|cpp|S|java|xml|sh|mk|aidl|vts)文件查找
  • ggrep,基于(.gradle)的文件查找
  • jgrep,基于(.java)文件查找
  • cgrep,基于(c|cc|cpp|h|hpp)文件查找
  • resgrep,基于res目录下(xml)文件查找
  • mangrep,基于AndroidManifest.xml文件查找
  • sepgrep,基于sepolicy目录下查找
  • rcgrep,基于*.rc*文件查找
  • mgrep,基于(Makefile|Makefile\..*|.*\.make|.*\.mak|.*\.mk)的Makefile文件查找
  • treegrep,基于代码的文件(c|h|cpp|S|java|xml)进行查找
# 针对MAC和非MAC环境定义sgrep函数
case `uname -s` in
    Darwin)
        function sgrep()
        {
            # 排除 .repo, .git目录
            # 并对后缀(c|h|cc|cpp|S|java|xml|sh|mk|aidl|vts)的文件执行grep模式搜索
            find -E . -name .repo -prune -o -name .git -prune -o  -type f -iregex '.*\.(c|h|cc|cpp|S|java|xml|sh|mk|aidl|vts)' \
                -exec grep --color -n "$@" {} +
        }

        ;;
    *)
        function sgrep()
        {
            find . -name .repo -prune -o -name .git -prune -o  -type f -iregex '.*\.\(c\|h\|cc\|cpp\|S\|java\|xml\|sh\|mk\|aidl\|vts\)' \
                -exec grep --color -n "$@" {} +
        }
        ;;
esac

function ggrep()
{
    # 排除 .repo, .git, out目录
    # 并对后缀.gradle的文件执行grep模式搜索
    find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f -name "*\.gradle" \
        -exec grep --color -n "$@" {} +
}

function jgrep()
{
    # 排除 .repo, .git, out目录
    # 并对后缀为.java的文件执行grep模式搜索
    find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f -name "*\.java" \
        -exec grep --color -n "$@" {} +
}

function cgrep()
{
    # 排除 .repo, .git, out目录
    # 并对后缀为(.c|.cc|.cpp|.h|.hpp)的文件执行grep模式搜索
    find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f \( -name '*.c' -o -name '*.cc' -o -name '*.cpp' -o -name '*.h' -o -name '*.hpp' \) \
        -exec grep --color -n "$@" {} +
}

function resgrep()
{
    # 排除 .repo, .git, out目录
    # 并对名为res目录下的*.xml文件执行grep模式搜索
    for dir in `find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -name res -type d`; do
        find $dir -type f -name '*\.xml' -exec grep --color -n "$@" {} +
    done
}

function mangrep()
{
    # 排除 .repo, .git, out目录
    # 并对名为AndroidManifest.xml的文件执行grep模式搜索
    find . -name .repo -prune -o -name .git -prune -o -path ./out -prune -o -type f -name 'AndroidManifest.xml' \
        -exec grep --color -n "$@" {} +
}

function sepgrep()
{
    # 排除 .repo, .git, out目录
    # 并对名为sepolicy目录中的文件执行grep模式搜索
    find . -name .repo -prune -o -name .git -prune -o -path ./out -prune -o -name sepolicy -type d \
        -exec grep --color -n -r --exclude-dir=\.git "$@" {} +
}

function rcgrep()
{
    # 排除 .repo, .git, out目录
    # 并对名为*.rc*的文件执行grep模式搜索
    find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f -name "*\.rc*" \
        -exec grep --color -n "$@" {} +
}

case `uname -s` in
    Darwin)
        function mgrep()
        {
            # 排除 .repo, .git, ./out目录
            # 并对Makefile,后缀为(.make|.mak|.mk)的文件或Makefile目录下的文件执行grep模式搜索
            find -E . -name .repo -prune -o -name .git -prune -o -path ./out -prune -o -type f -iregex '.*/(Makefile|Makefile\..*|.*\.make|.*\.mak|.*\.mk)' \
                -exec grep --color -n "$@" {} +
        }

        function treegrep()
        {
            # 排除 .repo, .git目录
            # 并对后缀为(.c|.h|.cpp|.S|.java|.xml)的文件执行grep模式搜索
            find -E . -name .repo -prune -o -name .git -prune -o -type f -iregex '.*\.(c|h|cpp|S|java|xml)' \
                -exec grep --color -n -i "$@" {} +
        }

        ;;
    *)
        function mgrep()
        {
            find . -name .repo -prune -o -name .git -prune -o -path ./out -prune -o -regextype posix-egrep -iregex '(.*\/Makefile|.*\/Makefile\..*|.*\.make|.*\.mak|.*\.mk)' -type f \
                -exec grep --color -n "$@" {} +
        }

        function treegrep()
        {
            find . -name .repo -prune -o -name .git -prune -o -regextype posix-egrep -iregex '.*\.(c|h|cpp|S|java|xml)' -type f \
                -exec grep --color -n -i "$@" {} +
        }

        ;;
esac

1.4 adb调试类

qpid

qpid

# simplified version of ps; output in the form
#  
function qpid() {
    local prepend=''
    local append=''
    if [ "$1" = "--exact" ]; then
        prepend=' '
        append='$'
        shift
    elif [ "$1" = "--help" -o "$1" = "-h" ]; then
        echo "usage: qpid [[--exact] "
        return 255
    fi

    local EXE="$1"
    if [ "$EXE" ] ; then
        qpid | \grep "$prepend$EXE$append"
    else
        adb shell ps \
            | tr -d '\r' \
            | sed -e 1d -e 's/^[^ ]* *\([0-9]*\).* \([^ ]*\)$/\1 \2/'
    fi
}

pid

pid

function pid()
{
    local prepend=''
    local append=''
    if [ "$1" = "--exact" ]; then
        prepend=' '
        append='$'
        shift
    fi
    local EXE="$1"
    if [ "$EXE" ] ; then
        local PID=`adb shell ps \
            | tr -d '\r' \
            | \grep "$prepend$EXE$append" \
            | sed -e 's/^[^ ]* *\([0-9]*\).*$/\1/'`
        echo "$PID"
    else
        echo "usage: pid [--exact] "
        return 255
    fi
}

coredump_setup

coredump_enable

core

systemstack

stacks

is64bit

tracedmdump

runhat

getbugreports

getsdcardpath

getscreenshotpath

getlastscreenshot

startviewserver

stopviewserver

isviewserverstarted

key_home

key_back

key_menu

smoketest

runtest

provision

provision

function provision()
{
    # 如果ANDROID_PRODUCT_OUT没有设置,则退出函数
    if [ ! "$ANDROID_PRODUCT_OUT" ]; then
        echo "Couldn't locate output files.  Try running 'lunch' first." >&2
        return 1
    fi
    # 如果$ANDROID_PRODUCT_OUT/provision-device文件不存在,则退出函数
    if [ ! -e "$ANDROID_PRODUCT_OUT/provision-device" ]; then
        echo "There is no provisioning script for the device." >&2
        return 1
    fi

    # Check if user really wants to do this.
    # 如果第一个参数是--no-confirmation,表明不需要交互执行脚本
    if [ "$1" = "--no-confirmation" ]; then
        shift 1
    else
        # 交互执行脚本,弹出确认信息
        echo "This action will reflash your device."
        echo ""
        echo "ALL DATA ON THE DEVICE WILL BE IRREVOCABLY ERASED."
        echo ""
        echo -n "Are you sure you want to do this (yes/no)? "
        read
        # 用户选择no,则退出程序
        if [[ "${REPLY}" != "yes" ]] ; then
            echo "Not taking any action. Exiting." >&2
            return 1
        fi
    fi
    # 开始执行provision-device脚本
    "$ANDROID_PRODUCT_OUT/provision-device" "$@"
}

2. 生成编译配置列表

envsetup.sh脚本中除去函数的定义外,剩下的就是自身的逻辑应用,为了方便起见,以下是删除函数定义后的脚本内容:

...
# 编译的可能选项后缀
VARIANT_CHOICES=(user userdebug eng)
...
# Clear this variable.  It will be built up again when the vendorsetup.sh
# files are included at the end of this file.
# 清空选项菜单
unset LUNCH_MENU_CHOICES
...
# add the default one here
# 添加默认的菜单编译选项,包括aosp_{arm, arm64, mips, mips64, x86, x86_64}-eng等选项
add_lunch_combo aosp_arm-eng
add_lunch_combo aosp_arm64-eng
add_lunch_combo aosp_mips-eng
add_lunch_combo aosp_mips64-eng
add_lunch_combo aosp_x86-eng
add_lunch_combo aosp_x86_64-eng
...
# 设置_lunch作为lunch命令的补全函数
complete -F _lunch lunch
...
# 检查当前执行的shell环境是否为bash,如果不是,输出警告信息
if [ "x$SHELL" != "x/bin/bash" ]; then
    case `ps -o command -p $$` in
        *bash*)
            ;;
        *)
            echo "WARNING: Only bash is supported, use of other shell would lead to erroneous results"
            ;;
    esac
fi

# Execute the contents of any vendorsetup.sh files we can find.
# 依次查找{device, vendor, product}目录下的vendorsetup.sh文件
for f in `test -d device && find -L device -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort` \
         `test -d vendor && find -L vendor -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort` \
         `test -d product && find -L product -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort`
do
    # 对查找到的vendorsetup.sh文件执行"."操作,将其内容导入到当前环境中来
    # 一个约定俗成是vendorsetup.sh里面调用add_lunch_combo添加编译选项,例如:
    # 文件device\asus\fugu\vendorsetup.sh的内容为:
    #     add_lunch_combo full_fugu-userdebug
    #     add_lunch_combo aosp_fugu-userdebug
    echo "including $f"
    . $f
done
# 清除变量f
unset f

# 调用addcompletions函数设置基于sdk/bash_completion的补全功能
addcompletions

简单来说,envsetup.sh搜集各个vendor定义的编译选项,存放到lunch menu中,供下一步的lunch操作使用。

执行lunch操作是,lunch函数解析传入的编译选项,如full_fugu-userdebug,更新相应的Android编译环境变量。

后记

对这样的脚本文件进行逐行分析真是花费时间,前后用掉了几个下午,写出来的内容也啰嗦冗长,也是够佩服自己的耐心,差点就忍不住了。
好吧,就当是对envsetup.sh的75个函数进行完全解读,留作字典来查询了。

如果只想知道envsetup.sh的大概功能,浏览下主要关心的函数就够了,没有必要对每一行代码都细致入微的分析,这样效率太低。
envsetup.sh的主要内容包括:
- m/mm/mmm操作
- lunch操作的过程
- 各个自定义的脚本文件vendorsetup.sh是如何起作用的就够了

除此之外的其它函数,如各种搜索函数,adb调试函数等,可能永远都不会用到。

你可能感兴趣的:(Android)