Android 编译系统分析

本文深入解析Android项目的Makefile结构,将其分为四大类:配置、模块编译、本地模块及打包。详细介绍了每类文件的作用与引用关系,以及如何加速编译过程。
把Android所有的Make文件分为4种:
 1、For config
      这类文件主要来配置product,board,以及根据你的Host和Target选择相应的工具以及设定相应的通用编译选项:
      build/core/config.mk         summary of config
      build/core/envsetup.mk    generate dir config and so on
      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结合的编译器和编译选项。

2. Module Compile
      这类文件主要定义了如何来处理Module的Android.mk,以及采用何种方式来生成目标模块,这些模块生成规则都定义在config.mk里面,我们可以看看:
      CLEAR_VARS:= $(BUILD_SYSTEM)/clear_vars.mk
      BUILD_HOST_STATIC_LIBRARY:= $(BUILD_SYSTEM)/host_static_library.mk
      BUILD_HOST_SHARED_LIBRARY:= $(BUILD_SYSTEM)/host_shared_library.mk
      BUILD_STATIC_LIBRARY:= $(BUILD_SYSTEM)/static_library.mk
      BUILD_RAW_STATIC_LIBRARY := $(BUILD_SYSTEM)/raw_static_library.mk
      BUILD_SHARED_LIBRARY:= $(BUILD_SYSTEM)/shared_library.mk
      BUILD_EXECUTABLE:= $(BUILD_SYSTEM)/executable.mk
      BUILD_RAW_EXECUTABLE:= $(BUILD_SYSTEM)/raw_executable.mk
      BUILD_HOST_EXECUTABLE:= $(BUILD_SYSTEM)/host_executable.mk
      BUILD_PACKAGE:= $(BUILD_SYSTEM)/package.mk
      BUILD_HOST_PREBUILT:= $(BUILD_SYSTEM)/host_prebuilt.mk
      BUILD_PREBUILT:= $(BUILD_SYSTEM)/prebuilt.mk
      BUILD_MULTI_PREBUILT:= $(BUILD_SYSTEM)/multi_prebuilt.mk
      BUILD_JAVA_LIBRARY:= $(BUILD_SYSTEM)/java_library.mk
      BUILD_STATIC_JAVA_LIBRARY:= $(BUILD_SYSTEM)/static_java_library.mk
      BUILD_HOST_JAVA_LIBRARY:= $(BUILD_SYSTEM)/host_java_library.mk
      BUILD_DROIDDOC:= $(BUILD_SYSTEM)/droiddoc.mk
      BUILD_COPY_HEADERS := $(BUILD_SYSTEM)/copy_headers.mk
      BUILD_KEY_CHAR_MAP := $(BUILD_SYSTEM)/key_char_map.mk
      除了CLEAR_VARS是清楚本地变量之外,其他所有的都对应了一种模块的生成规则,每一个本地模块最后都会include其中的一种来生成目标模 块。大部分上面的.mk都会包含base_rules.mk,这是对模块进行处理的基础文件,建议要写本地模块的都去看看,看明白了为什么 Android.mk要这么写就会大致明白了。

3.Local Module
      本地模块的Makefile文件就是我们在Android里面几乎上随处可见的Android.mk。Android进行编译的时候会通过下面的函数来 遍历所有子目录中的Android.mk,一旦找到就不会再往层子目录继续寻找(所有你的模块定义的顶层Android.mk必须包含自己定义的子目录中的Android.mk)。
      subdir_makefiles += \
          $(shell build/tools/findleaves.py --prune=out --prune=.repo --prune=.git $(subdirs) Android.mk)
       不同类型的本地模块具有不同的语法,但基本上是相通的,只有个别变量的不同。
       Android通过LOCAL_MODULE_TAGS来决定哪些本地模块会不会编译进系统,通过PRODUCT和LOCAL_MODULE_TAGS来决定哪些应用包会编译进系统(默认情况是只要设置为tests则不编译进系统),如果用户不指定LOCAL_MODULE_TAGS,默认它的值是user。此外用户可以通过buildspec.mk来指定你需要编译进系统的模块。
       用户也可以通过mm来编译指定模块,或者通过make clean-module_name来删除指定模块。

4.Package 
       这主要指的是build/core/Makefile这个文件,它定义了生成各种img的方式,包括ramdisk.img   userdata.img  system.img  update.zip  recover.img等。我们可以看看这些img都是如何生成的,对应着我们常用的几个make goals:

在实际的过程中,我们也可以自己编辑out目录下的生成文件,然后手工打包相应生成相应的img,最常用的是加入一些需要集成进的prebuilt file。
       
       所有的Makefile都通过build/core/main.mk这个文件组织在一起,它定义了一个默认goals:droid,当我们在TOP目录下 敲Make实际上就等同于我们执行make droid。当Make include所有的文件,完成对所有make我文件的解析以后就会寻找生成droid的规则,依次生成它的依赖,直到所有满足的模块被编译好,然后使用 相应的工具打包成相应的img。

       基本上Android building system就是以这样一种方式组织在一起的了,下面说一点闲散的东西。首先是如何来加快Android的编译过程,因为每次Android都要遍历所有 的Android.mk,不管是编译整个工程还是只编译某个模块。所以可以将遍历的结果保存下来,下次直接从文件读就好了,但是这里容易出错,所以一定要 确认是否正确包含了所有的.mk,当新加入文件的时候确认将原来保存的文件删除。下面是我写的加快编译的一个makefile,将下面的语句替换掉 main.mk中的相应部分就可以了:
       FROM:
       subdir_makefiles += \
           $(shell build/tools/findleaves.sh --prune="./out" $(subdirs) Android.mk)
       TO:
       ifneq ($(ONE_SHOT_MAKEFILE),)
       else
       ifneq ($(CASH_MK),true)
       subdir_makefiles += \
           $(shell build/tools/findleaves.sh --prune="./out" $(subdirs) Android.mk)
      else
     subdir-makefiles-cash := $(shell cat build/subdir_mk_cash)
     ifeq ($(subdir-makefiles-cash),)
     $(warning No .mk cash ,create now !)
     subdir_makefiles += \
           $(shell build/tools/findleaves.sh --prune="./out" $(subdirs) Android.mk)
     mk-to-file := $(shell echo $(subdir_makefiles) > build/subdir_mk_cash) 
     else
    $(warning Using cash mk !)
    subdir_makefiles := $(shell cat build/subdir_mk_cash)
    endif
    endif

    endif
       通过CASH_MK=true来打开快速编译的功能,因为没有对错误进行检测的操作,所以使用的时候一定要特别小心。

总起来说,Android的Makefile的引用关系是这样的,
Makfile-->build/core/main.mk-->build/core/config.mk--->build/core/envsetup.mk-->build/core/product_config.mk
在build/core/product_config.mk中编译系统首先调用build/core/product.mk/中定义的函数get-all-product-makefiles来遍历整个vendor的子目录,找到vendor下所有的AndroidProduct.mk,不同子目录下的AndroidProduct.mk中不同的PRODUCT_NAME,PRODUCT_DEVICE等信息(可以通过打开build/core/product_config.mk中的#$(dump-products)语句使控制台编译的时候输出都有的product信息),接着build/core/product_config.mk会调用resolve-short-product-name将TARGET_PRODUCT匹配的的AndroidProduct.mk总定义的PRODUCT_DEVICE赋值给TARGET_DEVICE。
在回到build/core/config.mk, include $(TARGET_DEVICE)/BoardConfig.mk
board_config_mk := \
$(strip $(wildcard \
$(SRC_TARGET_DIR)/board/$(TARGET_DEVICE)/BoardConfig.mk \
device/*/$(TARGET_DEVICE)/BoardConfig.mk \
vendor/*/$(TARGET_DEVICE)/BoardConfig.mk \
))
.....
include $(board_config_mk)
而这个BoardConfig.mk决定了目标系统的属性,比如使用ALSA还是GENERIC_AUDIO等等,另外在这里TARGET_DEVICE宏也决定了TARGET_DEVICE_DIR,(TARGET_DEVICE_DIR := $(patsubst %/,%,$(dir $(board_config_mk))))

当然Android的obj目标输出也是由TARGET_DEVICE决定,见build/core/envsetup.mk。再回到build/core/main.mk,编译系统接着做的一件事情是,遍历所有的子目录,找到所有的Android.mk文件,并将这些Android.mk文件include进来:
subdir_makefiles := \
$(shell build/tools/findleaves.py --prune=out --prune=.repo --prune=.git $(subdirs) Android.mk)

include $(subdir_makefiles)
我们再来看其中的./build/target/board/Android.mk,对它引用了include $(TARGET_DEVICE_DIR)/AndroidBoard.mk,由上面TARGET_DEVICE_DIR的定义可知,这下又进入了vendor下TARGET_DEVICE指向的目录了,这个mk文件中定义了特定Product需要编译和安装的app和script.



Android 编译系统是构建 Android 操作系统及其应用程序的基础设施,负责将源代码、资源文件以及其他组件编译并打包成可在 Android 设备上运行的软件包,如 APK 或系统镜像 [^1]。 ### 发展历程 在 Android 7.0 之前,Android 编译系统使用 GNU Make 描述和 shell 来构建编译规则,模块定义使用 Android.mk,其本质是 Makefile。但随着 Android 工程规模增大、模块增多,Makefile 组织的项目编译时间变长 [^3][^4]。从 Android 7.0 开始,Google 引入了 ninja 和 kati 进行编译 [^3]。 ### 核心构建脚本 - **Android.mk**:在 Android 7.0 以前,是常用的构建脚本文件,用于组织各模块的编译。它需要定义 `LOCAL_PATH` 变量以在开发树中查找源文件,宏函数 `my-dir` 可返回当前路径(即包含 `Android.mk` 文件的目录) [^2][^3][^4][^5]。 - **Android.bp**:用来替换 Android.mk 的配置文件,使用 Blueprint 框架解析。Blueprint 是生成、解析 Android.bp 的工具,是 Soong 的一部分。Soong 专为 Android 编译设计,Blueprint 负责解析文件形式,Soong 解释内容含义,最终转换成 Ninja 文件。Android.bp 文件用类似 JSON 的简洁声明描述需要构建的模块 [^5]。 ### 编译过程 #### 环境准备 编译需在特定环境下进行,一般为 Linux 系统,要安装必要的依赖包,如 JDK,同时安装编译工具,如 GCC 编译器等。还需从官方渠道获取 Android 源码,使用 `repo` 工具同步源码,之后进行配置,设置环境变量,指定编译的目标设备、版本等信息。 #### 命令执行 不同版本和系统环境下命令有所不同。在 Windows 系统中使用 NDK 编译 Android 二进制文件,可使用 `ndk-build NDK_PROJECT_PATH=. APP_BUILD_SCRIPT=./Android.mk NDK_APPLICATION_MK=./Application.mk` 命令。在 Linux 系统中,常见命令如下: ```bash source build/envsetup.sh lunch aosp_arm-eng make -j$(nproc) ``` 其中,`source build/envsetup.sh` 用于设置编译环境,`lunch aosp_arm-eng` 用于选择编译的目标设备和版本,`make -j$(nproc)` 用于并行编译,`$(nproc)` 表示使用系统的所有核心进行编译。 #### 依赖处理 编译过程中,各模块存在依赖关系,编译系统会自动处理这些依赖,确保每个模块正确编译。例如,一个 Java 模块可能依赖于其他 Java 库,编译系统会找到这些库并进行相应处理。 #### 产物生成 经过编译步骤,最终生成 Android 系统的二进制文件,包括系统镜像文件、可执行文件、共享库文件等,这些文件可烧录到 Android 设备中运行。 ### 系统架构 - **GNU Make**:早期 Android 编译系统使用,用于描述和构建编译规则。 - **Ninja**:从 Android 7.0 引入,是一种快速的构建系统,与 kati 配合使用,提高编译效率。 - **Kati**:辅助 Ninja 进行编译,帮助处理原有的 Makefile 规则。 - **Soong**:专为 Android 编译设计的工具,与 Blueprint 配合,将 Android.bp 文件解析转换为 Ninja 文件。 - **Blueprint**:生成、解析 Android.bp 的工具,是 Soong 的一部分,负责解析文件形式。 ### 编译系统的特点 - **模块化**:Android 系统由众多模块组成,每个模块有独立的编译规则,便于开发和维护。 - **灵活性**:支持多种编译方式和配置选项,可根据不同需求进行定制。 - **自动化**:编译系统能自动处理模块间的依赖关系,减少手动干预。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值