欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

build/envsetup.sh 简介

程序员文章站 2022-07-02 15:34:32
...

看了好多篇关于build/envsetup.sh 的介绍,记下的总结

build/envsetup.sh

每次开始编译开始的第一个命令便是source build/envsetup.sh,其中source命令就是用于运行shell脚本命令,功能等价于”.”,因此该命令也等价于. build/envsetup.sh。在文件envsetup.sh声明了当前会话终端可用的命令,这里需要注意的是当前会话终端,也就意味着每次新打开一个终端都必须再一次执行这些指令。build/envsetup.sh文件存在的意义就是,设置一些环境变量和shell函数为后续的编译工作做准备。 接下来我们分析下
envsetup.sh.

命令————envsetup.sh中的函数简介

导入envsetup.sh系统会都出一些命令,先看下命令的作用

常用的命令

编译指令 解释
mm 编译当前路径下所有模块,但不包含依赖
mmm [module_path] 编译指定路径下所有模块,但不包含依赖
mma 编译当前路径下所有模块,且包含依赖
mmma [module_path] 编译指定路径下所有模块,且包含依赖
make [module_name] 无参数,则表示编译整个Android代码
cgrep 当前目录下所有C/C++文件执行搜索操作
jgrep 当前目录下所有Java文件执行搜索操作
ggrep 当前目录下所有Gradle文件执行搜索操作
mangrep [keyword] 当前目录下所有AndroidManifest.xml文件执行搜索操作
mgrep [keyword] 当前目录下所有Android.mk文件执行搜索操作
sepgrep [keyword] 所有sepolicy文件执行搜索操作
resgrep [keyword] 当前目录下所有本地res/*.xml文件执行搜索操作
sgrep [keyword] 当前目录下基于(c
croot 切换至Android根目录
cproj 切换至工程的根目录
godir [filename] 跳转到包含某个文件的目录
hmm 查询所有的指令help信息

Tips: Android源码非常庞大,直接采用grep来搜索代码,不仅方法笨拙、浪费时间,而且搜索出很多无意义的混淆结果。根据具体需求,来选择合适的代码搜索指令,能节省代码搜索时间,提高搜索结果的精准度,方便定位目标代码。
godir [filename] 第一次执行速度较慢,之后运作效率比find命令高(具体见后面解析)

  • 其他指令
    make clean:执行清理操作,等价于 rm -rf out/
    make update-api:更新API,在framework API改动后需执行该指令,Api记录在目录frameworks/base/api

source build/envsetup.sh 执行流程

envsetup.sh 定义了很多函数,除此之外还执行了其它操作,以下是除去函数部分的代码::

VARIANT_CHOICES=(user userdebug eng)# 提前定义3种编译模式,供后面使用

# Clear this variable.  It will be built up again when the vendorsetup.sh
# files are included at the end of this file.
#LUNCH_MENU_CHOICES是供用户选择的prodcut列表,
#每次source build/envsetup.sh时需重置变量LUNCH_MENU_CHOICES
#不然后续的include vendor/cm/vendorsetup.sh时会继续添加产品至变量LUNCH_MENU_CHOICES里,
#导致出现很多重复产品
unset LUNCH_MENU_CHOICES   

#处理逻辑就是先从LUNCH_MENU_CHOICES中循环查找,看存不存在要添加的板型,如果存在就直接返回,如果不存在就添加到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)
}



 #默认会加载如下6个板型选项
# add the default one here
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函数,这行代码的意思是通过_lunch函数实现lunch命令的自动补全功能
complete -F _lunch lunch 

#shell检查和警告,这里只支持bash,如果是其他的shell会发出这个WARNING
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.
#source vendor和device下能找到的所有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`
do
    echo "including $f"
    . $f
done
unset f

addcompletions

最最后看看addcompletions干了啥

function addcompletions()
{
    local T dir f

    # Keep us from trying to run in something that isn't bash.
    if [ -z "${BASH_VERSION}" ]; then
        return
    fi

    # Keep us from trying to run in bash that's too old.
    if [ ${BASH_VERSINFO[0]} -lt 3 ]; then
        return
    fi

    dir="sdk/bash_completion"
    if [ -d ${dir} ]; then
        for f in `/bin/ls ${dir}/[a-z]*.bash 2> /dev/null`; do
            echo "including $f"
            . $f
        done
    fi
}

如果BASH的版本为空或者小于3都直接返回,否则打印android/sdk/bash_completion/目录下的以.bash结尾的所有文件,执行.bash文件,实现Linux终端下adb命令的补全

至此, source build/envsetup.sh的过程就分析完了。

build/envsetup.sh 常用函数介绍

lunch流程

在source流程之后,紧接着就是执行lunch操作,lunch操作执行的其实就是build/envsetup.sh脚本中的lunch函数,直接看代码:

function lunch()
{
    local answer

    #如果lunch命令后跟有参数,则直接赋给answer变量;
    #如果lunch命令后没有参数,则调用函数print_lunch_menu打印出板型列表供用户选择,并将用户的选择存储在answer变量中。
    if [ "$1" ] ; then
        answer=$1
    else
        print_lunch_menu #函数的作用就是打印数组LUNCH_MENU_CHOICES的内容
        echo -n "Which would you like? [aosp_arm-eng] "
        read answer
    fi

    local selection=


    #如果answer为空,则selection默认赋值为aosp_arm-eng;
    #如果answer是纯数字,则将answer作为数组下标从LUNCH_MENU_CHOICES数组中取出板型名称;
    #如果anwser是字符串,并且字符串使用”-”连接,而且”-”连接的前后两个子串中都没有”-”,则认为是板型名称字符串,直接赋给selection。
    if [ -z "$answer" ]
    then
        selection=aosp_arm-eng
    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
    elif (echo -n $answer | grep -q -e "^[^\-][^\-]*-[^\-][^\-]*$")
    then
        selection=$answer
    fi

    #如果selection仍然为空,则直接报错并退出
    if [ -z "$selection" ]
    then
        echo
        echo "Invalid lunch combo: $answer"
        return 1
    fi

    export TARGET_BUILD_APPS=


    #从selection中取出“-”前面的字符串到product,
    local product=$(echo -n $selection | sed -e "s/-.*$//")
    #检查product的合法性,涉及到make 不展开
    check_product $product
    if [ $? -ne 0 ]
    then
        echo
        echo "** Don't have a product spec for: '$product'"
        echo "** Do you have the right repo manifest?"
        product=
    fi


    #再从selection中取出“-”后面的字符串到variant,然后调用函数check_variant判断variant是否符合要求。check_variant函数很简单,主要就是用到前文source流程中初始化为"user userdebug eng"的VARIANT_CHOICES数组,判断variant是否是数组成员之一,是则返回0,不是则返回1。
    local variant=$(echo -n $selection | sed -e "s/^[^\-]*-//")
    check_variant $variant
    if [ $? -ne 0 ]
    then
        echo
        echo "** Invalid variant: '$variant'"
        echo "** Must be one of ${VARIANT_CHOICES[@]}"
        variant=
    fi

    if [ -z "$product" -o -z "$variant" ]
    then
        echo
        return 1
    fi

    #导出以下准备好的宏变量供整个shell环境使用
    export TARGET_PRODUCT=$product
    export TARGET_BUILD_VARIANT=$variant
    export TARGET_BUILD_TYPE=release

    echo

    #设置PROMPT_COMMAND,ANDROID_BUILD_PATHS,JAVA_HOME和BUILD_ENV_SEQUENCE_NUMBER等等环境变量
    set_stuff_for_environment

    #打印最终准备好的环境变量
    printconfig
} 

Tips:set_stuff_for_environment 里面的set_java_home函数,可以根据实际情况修改java路径等,这样可以避免编译不同的android版本还需手动切换java版本

_lunch函数

实现lunch命令的补全的步骤:

首先定义了lunch函数

然后定义了_lunch函数

使用complete命令

# 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
}

++complete -F _lunch lunch++ 是上面代码中最关键的一行。

当bash在遇到lunch这个词的时候,会调用_lunch函数。
该函数会传入三个参数:要补全的命令名、当前的光标所在的词、当前光标所在的词的前一个词。
补全的结果需要存储到COMPREPLY变量中,以待bash获取。

gettop

function gettop
{
    local TOPFILE=build/core/envsetup.mk
    if [ -n "$TOP" -a -f "$TOP/$TOPFILE" ] ; then
        # The following circumlocution ensures we remove symlinks from TOP.
        (cd $TOP; PWD= /bin/pwd)
    else
        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
            local HERE=$PWD
            T=
            while [ \( ! \( -f $TOPFILE \) \) -a \( $PWD != "/" \) ]; do
                \cd ..
                T=`PWD= /bin/pwd -P`
                done
            \cd $HERE
            if [ -f "$T/$TOPFILE" ]; then
                echo $T
            fi
        fi
    fi
}

-n 变量是否存在

-a 逻辑与

-f 文件是否存在

PWD 打印当前目录

这个函数主要是获取Android源码的根目录,根据根目录下的build/core/envsetup.mk文件来判断的。

注意:调用这个函数的时候有个要求:就是你必须在源码根目录的子目录中,如果你处在根目录的上级目录则调用失败,因为这个函数是通过不停的调用cd ..命令,然后通过文件build/core/envsetup.mk来判断源码根目录的。

hmm


function hmm() {
cat <<EOF
Invoke ". build/envsetup.sh" from your shell to add the following functions to your environment:
- lunch:   lunch <product_name>-<build_variant>
- tapas:   tapas [<App1> <App2> ...] [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.
- 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.
- sepgrep: Greps on all local sepolicy files.
- sgrep:   Greps on all local source files.
- godir:   Go to the directory containing a file.

Environemnt 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=""
    for i in `cat $T/build/envsetup.sh | sed -n "/^[ \t]*function /s/function \([a-z_]*\).*/\1/p" | sort | uniq`; do
      A="$A  $i"
    done
    echo $A
}

帮助函数,cat $T/build/envsetup.sh | sed -n “/^[ \t]function /s/function ([a-z_]).*/\1/p” | sort | uniq 这句脚本主要是获取envsetup.sh里面的以function开头的函数,函数名都是字母,并将函数名打印出来。
sed -n “/^[ \t]*function /p” 是打印出以function 开头的行
s/function ([a-z_])./\1 这部分是将函数名提取出来
sort 排序
uniq 去重

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.
    if [ -f build/core/envsetup.mk -a -f Makefile ]; then
        $DRV make [email protected]
    else
        # 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
        local M=`echo $M|sed 's:'$T'/::'`
        if [ ! "$T" ]; then
            echo "Couldn't locate the top of the tree.  Try setting TOP."
            return 1
        elif [ ! "$M" ]; then
            echo "Couldn't locate a makefile from the current directory."
            return 1
        else
            for ARG in [email protected]; do
                case $ARG in
                  GET-INSTALL-PATH) GET_INSTALL_PATH=$ARG;;
                esac
            done
            if [ -n "$GET_INSTALL_PATH" ]; then
              MODULES=
              ARGS=GET-INSTALL-PATH
            else
              MODULES=all_modules
              ARGS=[email protected]
            fi
            ONE_SHOT_MAKEFILE=$M $DRV make -C $T -f build/core/main.mk $MODULES $ARGS
        fi
    fi
}

如果当前处在源码根目录下,且存在Makefile文件,直接执行make [email protected]命令。

如果不是在源码根目录下:循环调用cd .. 调用findmakefile函数查找Android.mk文件,直到根目录,将Android.mk文件的绝对路径返回给M.

如找到了:M=/home/admin/android/frameworks/base/Android.mk,调用: local M=echo $M|sed 's}'$T'/}}'这句就是将源码根目录/home/admin/android/去掉,取得相对目录后,M=frameworks/base/Android.mk

最后执行:ONE_SHOT_MAKEFILE=frameworks/base/android.mk make -C /home/admin/android all_modules

相当于make -C /home/admin/android all_modules ONE_SHOT_MAKEFILE=frameworks/base/android.mk

接下来会调用到:源码根目录下的Makefile中,我们发现内容是include build/core/main.mk,即走到了build/core/main.mk文件中。后面就设计到Makefile的问题,我们后面会分析。现在只需要知道mm命令是怎么调用makefile里面去就行

mmm

mmm 命令就是从指定目录下开始编译所有模块

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 "[email protected]" | awk -v RS=" " -v ORS=" " '/^-.*$/')
        local DIRS=$(echo "[email protected]" | awk -v RS=" " -v ORS=" " '/^[^-].*$/')
        for DIR in $DIRS ; do
            MODULES=`echo $DIR | sed -n -e 's/.*:\(.*$\)/\1/p' | sed 's/,/ /'`
            if [ "$MODULES" = "" ]; then
                MODULES=all_modules
            fi
            DIR=`echo $DIR | sed -e 's/:.*//' -e 's:/$::'`
            if [ -f $DIR/Android.mk ]; then
                local TO_CHOP=`(\cd -P -- $T && pwd -P) | wc -c | tr -d ' '`
                local TO_CHOP=`expr $TO_CHOP + 1`
                local START=`PWD= /bin/pwd`
                local MFILE=`echo $START | cut -c${TO_CHOP}-`
                if [ "$MFILE" = "" ] ; then
                    MFILE=$DIR/Android.mk
                else
                    MFILE=$MFILE/$DIR/Android.mk
                fi
                MAKEFILE="$MAKEFILE $MFILE"
            else
                case $DIR in
                  showcommands | snod | dist | incrementaljavac | *=*) ARGS="$ARGS $DIR";;
                  GET-INSTALL-PATH) GET_INSTALL_PATH=$DIR;;
                  *) echo "No Android.mk in $DIR."; return 1;;
                esac
            fi
        done
        if [ -n "$GET_INSTALL_PATH" ]; then
          ARGS=$GET_INSTALL_PATH
          MODULES=
        fi
        ONE_SHOT_MAKEFILE="$MAKEFILE" $DRV make -C $T -f build/core/main.mk $DASH_ARGS $MODULES $ARGS
    else
        echo "Couldn't locate the top of the tree.  Try setting TOP."
        return 1
    fi
}

首先T=$(gettop)获取源码根目录,

local DASH_ARGS=$(echo "[email protected]" | awk -v RS=" " -v ORS=" " '/^-.*$/') 

根据mmm后面的参数获取以空格隔开,以-开头的字符串,如:mmm -B frameworks/base 则DASH_ARGS = -B

local DIRS=$(echo "[email protected]" | awk -v RS=" " -v ORS=" " '/^[^-].*$/') 

根据mmm后面参数获取以空格隔开,不以-开头的字符串。如:DIRS=frameworks/base/

DIR=`echo $DIR | sed -e 's:/$::'` 

去掉DIR结尾的/,DIR=frameworks/base

if [ -f $DIR/Android.mk ] ;then
TO_CHOP=`(cd -P -- $T && pwd -P) | wc -c | tr -d ' '`

跳到源码根目录,pwd -P打印出来,获取到字符串长度如:/home/admin/androids TO_CHOP就是20

TO_CHOP=`expr $TO_CHOP + 1`

START=`PWD= /bin/pwd`

MFILE=`echo $START | cut -c${TO_CHOP}-`

获取当前目录字符串,去掉前面21个字符,如:当前目录为/home/admin/androids,则MFILE=”“

  if [ "$MFILE" = "" ] ; then
    MFILE=$DIR/Android.mk
   else
    MFILE=$MFILE/frameworks/base/Android.mk 
 fi

这里进行判断当前目录是否就是源码目录,如果是的话,MFILE为空,所以MFILe=frameworks/base/Android.mk

否则:MFILE=$MFILE/frameworks/base/Android.mk

总之:就是把当前目录的前面源码根目录去了,然后加上后面mmm -B **参数部分相对的目录,最后加上Android.mk就是,最后Android.mk文件的相对路径。

ONE_SHOT_MAKEFILE=”MAKEFILE” make -C TDASH_ARGS all_modules $ARGS

相当于:make -C /home/admin/android -B all_modules ONE_SHOT_MAKEFILE=frameworks/base/Android.mk

godir

function godir () {
    #如果没有带参数直接提示错误返回
    if [[ -z "$1" ]]; then
        echo "Usage: godir <regex>"
        return
    fi

    T=$(gettop)
    #如果OUT_DIR 不等于空创建OUT_DIR这个目录,设置FILELIST文件路径
    if [ ! "$OUT_DIR" = "" ]; then
        mkdir -p $OUT_DIR
        FILELIST=$OUT_DIR/filelist
    else
        FILELIST=$T/filelist
    fi

    #判断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
                # 对每行以类似以下的格式进行输出:
                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
}

godir 是在编译路径下搜索匹配模式的目录,然后跳转到此目录,同时可以用来快速查找文件的路径
如像查找frameworks/base/core/res/res/values在那些地方被overlay 了直接godir frameworks/base/core/res/res/values/ 查找包含这个路径的所有文件夹,及所有可能被覆盖的地方

sgrep

case `uname -s` in
    Darwin)
        function sgrep()
        {
            find -E . -name .repo -prune -o -name .git -prune -o  -type f -iregex '.*\.(c|h|cc|cpp|S|java|xml|sh|mk|aidl)' -print0 | xargs -0 grep --color -n "[email protected]"
        }

        ;;
    *)
        function sgrep()
        {   
            # 排除 .repo, .git目录
            # 并对后缀(c|h|cc|cpp|S|java|xml|sh|mk|aidl|vts)的文件执行grep模式搜索
            find . -name .repo -prune -o -name .git -prune -o  -type f -iregex '.*\.\(c\|h\|cc\|cpp\|S\|java\|xml\|sh\|mk\|aidl\)' -print0 | xargs -0 grep --color -n "[email protected]"
        }
        ;;
esac

这里为什么没有把out目录去了表示无法理解

其他代码搜索原理类似就不一一列举

相关标签: android build