精华内容
下载资源
问答
  • Android 编译系统

    2019-01-11 23:00:58
    Android 编译系统(1) Android 编译系统设计原则(2) Android 顶层编译架构分析2. Android.mk编写(1) 可执行文件编译(2) so 编译3. make file 语法介绍 1. Android 编译系统 Android 编译采用GNU make. (1) Android ...

    1. Android 编译系统

    Android 编译采用GNU make.

    (1) Android 编译系统设计原则

    Android 编译系统在多次迭代演进中,主要坚持的设计原则如下:

    • 同一套代码支持编译出不同的构建目标。
      既支持运行与Android 设备端的软件包,也支持编译Host平台上的各种工具,如模拟器、辅助工具等。
    • 采用唯一的 Makefile 来组织所有的自动化编译。
    • 支持对项目中任意模块进行单独的编译验证。
    • 编译中间文件以及最终编译结果 与 源代码在存储目录上隔离

    (2) Android 顶层编译架构分析

    (1) 顶层模型
    主要分为四个阶段,如下图:

    Created with Raphaël 2.2.0开始初始化构建环境构建依赖关系树执行编译流程打包结束

    典型makefile 文件的解析步骤主要为三步:

    • 变量赋值、环境检测等初始化操作
    • 按照规则生成所有依赖树
    • 根据用户(通过传递编译参数)选择的依赖树,从叶到根逐步生成目标文件,即执行编译
      以上三步宏观上与前三个阶段是相互对应的,而对于整个系统而言,编译的目标最终会按照所属的架构范畴,打包到指定的镜像中,比如system.img, vendor.img等,即最后完成打包的动作。

    (2) build/core/Main.mk

    # This is the default target. It must be the first declared target.
    .PHONY: droid
    DEFAULT_GOAL  := droid
    ....
    droid: droidcore dist_files 
    ....
    

    如代码所示,Android 系统编译对应的根节点为droid,为默认的伪目标,也是第一个目标。
    droid依赖于droidcore 和 dist_files,其中droidcore 主要完成多个镜像编译,包括system、boot、recovery、userdata、cache、vendor。

    2. Android.mk编写

    官方参考链接:http://android.mk/

    (1)静态库、动态库、可执行文件的释义

    程序一般需经代码编译、链接、加载和运行几个步骤。在我们的应用中,有一些公共代码是需要反复使用,就把这些代码编译为“库”文件;

    • 静态库:如果在链接时,链接器将从库文件取得所需的(中间)代码,复制到生成的可执行文件中。这种库称为静态库,其特点是可执行文件中包含了库代码的一份完整拷贝;缺点就是被多次使用就会有多份冗余拷贝。
      include $(BUILD_STATIC_LIBRARY)
    • 动态库:如果在链接时,链接器仅仅是在可执行文件中打上标志,说明需要使用哪些动态连接库;当运行程序时,加载器根据这些标志把所需的动态链接库加载到内存。
      include $(BUILD_SHARED_LIBRARY)
    • 可执行文件:可以被运行的目标文件。
      include $(BUILD_EXECUTABLE);

    (2) 静态库编译

    假设有文件mul.c,实现了乘法操作函数,需要生成静态库libMul.a。
    添加 Android.mk 如下:

    #获取当前路径
    LOCAL_PATH := $(call my-dir)
    #清除本mk调用前的已经设置的编译参数,LOCAL_PATH除外
    include $(CLEAR_VARS)
    #指明编译的目标名称
    LOCAL_MODULE := Mul
    #指明编译依赖的源文件mul.c
    LOCAL_SRC_FILES := mul.c
    #定义本库为编译为静态库
    include $(BUILD_STATIC_LIBRARY)
    

    (3) 动态库编译

    假设有文件add.c,实现了加操作函数,需要生成动态库libMul.so

    #获取当前路径
    LOCAL_PATH := $(call my-dir)
    #清除本mk调用前的已经设置的编译参数,LOCAL_PATH除外
    include $(CLEAR_VARS)
    #指明编译的目标名称
    LOCAL_MODULE    := Add
    #指明编译依赖的源文件为add.c
    LOCAL_SRC_FILES := add.c
    #定义本库为编译为动态库
    include $(BUILD_SHARED_LIBRARY)
    

    (4) 可执行文件编译

    假设需要编译生成可执行文件arithmetic,其中libAdd.so与libSub.so做为两个动态库引入,libMul.a与libDiv.a做为两个静态库引入.

    # load shared library
    include $(CLEAR_VARS)
    LOCAL_MODULE    := Add
    LOCAL_SRC_FILES := libAdd.so
    include $(PREBUILT_SHARED_LIBRARY)
    
    include $(CLEAR_VARS)
    LOCAL_MODULE    := Sub
    LOCAL_SRC_FILES := libSub.so
    include $(PREBUILT_SHARED_LIBRARY)
    
    # load static library
    include $(CLEAR_VARS)
    LOCAL_MODULE    := Mul
    LOCAL_SRC_FILES := libMul.a
    include $(PREBUILT_STATIC_LIBRARY)
    
    include $(CLEAR_VARS)
    LOCAL_MODULE    := Div
    LOCAL_SRC_FILES := libDiv.a
    include $(PREBUILT_STATIC_LIBRARY)
    
    include $(CLEAR_VARS)
    
    LOCAL_MODULE    := arithmetic
    LOCAL_SRC_FILES := arithmetic.c
    
    LOCAL_SHARED_LIBRARIES := Add Sub
    LOCAL_STATIC_LIBRARIES := Mul Div
    

    3. make file 语法介绍

    详细见:
    跟我一起写makefile:
    https://blog.csdn.net/xiaoshuai537/article/details/79340153

    展开全文
  • Android编译系统

    2017-11-25 16:36:00
    转一篇来自现在项目组组长的Android编译系统相关的文章,转载请注明出处!

    1. 概要

    编译,就是将高级语言转换成机器语言。譬如,通过gcc将C语言编译成可以运行的二进制;通过javac将Java语言编译成可以在Java虚拟机上可以运行的字节码。

    对于简单的项目,源文件数量较少,通常只需要几条命令,组织一下源文件,调用一下编译器,生成一个可以运行的文件,就算是一个“编译系统”; 但对于大型的项目,文件数量很多,通常会被组织成众多的模块,模块之间构成依赖关系,这就不是简单几条命令就能够成为“编译系统”了。一个大型项目的编译系统,需要管理好各个模块的依赖关系,组织好大量的编译中间产物,具备随时应对模块变更的扩展性,同时,也能够高效的完成编译任务。

    在Linux上,一些传统大型项目的编译系统都是基于make这个工具,make并不是编译器,仍然需要调用gcc或javac等编译命令来对源码进行编译,make的输入是一个Makefile文本文件,make只是按照Makefile文件中定义的规则来完成工作。所以,这些编译系统中最核心的就是Makefile定义的编译规则和编译顺序,包括:哪些源文件需要编译、如何编译、哪些文件存在对其他文件的依赖,优先编译哪些文件。同其他编程语言一样,Makefile也有自己的语法,当正确书写完Makefile文件后,通过一个make命令,就能自动完成大型项目的编译。

    Android编译系统也是基于make的,要编译出整个Android系统的镜像文件并打包成刷机包(OTA Package),编译出SDK和文档(JavaDoc),同时,Android引入了很多第三方开源项目,需要兼顾不同模块的编译,仅仅是这些就已经够让Android编译系统受苦了,还别提支持不同设备厂商的硬件类型,方便设备厂商进行定制这些兼容性、扩展性、编译效率的问题。Android编译系统的复杂度可见一斑,纵观整个编译,除了大量的编译规则文件(Makefile文件片段),还有很多Shell和Python脚本组织在一起,基于make,但又不同于已有传统项目的编译系统,Android有自己的一套编译机制。

    本文会分析以下问题:

    Android编译系统的设计背景是什么? 这涉及到Android编译系统设计的意图。
    Android编译系统的设计原理是什么? 这涉及到Android编译系统运转的内在机制。

    2. 背景

    2.1 Makefile规则的基本形式

    Makefile文件的书写规则,不是本文要讨论的内容,但是Makefile规则的基本形式还是有必要在这里点出来,作为后续分析的理论基础。

    # 规则语法                                  # 示例 
    TARGET... : PREREQUISITES... main.o: main.c main.h
    COMMAND 1 gcc -c main.c
    ... ...
    COMMAND N echo "Compile finished"

    • TARGET: 表示当前规则要达成的目标,通常为最终生成的文件,或者为了最终生产的文件而产生的中间文件。譬如示例中的 main.o 就是一个中间文件
    • PREREQUISITES: 表示当前规则所依赖的前提条件,只有前提条件满足了,才能够生成目标。这些前提条件通常为另外的规则所定义的目标,这就决定了编译顺序,只有优先编译完所依赖的目标,才能编译当前规则所定义的目标。譬如示例中所依赖的的 main.cmain.h 两个文件都必须存在,才能开始编译 main.o
    • COMMAND: 表示当前规则的执行命令。一个规则下,可以有多条命令。当所依赖的条件满足后,就会依次执行这些命令序列,譬如调用gcc、文件拷贝、输出日志等

    复杂的Makefile规则,都是由若干基本的规则组合而成。最终,Makefile会被解析成一个有向无环图(Directed Acyclic Graph, DAG), 每一个目标(Target)构成DAG的节点,每一个依赖关系(Dependency)构成DAG的边。

    2.2 Resurvise Make的缺陷

    同所有基于make的编译系统一样,Android也需要Makefile文件来定义编译规则和编译顺序。但与大多数编译系统不同的是,Android并不是Recursive Make的,那么什么是Resursive Make呢?

    当在Makefile文件中,使用make命令来编译另一个模块时,就构成了Resurvise Make
    对于一个大型项目而言,可以为每个子系统构建一个Makefile文件,然后在最顶级的Makefile文件中,调用make命令来编译各个子系统:
       subsystem:
         cd subdir && make

    Resurvise Make的设计能够降低编译的复杂度,更容易理解和维护,很多Linux上的大型项目,包括Linux内核的编译系统,都是采用的Resurvise Make设计,但Android并没有采用,因为这种设计存在很多缺陷。早在1997年,Peter Miller就在Recursive Make Considered Harmful这篇论文中,指出了Resurvise Make会导致编译系统“做得太少(do too little)”或“做得太多(do too much)”, “do too little”会导致最终编译产物可能是错误的结果,而“do too much”会导致编译效率下降。

    TODO:补充Resurvise Make缺陷的示例

    2.3 Android编译系统的设计意图

    由于Resurvise Make的缺陷,Android采用了Non-Resursive Make的方式,将所有的编译规则集中于一个Makefile中,可以想象最终成型的这个Makefile是极其庞大的。 为了提升编译效率和灵活性,需要对模块的编译进行控制:单个模块可以单独进行编译,不需要的模块不会被重新编译,以便节省编译时间。

    在上述意图的驱使下,Android编译系统有以下主要的要求 (更详细的要求,请查阅build/core/build-system.html ):

    • 编译出多种目标: 除了最终Android系统的产物(譬如:system.img, boot.img),编译系统还要能够编译出很多实用的工具(譬如:aapt, adb),这些工具不仅是编译环境需要的,也是开发者需要的。

    • 支持多平台: Android需要在Linux和Mac上进行编译,编译产出也需要支持Linux和Mac。编译系统对Windows的支持并不好,但面向开发者的SDK是支持Windows的。

    • Makefile片段化:最终的规则集中于一个Makefile,并不意味着编译系统会维护这么大一个Makefile。为了提高代码的重复利用率,编译系统包含很多Makefile片段,最终通过include将片段包含进来。 每一个待编译的模块都会有一个Android.mk,其内容也是Makefile片段。

    • 自动构建依赖: 模块之间依赖关系是自动构建的,这意味着,我们只需要使用编译系统提供的接口,定义一个模块的编译规则,不需要关心编译系统如何管理众多模块之间的依赖关系。

    至此,我们分析了Android编译系统重新设计的背景,主要是为了避免Resursive Make存在的缺陷,同时应对多模块多平台的高效率编译需求。

    3. 设计原理

    在Android源码的根目录有一个Makefile文件,有效内容只有一行:

    include build/core/main.mk

    所有的编译规则都定义在build/core/main.mk文件中,最终所有的Makefile片段都将汇聚在这一个文件中。

    Android编译系统如此强大,要变成Makefile的最终形态,当然是要经过很长一段路的,下图是整个编译系统的框架:

    编译系统的框架

    我们会基于这个图来分析整个Android编译系统的设计原理:

    • 从Android源码来看,编译系统的核心功能位于build/core/目录, 在device和vendor目录下,存放了与具体机型相关的配置,这些配置信息都是.mk文件的形式存放的(譬如BoardConfig.mkAndroidProducts.mk), 另外,每一个模块的编译配置信息都是以独立的Android.mk文件的形式分散在各个模块的子目录中。

    • 编译系统需要经过初始化(setup)来完成必要的参数赋值。初始化的操作命令很简单,但实际要配置的参数是非常多的,Android支持不同平台上不同产品,甚至是不同模块的编译, 也支持编译SDK以及PC上一些常用的工具,编译系统通过配置信息的指导,来完成具体的编译任务。

    • 每一次的编译任务,就是给make一个Makefile文件,所以,每次编译任务,编译系统就会经过汇集众多零散Makefile片段的过程。编译整个工程和编译一个模块,最终 汇集成的Makefile是不一样的,这样就能做到灵活对各个模块进行编译。

    • Android将编译产物都放到了out/目录下,out/目录下又有host/target/两个子目录,分别表示PC上和手机上的编译产物。

    接下来,我们就来进一步分析编译系统的设计细节。

    3.1 编译系统的初始化

    要使Android编译系统运转起来,首先需要经过初始化,其实就是完成所有参数的配置。Android编译系统的配置,可以分为四个层级,从下到上依次是:结构级(Architecture),芯片级(Board),产品级(Product),模块级(Module)

    编译系统的配置层级
    • 芯片级(Architecture):涵盖CPU体系结构和指令集的配置,譬如arm, x86, mips

    • 平台级(Board):这个层级的配置通常定义在BoardConfig.mk,包括内核、驱动、CPU等与硬件紧密相关的

    • 产品级(Product): 这个层级的配置通常定义在AndroidProducts.mk,包括产品名称、需要包含哪些模块和文件等

    • 模块级(Module): 这个层级的配置都是由Android.mk定义的,模块具体的一些配置,包括模块名称、模块类型、对其他模块的依赖等。 要知道Android一共有多少个模块,可以在AOSP的源码根目录下执行以下命令:

    $ find . -name Android.mk | wc -l       # 笔者在Android 5.0.1下执行这个命令,得到的结果是3868,汗!

    这几个层级的配置并非独立的,而是在不同参数配置下相互影响的。Android提供两种方式来进行参数配置:运行envsetup.sh或配置buildspec.mk

    • 运行envsetup.sh

      Android提供了一个环境初始化的脚本build/envsetup.sh, 通过source命令,便可以将该脚本添加到shell环境中。接着,便发现多了一个lunch命令,我们就是通过这个命令来配置Android初始化的参数。 除了lunch,还会有很多其他命令,譬如: m, mm, mmm,我们会在编译系统(2)-初始化过程这篇文章中来详细介绍envsetup.sh的工作过程。

    $ source build/envsetup.sh  # 将envsetup.sh添加到shell执行环境中 
    $ lunch # 通过lunch来交互式的完成参数配置

    • 配置buildspec.mk

      该文件需要置于Android源码的根目录,Android提供一个配置模板build/buildspec.mk.default, 只需要将拷贝到根目录,重命名后,根据需要修改文件内容便可完成参数的配置。

      :支持这种文件配置的方式来完成初始化,是考虑到有些固定的编译场景,不需要每次都重复运行envsetup.sh脚本来配置相同的参数。

    3.1.1 产品级(Product)的参数配置

    无论是采用哪种方式,都会涉及到以下几个重要的参数:

    • TARGET_PRODUCT:目标产品。这个参数的取值来自于一个具体产品的定义,通常位于device/[manufacture]/[product]下的AndroidProducts.mk文件中, 通过PRODUCT_MAKEFILES这个属性来汇集所有产品级别的配置,包括产品名称PRODUCT_NAME,产品品牌PRODUCT_BRAND等。产品名称PRODUCT_NAME实际上就对应到了TARGET_PRODUCT

      Android 5.0.1提供了一些默认的目标产品:aosp_arm、aosp_arm64、aosp_mips、aosp_mips64、aosp_x86、aosp_x86_64,分别表示arm, mips, x86上32位和64位的产品类型。 到Android实际支持的机型,就有aosp_hammerhead, aosp_manta,分别表示LGE Nexus 5和SAMSUNG 4S。

    • TARGET_BUILD_VARIANT:目标产品的版本。每一个模块都可以通过LOCAL_MODULE_TAGS这个参数来标记自己,可选的标记值有user, debug, eng, tests, optional, 或samples。 设定目标产品的类型,就能筛选出指定标记的模块,只将符合要求的模块编译打包到最终的产品中去。

      TARGET_BUILD_VARIANT有以下取值,除了筛选模块,还有一些调试级别的差异:

      • eng:对应到工程版。编译打包所有模块。同时ro.secure=0, ro.debuggable=1, ro.kernel.android.checkjni=1,表示adbd处于ROOT状态,所有调试开关打开
      • userdebug:对应到用户调试版。同时ro.debuggable=1,打开调试开关,但并没有放开ROOT权限
      • user: 对应到用户版。同时ro.secure=1,ro.debuggable=0,关闭调试开关,关闭ROOT权限。最终发布到用户手上的版本,通常都是user
    • TARGET_BUILD_TYPE: 目标产品的类型。只有release和debug两种取值,默认的取值为release。Android源码中包含一些调试专用的代码,当取值为debug时,这些调试代码会编译到最终的产品中去。

    • TARGET_TOOLS_PREFIX: 编译工具链的路径前缀。默认情况下,Android使用prebuilts目录下的工具,但通过这个值也可自行定制为其他目录。

    TARGET_PRODUCT设定后,编译系统就可以基于它获取其他参数了。这个产品是哪个体系结构,哪个芯片,内核的命令参数是什么? 这些硬件相关的参数都是通过BoardConfig.mk来配置的,编译系统会搜寻device和vendor目录下的$(TARGET_DEVICE)/BoardConfig.mk,这样就能找到对应产品的的芯片级(Board)配置了。

    3.1.2 平台级(Board)的参数配置

    BoardConfig.mk文件中参数主要有以下几个类别:

    • CPU体系结构: TARGET_CPU_ABI, TARGET_CPU_VARIANT, TARGET_ARCH, TARGET_ARCH_VARIANT等。其中一些参数的取值不同,会连带引发其他参数的不同。

    • 内核参数: BOARD_KERNEL_BASE, BOARD_KERNEL_PAGESIZE, BOARD_KERNEL_CMDLINE这些参数最终会被打包到boot分区的镜像文件中(boot.img),作为内核的启动参数。

    • 分区镜像: TARGET_USERIMAGES_USE_EXT4, BOARD_BOOTIMAGE_PARTITION_SIZE, BOARD_SYSTEMIMAGE_PARTITION_SIZE等与分区格式、分区大小相关的参数。

    • 驱动: BOARD_HAVE_BLUETOOTH, BOARD_WLAN_DEVICE等与蓝牙、wifi硬件配置相关的参数

    3.1.3 芯片级(Architecture)的参数配置

    不同的产品(Product)配置会对应到不同的平台(Board)配置,而平台(Board)的配置也会影响到芯片(Architecture)的配置。BoardConfig.mk中定义的TARGET_ARCHTARGET_ARCH_VARIANT两个参数决定了TARGET_ARCH_SPECIFIC_MAKEFILE这个芯片级(Architecture)的配置文件,它的值等于build/core/combo/arch/(TARGETARCH)/(TARGET_ARCH_VARIANT).mk

    Android默认定义了arm, arm64, mips, mips64, x86, x86_64这几组与CPU芯片相关的编译参数。

    3.1.4 模块级(Module)的参数配置

    Android编译系统的设计理念是将模块级别的配置独立出来,每个模块的Android.mk都是独立的。在编译单个模块时,会将模块的Android.mk添加到main.mk中,形成一个Makefile。 当然,模块的Android.mk必须遵循编译系统定义的规则,具体的配置细节可以参见编译系统(4)-定制。这里只说明编译系统提供给模块编译的接口:

    接口变量 接口定义 作用
    BUILD_EXECUTABLE executable.mk 编译二进制可执行文件,如adbd
    BUILD_HOST_EXECUTABLE host_executable.mk 编译PC上的二进制可执行文件,如aapt
    BUILD_JAVA_LIBRARY java_library.mk 编译动态Java库文件,如framework.jar
    BUILD_HOST_JAVA_LIBRARY host_java_library.mk 编译PC上的Java库文件,如signapk.jar
    BUILD_SHARED_LIBRARY shared_library.mk 编译动态的库文件,如libfilterfw.so
    BUILD_STATIC_LIBRARY static_library.mk 编译静态的库文件,如libip6tc.so
    BUILD_PACKAGE package.mk 编译APK,如SystemUI.apk

    所有接口定义的源文件,都在build/core目录下,在Android.mk中,只需要引用这些变量,就能触发一个模块的编译,不同的模块使用不用的编译方式。 在将一个Android.mk文件includemain.mk中的时候,也会依次将上述变量定义的.mk文件include进来,从而生成最终的Makefile配置。

    3.2 编译系统的运行过程

    编译系统的运行过程可以分为两部分:

    • 合成最终的Makefile:零散的Makefile片段,会按照引用关系汇集到main.mk中,作为最终编译的Makefile

    • 根据依赖关系逐步构建出最终产物Makefile的编译规则最终成型为一个DAGmake会按照“后根顺序(post-order)”来遍历DAG,被依赖的目标总是先被执行

    3.2.1 汇集Makefile零散片段

    通过Makefileinclude语法,就能将其他Makefile片段包含到当前Makefile文件中来,当我们在Android目录下执行make命令时,实际上输入文件是根目录下的Makefile,所有的片段最终都汇集到该文件中。

    大体的汇集过程如下图所示:

    这里写图片描述

    Makefile片段汇集

    最终Makefile文件中仅仅引入了main.mk, main.mk位于build/core目录,望文生意,这就表示已经进入Android编译系统最为核心的部分了,main.mk会做编译环境检查,定义最重要的编译目标(droid),依次引入其他功能片段:

    • help.mk,最优先引入的片段,文件内容很简单,就是定义了一个名为help的目标,通过输入make help命令,就可以看到该目标的输出结果是一些最主要的make目标的帮助信息。

    • config.mk,文件内容很庞大,目的就是为了配置编译环境。该文件定义了用于其他模块编译的常量(BUILD_JAVA_LIBRARY, CLEAR_VARS等),也定义了编译时所依赖工具的本地路径(aapt, minigzip等),同时也会引入与基于机型配置相关的其他片段(BoardConfig.mk, AndroidProducts.mk等)。

    • cleanbuild.mk,定义了installclean这个编译目标,不同于clean,执行make installclean的时候,并不会完整的删除out/目录,而是仅仅删除与当前TARGET_PRODUCT, TARGET_BUILD_VARIANT, PRODUCT_LOCALES这属性关联到的编译产出。通俗一点来说,就是删除out/target/product/目录下本次机型编译的产物,out/host目录下的文件是保留下来的。

    • definitions.mk,定义了大量的函数,这些函数都是编译系统的其他文件将用到的。譬如:my-dir, all-subdir-makefiles, find-subdir-files等,通过Makefile$(call func_name)就能调用这些函数。

    • dex_preopt.mk,为了提升代码的运行效率,Android会对可执行文件做优化,即将dex格式的文件转换为odex格式的文件。在ART虚拟机下,仍然采用dex(Dalvik Executable)odex(Optimized Dalvik Executable)这个文件的命名方式,但实际上ART与Dalvik的文件格式是不同的。

    • Android.mk,Android有全编译和模块编译之分:

      • 全编译,会通过build/tools/findleaves.py这个脚本将所有模块的Android.mk加载到一个名为 subdir_makefiles这个变量中,然后逐个引入$subdir_makefiles中的Makefile片段;
      • 模块编译,是通过命令解析将待编译模块的Android.mk文件加载到ONE_SHOT_MAKEFILE这个变量中,编译时,仅仅是引入ONE_SHOT_MAKEFILE中的Makefile片段。

      Android.mk的编写模板基本都是一致的,它会引入很多编译系统已经初始化好的变量,譬如CLEAR_VARS, BUILD_JAVA_LIBRARY, 其实就是引入变量所对应的.mk文件,所以Android.mk的生成过程,也是一个Makefile片段的汇集过程。

    • post_clean.mk,在引入待编译模块后,就可以定义模块的清除规则了。该片段定义了基于上一次编译产出的清除规则,譬如,某个模块的AIDL文件或者资源发生了变化,那再次编译这个模块时,就需要清除上一次的编译产物。

    • legacy_prebuilts.mk,定义了GRANDFATHERED_ALL_PREBUILT变量,表示不需要经过编译的预装文件,譬如gps.conf(GPS配置文件), radio.img(射频分区镜像文件),这些文件都是预编译好的,只需要拷贝到编译产出即可。Android定义了一个默认的PREBUILT列表,而且不希望第三方改动这个列表。当第三方有预编译文件,但又不在PREBUILT列表中时,就需要通过PRODUCT_COPY_FILES这个变量来指定了。

    • Makefile,不同于AOSP根目录下的Makefile,这个Makefile位于build/core目录下,Android官方对这个文件的解释是”定义一些杂乱的编译规则(miscellaneous rules)”,实际上,这个文件相当重要,诸如system.img, recovery.img, userdata.img, cache.img的目标定义都在这个文件中,更笼统点说,out/target/product/PRODUCT_NAME/目录下大部分的编译产出都是由该文件定义的。

    3.2.2 生成Makefile目标依赖

    最终Makefile所定义的依赖关系可以用一个有向无环图(Directed Acyclic Graph, DAG)来表示,如果解析Makefile规则,发现存在一个依赖环,那就不是合法的Makefile规则。

    可以把编译系统构建的依赖关系分成系统级和模块级别两个层面:

    • 系统级的依赖关系,是指最终编译出Android系统所涉及到的目标之间的依赖。譬如编译出system.img有哪些依赖目标需要先编译完成。这一层的依赖是由编译系统定义的。

    • 模块级的依赖关系,是指各个模块之间的依赖。譬如模块A依赖模块B,那么就需要将这个依赖关系告诉编译系统,只有当模块B编译完成了,才能开始编译模块A。这一层的依赖是由各个模块单独定义的。

    3.2.2.1 系统级的依赖关系

    最终生成的DAG是极其庞大的,为了说明问题,我们仅列出了一些关键的编译目标之间的依赖关系图:

    这里写图片描述

    Makefile目标依赖
    • droid,是最顶层的编译目标,执行make命令时,就会默认编译这个目标,但这个目标并没有什么实质内容,仅仅作为所依赖目标的组合。其中dist_files是编译产物emma_meta.zip,主要用作测试覆盖率; apps_only这个编译目标是由TARGET_BUILD_APPS的值决定的,当只编译指定的app时,就会编译这个目标,否则就编译droidcore这个目标。

    • droidcore,Android全编译的顶层目标,也仅仅作为所依赖目标的组合。boot.img, recovery.img, userdata.img, cache.img, system.img这些最终刷入设备的镜像都是被依赖的目标。

    • files,该目标囊括所有预编译的模块prebuilt,需要编译安装的模块modules_to_install,这些模块的编译产物输出到最终需要打包的分区镜像文件中。

    • system.img,在Makefile中,真正的编译目标是systemiamge,它依赖于INSTALLED_SYSTEMIMAGE这个变量,其实就是编译完成后out/目录下的system.img, 要编译system.img,当然依赖于很多模块的编译产出(由ALL_DEFAULT_INSTALLED_MODULES变量描述的),这里就构成了两个目标依赖于同一个目标的依赖关系,但仍然没有构成环。

    • userdata.img, cache.img, recovery.img,这些都是分区镜像文件,最终刷入设备上对应的分区,它们都是被droidcore依赖的目标,当然,这些目标的生成也依赖于很多其他的目标。

    • installed-files.txt, 一个文本文件,最终生成在out/目录下,文件内容列出了所有安装的模块。

    这里并没有把更多的依赖细节体现出来,只是展开了几个很顶层的目标,实际上,各个目标之间的依赖关系是错综复杂的。

    3.2.2.2 模块级的依赖关系

    每一个模块的Android.mk文件中,都可以通过LOCAL_REQUIRED_MODULES的值,来设置所依赖的其他模块,但这只是一个依赖关系的定义,对LOCAL_REQUIRED_MODULES的处理还是编译系统完成的。

    Android.mk中,都会调用编译系统提供的模板,来编译一个特定的模块,这是通过引用Makefile变量来实现的,譬如:(BUILDHOSTEXECUTABLE)</strong>PC<strong>表示编译PC机上的可执行程序,(BUILD_PACKAGE)表示编译一个APK, $(BUILD_JAVA_LIBRARY)表示编译一个Java库文件。

    每一个$(BUILD_XX)的变量,都会对应到一个.mk文件,而这些.mk文件也会引用其他.mk文件,最终都会落到引用base_rules.mk中:

    这里写图片描述

    模块之间依赖

    base_rules.mk中,待编译的模块会把自己添加到ALL_MODULES这个变量中,如此一来,当所有的Android.mk引入完成后,ALL_MODULES就记录了所有待编译的模块。 所有被依赖的模块会保存在ALL_MODULES.(m).REQUIRED</strong><strong>这个变量中,(m)就是当前被引入的模块,在编译(m)</strong><strong>ALLMODULES.这个模块的时候,就会顺着ALL_MODULES.(m).REQUIRED变量找到被依赖的模块,从而构建出依赖关系图。

    3.2.3 编译产出

    我们最关心的当然是一次编译能够有什么产出,所有的编译产物都在out/目录下,有两个子目录:host/表示针对PC上的产物,target/表示针对移动设备上的产物。所有编译产物的生成过程可以大致分为三步:

    1. 各个模块编译出中间产物。这些中间产物都位于host/target/下面的obj/子目录中,对于C代码而言,中间产物通常就是.o文件;对于Java代码而言,中间产物通常就是.jar文件。

      target/下面的obj/为例,有以下类别的中间产物:

      • EXECUTABLES: BUILD_EXECUTABLE的中间产物,譬如adbd, app_process等模块
      • JAVA_LIBRARIES: BUILD_LIBRARY的中间产物,譬如framework, services等模块
      • SHARED_LIBRARIES: BUILD_SHARED_LIBRARY的中间产物, 譬如libart, libc, libsqlite等模块
      • STATIC_LIBRARIES: BUILD_STATIC_LIBRARY的中间产物,譬如libapplypatch等模块
      • APPS: BUILD_PACKAGE的中间产物, 譬如Browser, Email, Settings
      • ETC: 譬如init.rc, mac_permissions.xml, *.ttf字体文件等模块
      • PACKAGING: 打包时的中间文件,譬如systemimg, userdataimg, target_files, apkcerts, 这些编译目标需要打包其他编译产物
      • lib: 编译完成的so库,最终会按照目录结构加载到system/lib目录。

      对于host下面的obj/,也有类似的目录结构。

    2. 链接各个中间产物,生成可执行的文件。对于EXECUTABLES,会在模块的临时目录生成一个二进制程序;对于SHARED_LIBRARIES,会在模块的临时目录生成一个LINKED的子目录,存放这最终编译完成的so库文件。

    3. 打包二进制文件,生成镜像文件。譬如,将各种apk、jar和so文件拷贝到system/目录,打包生成system.img镜像。

    3.3 编译SDK

    TODO


    版权声明:本文为博主转载文章,转载请注明出处点这

    展开全文
  • android编译系统

    千次阅读 2014-01-24 19:14:56
    Android的优势就在于其开源,手机和平板生产商可以根据自己的硬件进行个性定制自己的手机产品,如小米,LePhone,M9等,因此,在我们在对Android的源码进行定制的时候,很有必要了解下,Android编译过程。...

    Android Makefile 文件讲解

    android编译系统的makefile文件Android.mk写法如下

    (1)   Android.mk文件首先需要指定LOCAL_PATH变量,用于查找源文件。由于一般情况下
    Android.mk和需要编译的源文件在同一目录下,宏函数“my-dir”由编译系统提供的,用于返回当前路径。所以定义成如下形式:
    LOCAL_PATH:=$(call my-dir)
    上面的语句的意思是将LOCAL_PATH变量定义成本文件所在目录路径。


    (2)   Android.mk中可以定义多个编译模块,每个编译模块都是以include $(CLEAR_VARS)开始
    以include $(BUILD_XXX)结束。
    include $(CLEAR_VARS)
    CLEAR_VARS由编译系统提供,指定让GNUMAKEFILE为你清除除LOCAL_PATH以外的所有LOCAL_XXX变量,
    如LOCAL_MODULE,LOCAL_SRC_FILES,LOCAL_SHARED_LIBRARIES,LOCAL_STATIC_LIBRARIES等。
    include $(BUILD_STATIC_LIBRARY)表示编译成静态库
    include $(BUILD_SHARED_LIBRARY)表示编译成动态库
    include $(BUILD_EXECUTABLE)表示编译成可执行程序

    include $(BUILD_PREBUILT)

    对于Android系统集成有源码的第三方库和程序,只需要提供Android.mk文件,把源码集成到 Android编译系统中就可以了,并且有很多例子可以参考。但是对于没有源码的pre-build第三方库和程序,采用include $(BUILD_PREBUILT)或者include $(BUILD_MULTI_PREBUILT),参考文章: 

    http://blog.csdn.net/zhangchiytu/article/details/6424910


    include $(BUILD_JAVA_LIBRARY)表示生成JAR包

    include $(BUILD_PACKAGE)表示编译成APK

    include $(BUILD_STATIC_LIBRARY)表示编译成静态库,include $(BUILD_SHARED_LIBRARY)表示编译成动态库,include $(BUILD_EXECUTABLE)表示编译成可执行程序”之前必须要定义LOCAL_MODULE变量,这样编译系统才会知道要生成的静态库、动态库、可执行程序的名称,编译系统会根据类型自动加载后缀。


    (3)   LOCAL_MODULE_TAGS:= optional

          LOCAL_MODULE_TAGS :=user eng tests optional

          user: 指该模块只在user版本下才编译

          eng: 指该模块只在eng版本下才编译

          tests: 指该模块只在tests版本下才编译

          optional:指该模块在所有版本下都编译


    (4)   LOCAL_SRC_FILES: 编译的源代码文件列表 

          添加src目录下所有的java 源文件:LOCAL_SRC_FILES:= $(call all-java-files-under, src)


    (5)   LOCAL_SDK_VERSION := current


    (6)   LOCAL_MODULE:= 模块名称,见(2)中描述。


    (7)   LOCAL_PACKAGE_NAME :=APK 包的名称


    (8)   LOCAL_JAVA_LIBRARIES :=platform core ext 依赖的共享jar


    (9)   LOCAL_STATIC_JAVA_LIBRARIES := 依赖的静态jar包 (jar包是使用 include $(BUILD_STATIC_JAVA_LIBRARY)生成的。)


    (10) LOCAL_CERTIFICATE: 签名认证   1.系统中所有使用android.uid.system作为共享UID的APK,都会首先在manifest节点中增加android:sharedUserId="android.uid.system",然后在Android.mk中增加LOCAL_CERTIFICATE:= platform。  2. 系统中所有使用android.uid.shared作为共享UID的APK,都会在manifest节点中增加android:sharedUserId="android.uid.shared",然后在Android.mk中增加LOCAL_CERTIFICATE:= shared。  3.系统中所有使用android.media作为共享UID的APK,都会在manifest节点中增加android:sharedUserId="android.media",然后在Android.mk中增加LOCAL_CERTIFICATE:= media


    (11)  编译一个应用程序(APK)
      LOCAL_PATH := $(call my-dir)
      include $(CLEAR_VARS)
       
      # Build all java files in the java subdirectory
      LOCAL_SRC_FILES := $(call all-subdir-java-files)
       
      # Name of the APK to build
      LOCAL_PACKAGE_NAME := LocalPackage
       
      # Tell it to build an APK
      include $(BUILD_PACKAGE)


    (12)  编译静态库

    #清除java环境变量

    include$(CLEAR_VARS)

    LOCAL_MODULE_TAGS:= user

    #添加aidl文件

    LOCAL_SRC_FILES+= src/net/sunniwell/download/aidl/IDownload.aidl

    # SDK 版本                

    LOCAL_SDK_VERSION:= current

    #jar包的名字

    LOCAL_MODULE:=DownLoadServerapi               

    #编译 DownLoadServerapi  jar 包

    include $(BUILD_STATIC_JAVA_LIBRARY)


    (13) 编译一个依赖于静态Java库(static.jar)的应用程序
      LOCAL_PATH := $(call my-dir)
      include $(CLEAR_VARS)
       
      # List of static libraries to include in the package
      LOCAL_STATIC_JAVA_LIBRARIES := DownLoadServerapi  第三方jar包
       
      # Build all java files in the java subdirectory
      LOCAL_SRC_FILES := $(call all-subdir-java-files)
       
      # Name of the APK to build
      LOCAL_PACKAGE_NAME := LocalPackage
       
      # Tell it to build an APK
      include $(BUILD_PACKAGE)


    (14) 编译一个需要用平台的key签名的应用程序
      LOCAL_PATH := $(call my-dir)
      include $(CLEAR_VARS)
       
      # Build all java files in the java subdirectory
      LOCAL_SRC_FILES := $(call all-subdir-java-files)
       
      # Name of the APK to build
      LOCAL_PACKAGE_NAME := LocalPackage
       
      LOCAL_CERTIFICATE := platform
       
      # Tell it to build an APK
      include $(BUILD_PACKAGE)


    (15) 添加一个静态JAVA库
      LOCAL_PATH := $(call my-dir)
      include $(CLEAR_VARS)
       
      # Build all java files in the java subdirectory
      LOCAL_SRC_FILES := $(call all-subdir-java-files)
       
      # Any libraries that this library depends on
      LOCAL_JAVA_LIBRARIES := android.test.runner
       
      # The name of the jar file to create
      LOCAL_MODULE := sample
       
      # Build a static jar file.
      include $(BUILD_STATIC_JAVA_LIBRARY)


    (16) 引用第三方JAR 包方式

    #引入第三方包

    include $(CLEAR_VARS) 

     

    # 表示在当前目录下的lib/iptv.jar进行预处理到android jar包的目录中去

     

    #LOCAL_PREBUILT_JAVA_LIBRARIES := libiptv:lib/iptv.jar

    LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \

    libiptv:lib/iptv.jar 

     

    include $(BUILD_MULTI_PREBUILT)

     

    需要在编译apk 中进行、并添加

    LOCAL_STATIC_JAVA_LIBRARIES := libiptv

    (17) LOCAL_AIDL_INCLUDES  := <path to aidl files>

            aidl文件所在的目录,关于aidl是Android Interface definition language的缩写,它是一种android内部进程通信接口的描述语言,通过它我们可以定义进程间的通信接口


    (18)include $(call all-makefiles-under,$(LOCAL_PATH))

    在Android的源代码中的mk文件中,我们经常会看到上面这句话,从字面意思看是,include目录下所有的mk文件,那么这里面有两个坑,

    1.只是include Android.mk文件,叫其他名字的mk文件,不include.
    2.只是include这个$(LOCAL_PATH)一级目录下的Android.mk文件,而不是所有子目录以及子目录下的Android.mk文件(这个坑掉进去2小时才出来 )。

    最后,看一下这个函数的源代码加强一下印象吧:

    define all-makefiles-under
    $(wildcard $(1)/*/Android.mk)
    endef


    (19)  LOCAL_PRELINK_MODULE 

    Prelink利用事先链接代替运行时链接的方法来加速共享库的加载,它不仅可以加快起动速度,还可以减少部分内存开销,是各种Linux架构上用于减少程序加载时间、缩短系统启动时间和加快应用程序启动的很受欢迎的一个工具。程序运行时的动态链接尤其是重定位(relocation)的开销对于大型系统来说是很大的。 

            动态链接和加载的过程开销很大,并且在大多数的系统上, 函数库并不会常常被更动, 每次程序被执行时所进行的链接动作都是完全相同的,对于嵌入式系统来说尤其如此。因此,这一过程可以改在运行时之前就可以预先处理好,即花一些时间利用Prelink工具对动态共享库和可执行文件进行处理,修改这些二进制文件并加入相应的重定位等信息,节约了本来在程序启动时的比较耗时的查询函数地址等工作,这样可以减少程序启动的时间,同时也减少了内存的耗用。 

    (20) 



    Android编译系统详解(一)

    http://blog.csdn.net/mr_raptor/article/details/7539978

    Android的优势就在于其开源,手机和平板生产商可以根据自己的硬件进行个性定制自己的手机产品,如小米,LePhone,M9等,因此,在我们在对Android的源码进行定制的时候,很有必要了解下,Android的编译过程。


    如果你从来没有做过Android代码的编译,那么最官方的编译过程就是查看Android的官方网站:http://source.android.com/source/building.html

    但是,这儿只是告诉你了如何去编译一个通用的系统,并没有详细告诉你细节,我们跟着编译过程来了解下。

    +-------------------------------------------------------------------------------------------------+

     本文使用Android版本为2.1,采用开发板为华清远见研发的FS_S5PC100 A8开发板。

    +-------------------------------------------------------------------------------------------------+

    按照google给出的编译步骤如下:

       1> source build/envsetup.sh:加载命令

       2> lunch:选择平台编译选项

       3> make:执行编译

    我们按照编译步骤来分析编译过程的细节,最终添加自己的平台编译选项。

    1. source build/envsetup.sh

    这个命令是用来将envsetup.sh里的所有用到的命令加载到环境变量里去,我们来分析下它。

    envsetup.sh里的主要命令如下:


    function help()                  # 显示帮助信息
    function get_abs_build_var()           # 获取绝对变量
    function get_build_var()             # 获取绝对变量
    function check_product()             # 检查product
    function check_variant()             # 检查变量
    function setpaths()                # 设置文件路径
    function printconfig()              # 打印配置
    function set_stuff_for_environment()        # 设置环境变量
    function set_sequence_number()            # 设置序号
    function settitle()                # 设置标题
    function choosetype()               # 设置type
    function chooseproduct()              # 设置product
    function choosevariant()              # 设置variant
    function tapas()                  # 功能同choosecombo
    function choosecombo()               # 设置编译参数
    function add_lunch_combo()             # 添加lunch项目
    function print_lunch_menu()            # 打印lunch列表
    function lunch()                 # 配置lunch
    function m()                   # make from top
    function findmakefile()              # 查找makefile
    function mm()                   # make from current directory
    function mmm()                   # make the supplied directories
    function croot()                 # 回到根目录
    function cproj()
    function pid()
    function systemstack()
    function gdbclient()
    function jgrep()                 # 查找java文件
    function cgrep()                  # 查找c/cpp文件
    function resgrep()
    function tracedmdump()
    function runhat()
    function getbugreports()
    function startviewserver()
    function stopviewserver()
    function isviewserverstarted()
    function smoketest()
    function runtest()
    function godir ()  #跳到指定目录

     #add_lunch_combo函数被多次调用,就是它来添加Android编译选项


     # Clear this variable.  It will be built up again when thevendorsetup.sh
     # files are included at the end of this file.

     # 清空LUNCH_MENU_CHOICES变量,用来存在编译选项

     unset LUNCH_MENU_CHOICES

     function add_lunch_combo()  
     {
         local new_combo=$1         # 获得add_lunch_combo被调用时的参数
         local c
         # 依次遍历LUNCH_MENU_CHOICES里的值,其实该函数第一次调用时,该值为空
         for c in ${LUNCH_MENU_CHOICES[@]} ; do
             if [ "$new_combo" = "$c" ] ; then    # 如果参数里的值已经存在于LUNCH_MENU_CHOICES变量里,则返回
                 return
             fi
         done
         # 如果参数的值不存在,则添加到LUNCH_MENU_CHOICES变量里
         LUNCH_MENU_CHOICES=(${LUNCH_MENU_CHOICES[@]} $new_combo)
     }


    # 这是系统自动增加了一个默认的编译项 generic-eng
     # add the default one here
     add_lunch_combo generic-eng    # 调用上面的add_lunch_combo函数,将generic-eng作为参数传递过去

     # if we're on linux, add the simulator.  There is a special case
     # in lunch to deal with the simulator
     if [ "$(uname)" = "Linux" ] ; then
         add_lunch_combo simulator
     fi

    # 下面的代码很重要,它要从vendor目录下查找vendorsetup.sh文件,如果查到了,就加载它
     # Execute the contents of any vendorsetup.sh files we can find.
     for f in `/bin/ls vendorbuild/vendorsetup.sh 2> /dev/null` #android4.0中也会检查/device/目录下是否有vendorsetup.sh
     do
         echo "including $f"
        . $f       # 执行找到的脚本,其实里面就是厂商自己定义的编译选项
     done
     unset f

    envsetup.sh其主要作用如下:

      1. 加载了编译时使用到的函数命令,如:help,lunch,m,mm,mmm等
      2. 添加了两个编译选项:generic-eng和simulator,这两个选项是系统默认选项
      3. 查找vendor/<-厂商目录>/和vendor/<厂商目录>/build/目录下的vendorsetup.sh,如果存在的话,加载执行它,添加厂商自己定义产品的编译选项
     其实,上述第3条是向编译系统添加了厂商自己定义产品的编译选项,里面的代码就是:add_lunch_combo xxx-xxx。

    根据上面的内容,可以推测出,如果要想定义自己的产品编译项,简单的办法是直接在envsetup.sh最后,添加上add_lunch_combo myProduct-eng,当然这么做,不太符合上面代码最后的本意,我们还是老实的在vendor目录下创建自己公司名字,然后在公司目录下创建一个新的vendorsetup.sh,在里面添加上自己的产品编译项。

     
    #mkdir vendor/farsight/
    #touch vendor/farsight/vendorsetup.sh
    #echo "add_lunch_combo fs100-eng" > vendor/farsight/vendorsetup.sh

     

    这样,当我们在执行source build/envsetup.sh命令的时候,可以在shell上看到下面的信息:


    including vendor/farsight/vendorsetup.sh

     
    2. 按照android官网的步骤,开始执行lunch full-eng



    当然如果你按上述命令执行,它编译的还是通用的eng版本系统,不是我们个性系统,我们可以执行lunch命令,它会打印出一个选择菜单,列出可用的编译选项

    如果你按照第一步中添加了vendorsetup.sh那么,在命令行中执行lunch:

    #lunch

    那么会显示如下信息:


    You're building on Linux
     
    Lunch menu... pick a combo:
         1. generic-eng
         2. simulator
         3. fs100-eng

     

    其中第3项是我们自己添加的编译项。

    因此如果想配置fs100-eng,则执行“lunch fs100-eng”或者“lunch 3”


     
    lunch命令是envsetup.sh里定义的一个命令,用来让用户选择编译项,来定义Product和编译过程中用到的全局变量。

    我们一直没有说明前面的fs100-eng是什么意思,现在来说明下,fs100是我定义的产品的名字,eng是产品的编译类型,除了eng外,还有user, userdebug,分别表示:

    eng: 工程机,

    user:最终用户机

    userdebug:调试测试机

    tests:测试机

    由此可见,除了eng和user外,另外两个一般不能交给最终用户的,记得m8出来的时候,先放出了一部分eng工程机,然后出来了user机之后,可以用工程机换。

     

    那么这四个类型是干什么用的呢?其实,在main.mk里有说明,在Android的源码里,每一个目标(也可以看成工程)目录都有一个Android.mk的makefile,每个目标的Android.mk中有一个类型声明:LOCAL_MODULE_TAGS,这个TAGS就是用来指定,当前的目标编译完了属于哪个分类里。

     

        PS:Android.mk和Linux里的makefile不太一样,它是Android编译系统自己定义的一个makefile来方便编译成:c,c++的动态、静态库或可执行程序,或java库或android的程序。

     

    好了,我们来分析下lunch命令干了什么?

     

    function lunch()
    {
        local answer

        if [ "$1" ] ; then
           # lunch后面直接带参数
            answer=$1
        else
           # lunch后面不带参数,则打印处所有的target product和variant菜单提供用户选择
            print_lunch_menu  
            echo -n "Which would you like? [generic-eng] "
            read answer
        fi

        local selection=

        if [ -z "$answer" ]
        then
               # 如果用户在菜单中没有选择,直接回车,则为系统缺省的generic-eng
            selection=generic-eng
        elif [ "$answer" = "simulator" ]
        then
            # 如果是模拟器
            selection=simulator
        elif (echo -n $answer | grep -q -e "^[0-9][0-9]*$")
        then
            # 如果answer是选择菜单的数字,则获取该数字对应的字符串  #这个解释了“lunch 3”
            if [ $answer -le ${#LUNCH_MENU_CHOICES[@]} ]
            then
                selection=${LUNCH_MENU_CHOICES[$(($answer-$_arrayoffset))]}
            fi
            #如果 answer字符串匹配 *-*模式(*的开头不能为-),例如“fs100-eng
        elif (echo -n $answer | grep -q -e "^[^\-][^\-]*-[^\-][^\-]*$")
        then
            selection=$answer
        fi

        if [ -z "$selection" ]
        then
            echo
            echo "Invalid lunch combo: $answer"
            return 1
        fi

        # special case the simulator
        if [ "$selection" = "simulator" ]
        then
            # 模拟器模式
            export TARGET_PRODUCT=sim
            export TARGET_BUILD_VARIANT=eng
            export TARGET_SIMULATOR=true
            export TARGET_BUILD_TYPE=debug
        else

            # 将 product-variant模式中的product分离出来
            local product=$(echo -n $selection | sed -e "s/-.*$//")

            # 检查之,调用关系 check_product()->get_build_var()->build/core/config.mk比较罗嗦,不展开了
            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

            # 将 product-variant模式中的variant分离出来
            local variant=$(echo -n $selection | sed -e "s/^[^\-]*-//")

            # 检查之,看看是否在 (user userdebug eng) 范围内
            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_SIMULATOR=false
            export TARGET_BUILD_TYPE=release
        fi # !simulator

        echo

        # 设置到环境变量,比较多,不再一一列出,最简单的方法 set >env.txt 可获得
        set_stuff_for_environment
        # 打印一些主要的变量, 调用关系 printconfig()->get_build_var()->build/core/config.mk->build/core/envsetup.mk 比较罗嗦,不展开了
        printconfig
    }

     

    由上面分析可知,lunch命令可以带参数和不带参数,最终导出一些重要的环境变量,从而影响编译系统的编译结果。导出的变量如下(以实际运行情况为例)

     

    TARGET_PRODUCT=fs100
    TARGET_BUILD_VARIANT=eng
    TARGET_SIMULATOR=false
    TARGET_BUILD_TYPE=release

     

    执行完上述两个步骤,就该执行:make命令了,下篇来分析。

    Android编译系统详解(二)

    http://blog.csdn.net/mr_raptor/article/details/7540066

    通过上篇文章,我们分析了编译android时source build/envsetup.sh和lunch命令,在执行完上述两个命令后, 我们就可以进行编译android了。

    1. make 

    执行make命令的结果就是去执行当前目录下的Makefile文件,我们来看下它的内容:

    [html] view plaincopy
    1. 1 ### DO NOT EDIT THIS FILE ###  
    2. 2 include build/core/main.mk  
    3. 3 ### DO NOT EDIT THIS FILE ###  

    呵呵,看到上面 的内容,我们都会笑,这是我见过最简单的Makefile了,我们再看下build/core/main.mk

    main.mk文件里虽然脚本不多,但是却定义了整个Android的编译关系,它主要引入了下列几个重要的mk文件:

    49 include $(BUILD_SYSTEM)/config.mk

    55 include $(BUILD_SYSTEM)/cleanbuild.mk

    142 include $(BUILD_SYSTEM)/definitions.mk

    当然每个mk文件都有自己独特的意义,我一并将主线流程通过下面这个图各表现出来,先有个整体的概念,然后再细化了解。

    所有的Makefile都通过build/core/main.mk这个文件组织在一起,它定义了一个默认goals:droid,当我们在TOP目录下,敲Make实际上就等同于我们执行make droid。

    当Make include所有的文件,完成对所有make我文件的解析以后就会寻找生成droid的规则,依次生成它的依赖,直到所有满足的模块被编译好,然后使用相应的工具打包成相应的img。其中,config.mk,envsetup.mk,product_config.mk文件是编译用户指定平台系统的关键文件。上图中红色部分是用户指定平台产品的编译主线,我们先来看下config.mk的主要作用。


    2. build/core/config.mk

    该文件被main.mk包含。

    定义了以下环境变量:

    [plain] view plaincopy
    1. 16 SRC_HEADERS := \  
    2. 17     $(TOPDIR)system/core/include \  
    3. 18     $(TOPDIR)hardware/libhardware/include \  
    4. 19     $(TOPDIR)hardware/libhardware_legacy/include \  
    5. 20     $(TOPDIR)hardware/ril/include \  
    6. 21     $(TOPDIR)dalvik/libnativehelper/include \  
    7. 22     $(TOPDIR)frameworks/base/include \  
    8. 23     $(TOPDIR)frameworks/base/opengl/include \  
    9. 24     $(TOPDIR)external/skia/include  
    10. 25 SRC_HOST_HEADERS:=$(TOPDIR)tools/include  
    11. 26 SRC_LIBRARIES:= $(TOPDIR)libs  
    12. 27 SRC_SERVERS:= $(TOPDIR)servers  
    13. 28 SRC_TARGET_DIR := $(TOPDIR)build/target  
    14. 29 SRC_API_DIR := $(TOPDIR)frameworks/base/api  
    [plain] view plaincopy
    1. .....然后定义了下面的重要的编译命令  
    [plain] view plaincopy
    1.  43 CLEAR_VARS:= $(BUILD_SYSTEM)/clear_vars.mk  
    2.  44 BUILD_HOST_STATIC_LIBRARY:= $(BUILD_SYSTEM)/host_static_library.mk  
    3.  45 BUILD_HOST_SHARED_LIBRARY:= $(BUILD_SYSTEM)/host_shared_library.mk  
    4.  46 BUILD_STATIC_LIBRARY:= $(BUILD_SYSTEM)/static_library.mk  
    5.  47 BUILD_RAW_STATIC_LIBRARY := $(BUILD_SYSTEM)/raw_static_library.mk  
    6.  48 BUILD_SHARED_LIBRARY:= $(BUILD_SYSTEM)/shared_library.mk  
    7.  49 BUILD_EXECUTABLE:= $(BUILD_SYSTEM)/executable.mk  
    8.  50 BUILD_RAW_EXECUTABLE:= $(BUILD_SYSTEM)/raw_executable.mk  
    9.  51 BUILD_HOST_EXECUTABLE:= $(BUILD_SYSTEM)/host_executable.mk  
    10.  52 BUILD_PACKAGE:= $(BUILD_SYSTEM)/package.mk  
    11.  53 BUILD_HOST_PREBUILT:= $(BUILD_SYSTEM)/host_prebuilt.mk  
    12.  54 BUILD_PREBUILT:= $(BUILD_SYSTEM)/prebuilt.mk  
    13.  55 BUILD_MULTI_PREBUILT:= $(BUILD_SYSTEM)/multi_prebuilt.mk  
    14.  56 BUILD_JAVA_LIBRARY:= $(BUILD_SYSTEM)/java_library.mk  
    15.  57 BUILD_STATIC_JAVA_LIBRARY:= $(BUILD_SYSTEM)/static_java_library.mk  
    16.  58 BUILD_HOST_JAVA_LIBRARY:= $(BUILD_SYSTEM)/host_java_library.mk  
    17.  59 BUILD_DROIDDOC:= $(BUILD_SYSTEM)/droiddoc.mk  
    18.  60 BUILD_COPY_HEADERS := $(BUILD_SYSTEM)/copy_headers.mk  
    19.  61 BUILD_KEY_CHAR_MAP := $(BUILD_SYSTEM)/key_char_map.mk  

    上述命令变量其实是对应的mk文件名,几乎所有的Android.mk文件里基本上都包含上述命令变量,如:

    CLEAR_VARS:用来清除之前定义的环境变量

    BUILD_SHARED_LIBRARY:用来指定编译动态库过程

    [plain] view plaincopy
    1. 109 # ---------------------------------------------------------------  
    2. 110 # Define most of the global variables.  These are the ones that  
    3. 111 # are specific to the user's build configuration.  
    4. ### evnsetup.mk文件里定义了大部分的全局变量,用户使用这些变量来编译系统  
    5. 112 include $(BUILD_SYSTEM)/envsetup.mk  
    6. 113   
    7. 114 # Boards may be defined under $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)  
    8. 115 # or under vendor/*/$(TARGET_DEVICE).  Search in both places, but  
    9. 116 # make sure only one exists.  
    10. 117 # Real boards should always be associated with an OEM vendor.  
    11. ### 板级配置信息通常定义在$(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)或vendor/*/$(TARGET_DEVICE)下面,从这两个地方搜索,但是只能有一个地方存在板级配置信息(TARGET_DEVICE变量在上面的envsetup.mk里定义)  
    12.   
    13.   
    14. ### wildcard命令用于在某个目录下查找匹配的文件,将找到的文件列表返回  
    15. 118 board_config_mk := \  
    16. 119     $(strip $(wildcard \  
    17. 120         $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)/BoardConfig.mk \  
    18. 121         vendor/*/$(TARGET_DEVICE)/BoardConfig.mk \  
    19. 122     ))  
    20. ### 如果没有找到,提示出错  
    21. 123 ifeq ($(board_config_mk),)  
    22. 124   $(error No config file found for TARGET_DEVICE $(TARGET_DEVICE))  
    23. 125 endif  
    24. ### 如果找到了1个以上时,提示出错  
    25. 126 ifneq ($(words $(board_config_mk)),1)  
    26. 127   $(error Multiple board config files for TARGET_DEVICE $(TARGET_DEVICE): $(board_config_mk))  
    27. 128 endif  
    28. 129 include $(board_config_mk)  # 将板级配置信息包含进来  
    29. 130 TARGET_DEVICE_DIR := $(patsubst %/,%,$(dir $(board_config_mk)))  
    30. 131 board_config_mk :=  
    112行又包含了另外一个重要的mk文件envsetup.mk,我们来看一下。


    3. envsetup.mk

    [plain] view plaincopy
    1. 25 ifeq ($(TARGET_PRODUCT),)       # 如果TARGET_PRODUCT为空  
    2. 26 ifeq ($(TARGET_SIMULATOR),true) # 编译为模拟器  
    3. 27 TARGET_PRODUCT := sim  
    4. 28 else  
    5. 29 TARGET_PRODUCT := generic       # 默认产品名字为generic  
    6. 30 endif  
    7. 31 endif  
    第25行,判断TARGET_PRODUCT是否为空,根据上一节分析可知,TARGET_PRODUCT=fs100 
    [plain] view plaincopy
    1. 34 # the variant -- the set of files that are included for a build  
    2. 35 ifeq ($(strip $(TARGET_BUILD_VARIANT)),)  # 如果编译版本型号变量为空  
    3. 36 TARGET_BUILD_VARIANT := eng  
    4. 37 endif  
    5. 38   
    6. 39 # Read the product specs so we an get TARGET_DEVICE and other  
    7. 40 # variables that we need in order to locate the output files.  
    8. ## product_config.mk文件,读取产品配置信息,从而得到目标设备,将其导出到TARGET_DEVICE变量里,后面的定义的OUT变量的值,也依赖于目标设备TARGET_DEVICE,用于指定目标代码的输出目录  
    9. 41 include $(BUILD_SYSTEM)/product_config.mk  
    在41行又包含了product_config.mk文件,等会我们再分析它,先看下面的

    [plain] view plaincopy
    1. 148 # ---------------------------------------------------------------  
    2. 149 # figure out the output directories  
    3. 150   
    4. 151 ifeq (,$(strip $(OUT_DIR)))  
    5. 152 OUT_DIR := $(TOPDIR)out  
    6. 153 endif  
    7. 154   
    8. 155 DEBUG_OUT_DIR := $(OUT_DIR)/debug  
    9. 156   
    10. 157 # Move the host or target under the debug/ directory  
    11. 158 # if necessary.  
    12. 159 TARGET_OUT_ROOT_release := $(OUT_DIR)/target  
    13. 160 TARGET_OUT_ROOT_debug := $(DEBUG_OUT_DIR)/target  
    14. 161 TARGET_OUT_ROOT := $(TARGET_OUT_ROOT_$(TARGET_BUILD_TYPE))  
    15. 162   
    16. ...  
    17. ### 这个重要的OUT变量,依赖于目标设备名TARGET_DEVICE  
    18. 184 PRODUCT_OUT := $(TARGET_PRODUCT_OUT_ROOT)/$(TARGET_DEVICE)    
    19. 187   
    20. 188 HOST_OUT_EXECUTABLES:= $(HOST_OUT)/bin  
    21. 189 HOST_OUT_SHARED_LIBRARIES:= $(HOST_OUT)/lib  
    22. 190 HOST_OUT_JAVA_LIBRARIES:= $(HOST_OUT)/framework  
    23. 191 HOST_OUT_SDK_ADDON := $(HOST_OUT)/sdk_addon  
    24. ...  
    25. 200 TARGET_OUT_INTERMEDIATES := $(PRODUCT_OUT)/obj  
    26. 201 TARGET_OUT_HEADERS:= $(TARGET_OUT_INTERMEDIATES)/include  
    27. 202 TARGET_OUT_INTERMEDIATE_LIBRARIES := $(TARGET_OUT_INTERMEDIATES)/lib  
    28. 203 TARGET_OUT_COMMON_INTERMEDIATES := $(TARGET_COMMON_OUT_ROOT)/obj  
    29. 204  
    30. ### 后面的OUT变量都要间接依赖于TARGET_DEVICE  
    31. 205 TARGET_OUT := PRODUCT_OUT)/system  
    32. 206 TARGET_OUT_EXECUTABLES:= $(TARGET_OUT)/bin  
    33. 207 TARGET_OUT_OPTIONAL_EXECUTABLES:= $(TARGET_OUT)/xbin  
    34. 208 TARGET_OUT_SHARED_LIBRARIES:= $(TARGET_OUT)/lib  
    35. 209 TARGET_OUT_JAVA_LIBRARIES:= $(TARGET_OUT)/framework  
    36. 210 TARGET_OUT_APPS:= $(TARGET_OUT)/app  
    37. 211 TARGET_OUT_KEYLAYOUT := $(TARGET_OUT)/usr/keylayout  
    38. 212 TARGET_OUT_KEYCHARS := $(TARGET_OUT)/usr/keychars  
    39. 213 TARGET_OUT_ETC := $(TARGET_OUT)/etc  
    40. 214 TARGET_OUT_STATIC_LIBRARIES:= $(TARGET_OUT_INTERMEDIATES)/lib  
    41. 215 TARGET_OUT_NOTICE_FILES:=$(TARGET_OUT_INTERMEDIATES)/NOTICE_FILES  
    42. 216   
    43. 217 TARGET_OUT_DATA := $(<strong>PRODUCT_OUT</strong>)/data  
    44. 218 TARGET_OUT_DATA_EXECUTABLES:= $(TARGET_OUT_EXECUTABLES)  
    45. 219 TARGET_OUT_DATA_SHARED_LIBRARIES:= $(TARGET_OUT_SHARED_LIBRARIES)  
    46. 220 TARGET_OUT_DATA_JAVA_LIBRARIES:= $(TARGET_OUT_JAVA_LIBRARIES)  
    47. 221 TARGET_OUT_DATA_APPS:= $(TARGET_OUT_DATA)/app  
    48. 222 TARGET_OUT_DATA_KEYLAYOUT := $(TARGET_OUT_KEYLAYOUT)  
    49. 223 TARGET_OUT_DATA_KEYCHARS := $(TARGET_OUT_KEYCHARS)  
    50. 224 TARGET_OUT_DATA_ETC := $(TARGET_OUT_ETC)  
    51. 225 TARGET_OUT_DATA_STATIC_LIBRARIES:= $(TARGET_OUT_STATIC_LIBRARIES)  
    52. 226   
    53. 227 TARGET_OUT_UNSTRIPPED := $(<strong>PRODUCT_OUT</strong>)/symbols  
    54. 228 TARGET_OUT_EXECUTABLES_UNSTRIPPED := $(TARGET_OUT_UNSTRIPPED)/system/bin  
    55. 229 TARGET_OUT_SHARED_LIBRARIES_UNSTRIPPED := $(TARGET_OUT_UNSTRIPPED)/system/lib  
    56. 230 TARGET_ROOT_OUT_UNSTRIPPED := $(TARGET_OUT_UNSTRIPPED)  
    57. 231 TARGET_ROOT_OUT_SBIN_UNSTRIPPED := $(TARGET_OUT_UNSTRIPPED)/sbin  
    58. 232 TARGET_ROOT_OUT_BIN_UNSTRIPPED := $(TARGET_OUT_UNSTRIPPED)/bin  
    59. 233   
    60. 234 TARGET_ROOT_OUT := $(<strong>PRODUCT_OUT</strong>)/root  
    61. 235 TARGET_ROOT_OUT_BIN := $(TARGET_ROOT_OUT)/bin  
    62. 236 TARGET_ROOT_OUT_SBIN := $(TARGET_ROOT_OUT)/sbin  
    63. 237 TARGET_ROOT_OUT_ETC := $(TARGET_ROOT_OUT)/etc  
    64. 238 TARGET_ROOT_OUT_USR := $(TARGET_ROOT_OUT)/usr  
    65. 239   
    66. 240 TARGET_RECOVERY_OUT := $(<strong>PRODUCT_OUT</strong>)/recovery  
    67. 241 TARGET_RECOVERY_ROOT_OUT := $(TARGET_RECOVERY_OUT)/root  
    68. 242   
    69. 243 TARGET_SYSLOADER_OUT := $(<strong>PRODUCT_OUT</strong>)/sysloader  
    70. 244 TARGET_SYSLOADER_ROOT_OUT := $(TARGET_SYSLOADER_OUT)/root  
    71. 245 TARGET_SYSLOADER_SYSTEM_OUT := $(TARGET_SYSLOADER_OUT)/root/system  
    72. 246   
    73. 247 TARGET_INSTALLER_OUT := $(<strong>PRODUCT_OUT</strong>)/installer  
    74. 248 TARGET_INSTALLER_DATA_OUT := $(TARGET_INSTALLER_OUT)/data  
    75. 249 TARGET_INSTALLER_ROOT_OUT := $(TARGET_INSTALLER_OUT)/root  
    76. 250 TARGET_INSTALLER_SYSTEM_OUT := $(TARGET_INSTALLER_OUT)/root/system  
    上面的代码是指定了目标输出代码的位置和主机输出代码的位置,重要的几个如下:

    [plain] view plaincopy
    1. TARGET_OUT = $(<strong>PRODUCT_OUT</strong>)/system  
    2. TARGET_OUT_EXECUTABLES =  $(PRODUCT_OUT)/system/bin  
    3. TARGET_OUT_SHARED_LIBRARIES =  $(PRODUCT_OUT)/system/lib  
    4. TARGET_OUT_JAVA_LIBRARIES = $(PRODUCT_OUT)/system/framework  
    5. TARGET_OUT_APPS = $(PRODUCT_OUT)/system/app  
    6. TARGET_OUT_ETC = $(PRODUCT_OUT)/system/etc  
    7. TARGET_OUT_STATIC_LIBRARIES  = $(PRODUCT_OUT)/obj/lib  
    8. TARGET_OUT_DATA = $(PRODUCT_OUT)/data  
    9. TARGET_OUT_DATA_APPS = $(PRODUCT_OUT)/data/app  
    10. TARGET_ROOT_OUT = $(PRODUCT_OUT)/root  
    11. TARGET_ROOT_OUT_BIN = $(PRODUCT_OUT)/bin  
    12. TARGET_ROOT_OUT_SBIN  = $(PRODUCT_OUT)/system/sbin  
    13. TARGET_ROOT_OUT_ETC = $(PRODUCT_OUT)/system/etc  
    14. TARGET_ROOT_OUT_USR = $(PRODUCT_OUT)/system/usr  

    总结下:

    envsetup.mk文件主要包含了product_config.mk文件,然后指定了编译时要输出的所有文件的OUT目录,这些OUT目录变量依赖于TARGET_DEVICE变量。


    4. build/core/product_config.mk

    [plain] view plaincopy
    1. 157 include $(BUILD_SYSTEM)/product.mk  
    2. ...  
    3. 160 # Read in all of the product definitions specified by the AndroidProducts.mk  
    4. 161 # files in the tree.  
    5. 162 #  
    6. 163 #TODO: when we start allowing direct pointers to product files,  
    7. 164 #    guarantee that they're in this list.  
    8. 165 $(call import-products, $(get-all-product-makefiles))  
    9. 166 $(check-all-products)  
    10. ...  
    11. 170 # Convert a short name like "sooner" into the path to the product  
    12. 171 # file defining that product.  
    13. 172 #  
    14. 173 INTERNAL_PRODUCT := $(call resolve-short-product-name, $(TARGET_PRODUCT))  
    15. ...  
    16. 176 # Find the device that this product maps to.  
    17. 177 TARGET_DEVICE := $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_DEVICE)  
    157行,我靠,又包含了product.mk文件

    165行,调用函数import-products, $(get-all-product-makefiles),这儿我们看上面的注释:

        Read in all of the product definitions specified by the AndroidProducts.mk files in the tree.
        TODO: when we start allowing direct pointers to product files, guarantee that they're in this list.

        意思是说:读取厂商目录下(vendor/*/products/AndroidProducts.mk)所有的AndrodProducts.mk文件中定义的产品信息

        其实get-all-product-makefiles返回厂商全部产品文件xxx.mk

        import-products函数去验证这些产品配置文件是否都包含有必须的配置信息,细节后面分析。

    173行调用了resolve-short-product-name函数,它将根据产品TARGET_PRODUCT,返回该产品配置文件的完整路径,并赋给INTERNAL_PRODUCT

        例如TARGET_PRODUCT=fs100,则:

        INTERNAL_PRODUCT = vendor/farsight/products/fs100.mk

        TARGET_DEVICE = fs100

        如果调试看其结果,可以在167行,将#$(dump-product)取消注释

        然后在175行添加: $(info $(INTERNAL_PRODUCT))

        在178行添加: $(info $(TARGET_DEVICE )),查看调试结果。

    总结一下:

    接合前面的图,product_config.mk主要读取vendor目录下不同厂商自己定义的AndrodProducts.mk文件(vendor/*/products/AndroidProducts.mk),从该文件里取得所有产品的配置文件,然后再根据lunch选择的编译项TARGET_PRODUCT,找到与之对应的配置文件,然后读取产品配置文件,找到里面的PRODUCT_DEVICE的值,设置给TARGET_DEVICE变量,用于后续编译。


    5. build/core/product.mk

    [plain] view plaincopy
    1. 17 #  
    2. 18 # Functions for including AndroidProducts.mk files  
    3. 19 #  
    4. 20   
    5. 21 #  
    6. 22 # Returns the list of all AndroidProducts.mk files.  
    7. 23 # $(call ) isn't necessary.  
    8. 24 #  
    9. 25 define <strong>_find-android-products-files</strong>  
    10. 26 $(shell test -d vendor && find vendor -maxdepth 6 -name AndroidProducts.mk) \  
    11. 27   $(SRC_TARGET_DIR)/product/AndroidProducts.mk  
    12. 28 endef  
    13. 29   
    14. 30 #  
    15. 31 # Returns the sorted concatenation of all PRODUCT_MAKEFILES  
    16. 32 # variables set in all AndroidProducts.mk files.  
    17. 33 # $(call ) isn't necessary.  
    18. 34 #  
    19. 35 define <strong>get-all-product-makefiles</strong>  
    20. 36 $(sort \  
    21. 37   $(foreach f,$(_find-android-products-files), \  
    22. 38     $(eval PRODUCT_MAKEFILES :=) \  
    23. 39     $(eval LOCAL_DIR := $(patsubst %/,%,$(dir $(f)))) \  
    24. 40     $(eval include $(f)) \  
    25. 41     $(PRODUCT_MAKEFILES) \  
    26. 42    ) \  
    27. 43   $(eval PRODUCT_MAKEFILES :=) \  
    28. 44   $(eval LOCAL_DIR :=) \  
    29. 45  )  
    30. 46 endef  
    通过注释可知,本文件中主要是一些用来处理AndroidProduct.mk的函数
    _find-android-products-files:

        用来获得vendor目录下,所有名字为AndroidProduct.mk的文件列表。
    get-all-product-makefiles:

        用来获得所有AndroidProduct.mk文件里定义的PRODUCT_MAKEFILES的值(其实是产品文件路径名)。


    在vendor目录下,每个厂商子目录下都会存在一个AndroidProduct.mk文件,这个文件是用来定义这个厂商的产品列表,每个产品用<product_name>.mk来表示
    如Android给的示例:

    [plain] view plaincopy
    1. vendor/sample/products/AndroidProduct.mk  
    其内容如下:

    [plain] view plaincopy
    1.  1 #  
    2.  2 # This file should set PRODUCT_MAKEFILES to a list of product makefiles  
    3.  3 # to expose to the build system.  LOCAL_DIR will already be set to  
    4.  4 # the directory containing this file.   
    5.  5 #  
    6.  6 # This file may not rely on the value of any variable other than  
    7.  7 # LOCAL_DIR; do not use any conditionals, and do not look up the  
    8.  8 # value of any variable that isn't set in this file or in a file that  
    9.  9 # it includes.  
    10. 10 #  
    11. 11   
    12. 12 PRODUCT_MAKEFILES := \  
    13. 13   $(LOCAL_DIR)/sample_addon.mk  
    里面只定义了一个产品配置文件,即当前目录下的sample_addon.mk:
    [plain] view plaincopy
    1. 1 # List of apps and optional libraries (Java and native) to put in the add-on system image.  
    2. 2 PRODUCT_PACKAGES := \  
    3. 3     PlatformLibraryClient \  
    4. 4     com.example.android.platform_library \  
    5. 5     libplatform_library_jni  
    上述文件里(sample_addon.mk)定义了产品相关个性化信息,如,PRODUCT_PACKAGES表示要在当前产品里添加一些安装包。
    由此可见,get-all-product-makefiles函数,其实就是返回了当前公司里全部的产品对应的mk文件列表。


    总结:

    如果用户想个性定制自己的产品,应该有以下流程,包含上一节内容:

    注:#表示shell提示符

    1. 创建厂商目录

        #mkdir vendor/farsight

    2. 创建一个vendorsetup.sh文件,将当前产品编译项添加到lunch里,让lunch能找到用户产品编译项

        #echo "add_lunch_combo fs100-eng" > vendor/farsight/vendorsetup.sh

        注:我们增加一个用户产品编译项,fs100-eng

    3. 仿着Android示例代码,在厂商目录下创建products目录

        #mkdir -p vendor/farsight/products

    4. 仿着Android示例代码,在products目录下创建两个mk文件

        #touch vendor/farsight/products/AndroidProduct.mk   vendor/farsight/products/fs100.mk

        注:其中AndroidProduct.mk是当前厂商产品列表文件,fs100.mk表示当前厂商的一款产品配置文件

    在AndroidProduct.mk里添加如下内容:

    [plain] view plaincopy
    1. PRODUCT_MAKEFILES := $(LOCAL_DIR)/fs100.mk  
       注:表示只有一个产品fs100,它对应的配置文件在当前目录下的fs100.mk。

    5. 在产品配置文件里添加最基本信息

    [plain] view plaincopy
    1.  1   
    2.  2 PRODUCT_PACKAGES := \  
    3.  3     IM \  
    4.  4     VoiceDialer  
    5.  5   
    6.  6 $(call inherit-product, build/target/product/generic.mk)  ##从某一默认配置开始派生余下内容参考派生起点  
    7.  7   
    8.  8 # Overrides  
    9.  9 PRODUCT_MANUFACTURER := farsight  
    10. 10 PRODUCT_NAME := fs100  
    11. 11 PRODUCT_DEVICE := fs100  

    一定要注意:

      PRODUCT_NAME:表示产品名字,它要和最终出现的编译项产品名一致,也就是说fs100-eng

      PRODUCT_DEVICE:表示设备名字,它要和将来创建的设备目录名字一致。


    Android编译系统详解(三)

    http://blog.csdn.net/mr_raptor/article/details/7540730

    前面两节讲解了自定义Android编译项和创建Product产品配置文件,除了编译和定义产品相关环境变量外,还需要定义Board相关环境变量。

    1. build/core/config.mk

    [plain] view plaincopy
    1. 109 # ---------------------------------------------------------------   
    2. 110 # Define most of the global variables.  These are the ones that   
    3. 111 # are specific to the user's build configuration.   
    4. 112 include $(BUILD_SYSTEM)/envsetup.mk   
    5. 113    
    6. 114 # Boards may be defined under $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)   
    7. 115 # or under vendor/*/$(TARGET_DEVICE).  Search in both places, but   
    8. 116 # make sure only one exists.   
    9. 117 # Real boards should always be associated with an OEM vendor.   
    10. 118 board_config_mk := \   
    11. 119     $(strip $(wildcard \   
    12. 120         $(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)/BoardConfig.mk \   
    13. 121         vendor/*/$(TARGET_DEVICE)/BoardConfig.mk \   
    14. 122     ))   
    15. 123 ifeq ($(board_config_mk),)   
    16. 124   $(error No config file found for TARGET_DEVICE $(TARGET_DEVICE))   
    17. 125 endif   
    18. 126 ifneq ($(words $(board_config_mk)),1)   
    19. 127   $(error Multiple board config files for TARGET_DEVICE $(TARGET_DEVICE): $(board_config_mk))   
    20. 128 endif   
    21. 129 include $(board_config_mk)   
    22. 130 TARGET_DEVICE_DIR := $(patsubst %/,%,$(dir $(board_config_mk)))   
    23. 131 board_config_mk :=   

    上述代码在上一节已经见到过,只是分析了112行的envsetup.mk,根据上一节内容可知,envsetup.mk设置了很多OUT变量,最终在build/core/product_config.mk文件里,设置了TARGET_DEVICE = fs100。

    我们从114行继续分析。

    • 从114~117行解释大意可知:

        Board相关配置文件会存在于$(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)/或vendor/*/$(TARGET_DEVICE)/目录中,一个Vendor厂商只能有一个对应的Board配置文件。

    • 118行,定义board_config_mk变量:

         $(wildcard xxx)函数就是找到与xxx的匹配项放到空格列表里,前面定义TARGET_DEVICE变量 = fs100,所以$(SRC_TARGET_DIR)/board/fs100/BoardConfig.mk不存在,必须要存在vendor/*/fs1 00/BoardConfig.mk文件来定义开发板配置信息。

    • 129行,通过include将vendor/*/fs100/BoardConfig.mk包含进来,
    • 130行,TARGET_DEVICE_DIR为board_config_mk的路径,即:vendor/*/fs100

      总结:

     一个vendor厂商必须要有一个对应的Board配置文件,即:vendor/*/fs100/BoardConfig.mk

        定义TARGET_DEVICE_DIR变量,为board_config_mk的路径,即:vendor/*/fs100

    指定board 相关特性,一定要包含:
          TARGET_CPU_ABI := armeabi/...
    其他属性参见其他board样例.(build/target/board/XXX


    2.  build/core/main.mk

    [plain] view plaincopy
    1. 141 # Bring in standard build system definitions.  
    2. 142 include $(BUILD_SYSTEM)/definitions.mk  
    3. ...  
    4. 347 ifeq ($(SDK_ONLY),true)  
    5. 348  
    6. 349 # ----- SDK for Windows ------  
    7. 350 # These configure the build targets that are available for the SDK under Cygwin.  
    8. 351 # The first section defines all the C/C++ tools that can be compiled under Cygwin,  
    9. 352 # the second section defines all the Java ones (assuming javac is available.)  
    10. 353  
    11. 354 subdirs := \  
    12. 355     prebuilt \  
    13. 356     build/libs/host \  
    14. 357     build/tools/zipalign \  
    15. ...  
    16. 382 # The following can only be built if "javac" is available.  
    17. 383 # This check is used when building parts of the SDK under Cygwin.  
    18. 384 ifneq (,$(shell which javac 2>/dev/null))  
    19. 385 $(warning sdk-only: javac available.)  
    20. 386 subdirs += \  
    21. 387     build/tools/signapk \  
    22. 388     dalvik/dx \  
    23. 389     dalvik/libcore \  
    24. ...  
    25. 414 else    # !SDK_ONLY  
    26. 415 ifeq ($(BUILD_TINY_ANDROID), true)  
    27. 416  
    28. 417 # TINY_ANDROID is a super-minimal build configuration, handy for board  
    29. 418 # bringup and very low level debugging  
    30. 419  
    31. 420 subdirs := \  
    32. 421     bionic \  
    33. 422     system/core \  
    34. 423     build/libs \  
    35. 424     build/target \  
    36. ...  
    37. 433 else    # !BUILD_TINY_ANDROID  
    38. 434  
    39. 435 #  
    40. 436 # Typical build; include any Android.mk files we can find.  
    41. 437 #  
    42. 438 subdirs := $(TOP)  
    43. 439  
    44. 440 FULL_BUILD := true  
    45. 441  
    46. 442 endif   # !BUILD_TINY_ANDROID  
    47. 443  
    48. 444 endif   # !SDK_ONLY  
    49. ...  
    50. 464 #  
    51. 465 # Include all of the makefiles in the system  
    52. 466 #  
    53. 467  
    54. 468 # Can't use first-makefiles-under here because  
    55. 469 # --mindepth=2 makes the prunes not work.  
    56. 470 subdir_makefiles := \  
    57. 471     $(shell build/tools/findleaves.py --prune=out --prune=.repo --prune=.git $(subdirs) Android.mk)  
    58. 472  
    59. 473 include $(subdir_makefiles)  

    上一节只是讲了main.mk第49行中包含了config.mk,我们继续分析。

    142行包含了:build/core/definitions.mk,该文件定义了很多全局变量与函数。

    如下列常见函数:

        my-dir:返回当前路径

        all-java-files-under:获得指定目录及子目录一所有java文件

        all-subdir-c-files:获得当前目录下及子目录下所有c文件

    354~444行,定义了subdirs变量,依据不同的用户编译条件,而包含Android源码中不同的目录。

    470行,定义了subdir_makefile变量,其值为subdirs定义的目录中的Android.mk文件。

    473行,将所有编译目录中的Android.mk文件包含进来。


    3. build/target/board/Android.mk

    [plain] view plaincopy
    1. 26 ifeq (,$(wildcard $(TARGET_DEVICE_DIR)/AndroidBoard.mk))  
    2.  27   ifeq (,$(wildcard $(TARGET_DEVICE_DIR)/Android.mk))  
    3.  28     $(error Missing "$(TARGET_DEVICE_DIR)/AndroidBoard.mk")  
    4.  29   else  
    5.  30     # TODO: Remove this check after people have had a chance to switch,  
    6.  31     # after April 2009.  
    7.  32     $(error Please rename "$(TARGET_DEVICE_DIR)/Android.mk" to "$(TARGET_DEVICE_DIR)/AndroidBoard.mk")  
    8.  33   endif  
    9.  34 endif  
    10.  35 include $(TARGET_DEVICE_DIR)/AndroidBoard.mk  
    由于将所有目录中Android.mk文件include进来,build/target/board/Android.mk自然被包含进来,根据前面分析,TARGET_DEVICE_DIR = vendor/*/fs100,其中26~35行用来判断对应的产品目录下是否存在AndrodiBoard.mk,如果不存在,提示出错退出,如果存在,将其包含到编译脚本中。

    由此可见:我们必须要在产品目录下创建AndrodiBoard.mk文件,来描述开发板相关配置项,我们可以借鉴:build/target/board/generic/AndroidBoard.mk内容,同时根据前面所分析,还要创建BoardConfig.mk文件。

    [plain] view plaincopy
    1. $ cp build/target/board/generic/AndroidBoard.mk build/target/board/generic/BoardConfig.mk  vendor/farsight/fs100/  

    至此,自定义Android编译选项基本步骤已经分部分析完,细节还需要针对不同开发板具体分析。

    总结:

    build/core/main.mk包含了config.mk,它主要定义了编译全部代码的依赖关系

          build/core/config.mk         定义了大量的编译脚本命令,编译时用到的环境变量,引入了envsetup.mk 文件,加载board相关配置文件。
          build/core/envsetup.mk   定义了编译时用到的大量OUT输出目录,加载product_config.mk文件
          build/core/product_config.mk 定义了Vendor目录下Product相关配置文件解析脚本,读取AndrodProducts.mk生成TARGET_DEVICE变量
          build/target/product          product config
          build/target/board            board config
          build/core/combo             build flags config 

          这里解释下这里的board和product。borad主要是设计到硬件芯片的配置,比如是否提供硬件的某些功能,比如说GPU等等,或者芯片支持浮 点运算等等。product是指针对当前的芯片配置定义你将要生产产品的个性配置,主要是指APK方面的配置,哪些APK会包含在哪个product中,哪些APK在当前product中是不提供的。
          config.mk是一个总括性的东西,它里面定义了各种module编译所需要使用的HOST工具以及如何来编译各种模块,比如说 BUILT_PREBUILT就定义了如何来编译预编译模块。envsetup.mk主要会读取由envsetup.sh写入环境变量中的一些变量来配置编译过程中的输出目录,combo里面主要定义了各种Host和Target结合的编译器和编译选项。

    1. 在vendor目录下创建自己公司目录,然后在公司目录下创建一个新的vendorsetup.sh,在里面添加上自己的产品编译项

    [plain] view plaincopy
    1. $ mkdir vendor/farsight/   
    2. $ touch vendor/farsight/vendorsetup.sh   
    3. $ echo "add_lunch_combo fs100-eng" > vendor/farsight/vendorsetup.sh   

    2. 仿着Android示例代码,在公司目录下创建products目录

    [plain] view plaincopy
    1. $ mkdir -p vendor/farsight/products  

    3. 仿着Android示例代码,在products目录下创建两个mk文件

    [plain] view plaincopy
    1. $ touch vendor/farsight/products/AndroidProduct.mk vendor/farsight/products/fs100.mk  

        在AndroidProduct.mk里添加如下内容:

    [plain] view plaincopy
    1. PRODUCT_MAKEFILES := $(LOCAL_DIR)/fs100.mk   

        在产品配置文件里添加最基本信息

    [plain] view plaincopy
    1. $(call inherit-product, build/target/product/generic.mk)   
    2.    
    3. # Overrides   
    4. PRODUCT_MANUFACTURER := farsight   
    5. PRODUCT_NAME := fs100   
    6. PRODUCT_DEVICE := fs100   

    4. 借鉴build/target/board/generic/AndroidBoard.mk和BoardConfig.mk,创建对应文件。

    [plain] view plaincopy
    1. $ cp build/target/board/generic/AndroidBoard.mk build/target/board/generic/BoardConfig.mk  vendor/farsight/fs100/  








    展开全文
  • Android编译系统分析系列文章: android编译系统分析一 Android编译系统-mm编译单个模块 android编译系统分析(三)-make android编译系统(四)-实战:新增一个产品 这篇博客的目标是摸清楚默认编译整个...

    Android编译系统分析系列文章:

    android编译系统分析一<source build/envsetup.sh与lunch>
    Android编译系统<二>-mm编译单个模块
    android编译系统分析(三)-make
    android编译系统(四)-实战:新增一个产品
    Android编译系统分析(五)-system.img的生成过程


    这篇博客的目标是摸清楚默认编译整个android系统时代码的流程。

    当我们执行make的时候,会查找当前的Makefie文件或者makefile文件并且执行,在android顶级源码目录下面,确实有个Makefile,它之后一行内容:

    ### DO NOT EDIT THIS FILE ###
    include build/core/main.mk
    ### DO NOT EDIT THIS FILE ###
    因此,正真执行的是build/core/main.mk

    一.依赖浅析

    当我们执行make命令的时候,如果没有传入一个目标,那么就会执行默认的目标。注意,我们在编译android系统的时候,只需要执行make就可以了,那么很显然它会执行默认的目标了,那么默认的目标是什么呢?

    在build/core/main.mk中:

    # This is the default target.  It must be the first declared target.
    .PHONY: droid
    DEFAULT_GOAL := droid
    $(DEFAULT_GOAL):

    在main.mk开始不久,就出现了一个伪目标,即便你看不懂Makefile也没有关系,注释上说的很清楚了,他就是默认的目标了。而且这个默认的目标是一个伪目标。make工具遇到伪目标以后,会检查解析伪目标的依赖,如果伪目标存在依赖,就会检查这些依赖,如果这些依赖是伪目标,继续检查这个伪目标的依赖,如果不是伪目标,就会生成这个目标。

    阅读一个Makefile,理清目标的依赖关系很重,下图列出了部分重要的以来关系:


    在对依赖关系有个了解之后,我们开始顺着make的加载流程,看看它到底做了什么。

    首先,我觉得很重要的就是加载特定产品的配置信息。

    二.配置产品信息

    首先,大致的流程如下图所示:


    在product_config.mk中:

    ifneq ($(strip $(TARGET_BUILD_APPS)),)
    # An unbundled app build needs only the core product makefiles.
    all_product_configs := $(call get-product-makefiles,\
        $(SRC_TARGET_DIR)/product/AndroidProducts.mk)
    else
    # Read in all of the product definitions specified by the AndroidProducts.mk
    # files in the tree.
    all_product_configs := $(get-all-product-makefiles)
    endif

    1.AndoridProducts.mk 使用get-all-product-makefiles获取所有的AndoridProducts.mk文件:

    define get-all-product-makefiles
    $(call get-product-makefiles,$(_find-android-products-files))
    endef
    调用_find-android-products-files获取所有的AndroidProducts.mk,然后交由get-product-makefiles函数处理。

    define _find-android-products-files
    $(shell test -d device && find device -maxdepth 6 -name AndroidProducts.mk) \
      $(shell test -d vendor && find vendor -maxdepth 6 -name AndroidProducts.mk) \
      $(SRC_TARGET_DIR)/product/AndroidProducts.mk
    endef
    define get-product-makefiles
    $(sort \
      $(foreach f,$(1), \
        $(eval PRODUCT_MAKEFILES :=) \
        $(eval LOCAL_DIR := $(patsubst %/,%,$(dir $(f)))) \
        $(eval include $(f)) \
        $(PRODUCT_MAKEFILES) \
       ) \
      $(eval PRODUCT_MAKEFILES :=) \
      $(eval LOCAL_DIR :=) \
     )
    endef
    可以看到最终处理的结果是加载了AndroidProducts.mk, 返回了一个排好顺序的PRODUCT_MAKEFILES。

    这里把所有的AndroidProducts.mk都加载进来了,但是我们只需要我们产品的配置信息呀,所以接着做一个查找,找到属于我们产品的AndroidProducts.mk:

    # Find the product config makefile for the current product.
    # all_product_configs consists items like:
    # <product_name>:<path_to_the_product_makefile>
    # or just <path_to_the_product_makefile> in case the product name is the
    # same as the base filename of the product config makefile.
    current_product_makefile :=
    all_product_makefiles :=
    $(foreach f, $(all_product_configs),\
        $(eval _cpm_words := $(subst :,$(space),$(f)))\
        $(eval _cpm_word1 := $(word 1,$(_cpm_words)))\
        $(eval _cpm_word2 := $(word 2,$(_cpm_words)))\
        $(if $(_cpm_word2),\
            $(eval all_product_makefiles += $(_cpm_word2))\
            $(if $(filter $(TARGET_PRODUCT),$(_cpm_word1)),\
                $(eval current_product_makefile += $(_cpm_word2)),),\
            $(eval all_product_makefiles += $(f))\
            $(if $(filter $(TARGET_PRODUCT),$(basename $(notdir $(f)))),\
                $(eval current_product_makefile += $(f)),)))
    _cpm_words :=
    _cpm_word1 :=
    _cpm_word2 :=
    current_product_makefile := $(strip $(current_product_makefile))
    all_product_makefiles := $(strip $(all_product_makefiles))

    2.current_product_makefile

    最终找到的结果存储在current_product_makefile中。关于它的值,这里举例说明:

    加入我们在lunch的时候选择了 5:

         1. aosp_arm-eng
         2. aosp_arm64-eng
         3. aosp_mips-eng
         4. aosp_mips64-eng
         5. aosp_x86-eng
         6. aosp_x86_64-eng
    那么经过以上查找current_product_makefile就等于device/generic/x86/mini_x86.mk

    3.加载产品配置文件

    ifneq (,$(filter product-graph dump-products, $(MAKECMDGOALS)))
    # Import all product makefiles.
    $(call import-products, $(all_product_makefiles))
    else
    # Import just the current product.
    ifndef current_product_makefile
    $(error Can not locate config makefile for product "$(TARGET_PRODUCT)")
    endif
    ifneq (1,$(words $(current_product_makefile)))
    $(error Product "$(TARGET_PRODUCT)" ambiguous: matches $(current_product_makefile))
    endif
    $(call import-products, $(current_product_makefile))
    endif  # Import all or just the current product makefile
    
    # Sanity check
    $(check-all-products)
    
    在import-products中导入产品的配置信息,这里就是device/generic/x86/mini_x86.mk。

    4然后获取TARGET_DEVICE的值:

    # Find the device that this product maps to.
    TARGET_DEVICE := $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_DEVICE)
    此时,TARGET_DEVICE = mini_x86.mk;

    5获取要拷贝的文件

    # A list of words like <source path>:<destination path>[:<owner>].
    # The file at the source path should be copied to the destination path
    # when building  this product.  <destination path> is relative to
    # $(PRODUCT_OUT), so it should look like, e.g., "system/etc/file.xml".
    # The rules for these copy steps are defined in build/core/Makefile.
    # The optional :<owner> is used to indicate the owner of a vendor file.
    PRODUCT_COPY_FILES := \
        $(strip $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_COPY_FILES))

    这个变量也很重要,它存储了需要拷贝的文件。格式为 <source path>:<destination path>,在build/core/Makefile一开始就会先拷贝这个变量指定的文件。

    6.加载BoardConfig.mk

    又回到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 \
    		$(shell test -d device && find device -maxdepth 4 -path '*/$(TARGET_DEVICE)/BoardConfig.mk') \
    		$(shell test -d vendor && find vendor -maxdepth 4 -path '*/$(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)
    ifeq ($(TARGET_ARCH),)
      $(error TARGET_ARCH not defined by board config: $(board_config_mk))
    endif

    BoardConfig.mk中配置了重要的板级信息,比如cpu架构等。

    至此,配置一个产品所需的AndroidProducts.mk,具体产品的配置文件,比如这里的mini_x86.mk以及BoardConfig.mk都加载进来了。


    三.加载所有模块

    加载完单板信息,make又回到main.mk中,不就就发现了ONE_SHOT_MAKEFILE变量的判断:

    1ONE_SHOT_MAKEFILE

    ifneq ($(ONE_SHOT_MAKEFILE),)
    # We've probably been invoked by the "mm" shell function
    # with a subdirectory's makefile.
    include $(ONE_SHOT_MAKEFILE)
    # Change CUSTOM_MODULES to include only modules that were
    # defined by this makefile; this will install all of those
    # modules as a side-effect.  Do this after including ONE_SHOT_MAKEFILE
    # so that the modules will be installed in the same place they
    # would have been with a normal make.
    CUSTOM_MODULES := $(sort $(call get-tagged-modules,$(ALL_MODULE_TAGS)))
    FULL_BUILD :=
    # Stub out the notice targets, which probably aren't defined
    # when using ONE_SHOT_MAKEFILE.
    NOTICE-HOST-%: ;
    NOTICE-TARGET-%: ;
    
    # A helper goal printing out install paths
    .PHONY: GET-INSTALL-PATH
    GET-INSTALL-PATH:
    	@$(foreach m, $(ALL_MODULES), $(if $(ALL_MODULES.$(m).INSTALLED), \
    		echo 'INSTALL-PATH: $(m) $(ALL_MODULES.$(m).INSTALLED)';))
    
    else # ONE_SHOT_MAKEFILE
    
    ifneq ($(dont_bother),true)
    #
    # Include all of the makefiles in the system
    #
    
    # Can't use first-makefiles-under here because
    # --mindepth=2 makes the prunes not work.
    subdir_makefiles := \
    	$(shell build/tools/findleaves.py --prune=$(OUT_DIR) --prune=.repo --prune=.git $(subdirs) Android.mk)
    
    $(foreach mk, $(subdir_makefiles), $(info including $(mk) ...)$(eval include $(mk)))
    
    endif # dont_bother
    
    endif # ONE_SHOT_MAKEFILE
    如果这个变量定义了,那么,就是编译一个模块,在上一篇博客中已将分析过了,如果没有定义,就说明是编译整个系统。

    MAKECMDGOALS是make的一个环境变量,当我们执行make的时候并没有设置它,因此它为空。所以dont_bother不等于true,因此,就会加载所有的Android.mk.这里使用
    一个python脚本查找系统中所有的Android.mk,然后Include进来。
    四 收集所有要安装的模块
    在main.mk中继续往下看:

    3.1FULL_BUILD

    ifdef FULL_BUILD
      # The base list of modules to build for this product is specified
      # by the appropriate product definition file, which was included
      # by product_config.mk.
      product_MODULES := $(PRODUCTS.$(INTERNAL_PRODUCT).PRODUCT_PACKAGES)
      # Filter out the overridden packages before doing expansion
      product_MODULES := $(filter-out $(foreach p, $(product_MODULES), \
          $(PACKAGES.$(p).OVERRIDES)), $(product_MODULES))
    
      # Resolve the :32 :64 module name
      modules_32 := $(patsubst %:32,%,$(filter %:32, $(product_MODULES)))
      modules_64 := $(patsubst %:64,%,$(filter %:64, $(product_MODULES)))
      modules_rest := $(filter-out %:32 %:64,$(product_MODULES))
      # Note for 32-bit product, $(modules_32) and $(modules_64) will be
      # added as their original module names.
      product_MODULES := $(call get-32-bit-modules-if-we-can, $(modules_32))
      product_MODULES += $(modules_64)
      # For the rest we add both
      product_MODULES += $(call get-32-bit-modules, $(modules_rest))
      product_MODULES += $(modules_rest)
    
      $(call expand-required-modules,product_MODULES,$(product_MODULES))
    
      product_FILES := $(call module-installed-files, $(product_MODULES))
      ifeq (0,1)
        $(info product_FILES for $(TARGET_DEVICE) ($(INTERNAL_PRODUCT)):)
        $(foreach p,$(product_FILES),$(info :   $(p)))
        $(error done)
      endif
    else
      # We're not doing a full build, and are probably only including
      # a subset of the module makefiles.  Don't try to build any modules
      # requested by the product, because we probably won't have rules
      # to build them.
      product_FILES :=
    endif
    在执行make的时候,FULL_BUILD:=true
    product_MODULES是所有产品配置文件中添加的要打包进系统镜像中的模块,它只是一个名字,比如上篇博客分析过的screencap。
    product_FILES获取对应模块的.INSTALLED的值。
    define module-installed-files
    $(foreach module,$(1),$(ALL_MODULES.$(module).INSTALLED))
    endef
    在加载单个模块的时候,会给每一个模块生成另外两个值:
    $(ALL_MODULES.$(target)).BUILT
    $(ALL_MODULES.$(target)).INSTALLED
    它们在base_rule.mk中生成:
    ALL_MODULES.$(my_register_name).BUILT := \
        $(ALL_MODULES.$(my_register_name).BUILT) $(LOCAL_BUILT_MODULE)
    ifneq (true,$(LOCAL_UNINSTALLABLE_MODULE))
    ALL_MODULES.$(my_register_name).INSTALLED := \
        $(strip $(ALL_MODULES.$(my_register_name).INSTALLED) $(LOCAL_INSTALLED_MODULE))
    ALL_MODULES.$(my_register_name).BUILT_INSTALLED := \
        $(strip $(ALL_MODULES.$(my_register_name).BUILT_INSTALLED) $(LOCAL_BUILT_MODULE):$(LOCAL_INSTALLED_MODULE))
    endif

    $(ALL_MODULES.$(target)).BUILT代表的一般是out/target/product/xxx/obj下编译生成的模块。
    $(ALL_MODULES.$(target)).INSTALLED代表的是out/target/product/xxx/system下生成的模块。

    3.2 全部安装模块

    modules_to_install := $(sort \
        $(ALL_DEFAULT_INSTALLED_MODULES) \
        $(product_FILES) \
        $(foreach tag,$(tags_to_install),$($(tag)_MODULES)) \
        $(CUSTOM_MODULES) \
      )

    ALL_DEFAULT_INSTALLED_MODULES是系统默认要安装的模块,product_FILES是特定产品附加的要安装的模块,foreach找到的是特定
    TAG的模块,以及加上CUSTOM_MODULES,这样modules_to_install就是全部的要安装的模块了。
    
    ALL_DEFAULT_INSTALLED_MODULES := $(modules_to_install)
    include $(BUILD_SYSTEM)/Makefile

    然后把modules_to_install的值全部赋给ALL_DEFAULT_INSTALLED_MODULES,接着加载build/core/Makefile。这个Makefile会使用
    ALL_DEFAULT_INSTALLED_MODULES变量最终生成所有的镜像文件。生成镜像文件的过程放在下一节讨论。


    四.编译所有模块

    依赖关系我们在一开始就做了简单的梳理,现在开始分析编译所有模块的依赖关系。

    从droid目标定义的地方来看,没有看到它的依赖,但我们向下搜索,就会发现:

    .PHONY: apps_only
    apps_only: $(unbundled_build_modules)
    
    droid: apps_only
    # Building a full system-- the default is to build droidcore
    droid: droidcore dist_files

    我们会发现它有出现了两个依赖,那它到底依赖哪一个呢?

    droid依赖哪一个取决于ifneq ($(TARGET_BUILD_APPS),)是否成立,也就是有没有给TARGET_BUILD_APPS赋值过,源码如下:

    ifneq ($(TARGET_BUILD_APPS),)
      # If this build is just for apps, only build apps and not the full system by default.
    
      unbundled_build_modules :=
      ifneq ($(filter all,$(TARGET_BUILD_APPS)),)
        # If they used the magic goal "all" then build all apps in the source tree.
        unbundled_build_modules := $(foreach m,$(sort $(ALL_MODULES)),$(if $(filter APPS,$(ALL_MODULES.$(m).CLASS)),$(m)))
      else
        unbundled_build_modules := $(TARGET_BUILD_APPS)
      endif
    
    ...
    
    .PHONY: apps_only
    apps_only: $(unbundled_build_modules)
    
    droid: apps_only
    
    else # TARGET_BUILD_APPS
      $(call dist-for-goals, droidcore, \
        $(INTERNAL_UPDATE_PACKAGE_TARGET) \
        $(INTERNAL_OTA_PACKAGE_TARGET) \
        $(BUILT_OTATOOLS_PACKAGE) \
        $(SYMBOLS_ZIP) \
        $(INSTALLED_FILES_FILE) \
        $(INSTALLED_BUILD_PROP_TARGET) \
        $(BUILT_TARGET_FILES_PACKAGE) \
        $(INSTALLED_ANDROID_INFO_TXT_TARGET) \
        $(INSTALLED_RAMDISK_TARGET) \
       )
    # Building a full system-- the default is to build droidcore
    droid: droidcore dist_files
    
    endif # TARGET_BUILD_APPS
    

    我们期望的是整个系统的编译,所以,droid依赖的是droidcore 和 dist_files

    4.1droidcore的定义:

    # Build files and then package it into the rom formats
    .PHONY: droidcore
    droidcore: files \
    	systemimage \
    	$(INSTALLED_BOOTIMAGE_TARGET) \
    	$(INSTALLED_RECOVERYIMAGE_TARGET) \
    	$(INSTALLED_USERDATAIMAGE_TARGET) \
    	$(INSTALLED_CACHEIMAGE_TARGET) \
    	$(INSTALLED_VENDORIMAGE_TARGET) \
    	$(INSTALLED_FILES_FILE)

    可以droidcore又是一个伪目标,它又依赖于files 等一系列目标,从名字来看,这些目标应该是systemimage,userdataimage,recoryimage等,也就是说,droidcore的最终目的就是生成system.img,userdata.img等系统镜像文件。

    看到变量的定义就明白了:

    1.boot.img:

    INSTALLED_BOOTIMAGE_TARGET := $(PRODUCT_OUT)/boot.img

    2.recovery.img:

    INSTALLED_RECOVERYIMAGE_TARGET := $(PRODUCT_OUT)/recovery.img

    3.userdata.img:

    INSTALLED_USERDATAIMAGE_TARGET := $(BUILT_USERDATAIMAGE_TARGET)

      --->BUILT_USERDATAIMAGE_TARGET := $(PRODUCT_OUT)/userdata.img

    4.cache.img

    INSTALLED_CACHEIMAGE_TARGET := $(BUILT_CACHEIMAGE_TARGET)

     --->BUILT_CACHEIMAGE_TARGET := $(PRODUCT_OUT)/cache.img

    5.vendor.img

    INSTALLED_VENDORIMAGE_TARGET := $(BUILT_VENDORIMAGE_TARGET)

    BUILT_VENDORIMAGE_TARGET := $(PRODUCT_OUT)/vendor.img

    因此,droidcore的最终目的就是生成这些.Img文件。

    dist_files的定义:

    # dist_files only for putting your library into the dist directory with a full build.
    .PHONY: dist_files
    从定义来看,dist_files也是个伪目标,并且它没有任何依赖,作用是完整编译系统的时候拷贝库文件。


    4.2.files

    它的第一个目标是files:

    # All the droid stuff, in directories
    .PHONY: files
    files: prebuilt \
            $(modules_to_install) \
            $(INSTALLED_ANDROID_INFO_TXT_TARGET)、

    1.1files又依赖了三个目标,第一个是prebuilt:

    # -------------------------------------------------------------------
    # This is used to to get the ordering right, you can also use these,
    # but they're considered undocumented, so don't complain if their
    # behavior changes.
    .PHONY: prebuilt
    prebuilt: $(ALL_PREBUILT)

    prebuilt又是一个伪目标,它又依赖于ALL_PREBUILT变量指向的目标,ALL_PREBUILT是一些预编译模块:

    Android.mk (makefile\frameworks\base\cmds\bmgr):ALL_PREBUILT += $(TARGET_OUT)/bin/bmgr
    Android.mk (makefile\frameworks\base\cmds\ime):ALL_PREBUILT += $(TARGET_OUT)/bin/ime
    Android.mk (makefile\frameworks\base\cmds\input):ALL_PREBUILT += $(TARGET_OUT)/bin/input
    Android.mk (makefile\frameworks\base\cmds\pm):ALL_PREBUILT += $(TARGET_OUT)/bin/pm
    Android.mk (makefile\frameworks\base\cmds\svc):ALL_PREBUILT += $(TARGET_OUT)/bin/svc


    4.3modules_to_install 

    modules_to_install := $(sort \
        $(ALL_DEFAULT_INSTALLED_MODULES) \
        $(product_FILES) \
        $(foreach tag,$(tags_to_install),$($(tag)_MODULES)) \
        $(CUSTOM_MODULES) \
      )

    这个变量之前已经分析过,它包含所有的要安装的模块,make会为这个目标生成依赖关系链,也就是会给其中的每一个模块生成依赖关系链,然后编译每一个模块,这个过程在上一节中已经说过了。
    至此,所有应该编译的模块都已经被编译,剩下的就是打包镜像文件了。这将在下一节讨论。


    展开全文
  • Android编译系统介绍

    2016-09-13 16:02:20
    Android编译系统基于GNU make, 也经常采用Python和bash脚本工具。不同于其他基于make的递归型编译系统,Android通过一个脚本搜索所有目录以及子目录,直到发现一个Android.mk文件为止。下图为Android编译系统的结构...
  • Android编译系统学习总结

    千次阅读 2018-04-17 16:56:54
    Android编译系统 Android编译系统 1. makefile入门 2. Android编译系统 3. Java编译链(java android compiler kit) 4. SDK的编译过程 5. Android GDB调试过程 1. makefile入门 makefile本质是创建的一...
  • Android编译系统流程

    2014-10-22 15:47:48
    Android编译系统流程
  • Android编译系统详解123

    2017-05-19 22:38:33
    Android编译系统详解1 http://www.cnblogs.com/mr-raptor/archive/2012/06/07/2540359.html Android编译系统详解2 http://www.cnblogs.com/mr-raptor/archive/2012/06/08/2541571.html Android编译系统详解3 ...
  • 一、概述  自Android开源以来,引起了嵌入式行业一股热潮,很多嵌入式开发者表示对Android有很强的兴趣,并下载Android源码进行编译和移植。Android源码的巨大(repo下来,... Android编译系统(build system)
  • Android编译系统中的Android.bp

    万次阅读 2018-05-14 16:34:19
    转自:http://note.qidong.name/2017/08/android-blueprint/Android编译系统中的Android.bp、Blueprint与Soong本文简单介绍Android Nougat(7.0)中引入的Android.bp,及其相关工具链。简介Android.bp,是用来替换...
  • Android编译系统详解

    2014-01-21 18:59:14
    Android编译系统详解(一):http://blog.csdn.net/mr_raptor/article/details/7539978 Android编译系统详解(二):http://blog.csdn.net/mr_raptor/article/details/7540066 Android编译系统详解(三):...
  • Android编译系统分析

    万次阅读 2012-09-21 18:42:55
    Android编译系统分析 概要 由于Android编译系统的复杂和使用了不熟悉的Python,所以对其一直望而却步;工作中使用Android.mk也仅仅是拷贝修改。最近由于工作需要解决一个编译方面的问题1,浏览了一下编译系统;...
  • Android编译系统 .

    千次阅读 2014-04-16 14:31:56
    Android编译系统 1. Android编译系统 编译脚本主要位置: *.mk build/core/ build/tools/ build/envsetup.sh:定义函数mm,mmm,choosecombo等; 2. 模块编译 2.1 Java模块 通常Java模块的编译提供一个...
  • Android编译系统结构

    千次阅读 2011-12-07 15:48:24
    Android编译系统的架构: 分析Android编译系统,你会发现,Android编译系统完成的并不仅仅是对目标(主机)系统二进制文件、java应用程序的编译、链接、打包等,而且还有包括生成各种依赖关系、确保某个模块的修改...
  • 第四章、Android编译系统与定制Android平台系统 4.1Android编译系统Android的源码由几十万个文件构成,这些文件之间有的相互依赖,有的又相互独立,它们按功能或类型又被放到不同目录下,对于这个大的一个工程,...
  • Android编译系统简要介绍和学习计划

    万次阅读 多人点赞 2014-02-10 01:02:53
    在Android源码环境中,我们开发好一个模块后,再写一个Android.mk文件,...这一切都得益于Android编译系统,它为我们处理了各种依赖关系,以及提供各种有用工具。本文对Android编译系统进行简单介绍以及制定学习计划。
  • Android编译系统简析

    2016-04-04 22:09:06
    Android 编译系统 的简单理解 一,Makefile 入门 :简单来说,Makefile提供了一种机制,让使用者可以有效的组织“工作”,这里说的是“工作”,而不是“编译”,因为Makefile并一定是用来完成编译工作,事实上它本身...
  • 深入理解:Android 编译系统

    千次阅读 2016-08-01 08:29:10
    深入理解:Android 编译系统
  • Android编译系统环境初始化过程分析

    万次阅读 多人点赞 2014-02-17 01:00:14
    Android源代码在编译之前,要先对编译环境进行初始化...为了确定源码支持的所有目标设备型号,Android编译系统在初始化的过程中,需要在特定的目录中加载特定的配置文件。接下来本文就对上述的初始化过程进行详细分析。
  • android编译系统分析

    2012-03-14 15:12:38
    自Android开源以来,引起了嵌入式行业一股热潮,很多嵌入式开发者表示对Android有...本文从Android编译系统的角度,让大家了解Android其实也是很纯真的。 Android编译系统(build system)集中于Android源码下的build/
  • Android编译系统分析系列文章: android编译系统分析一 Android编译系统-mm编译单个模块 android编译系统分析(三)-make android编译系统(四)-实战:新增一个产品 通过上一节“android编译系统(三)-...
  • Android 编译系统分析

    2011-12-29 23:24:22
    自Android开源以来,引起了嵌入式行业一股热潮,很多嵌入式开发者表示对Android有...本文从Android编译系统的角度,让大家了解Android其实也是很纯真的。 Android编译系统(build system)集中于Android源码下的buil
  • Android编译系统是基于GNU make,google设计它是为了提供编译性能和减少没必要重新编译的模块。而且相对makefile操作起来要容易多得。Android编译系统完成的并不仅仅是对目标(主机)系统二进制文件、java应用程序的...
  • Android编译系统参考手册

    千次阅读 2015-12-29 16:49:36
    build/core/base_rules.mk base_rules.mk里定义了生成...Android编译系统里每一种目标的生成方式对应一个makefile, 示例:如果某个模块需要编译成手机上的二进制程序,它需要include $(BUILD_EXECUTABLE)  而BUI

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 15,816
精华内容 6,326
关键字:

android编译系统