Android编译系统(一)——概述

0. 本文主要内容如下:

  • 概述
  • 编译流程
  • 编译环境
  • make
  • 编译工具链
  • 工具链的关系
  • 总结

1. 概述

在 Android 7.0 之前,Android 编译系统使用 GNU Make 描述和 shell 来构建编译规则,模块定义都使用 Android.mk 进行定义。Android.mk 的本质就是 Makefile,但是随着 Android 项目的工程越来越大,模块越来越多,Makefile 组织的项目编译时间越来越长。这样下去 Google 工程师觉得不行,得要优化。

因此,在 Android 7.0 开始,Google 采用 ninja 来取代之前使用的 make,由于之前的 Android.mk 数据实在巨大,因此 Google 加入了一个 kati 工具,用于将 Android.mk 转换成 ninja 的构建规则文件 buildxxx.ninja,再使用 ninja 来进行构建工作。

ninja 的网址:https://ninja-build.org

编译速度快了一些,但是既然要干,那就干个大的,最终目标要把 make 都取代,于是从 Android 8.0 开始,Google 为了进一步淘汰 Makefile,因此引入了 Android.bp 文件来替换之前的 Android.mk。

Android.bp 只是一个纯粹的配置文件,不包括分支,循环语句等流程控制,本质上就是一个 json 配置文件。Android.bp 通过 Blueprint+soong 转换成 ninja 的构建规则文件 build.ninja,再使用 ninja 来进行构建工作。

Android 10.0 上,mk 和 bp 编译的列表可以从 /out/.moudle_paths 的 Android.bp.list 中看到,Android 10.0 还有 400 多个 mk 文件没有替换完,Google 任重道远。

Android 编译演进过程:

  • Android 7.0 之前使用 GNU Make
  • android 7.0 引入 ninja、kati、Android.bp 和 soong 构建系统
  • Android 8.0 默认打开 Android.bp
  • Android 9.0 强制使用 Android.bp

Google 在 Android 7.0 之后,引入了 soong 构建系统, 旨在取代 make,它利用 kati GNU make 克隆工具和 ninja 构建系统组件来加速 Android 的构建。

make 构建系统得到了广泛的支持和使用,但在 Android 层面变得缓慢,容易出错,无法扩展且难以测试。soong 构建系统正好提供了 Android build 所需的灵活性。

Android 系统的编译历程:

2. 编译流程

2.1. 编译构成

Android 的编译目录在 /build 中,看一下 Android 13 源码中的 build 目录,现在是这个样子:

本文以及该系列文章中所引用的源码均以公司 Android 13 PM95 qssi 源码为参考:

http://172.31.2.45:8080/PM95_T_QSSI/

这个目录中可以看到 core 文件夹被 link 到了 make/core,envsetup.sh 被 link 到 make/envsetup.sh,这主要是为了对使用者屏蔽切换编译系统的误差。

这里重点看四个文件夹:blueprint,kati,make,soong

  • blueprint:用于处理 Android.bp,编译生成 *.ninja 文件,用于做 ninja 的处理
  • kati:用于处理 Android.mk,编译生成 *.ninja 文件,用于做 ninja 的处理
  • make:文件夹还是原始的 make 那一套流程,比如 envsetup.sh
  • soong:构建系统,核心编译为 soong_ui.bash

soong 编译系统家族成员及各自关系如下图所示:

在编译过程中,Android.bp 会被收集到 out/soong/build.ninja.d,blueprint 以此为基础,生成 out/soong/build.ninja。Android.mk 会由 kati/ckati 生成为 out/combined-qssi.ninja,两个 ninja 文件会被整合进入 out/combined-qssi.ninja。

out/combined-qssi.ninja 内容如下所示:

builddir = out
pool highmem_pool
 depth = 2
subninja out/build-qssi.ninja
subninja out/build-qssi-package.ninja
subninja out/soong/build.ninja

2.2. 编译步骤

  • source build/envsetup.sh
  • choosecombo
  • make

3. 编译环境初始化

3.1. envsetup

编译的第一部需要初始化一下环境变量,通过一下命令完成:

source build/envsetup.sh

这里的 envsetup.sh 被 link 到了 build/make/envsetup.sh

envsetup.sh 主要做了下面几个事情:

  • 定义一些 launch/m/mm/mmm/provision 等函数
  • 确认当前的 shell 环境,建立 shell 命令
  • 从 device/vendor/product 等目录遍历搜索 vendorsetup.sh,并 source 进来
  • 将下面这些 bash 文件导入到当前编译环境中
  • system/core/adb/adb.bash
  • system/core/fastboot/fastboot.bash
  • tools/asuite/asuite/sh

在 source build/envsetup.sh 后,输入 hmm 可以看到 envsetup 支持的一些接口:

命令说明
launch

lunch <product_name>-<build_variant>

选择<product_name>作为要构建的产品,<build_variant>作为要构建的变体,并将这些选择存储在环境中,以便后续调用“m”等读取

tapas

交互方式:tapas [<App1> <App2> ...] [arm|x86|mips|arm64|x86_64|mips64] [eng|userdebug|user]

croot

将目录更改到树的顶部或其子目录

m

编译整个源码,可以不用切换到根目录

mm

编译当前目录下的源码,不包含他们的依赖模块

mmm

编译指定目录下的所有模块,不包含他们的依赖模块

例如:mmm dir/:target1,target2.

mma

编译当前目录下的源码,包含他们的依赖模块

mmma

编译指定目录下的所模块,包含他们的依赖模块

provision

具有所有必需分区的闪存设备。选项将传递给fastboot

cgrep

对系统本地所有的C/C++文件执行grep命令

ggrep

对系统本地所有的Gradle文件执行grep命令

jgrep

对系统本地所有的Java文件执行grep命令

resgrep

对系统本地所有的res目录下的xml文件执行grep命令

mangrep

对系统本地所有的AndroidManifest.xml文件执行grep命令

mgrep

对系统本地所有的Makefiles文件执行grep命令

sepgrep

对系统本地所有的sepolicy文件执行grep命令

sgrep

对系统本地所有的source文件执行grep命令

godir

根据godir后的参数文件名在整个目录下查找,并且切换目录

allmod

列出所有模块

gomod

转到包含模块的目录

pathmod

获取包含模块的目录

refreshmod

刷新allmod/gomod的模块列表

3.2. choosecombo

环境变量初始化完成后,我们需要选择一个编译目标。choosecombo 主要作用是根据用户输入或者选择的产品名来设置与具体产品相关的环境变量。以 PM95  qssi 为例:

之后可以看到配置的一些环境变量:

这些环境变量的含义如下:

环境变量说明

PLATFORM_VERSION_CODENAME=REL

表示平台版本的名称

PLATFORM_VERSION=13

Android平台的版本号

TARGET_PRODUCT=qssi

所编译的产品名称

TARGET_BUILD_VARIANT=userdebug

所编译产品的类型

TARGET_BUILD_TYPE=release

编译的类型,debug和release

TARGET_ARCH=arm64

表示编译目标的CPU架构

TARGET_ARCH_VARIANT=armv8-a-branchprot

表示编译目标的CPU架构版本

TARGET_CPU_VARIANT=generic

表示编译目标的CPU代号

HOST_ARCH=x86_64

表示编译平台的架构

HOST_2ND_ARCH=x86

表示编译平台的第二CPU架构

HOST_OS=linux

表示编译平台的操作系统

HOST_OS_EXTRA=Linux-5.15.0-76-generic-x86_64-Ubuntu-20.04.2-LTS

编译系统之外的额外信息

HOST_CROSS_OS=windows
HOST_CROSS_ARCH=x86
HOST_CROSS_2ND_ARCH=x86_64
HOST_BUILD_TYPE=release

编译类型

BUILD_ID=TKQ1.230721.002

BUILD_ID会出现在版本信息中,可以利用

OUT_DIR=out

编译结果输出的路径

4. make

4.1. /build/envsetup.sh

# 执行完 choosecombo 命令后,就可以使用 make 命令来执行编译 Build
# Android 10.0 上是通过 soong 执行编译构建,这里执行 make 命令时
# main.mk 文件把一些环境变量和目标都配置好后,会执行 envsetup.sh 中的 make() 进行编译
# 
function make()
{
    _wrap_build $(get_make_command "$@") "$@"
}

# 如果找到“build/soong/soong_ui.bash”,就使用 soong_ui.bash 来进行编译
# 否则使用原始的 make 命令进行编译
function get_make_command()
{
    # If we're in the top of an Android tree, use soong_ui.bash instead of make
    if [ -f build/soong/soong_ui.bash ]; then
        # Always use the real make if -C is passed in
        for arg in "$@"; do
            if [[ $arg == -C* ]]; then
                echo command make
                return
            fi
        done
        echo build/soong/soong_ui.bash --make-mode
    else
        echo command make
    fi
}

4.2. /build/soong/soong_ui.bash

# 配置一些资源环境,得到一些函数命令,例如:"soong_build_go"
# 最终回退到根目录,执行 "out/soong_ui --make-mode" 进行真正的构建
# "soong_build_go soong_ui android/soong/cmd/soong_ui" 是通过编译
# "android/soong/cmd/soong_ui/main.go" 来编译生成 soong_ui
# Save the current PWD for use in soong_ui
export ORIGINAL_PWD=${PWD}
export TOP=$(gettop)
source ${TOP}/build/soong/scripts/microfactory.bash

soong_build_go soong_ui android/soong/cmd/soong_ui
soong_build_go mk2rbc android/soong/mk2rbc/cmd
soong_build_go rbcrun rbcrun/cmd

cd ${TOP}
exec "$(getoutdir)/soong_ui" "$@"

最终会执行 “exec out/soong_ui --make-mode” 进行编译

4.3. soong

soong 的编译过程如下所示:

执行 runKatiBuild 时,有个重要的步骤,就是加载 build/make/core/main.mk,main.mk 文件是Android Build 系统的主控文件。从 main.mk 开始,将通过 include 命令将其所有需要的 .mk 文件包含进来,最终在内存中形成一个包括所有编译脚本的集合,这个相当于一个巨大 Makefile 文件。Makefile 文件看上去很庞大,其实主要由三种内容构成:变量定义、函数定义和目标依赖规则,此外 mk 文件之间的包含也很重要。

4.4. main.mk

main.mk 的包含关系如下图所示:

一些关键的 mk 文件说明:

文件说明

build/make/core/main.mk

Build的主控文件,主要作用是包含其他mk,以及定义几个最重要的编译目标,同时检查编译工具的版本,例如如gcc、clang、java等

build/make/core/config.mk

Build的配置文件,主要是区分各个产品的配置,并将这些编译器参数引入产品配置 BoardConfig.mk,同时也配置了一些编译器的路径等

build/make/core/clang/config.mk

clang编译的配置文件

build/make/core/definitions.mk

最重要的Make文件之一,在其中定义了大量的函数。这些函数都是Build系统的其他文件将用到的。例如:my-dir,all-subdir-makefiles,find-subdir-files,sign-package等,关于这些函数的说明请参见每个函数的代码注释

build/make/core/dex_preopt.mk

定义了dex优化相关的路径和参数

build/make/core/pdk_config.mk

编译pdk的配置文件

build/make/core/Makefile

系统最终编译完成所需要的各种目标和规则

build/make/core/envsetup.mk

包含进product_config.mk文件并且根据其内容设置编译产品所需要的环境变量,并检查合法性,指定输出路径等

build/make/core/combo/select.mk

根据当前编译器的平台选择平台相关的Make文件

build/make/core/ninja_config.mk

解析makefile的的列表,传给kati,配置传给ninja和kati的目标

build/make/core/soong_config.mk

配置soong的环境变量,建立go变量和mk变量的json映射关系,让go变量可以获取到mk中定义的变量值

5. 编译工具链

Android 13.0 的编译系统中,涉及以下一些工具链,由这些工具链相辅相成,才最终编译出了我们所需要的镜像版本。

  • soong
  • kati
  • blueprint
  • ninja

5.1. soong

5.1.1. 概述

Soong 构建系统是在 Android 7.0(Nougat) 中引入的,旨在取代 Make。它利用 Kati GNU Make 克隆工具和 Ninja 构建系统组件来加速 Android 的构建。

Soong 是由 Go 语言写的一个项目,从 Android 7.0 开始,在 prebuilts/go/ 目录下新增了 Go 语言所需的运行环境,Soong 在编译时使用,解析 Android.bp,将之转化为 Ninja 文件,完成 Android 的选择编译,解析配置工作等。故 Soong 相当于 Makefile 编译系统的核心,即 build/make/core 下面的内容。

另外 Soong 还会编译产生一个 androidmk 命令,可以用来手动将 Android.mk 转换成 Android.bp 文件。不过这只对无选择、循环等复杂流程控制的 Android.mk 生效。

5.1.2. 脚本和代码目录

/build/soong

5.2. kati

5.2.1. 概述

kati 是一个基于 Makefile 来生成 ninja.build 的小项目。主要用于把 Makefiel 转成成 ninja file,自身没有编译能力,转换后使用 Ninja 编译。

在编译过程中,kati 负责把既有的 Makefile、Android.mk 文件,转换成 Ninja 文件。在 Android 8.0 以后,它与 Soong 一起,成为 Ninja 文件的两大来源。Kati 更像是 Google 过渡使用的一个工具,等所有 Android.mk 都被替换成 Android.bp 之后,Kati 有可能退出 Android 编译过程。

在单独使用时,它对普通的小项目还能勉强生效。面对复杂的、多嵌套的 Makefile 时,它往往无法支持,会出现各种各样的问题。当然,也可以理解为,它只为 Android 而设计。

5.2.2. 脚本和代码目录

/build/kati

5.3. blueprint

5.3.1. 概述

Blueprint 由 Go 语言编写,是生成、解析 Android.bp 的工具,是 Soong 的一部分。Soong 则是专为 Android 编译而设计的工具,Blueprint 只是解析文件的形式,而 Soong 则解释内容的含义。在 Android 编译最开始的准备阶段,会执行 build/soong/soong_ui.bash 进行环境准备。

对 blueprint 项目编译完成之后会在 out/soong/host/linux-x86/bin 目录下生成 soong 编译需要的 5 个执行文件(bpfix、bpfmt、bpmodify、microfatory、bpmodify)。

Soong 是与 Android 强关联的一个项目,而 Blueprint 则相对比较独立,可以单独编译、使用。

5.3.2. 脚本和代码目录

/build/blueprint

5.4. ninja

最开始,Ninja 是用于 Chromium 浏览器中,Android 在 SDK 7.0 中也引入了 Ninja。

Ninja 是一个致力于速度的小型编译系统(类似于 Make),如果把其他编译系统比做高级语言的话,Ninja 就是汇编语言。通常使用 Kati 或 soong 把 makefile 转换成 Ninja files,然后用 Ninja 编译。

主要两个特点:

  • 可以通过其他高级的编译系统生成其输入文件

  • 它的设计就是为了更快的编译

ninja 核心是由 C/C++ 编写的,同时有一部分辅助功能由 python 和 shell 实现。由于其开源性,所以可以利用 ninja 的开源代码进行各种个性化的编译定制。

从 Android 7 开始,编译时默认使用 Ninja。但是,Android 项目里是没有 .ninja 文件的。遵循 Ninja 的设计哲学,编译时,会先把 Makefile 通过 kati 转换成 .ninja 文件,然后使用 ninja 命令进行编译。这些 .ninja 文件,都产生在 out/ 目录下,共有三类:

  • build-*.ninja 文件,通常非常大,几十到几百 MB。对 make 全编译,命名是 build-<product_name>.ninja。如果 Makefile 发生修改,需要重新产生 Ninja 文件。mm、mma 的Ninja 文件,命名是 build-<product_name>-<path_to_Android.mk>.ninja。而 mmm、mmma的 Ninja 文件,命名是 build-<product_name>-_<path_to_Android.mk>.ninja

  • combined-*.ninja 文件。在使用了 Soong 后,除了 build-*.ninja 之外,还会产生对应的combined-*.ninja,二者的 * 内容相同。这类是组合文件,是把 build-*.ninja 和 out/soong/build.ninja 组合起来。所以,使用 Soong 后,combined-*.ninja 是编译执行的真正入口

  • 第三类是 out/soong/build.ninja 文件,它是从所有的 Android.bp 转换过来的。build-*.ninja 是从所有的 Makefile,用 Kati 转换过来的,包括 build/core/*.mk 和所有的 Android.mk。所以,在不使用 Soong 时,它是唯一入口。在使用了 Soong 以后,会新增源于 Android.bp 的out/soong/build.ninja,所以需要 combined-*.ninja 来组合一下

6. 工具链的关系

Android.mk、Android.bp、kati、Soong、Blueprint、Ninja之间的关系如下:

Blueprint 是生成、解析 Android.bp 的工具,是 Soong 的一部分。Soong 则是专为 Android 编译而设计的工具,Blueprint 只是解析文件的形式,而 Soong 则解释内容的含义

Android.mk 可以通过 Soong 提供的 androidmk 转换成 Android.bp,但仅限简单配置

现存的 Android.mk 文件、既有的 Android.bp,都会分别被转换成 Ninja。从 Android.mk 与其它 Makefile,会生成 out/build-<product_name>.ninja 文件。而从 Android.bp,则会生成 out/soong/build.ninja。此外,还会生成一个较小的 out/combined-<product_name>.ninja 文件,负责把二者组合起来,作为执行入口。

最终,Ninja 文件才是真正直接控制源码编译的工具。

7. 总结

Android 13.0 中,mk 文件通过 kati\ckati 编译生成 build-aosp_arm.ninja,bp 文件通过 blueprint-soong 解析编译生成为 build.ninja,这些 ninja 文件会合并成 combined-aosp_arm.ninja,最终通过 ninja 工具进行最终的编译。

随着 Google 的不停演进,make 的编译会最终退出历史舞台,kati\ckati 也会退出,最终全部切到 blueprint-soong 的编译。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值