Android源代码在编译之前,要先对编译环境进行初始化,其中最主要就是指定编译的类型和目标设备的型号。Android的编译类型主要有eng、userdebug和user三种,而支持的目标设备型号则是不确定的,它们由当前的源码配置情况所决定。为了确定源码支持的所有目标设备型号,Android编译系统在初始化的过程中,需要在特定的目录中加载特定的配置文件。接下来本文就对上述的初始化过程进行详细分析。

       对Android编译环境进行初始化很简单,分为两步。第一步是打开一个终端,并且将build/envsetup.sh加载到该终端中:

1. $ . ./build/envsetup.sh   
2. including device/asus/grouper/vendorsetup.sh  
3. including device/asus/tilapia/vendorsetup.sh  
4. including device/generic/armv7-a-neon/vendorsetup.sh  
5. including device/generic/armv7-a/vendorsetup.sh  
6. including device/generic/mips/vendorsetup.sh  
7. including device/generic/x86/vendorsetup.sh  
8. including device/lge/mako/vendorsetup.sh  
9. including device/samsung/maguro/vendorsetup.sh  
10. including device/samsung/manta/vendorsetup.sh  
11. including device/samsung/toroplus/vendorsetup.sh  
12. including device/samsung/toro/vendorsetup.sh  
13. including device/ti/panda/vendorsetup.sh  
14. including sdk/bash_completion/adb.bash


      从命令的输出可以知道,文件build/envsetup.sh在加载的过程中,又会在device目录中寻找那些名称为vendorsetup.sh的文件,并且也将它们加载到当前终端来。另外,在sdk/bash_completion目录下的adb.bash文件也会加载到当前终端来,它是用来实现adb命令的bash completion功能的。也就是说,加载了该文件之后,我们在运行adb相关的命令的时候,通过按tab键就可以帮助我们自动完成命令的输入。


      第二步是执行命令lunch,如下所示:


1. $ lunch  
2.   
3. You're building on Linux  
4.   
5. Lunch menu... pick a combo:  
6.      1. full-eng  
7.      2. full_x86-eng  
8.      3. vbox_x86-eng  
9.      4. full_mips-eng  
10.      5. full_grouper-userdebug  
11.      6. full_tilapia-userdebug  
12.      7. mini_armv7a_neon-userdebug  
13.      8. mini_armv7a-userdebug  
14.      9. mini_mips-userdebug  
15.      10. mini_x86-userdebug  
16.      11. full_mako-userdebug  
17.      12. full_maguro-userdebug  
18.      13. full_manta-userdebug  
19.      14. full_toroplus-userdebug  
20.      15. full_toro-userdebug  
21.      16. full_panda-userdebug  
22.   
23. Which would you like? [full-eng]


       我们看到lunch命令输出了一个Lunch菜单,该菜单列出了当前Android源码支持的所有设备型号及其编译类型。例如,第一项“full-eng”表示的设备“full”即为模拟器,并且编译类型为“eng”即为工程机。

       当我们选定了一个Lunch菜单项序号(1-16)之后,按回车键,就可以完成Android编译环境的初始化过程。例如,我们选择1,可以看到以下输出:

1. Which would you like? [full-eng] 1  
2.   
3. ============================================  
4. PLATFORM_VERSION_CODENAME=REL  
5. PLATFORM_VERSION=4.2  
6. TARGET_PRODUCT=full  
7. TARGET_BUILD_VARIANT=eng  
8. TARGET_BUILD_TYPE=release  
9. TARGET_BUILD_APPS=  
10. TARGET_ARCH=arm  
11. TARGET_ARCH_VARIANT=armv7-a  
12. HOST_ARCH=x86  
13. HOST_OS=linux  
14. HOST_OS_EXTRA=Linux-3.8.0-31-generic-x86_64-with-Ubuntu-13.04-raring  
15. HOST_BUILD_TYPE=release  
16. BUILD_ID=JOP40C  
17. OUT_DIR=out  
18. ============================================


       我们可以看到,lunch命令帮我们设置好了很多环境变量。通过设置这些环境变量,就配置好了Android编译环境。

       通过图1我们就可以直观地看到Android编译环境初始化完成后,我们所获得的东西:

android编译openssl Android编译环境_加载

图1 Android编译环境初始化完成之后


android编译openssl Android编译环境_android编译openssl_02

图2 自己的系统分析

       总体来说,Android编译环境初始化完成之后,获得了以下三样东西:

       1. 将vendor和device目录下的vendorsetup.sh文件加载到了当前终端;

       2. 新增了lunch、m、mm和mmm等命令;

       3. 通过执行lunch命令设置好了TARGET_PRODUCT、TARGET_BUILD_VARIANT、TARGET_BUILD_TYPE和TARGET_BUILD_APPS等环境变量。  

       接下来我们就主要分析build/envsetup.sh文件的加载过程以及lunch命令的执行过程。

       一. 文件build/envsetup.sh的加载过程

       文件build/envsetup.sh是一个bash shell脚本,从它里面定义的函数hmm可以知道,它提供了lunch、m、mm和mmm等命令供我们初始化编译环境或者编译Android源码。

       函数hmm的实现如下所示:



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] [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.  
9. - mmm:     Builds all of the modules in the supplied directories.  
10. - cgrep:   Greps on all local C/C++ files.  
11. - jgrep:   Greps on all local Java files.  
12. - resgrep: Greps on all local res/*.xml files.  
13. - godir:   Go to the directory containing a file.  
14.   
15. Look at the source to view more functions. The complete list is:  
16. EOF  
17.     T=$(gettop)  
18.     local A  
19.     A=""  
20.     for i in `cat $T/build/envsetup.sh | sed -n "/^function /s/function \([a-z_]*\).*/\1/p" | sort`; do  
21.       A="$A $i"  
22.     done  
23.     echo $A  
24. }


       我们在当前终端中执行hmm命令即可以看到函数hmm的完整输出。


       函数hmm主要完成三个工作:

       1. 调用另外一个函数gettop获得Android源码的根目录T。 

       2. 通过cat命令显示一个Here Document,说明$T/build/envsetup.sh文件加载到当前终端后所提供的主要命令。

       3. 通过sed命令解析$T/build/envsetup.sh文件,并且获得在里面定义的所有函数的名称,这些函数名称就是$T/build/envsetup.sh文件加载到当前终端后提供的所有命令。

       注意,sed命令是一个强大的文本分析工具,它以行为单位为执行文本替换、删除、新增和选取等操作。函数hmm通过执行以下的sed命令来获得在$T/build/envsetup.sh文件定义的函数的名称:



1. sed -n "/^function /s/function \([a-z_]*\).*/\1/p"


       它表示对所有以“function ”开头的行,如果紧接在“function ”后面的字符串仅由字母a-z和下横线(_)组成,那么就将这个字符串提取出来。这正好就对应于shell脚本里面函数的定义。

       文件build/envsetup.sh除了定义一堆函数之外,还有一个重要的代码段,如下所示:


1. # Execute the contents of any vendorsetup.sh files we can find.  
2. for f in `/bin/ls vendor/*/vendorsetup.sh vendor/*/*/vendorsetup.sh device/*/*/vendorsetup.sh 2> /dev/null`  
3. do  
4.     echo "including $f"  
5.     . $f  
6. done  
7. unset f


        这个for循环遍历vendor目录下的一级子目录和二级子目录以及device目录下的二级子目录中的vendorsetup.sh文件,并且通过source命令(.)将它们加载当前终端来。vendor和device相应子目录下的vendorsetup.sh文件的实现很简单,它们主要就是添加相应的设备型号及其编译类型支持到Lunch菜单中去。

        例如,device/samsung/maguro目录下的vendorsetup.sh文件的实现如下所示:


    1. add_lunch_combo full_maguro-userdebug


            它调用函数add_lunch_combo添加一个名称为“full_maguro-userdebug”的菜单项到Lunch菜单去。

            函数add_lunch_combo定义在build/envsetup.sh文件中,它的实现如下所示:



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


            传递给函数add_lunch_combo的参数保存在位置参数$1中,接着又保存在一个本地变量new_combo中,用来表示一个要即将要添加的Lunch菜单项。函数首先是在数组LUNCH_MENU_CHOICES中检查要添加的菜单项是否已经存在。只有在不存在的情况下,才会将它添加到数组LUNCH_MENU_CHOICES中去。注意,${LUNCH_MENU_CHOICES[@]}表示数组LUNCH_MENU_CHOICES的所有元素。

            数组LUNCH_MENU_CHOICES是定义在文件build/envsetup.sh的一个全局变量,当文件build/envsetup.sh被加载的时候,这个数组会被初始化为化full-eng、full_x86-eng、vbox_x86-eng和full_mips-eng,如下所示:

    1. # add the default one here  
    2. add_lunch_combo full-eng  
    3. add_lunch_combo full_x86-eng  
    4. add_lunch_combo vbox_x86-eng  
    5. add_lunch_combo full_mips-eng


           这样当文件build/envsetup.sh加载完成之后,数组LUNCH_MENU_CHOICES就包含了当前源码支持的所有设备型号及其编译类型,于是当接下来我们执行lunch命令的时候,就可以通过数组LUNCH_MENU_CHOICES看到一个完整的Lunch藤蔓。

           二. lunch命令的执行过程

           lunch命令实际上是定义在文件build/envsetup.sh的一个函数,它的实现如下所示:

    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? [full-eng] "  
    10.         read answer  
    11.     fi  
    12.   
    13.     local selection=  
    14.   
    15.     if [ -z "$answer" ]  
    16.     then  
    17.         selection=full-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. }