Soong模块类型
本文面向已经读过 Soong构建链 和 Android.bp属性链 的读者。你需要知道 Blueprint 会创建模块对象、mutator 会产生变体,但不必记住 Soong 的全部模块名称。
本文不做“模块类型字典”。它回答的问题是:cc_library 这个 BP 类型如何从注册字符串变成一个同时拥有编译器、链接器和安装器的 Go 对象,又如何因为 cc_library_shared、host_supported 或 compile_multilib 产生不同消费者可用的 variant;java_library 和 android_app 为什么沿着相同的 Soong 接口,却生成完全不同的输出。本文不展开每个属性的完整参考、APEX 内部打包或 Java 编译器实现。
读完后,读者应能从某个 RegisterModuleType 找到 factory,判断一个 factory 是通过 decorator 组合能力还是直接实现产物,定位依赖边建立的位置和最终 action 消费者,并能用 Soong 测试验证“模块类型”和“variant”不是同一层概念。
1. 类型边界
BP 中的 cc_library、java_library、android_app 是类型名;它们不是最终文件,也不是单一 variant。类型名先选择 Go factory,factory 创建模块和属性对象,mutator 再根据 host/device、架构、link 方式和产品条件复制或转换为具体 variant,最后由模块消费者生成 Ninja action。
“模块类型支持某属性”也不是 parser 的结论。Blueprint 只把属性解包到已注册的 Go struct;具体模块是否读取它、在哪个 variant 读取、是否把它传给 compiler 或 packager,由该模块自己的 mutator 和 action 代码决定。
2. 工厂注册
2.1 名称绑定
Soong 各包通过 RegistrationContext 注册类型。cc_library、cc_library_static 和 cc_library_shared 使用不同 factory,但它们可以共享 NewLibrary 的基础对象。注册发生在解析 BP 之前;解析器遇到字符串时只会查找这张表,不会根据后缀猜测模块。
源码文件:build/soong/cc/library.go
相关函数/类型:RegisterLibraryBuildComponents
func RegisterLibraryBuildComponents(ctx android.RegistrationContext) {
ctx.RegisterModuleType("cc_library_static", LibraryStaticFactory)
ctx.RegisterModuleType("cc_rustlibs_for_make", LibraryMakeRustlibsFactory)
ctx.RegisterModuleType("cc_library_shared", LibrarySharedFactory)
ctx.RegisterModuleType("cc_library", LibraryFactory)
ctx.RegisterModuleType("cc_library_host_static", LibraryHostStaticFactory)
ctx.RegisterModuleType("cc_library_host_shared", LibraryHostSharedFactory)
}android.Context.Register 会把这些工厂转交 Blueprint context。工厂注册成功只证明类型可实例化;它不证明该类型在当前产品、分区或架构下会产生 enabled variant。
2.2 基础组合
NewLibrary 返回两个对象:外层 Module 和 libraryDecorator。前者承载 Soong 通用模块能力,后者承载库的 compiler、linker、installer 以及静态/共享状态。这个返回形状解释了为什么 cc_library_static 和 cc_library_shared 能共享大部分实现,却在 factory 中改变构建策略。
源码文件:build/soong/cc/library.go
相关函数/类型:NewLibrary
func NewLibrary(hod android.HostOrDeviceSupported) (*Module, *libraryDecorator) {
module := newModule(hod, android.MultilibBoth)
library := &libraryDecorator{
MutatedProperties: LibraryMutatedProperties{
BuildShared: true,
BuildStatic: true,
},
baseCompiler: NewBaseCompiler(),
baseLinker: NewBaseLinker(module.sanitize),
baseInstaller: NewBaseInstaller("lib", "lib64", InstallInSystem),
sabi: module.sabi,
}
module.compiler = library
module.linker = library
module.installer = library
module.library = library
return module, library
}这里的 owner 分工很关键:Module 拥有 Soong 模块生命周期,decorator 拥有库特有的构建能力;后续 DepsMutator 和 GenerateAndroidBuildActions 会通过这些接口消费状态。不能把 libraryDecorator 当成独立 BP 模块。
2.3 类型差异
三个 factory 的差异很小,但会改变之后的变体集合:普通 cc_library 保留静态和共享能力,cc_library_static 调用 BuildOnlyStatic,cc_library_shared 调用 BuildOnlyShared。这比“名字中有 shared 就输出 so”更准确,因为最终还要经过 link transition 和模块 enabled 条件。
源码文件:build/soong/cc/library.go
相关函数/类型:LibraryFactory / LibraryStaticFactory / LibrarySharedFactory
func LibraryFactory() android.Module {
module, _ := NewLibrary(android.HostAndDeviceSupported)
module.sdkMemberTypes = []android.SdkMemberType{
sharedLibrarySdkMemberType,
staticLibrarySdkMemberType,
staticAndSharedLibrarySdkMemberType,
}
return module.Init()
}
func LibraryStaticFactory() android.Module {
module, library := NewLibrary(android.HostAndDeviceSupported)
library.BuildOnlyStatic()
return module.Init()
}
func LibrarySharedFactory() android.Module {
module, library := NewLibrary(android.HostAndDeviceSupported)
library.BuildOnlyShared()
return module.Init()
}3. 库变体
3.1 Link选择
库类型还有一层 link variation。cc_library 可以同时拥有 static 和 shared 变体;依赖者在 DepsMutator 中用 {Mutator: "link", Variation: "static"} 或 shared 请求目标。因而一个模块名在图中不是唯一节点,至少要带上 mutator 选择的 variant 才能判断实际输入。
真实 linkageTransitionMutator.split 并非无条件返回两个分支。prebuilt 为了能与同名 source module 对齐,可能始终创建 static/shared 后再禁用不用的一侧;普通 LinkableInterface 则读取 BuildStaticVariant、BuildSharedVariant 和 LLNDK 状态。没有可链接能力的模块返回空 variation。
源码文件:build/soong/cc/library.go
相关函数/类型:linkageTransitionMutator.split
// ... 前置逻辑已根据模块类型得到 library、isLLNDK 和 variations
buildStatic := library.BuildStaticVariant() && !isLLNDK
buildShared := library.BuildSharedVariant()
if buildStatic && buildShared {
variations = append([]string{"static", "shared"}, variations...)
return variations
} else if buildStatic {
variations = append([]string{"static"}, variations...)
} else if buildShared {
variations = append([]string{"shared"}, variations...)
}
if len(variations) > 0 {
return variations
}
return []string{""}Android 17 还支持 link variant on demand。Split 可以只立即创建第一个 variation,SplitOnDemand 在依赖真正请求第二个 variation 时再产生它;prebuilt、stubs 和 denylist 模块则强制完整拆分。因此调试时看到的 variant 数量还会受到构建 flag 与按需分析策略影响。
3.2 依赖请求
cc.Module.DepsMutator 把属性中的库名转换为带 dependency tag 和 variation 的边。静态库依赖明确请求 link=static;共享库依赖先剥离 stubs 版本后请求 link=shared。这一阶段只建立图边,不创建 .a 或 .so。
源码文件:build/soong/cc/cc.go
相关函数/类型:Module.DepsMutator
for _, lib := range deps.StaticLibs {
depTag := libraryDependencyTag{Kind: staticLibraryDependency}
actx.AddVariationDependencies([]blueprint.Variation{
{Mutator: "link", Variation: "static"},
}, depTag, lib)
}
for _, lib := range deps.SharedLibs {
depTag := libraryDependencyTag{Kind: sharedLibraryDependency}
name, version := StubsLibNameAndVersion(lib)
variations := []blueprint.Variation{
{Mutator: "link", Variation: "shared"},
}
AddSharedLibDependenciesWithVersions(
ctx, c, variations, depTag, name, version, false)
}如果 libfoo#29 被写进 shared_libs,版本不是装饰文本,而会参与 stubs variant 选择。只检查模块名会漏掉 API version 对依赖边的影响。
3.3 复用对象
cc_library 的 shared variant 可能依赖自己的 static variant,以复用已经生成的对象文件。源码中的 reuseStaticLibrary 只有在静态/共享 cflags、库列表和 system_shared_libs 满足相容条件时才添加 reuseObjTag;否则两个 variant 会各自编译。这是模块类型内部的优化条件,不应写成所有共享库都必然复用 .o。
4. 架构选择
4.1 OS与主机
NewLibrary(android.HostAndDeviceSupported) 只表示 factory 允许 host/device;host_supported: true 是 BP 属性,产品 target 和 archTransitionMutator 决定最终变体。Java library 也采用类似的 host/device 入口,但它的输出和 bootclasspath 消费者不同。
源码文件:build/soong/java/java.go
相关函数/类型:LibraryFactory
func LibraryFactory() android.Module {
module := &Library{}
module.addHostAndDeviceProperties()
module.AddProperties(&module.sourceProperties)
module.initModuleAndImport(module)
android.InitApexModule(module)
InitJavaModule(module, android.HostAndDeviceSupported)
return module
}Java factory 不创建 libraryDecorator,而是初始化 Java 专用 module/import 状态。它的 host_supported 变体编译到 host bootclasspath,device 变体则面对 Android bootclasspath;“host/device 支持”相同,不代表 flags 或依赖消费者相同。
4.2 Multilib
C/C++ 模块的 compile_multilib 会在 arch transition 中筛选 target。both、first、32、64 和 common 不是库类型,而是 variant 选择策略。一个 cc_library_shared 可能只有 arm64 shared variant,一个 cc_library 可能拥有 arm64 static、arm64 shared 以及 host 变体;必须结合产品 target 查看实际图。
5. Java与APK
5.1 Java库
java_library 的 factory 初始化 Java source properties、Apex module 和 Java module。GenerateAndroidBuildActions 消费 Java 依赖、源码、bootclasspath 和 dex/install 属性,不能套用 cc.Module 的 compiler/linker 解释。
5.2 Android应用
android_app 的 factory 注册在 build/soong/java/app.go,其 GenerateAndroidBuildActions 在 APK 生成后设置 AppInfoProvider、ApkCertInfoProvider 和 Proguard provider。provider 是后续安装、APEX 或打包消费者读取的接口,不是 APK 文本里的字段副本。
factory 先设置 dex 优化、instrument 和 installable 默认值,把 AAPT、app、override 和 Java source properties 都挂到同一个模块,再初始化为 device-only、common multilib 的 multi-target arch module。这里已经决定 app 与 Java library 的第一层差异:app 默认是可安装设备产物,而不是 host/device Java 库的同义类型。
源码文件:build/soong/java/app.go
相关函数/类型:AndroidAppFactory
func AndroidAppFactory() android.Module {
module := &AndroidApp{}
module.Module.dexProperties.Optimize.EnabledByDefault = true
module.Module.dexProperties.Optimize.ShrinkByDefault = true
module.Module.properties.Instrument = true
module.Module.properties.Installable = proptools.BoolPtr(true)
module.AddProperties(
&module.aaptProperties,
&module.appProperties,
&module.overridableAppProperties,
&module.Library.sourceProperties)
android.InitAndroidMultiTargetsArchModule(
module, android.DeviceSupported, android.MultilibCommon)
// ... defaults、override、APEX 与 load hook 初始化
return module
}factory 返回后,模块才进入统一的 variant 与 action 生命周期;下面的 GenerateAndroidBuildActions 不是 factory 的后续语句,而是 Soong 在对应 app variant 上的另一阶段调用。两者之间由 mutator 和 Blueprint Context 连接。
源码文件:build/soong/java/app.go
相关函数/类型:AndroidApp.GenerateAndroidBuildActions
func (a *AndroidApp) GenerateAndroidBuildActions(ctx android.ModuleContext) {
a.checkAppSdkVersions(ctx)
a.checkEmbedJnis(ctx)
a.generateAndroidBuildActions(ctx)
if ctx.Failed() {
return
}
// ... 生成 AppInfo 后发布 provider
android.SetProvider(ctx, AppInfoProvider, appInfo)
android.SetProvider(ctx, ApkCertInfoProvider, ApkCertInfo{
Certificate: appInfo.Certificate,
Name: appInfo.InstallApkName + ".apk",
})
}这里的失败时机很明确:checkAppSdkVersions 或 APK action 设置失败时,provider 不会以完整状态发布。下游读取 provider 的阶段必须晚于 app action;这也是模块类型不能只按最终文件名理解的原因。
6. 轻量模块
6.1 filegroup
filegroup 注册了 FileGroupFactory,其模块主要提供文件集合和标签查询,不承担 C/C++ 或 Java 编译。它适合作为 srcs、resource_dirs 或工具输入的命名 owner;消费者拿到的是路径集合,而不是编译产物。
它的 action 会解析 srcs 与 exclude_srcs,应用可选 path 前缀,把结果保存到 fg.srcs;同时汇总直接依赖的 CodegenInfoProvider。Srcs() 返回副本,避免消费者修改 filegroup 自己持有的路径切片。
源码文件:build/soong/android/filegroup.go
相关函数/类型:fileGroup.GenerateAndroidBuildActions
// ... 先遍历直接依赖,汇总 CodegenInfoProvider 到以下局部变量
srcs := PathsForModuleSrcExcludes(ctx,
fg.properties.Srcs.GetOrDefault(ctx, nil),
fg.properties.Exclude_srcs.GetOrDefault(ctx, nil))
path := fg.properties.Path.GetOrDefault(ctx, "")
if path != "" {
srcs = PathsWithModuleSrcSubDir(ctx, srcs, path)
}
fg.srcs = srcs
SetProvider(ctx, CodegenInfoProvider, CodegenInfo{
AconfigDeclarations: aconfigDeclarations,
Srcjars: srcjars,
ModeInfos: modeInfos,
})所以 filegroup 虽然不编译,也不是“纯文本别名”:它仍拥有路径解析结果和 codegen provider,并受 package boundary、exclude 和 tagged source 消费规则约束。
6.2 genrule
genrule 的 factory 创建命令型模块,GenerateAndroidBuildActions 把 srcs、tools、out 和 shell command 变成生成 action。它与 filegroup 的差别不在名字长短,而在是否拥有执行命令和 declared output。把任意生成文件都写成 filegroup 会让 Ninja 缺少 producer。
相关源码:
build/soong/genrule/genrule.gobuild/soong/android/filegroup.go
源码文件:build/soong/genrule/genrule.go
相关函数/类型:RegisterGenruleBuildComponents
func RegisterGenruleBuildComponents(ctx android.RegistrationContext) {
ctx.RegisterModuleType("genrule", GenRuleFactory)
}
// filegroup 仅注册路径集合模块
// build/soong/android/filegroup.go
ctx.RegisterModuleType("filegroup", FileGroupFactory)生成完成后,genrule 还要决定如何把输出暴露给消费者。输出不超过 6 个时直接把文件作为依赖;更多输出会创建一个 phony,避免每个消费者展开过多 Ninja 边。随后 SetOutputFiles 按默认 tag 和单文件相对路径 tag 注册输出。
源码文件:build/soong/genrule/genrule.go
相关函数/类型:Module.GenerateAndroidBuildActions
g.generateCommonBuildActions(ctx)
if len(g.outputFiles) <= 6 {
g.outputDeps = g.outputFiles
} else {
phonyFile := android.PathForModuleGen(ctx, "genrule-phony")
ctx.Build(pctx, android.BuildParams{
Rule: blueprint.Phony, Output: phonyFile, Inputs: g.outputFiles,
})
g.outputDeps = android.Paths{phonyFile}
}
g.setOutputFiles(ctx)因此 :generator 与 :generator{tag} 的消费者不仅依赖 command 成功,还依赖 producer 正确登记 output tag。命令执行成功但 out 或 tag 不匹配,仍然是模块图错误。
7. 故障定位
7.1 未知类型
unknown module type 应先查注册函数和对应包是否被链接进 soong_build,而不是先改 BP 属性。类型名拼写正确但仍未知,可能是构建工具版本、模块包注册或 bootstrap 产物不一致。
7.2 变体缺失
模块存在但依赖报 “missing variant” 时,检查 host_supported、device_supported、compile_multilib、link 请求、分区 image 和 stubs version。依赖者请求的是 variant,模块名本身不足以定位问题。
7.3 Provider缺失
provider 错误通常发生在消费者读取时机早于生产者 action,或生产者在失败分支提前 return。沿 SetProvider 的 producer 和 ModuleProvider 的 consumer 配对检查;不要仅查看 provider 结构体字段。
8. 测试验证
8.1 架构输入
android/arch_test.go 的 TestArchMutator 使用 BP fixture:默认模块、host_supported、device_supported: false、compile_multilib: "32" 和 "first"。测试运行后调用 ModuleVariantsForTests,把 enabled variant 与预期列表比较。它证明配置到 variation 的映射,不证明每个真实产品的 target 矩阵。
8.2 链接输入
cc/cc_test.go 的共享库/静态库测试检查具体 variant 的 Ninja rule 参数和输出路径;例如 TestSharedLibLinkingArgs 取得 android_arm64_armv8-a_shared 的 ld rule,再断言链接参数。这个断言覆盖 link consumer,不等于证明设备运行时加载成功。
8.3 应用边界
build/soong/java/app.go 的 app action 代码明确包含 SDK 检查、JNI 检查、APK 生成和 provider 发布;失败后提前 return。可以用 app fixture 观察 provider 与输出是否存在,但本文不把单元 fixture 外推成签名、安装和运行时成功。
9. 源码复述
拿一个 cc_library_shared 模块,先在 RegisterLibraryBuildComponents 找到 LibrarySharedFactory,再沿 NewLibrary 看 decorator 如何被装入 Module;随后查 linkage/arch mutator 产生的具体 variant,最后在 DepsMutator 和 GenerateAndroidBuildActions 分别定位依赖边与 action。若换成 android_app,入口改为 AndroidAppFactory,消费者变成 APK 与 provider,而不是 C/C++ linker。
模块类型的正确阅读顺序是“factory → 状态组合 → mutator → dependency consumer → output/provider”。只看 Android.bp 中的属性表,会丢失所有者、变体、生效时机和失败边界。
