6、Android.bp基础:Blueprint与Soong的关系、Android.bp文件结构、模块定义、属性类型
好,我们直接进入正题。Android.bp 这个文件,说白了就是 AOSP 构建系统的「配置文件」。你打开任何一个 AOSP 源码目录,大概率都能看到它。我刚开始接触的时候,第一反应是:这玩意儿跟 Makefile 长得也太不一样了。确实,它背后是一套全新的设计思路。
今天我们就把它拆开揉碎了讲清楚。核心就三件事:Blueprint 和 Soong 到底谁是谁、Android.bp 文件怎么写、模块和属性怎么玩。
6.1 Blueprint 与 Soong:一对黄金搭档
很多新手会混淆这两个概念。我简单给你捋一捋。
- Blueprint:它是一个解析框架。负责读取 .bp 文件,把它解析成内部的数据结构。它不关心具体怎么编译,只关心「文件里写了什么」。
- Soong:它是真正的构建引擎。它从 Blueprint 拿到解析结果,然后调用 Ninja 去执行具体的编译、链接、打包等操作。
你可以这么理解:Blueprint 是翻译官,把 Android.bp 翻译成 Soong 能听懂的语言。Soong 是包工头,拿着翻译好的图纸去指挥 Ninja 干活。
核心关系图:
Android.bp → Blueprint(解析)→ Soong(构建逻辑)→ Ninja(执行)→ 产物
我在项目中遇到过一个问题:修改了 Android.bp 后,增量编译居然没生效。排查了半天,发现是 Blueprint 的缓存没清掉。嗯,这里要注意:Blueprint 会缓存解析结果,如果你改了 .bp 文件但没触发重新解析,Soong 拿到的还是旧数据。解决办法很简单:make clean 或者删掉 out/ 目录下的相关缓存文件。
下面这张图可以帮你更直观地理解它们的关系:
小技巧:如果你只想重新解析某个模块的 .bp 文件,可以试试 m blueprint_clean 然后 m。我一般用这个来排查「改了 .bp 但没生效」的玄学问题。
6.2 Android.bp 文件结构
Android.bp 的语法其实很简单。它基于 JSON 的变体,但去掉了引号和逗号的一些限制。你想想看,如果每行都要写逗号,那改起来多麻烦。Google 的设计哲学就是:让配置文件更易读、易改。
一个典型的 Android.bp 文件长这样:
// 这是一个注释
cc_library {
name: "libhello",
srcs: ["hello.c", "utils.c"],
cflags: ["-Wall", "-O2"],
shared_libs: ["libcutils"],
export_include_dirs: ["include"],
}
结构上就三部分:
- 注释:用
//开头,跟 C++ 一样。没有/* */这种多行注释。 - 模块定义:比如
cc_library、android_app这些。每个模块用花括号{}包裹。 - 属性赋值:
name、srcs、cflags这些都是属性。属性名和值之间用冒号:隔开。
我个人习惯把 name 属性写在最前面,这样一眼就能看到这个模块叫什么。后面再跟 srcs、deps 这些。顺序不重要,但保持一致性会让代码更清爽。
注意:Android.bp 里不允许使用 # 作为注释。我曾经把 Makefile 的习惯带过来,写了个 # 这是注释,结果 Blueprint 直接报语法错误。记住,只能用 //。
6.3 模块定义:构建的基本单元
模块是 Android.bp 的核心。每个模块代表一个构建目标,比如一个动态库、一个可执行文件、一个 APK。模块的类型决定了 Soong 怎么处理它。
常见的模块类型有:
| 模块类型 | 说明 | 典型产物 |
|---|---|---|
cc_library |
C/C++ 库 | .so / .a |
cc_binary |
C/C++ 可执行文件 | 可执行文件 |
android_app |
Android 应用 | .apk |
java_library |
Java 库 | .jar |
genrule |
自定义生成规则 | 任意文件 |
每个模块必须有一个 name 属性,而且在整个构建系统中必须唯一。你想想看,如果两个模块都叫 libhello,Soong 就不知道该编译哪个了。我见过有人把模块名写错,结果链接的时候报「undefined reference」,排查了半天才发现是名字冲突了。
一个文件里可以定义多个模块:
cc_library {
name: "libhello",
srcs: ["hello.c"],
}
cc_binary {
name: "hello_world",
srcs: ["main.c"],
shared_libs: ["libhello"],
}
这里 hello_world 依赖 libhello。Soong 会自动处理依赖顺序,你不需要像 Makefile 那样手动指定依赖链。
6.4 属性类型:灵活配置的基石
属性是模块的「参数」。不同的模块类型支持的属性不一样,但有一些是通用的。我挑几个最常用的给你讲讲。
6.4.1 字符串与字符串列表
最简单的属性类型。单个字符串用双引号,列表用方括号:
name: "mymodule", // 字符串
srcs: ["a.c", "b.c"], // 字符串列表
cflags: ["-Wall"], // 也是字符串列表
如果列表只有一个元素,你可以省略方括号:
srcs: "single.c", // 等价于 ["single.c"]
嗯,这个语法糖挺方便的。但我建议你始终使用方括号,哪怕只有一个元素。这样以后加文件的时候,只需要加一行,不用改格式。
6.4.2 布尔值
用 true 或 false,不加引号:
enabled: true,
strip: false,
6.4.3 条件属性与默认值
有些属性只在特定条件下生效。比如 target 属性可以针对不同架构设置不同值:
cc_library {
name: "libfoo",
srcs: ["common.c"],
target: {
android_arm: {
srcs: ["arm_specific.c"],
},
android_arm64: {
srcs: ["arm64_specific.c"],
},
},
}
这里 srcs 的默认值是 ["common.c"],但在 ARM 架构下会额外加入 arm_specific.c。这种写法在移植代码时特别有用。我在做某个芯片平台的适配时,就是靠这个属性搞定了多架构的源码管理。
6.4.4 依赖属性
模块之间的依赖通过 shared_libs、static_libs、header_libs 等属性声明:
shared_libs: ["libcutils", "libutils"],
static_libs: ["libtinyxml2"],
header_libs: ["libmyheaders"],
这里有个坑:shared_libs 是动态链接,static_libs 是静态链接。如果你把动态库写成了静态库,产物体积会变大,而且可能引入符号冲突。我刚开始就犯过这个错,一个 .so 文件硬生生大了 3 倍。
总结一下:Android.bp 的语法很简洁,但背后是 Blueprint + Soong 这套强大的构建体系。你只要记住:模块是目标,属性是参数,依赖是关系。把这三点搞清楚了,写 .bp 文件就跟填表一样简单。
好了,这一章的内容就到这里。下一章我们会深入讲解 cc_library 和 cc_binary 的详细用法,包括如何配置编译选项、链接脚本、以及处理多架构编译。到时候我会分享一些我在实际项目中踩过的坑,保证让你少走弯路。
公众号:蓝海资料掘金营,微信deep3321