SuperIC社区_
标题: Android编译系统环境初始化过程分析(1) [打印本页]
作者: liuwei    时间: 2016-9-28 14:04
标题: Android编译系统环境初始化过程分析(1)
Android源代码在编译之前,要先对编译环境进行初始化,其中最主要就是指定编译的类型和目标设备的型号。Android的编译类型主要有eng、userdebug和user三种,而支持的目标设备型号则是不确定的,它们由当前的源码配置情况所决定。为了确定源码支持的所有目标设备型号,Android编译系统在初始化的过程中,需要在特定的目录中加载特定的配置文件。接下来本文就对上述的初始化过程进行详细分析。
       对Android编译环境进行初始化很简单,分为两步。第一步是打开一个终端,并且将build/envsetup.sh加载到该终端中:
[html] view plain copy
 

- $ . ./build/envsetup.sh   
 - including device/asus/grouper/vendorsetup.sh  
 - including device/asus/tilapia/vendorsetup.sh  
 - including device/generic/armv7-a-neon/vendorsetup.sh  
 - including device/generic/armv7-a/vendorsetup.sh  
 - including device/generic/mips/vendorsetup.sh  
 - including device/generic/x86/vendorsetup.sh  
 - including device/lge/mako/vendorsetup.sh  
 - including device/samsung/maguro/vendorsetup.sh  
 - including device/samsung/manta/vendorsetup.sh  
 - including device/samsung/toroplus/vendorsetup.sh  
 - including device/samsung/toro/vendorsetup.sh  
 - including device/ti/panda/vendorsetup.sh  
 - including sdk/bash_completion/adb.bash  
 
      从命令的输出可以知道,文件build/envsetup.sh在加载的过程中,又会在device目录中寻找那些名称为vendorsetup.sh的文件,并且也将它们加载到当前终端来。另外,在sdk/bash_completion目录下的adb.bash文件也会加载到当前终端来,它是用来实现adb命令的bash completion功能的。也就是说,加载了该文件之后,我们在运行adb相关的命令的时候,通过按tab键就可以帮助我们自动完成命令的输入。关于bash completion的知识,可以参考官方文档: http://www.gnu.org/s/bash/manual/bash.html#Programmable-Completion。
      第二步是执行命令lunch,如下所示:
[html] view plain copy
 

- $ lunch  
 -   
 - You're building on Linux  
 -   
 - Lunch menu... pick a combo:  
 -      1. full-eng  
 -      2. full_x86-eng  
 -      3. vbox_x86-eng  
 -      4. full_mips-eng  
 -      5. full_grouper-userdebug  
 -      6. full_tilapia-userdebug  
 -      7. mini_armv7a_neon-userdebug  
 -      8. mini_armv7a-userdebug  
 -      9. mini_mips-userdebug  
 -      10. mini_x86-userdebug  
 -      11. full_mako-userdebug  
 -      12. full_maguro-userdebug  
 -      13. full_manta-userdebug  
 -      14. full_toroplus-userdebug  
 -      15. full_toro-userdebug  
 -      16. full_panda-userdebug  
 -   
 - Which would you like? [full-eng]   
 
       我们看到lunch命令输出了一个Lunch菜单,该菜单列出了当前Android源码支持的所有设备型号及其编译类型。例如,第一项“full-eng”表示的设备“full”即为模拟器,并且编译类型为“eng”即为工程机。
       当我们选定了一个Lunch菜单项序号(1-16)之后,按回车键,就可以完成Android编译环境的初始化过程。例如,我们选择1,可以看到以下输出:
[html] view plain copy
 

- Which would you like? [full-eng] 1  
 -   
 - ============================================  
 - PLATFORM_VERSION_CODENAME=REL  
 - PLATFORM_VERSION=4.2  
 - TARGET_PRODUCT=full  
 - TARGET_BUILD_VARIANT=eng  
 - TARGET_BUILD_TYPE=release  
 - TARGET_BUILD_APPS=  
 - TARGET_ARCH=arm  
 - TARGET_ARCH_VARIANT=armv7-a  
 - HOST_ARCH=x86  
 - HOST_OS=linux  
 - HOST_OS_EXTRA=Linux-3.8.0-31-generic-x86_64-with-Ubuntu-13.04-raring  
 - HOST_BUILD_TYPE=release  
 - BUILD_ID=JOP40C  
 - OUT_DIR=out  
 - ============================================  
 
       我们可以看到,lunch命令帮我们设置好了很多环境变量。通过设置这些环境变量,就配置好了Android编译环境。
       通过图1我们就可以直观地看到Android编译环境初始化完成后,我们所获得的东西:

图1 Android编译环境初始化完成之后
       总体来说,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的实现如下所示:
[plain] view plain copy
 

- function hmm() {  
 - cat <<EOF  
 - Invoke ". build/envsetup.sh" from your shell to add the following functi** to your environment:  
 - - lunch:   lunch <product_name>-<build_variant>  
 - - tapas:   tapas [<App1> <App2> ...] [arm|x86|mips] [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.  
 - - mmm:     Builds all of the modules in the supplied directories.  
 - - cgrep:   Greps on all local C/C++ files.  
 - - jgrep:   Greps on all local Java files.  
 - - resgrep: Greps on all local res/*.xml files.  
 - - godir:   Go to the directory containing a file.  
 -   
 - Look at the source to view more functi**. The complete list is:  
 - EOF  
 -     T=$(gettop)  
 -     local A  
 -     A=""  
 -     for i in `cat $T/build/envsetup.sh | sed -n "/^function /s/function [a−z]∗.*/\1/p" | sort`; do  
 -       A="$A $i"  
 -     done  
 -     echo $A  
 - }  
 
       我们在当前终端中执行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文件定义的函数的名称:
[plain] view plain copy
 

- sed -n "/^function /s/function [a−z]∗.*/\1/p"  
 
       它表示对所有以“function ”开头的行,如果紧接在“function ”后面的字符串仅由字母a-z和下横线(_)组成,那么就将这个字符串提取出来。这正好就对应于shell脚本里面函数的定义。
       文件build/envsetup.sh除了定义一堆函数之外,还有一个重要的代码段,如下所示:
[plain] view plain copy
 

- # Execute the contents of any vendorsetup.sh files we can find.  
 - for f in `/bin/ls vendor/*/vendorsetup.sh vendor/*/*/vendorsetup.sh device/*/*/vendorsetup.sh 2> /dev/null`  
 - do  
 -     echo "including $f"  
 -     . $f  
 - done  
 - unset f  
 
        这个for循环遍历vendor目录下的一级子目录和二级子目录以及device目录下的二级子目录中的vendorsetup.sh文件,并且通过source命令(.)将它们加载当前终端来。vendor和device相应子目录下的vendorsetup.sh文件的实现很简单,它们主要就是添加相应的设备型号及其编译类型支持到Lunch菜单中去。
        例如,device/samsung/maguro目录下的vendorsetup.sh文件的实现如下所示:
[plain] view plain copy
 

- add_lunch_combo full_maguro-userdebug  
 
        它调用函数add_lunch_combo添加一个名称为“full_maguro-userdebug”的菜单项到Lunch菜单去。
        函数add_lunch_combo定义在build/envsetup.sh文件中,它的实现如下所示:
[plain] view plain copy
 

- 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)  
 - }  
 
        传递给函数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,如下所示:
[plain] view plain copy
 

- # add the default one here  
 - add_lunch_combo full-eng  
 - add_lunch_combo full_x86-eng  
 - add_lunch_combo vbox_x86-eng  
 - add_lunch_combo full_mips-eng  
 
       这样当文件build/envsetup.sh加载完成之后,数组LUNCH_MENU_CHOICES就包含了当前源码支持的所有设备型号及其编译类型,于是当接下来我们执行lunch命令的时候,就可以通过数组LUNCH_MENU_CHOICES看到一个完整的Lunch藤蔓。
       二. lunch命令的执行过程
       lunch命令实际上是定义在文件build/envsetup.sh的一个函数,它的实现如下所示:
[plain] view plain copy
 

- function lunch()  
 - {  
 -     local answer  
 -   
 -     if [ "$1" ] ; then  
 -         answer=$1  
 -     else  
 -         print_lunch_menu  
 -         echo -n "Which would you like? [full-eng] "  
 -         read answer  
 -     fi  
 -   
 -     local selection=  
 -   
 -     if [ -z "$answer" ]  
 -     then  
 -         selection=full-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  
 -   
 -     if [ -z "$selection" ]  
 -     then  
 -         echo  
 -         echo "Invalid lunch combo: $answer"  
 -         return 1  
 -     fi  
 -   
 -     export TARGET_BUILD_APPS=  
 -   
 -     local product=$(echo -n $selection | sed -e "s/-.*$//")  
 -     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  
 -   
 -     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  
 -   
 -     export TARGET_PRODUCT=$product  
 -     export TARGET_BUILD_VARIANT=$variant  
 -     export TARGET_BUILD_TYPE=release  
 -   
 -     echo  
 -   
 -     set_stuff_for_environment  
 -     printconfig  
 - }  
 
        函数lunch的执行逻辑如下所示:
        1. 检查是否带有参数,即位置参数$1是否等于空。如果不等于空的话,就表明带有参数,并且该参数是用来指定要编译的设备型号及其编译类型的。如果等于空的话,那么就调用另外一个函数print_lunch_menu来显示Lunch菜单项,并且通过调用read函数来等待用户输入。无论通过何种方式,最终变量answer的值就保存了用户所指定的备型号及其编译类型。
        2. 对变量answer的值的合法性进行检查。如果等于空的话,就将它设置为默认值“full-eng”。如果不等于空的话,就分为三种情况考虑。第一种情况是值为数字,那么就需要确保该数字的大小不能超过Lunch菜单项的个数。在这种情况下,会将输入的数字索引到数组LUNCH_MENU_CHOICES中去,以便获得一个用来表示设备型号及其编译类型的文本。第二种情况是非数字文本,那么就需要确保该文本符合<product>-<variant>的形式,其中<product>表示设备型号,而<variant>表示编译类型 。第三种情况是除了前面两种情况之外的所有情况,这是非法的。经过合法性检查后,变量selection代表了用户所指定的备型号及其编译类型,如果它的值是非法的,即它的值等于空,那么函数lunch就不往下执行了。
        3. 接下来是解析变量selection的值,也就是通过sed命令将它的<product>和<variant>值提取出来,并且分别保存在变量product和variant中。提取出来的product和variant值有可能是不合法的,因此需要进一步通过调用函数check_product和check_variant来检查。一旦检查失败,也就是函数check_product和check_variant的返回值$?等于非0,那么函数lunch就不往下执行了。
        4. 通过以上合法性检查之后,就将变量product和variant的值保存在环境变量TARGET_PRODUCT和TARGET_BUILD_VARIANT中。此外,另外一个环境变量TARGET_BUILD_TYPE的值会被设置为"release",表示此次编译是一个release版本的编译。另外,前面还有一个环境变量TARGET_BUILD_APPS,它的值被函数lunch设置为空,用来表示此次编译是对整个系统进行编译。如果环境变量TARGET_BUILD_APPS的值不等于空,那么就表示此次编译是只对某些APP模块进行编译,而这些APP模块就是由环境变量TARGET_BUILD_APPS来指定的。
        5. 调用函数set_stuff_for_environment来配置环境,例如设置Java SDK路径和交叉编译工具路径等。
        6. 调用函数printfconfig来显示已经配置好的编译环境参数。
        在上述执行过程中,函数check_product、check_variant和printconfig是比较关键的,因此接下来我们就继续分析它们的实现。
        函数check_product定义在文件build/envsetup.sh中,它的实现如下所示:
[plain] view plain copy
 

- # 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  
 -     CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core \  
 -         TARGET_PRODUCT=$1 \  
 -         TARGET_BUILD_VARIANT= \  
 -         TARGET_BUILD_TYPE= \  
 -         TARGET_BUILD_APPS= \  
 -         get_build_var TARGET_DEVICE > /dev/null  
 -     # hide successful answers, but allow the errors to show  
 - }  
 
        函数gettop用来返回Android源代码工程的根目录。函数check_product需要在Android源代码工程根目录或者子目录下调用。否则的话,函数check_product就出错返回。
        接下来函数check_product设置几个环境变量,其中最重要的是前面三个CALLED_FROM_SETUP、BUILD_SYSTEM和TARGET_PRODUCT。环境变量CALLED_FROM_SETUP的值等于true表示接下来执行的make命令是用来初始化Android编译环境的。环境变量BUILD_SYSTEM用来指定Android编译系统的核心目录,它的值被设置为build/core。环境变量TARGET_PRODUCT用来表示要检查的产品名称(也就是我们前面说的设备型号),它的值被设置为$1,即函数check_product的调用参数。
        最后函数check_product调用函数get_build_var来检查由环境变量TARGET_PRODUCT指定的产品名称是否合法,注意,它的调用参数为TARGET_DEVICE。
        函数get_build_var定义在文件build/envsetup.sh中,它的实现如下所示:
[plain] view plain copy
 

- # Get the exact value of a build variable.  
 - function get_build_var()  
 - {  
 -     T=$(gettop)  
 -     if [ ! "$T" ]; then  
 -         echo "Couldn't locate the top of the tree.  Try setting TOP." >&2  
 -         return  
 -     fi  
 -     CALLED_FROM_SETUP=true BUILD_SYSTEM=build/core \  
 -       make --no-print-directory -C "$T" -f build/core/config.mk dumpvar-$1  
 - }  
 
        这里就可以看到,函数get_build_var实际上就是通过make命令在Android源代码工程根目录中执行build/core/config.mk文件,并且将make目标设置为dumpvar-$1,也就是dumpvar-TARGET_DEVICE。
        文件build/core/config.mk的内容比较多,这里我们只关注与产品名称合法性检查相关的逻辑,这些逻辑也基本上涵盖了Android编译系统初始化的逻辑,如下所示:
[plain] view plain copy
 

- ......  
 -   
 - # ---------------------------------------------------------------  
 - # Define most of the global variables.  These are the ones that  
 - # are specific to the user's build configuration.  
 - include $(BUILD_SYSTEM)/envsetup.mk  
 -   
 - # Boards may be defined under $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)  
 - # or under vendor/*/$(TARGET_DEVICE).  Search in both places, but  
 - # make sure only one exists.  
 - # Real boards should always be associated with an OEM vendor.  
 - board_config_mk := \  
 -     $(strip $(wildcard \  
 -         $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)/BoardConfig.mk \  
 -         device/*/$(TARGET_DEVICE)/BoardConfig.mk \  
 -         vendor/*/$(TARGET_DEVICE)/BoardConfig.mk \  
 -     ))  
 - ifeq ($(board_config_mk),)  
 -   $(error No config file found for TARGET_DEVICE $(TARGET_DEVICE))  
 - endif  
 - ifneq ($(words $(board_config_mk)),1)  
 -   $(error Multiple board config files for TARGET_DEVICE $(TARGET_DEVICE): $(board_config_mk))  
 - endif  
 - include $(board_config_mk)  
 -   
 - ......  
 -   
 - include $(BUILD_SYSTEM)/dumpvar.mk  
 
       上述代**要就是将envsetup.mk、BoardConfig,mk和dumpvar.mk三个Makefile片段文件加载进来。其中,envsetup.mk文件位于$(BUILD_SYSTEM)目录中,也就是build/core目录中,BoardConfig.mk文件的位置主要就是由环境变量TARGET_DEVICE来确定,它是用来描述目标产品的硬件模块信息的,例如CPU体系结构。环境变量TARGET_DEVICE用来描述目标设备,它的值是在envsetup.mk文件加载的过程中确定的。一旦目标设备确定后,就可以在$(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)、device/*/$(TARGET_DEVICE)和vendor/*/$(TARGET_DEVICE)目录中找到对应的BoradConfig.mk文件。注意,变量SRC_TARGET_DIR的值等于build/target。最后,dumpvar.mk文件也是位于build/core目录中,它用来打印已经配置好的编译环境信息。
老罗的新浪微博:
http://weibo.com/shengyangluo
,欢迎关注!
| 欢迎光临 SuperIC社区_ (/) | 
Powered by Discuz! X3.3 |