26、构建系统扩展:自定义构建规则、构建插件开发、构建系统API
各位同学,今天我们来聊聊构建系统的扩展能力。说白了,AOSP 的构建系统不是死板的,它给你留了很多「后门」—— 嗯,专业点叫扩展点。我在做系统定制的时候,经常需要加一些奇奇怪怪的编译逻辑,比如给特定模块打上特殊标记,或者生成一些中间产物。这时候,光靠 Soong 内置的规则就不够用了。
为什么会这样?因为每个团队的硬件平台、软件需求都不一样。Google 不可能预判所有场景。所以,学会扩展构建系统,才是真正把 AOSP 玩转的关键一步。
26.1 自定义构建规则:从「用」到「造」
我们先从最基础的自定义构建规则说起。你平时用的 cc_binary、android_app 这些,其实都是 Soong 内置的模块类型。但如果你需要一种全新的模块类型呢?比如,我想把一堆图片打包成一个自定义的镜像文件。
这时候,你需要自己写一个 Blueprint 模块类型。我个人的习惯是,先在 build/soong/ 下新建一个目录,比如 my_custom_module,然后写一个 Go 文件。
核心思路:自定义构建规则 = 定义模块类型 + 实现构建逻辑 + 注册到 Soong
来看一个最简单的例子。假设我想定义一个 my_asset_bundle 模块,它把一堆文件打包成一个 zip:
// build/soong/my_custom_module/my_asset_bundle.go
package my_custom_module
import (
"android/soong/android"
"android/soong/cc"
)
func init() {
// 注册模块类型
android.RegisterModuleType("my_asset_bundle", MyAssetBundleFactory)
}
type MyAssetBundle struct {
android.ModuleBase
properties MyAssetBundleProperties
}
type MyAssetBundleProperties struct {
// 输入文件列表
Srcs []string
// 输出文件名
Stem *string
}
func MyAssetBundleFactory() android.Module {
m := &MyAssetBundle{}
m.AddProperties(&m.properties)
return m
}
func (m *MyAssetBundle) GenerateAndroidBuildActions(ctx android.ModuleContext) {
// 获取输入文件
inputs := android.PathsForModuleSrc(ctx, m.properties.Srcs)
// 确定输出文件
outputFile := android.PathForModuleOut(ctx, *m.properties.Stem + ".zip")
// 执行打包命令
ctx.Build(pctx, android.BuildParams{
Rule: android.NewRuleBuilder(pctx, "zip").
Command("zip -j $out $in").
Build(),
Inputs: inputs,
Outputs: []android.Path{outputFile},
})
}
嗯,这里要注意:GenerateAndroidBuildActions 是核心方法,所有构建动作都在这里定义。我曾经犯过一个错,忘记调用 AddProperties,结果模块属性一直读不到,排查了半天。
小技巧:写自定义规则时,先用 m my_asset_bundle 测试编译,看能不能正常解析。如果报错,多半是注册或者属性定义的问题。
26.2 构建插件开发:给 Soong 装上「外挂」
自定义规则适合「造新轮子」,但如果你只是想给现有模块加点行为呢?比如,每次编译某个模块时,自动生成一个版本文件。这时候,插件就派上用场了。
Soong 的插件机制,说白了就是 Hook。你可以在模块的特定生命周期插入自己的逻辑。我记得有一次,客户要求所有 APK 编译时都要嵌入一个时间戳,我要是改每个模块的 Android.bp,那得改到猴年马月去。用插件,一行代码都不用改。
插件开发分三步:
- 实现插件接口:Soong 提供了
PluginInterface,你需要实现它的方法。 - 注册插件:在
init()函数里调用android.RegisterPlugin。 - 在模块中启用:在 Android.bp 里加上
plugins: ["my_plugin"]。
来看一个实际例子。这个插件会在所有 cc_binary 编译完成后,打印一条日志:
// build/soong/my_plugin/plugin.go
package my_plugin
import (
"android/soong/android"
"fmt"
)
type MyPlugin struct {
android.PluginBase
}
func (p *MyPlugin) OnModuleBuilt(ctx android.PluginContext, module android.Module) {
// 只对 cc_binary 生效
if _, ok := module.(*cc.Module); ok {
fmt.Printf("[MyPlugin] Module %s built successfully!\n", module.Name())
}
}
func init() {
android.RegisterPlugin("my_plugin", func() android.Plugin {
return &MyPlugin{}
})
}
避坑指南:我曾经在插件里直接修改了模块的属性,结果导致增量编译缓存失效。记住,插件应该只读,不要改模块的状态。如果你非要改,请用 PluginContext.AddPostDeps 来添加依赖,而不是直接操作模块。
26.3 构建系统 API:Soong 给你留的「后门」
Soong 本身提供了一套 API,让你能跟构建系统深度交互。这些 API 分布在 android 包下,我挑几个常用的说说。
| API 名称 | 作用 | 使用场景 |
|---|---|---|
ModuleContext |
模块上下文,提供文件路径、构建动作等 | 自定义规则中获取输入输出 |
PluginContext |
插件上下文,提供模块信息、依赖关系 | 插件开发中遍历模块 |
android.PathForModuleSrc |
获取模块源文件路径 | 定位输入文件 |
android.PathForModuleOut |
获取模块输出目录路径 | 指定输出位置 |
android.NewRuleBuilder |
创建自定义构建规则 | 执行 shell 命令 |
android.RegisterModuleType |
注册新的模块类型 | 自定义构建规则 |
你想想看,这些 API 其实就是 Soong 的「骨架」。掌握了它们,你就能在构建系统里做任何事。我个人最常用的是 NewRuleBuilder,因为它能直接执行 shell 命令,灵活性极高。
举个例子,我想在编译时自动生成一个头文件,里面包含 Git 的 commit ID:
func (m *MyModule) GenerateBuildActions(ctx android.ModuleContext) {
outputFile := android.PathForModuleOut(ctx, "version.h")
ctx.Build(pctx, android.BuildParams{
Rule: android.NewRuleBuilder(pctx, "gen_version").
Command("echo '#define GIT_COMMIT \"$(git rev-parse HEAD)\"' > $out").
Build(),
Outputs: []android.Path{outputFile},
})
}
提示:API 的文档其实不多,最好的学习方式是看 Soong 源码。打开 build/soong/android/ 目录,里面全是宝藏。我当初就是靠读源码学会的。
26.4 知识体系总览
为了让你更直观地理解这三者的关系,我画了一张图。你可以看到,自定义规则、插件、API 是三个层次,从底层到上层,覆盖了构建系统扩展的方方面面。
这张图你看懂了吗?最底层是 API,它是所有扩展的基础。中间层是插件,它让你能「插入」到现有流程中。最上层是自定义规则,它让你能「创造」全新的构建逻辑。三者层层递进,缺一不可。
26.5 实战经验总结
最后,我分享几个实战中踩过的坑,希望能帮你少走弯路。
- 命名冲突:自定义模块类型名不要跟内置的重复,比如别叫
cc_binary。我建议加个前缀,比如my_。 - 增量编译:自定义规则里如果用了外部命令(比如
git),记得把命令的输出作为输入依赖,否则增量编译会失效。 - 调试困难:Soong 的日志默认不打印。你可以用
ctx.ModuleErrorf或者fmt.Printf输出到终端,但记得调试完删掉。 - 版本兼容:不同 AOSP 版本的 API 可能有变化。我遇到过在 Android 12 上能跑的插件,到 Android 13 就报错了。升级时一定要测试。
一句话总结:自定义规则让你「造轮子」,插件让你「改轮子」,API 让你「懂轮子」。三者结合,你就能随心所欲地控制 AOSP 的构建过程。
好了,这一章的内容就到这里。记住,构建系统扩展不是一蹴而就的,多读源码、多动手实验,你也能成为 Soong 专家。