Soong构建入门

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: truefalse)
  • 整数 (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_* 系列关键字实现。

  1. 模块定义关键字
    • soong_config_module_type: 定义一个"增强型"的模块模板。它指定了一个命名空间、一组变量以及这些变量可以影响哪些属性(如 cflags 或 srcs)。
    • soong_config_string_variable:定义一个字符串类型的变量及其允许的取值范围。子属性name 变量名字,values:取值列表
    • soong_config_bool_variable / soong_config_value_variable: 分别用于定义布尔开关(true/false)和数值插值(将变量值直接注入字符串)。 子属性name 变量名字,values:取值列表
  2. 属性关键字
    • config_namespace:将变量逻辑隔离。不同厂商可以定义自己的命名空间(如 acme),避免变量名冲突。
    • variables / bool_variables / value_variables: 在模块类型定义中声明该模板支持哪些类型的变量。
    • properties:声明哪些模块属性(如 srcs, cflags, shared_libs)允许根据变量值进行动态调整。
    • soong_config_variables:在具体的业务模块中,根据变量的实际取值来定义具体的补丁逻辑。
    • conditions_default:当变量未指定或取值不在预期列表中时,执行的默认构建逻辑。

下面是三步骤示例:

  1. 定义
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: [取值列表,],
}
  1. 使用
css 复制代码
// 使用定义的模板
定义的模板类型 {
    name: "名字",
    soong_config_variables: { // 里面是变量
        变量1: {
            取值1: {
                影响属性1: ,
                影响属性2: ,
            },
            ...,
            conditions_default: {
                ... // 默认影响属性设置
            },
        },
        ...,
    },
}
  1. 厂商设定
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 中写复杂逻辑,通常有三个步骤:

  1. 定义 Go 模块:编写处理逻辑。
  2. 注册插件:通过 android.RegisterModuleType 注册自定义的模块类型。
  3. 在 BP 中引用:使用 bootstrap_go_package 加载你的 Go 代码。

Go 处理后的属性有效性遵循 "模块在哪,逻辑在哪" 的原则。

  • 它不是通过物理目录强行覆盖,而是通过 BP 文件的引用关系(Defaults/Module Type) 来生效的。
  • 如果你在子目录的 .bp 里定义了一个模块并引用了该插件,那么该插件逻辑涉及到的所有文件(当前目录及子目录下涉及的 srcs)都会受到影响。

这里就不写示例

5 常用关键字

项目中执行 m soong_docs,将生成 out/soong/docs/soong_build.html,这里可以查阅所有的关键字;

  1. 基础通用
    • name: 模块的唯一身份标识。全源码树必须唯一。
    • srcs: 源文件列表。支持通配符 .cpp 和 **/.cpp(递归),以及引用模块 :my_filegroup。
    • defaults: 引用 cc_defaults 模块名,继承其配置。
    • cflags: 传递给编译器的标志(如 ["-Wall", "-Werror", "-DDEBUG"])。
    • shared_libs: 依赖的动态库。
    • static_libs: 依赖的静态库。
    • header_libs: 仅头文件依赖(不链接具体库)。
    • export_include_dirs: 将自己的头文件目录暴露给引用了本模块的其他模块。
    • stl: 指定使用的 C++ 标准库类型。
  2. 模块类型:见3.1章
  3. 定制与多变体:用于处理不同架构或平台的差异,替代传统的 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"。
  4. 权限与组织:管理模块的可见性和物理隔离。
    • 可见性关键字,见 2.9章
    • soong_namespace: 定义一个独立的命名空间,隔离模块名冲突。
    • imports: 在 soong_namespace 内部使用,导入其他命名空间。
  5. 厂商定制:用于与 BoardConfig.mk 联动的高级标签。
    • soong_config_module_type: 定义一个能感知厂商变量的新模块模板。
    • config_namespace: 绑定 BoardConfig.mk 中的变量空间。
    • soong_config_variables: 在具体模块中编写受变量控制的逻辑。
    • conditions_default: 当变量未设置时的默认配置。

如果在此文章中您有所收获,请给作者一个鼓励,点个赞,谢谢支持

技术变化都很快,但基础技术、理论知识永远都是那些;作者希望在余后的生活中,对常用技术点进行基础知识分享;如果你觉得文章写的不错,请给予关注和点赞;如果文章存在错误,也请多多指教!

相关推荐
笔夏1 小时前
【安卓学习之混淆】记录一些混淆导致闪退
android·学习
阿巴斯甜1 小时前
Kotlin高阶函数和Java 8 lambda的区别:
android
张小潇2 小时前
AOSP15 WMS/AMS系统开发 - WindowManagerService relayout调用流程详解
android
阿巴斯甜2 小时前
Kotlin 高阶函数:
android
ServBay2 小时前
2026年 Go 开发中没有它就不行的 10 个库
后端·go
之歆2 小时前
Day03_HTML 列表、表格、表单完整指南(下)
android·javascript·html
QING6182 小时前
Kotlin之【init】—— 新手须知
android·kotlin·android jetpack
阿巴斯甜2 小时前
MMKV 和DataStore 的区别:
android
阿巴斯甜3 小时前
MVVM和MVI的区别:
android