android 编译环境 笔记

来源:互联网 发布:帝势艺术学院知乎 编辑:程序博客网 时间:2024/06/12 00:24

本文内容,全部转自老罗系列:http://blog.csdn.net/luoshengyang/article/details/18466779



  为了保持工程文件依赖关系的整体性,我们就必须使得整个工程只存在一个Makefile。当整个工程只存在一个Makefile时,我们就可以很容易地构造出如图4所示的文件依赖图:


图4 完整的文件依赖关系图

        整个工程只有一个Makefile,听起来似乎是一件很疯狂的事情,因为这个Makefile可能会变得无比庞大和复杂。其实不用担心,我们可以按照模块来将这个Makefile划分成一个个Makefile片段(fragement),然后通过Makefile的include指令来将这些Makefile片段组装在一个Makefile中。与递归Makefile相比,每一个模块现在拥有的是一个Makefile片段,而不是一个Makefile文件。这正是Android编译系统的设计思想和原则,也就是说,我们平时所编写的Android.mk编译脚本都只不过是整个Android编译系统的一个Makefile片段。


     在使用Android编译系统之前,我们需要打开一个shell进入到Android源码根目录中,并且在该shell中将build/envsetup.sh脚本文件source进来。脚本文件build/envsetup.sh被source到当前shell的过程中,会在vendor和device两个目录将厂商指定的envsetup.sh也source到当前shell当中,这样就可以获得厂商提供的产品配置信息。此外,脚本文件build/envsetup.sh还提供了以下几个重要的命令来帮助我们编译Android源码:

     1. lunch

        用来初始化编译环境,例如设置环境变量和指定目标产品型号。Lunch命令在执行的时候,主要做两件事情。第一件事情是设置TARGET_PRODUCT、TARGET_BUILD_VARIANT、TARGET_BUILD_TYPE和TARGET_BUILD_APPS等环境变量,用来指定目标产品类型和编译类型。第二件事情是通过make命令执行build/core/config.mk脚本,并且通过加载另外一个脚本build/core/dumpvar.mk打印出当前的编译环境配置信息。注意,build/core/config.mk和build/core/dumpvar.mk均为Makefile脚本,因此它们可以通过make命令来执行。另外,build/core/config.mk脚本还会加载一个名称为BoradConfig.mk的脚本以及build/core/envsetup.mk脚本来配置目标产品型号的相关信息。

       2. m

       相当于是在执行make命令。对整个Android源码进行编译。

       3. mm

       如果是在Android源码根目录下执行,那么就相当于是执行make命令对整个源码进行编译。如果是在Android源码根目录下的某一个子目录执行,那么就在会在从该子目录开始,一直往上一个目录直至到根目录,寻找是否存在一个Android.mk文件。如果存在的话,那么就通过make命令对该Android.mk文件描述的模块进行编译。

       4. mmm

       后面可以跟一个或者若干个目录。如果指定了多个目录,那么目录之间以空格分隔,并且每一个目录下都必须存在一个Android,mk文件。如果没有在目录后面通过冒号指定模块名称,那么在Android.mk文件中描述的所有模块都会被编译,否则只有指定的模块会被编译。如果需要同时指定多个模块,那么这些模块名称必须以逗号分隔。它的语法如下所示:

[html]view plaincopy在CODE上查看代码片派生到我的代码片
  1. mmm <dir-1> <dir-2> ... <dir-N>[:module-1,module-2,...,module-M]  
       该命令会通过make命令来执行Android源码根目录下的Makefile文件,该Makefile文件又会将build/core/main.mk加载进来。文件build/core/main.mk在加载的过程中,还会加载以下几个主要的文件:

       (1). build/core/config.mk

       该文件根据lunch命令所配置的产品信息在build/target/board、vendor或者device目录中找到对应的BoradConfig.mk文件,以及通过加载build/core/product_config.mk文件在build/target/product、vendor或者device目录中找到对应的AndroidProducts.mk文件,来进一步对编译环境进行配置,以便接下来编译指定模块时可以获得必要的信息。

       (2). build/core/definitions.mk

       该文件定义了在编译过程需要调用到的各种自定义函数。

       (3). 指定的Android.mk

       这些指定的Android.mk环境是由mmm命令通过环境变量ONE_SHOT_MAKEFILE传递给build/core/main.mk文件使用的。这些Android.mk文件一般还会通过环境变量BUILD_PACKAGE、BUILD_JAVA_LIBRARY、BUILD_STATIC_JAVA_LIBRARY、BUILD_SHARED_LIBRARY、BUILD_STATIC_LIBRARY、BUILD_EXECUTABLE和BUILD_PREBUILT将build/core/package.mk、build/core/java_library.mk、build/core/static_java_library.mk、build/core/shared_library.mk、build/core/static_library.mk、build/core/executable.mk和build/core/prebuilt.mk等编译片段模板文件加载进来,来表示要编译是APK、Java库、Linux动态库/静态库/可执行文件或者预先编译好的文件等等。

       (4). build/core/Makefile

       该文件包含了用来制作system.img、ramdisk.img、boot.img和recovery.img等镜像文件的脚本。


 对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键就可以帮助我们自动完成命令的输入。关于bash completion的知识,可以参考官方文档:

 http://www.gnu.org/s/bash/manual/bash.html#Programmable-Completion。


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


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

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

我们可以看到,lunch命令帮我们设置好了很多环境变量。通过设置这些环境变量,就配置好了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的加载过程

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

函数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文件定义的函数的


函数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文件定义的函数的


  二. lunch命令的执行过程       lunch命令实际上是定义在文件build/envsetup.sh的一个函数

        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来显示已经配置好的编译环境参数


Android编译系统环境是由build/core/config.mk、build/core/envsetup.mk、build/core/product_config.mk、AndroidProducts.mk和BoardConfig.mk等文件来完成的。这些mk文件涉及到非常多的细节,



Android编译环境初始化完成后,我们就可以用m/mm/mmm/make命令编译源代码了。当然,这要求每一个模块都有一个Android.mk文件。Android.mk实际上是一个Makefile脚本,用来描述模块编译信息。Android编译系统通过整合Android.mk文件完成编译过程。


从前面这篇文章可以知道,lunch命令其实是定义在build/envsetup.sh文件中的函数lunch提供的。与lunch命令一样,m、mm和mmm命令也分别是由定义在build/envsetup.sh文件中的函数m、mm和mmm提供的,而这三个函数又都是通过make命令来对源代码进行编译的。事实上,命令m就是对make命令的简单封装,并且是用来对整个Android源代码进行编译,而命令mm和mmm都是通过make命令来对Android源码中的指定模块进行编译。


 m:    函数m调用函数gettop得到的是Android源代码根目录T。在执行make命令的时候,先通过-C选项指定工作目录为T,即Android源代码根目录,接着又将执行命令m指定的参数$@作为命令make的参数。从这里就可以看出,命令m实际上就是对命令make的简单封装。

mm:    函数mm首先是判断当前目录是否就是Android源码根目录,即当前目录下是否存在一个build/core/envsetup.mk文件和一个Makefile文件。如果是的话,就将命令mm当作是一个普通的make命令来执行。否则的话,就调用函数findmakefile从当前目录开始一直往上寻找是否存在一个Android.mk文件。如果在寻找的过程中,发现了一个Android.mk文件,那么就获得它的绝对路径,并且停止上述寻找过程。

        由于接下来执行make命令时,我们需要指定的是要编译的Android.mk文件的相对于Android源码根目录路径,因此函数mm需要将刚才找到的Android.mk绝对文件路径M中与Android源码根目录T相同的那部分路径去掉。这是通过sed命令来实现的,也就是将字符串M前面与字符串T相同的子串删掉。

        最后,将找到的Android.mk文件的相对路径设置给环境变量ONE_SHOT_MAKE,表示接下来要对它进行编译。另外,函数mm还将make命令目标设置为all_modules。这是什么意思呢?我们知道,一个Android.mk文件同时可以定义多个模块,因此,all_modules就表示要对前面指定的Android.mk文件中定义的所有模块进行编译

mmm:

  其中,dir-1、dir-2、dir-N都是包含有Android.mk文件的目录。在最后一个目录dir-N的后面可以带一个冒号,冒号后面可以通过逗号分隔一系列的模块名称module-1、module-2和module-M,用来表示要编译前面指定的Android.mk中的哪些模块

 函数mmm在Android源码根目录执行make命令的时候,没有通过-f指定Makefile文件,因此默认就使用Android源码根目录下的Makefile文件。它仅仅是将build/core/main.mk文件加载进来。build/core/main.mk是Android编译系统的入口文件,它通过加载其它的mk文件来对Android源码中的各个模块进行编译,以及将编译出来的文件打包成各种镜像文件。


  当在Android源码中定义的各个模块都编译好之后,我们还需要将编译得到的文件打包成相应的镜像文件,例如system.img、boot.img和recorvery.img等,这样我们才可以将这些镜像烧录到目标设备去运行。



  system.img镜像文件描述的是设备上的system分区,即/system目录,它是在build/core/Makefile文件中生成的

  boot.img:build/core/main.mk文件定义了boot.img镜像文件的依赖规则,我们可以通过执行make bootimage命令来执行。其中,bootimage是一个伪目标,它依赖于INSTALLED_BOOTIMAGE_TARGET

                   boot.img的存在就是为BootLoader的第一阶段提供第二阶段、Kernel镜像、Ramdisk镜像,以及相应的启动参数等等

ramdisk.img:build/core/main.mk文件定义了ramdisk.img镜像文件的依赖规则,我们可以通过执行make ramdisk命令来执行

userdata.img镜像描述的是Android系统的data分区,即/data目录,里面包含了用户安装的APP以及数据等等。

                    build/core/main.mk文件定义了userdata.img镜像文件的依赖规则,我们可以通过执行make userdataimage命令来执行。

recovery.img是设备进入recovery模式时所加载的镜像。recovery模式就是用来更新系统的,我们可以认为我们的设备具有两个系统。一个是正常的系统,它由boot.img、system.img、ramdisk.img和userdata.img等组成,另外一个就是recovery系统,由recovery.img组成。平时我们进入的都是正常的系统,只有当我们需要更新这个正常的系统时,才会进入到recovery系统。因此,我们可以将recovery系统理解为在Linux Kernel之上运行的一个小小的用户空间运行时。这个用户空间运行时可以访问我们平时经常使用的那个系统的文件,从而实现对它的更新。

               在build/core/Makefile文件中,定义了一个伪目标recoveryimage,用来生成recovery.img

               由于recovery.img和boot.img都是用来启动系统的,因此,它们的内容是比较类似,例如都包含有Kernel及其启动参数、       Ramdisk,以及可选的BootLoader第二阶段






0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 鸭子步走了腿疼怎么办? 跳爵士舞没感觉怎么办 军校学员体能考核不达标怎么办 俯卧撑新兵连做不动怎么办 宝宝胆小不敢上体能课怎么办 28岁老太太抬头纹剩两颗怎么办 大腿前侧抽筋痛怎么办 电脑看片缓冲慢怎么办 跑步跑的腿疼怎么办 第一次去健身房练瑜伽不会怎么办 跑步过后腿筋疼怎么办 俯卧撑只能做20个怎么办 被裤裆峰咬了怎么办? 新兵5公里不想跑怎么办 老公掉粪坑了你怎么办 在部队被欺负了怎么办 衣服上的标志洗掉了怎么办 整件白衬衣被染怎么办 遇到敲诈小混混该怎么办 纯棉衣服洗长了怎么办 纯棉的衣服洗后长了怎么办 睡觉把脖子扭了怎么办 微信限额10万了怎么办 新兵条令背不下来怎么办 武警部队改革去年入伍的新兵怎么办 规培考试没考上怎么办 规培如果没考上怎么办 农民工集体讨薪 领导不在怎么办 卷闸门钥匙丢了怎么办 邻居把路堵了该怎么办 邻居家的盆栽对准我家大门怎么办 袜子掉到了楼下的阳台怎么办? 合租房没有阳台晒衣服怎么办 车门锁了钥匙在里面怎么办 邻居忘带钥匙你看见会怎么办 把车钥匙锁车里了怎么办 偷了东西不承认怎么办 孩子偷了东西不承认怎么办 公司员工偷了东西不承认怎么办 知道被谁偷了没有证据怎么办 被贼偷了知道是谁没有证据怎么办