Skip to content

Soong模块类型

追踪 Soong 模块类型如何注册、组合 decorator、创建 link 与 OS 变体,并由 C/C++、Java 和 APK 消费者生成不同产物。

基于android-17.0.0_r1
AndroidSoongccJavaAndroid App

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

go
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

go
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

go
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

go
// ... 前置逻辑已根据模块类型得到 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

go
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

go
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

go
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

go
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

go
// ... 先遍历直接依赖,汇总 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.go
  • build/soong/android/filegroup.go

源码文件:build/soong/genrule/genrule.go

相关函数/类型:RegisterGenruleBuildComponents

go
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

go
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 中的属性表,会丢失所有者、变体、生效时机和失败边界。