前言:预计未来一致两年内移动互联网将会有很大的发展,必将孕育很多的机会,而 Google 推出的 Android 手机操作系统无疑将是移动互联网中的明星。由于其是开源系统,很有必要对此进行深入研究。
工欲善其事,必先利其器。 Makefile 无疑是打开系统架构的一扇窗户。但因 Android 的 Makefile (build system) 文件众多,架构复杂,分析起来较为困难。本文梳理了 build system 的大致脉络,希望对 build system 感兴趣的同学们有所帮助。
1. Android Makefile & build system 概述
Makefile 文件用来告诉 make 命令需要怎么样的去编译和链接程序。在编译时,需要根据编译环境和编译目标选择编译工具,编译参数,以及选择编译安装哪些模块。同时 Makefile 指定了构建目标所需的依赖性以及生成规则。 在 Android 中,主要的 Makefile 文件存在于 build/core/ 目录下,它的表现形式为多个后缀为 mk 的文件组成,也称为 build system 。 Android build system 主要有两大部分构成:配置部分,目标构建部分。 Build system 的主流程文件为 build/core/main.mk 文件。
Android build system 在设计中考虑了如下方面,具有良好的扩展性。
a) 增添子模块编译
b) 多 CPU 架构 – ARM/PPC(maybe)/X86(maybe)
c) 多语言编译 – C/C++/Java
d) 多目标 – static lib/share lib/execute/Java/Java library
e) 多发布版本
本文也将就围绕这些特性做进一步分析。
2. Build system 配置部分
配置部分主要完成以下几个工作:
a) 基于 Android 产品的配置( product config ):选择构建安装的运行程序 (user package)
b) 设置 target 等相关变量 TARGET_ARCH, TARGET_OS, TARGET_BUILD_TYPE, TARGET_PREBUILT_TAG
c) 根据编译环境设置 host 等相关变量 HOST_OS, HOST_ARCH, HOST_BUILD_TYPE, HOST_PREBUILT_TAG
d) 编译 target 上运行程序所需的工具链及编译参数设置,如 linux-arm-cc , cflag , include 目录等。
e) 编译 host 上运行程序所需的工具链及编译参数设置。
下图简要介绍了 Android build system 的配置部分的主要构成及相互关系。
多发布版本的支持
Android 会被不同的厂商所采用,他们内置的 packages( 应用程序 ) 相应也会有差别。 AndroidProducts.mk 文件即为 Android build system 提供给厂商的接口文件。通过此文件即可定义所需编译和安装的 packages (也即应用程序)。缺省选项是 generic 。为了更容易的扩展, Android 定义了基本 package(core.mk) 和通用 package(generic.mk) ,通用 package 包含基本 package 。同时 Android 还实现了一个继承函数( inherit-product )。通过继承通用 package ,可以很容易的配置所需编译和安装的 package 。
多 CPU 架构的扩展
Config.mk 文件中会设置 combo_target 为不同的变量,然后 include select.mk 文件。在 select.mk 文件中,会根据 OS 和 CPU 架构选择相应的 mk 文件,在这些相应的 mk 文件中,又定义了编译目标程序所需的工具链及编译参数。目前从 combo 目录下看, target 上不支持 PPC 架构。但如果要想支持 PPC 架构的话,只需在 combo 目录下创建 PPC 的 mk 文件,在其中定义工具链和参数即可。
3. build system 目标构建部分
目标构建部分的主要工作就是选择所需构建的目标,确定它们所需依赖的目标,然后根据规则来构建最终的目标。说起来简单,可是在实际中就需要考虑很多问题了。
a) 作为一个设计优秀的 Framework ,如何方便的添加子模块?
b) 在 Android 系统中,有多种编程语言的存在,它们的编译工具各不相同。即使同一种语言,如 C 语言,也存在 host 和 target 的差别,编译器也不同。如何选择不同的工具进行模块的构建?
c) 在 Android 系统中,存在不同种类的构建目标:有可执行文件, static library, dynamic library , java library , java 。如何构建这些不同的目标?
下图简要介绍了 Android build system 的构建部分的主要构成及相互关系
在 main.mk 中,非常关键的一个步骤就是找到 TOP 目录下所有 Android.mk 文件,并 include 它们。在 Include 的过程中,就会确定子模块的构建目标,类型,和规则。
Android.mk 就是 build system 提供给子模块的借口文件。 Android.mk 有下面几个关键词:
LOCAL_SRC_FILES – 指定模块的源文件
LOCAL_MODULE – 指定所需构建的目标名
include 构建类型对应的文件 – 例如想构建在 target 上运行的可执行文件,那就执行语句 include $(BUILD_EXECUTABLE) 。通过此语句可有如下结果: a) 指定构建目标的类型 b )确定构建此类型所需的工具及参数。
通过定义自己的 Android.mk 文件,再修改上述等变量,即可轻松的把自身模块放入至 build system 中。
Main.mk 文件 471 行: include $(subdir_makefiles)
subdir_makefiles 为 TOP 目录下所有 Android.mk 文件的集合。语句虽短,可确是整个 build system 中最为重要的一条语句。无论有多少子模块,无论构建模块的目标类型,无论它是什么语言所写,就这一条语句,完成了这些纷繁复杂的工作。
是否似曾相识? Android.mk 就类似于 build system 提供的基类, LOCAL_MODULE , LOCAL_SRC_FILES , include 构建类型文件等类似于基类提供的虚函数。通过继承基类 (Android.mk) ,重写虚函数(重新定义 LOCAL_ 等变量),遍历子类集合调用虚函数( include $(subdir_makefiles) ),完美的解决了本节开头的问题。为 Android build system 提供了良好的可扩展性。
后记:
设计模式,架构并不仅存于 OO 的语言中 (Java/C++) 。即便如 Makefile 的类脚本语言,也可写出如此之架构。设计的思想在于人,而不取决于他所用的工具。