zoukankan      html  css  js  c++  java
  • android编译系统分析(一)source build/envsetup.sh与lunch


    http://blog.csdn.net/u011913612/article/details/51878356


    虽然已经有很多人分析过Android的编译系统的代码了,我也看过他们的博客,也学到了不少知识,但单纯的看别人分析,终究还是理解的不深入,所以,我还是要自己再认真的分析一遍。

    想想我们编译android系统的过程:

    首先:source build/envsetup.sh

    其次:lunch    ---选择一个特定的类型

    最后:make

    按着这个顺序,追踪这看似简单的几步,到底有哪些背后的秘密?   

    1. source build/envsetup.sh

    这个文件虽然很大,但暂且不需要统统看一遍。它里面定义了很多函数,这些函数在使用的时候再具体详细学习,现在主要看看这个脚本做的事情。即便如此,打开这个脚本后第一个函数还是非常吸引人的,因为它里面介绍了这个脚本主要要做的事情:

    1. function hmm() {  
    2. cat <<EOF  
    3. Invoke ". build/envsetup.sh" from your shell to add the following functions to your environment:  
    4. - lunch:   lunch <product_name>-<build_variant>  
    5. - tapas:   tapas [<App1> <App2> ...] [arm|x86|mips|armv5|arm64|x86_64|mips64] [eng|userdebug|user]  
    6. - croot:   Changes directory to the top of the tree.  
    7. - m:       Makes from the top of the tree.  
    8. - mm:      Builds all of the modules in the current directory, but not their dependencies.  
    9. - mmm:     Builds all of the modules in the supplied directories, but not their dependencies.  
    10.            To limit the modules being built use the syntax: mmm dir/:target1,target2.  
    11. - mma:     Builds all of the modules in the current directory, and their dependencies.  
    12. - mmma:    Builds all of the modules in the supplied directories, and their dependencies.  
    13. - cgrep:   Greps on all local C/C++ files.  
    14. - ggrep:   Greps on all local Gradle files.  
    15. - jgrep:   Greps on all local Java files.  
    16. - resgrep: Greps on all local res/*.xml files.  
    17. - mangrep: Greps on all local AndroidManifest.xml files.  
    18. - sepgrep: Greps on all local sepolicy files.  
    19. - sgrep:   Greps on all local source files.  
    20. - godir:   Go to the directory containing a file.  
    21.   
    22. Environemnt options:  
    23. - SANITIZE_HOST: Set to 'true' to use ASAN for all host modules. Note that  
    24.                  ASAN_OPTIONS=detect_leaks=0 will be set by default until the  
    25.                  build is leak-check clean.  
    26.   
    27. Look at the source to view more functions. The complete list is:  
    28. EOF  
    29.     T=$(gettop)  
    30.     local A  
    31.     A=""  
    32.     for i in `cat $T/build/envsetup.sh | sed -n "/^[  ]*function /s/function [az].*/1/p" | sort | uniq`; do  
    33.       A="$A $i"  
    34.     done  
    35.     echo $A  
    36. }  
    这个函数列出来主要是它介绍了这个脚本的一些功能,第一行cat <<EOP是一个HERE文档,意思就是把EOF后面到下一个EOF前面的内容当做一个文件,然后cat 会接收这个文件的内容,而cat默认的输出是标准输出,也就是这个文件的内容会被打印到屏幕上来。这些内容介绍了这个脚本的用法和功能,用法就是“. build/envsetup.sh”注意.后面有个空格,这个.命令就是source命令,也就是说你也可以执行“source build/envsetup.sh”。此外,这个函数介绍了很多函数的功能,比如lunch,m,mm,mmm,cgrep等。

    函数只有在调用到它的时候才会执行,所以暂时统统不看,现在只看函数外面的内容:

    1. # Clear this variable.  It will be built up again when the vendorsetup.sh  
    2. # files are included at the end of this file.  
    3. unset LUNCH_MENU_CHOICES  
    这里调用了unset命令,unset是一个bash命令,它会删除给定的变量。也就是说它会删除LUNCH_MENU_CHOICES变量。既然这里刻意把它删除了,那么它肯定要立刻重新构造这个变量了,果然:
    1. # add the default one here  
    2. add_lunch_combo aosp_arm-eng  
    3. add_lunch_combo aosp_arm64-eng  
    4. add_lunch_combo aosp_mips-eng  
    5. add_lunch_combo aosp_mips64-eng  
    6. add_lunch_combo aosp_x86-eng  
    7. add_lunch_combo aosp_x86_64-eng  
    这几行调用了add_lunch_combo函数,并且传入了一些参数,这使得我们执行lunch函数的时候,多了这几条可以选择的选项。

    而这个函数,就是就是重新构造LUNCH_MENU_CHOICES变量:

    1. function add_lunch_combo()  
    2. {  
    3.     local new_combo=$1  
    4.     local c  
    5.     for c in ${LUNCH_MENU_CHOICES[@]} ; do  
    6.         if [ "$new_combo" = "$c" ] ; then  
    7.             return  
    8.         fi  
    9.     done  
    10.     LUNCH_MENU_CHOICES=(${LUNCH_MENU_CHOICES[@]} $new_combo)  
    11. }  

    了解过shell编程就知道,$1表示传入函数的第一个参数,然后又定义了一个变量c。

    从for循环中可以看出,LUNCH_MENU_CHOICES是一个数组,在shell中,可以通过 "变量名[@]"或者“变量名[*]”的方式过得数组的所有项。然后注意比较数组中的每一项,如果数组中已经有传入的参数项,就继续返回,否则,就把新的传入的参数加入到LUNCH_MENU_CHOIES数组中。

    这个脚本虽然很长,但是正真执行的代码没有多少,就是说当执行source build/envsetup.sh的时候执行的代码没有多少,它里面大多数内容都是函数的定义。在该文件的最后,又执行了一点代码:

    1. if [ "x$SHELL" != "x/bin/bash" ]; then  
    2.     case `ps -o command -p $$` in  
    3.         *bash*)  
    4.             ;;  
    5.         *)  
    6.             echo "WARNING: Only bash is supported, use of other shell would lead to erroneous results"  
    7.             ;;  
    8.     esac  
    9. fi  
    10.   
    11. # Execute the contents of any vendorsetup.sh files we can find.  
    12. for f in `test -d device && find -L device -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort`   
    13.          `test -d vendor && find -L vendor -maxdepth 4 -name 'vendorsetup.sh' 2> /dev/null | sort`  
    14. do  
    15.     echo "including $f"  
    16.     . $f  
    17. done  
    18. unset f  


    前面的if语句块就是检查支持的shell解析器。后面的for语句块比较关键。

    这里使用了shell的反引号的用法,它的作用就是把反引号的内容当做shell命令来执行。然后把执行结果使用echo输出到屏幕。反引号中,首先使用test命令测试 device目录是否存在,存在的话就查找vendorsetup.sh脚本,并且设定了查找深度为4层目录。2> /dev/null 是shell中的重定向语法,这里把标准错误重定向到/dev/null中,也就是,把错误统统删掉,左后把找到的vendorsetup.sh脚本使用sort命令进行排序。

    . $f相当于source $f,也就是source /device和/vendor下找到的所有vendorsetup.sh脚本。然后,这个脚本就分析结束了,接下来,自然是要去分析/device和/vendor下的vendorsetup.sh脚本了。


    2./device/vendor下的vendorsetup.sh

    执行一下source build/envsetup.sh,会打印一下内容:

    1. including device/generic/mini-emulator-arm64/vendorsetup.sh  
    2. including device/generic/mini-emulator-armv7-a-neon/vendorsetup.sh  
    3. including device/generic/mini-emulator-mips/vendorsetup.sh  
    4. including device/generic/mini-emulator-x86_64/vendorsetup.sh  
    5. including device/generic/mini-emulator-x86/vendorsetup.sh  
    这里只列出了一部分,那以第一条为例,看看它的内容:
    1. add_lunch_combo mini_emulator_arm64-userdebug  
    这个函数我们已经分析过了,而且这个文件也只有这么一行,这里列出的这几个vendorsetup.sh脚本都是只有这么一行,他就是在lunch的菜单中添加一项。当然也不都是如此,在有些厂家的vendorsetup.sh也会做一些其他的工作,但是,不管做多少其他的事情,第一件事情似乎都是一定的,就是调用add_lunch_combo 添加一项。

    因此,总结来说,envsetup.sh脚本做了这样的事情:


    3.lunch

    编译android的时候,执行完souce build/envsetup.sh,我们还需要执行lunch,选择一个特定的单板。

    1. function lunch()  
    2. {  
    3.     local answer  
    4.   
    5.     if [ "$1" ] ; then  
    6.         answer=$1  
    7.     else  
    8.         print_lunch_menu  
    9.         echo -n "Which would you like? [aosp_arm-eng] "  
    10.         read answer  
    11.     fi  
    12.   
    13.     local selection=  
    14.   
    15.     if [ -z "$answer" ]  
    16.     then  
    17.         selection=aosp_arm-eng  
    18.     elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")  
    19.     then  
    20.         if [ $answer -le ${#LUNCH_MENU_CHOICES[@]} ]  
    21.         then  
    22.             selection=${LUNCH_MENU_CHOICES[$(($answer-1))]}  
    23.         fi  
    24.     elif (echo -n $answer | grep -q -e "^[^-][^-]*-[^-][^-]*$")  
    25.     then  
    26.         selection=$answer  
    27.     fi  
    28.   
    29.     if [ -z "$selection" ]  
    30.     then  
    31.         echo  
    32.         echo "Invalid lunch combo: $answer"  
    33.         return 1  
    34.     fi  
    35.   
    36.     export TARGET_BUILD_APPS=  
    37.   
    38.     local product=$(echo -n $selection | sed -e "s/-.*$//")  
    39.     check_product $product  
    40.     if [ $? -ne 0 ]  
    41.     then  
    42.         echo  
    43.         echo "** Don't have a product spec for: '$product'"  
    44.         echo "** Do you have the right repo manifest?"  
    45.         product=  
    46.     fi  
    47.   
    48.     local variant=$(echo -n $selection | sed -e "s/^[^-]*-//")  
    49.     check_variant $variant  
    50.     if [ $? -ne 0 ]  
    51.     then  
    52.         echo  
    53.         echo "** Invalid variant: '$variant'"  
    54.         echo "** Must be one of ${VARIANT_CHOICES[@]}"  
    55.         variant=  
    56.     fi  
    57.   
    58.     if [ -z "$product" -o -z "$variant" ]  
    59.     then  
    60.         echo  
    61.         return 1  
    62.     fi  
    63.   
    64.     export TARGET_PRODUCT=$product  
    65.     export TARGET_BUILD_VARIANT=$variant  
    66.     export TARGET_BUILD_TYPE=release  
    67.   
    68.     echo  
    69.   
    70.     set_stuff_for_environment  
    71.     printconfig  
    72. }  
    这个函数首先判断有没有传入参数,如果传入了就把值赋给answer这个变量,如果没有传参数,也就是说知识执行了lunch,那么就会调用fprint_lunch_menu函数显示一份菜单出来,显示出来后,接受用户的输入。

    print_lunch_menu函数如下:

    1. function print_lunch_menu()  
    2. {  
    3.     local uname=$(uname)  
    4.     echo  
    5.     echo "You're building on" $uname  
    6.     echo  
    7.     echo "Lunch menu... pick a combo:"  
    8.   
    9.     local i=1  
    10.     local choice  
    11.     for choice in ${LUNCH_MENU_CHOICES[@]}  
    12.     do  
    13.         echo "     $i. $choice"  
    14.         i=$(($i+1))  
    15.     done  
    16.   
    17.     echo  
    18. }  
    可以看到,这个函数输出了一些头信息以后,就输出LUNCH_MENU_CHOICES数组,这里通过for遍历整个这个数组,打印每一项。这个数组在之前就已经分析过,它的每一项是通过调用add_lunch_combo函数添加进来的。

    1.不管执行lunch的时候有没有传入参数,最终都会将选择的结果存入到answer变量中,那么接下来,当然是检查输入的参数的合法性了。这里首先判断answer变量是不是为0,如果为零的话,selection变量就会赋值为aosp_arm-eng,但是如果不为零的话,首先会输出answer的值,使用echo -n $answer,-n选项是的输出的时候不输出换行符,answer变量的值并没有输出到屏幕上,而是通过管道传给了后面一条命令:grep -q -e "^[0-9][0-9]*$",这条命令从answer变量中搜寻以两位数字开头的字符串,如果找到,就认为是输入的是数字。然后进一步对这个数字做有没有越界的检查。如果这个数字小于LUNCH_MENU_CHOICES的大小,就会把LUNCH_MENU_CHOICES的地$answer-1项复制给selection变量。

    2.如果传入的不是数字,就会尝试匹配这样的字符串grep -q -e "^[^-][^-]*-[^-][^-]*$",-q标示quiet模式,也就是不打印信息,-e表示后面跟的是一个用于匹配的模式,这里,^标示匹配开始,[]中的^则标示排除,是转译字符,因为-可能是表示范围的符号。所以,这里匹配的是不以连续两个--开始,后面跟任意字符,然后必须有个-,最后又不以--结束的字符串。其实,这里就是期望得到product-varient的形式。也就是我们之前使用add_lunch_combo添加的那些字符串的格式。比如:aosp_arm-eng,product就是aosp_arm,varient就是eng.中间的-是必须的。如果发现符合要求的格式的话selection变量就会被$answer赋值,也就是说,selection其实就是一个product-varient模式的字符串。

    3.如果既不是数字,又不是合法的字符串,或者是数字,这个时候,selection应该就没有被赋值过,这个时候-z就成立了,那么就会提示你输入的东西不对。并且直接返回。

    如果输入合法,通过检测后,  export TARGET_BUILD_APPS=   这行导出了一个变量,但是它的值是空的。而之后的 local product=$(echo -n $selection | sed -e "s/-.*$//")这句则是得到了product部分,也就是把varient部分砍掉了。这里使用了sed编辑器,-e 表示执行多条命令,但这里只有一条,双引号的s表示替换,这里就是把-后面接任意字符,然后以任意字符结尾的部分替换为空,也就是砍掉-后面的了。得到produce后就开始检查product的合法性。 check_product $produc  ,这里使用了check_product函数:

    1. # check to see if the supplied product is one we can build  
    2. function check_product()  
    3. {  
    4.     T=$(gettop)  
    5.     if [ ! "$T" ]; then  
    6.         echo "Couldn't locate the top of the tree.  Try setting TOP." >&2  
    7.         return  
    8.     fi  
    9.         TARGET_PRODUCT=$1   
    10.         TARGET_BUILD_VARIANT=   
    11.         TARGET_BUILD_TYPE=   
    12.         TARGET_BUILD_APPS=   
    13.         get_build_var TARGET_DEVICE > /dev/null  
    14.     # hide successful answers, but allow the errors to show  
    15. }  

    函数的一开始就调用了gettop函数,所以,我们得先弄明白这个函数的功能:

    1. function gettop  
    2. {  
    3.     local TOPFILE=build/core/envsetup.mk  
    4.     if [ -n "$TOP" -a -f "$TOP/$TOPFILE" ] ; then  
    5.         # The following circumlocution ensures we remove symlinks from TOP.  
    6.         (cd $TOP; PWD= /bin/pwd)  
    7.     else  
    8.         if [ -f $TOPFILE ] ; then  
    9.             # The following circumlocution (repeated below as well) ensures  
    10.             # that we record the true directory name and not one that is  
    11.             # faked up with symlink names.  
    12.             PWD= /bin/pwd  
    13.         else  
    14.             local HERE=$PWD  
    15.             T=  
    16.             while [ !(f$TOPFILE ) -a ( $PWD != "/" ) ]; do  
    17.                 cd ..  
    18.                 T=`PWD= /bin/pwd -P`  
    19.             done  
    20.             cd $HERE  
    21.             if [ -f "$T/$TOPFILE" ]; then  
    22.                 echo $T  
    23.             fi  
    24.         fi  
    25.     fi  
    26. }  

    gettop函数一开始定义了一个局部变量TOPFILE,并且给他赋了值,然后是一个测试语句:if [ -n "$TOP" -a -f "$TOP/$TOPFILE" ] ; then,这里-n 是判断 $TOP是否不为空, -a 就是and的意思,和C语言中的&&相同, -f是判断给定的变量是不是文件,那么,这个测试语句就是如果 $TOP不为空 切同时 $TOP/$TOPFILE文件存在,就执行下面的代码:

    (cd $TOP; PWD= /bin/pwd)

    也就是进入到$TOP目录下,并且给PWD变量赋一个pwd命令的返回值,也就是当前目录的路劲。我试着在这个脚本中搜索TOP变量,发现它并没有出现并且赋值,所以,这里应该执行else部分。else中,首先判断build/core/envsetup.mk这个文件是否存在,当在源码顶层目录下的时候,这个文件是存在的,那么这里为真,然后PWD变量就是android代码的根目录。所以如果souce build/envsetup.sh的时候,如果处于android源码的顶级目录,那么这个函数就返回了。关于shell函数的返回值问题,还需要留意一下,当一个函数没有返回任何内容的时候,默认返回的是最后一条命令的执行结果,也就是这里的/bin/pwd的结果。那当然就是android源码的顶级目录了。这个时候如果不在顶级目录,build/core/envsetup.mk应该不存在,这个时候就会while循环不断的进入道上层目录,然后判断$TOPFILE是否存在,并且判断是否到达根目录了,如果这个文件不存在且没有到达根目录,那么就会一个往上一级目录查找。最终如果找到了这个文件,就意味着找到了android源码的顶层目录,并把这个路劲返回。前面的两次判断如果都成立的话也没有返回任何东西,是因为,当前目录肯定就是源码的顶级目录了。也就是说,这个函数就是找到源码的顶级目录,如果当前目录就是顶级目录,就什么也不返回,如果当前目录不是顶级目录,就返回顶级目录的路劲。
    再回过头来看check_product函数,可以看到在获取到android源码的顶级目录以后,就会判断这个T是不是空值,空的的话就说明没有获取到顶级目录,这个时候这个函数就直接返回了。如果一切正常,那么就会定义几个变量。

            TARGET_PRODUCT=$1
            TARGET_BUILD_VARIANT=
            TARGET_BUILD_TYPE=
            TARGET_BUILD_APPS=

    这几个变量只有一个变量都是全局变量,因为没有加local关键字修饰,它们中,只有第一个变量赋值为$1,也就是这个函数的第一个参数。目前,这几个变量的作用还不得而知,所以,我们继续向下分析。

    这个函数还有最后一件事情要做:

    get_build_var TARGET_DEVICE > /dev/null

    这里调用了get_build_var这个函数,这个函数如下:

    1. # Get the exact value of a build variable.  
    2. function get_build_var()  
    3. {  
    4.     T=$(gettop)  
    5.     if [ ! "$T" ]; then  
    6.         echo "Couldn't locate the top of the tree.  Try setting TOP." >&2  
    7.         return  
    8.     fi  
    9.     (cd $T; CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core   
    10.       command make --no-print-directory -f build/core/config.mk dumpvar-$1)  
    11. }  
    乍看之下,这个函数非常简单,一开始,就是获得android源码的顶层目录,然后检查是否获得成功,这在之前已经分析过了。做完这些以后,迎来了一个看着很奇怪的语句。这个语句先是进入到android源码的顶层目录,然后然后定义了两个变量,之后使用command执行了一条命令。command是一个shell中的命令,它的功能是执行指定的命令,
    这里就是执行make命令,-f给它指定了makefile,同时还传入了一个目标。然后,config.mk就会执行。

    这个函数,我们不妨感性认识一下先:在命令行中执行get_build_var TARGET_DEVICE

    可以看到打印了generic这个值。这个函数虽然分析起来比较复杂,但是它做的非常简单。config.mk会include dumpvar.mk,这个文件中会提取我们传入的dumpvar-TARGET_DEVICE变量中的TARGET_DEVICE,然后打印$(TARGET_DEVICE)。所以它做的事情很简单。

    这个函数执行完以后,有返回到lunch函数继续执行:

    1. if [ $? -ne 0 ]  
    2. then  
    3.     echo  
    4.     echo "** Don't have a product spec for: '$product'"  
    5.     echo "** Do you have the right repo manifest?"  
    6.     product=  
    7. fi  
    这里就是判断这个函数的返回值,-ne是不等于的意思,如果返回值不等于0,那么就出问题了。

    假定一切正常,继续执行代码:

    1. local variant=$(echo -n $selection | sed -e "s/^[^-]*-//")  
    2. check_variant $variant  
    3. if [ $? -ne 0 ]  
    4. then  
    5.     echo  
    6.     echo "** Invalid variant: '$variant'"  
    7.     echo "** Must be one of ${VARIANT_CHOICES[@]}"  
    8.     variant=  
    9. fi  
    这段代码时提取variant并且检查是否合法,不要忘了前面说过,add_lunch_combo添加的字符串就是product-variant格式的字符串,这两部分代码非常相似,就不具体分析这段代码了。

    下面的代码是:

    1. if [ -z "$product" -o -z "$variant" ]  
    2. then  
    3.     echo  
    4.     return 1  
    5. fi  
    检查得到的product和variant是不是为空,空就不可以 了。
    1. export TARGET_PRODUCT=$product  
    2. export TARGET_BUILD_VARIANT=$variant  
    3. export TARGET_BUILD_TYPE=release  
    然后把辛辛苦苦得到的product和variant变量复制给全局变量并且导出为环境变量。以后看到这几个变量,知道它们的值就可以了。

    然后调用了set_stuff_for_environment函数,这个函数内容如下:

    1. function set_stuff_for_environment()  
    2. {  
    3.     settitle  
    4.     set_java_home  
    5.     setpaths  
    6.     set_sequence_number  
    7.     export ANDROID_BUILD_TOP=$(gettop)  
    8.     # With this environment variable new GCC can apply colors to warnings/errors  
    9.     export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'  
    10.     export ASAN_OPTIONS=detect_leaks=0  
    11. }  

    这个函数会继续补充一些变量。其中set_java_home会检查导出JAVA_HOME这个环境变量,这个环境变量就是JDK坐在的路劲,setpaths函数会给PATH环境变量补充编译Android需要的一些路径。

    最后lunch调用了 printconfig函数,这个函数打印出了配置信息。

    至此,source build/envsetup.sh 和 lunch就分析完了。接下来将分析make 命令所做的事情。

    小结:source build/envsetup.sh会调用add_lunch_combo函数添加很多单板信息进来,同时还会查找/device和/vendor下的vendorsetup.sh文件,查找深度为4级目录,找到后就执行它,它里面至少会有这么一行:add_lunch_combo xxxx,继续添加单板信息。lunch函数则会打印出所有的单板信息供你选择,你输入选择后,luch命令会对你的选择做一系列检测,并从中提取出product和varient,并最终导出这些信息,供正式编译的时候使用。
  • 相关阅读:
    PostgreSQL 学习之使用psycopg2 操作之数据库不存在才创建
    终于还是离开这家公司了
    【转载】看完这篇文章,我奶奶都懂了https的原理
    PostgreSQL 函数学习
    Python 工作中比较实用的一些第三方库
    Python 获取对象的属性和方法—dir 函数
    Python 学习之type 函数的用法
    python 异常处理的基本语法
    pdb调试程序
    随机设置爬虫头部headers 信息
  • 原文地址:https://www.cnblogs.com/ztguang/p/12645313.html
Copyright © 2011-2022 走看看