1 简介
Soong 是 Android 7.0 后引入的构建系统,旨在取代基于 Make 的旧系统(Android.mk)。它将声明式的 Android.bp 文件解析为 Ninja 构建规范,从而实现更快的编译速度和更严谨的依赖管理。
- 核心理念:设计上追求极简,禁止在 Android.bp 中使用条件分支或控制流(如 if/else),复杂的逻辑必须移交给 Go 语言编写的构建插件。
- 语法风格:采用类 JSON 的声明式语法,与 Bazel 的 BUILD 文件高度相似。
2 概念
2.1 文件列表
接收文件列表的属性也支持使用 glob 模式和输出路径扩展。
-
Glob 模式 :可以包含标准的 Unix 通配符 (例如 ".java")。也可以包含单路径元素 **(匹配零个或多个路径元素),例如 java/**/*.java 会匹配 java/Main.java 和 java/com/android/Main.java。
-
输出路径扩展:格式为 :module 或 :module{.tag}。module 是产出输出文件的模块名,它会扩展为这些文件的列表。可选的 {.tag} 后缀允许模块根据不同标签产出不同的输出列表。这常用于引用 filegroup 模块。
2.2 变量
变量的作用域限于声明它的文件及其子 Android.bp 文件。变量是不可变的(immutable),但有一个例外:可以使用 += 进行追加赋值,但仅限于该变量被引用之前。
2.3 注释
支持 C 风格的多行注释 /* */ 和 C++ 风格的单行注释 //。
2.4 类型
变量和属性是强类型的。变量根据第一次赋值动态确定类型,属性根据模块类型静态确定类型。支持的类型包括:
- 布尔值 (Bool:
true或false) - 整数 (Integers:
int) - 字符串 (Strings:
"string") - 字符串列表 (Lists of strings:
["string1", "string2"]) - 映射 (Maps:
{key1: "value1", key2: ["value2"]})
映射可以包含任何类型的值,包括嵌套映射。列表和映射在最后一个值后可以有逗号。字符串中的双引号可以使用 " 转义。
2.5 运算符
字符串、字符串列表和映射可以使用 + 运算符追加。整数可以使用 + 求和。追加映射会产生键的并集,并追加两个映射中都存在的任何键的值。
2.6 包
构建过程被组织成"包"。包是相关文件的集合,以及它们之间以模块形式体现的依赖规范。包定义为包含 Android.bp 文件的目录。包名是其相对于顶层目录的路径。一个包包含其目录下及其所有子目录下的所有文件,除非子目录中本身包含 Android.bp 文件(那将定义一个新的子包)。
2.7 引用模块
模块可以通过其名称直接引用;这仅在全树模块名唯一时有效。为了解决重名问题(例如不同设备实现功能等同但名称相同的模块),Soong 引入了命名空间。 关键字: imports
2.8 命名空间
在 Android.bp 中加入 soong_namespace {..} 即可定义命名空间。该路径下所有模块名必须唯一,但可以与外部重名。
- 隐式全局命名空间:对应整个源码树,名称为空。
- 作用域 (Scope) :模块的作用域是包含它的最小命名空间。全局唯一的引用格式为
"//scope:name"。
2.9 可见性
visibility 属性控制模块是否能被其他包使用。
- "//visibility:public": 公开可见。
- "//visibility:private": 仅本包可见。
- "//some/package:pkg": 仅指定包可见。
- "//project:subpackages": 指定包及其子包可见。
- ":subpackages":当前路径及其子包,缩写形式。
如果模块未指定 visibility(可见配置列表),则使用其所属包(package 模块)的 default_visibility。
2.10 条件语句
Soong 故意不支持 Android.bp 中的大多数条件语句。复杂逻辑应在 Go 编写的构建逻辑中处理。原生支持的条件通常转换为映射属性。
3 模块
以模块类型开头,后跟一组 name: value, 格式的属性:
- 唯一性:每个模块必须有一个 name 属性,且其值在整个源码树的所有 Android.bp 文件中必须是唯一的。
- 多变体:一个模块可以通过配置(如 host_supported: true)同时构建出多个变体
3.1 类型
- cc_binary:可执行程序,编译生成二进制文件
- cc_library:库文件,同时编译出静态库(.a)和动态库(.so)
- cc_library_static:仅仅编译出静态库
- cc_library_shared:仅编译生成动态库
- cc_defaults:默认模板,高级配置核心。定义一套公共属性,供其他模块继承。
- filegroup:文件组,将一组源文件打包,通过 :name 形式在其他模块中引用
- soong_config_module_type:厂商配置,高级配置核心。定义一个可以感知 BoardConfig.mk 变量的新模块类型。
3.2 厂商配置
将 BoardConfig.mk 中的传统变量桥接到声明式的 Android.bp 中。这主要通过 soong_config_* 系列关键字实现。
- 模块定义关键字
- soong_config_module_type: 定义一个"增强型"的模块模板。它指定了一个命名空间、一组变量以及这些变量可以影响哪些属性(如 cflags 或 srcs)。
- soong_config_string_variable:定义一个字符串类型的变量及其允许的取值范围。子属性name 变量名字,values:取值列表
- soong_config_bool_variable / soong_config_value_variable: 分别用于定义布尔开关(true/false)和数值插值(将变量值直接注入字符串)。 子属性name 变量名字,values:取值列表
- 属性关键字
- config_namespace:将变量逻辑隔离。不同厂商可以定义自己的命名空间(如 acme),避免变量名冲突。
- variables / bool_variables / value_variables: 在模块类型定义中声明该模板支持哪些类型的变量。
- properties:声明哪些模块属性(如 srcs, cflags, shared_libs)允许根据变量值进行动态调整。
- soong_config_variables:在具体的业务模块中,根据变量的实际取值来定义具体的补丁逻辑。
- conditions_default:当变量未指定或取值不在预期列表中时,执行的默认构建逻辑。
下面是三步骤示例:
- 定义
arduino
// 定义厂商专用的模块类型
soong_config_module_type {
name: "定义模块名字",
module_type: "cc_defaults", // 它是对标准 cc_defaults 的扩展
config_namespace: "命名空间", // 对应 mk 中的命名空间
variables: [变量名字,], // 字符串变量
bool_variables: [], // 布尔变量
value_variables: [变量名字,], // 数值变量
properties: [影响属性名字], // 允许受影响的属性
}
// 定义字符串变量的枚举值
soong_config_string_variable {
name: "变量名字",
values: [取值列表,],
}
- 使用
css
// 使用定义的模板
定义的模板类型 {
name: "名字",
soong_config_variables: { // 里面是变量
变量1: {
取值1: {
影响属性1: ,
影响属性2: ,
},
...,
conditions_default: {
... // 默认影响属性设置
},
},
...,
},
}
- 厂商设定
makefile
# 在设备配置目录下的 BoardConfig.mk 中设置
$(call soong_config_set,厂商空间,变量名字,变量值)
3.3 多变体
指同一个模块定义可以根据不同的构建目标或配置,自动生成多个不同的编译产物。
- 主机与设备变体:host_supported: true,同时支持手机和电脑;device_supported: false 仅仅电脑
- 架构差异化配置:arch,其取值arm64,x86_64等
- 目标系统配置:target,其取值android,host等
android源码使用频繁的就是arch,这不就妥妥的神似厂商配置
4 Go语言配合使用
当 .bp 的声明式语法无法满足复杂逻辑(如根据多个环境变量动态计算源码列表)时,就需要用到 Go。
实现原理:Soong 的构建逻辑是用 Go 编写的,基于 Blueprint 框架。它通过反射(Reflection)将 .bp 定义解析为 Go 结构体,并生成 Ninja 构建规则。
要在 Go 中写复杂逻辑,通常有三个步骤:
- 定义 Go 模块:编写处理逻辑。
- 注册插件:通过 android.RegisterModuleType 注册自定义的模块类型。
- 在 BP 中引用:使用 bootstrap_go_package 加载你的 Go 代码。
Go 处理后的属性有效性遵循 "模块在哪,逻辑在哪" 的原则。
- 它不是通过物理目录强行覆盖,而是通过 BP 文件的引用关系(Defaults/Module Type) 来生效的。
- 如果你在子目录的 .bp 里定义了一个模块并引用了该插件,那么该插件逻辑涉及到的所有文件(当前目录及子目录下涉及的 srcs)都会受到影响。
这里就不写示例
5 常用关键字
项目中执行 m soong_docs,将生成 out/soong/docs/soong_build.html,这里可以查阅所有的关键字;
- 基础通用
- name: 模块的唯一身份标识。全源码树必须唯一。
- srcs: 源文件列表。支持通配符 .cpp 和 **/.cpp(递归),以及引用模块 :my_filegroup。
- defaults: 引用 cc_defaults 模块名,继承其配置。
- cflags: 传递给编译器的标志(如 ["-Wall", "-Werror", "-DDEBUG"])。
- shared_libs: 依赖的动态库。
- static_libs: 依赖的静态库。
- header_libs: 仅头文件依赖(不链接具体库)。
- export_include_dirs: 将自己的头文件目录暴露给引用了本模块的其他模块。
- stl: 指定使用的 C++ 标准库类型。
- 模块类型:见3.1章
- 定制与多变体:用于处理不同架构或平台的差异,替代传统的 if/else。
- host_supported: 布尔值。设为 true 表示该模块既能编手机版,也能编电脑版(Host)。
- device_supported: 默认为 true,指编译 Android 目标版。
- arch: 架构选择器。内部可包含 arm, arm64, x86 等子块。
- target: 目标选择器。内部可包含 android, host, linux_glibc 等子块。
- multilib: 控制编译 32 位还是 64 位。常用值:"both", "first", "32", "64"。
- 权限与组织:管理模块的可见性和物理隔离。
- 可见性关键字,见 2.9章
- soong_namespace: 定义一个独立的命名空间,隔离模块名冲突。
- imports: 在
soong_namespace内部使用,导入其他命名空间。
- 厂商定制:用于与 BoardConfig.mk 联动的高级标签。
- soong_config_module_type: 定义一个能感知厂商变量的新模块模板。
- config_namespace: 绑定 BoardConfig.mk 中的变量空间。
- soong_config_variables: 在具体模块中编写受变量控制的逻辑。
- conditions_default: 当变量未设置时的默认配置。
如果在此文章中您有所收获,请给作者一个鼓励,点个赞,谢谢支持
技术变化都很快,但基础技术、理论知识永远都是那些;作者希望在余后的生活中,对常用技术点进行基础知识分享;如果你觉得文章写的不错,请给予关注和点赞;如果文章存在错误,也请多多指教!