Android编译系统分析之make分析

Android 编译系统解析系列文档

编译系统入口envsetup.sh解析

解析lunch的执行过程以及make执行过程中include文件的顺序

关注一些make执行过程中的几个关键点

对一些独特的语法结构进行解析


概览

Android编译系统概览

  • makefile语法的简要说明Makefile文件的include顺序main.mk文件中include的各文件职责
  • config.mk文件中include的各文件职责

Android编译系统流程的简要总结

正文

编译android的源代码时我们要经历三个阶段

1
2
3
source build/envsetup.sh
lunch project_name
make (SHOW_COMMANDS)

关于前两阶段的具体内容请参见另一篇文章 android 编译系统之lunch分析
这一章节我们主要来看看整个android编译系统的整体框架

Android编译系统概览

makefile语法的简要说明

1
2
target ... : prerequisites ...
[tab] command

这是makefile基本语法

  • target 就是目标文件,可以是一个object,也可以是一个可执行文件,还可以是一个label(伪目标)
  • prerequisites 就是要生成target所依赖的文件或者目标
  • command 也就是make要执行的命令(任意的shell命令)

注意,command命令必须要使用[tab]键开头,否则make无法识别

明白了语法规则,来看看执行规则:
target 这一个或者多个目标的文件依赖于prerequistites中的文件,其生成规则在command中,这其中的依赖规则判断是:如果prerequisites中有一个或以上文件比target新(判断modification times),那么就执行command所定义的命令

默认情况下执行make命令会在当前目录下依次寻找GNUMakefile(GNU独有)、makefile、Makefile这三个文件,然后执行文件中声明的第一个目标
所以我们在源码根目录执行make文件时,会默认执行Makefile,而这个文件内容只有一行

1
include build/core/main.mk

我们直接来看main.mk文件干了什么

Makefile文件的include顺序

通过解析main.mk文件,我们可以大致看到include的文件的顺序

    # 以下为执行make与lunch时,include文件顺序以及行号

    #执行make时,会include build/core/main.mk
    main.mk (
        89:include help.mk
        93:include config.mk
        99:include cleanbuild.mk
        260:include definitions.mk
        263:include dex_preopt.mk
        297:include pdk_config.mk
        487:include $(ONE_SHOT_MAKEFILE)(如果变量不为空,则include)
        525:include post_clean.mk
        531:include legacy_prebuilts.mk
        797:include Makefile
    )

    #执行lunch时候,会直接make -f build/core/config.mk #
    config.mk (
        60:include pathmap.mk
        151:include envsetup.mk (
            10:include version_defaults.mk (
                34:include build_id.mk
            )
            138:include product_config.mk (
                178:include node_fns.mk
                179:include product.mk
                180:include device.mk
            )
            162:include $(board_config_mk)  (BoardConfig.mk, device和vendor目录,保证其only one)
        )
        226:include combo/select.mk
        342:include combo/javac.mk
        576:include clang/config.mk
        $:include dumpvar.mk
    )

main.mk文件中include的各文件职责

core/help.mk
  • Targets that provide quick help on the build system
    目的是为了在构建系统的时候提供快捷的帮助

core/help.mk文件中只定义了一个伪目标help,用来打印make命令的一些target提示

core/config.mk
  • Set up various standard variables based on configuration and host information
    目的是基于用户的配置和编译主机的环境信息来配置一些基本的变量(这些功能又由include的一些文件来承担)

core/config.mk文件通过收集用户定义的机型信息(在device以及vendor目录下的机型文件)以及定义一些基本的变量例如我们在编写模块编译文件也就是Android.mk文件时,经常要include一个变量CLEAR_VARS,这个变量实际的值就是build/core/clear_vars.mk文件,还有其他一些BUILD_STATIC_LIBRARY,BUILD_SHARED_LIBRARY也都是通过include对应的mk文件来添加一些定义项,这个文件还会指定一些编译工具的目录,方便后边的使用,也会收集一些主机编译环境来判断是否符合android编译的要求。
从上一篇lunch的分析中,我们得知,lunch时,直接执行config.mk文件
make -f build/core/config.mk
因此这个文件以及其中include的那些文件是加载各种配置文件的核心文件,这个文件以及其中include的那些文件我们稍后集中分析,我们继续往下看

core/cleanbuild.mk
  • This allows us to force a clean build - included after the config.mk
    environment setup is done, but before we generate any dependencies. This
    file does the rm -rf inline so the deps which are all done below will
    be generated correctly

允许我们进行强制的清理-包括在config.mk环境变量设置之后,在我们产生任何依赖之前
这个文件内部执行rm -rf ,因此后边完成的这些依赖可以被正确的产生,文件定义了一个installclean的伪目标

我们可以在make user或make all之间切换的时候执行make installclean,这样的好处在于只会清理build type相关的内容,而不会全清,节省编译时间。这个文件还定义了一个previous_build_config.mk文件,在每次编译的时候,将会写入下边的变量的值
$(TARGET_PRODUCT)-$(TARGET_BUILD_VARIANT)-{$(aapt_config_list)}
如果与和上一次不同,就会强制进行installclean操作,来避免不同build type切换时产生的影响

Tips:
这个文件的内容可以帮助我们快速确认当前编译的机型,编译类型,还有语言配置和aapt的配置

core/definitions.mk
  • Bring in standard build system definitions
    加入标准的通用编译系统的一些函数以及变量的定义

这个文件会定义一些通用的函数,比如我们很熟悉的all-subdir-makefile,all-java-files-under,copy-file-to-target等,这些函数的定义大大提升了我们在编写自己的机型以及模块mk文件的便捷性

core/dex_preopt.mk
  • 加入dexprepopt的支持

这个文件通常用来在构建user版的时候执行dexopt(优化dalvik)或者dex2oat(优化ART),来为jar和apk做优化

core/pdk_config.mk
  • The pdk (Platform Development Kit) build

PDK的配置文件,pdk是google引入的一套机制,方便手机硬件制造商在源代码版本开放之前取得android版本,从而更快的跟上google更新android的步伐,减少android的碎片化

$(ONE_SHOT_MAKEFILE)
  • 顾名思义,ONE_SHOT一次使用,主要用来include编译所需要的各个模块的Android.mk文件

这个变量在使用mm 与 mmm命令的时候被定义,也就是在编译某个模块的时候会被赋值,这种情况下只需要include需要的Android.mk文件就行,如果不是单编,并且make的目标是一些不需要依赖的目标的话,例如snod,clean,systemimage-nodeps等,那么什么都不干,直接生成目标就行,除这两种情况,也就是在全编的情况下,需要include所有模块的Android.mk文件

core/post_clean.mk
  • 在所有文件都加载完成之后,做一个clean或者check的工作

这个文件是在include 所有的makefile文件之后,因此overlay和aidl文件肯可能会有变化,这个文件的作用就是check是否发生变化,然后决定是否要重新生成

legacy_prebuilts.mk
  • 这是对一些预编译的文件的处理

之前使用的是ALL_PREBUILT这个变量来定义,在新版中使用PRODUCT_COPY_FILE来代替,这里的主要作用是检查ALL_PREBUILT变量中是否含有legacy_prebuild.mk所包含的预编译文件,如果有就会报错,提示你要将这些文件使用PRODUCT_COPY_FILE来预编译

Makefile
  • 核心编译文件,定义了版本固件的最终生成文件

这个文件算是android编译系统的核心文件了,之前include的所有的文件,定义的变量,用户的配置,都是为了这个文件服务,这个文件会先check预编译的文件的合法性(也就是检查PRODUCT_COPY_FILE变量),然后去构建build.prop,接着定义了systemimg,bootimage,ramdisk,otapackage等编译系统要生成的最终目标,也就是在这个文件加载之后,系统内所有的需要编译的mk文件都会汇合成一个大的makefile文件,然后按序执行

config.mk文件中include的各文件职责

core/pathmap.mk
  • central place to define mappings to paths, to avoid hard-coding them in Android.mk files.
    定义匹配路径的地方,避免在Android.mk中硬编码这些位置

将一些经常用到的路径定义为固定的宏,方便开发者编写Makefile文件时引用,主要是一些头文件的路径

core/envsetup.mk
  • 顾名思义,是一些环境变量的初始化

设置一些HOST主机以及TARGET目标的基本的环境变量,例如out,system,等的目录的宏定义,还有区分目标架构x86与64,HOST类型linux与windows,以及各个类型的生成文件该放置到目录的定义,例如bin,etc,jar等文件,除此之外还解析了BoardConfig.mk以及AndroidProduct.mk文件,并对其中的BOARD_xxx以及PRODUCT_xxx变量进行check和处理

core/version_defaults.mk
  • 定义一些BUILD环境的基本变量

PLATFORM_VERSION := 5.1.1,android版本号
PLATFORM_SDK_VERSION := 22,定义了目标SDK的版本,软件中最小的版本号不能超出这个值
PLATFORM_VERSION_CODENAME := REL 一个版本名称,如果是最后一个版本,可以简单的使用REL
DEFAULT_APP_TARGET_SDK := $(PLATFORM_VERSION_CODENAME) APP的默认的版本号,如果是最后发布版,则使用PLATFORM_SDK_VERSION的值
BUILD_ID := 通常用于usr版中显示的版本号
BUILD_NUMBER := eng.$(USER).$(shell date +%Y%m%d.%H%M%S) build的数字,默认是以工程版格式命名,用日期来区分

core/build_id.mk

导出BUILD_ID的变量的值,通常用于user版中的版本号,如果没有,就是UNKNOWN

core/product_config.mk
  • 关于product的一些用户设置的处理

主要内容有三点:1.android提供了一种方法可以直接指定要编译的product而不需要初始化环境变量来选择,只需要你在make的时候指定格式”PRODUCT–”,以76为例,我们可以直接make PRODUCT-meizu_m76-eng来编译76 eng的版本,只不过这样的话,你就无法使用croot,godir等函数了.与此类似的还有独立app的编译格式”APP-“
2.对product以及device等变量(PRODUCT_xxxx,TARGET_xxx,DEVICE_xxx,BOARD_xxx)的处理函数的定义
3.对device以及vendor目录下定义的PRODUCT_变量的处理

core/node_fns.mk
  • 编译系统对product相关变量(PRODUCT_xxx,DEVICE_xxx)处理的函数的定义

实际上这个文件的内容主要是对inherit这种继承的方式做了定义,是后边定义的product.mk与device.mk中使用到的一些函数的公共定义说明,主要使用的方式是inherit-product与inherit-device

core/product.mk
  • product相关变量的使用的帮助函数

这个文件提供了PRODUCT_xxx变量的处理方法,以及PRODCUT_xxx,TARGET_xxxx,BOARD_xxx等变量的声明,后边会使用这几个列表来过滤出相关的定义,然后做出处理,主要的内容是inherit-product的定义,这可以使得我们可以很方便的使用google提供的一些预定义的变量来初始化我们自己的product

core/device.mk
  • device相关变量的使用帮助函数

定义了DEVICE_xxx函数列表,以及同样的inherit-device来继承已有的变量定义

$(board_config_mk)
  • 对BOARD_xxx打头的变量的处理,也就是对BoardConfig.mk文件的处理

扫描device以及vendor目录下4层深度以内的BoardConfig.mk文件,并include

combo/select.mk
  • 对C/C++编译器参数做一些设置
combo/javac.mk
  • 对java编译器的选择和一些参数的设置
core/dumpvar.mk
  • 打印一些变量的值

这个mk文件的主要作用就是当我们lunch了一个机型之后,会以一个规范的格式打印相关变量的值

Android编译系统流程的简要总结

在了解了以上的include顺序之后,我们总结一下Android的编译系统:

  • 首先通过来检查一些系统编译环境的各个软件版本是否符合要求,并定义一些基本宏来表示不同的目录与基本的环境变量,参与文件: help.mk,pathmap.mk,version_defaults.mk,pathmap.mk,build_id.mk
  • 然后读取产品层ProductConfig的设置,这其中就包含了对以PRODUCT_xxxx,TARGET_xxxx,BOARD_xxxx等开头一系列变量(包括PRODUCT_LOCALES,PRODUCT_COPY_FILES等)的处理,参与文件:变量product_config.mk所包括的值(AndroidProduct.mk以及其中定义的类似device.mk的makefile文件)
  • 然后读取一些硬件层BoardConfig的设置,参与文件:变量board_config_mk所表示的值(BoardConfig.mk)
  • 然后根据当前编译的类型(mm还是mmm还是clean还是全编)来决定包含哪些模块,参与文件:Android.mk
  • 然后打印出一些已经配置好的变量的值,可以通过printconfig命令来查看,参与文件:dump-var.mk
  • 然后定义一些基本的函数方便开发者在编写makefile文件的时候使用,参与文件:definitions.mk
  • 然后对之前所作的操作做一些检查,参与文件:post_clean.mk
  • 最后定义最终的编译目标(systemiamge,bootimage,ramdisk等) 以及他们之间依赖的文件,参与文件:Makefile
作者

0xforee

发布于

2015-12-08

更新于

2015-12-10

许可协议


欢迎关注我的公众号 0xforee,第一时间获取更多有价值的思考

评论