Android 编译系统分析(三)

本文从Android编译系统的角度,详细介绍了其架构、规则以及关键文件的作用,旨在帮助开发者理解Android编译过程,实现更高效地移植和定制。

Android开源以来,引起了嵌入式行业一股热潮,很多嵌入式开发者表示对Android有很强的兴趣,并下载Android源码进行编译和移植。Android源码的巨大(repo下来,大概2G)给人以Android相当复杂的错觉。本文从Android编译系统的角度,让大家了解Android其实也是很纯真的。

Android编译系统(build system)集中于Android源码下的build/core下,在Android2.2中,共有56*.mk文件。另外还有一些shell脚本。可谓相当庞大,为什么google将它的编译系统弄的如此复杂庞大呢?在build/core下的build-system.html中有以下讲述:

1.     Multiple Targets

2.     Non-Recursive Make

3.     Rapid Compile-Test Cycles

4.     Both Environment and Config File Based Settings

5.     Object File Directory / make clean

基于以上目标,google Android开发人员将Android build system做成了现在的样子。在android.git.kernel.org上可以看到android build system作为一个项目一直在更新,因此,对于其编译系统的维护也是一个相当复杂的项目。为了实现Android在除ARM平台(x86mips甚至一个全新的架构)上移植,必须深入了解Android编译系统。

Makefile的规则:

target … : prerequisites …

command

target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label),对于标签这种特性,在后续的伪目标章节中会有叙述。

prerequisites就是,要生成那个target所需要的文件或是目标。

command也就是make需要执行的命令。(任意的Shell命令)

这是一个文件的依赖关 系,也就是说,target这一个或多个的 目标文件依赖于prerequisites中的文件,其生成规则定义在command中。 说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是 Makefile的规则。也就是Makefile中最核心的内容。

正如上面作者所述,上面是Makefile中最核心的内容,Android编译系统符合GNU make的标准,当然这也是Android 编译系统最核心的内容。

Android编译系统的架构:

分析Android编译系统,你会发现,Android编译系统完成的并不仅仅是对目标(主机)系统二进制文件、java应用程序的编译、链接、打包等,而且还有包括生成各种依赖关系、确保某个模块的修改引起相依赖的文件的重新编译链接,甚至还包括目标文件系统的生成,配置文件的生成等,因此Android编译系统具有支持多架构(linux-x86windowsarm等)、多语言(汇编、CC++Java等)、多目标、多编译方式。这些目标和结构决定其架构也很重要。

Android编译系统集中于build/core下,几个很重要的*.mk文件如下:

main.mk(主控Makefile)

base_rules.mk(对一些Makefile的变量规则化)

config.mk(关于编译参数、编译命令的一些配置)

definations.mk(定义了很多编译系统中用到的宏,相当于函数库)

Makefile(这个Makefile特指build/core下的Makefile,此文件主要控制生成system.img,ramdisk.img,userdata.img,以及recorvery imagesdk等)

Binary.mk(控制如何生成目标文件)

Clear_vars.mk(清除编译系统中用到的临时变量)

Combo/linux-arm.mk(控制如何生成linux-arm二进制文件,包括ARM相关的编译器,编译参数等的设置)

Copy_headers.mk(将头文件拷贝到指定目录)

分散于各个目录下的Android.mk(控制生成局部模块的源码,名称所需头文件路径,依赖库等特殊选项)

Build/envsetup.mk(编译环境初始化,定义一些实用的shell函数,方便编译使用)

以上几个主要的文件,可以按照社会分工打一个比方:

Main.mk是总统,是老大,承担了很多工作。

Makefile是副总统,辅佐老大Main.mk

Base_rules.mk是交警,让不规则的东西,变得规则。

Config.mk是省长,规定了各个人民群众该如何行事

Definations.mk是图书馆管理员

Binary.mk应该属于村长了,规定每个人该如何行事

Clear_vars.mk应该属于保洁公司的工人吧

Combo/linux-arm.mk应该属于社会公民了,他决定自己该如何去做

Main.mk分析

Main.mk主要包含如下几个部分的内容

1.       SHELL设置

2.       编译环境配置

3.       编译环境检查

4.       包含必要的宏

5.       根据make参数设置编译时的变量

6.       包含需要编译的Android.mk

7.       设置编译系统Targetprerequisites 控制整个编译流程

下面对上面几点进行必要解释:

有了前面小节对Android编译系统架构的分析,如果需要修改Android编译系统,就可以不至于盲目找问题,修改代码了。今天分析下main.mk,看它能为我们Android编译提供什么有价值的信息,以及如何自己定制我们的Android编译系统。

Main.mk的第一句就根据ANDROID_BUILD_SHELL来包裹编译系统用到的Shell,如果我们不想使用bash,而想使用sh,那么就可以在它前面写上ANDROID_BUILD_SHELL := /bin/sh,或者在build/envsetup.sh中添加相关定义。

定义完SHELL之后,就是对MAKE_VERSION的检查,然后定义了默认的编译目标droid!

如果我们敲入make之后,不加任何参数,默认的目标就是droid。注意虽然后面的include $(BUILD_SYSTEM)/config.mk写在默认目标droid依赖之后,但其和之后的语句都是要执行的,这是Makefile的语法决定的。

后面会include config.mk cleanbuild.mk对编译系统进行必要的配置。后面就是对编译环境的检查,包括是否大小写敏感、路径检查、java版本检查、javac版本检查。Android对编译环境的检查如果符合条件,在下次编译的时候,不会再次进行检查。

检查完版本之后,会包含进definations.mk,如前所述,definations.mk中定义了很多编译系统中用到的宏,这些宏在编译时需要经常调用,因此在编译的很靠前的阶段,就将之包含了进来。

然后就是针对make时传入的编译类型(eng user userdebug showcommands等)进行编译配置,这些配置会影响到最终编译目标所包括的模块。对于eng user userdebug sdk win_sdk tests等编译目标的区别,读者可以通过查看main.mk的代码找出其中到底有什么不同。

此处略去部分部分不重要的内容,直接跳到

Ifeq($(SDK_ONLY),true)处,大概368行附近,这个判断语句一直到这个语句块结束,都是对subdirs变量的设置,subdirs变量决定了哪些子文件夹最终被编译。

在后面的subdir_makefiles变量的设置,决定了哪些Android.mk被编译。紧接着include $(subdir_makefiles)就会添加所有这些Android.mk文件的依赖。这其中包含了droid的依赖,后面我们会发现。

然后就会根据这些Makefile,找出所有需要编译的模块(module),以及进行必要的分类(eng_MODULES/debug_MODULES/tests_MODULES等、modules_to_check/modules_to_install等),用以区别对待。

紧接着是定义了一系列的隐含目标:prebuiltall_copied_headersfilescheckbuildramdisksystemtallballuserdataimageuserdatatarballbootimgdroidcore等。最重要的一点,是会发现droid依赖于droidcore,droidcore依赖于

droidcore: files /

systemimage /

$(INSTALLED_BOOTIMAGE_TARGET) /

$(INSTALLED_RECOVERYIMAGE_TARGET) /

$(INSTALLED_USERDATAIMAGE_TARGET) /

$(INSTALLED_FILES_FILE)

正是这几个依赖项,控制着整个android的编译

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 系统由众多模块组成,每个模块有独立的编译规则,便于开发和维护。 - **灵活性**:支持多种编译方式和配置选项,可根据不同需求进行定制。 - **自动化**:编译系统能自动处理模块间的依赖关系,减少手动干预。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值