第4章 Soong构建系统入门:Android.bp语法、模块定义、build.go核心概念
4.1 Soong构建系统概述
Soong是AOSP从Android 7.0开始引入的下一代构建系统,用Go语言编写,逐步取代传统Android.mk/Makefile体系。其核心思想是:模块描述(Android.bp)→ Blueprint解析 → 构建逻辑(build.go) → Ninja文件 → 最终构建。相比Make,Soong提供更快的增量构建、严格的模块依赖检查、丰富的模块类型和更强的可扩展性。
关键对比:Make体系依赖全局变量隐式传递,Soong强制通过Android.bp显式声明模块属性,避免隐式副作用,使构建更可预测。
4.2 Soong构建流程
理解Soong必须先理解其分层架构:
- Blueprint解析层:读取所有Android.bp文件,解析为Go结构体,生成模块依赖图。
- 模块注册层(ModuleFactory):根据模块类型(如cc_library)调用对应的工厂函数构建模块实例。
- 属性处理层(PropertyStruct):将bp中的JSON-like属性映射到Go结构体的字段,支持类型校验、必要值检查。
- 构建逻辑层(build.go):每个模块类型通过实现Build方法生成具体规则(编译、链接、打包等),最终输出Ninja构建文件。
- Ninja执行层:Soong调用Ninja解析生成的*.ninja文件并执行实际编译。
核心命令:构建时AOSP会自动调用
soong_build,你通常只需要执行make或直接m,Soong会负责整个转换链。
4.3 Android.bp语法基础
Android.bp使用类似JSON的语法,但更简洁(无引号、无逗号强制)。每个文件可以包含多个模块定义。基本结构:
// 模块类型 + 括号内的属性表
cc_library {
name: "libfoo",
srcs: ["src/foo.cpp", "src/bar.cpp"],
cflags: ["-Wall", "-O2"],
shared_libs: ["libutils"],
export_include_dirs: ["include"],
stl: "libc++_static",
}
关键语法元素:
| 元素 | 说明 | 示例 |
|---|---|---|
| 模块类型 | 顶层标识,如cc_library、android_app | cc_binary { ... } |
| 属性名 | 小写蛇形,全部可选/必选取决于模块类型 | name, srcs, cflags |
| 列表 | 用方括号,元素为字符串 | ["a.cpp", "b.cpp"] |
| 布尔 | true / false | static_executable: true |
| 字符串 | 双引号 | "liblog" |
| 注释 | // 单行 或 /* ... */ | // 这是我的模块 |
名称唯一性:所有模块的
name必须在全局唯一(包含跨分区)。若重复会导致构建错误。
4.4 常见模块类型与属性
| 模块类型 | 用途 | 核心属性示例 |
|---|---|---|
| cc_library | C/C++静态或共享库 | srcs, shared_libs, static_libs, export_include_dirs |
| cc_binary | 可执行文件 | srcs, shared_libs, static_libs, linkerflags |
| android_app | APK模块 | srcs, platform_apis, certificate, dex_preopt |
| java_library | Java库(jar) | srcs, static_libs, sdk_version |
| genrule | 自定义生成规则 | tool_files, cmd, out |
4.5 build.go核心概念
build.go是Soong中最关键的扩展点,每个模块类型都有一个对应的Go文件实现Module接口:
type Module interface {
Name() string
// 返回该模块的无依赖规则
GenerateBuildActions(ctx ModuleContext)
// 其他接口方法
}
核心概念包括:
- ModuleFactory:注册模块类型的工厂函数,例如
cc.RegisterLibraryModule(…)。 - PropertyStruct:通过注解
// property:"name"将bp属性映射到Go结构体字段,支持默认值和校验。 - ModuleContext:提供了访问所有已注册模块、处理依赖、输出构建规则等API。
- GenerateBuildActions:每个模块在此方法内通过
ctx.Build(pctx, &builder)生成Ninja构建边。
调试技巧:查看Soong生成的Ninja文件有助于理解构建逻辑,位于
$OUT/soong/build.ninja。可以使用ninja -f $OUT/soong/build.ninja -t targets all列出所有目标。
4.6 常见错误与解决方案
| 错误现象 | 原因 | 解决方法 |
|---|---|---|
| “module name 'xxx' is not unique” | 两个Android.bp中定义了相同name | 重命名模块或通过//build/soong定位重复定义 |
| “unknown module type 'cc_library2'” | 写错模块类型 | 检查拼写,常见类型列表见4.4节 |
| “srcs: unexpected value 'string'” | srcs写成了字符串而非列表 | 改为 ["file.cpp"] |
依赖循环:circular dependency | 模块A依赖B,B又依赖A | 解耦依赖,或使用defaults模块简化 |
注意:不要在Android.bp中使用
include或ifeq等Make风格宏。Soong不支持条件编译(除了android_available等少数内置属性)。需要条件编译请使用enabled属性或arch变量。
4.7 性能优化建议
- 合理拆分模块:避免单个cc_library包含过多文件,尽量按功能拆分成多个小库,利用Ninja并行构建能力。
- 使用shared_libs而非static_libs(当需要多个模块共享代码时),减少编译膨胀。
- 指定arch变体:如
arch: { arm: { cflags: ["-mthumb"] } },避免非目标架构的无效编译。 - 使用
defaults模块:将公共属性(如编译选项、依赖)集中定义,减少文件变动触发的重建范围。 - 避免全局依赖:只声明实际使用的共享库,不要滥用
whole_static_libs。
4.8 动手实践:编写首个Android.bp
- 在AOSP源码任意目录(如
vendor/yourcompany/hello/Android.bp)创建文件。 - 编写一个简单可执行模块:
cc_binary { name: "hello_world", srcs: ["main.cpp"], shared_libs: ["libcutils"], } - 创建
main.cpp:#include <cutils/log.h> int main() { ALOGD("Hello Soong"); return 0; } - 在源码根目录执行
make hello_world。 - 产物位于
$OUT/system/bin/hello_world,可通过adb push到设备验证。
验证命令:
adb push $OUT/system/bin/hello_world /data/local/tmp/
adb shell chmod +x /data/local/tmp/hello_world
adb shell /data/local/tmp/hello_world
4.9 本章小结
Soong构建系统的核心是Android.bp→Blueprint→build.go→Ninja的四层转换。掌握bp语法(模块类型、属性表、列表与字符串表示)、理解build.go中Module接口和PropertyStruct机制,是编写和调试AOSP模块的基础。下一层级我们将深入模块依赖解析和编译资源管理。