跳转至

架构

如图 4-1 所示,理解 Android 编译系统的入口是 build/core/ 目录下的 main.mk 文件——我们在上一章已经看到,它通过顶层 makefile 被调用。build/core/ 目录实际上包含了编译系统的主体内容,后文将覆盖其中的关键文件。需要再次提醒的是:Android 的编译系统将所有内容聚合到一个 makefile 中,它并不递归。因此,你看到的每个 .mk 文件最终都会成为包含整个系统构建规则的单一巨大 makefile 的一部分。

图 4-1. Android 编译系统

为什么 make 会"挂起"?

每次你输入 make 时,都会目睹一个看似恼人的构建现象:编译系统输出构建配置信息后,会在屏幕静止很长一段时间后才继续。在这段漫长的沉默之后,它才真正开始编译 AOSP 的各个部分,并像普通构建系统那样向屏幕输出常规信息。任何构建过 AOSP 的人都曾好奇:在这段时间里编译系统到底在做什么?答案是:它正在将 AOSP 中的所有 Android.mk 文件聚合到一起来。

如果你想亲眼看看这个过程,可以编辑 build/core/main.mk,将:

include $(subdir_makefiles)

替换为:

$(foreach subdir_makefile, $(subdir_makefiles), \
$(info Including $(subdir_makefile)) \
$(eval include $(subdir_makefile)) \
)
subdir_makefile :=

下次执行 make -j16 时,你就会看到实际发生了什么:

============================================
PLATFORM_VERSION_CODENAME=REL
PLATFORM_VERSION=2.3.4
TARGET_PRODUCT=generic
...
============================================
Including ./bionic/Android.mk
Including ./development/samples/Snake/Android.mk
Including ./libcore/Android.mk
...

配置

编译系统做的第一件事,是通过包含 config.mk 来引入编译配置。编译配置可以通过两种方式完成:使用 envsetup.shlunch 命令,或者在顶层目录提供 buildspec.mk 文件。两种方式都需要设置以下部分变量。

envsetup.sh

envsetup.sh 脚本提供了一系列命令,用于配置和构建 AOSP。其核心是 lunch 命令,用于选择要构建的 Android 变体(flavor)和目标设备。

envsetup.sh 中包含的关键命令:

  • lunch:选择要构建的产品。执行 lunch <product_name> 可以设置 TARGET_PRODUCTTARGET_BUILD_VARIANT 等环境变量。
  • m:从源码树根目录运行 make,等价于完整编译。
  • croot:快速回到源码树根目录。
  • pid:查看正在运行的模拟器或设备的 ADB PID。
  • grep:在所有 Makefile 中搜索指定的 makefile 变量或目标。
  • get_build_var:获取指定变量的值。
  • set_stuff_for_environment:设置编译所需的环境变量。
  • set_sequence_number:设置序列号(用于并行构建)。
  • add_lunch_combo:向 lunch 菜单添加新的构建变体组合。

通过 lunch 设置的主要变量包括:

变量 说明
TARGET_PRODUCT 要构建的 Android 产品/设备型号
TARGET_BUILD_VARIANT 构建变体(如 useruserdebugeng
TARGET_ARCH CPU 架构(如 armx86
TARGET_OS 操作系统(通常为 linux
TARGET_BUILD_TYPE 构建类型(如 releasedebug
BUILD_SYSTEM 编译系统所在路径(通常为 build/core

执行 lunch <product> 后,这些变量被设置,后续的 make 命令会使用这些配置来决定构建哪些模块以及如何构建。

buildspec.mk 的优先级高于 envsetup.sh 的设置。如果 buildspec.mk 存在于顶层目录,编译系统会首先读取其中的变量,覆盖通过 lunch 设置的值。

函数定义

由于 Android 的编译系统相当庞大——仅 build/core/ 下就有超过 40 个 .mk 文件——为了尽可能复用代码,系统中定义了大量的函数和宏。这些函数定义分散在各个 .mk 文件中,其中最重要的定义位于 definitions.mk

definitions.mk 中定义的常用函数包括:

  • my_dir:返回当前 Android.mk 所在目录的路径。
  • all-java-files-under:返回指定目录下所有的 Java 文件。
  • all-c-files-under:返回指定目录下所有的 C 文件。
  • all-cpp-files-under:返回指定目录下所有的 C++ 文件。
  • intermediates:返回中间文件(.o、.dex 等)的输出目录。
  • gen:生成文件的目录。
  • java_library:构建 Java 库的标准模板。
  • shared_library:构建共享库的标准模板。
  • static_library:构建静态库的标准模板。
  • cc_binary:构建 C/C++ 二进制可执行文件。
  • cc_library:构建 C/C++ 库(可配置为静态或共享)。

这些函数通过 include 语句引入 Makefile,大幅简化了各个模块 Android.mk 的编写。

主要 Make 配方

你可能想知道各种产物(如 RAM disk 镜像、SDK 等)究竟是如何生成的。这些内容由 main.mk 中定义的主要构建目标控制。

核心构建目标:

  • droid:默认目标,构建完整的 Android 系统镜像(相当于 make droid)。
  • **sdk`:构建 Android SDK。
  • **cts`:构建兼容性测试套件(CTS)。
  • **ndk`:构建 Android 原生开发套件(NDK)。
  • **clean:清理构建输出(删除out/` 目录)。
  • clobber:完全清理,包括删除所有构建产物。
  • modules:仅构建所有模块,不生成系统镜像。
  • showcommands:在构建时显示每个命令的详细输出。

清理

如前所述,make clean 等价于删除 out/ 目录。clean 目标本身在 main.mk 中定义,但此外还有其他清理目标:

  • make clean:删除 out/target/product/<product>/ 下的所有内容。
  • make clobber:删除整个 out/ 目录,包括所有产品的构建产物。
  • make clean-<module>:清理指定模块的构建产物。

模块构建模板

以上描述的是编译系统的架构及其核心组件的运行机制。读完这些,你应该对 Android 的构建过程有了更清晰的认识——接下来看看各个模块的 Android.mk 是如何组织的。

每个 Android.mk 文件通常包含以下部分:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE := <模块名>
LOCAL_SRC_FILES := <源文件列表>
include $(BUILD_<TYPE>)

其中 BUILD_<TYPE> 可以是 BUILD_SHARED_LIBRARYBUILD_STATIC_LIBRARYBUILD_EXECUTABLE 等,对应编译系统提供的各种模块构建模板。

输出

了解了编译系统的工作原理和模块构建模板的使用方式后,来看看它在 out/ 目录下创建了哪些产物。在较高层面,out/ 目录的结构如下:

out/
├── target/
│   └── product/
│       └── <product>/
│           ├── system/          # 系统镜像内容
│           │   ├── app/         # 系统应用
│           │   ├── bin/         # 系统可执行文件
│           │   ├── lib/         # 系统库
│           │   └── etc/         # 系统配置文件
│           ├── root/            # 根文件系统
│           ├── ramdisk.img      # RAM disk 镜像
│           ├── system.img        # system 分区镜像
│           ├── userdata.img     # userdata 分区镜像
│           └── boot.img         # 启动镜像
├── host/
│   └── linux-x86/              # 为主机(Linux x86)构建的工具
│       └── bin/                # 主机端工具(如 aapt, dx, adb)
└── gen/                        # 生成的源代码(如 R.java, aidl)

构建产物按目标平台(target)和主机平台(host)分别组织。