4. Soong构建系统入门
在 Android 源代码树中,Soong 是当前主流的构建系统,它用 Android.bp 文件替代了旧版 Android.mk,把构建描述从 Make 的“命令式脚本”转变为“声明式配置”。理解 Soong 的关键,是理解 Blueprint 文件如何被解析、模块如何定义、属性如何组合,以及变量如何影响最终构建图。
一、核心原理:Blueprint 是 Soong 的配置语言
Soong 的输入是 Android.bp,它采用 Blueprint 语法。Blueprint 不是 Make,也不是 JSON,而是一种类似声明式配置的 DSL。每个 Android.bp 文件由若干模块定义组成,每个模块定义以模块类型开头,后跟一对花括号,内部是属性键值对。
Soong 解析时,会先读取所有 Android.bp 文件,把模块定义转换成内存中的模块对象,再根据模块类型和属性生成构建规则,最终交给 Ninja 执行。因此,Android.bp 只描述“要构建什么”,不直接描述“如何执行编译命令”。
模块类型 { 属性名: 属性值, ... }例如:
cc_library { name: "libfoo", srcs: ["foo.cpp"] }
二、Android.bp 语法与模块定义
一个典型的 Android.bp 文件可以包含多个模块。模块类型决定了这个模块是库、可执行文件、资源包还是测试程序。常见模块类型包括 cc_library、cc_binary、java_library、android_app、filegroup 等。
cc_library {
name: "libmylib",
srcs: [
"src/foo.cpp",
"src/bar.cpp",
],
shared_libs: [
"liblog",
"libbase",
],
cflags: [
"-Wall",
"-Werror",
],
visibility: [
"//vendor/mycompany:__subpackages__",
],
}
模块定义中的 name 是模块的唯一标识,其他模块通过这个名字引用它。属性值可以是字符串、布尔值、整数、列表或映射。列表用方括号表示,元素之间用逗号分隔;字符串必须用双引号包裹。
Blueprint 支持注释,使用 // 表示单行注释,使用 /* ... */ 表示多行注释。注释不会影响构建结果,但能显著提升可读性。
三、属性与变量:让配置更灵活
Soong 允许在 Android.bp 中定义变量,变量以 name = value 的形式出现,可以在后续模块中引用。变量常用于存放公共源文件列表、编译选项或依赖库列表,避免重复书写。
common_srcs = [
"src/common.cpp",
"src/utils.cpp",
]
common_cflags = [
"-Wall",
"-Wextra",
]
cc_library {
name: "libcommon",
srcs: common_srcs,
cflags: common_cflags,
}
cc_binary {
name: "my_tool",
srcs: common_srcs + ["src/main.cpp"],
cflags: common_cflags,
}
变量支持列表拼接,例如 common_srcs + ["src/main.cpp"]。这种写法在大型项目中非常常见,可以让多个模块共享同一组源文件或编译参数。
除了自定义变量,Soong 还提供了一些内置变量和函数,例如 soong_config_module_type、select 等。其中 select 用于根据条件选择不同属性值,常用于区分不同架构、不同产品配置或不同编译变体。
cc_library {
name: "libarch",
srcs: ["src/common.cpp"],
arch: {
arm: {
srcs: ["src/arm.cpp"],
},
arm64: {
srcs: ["src/arm64.cpp"],
},
},
}
上面的 arch 属性是 Soong 中常见的条件属性,它根据目标架构选择不同的源文件。类似地,还有 product_variables、vendor、proprietary 等属性,用于处理产品差异和分区差异。
四、模块依赖与可见性
模块之间通过属性建立依赖关系。例如 shared_libs、static_libs、header_libs 用于 C/C++ 模块;libs、static_libs 用于 Java 模块;deps 用于通用依赖。Soong 会根据这些属性构建依赖图,并确保依赖模块先于当前模块构建。
可见性属性 visibility 控制模块可以被哪些其他模块引用。默认情况下,模块只对同一目录或子目录可见。如果希望跨目录引用,需要显式声明可见性,例如 //vendor/mycompany:__subpackages__ 表示对 vendor/mycompany 及其子目录可见。
| 属性 | 作用 | 常见模块类型 |
|---|---|---|
name |
模块唯一名称 | 所有模块 |
srcs |
源文件列表 | cc_library、java_library 等 |
shared_libs |
动态库依赖 | cc_library、cc_binary |
static_libs |
静态库依赖 | cc_library、cc_binary |
cflags |
C/C++ 编译选项 | cc_library、cc_binary |
visibility |
模块可见范围 | 所有模块 |
五、案例:从零编写一个 Android.bp
假设我们要在 vendor/demo/hello 目录下构建一个可执行程序 hello,它依赖一个本地静态库 libhello。可以这样写:
cc_library_static {
name: "libhello",
srcs: ["hello.cpp"],
cflags: ["-Wall"],
visibility: [":__subpackages__"],
}
cc_binary {
name: "hello",
srcs: ["main.cpp"],
static_libs: ["libhello"],
cflags: ["-Wall"],
}
执行 m hello 或 mm 后,Soong 会解析这两个模块,生成 Ninja 规则,最终产出 hello 可执行文件。如果依赖关系或可见性配置错误,构建会直接报错,而不是像 Make 那样在链接阶段才暴露问题。
m nothing 或 soong_ui --make-mode nothing,让 Soong 只解析 Android.bp 而不执行完整编译,可以快速发现语法错误和依赖问题。
六、常见误区与复盘
- 把 Android.bp 当 Makefile 写:Blueprint 不支持 shell 命令、条件分支和函数调用,不能用
if、foreach等 Make 语法。 - 忽略 visibility:跨目录引用模块时,必须显式声明可见性,否则会报“module not visible”错误。
- 变量作用域混淆:变量只在当前
Android.bp文件内有效,不能跨文件直接引用。 - 依赖顺序错误:Soong 会自动处理依赖顺序,但循环依赖会导致解析失败,需要拆分模块或提取公共库。
- 属性名拼写错误:Soong 对属性名大小写敏感,拼写错误会直接报 unknown property。
掌握 Blueprint 文件、模块定义、属性和变量,是进入 Soong 构建系统的第一步。后续章节会进一步介绍 Soong 的模块类型体系、条件编译、产品配置以及如何与 Ninja 协同工作。