Merge "Remove SdkVersion condition from image variants" into main
diff --git a/Android.bp b/Android.bp
index 9763622..434ee9f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -172,6 +172,9 @@
     name: "system-build.prop",
     stem: "build.prop",
     product_config: ":product_config",
+    footer_files: [
+        ":applied_backported_fixes",
+    ],
     // Currently, only microdroid, Ravenwood, and cf system image can refer to system-build.prop
     visibility: [
         "//build/make/target/product/generic",
@@ -234,3 +237,12 @@
     relative_install_path: "etc", // odm_dlkm/etc/build.prop
     visibility: ["//visibility:private"],
 }
+
+build_prop {
+    name: "ramdisk-build.prop",
+    stem: "build.prop",
+    ramdisk: true,
+    product_config: ":product_config",
+    relative_install_path: "etc/ramdisk", // ramdisk/system/etc/ramdisk/build.prop
+    visibility: ["//visibility:private"],
+}
diff --git a/aconfig/build_flags/build_flags.go b/aconfig/build_flags/build_flags.go
index e878b5a..94e1eb1 100644
--- a/aconfig/build_flags/build_flags.go
+++ b/aconfig/build_flags/build_flags.go
@@ -35,7 +35,7 @@
 type buildFlags struct {
 	android.ModuleBase
 
-	outputPath android.OutputPath
+	outputPath android.Path
 }
 
 func buildFlagsFactory() android.Module {
@@ -48,7 +48,7 @@
 	// Read the build_flags_<partition>.json file generated by soong
 	// 'release-config' command.
 	srcPath := android.PathForOutput(ctx, "release-config", fmt.Sprintf("build_flags_%s.json", m.PartitionTag(ctx.DeviceConfig())))
-	m.outputPath = android.PathForModuleOut(ctx, outJsonFileName).OutputPath
+	outputPath := android.PathForModuleOut(ctx, outJsonFileName)
 
 	// The 'release-config' command is called for every build, and generates the
 	// build_flags_<partition>.json file.
@@ -56,11 +56,12 @@
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   android.CpIfChanged,
 		Input:  srcPath,
-		Output: m.outputPath,
+		Output: outputPath,
 	})
 
 	installPath := android.PathForModuleInstall(ctx, "etc")
-	ctx.InstallFile(installPath, outJsonFileName, m.outputPath)
+	ctx.InstallFile(installPath, outJsonFileName, outputPath)
+	m.outputPath = outputPath
 }
 
 func (m *buildFlags) AndroidMkEntries() []android.AndroidMkEntries {
diff --git a/android/aconfig_providers.go b/android/aconfig_providers.go
index b902f8b..210a656 100644
--- a/android/aconfig_providers.go
+++ b/android/aconfig_providers.go
@@ -107,7 +107,7 @@
 	mergedAconfigFiles := make(map[string]Paths)
 	mergedModeInfos := make(map[string]ModeInfo)
 
-	ctx.VisitDirectDeps(func(module Module) {
+	ctx.VisitDirectDepsProxy(func(module ModuleProxy) {
 		if aconfig_dep, ok := OtherModuleProvider(ctx, module, CodegenInfoProvider); ok && len(aconfig_dep.ModeInfos) > 0 {
 			maps.Copy(mergedModeInfos, aconfig_dep.ModeInfos)
 		}
diff --git a/android/apex.go b/android/apex.go
index 3486350..9277ff3 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -870,28 +870,6 @@
 	return notInApex
 }
 
-// Tests whether a module named moduleName is directly included in the apexBundle where this
-// ApexContents is tagged.
-func (ac *ApexContents) DirectlyInApex(moduleName string) bool {
-	return ac.contents[moduleName] == directlyInApex
-}
-
-// Tests whether a module named moduleName is included in the apexBundle where this ApexContent is
-// tagged.
-func (ac *ApexContents) InApex(moduleName string) bool {
-	return ac.contents[moduleName] != notInApex
-}
-
-// Tests whether a module named moduleName is directly depended on by all APEXes in an ApexInfo.
-func DirectlyInAllApexes(apexInfo ApexInfo, moduleName string) bool {
-	for _, contents := range apexInfo.ApexContents {
-		if !contents.DirectlyInApex(moduleName) {
-			return false
-		}
-	}
-	return true
-}
-
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 //Below are routines for extra safety checks.
 //
@@ -918,8 +896,8 @@
 type DepNameToDepInfoMap map[string]ApexModuleDepInfo
 
 type ApexBundleDepsInfo struct {
-	flatListPath OutputPath
-	fullListPath OutputPath
+	flatListPath Path
+	fullListPath Path
 }
 
 type ApexBundleDepsInfoIntf interface {
@@ -956,13 +934,15 @@
 		fmt.Fprintf(&flatContent, "%s\n", toName)
 	}
 
-	d.fullListPath = PathForModuleOut(ctx, "depsinfo", "fulllist.txt").OutputPath
-	WriteFileRule(ctx, d.fullListPath, fullContent.String())
+	fullListPath := PathForModuleOut(ctx, "depsinfo", "fulllist.txt")
+	WriteFileRule(ctx, fullListPath, fullContent.String())
+	d.fullListPath = fullListPath
 
-	d.flatListPath = PathForModuleOut(ctx, "depsinfo", "flatlist.txt").OutputPath
-	WriteFileRule(ctx, d.flatListPath, flatContent.String())
+	flatListPath := PathForModuleOut(ctx, "depsinfo", "flatlist.txt")
+	WriteFileRule(ctx, flatListPath, flatContent.String())
+	d.flatListPath = flatListPath
 
-	ctx.Phony(fmt.Sprintf("%s-depsinfo", ctx.ModuleName()), d.fullListPath, d.flatListPath)
+	ctx.Phony(fmt.Sprintf("%s-depsinfo", ctx.ModuleName()), fullListPath, flatListPath)
 }
 
 // Function called while walking an APEX's payload dependencies.
diff --git a/android/base_module_context.go b/android/base_module_context.go
index 060fae5..1f89dea 100644
--- a/android/base_module_context.go
+++ b/android/base_module_context.go
@@ -113,7 +113,7 @@
 	// GetDirectDepWithTag returns the Module the direct dependency with the specified name, or nil if
 	// none exists.  It panics if the dependency does not have the specified tag.  It skips any
 	// dependencies that are not an android.Module.
-	GetDirectDepWithTag(name string, tag blueprint.DependencyTag) blueprint.Module
+	GetDirectDepWithTag(name string, tag blueprint.DependencyTag) Module
 
 	// GetDirectDep returns the Module and DependencyTag for the direct dependency with the specified
 	// name, or nil if none exists.  If there are multiple dependencies on the same module it returns
@@ -129,13 +129,14 @@
 	// function, it may be invalidated by future mutators.
 	VisitDirectDeps(visit func(Module))
 
-	// VisitDirectDeps calls visit for each direct dependency.  If there are multiple
+	// VisitDirectDepsProxy calls visit for each direct dependency.  If there are multiple
 	// direct dependencies on the same module visit will be called multiple times on that module
-	// and OtherModuleDependencyTag will return a different tag for each.
+	// and OtherModuleDependencyTag will return a different tag for each. It raises an error if any of the
+	// dependencies are disabled.
 	//
-	// The Module passed to the visit function should not be retained outside of the visit
+	// The ModuleProxy passed to the visit function should not be retained outside of the visit
 	// function, it may be invalidated by future mutators.
-	VisitDirectDepsAllowDisabled(visit func(Module))
+	VisitDirectDepsProxy(visit func(proxy ModuleProxy))
 
 	// VisitDirectDepsProxyAllowDisabled calls visit for each direct dependency.  If there are
 	// multiple direct dependencies on the same module visit will be called multiple times on
@@ -210,6 +211,12 @@
 	// data modified by the current mutator.
 	VisitAllModuleVariants(visit func(Module))
 
+	// VisitAllModuleVariantProxies calls visit for each variant of the current module.  Variants of a module are always
+	// visited in order by mutators and GenerateBuildActions, so the data created by the current mutator can be read
+	// from all variants if the current module is the last one. Otherwise, care must be taken to not access any
+	// data modified by the current mutator.
+	VisitAllModuleVariantProxies(visit func(proxy ModuleProxy))
+
 	// GetTagPath is supposed to be called in visit function passed in WalkDeps()
 	// and returns a top-down dependency tags path from a start module to current child module.
 	// It has one less entry than GetWalkPath() as it contains the dependency tags that
@@ -261,9 +268,11 @@
 func (b *baseModuleContext) OtherModuleName(m blueprint.Module) string {
 	return b.bp.OtherModuleName(getWrappedModule(m))
 }
-func (b *baseModuleContext) OtherModuleDir(m blueprint.Module) string { return b.bp.OtherModuleDir(m) }
+func (b *baseModuleContext) OtherModuleDir(m blueprint.Module) string {
+	return b.bp.OtherModuleDir(getWrappedModule(m))
+}
 func (b *baseModuleContext) OtherModuleErrorf(m blueprint.Module, fmt string, args ...interface{}) {
-	b.bp.OtherModuleErrorf(m, fmt, args...)
+	b.bp.OtherModuleErrorf(getWrappedModule(m), fmt, args...)
 }
 func (b *baseModuleContext) OtherModuleDependencyTag(m blueprint.Module) blueprint.DependencyTag {
 	return b.bp.OtherModuleDependencyTag(getWrappedModule(m))
@@ -298,8 +307,11 @@
 	b.bp.SetProvider(provider, value)
 }
 
-func (b *baseModuleContext) GetDirectDepWithTag(name string, tag blueprint.DependencyTag) blueprint.Module {
-	return b.bp.GetDirectDepWithTag(name, tag)
+func (b *baseModuleContext) GetDirectDepWithTag(name string, tag blueprint.DependencyTag) Module {
+	if module := b.bp.GetDirectDepWithTag(name, tag); module != nil {
+		return module.(Module)
+	}
+	return nil
 }
 
 func (b *baseModuleContext) blueprintBaseModuleContext() blueprint.BaseModuleContext {
@@ -376,7 +388,7 @@
 		return &aModule
 	}
 
-	if !OtherModuleProviderOrDefault(b, module, CommonPropertiesProviderKey).Enabled {
+	if !OtherModuleProviderOrDefault(b, module, CommonModuleInfoKey).Enabled {
 		if t, ok := tag.(AllowDisabledModuleDependency); !ok || !t.AllowDisabledModuleDependencyProxy(b, aModule) {
 			if b.Config().AllowMissingDependencies() {
 				b.AddMissingDependencies([]string{b.OtherModuleName(aModule)})
@@ -464,18 +476,16 @@
 	})
 }
 
-func (b *baseModuleContext) VisitDirectDepsAllowDisabled(visit func(Module)) {
-	b.bp.VisitDirectDeps(func(module blueprint.Module) {
-		visit(module.(Module))
+func (b *baseModuleContext) VisitDirectDepsProxy(visit func(ModuleProxy)) {
+	b.bp.VisitDirectDepsProxy(func(module blueprint.ModuleProxy) {
+		if aModule := b.validateAndroidModuleProxy(module, b.bp.OtherModuleDependencyTag(module), b.strictVisitDeps); aModule != nil {
+			visit(*aModule)
+		}
 	})
 }
 
 func (b *baseModuleContext) VisitDirectDepsProxyAllowDisabled(visit func(proxy ModuleProxy)) {
-	b.bp.VisitDirectDepsProxy(func(module blueprint.ModuleProxy) {
-		visit(ModuleProxy{
-			module: module,
-		})
-	})
+	b.bp.VisitDirectDepsProxy(visitProxyAdaptor(visit))
 }
 
 func (b *baseModuleContext) VisitDirectDepsWithTag(tag blueprint.DependencyTag, visit func(Module)) {
@@ -590,6 +600,10 @@
 	})
 }
 
+func (b *baseModuleContext) VisitAllModuleVariantProxies(visit func(ModuleProxy)) {
+	b.bp.VisitAllModuleVariantProxies(visitProxyAdaptor(visit))
+}
+
 func (b *baseModuleContext) PrimaryModule() Module {
 	return b.bp.PrimaryModule().(Module)
 }
diff --git a/android/build_prop.go b/android/build_prop.go
index 8389470..2f71bc0 100644
--- a/android/build_prop.go
+++ b/android/build_prop.go
@@ -15,6 +15,8 @@
 package android
 
 import (
+	"fmt"
+
 	"github.com/google/blueprint/proptools"
 )
 
@@ -55,7 +57,7 @@
 
 	properties buildPropProperties
 
-	outputFilePath OutputPath
+	outputFilePath Path
 	installPath    InstallPath
 }
 
@@ -115,33 +117,22 @@
 		return "vendor_dlkm"
 	} else if p.InstallInOdmDlkm() {
 		return "odm_dlkm"
+	} else if p.InstallInRamdisk() {
+		// From this hardcoding in make:
+		// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/sysprop.mk;l=311;drc=274435657e4682e5cee3fffd11fb301ab32a828d
+		return "bootimage"
 	}
 	return "system"
 }
 
-var validPartitions = []string{
-	"system",
-	"system_ext",
-	"product",
-	"odm",
-	"vendor",
-	"system_dlkm",
-	"vendor_dlkm",
-	"odm_dlkm",
-}
-
 func (p *buildPropModule) GenerateAndroidBuildActions(ctx ModuleContext) {
 	if !p.SocSpecific() && p.properties.Android_info != nil {
 		ctx.ModuleErrorf("Android_info cannot be set if build.prop is not installed in vendor partition")
 	}
 
-	p.outputFilePath = PathForModuleOut(ctx, "build.prop").OutputPath
+	outputFilePath := PathForModuleOut(ctx, "build.prop")
 
 	partition := p.partition(ctx.DeviceConfig())
-	if !InList(partition, validPartitions) {
-		ctx.PropertyErrorf("partition", "unsupported partition %q: only %q are supported", partition, validPartitions)
-		return
-	}
 
 	rule := NewRuleBuilder(pctx, ctx)
 
@@ -168,7 +159,7 @@
 	cmd.FlagWithInput("--product-config=", PathForModuleSrc(ctx, proptools.String(p.properties.Product_config)))
 	cmd.FlagWithArg("--partition=", partition)
 	cmd.FlagForEachInput("--prop-files=", p.propFiles(ctx))
-	cmd.FlagWithOutput("--out=", p.outputFilePath)
+	cmd.FlagWithOutput("--out=", outputFilePath)
 
 	postProcessCmd := rule.Command().BuiltTool("post_process_props")
 	if ctx.DeviceConfig().BuildBrokenDupSysprop() {
@@ -181,17 +172,35 @@
 		// still need to pass an empty string to kernel-version-file-for-uffd-gc
 		postProcessCmd.FlagWithArg("--kernel-version-file-for-uffd-gc ", `""`)
 	}
-	postProcessCmd.Text(p.outputFilePath.String())
+	postProcessCmd.Text(outputFilePath.String())
 	postProcessCmd.Flags(p.properties.Block_list)
 
-	rule.Command().Text("echo").Text(proptools.NinjaAndShellEscape("# end of file")).FlagWithArg(">> ", p.outputFilePath.String())
+	for _, footer := range p.properties.Footer_files {
+		path := PathForModuleSrc(ctx, footer)
+		rule.appendText(outputFilePath, "####################################")
+		rule.appendTextf(outputFilePath, "# Adding footer from %v", footer)
+		rule.appendTextf(outputFilePath, "# with path %v", path)
+		rule.appendText(outputFilePath, "####################################")
+		rule.Command().Text("cat").FlagWithInput("", path).FlagWithArg(">> ", outputFilePath.String())
+	}
+
+	rule.appendText(outputFilePath, "# end of file")
 
 	rule.Build(ctx.ModuleName(), "generating build.prop")
 
 	p.installPath = PathForModuleInstall(ctx, proptools.String(p.properties.Relative_install_path))
-	ctx.InstallFile(p.installPath, p.stem(), p.outputFilePath)
+	ctx.InstallFile(p.installPath, p.stem(), outputFilePath)
 
-	ctx.SetOutputFiles(Paths{p.outputFilePath}, "")
+	ctx.SetOutputFiles(Paths{outputFilePath}, "")
+	p.outputFilePath = outputFilePath
+}
+
+func (r *RuleBuilder) appendText(path ModuleOutPath, text string) {
+	r.Command().Text("echo").Text(proptools.NinjaAndShellEscape(text)).FlagWithArg(">> ", path.String())
+}
+
+func (r *RuleBuilder) appendTextf(path ModuleOutPath, format string, a ...any) {
+	r.appendText(path, fmt.Sprintf(format, a...))
 }
 
 func (p *buildPropModule) AndroidMkEntries() []AndroidMkEntries {
diff --git a/android/csuite_config.go b/android/csuite_config.go
index 20bd035..26ad6e1 100644
--- a/android/csuite_config.go
+++ b/android/csuite_config.go
@@ -30,11 +30,11 @@
 type CSuiteConfig struct {
 	ModuleBase
 	properties     csuiteConfigProperties
-	OutputFilePath OutputPath
+	OutputFilePath Path
 }
 
 func (me *CSuiteConfig) GenerateAndroidBuildActions(ctx ModuleContext) {
-	me.OutputFilePath = PathForModuleOut(ctx, me.BaseModuleName()).OutputPath
+	me.OutputFilePath = PathForModuleOut(ctx, me.BaseModuleName())
 }
 
 func (me *CSuiteConfig) AndroidMkEntries() []AndroidMkEntries {
diff --git a/android/module.go b/android/module.go
index 3b30c11..ce995ad 100644
--- a/android/module.go
+++ b/android/module.go
@@ -1643,25 +1643,27 @@
 func (m *ModuleBase) generateModuleTarget(ctx *moduleContext) {
 	var allInstalledFiles InstallPaths
 	var allCheckbuildTargets Paths
-	ctx.VisitAllModuleVariants(func(module Module) {
-		a := module.base()
+	ctx.VisitAllModuleVariantProxies(func(module ModuleProxy) {
 		var checkbuildTarget Path
 		var uncheckedModule bool
-		if a == m {
+		var skipAndroidMkProcessing bool
+		if ctx.EqualModules(m.module, module) {
 			allInstalledFiles = append(allInstalledFiles, ctx.installFiles...)
 			checkbuildTarget = ctx.checkbuildTarget
 			uncheckedModule = ctx.uncheckedModule
+			skipAndroidMkProcessing = shouldSkipAndroidMkProcessing(ctx, m)
 		} else {
 			info := OtherModuleProviderOrDefault(ctx, module, InstallFilesProvider)
 			allInstalledFiles = append(allInstalledFiles, info.InstallFiles...)
 			checkbuildTarget = info.CheckbuildTarget
 			uncheckedModule = info.UncheckedModule
+			skipAndroidMkProcessing = OtherModuleProviderOrDefault(ctx, module, CommonModuleInfoKey).SkipAndroidMkProcessing
 		}
 		// A module's -checkbuild phony targets should
 		// not be created if the module is not exported to make.
 		// Those could depend on the build target and fail to compile
 		// for the current build target.
-		if (!ctx.Config().KatiEnabled() || !shouldSkipAndroidMkProcessing(ctx, a)) && !uncheckedModule && checkbuildTarget != nil {
+		if (!ctx.Config().KatiEnabled() || !skipAndroidMkProcessing) && !uncheckedModule && checkbuildTarget != nil {
 			allCheckbuildTargets = append(allCheckbuildTargets, checkbuildTarget)
 		}
 	})
@@ -1834,6 +1836,12 @@
 
 var InstallFilesProvider = blueprint.NewProvider[InstallFilesInfo]()
 
+type SourceFilesInfo struct {
+	Srcs Paths
+}
+
+var SourceFilesInfoKey = blueprint.NewProvider[SourceFilesInfo]()
+
 type FinalModuleBuildTargetsInfo struct {
 	// Used by buildTargetSingleton to create checkbuild and per-directory build targets
 	// Only set on the final variant of each module
@@ -1844,15 +1852,16 @@
 
 var FinalModuleBuildTargetsProvider = blueprint.NewProvider[FinalModuleBuildTargetsInfo]()
 
-type CommonPropertiesProviderData struct {
+type CommonModuleInfo struct {
 	Enabled bool
 	// Whether the module has been replaced by a prebuilt
 	ReplacedByPrebuilt bool
 	// The Target of artifacts that this module variant is responsible for creating.
-	CompileTarget Target
+	CompileTarget           Target
+	SkipAndroidMkProcessing bool
 }
 
-var CommonPropertiesProviderKey = blueprint.NewProvider[CommonPropertiesProviderData]()
+var CommonModuleInfoKey = blueprint.NewProvider[CommonModuleInfo]()
 
 type PrebuiltModuleProviderData struct {
 	// Empty for now
@@ -1934,9 +1943,7 @@
 
 	if m.Enabled(ctx) {
 		// ensure all direct android.Module deps are enabled
-		ctx.VisitDirectDeps(func(m Module) {
-			ctx.validateAndroidModule(m, ctx.OtherModuleDependencyTag(m), ctx.baseModuleContext.strictVisitDeps)
-		})
+		ctx.VisitDirectDepsProxy(func(m ModuleProxy) {})
 
 		if m.Device() {
 			// Handle any init.rc and vintf fragment files requested by the module.  All files installed by this
@@ -2038,6 +2045,10 @@
 		ctx.GetMissingDependencies()
 	}
 
+	if sourceFileProducer, ok := m.module.(SourceFileProducer); ok {
+		SetProvider(ctx, SourceFilesInfoKey, SourceFilesInfo{Srcs: sourceFileProducer.Srcs()})
+	}
+
 	if ctx.IsFinalModule(m.module) {
 		m.generateModuleTarget(ctx)
 		if ctx.Failed() {
@@ -2112,16 +2123,17 @@
 	}
 	buildComplianceMetadataProvider(ctx, m)
 
-	commonData := CommonPropertiesProviderData{
-		ReplacedByPrebuilt: m.commonProperties.ReplacedByPrebuilt,
-		CompileTarget:      m.commonProperties.CompileTarget,
+	commonData := CommonModuleInfo{
+		ReplacedByPrebuilt:      m.commonProperties.ReplacedByPrebuilt,
+		CompileTarget:           m.commonProperties.CompileTarget,
+		SkipAndroidMkProcessing: shouldSkipAndroidMkProcessing(ctx, m),
 	}
 	if m.commonProperties.ForcedDisabled {
 		commonData.Enabled = false
 	} else {
 		commonData.Enabled = m.commonProperties.Enabled.GetOrDefault(m.ConfigurableEvaluator(ctx), !m.Os().DefaultDisabled)
 	}
-	SetProvider(ctx, CommonPropertiesProviderKey, commonData)
+	SetProvider(ctx, CommonModuleInfoKey, commonData)
 	if p, ok := m.module.(PrebuiltInterface); ok && p.Prebuilt() != nil {
 		SetProvider(ctx, PrebuiltModuleProviderKey, PrebuiltModuleProviderData{})
 	}
@@ -2130,7 +2142,7 @@
 			HostToolPath: h.HostToolPath()})
 	}
 
-	if p, ok := m.module.(AndroidMkProviderInfoProducer); ok && !shouldSkipAndroidMkProcessing(ctx, m) {
+	if p, ok := m.module.(AndroidMkProviderInfoProducer); ok && !commonData.SkipAndroidMkProcessing {
 		SetProvider(ctx, AndroidMkInfoProvider, p.PrepareAndroidMKProviderInfo(ctx.Config()))
 	}
 }
@@ -2634,7 +2646,7 @@
 
 // OutputFilesForModule returns the output file paths with the given tag. On error, including if the
 // module produced zero paths, it reports errors to the ctx and returns nil.
-func OutputFilesForModule(ctx PathContext, module blueprint.Module, tag string) Paths {
+func OutputFilesForModule(ctx PathContext, module Module, tag string) Paths {
 	paths, err := outputFilesForModule(ctx, module, tag)
 	if err != nil {
 		reportPathError(ctx, err)
@@ -2645,7 +2657,7 @@
 
 // OutputFileForModule returns the output file paths with the given tag.  On error, including if the
 // module produced zero or multiple paths, it reports errors to the ctx and returns nil.
-func OutputFileForModule(ctx PathContext, module blueprint.Module, tag string) Path {
+func OutputFileForModule(ctx PathContext, module Module, tag string) Path {
 	paths, err := outputFilesForModule(ctx, module, tag)
 	if err != nil {
 		reportPathError(ctx, err)
@@ -2678,20 +2690,34 @@
 	return paths[0]
 }
 
-func outputFilesForModule(ctx PathContext, module blueprint.Module, tag string) (Paths, error) {
+type OutputFilesProviderModuleContext interface {
+	OtherModuleProviderContext
+	Module() Module
+	GetOutputFiles() OutputFilesInfo
+	EqualModules(m1, m2 Module) bool
+}
+
+func outputFilesForModule(ctx PathContext, module Module, tag string) (Paths, error) {
 	outputFilesFromProvider, err := outputFilesForModuleFromProvider(ctx, module, tag)
 	if outputFilesFromProvider != nil || err != OutputFilesProviderNotSet {
 		return outputFilesFromProvider, err
 	}
-	if sourceFileProducer, ok := module.(SourceFileProducer); ok {
-		if tag != "" {
-			return nil, fmt.Errorf("module %q is a SourceFileProducer, which does not support tag %q", pathContextName(ctx, module), tag)
+
+	if octx, ok := ctx.(OutputFilesProviderModuleContext); ok {
+		if octx.EqualModules(octx.Module(), module) {
+			if sourceFileProducer, ok := module.(SourceFileProducer); ok {
+				return sourceFileProducer.Srcs(), nil
+			}
+		} else if sourceFiles, ok := OtherModuleProvider(octx, module, SourceFilesInfoKey); ok {
+			if tag != "" {
+				return nil, fmt.Errorf("module %q is a SourceFileProducer, which does not support tag %q", pathContextName(ctx, module), tag)
+			}
+			paths := sourceFiles.Srcs
+			return paths, nil
 		}
-		paths := sourceFileProducer.Srcs()
-		return paths, nil
-	} else {
-		return nil, fmt.Errorf("module %q is not a SourceFileProducer or having valid output file for tag %q", pathContextName(ctx, module), tag)
 	}
+
+	return nil, fmt.Errorf("module %q is not a SourceFileProducer or having valid output file for tag %q", pathContextName(ctx, module), tag)
 }
 
 // This method uses OutputFilesProvider for output files
@@ -2700,26 +2726,19 @@
 // from outputFiles property of module base, to avoid both setting and
 // reading OutputFilesProvider before GenerateBuildActions is finished.
 // If a module doesn't have the OutputFilesProvider, nil is returned.
-func outputFilesForModuleFromProvider(ctx PathContext, module blueprint.Module, tag string) (Paths, error) {
+func outputFilesForModuleFromProvider(ctx PathContext, module Module, tag string) (Paths, error) {
 	var outputFiles OutputFilesInfo
 	fromProperty := false
 
-	type OutputFilesProviderModuleContext interface {
-		OtherModuleProviderContext
-		Module() Module
-		GetOutputFiles() OutputFilesInfo
-	}
-
 	if mctx, isMctx := ctx.(OutputFilesProviderModuleContext); isMctx {
-		if mctx.Module() != module {
+		if !mctx.EqualModules(mctx.Module(), module) {
 			outputFiles, _ = OtherModuleProvider(mctx, module, OutputFilesProvider)
 		} else {
 			outputFiles = mctx.GetOutputFiles()
 			fromProperty = true
 		}
 	} else if cta, isCta := ctx.(*singletonContextAdaptor); isCta {
-		providerData, _ := cta.otherModuleProvider(module, OutputFilesProvider)
-		outputFiles, _ = providerData.(OutputFilesInfo)
+		outputFiles, _ = OtherModuleProvider(cta, module, OutputFilesProvider)
 	} else {
 		return nil, fmt.Errorf("unsupported context %q in method outputFilesForModuleFromProvider", reflect.TypeOf(ctx))
 	}
diff --git a/android/module_context.go b/android/module_context.go
index 41cb0cc..2014907 100644
--- a/android/module_context.go
+++ b/android/module_context.go
@@ -16,13 +16,13 @@
 
 import (
 	"fmt"
-	"github.com/google/blueprint/depset"
 	"path"
 	"path/filepath"
 	"slices"
 	"strings"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/depset"
 	"github.com/google/blueprint/proptools"
 )
 
@@ -439,9 +439,11 @@
 	return missingDeps
 }
 
-func (m *moduleContext) GetDirectDepWithTag(name string, tag blueprint.DependencyTag) blueprint.Module {
-	module, _ := m.getDirectDepInternal(name, tag)
-	return module
+func (m *moduleContext) GetDirectDepWithTag(name string, tag blueprint.DependencyTag) Module {
+	if module, _ := m.getDirectDepInternal(name, tag); module != nil {
+		return module.(Module)
+	}
+	return nil
 }
 
 func (m *moduleContext) ModuleSubDir() string {
diff --git a/android/module_test.go b/android/module_test.go
index d76d9b3..d5bf941 100644
--- a/android/module_test.go
+++ b/android/module_test.go
@@ -998,6 +998,10 @@
 	return OutputFilesInfo{}
 }
 
+func (p *pathContextAddMissingDependenciesWrapper) EqualModules(m1, m2 Module) bool {
+	return m1 == m2
+}
+
 func TestOutputFileForModule(t *testing.T) {
 	testcases := []struct {
 		name        string
diff --git a/android/neverallow.go b/android/neverallow.go
index 6176a99..326150b 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -338,6 +338,7 @@
 			"prebuilt_bt_firmware",
 			"prebuilt_tvservice",
 			"prebuilt_optee",
+			"prebuilt_tvconfig",
 		).
 		DefinedInBpFile().
 		Because("module type not allowed to be defined in bp file")
diff --git a/android/packaging.go b/android/packaging.go
index 635922c..dcd8844 100644
--- a/android/packaging.go
+++ b/android/packaging.go
@@ -410,9 +410,9 @@
 	return true
 }
 
-// highPriorityDepTag provides default implementation of HighPriorityPackagingItem interface.
 type highPriorityDepTag struct {
-	blueprint.DependencyTag
+	blueprint.BaseDependencyTag
+	PackagingItemAlwaysDepTag
 }
 
 // See PackageModule.AddDeps
@@ -433,7 +433,7 @@
 		}
 		depTagToUse := depTag
 		if highPriority {
-			depTagToUse = highPriorityDepTag{depTag}
+			depTagToUse = highPriorityDepTag{}
 		}
 
 		ctx.AddFarVariationDependencies(targetVariation, depTagToUse, dep)
@@ -480,7 +480,7 @@
 		return false
 	}
 
-	ctx.VisitDirectDeps(func(child Module) {
+	ctx.VisitDirectDepsProxy(func(child ModuleProxy) {
 		depTag := ctx.OtherModuleDependencyTag(child)
 		if pi, ok := depTag.(PackagingItem); !ok || !pi.IsPackagingItem() {
 			return
diff --git a/android/path_properties_test.go b/android/path_properties_test.go
index 07b4869..6f44f28 100644
--- a/android/path_properties_test.go
+++ b/android/path_properties_test.go
@@ -64,7 +64,7 @@
 	if p.props.Foo != "" {
 		// Make sure there is only one dependency on a module listed in a property present in multiple property structs
 		m := SrcIsModule(p.props.Foo)
-		if GetModuleFromPathDep(ctx, m, "") == nil {
+		if GetModuleProxyFromPathDep(ctx, m, "") == nil {
 			ctx.ModuleErrorf("GetDirectDepWithTag failed")
 		}
 	}
diff --git a/android/paths.go b/android/paths.go
index 9cb872d..7ab1f22 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -91,6 +91,8 @@
 	EarlyModulePathContext
 	OtherModuleProviderContext
 	VisitDirectDeps(visit func(Module))
+	VisitDirectDepsProxy(visit func(ModuleProxy))
+	VisitDirectDepsProxyWithTag(tag blueprint.DependencyTag, visit func(ModuleProxy))
 	OtherModuleDependencyTag(m blueprint.Module) blueprint.DependencyTag
 	HasMutatorFinished(mutatorName string) bool
 }
@@ -598,7 +600,7 @@
 
 	for _, path := range paths {
 		if m, t := SrcIsModuleWithTag(path); m != "" {
-			module := GetModuleFromPathDep(ctx, m, t)
+			module := GetModuleProxyFromPathDep(ctx, m, t)
 			if module == nil {
 				ctx.ModuleErrorf(`missing dependency on %q, is the property annotated with android:"path"?`, m)
 				continue
@@ -611,7 +613,7 @@
 			if !ok {
 				panic(fmt.Errorf("%s is not an OtherModuleProviderContext", ctx))
 			}
-			if dirProvider, ok := OtherModuleProvider(mctx, module, DirProvider); ok {
+			if dirProvider, ok := OtherModuleProvider(mctx, *module, DirProvider); ok {
 				ret = append(ret, dirProvider.Dirs...)
 			} else {
 				ReportPathErrorf(ctx, "module %q does not implement DirProvider", module)
@@ -669,14 +671,15 @@
 // If the dependency is not found, a missingErrorDependency is returned.
 // If the module dependency is not a SourceFileProducer or OutputFileProducer, appropriate errors will be returned.
 func getPathsFromModuleDep(ctx ModuleWithDepsPathContext, path, moduleName, tag string) (Paths, error) {
-	module := GetModuleFromPathDep(ctx, moduleName, tag)
+	module := GetModuleProxyFromPathDep(ctx, moduleName, tag)
 	if module == nil {
 		return nil, missingDependencyError{[]string{moduleName}}
 	}
-	if aModule, ok := module.(Module); ok && !aModule.Enabled(ctx) {
+	if !OtherModuleProviderOrDefault(ctx, *module, CommonModuleInfoKey).Enabled {
 		return nil, missingDependencyError{[]string{moduleName}}
 	}
-	outputFiles, err := outputFilesForModule(ctx, module, tag)
+
+	outputFiles, err := outputFilesForModule(ctx, *module, tag)
 	if outputFiles != nil && err == nil {
 		return outputFiles, nil
 	} else {
@@ -684,7 +687,7 @@
 	}
 }
 
-// GetModuleFromPathDep will return the module that was added as a dependency automatically for
+// GetModuleProxyFromPathDep will return the module that was added as a dependency automatically for
 // properties tagged with `android:"path"` or manually using ExtractSourceDeps or
 // ExtractSourcesDeps.
 //
@@ -694,6 +697,27 @@
 //
 // If tag is "" then the returned module will be the dependency that was added for ":moduleName".
 // Otherwise, it is the dependency that was added for ":moduleName{tag}".
+func GetModuleProxyFromPathDep(ctx ModuleWithDepsPathContext, moduleName, tag string) *ModuleProxy {
+	var found *ModuleProxy
+	// The sourceOrOutputDepTag uniquely identifies the module dependency as it contains both the
+	// module name and the tag. Dependencies added automatically for properties tagged with
+	// `android:"path"` are deduped so are guaranteed to be unique. It is possible for duplicate
+	// dependencies to be added manually using ExtractSourcesDeps or ExtractSourceDeps but even then
+	// it will always be the case that the dependencies will be identical, i.e. the same tag and same
+	// moduleName referring to the same dependency module.
+	//
+	// It does not matter whether the moduleName is a fully qualified name or if the module
+	// dependency is a prebuilt module. All that matters is the same information is supplied to
+	// create the tag here as was supplied to create the tag when the dependency was added so that
+	// this finds the matching dependency module.
+	expectedTag := sourceOrOutputDepTag(moduleName, tag)
+	ctx.VisitDirectDepsProxyWithTag(expectedTag, func(module ModuleProxy) {
+		found = &module
+	})
+	return found
+}
+
+// Deprecated: use GetModuleProxyFromPathDep
 func GetModuleFromPathDep(ctx ModuleWithDepsPathContext, moduleName, tag string) blueprint.Module {
 	var found blueprint.Module
 	// The sourceOrOutputDepTag uniquely identifies the module dependency as it contains both the
@@ -2589,3 +2613,19 @@
 	}
 	return false
 }
+
+// ToRelativeSourcePath converts absolute source path to the path relative to the source root.
+// This throws an error if the input path is outside of the source root and cannot be converted
+// to the relative path.
+// This should be rarely used given that the source path is relative in Soong.
+func ToRelativeSourcePath(ctx PathContext, path string) string {
+	ret := path
+	if filepath.IsAbs(path) {
+		relPath, err := filepath.Rel(absSrcDir, path)
+		if err != nil || strings.HasPrefix(relPath, "..") {
+			ReportPathErrorf(ctx, "%s is outside of the source root", path)
+		}
+		ret = relPath
+	}
+	return ret
+}
diff --git a/android/prebuilt.go b/android/prebuilt.go
index 51e72af..0ac67b3 100644
--- a/android/prebuilt.go
+++ b/android/prebuilt.go
@@ -381,7 +381,7 @@
 // the right module. This function is only safe to call after all TransitionMutators
 // have run, e.g. in GenerateAndroidBuildActions.
 func PrebuiltGetPreferred(ctx BaseModuleContext, module Module) Module {
-	if !OtherModuleProviderOrDefault(ctx, module, CommonPropertiesProviderKey).ReplacedByPrebuilt {
+	if !OtherModuleProviderOrDefault(ctx, module, CommonModuleInfoKey).ReplacedByPrebuilt {
 		return module
 	}
 	if _, ok := OtherModuleProvider(ctx, module, PrebuiltModuleProviderKey); ok {
diff --git a/android/product_config.go b/android/product_config.go
index ce3acc9..850f003 100644
--- a/android/product_config.go
+++ b/android/product_config.go
@@ -32,7 +32,7 @@
 		ctx.ModuleErrorf("There can only be one product_config module in build/soong")
 		return
 	}
-	outputFilePath := PathForModuleOut(ctx, p.Name()+".json").OutputPath
+	outputFilePath := PathForModuleOut(ctx, p.Name()+".json")
 
 	// DeviceProduct can be null so calling ctx.Config().DeviceProduct() may cause null dereference
 	targetProduct := proptools.String(ctx.Config().config.productVariables.DeviceProduct)
diff --git a/android/vintf_data.go b/android/vintf_data.go
index 7823397..401f4d2 100644
--- a/android/vintf_data.go
+++ b/android/vintf_data.go
@@ -49,7 +49,7 @@
 	properties vintfDataProperties
 
 	installDirPath InstallPath
-	outputFilePath OutputPath
+	outputFilePath Path
 	noAction       bool
 }
 
@@ -148,7 +148,7 @@
 	builder.Build("assemble_vintf", "Process vintf data "+gensrc.String())
 
 	m.installDirPath = PathForModuleInstall(ctx, "etc", "vintf")
-	m.outputFilePath = gensrc.OutputPath
+	m.outputFilePath = gensrc
 
 	installFileName := "manifest.xml"
 	if filename := proptools.String(m.properties.Filename); filename != "" {
diff --git a/android/vintf_fragment.go b/android/vintf_fragment.go
index 42eaaf0..a3343fd 100644
--- a/android/vintf_fragment.go
+++ b/android/vintf_fragment.go
@@ -25,7 +25,7 @@
 	properties vintfFragmentProperties
 
 	installDirPath InstallPath
-	outputFilePath OutputPath
+	outputFilePath Path
 }
 
 func init() {
@@ -64,7 +64,7 @@
 	builder.Build("assemble_vintf", "Process vintf fragment "+processedVintfFragment.String())
 
 	m.installDirPath = PathForModuleInstall(ctx, "etc", "vintf", "manifest")
-	m.outputFilePath = processedVintfFragment.OutputPath
+	m.outputFilePath = processedVintfFragment
 
 	ctx.InstallFile(m.installDirPath, processedVintfFragment.Base(), processedVintfFragment)
 }
diff --git a/apex/apex.go b/apex/apex.go
index 04b5a07..dc24df3 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -511,7 +511,7 @@
 
 	// Text file having the list of individual files that are included in this APEX. Used for
 	// debugging purpose.
-	installedFilesFile android.WritablePath
+	installedFilesFile android.Path
 
 	// List of module names that this APEX is including (to be shown via *-deps-info target).
 	// Used for debugging purpose.
@@ -2071,8 +2071,7 @@
 			af := apexFileForNativeLibrary(ctx, ch, vctx.handleSpecialLibs)
 			af.transitiveDep = true
 
-			abInfo, _ := android.ModuleProvider(ctx, android.ApexBundleInfoProvider)
-			if !abInfo.Contents.DirectlyInApex(depName) && (ch.IsStubs() || ch.HasStubsVariants()) {
+			if ch.IsStubs() || ch.HasStubsVariants() {
 				// If the dependency is a stubs lib, don't include it in this APEX,
 				// but make sure that the lib is installed on the device.
 				// In case no APEX is having the lib, the lib is installed to the system
@@ -2576,7 +2575,10 @@
 		return
 	}
 
-	abInfo, _ := android.ModuleProvider(ctx, android.ApexBundleInfoProvider)
+	librariesDirectlyInApex := make(map[string]bool)
+	ctx.VisitDirectDepsProxyWithTag(sharedLibTag, func(dep android.ModuleProxy) {
+		librariesDirectlyInApex[ctx.OtherModuleName(dep)] = true
+	})
 
 	a.WalkPayloadDeps(ctx, func(ctx android.BaseModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
 		if ccm, ok := to.(*cc.Module); ok {
@@ -2602,7 +2604,7 @@
 				return false
 			}
 
-			isStubLibraryFromOtherApex := ccm.HasStubsVariants() && !abInfo.Contents.DirectlyInApex(toName)
+			isStubLibraryFromOtherApex := ccm.HasStubsVariants() && !librariesDirectlyInApex[toName]
 			if isStubLibraryFromOtherApex && !externalDep {
 				ctx.ModuleErrorf("%q required by %q is a native library providing stub. "+
 					"It shouldn't be included in this APEX via static linking. Dependency path: %s", to.String(), fromName, ctx.GetPathString(false))
@@ -2635,7 +2637,7 @@
 
 // checkClasspathFragments enforces that all classpath fragments in deps generate classpaths.proto config.
 func (a *apexBundle) checkClasspathFragments(ctx android.ModuleContext) {
-	ctx.VisitDirectDeps(func(module android.Module) {
+	ctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 		if tag := ctx.OtherModuleDependencyTag(module); tag == bcpfTag || tag == sscpfTag {
 			info, _ := android.OtherModuleProvider(ctx, module, java.ClasspathFragmentProtoContentInfoProvider)
 			if !info.ClasspathFragmentProtoGenerated {
@@ -2742,12 +2744,12 @@
 
 // checkStaticExecutable ensures that executables in an APEX are not static.
 func (a *apexBundle) checkStaticExecutables(ctx android.ModuleContext) {
-	ctx.VisitDirectDeps(func(module android.Module) {
+	ctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 		if ctx.OtherModuleDependencyTag(module) != executableTag {
 			return
 		}
 
-		if l, ok := module.(cc.LinkableInterface); ok && l.StaticExecutable() {
+		if android.OtherModuleProviderOrDefault(ctx, module, cc.LinkableInfoKey).StaticExecutable {
 			apex := a.ApexVariationName()
 			exec := ctx.OtherModuleName(module)
 			if isStaticExecutableAllowed(apex, exec) {
diff --git a/apex/apex_singleton.go b/apex/apex_singleton.go
index 00dd446..d46104e 100644
--- a/apex/apex_singleton.go
+++ b/apex/apex_singleton.go
@@ -64,12 +64,14 @@
 			if grep -v -h '^#' ${allowed_deps_list} | sort -u -f| diff -B -u - ${new_allowed_deps}; then
 			   touch ${out};
 			else
-				echo -e "\n******************************";
+				echo;
+				echo "******************************";
 				echo "ERROR: go/apex-allowed-deps-error contains more information";
 				echo "******************************";
 				echo "Detected changes to allowed dependencies in updatable modules.";
 				echo "To fix and update packages/modules/common/build/allowed_deps.txt, please run:";
-				echo -e "$$ (croot && packages/modules/common/build/update-apex-allowed-deps.sh)\n";
+				echo "$$ (croot && packages/modules/common/build/update-apex-allowed-deps.sh)";
+				echo;
 				echo "When submitting the generated CL, you must include the following information";
 				echo "in the commit message if you are adding a new dependency:";
 				echo "Apex-Size-Increase: Expected binary size increase for affected APEXes (or the size of the .jar / .so file of the new library)";
@@ -78,7 +80,8 @@
 				echo "Test-Info: What’s the testing strategy for the new dependency? Does it have its own tests, and are you adding integration tests? How/when are the tests run?";
 				echo "You do not need OWNERS approval to submit the change, but mainline-modularization@";
 				echo "will periodically review additions and may require changes.";
-				echo -e "******************************\n";
+				echo "******************************";
+				echo;
 				exit 1;
 			fi;
 		`,
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 17cea5e..d0494d6 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -929,7 +929,7 @@
 		cc_library {
 			name: "mylib",
 			srcs: ["mylib.cpp"],
-			shared_libs: ["mylib2", "mylib3", "my_prebuilt_platform_lib", "my_prebuilt_platform_stub_only_lib"],
+			shared_libs: ["mylib2", "mylib3#impl", "my_prebuilt_platform_lib", "my_prebuilt_platform_stub_only_lib"],
 			system_shared_libs: [],
 			stl: "none",
 			apex_available: [ "myapex" ],
@@ -1025,7 +1025,7 @@
 	// ... and not linking to the non-stub (impl) variant of mylib2
 	ensureNotContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared/mylib2.so")
 
-	// Ensure that mylib is linking with the non-stub (impl) of mylib3 (because mylib3 is in the same apex)
+	// Ensure that mylib is linking with the non-stub (impl) of mylib3 (because the dependency is added with mylib3#impl)
 	ensureContains(t, mylibLdFlags, "mylib3/android_arm64_armv8-a_shared_apex10000/mylib3.so")
 	// .. and not linking to the stubs variant of mylib3
 	ensureNotContains(t, mylibLdFlags, "mylib3/android_arm64_armv8-a_shared_12/mylib3.so")
@@ -1201,7 +1201,7 @@
 		cc_library {
 			name: "mylib",
 			srcs: ["mylib.cpp"],
-			shared_libs: ["mylib2", "mylib3"],
+			shared_libs: ["mylib2", "mylib3#impl"],
 			system_shared_libs: [],
 			stl: "none",
 			apex_available: [ "myapex" ],
@@ -1264,7 +1264,7 @@
 	// ... and not linking to the non-stub (impl) variant of mylib2
 	ensureNotContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared/mylib2.so")
 
-	// Ensure that mylib is linking with the non-stub (impl) of mylib3 (because mylib3 is in the same apex)
+	// Ensure that mylib is linking with the non-stub (impl) of mylib3 (because the dependency is added with mylib3#impl)
 	ensureContains(t, mylibLdFlags, "mylib3/android_arm64_armv8-a_shared_apex29/mylib3.so")
 	// .. and not linking to the stubs variant of mylib3
 	ensureNotContains(t, mylibLdFlags, "mylib3/android_arm64_armv8-a_shared_29/mylib3.so")
@@ -1797,8 +1797,8 @@
 		cc_library {
 			name: "mylib",
 			srcs: ["mylib.cpp"],
-			system_shared_libs: ["libc", "libm"],
-			shared_libs: ["libdl#27"],
+			system_shared_libs: ["libc"],
+			shared_libs: ["libdl#27", "libm#impl"],
 			stl: "none",
 			apex_available: [ "myapex" ],
 		}
@@ -2962,8 +2962,7 @@
 			private_key: "testkey.pem",
 		}
 
-		// mylib in myapex will link to mylib2#current
-		// mylib in otherapex will link to mylib2(non-stub) in otherapex as well
+		// mylib will link to mylib2#current
 		cc_library {
 			name: "mylib",
 			srcs: ["mylib.cpp"],
@@ -2997,7 +2996,7 @@
 		ensureContains(t, libFlags, "android_arm64_armv8-a_"+to_variant+"/"+to+".so")
 	}
 	expectLink("mylib", "shared_apex29", "mylib2", "shared_current")
-	expectLink("mylib", "shared_apex30", "mylib2", "shared_apex30")
+	expectLink("mylib", "shared_apex30", "mylib2", "shared_current")
 }
 
 func TestApexMinSdkVersion_WorksWithSdkCodename(t *testing.T) {
diff --git a/apex/builder.go b/apex/builder.go
index 305d509..d0acc8d 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -196,7 +196,7 @@
 		Command: `diff --unchanged-group-format='' \` +
 			`--changed-group-format='%<' \` +
 			`${image_content_file} ${allowed_files_file} || (` +
-			`echo -e "New unexpected files were added to ${apex_module_name}." ` +
+			`echo "New unexpected files were added to ${apex_module_name}." ` +
 			` "To fix the build run following command:" && ` +
 			`echo "system/apex/tools/update_allowed_list.sh ${allowed_files_file} ${image_content_file}" && ` +
 			`exit 1); touch ${out}`,
@@ -388,7 +388,7 @@
 // file for this APEX which is either from /systme/sepolicy/apex/<apexname>-file_contexts or from
 // the file_contexts property of this APEX. This is to make sure that the manifest file is correctly
 // labeled as system_file or vendor_apex_metadata_file.
-func (a *apexBundle) buildFileContexts(ctx android.ModuleContext) android.OutputPath {
+func (a *apexBundle) buildFileContexts(ctx android.ModuleContext) android.Path {
 	var fileContexts android.Path
 	var fileContextsDir string
 	isFileContextsModule := false
@@ -397,8 +397,10 @@
 	} else {
 		if m, t := android.SrcIsModuleWithTag(*a.properties.File_contexts); m != "" {
 			isFileContextsModule = true
-			otherModule := android.GetModuleFromPathDep(ctx, m, t)
-			fileContextsDir = ctx.OtherModuleDir(otherModule)
+			otherModule := android.GetModuleProxyFromPathDep(ctx, m, t)
+			if otherModule != nil {
+				fileContextsDir = ctx.OtherModuleDir(*otherModule)
+			}
 		}
 		fileContexts = android.PathForModuleSrc(ctx, *a.properties.File_contexts)
 	}
@@ -441,13 +443,13 @@
 	}
 
 	rule.Build("file_contexts."+a.Name(), "Generate file_contexts")
-	return output.OutputPath
+	return output
 }
 
 // buildInstalledFilesFile creates a build rule for the installed-files.txt file where the list of
 // files included in this APEX is shown. The text file is dist'ed so that people can see what's
 // included in the APEX without actually downloading and extracting it.
-func (a *apexBundle) buildInstalledFilesFile(ctx android.ModuleContext, builtApex android.Path, imageDir android.Path) android.OutputPath {
+func (a *apexBundle) buildInstalledFilesFile(ctx android.ModuleContext, builtApex android.Path, imageDir android.Path) android.Path {
 	output := android.PathForModuleOut(ctx, "installed-files.txt")
 	rule := android.NewRuleBuilder(pctx, ctx)
 	rule.Command().
@@ -457,12 +459,12 @@
 		Text(" | sort -nr > ").
 		Output(output)
 	rule.Build("installed-files."+a.Name(), "Installed files")
-	return output.OutputPath
+	return output
 }
 
 // buildBundleConfig creates a build rule for the bundle config file that will control the bundle
 // creation process.
-func (a *apexBundle) buildBundleConfig(ctx android.ModuleContext) android.OutputPath {
+func (a *apexBundle) buildBundleConfig(ctx android.ModuleContext) android.Path {
 	output := android.PathForModuleOut(ctx, "bundle_config.json")
 
 	type ApkConfig struct {
@@ -507,7 +509,7 @@
 
 	android.WriteFileRule(ctx, output, string(j))
 
-	return output.OutputPath
+	return output
 }
 
 func markManifestTestOnly(ctx android.ModuleContext, androidManifestFile android.Path) android.Path {
@@ -920,17 +922,17 @@
 		args["outCommaList"] = signedOutputFile.String()
 	}
 	var validations android.Paths
-	validations = append(validations, runApexLinkerconfigValidation(ctx, unsignedOutputFile.OutputPath, imageDir.OutputPath))
+	validations = append(validations, runApexLinkerconfigValidation(ctx, unsignedOutputFile, imageDir))
 	// TODO(b/279688635) deapexer supports [ext4]
 	if !a.testApex && suffix == imageApexSuffix && ext4 == a.payloadFsType {
-		validations = append(validations, runApexSepolicyTests(ctx, unsignedOutputFile.OutputPath))
+		validations = append(validations, runApexSepolicyTests(ctx, unsignedOutputFile))
 	}
 	if !a.testApex && len(a.properties.Unwanted_transitive_deps) > 0 {
 		validations = append(validations,
-			runApexElfCheckerUnwanted(ctx, unsignedOutputFile.OutputPath, a.properties.Unwanted_transitive_deps))
+			runApexElfCheckerUnwanted(ctx, unsignedOutputFile, a.properties.Unwanted_transitive_deps))
 	}
 	if !a.testApex && android.InList(a.payloadFsType, []fsType{ext4, erofs}) {
-		validations = append(validations, runApexHostVerifier(ctx, a, unsignedOutputFile.OutputPath))
+		validations = append(validations, runApexHostVerifier(ctx, a, unsignedOutputFile))
 	}
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        rule,
@@ -1133,7 +1135,7 @@
 	a.lintReports = java.BuildModuleLintReportZips(ctx, depSets, validations)
 }
 
-func (a *apexBundle) buildCannedFsConfig(ctx android.ModuleContext) android.OutputPath {
+func (a *apexBundle) buildCannedFsConfig(ctx android.ModuleContext) android.Path {
 	var readOnlyPaths = []string{"apex_manifest.json", "apex_manifest.pb"}
 	var executablePaths []string // this also includes dirs
 	var appSetDirs []string
@@ -1197,10 +1199,10 @@
 	cmd.Text(")").FlagWithOutput("> ", cannedFsConfig)
 	builder.Build("generateFsConfig", fmt.Sprintf("Generating canned fs config for %s", a.BaseModuleName()))
 
-	return cannedFsConfig.OutputPath
+	return cannedFsConfig
 }
 
-func runApexLinkerconfigValidation(ctx android.ModuleContext, apexFile android.OutputPath, imageDir android.OutputPath) android.Path {
+func runApexLinkerconfigValidation(ctx android.ModuleContext, apexFile android.Path, imageDir android.Path) android.Path {
 	timestamp := android.PathForModuleOut(ctx, "apex_linkerconfig_validation.timestamp")
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   apexLinkerconfigValidationRule,
@@ -1217,7 +1219,7 @@
 //
 // $ deapexer list -Z {apex_file} > {file_contexts}
 // $ apex_sepolicy_tests -f {file_contexts}
-func runApexSepolicyTests(ctx android.ModuleContext, apexFile android.OutputPath) android.Path {
+func runApexSepolicyTests(ctx android.ModuleContext, apexFile android.Path) android.Path {
 	timestamp := android.PathForModuleOut(ctx, "sepolicy_tests.timestamp")
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   apexSepolicyTestsRule,
@@ -1227,7 +1229,7 @@
 	return timestamp
 }
 
-func runApexElfCheckerUnwanted(ctx android.ModuleContext, apexFile android.OutputPath, unwanted []string) android.Path {
+func runApexElfCheckerUnwanted(ctx android.ModuleContext, apexFile android.Path, unwanted []string) android.Path {
 	timestamp := android.PathForModuleOut(ctx, "apex_elf_unwanted.timestamp")
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   apexElfCheckerUnwantedRule,
@@ -1241,7 +1243,7 @@
 	return timestamp
 }
 
-func runApexHostVerifier(ctx android.ModuleContext, a *apexBundle, apexFile android.OutputPath) android.Path {
+func runApexHostVerifier(ctx android.ModuleContext, a *apexBundle, apexFile android.Path) android.Path {
 	timestamp := android.PathForModuleOut(ctx, "host_apex_verifier.timestamp")
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   apexHostVerifierRule,
diff --git a/apex/prebuilt.go b/apex/prebuilt.go
index 9cd5688..acf3b91 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -386,7 +386,7 @@
 
 	inputApex android.Path
 
-	provenanceMetaDataFile android.OutputPath
+	provenanceMetaDataFile android.Path
 }
 
 type ApexFileProperties struct {
@@ -697,7 +697,7 @@
 	ctx.SetOutputFiles(android.Paths{p.outputApex}, "")
 }
 
-func (p *Prebuilt) ProvenanceMetaDataFile() android.OutputPath {
+func (p *Prebuilt) ProvenanceMetaDataFile() android.Path {
 	return p.provenanceMetaDataFile
 }
 
diff --git a/cc/cc.go b/cc/cc.go
index 08a93cb9..76d01a5 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -53,6 +53,13 @@
 
 var CcObjectInfoProvider = blueprint.NewProvider[CcObjectInfo]()
 
+type LinkableInfo struct {
+	// StaticExecutable returns true if this is a binary module with "static_executable: true".
+	StaticExecutable bool
+}
+
+var LinkableInfoKey = blueprint.NewProvider[LinkableInfo]()
+
 func init() {
 	RegisterCCBuildComponents(android.InitRegistrationContext)
 
@@ -2119,6 +2126,10 @@
 		android.SetProvider(ctx, CcObjectInfoProvider, ccObjectInfo)
 	}
 
+	android.SetProvider(ctx, LinkableInfoKey, LinkableInfo{
+		StaticExecutable: c.StaticExecutable(),
+	})
+
 	c.setOutputFiles(ctx)
 
 	if c.makeVarsInfo != nil {
@@ -3353,8 +3364,6 @@
 }
 
 func ShouldUseStubForApex(ctx android.ModuleContext, dep android.Module) bool {
-	depName := ctx.OtherModuleName(dep)
-
 	inVendorOrProduct := false
 	bootstrap := false
 	if linkable, ok := ctx.Module().(LinkableInterface); !ok {
@@ -3384,9 +3393,8 @@
 
 		useStubs = isNotInPlatform && !bootstrap
 	} else {
-		// If building for APEX, use stubs when the parent is in any APEX that
-		// the child is not in.
-		useStubs = !android.DirectlyInAllApexes(apexInfo, depName)
+		// If building for APEX, always use stubs (can be bypassed by depending on <dep>#impl)
+		useStubs = true
 	}
 
 	return useStubs
diff --git a/cc/cmake_snapshot.go b/cc/cmake_snapshot.go
index 790a865..f553f27 100644
--- a/cc/cmake_snapshot.go
+++ b/cc/cmake_snapshot.go
@@ -484,7 +484,7 @@
 	// Packaging all make files into the zip file
 	makefilesRspFile := android.PathForModuleObj(ctx, ctx.ModuleName()+"_makefiles.rsp")
 	zipCmd.
-		FlagWithArg("-C ", android.PathForModuleGen(ctx).OutputPath.String()).
+		FlagWithArg("-C ", android.PathForModuleGen(ctx).String()).
 		FlagWithRspFileInputList("-r ", makefilesRspFile, makefilesList)
 
 	// Packaging all prebuilts into the zip file
diff --git a/cc/config/global.go b/cc/config/global.go
index 36690d6..6984ea4 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -290,8 +290,6 @@
 		"-Wno-error=deprecated",          // in external/googletest/googletest
 		// Disabling until the warning is fixed in libc++abi header files b/366180429
 		"-Wno-deprecated-dynamic-exception-spec",
-		// New warnings to be fixed after clang-r475365
-		"-Wno-error=enum-constexpr-conversion", // http://b/243964282
 		// New warnings to be fixed after clang-r522817
 		"-Wno-error=invalid-offsetof",
 		"-Wno-error=thread-safety-reference-return",
diff --git a/cc/sanitize.go b/cc/sanitize.go
index 124dda4..f0d7343 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -1813,7 +1813,7 @@
 type sanitizerLibrariesTxtModule struct {
 	android.ModuleBase
 
-	outputFile android.OutputPath
+	outputFile android.Path
 }
 
 var _ etc.PrebuiltEtcModule = (*sanitizerLibrariesTxtModule)(nil)
@@ -1896,13 +1896,14 @@
 func (txt *sanitizerLibrariesTxtModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	filename := txt.Name()
 
-	txt.outputFile = android.PathForModuleOut(ctx, filename).OutputPath
-	android.WriteFileRule(ctx, txt.outputFile, txt.getSanitizerLibs(ctx))
+	outputFile := android.PathForModuleOut(ctx, filename)
+	android.WriteFileRule(ctx, outputFile, txt.getSanitizerLibs(ctx))
 
 	installPath := android.PathForModuleInstall(ctx, "etc")
-	ctx.InstallFile(installPath, filename, txt.outputFile)
+	ctx.InstallFile(installPath, filename, outputFile)
 
-	ctx.SetOutputFiles(android.Paths{txt.outputFile}, "")
+	ctx.SetOutputFiles(android.Paths{outputFile}, "")
+	txt.outputFile = outputFile
 }
 
 func (txt *sanitizerLibrariesTxtModule) PrepareAndroidMKProviderInfo(config android.Config) *android.AndroidMkProviderInfo {
diff --git a/cc/tidy.go b/cc/tidy.go
index 5cbf8f0..6481b95 100644
--- a/cc/tidy.go
+++ b/cc/tidy.go
@@ -220,7 +220,7 @@
 
 	// (1) Collect all obj/tidy files into OS-specific groups.
 	ctx.VisitAllModuleVariantProxies(module, func(variant android.ModuleProxy) {
-		osName := android.OtherModuleProviderOrDefault(ctx, variant, android.CommonPropertiesProviderKey).CompileTarget.Os.Name
+		osName := android.OtherModuleProviderOrDefault(ctx, variant, android.CommonModuleInfoKey).CompileTarget.Os.Name
 		info := android.OtherModuleProviderOrDefault(ctx, variant, CcObjectInfoProvider)
 		addToOSGroup(osName, info.objFiles, allObjFileGroups, subsetObjFileGroups)
 		addToOSGroup(osName, info.tidyFiles, allTidyFileGroups, subsetTidyFileGroups)
diff --git a/cmd/find_input_delta/find_input_delta/main.go b/cmd/find_input_delta/find_input_delta/main.go
index 6b657ea..a864584 100644
--- a/cmd/find_input_delta/find_input_delta/main.go
+++ b/cmd/find_input_delta/find_input_delta/main.go
@@ -85,4 +85,11 @@
 	if err = file_list.Format(os.Stdout, template); err != nil {
 		panic(err)
 	}
+
+	metrics_file := os.Getenv("SOONG_METRICS_AGGREGATION_FILE")
+	if metrics_file != "" {
+		if err = file_list.SendMetrics(metrics_file); err != nil {
+			panic(err)
+		}
+	}
 }
diff --git a/cmd/find_input_delta/find_input_delta_lib/Android.bp b/cmd/find_input_delta/find_input_delta_lib/Android.bp
index 95bdba8..ef9c65b 100644
--- a/cmd/find_input_delta/find_input_delta_lib/Android.bp
+++ b/cmd/find_input_delta/find_input_delta_lib/Android.bp
@@ -25,6 +25,7 @@
         "golang-protobuf-runtime-protoimpl",
         "soong-cmd-find_input_delta-proto",
         "soong-cmd-find_input_delta-proto_internal",
+        "android-archive-zip",
         "blueprint-pathtools",
     ],
     srcs: [
diff --git a/cmd/find_input_delta/find_input_delta_lib/file_list.go b/cmd/find_input_delta/find_input_delta_lib/file_list.go
index 23337ad..01242a0 100644
--- a/cmd/find_input_delta/find_input_delta_lib/file_list.go
+++ b/cmd/find_input_delta/find_input_delta_lib/file_list.go
@@ -15,10 +15,15 @@
 package find_input_delta_lib
 
 import (
+	"fmt"
 	"io"
+	"os"
+	"path/filepath"
+	"slices"
 	"text/template"
 
 	fid_exp "android/soong/cmd/find_input_delta/find_input_delta_proto"
+	"google.golang.org/protobuf/encoding/protowire"
 	"google.golang.org/protobuf/proto"
 )
 
@@ -47,28 +52,148 @@
 
 	// The modified files
 	Changes []FileList
+
+	// Map of file_extension:counts
+	ExtCountMap map[string]*FileCounts
+
+	// Total number of added/changed/deleted files.
+	TotalDelta uint32
 }
 
-func (fl FileList) Marshal() (*fid_exp.FileList, error) {
+// The maximum number of files that will be recorded by name.
+var MaxFilesRecorded uint32 = 50
+
+type FileCounts struct {
+	Additions uint32
+	Deletions uint32
+	Changes   uint32
+}
+
+func FileListFactory(name string) *FileList {
+	return &FileList{
+		Name:        name,
+		ExtCountMap: make(map[string]*FileCounts),
+	}
+}
+
+func (fl *FileList) addFile(name string) {
+	fl.Additions = append(fl.Additions, name)
+	fl.TotalDelta += 1
+	ext := filepath.Ext(name)
+	if _, ok := fl.ExtCountMap[ext]; !ok {
+		fl.ExtCountMap[ext] = &FileCounts{}
+	}
+	fl.ExtCountMap[ext].Additions += 1
+}
+
+func (fl *FileList) deleteFile(name string) {
+	fl.Deletions = append(fl.Deletions, name)
+	fl.TotalDelta += 1
+	ext := filepath.Ext(name)
+	if _, ok := fl.ExtCountMap[ext]; !ok {
+		fl.ExtCountMap[ext] = &FileCounts{}
+	}
+	fl.ExtCountMap[ext].Deletions += 1
+}
+
+func (fl *FileList) changeFile(name string, ch *FileList) {
+	fl.Changes = append(fl.Changes, *ch)
+	fl.TotalDelta += 1
+	ext := filepath.Ext(name)
+	if _, ok := fl.ExtCountMap[ext]; !ok {
+		fl.ExtCountMap[ext] = &FileCounts{}
+	}
+	fl.ExtCountMap[ext].Changes += 1
+}
+
+func (fl FileList) ToProto() (*fid_exp.FileList, error) {
+	var count uint32
+	return fl.toProto(&count)
+}
+
+func (fl FileList) toProto(count *uint32) (*fid_exp.FileList, error) {
 	ret := &fid_exp.FileList{
 		Name: proto.String(fl.Name),
 	}
-	if len(fl.Additions) > 0 {
-		ret.Additions = fl.Additions
+	for _, a := range fl.Additions {
+		if *count >= MaxFilesRecorded {
+			break
+		}
+		ret.Additions = append(ret.Additions, a)
+		*count += 1
 	}
 	for _, ch := range fl.Changes {
-		change, err := ch.Marshal()
-		if err != nil {
-			return nil, err
+		if *count >= MaxFilesRecorded {
+			break
+		} else {
+			// Pre-increment to limit what the call adds.
+			*count += 1
+			change, err := ch.toProto(count)
+			if err != nil {
+				return nil, err
+			}
+			ret.Changes = append(ret.Changes, change)
 		}
-		ret.Changes = append(ret.Changes, change)
 	}
-	if len(fl.Deletions) > 0 {
-		ret.Deletions = fl.Deletions
+	for _, d := range fl.Deletions {
+		if *count >= MaxFilesRecorded {
+			break
+		}
+		ret.Deletions = append(ret.Deletions, d)
+	}
+	ret.TotalDelta = proto.Uint32(*count)
+	exts := []string{}
+	for k := range fl.ExtCountMap {
+		exts = append(exts, k)
+	}
+	slices.Sort(exts)
+	for _, k := range exts {
+		v := fl.ExtCountMap[k]
+		ret.Counts = append(ret.Counts, &fid_exp.FileCount{
+			Extension:     proto.String(k),
+			Additions:     proto.Uint32(v.Additions),
+			Deletions:     proto.Uint32(v.Deletions),
+			Modifications: proto.Uint32(v.Changes),
+		})
 	}
 	return ret, nil
 }
 
+func (fl FileList) SendMetrics(path string) error {
+	if path == "" {
+		return fmt.Errorf("No path given")
+	}
+	message, err := fl.ToProto()
+	if err != nil {
+		return err
+	}
+
+	// Marshal the message wrapped in SoongCombinedMetrics.
+	data := protowire.AppendVarint(
+		[]byte{},
+		protowire.EncodeTag(
+			protowire.Number(fid_exp.FieldNumbers_FIELD_NUMBERS_FILE_LIST),
+			protowire.BytesType))
+	size := uint64(proto.Size(message))
+	data = protowire.AppendVarint(data, size)
+	data, err = proto.MarshalOptions{UseCachedSize: true}.MarshalAppend(data, message)
+	if err != nil {
+		return err
+	}
+
+	out, err := os.Create(path)
+	if err != nil {
+		return err
+	}
+	defer func() {
+		if err := out.Close(); err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to close %s: %v\n", path, err)
+		}
+	}()
+	_, err = out.Write(data)
+	return err
+}
+
 func (fl FileList) Format(wr io.Writer, format string) error {
 	tmpl, err := template.New("filelist").Parse(format)
 	if err != nil {
diff --git a/cmd/find_input_delta/find_input_delta_lib/fs.go b/cmd/find_input_delta/find_input_delta_lib/fs.go
index 4a83ed7..09a8aa6 100644
--- a/cmd/find_input_delta/find_input_delta_lib/fs.go
+++ b/cmd/find_input_delta/find_input_delta_lib/fs.go
@@ -15,7 +15,6 @@
 package find_input_delta_lib
 
 import (
-	"io"
 	"io/fs"
 	"os"
 )
@@ -30,14 +29,6 @@
 	ReadFile(path string) ([]byte, error)
 }
 
-type file interface {
-	io.Closer
-	io.Reader
-	io.ReaderAt
-	io.Seeker
-	Stat() (os.FileInfo, error)
-}
-
 // osFS implements fileSystem using the local disk.
 type osFS struct{}
 
diff --git a/cmd/find_input_delta/find_input_delta_lib/internal_state.go b/cmd/find_input_delta/find_input_delta_lib/internal_state.go
index b2ff8c7..f0242b7 100644
--- a/cmd/find_input_delta/find_input_delta_lib/internal_state.go
+++ b/cmd/find_input_delta/find_input_delta_lib/internal_state.go
@@ -18,9 +18,11 @@
 	"errors"
 	"fmt"
 	"io/fs"
+	"path/filepath"
 	"slices"
 
 	fid_proto "android/soong/cmd/find_input_delta/find_input_delta_proto_internal"
+	"android/soong/third_party/zip"
 	"github.com/google/blueprint/pathtools"
 	"google.golang.org/protobuf/proto"
 )
@@ -57,6 +59,7 @@
 			// If we ever have an easy hash, assign it here.
 		}
 		if inspect_contents {
+			// NOTE: When we find it useful, we can parallelize the file inspection for speed.
 			contents, err := InspectFileContents(input)
 			if err != nil {
 				return ret, err
@@ -73,9 +76,30 @@
 // Inspect the file and extract the state of the elements in the archive.
 // If this is not an archive of some sort, nil is returned.
 func InspectFileContents(name string) ([]*fid_proto.PartialCompileInput, error) {
-	// TODO: Actually inspect the contents.
-	fmt.Printf("inspecting contents for %s\n", name)
-	return nil, nil
+	switch filepath.Ext(name) {
+	case ".jar", ".apex", ".apk":
+		return inspectZipFileContents(name)
+	default:
+		return nil, nil
+	}
+}
+
+func inspectZipFileContents(name string) ([]*fid_proto.PartialCompileInput, error) {
+	rc, err := zip.OpenReader(name)
+	if err != nil {
+		return nil, err
+	}
+	ret := []*fid_proto.PartialCompileInput{}
+	for _, v := range rc.File {
+		pci := &fid_proto.PartialCompileInput{
+			Name:      proto.String(v.Name),
+			MtimeNsec: proto.Int64(v.ModTime().UnixNano()),
+			Hash:      proto.String(fmt.Sprintf("%08x", v.CRC32)),
+		}
+		ret = append(ret, pci)
+		// We do not support nested inspection.
+	}
+	return ret, nil
 }
 
 func WriteState(s *fid_proto.PartialCompileInputs, path string) error {
@@ -91,9 +115,7 @@
 }
 
 func CompareInputFiles(prior, other []*fid_proto.PartialCompileInput, name string) *FileList {
-	fl := &FileList{
-		Name: name,
-	}
+	fl := FileListFactory(name)
 	PriorMap := make(map[string]*fid_proto.PartialCompileInput, len(prior))
 	// We know that the lists are properly sorted, so we can simply compare them.
 	for _, v := range prior {
@@ -105,17 +127,17 @@
 		otherMap[name] = v
 		if _, ok := PriorMap[name]; !ok {
 			// Added file
-			fl.Additions = append(fl.Additions, name)
+			fl.addFile(name)
 		} else if !proto.Equal(PriorMap[name], v) {
 			// Changed file
-			fl.Changes = append(fl.Changes, *CompareInputFiles(PriorMap[name].GetContents(), v.GetContents(), name))
+			fl.changeFile(name, CompareInputFiles(PriorMap[name].GetContents(), v.GetContents(), name))
 		}
 	}
 	for _, v := range prior {
 		name := v.GetName()
 		if _, ok := otherMap[name]; !ok {
 			// Deleted file
-			fl.Deletions = append(fl.Deletions, name)
+			fl.deleteFile(name)
 		}
 	}
 	return fl
diff --git a/cmd/find_input_delta/find_input_delta_lib/internal_state_test.go b/cmd/find_input_delta/find_input_delta_lib/internal_state_test.go
index 20b8efa..e69424c 100644
--- a/cmd/find_input_delta/find_input_delta_lib/internal_state_test.go
+++ b/cmd/find_input_delta/find_input_delta_lib/internal_state_test.go
@@ -199,7 +199,7 @@
 			},
 		},
 		{
-			Name:   "one of each",
+			Name:   "one each add modify delete",
 			Target: "foo",
 			Prior: &fid_proto.PartialCompileInputs{
 				InputFiles: []*fid_proto.PartialCompileInput{
@@ -222,11 +222,40 @@
 				Deletions: []string{"file2"},
 			},
 		},
+		{
+			Name:   "interior one each add modify delete",
+			Target: "bar",
+			Prior: &fid_proto.PartialCompileInputs{
+				InputFiles: []*fid_proto.PartialCompileInput{
+					protoFile("file1", 405, "", []*fid_proto.PartialCompileInput{
+						protoFile("innerC", 400, "crc32:11111111", nil),
+						protoFile("innerD", 400, "crc32:44444444", nil),
+					}),
+				},
+			},
+			New: &fid_proto.PartialCompileInputs{
+				InputFiles: []*fid_proto.PartialCompileInput{
+					protoFile("file1", 505, "", []*fid_proto.PartialCompileInput{
+						protoFile("innerA", 400, "crc32:55555555", nil),
+						protoFile("innerC", 500, "crc32:66666666", nil),
+					}),
+				},
+			},
+			Expected: &FileList{
+				Name: "bar",
+				Changes: []FileList{FileList{
+					Name:      "file1",
+					Additions: []string{"innerA"},
+					Changes:   []FileList{FileList{Name: "innerC"}},
+					Deletions: []string{"innerD"},
+				}},
+			},
+		},
 	}
 	for _, tc := range testCases {
 		actual := CompareInternalState(tc.Prior, tc.New, tc.Target)
 		if !tc.Expected.Equal(actual) {
-			t.Errorf("%s: expected %q, actual %q", tc.Name, tc.Expected, actual)
+			t.Errorf("%s: expected %v, actual %v", tc.Name, tc.Expected, actual)
 		}
 	}
 }
diff --git a/cmd/find_input_delta/find_input_delta_proto/file_list.pb.go b/cmd/find_input_delta/find_input_delta_proto/file_list.pb.go
index 648ef22..745de2d 100644
--- a/cmd/find_input_delta/find_input_delta_proto/file_list.pb.go
+++ b/cmd/find_input_delta/find_input_delta_proto/file_list.pb.go
@@ -19,7 +19,7 @@
 // 	protoc        v3.21.12
 // source: file_list.proto
 
-package proto
+package find_input_delta_proto
 
 import (
 	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
@@ -35,6 +35,62 @@
 	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
 )
 
+type FieldNumbers int32
+
+const (
+	FieldNumbers_FIELD_NUMBERS_UNSPECIFIED FieldNumbers = 0
+	FieldNumbers_FIELD_NUMBERS_FILE_LIST   FieldNumbers = 1
+)
+
+// Enum value maps for FieldNumbers.
+var (
+	FieldNumbers_name = map[int32]string{
+		0: "FIELD_NUMBERS_UNSPECIFIED",
+		1: "FIELD_NUMBERS_FILE_LIST",
+	}
+	FieldNumbers_value = map[string]int32{
+		"FIELD_NUMBERS_UNSPECIFIED": 0,
+		"FIELD_NUMBERS_FILE_LIST":   1,
+	}
+)
+
+func (x FieldNumbers) Enum() *FieldNumbers {
+	p := new(FieldNumbers)
+	*p = x
+	return p
+}
+
+func (x FieldNumbers) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (FieldNumbers) Descriptor() protoreflect.EnumDescriptor {
+	return file_file_list_proto_enumTypes[0].Descriptor()
+}
+
+func (FieldNumbers) Type() protoreflect.EnumType {
+	return &file_file_list_proto_enumTypes[0]
+}
+
+func (x FieldNumbers) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Do not use.
+func (x *FieldNumbers) UnmarshalJSON(b []byte) error {
+	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
+	if err != nil {
+		return err
+	}
+	*x = FieldNumbers(num)
+	return nil
+}
+
+// Deprecated: Use FieldNumbers.Descriptor instead.
+func (FieldNumbers) EnumDescriptor() ([]byte, []int) {
+	return file_file_list_proto_rawDescGZIP(), []int{0}
+}
+
 type FileList struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -46,10 +102,14 @@
 	Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
 	// The added files.
 	Additions []string `protobuf:"bytes,2,rep,name=additions" json:"additions,omitempty"`
-	// The deleted files.
-	Deletions []string `protobuf:"bytes,3,rep,name=deletions" json:"deletions,omitempty"`
 	// The changed files.
-	Changes []*FileList `protobuf:"bytes,4,rep,name=changes" json:"changes,omitempty"`
+	Changes []*FileList `protobuf:"bytes,3,rep,name=changes" json:"changes,omitempty"`
+	// The deleted files.
+	Deletions []string `protobuf:"bytes,4,rep,name=deletions" json:"deletions,omitempty"`
+	// Count of files added/changed/deleted.
+	TotalDelta *uint32 `protobuf:"varint,5,opt,name=total_delta,json=totalDelta" json:"total_delta,omitempty"`
+	// Counts by extension.
+	Counts []*FileCount `protobuf:"bytes,6,rep,name=counts" json:"counts,omitempty"`
 }
 
 func (x *FileList) Reset() {
@@ -98,6 +158,13 @@
 	return nil
 }
 
+func (x *FileList) GetChanges() []*FileList {
+	if x != nil {
+		return x.Changes
+	}
+	return nil
+}
+
 func (x *FileList) GetDeletions() []string {
 	if x != nil {
 		return x.Deletions
@@ -105,32 +172,135 @@
 	return nil
 }
 
-func (x *FileList) GetChanges() []*FileList {
+func (x *FileList) GetTotalDelta() uint32 {
+	if x != nil && x.TotalDelta != nil {
+		return *x.TotalDelta
+	}
+	return 0
+}
+
+func (x *FileList) GetCounts() []*FileCount {
 	if x != nil {
-		return x.Changes
+		return x.Counts
 	}
 	return nil
 }
 
+type FileCount struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The file extension
+	Extension *string `protobuf:"bytes,1,opt,name=extension" json:"extension,omitempty"`
+	// Number of added files with this extension.
+	Additions *uint32 `protobuf:"varint,2,opt,name=additions" json:"additions,omitempty"`
+	// Number of modified files with this extension.
+	Modifications *uint32 `protobuf:"varint,3,opt,name=modifications" json:"modifications,omitempty"`
+	// Number of deleted files with this extension.
+	Deletions *uint32 `protobuf:"varint,4,opt,name=deletions" json:"deletions,omitempty"`
+}
+
+func (x *FileCount) Reset() {
+	*x = FileCount{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_file_list_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *FileCount) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*FileCount) ProtoMessage() {}
+
+func (x *FileCount) ProtoReflect() protoreflect.Message {
+	mi := &file_file_list_proto_msgTypes[1]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use FileCount.ProtoReflect.Descriptor instead.
+func (*FileCount) Descriptor() ([]byte, []int) {
+	return file_file_list_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *FileCount) GetExtension() string {
+	if x != nil && x.Extension != nil {
+		return *x.Extension
+	}
+	return ""
+}
+
+func (x *FileCount) GetAdditions() uint32 {
+	if x != nil && x.Additions != nil {
+		return *x.Additions
+	}
+	return 0
+}
+
+func (x *FileCount) GetModifications() uint32 {
+	if x != nil && x.Modifications != nil {
+		return *x.Modifications
+	}
+	return 0
+}
+
+func (x *FileCount) GetDeletions() uint32 {
+	if x != nil && x.Deletions != nil {
+		return *x.Deletions
+	}
+	return 0
+}
+
 var File_file_list_proto protoreflect.FileDescriptor
 
 var file_file_list_proto_rawDesc = []byte{
 	0x0a, 0x0f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74,
 	0x6f, 0x12, 0x1e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x66, 0x69, 0x6e, 0x64, 0x5f,
 	0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x22, 0x9e, 0x01, 0x0a, 0x08, 0x46, 0x69, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x12,
+	0x6f, 0x22, 0x82, 0x02, 0x0a, 0x08, 0x46, 0x69, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x12,
 	0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
 	0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18,
 	0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73,
-	0x12, 0x1c, 0x0a, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20,
-	0x03, 0x28, 0x09, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x42,
-	0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32,
-	0x28, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69,
-	0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x67,
-	0x65, 0x73, 0x42, 0x26, 0x5a, 0x24, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f,
-	0x6f, 0x6e, 0x67, 0x2f, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64,
-	0x65, 0x6c, 0x74, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x12, 0x42, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
+	0x0b, 0x32, 0x28, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x66, 0x69, 0x6e, 0x64,
+	0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x07, 0x63, 0x68, 0x61,
+	0x6e, 0x67, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e,
+	0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x69, 0x6f,
+	0x6e, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x64, 0x65, 0x6c, 0x74,
+	0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x44, 0x65,
+	0x6c, 0x74, 0x61, 0x12, 0x41, 0x0a, 0x06, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x18, 0x06, 0x20,
+	0x03, 0x28, 0x0b, 0x32, 0x29, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x66, 0x69,
+	0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x70,
+	0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x06,
+	0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x22, 0x8b, 0x01, 0x0a, 0x09, 0x46, 0x69, 0x6c, 0x65, 0x43,
+	0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f,
+	0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69,
+	0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18,
+	0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73,
+	0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e,
+	0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x63,
+	0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x69,
+	0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74,
+	0x69, 0x6f, 0x6e, 0x73, 0x2a, 0x4a, 0x0a, 0x0c, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x4e, 0x75, 0x6d,
+	0x62, 0x65, 0x72, 0x73, 0x12, 0x1d, 0x0a, 0x19, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x4e, 0x55,
+	0x4d, 0x42, 0x45, 0x52, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45,
+	0x44, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f, 0x4e, 0x55, 0x4d,
+	0x42, 0x45, 0x52, 0x53, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x4c, 0x49, 0x53, 0x54, 0x10, 0x01,
+	0x42, 0x3b, 0x5a, 0x39, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e,
+	0x67, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74,
+	0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x2f, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75,
+	0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
 }
 
 var (
@@ -145,17 +315,21 @@
 	return file_file_list_proto_rawDescData
 }
 
-var file_file_list_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_file_list_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_file_list_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
 var file_file_list_proto_goTypes = []interface{}{
-	(*FileList)(nil), // 0: android.find_input_delta_proto.FileList
+	(FieldNumbers)(0), // 0: android.find_input_delta_proto.FieldNumbers
+	(*FileList)(nil),  // 1: android.find_input_delta_proto.FileList
+	(*FileCount)(nil), // 2: android.find_input_delta_proto.FileCount
 }
 var file_file_list_proto_depIdxs = []int32{
-	0, // 0: android.find_input_delta_proto.FileList.changes:type_name -> android.find_input_delta_proto.FileList
-	1, // [1:1] is the sub-list for method output_type
-	1, // [1:1] is the sub-list for method input_type
-	1, // [1:1] is the sub-list for extension type_name
-	1, // [1:1] is the sub-list for extension extendee
-	0, // [0:1] is the sub-list for field type_name
+	1, // 0: android.find_input_delta_proto.FileList.changes:type_name -> android.find_input_delta_proto.FileList
+	2, // 1: android.find_input_delta_proto.FileList.counts:type_name -> android.find_input_delta_proto.FileCount
+	2, // [2:2] is the sub-list for method output_type
+	2, // [2:2] is the sub-list for method input_type
+	2, // [2:2] is the sub-list for extension type_name
+	2, // [2:2] is the sub-list for extension extendee
+	0, // [0:2] is the sub-list for field type_name
 }
 
 func init() { file_file_list_proto_init() }
@@ -176,19 +350,32 @@
 				return nil
 			}
 		}
+		file_file_list_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*FileCount); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
 	}
 	type x struct{}
 	out := protoimpl.TypeBuilder{
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_file_list_proto_rawDesc,
-			NumEnums:      0,
-			NumMessages:   1,
+			NumEnums:      1,
+			NumMessages:   2,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
 		GoTypes:           file_file_list_proto_goTypes,
 		DependencyIndexes: file_file_list_proto_depIdxs,
+		EnumInfos:         file_file_list_proto_enumTypes,
 		MessageInfos:      file_file_list_proto_msgTypes,
 	}.Build()
 	File_file_list_proto = out.File
diff --git a/cmd/find_input_delta/find_input_delta_proto/file_list.proto b/cmd/find_input_delta/find_input_delta_proto/file_list.proto
index d7faca9..7180358 100644
--- a/cmd/find_input_delta/find_input_delta_proto/file_list.proto
+++ b/cmd/find_input_delta/find_input_delta_proto/file_list.proto
@@ -15,7 +15,12 @@
 
 syntax = "proto2";
 package android.find_input_delta_proto;
-option go_package = "android/soong/find_input_delta/proto";
+option go_package = "android/soong/cmd/find_input_delta/find_input_delta_proto";
+
+enum FieldNumbers {
+  FIELD_NUMBERS_UNSPECIFIED = 0;
+  FIELD_NUMBERS_FILE_LIST = 1;
+}
 
 message FileList {
   // The name of the file.
@@ -26,9 +31,29 @@
   // The added files.
   repeated string additions = 2;
 
-  // The deleted files.
-  repeated string deletions = 3;
-
   // The changed files.
-  repeated FileList changes = 4;
+  repeated FileList changes = 3;
+
+  // The deleted files.
+  repeated string deletions = 4;
+
+  // Count of files added/changed/deleted.
+  optional uint32 total_delta = 5;
+
+  // Counts by extension.
+  repeated FileCount counts = 6;
+}
+
+message FileCount {
+  // The file extension
+  optional string extension = 1;
+
+  // Number of added files with this extension.
+  optional uint32 additions = 2;
+
+  // Number of modified files with this extension.
+  optional uint32 modifications = 3;
+
+  // Number of deleted files with this extension.
+  optional uint32 deletions = 4;
 }
diff --git a/cmd/find_input_delta/find_input_delta_proto_internal/internal_state.pb.go b/cmd/find_input_delta/find_input_delta_proto_internal/internal_state.pb.go
index 2229a32..c5b048b 100644
--- a/cmd/find_input_delta/find_input_delta_proto_internal/internal_state.pb.go
+++ b/cmd/find_input_delta/find_input_delta_proto_internal/internal_state.pb.go
@@ -19,7 +19,7 @@
 // 	protoc        v3.21.12
 // source: internal_state.proto
 
-package proto
+package find_input_delta_proto_internal
 
 import (
 	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
@@ -94,7 +94,7 @@
 	Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
 	// The timestamp of the file in (Unix) nanoseconds.
 	MtimeNsec *int64 `protobuf:"varint,2,opt,name=mtime_nsec,json=mtimeNsec" json:"mtime_nsec,omitempty"`
-	// The hash of the file, in the form ‘{HASHNAME}:{VALUE}’
+	// The hash of the file.  For crc32 hashes, this will be 8 hex digits.
 	Hash *string `protobuf:"bytes,3,opt,name=hash" json:"hash,omitempty"`
 	// Contents of the file, if the file was inspected (such as jar files, etc).
 	Contents []*PartialCompileInput `protobuf:"bytes,4,rep,name=contents" json:"contents,omitempty"`
@@ -164,29 +164,33 @@
 
 var file_internal_state_proto_rawDesc = []byte{
 	0x0a, 0x14, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65,
-	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e,
+	0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x27, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e,
 	0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61,
-	0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x6c, 0x0a, 0x14, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61,
-	0x6c, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x54,
-	0x0a, 0x0b, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20,
-	0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x66, 0x69,
-	0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x70,
-	0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x43, 0x6f, 0x6d, 0x70,
-	0x69, 0x6c, 0x65, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x0a, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x46,
-	0x69, 0x6c, 0x65, 0x73, 0x22, 0xad, 0x01, 0x0a, 0x13, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c,
-	0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x12, 0x0a, 0x04,
-	0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
-	0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6e, 0x73, 0x65, 0x63, 0x18, 0x02,
-	0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x6d, 0x74, 0x69, 0x6d, 0x65, 0x4e, 0x73, 0x65, 0x63, 0x12,
-	0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68,
-	0x61, 0x73, 0x68, 0x12, 0x4f, 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x18,
-	0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e,
-	0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61,
-	0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x43, 0x6f,
-	0x6d, 0x70, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x08, 0x63, 0x6f, 0x6e, 0x74,
-	0x65, 0x6e, 0x74, 0x73, 0x42, 0x26, 0x5a, 0x24, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f,
-	0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74,
-	0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x22,
+	0x75, 0x0a, 0x14, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c,
+	0x65, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x5d, 0x0a, 0x0b, 0x69, 0x6e, 0x70, 0x75, 0x74,
+	0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3c, 0x2e, 0x61,
+	0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75,
+	0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5f, 0x69, 0x6e,
+	0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x43, 0x6f,
+	0x6d, 0x70, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x0a, 0x69, 0x6e, 0x70, 0x75,
+	0x74, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x22, 0xb6, 0x01, 0x0a, 0x13, 0x50, 0x61, 0x72, 0x74, 0x69,
+	0x61, 0x6c, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x12,
+	0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
+	0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6e, 0x73, 0x65, 0x63,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x6d, 0x74, 0x69, 0x6d, 0x65, 0x4e, 0x73, 0x65,
+	0x63, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x58, 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,
+	0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3c, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69,
+	0x64, 0x2e, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x6c,
+	0x74, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61,
+	0x6c, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65,
+	0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x42,
+	0x40, 0x5a, 0x3e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67,
+	0x2f, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74,
+	0x61, 0x2f, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x6c,
+	0x74, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61,
+	0x6c,
 }
 
 var (
@@ -203,12 +207,12 @@
 
 var file_internal_state_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
 var file_internal_state_proto_goTypes = []interface{}{
-	(*PartialCompileInputs)(nil), // 0: android.find_input_delta_proto.PartialCompileInputs
-	(*PartialCompileInput)(nil),  // 1: android.find_input_delta_proto.PartialCompileInput
+	(*PartialCompileInputs)(nil), // 0: android.find_input_delta_proto_internal.PartialCompileInputs
+	(*PartialCompileInput)(nil),  // 1: android.find_input_delta_proto_internal.PartialCompileInput
 }
 var file_internal_state_proto_depIdxs = []int32{
-	1, // 0: android.find_input_delta_proto.PartialCompileInputs.input_files:type_name -> android.find_input_delta_proto.PartialCompileInput
-	1, // 1: android.find_input_delta_proto.PartialCompileInput.contents:type_name -> android.find_input_delta_proto.PartialCompileInput
+	1, // 0: android.find_input_delta_proto_internal.PartialCompileInputs.input_files:type_name -> android.find_input_delta_proto_internal.PartialCompileInput
+	1, // 1: android.find_input_delta_proto_internal.PartialCompileInput.contents:type_name -> android.find_input_delta_proto_internal.PartialCompileInput
 	2, // [2:2] is the sub-list for method output_type
 	2, // [2:2] is the sub-list for method input_type
 	2, // [2:2] is the sub-list for extension type_name
diff --git a/cmd/find_input_delta/find_input_delta_proto_internal/internal_state.proto b/cmd/find_input_delta/find_input_delta_proto_internal/internal_state.proto
index 113fc64..54c90cc 100644
--- a/cmd/find_input_delta/find_input_delta_proto_internal/internal_state.proto
+++ b/cmd/find_input_delta/find_input_delta_proto_internal/internal_state.proto
@@ -14,8 +14,8 @@
 // limitations under the License.
 
 syntax = "proto2";
-package android.find_input_delta_proto;
-option go_package = "android/soong/find_input_delta/proto";
+package android.find_input_delta_proto_internal;
+option go_package = "android/soong/find_input_delta/find_input_delta_proto_internal";
 
 // The state of all inputs.
 message PartialCompileInputs {
@@ -31,7 +31,7 @@
   // The timestamp of the file in (Unix) nanoseconds.
   optional int64 mtime_nsec = 2;
 
-  // The hash of the file, in the form ‘{HASHNAME}:{VALUE}’
+  // The hash of the file.  For crc32 hashes, this will be 8 hex digits.
   optional string hash = 3;
 
   // Contents of the file, if the file was inspected (such as jar files, etc).
diff --git a/cmd/release_config/build_flag/main.go b/cmd/release_config/build_flag/main.go
index 46efce7..5d103cc 100644
--- a/cmd/release_config/build_flag/main.go
+++ b/cmd/release_config/build_flag/main.go
@@ -90,6 +90,9 @@
 	if !ok {
 		return "", fmt.Errorf("%s not found in %s", name, config.Name)
 	}
+	if fa.Redacted {
+		return "==REDACTED==", nil
+	}
 	return rc_lib.MarshalValue(fa.Value), nil
 }
 
diff --git a/cmd/release_config/release_config_lib/flag_artifact.go b/cmd/release_config/release_config_lib/flag_artifact.go
index 51c02d2..cb13fdc 100644
--- a/cmd/release_config/release_config_lib/flag_artifact.go
+++ b/cmd/release_config/release_config_lib/flag_artifact.go
@@ -84,8 +84,10 @@
 
 func (fas *FlagArtifacts) SortedFlagNames() []string {
 	var names []string
-	for k, _ := range *fas {
-		names = append(names, k)
+	for k, v := range *fas {
+		if !v.Redacted {
+			names = append(names, k)
+		}
 	}
 	slices.Sort(names)
 	return names
@@ -99,6 +101,9 @@
 	if namespace := fa.FlagDeclaration.GetNamespace(); namespace != "" {
 		ret.Namespace = proto.String(namespace)
 	}
+	if bugs := fa.FlagDeclaration.GetBugs(); bugs != nil {
+		ret.Bugs = bugs
+	}
 	if description := fa.FlagDeclaration.GetDescription(); description != "" {
 		ret.Description = proto.String(description)
 	}
@@ -180,15 +185,20 @@
 //	error: any error encountered
 func (fa *FlagArtifact) UpdateValue(flagValue FlagValue) error {
 	name := *flagValue.proto.Name
-	fa.Traces = append(fa.Traces, &rc_proto.Tracepoint{Source: proto.String(flagValue.path), Value: flagValue.proto.Value})
-	if flagValue.proto.GetRedacted() {
-		fa.Redacted = true
+	redacted := flagValue.proto.GetRedacted()
+	if redacted {
+		fa.Redact()
+		flagValue.proto.Value = fa.Value
 		fmt.Printf("Redacting flag %s in %s\n", name, flagValue.path)
-		return nil
+	} else {
+		// If we are assigning a value, then the flag is no longer redacted.
+		fa.Redacted = false
 	}
+	fa.Traces = append(fa.Traces, &rc_proto.Tracepoint{Source: proto.String(flagValue.path), Value: flagValue.proto.Value})
 	if fa.Value.GetObsolete() {
 		return fmt.Errorf("Attempting to set obsolete flag %s. Trace=%v", name, fa.Traces)
 	}
+
 	var newValue *rc_proto.Value
 	switch val := flagValue.proto.Value.Val.(type) {
 	case *rc_proto.Value_StringValue:
@@ -210,6 +220,11 @@
 	return nil
 }
 
+func (fa *FlagArtifact) Redact() {
+	fa.Redacted = true
+	fa.Value = &rc_proto.Value{Val: &rc_proto.Value_StringValue{StringValue: "*REDACTED*"}}
+}
+
 // Marshal the FlagArtifact into a flag_artifact message.
 func (fa *FlagArtifact) Marshal() (*rc_proto.FlagArtifact, error) {
 	if fa.Redacted {
diff --git a/cmd/release_config/release_config_lib/release_config.go b/cmd/release_config/release_config_lib/release_config.go
index ee71336..3c834cc 100644
--- a/cmd/release_config/release_config_lib/release_config.go
+++ b/cmd/release_config/release_config_lib/release_config.go
@@ -21,7 +21,6 @@
 	"path/filepath"
 	"regexp"
 	"slices"
-	"sort"
 	"strings"
 
 	rc_proto "android/soong/cmd/release_config/release_config_proto"
@@ -107,6 +106,9 @@
 		if !ok {
 			return fmt.Errorf("Could not inherit flag %s from %s", name, iConfig.Name)
 		}
+		if fa.Redacted {
+			myFa.Redact()
+		}
 		if name == "RELEASE_ACONFIG_VALUE_SETS" {
 			// If there is a value assigned, add the trace.
 			if len(fa.Value.GetStringValue()) > 0 {
@@ -166,6 +168,13 @@
 		}
 		myInherits = append(myInherits, inherit)
 		myInheritsSet[inherit] = true
+		// TODO: there are some configs that rely on vgsbr being
+		// present on branches where it isn't. Once the broken configs
+		// are fixed, we can be more strict.  In the meantime, they
+		// will wind up inheriting `trunk_stable` instead of the
+		// non-existent (alias) that they reference today.  Once fixed,
+		// this becomes:
+		//    iConfig, err := configs.GetReleaseConfigStrict(inherit)
 		iConfig, err := configs.GetReleaseConfig(inherit)
 		if err != nil {
 			return err
@@ -261,9 +270,6 @@
 			if err := fa.UpdateValue(*value); err != nil {
 				return err
 			}
-			if fa.Redacted {
-				delete(config.FlagArtifacts, name)
-			}
 		}
 	}
 	// Now remove any duplicates from the actual value of RELEASE_ACONFIG_VALUE_SETS
@@ -313,6 +319,10 @@
 		if err != nil {
 			return err
 		}
+		// Redacted flags return nil when rendered.
+		if artifact == nil {
+			continue
+		}
 		for _, container := range v.FlagDeclaration.Containers {
 			if _, ok := config.PartitionBuildFlags[container]; !ok {
 				config.PartitionBuildFlags[container] = &rc_proto.FlagArtifacts{}
@@ -325,12 +335,7 @@
 		OtherNames: config.OtherNames,
 		Flags: func() []*rc_proto.FlagArtifact {
 			ret := []*rc_proto.FlagArtifact{}
-			flagNames := []string{}
-			for k := range config.FlagArtifacts {
-				flagNames = append(flagNames, k)
-			}
-			sort.Strings(flagNames)
-			for _, flagName := range flagNames {
+			for _, flagName := range config.FlagArtifacts.SortedFlagNames() {
 				flag := config.FlagArtifacts[flagName]
 				ret = append(ret, &rc_proto.FlagArtifact{
 					FlagDeclaration: flag.FlagDeclaration,
@@ -365,7 +370,7 @@
 		}
 	}
 	for _, rcName := range extraAconfigReleaseConfigs {
-		rc, err := configs.GetReleaseConfig(rcName)
+		rc, err := configs.GetReleaseConfigStrict(rcName)
 		if err != nil {
 			return err
 		}
diff --git a/cmd/release_config/release_config_lib/release_configs.go b/cmd/release_config/release_config_lib/release_configs.go
index 831ec02..f947b7f 100644
--- a/cmd/release_config/release_config_lib/release_configs.go
+++ b/cmd/release_config/release_config_lib/release_configs.go
@@ -410,6 +410,14 @@
 }
 
 func (configs *ReleaseConfigs) GetReleaseConfig(name string) (*ReleaseConfig, error) {
+	return configs.getReleaseConfig(name, configs.allowMissing)
+}
+
+func (configs *ReleaseConfigs) GetReleaseConfigStrict(name string) (*ReleaseConfig, error) {
+	return configs.getReleaseConfig(name, false)
+}
+
+func (configs *ReleaseConfigs) getReleaseConfig(name string, allow_missing bool) (*ReleaseConfig, error) {
 	trace := []string{name}
 	for target, ok := configs.Aliases[name]; ok; target, ok = configs.Aliases[name] {
 		name = *target
@@ -418,7 +426,7 @@
 	if config, ok := configs.ReleaseConfigs[name]; ok {
 		return config, nil
 	}
-	if configs.allowMissing {
+	if allow_missing {
 		if config, ok := configs.ReleaseConfigs["trunk_staging"]; ok {
 			return config, nil
 		}
@@ -467,7 +475,7 @@
 		dirName := filepath.Dir(contrib.path)
 		for k, names := range contrib.FlagValueDirs {
 			for _, rcName := range names {
-				if config, err := configs.GetReleaseConfig(rcName); err == nil {
+				if config, err := configs.getReleaseConfig(rcName, false); err == nil {
 					rcPath := filepath.Join(dirName, "release_configs", fmt.Sprintf("%s.textproto", config.Name))
 					if _, err := os.Stat(rcPath); err != nil {
 						errors = append(errors, fmt.Sprintf("%s exists but %s does not contribute to %s",
diff --git a/cmd/release_config/release_config_proto/build_flags_declarations.pb.go b/cmd/release_config/release_config_proto/build_flags_declarations.pb.go
index 7db945a..1df246c 100644
--- a/cmd/release_config/release_config_proto/build_flags_declarations.pb.go
+++ b/cmd/release_config/release_config_proto/build_flags_declarations.pb.go
@@ -48,6 +48,8 @@
 	Namespace *string `protobuf:"bytes,2,opt,name=namespace" json:"namespace,omitempty"`
 	// Text description of the flag's purpose.
 	Description *string `protobuf:"bytes,3,opt,name=description" json:"description,omitempty"`
+	// The bug number associated with the flag.
+	Bugs []string `protobuf:"bytes,4,rep,name=bugs" json:"bugs,omitempty"`
 	// Where the flag was declared.
 	DeclarationPath *string `protobuf:"bytes,5,opt,name=declaration_path,json=declarationPath" json:"declaration_path,omitempty"`
 	// Workflow for this flag.
@@ -110,6 +112,13 @@
 	return ""
 }
 
+func (x *FlagDeclarationArtifact) GetBugs() []string {
+	if x != nil {
+		return x.Bugs
+	}
+	return nil
+}
+
 func (x *FlagDeclarationArtifact) GetDeclarationPath() string {
 	if x != nil && x.DeclarationPath != nil {
 		return *x.DeclarationPath
@@ -187,37 +196,38 @@
 	0x12, 0x1c, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73,
 	0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x18,
 	0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x5f, 0x63, 0x6f, 0x6d, 0x6d,
-	0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x8c, 0x02, 0x0a, 0x17, 0x46, 0x6c, 0x61,
+	0x6f, 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9a, 0x02, 0x0a, 0x17, 0x46, 0x6c, 0x61,
 	0x67, 0x44, 0x65, 0x63, 0x6c, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x72, 0x74, 0x69,
 	0x66, 0x61, 0x63, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01,
 	0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65,
 	0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d,
 	0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69,
 	0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73,
-	0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x64, 0x65, 0x63, 0x6c,
-	0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01,
-	0x28, 0x09, 0x52, 0x0f, 0x64, 0x65, 0x63, 0x6c, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x50,
-	0x61, 0x74, 0x68, 0x12, 0x43, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18,
-	0xcd, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
-	0x2e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x08,
-	0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1f, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x74,
-	0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x18, 0xce, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63,
-	0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a,
-	0x06, 0x08, 0xcf, 0x01, 0x10, 0xd0, 0x01, 0x22, 0x96, 0x01, 0x0a, 0x18, 0x46, 0x6c, 0x61, 0x67,
-	0x44, 0x65, 0x63, 0x6c, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x72, 0x74, 0x69, 0x66,
-	0x61, 0x63, 0x74, 0x73, 0x12, 0x7a, 0x0a, 0x1e, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x64, 0x65, 0x63,
-	0x6c, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63,
-	0x74, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x61,
-	0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63,
-	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x6c, 0x61, 0x67,
-	0x44, 0x65, 0x63, 0x6c, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x72, 0x74, 0x69, 0x66,
-	0x61, 0x63, 0x74, 0x52, 0x1b, 0x66, 0x6c, 0x61, 0x67, 0x44, 0x65, 0x63, 0x6c, 0x61, 0x72, 0x61,
-	0x74, 0x69, 0x6f, 0x6e, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x4c, 0x69, 0x73, 0x74,
-	0x42, 0x33, 0x5a, 0x31, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e,
-	0x67, 0x2f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-	0x2f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f,
-	0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x75, 0x67, 0x73,
+	0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x62, 0x75, 0x67, 0x73, 0x12, 0x29, 0x0a, 0x10,
+	0x64, 0x65, 0x63, 0x6c, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x70, 0x61, 0x74, 0x68,
+	0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x64, 0x65, 0x63, 0x6c, 0x61, 0x72, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x50, 0x61, 0x74, 0x68, 0x12, 0x43, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66,
+	0x6c, 0x6f, 0x77, 0x18, 0xcd, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x61, 0x6e, 0x64,
+	0x72, 0x6f, 0x69, 0x64, 0x2e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c,
+	0x6f, 0x77, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1f, 0x0a, 0x0a,
+	0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x18, 0xce, 0x01, 0x20, 0x03, 0x28,
+	0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x4a, 0x06, 0x08,
+	0xcf, 0x01, 0x10, 0xd0, 0x01, 0x22, 0x96, 0x01, 0x0a, 0x18, 0x46, 0x6c, 0x61, 0x67, 0x44, 0x65,
+	0x63, 0x6c, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63,
+	0x74, 0x73, 0x12, 0x7a, 0x0a, 0x1e, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x64, 0x65, 0x63, 0x6c, 0x61,
+	0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x5f,
+	0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x35, 0x2e, 0x61, 0x6e, 0x64,
+	0x72, 0x6f, 0x69, 0x64, 0x2e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x6c, 0x61, 0x67, 0x44, 0x65,
+	0x63, 0x6c, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63,
+	0x74, 0x52, 0x1b, 0x66, 0x6c, 0x61, 0x67, 0x44, 0x65, 0x63, 0x6c, 0x61, 0x72, 0x61, 0x74, 0x69,
+	0x6f, 0x6e, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x42, 0x33,
+	0x5a, 0x31, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f,
+	0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x72,
+	0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x72,
+	0x6f, 0x74, 0x6f,
 }
 
 var (
diff --git a/cmd/release_config/release_config_proto/build_flags_declarations.proto b/cmd/release_config/release_config_proto/build_flags_declarations.proto
index d755e02..ccdccfb 100644
--- a/cmd/release_config/release_config_proto/build_flags_declarations.proto
+++ b/cmd/release_config/release_config_proto/build_flags_declarations.proto
@@ -51,8 +51,8 @@
   // Text description of the flag's purpose.
   optional string description = 3;
 
-  // reserve this for bug, if needed.
-  reserved 4;
+  // The bug number associated with the flag.
+  repeated string bugs = 4;
 
   // Where the flag was declared.
   optional string declaration_path = 5;
diff --git a/cmd/release_config/release_config_proto/build_flags_src.pb.go b/cmd/release_config/release_config_proto/build_flags_src.pb.go
index d784dee..123c6d3 100644
--- a/cmd/release_config/release_config_proto/build_flags_src.pb.go
+++ b/cmd/release_config/release_config_proto/build_flags_src.pb.go
@@ -159,6 +159,8 @@
 	Namespace *string `protobuf:"bytes,2,opt,name=namespace" json:"namespace,omitempty"`
 	// Text description of the flag's purpose.
 	Description *string `protobuf:"bytes,3,opt,name=description" json:"description,omitempty"`
+	// The bug number associated with the flag.
+	Bugs []string `protobuf:"bytes,4,rep,name=bugs" json:"bugs,omitempty"`
 	// Value for the flag
 	Value *Value `protobuf:"bytes,201,opt,name=value" json:"value,omitempty"`
 	// Workflow for this flag.
@@ -221,6 +223,13 @@
 	return ""
 }
 
+func (x *FlagDeclaration) GetBugs() []string {
+	if x != nil {
+		return x.Bugs
+	}
+	return nil
+}
+
 func (x *FlagDeclaration) GetValue() *Value {
 	if x != nil {
 		return x.Value
@@ -541,62 +550,63 @@
 	0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x09, 0x62, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75,
 	0x65, 0x12, 0x1d, 0x0a, 0x08, 0x6f, 0x62, 0x73, 0x6f, 0x6c, 0x65, 0x74, 0x65, 0x18, 0xcb, 0x01,
 	0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x08, 0x6f, 0x62, 0x73, 0x6f, 0x6c, 0x65, 0x74, 0x65,
-	0x42, 0x05, 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x22, 0x95, 0x02, 0x0a, 0x0f, 0x46, 0x6c, 0x61, 0x67,
+	0x42, 0x05, 0x0a, 0x03, 0x76, 0x61, 0x6c, 0x22, 0xa3, 0x02, 0x0a, 0x0f, 0x46, 0x6c, 0x61, 0x67,
 	0x44, 0x65, 0x63, 0x6c, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e,
 	0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
 	0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01,
 	0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x20, 0x0a,
 	0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01,
 	0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12,
-	0x3a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0xc9, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32,
-	0x23, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73,
-	0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56,
-	0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x43, 0x0a, 0x08, 0x77,
-	0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0xcd, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26,
+	0x12, 0x0a, 0x04, 0x62, 0x75, 0x67, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x62,
+	0x75, 0x67, 0x73, 0x12, 0x3a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0xc9, 0x01, 0x20,
+	0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x72, 0x65,
+	0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x72, 0x6f,
+	0x74, 0x6f, 0x2e, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12,
+	0x43, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x18, 0xcd, 0x01, 0x20, 0x01,
+	0x28, 0x0e, 0x32, 0x26, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x72, 0x65, 0x6c,
+	0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x74,
+	0x6f, 0x2e, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b,
+	0x66, 0x6c, 0x6f, 0x77, 0x12, 0x1f, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65,
+	0x72, 0x73, 0x18, 0xce, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x61,
+	0x69, 0x6e, 0x65, 0x72, 0x73, 0x4a, 0x06, 0x08, 0xcf, 0x01, 0x10, 0xd0, 0x01, 0x22, 0x78, 0x0a,
+	0x09, 0x46, 0x6c, 0x61, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61,
+	0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3a,
+	0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0xc9, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23,
 	0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65,
-	0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x57, 0x6f,
-	0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x08, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77,
-	0x12, 0x1f, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x18, 0xce,
-	0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72,
-	0x73, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x06, 0x08, 0xcf, 0x01, 0x10, 0xd0, 0x01, 0x22,
-	0x78, 0x0a, 0x09, 0x46, 0x6c, 0x61, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x12, 0x0a, 0x04,
-	0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65,
-	0x12, 0x3a, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0xc9, 0x01, 0x20, 0x01, 0x28, 0x0b,
-	0x32, 0x23, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x72, 0x65, 0x6c, 0x65, 0x61,
-	0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
-	0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1b, 0x0a, 0x08,
-	0x72, 0x65, 0x64, 0x61, 0x63, 0x74, 0x65, 0x64, 0x18, 0xca, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52,
-	0x08, 0x72, 0x65, 0x64, 0x61, 0x63, 0x74, 0x65, 0x64, 0x22, 0xbe, 0x01, 0x0a, 0x0d, 0x52, 0x65,
-	0x6c, 0x65, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6e,
-	0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
-	0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x68, 0x65, 0x72, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28,
-	0x09, 0x52, 0x08, 0x69, 0x6e, 0x68, 0x65, 0x72, 0x69, 0x74, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x61,
-	0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x73, 0x65, 0x74,
-	0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x61, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-	0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x65, 0x74, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x63, 0x6f,
-	0x6e, 0x66, 0x69, 0x67, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18,
-	0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x61, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x6c,
-	0x61, 0x67, 0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x69, 0x6f, 0x72,
-	0x5f, 0x73, 0x74, 0x61, 0x67, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x70,
-	0x72, 0x69, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x67, 0x65, 0x73, 0x22, 0x3a, 0x0a, 0x0c, 0x52, 0x65,
-	0x6c, 0x65, 0x61, 0x73, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61,
-	0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16,
-	0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06,
-	0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0xa9, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x6c, 0x65, 0x61,
-	0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x61, 0x70, 0x12, 0x44, 0x0a, 0x07, 0x61,
-	0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x61,
-	0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63,
-	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x6c, 0x65,
-	0x61, 0x73, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65,
-	0x73, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e,
-	0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
-	0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x0a, 0x12, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x63,
-	0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52,
-	0x11, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65,
-	0x72, 0x73, 0x42, 0x33, 0x5a, 0x31, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f,
-	0x6f, 0x6e, 0x67, 0x2f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66,
-	0x69, 0x67, 0x2f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69,
-	0x67, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x56, 0x61,
+	0x6c, 0x75, 0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1b, 0x0a, 0x08, 0x72, 0x65,
+	0x64, 0x61, 0x63, 0x74, 0x65, 0x64, 0x18, 0xca, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x72,
+	0x65, 0x64, 0x61, 0x63, 0x74, 0x65, 0x64, 0x22, 0xbe, 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x6c, 0x65,
+	0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
+	0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1a, 0x0a,
+	0x08, 0x69, 0x6e, 0x68, 0x65, 0x72, 0x69, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52,
+	0x08, 0x69, 0x6e, 0x68, 0x65, 0x72, 0x69, 0x74, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x63, 0x6f,
+	0x6e, 0x66, 0x69, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x73, 0x18,
+	0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x61, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x61,
+	0x6c, 0x75, 0x65, 0x53, 0x65, 0x74, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x63, 0x6f, 0x6e, 0x66,
+	0x69, 0x67, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x04, 0x20,
+	0x01, 0x28, 0x08, 0x52, 0x10, 0x61, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x6c, 0x61, 0x67,
+	0x73, 0x4f, 0x6e, 0x6c, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x5f, 0x73,
+	0x74, 0x61, 0x67, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x70, 0x72, 0x69,
+	0x6f, 0x72, 0x53, 0x74, 0x61, 0x67, 0x65, 0x73, 0x22, 0x3a, 0x0a, 0x0c, 0x52, 0x65, 0x6c, 0x65,
+	0x61, 0x73, 0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06,
+	0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x61,
+	0x72, 0x67, 0x65, 0x74, 0x22, 0xa9, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65,
+	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x61, 0x70, 0x12, 0x44, 0x0a, 0x07, 0x61, 0x6c, 0x69,
+	0x61, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x61, 0x6e, 0x64,
+	0x72, 0x6f, 0x69, 0x64, 0x2e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73,
+	0x65, 0x41, 0x6c, 0x69, 0x61, 0x73, 0x52, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12,
+	0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f,
+	0x6e, 0x12, 0x2d, 0x0a, 0x12, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x63, 0x6f, 0x6e,
+	0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x64,
+	0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73,
+	0x42, 0x33, 0x5a, 0x31, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e,
+	0x67, 0x2f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+	0x2f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f,
+	0x70, 0x72, 0x6f, 0x74, 0x6f,
 }
 
 var (
diff --git a/cmd/release_config/release_config_proto/build_flags_src.proto b/cmd/release_config/release_config_proto/build_flags_src.proto
index e1925bc..b28b5e2 100644
--- a/cmd/release_config/release_config_proto/build_flags_src.proto
+++ b/cmd/release_config/release_config_proto/build_flags_src.proto
@@ -67,8 +67,8 @@
   // Text description of the flag's purpose.
   optional string description = 3;
 
-  // reserve this for bug, if needed.
-  reserved 4;
+  // The bug number associated with the flag.
+  repeated string bugs = 4;
 
   // Value for the flag
   optional Value value = 201;
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index 3dd6f9a..e3804e5 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -465,7 +465,7 @@
 func (d dex2oatDependencyTag) AllowDisabledModuleDependencyProxy(
 	ctx android.OtherModuleProviderContext, target android.ModuleProxy) bool {
 	return android.OtherModuleProviderOrDefault(
-		ctx, target, android.CommonPropertiesProviderKey).ReplacedByPrebuilt
+		ctx, target, android.CommonModuleInfoKey).ReplacedByPrebuilt
 }
 
 // Dex2oatDepTag represents the dependency onto the dex2oatd module. It is added to any module that
diff --git a/etc/Android.bp b/etc/Android.bp
index 8e043b8..e92437e 100644
--- a/etc/Android.bp
+++ b/etc/Android.bp
@@ -12,6 +12,7 @@
     ],
     srcs: [
         "adb_keys.go",
+        "avbpubkey.go",
         "install_symlink.go",
         "otacerts_zip.go",
         "prebuilt_etc.go",
diff --git a/etc/adb_keys.go b/etc/adb_keys.go
index 73bc347..cfcb1d5 100644
--- a/etc/adb_keys.go
+++ b/etc/adb_keys.go
@@ -24,7 +24,7 @@
 
 type AdbKeysModule struct {
 	android.ModuleBase
-	outputPath  android.OutputPath
+	outputPath  android.Path
 	installPath android.InstallPath
 }
 
@@ -46,15 +46,16 @@
 		return
 	}
 
-	m.outputPath = android.PathForModuleOut(ctx, "adb_keys").OutputPath
+	outputPath := android.PathForModuleOut(ctx, "adb_keys")
 	input := android.ExistentPathForSource(ctx, android.String(productVariables.AdbKeys))
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   android.Cp,
-		Output: m.outputPath,
+		Output: outputPath,
 		Input:  input.Path(),
 	})
 	m.installPath = android.PathForModuleInstall(ctx, "etc/security")
-	ctx.InstallFile(m.installPath, "adb_keys", m.outputPath)
+	ctx.InstallFile(m.installPath, "adb_keys", outputPath)
+	m.outputPath = outputPath
 }
 
 func (m *AdbKeysModule) AndroidMkEntries() []android.AndroidMkEntries {
diff --git a/etc/avbpubkey.go b/etc/avbpubkey.go
new file mode 100644
index 0000000..3f998d4
--- /dev/null
+++ b/etc/avbpubkey.go
@@ -0,0 +1,84 @@
+// Copyright 2024 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package etc
+
+import (
+	"android/soong/android"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+func init() {
+	android.RegisterModuleType("avbpubkey", AvbpubkeyModuleFactory)
+	pctx.HostBinToolVariable("avbtool", "avbtool")
+}
+
+type avbpubkeyProperty struct {
+	Private_key *string `android:"path"`
+}
+
+type AvbpubkeyModule struct {
+	android.ModuleBase
+
+	properties avbpubkeyProperty
+
+	outputPath  android.WritablePath
+	installPath android.InstallPath
+}
+
+func AvbpubkeyModuleFactory() android.Module {
+	module := &AvbpubkeyModule{}
+	module.AddProperties(&module.properties)
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	return module
+}
+
+var avbPubKeyRule = pctx.AndroidStaticRule("avbpubkey",
+	blueprint.RuleParams{
+		Command: `${avbtool} extract_public_key --key ${in} --output ${out}.tmp` +
+			` && ( if cmp -s ${out}.tmp ${out} ; then rm ${out}.tmp ; else mv ${out}.tmp ${out} ; fi )`,
+		CommandDeps: []string{"${avbtool}"},
+		Description: "Extracting system_other avb key",
+	})
+
+func (m *AvbpubkeyModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	if !m.ProductSpecific() {
+		ctx.ModuleErrorf("avbpubkey module type must set product_specific to true")
+	}
+
+	m.outputPath = android.PathForModuleOut(ctx, ctx.ModuleName(), "system_other.avbpubkey")
+
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   avbPubKeyRule,
+		Input:  android.PathForModuleSrc(ctx, proptools.String(m.properties.Private_key)),
+		Output: m.outputPath,
+	})
+
+	m.installPath = android.PathForModuleInstall(ctx, "etc/security/avb")
+	ctx.InstallFile(m.installPath, "system_other.avbpubkey", m.outputPath)
+}
+
+func (m *AvbpubkeyModule) AndroidMkEntries() []android.AndroidMkEntries {
+	if m.IsSkipInstall() {
+		return []android.AndroidMkEntries{}
+	}
+
+	return []android.AndroidMkEntries{
+		{
+			Class:      "ETC",
+			OutputFile: android.OptionalPathForPath(m.outputPath),
+		}}
+}
diff --git a/etc/otacerts_zip.go b/etc/otacerts_zip.go
index d12bdac..8220cea 100644
--- a/etc/otacerts_zip.go
+++ b/etc/otacerts_zip.go
@@ -44,7 +44,7 @@
 	android.ModuleBase
 
 	properties otacertsZipProperties
-	outputPath android.OutputPath
+	outputPath android.Path
 }
 
 // otacerts_zip collects key files defined in PRODUCT_DEFAULT_DEV_CERTIFICATE
@@ -117,11 +117,11 @@
 	// Read .x509.pem files listed  in PRODUCT_EXTRA_OTA_KEYS or PRODUCT_EXTRA_RECOVERY_KEYS.
 	extras := ctx.Config().ExtraOtaKeys(ctx, m.InRecovery())
 	srcPaths := append([]android.SourcePath{pem}, extras...)
-	m.outputPath = android.PathForModuleOut(ctx, m.outputFileName()).OutputPath
+	outputPath := android.PathForModuleOut(ctx, m.outputFileName())
 
 	rule := android.NewRuleBuilder(pctx, ctx)
 	cmd := rule.Command().BuiltTool("soong_zip").
-		FlagWithOutput("-o ", m.outputPath).
+		FlagWithOutput("-o ", outputPath).
 		Flag("-j ").
 		Flag("-symlinks=false ")
 	for _, src := range srcPaths {
@@ -130,7 +130,8 @@
 	rule.Build(ctx.ModuleName(), "Generating the otacerts zip file")
 
 	installPath := android.PathForModuleInstall(ctx, "etc", proptools.String(m.properties.Relative_install_path))
-	ctx.InstallFile(installPath, m.outputFileName(), m.outputPath)
+	ctx.InstallFile(installPath, m.outputFileName(), outputPath)
+	m.outputPath = outputPath
 }
 
 func (m *otacertsZipModule) AndroidMkEntries() []android.AndroidMkEntries {
diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index be943a3..b0b5da9 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -80,6 +80,7 @@
 	ctx.RegisterModuleType("prebuilt_bt_firmware", PrebuiltBtFirmwareFactory)
 	ctx.RegisterModuleType("prebuilt_tvservice", PrebuiltTvServiceFactory)
 	ctx.RegisterModuleType("prebuilt_optee", PrebuiltOpteeFactory)
+	ctx.RegisterModuleType("prebuilt_tvconfig", PrebuiltTvConfigFactory)
 
 	ctx.RegisterModuleType("prebuilt_defaults", defaultsFactory)
 
@@ -87,7 +88,7 @@
 
 var PrepareForTestWithPrebuiltEtc = android.FixtureRegisterWithContext(RegisterPrebuiltEtcBuildComponents)
 
-type prebuiltEtcProperties struct {
+type PrebuiltEtcProperties struct {
 	// Source file of this prebuilt. Can reference a genrule type module with the ":module" syntax.
 	// Mutually exclusive with srcs.
 	Src proptools.Configurable[string] `android:"path,arch_variant,replace_instead_of_append"`
@@ -174,7 +175,7 @@
 	android.ModuleBase
 	android.DefaultableModuleBase
 
-	properties prebuiltEtcProperties
+	properties PrebuiltEtcProperties
 
 	// rootProperties is used to return the value of the InstallInRoot() method. Currently, only
 	// prebuilt_avb and prebuilt_root modules use this.
@@ -183,7 +184,7 @@
 	subdirProperties prebuiltSubdirProperties
 
 	sourceFilePaths android.Paths
-	outputFilePaths android.OutputPaths
+	outputFilePaths android.WritablePaths
 	// The base install location, e.g. "etc" for prebuilt_etc, "usr/share" for prebuilt_usr_share.
 	installDirBase               string
 	installDirBase64             string
@@ -316,7 +317,7 @@
 	p.additionalDependencies = &paths
 }
 
-func (p *PrebuiltEtc) OutputFile() android.OutputPath {
+func (p *PrebuiltEtc) OutputFile() android.Path {
 	if p.usedSrcsProperty {
 		panic(fmt.Errorf("OutputFile not available on multi-source prebuilt %q", p.Name()))
 	}
@@ -409,7 +410,7 @@
 			ctx.PropertyErrorf("filename", "filename cannot contain separator '/'")
 			return
 		}
-		p.outputFilePaths = android.OutputPaths{android.PathForModuleOut(ctx, filename).OutputPath}
+		p.outputFilePaths = android.WritablePaths{android.PathForModuleOut(ctx, filename)}
 
 		ip := installProperties{
 			filename:       filename,
@@ -452,7 +453,7 @@
 				filename = src.Base()
 				installDirPath = baseInstallDirPath
 			}
-			output := android.PathForModuleOut(ctx, filename).OutputPath
+			output := android.PathForModuleOut(ctx, filename)
 			ip := installProperties{
 				filename:       filename,
 				sourceFilePath: src,
@@ -472,7 +473,7 @@
 		if filename == "" {
 			filename = ctx.ModuleName()
 		}
-		p.outputFilePaths = android.OutputPaths{android.PathForModuleOut(ctx, filename).OutputPath}
+		p.outputFilePaths = android.WritablePaths{android.PathForModuleOut(ctx, filename)}
 		ip := installProperties{
 			filename:       filename,
 			sourceFilePath: p.sourceFilePaths[0],
@@ -500,7 +501,7 @@
 type installProperties struct {
 	filename       string
 	sourceFilePath android.Path
-	outputFilePath android.OutputPath
+	outputFilePath android.WritablePath
 	installDirPath android.InstallPath
 	symlinks       []string
 }
@@ -606,7 +607,7 @@
 
 	module.AddProperties(props...)
 	module.AddProperties(
-		&prebuiltEtcProperties{},
+		&PrebuiltEtcProperties{},
 		&prebuiltSubdirProperties{},
 	)
 
@@ -961,3 +962,13 @@
 	android.InitDefaultableModule(module)
 	return module
 }
+
+// prebuilt_tvconfig installs files in <partition>/tvconfig directory.
+func PrebuiltTvConfigFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "tvconfig")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	return module
+}
diff --git a/filesystem/avb_add_hash_footer.go b/filesystem/avb_add_hash_footer.go
index 469f1fb..9d4ba3e 100644
--- a/filesystem/avb_add_hash_footer.go
+++ b/filesystem/avb_add_hash_footer.go
@@ -29,7 +29,7 @@
 
 	properties avbAddHashFooterProperties
 
-	output     android.OutputPath
+	output     android.Path
 	installDir android.InstallPath
 }
 
@@ -97,8 +97,8 @@
 		return
 	}
 	input := android.PathForModuleSrc(ctx, proptools.String(a.properties.Src))
-	a.output = android.PathForModuleOut(ctx, a.installFileName()).OutputPath
-	builder.Command().Text("cp").Input(input).Output(a.output)
+	output := android.PathForModuleOut(ctx, a.installFileName())
+	builder.Command().Text("cp").Input(input).Output(output)
 
 	cmd := builder.Command().BuiltTool("avbtool").Text("add_hash_footer")
 
@@ -141,12 +141,13 @@
 		cmd.Flag(fmt.Sprintf(" --rollback_index %d", rollbackIndex))
 	}
 
-	cmd.FlagWithOutput("--image ", a.output)
+	cmd.FlagWithOutput("--image ", output)
 
 	builder.Build("avbAddHashFooter", fmt.Sprintf("avbAddHashFooter %s", ctx.ModuleName()))
 
 	a.installDir = android.PathForModuleInstall(ctx, "etc")
-	ctx.InstallFile(a.installDir, a.installFileName(), a.output)
+	ctx.InstallFile(a.installDir, a.installFileName(), output)
+	a.output = output
 }
 
 func addAvbProp(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, prop avbProp) {
diff --git a/filesystem/avb_gen_vbmeta_image.go b/filesystem/avb_gen_vbmeta_image.go
index a7fd782..0669a2c 100644
--- a/filesystem/avb_gen_vbmeta_image.go
+++ b/filesystem/avb_gen_vbmeta_image.go
@@ -28,7 +28,7 @@
 
 	properties avbGenVbmetaImageProperties
 
-	output     android.OutputPath
+	output     android.Path
 	installDir android.InstallPath
 }
 
@@ -78,11 +78,12 @@
 	}
 	cmd.FlagWithArg("--salt ", proptools.String(a.properties.Salt))
 
-	a.output = android.PathForModuleOut(ctx, a.installFileName()).OutputPath
-	cmd.FlagWithOutput("--output_vbmeta_image ", a.output)
+	output := android.PathForModuleOut(ctx, a.installFileName())
+	cmd.FlagWithOutput("--output_vbmeta_image ", output)
 	builder.Build("avbGenVbmetaImage", fmt.Sprintf("avbGenVbmetaImage %s", ctx.ModuleName()))
 
-	ctx.SetOutputFiles([]android.Path{a.output}, "")
+	ctx.SetOutputFiles([]android.Path{output}, "")
+	a.output = output
 }
 
 var _ android.AndroidMkEntriesProvider = (*avbGenVbmetaImage)(nil)
diff --git a/filesystem/bootimg.go b/filesystem/bootimg.go
index 9d93925..c9bd617 100644
--- a/filesystem/bootimg.go
+++ b/filesystem/bootimg.go
@@ -34,7 +34,7 @@
 
 	properties bootimgProperties
 
-	output     android.OutputPath
+	output     android.Path
 	installDir android.InstallPath
 }
 
@@ -115,20 +115,20 @@
 	vendor := proptools.Bool(b.properties.Vendor_boot)
 	unsignedOutput := b.buildBootImage(ctx, vendor)
 
+	output := unsignedOutput
 	if proptools.Bool(b.properties.Use_avb) {
-		b.output = b.signImage(ctx, unsignedOutput)
-	} else {
-		b.output = unsignedOutput
+		output = b.signImage(ctx, unsignedOutput)
 	}
 
 	b.installDir = android.PathForModuleInstall(ctx, "etc")
-	ctx.InstallFile(b.installDir, b.installFileName(), b.output)
+	ctx.InstallFile(b.installDir, b.installFileName(), output)
 
-	ctx.SetOutputFiles([]android.Path{b.output}, "")
+	ctx.SetOutputFiles([]android.Path{output}, "")
+	b.output = output
 }
 
-func (b *bootimg) buildBootImage(ctx android.ModuleContext, vendor bool) android.OutputPath {
-	output := android.PathForModuleOut(ctx, "unsigned", b.installFileName()).OutputPath
+func (b *bootimg) buildBootImage(ctx android.ModuleContext, vendor bool) android.Path {
+	output := android.PathForModuleOut(ctx, "unsigned", b.installFileName())
 
 	builder := android.NewRuleBuilder(pctx, ctx)
 	cmd := builder.Command().BuiltTool("mkbootimg")
@@ -215,10 +215,10 @@
 	return output
 }
 
-func (b *bootimg) signImage(ctx android.ModuleContext, unsignedImage android.OutputPath) android.OutputPath {
+func (b *bootimg) signImage(ctx android.ModuleContext, unsignedImage android.Path) android.Path {
 	propFile, toolDeps := b.buildPropFile(ctx)
 
-	output := android.PathForModuleOut(ctx, b.installFileName()).OutputPath
+	output := android.PathForModuleOut(ctx, b.installFileName())
 	builder := android.NewRuleBuilder(pctx, ctx)
 	builder.Command().Text("cp").Input(unsignedImage).Output(output)
 	builder.Command().BuiltTool("verity_utils").
@@ -239,7 +239,7 @@
 	return sha1sum(input)
 }
 
-func (b *bootimg) buildPropFile(ctx android.ModuleContext) (propFile android.OutputPath, toolDeps android.Paths) {
+func (b *bootimg) buildPropFile(ctx android.ModuleContext) (android.Path, android.Paths) {
 	var sb strings.Builder
 	var deps android.Paths
 	addStr := func(name string, value string) {
@@ -261,7 +261,7 @@
 	addStr("partition_name", partitionName)
 	addStr("avb_salt", b.salt())
 
-	propFile = android.PathForModuleOut(ctx, "prop").OutputPath
+	propFile := android.PathForModuleOut(ctx, "prop")
 	android.WriteFileRule(ctx, propFile, sb.String())
 	return propFile, deps
 }
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index 29e5ec3..c346770 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -52,10 +52,10 @@
 
 	properties FilesystemProperties
 
-	output     android.OutputPath
+	output     android.Path
 	installDir android.InstallPath
 
-	fileListFile android.OutputPath
+	fileListFile android.Path
 
 	// Keeps the entries installed from this filesystem
 	entries []string
@@ -301,13 +301,24 @@
 	}
 	if proptools.Bool(f.properties.Is_auto_generated) { // TODO (spandandas): Remove this.
 		pt := f.PartitionType()
-		return pt == "ramdisk" || ps.Partition() == pt
+		return ps.Partition() == pt || strings.HasPrefix(ps.Partition(), pt+"/")
 	}
 	return true
 }
 
 func (f *filesystem) ModifyPackagingSpec(ps *android.PackagingSpec) {
-	// do nothing by default
+	// Sometimes, android.modulePartition() returns a path with >1 path components.
+	// This makes the partition field of packagingSpecs have multiple components, like
+	// "system/product". Right now, the filesystem module doesn't look at the partition field
+	// when deciding what path to install the file under, only the RelPathInPackage field, so
+	// we move the later path components from partition to relPathInPackage. This should probably
+	// be revisited in the future.
+	prefix := f.PartitionType() + "/"
+	if strings.HasPrefix(ps.Partition(), prefix) {
+		subPartition := strings.TrimPrefix(ps.Partition(), prefix)
+		ps.SetPartition(f.PartitionType())
+		ps.SetRelPathInPackage(filepath.Join(subPartition, ps.RelPathInPackage()))
+	}
 }
 
 var pctx = android.NewPackageContext("android/soong/filesystem")
@@ -329,19 +340,20 @@
 	ctx.InstallFile(f.installDir, f.installFileName(), f.output)
 	ctx.SetOutputFiles([]android.Path{f.output}, "")
 
-	f.fileListFile = android.PathForModuleOut(ctx, "fileList").OutputPath
-	android.WriteFileRule(ctx, f.fileListFile, f.installedFilesList())
+	fileListFile := android.PathForModuleOut(ctx, "fileList")
+	android.WriteFileRule(ctx, fileListFile, f.installedFilesList())
 
 	android.SetProvider(ctx, FilesystemProvider, FilesystemInfo{
-		FileListFile: f.fileListFile,
+		FileListFile: fileListFile,
 	})
+	f.fileListFile = fileListFile
 
 	if proptools.Bool(f.properties.Unchecked_module) {
 		ctx.UncheckedModule()
 	}
 }
 
-func (f *filesystem) appendToEntry(ctx android.ModuleContext, installedFile android.OutputPath) {
+func (f *filesystem) appendToEntry(ctx android.ModuleContext, installedFile android.Path) {
 	partitionBaseDir := android.PathForModuleOut(ctx, "root", f.partitionName()).String() + "/"
 
 	relPath, inTargetPartition := strings.CutPrefix(installedFile.String(), partitionBaseDir)
@@ -432,7 +444,7 @@
 	builder.Command().Textf("cp -prf %s/* %s", rebasedDir, installPath)
 }
 
-func (f *filesystem) buildImageUsingBuildImage(ctx android.ModuleContext) android.OutputPath {
+func (f *filesystem) buildImageUsingBuildImage(ctx android.ModuleContext) android.Path {
 	rootDir := android.PathForModuleOut(ctx, "root").OutputPath
 	rebasedDir := rootDir
 	if f.properties.Base_dir != nil {
@@ -461,7 +473,7 @@
 		FlagWithArg("--out_system=", rootDir.String()+"/system")
 
 	propFile, toolDeps := f.buildPropFile(ctx)
-	output := android.PathForModuleOut(ctx, f.installFileName()).OutputPath
+	output := android.PathForModuleOut(ctx, f.installFileName())
 	builder.Command().BuiltTool("build_image").
 		Text(rootDir.String()). // input directory
 		Input(propFile).
@@ -475,14 +487,14 @@
 	return output
 }
 
-func (f *filesystem) buildFileContexts(ctx android.ModuleContext) android.OutputPath {
+func (f *filesystem) buildFileContexts(ctx android.ModuleContext) android.Path {
 	builder := android.NewRuleBuilder(pctx, ctx)
 	fcBin := android.PathForModuleOut(ctx, "file_contexts.bin")
 	builder.Command().BuiltTool("sefcontext_compile").
 		FlagWithOutput("-o ", fcBin).
 		Input(android.PathForModuleSrc(ctx, proptools.String(f.properties.File_contexts)))
 	builder.Build("build_filesystem_file_contexts", fmt.Sprintf("Creating filesystem file contexts for %s", f.BaseModuleName()))
-	return fcBin.OutputPath
+	return fcBin
 }
 
 // Calculates avb_salt from entry list (sorted) for deterministic output.
@@ -490,7 +502,7 @@
 	return sha1sum(f.entries)
 }
 
-func (f *filesystem) buildPropFile(ctx android.ModuleContext) (propFile android.OutputPath, toolDeps android.Paths) {
+func (f *filesystem) buildPropFile(ctx android.ModuleContext) (android.Path, android.Paths) {
 	var deps android.Paths
 	var propFileString strings.Builder
 	addStr := func(name string, value string) {
@@ -586,7 +598,7 @@
 	}
 	f.checkFsTypePropertyError(ctx, fst, fsTypeStr(fst))
 
-	propFile = android.PathForModuleOut(ctx, "prop").OutputPath
+	propFile := android.PathForModuleOut(ctx, "prop")
 	android.WriteFileRuleVerbatim(ctx, propFile, propFileString.String())
 	return propFile, deps
 }
@@ -611,7 +623,7 @@
 	}
 }
 
-func (f *filesystem) buildCpioImage(ctx android.ModuleContext, compressed bool) android.OutputPath {
+func (f *filesystem) buildCpioImage(ctx android.ModuleContext, compressed bool) android.Path {
 	if proptools.Bool(f.properties.Use_avb) {
 		ctx.PropertyErrorf("use_avb", "signing compresed cpio image using avbtool is not supported."+
 			"Consider adding this to bootimg module and signing the entire boot image.")
@@ -643,7 +655,7 @@
 	f.filesystemBuilder.BuildLinkerConfigFile(ctx, builder, rebasedDir)
 	f.copyFilesToProductOut(ctx, builder, rebasedDir)
 
-	output := android.PathForModuleOut(ctx, f.installFileName()).OutputPath
+	output := android.PathForModuleOut(ctx, f.installFileName())
 	cmd := builder.Command().
 		BuiltTool("mkbootfs").
 		Text(rootDir.String()) // input directory
diff --git a/filesystem/fsverity_metadata.go b/filesystem/fsverity_metadata.go
index 199c845..3119f2f 100644
--- a/filesystem/fsverity_metadata.go
+++ b/filesystem/fsverity_metadata.go
@@ -33,7 +33,7 @@
 	Libs []string `android:"path"`
 }
 
-func (f *filesystem) writeManifestGeneratorListFile(ctx android.ModuleContext, outputPath android.OutputPath, matchedSpecs []android.PackagingSpec, rebasedDir android.OutputPath) {
+func (f *filesystem) writeManifestGeneratorListFile(ctx android.ModuleContext, outputPath android.WritablePath, matchedSpecs []android.PackagingSpec, rebasedDir android.OutputPath) {
 	var buf strings.Builder
 	for _, spec := range matchedSpecs {
 		buf.WriteString(rebasedDir.Join(ctx, spec.RelPathInPackage()).String())
@@ -113,12 +113,12 @@
 	f.appendToEntry(ctx, manifestPbPath)
 
 	manifestGeneratorListPath := android.PathForModuleOut(ctx, "fsverity_manifest.list")
-	f.writeManifestGeneratorListFile(ctx, manifestGeneratorListPath.OutputPath, matchedSpecs, rebasedDir)
+	f.writeManifestGeneratorListFile(ctx, manifestGeneratorListPath, matchedSpecs, rebasedDir)
 	sb.WriteRune('@')
 	sb.WriteString(manifestGeneratorListPath.String())
 	sb.WriteRune('\n')
 	cmd.Implicit(manifestGeneratorListPath)
-	f.appendToEntry(ctx, manifestGeneratorListPath.OutputPath)
+	f.appendToEntry(ctx, manifestGeneratorListPath)
 
 	// STEP 2-2: generate BuildManifest.apk (unsigned)
 	aapt2Path := ctx.Config().HostToolPath(ctx, "aapt2")
diff --git a/filesystem/logical_partition.go b/filesystem/logical_partition.go
index 988a57b..d0888a9 100644
--- a/filesystem/logical_partition.go
+++ b/filesystem/logical_partition.go
@@ -32,7 +32,7 @@
 
 	properties logicalPartitionProperties
 
-	output     android.OutputPath
+	output     android.Path
 	installDir android.InstallPath
 }
 
@@ -95,11 +95,14 @@
 	builder := android.NewRuleBuilder(pctx, ctx)
 
 	// Sparse the filesystem images and calculate their sizes
-	sparseImages := make(map[string]android.OutputPath)
-	sparseImageSizes := make(map[string]android.OutputPath)
+	sparseImages := make(map[string]android.Path)
+	sparseImageSizes := make(map[string]android.Path)
 
 	sparsePartitions := func(partitions []partitionProperties) {
 		for _, part := range partitions {
+			if part.Filesystem == nil {
+				continue
+			}
 			sparseImg, sizeTxt := sparseFilesystem(ctx, part, builder)
 			pName := proptools.String(part.Name)
 			sparseImages[pName] = sparseImg
@@ -185,31 +188,29 @@
 		addPartitionsToGroup(group.Partitions, gName)
 	}
 
-	l.output = android.PathForModuleOut(ctx, l.installFileName()).OutputPath
-	cmd.FlagWithOutput("--output=", l.output)
+	output := android.PathForModuleOut(ctx, l.installFileName())
+	cmd.FlagWithOutput("--output=", output)
 
 	builder.Build("build_logical_partition", fmt.Sprintf("Creating %s", l.BaseModuleName()))
 
 	l.installDir = android.PathForModuleInstall(ctx, "etc")
-	ctx.InstallFile(l.installDir, l.installFileName(), l.output)
+	ctx.InstallFile(l.installDir, l.installFileName(), output)
 
-	ctx.SetOutputFiles([]android.Path{l.output}, "")
+	ctx.SetOutputFiles([]android.Path{output}, "")
+	l.output = output
 }
 
 // Add a rule that converts the filesystem for the given partition to the given rule builder. The
 // path to the sparse file and the text file having the size of the partition are returned.
-func sparseFilesystem(ctx android.ModuleContext, p partitionProperties, builder *android.RuleBuilder) (sparseImg android.OutputPath, sizeTxt android.OutputPath) {
-	if p.Filesystem == nil {
-		return
-	}
-	img := android.PathForModuleSrc(ctx, proptools.String(p.Filesystem))
+func sparseFilesystem(ctx android.ModuleContext, p partitionProperties, builder *android.RuleBuilder) (android.Path, android.Path) {
+	img := android.PathForModuleSrc(ctx, *p.Filesystem)
 	name := proptools.String(p.Name)
-	sparseImg = android.PathForModuleOut(ctx, name+".img").OutputPath
 
+	sparseImg := android.PathForModuleOut(ctx, name+".img")
 	builder.Temporary(sparseImg)
 	builder.Command().BuiltTool("img2simg").Input(img).Output(sparseImg)
 
-	sizeTxt = android.PathForModuleOut(ctx, name+"-size.txt").OutputPath
+	sizeTxt := android.PathForModuleOut(ctx, name+"-size.txt")
 	builder.Temporary(sizeTxt)
 	builder.Command().BuiltTool("sparse_img").Flag("--get_partition_size").Input(sparseImg).
 		Text("| ").Text("tr").FlagWithArg("-d ", "'\n'").
diff --git a/filesystem/raw_binary.go b/filesystem/raw_binary.go
index ad36c29..707fba0 100644
--- a/filesystem/raw_binary.go
+++ b/filesystem/raw_binary.go
@@ -42,7 +42,7 @@
 
 	properties rawBinaryProperties
 
-	output     android.OutputPath
+	output     android.Path
 	installDir android.InstallPath
 }
 
@@ -71,7 +71,7 @@
 
 func (r *rawBinary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	inputFile := android.PathForModuleSrc(ctx, proptools.String(r.properties.Src))
-	outputFile := android.PathForModuleOut(ctx, r.installFileName()).OutputPath
+	outputFile := android.PathForModuleOut(ctx, r.installFileName())
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        toRawBinary,
@@ -83,11 +83,11 @@
 		},
 	})
 
-	r.output = outputFile
 	r.installDir = android.PathForModuleInstall(ctx, "etc")
-	ctx.InstallFile(r.installDir, r.installFileName(), r.output)
+	ctx.InstallFile(r.installDir, r.installFileName(), outputFile)
 
-	ctx.SetOutputFiles([]android.Path{r.output}, "")
+	ctx.SetOutputFiles([]android.Path{outputFile}, "")
+	r.output = outputFile
 }
 
 var _ android.AndroidMkEntriesProvider = (*rawBinary)(nil)
diff --git a/filesystem/system_image.go b/filesystem/system_image.go
index 4d176ae..d03eab4 100644
--- a/filesystem/system_image.go
+++ b/filesystem/system_image.go
@@ -18,7 +18,6 @@
 	"android/soong/android"
 	"android/soong/linkerconfig"
 
-	"path/filepath"
 	"strings"
 
 	"github.com/google/blueprint/proptools"
@@ -64,11 +63,3 @@
 		(ps.Partition() == "system" || ps.Partition() == "root" ||
 			strings.HasPrefix(ps.Partition(), "system/"))
 }
-
-func (s *systemImage) ModifyPackagingSpec(ps *android.PackagingSpec) {
-	if strings.HasPrefix(ps.Partition(), "system/") {
-		subPartition := strings.TrimPrefix(ps.Partition(), "system/")
-		ps.SetPartition("system")
-		ps.SetRelPathInPackage(filepath.Join(subPartition, ps.RelPathInPackage()))
-	}
-}
diff --git a/filesystem/vbmeta.go b/filesystem/vbmeta.go
index 6a3fc1f..6a47859 100644
--- a/filesystem/vbmeta.go
+++ b/filesystem/vbmeta.go
@@ -26,14 +26,25 @@
 
 func init() {
 	android.RegisterModuleType("vbmeta", VbmetaFactory)
+	pctx.HostBinToolVariable("avbtool", "avbtool")
 }
 
+var (
+	extractPublicKeyRule = pctx.AndroidStaticRule("avb_extract_public_key",
+		blueprint.RuleParams{
+			Command: `${avbtool} extract_public_key --key $in --output $out`,
+			CommandDeps: []string{
+				"${avbtool}",
+			},
+		})
+)
+
 type vbmeta struct {
 	android.ModuleBase
 
 	properties VbmetaProperties
 
-	output     android.OutputPath
+	output     android.Path
 	installDir android.InstallPath
 }
 
@@ -60,8 +71,15 @@
 	// have to be signed (use_avb: true).
 	Partitions proptools.Configurable[[]string]
 
-	// List of chained partitions that this vbmeta deletages the verification.
-	Chained_partitions []ChainedPartitionProperties
+	// Metadata about the chained partitions that this vbmeta delegates the verification.
+	// This is an alternative to chained_partitions, using chained_partitions instead is simpler
+	// in most cases. However, this property allows building this vbmeta partition without
+	// its chained partitions existing in this build.
+	Chained_partition_metadata []ChainedPartitionProperties
+
+	// List of chained partitions that this vbmeta delegates the verification. They are the
+	// names of other vbmeta modules.
+	Chained_partitions []string
 
 	// List of key-value pair of avb properties
 	Avb_properties []avbProperty
@@ -93,6 +111,20 @@
 	Private_key *string `android:"path"`
 }
 
+type vbmetaPartitionInfo struct {
+	// Name of the partition
+	Name string
+
+	// Rollback index location, non-negative int
+	RollbackIndexLocation int
+
+	// The path to the public key of the private key used to sign this partition. Derived from
+	// the private key.
+	PublicKey android.Path
+}
+
+var vbmetaPartitionProvider = blueprint.NewProvider[vbmetaPartitionInfo]()
+
 // vbmeta is the partition image that has the verification information for other partitions.
 func VbmetaFactory() android.Module {
 	module := &vbmeta{}
@@ -103,13 +135,18 @@
 
 type vbmetaDep struct {
 	blueprint.BaseDependencyTag
-	kind string
 }
 
-var vbmetaPartitionDep = vbmetaDep{kind: "partition"}
+type chainedPartitionDep struct {
+	blueprint.BaseDependencyTag
+}
+
+var vbmetaPartitionDep = vbmetaDep{}
+var vbmetaChainedPartitionDep = chainedPartitionDep{}
 
 func (v *vbmeta) DepsMutator(ctx android.BottomUpMutatorContext) {
 	ctx.AddDependency(ctx.Module(), vbmetaPartitionDep, v.properties.Partitions.GetOrDefault(ctx, nil)...)
+	ctx.AddDependency(ctx.Module(), vbmetaChainedPartitionDep, v.properties.Chained_partitions...)
 }
 
 func (v *vbmeta) installFileName() string {
@@ -124,10 +161,6 @@
 const vbmetaMaxSize = 64 * 1024
 
 func (v *vbmeta) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	extractedPublicKeys := v.extractPublicKeys(ctx)
-
-	v.output = android.PathForModuleOut(ctx, v.installFileName()).OutputPath
-
 	builder := android.NewRuleBuilder(pctx, ctx)
 	cmd := builder.Command().BuiltTool("avbtool").Text("make_vbmeta_image")
 
@@ -175,43 +208,99 @@
 		cmd.FlagWithInput("--include_descriptors_from_image ", signedImage)
 	}
 
-	for i, cp := range v.properties.Chained_partitions {
-		name := proptools.String(cp.Name)
+	seenRils := make(map[int]bool)
+	for _, cp := range ctx.GetDirectDepsWithTag(vbmetaChainedPartitionDep) {
+		info, ok := android.OtherModuleProvider(ctx, cp, vbmetaPartitionProvider)
+		if !ok {
+			ctx.PropertyErrorf("chained_partitions", "Expected all modules in chained_partitions to provide vbmetaPartitionProvider, but %s did not", cp.Name())
+			continue
+		}
+		if info.Name == "" {
+			ctx.PropertyErrorf("chained_partitions", "name must be specified")
+			continue
+		}
+
+		ril := info.RollbackIndexLocation
+		if ril < 0 {
+			ctx.PropertyErrorf("chained_partitions", "rollback index location must be 0, 1, 2, ...")
+			continue
+		} else if seenRils[ril] {
+			ctx.PropertyErrorf("chained_partitions", "Multiple chained partitions with the same rollback index location %d", ril)
+			continue
+		}
+		seenRils[ril] = true
+
+		publicKey := info.PublicKey
+		cmd.FlagWithArg("--chain_partition ", fmt.Sprintf("%s:%d:%s", info.Name, ril, publicKey.String()))
+		cmd.Implicit(publicKey)
+	}
+	for _, cpm := range v.properties.Chained_partition_metadata {
+		name := proptools.String(cpm.Name)
 		if name == "" {
 			ctx.PropertyErrorf("chained_partitions", "name must be specified")
 			continue
 		}
 
-		ril := proptools.IntDefault(cp.Rollback_index_location, i+1)
+		ril := proptools.IntDefault(cpm.Rollback_index_location, -1)
 		if ril < 0 {
-			ctx.PropertyErrorf("chained_partitions", "must be 0, 1, 2, ...")
+			ctx.PropertyErrorf("chained_partition_metadata", "rollback index location must be 0, 1, 2, ...")
+			continue
+		} else if seenRils[ril] {
+			ctx.PropertyErrorf("chained_partition_metadata", "Multiple chained partitions with the same rollback index location %d", ril)
+			continue
+		}
+		seenRils[ril] = true
+
+		var publicKey android.Path
+		if cpm.Public_key != nil {
+			publicKey = android.PathForModuleSrc(ctx, *cpm.Public_key)
+		} else if cpm.Private_key != nil {
+			privateKey := android.PathForModuleSrc(ctx, *cpm.Private_key)
+			extractedPublicKey := android.PathForModuleOut(ctx, "chained_metadata", name+".avbpubkey")
+			ctx.Build(pctx, android.BuildParams{
+				Rule:   extractPublicKeyRule,
+				Input:  privateKey,
+				Output: extractedPublicKey,
+			})
+			publicKey = extractedPublicKey
+		} else {
+			ctx.PropertyErrorf("public_key", "Either public_key or private_key must be specified")
 			continue
 		}
 
-		var publicKey android.Path
-		if cp.Public_key != nil {
-			publicKey = android.PathForModuleSrc(ctx, proptools.String(cp.Public_key))
-		} else {
-			publicKey = extractedPublicKeys[name]
-		}
 		cmd.FlagWithArg("--chain_partition ", fmt.Sprintf("%s:%d:%s", name, ril, publicKey.String()))
 		cmd.Implicit(publicKey)
 	}
 
-	cmd.FlagWithOutput("--output ", v.output)
+	output := android.PathForModuleOut(ctx, v.installFileName())
+	cmd.FlagWithOutput("--output ", output)
 
 	// libavb expects to be able to read the maximum vbmeta size, so we must provide a partition
 	// which matches this or the read will fail.
 	builder.Command().Text("truncate").
 		FlagWithArg("-s ", strconv.Itoa(vbmetaMaxSize)).
-		Output(v.output)
+		Output(output)
 
 	builder.Build("vbmeta", fmt.Sprintf("vbmeta %s", ctx.ModuleName()))
 
 	v.installDir = android.PathForModuleInstall(ctx, "etc")
-	ctx.InstallFile(v.installDir, v.installFileName(), v.output)
+	ctx.InstallFile(v.installDir, v.installFileName(), output)
 
-	ctx.SetOutputFiles([]android.Path{v.output}, "")
+	extractedPublicKey := android.PathForModuleOut(ctx, v.partitionName()+".avbpubkey")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   extractPublicKeyRule,
+		Input:  key,
+		Output: extractedPublicKey,
+	})
+
+	android.SetProvider(ctx, vbmetaPartitionProvider, vbmetaPartitionInfo{
+		Name:                  v.partitionName(),
+		RollbackIndexLocation: ril,
+		PublicKey:             extractedPublicKey,
+	})
+
+	ctx.SetOutputFiles([]android.Path{output}, "")
+	v.output = output
 }
 
 // Returns the embedded shell command that prints the rollback index
@@ -224,43 +313,6 @@
 	}
 }
 
-// Extract public keys from chained_partitions.private_key. The keys are indexed with the partition
-// name.
-func (v *vbmeta) extractPublicKeys(ctx android.ModuleContext) map[string]android.OutputPath {
-	result := make(map[string]android.OutputPath)
-
-	builder := android.NewRuleBuilder(pctx, ctx)
-	for _, cp := range v.properties.Chained_partitions {
-		if cp.Private_key == nil {
-			continue
-		}
-
-		name := proptools.String(cp.Name)
-		if name == "" {
-			ctx.PropertyErrorf("chained_partitions", "name must be specified")
-			continue
-		}
-
-		if _, ok := result[name]; ok {
-			ctx.PropertyErrorf("chained_partitions", "name %q is duplicated", name)
-			continue
-		}
-
-		privateKeyFile := android.PathForModuleSrc(ctx, proptools.String(cp.Private_key))
-		publicKeyFile := android.PathForModuleOut(ctx, name+".avbpubkey").OutputPath
-
-		builder.Command().
-			BuiltTool("avbtool").
-			Text("extract_public_key").
-			FlagWithInput("--key ", privateKeyFile).
-			FlagWithOutput("--output ", publicKeyFile)
-
-		result[name] = publicKeyFile
-	}
-	builder.Build("vbmeta_extract_public_key", fmt.Sprintf("Extract public keys for %s", ctx.ModuleName()))
-	return result
-}
-
 var _ android.AndroidMkProviderInfoProducer = (*vbmeta)(nil)
 
 func (v *vbmeta) PrepareAndroidMKProviderInfo(config android.Config) *android.AndroidMkProviderInfo {
diff --git a/fsgen/filesystem_creator.go b/fsgen/filesystem_creator.go
index 0a65c6c..c74579a 100644
--- a/fsgen/filesystem_creator.go
+++ b/fsgen/filesystem_creator.go
@@ -62,7 +62,9 @@
 	module.AddProperties(&module.properties)
 	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
 		generatedPrebuiltEtcModuleNames := createPrebuiltEtcModules(ctx)
-		createFsGenState(ctx, generatedPrebuiltEtcModuleNames)
+		avbpubkeyGenerated := createAvbpubkeyModule(ctx)
+		createFsGenState(ctx, generatedPrebuiltEtcModuleNames, avbpubkeyGenerated)
+		module.createAvbKeyFilegroups(ctx)
 		module.createInternalModules(ctx)
 	})
 
@@ -253,6 +255,51 @@
 	return true
 }
 
+// Creates filegroups for the files specified in BOARD_(partition_)AVB_KEY_PATH
+func (f *filesystemCreator) createAvbKeyFilegroups(ctx android.LoadHookContext) {
+	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+	var files []string
+
+	if len(partitionVars.BoardAvbKeyPath) > 0 {
+		files = append(files, partitionVars.BoardAvbKeyPath)
+	}
+	for _, partition := range android.SortedKeys(partitionVars.PartitionQualifiedVariables) {
+		specificPartitionVars := partitionVars.PartitionQualifiedVariables[partition]
+		if len(specificPartitionVars.BoardAvbKeyPath) > 0 {
+			files = append(files, specificPartitionVars.BoardAvbKeyPath)
+		}
+	}
+
+	fsGenState := ctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
+	for _, file := range files {
+		if _, ok := fsGenState.avbKeyFilegroups[file]; ok {
+			continue
+		}
+		if file == "external/avb/test/data/testkey_rsa4096.pem" {
+			// There already exists a checked-in filegroup for this commonly-used key, just use that
+			fsGenState.avbKeyFilegroups[file] = "avb_testkey_rsa4096"
+			continue
+		}
+		dir := filepath.Dir(file)
+		base := filepath.Base(file)
+		name := fmt.Sprintf("avb_key_%x", strings.ReplaceAll(file, "/", "_"))
+		ctx.CreateModuleInDirectory(
+			android.FileGroupFactory,
+			dir,
+			&struct {
+				Name       *string
+				Srcs       []string
+				Visibility []string
+			}{
+				Name:       proptools.StringPtr(name),
+				Srcs:       []string{base},
+				Visibility: []string{"//visibility:public"},
+			},
+		)
+		fsGenState.avbKeyFilegroups[file] = name
+	}
+}
+
 // createPrebuiltKernelModules creates `prebuilt_kernel_modules`. These modules will be added to deps of the
 // autogenerated *_dlkm filsystem modules. Each _dlkm partition should have a single prebuilt_kernel_modules dependency.
 // This ensures that the depmod artifacts (modules.* installed in /lib/modules/) are generated with a complete view.
@@ -418,21 +465,41 @@
 }
 
 func generateFsProps(ctx android.EarlyModuleContext, partitionType string) (*filesystem.FilesystemProperties, bool) {
+	fsGenState := ctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
 	fsProps := &filesystem.FilesystemProperties{}
 
 	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
-	var specificPartitionVars android.PartitionQualifiedVariablesType
 	var boardAvbEnable bool
+	var boardAvbKeyPath string
+	var boardAvbAlgorithm string
+	var boardAvbRollbackIndex string
 	var fsType string
 	if strings.Contains(partitionType, "ramdisk") {
 		fsType = "compressed_cpio"
 	} else {
-		specificPartitionVars = partitionVars.PartitionQualifiedVariables[partitionType]
-		boardAvbEnable = partitionVars.BoardAvbEnable
+		specificPartitionVars := partitionVars.PartitionQualifiedVariables[partitionType]
 		fsType = specificPartitionVars.BoardFileSystemType
+		boardAvbEnable = partitionVars.BoardAvbEnable
+		boardAvbKeyPath = specificPartitionVars.BoardAvbKeyPath
+		boardAvbAlgorithm = specificPartitionVars.BoardAvbAlgorithm
+		boardAvbRollbackIndex = specificPartitionVars.BoardAvbRollbackIndex
+		if boardAvbEnable {
+			if boardAvbKeyPath == "" {
+				boardAvbKeyPath = partitionVars.BoardAvbKeyPath
+			}
+			if boardAvbAlgorithm == "" {
+				boardAvbAlgorithm = partitionVars.BoardAvbAlgorithm
+			}
+			if boardAvbRollbackIndex == "" {
+				boardAvbRollbackIndex = partitionVars.BoardAvbRollbackIndex
+			}
+		}
+		if fsType == "" {
+			fsType = "ext4" //default
+		}
 	}
-	if fsType == "" {
-		fsType = "ext4" //default
+	if boardAvbKeyPath != "" {
+		boardAvbKeyPath = ":" + fsGenState.avbKeyFilegroups[boardAvbKeyPath]
 	}
 
 	fsProps.Type = proptools.StringPtr(fsType)
@@ -448,11 +515,11 @@
 	// BOARD_AVB_ENABLE
 	fsProps.Use_avb = proptools.BoolPtr(boardAvbEnable)
 	// BOARD_AVB_KEY_PATH
-	fsProps.Avb_private_key = proptools.StringPtr(specificPartitionVars.BoardAvbKeyPath)
+	fsProps.Avb_private_key = proptools.StringPtr(boardAvbKeyPath)
 	// BOARD_AVB_ALGORITHM
-	fsProps.Avb_algorithm = proptools.StringPtr(specificPartitionVars.BoardAvbAlgorithm)
+	fsProps.Avb_algorithm = proptools.StringPtr(boardAvbAlgorithm)
 	// BOARD_AVB_SYSTEM_ROLLBACK_INDEX
-	if rollbackIndex, err := strconv.ParseInt(specificPartitionVars.BoardAvbRollbackIndex, 10, 64); err == nil {
+	if rollbackIndex, err := strconv.ParseInt(boardAvbRollbackIndex, 10, 64); err == nil {
 		fsProps.Rollback_index = proptools.Int64Ptr(rollbackIndex)
 	}
 
@@ -591,8 +658,10 @@
 	}
 
 	baseProps := generateBaseProps(proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), partitionType)))
-	deps := ctx.Config().Get(fsGenStateOnceKey).(*FsGenState).fsDeps[partitionType]
-	depProps := generateDepStruct(*deps)
+	fsGenState := ctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
+	deps := fsGenState.fsDeps[partitionType]
+	highPriorityDeps := fsGenState.generatedPrebuiltEtcModuleNames
+	depProps := generateDepStruct(*deps, highPriorityDeps)
 
 	result, err := proptools.RepackProperties([]interface{}{baseProps, fsProps, depProps})
 	if err != nil {
diff --git a/fsgen/filesystem_creator_test.go b/fsgen/filesystem_creator_test.go
index 199eaad..fe4a403 100644
--- a/fsgen/filesystem_creator_test.go
+++ b/fsgen/filesystem_creator_test.go
@@ -16,6 +16,7 @@
 
 import (
 	"android/soong/android"
+	"android/soong/etc"
 	"android/soong/filesystem"
 	"android/soong/java"
 	"testing"
@@ -46,6 +47,12 @@
 		}),
 		android.FixtureMergeMockFs(android.MockFS{
 			"external/avb/test/data/testkey_rsa4096.pem": nil,
+			"external/avb/test/Android.bp": []byte(`
+			filegroup {
+				name: "avb_testkey_rsa4096",
+				srcs: ["data/testkey_rsa4096.pem"],
+			}
+			`),
 			"build/soong/fsgen/Android.bp": []byte(`
 			soong_filesystem_creator {
 				name: "foo",
@@ -65,8 +72,8 @@
 	)
 	android.AssertStringEquals(
 		t,
-		"Property expected to match the product variable 'BOARD_AVB_KEY_PATH'",
-		"external/avb/test/data/testkey_rsa4096.pem",
+		"Property the avb_private_key property to be set to the existing filegroup",
+		":avb_testkey_rsa4096",
 		proptools.String(fooSystem.FsProps().Avb_private_key),
 	)
 	android.AssertStringEquals(
@@ -253,3 +260,129 @@
 	_, libFooInDeps := (*resolvedSystemDeps)["libfoo"]
 	android.AssertBoolEquals(t, "libfoo should not appear in deps because it has been overridden by libbaz. The latter is a required dep of libbar, which is listed in PRODUCT_PACKAGES", false, libFooInDeps)
 }
+
+func TestPrebuiltEtcModuleGen(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		android.PrepareForIntegrationTestWithAndroid,
+		android.PrepareForTestWithAndroidBuildComponents,
+		android.PrepareForTestWithAllowMissingDependencies,
+		filesystem.PrepareForTestWithFilesystemBuildComponents,
+		prepareForTestWithFsgenBuildComponents,
+		android.FixtureModifyConfig(func(config android.Config) {
+			config.TestProductVariables.PartitionVarsForSoongMigrationOnlyDoNotUse.ProductCopyFiles = []string{
+				"frameworks/base/config/preloaded-classes:system/etc/preloaded-classes",
+				"frameworks/base/data/keyboards/Vendor_0079_Product_0011.kl:system/usr/keylayout/subdir/Vendor_0079_Product_0011.kl",
+				"frameworks/base/data/keyboards/Vendor_0079_Product_18d4.kl:system/usr/keylayout/subdir/Vendor_0079_Product_18d4.kl",
+				"some/non/existing/file.txt:system/etc/file.txt",
+				"device/sample/etc/apns-full-conf.xml:product/etc/apns-conf.xml:google",
+				"device/sample/etc/apns-full-conf.xml:product/etc/apns-conf-2.xml",
+			}
+			config.TestProductVariables.PartitionVarsForSoongMigrationOnlyDoNotUse.PartitionQualifiedVariables =
+				map[string]android.PartitionQualifiedVariablesType{
+					"system": {
+						BoardFileSystemType: "ext4",
+					},
+				}
+		}),
+		android.FixtureMergeMockFs(android.MockFS{
+			"external/avb/test/data/testkey_rsa4096.pem": nil,
+			"build/soong/fsgen/Android.bp": []byte(`
+			soong_filesystem_creator {
+				name: "foo",
+			}
+			`),
+			"frameworks/base/config/preloaded-classes":                   nil,
+			"frameworks/base/data/keyboards/Vendor_0079_Product_0011.kl": nil,
+			"frameworks/base/data/keyboards/Vendor_0079_Product_18d4.kl": nil,
+			"device/sample/etc/apns-full-conf.xml":                       nil,
+		}),
+	).RunTest(t)
+
+	checkModuleProp := func(m android.Module, matcher func(actual interface{}) bool) bool {
+		for _, prop := range m.GetProperties() {
+
+			if matcher(prop) {
+				return true
+			}
+		}
+		return false
+	}
+
+	// check generated prebuilt_* module type install path and install partition
+	generatedModule := result.ModuleForTests("system-frameworks_base_config-etc-0", "android_arm64_armv8-a").Module()
+	etcModule, _ := generatedModule.(*etc.PrebuiltEtc)
+	android.AssertStringEquals(
+		t,
+		"module expected to have etc install path",
+		"etc",
+		etcModule.BaseDir(),
+	)
+	android.AssertBoolEquals(
+		t,
+		"module expected to be installed in system partition",
+		true,
+		!generatedModule.InstallInProduct() &&
+			!generatedModule.InstallInVendor() &&
+			!generatedModule.InstallInSystemExt(),
+	)
+
+	// check generated prebuilt_* module specifies correct relative_install_path property
+	generatedModule = result.ModuleForTests("system-frameworks_base_data_keyboards-usr_keylayout_subdir-0", "android_arm64_armv8-a").Module()
+	etcModule, _ = generatedModule.(*etc.PrebuiltEtc)
+	android.AssertStringEquals(
+		t,
+		"module expected to set correct relative_install_path properties",
+		"subdir",
+		etcModule.SubDir(),
+	)
+
+	// check that prebuilt_* module is not generated for non existing source file
+	android.AssertPanicMessageContains(
+		t,
+		"prebuilt_* module not generated for non existing source file",
+		"failed to find module \"system-some_non_existing-etc-0\"",
+		func() { result.ModuleForTests("system-some_non_existing-etc-0", "android_arm64_armv8-a") },
+	)
+
+	// check that duplicate src file can exist in PRODUCT_COPY_FILES and generates separate modules
+	generatedModule0 := result.ModuleForTests("product-device_sample_etc-etc-0", "android_arm64_armv8-a").Module()
+	generatedModule1 := result.ModuleForTests("product-device_sample_etc-etc-1", "android_arm64_armv8-a").Module()
+
+	// check that generated prebuilt_* module sets correct srcs and dsts property
+	eval := generatedModule0.ConfigurableEvaluator(android.PanickingConfigAndErrorContext(result.TestContext))
+	android.AssertBoolEquals(
+		t,
+		"module expected to set correct srcs and dsts properties",
+		true,
+		checkModuleProp(generatedModule0, func(actual interface{}) bool {
+			if p, ok := actual.(*etc.PrebuiltEtcProperties); ok {
+				srcs := p.Srcs.GetOrDefault(eval, nil)
+				dsts := p.Dsts.GetOrDefault(eval, nil)
+				return len(srcs) == 1 &&
+					srcs[0] == "apns-full-conf.xml" &&
+					len(dsts) == 1 &&
+					dsts[0] == "apns-conf.xml"
+			}
+			return false
+		}),
+	)
+
+	// check that generated prebuilt_* module sets correct srcs and dsts property
+	eval = generatedModule1.ConfigurableEvaluator(android.PanickingConfigAndErrorContext(result.TestContext))
+	android.AssertBoolEquals(
+		t,
+		"module expected to set correct srcs and dsts properties",
+		true,
+		checkModuleProp(generatedModule1, func(actual interface{}) bool {
+			if p, ok := actual.(*etc.PrebuiltEtcProperties); ok {
+				srcs := p.Srcs.GetOrDefault(eval, nil)
+				dsts := p.Dsts.GetOrDefault(eval, nil)
+				return len(srcs) == 1 &&
+					srcs[0] == "apns-full-conf.xml" &&
+					len(dsts) == 1 &&
+					dsts[0] == "apns-conf-2.xml"
+			}
+			return false
+		}),
+	)
+}
diff --git a/fsgen/fsgen_mutators.go b/fsgen/fsgen_mutators.go
index e9fd513..7b30264 100644
--- a/fsgen/fsgen_mutators.go
+++ b/fsgen/fsgen_mutators.go
@@ -66,6 +66,10 @@
 	fsDepsMutex sync.Mutex
 	// Map of _all_ soong module names to their corresponding installation properties
 	moduleToInstallationProps map[string]installationProperties
+	// List of prebuilt_* modules that are autogenerated.
+	generatedPrebuiltEtcModuleNames []string
+	// Mapping from a path to an avb key to the name of a filegroup module that contains it
+	avbKeyFilegroups map[string]string
 }
 
 type installationProperties struct {
@@ -110,13 +114,13 @@
 	return generatedPartitions
 }
 
-func createFsGenState(ctx android.LoadHookContext, generatedPrebuiltEtcModuleNames []string) *FsGenState {
+func createFsGenState(ctx android.LoadHookContext, generatedPrebuiltEtcModuleNames []string, avbpubkeyGenerated bool) *FsGenState {
 	return ctx.Config().Once(fsGenStateOnceKey, func() interface{} {
 		partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
 		candidates := android.FirstUniqueStrings(android.Concat(partitionVars.ProductPackages, partitionVars.ProductPackagesDebug))
 		candidates = android.Concat(candidates, generatedPrebuiltEtcModuleNames)
 
-		return &FsGenState{
+		fsGenState := FsGenState{
 			depCandidates: candidates,
 			fsDeps: map[string]*multilibDeps{
 				// These additional deps are added according to the cuttlefish system image bp.
@@ -174,9 +178,17 @@
 				},
 				"ramdisk": {},
 			},
-			fsDepsMutex:               sync.Mutex{},
-			moduleToInstallationProps: map[string]installationProperties{},
+			fsDepsMutex:                     sync.Mutex{},
+			moduleToInstallationProps:       map[string]installationProperties{},
+			generatedPrebuiltEtcModuleNames: generatedPrebuiltEtcModuleNames,
+			avbKeyFilegroups:                map[string]string{},
 		}
+
+		if avbpubkeyGenerated {
+			(*fsGenState.fsDeps["product"])["system_other_avbpubkey"] = defaultDepCandidateProps(ctx.Config())
+		}
+
+		return &fsGenState
 	}).(*FsGenState)
 }
 
@@ -278,7 +290,7 @@
 	soongGeneratedPartitionMap := getAllSoongGeneratedPartitionNames(mctx.Config(), fsGenState.soongGeneratedPartitions)
 	m := mctx.Module()
 	if partition, ok := soongGeneratedPartitionMap[m.Name()]; ok {
-		depsStruct := generateDepStruct(*fsDeps[partition])
+		depsStruct := generateDepStruct(*fsDeps[partition], fsGenState.generatedPrebuiltEtcModuleNames)
 		if err := proptools.AppendMatchingProperties(m.GetProperties(), depsStruct, nil); err != nil {
 			mctx.ModuleErrorf(err.Error())
 		}
@@ -337,12 +349,12 @@
 	return false
 }
 
-func generateDepStruct(deps map[string]*depCandidateProps) *packagingPropsStruct {
+func generateDepStruct(deps map[string]*depCandidateProps, highPriorityDeps []string) *packagingPropsStruct {
 	depsStruct := packagingPropsStruct{}
 	for depName, depProps := range deps {
 		bitness := getBitness(depProps.Arch)
 		fullyQualifiedDepName := fullyQualifiedModuleName(depName, depProps.Namespace)
-		if isHighPriorityDep(depName) {
+		if android.InList(depName, highPriorityDeps) {
 			depsStruct.High_priority_deps = append(depsStruct.High_priority_deps, fullyQualifiedDepName)
 		} else if android.InList("32", bitness) && android.InList("64", bitness) {
 			// If both 32 and 64 bit variants are enabled for this module
@@ -377,6 +389,7 @@
 	depsStruct.Multilib.Prefer32.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Prefer32.Deps)
 	depsStruct.Multilib.Both.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Both.Deps)
 	depsStruct.Multilib.Common.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Common.Deps)
+	depsStruct.High_priority_deps = android.SortedUniqueStrings(depsStruct.High_priority_deps)
 
 	return &depsStruct
 }
diff --git a/fsgen/prebuilt_etc_modules_gen.go b/fsgen/prebuilt_etc_modules_gen.go
index cbcd4a1..efbc462 100644
--- a/fsgen/prebuilt_etc_modules_gen.go
+++ b/fsgen/prebuilt_etc_modules_gen.go
@@ -100,10 +100,15 @@
 			ctx.ModuleErrorf("PRODUCT_COPY_FILES must follow the format \"src:dest\", got: %s", copyFilePair)
 		}
 		src, dest := srcDestList[0], srcDestList[1]
+
+		// Some downstream branches use absolute path as entries in PRODUCT_COPY_FILES.
+		// Convert them to relative path from top and check if they do not escape the tree root.
+		relSrc := android.ToRelativeSourcePath(ctx, src)
+
 		if _, ok := seen[dest]; !ok {
-			if optionalPath := android.ExistentPathForSource(ctx, src); optionalPath.Valid() {
+			if optionalPath := android.ExistentPathForSource(ctx, relSrc); optionalPath.Valid() {
 				seen[dest] = true
-				filtered[src] = append(filtered[src], dest)
+				filtered[relSrc] = append(filtered[relSrc], dest)
 			}
 		}
 	}
@@ -194,6 +199,7 @@
 		"res":             etc.PrebuiltResFactory,
 		"rfs":             etc.PrebuiltRfsFactory,
 		"tts":             etc.PrebuiltVoicepackFactory,
+		"tvconfig":        etc.PrebuiltTvConfigFactory,
 		"tvservice":       etc.PrebuiltTvServiceFactory,
 		"usr/share":       etc.PrebuiltUserShareFactory,
 		"usr/hyphen-data": etc.PrebuiltUserHyphenDataFactory,
@@ -273,7 +279,8 @@
 
 	for fileIndex := range maxLen {
 		srcTuple := []srcBaseFileInstallBaseFileTuple{}
-		for _, groupedDestFile := range groupedDestFiles {
+		for _, srcFile := range android.SortedKeys(groupedDestFiles) {
+			groupedDestFile := groupedDestFiles[srcFile]
 			if len(groupedDestFile) > fileIndex {
 				srcTuple = append(srcTuple, groupedDestFile[fileIndex])
 			}
@@ -343,3 +350,28 @@
 
 	return ret
 }
+
+func createAvbpubkeyModule(ctx android.LoadHookContext) bool {
+	avbKeyPath := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.BoardAvbKeyPath
+	if avbKeyPath == "" {
+		return false
+	}
+	ctx.CreateModuleInDirectory(
+		etc.AvbpubkeyModuleFactory,
+		".",
+		&struct {
+			Name             *string
+			Product_specific *bool
+			Private_key      *string
+			No_full_install  *bool
+			Visibility       []string
+		}{
+			Name:             proptools.StringPtr("system_other_avbpubkey"),
+			Product_specific: proptools.BoolPtr(true),
+			Private_key:      proptools.StringPtr(avbKeyPath),
+			No_full_install:  proptools.BoolPtr(true),
+			Visibility:       []string{"//visibility:public"},
+		},
+	)
+	return true
+}
diff --git a/fsgen/vbmeta_partitions.go b/fsgen/vbmeta_partitions.go
index f7b4638..b7fff68 100644
--- a/fsgen/vbmeta_partitions.go
+++ b/fsgen/vbmeta_partitions.go
@@ -51,9 +51,10 @@
 
 	var result []vbmetaModuleInfo
 
-	var chainedPartitions []filesystem.ChainedPartitionProperties
+	var chainedPartitions []string
 	var partitionTypesHandledByChainedPartitions []string
-	for chainedName, props := range partitionVars.ChainedVbmetaPartitions {
+	for _, chainedName := range android.SortedKeys(partitionVars.ChainedVbmetaPartitions) {
+		props := partitionVars.ChainedVbmetaPartitions[chainedName]
 		chainedName = "vbmeta_" + chainedName
 		if len(props.Partitions) == 0 {
 			continue
@@ -117,11 +118,7 @@
 			},
 		).HideFromMake()
 
-		chainedPartitions = append(chainedPartitions, filesystem.ChainedPartitionProperties{
-			Name:                    &chainedName,
-			Rollback_index_location: &ril,
-			Private_key:             &props.Key,
-		})
+		chainedPartitions = append(chainedPartitions, name)
 
 		result = append(result, vbmetaModuleInfo{
 			moduleName:    name,
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 1282bfb..9d2dbc7 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -115,7 +115,7 @@
 func (t hostToolDependencyTag) AllowDisabledModuleDependencyProxy(
 	ctx android.OtherModuleProviderContext, target android.ModuleProxy) bool {
 	return android.OtherModuleProviderOrDefault(
-		ctx, target, android.CommonPropertiesProviderKey).ReplacedByPrebuilt
+		ctx, target, android.CommonModuleInfoKey).ReplacedByPrebuilt
 }
 
 var _ android.AllowDisabledModuleDependency = (*hostToolDependencyTag)(nil)
@@ -353,7 +353,7 @@
 				if h, ok := android.OtherModuleProvider(ctx, module, android.HostToolProviderKey); ok {
 					// A HostToolProvider provides the path to a tool, which will be copied
 					// into the sandbox.
-					if !android.OtherModuleProviderOrDefault(ctx, module, android.CommonPropertiesProviderKey).Enabled {
+					if !android.OtherModuleProviderOrDefault(ctx, module, android.CommonModuleInfoKey).Enabled {
 						if ctx.Config().AllowMissingDependencies() {
 							ctx.AddMissingDependencies([]string{tool})
 						} else {
diff --git a/java/app.go b/java/app.go
index 0939d17..8bb73cb 100644
--- a/java/app.go
+++ b/java/app.go
@@ -848,7 +848,7 @@
 
 	packageName := packageNameProp.Get()
 	fileName := "privapp_allowlist_" + packageName + ".xml"
-	outPath := android.PathForModuleOut(ctx, fileName).OutputPath
+	outPath := android.PathForModuleOut(ctx, fileName)
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   modifyAllowlist,
 		Input:  android.PathForModuleSrc(ctx, *a.appProperties.Privapp_allowlist),
@@ -857,7 +857,7 @@
 			"packageName": packageName,
 		},
 	})
-	return &outPath
+	return outPath
 }
 
 func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) {
diff --git a/java/app_import.go b/java/app_import.go
index 6b88f1c..f044c68 100644
--- a/java/app_import.go
+++ b/java/app_import.go
@@ -88,7 +88,7 @@
 
 	hideApexVariantFromMake bool
 
-	provenanceMetaDataFile android.OutputPath
+	provenanceMetaDataFile android.Path
 }
 
 type AndroidAppImportProperties struct {
@@ -259,7 +259,7 @@
 }
 
 func (a *AndroidAppImport) uncompressEmbeddedJniLibs(
-	ctx android.ModuleContext, inputPath android.Path, outputPath android.OutputPath) {
+	ctx android.ModuleContext, inputPath android.Path, outputPath android.WritablePath) {
 	// Test apps don't need their JNI libraries stored uncompressed. As a matter of fact, messing
 	// with them may invalidate pre-existing signature data.
 	if ctx.InstallInTestcases() && (Bool(a.properties.Presigned) || Bool(a.properties.Preprocessed)) {
@@ -345,7 +345,7 @@
 
 	// Uncompress JNI libraries in the apk
 	jnisUncompressed := android.PathForModuleOut(ctx, "jnis-uncompressed", ctx.ModuleName()+".apk")
-	a.uncompressEmbeddedJniLibs(ctx, srcApk, jnisUncompressed.OutputPath)
+	a.uncompressEmbeddedJniLibs(ctx, srcApk, jnisUncompressed)
 
 	var pathFragments []string
 	relInstallPath := String(a.properties.Relative_install_path)
@@ -493,7 +493,7 @@
 	return a.certificate
 }
 
-func (a *AndroidAppImport) ProvenanceMetaDataFile() android.OutputPath {
+func (a *AndroidAppImport) ProvenanceMetaDataFile() android.Path {
 	return a.provenanceMetaDataFile
 }
 
diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go
index 7c0f544..c778f04 100644
--- a/java/bootclasspath_fragment.go
+++ b/java/bootclasspath_fragment.go
@@ -111,7 +111,7 @@
 	// property.
 	//
 	// The order of this list matters as it is the order that is used in the bootclasspath.
-	Contents []string
+	Contents proptools.Configurable[[]string] `android:"arch_variant"`
 
 	// The properties for specifying the API stubs provided by this fragment.
 	BootclasspathAPIProperties
@@ -295,8 +295,8 @@
 	return m
 }
 
-func (m *BootclasspathFragmentModule) bootclasspathFragmentPropertyCheck(ctx android.EarlyModuleContext) {
-	contents := m.properties.Contents
+func (m *BootclasspathFragmentModule) bootclasspathFragmentPropertyCheck(ctx android.ModuleContext) {
+	contents := m.properties.Contents.GetOrDefault(ctx, nil)
 	if len(contents) == 0 {
 		ctx.PropertyErrorf("contents", "required property is missing")
 		return
@@ -434,7 +434,7 @@
 	module := ctx.Module()
 	_, isSourceModule := module.(*BootclasspathFragmentModule)
 
-	for _, name := range b.properties.Contents {
+	for _, name := range b.properties.Contents.GetOrDefault(ctx, nil) {
 		// A bootclasspath_fragment must depend only on other source modules, while the
 		// prebuilt_bootclasspath_fragment must only depend on other prebuilt modules.
 		//
@@ -588,7 +588,7 @@
 		return global.ArtApexJars
 	}
 
-	possibleUpdatableModules := gatherPossibleApexModuleNamesAndStems(ctx, b.properties.Contents, bootclasspathFragmentContentDepTag)
+	possibleUpdatableModules := gatherPossibleApexModuleNamesAndStems(ctx, b.properties.Contents.GetOrDefault(ctx, nil), bootclasspathFragmentContentDepTag)
 	jars, unknown := global.ApexBootJars.Filter(possibleUpdatableModules)
 
 	// TODO(satayev): for apex_test we want to include all contents unconditionally to classpaths
@@ -600,7 +600,7 @@
 	} else if android.InList("test_framework-apexd", possibleUpdatableModules) {
 		jars = jars.Append("com.android.apex.test_package", "test_framework-apexd")
 	} else if global.ApexBootJars.Len() != 0 {
-		unknown = android.RemoveListFromList(unknown, b.properties.Coverage.Contents)
+		unknown = android.RemoveListFromList(unknown, b.properties.Coverage.Contents.GetOrDefault(ctx, nil))
 		_, unknown = android.RemoveFromList("core-icu4j", unknown)
 		// This module only exists in car products.
 		// So ignore it even if it is not in PRODUCT_APEX_BOOT_JARS.
@@ -847,7 +847,7 @@
 
 // Collect information for opening IDE project files in java/jdeps.go.
 func (b *BootclasspathFragmentModule) IDEInfo(ctx android.BaseModuleContext, dpInfo *android.IdeInfo) {
-	dpInfo.Deps = append(dpInfo.Deps, b.properties.Contents...)
+	dpInfo.Deps = append(dpInfo.Deps, b.properties.Contents.GetOrDefault(ctx, nil)...)
 }
 
 type bootclasspathFragmentMemberType struct {
@@ -923,7 +923,7 @@
 	module := variant.(*BootclasspathFragmentModule)
 
 	b.Image_name = module.properties.Image_name
-	b.Contents = module.properties.Contents
+	b.Contents = module.properties.Contents.GetOrDefault(ctx.SdkModuleContext(), nil)
 
 	// Get the hidden API information from the module.
 	mctx := ctx.SdkModuleContext()
diff --git a/java/bootclasspath_fragment_test.go b/java/bootclasspath_fragment_test.go
index 60f1a50..3aa1258 100644
--- a/java/bootclasspath_fragment_test.go
+++ b/java/bootclasspath_fragment_test.go
@@ -191,7 +191,8 @@
 
 	checkContents := func(t *testing.T, result *android.TestResult, expected ...string) {
 		module := result.Module("myfragment", "android_common").(*BootclasspathFragmentModule)
-		android.AssertArrayString(t, "contents property", expected, module.properties.Contents)
+		eval := module.ConfigurableEvaluator(android.PanickingConfigAndErrorContext(result.TestContext))
+		android.AssertArrayString(t, "contents property", expected, module.properties.Contents.GetOrDefault(eval, nil))
 	}
 
 	preparer := android.GroupFixturePreparers(
diff --git a/java/classpath_fragment.go b/java/classpath_fragment.go
index 18a5dae..fdccd3a 100644
--- a/java/classpath_fragment.go
+++ b/java/classpath_fragment.go
@@ -18,9 +18,10 @@
 
 import (
 	"fmt"
+	"strings"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
-	"strings"
 
 	"android/soong/android"
 )
@@ -103,7 +104,7 @@
 func gatherPossibleApexModuleNamesAndStems(ctx android.ModuleContext, contents []string, tag blueprint.DependencyTag) []string {
 	set := map[string]struct{}{}
 	for _, name := range contents {
-		dep, _ := ctx.GetDirectDepWithTag(name, tag).(android.Module)
+		dep := ctx.GetDirectDepWithTag(name, tag)
 		set[ModuleStemForDeapexing(dep)] = struct{}{}
 		if m, ok := dep.(ModuleWithStem); ok {
 			set[m.Stem()] = struct{}{}
diff --git a/java/dex.go b/java/dex.go
index 1f71aee..983377e 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -295,7 +295,7 @@
 	return d8Flags, d8Deps, artProfileOutput
 }
 
-func (d *dexer) r8Flags(ctx android.ModuleContext, dexParams *compileDexParams) (r8Flags []string, r8Deps android.Paths, artProfileOutput *android.OutputPath) {
+func (d *dexer) r8Flags(ctx android.ModuleContext, dexParams *compileDexParams, debugMode bool) (r8Flags []string, r8Deps android.Paths, artProfileOutput *android.OutputPath) {
 	flags := dexParams.flags
 	opt := d.dexProperties.Optimize
 
@@ -363,7 +363,9 @@
 		r8Flags = append(r8Flags, "--force-proguard-compatibility")
 	}
 
-	if Bool(opt.Optimize) || Bool(opt.Obfuscate) {
+	// Avoid unnecessary stack frame noise by only injecting source map ids for non-debug
+	// optimized or obfuscated targets.
+	if (Bool(opt.Optimize) || Bool(opt.Obfuscate)) && !debugMode {
 		// TODO(b/213833843): Allow configuration of the prefix via a build variable.
 		var sourceFilePrefix = "go/retraceme "
 		var sourceFileTemplate = "\"" + sourceFilePrefix + "%MAP_ID\""
@@ -428,17 +430,18 @@
 // Adds --art-profile to r8/d8 command.
 // r8/d8 will output a generated profile file to match the optimized dex code.
 func (d *dexer) addArtProfile(ctx android.ModuleContext, dexParams *compileDexParams) (flags []string, deps android.Paths, artProfileOutputPath *android.OutputPath) {
-	if dexParams.artProfileInput != nil {
-		artProfileInputPath := android.PathForModuleSrc(ctx, *dexParams.artProfileInput)
-		artProfileOutputPathValue := android.PathForModuleOut(ctx, "profile.prof.txt").OutputPath
-		artProfileOutputPath = &artProfileOutputPathValue
-		flags = []string{
-			"--art-profile",
-			artProfileInputPath.String(),
-			artProfileOutputPath.String(),
-		}
-		deps = append(deps, artProfileInputPath)
+	if dexParams.artProfileInput == nil {
+		return nil, nil, nil
 	}
+	artProfileInputPath := android.PathForModuleSrc(ctx, *dexParams.artProfileInput)
+	artProfileOutputPathValue := android.PathForModuleOut(ctx, "profile.prof.txt").OutputPath
+	artProfileOutputPath = &artProfileOutputPathValue
+	flags = []string{
+		"--art-profile",
+		artProfileInputPath.String(),
+		artProfileOutputPath.String(),
+	}
+	deps = append(deps, artProfileInputPath)
 	return flags, deps, artProfileOutputPath
 
 }
@@ -482,7 +485,8 @@
 			proguardUsageZip,
 			proguardConfiguration,
 		}
-		r8Flags, r8Deps, r8ArtProfileOutputPath := d.r8Flags(ctx, dexParams)
+		debugMode := android.InList("--debug", commonFlags)
+		r8Flags, r8Deps, r8ArtProfileOutputPath := d.r8Flags(ctx, dexParams, debugMode)
 		rule := r8
 		args := map[string]string{
 			"r8Flags":        strings.Join(append(commonFlags, r8Flags...), " "),
diff --git a/java/droiddoc.go b/java/droiddoc.go
index 2980d91..8271392 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -427,9 +427,9 @@
 	// Find the corresponding aconfig_declarations module name for such case.
 	for _, src := range j.properties.Srcs {
 		if moduleName, tag := android.SrcIsModuleWithTag(src); moduleName != "" {
-			otherModule := android.GetModuleFromPathDep(ctx, moduleName, tag)
+			otherModule := android.GetModuleProxyFromPathDep(ctx, moduleName, tag)
 			if otherModule != nil {
-				if dep, ok := android.OtherModuleProvider(ctx, otherModule, android.CodegenInfoProvider); ok {
+				if dep, ok := android.OtherModuleProvider(ctx, *otherModule, android.CodegenInfoProvider); ok {
 					deps.aconfigProtoFiles = append(deps.aconfigProtoFiles, dep.IntermediateCacheOutputPaths...)
 				}
 			}
diff --git a/java/droidstubs.go b/java/droidstubs.go
index 6bcdf85..cf3e219 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -997,12 +997,13 @@
 		msg := `$'` + // Enclose with $' ... '
 			`************************************************************\n` +
 			`Your API changes are triggering API Lint warnings or errors.\n` +
-			`To make these errors go away, fix the code according to the\n` +
-			`error and/or warning messages above.\n` +
 			`\n` +
-			`If it is not possible to do so, there are workarounds:\n` +
+			`To make the failures go away:\n` +
 			`\n` +
-			`1. You can suppress the errors with @SuppressLint("<id>")\n` +
+			`1. REQUIRED: Read the messages carefully and address them by` +
+			`   fixing the API if appropriate.\n` +
+			`2. If the failure is a false positive, you can suppress it with:\n` +
+			`        @SuppressLint("<id>")\n` +
 			`   where the <id> is given in brackets in the error message above.\n`
 
 		if baselineFile.Valid() {
@@ -1010,8 +1011,8 @@
 			cmd.FlagWithOutput("--update-baseline:api-lint ", updatedBaselineOutput)
 
 			msg += fmt.Sprintf(``+
-				`2. You can update the baseline by executing the following\n`+
-				`   command:\n`+
+				`3. FOR LSC ONLY: You can update the baseline by executing\n` +
+				`   the following command:\n`+
 				`       (cd $ANDROID_BUILD_TOP && cp \\\n`+
 				`       "%s" \\\n`+
 				`       "%s")\n`+
@@ -1019,7 +1020,7 @@
 				`   repository, you will need approval.\n`, updatedBaselineOutput, baselineFile.Path())
 		} else {
 			msg += fmt.Sprintf(``+
-				`2. You can add a baseline file of existing lint failures\n`+
+				`3. FOR LSC ONLY: You can add a baseline file of existing lint failures\n`+
 				`   to the build rule of %s.\n`, d.Name())
 		}
 		// Note the message ends with a ' (single quote), to close the $' ... ' .
diff --git a/java/java.go b/java/java.go
index 078f578..64ef782 100644
--- a/java/java.go
+++ b/java/java.go
@@ -226,9 +226,9 @@
 
 	// Rule for generating device binary default wrapper
 	deviceBinaryWrapper = pctx.StaticRule("deviceBinaryWrapper", blueprint.RuleParams{
-		Command: `echo -e '#!/system/bin/sh\n` +
+		Command: `printf '#!/system/bin/sh\n` +
 			`export CLASSPATH=/system/framework/$jar_name\n` +
-			`exec app_process /$partition/bin $main_class "$$@"'> ${out}`,
+			`exec app_process /$partition/bin $main_class "$$@"\n'> ${out}`,
 		Description: "Generating device binary wrapper ${jar_name}",
 	}, "jar_name", "partition", "main_class")
 )
diff --git a/java/platform_bootclasspath.go b/java/platform_bootclasspath.go
index 8b0ca97..d09a02e 100644
--- a/java/platform_bootclasspath.go
+++ b/java/platform_bootclasspath.go
@@ -184,7 +184,7 @@
 	}
 	jarArgs := resourcePathsToJarArgs(transitiveSrcFiles)
 	jarArgs = append(jarArgs, "-srcjar") // Move srcfiles to the right package
-	srcjar := android.PathForModuleOut(ctx, ctx.ModuleName()+"-transitive.srcjar").OutputPath
+	srcjar := android.PathForModuleOut(ctx, ctx.ModuleName()+"-transitive.srcjar")
 	TransformResourcesToJar(ctx, srcjar, jarArgs, transitiveSrcFiles)
 
 	// Gather all the fragments dependencies.
diff --git a/java/ravenwood.go b/java/ravenwood.go
index 4c9fdc2..4c43a9f 100644
--- a/java/ravenwood.go
+++ b/java/ravenwood.go
@@ -14,6 +14,8 @@
 package java
 
 import (
+	"strconv"
+
 	"android/soong/android"
 	"android/soong/tradefed"
 
@@ -36,6 +38,14 @@
 var ravenwoodTestResourceApkTag = dependencyTag{name: "ravenwoodtestresapk"}
 var ravenwoodTestInstResourceApkTag = dependencyTag{name: "ravenwoodtest-inst-res-apk"}
 
+var genManifestProperties = pctx.AndroidStaticRule("genManifestProperties",
+	blueprint.RuleParams{
+		Command: "echo targetSdkVersionInt=$targetSdkVersionInt > $out && " +
+			"echo targetSdkVersionRaw=$targetSdkVersionRaw >> $out && " +
+			"echo packageName=$packageName >> $out && " +
+			"echo instPackageName=$instPackageName >> $out",
+	}, "targetSdkVersionInt", "targetSdkVersionRaw", "packageName", "instPackageName")
+
 const ravenwoodUtilsName = "ravenwood-utils"
 const ravenwoodRuntimeName = "ravenwood-runtime"
 
@@ -68,6 +78,17 @@
 	// the ravenwood test can access it. This APK will be loaded as resources of the test
 	// instrumentation app itself.
 	Inst_resource_apk *string
+
+	// Specify the package name of the test target apk.
+	// This will be set to the target Context's package name.
+	// (i.e. Instrumentation.getTargetContext().getPackageName())
+	// If this is omitted, Package_name will be used.
+	Package_name *string
+
+	// Specify the package name of this test module.
+	// This will be set to the test Context's package name.
+	//(i.e. Instrumentation.getContext().getPackageName())
+	Inst_package_name *string
 }
 
 type ravenwoodTest struct {
@@ -216,6 +237,27 @@
 	copyResApk(ravenwoodTestResourceApkTag, "ravenwood-res.apk")
 	copyResApk(ravenwoodTestInstResourceApkTag, "ravenwood-inst-res.apk")
 
+	// Generate manifest properties
+	propertiesOutputPath := android.PathForModuleGen(ctx, "ravenwood.properties")
+
+	targetSdkVersion := proptools.StringDefault(r.deviceProperties.Target_sdk_version, "")
+	targetSdkVersionInt := r.TargetSdkVersion(ctx).FinalOrFutureInt() // FinalOrFutureInt may be 10000.
+	packageName := proptools.StringDefault(r.ravenwoodTestProperties.Package_name, "")
+	instPackageName := proptools.StringDefault(r.ravenwoodTestProperties.Inst_package_name, "")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        genManifestProperties,
+		Description: "genManifestProperties",
+		Output:      propertiesOutputPath,
+		Args: map[string]string{
+			"targetSdkVersionInt": strconv.Itoa(targetSdkVersionInt),
+			"targetSdkVersionRaw": targetSdkVersion,
+			"packageName":         packageName,
+			"instPackageName":     instPackageName,
+		},
+	})
+	installProps := ctx.InstallFile(installPath, "ravenwood.properties", propertiesOutputPath)
+	installDeps = append(installDeps, installProps)
+
 	// Install our JAR with all dependencies
 	ctx.InstallFile(installPath, ctx.ModuleName()+".jar", r.outputFile, installDeps...)
 }
diff --git a/java/ravenwood_test.go b/java/ravenwood_test.go
index 5d62ede..6394a9a 100644
--- a/java/ravenwood_test.go
+++ b/java/ravenwood_test.go
@@ -177,6 +177,12 @@
 			resource_apk: "app2",
 			inst_resource_apk: "app3",
 			sdk_version: "test_current",
+			target_sdk_version: "34",
+			package_name: "a.b.c",
+			inst_package_name: "x.y.z",
+		}
+		android_ravenwood_test {
+			name: "ravenwood-test-empty",
 		}
 	`)
 
@@ -199,12 +205,16 @@
 	// Verify that we've emitted test artifacts in expected location
 	outputJar := module.Output(installPathPrefix + "/ravenwood-test/ravenwood-test.jar")
 	module.Output(installPathPrefix + "/ravenwood-test/ravenwood-test.config")
+	module.Output(installPathPrefix + "/ravenwood-test/ravenwood.properties")
 	module.Output(installPathPrefix + "/ravenwood-test/lib64/jni-lib1.so")
 	module.Output(installPathPrefix + "/ravenwood-test/lib64/libblue.so")
 	module.Output(installPathPrefix + "/ravenwood-test/lib64/libpink.so")
 	module.Output(installPathPrefix + "/ravenwood-test/ravenwood-res-apks/ravenwood-res.apk")
 	module.Output(installPathPrefix + "/ravenwood-test/ravenwood-res-apks/ravenwood-inst-res.apk")
 
+	module = ctx.ModuleForTests("ravenwood-test-empty", "android_common")
+	module.Output(installPathPrefix + "/ravenwood-test-empty/ravenwood.properties")
+
 	// ravenwood-runtime*.so are included in the runtime, so it shouldn't be emitted.
 	for _, o := range module.AllOutputs() {
 		android.AssertStringDoesNotContain(t, "runtime libs shouldn't be included", o, "/ravenwood-test/lib64/ravenwood-runtime")
diff --git a/java/systemserver_classpath_fragment.go b/java/systemserver_classpath_fragment.go
index aad1060..608a616 100644
--- a/java/systemserver_classpath_fragment.go
+++ b/java/systemserver_classpath_fragment.go
@@ -19,6 +19,7 @@
 	"android/soong/dexpreopt"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
 )
 
 func init() {
@@ -98,12 +99,12 @@
 	// List of system_server classpath jars, could be either java_library, or java_sdk_library.
 	//
 	// The order of this list matters as it is the order that is used in the SYSTEMSERVERCLASSPATH.
-	Contents []string
+	Contents proptools.Configurable[[]string] `android:"arch_variant"`
 
 	// List of jars that system_server loads dynamically using separate classloaders.
 	//
 	// The order does not matter.
-	Standalone_contents []string
+	Standalone_contents proptools.Configurable[[]string] `android:"arch_variant"`
 }
 
 func systemServerClasspathFactory() android.Module {
@@ -116,7 +117,7 @@
 }
 
 func (s *SystemServerClasspathModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	if len(s.properties.Contents) == 0 && len(s.properties.Standalone_contents) == 0 {
+	if len(s.properties.Contents.GetOrDefault(ctx, nil)) == 0 && len(s.properties.Standalone_contents.GetOrDefault(ctx, nil)) == 0 {
 		ctx.PropertyErrorf("contents", "Either contents or standalone_contents needs to be non-empty")
 	}
 
@@ -152,7 +153,7 @@
 func (s *SystemServerClasspathModule) configuredJars(ctx android.ModuleContext) android.ConfiguredJarList {
 	global := dexpreopt.GetGlobalConfig(ctx)
 
-	possibleUpdatableModules := gatherPossibleApexModuleNamesAndStems(ctx, s.properties.Contents, systemServerClasspathFragmentContentDepTag)
+	possibleUpdatableModules := gatherPossibleApexModuleNamesAndStems(ctx, s.properties.Contents.GetOrDefault(ctx, nil), systemServerClasspathFragmentContentDepTag)
 	jars, unknown := global.ApexSystemServerJars.Filter(possibleUpdatableModules)
 	// TODO(satayev): remove geotz ssc_fragment, since geotz is not part of SSCP anymore.
 	_, unknown = android.RemoveFromList("geotz", unknown)
@@ -184,7 +185,7 @@
 func (s *SystemServerClasspathModule) standaloneConfiguredJars(ctx android.ModuleContext) android.ConfiguredJarList {
 	global := dexpreopt.GetGlobalConfig(ctx)
 
-	possibleUpdatableModules := gatherPossibleApexModuleNamesAndStems(ctx, s.properties.Standalone_contents, systemServerClasspathFragmentContentDepTag)
+	possibleUpdatableModules := gatherPossibleApexModuleNamesAndStems(ctx, s.properties.Standalone_contents.GetOrDefault(ctx, nil), systemServerClasspathFragmentContentDepTag)
 	jars, _ := global.ApexStandaloneSystemServerJars.Filter(possibleUpdatableModules)
 
 	// TODO(jiakaiz): add a check to ensure that the contents are declared in make.
@@ -245,8 +246,8 @@
 	module := ctx.Module()
 	_, isSourceModule := module.(*SystemServerClasspathModule)
 	var deps []string
-	deps = append(deps, s.properties.Contents...)
-	deps = append(deps, s.properties.Standalone_contents...)
+	deps = append(deps, s.properties.Contents.GetOrDefault(ctx, nil)...)
+	deps = append(deps, s.properties.Standalone_contents.GetOrDefault(ctx, nil)...)
 
 	for _, name := range deps {
 		// A systemserverclasspath_fragment must depend only on other source modules, while the
@@ -260,8 +261,8 @@
 
 // Collect information for opening IDE project files in java/jdeps.go.
 func (s *SystemServerClasspathModule) IDEInfo(ctx android.BaseModuleContext, dpInfo *android.IdeInfo) {
-	dpInfo.Deps = append(dpInfo.Deps, s.properties.Contents...)
-	dpInfo.Deps = append(dpInfo.Deps, s.properties.Standalone_contents...)
+	dpInfo.Deps = append(dpInfo.Deps, s.properties.Contents.GetOrDefault(ctx, nil)...)
+	dpInfo.Deps = append(dpInfo.Deps, s.properties.Standalone_contents.GetOrDefault(ctx, nil)...)
 }
 
 type systemServerClasspathFragmentMemberType struct {
@@ -302,8 +303,8 @@
 func (s *systemServerClasspathFragmentSdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) {
 	module := variant.(*SystemServerClasspathModule)
 
-	s.Contents = module.properties.Contents
-	s.Standalone_contents = module.properties.Standalone_contents
+	s.Contents = module.properties.Contents.GetOrDefault(ctx.SdkModuleContext(), nil)
+	s.Standalone_contents = module.properties.Standalone_contents.GetOrDefault(ctx.SdkModuleContext(), nil)
 }
 
 func (s *systemServerClasspathFragmentSdkMemberProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) {
diff --git a/provenance/provenance_singleton.go b/provenance/provenance_singleton.go
index 679632c..c372db2 100644
--- a/provenance/provenance_singleton.go
+++ b/provenance/provenance_singleton.go
@@ -46,7 +46,7 @@
 )
 
 type ProvenanceMetadata interface {
-	ProvenanceMetaDataFile() android.OutputPath
+	ProvenanceMetaDataFile() android.Path
 }
 
 func init() {
@@ -74,7 +74,7 @@
 			return false
 		}
 		if p, ok := module.(ProvenanceMetadata); ok {
-			return p.ProvenanceMetaDataFile().String() != ""
+			return p.ProvenanceMetaDataFile() != nil
 		}
 		return false
 	}
@@ -101,7 +101,7 @@
 	context.Phony("droidcore", android.PathForPhony(context, "provenance_metadata"))
 }
 
-func GenerateArtifactProvenanceMetaData(ctx android.ModuleContext, artifactPath android.Path, installedFile android.InstallPath) android.OutputPath {
+func GenerateArtifactProvenanceMetaData(ctx android.ModuleContext, artifactPath android.Path, installedFile android.InstallPath) android.Path {
 	onDevicePathOfInstalledFile := android.InstallPathToOnDevicePath(ctx, installedFile)
 	artifactMetaDataFile := android.PathForIntermediates(ctx, "provenance_metadata", ctx.ModuleDir(), ctx.ModuleName(), "provenance_metadata.textproto")
 	ctx.Build(pctx, android.BuildParams{
diff --git a/rust/rust.go b/rust/rust.go
index ed38ad7..48f946e 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -1001,6 +1001,10 @@
 		ctx.Phony("rust", ctx.RustModule().OutputFile().Path())
 	}
 
+	android.SetProvider(ctx, cc.LinkableInfoKey, cc.LinkableInfo{
+		StaticExecutable: mod.StaticExecutable(),
+	})
+
 	mod.setOutputFiles(ctx)
 
 	buildComplianceMetadataInfo(ctx, mod, deps)
diff --git a/scripts/gen_build_prop.py b/scripts/gen_build_prop.py
index 0b7780e..5f52d6f 100644
--- a/scripts/gen_build_prop.py
+++ b/scripts/gen_build_prop.py
@@ -608,7 +608,7 @@
         build_product_prop(args)
       case "vendor":
         build_vendor_prop(args)
-      case "system_dlkm" | "vendor_dlkm" | "odm_dlkm":
+      case "system_dlkm" | "vendor_dlkm" | "odm_dlkm" | "bootimage":
         build_prop(args, gen_build_info=False, gen_common_build_props=True, variables=[])
       case _:
         sys.exit(f"not supported partition {args.partition}")
diff --git a/scripts/ninja_determinism_test.py b/scripts/ninja_determinism_test.py
new file mode 100755
index 0000000..e207b96
--- /dev/null
+++ b/scripts/ninja_determinism_test.py
@@ -0,0 +1,210 @@
+#!/usr/bin/env python3
+
+import asyncio
+import argparse
+import dataclasses
+import hashlib
+import os
+import re
+import socket
+import subprocess
+import sys
+import zipfile
+
+from typing import List
+
+def get_top() -> str:
+  path = '.'
+  while not os.path.isfile(os.path.join(path, 'build/soong/soong_ui.bash')):
+    if os.path.abspath(path) == '/':
+      sys.exit('Could not find android source tree root.')
+    path = os.path.join(path, '..')
+  return os.path.abspath(path)
+
+
+_PRODUCT_REGEX = re.compile(r'([a-zA-Z_][a-zA-Z0-9_]*)(?:(?:-([a-zA-Z_][a-zA-Z0-9_]*))?-(user|userdebug|eng))?')
+
+
+@dataclasses.dataclass(frozen=True)
+class Product:
+  """Represents a TARGET_PRODUCT and TARGET_BUILD_VARIANT."""
+  product: str
+  release: str
+  variant: str
+
+  def __post_init__(self):
+    if not _PRODUCT_REGEX.match(str(self)):
+      raise ValueError(f'Invalid product name: {self}')
+
+  def __str__(self):
+    return self.product + '-' + self.release + '-' + self.variant
+
+
+async def run_make_nothing(product: Product, out_dir: str) -> bool:
+  """Runs a build and returns if it succeeded or not."""
+  with open(os.path.join(out_dir, 'build.log'), 'wb') as f:
+    result = await asyncio.create_subprocess_exec(
+        'prebuilts/build-tools/linux-x86/bin/nsjail',
+        '-q',
+        '--cwd',
+        os.getcwd(),
+        '-e',
+        '-B',
+        '/',
+        '-B',
+        f'{os.path.abspath(out_dir)}:{os.path.join(os.getcwd(), "out")}',
+        '--time_limit',
+        '0',
+        '--skip_setsid',
+        '--keep_caps',
+        '--disable_clone_newcgroup',
+        '--disable_clone_newnet',
+        '--rlimit_as',
+        'soft',
+        '--rlimit_core',
+        'soft',
+        '--rlimit_cpu',
+        'soft',
+        '--rlimit_fsize',
+        'soft',
+        '--rlimit_nofile',
+        'soft',
+        '--proc_rw',
+        '--hostname',
+        socket.gethostname(),
+        '--',
+        'build/soong/soong_ui.bash',
+        '--make-mode',
+        f'TARGET_PRODUCT={product.product}',
+        f'TARGET_RELEASE={product.release}',
+        f'TARGET_BUILD_VARIANT={product.variant}',
+        '--skip-ninja',
+        'nothing', stdout=f, stderr=subprocess.STDOUT)
+    return await result.wait() == 0
+
+SUBNINJA_OR_INCLUDE_REGEX = re.compile(rb'\n(?:include|subninja) ')
+
+def find_subninjas_and_includes(contents) -> List[str]:
+  results = []
+  def get_path_from_directive(i):
+    j = contents.find(b'\n', i)
+    if j < 0:
+      path_bytes = contents[i:]
+    else:
+      path_bytes = contents[i:j]
+    path_bytes = path_bytes.removesuffix(b'\r')
+    path = path_bytes.decode()
+    if '$' in path:
+      sys.exit('includes/subninjas with variables are unsupported: '+path)
+    return path
+
+  if contents.startswith(b"include "):
+    results.append(get_path_from_directive(len(b"include ")))
+  elif contents.startswith(b"subninja "):
+    results.append(get_path_from_directive(len(b"subninja ")))
+
+  for match in SUBNINJA_OR_INCLUDE_REGEX.finditer(contents):
+    results.append(get_path_from_directive(match.end()))
+
+  return results
+
+
+def transitively_included_ninja_files(out_dir: str, ninja_file: str, seen):
+  with open(ninja_file, 'rb') as f:
+    contents = f.read()
+
+  results = [ninja_file]
+  seen[ninja_file] = True
+  sub_files = find_subninjas_and_includes(contents)
+  for sub_file in sub_files:
+    sub_file = os.path.join(out_dir, sub_file.removeprefix('out/'))
+    if sub_file not in seen:
+      results.extend(transitively_included_ninja_files(out_dir, sub_file, seen))
+
+  return results
+
+
+def hash_ninja_file(out_dir: str, ninja_file: str, hasher):
+  with open(ninja_file, 'rb') as f:
+    contents = f.read()
+
+  sub_files = find_subninjas_and_includes(contents)
+
+  hasher.update(contents)
+
+  for sub_file in sub_files:
+    hash_ninja_file(out_dir, os.path.join(out_dir, sub_file.removeprefix('out/')), hasher)
+
+
+def hash_files(files: List[str]) -> str:
+  hasher = hashlib.md5()
+  for file in files:
+    with open(file, 'rb') as f:
+      hasher.update(f.read())
+  return hasher.hexdigest()
+
+
+def dist_ninja_files(out_dir: str, zip_name: str, ninja_files: List[str]):
+  dist_dir = os.getenv('DIST_DIR', os.path.join(os.getenv('OUT_DIR', 'out'), 'dist'))
+  os.makedirs(dist_dir, exist_ok=True)
+
+  with open(os.path.join(dist_dir, zip_name), 'wb') as f:
+    with zipfile.ZipFile(f, mode='w') as zf:
+      for ninja_file in ninja_files:
+        zf.write(ninja_file, arcname=os.path.basename(out_dir)+'/out/' + os.path.relpath(ninja_file, out_dir))
+
+
+async def main():
+    parser = argparse.ArgumentParser()
+    args = parser.parse_args()
+
+    os.chdir(get_top())
+    subprocess.check_call(['touch', 'build/soong/Android.bp'])
+
+    product = Product(
+      'aosp_cf_x86_64_phone',
+      'trunk_staging',
+      'userdebug',
+    )
+    os.environ['TARGET_PRODUCT'] = 'aosp_cf_x86_64_phone'
+    os.environ['TARGET_RELEASE'] = 'trunk_staging'
+    os.environ['TARGET_BUILD_VARIANT'] = 'userdebug'
+
+    out_dir1 = os.path.join(os.getenv('OUT_DIR', 'out'), 'determinism_test_out1')
+    out_dir2 = os.path.join(os.getenv('OUT_DIR', 'out'), 'determinism_test_out2')
+
+    os.makedirs(out_dir1, exist_ok=True)
+    os.makedirs(out_dir2, exist_ok=True)
+
+    success1, success2 = await asyncio.gather(
+      run_make_nothing(product, out_dir1),
+      run_make_nothing(product, out_dir2))
+
+    if not success1:
+      with open(os.path.join(out_dir1, 'build.log'), 'r') as f:
+        print(f.read(), file=sys.stderr)
+      sys.exit('build failed')
+    if not success2:
+      with open(os.path.join(out_dir2, 'build.log'), 'r') as f:
+        print(f.read(), file=sys.stderr)
+      sys.exit('build failed')
+
+    ninja_files1 = transitively_included_ninja_files(out_dir1, os.path.join(out_dir1, f'combined-{product.product}.ninja'), {})
+    ninja_files2 = transitively_included_ninja_files(out_dir2, os.path.join(out_dir2, f'combined-{product.product}.ninja'), {})
+
+    dist_ninja_files(out_dir1, 'determinism_test_files_1.zip', ninja_files1)
+    dist_ninja_files(out_dir2, 'determinism_test_files_2.zip', ninja_files2)
+
+    hash1 = hash_files(ninja_files1)
+    hash2 = hash_files(ninja_files2)
+
+    if hash1 != hash2:
+        sys.exit("ninja files were not deterministic! See disted determinism_test_files_1/2.zip")
+
+    print("Success, ninja files were deterministic")
+
+
+if __name__ == "__main__":
+    asyncio.run(main())
+
+
diff --git a/sh/sh_binary.go b/sh/sh_binary.go
index 320e97f..7f5a426 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -204,7 +204,7 @@
 func (s *ShBinary) DepsMutator(ctx android.BottomUpMutatorContext) {
 }
 
-func (s *ShBinary) OutputFile() android.OutputPath {
+func (s *ShBinary) OutputFile() android.Path {
 	return s.outputFilePath
 }
 
diff --git a/ui/build/androidmk_denylist.go b/ui/build/androidmk_denylist.go
index c54d55f..6f57cb1 100644
--- a/ui/build/androidmk_denylist.go
+++ b/ui/build/androidmk_denylist.go
@@ -29,6 +29,9 @@
 	"device/google_car/",
 	"device/sample/",
 	"frameworks/",
+	"hardware/libhardware/",
+	"hardware/libhardware_legacy/",
+	"hardware/ril/",
 	// Do not block other directories in kernel/, see b/319658303.
 	"kernel/configs/",
 	"kernel/prebuilts/",
@@ -37,8 +40,10 @@
 	"libnativehelper/",
 	"packages/",
 	"pdk/",
+	"platform_testing/",
 	"prebuilts/",
 	"sdk/",
+	"system/",
 	"test/",
 	"trusty/",
 	// Add back toolchain/ once defensive Android.mk files are removed
diff --git a/ui/build/soong.go b/ui/build/soong.go
index e6d01dd..0963f76 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -433,13 +433,13 @@
 	}
 }
 
-func updateSymlinks(ctx Context, dir, prevCWD, cwd string) error {
+func updateSymlinks(ctx Context, dir, prevCWD, cwd string, updateSemaphore chan struct{}) error {
 	defer symlinkWg.Done()
 
 	visit := func(path string, d fs.DirEntry, err error) error {
 		if d.IsDir() && path != dir {
 			symlinkWg.Add(1)
-			go updateSymlinks(ctx, path, prevCWD, cwd)
+			go updateSymlinks(ctx, path, prevCWD, cwd, updateSemaphore)
 			return filepath.SkipDir
 		}
 		f, err := d.Info()
@@ -470,12 +470,27 @@
 		return nil
 	}
 
+	<-updateSemaphore
+	defer func() { updateSemaphore <- struct{}{} }()
 	if err := filepath.WalkDir(dir, visit); err != nil {
 		return err
 	}
 	return nil
 }
 
+// b/376466642: If the concurrency of updateSymlinks is unbounded, Go's runtime spawns a
+// theoretically unbounded number of threads to handle blocking syscalls. This causes the runtime to
+// panic due to hitting thread limits in rare cases. Limiting to GOMAXPROCS concurrent symlink
+// updates should make this a non-issue.
+func newUpdateSemaphore() chan struct{} {
+	numPermits := runtime.GOMAXPROCS(0)
+	c := make(chan struct{}, numPermits)
+	for i := 0; i < numPermits; i++ {
+		c <- struct{}{}
+	}
+	return c
+}
+
 func fixOutDirSymlinks(ctx Context, config Config, outDir string) error {
 	cwd, err := os.Getwd()
 	if err != nil {
@@ -508,7 +523,7 @@
 	}
 
 	symlinkWg.Add(1)
-	if err := updateSymlinks(ctx, outDir, prevCWD, cwd); err != nil {
+	if err := updateSymlinks(ctx, outDir, prevCWD, cwd, newUpdateSemaphore()); err != nil {
 		return err
 	}
 	symlinkWg.Wait()
diff --git a/ui/metrics/Android.bp b/ui/metrics/Android.bp
index 77871fc..591e3cc 100644
--- a/ui/metrics/Android.bp
+++ b/ui/metrics/Android.bp
@@ -26,6 +26,7 @@
         "soong-ui-metrics_proto",
         "soong-ui-mk_metrics_proto",
         "soong-shared",
+        "soong-ui-metrics_combined_proto",
     ],
     srcs: [
         "hostinfo.go",
@@ -63,6 +64,19 @@
 }
 
 bootstrap_go_package {
+    name: "soong-ui-metrics_combined_proto",
+    pkgPath: "android/soong/ui/metrics/combined_metrics_proto",
+    deps: [
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+        "soong-cmd-find_input_delta-proto",
+    ],
+    srcs: [
+        "metrics_proto/metrics.pb.go",
+    ],
+}
+
+bootstrap_go_package {
     name: "soong-ui-metrics_upload_proto",
     pkgPath: "android/soong/ui/metrics/upload_proto",
     deps: [
diff --git a/ui/metrics/metrics_proto/Android.bp b/ui/metrics/metrics_proto/Android.bp
new file mode 100644
index 0000000..aae5266
--- /dev/null
+++ b/ui/metrics/metrics_proto/Android.bp
@@ -0,0 +1,16 @@
+python_library_host {
+    name: "soong-metrics-proto-py",
+    srcs: [
+        "metrics.proto",
+    ],
+    visibility: [
+        "//build/make/ci",
+    ],
+    libs: [
+        "libprotobuf-python",
+    ],
+    proto: {
+        include_dirs: ["external/protobuf/src"],
+        canonical_path_from_root: false,
+    },
+}
diff --git a/ui/metrics/metrics_proto/combined_metrics.pb.go b/ui/metrics/metrics_proto/combined_metrics.pb.go
new file mode 100644
index 0000000..f49d64d
--- /dev/null
+++ b/ui/metrics/metrics_proto/combined_metrics.pb.go
@@ -0,0 +1,239 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.33.0
+// 	protoc        v3.21.12
+// source: combined_metrics.proto
+
+package metrics_proto
+
+import (
+	find_input_delta_proto "android/soong/cmd/find_input_delta/find_input_delta_proto"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// These field numbers are also found in the inner message declarations.
+// We verify that the values are the same, and that every enum value is checked
+// in combined_metrics_test.go.
+// Do not change this enum without also updating:
+//   - the submessage's .proto file
+//   - combined_metrics_test.go
+type FieldNumbers int32
+
+const (
+	FieldNumbers_FIELD_NUMBERS_UNSPECIFIED FieldNumbers = 0
+	FieldNumbers_FIELD_NUMBERS_FILE_LIST   FieldNumbers = 1
+)
+
+// Enum value maps for FieldNumbers.
+var (
+	FieldNumbers_name = map[int32]string{
+		0: "FIELD_NUMBERS_UNSPECIFIED",
+		1: "FIELD_NUMBERS_FILE_LIST",
+	}
+	FieldNumbers_value = map[string]int32{
+		"FIELD_NUMBERS_UNSPECIFIED": 0,
+		"FIELD_NUMBERS_FILE_LIST":   1,
+	}
+)
+
+func (x FieldNumbers) Enum() *FieldNumbers {
+	p := new(FieldNumbers)
+	*p = x
+	return p
+}
+
+func (x FieldNumbers) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (FieldNumbers) Descriptor() protoreflect.EnumDescriptor {
+	return file_combined_metrics_proto_enumTypes[0].Descriptor()
+}
+
+func (FieldNumbers) Type() protoreflect.EnumType {
+	return &file_combined_metrics_proto_enumTypes[0]
+}
+
+func (x FieldNumbers) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Do not use.
+func (x *FieldNumbers) UnmarshalJSON(b []byte) error {
+	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
+	if err != nil {
+		return err
+	}
+	*x = FieldNumbers(num)
+	return nil
+}
+
+// Deprecated: Use FieldNumbers.Descriptor instead.
+func (FieldNumbers) EnumDescriptor() ([]byte, []int) {
+	return file_combined_metrics_proto_rawDescGZIP(), []int{0}
+}
+
+type SoongCombinedMetrics struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// cmd/find_input_delta/find_input_delta_proto.FileList
+	FileList *find_input_delta_proto.FileList `protobuf:"bytes,1,opt,name=file_list,json=fileList" json:"file_list,omitempty"`
+}
+
+func (x *SoongCombinedMetrics) Reset() {
+	*x = SoongCombinedMetrics{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_combined_metrics_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SoongCombinedMetrics) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SoongCombinedMetrics) ProtoMessage() {}
+
+func (x *SoongCombinedMetrics) ProtoReflect() protoreflect.Message {
+	mi := &file_combined_metrics_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SoongCombinedMetrics.ProtoReflect.Descriptor instead.
+func (*SoongCombinedMetrics) Descriptor() ([]byte, []int) {
+	return file_combined_metrics_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *SoongCombinedMetrics) GetFileList() *find_input_delta_proto.FileList {
+	if x != nil {
+		return x.FileList
+	}
+	return nil
+}
+
+var File_combined_metrics_proto protoreflect.FileDescriptor
+
+var file_combined_metrics_proto_rawDesc = []byte{
+	0x0a, 0x16, 0x63, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69,
+	0x63, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x13, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f,
+	0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x1a, 0x3b, 0x63,
+	0x6d, 0x64, 0x2f, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x65,
+	0x6c, 0x74, 0x61, 0x2f, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64,
+	0x65, 0x6c, 0x74, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x66, 0x69, 0x6c, 0x65, 0x5f,
+	0x6c, 0x69, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x5d, 0x0a, 0x14, 0x53, 0x6f,
+	0x6f, 0x6e, 0x67, 0x43, 0x6f, 0x6d, 0x62, 0x69, 0x6e, 0x65, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69,
+	0x63, 0x73, 0x12, 0x45, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e,
+	0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61,
+	0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52,
+	0x08, 0x66, 0x69, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x2a, 0x4a, 0x0a, 0x0c, 0x46, 0x69, 0x65,
+	0x6c, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x1d, 0x0a, 0x19, 0x46, 0x49, 0x45,
+	0x4c, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45,
+	0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x46, 0x49, 0x45, 0x4c,
+	0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x53, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x4c,
+	0x49, 0x53, 0x54, 0x10, 0x01, 0x42, 0x28, 0x5a, 0x26, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
+	0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x75, 0x69, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
+	0x73, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+}
+
+var (
+	file_combined_metrics_proto_rawDescOnce sync.Once
+	file_combined_metrics_proto_rawDescData = file_combined_metrics_proto_rawDesc
+)
+
+func file_combined_metrics_proto_rawDescGZIP() []byte {
+	file_combined_metrics_proto_rawDescOnce.Do(func() {
+		file_combined_metrics_proto_rawDescData = protoimpl.X.CompressGZIP(file_combined_metrics_proto_rawDescData)
+	})
+	return file_combined_metrics_proto_rawDescData
+}
+
+var file_combined_metrics_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_combined_metrics_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_combined_metrics_proto_goTypes = []interface{}{
+	(FieldNumbers)(0),                       // 0: soong_build_metrics.FieldNumbers
+	(*SoongCombinedMetrics)(nil),            // 1: soong_build_metrics.SoongCombinedMetrics
+	(*find_input_delta_proto.FileList)(nil), // 2: android.find_input_delta_proto.FileList
+}
+var file_combined_metrics_proto_depIdxs = []int32{
+	2, // 0: soong_build_metrics.SoongCombinedMetrics.file_list:type_name -> android.find_input_delta_proto.FileList
+	1, // [1:1] is the sub-list for method output_type
+	1, // [1:1] is the sub-list for method input_type
+	1, // [1:1] is the sub-list for extension type_name
+	1, // [1:1] is the sub-list for extension extendee
+	0, // [0:1] is the sub-list for field type_name
+}
+
+func init() { file_combined_metrics_proto_init() }
+func file_combined_metrics_proto_init() {
+	if File_combined_metrics_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_combined_metrics_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SoongCombinedMetrics); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_combined_metrics_proto_rawDesc,
+			NumEnums:      1,
+			NumMessages:   1,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_combined_metrics_proto_goTypes,
+		DependencyIndexes: file_combined_metrics_proto_depIdxs,
+		EnumInfos:         file_combined_metrics_proto_enumTypes,
+		MessageInfos:      file_combined_metrics_proto_msgTypes,
+	}.Build()
+	File_combined_metrics_proto = out.File
+	file_combined_metrics_proto_rawDesc = nil
+	file_combined_metrics_proto_goTypes = nil
+	file_combined_metrics_proto_depIdxs = nil
+}
diff --git a/ui/metrics/metrics_proto/combined_metrics.proto b/ui/metrics/metrics_proto/combined_metrics.proto
new file mode 100644
index 0000000..3cd9a53
--- /dev/null
+++ b/ui/metrics/metrics_proto/combined_metrics.proto
@@ -0,0 +1,36 @@
+// Copyright 2018 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto2";
+
+package soong_build_metrics;
+option go_package = "android/soong/ui/metrics/metrics_proto";
+
+import "cmd/find_input_delta/find_input_delta_proto/file_list.proto";
+
+// These field numbers are also found in the inner message declarations.
+// We verify that the values are the same, and that every enum value is checked
+// in combined_metrics_test.go.
+// Do not change this enum without also updating:
+//  - the submessage's .proto file
+//  - combined_metrics_test.go
+enum FieldNumbers {
+  FIELD_NUMBERS_UNSPECIFIED = 0;
+  FIELD_NUMBERS_FILE_LIST = 1;
+}
+
+message SoongCombinedMetrics {
+  // cmd/find_input_delta/find_input_delta_proto.FileList
+  optional android.find_input_delta_proto.FileList file_list = 1;
+}
diff --git a/ui/metrics/metrics_proto/combined_metrics_test.go b/ui/metrics/metrics_proto/combined_metrics_test.go
new file mode 100644
index 0000000..eedb12a
--- /dev/null
+++ b/ui/metrics/metrics_proto/combined_metrics_test.go
@@ -0,0 +1,33 @@
+package metrics_proto
+
+import (
+	"testing"
+
+	find_input_delta_proto "android/soong/cmd/find_input_delta/find_input_delta_proto"
+)
+
+func TestCombinedMetricsMessageNums(t *testing.T) {
+	testCases := []struct {
+		Name         string
+		FieldNumbers map[string]int32
+	}{
+		{
+			Name:         "find_input_delta_proto",
+			FieldNumbers: find_input_delta_proto.FieldNumbers_value,
+		},
+	}
+	verifiedMap := make(map[string]bool)
+	for _, tc := range testCases {
+		for k, v := range tc.FieldNumbers {
+			if FieldNumbers_value[k] != v {
+				t.Errorf("%s: Expected FieldNumbers.%s == %v, found %v", tc.Name, k, FieldNumbers_value[k], v)
+			}
+			verifiedMap[k] = true
+		}
+	}
+	for k, v := range FieldNumbers_value {
+		if !verifiedMap[k] {
+			t.Errorf("No test case verifies FieldNumbers.%s=%v", k, v)
+		}
+	}
+}
diff --git a/ui/metrics/metrics_proto/regen.sh b/ui/metrics/metrics_proto/regen.sh
index 8eb2d74..5e5f9b8 100755
--- a/ui/metrics/metrics_proto/regen.sh
+++ b/ui/metrics/metrics_proto/regen.sh
@@ -12,6 +12,6 @@
   die "could not find aprotoc. ${error_msg}"
 fi
 
-if ! aprotoc --go_out=paths=source_relative:. metrics.proto; then
+if ! aprotoc --go_out=paths=source_relative:. -I .:../../.. metrics.proto combined_metrics.proto; then
   die "build failed. ${error_msg}"
 fi