第 2 章:源码与构建系统
Android 开源项目(AOSP)包含数千个 Git 仓库、总计上亿行代码。编译 AOSP 需要一套专属工具链,历经十余年持续演进:从递归式 GNU Make,到 Soong/Blueprint 元构建系统,最新方向则迁移至 Bazel。本章完整梳理整条构建流水线:拉取源码、理解构建系统三层架构、产品配置、模块定义、生成分区镜像,以及在模拟器上运行系统。
本章所有路径与代码片段均基于android17-release分支验证。文中引用源码文件时,路径均相对于源码根目录,你可以在本地同步的源码中对照查阅。
2.1 获取源码
2.1.1 前置条件与硬件要求
拉取 AOSP 源码前,开发工作站需要满足最低资源标准:
| 资源项 | 最低配置 | 推荐配置 |
|---|---|---|
| 磁盘空间(仅源码) | 250 GB | 400 GB(存放一次编译产物) |
| 磁盘空间(源码 + 编译产物) | 400 GB | 600 GB 以上(强烈建议 SSD) |
| 内存 (RAM) | 32 GB | 64 GB 以上 |
| CPU 核心 | 4 核 | 16 核以上(编译高度并行) |
| 操作系统 | Ubuntu 22.04+ / macOS(Intel/Apple Silicon) | Ubuntu 24.04 LTS |
| 文件系统 | 区分大小写(Linux 使用 ext4) | ext4 或 macOS APFS |
构建系统强制要求区分大小写的文件系统。macOS 上,独立磁盘分区的 APFS 默认区分大小写;Linux ext4 原生区分大小写。若使用 NTFS、旧版 HFS+(不区分大小写),会出现难以排查的隐性编译故障。
Debian/Ubuntu 系统需要预先安装依赖包:
sudo apt-get install git-core gnupg flex bison build-essential \ zip curl zlib1g-dev libc6-dev-i386 x11proto-core-dev \ libx11-dev lib32z1-dev libgl1-mesa-dev libxml2-utils \ xsltproc unzip fontconfig python32.1.2 repo 工具
AOSP并非单一 Git 仓库,而是由数百个 Git 仓库组成,依靠repo工具统一管理。repo 是封装 Git 的 Python 脚本,负责:
- 批量拉取、同步大量 Git 仓库
- 维护清单文件(manifest):定义仓库路径、跟踪分支 / 标签
- 提供便捷命令:创建特性分支、向 Gerrit 推送代码评审、多仓库批量操作
安装 repo 工具:
# 创建存放repo脚本的目录 mkdir -p ~/bin export PATH=~/bin:$PATH # 下载repo启动器 curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo chmod a+x ~/bin/reporepo 启动器是小型引导脚本,首次运行时会从https://gerrit.googlesource.com/git-repo下载完整 repo 实现。
为什么不使用单个巨型 Git 仓库?Git 不适合存放海量文件的单体仓库。即便 Git 对象存储经过优化,巨型仓库的克隆、分支切换、状态查询速度都会极其缓慢。多仓库架构同时具备这些优势:
- 独立提交历史:框架、内核、第三方库等子系统拥有独立提交记录,可单独创建分支
- 选择性同步:只拉取源码树中需要的子项目
- 精细化权限控制:不同仓库可以配置独立访问者与评审规则
- 上游项目便捷引入:AOSP 导入 BoringSSL、ICU、LLVM 等开源项目时,独立仓库更便于维护与合入
必须掌握的 repo 核心子命令:
| 命令 | 作用 |
|---|---|
| repo init | 初始化源码工作区 |
| repo sync | 拉取并更新所有仓库代码 |
| repo start | 跨仓库创建特性分支 |
| repo upload | 推送代码至 Gerrit 进行评审 |
| repo status | 查看所有仓库工作区变更状态 |
| repo diff | 查看所有仓库统一格式 diff |
| repo forall | 在每一个仓库执行指定 shell 命令 |
| repo info | 查看清单信息 |
| repo manifest | 输出当前生效的清单文件 |
| repo branches | 列出所有特性分支 |
| repo prune | 清理已经合并的特性分支 |
repo forall 使用示例:
# 查找所有存在未提交变更的仓库 repo forall -c 'git status --short' | grep -v "^$" # 统计全部C/C++源码总行数 repo forall -c 'find . -name "*.cpp" -o -name "*.c" -o -name "*.h" \ | xargs wc -l 2>/dev/null' | tail -1 # 在所有仓库执行git gc,压缩Git存储 repo forall -c 'git gc --auto'2.1.3 初始化工作区:repo init
获取源码第一步:创建并初始化工作目录
mkdir aosp && cd aosp # 指定分支初始化清单 repo init -u https://android.googlesource.com/platform/manifest \ -b android17-releaserepo init 常用参数:
| 参数 | 作用 |
|---|---|
| -u URL | 清单仓库地址 |
| -b BRANCH | 指定检出分支或标签 |
| -m MANIFEST | 选择仓库内指定清单文件(默认 default.xml) |
| --depth=N | 浅克隆深度,节省磁盘与时间 |
| --partial-clone | 启用 Git 增量克隆,文件 Blob 按需下载 |
| --clone-filter=blob:limit=10M | 仅预下载小于 10MB 的二进制文件 |
| -g GROUP | 只同步指定分组内的项目 |
| --repo-rev=REV | 锁定 repo 工具自身版本 |
执行 repo init 后,工作区生成.repo/目录:
aosp/ .repo/ manifests/ # 清单Git仓库 default.xml # 主清单文件 GLOBAL-PREUPLOAD.cfg manifests.git/ # 清单仓库裸库 manifest.xml # 符号链接,指向当前生效清单 repo/ # repo工具源码 project.list # 缓存所有项目路径 project-objects/ # 共享裸仓库(启用--reference时) projects/ # 各个子项目裸Git仓库2.1.4 清单文件(Manifest File)
清单文件定义整个源码树构成:包含哪些仓库、本地存放路径、跟踪分支。理解清单是掌握 AOSP 源码结构的关键。
.repo/manifests/default.xml(android17-release,1122 行)片段示例:
<?xml version="1.0" encoding="UTF-8"?> <manifest> <remote fetch=".." review="https://android-review.googlesource.com/" /> <default revision="android17-release" remote="aosp" sync-j="4" /> <superproject remote="aosp" revision="android-latest-release"/> <contactinfo bugurl="go/repo-bug" /> <!-- 开源项目 --> <project path="build/make" groups="pdk,sysui-studio" > <linkfile dest="build/CleanSpec.mk" /> <linkfile dest="build/buildspec.mk.default" /> <linkfile dest="build/core" /> <linkfile dest="build/envsetup.sh" /> <linkfile dest="build/target" /> <linkfile dest="build/tools" /> </project> <project path="build/blueprint" groups="pdk,tradefed" /> <project path="build/soong" groups="pdk,tradefed,sysui-studio" > <linkfile dest="Android.bp" /> <linkfile dest="bootstrap.bash" /> </project> ... </manifest>清单核心标签说明:
| 标签 | 作用 |
|---|---|
<remote> | 定义 Git 服务器地址、Gerrit 评审地址 |
<default> | 设置所有项目默认分支、远端地址、同步并发数 |
<project> | 定义单个 Git 仓库:服务端仓库名 → 本地路径映射 |
<linkfile> | 代码检出后创建符号链接(build/make 大量使用) |
<copyfile> | 检出后复制文件 |
<superproject> | Git 超级项目,用于全源码树原子快照 |
<include> | 引入外部清单片段 |
| groups | 项目分组,用于选择性同步 |
重点说明:build/make内的<linkfile>在源码根目录创建符号链接,保证传统脚本能够在历史路径找到build/envsetup.sh、build/core等目录。build/soong创建两个关键链接:
root.bp→ 源码根目录Android.bp(Soong 入口)bootstrap.bash→ 源码根目录bootstrap.bash
<default>内sync-j="4"代表默认 4 个同步线程;网络良好时,命令行使用-j16可覆盖默认值,加速同步。
清单结构深入解析
<remote>标签
<remote fetch=".." review="https://android-review.googlesource.com/" />- name:标签名,供
<project>引用指定服务器 - fetch:基础拉取地址。
..代表相对清单仓库地址;清单地址为https://android.googlesource.com/platform/manifest,则..解析为https://android.googlesource.com/ - review:Gerrit 代码评审地址,
repo upload依赖此地址推送变更
<default>标签
<default revision="android17-release" remote="aosp" sync-j="4" />- revision:未单独指定分支的项目默认跟踪分支
- remote:默认远端服务器
- sync-j:同步默认并发数
<project>标签
<project path="build/make" groups="pdk,sysui-studio" > <linkfile dest="build/envsetup.sh" /> </project>- path:仓库在本地源码树存放路径
- name:服务端仓库名称,拼接 remote 地址形成完整 Git 地址
- groups:项目所属分组,逗号分隔
- revision(可选):覆盖默认分支
- clone-depth(可选):浅克隆深度
- 子标签:
<linkfile><copyfile><annotation>
<superproject>标签
<superproject remote="aosp" revision="android-latest-release"/>指向 Git 超级项目,记录所有子仓库某一时间点的 commit SHA,实现整个源码树的原子快照,适用于可复现构建、二分调试。
本地清单 Local Manifests
不需要修改上游官方清单,可在.repo/local_manifests/放置自定义清单实现扩展。 示例:新增自定义厂商仓库
<!-- .repo/local_manifests/my_projects.xml --> <?xml version="1.0" encoding="UTF-8"?> <manifest> <project path="vendor/mycompany" remote="myremote" revision="main" /> <remote fetch="https://github.com/mycompany/" /> </manifest>移除上游清单内置项目:
<manifest> <remove-project /> </manifest>OEM、SoC 厂商常用此方式在源码树添加闭源组件,无需 Fork 官方清单仓库。
2.1.5 同步源码:repo sync
初始化完成后拉取完整源码:
# 完整同步,16并发线程 repo sync -j16完整 AOSP 初次同步下载约 100GB 压缩 Git 数据,耗时 1~3 小时,取决于网络质量。 启用增量克隆可以大幅降低下载量:
repo init -u https://android.googlesource.com/platform/manifest \ -b android17-release \ --partial-clone \ --clone-filter=blob:limit=10M repo sync -c -j16 --no-tagsrepo sync 常用参数
表格
| 参数 | 作用 |
|---|---|
| -j N | 同步并发任务数量 |
| -c | 仅拉取当前分支,提速 |
| --no-tags | 不下载标签,节省空间与时间 |
| --optimized-fetch | 仅同步发生变更的项目 |
| --prune | 清理本地过期分支 |
| -f | 单个仓库同步失败时继续执行,不中断整体同步 |
2.1.6 选择性同步与分组
开发并非总是需要完整源码树。清单中将项目划分不同分组,支持按需同步:
# 仅同步PDK(平台开发套件)项目 repo init -u https://android.googlesource.com/platform/manifest \ -b android17-release \ -g pdk repo sync -j16内置分组:pdk、tradefed、cts、device、vendor,以及设备专属分组yukawa、hikey等。
也可以只同步指定单个 / 多个仓库:
# 仅同步 frameworks/base repo sync frameworks/base # 同步多个指定仓库 repo sync frameworks/base packages/apps/Settings system/core分组支持包含与排除语法:
# 同步全部项目,排除设备相关代码 repo init ... -g default,-device # 仅同步 pdk + tradefed repo init ... -g pdk,tradefed # 列出所有项目及其所属分组 repo list -g特殊分组default:未显式分配分组的项目默认归属;分组名前缀-代表排除。
不同同步方案大致占用空间:
| 同步配置 | 源码占用大小 |
|---|---|
| 完整同步(所有分组) | ~100GB(Git 压缩数据) |
| 仅 PDK (-g pdk) | ~60GB |
| 最小编译系统 | ~20GB |
| 增量克隆 + 仅当前分支 | ~30GB |
2.1.7 使用特性分支(Topic Branches)
跨多个仓库开发时,repo 统一管理特性分支:
# 在指定仓库创建特性分支 repo start my-feature frameworks/base packages/apps/Settings # 在所有仓库创建特性分支 repo start my-feature --all # 查看所有特性分支状态 repo branches # 推送变更至Gerrit评审 repo upload # 删除已经合并完毕的特性分支 repo prunerepo upload打包本地提交,推送至 Gerrit。Gerrit 是谷歌网页代码评审平台,所有 AOSP 官方贡献均通过 https://android-review.googlesource.com/ 提交。
2.1.8 源码目录布局
完整同步后,源码顶层目录结构:
aosp/ Android.bp # 符号链接指向 build/soong/root.bp art/ # ART运行时 bionic/ # C标准库、动态链接器、libm数学库 bootable/ # Recovery、Bootloader相关库 build/ # 构建系统 blueprint/ # Blueprint解析器与框架 make/ # 遗留Make构建系统(胶水层) soong/ # Go语言实现的Soong构建系统 pesto/ # Bazel实验相关 release/ # 版本发布配置 cts/ # 兼容性测试套件 dalvik/ # 旧Dalvik虚拟机(基本被ART替代) development/ # 开发者工具与示例 device/ # 设备配置 generic/ # 模拟器目标(goldfish、cuttlefish) google/ # Pixel谷歌设备 external/ # 第三方开源库 frameworks/ # Android框架 base/ # 核心框架(Java + Native) native/ # SurfaceFlinger、Binder等Native服务 hardware/ # HAL接口与实现 kernel/ # 内核编译配置与预编译文件 libcore/ # Java核心库(OpenJDK) libnativehelper/ # JNI辅助库 packages/ # 系统应用与服务 apps/ # 设置、桌面启动器、相机等应用 modules/ # Mainline主线模块(APEX包) prebuilts/ # 预编译编译器、SDK、工具链 system/ # 底层系统组件(init、adb等) tools/ # 各类开发工具 vendor/ # 厂商私有代码源码根目录的Android.bp实际是软链接:build/soong/root.bp
// build/soong/root.bp // Soong自动递归查找源码树内所有Android.bp与Blueprints文件 // subdirs=、optional_subdirs= 已经废弃,此文件不再需要罗列顶层目录看似空白的文件至关重要:标记源码树根目录,引导 Soong 递归遍历所有子目录 Android.bp。
2.2 构建系统架构
2.2.1 构建系统演进历史
AOSP 构建系统经历三代重大迭代:
第一代:GNU Make(2008–2015)
Android 最初的构建系统完全基于 GNU Make。所有模块都通过Android.mk文件描述,借助 Make 变量与 include 指令实现。一份典型的Android.mk文件示例如下:
makefile
# 旧版 Android.mk 格式(仍可使用,但已废弃) LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := libexample LOCAL_SRC_FILES := example.cpp LOCAL_SHARED_LIBRARIES := liblog libutils LOCAL_C_INCLUDES := $(LOCAL_PATH)/include LOCAL_CFLAGS := -Wall -Werror include $(BUILD_SHARED_LIBRARY)这套系统虽然可用,但存在诸多广为人知的 Make 原生缺陷:
- 增量构建速度缓慢:每次执行构建时,Make 都需要重新完整解析依赖图,加载数千条 Makefile 引入语句。
- 变量作用域不可靠:Make 变量默认全局生效,两个模块若不慎使用同名变量,极易引发隐蔽 Bug。
- 并行构建能力受限:递归式 Make 在跨目录场景下本质上只能串行执行。
- 依赖约束缺失:任意 Makefile 都能读取其他文件定义的变量,无法清晰界定模块边界。
- 错误提示可读性差:一旦多层嵌套的 include 调用链出现异常,输出的错误信息极难排查。
在鼎盛时期,这套基于 Make 的构建系统包含超过 10000 个Android.mk文件,仅仅完成解析阶段就需要耗费数小时,之后才正式启动编译。
第二代:Soong / Blueprint(2015–至今)
Google 推出 Soong 作为替代方案,整体采用三层架构(下文说明)。模块现在通过Android.bp文件定义,语法简洁、声明式,风格类似 JSON;构建逻辑本身使用 Go 语言实现。 Make 依旧保留,作为一层轻量化适配层,负责产品配置与镜像组装,但新增模块必须使用 Android.bp 定义。
从 Make 向 Soong 的迁移是渐进式过程:androidmk工具支持自动转换,两套构建系统长期共存。随着 Android 各版本迭代,越来越多模块完成迁移。截至当前版本,平台绝大多数模块均采用Android.bp。
Soong 核心设计思想:声明与逻辑分离。 在 Make 体系中,构建脚本格式同时承担编程语言职责 —— 模块声明、构建逻辑混杂在同一文件内。 而 Soong 中,Android.bp仅做纯粹声明(不支持条件分支、循环),所有构建逻辑全部实现在 Soong 二进制程序内部的 Go 代码中。这使得Android.bp文件更加简洁,不易出错。
第三代:Bazel(2020–至今,实验阶段)
Google 持续推进将 AOSP 构建系统迁移至 Bazel(Google 内部 Blaze 构建系统的开源版本),相关进展跟踪目录:build/pesto/。 截至当前版本,Bazel 已用于内核构建(Kleaf)与部分试点项目;平台主体构建仍然依托 Soong。(早期用于将 Android.bp 转换为 Bazel BUILD 文件的bp2build工具,已从源码树中移除。)
转向 Bazel 的核心动因:
- 构建封闭性(hermeticity):Bazel 为每一步构建动作提供沙箱环境,保障构建结果可复现。
- 远程执行:构建任务可以分发到集群多台机器并行运行。
- 内容寻址缓存:开发者、持续集成系统、不同代码分支之间可以共享构建产物缓存。
- 高扩展性:Bazel 专为超大型代码仓库设计(Google 内部单体仓库拥有数十亿行代码)。
不过,迁移 AOSP 这种复杂度极高的构建系统是一项长达数年的工程。在可预见的未来,Soong 仍会是 AOSP 主力构建系统。
2.2.2 三层架构
现代 AOSP 构建系统分为三层,由不同技术实现:
2.2.3 第一层:Blueprint(build/blueprint/)
Blueprint 是元构建框架,它是一套 Go 语言库,提供解析模块定义文件、解析依赖、执行修改器、生成 Ninja 构建规则的底层能力。Blueprint 并非 Android 专用,它是通用型工具。
build/blueprint/ 目录下的 doc.go 文件对该框架描述如下:
// Blueprint 是一套元构建系统。它读取 Blueprint 文件,文件中描述待构建模块; // 随后生成 Ninja(https://ninja-build.org/)清单文件,清单定义需要执行的命令及其依赖关系。 // 大多数构建系统采用内置规则或领域专用语言描述模块转换为构建规则的逻辑, // 而 Blueprint 将该逻辑交由各项目使用 Go 语言编写的构建逻辑实现。来源:build/blueprint/doc.go
Blueprint 的核心是 context.go(6486 行,约 195KB),其中定义了 Context 结构体。它是中心状态对象,通过四个阶段调度完整构建流程:
// Context 包含解析一组 Blueprint 文件并生成 Ninja 文件所需的全部状态。 // Ninja 文件生成流程分为四个阶段,每个阶段对应 Context 对象上的若干方法。 // // 阶段 对应方法 // ------------ ------------------------------------------- // 1. 注册阶段 RegisterModuleType、RegisterSingletonType // // 2. 解析阶段 ParseBlueprintsFiles、Parse // // 3. 生成阶段 ResolveDependencies、PrepareBuildActions // // 4. 写入阶段 WriteBuildFile来源:build/blueprint/context.go,第 117–131 行
四大阶段简要流程:
详细阶段说明
1. 注册阶段向上下文注册模块类型(例如 cc_binary、java_library)与单例对象。每种模块类型对应一个 Go 工厂函数。
2. 解析阶段扫描源码树内全部 Android.bp 文件。Blueprint 解析器读取类 JSON 语法,并通过反射填充 Go 结构体。
3. 生成阶段解析模块间依赖;修改器按照注册顺序执行,可自上而下或自下而上遍历模块,传递配置信息,或将模块拆分为多个变体(例如为不同目标架构生成独立变体)。之后每个模块生成自身构建动作。
4. 写入阶段将所有收集完毕的构建动作序列化为 Ninja 清单文件。
build/blueprint/ 关键目录 / 文件
| 目录 / 文件 | 用途 |
|---|---|
| context.go | 核心调度逻辑(6486 行) |
| parser/ | Blueprint 文件解析器 |
| proptools/ | 属性反射与操作工具 |
| pathtools/ | 路径工具与通配符匹配 |
| depset/ | 依赖集合实现(类似 Bazel depset) |
| bpfmt/ | Blueprint 文件格式化工具 |
| bpmodify/ | 以程序方式修改 Blueprint 文件 |
| bootstrap/ | 自举逻辑 |
| gobtools/ | 用于序列化的 Go 二进制工具 |
| gotestmain/ | 测试主程序生成器 |
| gotestrunner/ | 测试运行工具 |
| metrics/ | 构建指标与事件处理 |
| incremental.go | 增量构建支持 |
| live_tracker.go | 依赖文件实时跟踪 |
Blueprint 修改器(Mutators)
修改器是 Blueprint 最重要的概念之一。修改器是遍历模块并可以修改模块配置的函数。修改器用于:
- 创建变体:一份模块声明可拆分多个变体。例如 cc_library 会拆分为设备端、主机端变体,进一步区分架构变体(arm64、x86_64 等)。
- 传递依赖信息:模块信息传递给依赖方(或反向传递)。
- 属性默认值填充:基于全局构建配置计算属性默认值。
修改器执行顺序: 前置依赖修改器 → 依赖解析 → 后置依赖修改器 → 最终依赖修改器 → 生成构建动作
- 前置依赖修改器:依赖解析前运行,可新增依赖、创建变体
- 依赖解析:将依赖名称匹配至真实模块
- 后置依赖修改器:依赖解析完成后运行,可读取依赖信息
- 最终依赖修改器:最后执行,用于末期调整
示例:APEX 系统使用后置依赖修改器,为库在每个所属 APEX 中生成独立变体
// 取自 build/soong/apex/apex.go func RegisterPostDepsMutators(ctx android.RegisterMutatorsContext) { ctx.BottomUp("apex_unique", apexUniqueVariationsMutator) ctx.BottomUp("mark_platform_availability", markPlatformAvailability) ctx.InfoBasedTransition("apex", android.NewGenericTransitionMutatorAdapter(&apexTransitionMutator{})) }来源:build/soong/apex/apex.go,第 66–71 行
Blueprint 提供者(Providers)
提供者是 Blueprint 在模块间传递信息的机制。模块生成构建动作时可以设置提供者数据,供依赖该模块的其他模块读取。相比 Make 的全局变量,该方式结构更规范。
// 提供者声明(取自 build/soong/cc/cc.go) var CcObjectInfoProvider = blueprint.NewProvider[CcObjectInfo]() // 在目标模块设置提供者 ctx.SetProvider(CcObjectInfoProvider, CcObjectInfo{ ObjFiles: objFiles, TidyFiles: tidyFiles, KytheFiles: kytheFiles, }) // 在依赖模块读取提供者数据 if info, ok := ctx.OtherModuleProvider(dep, CcObjectInfoProvider); ok { // 使用 info.ObjFiles 等字段 }2.2.4 第二层:Soong(build/soong/)
Soong 是 Android 正式的构建系统,基于 Blueprint 构建。它注册 Android 专属模块类型、修改器与单例对象。build/soong/ 包含 59 个子目录,按照模块类型与构建功能组织。
build/soong/README.md 描述:
Soong 是 Android 使用的构建系统之一,由 Android.bp 文件驱动。 同时存在老旧的 Make 构建系统,由 Android.mk 文件驱动。 Android.bp 文件采用类JSON声明式语法描述待构建“模块”; 模块是 Soong 识别的基础构建单元,类似 Make 中的目标(target)。来源:build/soong/README.md,第 1–8 行
构建逻辑补充说明:
构建逻辑基于 Blueprint 框架,使用 Go 语言编写。 构建逻辑接收通过反射解析为 Go 结构体的模块定义,生成构建规则。 Blueprint 收集所有构建规则并写入 Ninja 构建文件。来源:build/soong/README.md,第 610–614 行
build/soong/ 关键子目录
表格
| 目录 | 用途 | 核心文件 |
|---|---|---|
| cc/ | C/C++ 模块类型(cc_binary、cc_library 等) | cc.go、library.go、binary.go |
| java/ | Java/Kotlin 模块类型(java_library、android_app 等) | java.go、app.go、sdk_library.go |
| apex/ | APEX 模块类型(apex.go 共 3096 行) | apex.go、builder.go、key.go |
| rust/ | Rust 模块类型 | rust.go、library.go |
| python/ | Python 模块类型 | python.go |
| sh/ | Shell 脚本模块类型 | sh_binary.go |
| genrule/ | 通用构建规则模块 | genrule.go |
| android/ | Soong 核心框架(模块基类、架构处理) | module.go、arch.go、paths.go |
| filesystem/ | 镜像文件构建 | filesystem.go |
| ui/ | 构建界面与进度输出 | build.go |
| cmd/ | 命令行入口 | soong_build/、soong_ui/ |
| bpf/ | BPF 程序编译 | bpf.go |
| sdk/ | SDK 快照生成 | sdk.go |
| snapshot/ | 厂商快照管理 | snapshot.go |
| linkerconfig/ | 链接器命名空间配置 | linkerconfig.go |
| aconfig/ | 构建开关(aconfig)集成 | aconfig.go |
| bin/ | m、mm、mmm 等脚本 | m、mm、mmm |
| kernel/ | 内核相关构建逻辑 | kernel.go |
Go 代码内部:模块注册
每种模块类型通过 Go 的 init () 函数注册到 Soong。下面介绍三大模块系列的注册方式:
C/C++ 模块(build/soong/cc/cc.go,4885 行)
// 本文件包含Android平台编译C/C++代码所需的模块类型, // 将属性转换为编译器所需参数与文件名。 // 最终构建规则生成逻辑在 builder.go 中实现。 package cc来源:build/soong/cc/cc.go,第 15–19 行
C/C++ 模块系统定义大量结构体跟踪编译状态。例如 LinkerInfo 结构体保存所有链接依赖:
type LinkerInfo struct { WholeStaticLibs []string StaticLibs []string // 需要静态链接的模块 SharedLibs []string // 需要动态链接的模块 HeaderLibs []string // 仅头文件依赖 SystemSharedLibs []string ... }来源:build/soong/cc/cc.go,第 81–99 行
cc / 目录包含 30 余个 Go 文件,分别处理 C/C++ 编译各个环节:
| 文件 | 用途 | 代码行数 |
|---|---|---|
| cc.go | 核心模块类型与属性 | 4885 |
| builder.go | Ninja 规则生成 | 约 2000 |
| binary.go | cc_binary 实现 | 约 500 |
| library.go | cc_library 实现 | 约 2000 |
| sanitize.go | ASan/TSan/UBSan 支持 | 约 1500 |
| ndk_sysroot.go | NDK 系统根目录管理 | 约 400 |
| stl.go | C++ 标准库选择 | 约 300 |
| cmake_snapshot.go | CMake 工程生成 | 约 400 |
| check.go | 构建一致性检查 | 约 200 |
Java 模块(build/soong/java/java.go,4176 行)
// 本文件包含Android平台编译Java代码所需的模块类型, // 将属性转换为编译器所需参数与文件名。 // 最终构建规则生成逻辑在 builder.go 中实现。 package java func registerJavaBuildComponents(ctx android.RegistrationContext) { ctx.RegisterModuleType("java_defaults", DefaultsFactory) ctx.RegisterModuleType("java_library", LibraryFactory) ctx.RegisterModuleType("java_library_static", LibraryStaticFactory) ctx.RegisterModuleType("java_library_host", LibraryHostFactory) ctx.RegisterModuleType("java_binary", BinaryFactory) ctx.RegisterModuleType("java_binary_host", BinaryHostFactory) ctx.RegisterModuleType("java_test", TestFactory) ctx.RegisterModuleType("java_test_helper_library", TestHelperLibraryFactory) ctx.RegisterModuleType("java_test_host", TestHostFactory) ctx.RegisterModuleType("java_test_import", JavaTestImportFactory) ctx.RegisterModuleType("java_import", ImportFactory) ctx.RegisterModuleType("java_import_host", ImportFactoryHost) ctx.RegisterModuleType("java_device_for_host", DeviceForHostFactory) ctx.RegisterModuleType("java_host_for_device", HostForDeviceFactory) ctx.RegisterModuleType("dex_import", DexImportFactory) ctx.RegisterModuleType("java_api_library", ApiLibraryFactory) ctx.RegisterModuleType("java_api_contribution", ApiContributionFactory) ... }来源:build/soong/java/java.go,第 50–70 行
Genrule 模块(build/soong/genrule/genrule.go,1103 行)
// genrule模块接收源文件列表(srcs属性)、可选工具列表(tools属性)、 // 命令行(cmd属性),用于生成输出文件(out属性)。 package genrule func RegisterGenruleBuildComponents(ctx android.RegistrationContext) { ctx.RegisterModuleType("genrule_defaults", defaultsFactory) ctx.RegisterModuleType("gensrcs", GenSrcsFactory) ctx.RegisterModuleType("genrule", GenRuleFactory) ... }来源:build/soong/genrule/genrule.go,第 15–68 行
genrule 模块非常适合代码生成、Protocol Buffer 编译、AIDL 接口生成,或是任意需要运行自定义命令生成源码的场景。
Soong 内部构建流程
核心步骤为 GenerateAndroidBuildActions。所有模块类型必须实现该方法。方法读取模块属性、解析依赖、输出 Ninja 构建规则(编译、链接、文件复制等指令)。
构建入口脚本 build/soong/soong_ui.bash:
#!/bin/bash -eu source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../make/shell_utils.sh require_top # 记录启动耗时起点 case $(uname -s) in Darwin) export TRACE_BEGIN_SOONG=`$TOP/prebuilts/build-tools/path/darwin-x86/date +%s%3N` ;; *) export TRACE_BEGIN_SOONG=$(date +%s%N) ;; esac setup_cog_env_if_needed set_network_file_system_type_env_var # 保存当前工作目录,供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/mk2rbc soong_build_go rbcrun rbcrun/rbcrun soong_build_go release-config android/soong/cmd/release_config/release_config cd ${TOP} exec "$(getoutdir)/soong_ui" "$@"来源:build/soong/soong_ui.bash
该脚本完成 Go 构建系统自举:首先编译 soong_ui(构建驱动)与若干辅助工具,随后执行 soong_ui,由它调度整个构建流程。
2.2.5 第三层:Make 胶水层(build/make/)
尽管 Soong 负责模块编译,但 GNU Make(借助专为 Android 优化的 Make 克隆工具 Kati)依旧承担重要作用:
产品配置:PRODUCT_*系列变量、BoardConfig.mk以及设备配置文件仍采用 Make 语法编写。 镜像组装:将编译产物打包为分区镜像(system.img、vendor.img 等)的构建规则存放于 Make 文件中。 遗留模块:部分模块仍使用 Android.mk(不过随着版本迭代,这类模块数量持续减少)。
build/make/目录包含 26 个顶层条目:
| 目录 / 文件 | 用途 |
|---|---|
| core/ | 核心构建逻辑(头文件、规则、模块定义) |
| target/ | 产品与硬件板级配置文件 |
| tools/ | 构建工具(发布工具、签名工具 signapk 等) |
| envsetup.sh | Shell 环境初 |