Merge "Don't magically use implementations for modules in the same apex" into main
diff --git a/Android.bp b/Android.bp
index cbe1c7b..9763622 100644
--- a/Android.bp
+++ b/Android.bp
@@ -175,6 +175,7 @@
     // Currently, only microdroid, Ravenwood, and cf system image can refer to system-build.prop
     visibility: [
         "//build/make/target/product/generic",
+        "//build/make/target/product/gsi",
         "//packages/modules/Virtualization/build/microdroid",
         "//frameworks/base/ravenwood",
     ],
@@ -186,7 +187,7 @@
     system_ext_specific: true,
     product_config: ":product_config",
     relative_install_path: "etc", // system_ext/etc/build.prop
-    visibility: ["//visibility:private"],
+    visibility: ["//build/make/target/product/gsi"],
 }
 
 build_prop {
@@ -195,7 +196,7 @@
     product_specific: true,
     product_config: ":product_config",
     relative_install_path: "etc", // product/etc/build.prop
-    visibility: ["//visibility:private"],
+    visibility: ["//build/make/target/product/gsi"],
 }
 
 build_prop {
@@ -206,3 +207,30 @@
     relative_install_path: "etc", // odm/etc/build.prop
     visibility: ["//visibility:private"],
 }
+
+build_prop {
+    name: "system_dlkm-build.prop",
+    stem: "build.prop",
+    system_dlkm_specific: true,
+    product_config: ":product_config",
+    relative_install_path: "etc", // system_dlkm/etc/build.prop
+    visibility: ["//visibility:private"],
+}
+
+build_prop {
+    name: "vendor_dlkm-build.prop",
+    stem: "build.prop",
+    vendor_dlkm_specific: true,
+    product_config: ":product_config",
+    relative_install_path: "etc", // vendor_dlkm/etc/build.prop
+    visibility: ["//visibility:private"],
+}
+
+build_prop {
+    name: "odm_dlkm-build.prop",
+    stem: "build.prop",
+    odm_dlkm_specific: true,
+    product_config: ":product_config",
+    relative_install_path: "etc", // odm_dlkm/etc/build.prop
+    visibility: ["//visibility:private"],
+}
diff --git a/android/Android.bp b/android/Android.bp
index 1ed2dba..a9a3564 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -31,6 +31,7 @@
     srcs: [
         "aconfig_providers.go",
         "all_teams.go",
+        "android_info.go",
         "androidmk.go",
         "apex.go",
         "apex_contributions.go",
@@ -111,6 +112,7 @@
         "util.go",
         "variable.go",
         "vintf_fragment.go",
+        "vintf_data.go",
         "visibility.go",
     ],
     testSrcs: [
@@ -120,6 +122,7 @@
         "apex_test.go",
         "arch_test.go",
         "blueprint_e2e_test.go",
+        "build_prop_test.go",
         "config_test.go",
         "configured_jars_test.go",
         "csuite_config_test.go",
diff --git a/android/android_info.go b/android/android_info.go
new file mode 100644
index 0000000..a8d3d4e
--- /dev/null
+++ b/android/android_info.go
@@ -0,0 +1,91 @@
+// Copyright 2024 The Android Open Source Project
+//
+// 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 android
+
+import (
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+var (
+	mergeAndRemoveComments = pctx.AndroidStaticRule("merge_and_remove_comments",
+		blueprint.RuleParams{
+			Command: "cat $in | grep -v '#' > $out",
+		},
+	)
+	androidInfoTxtToProp = pctx.AndroidStaticRule("android_info_txt_to_prop",
+		blueprint.RuleParams{
+			Command: "grep 'require version-' $in | sed -e 's/require version-/ro.build.expect./g' > $out",
+		},
+	)
+)
+
+type androidInfoProperties struct {
+	// Name of output file. Defaults to module name
+	Stem *string
+
+	// Paths of board-info.txt files.
+	Board_info_files []string `android:"path"`
+
+	// Name of bootloader board. If board_info_files is empty, `board={bootloader_board_name}` will
+	// be printed to output. Ignored if board_info_files is not empty.
+	Bootloader_board_name *string
+}
+
+type androidInfoModule struct {
+	ModuleBase
+
+	properties androidInfoProperties
+}
+
+func (p *androidInfoModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	if len(p.properties.Board_info_files) > 0 && p.properties.Bootloader_board_name != nil {
+		ctx.ModuleErrorf("Either Board_info_files or Bootloader_board_name should be set. Please remove one of them\n")
+		return
+	}
+	androidInfoTxtName := proptools.StringDefault(p.properties.Stem, ctx.ModuleName()+".txt")
+	androidInfoTxt := PathForModuleOut(ctx, androidInfoTxtName)
+	androidInfoProp := androidInfoTxt.ReplaceExtension(ctx, "prop")
+
+	if boardInfoFiles := PathsForModuleSrc(ctx, p.properties.Board_info_files); len(boardInfoFiles) > 0 {
+		ctx.Build(pctx, BuildParams{
+			Rule:   mergeAndRemoveComments,
+			Inputs: boardInfoFiles,
+			Output: androidInfoTxt,
+		})
+	} else if bootloaderBoardName := proptools.String(p.properties.Bootloader_board_name); bootloaderBoardName != "" {
+		WriteFileRule(ctx, androidInfoTxt, "board="+bootloaderBoardName)
+	} else {
+		WriteFileRule(ctx, androidInfoTxt, "")
+	}
+
+	// Create android_info.prop
+	ctx.Build(pctx, BuildParams{
+		Rule:   androidInfoTxtToProp,
+		Input:  androidInfoTxt,
+		Output: androidInfoProp,
+	})
+
+	ctx.SetOutputFiles(Paths{androidInfoProp}, "")
+}
+
+// android_info module generate a file named android-info.txt that contains various information
+// about the device we're building for.  This file is typically packaged up with everything else.
+func AndroidInfoFactory() Module {
+	module := &androidInfoModule{}
+	module.AddProperties(&module.properties)
+	InitAndroidModule(module)
+	return module
+}
diff --git a/android/apex.go b/android/apex.go
index e90248e..3486350 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -812,7 +812,7 @@
 
 	// If this is the FinalModule (last visited module) copy
 	// AnyVariantDirectlyInAnyApex to all the other variants
-	if am == mctx.FinalModule().(ApexModule) {
+	if mctx.IsFinalModule(am) {
 		mctx.VisitAllModuleVariants(func(variant Module) {
 			variant.(ApexModule).apexModuleBase().ApexProperties.AnyVariantDirectlyInAnyApex =
 				base.ApexProperties.AnyVariantDirectlyInAnyApex
diff --git a/android/apex_contributions.go b/android/apex_contributions.go
index 4cd8dda..ce34278 100644
--- a/android/apex_contributions.go
+++ b/android/apex_contributions.go
@@ -26,7 +26,7 @@
 func RegisterApexContributionsBuildComponents(ctx RegistrationContext) {
 	ctx.RegisterModuleType("apex_contributions", apexContributionsFactory)
 	ctx.RegisterModuleType("apex_contributions_defaults", apexContributionsDefaultsFactory)
-	ctx.RegisterSingletonModuleType("all_apex_contributions", allApexContributionsFactory)
+	ctx.RegisterModuleType("all_apex_contributions", allApexContributionsFactory)
 }
 
 type apexContributions struct {
@@ -87,10 +87,10 @@
 // Based on product_config, it will create a dependency on the selected
 // apex_contributions per mainline module
 type allApexContributions struct {
-	SingletonModuleBase
+	ModuleBase
 }
 
-func allApexContributionsFactory() SingletonModule {
+func allApexContributionsFactory() Module {
 	module := &allApexContributions{}
 	InitAndroidModule(module)
 	return module
@@ -191,7 +191,7 @@
 
 // This module type does not have any build actions.
 func (a *allApexContributions) GenerateAndroidBuildActions(ctx ModuleContext) {
-}
-
-func (a *allApexContributions) GenerateSingletonBuildActions(ctx SingletonContext) {
+	if ctx.ModuleName() != "all_apex_contributions" {
+		ctx.ModuleErrorf("There can only be 1 all_apex_contributions module in build/soong")
+	}
 }
diff --git a/android/base_module_context.go b/android/base_module_context.go
index 7fa5876..719d6d5 100644
--- a/android/base_module_context.go
+++ b/android/base_module_context.go
@@ -88,6 +88,10 @@
 	// This method shouldn't be used directly, prefer the type-safe android.OtherModuleProvider instead.
 	otherModuleProvider(m blueprint.Module, provider blueprint.AnyProviderKey) (any, bool)
 
+	// OtherModuleIsAutoGenerated returns true if the module is auto generated by another module
+	// instead of being defined in Android.bp file.
+	OtherModuleIsAutoGenerated(m blueprint.Module) bool
+
 	// Provider returns the value for a provider for the current module.  If the value is
 	// not set it returns nil and false.  It panics if called before the appropriate
 	// mutator or GenerateBuildActions pass for the provider.  The value returned may be a deep
@@ -109,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
@@ -125,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
@@ -194,9 +199,15 @@
 	// singleton actions that are only done once for all variants of a module.
 	FinalModule() Module
 
+	// IsFinalModule returns if the current module is the last variant.  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 using VisitAllModuleVariants if the current module is the last one. This can be used to perform
+	// singleton actions that are only done once for all variants of a module.
+	IsFinalModule(module Module) bool
+
 	// VisitAllModuleVariants 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 == FinalModule().  Otherwise, care must be taken to not access any
+	// 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.
 	VisitAllModuleVariants(visit func(Module))
 
@@ -251,7 +262,9 @@
 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...)
 }
@@ -276,6 +289,10 @@
 	return b.bp.OtherModuleProvider(m, provider)
 }
 
+func (b *baseModuleContext) OtherModuleIsAutoGenerated(m blueprint.Module) bool {
+	return b.bp.OtherModuleIsAutoGenerated(m)
+}
+
 func (b *baseModuleContext) provider(provider blueprint.AnyProviderKey) (any, bool) {
 	return b.bp.Provider(provider)
 }
@@ -284,8 +301,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 {
@@ -450,18 +470,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)) {
@@ -584,6 +602,10 @@
 	return b.bp.FinalModule().(Module)
 }
 
+func (b *baseModuleContext) IsFinalModule(module Module) bool {
+	return b.bp.IsFinalModule(module)
+}
+
 // IsMetaDependencyTag returns true for cross-cutting metadata dependencies.
 func IsMetaDependencyTag(tag blueprint.DependencyTag) bool {
 	if tag == licenseKindTag {
diff --git a/android/build_prop.go b/android/build_prop.go
index 7c3c506..8389470 100644
--- a/android/build_prop.go
+++ b/android/build_prop.go
@@ -19,8 +19,12 @@
 )
 
 func init() {
-	ctx := InitRegistrationContext
+	registerBuildPropComponents(InitRegistrationContext)
+}
+
+func registerBuildPropComponents(ctx RegistrationContext) {
 	ctx.RegisterModuleType("build_prop", BuildPropFactory)
+	ctx.RegisterModuleType("android_info", AndroidInfoFactory)
 }
 
 type buildPropProperties struct {
@@ -38,6 +42,10 @@
 	// Path to a JSON file containing product configs.
 	Product_config *string `android:"path"`
 
+	// Path to android-info.txt file containing board specific info.
+	// This is empty for build.prop of all partitions except vendor.
+	Android_info *string `android:"path"`
+
 	// Optional subdirectory under which this file is installed into
 	Relative_install_path *string
 }
@@ -66,7 +74,10 @@
 	} else if partition == "odm" {
 		return ctx.Config().OdmPropFiles(ctx)
 	} else if partition == "vendor" {
-		// TODO (b/375500423): Add android-info.txt to prop files
+		if p.properties.Android_info != nil {
+			androidInfo := PathForModuleSrc(ctx, proptools.String(p.properties.Android_info))
+			return append(ctx.Config().VendorPropFiles(ctx), androidInfo)
+		}
 		return ctx.Config().VendorPropFiles(ctx)
 	}
 	return nil
@@ -98,6 +109,12 @@
 		return "product"
 	} else if p.SystemExtSpecific() {
 		return "system_ext"
+	} else if p.InstallInSystemDlkm() {
+		return "system_dlkm"
+	} else if p.InstallInVendorDlkm() {
+		return "vendor_dlkm"
+	} else if p.InstallInOdmDlkm() {
+		return "odm_dlkm"
 	}
 	return "system"
 }
@@ -108,16 +125,18 @@
 	"product",
 	"odm",
 	"vendor",
+	"system_dlkm",
+	"vendor_dlkm",
+	"odm_dlkm",
 }
 
 func (p *buildPropModule) GenerateAndroidBuildActions(ctx ModuleContext) {
-	p.outputFilePath = PathForModuleOut(ctx, "build.prop").OutputPath
-	if !ctx.Config().KatiEnabled() {
-		WriteFileRule(ctx, p.outputFilePath, "# no build.prop if kati is disabled")
-		ctx.SetOutputFiles(Paths{p.outputFilePath}, "")
-		return
+	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
+
 	partition := p.partition(ctx.DeviceConfig())
 	if !InList(partition, validPartitions) {
 		ctx.PropertyErrorf("partition", "unsupported partition %q: only %q are supported", partition, validPartitions)
diff --git a/android/build_prop_test.go b/android/build_prop_test.go
new file mode 100644
index 0000000..e75975a
--- /dev/null
+++ b/android/build_prop_test.go
@@ -0,0 +1,41 @@
+// 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 android
+
+import (
+	"testing"
+)
+
+func TestPropFileInputs(t *testing.T) {
+	bp := `
+build_prop {
+    name: "vendor-build.prop",
+    stem: "build.prop",
+    vendor: true,
+    android_info: ":board-info",
+    //product_config: ":product_config",
+}
+android_info {
+    name: "board-info",
+    stem: "android-info.txt",
+}
+`
+
+	res := GroupFixturePreparers(
+		FixtureRegisterWithContext(registerBuildPropComponents),
+	).RunTestWithBp(t, bp)
+	buildPropCmd := res.ModuleForTests("vendor-build.prop", "").Rule("vendor-build.prop_.vendor-build.prop").RuleParams.Command
+	AssertStringDoesContain(t, "Could not find android-info in prop files of vendor build.prop", buildPropCmd, "--prop-files=out/soong/.intermediates/board-info/android-info.prop")
+}
diff --git a/android/config.go b/android/config.go
index 380cd1c..27d3b87 100644
--- a/android/config.go
+++ b/android/config.go
@@ -413,18 +413,21 @@
 // To add a new feature to the list, add the field in the struct
 // `partialCompileFlags` above, and then add the name of the field in the
 // switch statement below.
-func (c *config) parsePartialCompileFlags() (partialCompileFlags, error) {
-	defaultFlags := partialCompileFlags{
-		// Set any opt-out flags here.  Opt-in flags are off by default.
-		enabled: false,
+var defaultPartialCompileFlags = partialCompileFlags{
+	// Set any opt-out flags here.  Opt-in flags are off by default.
+	enabled: false,
+}
+
+func (c *config) parsePartialCompileFlags(isEngBuild bool) (partialCompileFlags, error) {
+	if !isEngBuild {
+		return partialCompileFlags{}, nil
 	}
 	value := c.Getenv("SOONG_PARTIAL_COMPILE")
-
 	if value == "" {
-		return defaultFlags, nil
+		return defaultPartialCompileFlags, nil
 	}
 
-	ret := defaultFlags
+	ret := defaultPartialCompileFlags
 	tokens := strings.Split(strings.ToLower(value), ",")
 	makeVal := func(state string, defaultValue bool) bool {
 		switch state {
@@ -455,17 +458,17 @@
 		}
 		switch tok {
 		case "true":
-			ret = defaultFlags
+			ret = defaultPartialCompileFlags
 			ret.enabled = true
 		case "false":
 			// Set everything to false.
 			ret = partialCompileFlags{}
 		case "enabled":
-			ret.enabled = makeVal(state, defaultFlags.enabled)
+			ret.enabled = makeVal(state, defaultPartialCompileFlags.enabled)
 		case "use_d8":
-			ret.use_d8 = makeVal(state, defaultFlags.use_d8)
+			ret.use_d8 = makeVal(state, defaultPartialCompileFlags.use_d8)
 		default:
-			return partialCompileFlags{}, fmt.Errorf("Unknown SOONG_PARTIAL_COMPILE value: %v", value)
+			return partialCompileFlags{}, fmt.Errorf("Unknown SOONG_PARTIAL_COMPILE value: %v", tok)
 		}
 	}
 	return ret, nil
@@ -616,6 +619,8 @@
 
 		buildFromSourceStub: cmdArgs.BuildFromSourceStub,
 	}
+	variant, ok := os.LookupEnv("TARGET_BUILD_VARIANT")
+	isEngBuild := !ok || variant == "eng"
 
 	config.deviceConfig = &deviceConfig{
 		config: config,
@@ -657,7 +662,7 @@
 		return Config{}, err
 	}
 
-	config.partialCompileFlags, err = config.parsePartialCompileFlags()
+	config.partialCompileFlags, err = config.parsePartialCompileFlags(isEngBuild)
 	if err != nil {
 		return Config{}, err
 	}
@@ -862,7 +867,7 @@
 }
 
 func (c *config) TargetsJava21() bool {
-	return c.IsEnvTrue("EXPERIMENTAL_TARGET_JAVA_VERSION_21")
+	return c.productVariables.GetBuildFlagBool("RELEASE_TARGET_JAVA_21")
 }
 
 // EnvDeps returns the environment variables this build depends on. The first
@@ -1517,6 +1522,13 @@
 	return "vendor"
 }
 
+func (c *deviceConfig) VendorDlkmPath() string {
+	if c.config.productVariables.VendorDlkmPath != nil {
+		return *c.config.productVariables.VendorDlkmPath
+	}
+	return "vendor_dlkm"
+}
+
 func (c *deviceConfig) BuildingVendorImage() bool {
 	return proptools.Bool(c.config.productVariables.BuildingVendorImage)
 }
@@ -1544,6 +1556,17 @@
 	return "odm"
 }
 
+func (c *deviceConfig) BuildingOdmImage() bool {
+	return proptools.Bool(c.config.productVariables.BuildingOdmImage)
+}
+
+func (c *deviceConfig) OdmDlkmPath() string {
+	if c.config.productVariables.OdmDlkmPath != nil {
+		return *c.config.productVariables.OdmDlkmPath
+	}
+	return "odm_dlkm"
+}
+
 func (c *deviceConfig) ProductPath() string {
 	if c.config.productVariables.ProductPath != nil {
 		return *c.config.productVariables.ProductPath
@@ -1562,6 +1585,31 @@
 	return "system_ext"
 }
 
+func (c *deviceConfig) SystemDlkmPath() string {
+	if c.config.productVariables.SystemDlkmPath != nil {
+		return *c.config.productVariables.SystemDlkmPath
+	}
+	return "system_dlkm"
+}
+
+func (c *deviceConfig) OemPath() string {
+	if c.config.productVariables.OemPath != nil {
+		return *c.config.productVariables.OemPath
+	}
+	return "oem"
+}
+
+func (c *deviceConfig) UserdataPath() string {
+	if c.config.productVariables.UserdataPath != nil {
+		return *c.config.productVariables.UserdataPath
+	}
+	return "data"
+}
+
+func (c *deviceConfig) BuildingUserdataImage() bool {
+	return proptools.Bool(c.config.productVariables.BuildingUserdataImage)
+}
+
 func (c *deviceConfig) BtConfigIncludeDir() string {
 	return String(c.config.productVariables.BtConfigIncludeDir)
 }
@@ -2225,3 +2273,27 @@
 func (c Config) InstallApexSystemServerDexpreoptSamePartition() bool {
 	return c.config.productVariables.GetBuildFlagBool("RELEASE_INSTALL_APEX_SYSTEMSERVER_DEXPREOPT_SAME_PARTITION")
 }
+
+func (c *config) DeviceMatrixFile() []string {
+	return c.productVariables.DeviceMatrixFile
+}
+
+func (c *config) ProductManifestFiles() []string {
+	return c.productVariables.ProductManifestFiles
+}
+
+func (c *config) SystemManifestFile() []string {
+	return c.productVariables.SystemManifestFile
+}
+
+func (c *config) SystemExtManifestFiles() []string {
+	return c.productVariables.SystemExtManifestFiles
+}
+
+func (c *config) DeviceManifestFiles() []string {
+	return c.productVariables.DeviceManifestFiles
+}
+
+func (c *config) OdmManifestFiles() []string {
+	return c.productVariables.OdmManifestFiles
+}
diff --git a/android/config_test.go b/android/config_test.go
index 7732168..adb5ffa 100644
--- a/android/config_test.go
+++ b/android/config_test.go
@@ -212,3 +212,48 @@
 		assertStringEquals(t, "apex1:jarA", list5.String())
 	})
 }
+
+func (p partialCompileFlags) updateEnabled(value bool) partialCompileFlags {
+	p.enabled = value
+	return p
+}
+
+func (p partialCompileFlags) updateUseD8(value bool) partialCompileFlags {
+	p.use_d8 = value
+	return p
+}
+
+func TestPartialCompile(t *testing.T) {
+	mockConfig := func(value string) *config {
+		c := &config{
+			env: map[string]string{
+				"SOONG_PARTIAL_COMPILE": value,
+			},
+		}
+		return c
+	}
+	tests := []struct {
+		value      string
+		isEngBuild bool
+		expected   partialCompileFlags
+	}{
+		{"", true, defaultPartialCompileFlags},
+		{"false", true, partialCompileFlags{}},
+		{"true", true, defaultPartialCompileFlags.updateEnabled(true)},
+		{"true", false, partialCompileFlags{}},
+		{"true,use_d8", true, defaultPartialCompileFlags.updateEnabled(true).updateUseD8(true)},
+		{"true,-use_d8", true, defaultPartialCompileFlags.updateEnabled(true).updateUseD8(false)},
+		{"use_d8,false", true, partialCompileFlags{}},
+		{"false,+use_d8", true, partialCompileFlags{}.updateUseD8(true)},
+	}
+
+	for _, test := range tests {
+		t.Run(test.value, func(t *testing.T) {
+			config := mockConfig(test.value)
+			flags, _ := config.parsePartialCompileFlags(test.isEngBuild)
+			if flags != test.expected {
+				t.Errorf("expected %v found %v", test.expected, flags)
+			}
+		})
+	}
+}
diff --git a/android/dirgroup.go b/android/dirgroup.go
index 20c4d13..62fbaa5 100644
--- a/android/dirgroup.go
+++ b/android/dirgroup.go
@@ -39,8 +39,7 @@
 }
 
 type DirInfo struct {
-	// TODO(b/358302178): Use DirectoryPaths instead of Paths
-	Dirs Paths
+	Dirs DirectoryPaths
 }
 
 var DirProvider = blueprint.NewProvider[DirInfo]()
diff --git a/android/filegroup_test.go b/android/filegroup_test.go
index 14e9368..670037d 100644
--- a/android/filegroup_test.go
+++ b/android/filegroup_test.go
@@ -1,55 +1,9 @@
 package android
 
 import (
-	"path/filepath"
 	"testing"
 )
 
-func TestFileGroupWithPathProp(t *testing.T) {
-	// TODO(b/247782695), TODO(b/242847534) Fix mixed builds for filegroups
-	t.Skip("Re-enable once filegroups are corrected for mixed builds")
-	outBaseDir := "outputbase"
-	pathPrefix := outBaseDir + "/execroot/__main__"
-	expectedOutputfile := filepath.Join(pathPrefix, "a/b/c/d/test.aidl")
-
-	testCases := []struct {
-		bp  string
-		rel string
-	}{
-		{
-			bp: `
-	filegroup {
-		name: "baz",
-		srcs: ["a/b/c/d/test.aidl"],
-		path: "a/b",
-		bazel_module: { label: "//:baz" },
-	}
-`,
-			rel: "c/d/test.aidl",
-		},
-		{
-			bp: `
-	filegroup {
-		name: "baz",
-		srcs: ["a/b/c/d/test.aidl"],
-		bazel_module: { label: "//:baz" },
-	}
-`,
-			rel: "a/b/c/d/test.aidl",
-		},
-	}
-
-	for _, testCase := range testCases {
-		result := GroupFixturePreparers(
-			PrepareForTestWithFilegroup,
-		).RunTestWithBp(t, testCase.bp)
-
-		fg := result.Module("baz", "").(*fileGroup)
-		AssertStringEquals(t, "src relativeRoot", testCase.rel, fg.srcs[0].Rel())
-		AssertStringEquals(t, "src full path", expectedOutputfile, fg.srcs[0].String())
-	}
-}
-
 func TestFilegroupDefaults(t *testing.T) {
 	bp := FixtureAddTextFile("p/Android.bp", `
 		filegroup_defaults {
diff --git a/android/init.go b/android/init.go
index 1ace344..d3a13d0 100644
--- a/android/init.go
+++ b/android/init.go
@@ -20,6 +20,7 @@
 	gob.Register(extraFilesZip{})
 	gob.Register(InstallPath{})
 	gob.Register(ModuleGenPath{})
+	gob.Register(ModuleObjPath{})
 	gob.Register(ModuleOutPath{})
 	gob.Register(OutputPath{})
 	gob.Register(PhonyPath{})
diff --git a/android/module.go b/android/module.go
index 1148430..fba989f 100644
--- a/android/module.go
+++ b/android/module.go
@@ -81,10 +81,14 @@
 	InstallInProduct() bool
 	InstallInVendor() bool
 	InstallInSystemExt() bool
+	InstallInSystemDlkm() bool
+	InstallInVendorDlkm() bool
+	InstallInOdmDlkm() bool
 	InstallForceOS() (*OsType, *ArchType)
 	PartitionTag(DeviceConfig) string
 	HideFromMake()
 	IsHideFromMake() bool
+	SkipInstall()
 	IsSkipInstall() bool
 	MakeUninstallable()
 	ReplacedByPrebuilt()
@@ -385,6 +389,15 @@
 	// Whether this module is installed to debug ramdisk
 	Debug_ramdisk *bool
 
+	// Install to partition system_dlkm when set to true.
+	System_dlkm_specific *bool
+
+	// Install to partition vendor_dlkm when set to true.
+	Vendor_dlkm_specific *bool
+
+	// Install to partition odm_dlkm when set to true.
+	Odm_dlkm_specific *bool
+
 	// Whether this module is built for non-native architectures (also known as native bridge binary)
 	Native_bridge_supported *bool `android:"arch_variant"`
 
@@ -1369,6 +1382,8 @@
 		if config.SystemExtPath() == "system_ext" {
 			partition = "system_ext"
 		}
+	} else if m.InstallInRamdisk() {
+		partition = "ramdisk"
 	}
 	return partition
 }
@@ -1421,6 +1436,7 @@
 func (m *ModuleBase) MakeUninstallable() {
 	m.commonProperties.UninstallableApexPlatformVariant = true
 	m.HideFromMake()
+	m.SkipInstall()
 }
 
 func (m *ModuleBase) ReplacedByPrebuilt() {
@@ -1533,6 +1549,18 @@
 	return false
 }
 
+func (m *ModuleBase) InstallInSystemDlkm() bool {
+	return Bool(m.commonProperties.System_dlkm_specific)
+}
+
+func (m *ModuleBase) InstallInVendorDlkm() bool {
+	return Bool(m.commonProperties.Vendor_dlkm_specific)
+}
+
+func (m *ModuleBase) InstallInOdmDlkm() bool {
+	return Bool(m.commonProperties.Odm_dlkm_specific)
+}
+
 func (m *ModuleBase) InstallForceOS() (*OsType, *ArchType) {
 	return nil, nil
 }
@@ -1806,6 +1834,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
@@ -1820,6 +1854,8 @@
 	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
 }
 
 var CommonPropertiesProviderKey = blueprint.NewProvider[CommonPropertiesProviderData]()
@@ -2008,7 +2044,11 @@
 		ctx.GetMissingDependencies()
 	}
 
-	if m == ctx.FinalModule().(Module).base() {
+	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() {
 			return
@@ -2084,6 +2124,7 @@
 
 	commonData := CommonPropertiesProviderData{
 		ReplacedByPrebuilt: m.commonProperties.ReplacedByPrebuilt,
+		CompileTarget:      m.commonProperties.CompileTarget,
 	}
 	if m.commonProperties.ForcedDisabled {
 		commonData.Enabled = false
@@ -2603,7 +2644,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)
@@ -2614,7 +2655,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)
@@ -2647,20 +2688,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
@@ -2669,26 +2724,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 9fa3a62..2014907 100644
--- a/android/module_context.go
+++ b/android/module_context.go
@@ -16,12 +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"
 )
 
@@ -195,6 +196,9 @@
 	InstallInOdm() bool
 	InstallInProduct() bool
 	InstallInVendor() bool
+	InstallInSystemDlkm() bool
+	InstallInVendorDlkm() bool
+	InstallInOdmDlkm() bool
 	InstallForceOS() (*OsType, *ArchType)
 
 	RequiredModuleNames(ctx ConfigurableEvaluatorContext) []string
@@ -435,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 {
@@ -492,15 +498,23 @@
 	return m.module.InstallInVendor()
 }
 
+func (m *moduleContext) InstallInSystemDlkm() bool {
+	return m.module.InstallInSystemDlkm()
+}
+
+func (m *moduleContext) InstallInVendorDlkm() bool {
+	return m.module.InstallInVendorDlkm()
+}
+
+func (m *moduleContext) InstallInOdmDlkm() bool {
+	return m.module.InstallInOdmDlkm()
+}
+
 func (m *moduleContext) skipInstall() bool {
 	if m.module.base().commonProperties.SkipInstall {
 		return true
 	}
 
-	if m.module.base().commonProperties.HideFromMake {
-		return true
-	}
-
 	// We'll need a solution for choosing which of modules with the same name in different
 	// namespaces to install.  For now, reuse the list of namespaces exported to Make as the
 	// list of namespaces to install in a Soong-only build.
@@ -519,6 +533,10 @@
 		return false
 	}
 
+	if m.module.base().commonProperties.HideFromMake {
+		return false
+	}
+
 	if proptools.Bool(m.module.base().commonProperties.No_full_install) {
 		return false
 	}
@@ -562,9 +580,22 @@
 	m.aconfigFilePaths = paths
 }
 
+func (m *moduleContext) getOwnerAndOverrides() (string, []string) {
+	owner := m.ModuleName()
+	overrides := slices.Clone(m.Module().base().commonProperties.Overrides)
+	if b, ok := m.Module().(OverridableModule); ok {
+		if b.GetOverriddenBy() != "" {
+			// overriding variant of base module
+			overrides = append(overrides, m.ModuleName()) // com.android.foo
+			owner = m.Module().Name()                     // com.company.android.foo
+		}
+	}
+	return owner, overrides
+}
+
 func (m *moduleContext) packageFile(fullInstallPath InstallPath, srcPath Path, executable bool) PackagingSpec {
 	licenseFiles := m.Module().EffectiveLicenseFiles()
-	overrides := CopyOf(m.Module().base().commonProperties.Overrides)
+	owner, overrides := m.getOwnerAndOverrides()
 	spec := PackagingSpec{
 		relPathInPackage:      Rel(m, fullInstallPath.PartitionDir(), fullInstallPath.String()),
 		srcPath:               srcPath,
@@ -576,7 +607,7 @@
 		aconfigPaths:          m.getAconfigPaths(),
 		archType:              m.target.Arch.ArchType,
 		overrides:             &overrides,
-		owner:                 m.ModuleName(),
+		owner:                 owner,
 	}
 	m.packagingSpecs = append(m.packagingSpecs, spec)
 	return spec
@@ -695,7 +726,7 @@
 		m.installFiles = append(m.installFiles, fullInstallPath)
 	}
 
-	overrides := CopyOf(m.Module().base().commonProperties.Overrides)
+	owner, overrides := m.getOwnerAndOverrides()
 	m.packagingSpecs = append(m.packagingSpecs, PackagingSpec{
 		relPathInPackage: Rel(m, fullInstallPath.PartitionDir(), fullInstallPath.String()),
 		srcPath:          nil,
@@ -706,7 +737,7 @@
 		aconfigPaths:     m.getAconfigPaths(),
 		archType:         m.target.Arch.ArchType,
 		overrides:        &overrides,
-		owner:            m.ModuleName(),
+		owner:            owner,
 	})
 
 	return fullInstallPath
@@ -742,7 +773,7 @@
 		m.installFiles = append(m.installFiles, fullInstallPath)
 	}
 
-	overrides := CopyOf(m.Module().base().commonProperties.Overrides)
+	owner, overrides := m.getOwnerAndOverrides()
 	m.packagingSpecs = append(m.packagingSpecs, PackagingSpec{
 		relPathInPackage: Rel(m, fullInstallPath.PartitionDir(), fullInstallPath.String()),
 		srcPath:          nil,
@@ -753,7 +784,7 @@
 		aconfigPaths:     m.getAconfigPaths(),
 		archType:         m.target.Arch.ArchType,
 		overrides:        &overrides,
-		owner:            m.ModuleName(),
+		owner:            owner,
 	})
 
 	return fullInstallPath
diff --git a/android/module_proxy.go b/android/module_proxy.go
index 2a65072..1f96799 100644
--- a/android/module_proxy.go
+++ b/android/module_proxy.go
@@ -106,6 +106,18 @@
 	panic("method is not implemented on ModuleProxy")
 }
 
+func (m ModuleProxy) InstallInSystemDlkm() bool {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) InstallInVendorDlkm() bool {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) InstallInOdmDlkm() bool {
+	panic("method is not implemented on ModuleProxy")
+}
+
 func (m ModuleProxy) InstallForceOS() (*OsType, *ArchType) {
 	panic("method is not implemented on ModuleProxy")
 }
@@ -122,6 +134,10 @@
 	panic("method is not implemented on ModuleProxy")
 }
 
+func (m ModuleProxy) SkipInstall() {
+	panic("method is not implemented on ModuleProxy")
+}
+
 func (m ModuleProxy) IsSkipInstall() bool {
 	panic("method is not implemented on ModuleProxy")
 }
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/mutator.go b/android/mutator.go
index 4ddc606..fdd16a8 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -70,7 +70,7 @@
 	TopDown(name string, m TopDownMutator) MutatorHandle
 	BottomUp(name string, m BottomUpMutator) MutatorHandle
 	BottomUpBlueprint(name string, m blueprint.BottomUpMutator) MutatorHandle
-	Transition(name string, m TransitionMutator)
+	Transition(name string, m TransitionMutator) TransitionMutatorHandle
 }
 
 type RegisterMutatorFunc func(RegisterMutatorsContext)
@@ -579,7 +579,7 @@
 	}
 }
 
-func (x *registerMutatorsContext) Transition(name string, m TransitionMutator) {
+func (x *registerMutatorsContext) Transition(name string, m TransitionMutator) TransitionMutatorHandle {
 	atm := &androidTransitionMutator{
 		finalPhase: x.finalPhase,
 		mutator:    m,
@@ -587,8 +587,10 @@
 	}
 	mutator := &mutator{
 		name:              name,
-		transitionMutator: atm}
+		transitionMutator: atm,
+	}
 	x.mutators = append(x.mutators, mutator)
+	return mutator
 }
 
 func (x *registerMutatorsContext) mutatorName(name string) string {
@@ -625,7 +627,10 @@
 	} else if mutator.topDownMutator != nil {
 		handle = blueprintCtx.RegisterTopDownMutator(mutator.name, mutator.topDownMutator)
 	} else if mutator.transitionMutator != nil {
-		blueprintCtx.RegisterTransitionMutator(mutator.name, mutator.transitionMutator)
+		handle := blueprintCtx.RegisterTransitionMutator(mutator.name, mutator.transitionMutator)
+		if mutator.neverFar {
+			handle.NeverFar()
+		}
 	}
 
 	// Forward booleans set on the MutatorHandle to the blueprint.MutatorHandle.
@@ -681,6 +686,14 @@
 	MutatesGlobalState() MutatorHandle
 }
 
+type TransitionMutatorHandle interface {
+	// NeverFar causes the variations created by this mutator to never be ignored when adding
+	// far variation dependencies. Normally, far variation dependencies ignore all the variants
+	// of the source module, and only use the variants explicitly requested by the
+	// AddFarVariationDependencies call.
+	NeverFar() MutatorHandle
+}
+
 func (mutator *mutator) Parallel() MutatorHandle {
 	return mutator
 }
@@ -715,6 +728,11 @@
 	return mutator
 }
 
+func (mutator *mutator) NeverFar() MutatorHandle {
+	mutator.neverFar = true
+	return mutator
+}
+
 func RegisterComponentsMutator(ctx RegisterMutatorsContext) {
 	ctx.BottomUp("component-deps", componentDepsMutator)
 }
diff --git a/android/neverallow.go b/android/neverallow.go
index 44ac2cd..326150b 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -63,6 +63,7 @@
 	AddNeverAllowRules(createLimitDirgroupRule()...)
 	AddNeverAllowRules(createFilesystemIsAutoGeneratedRule())
 	AddNeverAllowRules(createKotlinPluginRule()...)
+	AddNeverAllowRules(createPrebuiltEtcBpDefineRule())
 }
 
 // Add a NeverAllow rule to the set of rules to apply.
@@ -321,6 +322,28 @@
 	}
 }
 
+// These module types are introduced to convert PRODUCT_COPY_FILES to Soong,
+// and is only intended to be used by filesystem_creator.
+func createPrebuiltEtcBpDefineRule() Rule {
+	return NeverAllow().
+		ModuleType(
+			"prebuilt_usr_srec",
+			"prebuilt_priv_app",
+			"prebuilt_rfs",
+			"prebuilt_framework",
+			"prebuilt_res",
+			"prebuilt_wlc_upt",
+			"prebuilt_odm",
+			"prebuilt_vendor_dlkm",
+			"prebuilt_bt_firmware",
+			"prebuilt_tvservice",
+			"prebuilt_optee",
+			"prebuilt_tvconfig",
+		).
+		DefinedInBpFile().
+		Because("module type not allowed to be defined in bp file")
+}
+
 func neverallowMutator(ctx BottomUpMutatorContext) {
 	m, ok := ctx.Module().(Module)
 	if !ok {
@@ -354,6 +377,10 @@
 			continue
 		}
 
+		if !n.appliesToBpDefinedModule(ctx) {
+			continue
+		}
+
 		ctx.ModuleErrorf("violates " + n.String())
 	}
 }
@@ -477,6 +504,8 @@
 
 	WithoutMatcher(properties string, matcher ValueMatcher) Rule
 
+	DefinedInBpFile() Rule
+
 	Because(reason string) Rule
 }
 
@@ -498,6 +527,8 @@
 	unlessProps ruleProperties
 
 	onlyBootclasspathJar bool
+
+	definedInBp bool
 }
 
 // Create a new NeverAllow rule.
@@ -571,6 +602,13 @@
 	return r
 }
 
+// DefinedInBpFile specifies that this rule applies to modules that are defined
+// in bp files, and does not apply to modules that are auto generated by other modules.
+func (r *rule) DefinedInBpFile() Rule {
+	r.definedInBp = true
+	return r
+}
+
 func selectMatcher(expected string) ValueMatcher {
 	if expected == "*" {
 		return anyMatcherInstance
@@ -665,6 +703,13 @@
 	return includeProps && !excludeProps
 }
 
+func (r *rule) appliesToBpDefinedModule(ctx BottomUpMutatorContext) bool {
+	if !r.definedInBp {
+		return true
+	}
+	return !ctx.OtherModuleIsAutoGenerated(ctx.Module()) == r.definedInBp
+}
+
 func StartsWith(prefix string) ValueMatcher {
 	return &startsWithMatcher{prefix}
 }
diff --git a/android/neverallow_test.go b/android/neverallow_test.go
index caec8c7..c74d5ff 100644
--- a/android/neverallow_test.go
+++ b/android/neverallow_test.go
@@ -374,6 +374,20 @@
 			`is_auto_generated property is only allowed for filesystem modules in build/soong/fsgen directory`,
 		},
 	},
+	// Test for the rule restricting use of prebuilt_* module
+	{
+		name: `"prebuilt_usr_srec" defined in Android.bp file`,
+		fs: map[string][]byte{
+			"a/b/Android.bp": []byte(`
+				prebuilt_usr_srec {
+					name: "foo",
+				}
+			`),
+		},
+		expectedErrors: []string{
+			`module type not allowed to be defined in bp file`,
+		},
+	},
 }
 
 var prepareForNeverAllowTest = GroupFixturePreparers(
@@ -383,6 +397,7 @@
 		ctx.RegisterModuleType("java_library_host", newMockJavaLibraryModule)
 		ctx.RegisterModuleType("java_device_for_host", newMockJavaLibraryModule)
 		ctx.RegisterModuleType("filesystem", newMockFilesystemModule)
+		ctx.RegisterModuleType("prebuilt_usr_srec", newMockPrebuiltUsrSrecModule)
 	}),
 )
 
@@ -482,3 +497,16 @@
 
 func (p *mockJavaLibraryModule) GenerateAndroidBuildActions(ModuleContext) {
 }
+
+type mockPrebuiltUsrSrecModule struct {
+	ModuleBase
+}
+
+func (p *mockPrebuiltUsrSrecModule) GenerateAndroidBuildActions(ModuleContext) {
+}
+
+func newMockPrebuiltUsrSrecModule() Module {
+	m := &mockPrebuiltUsrSrecModule{}
+	InitAndroidModule(m)
+	return m
+}
diff --git a/android/packaging.go b/android/packaging.go
index acafcd4..635922c 100644
--- a/android/packaging.go
+++ b/android/packaging.go
@@ -165,6 +165,10 @@
 	return p.partition
 }
 
+func (p *PackagingSpec) SetPartition(partition string) {
+	p.partition = partition
+}
+
 func (p *PackagingSpec) SkipInstall() bool {
 	return p.skipInstall
 }
@@ -186,6 +190,7 @@
 	// GatherPackagingSpecs gathers PackagingSpecs of transitive dependencies.
 	GatherPackagingSpecs(ctx ModuleContext) map[string]PackagingSpec
 	GatherPackagingSpecsWithFilter(ctx ModuleContext, filter func(PackagingSpec) bool) map[string]PackagingSpec
+	GatherPackagingSpecsWithFilterAndModifier(ctx ModuleContext, filter func(PackagingSpec) bool, modifier func(*PackagingSpec)) map[string]PackagingSpec
 
 	// CopyDepsToZip zips the built artifacts of the dependencies into the given zip file and
 	// returns zip entries in it. This is expected to be called in GenerateAndroidBuildActions,
@@ -207,38 +212,49 @@
 	// If this is set to true by a module type inheriting PackagingBase, the deps property
 	// collects the first target only even with compile_multilib: true.
 	DepsCollectFirstTargetOnly bool
+
+	// If this is set to try by a module type inheriting PackagingBase, the module type is
+	// allowed to utilize High_priority_deps.
+	AllowHighPriorityDeps bool
 }
 
-type depsProperty struct {
+type DepsProperty struct {
+	// Deps that have higher priority in packaging when there is a packaging conflict.
+	// For example, if multiple files are being installed to same filepath, the install file
+	// of the module listed in this property will have a higher priority over those in other
+	// deps properties.
+	High_priority_deps []string `android:"arch_variant"`
+
 	// Modules to include in this package
 	Deps proptools.Configurable[[]string] `android:"arch_variant"`
 }
 
 type packagingMultilibProperties struct {
-	First    depsProperty `android:"arch_variant"`
-	Common   depsProperty `android:"arch_variant"`
-	Lib32    depsProperty `android:"arch_variant"`
-	Lib64    depsProperty `android:"arch_variant"`
-	Both     depsProperty `android:"arch_variant"`
-	Prefer32 depsProperty `android:"arch_variant"`
+	First    DepsProperty `android:"arch_variant"`
+	Common   DepsProperty `android:"arch_variant"`
+	Lib32    DepsProperty `android:"arch_variant"`
+	Lib64    DepsProperty `android:"arch_variant"`
+	Both     DepsProperty `android:"arch_variant"`
+	Prefer32 DepsProperty `android:"arch_variant"`
 }
 
 type packagingArchProperties struct {
-	Arm64  depsProperty
-	Arm    depsProperty
-	X86_64 depsProperty
-	X86    depsProperty
+	Arm64  DepsProperty
+	Arm    DepsProperty
+	X86_64 DepsProperty
+	X86    DepsProperty
 }
 
 type PackagingProperties struct {
-	Deps     proptools.Configurable[[]string] `android:"arch_variant"`
-	Multilib packagingMultilibProperties      `android:"arch_variant"`
+	DepsProperty
+
+	Multilib packagingMultilibProperties `android:"arch_variant"`
 	Arch     packagingArchProperties
 }
 
 func InitPackageModule(p PackageModule) {
 	base := p.packagingBase()
-	p.AddProperties(&base.properties)
+	p.AddProperties(&base.properties, &base.properties.DepsProperty)
 }
 
 func (p *PackagingBase) packagingBase() *PackagingBase {
@@ -249,55 +265,72 @@
 // the current archicture when this module is not configured for multi target. When configured for
 // multi target, deps is selected for each of the targets and is NOT selected for the current
 // architecture which would be Common.
-func (p *PackagingBase) getDepsForArch(ctx BaseModuleContext, arch ArchType) []string {
-	get := func(prop proptools.Configurable[[]string]) []string {
-		return prop.GetOrDefault(ctx, nil)
+// It returns two lists, the normal and high priority deps, respectively.
+func (p *PackagingBase) getDepsForArch(ctx BaseModuleContext, arch ArchType) ([]string, []string) {
+	var normalDeps []string
+	var highPriorityDeps []string
+
+	get := func(prop DepsProperty) {
+		normalDeps = append(normalDeps, prop.Deps.GetOrDefault(ctx, nil)...)
+		highPriorityDeps = append(highPriorityDeps, prop.High_priority_deps...)
+	}
+	has := func(prop DepsProperty) bool {
+		return len(prop.Deps.GetOrDefault(ctx, nil)) > 0 || len(prop.High_priority_deps) > 0
 	}
 
-	var ret []string
 	if arch == ctx.Target().Arch.ArchType && len(ctx.MultiTargets()) == 0 {
-		ret = append(ret, get(p.properties.Deps)...)
+		get(p.properties.DepsProperty)
 	} else if arch.Multilib == "lib32" {
-		ret = append(ret, get(p.properties.Multilib.Lib32.Deps)...)
+		get(p.properties.Multilib.Lib32)
 		// multilib.prefer32.deps are added for lib32 only when they support 32-bit arch
-		for _, dep := range get(p.properties.Multilib.Prefer32.Deps) {
+		for _, dep := range p.properties.Multilib.Prefer32.Deps.GetOrDefault(ctx, nil) {
 			if checkIfOtherModuleSupportsLib32(ctx, dep) {
-				ret = append(ret, dep)
+				normalDeps = append(normalDeps, dep)
+			}
+		}
+		for _, dep := range p.properties.Multilib.Prefer32.High_priority_deps {
+			if checkIfOtherModuleSupportsLib32(ctx, dep) {
+				highPriorityDeps = append(highPriorityDeps, dep)
 			}
 		}
 	} else if arch.Multilib == "lib64" {
-		ret = append(ret, get(p.properties.Multilib.Lib64.Deps)...)
+		get(p.properties.Multilib.Lib64)
 		// multilib.prefer32.deps are added for lib64 only when they don't support 32-bit arch
-		for _, dep := range get(p.properties.Multilib.Prefer32.Deps) {
+		for _, dep := range p.properties.Multilib.Prefer32.Deps.GetOrDefault(ctx, nil) {
 			if !checkIfOtherModuleSupportsLib32(ctx, dep) {
-				ret = append(ret, dep)
+				normalDeps = append(normalDeps, dep)
+			}
+		}
+		for _, dep := range p.properties.Multilib.Prefer32.High_priority_deps {
+			if !checkIfOtherModuleSupportsLib32(ctx, dep) {
+				highPriorityDeps = append(highPriorityDeps, dep)
 			}
 		}
 	} else if arch == Common {
-		ret = append(ret, get(p.properties.Multilib.Common.Deps)...)
+		get(p.properties.Multilib.Common)
 	}
 
 	if p.DepsCollectFirstTargetOnly {
-		if len(get(p.properties.Multilib.First.Deps)) > 0 {
+		if has(p.properties.Multilib.First) {
 			ctx.PropertyErrorf("multilib.first.deps", "not supported. use \"deps\" instead")
 		}
 		for i, t := range ctx.MultiTargets() {
 			if t.Arch.ArchType == arch {
-				ret = append(ret, get(p.properties.Multilib.Both.Deps)...)
+				get(p.properties.Multilib.Both)
 				if i == 0 {
-					ret = append(ret, get(p.properties.Deps)...)
+					get(p.properties.DepsProperty)
 				}
 			}
 		}
 	} else {
-		if len(get(p.properties.Multilib.Both.Deps)) > 0 {
+		if has(p.properties.Multilib.Both) {
 			ctx.PropertyErrorf("multilib.both.deps", "not supported. use \"deps\" instead")
 		}
 		for i, t := range ctx.MultiTargets() {
 			if t.Arch.ArchType == arch {
-				ret = append(ret, get(p.properties.Deps)...)
+				get(p.properties.DepsProperty)
 				if i == 0 {
-					ret = append(ret, get(p.properties.Multilib.First.Deps)...)
+					get(p.properties.Multilib.First)
 				}
 			}
 		}
@@ -306,17 +339,21 @@
 	if ctx.Arch().ArchType == Common {
 		switch arch {
 		case Arm64:
-			ret = append(ret, get(p.properties.Arch.Arm64.Deps)...)
+			get(p.properties.Arch.Arm64)
 		case Arm:
-			ret = append(ret, get(p.properties.Arch.Arm.Deps)...)
+			get(p.properties.Arch.Arm)
 		case X86_64:
-			ret = append(ret, get(p.properties.Arch.X86_64.Deps)...)
+			get(p.properties.Arch.X86_64)
 		case X86:
-			ret = append(ret, get(p.properties.Arch.X86.Deps)...)
+			get(p.properties.Arch.X86)
 		}
 	}
 
-	return FirstUniqueStrings(ret)
+	if len(highPriorityDeps) > 0 && !p.AllowHighPriorityDeps {
+		ctx.ModuleErrorf("Usage of high_priority_deps is not allowed for %s module type", ctx.ModuleType())
+	}
+
+	return FirstUniqueStrings(normalDeps), FirstUniqueStrings(highPriorityDeps)
 }
 
 func getSupportedTargets(ctx BaseModuleContext) []Target {
@@ -360,6 +397,8 @@
 	IsPackagingItem() bool
 }
 
+var _ PackagingItem = (*PackagingItemAlwaysDepTag)(nil)
+
 // DepTag provides default implementation of PackagingItem interface.
 // PackagingBase-derived modules can define their own dependency tag by embedding this, which
 // can be passed to AddDeps() or AddDependencies().
@@ -371,31 +410,53 @@
 	return true
 }
 
+// highPriorityDepTag provides default implementation of HighPriorityPackagingItem interface.
+type highPriorityDepTag struct {
+	blueprint.DependencyTag
+}
+
 // See PackageModule.AddDeps
 func (p *PackagingBase) AddDeps(ctx BottomUpMutatorContext, depTag blueprint.DependencyTag) {
+	addDep := func(t Target, dep string, highPriority bool) {
+		if p.IgnoreMissingDependencies && !ctx.OtherModuleExists(dep) {
+			return
+		}
+		targetVariation := t.Variations()
+		sharedVariation := blueprint.Variation{
+			Mutator:   "link",
+			Variation: "shared",
+		}
+		// If a shared variation exists, use that. Static variants do not provide any standalone files
+		// for packaging.
+		if ctx.OtherModuleFarDependencyVariantExists([]blueprint.Variation{sharedVariation}, dep) {
+			targetVariation = append(targetVariation, sharedVariation)
+		}
+		depTagToUse := depTag
+		if highPriority {
+			depTagToUse = highPriorityDepTag{depTag}
+		}
+
+		ctx.AddFarVariationDependencies(targetVariation, depTagToUse, dep)
+	}
 	for _, t := range getSupportedTargets(ctx) {
-		for _, dep := range p.getDepsForArch(ctx, t.Arch.ArchType) {
-			if p.IgnoreMissingDependencies && !ctx.OtherModuleExists(dep) {
-				continue
-			}
-			targetVariation := t.Variations()
-			sharedVariation := blueprint.Variation{
-				Mutator:   "link",
-				Variation: "shared",
-			}
-			// If a shared variation exists, use that. Static variants do not provide any standalone files
-			// for packaging.
-			if ctx.OtherModuleFarDependencyVariantExists([]blueprint.Variation{sharedVariation}, dep) {
-				targetVariation = append(targetVariation, sharedVariation)
-			}
-			ctx.AddFarVariationDependencies(targetVariation, depTag, dep)
+		normalDeps, highPriorityDeps := p.getDepsForArch(ctx, t.Arch.ArchType)
+		for _, dep := range normalDeps {
+			addDep(t, dep, false)
+		}
+		for _, dep := range highPriorityDeps {
+			addDep(t, dep, true)
 		}
 	}
 }
 
-func (p *PackagingBase) GatherPackagingSpecsWithFilter(ctx ModuleContext, filter func(PackagingSpec) bool) map[string]PackagingSpec {
-	// all packaging specs gathered from the dep.
-	var all []PackagingSpec
+// See PackageModule.GatherPackagingSpecs
+func (p *PackagingBase) GatherPackagingSpecsWithFilterAndModifier(ctx ModuleContext, filter func(PackagingSpec) bool, modifier func(*PackagingSpec)) map[string]PackagingSpec {
+	// packaging specs gathered from the dep that are not high priorities.
+	var regularPriorities []PackagingSpec
+
+	// all packaging specs gathered from the high priority deps.
+	var highPriorities []PackagingSpec
+
 	// Name of the dependency which requested the packaging spec.
 	// If this dep is overridden, the packaging spec will not be installed via this dependency chain.
 	// (the packaging spec might still be installed if there are some other deps which depend on it).
@@ -420,7 +481,8 @@
 	}
 
 	ctx.VisitDirectDeps(func(child Module) {
-		if pi, ok := ctx.OtherModuleDependencyTag(child).(PackagingItem); !ok || !pi.IsPackagingItem() {
+		depTag := ctx.OtherModuleDependencyTag(child)
+		if pi, ok := depTag.(PackagingItem); !ok || !pi.IsPackagingItem() {
 			return
 		}
 		for _, ps := range OtherModuleProviderOrDefault(
@@ -434,7 +496,17 @@
 					continue
 				}
 			}
-			all = append(all, ps)
+
+			if modifier != nil {
+				modifier(&ps)
+			}
+
+			if _, ok := depTag.(highPriorityDepTag); ok {
+				highPriorities = append(highPriorities, ps)
+			} else {
+				regularPriorities = append(regularPriorities, ps)
+			}
+
 			depNames = append(depNames, child.Name())
 			if ps.overrides != nil {
 				overridden = append(overridden, *ps.overrides...)
@@ -442,21 +514,26 @@
 		}
 	})
 
-	// all minus packaging specs that are overridden
-	var filtered []PackagingSpec
-	for index, ps := range all {
-		if ps.owner != "" && InList(ps.owner, overridden) {
-			continue
+	filterOverridden := func(input []PackagingSpec) []PackagingSpec {
+		// input minus packaging specs that are overridden
+		var filtered []PackagingSpec
+		for index, ps := range input {
+			if ps.owner != "" && InList(ps.owner, overridden) {
+				continue
+			}
+			// The dependency which requested this packaging spec has been overridden.
+			if InList(depNames[index], overridden) {
+				continue
+			}
+			filtered = append(filtered, ps)
 		}
-		// The dependency which requested this packaging spec has been overridden.
-		if InList(depNames[index], overridden) {
-			continue
-		}
-		filtered = append(filtered, ps)
+		return filtered
 	}
 
+	filteredRegularPriority := filterOverridden(regularPriorities)
+
 	m := make(map[string]PackagingSpec)
-	for _, ps := range filtered {
+	for _, ps := range filteredRegularPriority {
 		dstPath := ps.relPathInPackage
 		if existingPs, ok := m[dstPath]; ok {
 			if !existingPs.Equals(&ps) {
@@ -466,10 +543,30 @@
 		}
 		m[dstPath] = ps
 	}
+
+	filteredHighPriority := filterOverridden(highPriorities)
+	highPriorityPs := make(map[string]PackagingSpec)
+	for _, ps := range filteredHighPriority {
+		dstPath := ps.relPathInPackage
+		if existingPs, ok := highPriorityPs[dstPath]; ok {
+			if !existingPs.Equals(&ps) {
+				ctx.ModuleErrorf("packaging conflict at %v:\n%v\n%v", dstPath, existingPs, ps)
+			}
+			continue
+		}
+		highPriorityPs[dstPath] = ps
+		m[dstPath] = ps
+	}
+
 	return m
 }
 
 // See PackageModule.GatherPackagingSpecs
+func (p *PackagingBase) GatherPackagingSpecsWithFilter(ctx ModuleContext, filter func(PackagingSpec) bool) map[string]PackagingSpec {
+	return p.GatherPackagingSpecsWithFilterAndModifier(ctx, filter, nil)
+}
+
+// See PackageModule.GatherPackagingSpecs
 func (p *PackagingBase) GatherPackagingSpecs(ctx ModuleContext) map[string]PackagingSpec {
 	return p.GatherPackagingSpecsWithFilter(ctx, nil)
 }
diff --git a/android/path_properties.go b/android/path_properties.go
index f3c62ea..55a4dc0 100644
--- a/android/path_properties.go
+++ b/android/path_properties.go
@@ -52,16 +52,12 @@
 	var pathProperties []string
 	var pathDeviceFirstProperties []string
 	var pathDeviceFirstPrefer32Properties []string
-	var pathDeviceFirstVendorProperties []string
-	var pathDeviceFirstVendorSharedProperties []string
 	var pathDeviceCommonProperties []string
 	var pathCommonOsProperties []string
 	for _, ps := range props {
 		pathProperties = append(pathProperties, taggedPropertiesForPropertyStruct(ctx, ps, "path")...)
 		pathDeviceFirstProperties = append(pathDeviceFirstProperties, taggedPropertiesForPropertyStruct(ctx, ps, "path_device_first")...)
 		pathDeviceFirstPrefer32Properties = append(pathDeviceFirstPrefer32Properties, taggedPropertiesForPropertyStruct(ctx, ps, "path_device_first_prefer32")...)
-		pathDeviceFirstVendorProperties = append(pathDeviceFirstVendorProperties, taggedPropertiesForPropertyStruct(ctx, ps, "path_device_first_vendor")...)
-		pathDeviceFirstVendorSharedProperties = append(pathDeviceFirstVendorSharedProperties, taggedPropertiesForPropertyStruct(ctx, ps, "path_device_first_vendor_shared")...)
 		pathDeviceCommonProperties = append(pathDeviceCommonProperties, taggedPropertiesForPropertyStruct(ctx, ps, "path_device_common")...)
 		pathCommonOsProperties = append(pathCommonOsProperties, taggedPropertiesForPropertyStruct(ctx, ps, "path_common_os")...)
 	}
@@ -70,8 +66,6 @@
 	pathProperties = FirstUniqueStrings(pathProperties)
 	pathDeviceFirstProperties = FirstUniqueStrings(pathDeviceFirstProperties)
 	pathDeviceFirstPrefer32Properties = FirstUniqueStrings(pathDeviceFirstPrefer32Properties)
-	pathDeviceFirstVendorProperties = FirstUniqueStrings(pathDeviceFirstVendorProperties)
-	pathDeviceFirstVendorSharedProperties = FirstUniqueStrings(pathDeviceFirstVendorSharedProperties)
 	pathDeviceCommonProperties = FirstUniqueStrings(pathDeviceCommonProperties)
 	pathCommonOsProperties = FirstUniqueStrings(pathCommonOsProperties)
 
@@ -108,23 +102,6 @@
 			}
 		}
 	}
-	// path_device_first_vendor is path_device_first + vendor variation
-	deviceFirstVendorVariations := ctx.Config().AndroidFirstDeviceTarget.Variations()
-	deviceFirstVendorVariations = append(deviceFirstVendorVariations,
-		blueprint.Variation{Mutator: "image", Variation: "vendor"})
-	for _, s := range pathDeviceFirstVendorProperties {
-		if m, t := SrcIsModuleWithTag(s); m != "" {
-			ctx.AddVariationDependencies(deviceFirstVendorVariations, sourceOrOutputDepTag(m, t), m)
-		}
-	}
-	// path_device_first_vendor_shared is path_device_first_vendor + shared linkage variation
-	deviceFirstVendorSharedVariations := append(deviceFirstVendorVariations,
-		blueprint.Variation{Mutator: "link", Variation: "shared"})
-	for _, s := range pathDeviceFirstVendorSharedProperties {
-		if m, t := SrcIsModuleWithTag(s); m != "" {
-			ctx.AddVariationDependencies(deviceFirstVendorSharedVariations, sourceOrOutputDepTag(m, t), m)
-		}
-	}
 	// properties tagged "path_device_common" get the device common variant
 	for _, s := range pathDeviceCommonProperties {
 		if m, t := SrcIsModuleWithTag(s); m != "" {
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 bf2c3a0..8f066cc 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
 }
@@ -117,6 +119,9 @@
 	InstallInOdm() bool
 	InstallInProduct() bool
 	InstallInVendor() bool
+	InstallInSystemDlkm() bool
+	InstallInVendorDlkm() bool
+	InstallInOdmDlkm() bool
 	InstallForceOS() (*OsType, *ArchType)
 }
 
@@ -170,6 +175,18 @@
 	return ctx.Module().InstallInVendor()
 }
 
+func (ctx *baseModuleContextToModuleInstallPathContext) InstallInSystemDlkm() bool {
+	return ctx.Module().InstallInSystemDlkm()
+}
+
+func (ctx *baseModuleContextToModuleInstallPathContext) InstallInVendorDlkm() bool {
+	return ctx.Module().InstallInVendorDlkm()
+}
+
+func (ctx *baseModuleContextToModuleInstallPathContext) InstallInOdmDlkm() bool {
+	return ctx.Module().InstallInOdmDlkm()
+}
+
 func (ctx *baseModuleContextToModuleInstallPathContext) InstallForceOS() (*OsType, *ArchType) {
 	return ctx.Module().InstallForceOS()
 }
@@ -551,19 +568,39 @@
 	return ret
 }
 
+type directoryPath struct {
+	basePath
+}
+
+func (d *directoryPath) String() string {
+	return d.basePath.String()
+}
+
+func (d *directoryPath) base() basePath {
+	return d.basePath
+}
+
+// DirectoryPath represents a source path for directories. Incompatible with Path by design.
+type DirectoryPath interface {
+	String() string
+	base() basePath
+}
+
+var _ DirectoryPath = (*directoryPath)(nil)
+
+type DirectoryPaths []DirectoryPath
+
 // DirectoryPathsForModuleSrcExcludes returns a Paths{} containing the resolved references in
 // directory paths. Elements of paths are resolved as:
 //   - filepath, relative to local module directory, resolves as a filepath relative to the local
 //     source directory
 //   - other modules using the ":name" syntax. These modules must implement DirProvider.
-//
-// TODO(b/358302178): Implement DirectoryPath and change the return type.
-func DirectoryPathsForModuleSrc(ctx ModuleMissingDepsPathContext, paths []string) Paths {
-	var ret Paths
+func DirectoryPathsForModuleSrc(ctx ModuleMissingDepsPathContext, paths []string) DirectoryPaths {
+	var ret DirectoryPaths
 
 	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
@@ -576,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)
@@ -588,12 +625,12 @@
 			} else if !isDir {
 				ReportPathErrorf(ctx, "module directory path %q is not a directory", p)
 			} else {
-				ret = append(ret, p)
+				ret = append(ret, &directoryPath{basePath{path: p.path, rel: p.rel}})
 			}
 		}
 	}
 
-	seen := make(map[Path]bool, len(ret))
+	seen := make(map[DirectoryPath]bool, len(ret))
 	for _, path := range ret {
 		if seen[path] {
 			ReportPathErrorf(ctx, "duplicated path %q", path)
@@ -634,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, CommonPropertiesProviderKey).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 {
@@ -649,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.
 //
@@ -659,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
@@ -2057,6 +2116,10 @@
 	return base.Join(ctx, paths...)
 }
 
+func PathForSuiteInstall(ctx PathContext, suite string, pathComponents ...string) InstallPath {
+	return pathForPartitionInstallDir(ctx, "test_suites", "test_suites", false).Join(ctx, suite).Join(ctx, pathComponents...)
+}
+
 func InstallPathToOnDevicePath(ctx PathContext, path InstallPath) string {
 	rel := Rel(ctx, strings.TrimSuffix(path.PartitionDir(), path.partition), path.String())
 	return "/" + rel
@@ -2111,6 +2174,12 @@
 			partition = ctx.DeviceConfig().SystemExtPath()
 		} else if ctx.InstallInRoot() {
 			partition = "root"
+		} else if ctx.InstallInSystemDlkm() {
+			partition = ctx.DeviceConfig().SystemDlkmPath()
+		} else if ctx.InstallInVendorDlkm() {
+			partition = ctx.DeviceConfig().VendorDlkmPath()
+		} else if ctx.InstallInOdmDlkm() {
+			partition = ctx.DeviceConfig().OdmDlkmPath()
 		} else {
 			partition = "system"
 		}
@@ -2314,6 +2383,9 @@
 	inOdm           bool
 	inProduct       bool
 	inVendor        bool
+	inSystemDlkm    bool
+	inVendorDlkm    bool
+	inOdmDlkm       bool
 	forceOS         *OsType
 	forceArch       *ArchType
 }
@@ -2368,6 +2440,18 @@
 	return m.inVendor
 }
 
+func (m testModuleInstallPathContext) InstallInSystemDlkm() bool {
+	return m.inSystemDlkm
+}
+
+func (m testModuleInstallPathContext) InstallInVendorDlkm() bool {
+	return m.inVendorDlkm
+}
+
+func (m testModuleInstallPathContext) InstallInOdmDlkm() bool {
+	return m.inOdmDlkm
+}
+
 func (m testModuleInstallPathContext) InstallForceOS() (*OsType, *ArchType) {
 	return m.forceOS, m.forceArch
 }
@@ -2529,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/paths_test.go b/android/paths_test.go
index 941f0ca..5e618f9 100644
--- a/android/paths_test.go
+++ b/android/paths_test.go
@@ -1592,6 +1592,12 @@
 	})
 }
 
+func TestDirectoryPathIsIncompatibleWithPath(t *testing.T) {
+	d := (DirectoryPath)(&directoryPath{})
+	_, ok := d.(Path)
+	AssertBoolEquals(t, "directoryPath shouldn't implement Path", ok, false)
+}
+
 func ExampleOutputPath_ReplaceExtension() {
 	ctx := &configErrorWrapper{
 		config: TestConfig("out", nil, "", nil),
diff --git a/android/prebuilt.go b/android/prebuilt.go
index 5d75b62..51e72af 100644
--- a/android/prebuilt.go
+++ b/android/prebuilt.go
@@ -569,6 +569,7 @@
 		for _, moduleInFamily := range allModulesInFamily {
 			if moduleInFamily.Name() != selectedModuleInFamily.Name() {
 				moduleInFamily.HideFromMake()
+				moduleInFamily.SkipInstall()
 				// If this is a prebuilt module, unset properties.UsePrebuilt
 				// properties.UsePrebuilt might evaluate to true via soong config var fallback mechanism
 				// Set it to false explicitly so that the following mutator does not replace rdeps to this unselected prebuilt
@@ -639,6 +640,7 @@
 			}
 		} else {
 			m.HideFromMake()
+			m.SkipInstall()
 		}
 	}
 }
diff --git a/android/register.go b/android/register.go
index bb1ead7..8d2f19e 100644
--- a/android/register.go
+++ b/android/register.go
@@ -98,6 +98,7 @@
 	usesCreateModule        bool
 	mutatesDependencies     bool
 	mutatesGlobalState      bool
+	neverFar                bool
 }
 
 var _ sortableComponent = &mutator{}
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 56de9cd..403c184 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -575,25 +575,28 @@
 		nsjailCmd.WriteString(r.outDir.String())
 		nsjailCmd.WriteString(":nsjail_build_sandbox/out")
 
-		for _, input := range inputs {
+		addBindMount := func(src, dst string) {
 			nsjailCmd.WriteString(" -R $PWD/")
-			nsjailCmd.WriteString(input.String())
+			nsjailCmd.WriteString(src)
 			nsjailCmd.WriteString(":nsjail_build_sandbox/")
-			nsjailCmd.WriteString(r.nsjailPathForInputRel(input))
+			nsjailCmd.WriteString(dst)
+		}
+
+		for _, input := range inputs {
+			addBindMount(input.String(), r.nsjailPathForInputRel(input))
 		}
 		for _, tool := range tools {
-			nsjailCmd.WriteString(" -R $PWD/")
-			nsjailCmd.WriteString(tool.String())
-			nsjailCmd.WriteString(":nsjail_build_sandbox/")
-			nsjailCmd.WriteString(nsjailPathForToolRel(r.ctx, tool))
+			addBindMount(tool.String(), nsjailPathForToolRel(r.ctx, tool))
 		}
 		inputs = append(inputs, tools...)
 		for _, c := range r.commands {
+			for _, directory := range c.implicitDirectories {
+				addBindMount(directory.String(), directory.String())
+				// TODO(b/375551969): Add implicitDirectories to BuildParams, rather than relying on implicits
+				inputs = append(inputs, SourcePath{basePath: directory.base()})
+			}
 			for _, tool := range c.packagedTools {
-				nsjailCmd.WriteString(" -R $PWD/")
-				nsjailCmd.WriteString(tool.srcPath.String())
-				nsjailCmd.WriteString(":nsjail_build_sandbox/")
-				nsjailCmd.WriteString(nsjailPathForPackagedToolRel(tool))
+				addBindMount(tool.srcPath.String(), nsjailPathForPackagedToolRel(tool))
 				inputs = append(inputs, tool.srcPath)
 			}
 		}
@@ -917,16 +920,17 @@
 type RuleBuilderCommand struct {
 	rule *RuleBuilder
 
-	buf           strings.Builder
-	inputs        Paths
-	implicits     Paths
-	orderOnlys    Paths
-	validations   Paths
-	outputs       WritablePaths
-	depFiles      WritablePaths
-	tools         Paths
-	packagedTools []PackagingSpec
-	rspFiles      []rspFileAndPaths
+	buf                 strings.Builder
+	inputs              Paths
+	implicits           Paths
+	orderOnlys          Paths
+	validations         Paths
+	outputs             WritablePaths
+	depFiles            WritablePaths
+	tools               Paths
+	packagedTools       []PackagingSpec
+	rspFiles            []rspFileAndPaths
+	implicitDirectories DirectoryPaths
 }
 
 type rspFileAndPaths struct {
@@ -951,6 +955,10 @@
 	c.implicits = append(c.implicits, path)
 }
 
+func (c *RuleBuilderCommand) addImplicitDirectory(path DirectoryPath) {
+	c.implicitDirectories = append(c.implicitDirectories, path)
+}
+
 func (c *RuleBuilderCommand) addOrderOnly(path Path) {
 	checkPathNotNil(path)
 	c.orderOnlys = append(c.orderOnlys, path)
@@ -1313,6 +1321,16 @@
 	return c
 }
 
+// ImplicitDirectory adds the specified input directory to the dependencies without modifying the
+// command line. Added directories will be bind-mounted for the nsjail.
+func (c *RuleBuilderCommand) ImplicitDirectory(path DirectoryPath) *RuleBuilderCommand {
+	if !c.rule.nsjail {
+		panic("ImplicitDirectory() must be called after Nsjail()")
+	}
+	c.addImplicitDirectory(path)
+	return c
+}
+
 // GetImplicits returns the command's implicit inputs.
 func (c *RuleBuilderCommand) GetImplicits() Paths {
 	return c.implicits
diff --git a/android/singleton.go b/android/singleton.go
index 913bf6a..0754b0c 100644
--- a/android/singleton.go
+++ b/android/singleton.go
@@ -64,6 +64,7 @@
 
 	VisitAllModulesBlueprint(visit func(blueprint.Module))
 	VisitAllModules(visit func(Module))
+	VisitAllModuleProxies(visit func(proxy ModuleProxy))
 	VisitAllModulesIf(pred func(Module) bool, visit func(Module))
 
 	VisitDirectDeps(module Module, visit func(Module))
@@ -77,8 +78,10 @@
 
 	VisitAllModuleVariants(module Module, visit func(Module))
 
+	VisitAllModuleVariantProxies(module Module, visit func(proxy ModuleProxy))
+
 	PrimaryModule(module Module) Module
-	FinalModule(module Module) Module
+	IsFinalModule(module Module) bool
 
 	AddNinjaFileDeps(deps ...string)
 
@@ -193,7 +196,7 @@
 }
 
 // visitAdaptor wraps a visit function that takes an android.Module parameter into
-// a function that takes an blueprint.Module parameter and only calls the visit function if the
+// a function that takes a blueprint.Module parameter and only calls the visit function if the
 // blueprint.Module is an android.Module.
 func visitAdaptor(visit func(Module)) func(blueprint.Module) {
 	return func(module blueprint.Module) {
@@ -203,6 +206,16 @@
 	}
 }
 
+// visitProxyAdaptor wraps a visit function that takes an android.ModuleProxy parameter into
+// a function that takes a blueprint.ModuleProxy parameter.
+func visitProxyAdaptor(visit func(proxy ModuleProxy)) func(proxy blueprint.ModuleProxy) {
+	return func(module blueprint.ModuleProxy) {
+		visit(ModuleProxy{
+			module: module,
+		})
+	}
+}
+
 // predAdaptor wraps a pred function that takes an android.Module parameter
 // into a function that takes an blueprint.Module parameter and only calls the visit function if the
 // blueprint.Module is an android.Module, otherwise returns false.
@@ -224,6 +237,10 @@
 	s.SingletonContext.VisitAllModules(visitAdaptor(visit))
 }
 
+func (s *singletonContextAdaptor) VisitAllModuleProxies(visit func(proxy ModuleProxy)) {
+	s.SingletonContext.VisitAllModuleProxies(visitProxyAdaptor(visit))
+}
+
 func (s *singletonContextAdaptor) VisitAllModulesIf(pred func(Module) bool, visit func(Module)) {
 	s.SingletonContext.VisitAllModulesIf(predAdaptor(pred), visitAdaptor(visit))
 }
@@ -248,12 +265,16 @@
 	s.SingletonContext.VisitAllModuleVariants(module, visitAdaptor(visit))
 }
 
+func (s *singletonContextAdaptor) VisitAllModuleVariantProxies(module Module, visit func(proxy ModuleProxy)) {
+	s.SingletonContext.VisitAllModuleVariantProxies(module, visitProxyAdaptor(visit))
+}
+
 func (s *singletonContextAdaptor) PrimaryModule(module Module) Module {
 	return s.SingletonContext.PrimaryModule(module).(Module)
 }
 
-func (s *singletonContextAdaptor) FinalModule(module Module) Module {
-	return s.SingletonContext.FinalModule(module).(Module)
+func (s *singletonContextAdaptor) IsFinalModule(module Module) bool {
+	return s.SingletonContext.IsFinalModule(module)
 }
 
 func (s *singletonContextAdaptor) ModuleVariantsFromName(referer Module, name string) []Module {
diff --git a/android/util.go b/android/util.go
index 2d269b7..3fc4608 100644
--- a/android/util.go
+++ b/android/util.go
@@ -660,3 +660,13 @@
 	v, loaded := m.Map.LoadOrStore(key, value)
 	return v.(V), loaded
 }
+
+// AppendIfNotZero append the given value to the slice if it is not the zero value
+// for its type.
+func AppendIfNotZero[T comparable](slice []T, value T) []T {
+	var zeroValue T // Get the zero value of the type T
+	if value != zeroValue {
+		return append(slice, value)
+	}
+	return slice
+}
diff --git a/android/variable.go b/android/variable.go
index 2969f97..2d43c6d 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -338,12 +338,19 @@
 	HWASanIncludePaths []string `json:",omitempty"`
 	HWASanExcludePaths []string `json:",omitempty"`
 
-	VendorPath           *string `json:",omitempty"`
-	BuildingVendorImage  *bool   `json:",omitempty"`
-	OdmPath              *string `json:",omitempty"`
-	ProductPath          *string `json:",omitempty"`
-	BuildingProductImage *bool   `json:",omitempty"`
-	SystemExtPath        *string `json:",omitempty"`
+	VendorPath            *string `json:",omitempty"`
+	VendorDlkmPath        *string `json:",omitempty"`
+	BuildingVendorImage   *bool   `json:",omitempty"`
+	OdmPath               *string `json:",omitempty"`
+	BuildingOdmImage      *bool   `json:",omitempty"`
+	OdmDlkmPath           *string `json:",omitempty"`
+	ProductPath           *string `json:",omitempty"`
+	BuildingProductImage  *bool   `json:",omitempty"`
+	SystemExtPath         *string `json:",omitempty"`
+	SystemDlkmPath        *string `json:",omitempty"`
+	OemPath               *string `json:",omitempty"`
+	UserdataPath          *string `json:",omitempty"`
+	BuildingUserdataImage *bool   `json:",omitempty"`
 
 	ClangTidy  *bool   `json:",omitempty"`
 	TidyChecks *string `json:",omitempty"`
@@ -534,6 +541,13 @@
 	ExtraAllowedDepsTxt *string `json:",omitempty"`
 
 	AdbKeys *string `json:",omitempty"`
+
+	DeviceMatrixFile       []string `json:",omitempty"`
+	ProductManifestFiles   []string `json:",omitempty"`
+	SystemManifestFile     []string `json:",omitempty"`
+	SystemExtManifestFiles []string `json:",omitempty"`
+	DeviceManifestFiles    []string `json:",omitempty"`
+	OdmManifestFiles       []string `json:",omitempty"`
 }
 
 type PartitionQualifiedVariablesType struct {
@@ -564,6 +578,14 @@
 	BoardAvbRollbackIndexLocation string `json:",omitempty"`
 }
 
+type ChainedAvbPartitionProps struct {
+	Partitions            []string `json:",omitempty"`
+	Key                   string   `json:",omitempty"`
+	Algorithm             string   `json:",omitempty"`
+	RollbackIndex         string   `json:",omitempty"`
+	RollbackIndexLocation string   `json:",omitempty"`
+}
+
 type PartitionVariables struct {
 	ProductDirectory            string `json:",omitempty"`
 	PartitionQualifiedVariables map[string]PartitionQualifiedVariablesType
@@ -584,16 +606,47 @@
 	BoardExt4ShareDupBlocks        string `json:",omitempty"`
 	BoardFlashLogicalBlockSize     string `json:",omitempty"`
 	BoardFlashEraseBlockSize       string `json:",omitempty"`
-	BoardUsesRecoveryAsBoot        bool   `json:",omitempty"`
 	ProductUseDynamicPartitionSize bool   `json:",omitempty"`
 	CopyImagesForTargetFilesZip    bool   `json:",omitempty"`
 
-	BoardAvbEnable bool `json:",omitempty"`
+	// Boot image stuff
+	ProductBuildBootImage           bool   `json:",omitempty"`
+	ProductBuildInitBootImage       bool   `json:",omitempty"`
+	BoardUsesRecoveryAsBoot         bool   `json:",omitempty"`
+	BoardPrebuiltBootimage          string `json:",omitempty"`
+	BoardPrebuiltInitBootimage      string `json:",omitempty"`
+	BoardBootimagePartitionSize     string `json:",omitempty"`
+	BoardInitBootimagePartitionSize string `json:",omitempty"`
+	BoardBootHeaderVersion          string `json:",omitempty"`
 
-	ProductPackages      []string `json:",omitempty"`
-	ProductPackagesDebug []string `json:",omitempty"`
+	// Avb (android verified boot) stuff
+	BoardAvbEnable          bool                                `json:",omitempty"`
+	BoardAvbAlgorithm       string                              `json:",omitempty"`
+	BoardAvbKeyPath         string                              `json:",omitempty"`
+	BoardAvbRollbackIndex   string                              `json:",omitempty"`
+	BuildingVbmetaImage     bool                                `json:",omitempty"`
+	ChainedVbmetaPartitions map[string]ChainedAvbPartitionProps `json:",omitempty"`
 
-	ProductCopyFiles map[string]string `json:",omitempty"`
+	ProductPackages         []string `json:",omitempty"`
+	ProductPackagesDebug    []string `json:",omitempty"`
+	VendorLinkerConfigSrcs  []string `json:",omitempty"`
+	ProductLinkerConfigSrcs []string `json:",omitempty"`
+
+	BoardInfoFiles      []string `json:",omitempty"`
+	BootLoaderBoardName string   `json:",omitempty"`
+
+	ProductCopyFiles []string `json:",omitempty"`
+
+	BuildingSystemDlkmImage   bool     `json:",omitempty"`
+	SystemKernelModules       []string `json:",omitempty"`
+	SystemKernelBlocklistFile string   `json:",omitempty"`
+	SystemKernelLoadModules   []string `json:",omitempty"`
+	BuildingVendorDlkmImage   bool     `json:",omitempty"`
+	VendorKernelModules       []string `json:",omitempty"`
+	VendorKernelBlocklistFile string   `json:",omitempty"`
+	BuildingOdmDlkmImage      bool     `json:",omitempty"`
+	OdmKernelModules          []string `json:",omitempty"`
+	OdmKernelBlocklistFile    string   `json:",omitempty"`
 }
 
 func boolPtr(v bool) *bool {
diff --git a/android/vintf_data.go b/android/vintf_data.go
new file mode 100644
index 0000000..7823397
--- /dev/null
+++ b/android/vintf_data.go
@@ -0,0 +1,171 @@
+// 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 android
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/google/blueprint/proptools"
+)
+
+const (
+	deviceCmType          = "device_cm"
+	systemManifestType    = "system_manifest"
+	productManifestType   = "product_manifest"
+	systemExtManifestType = "system_ext_manifest"
+	vendorManifestType    = "vendor_manifest"
+	odmManifestType       = "odm_manifest"
+
+	defaultDcm               = "system/libhidl/vintfdata/device_compatibility_matrix.default.xml"
+	defaultSystemManifest    = "system/libhidl/vintfdata/manifest.xml"
+	defaultSystemExtManifest = "system/libhidl/vintfdata/system_ext_manifest.default.xml"
+)
+
+type vintfDataProperties struct {
+	// Optional name for the installed file. If unspecified it will be manifest.xml by default.
+	Filename *string
+
+	// Type of the vintf data type, the allowed type are device_compatibility_matrix, system_manifest,
+	// product_manifest, and system_ext_manifest.
+	Type *string
+}
+
+type vintfDataRule struct {
+	ModuleBase
+
+	properties vintfDataProperties
+
+	installDirPath InstallPath
+	outputFilePath OutputPath
+	noAction       bool
+}
+
+func init() {
+	registerVintfDataComponents(InitRegistrationContext)
+}
+
+func registerVintfDataComponents(ctx RegistrationContext) {
+	ctx.RegisterModuleType("vintf_data", vintfDataFactory)
+}
+
+// vintf_fragment module processes vintf fragment file and installs under etc/vintf/manifest.
+func vintfDataFactory() Module {
+	m := &vintfDataRule{}
+	m.AddProperties(
+		&m.properties,
+	)
+	InitAndroidArchModule(m, DeviceSupported, MultilibFirst)
+
+	return m
+}
+
+func (m *vintfDataRule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	builder := NewRuleBuilder(pctx, ctx)
+	gensrc := PathForModuleOut(ctx, "manifest.xml")
+	assembleVintfEnvs := []string{}
+	inputPaths := make(Paths, 0)
+
+	switch proptools.String(m.properties.Type) {
+	case deviceCmType:
+		assembleVintfEnvs = append(assembleVintfEnvs, fmt.Sprintf("BOARD_SYSTEMSDK_VERSIONS=\"%s\"", strings.Join(ctx.DeviceConfig().SystemSdkVersions(), " ")))
+
+		deviceMatrixs := PathsForSource(ctx, ctx.Config().DeviceMatrixFile())
+		if len(deviceMatrixs) > 0 {
+			inputPaths = append(inputPaths, deviceMatrixs...)
+		} else {
+			inputPaths = append(inputPaths, PathForSource(ctx, defaultDcm))
+		}
+	case systemManifestType:
+		assembleVintfEnvs = append(assembleVintfEnvs, fmt.Sprintf("PLATFORM_SYSTEMSDK_VERSIONS=\"%s\"", strings.Join(ctx.DeviceConfig().PlatformSystemSdkVersions(), " ")))
+
+		inputPaths = append(inputPaths, PathForSource(ctx, defaultSystemManifest))
+		systemManifestFiles := PathsForSource(ctx, ctx.Config().SystemManifestFile())
+		if len(systemManifestFiles) > 0 {
+			inputPaths = append(inputPaths, systemManifestFiles...)
+		}
+	case productManifestType:
+		productManifestFiles := PathsForSource(ctx, ctx.Config().ProductManifestFiles())
+		// Only need to generate the manifest if PRODUCT_MANIFEST_FILES not defined.
+		if len(productManifestFiles) == 0 {
+			m.noAction = true
+			return
+		}
+
+		inputPaths = append(inputPaths, productManifestFiles...)
+	case systemExtManifestType:
+		assembleVintfEnvs = append(assembleVintfEnvs, fmt.Sprintf("PROVIDED_VNDK_VERSIONS=\"%s\"", strings.Join(ctx.DeviceConfig().ExtraVndkVersions(), " ")))
+
+		inputPaths = append(inputPaths, PathForSource(ctx, defaultSystemExtManifest))
+		systemExtManifestFiles := PathsForSource(ctx, ctx.Config().SystemExtManifestFiles())
+		if len(systemExtManifestFiles) > 0 {
+			inputPaths = append(inputPaths, systemExtManifestFiles...)
+		}
+	case vendorManifestType:
+		assembleVintfEnvs = append(assembleVintfEnvs, fmt.Sprintf("BOARD_SEPOLICY_VERS=\"%s\"", ctx.DeviceConfig().BoardSepolicyVers()))
+		assembleVintfEnvs = append(assembleVintfEnvs, fmt.Sprintf("PRODUCT_ENFORCE_VINTF_MANIFEST=%t", *ctx.Config().productVariables.Enforce_vintf_manifest))
+		deviceManifestFiles := PathsForSource(ctx, ctx.Config().DeviceManifestFiles())
+		// Only need to generate the manifest if DEVICE_MANIFEST_FILE is defined.
+		if len(deviceManifestFiles) == 0 {
+			m.noAction = true
+			return
+		}
+
+		inputPaths = append(inputPaths, deviceManifestFiles...)
+	case odmManifestType:
+		assembleVintfEnvs = append(assembleVintfEnvs, "VINTF_IGNORE_TARGET_FCM_VERSION=true")
+		odmManifestFiles := PathsForSource(ctx, ctx.Config().OdmManifestFiles())
+		// Only need to generate the manifest if ODM_MANIFEST_FILES is defined.
+		if len(odmManifestFiles) == 0 {
+			m.noAction = true
+			return
+		}
+
+		inputPaths = append(inputPaths, odmManifestFiles...)
+	default:
+		panic(fmt.Errorf("For %s: The attribute 'type' value only allowed device_cm, system_manifest, product_manifest, system_ext_manifest!", ctx.Module().Name()))
+	}
+
+	// Process vintf fragment source file with assemble_vintf tool
+	builder.Command().
+		Flags(assembleVintfEnvs).
+		BuiltTool("assemble_vintf").
+		FlagWithArg("-i ", strings.Join(inputPaths.Strings(), ":")).
+		FlagWithOutput("-o ", gensrc)
+
+	builder.Build("assemble_vintf", "Process vintf data "+gensrc.String())
+
+	m.installDirPath = PathForModuleInstall(ctx, "etc", "vintf")
+	m.outputFilePath = gensrc.OutputPath
+
+	installFileName := "manifest.xml"
+	if filename := proptools.String(m.properties.Filename); filename != "" {
+		installFileName = filename
+	}
+
+	ctx.InstallFile(m.installDirPath, installFileName, gensrc)
+}
+
+// Make this module visible to AndroidMK so it can be referenced from modules defined from Android.mk files
+func (m *vintfDataRule) AndroidMkEntries() []AndroidMkEntries {
+	if m.noAction {
+		return []AndroidMkEntries{}
+	}
+
+	return []AndroidMkEntries{{
+		Class:      "ETC",
+		OutputFile: OptionalPathForPath(m.outputFilePath),
+	}}
+}
diff --git a/apex/apex.go b/apex/apex.go
index a1879f5..04b5a07 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -105,11 +105,10 @@
 	Rros []string
 
 	// List of bootclasspath fragments that are embedded inside this APEX bundle.
-	Bootclasspath_fragments []string
+	Bootclasspath_fragments proptools.Configurable[[]string]
 
 	// List of systemserverclasspath fragments that are embedded inside this APEX bundle.
-	Systemserverclasspath_fragments        proptools.Configurable[[]string]
-	ResolvedSystemserverclasspathFragments []string `blueprint:"mutated"`
+	Systemserverclasspath_fragments proptools.Configurable[[]string]
 
 	// List of java libraries that are embedded inside this APEX bundle.
 	Java_libs []string
@@ -891,13 +890,11 @@
 		}
 	}
 
-	a.properties.ResolvedSystemserverclasspathFragments = a.properties.Systemserverclasspath_fragments.GetOrDefault(ctx, nil)
-
 	// Common-arch dependencies come next
 	commonVariation := ctx.Config().AndroidCommonTarget.Variations()
 	ctx.AddFarVariationDependencies(commonVariation, rroTag, a.properties.Rros...)
-	ctx.AddFarVariationDependencies(commonVariation, bcpfTag, a.properties.Bootclasspath_fragments...)
-	ctx.AddFarVariationDependencies(commonVariation, sscpfTag, a.properties.ResolvedSystemserverclasspathFragments...)
+	ctx.AddFarVariationDependencies(commonVariation, bcpfTag, a.properties.Bootclasspath_fragments.GetOrDefault(ctx, nil)...)
+	ctx.AddFarVariationDependencies(commonVariation, sscpfTag, a.properties.Systemserverclasspath_fragments.GetOrDefault(ctx, nil)...)
 	ctx.AddFarVariationDependencies(commonVariation, javaLibTag, a.properties.Java_libs...)
 	ctx.AddFarVariationDependencies(commonVariation, fsTag, a.properties.Filesystems...)
 	ctx.AddFarVariationDependencies(commonVariation, compatConfigTag, a.properties.Compat_configs...)
@@ -2776,8 +2773,8 @@
 // Collect information for opening IDE project files in java/jdeps.go.
 func (a *apexBundle) IDEInfo(ctx android.BaseModuleContext, dpInfo *android.IdeInfo) {
 	dpInfo.Deps = append(dpInfo.Deps, a.properties.Java_libs...)
-	dpInfo.Deps = append(dpInfo.Deps, a.properties.Bootclasspath_fragments...)
-	dpInfo.Deps = append(dpInfo.Deps, a.properties.ResolvedSystemserverclasspathFragments...)
+	dpInfo.Deps = append(dpInfo.Deps, a.properties.Bootclasspath_fragments.GetOrDefault(ctx, nil)...)
+	dpInfo.Deps = append(dpInfo.Deps, a.properties.Systemserverclasspath_fragments.GetOrDefault(ctx, nil)...)
 }
 
 func init() {
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 6110bac..d0494d6 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -10186,7 +10186,10 @@
 			deps: [
 				"libfoo",
 			],
-			linker_config_src: "linker.config.json",
+			linker_config: {
+				gen_linker_config: true,
+				linker_config_srcs: ["linker.config.json"],
+			},
 		}
 
 		cc_library {
diff --git a/apex/builder.go b/apex/builder.go
index 305d509..b04a9d7 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -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)
 	}
diff --git a/cc/Android.bp b/cc/Android.bp
index a7b6d81..3b29ae8 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -122,3 +122,31 @@
     // Used by plugins
     visibility: ["//visibility:public"],
 }
+
+phony {
+    name: "llndk_libs",
+    required: [
+        "libEGL",
+        "libGLESv1_CM",
+        "libGLESv2",
+        "libGLESv3",
+        "libRS",
+        "libandroid_net",
+        "libapexsupport",
+        "libbinder_ndk",
+        "libc",
+        "libcgrouprc",
+        "libclang_rt.asan",
+        "libdl",
+        "libft2",
+        "liblog",
+        "libm",
+        "libmediandk",
+        "libnativewindow",
+        "libselinux",
+        "libsync",
+        "libvendorsupport",
+        "libvndksupport",
+        "libvulkan",
+    ],
+}
diff --git a/cc/builder.go b/cc/builder.go
index cd535c1..2948ca3 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -821,7 +821,7 @@
 		return nil
 	}
 
-	output := android.PathForModuleOut(ctx, "generated_rust_staticlib", "lib"+ctx.ModuleName()+"_rust_staticlib.a")
+	output := android.PathForModuleOut(ctx, "generated_rust_staticlib", "librustlibs.a")
 	stemFile := output.ReplaceExtension(ctx, "rs")
 	crateNames := []string{}
 
diff --git a/cc/cc.go b/cc/cc.go
index 59b13ef..2b011d6 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -37,6 +37,22 @@
 	"android/soong/genrule"
 )
 
+type CcMakeVarsInfo struct {
+	WarningsAllowed string
+	UsingWnoError   string
+	MissingProfile  string
+}
+
+var CcMakeVarsInfoProvider = blueprint.NewProvider[*CcMakeVarsInfo]()
+
+type CcObjectInfo struct {
+	objFiles   android.Paths
+	tidyFiles  android.Paths
+	kytheFiles android.Paths
+}
+
+var CcObjectInfoProvider = blueprint.NewProvider[CcObjectInfo]()
+
 func init() {
 	RegisterCCBuildComponents(android.InitRegistrationContext)
 
@@ -531,6 +547,7 @@
 	getSharedFlags() *SharedFlags
 	notInPlatform() bool
 	optimizeForSize() bool
+	getOrCreateMakeVarsInfo() *CcMakeVarsInfo
 }
 
 type SharedFlags struct {
@@ -639,10 +656,6 @@
 	installInRoot() bool
 }
 
-type xref interface {
-	XrefCcFiles() android.Paths
-}
-
 type overridable interface {
 	overriddenModules() []string
 }
@@ -845,9 +858,10 @@
 	sourceProperties android.SourceProperties
 
 	// initialize before calling Init
-	hod        android.HostOrDeviceSupported
-	multilib   android.Multilib
-	testModule bool
+	hod         android.HostOrDeviceSupported
+	multilib    android.Multilib
+	testModule  bool
+	incremental bool
 
 	// Allowable SdkMemberTypes of this module type.
 	sdkMemberTypes []android.SdkMemberType
@@ -890,12 +904,6 @@
 	staticAnalogue *StaticLibraryInfo
 
 	makeLinkType string
-	// Kythe (source file indexer) paths for this compilation module
-	kytheFiles android.Paths
-	// Object .o file output paths for this compilation module
-	objFiles android.Paths
-	// Tidy .tidy file output paths for this compilation module
-	tidyFiles android.Paths
 
 	// For apex variants, this is set as apex.min_sdk_version
 	apexSdkVersion android.ApiLevel
@@ -913,8 +921,16 @@
 	hasSysprop      bool
 	hasWinMsg       bool
 	hasYacc         bool
+
+	makeVarsInfo *CcMakeVarsInfo
 }
 
+func (c *Module) IncrementalSupported() bool {
+	return c.incremental
+}
+
+var _ blueprint.Incremental = (*Module)(nil)
+
 func (c *Module) AddJSONData(d *map[string]interface{}) {
 	c.AndroidModuleBase().AddJSONData(d)
 	(*d)["Cc"] = map[string]interface{}{
@@ -1454,10 +1470,6 @@
 	return isBionic(name)
 }
 
-func (c *Module) XrefCcFiles() android.Paths {
-	return c.kytheFiles
-}
-
 func (c *Module) isCfiAssemblySupportEnabled() bool {
 	return c.sanitize != nil &&
 		Bool(c.sanitize.Properties.Sanitize.Config.Cfi_assembly_support)
@@ -1548,12 +1560,11 @@
 	}
 
 	if ctx.ctx.Device() {
-		config := ctx.ctx.Config()
-		if ctx.inVendor() {
-			// If building for vendor with final API, then use the latest _stable_ API as "current".
-			if config.VendorApiLevelFrozen() && (ver == "" || ver == "current") {
-				ver = config.PlatformSdkVersion().String()
-			}
+		// When building for vendor/product, use the latest _stable_ API as "current".
+		// This is passed to clang/aidl compilers so that compiled/generated code works
+		// with the system.
+		if (ctx.inVendor() || ctx.inProduct()) && (ver == "" || ver == "current") {
+			ver = ctx.ctx.Config().PlatformSdkVersion().String()
 		}
 	}
 
@@ -1699,6 +1710,13 @@
 	return ctx.mod.NotInPlatform()
 }
 
+func (ctx *moduleContextImpl) getOrCreateMakeVarsInfo() *CcMakeVarsInfo {
+	if ctx.mod.makeVarsInfo == nil {
+		ctx.mod.makeVarsInfo = &CcMakeVarsInfo{}
+	}
+	return ctx.mod.makeVarsInfo
+}
+
 func newBaseModule(hod android.HostOrDeviceSupported, multilib android.Multilib) *Module {
 	return &Module{
 		hod:      hod,
@@ -2022,9 +2040,6 @@
 		if ctx.Failed() {
 			return
 		}
-		c.kytheFiles = objs.kytheFiles
-		c.objFiles = objs.objFiles
-		c.tidyFiles = objs.tidyFiles
 	}
 
 	if c.linker != nil {
@@ -2093,7 +2108,28 @@
 		c.hasYacc = b.hasSrcExt(ctx, ".y") || b.hasSrcExt(ctx, ".yy")
 	}
 
+	ccObjectInfo := CcObjectInfo{
+		kytheFiles: objs.kytheFiles,
+	}
+	if !ctx.Config().KatiEnabled() || !android.ShouldSkipAndroidMkProcessing(ctx, c) {
+		ccObjectInfo.objFiles = objs.objFiles
+		ccObjectInfo.tidyFiles = objs.tidyFiles
+	}
+	if len(ccObjectInfo.kytheFiles)+len(ccObjectInfo.objFiles)+len(ccObjectInfo.tidyFiles) > 0 {
+		android.SetProvider(ctx, CcObjectInfoProvider, ccObjectInfo)
+	}
+
 	c.setOutputFiles(ctx)
+
+	if c.makeVarsInfo != nil {
+		android.SetProvider(ctx, CcMakeVarsInfoProvider, c.makeVarsInfo)
+	}
+}
+
+func setOutputFilesIfNotEmpty(ctx ModuleContext, files android.Paths, tag string) {
+	if len(files) > 0 {
+		ctx.SetOutputFiles(files, tag)
+	}
 }
 
 func (c *Module) setOutputFiles(ctx ModuleContext) {
@@ -2169,6 +2205,7 @@
 		// modules can be hidden from make as some are needed for resolving make side
 		// dependencies.
 		c.HideFromMake()
+		c.SkipInstall()
 	} else if !installable(c, apexInfo) {
 		c.SkipInstall()
 	}
@@ -3926,9 +3963,10 @@
 
 func (ks *kytheExtractAllSingleton) GenerateBuildActions(ctx android.SingletonContext) {
 	var xrefTargets android.Paths
-	ctx.VisitAllModules(func(module android.Module) {
-		if ccModule, ok := module.(xref); ok {
-			xrefTargets = append(xrefTargets, ccModule.XrefCcFiles()...)
+	ctx.VisitAllModuleProxies(func(module android.ModuleProxy) {
+		files := android.OtherModuleProviderOrDefault(ctx, module, CcObjectInfoProvider).kytheFiles
+		if len(files) > 0 {
+			xrefTargets = append(xrefTargets, files...)
 		}
 	})
 	// TODO(asmundak): Perhaps emit a rule to output a warning if there were no xrefTargets
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 989b043..144b90b 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -1008,7 +1008,7 @@
 	android.AssertArrayString(t, "variants for llndk stubs", expected, actual)
 
 	params := result.ModuleForTests("libllndk", "android_vendor_arm_armv7-a-neon_shared").Description("generate stub")
-	android.AssertSame(t, "use Vendor API level for default stubs", "202404", params.Args["apiLevel"])
+	android.AssertSame(t, "use Vendor API level for default stubs", "999999", params.Args["apiLevel"])
 
 	checkExportedIncludeDirs := func(module, variant string, expectedSystemDirs []string, expectedDirs ...string) {
 		t.Helper()
@@ -3153,7 +3153,7 @@
 	testDepWithVariant("product")
 }
 
-func TestVendorSdkVersion(t *testing.T) {
+func TestVendorOrProductVariantUsesPlatformSdkVersionAsDefault(t *testing.T) {
 	t.Parallel()
 
 	bp := `
@@ -3161,31 +3161,29 @@
 			name: "libfoo",
 			srcs: ["libfoo.cc"],
 			vendor_available: true,
+			product_available: true,
 		}
 
 		cc_library {
 			name: "libbar",
 			srcs: ["libbar.cc"],
 			vendor_available: true,
+			product_available: true,
 			min_sdk_version: "29",
 		}
 	`
 
 	ctx := prepareForCcTest.RunTestWithBp(t, bp)
-	testSdkVersionFlag := func(module, version string) {
-		flags := ctx.ModuleForTests(module, "android_vendor_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
-		android.AssertStringDoesContain(t, "min sdk version", flags, "-target aarch64-linux-android"+version)
+	testSdkVersionFlag := func(module, variant, version string) {
+		flags := ctx.ModuleForTests(module, "android_"+variant+"_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
+		android.AssertStringDoesContain(t, "target SDK version", flags, "-target aarch64-linux-android"+version)
 	}
 
-	testSdkVersionFlag("libfoo", "10000")
-	testSdkVersionFlag("libbar", "29")
-
-	ctx = android.GroupFixturePreparers(
-		prepareForCcTest,
-		android.PrepareForTestWithBuildFlag("RELEASE_BOARD_API_LEVEL_FROZEN", "true"),
-	).RunTestWithBp(t, bp)
-	testSdkVersionFlag("libfoo", "30")
-	testSdkVersionFlag("libbar", "29")
+	testSdkVersionFlag("libfoo", "vendor", "30")
+	testSdkVersionFlag("libfoo", "product", "30")
+	// target SDK version can be set explicitly with min_sdk_version
+	testSdkVersionFlag("libbar", "vendor", "29")
+	testSdkVersionFlag("libbar", "product", "29")
 }
 
 func TestClangVerify(t *testing.T) {
diff --git a/cc/compdb.go b/cc/compdb.go
index b33f490..4132e09 100644
--- a/cc/compdb.go
+++ b/cc/compdb.go
@@ -146,6 +146,8 @@
 		isAsm = false
 		isCpp = true
 		clangPath = cxxPath
+	case ".o":
+		return nil
 	default:
 		log.Print("Unknown file extension " + src.Ext() + " on file " + src.String())
 		isAsm = true
@@ -185,6 +187,10 @@
 	}
 	for _, src := range srcs {
 		if _, ok := builds[src.String()]; !ok {
+			args := getArguments(src, ctx, ccModule, ccPath, cxxPath)
+			if args == nil {
+				continue
+			}
 			builds[src.String()] = compDbEntry{
 				Directory: android.AbsSrcDirForExistingUseCases(),
 				Arguments: getArguments(src, ctx, ccModule, ccPath, cxxPath),
diff --git a/cc/compiler.go b/cc/compiler.go
index f06287c..91f107c 100644
--- a/cc/compiler.go
+++ b/cc/compiler.go
@@ -78,11 +78,11 @@
 	// If possible, don't use this.  If adding paths from the current directory use
 	// local_include_dirs, if adding paths from other modules use export_include_dirs in
 	// that module.
-	Include_dirs []string `android:"arch_variant,variant_prepend"`
+	Include_dirs proptools.Configurable[[]string] `android:"arch_variant,variant_prepend"`
 
 	// list of directories relative to the Blueprints file that will
 	// be added to the include path using -I
-	Local_include_dirs []string `android:"arch_variant,variant_prepend"`
+	Local_include_dirs proptools.Configurable[[]string] `android:"arch_variant,variant_prepend"`
 
 	// Add the directory containing the Android.bp file to the list of include
 	// directories. Defaults to true.
@@ -411,13 +411,13 @@
 	}
 
 	// Include dir cflags
-	localIncludeDirs := android.PathsForModuleSrc(ctx, compiler.Properties.Local_include_dirs)
+	localIncludeDirs := android.PathsForModuleSrc(ctx, compiler.Properties.Local_include_dirs.GetOrDefault(ctx, nil))
 	if len(localIncludeDirs) > 0 {
 		f := includeDirsToFlags(localIncludeDirs)
 		flags.Local.CommonFlags = append(flags.Local.CommonFlags, f)
 		flags.Local.YasmFlags = append(flags.Local.YasmFlags, f)
 	}
-	rootIncludeDirs := android.PathsForSource(ctx, compiler.Properties.Include_dirs)
+	rootIncludeDirs := android.PathsForSource(ctx, compiler.Properties.Include_dirs.GetOrDefault(ctx, nil))
 	if len(rootIncludeDirs) > 0 {
 		f := includeDirsToFlags(rootIncludeDirs)
 		flags.Local.CommonFlags = append(flags.Local.CommonFlags, f)
@@ -685,10 +685,10 @@
 	if len(srcs) > 0 {
 		module := ctx.ModuleDir() + "/Android.bp:" + ctx.ModuleName()
 		if inList("-Wno-error", flags.Local.CFlags) || inList("-Wno-error", flags.Local.CppFlags) {
-			addToModuleList(ctx, modulesUsingWnoErrorKey, module)
+			ctx.getOrCreateMakeVarsInfo().UsingWnoError = module
 		} else if !inList("-Werror", flags.Local.CFlags) && !inList("-Werror", flags.Local.CppFlags) {
 			if warningsAreAllowed(ctx.ModuleDir()) {
-				addToModuleList(ctx, modulesWarningsAllowedKey, module)
+				ctx.getOrCreateMakeVarsInfo().WarningsAllowed = module
 			} else {
 				flags.Local.CFlags = append([]string{"-Werror"}, flags.Local.CFlags...)
 			}
@@ -807,7 +807,7 @@
 type RustBindgenClangProperties struct {
 	// list of directories relative to the Blueprints file that will
 	// be added to the include path using -I
-	Local_include_dirs []string `android:"arch_variant,variant_prepend"`
+	Local_include_dirs proptools.Configurable[[]string] `android:"arch_variant,variant_prepend"`
 
 	// list of static libraries that provide headers for this binding.
 	Static_libs proptools.Configurable[[]string] `android:"arch_variant,variant_prepend"`
diff --git a/cc/config/global.go b/cc/config/global.go
index d63f86d..36690d6 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -72,6 +72,9 @@
 
 		// Warnings disabled by default.
 
+		// We should encourage use of C23 features even when the whole project
+		// isn't C23-ready.
+		"-Wno-c23-extensions",
 		// Designated initializer syntax is recommended by the Google C++ style
 		// and is OK to use even if not formally supported by the chosen C++
 		// version.
diff --git a/cc/library.go b/cc/library.go
index 7dffa72..4ce506e 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -566,16 +566,10 @@
 
 func (library *libraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects {
 	if ctx.IsLlndk() {
-		vendorApiLevel := ctx.Config().VendorApiLevel()
-		if vendorApiLevel == "" {
-			// TODO(b/321892570): Some tests relying on old fixtures which
-			// doesn't set vendorApiLevel. Needs to fix them.
-			vendorApiLevel = ctx.Config().PlatformSdkVersion().String()
-		}
-		// This is the vendor variant of an LLNDK library, build the LLNDK stubs.
+		futureVendorApiLevel := android.ApiLevelOrPanic(ctx, "999999")
 		nativeAbiResult := parseNativeAbiDefinition(ctx,
 			String(library.Properties.Llndk.Symbol_file),
-			android.ApiLevelOrPanic(ctx, vendorApiLevel), "--llndk")
+			futureVendorApiLevel, "--llndk")
 		objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
 		if !Bool(library.Properties.Llndk.Unversioned) {
 			library.versionScriptPath = android.OptionalPathForPath(
diff --git a/cc/makevars.go b/cc/makevars.go
index f82e0e9..4cb98e7 100644
--- a/cc/makevars.go
+++ b/cc/makevars.go
@@ -25,10 +25,7 @@
 )
 
 var (
-	modulesWarningsAllowedKey    = android.NewOnceKey("ModulesWarningsAllowed")
-	modulesUsingWnoErrorKey      = android.NewOnceKey("ModulesUsingWnoError")
-	modulesMissingProfileFileKey = android.NewOnceKey("ModulesMissingProfileFile")
-	sanitizerVariables           = map[string]string{
+	sanitizerVariables = map[string]string{
 		"ADDRESS_SANITIZER_RUNTIME_LIBRARY":   config.AddressSanitizerRuntimeLibrary(),
 		"HWADDRESS_SANITIZER_RUNTIME_LIBRARY": config.HWAddressSanitizerRuntimeLibrary(),
 		"HWADDRESS_SANITIZER_STATIC_LIBRARY":  config.HWAddressSanitizerStaticLibrary(),
@@ -50,15 +47,9 @@
 	}).(*sync.Map)
 }
 
-func makeStringOfKeys(ctx android.MakeVarsContext, key android.OnceKey) string {
-	set := getNamedMapForConfig(ctx.Config(), key)
-	keys := []string{}
-	set.Range(func(key interface{}, value interface{}) bool {
-		keys = append(keys, key.(string))
-		return true
-	})
-	sort.Strings(keys)
-	return strings.Join(keys, " ")
+func makeVarsString(items []string) string {
+	items = android.SortedUniqueStrings(items)
+	return strings.Join(items, " ")
 }
 
 func makeStringOfWarningAllowedProjects() string {
@@ -108,28 +99,33 @@
 	ctx.Strict("GLOBAL_CLANG_EXTERNAL_CFLAGS_NO_OVERRIDE", "${config.NoOverrideExternalGlobalCflags}")
 
 	// Filter vendor_public_library that are exported to make
-	exportedVendorPublicLibraries := []string{}
+	var exportedVendorPublicLibraries []string
+	var warningsAllowed []string
+	var usingWnoErrors []string
+	var missingProfiles []string
 	ctx.VisitAllModules(func(module android.Module) {
+		if v, ok := android.OtherModuleProvider(ctx, module, CcMakeVarsInfoProvider); ok {
+			warningsAllowed = android.AppendIfNotZero(warningsAllowed, v.WarningsAllowed)
+			usingWnoErrors = android.AppendIfNotZero(usingWnoErrors, v.UsingWnoError)
+			missingProfiles = android.AppendIfNotZero(missingProfiles, v.MissingProfile)
+		}
 		if ccModule, ok := module.(*Module); ok {
 			baseName := ccModule.BaseModuleName()
 			if ccModule.IsVendorPublicLibrary() && module.ExportedToMake() {
-				if !inList(baseName, exportedVendorPublicLibraries) {
-					exportedVendorPublicLibraries = append(exportedVendorPublicLibraries, baseName)
-				}
+				exportedVendorPublicLibraries = append(exportedVendorPublicLibraries, baseName)
 			}
 		}
 	})
-	sort.Strings(exportedVendorPublicLibraries)
-	ctx.Strict("VENDOR_PUBLIC_LIBRARIES", strings.Join(exportedVendorPublicLibraries, " "))
+	ctx.Strict("VENDOR_PUBLIC_LIBRARIES", makeVarsString(exportedVendorPublicLibraries))
 
 	lsdumpPaths := *lsdumpPaths(ctx.Config())
 	sort.Strings(lsdumpPaths)
 	ctx.Strict("LSDUMP_PATHS", strings.Join(lsdumpPaths, " "))
 
 	ctx.Strict("ANDROID_WARNING_ALLOWED_PROJECTS", makeStringOfWarningAllowedProjects())
-	ctx.Strict("SOONG_MODULES_WARNINGS_ALLOWED", makeStringOfKeys(ctx, modulesWarningsAllowedKey))
-	ctx.Strict("SOONG_MODULES_USING_WNO_ERROR", makeStringOfKeys(ctx, modulesUsingWnoErrorKey))
-	ctx.Strict("SOONG_MODULES_MISSING_PGO_PROFILE_FILE", makeStringOfKeys(ctx, modulesMissingProfileFileKey))
+	ctx.Strict("SOONG_MODULES_WARNINGS_ALLOWED", makeVarsString(warningsAllowed))
+	ctx.Strict("SOONG_MODULES_USING_WNO_ERROR", makeVarsString(usingWnoErrors))
+	ctx.Strict("SOONG_MODULES_MISSING_PGO_PROFILE_FILE", makeVarsString(missingProfiles))
 
 	ctx.Strict("CLANG_COVERAGE_CONFIG_CFLAGS", strings.Join(clangCoverageCFlags, " "))
 	ctx.Strict("CLANG_COVERAGE_CONFIG_COMMFLAGS", strings.Join(clangCoverageCommonFlags, " "))
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index 01551ab..2411614 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -144,11 +144,9 @@
 }
 
 func ndkLibraryVersions(ctx android.BaseModuleContext, from android.ApiLevel) []string {
-	var versions []android.ApiLevel
 	versionStrs := []string{}
-	for _, version := range ctx.Config().AllSupportedApiLevels() {
+	for _, version := range ctx.Config().FinalApiLevels() {
 		if version.GreaterThanOrEqualTo(from) {
-			versions = append(versions, version)
 			versionStrs = append(versionStrs, version.String())
 		}
 	}
@@ -330,6 +328,12 @@
 	return android.ExistentPathForSource(ctx, subpath)
 }
 
+func (this *stubDecorator) builtAbiDumpLocation(ctx ModuleContext, apiLevel android.ApiLevel) android.OutputPath {
+	return getNdkAbiDumpInstallBase(ctx).Join(ctx,
+		apiLevel.String(), ctx.Arch().ArchType.String(),
+		this.libraryName(ctx), "abi.stg")
+}
+
 // Feature flag.
 func (this *stubDecorator) canDumpAbi(ctx ModuleContext) bool {
 	if runtime.GOOS == "darwin" {
@@ -345,25 +349,24 @@
 		return false
 	}
 
-	if this.apiLevel.IsCurrent() {
-		// "current" (AKA 10000) is not tracked.
-		return false
-	}
-
 	// http://b/156513478
 	return ctx.Config().ReleaseNdkAbiMonitored()
 }
 
 // Feature flag to disable diffing against prebuilts.
-func canDiffAbi(config android.Config) bool {
+func (this *stubDecorator) canDiffAbi(config android.Config) bool {
+	if this.apiLevel.IsCurrent() {
+		// Diffs are performed from this to next, and there's nothing after
+		// current.
+		return false
+	}
+
 	return config.ReleaseNdkAbiMonitored()
 }
 
 func (this *stubDecorator) dumpAbi(ctx ModuleContext, symbolList android.Path) {
 	implementationLibrary := this.findImplementationLibrary(ctx)
-	this.abiDumpPath = getNdkAbiDumpInstallBase(ctx).Join(ctx,
-		this.apiLevel.String(), ctx.Arch().ArchType.String(),
-		this.libraryName(ctx), "abi.stg")
+	this.abiDumpPath = this.builtAbiDumpLocation(ctx, this.apiLevel)
 	this.hasAbiDump = true
 	headersList := getNdkABIHeadersFile(ctx)
 	ctx.Build(pctx, android.BuildParams{
@@ -383,7 +386,7 @@
 }
 
 func findNextApiLevel(ctx ModuleContext, apiLevel android.ApiLevel) *android.ApiLevel {
-	apiLevels := append(ctx.Config().AllSupportedApiLevels(),
+	apiLevels := append(ctx.Config().FinalApiLevels(),
 		android.FutureApiLevel)
 	for _, api := range apiLevels {
 		if api.GreaterThan(apiLevel) {
@@ -436,38 +439,49 @@
 				"non-current API level %s", this.apiLevel))
 		}
 
-		// "current" ABI is not tracked.
-		if !nextApiLevel.IsCurrent() {
-			nextAbiDiffPath := android.PathForModuleOut(ctx,
-				"abidiff_next.timestamp")
-			nextAbiDump := this.findPrebuiltAbiDump(ctx, *nextApiLevel)
+		// Preview ABI levels are not recorded in prebuilts. ABI compatibility
+		// for preview APIs is still monitored via "current" so we have early
+		// warning rather than learning about an ABI break during finalization,
+		// but is only checked against the "current" API dumps in the out
+		// directory.
+		nextAbiDiffPath := android.PathForModuleOut(ctx,
+			"abidiff_next.timestamp")
+
+		var nextAbiDump android.OptionalPath
+		if nextApiLevel.IsCurrent() {
+			nextAbiDump = android.OptionalPathForPath(
+				this.builtAbiDumpLocation(ctx, *nextApiLevel),
+			)
+		} else {
+			nextAbiDump = this.findPrebuiltAbiDump(ctx, *nextApiLevel)
+		}
+
+		if !nextAbiDump.Valid() {
 			missingNextPrebuiltError := fmt.Sprintf(
 				missingPrebuiltErrorTemplate, this.libraryName(ctx),
 				nextAbiDump.InvalidReason())
-			if !nextAbiDump.Valid() {
-				ctx.Build(pctx, android.BuildParams{
-					Rule:   android.ErrorRule,
-					Output: nextAbiDiffPath,
-					Args: map[string]string{
-						"error": missingNextPrebuiltError,
-					},
-				})
-			} else {
-				ctx.Build(pctx, android.BuildParams{
-					Rule: stgdiff,
-					Description: fmt.Sprintf(
-						"Comparing ABI to the next API level %s %s",
-						prebuiltAbiDump, nextAbiDump),
-					Output: nextAbiDiffPath,
-					Inputs: android.Paths{
-						prebuiltAbiDump.Path(), nextAbiDump.Path()},
-					Args: map[string]string{
-						"args": "--format=small --ignore=interface_addition",
-					},
-				})
-			}
-			this.abiDiffPaths = append(this.abiDiffPaths, nextAbiDiffPath)
+			ctx.Build(pctx, android.BuildParams{
+				Rule:   android.ErrorRule,
+				Output: nextAbiDiffPath,
+				Args: map[string]string{
+					"error": missingNextPrebuiltError,
+				},
+			})
+		} else {
+			ctx.Build(pctx, android.BuildParams{
+				Rule: stgdiff,
+				Description: fmt.Sprintf(
+					"Comparing ABI to the next API level %s %s",
+					prebuiltAbiDump, nextAbiDump),
+				Output: nextAbiDiffPath,
+				Inputs: android.Paths{
+					prebuiltAbiDump.Path(), nextAbiDump.Path()},
+				Args: map[string]string{
+					"args": "--format=small --ignore=interface_addition",
+				},
+			})
 		}
+		this.abiDiffPaths = append(this.abiDiffPaths, nextAbiDiffPath)
 	}
 }
 
@@ -492,7 +506,7 @@
 	c.versionScriptPath = nativeAbiResult.versionScript
 	if c.canDumpAbi(ctx) {
 		c.dumpAbi(ctx, nativeAbiResult.symbolList)
-		if canDiffAbi(ctx.Config()) {
+		if c.canDiffAbi(ctx.Config()) {
 			c.diffAbi(ctx)
 		}
 	}
diff --git a/cc/orderfile.go b/cc/orderfile.go
index 38b8905..6e08da7 100644
--- a/cc/orderfile.go
+++ b/cc/orderfile.go
@@ -54,10 +54,6 @@
 	})
 }
 
-func recordMissingOrderfile(ctx BaseModuleContext, missing string) {
-	getNamedMapForConfig(ctx.Config(), modulesMissingProfileFileKey).Store(missing, true)
-}
-
 type OrderfileProperties struct {
 	Orderfile struct {
 		Instrumentation *bool
@@ -117,7 +113,7 @@
 
 	// Record that this module's order file is absent
 	missing := *props.Orderfile.Order_file_path + ":" + ctx.ModuleDir() + "/Android.bp:" + ctx.ModuleName()
-	recordMissingOrderfile(ctx, missing)
+	ctx.getOrCreateMakeVarsInfo().MissingProfile = missing
 
 	return android.OptionalPath{}
 }
diff --git a/cc/tidy.go b/cc/tidy.go
index ec1e8a2..5cbf8f0 100644
--- a/cc/tidy.go
+++ b/cc/tidy.go
@@ -219,15 +219,11 @@
 	subsetTidyFileGroups := make(map[string]android.Paths) // subset group name => tidy file Paths
 
 	// (1) Collect all obj/tidy files into OS-specific groups.
-	ctx.VisitAllModuleVariants(module, func(variant android.Module) {
-		if ctx.Config().KatiEnabled() && android.ShouldSkipAndroidMkProcessing(ctx, variant) {
-			return
-		}
-		if m, ok := variant.(*Module); ok {
-			osName := variant.Target().Os.Name
-			addToOSGroup(osName, m.objFiles, allObjFileGroups, subsetObjFileGroups)
-			addToOSGroup(osName, m.tidyFiles, allTidyFileGroups, subsetTidyFileGroups)
-		}
+	ctx.VisitAllModuleVariantProxies(module, func(variant android.ModuleProxy) {
+		osName := android.OtherModuleProviderOrDefault(ctx, variant, android.CommonPropertiesProviderKey).CompileTarget.Os.Name
+		info := android.OtherModuleProviderOrDefault(ctx, variant, CcObjectInfoProvider)
+		addToOSGroup(osName, info.objFiles, allObjFileGroups, subsetObjFileGroups)
+		addToOSGroup(osName, info.tidyFiles, allTidyFileGroups, subsetTidyFileGroups)
 	})
 
 	// (2) Add an all-OS group, with "" or "subset" name, to include all os-specific phony targets.
@@ -258,7 +254,7 @@
 
 	// Collect tidy/obj targets from the 'final' modules.
 	ctx.VisitAllModules(func(module android.Module) {
-		if module == ctx.FinalModule(module) {
+		if ctx.IsFinalModule(module) {
 			collectTidyObjModuleTargets(ctx, module, tidyModulesInDirGroup, objModulesInDirGroup)
 		}
 	})
diff --git a/cmd/find_input_delta/find_input_delta/Android.bp b/cmd/find_input_delta/find_input_delta/Android.bp
new file mode 100644
index 0000000..93a7708
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta/Android.bp
@@ -0,0 +1,18 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+blueprint_go_binary {
+    name: "find_input_delta",
+    deps: [
+        "golang-protobuf-encoding-prototext",
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+        "soong-cmd-find_input_delta-lib",
+        "soong-cmd-find_input_delta-proto",
+        "soong-cmd-find_input_delta-proto_internal",
+    ],
+    srcs: [
+        "main.go",
+    ],
+}
diff --git a/cmd/find_input_delta/find_input_delta/main.go b/cmd/find_input_delta/find_input_delta/main.go
new file mode 100644
index 0000000..6b657ea
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta/main.go
@@ -0,0 +1,88 @@
+// 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 main
+
+import (
+	"flag"
+	"os"
+	"strings"
+
+	fid_lib "android/soong/cmd/find_input_delta/find_input_delta_lib"
+)
+
+func main() {
+	var top string
+	var prior_state_file string
+	var new_state_file string
+	var target string
+	var inputs_file string
+	var template string
+	var inputs []string
+	var inspect bool
+	var err error
+
+	flag.StringVar(&top, "top", ".", "path to top of workspace")
+	flag.StringVar(&prior_state_file, "prior_state", "", "prior internal state file")
+	flag.StringVar(&new_state_file, "new_state", "", "new internal state file")
+	flag.StringVar(&target, "target", "", "name of ninja output file for build action")
+	flag.StringVar(&inputs_file, "inputs_file", "", "file containing list of input files")
+	flag.StringVar(&template, "template", fid_lib.DefaultTemplate, "output template for FileList")
+	flag.BoolVar(&inspect, "inspect", false, "whether to inspect file contents")
+
+	flag.Parse()
+
+	if target == "" {
+		panic("must specify --target")
+	}
+	if prior_state_file == "" {
+		prior_state_file = target + ".pc_state"
+	}
+	if new_state_file == "" {
+		new_state_file = prior_state_file + ".new"
+	}
+
+	if err = os.Chdir(top); err != nil {
+		panic(err)
+	}
+
+	inputs = flag.Args()
+	if inputs_file != "" {
+		data, err := os.ReadFile(inputs_file)
+		if err != nil {
+			panic(err)
+		}
+		inputs = append(inputs, strings.Split(string(data), "\n")...)
+	}
+
+	// Read the prior state
+	prior_state, err := fid_lib.LoadState(prior_state_file, fid_lib.OsFs)
+	if err != nil {
+		panic(err)
+	}
+	// Create the new state
+	new_state, err := fid_lib.CreateState(inputs, inspect, fid_lib.OsFs)
+	if err != nil {
+		panic(err)
+	}
+	if err = fid_lib.WriteState(new_state, new_state_file); err != nil {
+		panic(err)
+	}
+
+	file_list := *fid_lib.CompareInternalState(prior_state, new_state, target)
+
+	if err = file_list.Format(os.Stdout, template); 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
new file mode 100644
index 0000000..95bdba8
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta_lib/Android.bp
@@ -0,0 +1,35 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+    name: "soong-cmd-find_input_delta-lib",
+    pkgPath: "android/soong/cmd/find_input_delta/find_input_delta_lib",
+    deps: [
+        "golang-protobuf-encoding-prototext",
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+        "soong-cmd-find_input_delta-proto",
+        "soong-cmd-find_input_delta-proto_internal",
+        "blueprint-pathtools",
+    ],
+    srcs: [
+        "fs.go",
+        "file_list.go",
+        "internal_state.go",
+    ],
+}
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
new file mode 100644
index 0000000..23337ad
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta_lib/file_list.go
@@ -0,0 +1,78 @@
+// 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 find_input_delta_lib
+
+import (
+	"io"
+	"text/template"
+
+	fid_exp "android/soong/cmd/find_input_delta/find_input_delta_proto"
+	"google.golang.org/protobuf/proto"
+)
+
+var DefaultTemplate = `
+	{{- define "contents"}}
+		{{- range .Deletions}}-{{.}} {{end}}
+		{{- range .Additions}}+{{.}} {{end}}
+		{{- range .Changes}}+{{- .Name}} {{end}}
+		{{- range .Changes}}
+		  {{- if or .Additions .Deletions .Changes}}--file {{.Name}} {{template "contents" .}}--endfile {{end}}
+		{{- end}}
+	{{- end}}
+	{{- template "contents" .}}`
+
+type FileList struct {
+	// The name of the parent for the list of file differences.
+	// For the outermost FileList, this is the name of the ninja target.
+	// Under `Changes`, it is the name of the changed file.
+	Name string
+
+	// The added files
+	Additions []string
+
+	// The deleted files
+	Deletions []string
+
+	// The modified files
+	Changes []FileList
+}
+
+func (fl FileList) Marshal() (*fid_exp.FileList, error) {
+	ret := &fid_exp.FileList{
+		Name: proto.String(fl.Name),
+	}
+	if len(fl.Additions) > 0 {
+		ret.Additions = fl.Additions
+	}
+	for _, ch := range fl.Changes {
+		change, err := ch.Marshal()
+		if err != nil {
+			return nil, err
+		}
+		ret.Changes = append(ret.Changes, change)
+	}
+	if len(fl.Deletions) > 0 {
+		ret.Deletions = fl.Deletions
+	}
+	return ret, nil
+}
+
+func (fl FileList) Format(wr io.Writer, format string) error {
+	tmpl, err := template.New("filelist").Parse(format)
+	if err != nil {
+		return err
+	}
+	return tmpl.Execute(wr, fl)
+}
diff --git a/cmd/find_input_delta/find_input_delta_lib/file_list_test.go b/cmd/find_input_delta/find_input_delta_lib/file_list_test.go
new file mode 100644
index 0000000..2459f1e
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta_lib/file_list_test.go
@@ -0,0 +1,131 @@
+// 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 find_input_delta_lib
+
+import (
+	"bytes"
+	"slices"
+	"testing"
+
+	// For Assert*.
+	"android/soong/android"
+)
+
+func (fl *FileList) Equal(other *FileList) bool {
+	if fl.Name != other.Name {
+		return false
+	}
+	if !slices.Equal(fl.Additions, other.Additions) {
+		return false
+	}
+	if !slices.Equal(fl.Deletions, other.Deletions) {
+		return false
+	}
+	if len(fl.Changes) != len(other.Changes) {
+		return false
+	}
+	for idx, ch := range fl.Changes {
+		if !ch.Equal(&other.Changes[idx]) {
+			return false
+		}
+	}
+	return true
+}
+
+func TestFormat(t *testing.T) {
+	testCases := []struct {
+		Name     string
+		Template string
+		Input    FileList
+		Expected string
+		Err      error
+	}{
+		{
+			Name:     "no contents",
+			Template: DefaultTemplate,
+			Input: FileList{
+				Name:      "target",
+				Additions: []string{"add1", "add2"},
+				Deletions: []string{"del1", "del2"},
+				Changes: []FileList{
+					FileList{Name: "mod1"},
+					FileList{Name: "mod2"},
+				},
+			},
+			Expected: "-del1 -del2 +add1 +add2 +mod1 +mod2 ",
+			Err:      nil,
+		},
+		{
+			Name:     "adds",
+			Template: DefaultTemplate,
+			Input: FileList{
+				Name:      "target",
+				Additions: []string{"add1", "add2"},
+			},
+			Expected: "+add1 +add2 ",
+			Err:      nil,
+		},
+		{
+			Name:     "deletes",
+			Template: DefaultTemplate,
+			Input: FileList{
+				Name:      "target",
+				Deletions: []string{"del1", "del2"},
+			},
+			Expected: "-del1 -del2 ",
+			Err:      nil,
+		},
+		{
+			Name:     "changes",
+			Template: DefaultTemplate,
+			Input: FileList{
+				Name: "target",
+				Changes: []FileList{
+					FileList{Name: "mod1"},
+					FileList{Name: "mod2"},
+				},
+			},
+			Expected: "+mod1 +mod2 ",
+			Err:      nil,
+		},
+		{
+			Name:     "with contents",
+			Template: DefaultTemplate,
+			Input: FileList{
+				Name:      "target",
+				Additions: []string{"add1", "add2"},
+				Deletions: []string{"del1", "del2"},
+				Changes: []FileList{
+					FileList{
+						Name: "mod1",
+					},
+					FileList{
+						Name:      "mod2",
+						Additions: []string{"a1"},
+						Deletions: []string{"d1"},
+					},
+				},
+			},
+			Expected: "-del1 -del2 +add1 +add2 +mod1 +mod2 --file mod2 -d1 +a1 --endfile ",
+			Err:      nil,
+		},
+	}
+	for _, tc := range testCases {
+		buf := bytes.NewBuffer([]byte{})
+		err := tc.Input.Format(buf, tc.Template)
+		android.AssertSame(t, tc.Name, tc.Err, err)
+		android.AssertSame(t, tc.Name, tc.Expected, buf.String())
+	}
+}
diff --git a/cmd/find_input_delta/find_input_delta_lib/fs.go b/cmd/find_input_delta/find_input_delta_lib/fs.go
new file mode 100644
index 0000000..4a83ed7
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta_lib/fs.go
@@ -0,0 +1,46 @@
+// 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 find_input_delta_lib
+
+import (
+	"io"
+	"io/fs"
+	"os"
+)
+
+// OsFs provides a minimal implementation so that we can use testing/fstest for
+// unit tests.
+var OsFs fileSystem = osFS{}
+
+type fileSystem interface {
+	Open(path string) (fs.File, error)
+	Stat(path string) (os.FileInfo, error)
+	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{}
+
+func (osFS) Open(path string) (fs.File, error)     { return os.Open(path) }
+func (osFS) Stat(path string) (os.FileInfo, error) { return os.Stat(path) }
+func (osFS) ReadFile(path string) ([]byte, error)  { return os.ReadFile(path) }
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
new file mode 100644
index 0000000..b2ff8c7
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta_lib/internal_state.go
@@ -0,0 +1,122 @@
+// 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 find_input_delta_lib
+
+import (
+	"errors"
+	"fmt"
+	"io/fs"
+	"slices"
+
+	fid_proto "android/soong/cmd/find_input_delta/find_input_delta_proto_internal"
+	"github.com/google/blueprint/pathtools"
+	"google.golang.org/protobuf/proto"
+)
+
+// Load the internal state from a file.
+// If the file does not exist, an empty state is returned.
+func LoadState(filename string, fsys fs.ReadFileFS) (*fid_proto.PartialCompileInputs, error) {
+	var message = &fid_proto.PartialCompileInputs{}
+	data, err := fsys.ReadFile(filename)
+	if err != nil && !errors.Is(err, fs.ErrNotExist) {
+		return message, err
+	}
+	proto.Unmarshal(data, message)
+	return message, nil
+}
+
+type StatReadFileFS interface {
+	fs.StatFS
+	fs.ReadFileFS
+}
+
+// Create the internal state by examining the inputs.
+func CreateState(inputs []string, inspect_contents bool, fsys StatReadFileFS) (*fid_proto.PartialCompileInputs, error) {
+	ret := &fid_proto.PartialCompileInputs{}
+	slices.Sort(inputs)
+	for _, input := range inputs {
+		stat, err := fs.Stat(fsys, input)
+		if err != nil {
+			return ret, err
+		}
+		pci := &fid_proto.PartialCompileInput{
+			Name:      proto.String(input),
+			MtimeNsec: proto.Int64(stat.ModTime().UnixNano()),
+			// If we ever have an easy hash, assign it here.
+		}
+		if inspect_contents {
+			contents, err := InspectFileContents(input)
+			if err != nil {
+				return ret, err
+			}
+			if contents != nil {
+				pci.Contents = contents
+			}
+		}
+		ret.InputFiles = append(ret.InputFiles, pci)
+	}
+	return ret, nil
+}
+
+// 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
+}
+
+func WriteState(s *fid_proto.PartialCompileInputs, path string) error {
+	data, err := proto.Marshal(s)
+	if err != nil {
+		return err
+	}
+	return pathtools.WriteFileIfChanged(path, data, 0644)
+}
+
+func CompareInternalState(prior, other *fid_proto.PartialCompileInputs, target string) *FileList {
+	return CompareInputFiles(prior.GetInputFiles(), other.GetInputFiles(), target)
+}
+
+func CompareInputFiles(prior, other []*fid_proto.PartialCompileInput, name string) *FileList {
+	fl := &FileList{
+		Name: 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 {
+		PriorMap[v.GetName()] = v
+	}
+	otherMap := make(map[string]*fid_proto.PartialCompileInput, len(other))
+	for _, v := range other {
+		name = v.GetName()
+		otherMap[name] = v
+		if _, ok := PriorMap[name]; !ok {
+			// Added file
+			fl.Additions = append(fl.Additions, name)
+		} else if !proto.Equal(PriorMap[name], v) {
+			// Changed file
+			fl.Changes = append(fl.Changes, *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)
+		}
+	}
+	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
new file mode 100644
index 0000000..20b8efa
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta_lib/internal_state_test.go
@@ -0,0 +1,232 @@
+// 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 find_input_delta_lib
+
+import (
+	"errors"
+	"io/fs"
+	"testing"
+	"testing/fstest"
+	"time"
+
+	// For Assert*.
+	"android/soong/android"
+
+	fid_proto "android/soong/cmd/find_input_delta/find_input_delta_proto_internal"
+	"google.golang.org/protobuf/proto"
+)
+
+// Various state files
+
+func marshalProto(t *testing.T, message proto.Message) []byte {
+	data, err := proto.Marshal(message)
+	if err != nil {
+		t.Errorf("%v", err)
+	}
+	return data
+}
+
+func protoFile(name string, mtime_nsec int64, hash string, contents []*fid_proto.PartialCompileInput) (pci *fid_proto.PartialCompileInput) {
+	pci = &fid_proto.PartialCompileInput{
+		Name: proto.String(name),
+	}
+	if mtime_nsec != 0 {
+		pci.MtimeNsec = proto.Int64(mtime_nsec)
+	}
+	if len(hash) > 0 {
+		pci.Hash = proto.String(hash)
+	}
+	if contents != nil {
+		pci.Contents = contents
+	}
+	return
+}
+
+func TestLoadState(t *testing.T) {
+	testCases := []struct {
+		Name     string
+		Filename string
+		Mapfs    fs.ReadFileFS
+		Expected *fid_proto.PartialCompileInputs
+		Err      error
+	}{
+		{
+			Name:     "missing file",
+			Filename: "missing",
+			Mapfs:    fstest.MapFS{},
+			Expected: &fid_proto.PartialCompileInputs{},
+			Err:      nil,
+		},
+		{
+			Name:     "bad file",
+			Filename: ".",
+			Mapfs:    OsFs,
+			Expected: &fid_proto.PartialCompileInputs{},
+			Err:      errors.New("read failed"),
+		},
+		{
+			Name:     "file with mtime",
+			Filename: "state.old",
+			Mapfs: fstest.MapFS{
+				"state.old": &fstest.MapFile{
+					Data: marshalProto(t, &fid_proto.PartialCompileInputs{
+						InputFiles: []*fid_proto.PartialCompileInput{
+							protoFile("input1", 100, "", nil),
+						},
+					}),
+				},
+			},
+			Expected: &fid_proto.PartialCompileInputs{
+				InputFiles: []*fid_proto.PartialCompileInput{
+					protoFile("input1", 100, "", nil),
+				},
+			},
+			Err: nil,
+		},
+		{
+			Name:     "file with mtime and hash",
+			Filename: "state.old",
+			Mapfs: fstest.MapFS{
+				"state.old": &fstest.MapFile{
+					Data: marshalProto(t, &fid_proto.PartialCompileInputs{
+						InputFiles: []*fid_proto.PartialCompileInput{
+							protoFile("input1", 100, "crc:crc_value", nil),
+						},
+					}),
+				},
+			},
+			Expected: &fid_proto.PartialCompileInputs{
+				InputFiles: []*fid_proto.PartialCompileInput{
+					protoFile("input1", 100, "crc:crc_value", nil),
+				},
+			},
+			Err: nil,
+		},
+	}
+	for _, tc := range testCases {
+		actual, err := LoadState(tc.Filename, tc.Mapfs)
+		if tc.Err == nil {
+			android.AssertSame(t, tc.Name, tc.Err, err)
+		} else if err == nil {
+			t.Errorf("%s: expected error, did not get one", tc.Name)
+		}
+		if !proto.Equal(tc.Expected, actual) {
+			t.Errorf("%s: expected %v, actual %v", tc.Name, tc.Expected, actual)
+		}
+	}
+}
+
+func TestCreateState(t *testing.T) {
+	testCases := []struct {
+		Name     string
+		Inputs   []string
+		Inspect  bool
+		Mapfs    StatReadFileFS
+		Expected *fid_proto.PartialCompileInputs
+		Err      error
+	}{
+		{
+			Name:     "no inputs",
+			Inputs:   []string{},
+			Mapfs:    fstest.MapFS{},
+			Expected: &fid_proto.PartialCompileInputs{},
+			Err:      nil,
+		},
+		{
+			Name:   "files found",
+			Inputs: []string{"baz", "foo", "bar"},
+			Mapfs: fstest.MapFS{
+				"foo": &fstest.MapFile{ModTime: time.Unix(0, 100).UTC()},
+				"baz": &fstest.MapFile{ModTime: time.Unix(0, 300).UTC()},
+				"bar": &fstest.MapFile{ModTime: time.Unix(0, 200).UTC()},
+			},
+			Expected: &fid_proto.PartialCompileInputs{
+				InputFiles: []*fid_proto.PartialCompileInput{
+					// Files are always sorted.
+					protoFile("bar", 200, "", nil),
+					protoFile("baz", 300, "", nil),
+					protoFile("foo", 100, "", nil),
+				},
+			},
+			Err: nil,
+		},
+	}
+	for _, tc := range testCases {
+		actual, err := CreateState(tc.Inputs, tc.Inspect, tc.Mapfs)
+		if tc.Err == nil {
+			android.AssertSame(t, tc.Name, tc.Err, err)
+		} else if err == nil {
+			t.Errorf("%s: expected error, did not get one", tc.Name)
+		}
+		if !proto.Equal(tc.Expected, actual) {
+			t.Errorf("%s: expected %v, actual %v", tc.Name, tc.Expected, actual)
+		}
+	}
+}
+
+func TestCompareInternalState(t *testing.T) {
+	testCases := []struct {
+		Name     string
+		Target   string
+		Prior    *fid_proto.PartialCompileInputs
+		New      *fid_proto.PartialCompileInputs
+		Expected *FileList
+	}{
+		{
+			Name:   "prior is empty",
+			Target: "foo",
+			Prior:  &fid_proto.PartialCompileInputs{},
+			New: &fid_proto.PartialCompileInputs{
+				InputFiles: []*fid_proto.PartialCompileInput{
+					protoFile("file1", 100, "", nil),
+				},
+			},
+			Expected: &FileList{
+				Name:      "foo",
+				Additions: []string{"file1"},
+			},
+		},
+		{
+			Name:   "one of each",
+			Target: "foo",
+			Prior: &fid_proto.PartialCompileInputs{
+				InputFiles: []*fid_proto.PartialCompileInput{
+					protoFile("file0", 100, "", nil),
+					protoFile("file1", 100, "", nil),
+					protoFile("file2", 200, "", nil),
+				},
+			},
+			New: &fid_proto.PartialCompileInputs{
+				InputFiles: []*fid_proto.PartialCompileInput{
+					protoFile("file0", 100, "", nil),
+					protoFile("file1", 200, "", nil),
+					protoFile("file3", 300, "", nil),
+				},
+			},
+			Expected: &FileList{
+				Name:      "foo",
+				Additions: []string{"file3"},
+				Changes:   []FileList{FileList{Name: "file1"}},
+				Deletions: []string{"file2"},
+			},
+		},
+	}
+	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)
+		}
+	}
+}
diff --git a/cmd/find_input_delta/find_input_delta_proto/Android.bp b/cmd/find_input_delta/find_input_delta_proto/Android.bp
new file mode 100644
index 0000000..1a05b9e
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta_proto/Android.bp
@@ -0,0 +1,29 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+    name: "soong-cmd-find_input_delta-proto",
+    pkgPath: "android/soong/cmd/find_input_delta/find_input_delta_proto",
+    deps: [
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+    ],
+    srcs: [
+        "file_list.pb.go",
+    ],
+}
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
new file mode 100644
index 0000000..648ef22
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta_proto/file_list.pb.go
@@ -0,0 +1,198 @@
+//
+// Copyright (C) 2024 The Android Open-Source Project
+//
+// 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: file_list.proto
+
+package proto
+
+import (
+	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)
+)
+
+type FileList struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The name of the file.
+	// In the outermost message, this is the name of the Ninja target.
+	// When used in `changes`, this is the name of the changed file.
+	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"`
+}
+
+func (x *FileList) Reset() {
+	*x = FileList{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_file_list_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *FileList) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*FileList) ProtoMessage() {}
+
+func (x *FileList) ProtoReflect() protoreflect.Message {
+	mi := &file_file_list_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 FileList.ProtoReflect.Descriptor instead.
+func (*FileList) Descriptor() ([]byte, []int) {
+	return file_file_list_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *FileList) GetName() string {
+	if x != nil && x.Name != nil {
+		return *x.Name
+	}
+	return ""
+}
+
+func (x *FileList) GetAdditions() []string {
+	if x != nil {
+		return x.Additions
+	}
+	return nil
+}
+
+func (x *FileList) GetDeletions() []string {
+	if x != nil {
+		return x.Deletions
+	}
+	return nil
+}
+
+func (x *FileList) GetChanges() []*FileList {
+	if x != nil {
+		return x.Changes
+	}
+	return nil
+}
+
+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,
+	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,
+}
+
+var (
+	file_file_list_proto_rawDescOnce sync.Once
+	file_file_list_proto_rawDescData = file_file_list_proto_rawDesc
+)
+
+func file_file_list_proto_rawDescGZIP() []byte {
+	file_file_list_proto_rawDescOnce.Do(func() {
+		file_file_list_proto_rawDescData = protoimpl.X.CompressGZIP(file_file_list_proto_rawDescData)
+	})
+	return file_file_list_proto_rawDescData
+}
+
+var file_file_list_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_file_list_proto_goTypes = []interface{}{
+	(*FileList)(nil), // 0: android.find_input_delta_proto.FileList
+}
+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
+}
+
+func init() { file_file_list_proto_init() }
+func file_file_list_proto_init() {
+	if File_file_list_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_file_list_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*FileList); 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,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_file_list_proto_goTypes,
+		DependencyIndexes: file_file_list_proto_depIdxs,
+		MessageInfos:      file_file_list_proto_msgTypes,
+	}.Build()
+	File_file_list_proto = out.File
+	file_file_list_proto_rawDesc = nil
+	file_file_list_proto_goTypes = nil
+	file_file_list_proto_depIdxs = nil
+}
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
new file mode 100644
index 0000000..d7faca9
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta_proto/file_list.proto
@@ -0,0 +1,34 @@
+//
+// Copyright (C) 2024 The Android Open-Source Project
+//
+// 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 android.find_input_delta_proto;
+option go_package = "android/soong/find_input_delta/proto";
+
+message FileList {
+  // The name of the file.
+  // In the outermost message, this is the name of the Ninja target.
+  // When used in `changes`, this is the name of the changed file.
+  optional string name = 1;
+
+  // The added files.
+  repeated string additions = 2;
+
+  // The deleted files.
+  repeated string deletions = 3;
+
+  // The changed files.
+  repeated FileList changes = 4;
+}
diff --git a/cmd/find_input_delta/find_input_delta_proto/regen.sh b/cmd/find_input_delta/find_input_delta_proto/regen.sh
new file mode 100644
index 0000000..d773659
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta_proto/regen.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+aprotoc --go_out=paths=source_relative:. file_list.proto
diff --git a/cmd/find_input_delta/find_input_delta_proto_internal/Android.bp b/cmd/find_input_delta/find_input_delta_proto_internal/Android.bp
new file mode 100644
index 0000000..00ba9ff
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta_proto_internal/Android.bp
@@ -0,0 +1,29 @@
+// 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+    name: "soong-cmd-find_input_delta-proto_internal",
+    pkgPath: "android/soong/cmd/find_input_delta/find_input_delta_proto_internal",
+    deps: [
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+    ],
+    srcs: [
+        "internal_state.pb.go",
+    ],
+}
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
new file mode 100644
index 0000000..2229a32
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta_proto_internal/internal_state.pb.go
@@ -0,0 +1,268 @@
+//
+// Copyright (C) 2024 The Android Open-Source Project
+//
+// 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: internal_state.proto
+
+package proto
+
+import (
+	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)
+)
+
+// The state of all inputs.
+type PartialCompileInputs struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The status of each file.
+	InputFiles []*PartialCompileInput `protobuf:"bytes,1,rep,name=input_files,json=inputFiles" json:"input_files,omitempty"`
+}
+
+func (x *PartialCompileInputs) Reset() {
+	*x = PartialCompileInputs{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_internal_state_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *PartialCompileInputs) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PartialCompileInputs) ProtoMessage() {}
+
+func (x *PartialCompileInputs) ProtoReflect() protoreflect.Message {
+	mi := &file_internal_state_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 PartialCompileInputs.ProtoReflect.Descriptor instead.
+func (*PartialCompileInputs) Descriptor() ([]byte, []int) {
+	return file_internal_state_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *PartialCompileInputs) GetInputFiles() []*PartialCompileInput {
+	if x != nil {
+		return x.InputFiles
+	}
+	return nil
+}
+
+// The state of one input.
+type PartialCompileInput struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The name of the file.
+	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}’
+	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"`
+}
+
+func (x *PartialCompileInput) Reset() {
+	*x = PartialCompileInput{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_internal_state_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *PartialCompileInput) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*PartialCompileInput) ProtoMessage() {}
+
+func (x *PartialCompileInput) ProtoReflect() protoreflect.Message {
+	mi := &file_internal_state_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 PartialCompileInput.ProtoReflect.Descriptor instead.
+func (*PartialCompileInput) Descriptor() ([]byte, []int) {
+	return file_internal_state_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *PartialCompileInput) GetName() string {
+	if x != nil && x.Name != nil {
+		return *x.Name
+	}
+	return ""
+}
+
+func (x *PartialCompileInput) GetMtimeNsec() int64 {
+	if x != nil && x.MtimeNsec != nil {
+		return *x.MtimeNsec
+	}
+	return 0
+}
+
+func (x *PartialCompileInput) GetHash() string {
+	if x != nil && x.Hash != nil {
+		return *x.Hash
+	}
+	return ""
+}
+
+func (x *PartialCompileInput) GetContents() []*PartialCompileInput {
+	if x != nil {
+		return x.Contents
+	}
+	return nil
+}
+
+var File_internal_state_proto protoreflect.FileDescriptor
+
+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,
+	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,
+}
+
+var (
+	file_internal_state_proto_rawDescOnce sync.Once
+	file_internal_state_proto_rawDescData = file_internal_state_proto_rawDesc
+)
+
+func file_internal_state_proto_rawDescGZIP() []byte {
+	file_internal_state_proto_rawDescOnce.Do(func() {
+		file_internal_state_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_state_proto_rawDescData)
+	})
+	return file_internal_state_proto_rawDescData
+}
+
+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
+}
+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
+	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_internal_state_proto_init() }
+func file_internal_state_proto_init() {
+	if File_internal_state_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_internal_state_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*PartialCompileInputs); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_internal_state_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*PartialCompileInput); 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_internal_state_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   2,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_internal_state_proto_goTypes,
+		DependencyIndexes: file_internal_state_proto_depIdxs,
+		MessageInfos:      file_internal_state_proto_msgTypes,
+	}.Build()
+	File_internal_state_proto = out.File
+	file_internal_state_proto_rawDesc = nil
+	file_internal_state_proto_goTypes = nil
+	file_internal_state_proto_depIdxs = nil
+}
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
new file mode 100644
index 0000000..113fc64
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta_proto_internal/internal_state.proto
@@ -0,0 +1,39 @@
+//
+// Copyright (C) 2024 The Android Open-Source Project
+//
+// 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 android.find_input_delta_proto;
+option go_package = "android/soong/find_input_delta/proto";
+
+// The state of all inputs.
+message PartialCompileInputs {
+  // The status of each file.
+  repeated PartialCompileInput input_files = 1;
+}
+
+// The state of one input.
+message PartialCompileInput {
+  // The name of the file.
+  optional string name = 1;
+
+  // The timestamp of the file in (Unix) nanoseconds.
+  optional int64 mtime_nsec = 2;
+
+  // The hash of the file, in the form ‘{HASHNAME}:{VALUE}’
+  optional string hash = 3;
+
+  // Contents of the file, if the file was inspected (such as jar files, etc).
+  repeated PartialCompileInput contents = 4;
+}
diff --git a/cmd/find_input_delta/find_input_delta_proto_internal/regen.sh b/cmd/find_input_delta/find_input_delta_proto_internal/regen.sh
new file mode 100644
index 0000000..cbaf7d0
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta_proto_internal/regen.sh
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+aprotoc --go_out=paths=source_relative:.  internal_state.proto
diff --git a/cmd/release_config/release_config_lib/flag_artifact.go b/cmd/release_config/release_config_lib/flag_artifact.go
index 51c02d2..bb321da 100644
--- a/cmd/release_config/release_config_lib/flag_artifact.go
+++ b/cmd/release_config/release_config_lib/flag_artifact.go
@@ -99,6 +99,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)
 	}
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/cmd/symbols_map/symbols_map_proto/symbols_map.proto b/cmd/symbols_map/symbols_map_proto/symbols_map.proto
index 693fe3e..a76d171 100644
--- a/cmd/symbols_map/symbols_map_proto/symbols_map.proto
+++ b/cmd/symbols_map/symbols_map_proto/symbols_map.proto
@@ -37,6 +37,21 @@
 
   // type is the type of the mapping, either ELF or R8.
   optional Type type = 3;
+
+  // LocationType is the place where to look for the file with the given
+  // identifier.
+  Enum LocationType {
+    // ZIP denotes the file with the given identifier is in the distribuited
+    // symbols.zip or proguard_dict.zip files, or the local disc.
+    ZIP = 0;
+    // AB denotes the file with the given identifier is in the AB artifacts but
+    // not in a symbols.zip or proguard_dict.zip.
+    AB = 1;
+  }
+
+  // location_type is the Location Type that dictates where to search for the
+  // file with the given identifier. Defaults to ZIP if not present.
+  optional LocationType location_type = 4;
 }
 
 message Mappings {
diff --git a/compliance/Android.bp b/compliance/Android.bp
index 72c2f27..6662970 100644
--- a/compliance/Android.bp
+++ b/compliance/Android.bp
@@ -35,5 +35,6 @@
     partition_name: "system",
     visibility: [
         "//build/make/target/product/generic",
+        "//build/make/target/product/gsi",
     ],
 }
diff --git a/compliance/notice.go b/compliance/notice.go
index 4fc83ab..edd1b34 100644
--- a/compliance/notice.go
+++ b/compliance/notice.go
@@ -18,6 +18,7 @@
 	"path/filepath"
 
 	"android/soong/android"
+
 	"github.com/google/blueprint"
 )
 
@@ -62,8 +63,7 @@
 
 	props noticeXmlProperties
 
-	outputFile  android.OutputPath
-	installPath android.InstallPath
+	outputFile android.OutputPath
 }
 
 type noticeXmlProperties struct {
@@ -86,10 +86,8 @@
 
 	nx.outputFile = output.OutputPath
 
-	if android.Bool(ctx.Config().ProductVariables().UseSoongSystemImage) {
-		nx.installPath = android.PathForModuleInPartitionInstall(ctx, nx.props.Partition_name, "etc")
-		ctx.InstallFile(nx.installPath, "NOTICE.xml.gz", nx.outputFile)
-	}
+	installPath := android.PathForModuleInPartitionInstall(ctx, nx.props.Partition_name, "etc")
+	ctx.PackageFile(installPath, "NOTICE.xml.gz", nx.outputFile)
 }
 
 func (nx *NoticeXmlModule) AndroidMkEntries() []android.AndroidMkEntries {
diff --git a/docs/selects.md b/docs/selects.md
new file mode 100644
index 0000000..4f436ac
--- /dev/null
+++ b/docs/selects.md
@@ -0,0 +1,170 @@
+# Select statements
+
+## Introduction
+
+Soong currently has the arch, target, product_variables, and soong config variable properties that all support changing the values of soong properties based on some condition. These are confusing for users, and particularly the soong config variable properties require a lot of boilerplate that is annoying to write.
+
+In addition, these properties are all implemented using reflection on property structs, and can combine in ways that the original authors did not expect. For example, soong config variables can be combined with arch/target by saying that they operate on "arch.arm.enabled" instead of just "enabled". But product variables do not have the same abilities.
+
+The goal here is to combine all these different configuration mechanisms into one, to reduce complexity and boilerplate both in Android.bp files and in soong code.
+
+## Usage
+
+The soong select statements take their name and inspiration from [bazel select statements](https://bazel.build/docs/configurable-attributes).
+
+### Syntax
+
+#### Basic
+
+The basic syntax for select statements looks like:
+
+```
+my_module_type {
+  name: "my_module",
+  some_string_property: select(arch(), {
+    "arm": "foo",
+    "x86": "bar",
+    default: "baz",
+  }),
+}
+```
+
+That is, `select(` followed by a variable function, then a map of values of the variable to values to set the property to.
+
+Arguments can be passed to the "functions" that look up axes:
+
+```
+select(soong_config_variable("my_namespace", "my_variable"), {
+  "value1": "foo",
+  default: "bar",
+})
+```
+
+
+The list of functions that can currently be selected on:
+ - `arch()`
+ - `os()`
+ - `soong_config_variable(namespace, variable)`
+ - `release_flag(flag)`
+
+The functions are [defined here](https://cs.android.com/android/platform/superproject/main/+/main:build/soong/android/module.go;l=2144;drc=3f01580c04bfe37c920e247015cce93cff2451c0), and it should be easy to add more.
+
+#### Multivariable
+
+To handle multivariable selects, multiple axes can be specified within parenthesis, to look like tuple destructuring. All of the variables being selected must match the corresponding value from the branch in order for the branch to be chosen.
+
+```
+select((arch(), os()), {
+  ("arm", "linux"): "foo",
+  (default, "windows"): "bar",
+  (default, default): "baz",
+})
+```
+
+#### Unset
+
+You can have unset branches of selects using the "unset" keyword, which will act as if the property was not assigned to. This is only really useful if you’re using defaults modules.
+
+```
+cc_binary {
+  name: "my_binary",
+  enabled: select(os(), {
+    "darwin": false,
+    default: unset,
+  }),
+}
+```
+
+#### Appending
+
+You can append select statements to both scalar values and other select statements:
+
+```
+my_module_type {
+  name: "my_module",
+  // string_property will be set to something like penguin-four, apple-two, etc.
+  string_property: select(os(), {
+    "linux_glibc": "penguin",
+    "darwin": "apple",
+    default: "unknown",
+  }) + "-" + select(soong_config_variable("ANDROID", "favorite_vehicle"), {
+    "car": "four",
+    "tricycle": "three",
+    "bike": "two",
+     default: "unknown",
+  })
+}
+```
+
+
+You can also append a select with a value with another select that may not have a value, because some of its branches are "unset". If an unset branch was selected it will not append anything to the other select.
+
+#### Binding the selected value to a Blueprint variable and the "any" keyword
+
+In case you want to allow a selected value to have an unbounded number of possible values, you can bind its value to a blueprint variable and use it within the expression for that select branch.
+
+```
+my_module_type {
+  name: "my_module",
+  my_string_property: select(soong_config_variable("my_namespace", "my_variable"), {
+    "some_value": "baz",
+    any @ my_var: "foo" + my_var,
+    default: "bar",
+  }),
+}
+```
+
+The syntax is `any @ my_variable_name: <expression using my_variable_name>`. `any` is currently the only pattern that can be bound to a variable, but we may add more in the future. `any` is equivalent to `default` except it will not match undefined select conditions.
+
+#### Errors
+
+If a select statement does not have a "default" branch, and none of the other branches match the variable being selected on, it’s a compile-time error. This may be useful for enforcing a variable is 1 of only a few values.
+
+```
+# in product config:
+$(call soong_config_set,ANDROID,my_variable,foo)
+
+// in an Android.bp:
+my_module_type {
+  name: "my_module",
+  // Will error out with: soong_config_variable("ANDROID", "my_variable") had value "foo", which was not handled by the select
+  enabled: select(soong_config_variable("ANDROID", "my_variable"), {
+    "bar": true,
+    "baz": false,
+  }),
+}
+```
+
+### Changes to property structs to support selects
+
+Currently, the way configurable properties work is that there is secretly another property struct that has the `target`, `arch`, etc. properties, and then when the arch mutator (or other relevant mutator) runs, it copies the values of these properties onto the regular property structs. There’s nothing stopping you from accessing your properties from a mutator that runs before the one that updates the properties based on configurable values. This is a potential source of bugs, and we want to make sure that select statements don’t have the same pitfall. For that reason, you have to read property’s values through a getter which can do this check. This requires changing the code on a property-by-property basis to support selects.
+
+To make a property support selects, it must be of type [proptools.Configurable[T]](https://cs.android.com/android/platform/superproject/main/+/main:build/blueprint/proptools/configurable.go;l=341;drc=a52b058cccd2caa778d0f97077adcd4ef7ffb68a). T is the old type of the property. Currently we support bool, string, and []string. Configurable has a `Get(evaluator)` method to get the value of the property. The evaluator can be a ModuleContext, or if you’re in a situation where you only have a very limited context and a module, (such as in a singleton) you can use [ModuleBase.ConfigurableEvaluator](https://cs.android.com/android/platform/superproject/main/+/main:build/soong/android/module.go;l=2133;drc=e19f741052cce097da940d9083d3f29e668de5cb).
+
+`proptools.Configurable[T]` will handle unset properties for you, so you don’t need to make it a pointer type. However, there is a not-widely-known feature of property structs, where normally, properties are appended when squashing defaults. But if the property was a pointer property, later defaults replace earlier values instead of appending. With selects, to maintain this behavior, add the `android:"replace_instead_of_append"` struct tag. The "append" behavior for boolean values is to boolean OR them together, which is rarely what you want, so most boolean properties are pointers today.
+
+Old:
+```
+type commonProperties struct {
+  Enabled *bool `android:"arch_variant"`
+}
+
+func (m *ModuleBase) Enabled() *bool {
+  return m.commonProperties.Enabled
+}
+```
+
+New:
+```
+type commonProperties struct {
+  Enabled proptools.Configurable[bool] `android:"arch_variant,replace_instead_of_append"`
+}
+
+func (m *ModuleBase) Enabled(ctx ConfigAndErrorContext) *bool {
+  return m.commonProperties.Enabled.Get(m.ConfigurableEvaluator(ctx))
+}
+```
+
+The `android:"arch_variant"` tag is kept to support the old `target:` and `arch:` properties with this property, but if all their usages in bp files were replaced by selects, then that tag could be removed.
+
+The enabled property underwent this migration in https://r.android.com/3066188
diff --git a/etc/adb_keys.go b/etc/adb_keys.go
index a2df41c..73bc347 100644
--- a/etc/adb_keys.go
+++ b/etc/adb_keys.go
@@ -36,6 +36,11 @@
 
 func (m *AdbKeysModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	productVariables := ctx.Config().ProductVariables()
+
+	if !m.ProductSpecific() {
+		ctx.ModuleErrorf("adb_keys module type must set product_specific to true")
+	}
+
 	if !(android.Bool(productVariables.Debuggable) && len(android.String(productVariables.AdbKeys)) > 0) {
 		m.SkipInstall()
 		return
@@ -48,7 +53,7 @@
 		Output: m.outputPath,
 		Input:  input.Path(),
 	})
-	m.installPath = android.PathForModuleInPartitionInstall(ctx, ctx.DeviceConfig().ProductPath(), "etc/security")
+	m.installPath = android.PathForModuleInstall(ctx, "etc/security")
 	ctx.InstallFile(m.installPath, "adb_keys", m.outputPath)
 }
 
diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index 743bfd4..943ec81 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -59,16 +59,28 @@
 	ctx.RegisterModuleType("prebuilt_usr_keylayout", PrebuiltUserKeyLayoutFactory)
 	ctx.RegisterModuleType("prebuilt_usr_keychars", PrebuiltUserKeyCharsFactory)
 	ctx.RegisterModuleType("prebuilt_usr_idc", PrebuiltUserIdcFactory)
+	ctx.RegisterModuleType("prebuilt_usr_srec", PrebuiltUserSrecFactory)
 	ctx.RegisterModuleType("prebuilt_font", PrebuiltFontFactory)
 	ctx.RegisterModuleType("prebuilt_overlay", PrebuiltOverlayFactory)
 	ctx.RegisterModuleType("prebuilt_firmware", PrebuiltFirmwareFactory)
 	ctx.RegisterModuleType("prebuilt_dsp", PrebuiltDSPFactory)
 	ctx.RegisterModuleType("prebuilt_rfsa", PrebuiltRFSAFactory)
 	ctx.RegisterModuleType("prebuilt_renderscript_bitcode", PrebuiltRenderScriptBitcodeFactory)
-	ctx.RegisterModuleType("prebuilt_media_audio", PrebuiltMediaAudioFactory)
+	ctx.RegisterModuleType("prebuilt_media", PrebuiltMediaFactory)
 	ctx.RegisterModuleType("prebuilt_voicepack", PrebuiltVoicepackFactory)
 	ctx.RegisterModuleType("prebuilt_bin", PrebuiltBinaryFactory)
 	ctx.RegisterModuleType("prebuilt_wallpaper", PrebuiltWallpaperFactory)
+	ctx.RegisterModuleType("prebuilt_priv_app", PrebuiltPrivAppFactory)
+	ctx.RegisterModuleType("prebuilt_rfs", PrebuiltRfsFactory)
+	ctx.RegisterModuleType("prebuilt_framework", PrebuiltFrameworkFactory)
+	ctx.RegisterModuleType("prebuilt_res", PrebuiltResFactory)
+	ctx.RegisterModuleType("prebuilt_wlc_upt", PrebuiltWlcUptFactory)
+	ctx.RegisterModuleType("prebuilt_odm", PrebuiltOdmFactory)
+	ctx.RegisterModuleType("prebuilt_vendor_dlkm", PrebuiltVendorDlkmFactory)
+	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)
 
@@ -76,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"`
@@ -125,6 +137,9 @@
 
 	// Install symlinks to the installed file.
 	Symlinks []string `android:"arch_variant"`
+
+	// Install to partition oem when set to true.
+	Oem_specific *bool `android:"arch_variant"`
 }
 
 type prebuiltSubdirProperties struct {
@@ -160,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.
@@ -362,6 +377,10 @@
 		ctx.PropertyErrorf("sub_dir", "relative_install_path is set. Cannot set sub_dir")
 	}
 	baseInstallDirPath := android.PathForModuleInstall(ctx, p.installBaseDir(ctx), p.SubDir())
+	// TODO(b/377304441)
+	if android.Bool(p.properties.Oem_specific) {
+		baseInstallDirPath = android.PathForModuleInPartitionInstall(ctx, ctx.DeviceConfig().OemPath(), p.installBaseDir(ctx), p.SubDir())
+	}
 
 	filename := proptools.String(p.properties.Filename)
 	filenameFromSrc := proptools.Bool(p.properties.Filename_from_src)
@@ -588,7 +607,7 @@
 
 	module.AddProperties(props...)
 	module.AddProperties(
-		&prebuiltEtcProperties{},
+		&PrebuiltEtcProperties{},
 		&prebuiltSubdirProperties{},
 	)
 
@@ -720,6 +739,17 @@
 	return module
 }
 
+// prebuilt_usr_srec is for a prebuilt artifact that is installed in
+// <partition>/usr/srec/<sub_dir> directory.
+func PrebuiltUserSrecFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "usr/srec")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
+	return module
+}
+
 // prebuilt_font installs a font in <partition>/fonts directory.
 func PrebuiltFontFactory() android.Module {
 	module := &PrebuiltEtc{}
@@ -793,10 +823,10 @@
 	return module
 }
 
-// prebuilt_media_audio installs audio files in <partition>/media/audio directory.
-func PrebuiltMediaAudioFactory() android.Module {
+// prebuilt_media installs media files in <partition>/media directory.
+func PrebuiltMediaFactory() android.Module {
 	module := &PrebuiltEtc{}
-	InitPrebuiltEtcModule(module, "media/audio")
+	InitPrebuiltEtcModule(module, "media")
 	// This module is device-only
 	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	android.InitDefaultableModule(module)
@@ -832,3 +862,113 @@
 	android.InitDefaultableModule(module)
 	return module
 }
+
+// prebuilt_priv_app installs files in <partition>/priv-app directory.
+func PrebuiltPrivAppFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "priv-app")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+// prebuilt_rfs installs files in <partition>/rfs directory.
+func PrebuiltRfsFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "rfs")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+// prebuilt_framework installs files in <partition>/framework directory.
+func PrebuiltFrameworkFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "framework")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+// prebuilt_res installs files in <partition>/res directory.
+func PrebuiltResFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "res")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+// prebuilt_wlc_upt installs files in <partition>/wlc_upt directory.
+func PrebuiltWlcUptFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "wlc_upt")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+// prebuilt_odm installs files in <partition>/odm directory.
+func PrebuiltOdmFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "odm")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+// prebuilt_vendor_dlkm installs files in <partition>/vendor_dlkm directory.
+func PrebuiltVendorDlkmFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "vendor_dlkm")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+// prebuilt_bt_firmware installs files in <partition>/bt_firmware directory.
+func PrebuiltBtFirmwareFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "bt_firmware")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+// prebuilt_tvservice installs files in <partition>/tvservice directory.
+func PrebuiltTvServiceFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "tvservice")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+// prebuilt_optee installs files in <partition>/optee directory.
+func PrebuiltOpteeFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "optee")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	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/etc/prebuilt_etc_test.go b/etc/prebuilt_etc_test.go
index 4a0312f..0fd04d8 100644
--- a/etc/prebuilt_etc_test.go
+++ b/etc/prebuilt_etc_test.go
@@ -591,7 +591,7 @@
 
 func TestPrebuiltMediaAutoDirPath(t *testing.T) {
 	result := prepareForPrebuiltEtcTest.RunTestWithBp(t, `
-		prebuilt_media_audio {
+		prebuilt_media {
 			name: "foo",
 			src: "Alarm_Beep_01.ogg",
 			product_specific: true,
@@ -600,6 +600,6 @@
 	`)
 
 	p := result.Module("foo", "android_common").(*PrebuiltEtc)
-	expected := "out/soong/target/product/test_device/product/media/audio/alarms"
+	expected := "out/soong/target/product/test_device/product/media/alarms"
 	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
 }
diff --git a/filesystem/android_device.go b/filesystem/android_device.go
index 68e6053..ab1b96e 100644
--- a/filesystem/android_device.go
+++ b/filesystem/android_device.go
@@ -32,6 +32,12 @@
 	Product_partition_name *string
 	// Name of the Vendor partition filesystem module
 	Vendor_partition_name *string
+	// Name of the Odm partition filesystem module
+	Odm_partition_name *string
+	// The vbmeta partition and its "chained" partitions
+	Vbmeta_partitions []string
+	// Name of the Userdata partition filesystem module
+	Userdata_partition_name *string
 }
 
 type androidDevice struct {
@@ -44,7 +50,6 @@
 	module := &androidDevice{}
 	module.AddProperties(&module.partitionProps)
 	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
-
 	return module
 }
 
@@ -66,6 +71,11 @@
 	addDependencyIfDefined(a.partitionProps.System_ext_partition_name)
 	addDependencyIfDefined(a.partitionProps.Product_partition_name)
 	addDependencyIfDefined(a.partitionProps.Vendor_partition_name)
+	addDependencyIfDefined(a.partitionProps.Odm_partition_name)
+	addDependencyIfDefined(a.partitionProps.Userdata_partition_name)
+	for _, vbmetaPartition := range a.partitionProps.Vbmeta_partitions {
+		ctx.AddDependency(ctx.Module(), filesystemDepTag, vbmetaPartition)
+	}
 }
 
 func (a *androidDevice) GenerateAndroidBuildActions(ctx android.ModuleContext) {
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index 97421c8..fa66451 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -25,6 +25,7 @@
 
 	"android/soong/android"
 	"android/soong/cc"
+	"android/soong/linkerconfig"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
@@ -51,12 +52,6 @@
 
 	properties FilesystemProperties
 
-	// Function that builds extra files under the root directory and returns the files
-	buildExtraFiles func(ctx android.ModuleContext, root android.OutputPath) android.OutputPaths
-
-	// Function that filters PackagingSpec in PackagingBase.GatherPackagingSpecs()
-	filterPackagingSpec func(spec android.PackagingSpec) bool
-
 	output     android.OutputPath
 	installDir android.InstallPath
 
@@ -64,8 +59,22 @@
 
 	// Keeps the entries installed from this filesystem
 	entries []string
+
+	filesystemBuilder filesystemBuilder
 }
 
+type filesystemBuilder interface {
+	BuildLinkerConfigFile(ctx android.ModuleContext, builder *android.RuleBuilder, rebasedDir android.OutputPath)
+	// Function that filters PackagingSpec in PackagingBase.GatherPackagingSpecs()
+	FilterPackagingSpec(spec android.PackagingSpec) bool
+	// Function that modifies PackagingSpec in PackagingBase.GatherPackagingSpecs() to customize.
+	// For example, GSI system.img contains system_ext and product artifacts and their
+	// relPathInPackage need to be rebased to system/system_ext and system/system_product.
+	ModifyPackagingSpec(spec *android.PackagingSpec)
+}
+
+var _ filesystemBuilder = (*filesystem)(nil)
+
 type SymlinkDefinition struct {
 	Target *string
 	Name   *string
@@ -146,6 +155,10 @@
 
 	Erofs ErofsProperties
 
+	F2fs F2fsProperties
+
+	Linker_config LinkerConfigProperties
+
 	// Determines if the module is auto-generated from Soong or not. If the module is
 	// auto-generated, its deps are exempted from visibility enforcement.
 	Is_auto_generated *bool
@@ -163,6 +176,21 @@
 	Sparse *bool
 }
 
+// Additional properties required to generate f2fs FS partitions.
+type F2fsProperties struct {
+	Sparse *bool
+}
+
+type LinkerConfigProperties struct {
+
+	// Build a linker.config.pb file
+	Gen_linker_config *bool
+
+	// List of files (in .json format) that will be converted to a linker config file (in .pb format).
+	// The linker config file be installed in the filesystem at /etc/linker.config.pb
+	Linker_config_srcs []string `android:"path"`
+}
+
 // android_filesystem packages a set of modules and their transitive dependencies into a filesystem
 // image. The filesystem images are expected to be mounted in the target device, which means the
 // modules in the filesystem image are built for the target device (i.e. Android, not Linux host).
@@ -170,7 +198,7 @@
 // partitions like system.img. For example, cc_library modules are placed under ./lib[64] directory.
 func FilesystemFactory() android.Module {
 	module := &filesystem{}
-	module.filterPackagingSpec = module.filterInstallablePackagingSpec
+	module.filesystemBuilder = module
 	initFilesystemModule(module, module)
 	return module
 }
@@ -179,6 +207,7 @@
 	module.AddProperties(&filesystemModule.properties)
 	android.InitPackageModule(filesystemModule)
 	filesystemModule.PackagingBase.DepsCollectFirstTargetOnly = true
+	filesystemModule.PackagingBase.AllowHighPriorityDeps = true
 	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	android.InitDefaultableModule(module)
 }
@@ -213,6 +242,7 @@
 const (
 	ext4Type fsType = iota
 	erofsType
+	f2fsType
 	compressedCpioType
 	cpioType // uncompressed
 	unknown
@@ -235,6 +265,8 @@
 		return ext4Type
 	case "erofs":
 		return erofsType
+	case "f2fs":
+		return f2fsType
 	case "compressed_cpio":
 		return compressedCpioType
 	case "cpio":
@@ -261,13 +293,32 @@
 	return proptools.StringDefault(f.properties.Partition_name, f.Name())
 }
 
-func (f *filesystem) filterInstallablePackagingSpec(ps android.PackagingSpec) bool {
+func (f *filesystem) FilterPackagingSpec(ps android.PackagingSpec) bool {
 	// Filesystem module respects the installation semantic. A PackagingSpec from a module with
 	// IsSkipInstall() is skipped.
-	if proptools.Bool(f.properties.Is_auto_generated) { // TODO (spandandas): Remove this.
-		return !ps.SkipInstall() && (ps.Partition() == f.PartitionType())
+	if ps.SkipInstall() {
+		return false
 	}
-	return !ps.SkipInstall()
+	if proptools.Bool(f.properties.Is_auto_generated) { // TODO (spandandas): Remove this.
+		pt := f.PartitionType()
+		return ps.Partition() == pt || strings.HasPrefix(ps.Partition(), pt+"/")
+	}
+	return true
+}
+
+func (f *filesystem) ModifyPackagingSpec(ps *android.PackagingSpec) {
+	// 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")
@@ -275,7 +326,7 @@
 func (f *filesystem) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	validatePartitionType(ctx, f)
 	switch f.fsType(ctx) {
-	case ext4Type, erofsType:
+	case ext4Type, erofsType, f2fsType:
 		f.output = f.buildImageUsingBuildImage(ctx)
 	case compressedCpioType:
 		f.output = f.buildCpioImage(ctx, true)
@@ -363,25 +414,6 @@
 		builder.Command().Text("ln -sf").Text(proptools.ShellEscape(target)).Text(dst.String())
 		f.appendToEntry(ctx, dst)
 	}
-
-	// create extra files if there's any
-	if f.buildExtraFiles != nil {
-		rootForExtraFiles := android.PathForModuleGen(ctx, "root-extra").OutputPath
-		extraFiles := f.buildExtraFiles(ctx, rootForExtraFiles)
-		for _, extraFile := range extraFiles {
-			rel, err := filepath.Rel(rootForExtraFiles.String(), extraFile.String())
-			if err != nil || strings.HasPrefix(rel, "..") {
-				ctx.ModuleErrorf("can't make %q relative to %q", extraFile, rootForExtraFiles)
-			}
-			f.appendToEntry(ctx, rootDir.Join(ctx, rel))
-		}
-		if len(extraFiles) > 0 {
-			builder.Command().BuiltTool("merge_directories").
-				Implicits(extraFiles.Paths()).
-				Text(rootDir.String()).
-				Text(rootForExtraFiles.String())
-		}
-	}
 }
 
 func (f *filesystem) copyPackagingSpecs(ctx android.ModuleContext, builder *android.RuleBuilder, specs map[string]android.PackagingSpec, rootDir, rebasedDir android.WritablePath) []string {
@@ -428,6 +460,7 @@
 	f.buildFsverityMetadataFiles(ctx, builder, specs, rootDir, rebasedDir)
 	f.buildEventLogtagsFile(ctx, builder, rebasedDir)
 	f.buildAconfigFlagsFiles(ctx, builder, specs, rebasedDir)
+	f.filesystemBuilder.BuildLinkerConfigFile(ctx, builder, rebasedDir)
 	f.copyFilesToProductOut(ctx, builder, rebasedDir)
 
 	// run host_init_verifier
@@ -490,6 +523,8 @@
 			return "ext4"
 		case erofsType:
 			return "erofs"
+		case f2fsType:
+			return "f2fs"
 		}
 		panic(fmt.Errorf("unsupported fs type %v", t))
 	}
@@ -539,8 +574,11 @@
 		addStr("uuid", uuid)
 		addStr("hash_seed", uuid)
 	}
-	// Add erofs properties
-	if f.fsType(ctx) == erofsType {
+
+	fst := f.fsType(ctx)
+	switch fst {
+	case erofsType:
+		// Add erofs properties
 		if compressor := f.properties.Erofs.Compressor; compressor != nil {
 			addStr("erofs_default_compressor", proptools.String(compressor))
 		}
@@ -551,17 +589,39 @@
 			// https://source.corp.google.com/h/googleplex-android/platform/build/+/88b1c67239ca545b11580237242774b411f2fed9:core/Makefile;l=2292;bpv=1;bpt=0;drc=ea8f34bc1d6e63656b4ec32f2391e9d54b3ebb6b
 			addStr("erofs_sparse_flag", "-s")
 		}
-	} else if f.properties.Erofs.Compressor != nil || f.properties.Erofs.Compress_hints != nil || f.properties.Erofs.Sparse != nil {
-		// Raise an exception if the propfile contains erofs properties, but the fstype is not erofs
-		fs := fsTypeStr(f.fsType(ctx))
-		ctx.PropertyErrorf("erofs", "erofs is non-empty, but FS type is %s\n. Please delete erofs properties if this partition should use %s\n", fs, fs)
+	case f2fsType:
+		if proptools.BoolDefault(f.properties.F2fs.Sparse, true) {
+			// https://source.corp.google.com/h/googleplex-android/platform/build/+/88b1c67239ca545b11580237242774b411f2fed9:core/Makefile;l=2294;drc=ea8f34bc1d6e63656b4ec32f2391e9d54b3ebb6b;bpv=1;bpt=0
+			addStr("f2fs_sparse_flag", "-S")
+		}
 	}
+	f.checkFsTypePropertyError(ctx, fst, fsTypeStr(fst))
 
 	propFile = android.PathForModuleOut(ctx, "prop").OutputPath
 	android.WriteFileRuleVerbatim(ctx, propFile, propFileString.String())
 	return propFile, deps
 }
 
+// This method checks if there is any property set for the fstype(s) other than
+// the current fstype.
+func (f *filesystem) checkFsTypePropertyError(ctx android.ModuleContext, t fsType, fs string) {
+	raiseError := func(otherFsType, currentFsType string) {
+		errMsg := fmt.Sprintf("%s is non-empty, but FS type is %s\n. Please delete %s properties if this partition should use %s\n", otherFsType, currentFsType, otherFsType, currentFsType)
+		ctx.PropertyErrorf(otherFsType, errMsg)
+	}
+
+	if t != erofsType {
+		if f.properties.Erofs.Compressor != nil || f.properties.Erofs.Compress_hints != nil || f.properties.Erofs.Sparse != nil {
+			raiseError("erofs", fs)
+		}
+	}
+	if t != f2fsType {
+		if f.properties.F2fs.Sparse != nil {
+			raiseError("f2fs", fs)
+		}
+	}
+}
+
 func (f *filesystem) buildCpioImage(ctx android.ModuleContext, compressed bool) android.OutputPath {
 	if proptools.Bool(f.properties.Use_avb) {
 		ctx.PropertyErrorf("use_avb", "signing compresed cpio image using avbtool is not supported."+
@@ -591,6 +651,7 @@
 	f.buildFsverityMetadataFiles(ctx, builder, specs, rootDir, rebasedDir)
 	f.buildEventLogtagsFile(ctx, builder, rebasedDir)
 	f.buildAconfigFlagsFiles(ctx, builder, specs, rebasedDir)
+	f.filesystemBuilder.BuildLinkerConfigFile(ctx, builder, rebasedDir)
 	f.copyFilesToProductOut(ctx, builder, rebasedDir)
 
 	output := android.PathForModuleOut(ctx, f.installFileName()).OutputPath
@@ -626,6 +687,7 @@
 	"vendor_dlkm",
 	"odm_dlkm",
 	"system_dlkm",
+	"ramdisk",
 }
 
 func (f *filesystem) addMakeBuiltFiles(ctx android.ModuleContext, builder *android.RuleBuilder, rootDir android.Path) {
@@ -682,6 +744,18 @@
 	f.appendToEntry(ctx, eventLogtagsPath)
 }
 
+func (f *filesystem) BuildLinkerConfigFile(ctx android.ModuleContext, builder *android.RuleBuilder, rebasedDir android.OutputPath) {
+	if !proptools.Bool(f.properties.Linker_config.Gen_linker_config) {
+		return
+	}
+
+	provideModules, _ := f.getLibsForLinkerConfig(ctx)
+	output := rebasedDir.Join(ctx, "etc", "linker.config.pb")
+	linkerconfig.BuildLinkerConfig(ctx, builder, android.PathsForModuleSrc(ctx, f.properties.Linker_config.Linker_config_srcs), provideModules, nil, output)
+
+	f.appendToEntry(ctx, output)
+}
+
 type partition interface {
 	PartitionType() string
 }
@@ -737,7 +811,7 @@
 // Note that "apex" module installs its contents to "apex"(fake partition) as well
 // for symbol lookup by imitating "activated" paths.
 func (f *filesystem) gatherFilteredPackagingSpecs(ctx android.ModuleContext) map[string]android.PackagingSpec {
-	specs := f.PackagingBase.GatherPackagingSpecsWithFilter(ctx, f.filterPackagingSpec)
+	specs := f.PackagingBase.GatherPackagingSpecsWithFilterAndModifier(ctx, f.filesystemBuilder.FilterPackagingSpec, f.filesystemBuilder.ModifyPackagingSpec)
 	return specs
 }
 
@@ -763,13 +837,7 @@
 	android.ModuleBase
 	android.DefaultsModuleBase
 
-	properties filesystemDefaultsProperties
-}
-
-type filesystemDefaultsProperties struct {
-	// Identifies which partition this is for //visibility:any_system_image (and others) visibility
-	// checks, and will be used in the future for API surface checks.
-	Partition_type *string
+	properties FilesystemProperties
 }
 
 // android_filesystem_defaults is a default module for android_filesystem and android_system_image
@@ -790,3 +858,48 @@
 func (f *filesystemDefaults) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	validatePartitionType(ctx, f)
 }
+
+// getLibsForLinkerConfig returns
+// 1. A list of libraries installed in this filesystem
+// 2. A list of dep libraries _not_ installed in this filesystem
+//
+// `linkerconfig.BuildLinkerConfig` will convert these two to a linker.config.pb for the filesystem
+// (1) will be added to --provideLibs if they are C libraries with a stable interface (has stubs)
+// (2) will be added to --requireLibs if they are C libraries with a stable interface (has stubs)
+func (f *filesystem) getLibsForLinkerConfig(ctx android.ModuleContext) ([]android.Module, []android.Module) {
+	// we need "Module"s for packaging items
+	modulesInPackageByModule := make(map[android.Module]bool)
+	modulesInPackageByName := make(map[string]bool)
+
+	deps := f.gatherFilteredPackagingSpecs(ctx)
+	ctx.WalkDeps(func(child, parent android.Module) bool {
+		for _, ps := range android.OtherModuleProviderOrDefault(
+			ctx, child, android.InstallFilesProvider).PackagingSpecs {
+			if _, ok := deps[ps.RelPathInPackage()]; ok {
+				modulesInPackageByModule[child] = true
+				modulesInPackageByName[child.Name()] = true
+				return true
+			}
+		}
+		return true
+	})
+
+	provideModules := make([]android.Module, 0, len(modulesInPackageByModule))
+	for mod := range modulesInPackageByModule {
+		provideModules = append(provideModules, mod)
+	}
+
+	var requireModules []android.Module
+	ctx.WalkDeps(func(child, parent android.Module) bool {
+		_, parentInPackage := modulesInPackageByModule[parent]
+		_, childInPackageName := modulesInPackageByName[child.Name()]
+
+		// When parent is in the package, and child (or its variant) is not, this can be from an interface.
+		if parentInPackage && !childInPackageName {
+			requireModules = append(requireModules, child)
+		}
+		return true
+	})
+
+	return provideModules, requireModules
+}
diff --git a/filesystem/filesystem_test.go b/filesystem/filesystem_test.go
index 7300061..f325d96 100644
--- a/filesystem/filesystem_test.go
+++ b/filesystem/filesystem_test.go
@@ -156,11 +156,15 @@
 	result := fixture.RunTestWithBp(t, `
 		android_system_image {
 			name: "myfilesystem",
+			base_dir: "system",
 			deps: [
 				"libfoo",
 				"libbar",
 			],
-			linker_config_src: "linker.config.json",
+			linker_config: {
+				gen_linker_config: true,
+				linker_config_srcs: ["linker.config.json"],
+			},
 		}
 
 		cc_library {
@@ -176,7 +180,7 @@
 	`)
 
 	module := result.ModuleForTests("myfilesystem", "android_common")
-	output := module.Output("system/etc/linker.config.pb")
+	output := module.Output("out/soong/.intermediates/myfilesystem/android_common/root/system/etc/linker.config.pb")
 
 	android.AssertStringDoesContain(t, "linker.config.pb should have libfoo",
 		output.RuleParams.Command, "libfoo.so")
@@ -223,7 +227,10 @@
 					deps: ["foo"],
 				},
 			},
-			linker_config_src: "linker.config.json",
+			linker_config: {
+				gen_linker_config: true,
+				linker_config_srcs: ["linker.config.json"],
+			},
 		}
 		component {
 			name: "foo",
@@ -318,7 +325,10 @@
 			deps: [
 				"libfoo",
 			],
-			linker_config_src: "linker.config.json",
+			linker_config: {
+				gen_linker_config: true,
+				linker_config_srcs: ["linker.config.json"],
+			},
 		}
 
 		cc_library {
@@ -585,6 +595,35 @@
 	android.AssertStringDoesContain(t, "erofs fs type sparse", buildImageConfig, "erofs_sparse_flag=-s")
 }
 
+func TestF2fsPartition(t *testing.T) {
+	result := fixture.RunTestWithBp(t, `
+		android_filesystem {
+			name: "f2fs_partition",
+			type: "f2fs",
+		}
+	`)
+
+	partition := result.ModuleForTests("f2fs_partition", "android_common")
+	buildImageConfig := android.ContentFromFileRuleForTests(t, result.TestContext, partition.Output("prop"))
+	android.AssertStringDoesContain(t, "f2fs fs type", buildImageConfig, "fs_type=f2fs")
+	android.AssertStringDoesContain(t, "f2fs fs type sparse", buildImageConfig, "f2fs_sparse_flag=-S")
+}
+
+func TestFsTypesPropertyError(t *testing.T) {
+	fixture.ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(
+		"erofs: erofs is non-empty, but FS type is f2fs\n. Please delete erofs properties if this partition should use f2fs\n")).
+		RunTestWithBp(t, `
+		android_filesystem {
+			name: "f2fs_partition",
+			type: "f2fs",
+			erofs: {
+				compressor: "lz4hc,9",
+				compress_hints: "compress_hints.txt",
+			},
+		}
+	`)
+}
+
 // If a system_ext/ module depends on system/ module, the dependency should *not*
 // be installed in system_ext/
 func TestDoNotPackageCrossPartitionDependencies(t *testing.T) {
@@ -664,3 +703,54 @@
 	fileList := android.ContentFromFileRuleForTests(t, result.TestContext, partition.Output("fileList"))
 	android.AssertDeepEquals(t, "Shared library dep of overridden binary should not be installed", fileList, "bin/binfoo1\nlib64/libc++.so\nlib64/libc.so\nlib64/libdl.so\nlib64/libfoo2.so\nlib64/libm.so\n")
 }
+
+func TestInstallLinkerConfigFile(t *testing.T) {
+	result := fixture.RunTestWithBp(t, `
+android_filesystem {
+    name: "myfilesystem",
+    deps: ["libfoo_has_no_stubs", "libfoo_has_stubs"],
+    linker_config: {
+        gen_linker_config: true,
+        linker_config_srcs: ["linker.config.json"],
+    },
+    partition_type: "vendor",
+}
+cc_library {
+    name: "libfoo_has_no_stubs",
+    vendor: true,
+}
+cc_library {
+    name: "libfoo_has_stubs",
+    stubs: {symbol_file: "libfoo.map.txt"},
+    vendor: true,
+}
+	`)
+
+	linkerConfigCmd := result.ModuleForTests("myfilesystem", "android_common").Rule("build_filesystem_image").RuleParams.Command
+	android.AssertStringDoesContain(t, "Could not find linker.config.json file in cmd", linkerConfigCmd, "conv_linker_config proto --force -s linker.config.json")
+	android.AssertStringDoesContain(t, "Could not find stub in `provideLibs`", linkerConfigCmd, "--key provideLibs --value libfoo_has_stubs.so")
+}
+
+// override_android_* modules implicitly override their base module.
+// If both of these are listed in `deps`, the base module should not be installed.
+func TestOverrideModulesInDeps(t *testing.T) {
+	result := fixture.RunTestWithBp(t, `
+		android_filesystem {
+			name: "myfilesystem",
+			deps: ["myapp", "myoverrideapp"],
+		}
+
+		android_app {
+			name: "myapp",
+			platform_apis: true,
+		}
+		override_android_app {
+			name: "myoverrideapp",
+			base: "myapp",
+		}
+	`)
+
+	partition := result.ModuleForTests("myfilesystem", "android_common")
+	fileList := android.ContentFromFileRuleForTests(t, result.TestContext, partition.Output("fileList"))
+	android.AssertStringEquals(t, "filesystem with override app", "app/myoverrideapp/myoverrideapp.apk\n", fileList)
+}
diff --git a/filesystem/system_image.go b/filesystem/system_image.go
index 7dbf986..d03eab4 100644
--- a/filesystem/system_image.go
+++ b/filesystem/system_image.go
@@ -17,27 +17,24 @@
 import (
 	"android/soong/android"
 	"android/soong/linkerconfig"
+
+	"strings"
+
+	"github.com/google/blueprint/proptools"
 )
 
 type systemImage struct {
 	filesystem
-
-	properties systemImageProperties
 }
 
-type systemImageProperties struct {
-	// Path to the input linker config json file.
-	Linker_config_src *string `android:"path"`
-}
+var _ filesystemBuilder = (*systemImage)(nil)
 
 // android_system_image is a specialization of android_filesystem for the 'system' partition.
 // Currently, the only difference is the inclusion of linker.config.pb file which specifies
 // the provided and the required libraries to and from APEXes.
 func SystemImageFactory() android.Module {
 	module := &systemImage{}
-	module.AddProperties(&module.properties)
-	module.filesystem.buildExtraFiles = module.buildExtraFiles
-	module.filesystem.filterPackagingSpec = module.filterPackagingSpec
+	module.filesystemBuilder = module
 	initFilesystemModule(module, &module.filesystem)
 	return module
 }
@@ -46,63 +43,23 @@
 	return s.filesystem.properties
 }
 
-func (s *systemImage) buildExtraFiles(ctx android.ModuleContext, root android.OutputPath) android.OutputPaths {
-	if s.filesystem.properties.Partition_type != nil {
-		ctx.PropertyErrorf("partition_type", "partition_type must be unset on an android_system_image module. It is assumed to be 'system'.")
-	}
-	lc := s.buildLinkerConfigFile(ctx, root)
-	// Add more files if needed
-	return []android.OutputPath{lc}
-}
-
-func (s *systemImage) buildLinkerConfigFile(ctx android.ModuleContext, root android.OutputPath) android.OutputPath {
-	input := android.PathForModuleSrc(ctx, android.String(s.properties.Linker_config_src))
-	output := root.Join(ctx, "system", "etc", "linker.config.pb")
-
-	// we need "Module"s for packaging items
-	modulesInPackageByModule := make(map[android.Module]bool)
-	modulesInPackageByName := make(map[string]bool)
-
-	deps := s.gatherFilteredPackagingSpecs(ctx)
-	ctx.WalkDeps(func(child, parent android.Module) bool {
-		for _, ps := range android.OtherModuleProviderOrDefault(
-			ctx, child, android.InstallFilesProvider).PackagingSpecs {
-			if _, ok := deps[ps.RelPathInPackage()]; ok {
-				modulesInPackageByModule[child] = true
-				modulesInPackageByName[child.Name()] = true
-				return true
-			}
-		}
-		return true
-	})
-
-	provideModules := make([]android.Module, 0, len(modulesInPackageByModule))
-	for mod := range modulesInPackageByModule {
-		provideModules = append(provideModules, mod)
+func (s *systemImage) BuildLinkerConfigFile(ctx android.ModuleContext, builder *android.RuleBuilder, rebasedDir android.OutputPath) {
+	if !proptools.Bool(s.filesystem.properties.Linker_config.Gen_linker_config) {
+		return
 	}
 
-	var requireModules []android.Module
-	ctx.WalkDeps(func(child, parent android.Module) bool {
-		_, parentInPackage := modulesInPackageByModule[parent]
-		_, childInPackageName := modulesInPackageByName[child.Name()]
+	provideModules, requireModules := s.getLibsForLinkerConfig(ctx)
+	output := rebasedDir.Join(ctx, "etc", "linker.config.pb")
+	linkerconfig.BuildLinkerConfig(ctx, builder, android.PathsForModuleSrc(ctx, s.filesystem.properties.Linker_config.Linker_config_srcs), provideModules, requireModules, output)
 
-		// When parent is in the package, and child (or its variant) is not, this can be from an interface.
-		if parentInPackage && !childInPackageName {
-			requireModules = append(requireModules, child)
-		}
-		return true
-	})
-
-	builder := android.NewRuleBuilder(pctx, ctx)
-	linkerconfig.BuildLinkerConfig(ctx, builder, input, provideModules, requireModules, output)
-	builder.Build("conv_linker_config", "Generate linker config protobuf "+output.String())
-	return output
+	s.appendToEntry(ctx, output)
 }
 
 // Filter the result of GatherPackagingSpecs to discard items targeting outside "system" / "root"
 // partition.  Note that "apex" module installs its contents to "apex"(fake partition) as well
 // for symbol lookup by imitating "activated" paths.
-func (s *systemImage) filterPackagingSpec(ps android.PackagingSpec) bool {
+func (s *systemImage) FilterPackagingSpec(ps android.PackagingSpec) bool {
 	return !ps.SkipInstall() &&
-		(ps.Partition() == "system" || ps.Partition() == "root")
+		(ps.Partition() == "system" || ps.Partition() == "root" ||
+			strings.HasPrefix(ps.Partition(), "system/"))
 }
diff --git a/filesystem/vbmeta.go b/filesystem/vbmeta.go
index 0bae479..ebb3ff9 100644
--- a/filesystem/vbmeta.go
+++ b/filesystem/vbmeta.go
@@ -25,19 +25,30 @@
 )
 
 func init() {
-	android.RegisterModuleType("vbmeta", vbmetaFactory)
+	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
+	properties VbmetaProperties
 
 	output     android.OutputPath
 	installDir android.InstallPath
 }
 
-type vbmetaProperties struct {
+type VbmetaProperties struct {
 	// Name of the partition stored in vbmeta desc. Defaults to the name of this module.
 	Partition_name *string
 
@@ -50,9 +61,8 @@
 	// Algorithm that avbtool will use to sign this vbmeta image. Default is SHA256_RSA4096.
 	Algorithm *string
 
-	// File whose content will provide the rollback index. If unspecified, the rollback index
-	// is from PLATFORM_SECURITY_PATCH
-	Rollback_index_file *string `android:"path"`
+	// The rollback index. If unspecified, the rollback index is from PLATFORM_SECURITY_PATCH
+	Rollback_index *int64
 
 	// Rollback index location of this vbmeta image. Must be 0, 1, 2, etc. Default is 0.
 	Rollback_index_location *int64
@@ -61,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
@@ -76,7 +93,7 @@
 	Value *string
 }
 
-type chainedPartitionProperties struct {
+type ChainedPartitionProperties struct {
 	// Name of the chained partition
 	Name *string
 
@@ -94,8 +111,22 @@
 	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 {
+func VbmetaFactory() android.Module {
 	module := &vbmeta{}
 	module.AddProperties(&module.properties)
 	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
@@ -104,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 {
@@ -125,8 +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)
@@ -176,25 +210,66 @@
 		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)
 	}
@@ -212,57 +287,30 @@
 	v.installDir = android.PathForModuleInstall(ctx, "etc")
 	ctx.InstallFile(v.installDir, v.installFileName(), 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{v.output}, "")
 }
 
 // Returns the embedded shell command that prints the rollback index
 func (v *vbmeta) rollbackIndexCommand(ctx android.ModuleContext) string {
-	var cmd string
-	if v.properties.Rollback_index_file != nil {
-		f := android.PathForModuleSrc(ctx, proptools.String(v.properties.Rollback_index_file))
-		cmd = "cat " + f.String()
+	if v.properties.Rollback_index != nil {
+		return fmt.Sprintf("%d", *v.properties.Rollback_index)
 	} else {
-		cmd = "date -d 'TZ=\"GMT\" " + ctx.Config().PlatformSecurityPatch() + "' +%s"
+		// Take the first line and remove the newline char
+		return "$(date -d 'TZ=\"GMT\" " + ctx.Config().PlatformSecurityPatch() + "' +%s | head -1 | tr -d '\n'" + ")"
 	}
-	// Take the first line and remove the newline char
-	return "$(" + cmd + " | head -1 | tr -d '\n'" + ")"
-}
-
-// 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)
diff --git a/fsgen/Android.bp b/fsgen/Android.bp
index 9fa9557..8cd7518 100644
--- a/fsgen/Android.bp
+++ b/fsgen/Android.bp
@@ -10,14 +10,19 @@
         "soong",
         "soong-android",
         "soong-filesystem",
+        "soong-kernel",
     ],
     srcs: [
         "filesystem_creator.go",
+        "fsgen_mutators.go",
+        "prebuilt_etc_modules_gen.go",
+        "vbmeta_partitions.go",
     ],
     testSrcs: [
         "filesystem_creator_test.go",
     ],
     pluginFor: ["soong_build"],
+    visibility: ["//visibility:public"],
 }
 
 soong_filesystem_creator {
diff --git a/fsgen/filesystem_creator.go b/fsgen/filesystem_creator.go
index 766176d..0a65c6c 100644
--- a/fsgen/filesystem_creator.go
+++ b/fsgen/filesystem_creator.go
@@ -17,13 +17,13 @@
 import (
 	"crypto/sha256"
 	"fmt"
-	"slices"
+	"path/filepath"
 	"strconv"
 	"strings"
-	"sync"
 
 	"android/soong/android"
 	"android/soong/filesystem"
+	"android/soong/kernel"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/parser"
@@ -41,315 +41,12 @@
 	ctx.PreDepsMutators(RegisterCollectFileSystemDepsMutators)
 }
 
-func RegisterCollectFileSystemDepsMutators(ctx android.RegisterMutatorsContext) {
-	ctx.BottomUp("fs_collect_deps", collectDepsMutator).MutatesGlobalState()
-	ctx.BottomUp("fs_set_deps", setDepsMutator)
-}
-
-var fsGenStateOnceKey = android.NewOnceKey("FsGenState")
-var fsGenRemoveOverridesOnceKey = android.NewOnceKey("FsGenRemoveOverrides")
-
-// Map of partition module name to its partition that may be generated by Soong.
-// Note that it is not guaranteed that all modules returned by this function are successfully
-// created.
-func getAllSoongGeneratedPartitionNames(config android.Config, partitions []string) map[string]string {
-	ret := map[string]string{}
-	for _, partition := range partitions {
-		ret[generatedModuleNameForPartition(config, partition)] = partition
-	}
-	return ret
-}
-
-type depCandidateProps struct {
-	Namespace string
-	Multilib  string
-	Arch      []android.ArchType
-}
-
-// Map of module name to depCandidateProps
-type multilibDeps *map[string]*depCandidateProps
-
-// Information necessary to generate the filesystem modules, including details about their
-// dependencies
-type FsGenState struct {
-	// List of modules in `PRODUCT_PACKAGES` and `PRODUCT_PACKAGES_DEBUG`
-	depCandidates []string
-	// Map of names of partition to the information of modules to be added as deps
-	fsDeps map[string]multilibDeps
-	// List of name of partitions to be generated by the filesystem_creator module
-	soongGeneratedPartitions []string
-	// Mutex to protect the fsDeps
-	fsDepsMutex sync.Mutex
-	// Map of _all_ soong module names to their corresponding installation properties
-	moduleToInstallationProps map[string]installationProperties
-}
-
-type installationProperties struct {
-	Required  []string
-	Overrides []string
-}
-
-func newMultilibDeps() multilibDeps {
-	return &map[string]*depCandidateProps{}
-}
-
-func defaultDepCandidateProps(config android.Config) *depCandidateProps {
-	return &depCandidateProps{
-		Namespace: ".",
-		Arch:      []android.ArchType{config.BuildArch},
-	}
-}
-
-func createFsGenState(ctx android.LoadHookContext) *FsGenState {
-	return ctx.Config().Once(fsGenStateOnceKey, func() interface{} {
-		partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
-		candidates := android.FirstUniqueStrings(android.Concat(partitionVars.ProductPackages, partitionVars.ProductPackagesDebug))
-
-		generatedPartitions := []string{"system"}
-		if ctx.DeviceConfig().SystemExtPath() == "system_ext" {
-			generatedPartitions = append(generatedPartitions, "system_ext")
-		}
-		if ctx.DeviceConfig().BuildingVendorImage() && ctx.DeviceConfig().VendorPath() == "vendor" {
-			generatedPartitions = append(generatedPartitions, "vendor")
-		}
-		if ctx.DeviceConfig().BuildingProductImage() && ctx.DeviceConfig().ProductPath() == "product" {
-			generatedPartitions = append(generatedPartitions, "product")
-		}
-
-		return &FsGenState{
-			depCandidates: candidates,
-			fsDeps: map[string]multilibDeps{
-				// These additional deps are added according to the cuttlefish system image bp.
-				"system": &map[string]*depCandidateProps{
-					"com.android.apex.cts.shim.v1_prebuilt":     defaultDepCandidateProps(ctx.Config()),
-					"dex_bootjars":                              defaultDepCandidateProps(ctx.Config()),
-					"framework_compatibility_matrix.device.xml": defaultDepCandidateProps(ctx.Config()),
-					"idc_data":                     defaultDepCandidateProps(ctx.Config()),
-					"init.environ.rc-soong":        defaultDepCandidateProps(ctx.Config()),
-					"keychars_data":                defaultDepCandidateProps(ctx.Config()),
-					"keylayout_data":               defaultDepCandidateProps(ctx.Config()),
-					"libclang_rt.asan":             defaultDepCandidateProps(ctx.Config()),
-					"libcompiler_rt":               defaultDepCandidateProps(ctx.Config()),
-					"libdmabufheap":                defaultDepCandidateProps(ctx.Config()),
-					"libgsi":                       defaultDepCandidateProps(ctx.Config()),
-					"llndk.libraries.txt":          defaultDepCandidateProps(ctx.Config()),
-					"logpersist.start":             defaultDepCandidateProps(ctx.Config()),
-					"preloaded-classes":            defaultDepCandidateProps(ctx.Config()),
-					"public.libraries.android.txt": defaultDepCandidateProps(ctx.Config()),
-					"update_engine_sideload":       defaultDepCandidateProps(ctx.Config()),
-				},
-				"vendor": &map[string]*depCandidateProps{
-					"fs_config_files_vendor":                               defaultDepCandidateProps(ctx.Config()),
-					"fs_config_dirs_vendor":                                defaultDepCandidateProps(ctx.Config()),
-					generatedModuleName(ctx.Config(), "vendor-build.prop"): defaultDepCandidateProps(ctx.Config()),
-				},
-				"odm":     newMultilibDeps(),
-				"product": newMultilibDeps(),
-				"system_ext": &map[string]*depCandidateProps{
-					// VNDK apexes are automatically included.
-					// This hardcoded list will need to be updated if `PRODUCT_EXTRA_VNDK_VERSIONS` is updated.
-					// https://cs.android.com/android/_/android/platform/build/+/adba533072b00c53ac0f198c550a3cbd7a00e4cd:core/main.mk;l=984;bpv=1;bpt=0;drc=174db7b179592cf07cbfd2adb0119486fda911e7
-					"com.android.vndk.v30": defaultDepCandidateProps(ctx.Config()),
-					"com.android.vndk.v31": defaultDepCandidateProps(ctx.Config()),
-					"com.android.vndk.v32": defaultDepCandidateProps(ctx.Config()),
-					"com.android.vndk.v33": defaultDepCandidateProps(ctx.Config()),
-					"com.android.vndk.v34": defaultDepCandidateProps(ctx.Config()),
-				},
-			},
-			soongGeneratedPartitions:  generatedPartitions,
-			fsDepsMutex:               sync.Mutex{},
-			moduleToInstallationProps: map[string]installationProperties{},
-		}
-	}).(*FsGenState)
-}
-
-func checkDepModuleInMultipleNamespaces(mctx android.BottomUpMutatorContext, foundDeps map[string]*depCandidateProps, module string, partitionName string) {
-	otherNamespace := mctx.Namespace().Path
-	if val, found := foundDeps[module]; found && otherNamespace != "." && !android.InList(val.Namespace, []string{".", otherNamespace}) {
-		mctx.ModuleErrorf("found in multiple namespaces(%s and %s) when including in %s partition", val.Namespace, otherNamespace, partitionName)
-	}
-}
-
-func appendDepIfAppropriate(mctx android.BottomUpMutatorContext, deps *map[string]*depCandidateProps, installPartition string) {
-	checkDepModuleInMultipleNamespaces(mctx, *deps, mctx.Module().Name(), installPartition)
-	if _, ok := (*deps)[mctx.Module().Name()]; ok {
-		// Prefer the namespace-specific module over the platform module
-		if mctx.Namespace().Path != "." {
-			(*deps)[mctx.Module().Name()].Namespace = mctx.Namespace().Path
-		}
-		(*deps)[mctx.Module().Name()].Arch = append((*deps)[mctx.Module().Name()].Arch, mctx.Module().Target().Arch.ArchType)
-	} else {
-		multilib, _ := mctx.Module().DecodeMultilib(mctx)
-		(*deps)[mctx.Module().Name()] = &depCandidateProps{
-			Namespace: mctx.Namespace().Path,
-			Multilib:  multilib,
-			Arch:      []android.ArchType{mctx.Module().Target().Arch.ArchType},
-		}
-	}
-}
-
-func collectDepsMutator(mctx android.BottomUpMutatorContext) {
-	fsGenState := mctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
-
-	m := mctx.Module()
-	if m.Target().Os.Class == android.Device && slices.Contains(fsGenState.depCandidates, m.Name()) {
-		installPartition := m.PartitionTag(mctx.DeviceConfig())
-		fsGenState.fsDepsMutex.Lock()
-		// Only add the module as dependency when:
-		// - its enabled
-		// - its namespace is included in PRODUCT_SOONG_NAMESPACES
-		if m.Enabled(mctx) && m.ExportedToMake() {
-			appendDepIfAppropriate(mctx, fsGenState.fsDeps[installPartition], installPartition)
-		}
-		fsGenState.fsDepsMutex.Unlock()
-	}
-	// store the map of module to (required,overrides) even if the module is not in PRODUCT_PACKAGES.
-	// the module might be installed transitively.
-	if m.Target().Os.Class == android.Device && m.Enabled(mctx) && m.ExportedToMake() {
-		fsGenState.fsDepsMutex.Lock()
-		fsGenState.moduleToInstallationProps[m.Name()] = installationProperties{
-			Required:  m.RequiredModuleNames(mctx),
-			Overrides: m.Overrides(),
-		}
-		fsGenState.fsDepsMutex.Unlock()
-	}
-}
-
-type depsStruct struct {
-	Deps []string
-}
-
-type multilibDepsStruct struct {
-	Common   depsStruct
-	Lib32    depsStruct
-	Lib64    depsStruct
-	Both     depsStruct
-	Prefer32 depsStruct
-}
-
-type packagingPropsStruct struct {
-	Deps     []string
-	Multilib multilibDepsStruct
-}
-
-func fullyQualifiedModuleName(moduleName, namespace string) string {
-	if namespace == "." {
-		return moduleName
-	}
-	return fmt.Sprintf("//%s:%s", namespace, moduleName)
-}
-
-func getBitness(archTypes []android.ArchType) (ret []string) {
-	for _, archType := range archTypes {
-		if archType.Multilib == "" {
-			ret = append(ret, android.COMMON_VARIANT)
-		} else {
-			ret = append(ret, archType.Bitness())
-		}
-	}
-	return ret
-}
-
-func setDepsMutator(mctx android.BottomUpMutatorContext) {
-	removeOverriddenDeps(mctx)
-	fsGenState := mctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
-	fsDeps := fsGenState.fsDeps
-	soongGeneratedPartitionMap := getAllSoongGeneratedPartitionNames(mctx.Config(), fsGenState.soongGeneratedPartitions)
-	m := mctx.Module()
-	if partition, ok := soongGeneratedPartitionMap[m.Name()]; ok {
-		depsStruct := generateDepStruct(*fsDeps[partition])
-		if err := proptools.AppendMatchingProperties(m.GetProperties(), depsStruct, nil); err != nil {
-			mctx.ModuleErrorf(err.Error())
-		}
-	}
-}
-
-// removeOverriddenDeps collects PRODUCT_PACKAGES and (transitive) required deps.
-// it then removes any modules which appear in `overrides` of the above list.
-func removeOverriddenDeps(mctx android.BottomUpMutatorContext) {
-	mctx.Config().Once(fsGenRemoveOverridesOnceKey, func() interface{} {
-		fsGenState := mctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
-		fsDeps := fsGenState.fsDeps
-		overridden := map[string]bool{}
-		allDeps := []string{}
-
-		// Step 1: Initialization: Append PRODUCT_PACKAGES to the queue
-		for _, fsDep := range fsDeps {
-			for depName, _ := range *fsDep {
-				allDeps = append(allDeps, depName)
-			}
-		}
-
-		// Step 2: Process the queue, and add required modules to the queue.
-		i := 0
-		for {
-			if i == len(allDeps) {
-				break
-			}
-			depName := allDeps[i]
-			for _, overrides := range fsGenState.moduleToInstallationProps[depName].Overrides {
-				overridden[overrides] = true
-			}
-			// add required dep to the queue.
-			allDeps = append(allDeps, fsGenState.moduleToInstallationProps[depName].Required...)
-			i += 1
-		}
-
-		// Step 3: Delete all the overridden modules.
-		for overridden, _ := range overridden {
-			for partition, _ := range fsDeps {
-				delete(*fsDeps[partition], overridden)
-			}
-		}
-		return nil
-	})
-}
-
-func generateDepStruct(deps map[string]*depCandidateProps) *packagingPropsStruct {
-	depsStruct := packagingPropsStruct{}
-	for depName, depProps := range deps {
-		bitness := getBitness(depProps.Arch)
-		fullyQualifiedDepName := fullyQualifiedModuleName(depName, depProps.Namespace)
-		if android.InList("32", bitness) && android.InList("64", bitness) {
-			// If both 32 and 64 bit variants are enabled for this module
-			switch depProps.Multilib {
-			case string(android.MultilibBoth):
-				depsStruct.Multilib.Both.Deps = append(depsStruct.Multilib.Both.Deps, fullyQualifiedDepName)
-			case string(android.MultilibCommon), string(android.MultilibFirst):
-				depsStruct.Deps = append(depsStruct.Deps, fullyQualifiedDepName)
-			case "32":
-				depsStruct.Multilib.Lib32.Deps = append(depsStruct.Multilib.Lib32.Deps, fullyQualifiedDepName)
-			case "64", "darwin_universal":
-				depsStruct.Multilib.Lib64.Deps = append(depsStruct.Multilib.Lib64.Deps, fullyQualifiedDepName)
-			case "prefer32", "first_prefer32":
-				depsStruct.Multilib.Prefer32.Deps = append(depsStruct.Multilib.Prefer32.Deps, fullyQualifiedDepName)
-			default:
-				depsStruct.Multilib.Both.Deps = append(depsStruct.Multilib.Both.Deps, fullyQualifiedDepName)
-			}
-		} else if android.InList("64", bitness) {
-			// If only 64 bit variant is enabled
-			depsStruct.Multilib.Lib64.Deps = append(depsStruct.Multilib.Lib64.Deps, fullyQualifiedDepName)
-		} else if android.InList("32", bitness) {
-			// If only 32 bit variant is enabled
-			depsStruct.Multilib.Lib32.Deps = append(depsStruct.Multilib.Lib32.Deps, fullyQualifiedDepName)
-		} else {
-			// If only common variant is enabled
-			depsStruct.Multilib.Common.Deps = append(depsStruct.Multilib.Common.Deps, fullyQualifiedDepName)
-		}
-	}
-	depsStruct.Deps = android.SortedUniqueStrings(depsStruct.Deps)
-	depsStruct.Multilib.Lib32.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Lib32.Deps)
-	depsStruct.Multilib.Lib64.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Lib64.Deps)
-	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)
-
-	return &depsStruct
-}
-
 type filesystemCreatorProps struct {
 	Generated_partition_types   []string `blueprint:"mutated"`
 	Unsupported_partition_types []string `blueprint:"mutated"`
+
+	Vbmeta_module_names    []string `blueprint:"mutated"`
+	Vbmeta_partition_names []string `blueprint:"mutated"`
 }
 
 type filesystemCreator struct {
@@ -364,7 +61,8 @@
 	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	module.AddProperties(&module.properties)
 	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
-		createFsGenState(ctx)
+		generatedPrebuiltEtcModuleNames := createPrebuiltEtcModules(ctx)
+		createFsGenState(ctx, generatedPrebuiltEtcModuleNames)
 		module.createInternalModules(ctx)
 	})
 
@@ -372,16 +70,24 @@
 }
 
 func (f *filesystemCreator) createInternalModules(ctx android.LoadHookContext) {
-	soongGeneratedPartitions := &ctx.Config().Get(fsGenStateOnceKey).(*FsGenState).soongGeneratedPartitions
-	for _, partitionType := range *soongGeneratedPartitions {
+	soongGeneratedPartitions := generatedPartitions(ctx)
+	finalSoongGeneratedPartitions := make([]string, 0, len(soongGeneratedPartitions))
+	for _, partitionType := range soongGeneratedPartitions {
 		if f.createPartition(ctx, partitionType) {
 			f.properties.Generated_partition_types = append(f.properties.Generated_partition_types, partitionType)
+			finalSoongGeneratedPartitions = append(finalSoongGeneratedPartitions, partitionType)
 		} else {
 			f.properties.Unsupported_partition_types = append(f.properties.Unsupported_partition_types, partitionType)
-			_, *soongGeneratedPartitions = android.RemoveFromList(partitionType, *soongGeneratedPartitions)
 		}
 	}
-	f.createDeviceModule(ctx)
+
+	for _, x := range createVbmetaPartitions(ctx, finalSoongGeneratedPartitions) {
+		f.properties.Vbmeta_module_names = append(f.properties.Vbmeta_module_names, x.moduleName)
+		f.properties.Vbmeta_partition_names = append(f.properties.Vbmeta_partition_names, x.partitionName)
+	}
+
+	ctx.Config().Get(fsGenStateOnceKey).(*FsGenState).soongGeneratedPartitions = finalSoongGeneratedPartitions
+	f.createDeviceModule(ctx, finalSoongGeneratedPartitions, f.properties.Vbmeta_module_names)
 }
 
 func generatedModuleName(cfg android.Config, suffix string) string {
@@ -396,7 +102,11 @@
 	return generatedModuleName(cfg, fmt.Sprintf("%s_image", partitionType))
 }
 
-func (f *filesystemCreator) createDeviceModule(ctx android.LoadHookContext) {
+func (f *filesystemCreator) createDeviceModule(
+	ctx android.LoadHookContext,
+	generatedPartitionTypes []string,
+	vbmetaPartitions []string,
+) {
 	baseProps := &struct {
 		Name *string
 	}{
@@ -405,18 +115,25 @@
 
 	// Currently, only the system and system_ext partition module is created.
 	partitionProps := &filesystem.PartitionNameProperties{}
-	if android.InList("system", f.properties.Generated_partition_types) {
+	if android.InList("system", generatedPartitionTypes) {
 		partitionProps.System_partition_name = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "system"))
 	}
-	if android.InList("system_ext", f.properties.Generated_partition_types) {
+	if android.InList("system_ext", generatedPartitionTypes) {
 		partitionProps.System_ext_partition_name = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "system_ext"))
 	}
-	if android.InList("vendor", f.properties.Generated_partition_types) {
+	if android.InList("vendor", generatedPartitionTypes) {
 		partitionProps.Vendor_partition_name = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "vendor"))
 	}
-	if android.InList("product", f.properties.Generated_partition_types) {
+	if android.InList("product", generatedPartitionTypes) {
 		partitionProps.Product_partition_name = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "product"))
 	}
+	if android.InList("odm", generatedPartitionTypes) {
+		partitionProps.Odm_partition_name = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "odm"))
+	}
+	if android.InList("userdata", f.properties.Generated_partition_types) {
+		partitionProps.Userdata_partition_name = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "userdata"))
+	}
+	partitionProps.Vbmeta_partitions = vbmetaPartitions
 
 	ctx.CreateModule(filesystem.AndroidDeviceFactory, baseProps, partitionProps)
 }
@@ -438,6 +155,26 @@
 			"framework/oat/*/*", // framework/oat/{arch}
 		}
 		fsProps.Fsverity.Libs = []string{":framework-res{.export-package.apk}"}
+		// TODO(b/377734331): only generate the symlinks if the relevant partitions exist
+		fsProps.Symlinks = []filesystem.SymlinkDefinition{
+			filesystem.SymlinkDefinition{
+				Target: proptools.StringPtr("/product"),
+				Name:   proptools.StringPtr("system/product"),
+			},
+			filesystem.SymlinkDefinition{
+				Target: proptools.StringPtr("/system_ext"),
+				Name:   proptools.StringPtr("system/system_ext"),
+			},
+			filesystem.SymlinkDefinition{
+				Target: proptools.StringPtr("/vendor"),
+				Name:   proptools.StringPtr("system/vendor"),
+			},
+			filesystem.SymlinkDefinition{
+				Target: proptools.StringPtr("/system_dlkm/lib/modules"),
+				Name:   proptools.StringPtr("system/lib/modules"),
+			},
+		}
+		fsProps.Base_dir = proptools.StringPtr("system")
 	case "system_ext":
 		fsProps.Fsverity.Inputs = []string{
 			"framework/*",
@@ -460,9 +197,28 @@
 			},
 		}
 		fsProps.Base_dir = proptools.StringPtr("vendor")
+	case "odm":
+		fsProps.Symlinks = []filesystem.SymlinkDefinition{
+			filesystem.SymlinkDefinition{
+				Target: proptools.StringPtr("/odm_dlkm/lib/modules"),
+				Name:   proptools.StringPtr("odm/lib/modules"),
+			},
+		}
+		fsProps.Base_dir = proptools.StringPtr("odm")
+	case "userdata":
+		fsProps.Base_dir = proptools.StringPtr("data")
+
 	}
 }
 
+var (
+	dlkmPartitions = []string{
+		"system_dlkm",
+		"vendor_dlkm",
+		"odm_dlkm",
+	}
+)
+
 // Creates a soong module to build the given partition. Returns false if we can't support building
 // it.
 func (f *filesystemCreator) createPartition(ctx android.LoadHookContext, partitionType string) bool {
@@ -473,6 +229,15 @@
 		return false
 	}
 
+	if partitionType == "vendor" || partitionType == "product" {
+		fsProps.Linker_config.Gen_linker_config = proptools.BoolPtr(true)
+		fsProps.Linker_config.Linker_config_srcs = f.createLinkerConfigSourceFilegroups(ctx, partitionType)
+	}
+
+	if android.InList(partitionType, dlkmPartitions) {
+		f.createPrebuiltKernelModules(ctx, partitionType)
+	}
+
 	var module android.Module
 	if partitionType == "system" {
 		module = ctx.CreateModule(filesystem.SystemImageFactory, baseProps, fsProps)
@@ -483,36 +248,172 @@
 	}
 	module.HideFromMake()
 	if partitionType == "vendor" {
-		// Create a build prop for vendor
-		vendorBuildProps := &struct {
-			Name           *string
-			Vendor         *bool
-			Stem           *string
-			Product_config *string
-		}{
-			Name:           proptools.StringPtr(generatedModuleName(ctx.Config(), "vendor-build.prop")),
-			Vendor:         proptools.BoolPtr(true),
-			Stem:           proptools.StringPtr("build.prop"),
-			Product_config: proptools.StringPtr(":product_config"),
-		}
-		vendorBuildProp := ctx.CreateModule(
-			android.BuildPropFactory,
-			vendorBuildProps,
-		)
-		vendorBuildProp.HideFromMake()
+		f.createVendorBuildProp(ctx)
 	}
 	return true
 }
 
+// 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.
+func (f *filesystemCreator) createPrebuiltKernelModules(ctx android.LoadHookContext, partitionType string) {
+	fsGenState := ctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
+	name := generatedModuleName(ctx.Config(), fmt.Sprintf("%s-kernel-modules", partitionType))
+	props := &struct {
+		Name                 *string
+		Srcs                 []string
+		System_deps          []string
+		System_dlkm_specific *bool
+		Vendor_dlkm_specific *bool
+		Odm_dlkm_specific    *bool
+		Load_by_default      *bool
+		Blocklist_file       *string
+	}{
+		Name: proptools.StringPtr(name),
+	}
+	switch partitionType {
+	case "system_dlkm":
+		props.Srcs = ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.SystemKernelModules
+		props.System_dlkm_specific = proptools.BoolPtr(true)
+		if len(ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.SystemKernelLoadModules) == 0 {
+			// Create empty modules.load file for system
+			// https://source.corp.google.com/h/googleplex-android/platform/build/+/ef55daac9954896161b26db4f3ef1781b5a5694c:core/Makefile;l=695-700;drc=549fe2a5162548bd8b47867d35f907eb22332023;bpv=1;bpt=0
+			props.Load_by_default = proptools.BoolPtr(false)
+		}
+		if blocklistFile := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.SystemKernelBlocklistFile; blocklistFile != "" {
+			props.Blocklist_file = proptools.StringPtr(blocklistFile)
+		}
+	case "vendor_dlkm":
+		props.Srcs = ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.VendorKernelModules
+		if len(ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.SystemKernelModules) > 0 {
+			props.System_deps = []string{":" + generatedModuleName(ctx.Config(), "system_dlkm-kernel-modules") + "{.modules}"}
+		}
+		props.Vendor_dlkm_specific = proptools.BoolPtr(true)
+		if blocklistFile := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.VendorKernelBlocklistFile; blocklistFile != "" {
+			props.Blocklist_file = proptools.StringPtr(blocklistFile)
+		}
+	case "odm_dlkm":
+		props.Srcs = ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.OdmKernelModules
+		props.Odm_dlkm_specific = proptools.BoolPtr(true)
+		if blocklistFile := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.OdmKernelBlocklistFile; blocklistFile != "" {
+			props.Blocklist_file = proptools.StringPtr(blocklistFile)
+		}
+	default:
+		ctx.ModuleErrorf("DLKM is not supported for %s\n", partitionType)
+	}
+
+	if len(props.Srcs) == 0 {
+		return // do not generate `prebuilt_kernel_modules` if there are no sources
+	}
+
+	kernelModule := ctx.CreateModuleInDirectory(
+		kernel.PrebuiltKernelModulesFactory,
+		".", // create in root directory for now
+		props,
+	)
+	kernelModule.HideFromMake()
+	// Add to deps
+	(*fsGenState.fsDeps[partitionType])[name] = defaultDepCandidateProps(ctx.Config())
+}
+
+// Create a build_prop and android_info module. This will be used to create /vendor/build.prop
+func (f *filesystemCreator) createVendorBuildProp(ctx android.LoadHookContext) {
+	// Create a android_info for vendor
+	// The board info files might be in a directory outside the root soong namespace, so create
+	// the module in "."
+	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+	androidInfoProps := &struct {
+		Name                  *string
+		Board_info_files      []string
+		Bootloader_board_name *string
+	}{
+		Name:             proptools.StringPtr(generatedModuleName(ctx.Config(), "android-info.prop")),
+		Board_info_files: partitionVars.BoardInfoFiles,
+	}
+	if len(androidInfoProps.Board_info_files) == 0 {
+		androidInfoProps.Bootloader_board_name = proptools.StringPtr(partitionVars.BootLoaderBoardName)
+	}
+	androidInfoProp := ctx.CreateModuleInDirectory(
+		android.AndroidInfoFactory,
+		".",
+		androidInfoProps,
+	)
+	androidInfoProp.HideFromMake()
+	// Create a build prop for vendor
+	vendorBuildProps := &struct {
+		Name           *string
+		Vendor         *bool
+		Stem           *string
+		Product_config *string
+		Android_info   *string
+	}{
+		Name:           proptools.StringPtr(generatedModuleName(ctx.Config(), "vendor-build.prop")),
+		Vendor:         proptools.BoolPtr(true),
+		Stem:           proptools.StringPtr("build.prop"),
+		Product_config: proptools.StringPtr(":product_config"),
+		Android_info:   proptools.StringPtr(":" + androidInfoProp.Name()),
+	}
+	vendorBuildProp := ctx.CreateModule(
+		android.BuildPropFactory,
+		vendorBuildProps,
+	)
+	vendorBuildProp.HideFromMake()
+}
+
+// createLinkerConfigSourceFilegroups creates filegroup modules to generate linker.config.pb for the following partitions
+// 1. vendor: Using PRODUCT_VENDOR_LINKER_CONFIG_FRAGMENTS (space separated file list)
+// 1. product: Using PRODUCT_PRODUCT_LINKER_CONFIG_FRAGMENTS (space separated file list)
+// It creates a filegroup for each file in the fragment list
+// The filegroup modules are then added to `linker_config_srcs` of the autogenerated vendor `android_filesystem`.
+func (f *filesystemCreator) createLinkerConfigSourceFilegroups(ctx android.LoadHookContext, partitionType string) []string {
+	ret := []string{}
+	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+	var linkerConfigSrcs []string
+	if partitionType == "vendor" {
+		linkerConfigSrcs = android.FirstUniqueStrings(partitionVars.VendorLinkerConfigSrcs)
+	} else if partitionType == "product" {
+		linkerConfigSrcs = android.FirstUniqueStrings(partitionVars.ProductLinkerConfigSrcs)
+	} else {
+		ctx.ModuleErrorf("linker.config.pb is only supported for vendor and product partitions. For system partition, use `android_system_image`")
+	}
+
+	if len(linkerConfigSrcs) > 0 {
+		// Create a filegroup, and add `:<filegroup_name>` to ret.
+		for index, linkerConfigSrc := range linkerConfigSrcs {
+			dir := filepath.Dir(linkerConfigSrc)
+			base := filepath.Base(linkerConfigSrc)
+			fgName := generatedModuleName(ctx.Config(), fmt.Sprintf("%s-linker-config-src%s", partitionType, strconv.Itoa(index)))
+			srcs := []string{base}
+			fgProps := &struct {
+				Name *string
+				Srcs proptools.Configurable[[]string]
+			}{
+				Name: proptools.StringPtr(fgName),
+				Srcs: proptools.NewSimpleConfigurable(srcs),
+			}
+			ctx.CreateModuleInDirectory(
+				android.FileGroupFactory,
+				dir,
+				fgProps,
+			)
+			ret = append(ret, ":"+fgName)
+		}
+	}
+	return ret
+}
+
 type filesystemBaseProperty struct {
 	Name             *string
 	Compile_multilib *string
+	Visibility       []string
 }
 
 func generateBaseProps(namePtr *string) *filesystemBaseProperty {
 	return &filesystemBaseProperty{
 		Name:             namePtr,
 		Compile_multilib: proptools.StringPtr("both"),
+		// The vbmeta modules are currently in the root directory and depend on the partitions
+		Visibility: []string{"//.", "//build/soong:__subpackages__"},
 	}
 }
 
@@ -520,13 +421,20 @@
 	fsProps := &filesystem.FilesystemProperties{}
 
 	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
-	specificPartitionVars := partitionVars.PartitionQualifiedVariables[partitionType]
-
-	// BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE
-	fsType := specificPartitionVars.BoardFileSystemType
+	var specificPartitionVars android.PartitionQualifiedVariablesType
+	var boardAvbEnable bool
+	var fsType string
+	if strings.Contains(partitionType, "ramdisk") {
+		fsType = "compressed_cpio"
+	} else {
+		specificPartitionVars = partitionVars.PartitionQualifiedVariables[partitionType]
+		boardAvbEnable = partitionVars.BoardAvbEnable
+		fsType = specificPartitionVars.BoardFileSystemType
+	}
 	if fsType == "" {
 		fsType = "ext4" //default
 	}
+
 	fsProps.Type = proptools.StringPtr(fsType)
 	if filesystem.GetFsTypeFromString(ctx, *fsProps.Type).IsUnknown() {
 		// Currently the android_filesystem module type only supports a handful of FS types like ext4, erofs
@@ -538,7 +446,7 @@
 	fsProps.Unchecked_module = proptools.BoolPtr(true)
 
 	// BOARD_AVB_ENABLE
-	fsProps.Use_avb = proptools.BoolPtr(partitionVars.BoardAvbEnable)
+	fsProps.Use_avb = proptools.BoolPtr(boardAvbEnable)
 	// BOARD_AVB_KEY_PATH
 	fsProps.Avb_private_key = proptools.StringPtr(specificPartitionVars.BoardAvbKeyPath)
 	// BOARD_AVB_ALGORITHM
@@ -579,18 +487,13 @@
 		ctx.ModuleErrorf("Expected module %s to provide FileysystemInfo", partitionModuleName)
 	}
 	makeFileList := android.PathForArbitraryOutput(ctx, fmt.Sprintf("target/product/%s/obj/PACKAGING/%s_intermediates/file_list.txt", ctx.Config().DeviceName(), partitionType))
-	// For now, don't allowlist anything. The test will fail, but that's fine in the current
-	// early stages where we're just figuring out what we need
-	emptyAllowlistFile := android.PathForModuleOut(ctx, fmt.Sprintf("allowlist_%s.txt", partitionModuleName))
-	android.WriteFileRule(ctx, emptyAllowlistFile, "")
 	diffTestResultFile := android.PathForModuleOut(ctx, fmt.Sprintf("diff_test_%s.txt", partitionModuleName))
 
 	builder := android.NewRuleBuilder(pctx, ctx)
 	builder.Command().BuiltTool("file_list_diff").
 		Input(makeFileList).
 		Input(filesystemInfo.FileListFile).
-		Text(partitionModuleName).
-		FlagWithInput("--allowlists ", emptyAllowlistFile)
+		Text(partitionModuleName)
 	builder.Command().Text("touch").Output(diffTestResultFile)
 	builder.Build(partitionModuleName+" diff test", partitionModuleName+" diff test")
 	return diffTestResultFile
@@ -608,16 +511,42 @@
 	return file
 }
 
+func createVbmetaDiff(ctx android.ModuleContext, vbmetaModuleName string, vbmetaPartitionName string) android.Path {
+	vbmetaModule := ctx.GetDirectDepWithTag(vbmetaModuleName, generatedVbmetaPartitionDepTag)
+	outputFilesProvider, ok := android.OtherModuleProvider(ctx, vbmetaModule, android.OutputFilesProvider)
+	if !ok {
+		ctx.ModuleErrorf("Expected module %s to provide OutputFiles", vbmetaModule)
+	}
+	if len(outputFilesProvider.DefaultOutputFiles) != 1 {
+		ctx.ModuleErrorf("Expected 1 output file from module %s", vbmetaModule)
+	}
+	soongVbMetaFile := outputFilesProvider.DefaultOutputFiles[0]
+	makeVbmetaFile := android.PathForArbitraryOutput(ctx, fmt.Sprintf("target/product/%s/%s.img", ctx.Config().DeviceName(), vbmetaPartitionName))
+
+	diffTestResultFile := android.PathForModuleOut(ctx, fmt.Sprintf("diff_test_%s.txt", vbmetaModuleName))
+	builder := android.NewRuleBuilder(pctx, ctx)
+	builder.Command().Text("diff").
+		Input(soongVbMetaFile).
+		Input(makeVbmetaFile)
+	builder.Command().Text("touch").Output(diffTestResultFile)
+	builder.Build(vbmetaModuleName+" diff test", vbmetaModuleName+" diff test")
+	return diffTestResultFile
+}
+
 type systemImageDepTagType struct {
 	blueprint.BaseDependencyTag
 }
 
 var generatedFilesystemDepTag systemImageDepTagType
+var generatedVbmetaPartitionDepTag systemImageDepTagType
 
 func (f *filesystemCreator) DepsMutator(ctx android.BottomUpMutatorContext) {
 	for _, partitionType := range f.properties.Generated_partition_types {
 		ctx.AddDependency(ctx.Module(), generatedFilesystemDepTag, generatedModuleNameForPartition(ctx.Config(), partitionType))
 	}
+	for _, vbmetaModule := range f.properties.Vbmeta_module_names {
+		ctx.AddDependency(ctx.Module(), generatedVbmetaPartitionDepTag, vbmetaModule)
+	}
 }
 
 func (f *filesystemCreator) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -647,6 +576,11 @@
 		diffTestFiles = append(diffTestFiles, diffTestFile)
 		ctx.Phony(fmt.Sprintf("soong_generated_%s_filesystem_test", partitionType), diffTestFile)
 	}
+	for i, vbmetaModule := range f.properties.Vbmeta_module_names {
+		diffTestFile := createVbmetaDiff(ctx, vbmetaModule, f.properties.Vbmeta_partition_names[i])
+		diffTestFiles = append(diffTestFiles, diffTestFile)
+		ctx.Phony(fmt.Sprintf("soong_generated_%s_filesystem_test", f.properties.Vbmeta_partition_names[i]), diffTestFile)
+	}
 	ctx.Phony("soong_generated_filesystem_tests", diffTestFiles...)
 }
 
@@ -655,9 +589,6 @@
 	if !fsTypeSupported {
 		return ""
 	}
-	if partitionType == "vendor" {
-		return "" // TODO: Handle struct props
-	}
 
 	baseProps := generateBaseProps(proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), partitionType)))
 	deps := ctx.Config().Get(fsGenStateOnceKey).(*FsGenState).fsDeps[partitionType]
@@ -665,7 +596,8 @@
 
 	result, err := proptools.RepackProperties([]interface{}{baseProps, fsProps, depProps})
 	if err != nil {
-		ctx.ModuleErrorf(err.Error())
+		ctx.ModuleErrorf("%s", err.Error())
+		return ""
 	}
 
 	moduleType := "android_filesystem"
diff --git a/fsgen/filesystem_creator_test.go b/fsgen/filesystem_creator_test.go
index 199eaad..6111a41 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"
@@ -253,3 +254,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
new file mode 100644
index 0000000..e9fd513
--- /dev/null
+++ b/fsgen/fsgen_mutators.go
@@ -0,0 +1,382 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// 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 fsgen
+
+import (
+	"fmt"
+	"slices"
+	"strings"
+	"sync"
+
+	"android/soong/android"
+
+	"github.com/google/blueprint/proptools"
+)
+
+func RegisterCollectFileSystemDepsMutators(ctx android.RegisterMutatorsContext) {
+	ctx.BottomUp("fs_collect_deps", collectDepsMutator).MutatesGlobalState()
+	ctx.BottomUp("fs_set_deps", setDepsMutator)
+}
+
+var fsGenStateOnceKey = android.NewOnceKey("FsGenState")
+var fsGenRemoveOverridesOnceKey = android.NewOnceKey("FsGenRemoveOverrides")
+
+// Map of partition module name to its partition that may be generated by Soong.
+// Note that it is not guaranteed that all modules returned by this function are successfully
+// created.
+func getAllSoongGeneratedPartitionNames(config android.Config, partitions []string) map[string]string {
+	ret := map[string]string{}
+	for _, partition := range partitions {
+		ret[generatedModuleNameForPartition(config, partition)] = partition
+	}
+	return ret
+}
+
+type depCandidateProps struct {
+	Namespace string
+	Multilib  string
+	Arch      []android.ArchType
+}
+
+// Map of module name to depCandidateProps
+type multilibDeps map[string]*depCandidateProps
+
+// Information necessary to generate the filesystem modules, including details about their
+// dependencies
+type FsGenState struct {
+	// List of modules in `PRODUCT_PACKAGES` and `PRODUCT_PACKAGES_DEBUG`
+	depCandidates []string
+	// Map of names of partition to the information of modules to be added as deps
+	fsDeps map[string]*multilibDeps
+	// List of name of partitions to be generated by the filesystem_creator module
+	soongGeneratedPartitions []string
+	// Mutex to protect the fsDeps
+	fsDepsMutex sync.Mutex
+	// Map of _all_ soong module names to their corresponding installation properties
+	moduleToInstallationProps map[string]installationProperties
+}
+
+type installationProperties struct {
+	Required  []string
+	Overrides []string
+}
+
+func defaultDepCandidateProps(config android.Config) *depCandidateProps {
+	return &depCandidateProps{
+		Namespace: ".",
+		Arch:      []android.ArchType{config.BuildArch},
+	}
+}
+
+func generatedPartitions(ctx android.LoadHookContext) []string {
+	generatedPartitions := []string{"system", "ramdisk"}
+	if ctx.DeviceConfig().SystemExtPath() == "system_ext" {
+		generatedPartitions = append(generatedPartitions, "system_ext")
+	}
+	if ctx.DeviceConfig().BuildingVendorImage() && ctx.DeviceConfig().VendorPath() == "vendor" {
+		generatedPartitions = append(generatedPartitions, "vendor")
+	}
+	if ctx.DeviceConfig().BuildingProductImage() && ctx.DeviceConfig().ProductPath() == "product" {
+		generatedPartitions = append(generatedPartitions, "product")
+	}
+	if ctx.DeviceConfig().BuildingOdmImage() && ctx.DeviceConfig().OdmPath() == "odm" {
+		generatedPartitions = append(generatedPartitions, "odm")
+	}
+	if ctx.DeviceConfig().BuildingUserdataImage() && ctx.DeviceConfig().UserdataPath() == "data" {
+		generatedPartitions = append(generatedPartitions, "userdata")
+	}
+	if ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.BuildingSystemDlkmImage {
+		generatedPartitions = append(generatedPartitions, "system_dlkm")
+	}
+	if ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.BuildingVendorDlkmImage {
+		generatedPartitions = append(generatedPartitions, "vendor_dlkm")
+	}
+	if ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.BuildingOdmDlkmImage {
+		generatedPartitions = append(generatedPartitions, "odm_dlkm")
+	}
+
+	return generatedPartitions
+}
+
+func createFsGenState(ctx android.LoadHookContext, generatedPrebuiltEtcModuleNames []string) *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{
+			depCandidates: candidates,
+			fsDeps: map[string]*multilibDeps{
+				// These additional deps are added according to the cuttlefish system image bp.
+				"system": {
+					"com.android.apex.cts.shim.v1_prebuilt":     defaultDepCandidateProps(ctx.Config()),
+					"dex_bootjars":                              defaultDepCandidateProps(ctx.Config()),
+					"framework_compatibility_matrix.device.xml": defaultDepCandidateProps(ctx.Config()),
+					"init.environ.rc-soong":                     defaultDepCandidateProps(ctx.Config()),
+					"libcompiler_rt":                            defaultDepCandidateProps(ctx.Config()),
+					"libdmabufheap":                             defaultDepCandidateProps(ctx.Config()),
+					"libgsi":                                    defaultDepCandidateProps(ctx.Config()),
+					"llndk.libraries.txt":                       defaultDepCandidateProps(ctx.Config()),
+					"logpersist.start":                          defaultDepCandidateProps(ctx.Config()),
+					"update_engine_sideload":                    defaultDepCandidateProps(ctx.Config()),
+				},
+				"vendor": {
+					"fs_config_files_vendor":                               defaultDepCandidateProps(ctx.Config()),
+					"fs_config_dirs_vendor":                                defaultDepCandidateProps(ctx.Config()),
+					generatedModuleName(ctx.Config(), "vendor-build.prop"): defaultDepCandidateProps(ctx.Config()),
+				},
+				"odm": {
+					// fs_config_* files are automatically installed for all products with odm partitions.
+					// https://cs.android.com/android/_/android/platform/build/+/e4849e87ab660b59a6501b3928693db065ee873b:tools/fs_config/Android.mk;l=34;drc=8d6481b92c4b4e9b9f31a61545b6862090fcc14b;bpv=1;bpt=0
+					"fs_config_files_odm": defaultDepCandidateProps(ctx.Config()),
+					"fs_config_dirs_odm":  defaultDepCandidateProps(ctx.Config()),
+				},
+				"product": {},
+				"system_ext": {
+					// VNDK apexes are automatically included.
+					// This hardcoded list will need to be updated if `PRODUCT_EXTRA_VNDK_VERSIONS` is updated.
+					// https://cs.android.com/android/_/android/platform/build/+/adba533072b00c53ac0f198c550a3cbd7a00e4cd:core/main.mk;l=984;bpv=1;bpt=0;drc=174db7b179592cf07cbfd2adb0119486fda911e7
+					"com.android.vndk.v30": defaultDepCandidateProps(ctx.Config()),
+					"com.android.vndk.v31": defaultDepCandidateProps(ctx.Config()),
+					"com.android.vndk.v32": defaultDepCandidateProps(ctx.Config()),
+					"com.android.vndk.v33": defaultDepCandidateProps(ctx.Config()),
+					"com.android.vndk.v34": defaultDepCandidateProps(ctx.Config()),
+				},
+				"userdata": {},
+				"system_dlkm": {
+					// these are phony required deps of the phony fs_config_dirs_nonsystem
+					"fs_config_dirs_system_dlkm":  defaultDepCandidateProps(ctx.Config()),
+					"fs_config_files_system_dlkm": defaultDepCandidateProps(ctx.Config()),
+					// build props are automatically added to `ALL_DEFAULT_INSTALLED_MODULES`
+					"system_dlkm-build.prop": defaultDepCandidateProps(ctx.Config()),
+				},
+				"vendor_dlkm": {
+					"fs_config_dirs_vendor_dlkm":  defaultDepCandidateProps(ctx.Config()),
+					"fs_config_files_vendor_dlkm": defaultDepCandidateProps(ctx.Config()),
+					"vendor_dlkm-build.prop":      defaultDepCandidateProps(ctx.Config()),
+				},
+				"odm_dlkm": {
+					"fs_config_dirs_odm_dlkm":  defaultDepCandidateProps(ctx.Config()),
+					"fs_config_files_odm_dlkm": defaultDepCandidateProps(ctx.Config()),
+					"odm_dlkm-build.prop":      defaultDepCandidateProps(ctx.Config()),
+				},
+				"ramdisk": {},
+			},
+			fsDepsMutex:               sync.Mutex{},
+			moduleToInstallationProps: map[string]installationProperties{},
+		}
+	}).(*FsGenState)
+}
+
+func checkDepModuleInMultipleNamespaces(mctx android.BottomUpMutatorContext, foundDeps multilibDeps, module string, partitionName string) {
+	otherNamespace := mctx.Namespace().Path
+	if val, found := foundDeps[module]; found && otherNamespace != "." && !android.InList(val.Namespace, []string{".", otherNamespace}) {
+		mctx.ModuleErrorf("found in multiple namespaces(%s and %s) when including in %s partition", val.Namespace, otherNamespace, partitionName)
+	}
+}
+
+func appendDepIfAppropriate(mctx android.BottomUpMutatorContext, deps *multilibDeps, installPartition string) {
+	moduleName := mctx.ModuleName()
+	checkDepModuleInMultipleNamespaces(mctx, *deps, moduleName, installPartition)
+	if _, ok := (*deps)[moduleName]; ok {
+		// Prefer the namespace-specific module over the platform module
+		if mctx.Namespace().Path != "." {
+			(*deps)[moduleName].Namespace = mctx.Namespace().Path
+		}
+		(*deps)[moduleName].Arch = append((*deps)[moduleName].Arch, mctx.Module().Target().Arch.ArchType)
+	} else {
+		multilib, _ := mctx.Module().DecodeMultilib(mctx)
+		(*deps)[moduleName] = &depCandidateProps{
+			Namespace: mctx.Namespace().Path,
+			Multilib:  multilib,
+			Arch:      []android.ArchType{mctx.Module().Target().Arch.ArchType},
+		}
+	}
+}
+
+func collectDepsMutator(mctx android.BottomUpMutatorContext) {
+	m := mctx.Module()
+	if m.Target().Os.Class != android.Device {
+		return
+	}
+	fsGenState := mctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
+
+	fsGenState.fsDepsMutex.Lock()
+	defer fsGenState.fsDepsMutex.Unlock()
+
+	if slices.Contains(fsGenState.depCandidates, mctx.ModuleName()) {
+		installPartition := m.PartitionTag(mctx.DeviceConfig())
+		// Only add the module as dependency when:
+		// - its enabled
+		// - its namespace is included in PRODUCT_SOONG_NAMESPACES
+		if m.Enabled(mctx) && m.ExportedToMake() {
+			appendDepIfAppropriate(mctx, fsGenState.fsDeps[installPartition], installPartition)
+		}
+	}
+	// store the map of module to (required,overrides) even if the module is not in PRODUCT_PACKAGES.
+	// the module might be installed transitively.
+	if m.Enabled(mctx) && m.ExportedToMake() {
+		fsGenState.moduleToInstallationProps[m.Name()] = installationProperties{
+			Required:  m.RequiredModuleNames(mctx),
+			Overrides: m.Overrides(),
+		}
+	}
+}
+
+type depsStruct struct {
+	Deps []string
+}
+
+type multilibDepsStruct struct {
+	Common   depsStruct
+	Lib32    depsStruct
+	Lib64    depsStruct
+	Both     depsStruct
+	Prefer32 depsStruct
+}
+
+type packagingPropsStruct struct {
+	High_priority_deps []string
+	Deps               []string
+	Multilib           multilibDepsStruct
+}
+
+func fullyQualifiedModuleName(moduleName, namespace string) string {
+	if namespace == "." {
+		return moduleName
+	}
+	return fmt.Sprintf("//%s:%s", namespace, moduleName)
+}
+
+func getBitness(archTypes []android.ArchType) (ret []string) {
+	for _, archType := range archTypes {
+		if archType.Multilib == "" {
+			ret = append(ret, android.COMMON_VARIANT)
+		} else {
+			ret = append(ret, archType.Bitness())
+		}
+	}
+	return ret
+}
+
+func setDepsMutator(mctx android.BottomUpMutatorContext) {
+	removeOverriddenDeps(mctx)
+	fsGenState := mctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
+	fsDeps := fsGenState.fsDeps
+	soongGeneratedPartitionMap := getAllSoongGeneratedPartitionNames(mctx.Config(), fsGenState.soongGeneratedPartitions)
+	m := mctx.Module()
+	if partition, ok := soongGeneratedPartitionMap[m.Name()]; ok {
+		depsStruct := generateDepStruct(*fsDeps[partition])
+		if err := proptools.AppendMatchingProperties(m.GetProperties(), depsStruct, nil); err != nil {
+			mctx.ModuleErrorf(err.Error())
+		}
+	}
+}
+
+// removeOverriddenDeps collects PRODUCT_PACKAGES and (transitive) required deps.
+// it then removes any modules which appear in `overrides` of the above list.
+func removeOverriddenDeps(mctx android.BottomUpMutatorContext) {
+	mctx.Config().Once(fsGenRemoveOverridesOnceKey, func() interface{} {
+		fsGenState := mctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
+		fsDeps := fsGenState.fsDeps
+		overridden := map[string]bool{}
+		allDeps := []string{}
+
+		// Step 1: Initialization: Append PRODUCT_PACKAGES to the queue
+		for _, fsDep := range fsDeps {
+			for depName, _ := range *fsDep {
+				allDeps = append(allDeps, depName)
+			}
+		}
+
+		// Step 2: Process the queue, and add required modules to the queue.
+		i := 0
+		for {
+			if i == len(allDeps) {
+				break
+			}
+			depName := allDeps[i]
+			for _, overrides := range fsGenState.moduleToInstallationProps[depName].Overrides {
+				overridden[overrides] = true
+			}
+			// add required dep to the queue.
+			allDeps = append(allDeps, fsGenState.moduleToInstallationProps[depName].Required...)
+			i += 1
+		}
+
+		// Step 3: Delete all the overridden modules.
+		for overridden, _ := range overridden {
+			for partition, _ := range fsDeps {
+				delete(*fsDeps[partition], overridden)
+			}
+		}
+		return nil
+	})
+}
+
+var HighPriorityDeps = []string{}
+
+func isHighPriorityDep(depName string) bool {
+	for _, highPriorityDeps := range HighPriorityDeps {
+		if strings.HasPrefix(depName, highPriorityDeps) {
+			return true
+		}
+	}
+	return false
+}
+
+func generateDepStruct(deps map[string]*depCandidateProps) *packagingPropsStruct {
+	depsStruct := packagingPropsStruct{}
+	for depName, depProps := range deps {
+		bitness := getBitness(depProps.Arch)
+		fullyQualifiedDepName := fullyQualifiedModuleName(depName, depProps.Namespace)
+		if isHighPriorityDep(depName) {
+			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
+			switch depProps.Multilib {
+			case string(android.MultilibBoth):
+				depsStruct.Multilib.Both.Deps = append(depsStruct.Multilib.Both.Deps, fullyQualifiedDepName)
+			case string(android.MultilibCommon), string(android.MultilibFirst):
+				depsStruct.Deps = append(depsStruct.Deps, fullyQualifiedDepName)
+			case "32":
+				depsStruct.Multilib.Lib32.Deps = append(depsStruct.Multilib.Lib32.Deps, fullyQualifiedDepName)
+			case "64", "darwin_universal":
+				depsStruct.Multilib.Lib64.Deps = append(depsStruct.Multilib.Lib64.Deps, fullyQualifiedDepName)
+			case "prefer32", "first_prefer32":
+				depsStruct.Multilib.Prefer32.Deps = append(depsStruct.Multilib.Prefer32.Deps, fullyQualifiedDepName)
+			default:
+				depsStruct.Multilib.Both.Deps = append(depsStruct.Multilib.Both.Deps, fullyQualifiedDepName)
+			}
+		} else if android.InList("64", bitness) {
+			// If only 64 bit variant is enabled
+			depsStruct.Multilib.Lib64.Deps = append(depsStruct.Multilib.Lib64.Deps, fullyQualifiedDepName)
+		} else if android.InList("32", bitness) {
+			// If only 32 bit variant is enabled
+			depsStruct.Multilib.Lib32.Deps = append(depsStruct.Multilib.Lib32.Deps, fullyQualifiedDepName)
+		} else {
+			// If only common variant is enabled
+			depsStruct.Multilib.Common.Deps = append(depsStruct.Multilib.Common.Deps, fullyQualifiedDepName)
+		}
+	}
+	depsStruct.Deps = android.SortedUniqueStrings(depsStruct.Deps)
+	depsStruct.Multilib.Lib32.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Lib32.Deps)
+	depsStruct.Multilib.Lib64.Deps = android.SortedUniqueStrings(depsStruct.Multilib.Lib64.Deps)
+	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)
+
+	return &depsStruct
+}
diff --git a/fsgen/prebuilt_etc_modules_gen.go b/fsgen/prebuilt_etc_modules_gen.go
new file mode 100644
index 0000000..97d5482
--- /dev/null
+++ b/fsgen/prebuilt_etc_modules_gen.go
@@ -0,0 +1,352 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// 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 fsgen
+
+import (
+	"android/soong/android"
+	"android/soong/etc"
+	"fmt"
+	"path/filepath"
+	"strings"
+
+	"github.com/google/blueprint/proptools"
+)
+
+type srcBaseFileInstallBaseFileTuple struct {
+	srcBaseFile     string
+	installBaseFile string
+}
+
+// prebuilt src files grouped by the install partitions.
+// Each groups are a mapping of the relative install path to the name of the files
+type prebuiltSrcGroupByInstallPartition struct {
+	system     map[string][]srcBaseFileInstallBaseFileTuple
+	system_ext map[string][]srcBaseFileInstallBaseFileTuple
+	product    map[string][]srcBaseFileInstallBaseFileTuple
+	vendor     map[string][]srcBaseFileInstallBaseFileTuple
+}
+
+func newPrebuiltSrcGroupByInstallPartition() *prebuiltSrcGroupByInstallPartition {
+	return &prebuiltSrcGroupByInstallPartition{
+		system:     map[string][]srcBaseFileInstallBaseFileTuple{},
+		system_ext: map[string][]srcBaseFileInstallBaseFileTuple{},
+		product:    map[string][]srcBaseFileInstallBaseFileTuple{},
+		vendor:     map[string][]srcBaseFileInstallBaseFileTuple{},
+	}
+}
+
+func isSubdirectory(parent, child string) bool {
+	rel, err := filepath.Rel(parent, child)
+	if err != nil {
+		return false
+	}
+	return !strings.HasPrefix(rel, "..")
+}
+
+func appendIfCorrectInstallPartition(partitionToInstallPathList []partitionToInstallPath, destPath, srcPath string, srcGroup *prebuiltSrcGroupByInstallPartition) {
+	for _, part := range partitionToInstallPathList {
+		partition := part.name
+		installPath := part.installPath
+
+		if isSubdirectory(installPath, destPath) {
+			relativeInstallPath, _ := filepath.Rel(installPath, destPath)
+			relativeInstallDir := filepath.Dir(relativeInstallPath)
+			var srcMap map[string][]srcBaseFileInstallBaseFileTuple
+			switch partition {
+			case "system":
+				srcMap = srcGroup.system
+			case "system_ext":
+				srcMap = srcGroup.system_ext
+			case "product":
+				srcMap = srcGroup.product
+			case "vendor":
+				srcMap = srcGroup.vendor
+			}
+			if srcMap != nil {
+				srcMap[relativeInstallDir] = append(srcMap[relativeInstallDir], srcBaseFileInstallBaseFileTuple{
+					srcBaseFile:     filepath.Base(srcPath),
+					installBaseFile: filepath.Base(destPath),
+				})
+			}
+			return
+		}
+	}
+}
+
+// Create a map of source files to the list of destination files from PRODUCT_COPY_FILES entries.
+// Note that the value of the map is a list of string, given that a single source file can be
+// copied to multiple files.
+// This function also checks the existence of the source files, and validates that there is no
+// multiple source files copying to the same dest file.
+func uniqueExistingProductCopyFileMap(ctx android.LoadHookContext) map[string][]string {
+	seen := make(map[string]bool)
+	filtered := make(map[string][]string)
+
+	for _, copyFilePair := range ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.ProductCopyFiles {
+		srcDestList := strings.Split(copyFilePair, ":")
+		if len(srcDestList) < 2 {
+			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, relSrc); optionalPath.Valid() {
+				seen[dest] = true
+				filtered[relSrc] = append(filtered[relSrc], dest)
+			}
+		}
+	}
+
+	return filtered
+}
+
+type partitionToInstallPath struct {
+	name        string
+	installPath string
+}
+
+func processProductCopyFiles(ctx android.LoadHookContext) map[string]*prebuiltSrcGroupByInstallPartition {
+	// Filter out duplicate dest entries and non existing src entries
+	productCopyFileMap := uniqueExistingProductCopyFileMap(ctx)
+
+	// System is intentionally added at the last to consider the scenarios where
+	// non-system partitions are installed as part of the system partition
+	partitionToInstallPathList := []partitionToInstallPath{
+		{name: "vendor", installPath: ctx.DeviceConfig().VendorPath()},
+		{name: "product", installPath: ctx.DeviceConfig().ProductPath()},
+		{name: "system_ext", installPath: ctx.DeviceConfig().SystemExtPath()},
+		{name: "system", installPath: "system"},
+	}
+
+	groupedSources := map[string]*prebuiltSrcGroupByInstallPartition{}
+	for _, src := range android.SortedKeys(productCopyFileMap) {
+		destFiles := productCopyFileMap[src]
+		srcFileDir := filepath.Dir(src)
+		if _, ok := groupedSources[srcFileDir]; !ok {
+			groupedSources[srcFileDir] = newPrebuiltSrcGroupByInstallPartition()
+		}
+		for _, dest := range destFiles {
+			appendIfCorrectInstallPartition(partitionToInstallPathList, dest, filepath.Base(src), groupedSources[srcFileDir])
+		}
+	}
+
+	return groupedSources
+}
+
+type prebuiltModuleProperties struct {
+	Name *string
+
+	Soc_specific        *bool
+	Product_specific    *bool
+	System_ext_specific *bool
+
+	Srcs []string
+	Dsts []string
+
+	No_full_install *bool
+
+	NamespaceExportedToMake bool
+
+	Visibility []string
+}
+
+// Split relative_install_path to a separate struct, because it is not supported for every
+// modules listed in [etcInstallPathToFactoryMap]
+type prebuiltSubdirProperties struct {
+	// If the base file name of the src and dst all match, dsts property does not need to be
+	// set, and only relative_install_path can be set.
+	Relative_install_path *string
+}
+
+var (
+	etcInstallPathToFactoryList = map[string]android.ModuleFactory{
+		"":                etc.PrebuiltRootFactory,
+		"avb":             etc.PrebuiltAvbFactory,
+		"bin":             etc.PrebuiltBinaryFactory,
+		"bt_firmware":     etc.PrebuiltBtFirmwareFactory,
+		"cacerts":         etc.PrebuiltEtcCaCertsFactory,
+		"dsp":             etc.PrebuiltDSPFactory,
+		"etc":             etc.PrebuiltEtcFactory,
+		"etc/dsp":         etc.PrebuiltDSPFactory,
+		"etc/firmware":    etc.PrebuiltFirmwareFactory,
+		"firmware":        etc.PrebuiltFirmwareFactory,
+		"fonts":           etc.PrebuiltFontFactory,
+		"framework":       etc.PrebuiltFrameworkFactory,
+		"lib":             etc.PrebuiltRenderScriptBitcodeFactory,
+		"lib64":           etc.PrebuiltRenderScriptBitcodeFactory,
+		"lib/rfsa":        etc.PrebuiltRFSAFactory,
+		"media":           etc.PrebuiltMediaFactory,
+		"odm":             etc.PrebuiltOdmFactory,
+		"optee":           etc.PrebuiltOpteeFactory,
+		"overlay":         etc.PrebuiltOverlayFactory,
+		"priv-app":        etc.PrebuiltPrivAppFactory,
+		"res":             etc.PrebuiltResFactory,
+		"rfs":             etc.PrebuiltRfsFactory,
+		"tts":             etc.PrebuiltVoicepackFactory,
+		"tvconfig":        etc.PrebuiltTvConfigFactory,
+		"tvservice":       etc.PrebuiltTvServiceFactory,
+		"usr/share":       etc.PrebuiltUserShareFactory,
+		"usr/hyphen-data": etc.PrebuiltUserHyphenDataFactory,
+		"usr/keylayout":   etc.PrebuiltUserKeyLayoutFactory,
+		"usr/keychars":    etc.PrebuiltUserKeyCharsFactory,
+		"usr/srec":        etc.PrebuiltUserSrecFactory,
+		"usr/idc":         etc.PrebuiltUserIdcFactory,
+		"vendor_dlkm":     etc.PrebuiltVendorDlkmFactory,
+		"wallpaper":       etc.PrebuiltWallpaperFactory,
+		"wlc_upt":         etc.PrebuiltWlcUptFactory,
+	}
+)
+
+func generatedPrebuiltEtcModuleName(partition, srcDir, destDir string, count int) string {
+	// generated module name follows the pattern:
+	// <install partition>-<src file path>-<relative install path from partition root>-<number>
+	// Note that all path separators are replaced with "_" in the name
+	moduleName := partition
+	if !android.InList(srcDir, []string{"", "."}) {
+		moduleName += fmt.Sprintf("-%s", strings.ReplaceAll(srcDir, string(filepath.Separator), "_"))
+	}
+	if !android.InList(destDir, []string{"", "."}) {
+		moduleName += fmt.Sprintf("-%s", strings.ReplaceAll(destDir, string(filepath.Separator), "_"))
+	}
+	moduleName += fmt.Sprintf("-%d", count)
+
+	return moduleName
+}
+
+func groupDestFilesBySrc(destFiles []srcBaseFileInstallBaseFileTuple) (ret map[string][]srcBaseFileInstallBaseFileTuple, maxLen int) {
+	ret = map[string][]srcBaseFileInstallBaseFileTuple{}
+	maxLen = 0
+	for _, tuple := range destFiles {
+		if _, ok := ret[tuple.srcBaseFile]; !ok {
+			ret[tuple.srcBaseFile] = []srcBaseFileInstallBaseFileTuple{}
+		}
+		ret[tuple.srcBaseFile] = append(ret[tuple.srcBaseFile], tuple)
+		maxLen = max(maxLen, len(ret[tuple.srcBaseFile]))
+	}
+	return ret, maxLen
+}
+
+func prebuiltEtcModuleProps(moduleName, partition string) prebuiltModuleProperties {
+	moduleProps := prebuiltModuleProperties{}
+	moduleProps.Name = proptools.StringPtr(moduleName)
+
+	// Set partition specific properties
+	switch partition {
+	case "system_ext":
+		moduleProps.System_ext_specific = proptools.BoolPtr(true)
+	case "product":
+		moduleProps.Product_specific = proptools.BoolPtr(true)
+	case "vendor":
+		moduleProps.Soc_specific = proptools.BoolPtr(true)
+	}
+
+	moduleProps.No_full_install = proptools.BoolPtr(true)
+	moduleProps.NamespaceExportedToMake = true
+	moduleProps.Visibility = []string{"//visibility:public"}
+
+	return moduleProps
+}
+
+func createPrebuiltEtcModulesInDirectory(ctx android.LoadHookContext, partition, srcDir, destDir string, destFiles []srcBaseFileInstallBaseFileTuple) (moduleNames []string) {
+	groupedDestFiles, maxLen := groupDestFilesBySrc(destFiles)
+
+	// Find out the most appropriate module type to generate
+	var etcInstallPathKey string
+	for _, etcInstallPath := range android.SortedKeys(etcInstallPathToFactoryList) {
+		// Do not break when found but iterate until the end to find a module with more
+		// specific install path
+		if strings.HasPrefix(destDir, etcInstallPath) {
+			etcInstallPathKey = etcInstallPath
+		}
+	}
+	relDestDirFromInstallDirBase, _ := filepath.Rel(etcInstallPathKey, destDir)
+
+	for fileIndex := range maxLen {
+		srcTuple := []srcBaseFileInstallBaseFileTuple{}
+		for _, srcFile := range android.SortedKeys(groupedDestFiles) {
+			groupedDestFile := groupedDestFiles[srcFile]
+			if len(groupedDestFile) > fileIndex {
+				srcTuple = append(srcTuple, groupedDestFile[fileIndex])
+			}
+		}
+
+		moduleName := generatedPrebuiltEtcModuleName(partition, srcDir, destDir, fileIndex)
+		moduleProps := prebuiltEtcModuleProps(moduleName, partition)
+		modulePropsPtr := &moduleProps
+		propsList := []interface{}{modulePropsPtr}
+
+		allCopyFileNamesUnchanged := true
+		var srcBaseFiles, installBaseFiles []string
+		for _, tuple := range srcTuple {
+			if tuple.srcBaseFile != tuple.installBaseFile {
+				allCopyFileNamesUnchanged = false
+			}
+			srcBaseFiles = append(srcBaseFiles, tuple.srcBaseFile)
+			installBaseFiles = append(installBaseFiles, tuple.installBaseFile)
+		}
+
+		// Set appropriate srcs, dsts, and releative_install_path based on
+		// the source and install file names
+		if allCopyFileNamesUnchanged {
+			modulePropsPtr.Srcs = srcBaseFiles
+
+			// Specify relative_install_path if it is not installed in the root directory of the
+			// partition
+			if !android.InList(relDestDirFromInstallDirBase, []string{"", "."}) {
+				propsList = append(propsList, &prebuiltSubdirProperties{
+					Relative_install_path: proptools.StringPtr(relDestDirFromInstallDirBase),
+				})
+			}
+		} else {
+			modulePropsPtr.Srcs = srcBaseFiles
+			dsts := []string{}
+			for _, installBaseFile := range installBaseFiles {
+				dsts = append(dsts, filepath.Join(relDestDirFromInstallDirBase, installBaseFile))
+			}
+			modulePropsPtr.Dsts = dsts
+		}
+
+		ctx.CreateModuleInDirectory(etcInstallPathToFactoryList[etcInstallPathKey], srcDir, propsList...)
+		moduleNames = append(moduleNames, moduleName)
+	}
+
+	return moduleNames
+}
+
+func createPrebuiltEtcModulesForPartition(ctx android.LoadHookContext, partition, srcDir string, destDirFilesMap map[string][]srcBaseFileInstallBaseFileTuple) (ret []string) {
+	for _, destDir := range android.SortedKeys(destDirFilesMap) {
+		ret = append(ret, createPrebuiltEtcModulesInDirectory(ctx, partition, srcDir, destDir, destDirFilesMap[destDir])...)
+	}
+	return ret
+}
+
+// Creates prebuilt_* modules based on the install paths and returns the list of generated
+// module names
+func createPrebuiltEtcModules(ctx android.LoadHookContext) (ret []string) {
+	groupedSources := processProductCopyFiles(ctx)
+	for _, srcDir := range android.SortedKeys(groupedSources) {
+		groupedSource := groupedSources[srcDir]
+		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "system", srcDir, groupedSource.system)...)
+		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "system_ext", srcDir, groupedSource.system_ext)...)
+		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "product", srcDir, groupedSource.product)...)
+		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "vendor", srcDir, groupedSource.vendor)...)
+	}
+
+	return ret
+}
diff --git a/fsgen/vbmeta_partitions.go b/fsgen/vbmeta_partitions.go
new file mode 100644
index 0000000..52f8ae5
--- /dev/null
+++ b/fsgen/vbmeta_partitions.go
@@ -0,0 +1,184 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// 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 fsgen
+
+import (
+	"android/soong/android"
+	"android/soong/filesystem"
+	"slices"
+	"strconv"
+
+	"github.com/google/blueprint/proptools"
+)
+
+type vbmetaModuleInfo struct {
+	// The name of the generated vbmeta module
+	moduleName string
+	// The name of the module that avb understands. This is the name passed to --chain_partition,
+	// and also the basename of the output file. (the output file is called partitionName + ".img")
+	partitionName string
+}
+
+// Creates the vbmeta partition and the chained vbmeta partitions. Returns the list of module names
+// that the function created. May return nil if the product isn't using avb.
+//
+// AVB is Android Verified Boot: https://source.android.com/docs/security/features/verifiedboot
+// It works by signing all the partitions, but then also including an extra metadata paritition
+// called vbmeta that depends on all the other signed partitions. This creates a requirement
+// that you update all those partitions and the vbmeta partition together, so in order to relax
+// that requirement products can set up "chained" vbmeta partitions, where a chained partition
+// like vbmeta_system might contain the avb metadata for just a few products. In cuttlefish
+// vbmeta_system contains metadata about product, system, and system_ext. Using chained partitions,
+// that group of partitions can be updated independently from the other signed partitions.
+func createVbmetaPartitions(ctx android.LoadHookContext, generatedPartitionTypes []string) []vbmetaModuleInfo {
+	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+	// Some products seem to have BuildingVbmetaImage as true even when BoardAvbEnable is false
+	if !partitionVars.BuildingVbmetaImage || !partitionVars.BoardAvbEnable {
+		return nil
+	}
+
+	var result []vbmetaModuleInfo
+
+	var chainedPartitions []string
+	var partitionTypesHandledByChainedPartitions []string
+	for chainedName, props := range partitionVars.ChainedVbmetaPartitions {
+		chainedName = "vbmeta_" + chainedName
+		if len(props.Partitions) == 0 {
+			continue
+		}
+		if len(props.Key) == 0 {
+			ctx.ModuleErrorf("No key found for chained avb partition %q", chainedName)
+			continue
+		}
+		if len(props.Algorithm) == 0 {
+			ctx.ModuleErrorf("No algorithm found for chained avb partition %q", chainedName)
+			continue
+		}
+		if len(props.RollbackIndex) == 0 {
+			ctx.ModuleErrorf("No rollback index found for chained avb partition %q", chainedName)
+			continue
+		}
+		ril, err := strconv.ParseInt(props.RollbackIndexLocation, 10, 32)
+		if err != nil {
+			ctx.ModuleErrorf("Rollback index location must be an int, got %q", props.RollbackIndexLocation)
+			continue
+		}
+		// The default is to use the PlatformSecurityPatch, and a lot of product config files
+		// just set it to the platform security patch, so detect that and don't set the property
+		// in soong.
+		var rollbackIndex *int64
+		if props.RollbackIndex != ctx.Config().PlatformSecurityPatch() {
+			i, err := strconv.ParseInt(props.RollbackIndex, 10, 32)
+			if err != nil {
+				ctx.ModuleErrorf("Rollback index must be an int, got %q", props.RollbackIndex)
+				continue
+			}
+			rollbackIndex = &i
+		}
+
+		var partitionModules []string
+		for _, partition := range props.Partitions {
+			partitionTypesHandledByChainedPartitions = append(partitionTypesHandledByChainedPartitions, partition)
+			if !slices.Contains(generatedPartitionTypes, partition) {
+				// The partition is probably unsupported.
+				continue
+			}
+			partitionModules = append(partitionModules, generatedModuleNameForPartition(ctx.Config(), partition))
+		}
+
+		name := generatedModuleName(ctx.Config(), chainedName)
+		ctx.CreateModuleInDirectory(
+			filesystem.VbmetaFactory,
+			".", // Create in the root directory for now so its easy to get the key
+			&filesystem.VbmetaProperties{
+				Partition_name:          proptools.StringPtr(chainedName),
+				Stem:                    proptools.StringPtr(chainedName + ".img"),
+				Private_key:             proptools.StringPtr(props.Key),
+				Algorithm:               &props.Algorithm,
+				Rollback_index:          rollbackIndex,
+				Rollback_index_location: &ril,
+				Partitions:              proptools.NewSimpleConfigurable(partitionModules),
+			}, &struct {
+				Name *string
+			}{
+				Name: &name,
+			},
+		).HideFromMake()
+
+		chainedPartitions = append(chainedPartitions, name)
+
+		result = append(result, vbmetaModuleInfo{
+			moduleName:    name,
+			partitionName: chainedName,
+		})
+	}
+
+	vbmetaModuleName := generatedModuleName(ctx.Config(), "vbmeta")
+
+	var algorithm *string
+	var ri *int64
+	var key *string
+	if len(partitionVars.BoardAvbKeyPath) == 0 {
+		// Match make's defaults: https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=4568;drc=5b55f926830963c02ab1d2d91e46442f04ba3af0
+		key = proptools.StringPtr("external/avb/test/data/testkey_rsa4096.pem")
+		algorithm = proptools.StringPtr("SHA256_RSA4096")
+	} else {
+		key = proptools.StringPtr(partitionVars.BoardAvbKeyPath)
+		algorithm = proptools.StringPtr(partitionVars.BoardAvbAlgorithm)
+	}
+	if len(partitionVars.BoardAvbRollbackIndex) > 0 {
+		parsedRi, err := strconv.ParseInt(partitionVars.BoardAvbRollbackIndex, 10, 32)
+		if err != nil {
+			ctx.ModuleErrorf("Rollback index location must be an int, got %q", partitionVars.BoardAvbRollbackIndex)
+		}
+		ri = &parsedRi
+	}
+
+	var partitionModules []string
+	for _, partitionType := range generatedPartitionTypes {
+		if slices.Contains(partitionTypesHandledByChainedPartitions, partitionType) {
+			// Already handled by a chained vbmeta partition
+			continue
+		}
+		if partitionType == "ramdisk" {
+			// ramdisk is never signed with avb information
+			continue
+		}
+		partitionModules = append(partitionModules, generatedModuleNameForPartition(ctx.Config(), partitionType))
+	}
+
+	ctx.CreateModuleInDirectory(
+		filesystem.VbmetaFactory,
+		".", // Create in the root directory for now so its easy to get the key
+		&filesystem.VbmetaProperties{
+			Stem:               proptools.StringPtr("vbmeta.img"),
+			Algorithm:          algorithm,
+			Private_key:        key,
+			Rollback_index:     ri,
+			Chained_partitions: chainedPartitions,
+			Partitions:         proptools.NewSimpleConfigurable(partitionModules),
+		}, &struct {
+			Name *string
+		}{
+			Name: &vbmetaModuleName,
+		},
+	).HideFromMake()
+
+	result = append(result, vbmetaModuleInfo{
+		moduleName:    vbmetaModuleName,
+		partitionName: "vbmeta",
+	})
+	return result
+}
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 1ab1378..1282bfb 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -231,7 +231,7 @@
 
 	// For nsjail tasks
 	useNsjail bool
-	dirSrcs   android.Paths
+	dirSrcs   android.DirectoryPaths
 }
 
 func (g *Module) GeneratedSourceFiles() android.Paths {
@@ -604,7 +604,8 @@
 
 		if task.useNsjail {
 			for _, input := range task.dirSrcs {
-				cmd.Implicit(input)
+				cmd.ImplicitDirectory(input)
+				// TODO(b/375551969): remove glob
 				if paths, err := ctx.GlobWithDeps(filepath.Join(input.String(), "**/*"), nil); err == nil {
 					rule.NsjailImplicits(android.PathsForSource(ctx, paths))
 				} else {
diff --git a/java/aar.go b/java/aar.go
index 66ca00a..b5cdde3 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -77,7 +77,7 @@
 	// list of directories relative to the Blueprints file containing
 	// Android resources.  Defaults to ["res"] if a directory called res exists.
 	// Set to [] to disable the default.
-	Resource_dirs []string `android:"path"`
+	Resource_dirs proptools.Configurable[[]string] `android:"path"`
 
 	// list of zip files containing Android resources.
 	Resource_zips []string `android:"path"`
@@ -275,7 +275,7 @@
 		IncludeDirs: false,
 	})
 	assetDirs := android.PathsWithOptionalDefaultForModuleSrc(ctx, a.aaptProperties.Asset_dirs, "assets")
-	resourceDirs := android.PathsWithOptionalDefaultForModuleSrc(ctx, a.aaptProperties.Resource_dirs, "res")
+	resourceDirs := android.PathsWithOptionalDefaultForModuleSrc(ctx, a.aaptProperties.Resource_dirs.GetOrDefault(ctx, nil), "res")
 	resourceZips := android.PathsForModuleSrc(ctx, a.aaptProperties.Resource_zips)
 
 	// Glob directories into lists of paths
diff --git a/java/app.go b/java/app.go
index 15b1114..0939d17 100644
--- a/java/app.go
+++ b/java/app.go
@@ -1361,7 +1361,7 @@
 			Filter_product *string
 			Aaptflags      []string
 			Manifest       *string
-			Resource_dirs  []string
+			Resource_dirs  proptools.Configurable[[]string]
 			Flags_packages []string
 		}{
 			Name:           proptools.StringPtr(rroPackageName),
@@ -1465,10 +1465,9 @@
 	a.data = append(a.data, android.PathsForModuleSrc(ctx, a.testProperties.Device_common_data)...)
 	a.data = append(a.data, android.PathsForModuleSrc(ctx, a.testProperties.Device_first_data)...)
 	a.data = append(a.data, android.PathsForModuleSrc(ctx, a.testProperties.Device_first_prefer32_data)...)
-	a.data = append(a.data, android.PathsForModuleSrc(ctx, a.testProperties.Device_first_vendor_data)...)
-	a.data = append(a.data, android.PathsForModuleSrc(ctx, a.testProperties.Device_first_vendor_shared_data)...)
+
 	android.SetProvider(ctx, tradefed.BaseTestProviderKey, tradefed.BaseTestProviderData{
-		InstalledFiles:          a.data,
+		TestcaseRelDataFiles:    testcaseRel(a.data),
 		OutputFile:              a.OutputFile(),
 		TestConfig:              a.testConfig,
 		HostRequiredModuleNames: a.HostRequiredModuleNames(),
@@ -1476,6 +1475,8 @@
 		IsHost:                  false,
 		LocalCertificate:        a.certificate.AndroidMkString(),
 		IsUnitTest:              Bool(a.testProperties.Test_options.Unit_test),
+		MkInclude:               "$(BUILD_SYSTEM)/soong_app_prebuilt.mk",
+		MkAppClass:              "APPS",
 	})
 	android.SetProvider(ctx, android.TestOnlyProviderKey, android.TestModuleInformation{
 		TestOnly:       true,
@@ -1484,6 +1485,14 @@
 
 }
 
+func testcaseRel(paths android.Paths) []string {
+	relPaths := []string{}
+	for _, p := range paths {
+		relPaths = append(relPaths, p.Rel())
+	}
+	return relPaths
+}
+
 func (a *AndroidTest) FixTestConfig(ctx android.ModuleContext, testConfig android.Path) android.Path {
 	if testConfig == nil {
 		return nil
diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go
index 1a33680..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.
 		//
@@ -520,7 +520,7 @@
 	// be output to Make but it does not really matter which variant is output. The default/platform
 	// variant is the first (ctx.PrimaryModule()) and is usually hidden from make so this just picks
 	// the last variant (ctx.FinalModule()).
-	if ctx.Module() != ctx.FinalModule() {
+	if !ctx.IsFinalModule(ctx.Module()) {
 		b.HideFromMake()
 	}
 }
@@ -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/core-libraries/Android.bp b/java/core-libraries/Android.bp
index da86540..8c808e4 100644
--- a/java/core-libraries/Android.bp
+++ b/java/core-libraries/Android.bp
@@ -132,10 +132,10 @@
 // prebuilts/sdk/update_prebuilts.py script to update the prebuilts/sdk
 // directory.
 java_library {
-    name: "core-current-stubs-for-system-modules",
+    name: "core-current-stubs-for-system-modules-exportable",
     visibility: ["//development/sdk"],
     static_libs: [
-        "core.current.stubs",
+        "core.current.stubs.exportable",
         // This one is not on device but it's needed when javac compiles code
         // containing lambdas.
         "core-lambda-stubs-for-system-modules",
@@ -155,6 +155,19 @@
     ],
 }
 
+java_library {
+    name: "core-current-stubs-for-system-modules",
+    visibility: ["//development/sdk"],
+    static_libs: [
+        "core.current.stubs",
+        // This one is not on device but it's needed when javac compiles code
+        // containing lambdas.
+        "core-lambda-stubs-for-system-modules",
+    ],
+    sdk_version: "none",
+    system_modules: "none",
+}
+
 // Defaults module to strip out android annotations
 java_defaults {
     name: "system-modules-no-annotations",
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 ac4f496..078f578 100644
--- a/java/java.go
+++ b/java/java.go
@@ -606,10 +606,8 @@
 	} else if ctx.Device() {
 		return defaultJavaLanguageVersion(ctx, sdkContext.SdkVersion(ctx))
 	} else if ctx.Config().TargetsJava21() {
-		// Temporary experimental flag to be able to try and build with
-		// java version 21 options.  The flag, if used, just sets Java
-		// 21 as the default version, leaving any components that
-		// target an older version intact.
+		// Build flag that controls whether Java 21 is used as the default
+		// target version, or Java 17.
 		return JAVA_VERSION_21
 	} else {
 		return JAVA_VERSION_17
@@ -944,6 +942,7 @@
 		// Even though the source javalib is not used, we need to hide it to prevent duplicate installation rules.
 		// TODO (b/331665856): Implement a principled solution for this.
 		j.HideFromMake()
+		j.SkipInstall()
 	}
 	j.provideHiddenAPIPropertyInfo(ctx)
 
@@ -1301,16 +1300,6 @@
 	// host test.
 	Device_first_data []string `android:"path_device_first"`
 
-	// same as data, but adds dependencies using the device's os variation, the device's first
-	// architecture's variation, and the vendor image variation. Can be used to add a module built
-	// for device to the data of a host test.
-	Device_first_vendor_data []string `android:"path_device_first_vendor"`
-
-	// same as data, but adds dependencies using the device's os variation, the device's first
-	// architecture's variation, the vendor image variation, and the shared linkage variation. Can
-	// be used to add a module built for device to the data of a host test.
-	Device_first_vendor_shared_data []string `android:"path_device_first_vendor_shared"`
-
 	// same as data, but adds dependencies using the device's os variation and the device's first
 	// 32-bit architecture's variation. If a 32-bit arch doesn't exist for this device, it will use
 	// a 64 bit arch instead. Can be used to add a module built for device to the data of a
@@ -1568,14 +1557,16 @@
 
 	j.Test.generateAndroidBuildActionsWithConfig(ctx, configs)
 	android.SetProvider(ctx, tradefed.BaseTestProviderKey, tradefed.BaseTestProviderData{
-		InstalledFiles:      j.data,
-		OutputFile:          j.outputFile,
-		TestConfig:          j.testConfig,
-		RequiredModuleNames: j.RequiredModuleNames(ctx),
-		TestSuites:          j.testProperties.Test_suites,
-		IsHost:              true,
-		LocalSdkVersion:     j.sdkVersion.String(),
-		IsUnitTest:          Bool(j.testProperties.Test_options.Unit_test),
+		TestcaseRelDataFiles: testcaseRel(j.data),
+		OutputFile:           j.outputFile,
+		TestConfig:           j.testConfig,
+		RequiredModuleNames:  j.RequiredModuleNames(ctx),
+		TestSuites:           j.testProperties.Test_suites,
+		IsHost:               true,
+		LocalSdkVersion:      j.sdkVersion.String(),
+		IsUnitTest:           Bool(j.testProperties.Test_options.Unit_test),
+		MkInclude:            "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
+		MkAppClass:           "JAVA_LIBRARIES",
 	})
 }
 
@@ -1608,8 +1599,6 @@
 	j.data = append(j.data, android.PathsForModuleSrc(ctx, j.testProperties.Device_common_data)...)
 	j.data = append(j.data, android.PathsForModuleSrc(ctx, j.testProperties.Device_first_data)...)
 	j.data = append(j.data, android.PathsForModuleSrc(ctx, j.testProperties.Device_first_prefer32_data)...)
-	j.data = append(j.data, android.PathsForModuleSrc(ctx, j.testProperties.Device_first_vendor_data)...)
-	j.data = append(j.data, android.PathsForModuleSrc(ctx, j.testProperties.Device_first_vendor_shared_data)...)
 
 	j.extraTestConfigs = android.PathsForModuleSrc(ctx, j.testProperties.Test_options.Extra_test_configs)
 
diff --git a/java/platform_bootclasspath.go b/java/platform_bootclasspath.go
index acfc774..8b0ca97 100644
--- a/java/platform_bootclasspath.go
+++ b/java/platform_bootclasspath.go
@@ -24,7 +24,7 @@
 }
 
 func registerPlatformBootclasspathBuildComponents(ctx android.RegistrationContext) {
-	ctx.RegisterParallelSingletonModuleType("platform_bootclasspath", platformBootclasspathFactory)
+	ctx.RegisterModuleType("platform_bootclasspath", platformBootclasspathFactory)
 }
 
 // The tags used for the dependencies between the platform bootclasspath and any configured boot
@@ -37,7 +37,7 @@
 )
 
 type platformBootclasspathModule struct {
-	android.SingletonModuleBase
+	android.ModuleBase
 	ClasspathFragmentBase
 
 	properties platformBootclasspathProperties
@@ -64,7 +64,7 @@
 	HiddenAPIFlagFileProperties
 }
 
-func platformBootclasspathFactory() android.SingletonModule {
+func platformBootclasspathFactory() android.Module {
 	m := &platformBootclasspathModule{}
 	m.AddProperties(&m.properties)
 	initClasspathFragment(m, BOOTCLASSPATH)
@@ -156,18 +156,6 @@
 	}
 }
 
-// GenerateSingletonBuildActions does nothing and must never do anything.
-//
-// This module only implements android.SingletonModule so that it can implement
-// android.SingletonMakeVarsProvider.
-func (b *platformBootclasspathModule) GenerateSingletonBuildActions(android.SingletonContext) {
-	// Keep empty
-}
-
-func (d *platformBootclasspathModule) MakeVars(ctx android.MakeVarsContext) {
-	d.generateHiddenApiMakeVars(ctx)
-}
-
 func (b *platformBootclasspathModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	// Gather all the dependencies from the art, platform, and apex boot jars.
 	artModules := gatherApexModulePairDepsWithTag(ctx, platformBootclasspathArtBootJarDepTag)
@@ -428,13 +416,3 @@
 
 	rule.Build(desc, desc)
 }
-
-// generateHiddenApiMakeVars generates make variables needed by hidden API related make rules, e.g.
-// veridex and run-appcompat.
-func (b *platformBootclasspathModule) generateHiddenApiMakeVars(ctx android.MakeVarsContext) {
-	if ctx.Config().IsEnvTrue("UNSAFE_DISABLE_HIDDENAPI_FLAGS") {
-		return
-	}
-	// INTERNAL_PLATFORM_HIDDENAPI_FLAGS is used by Make rules in art/ and cts/.
-	ctx.Strict("INTERNAL_PLATFORM_HIDDENAPI_FLAGS", b.hiddenAPIFlagsCSV.String())
-}
diff --git a/java/robolectric.go b/java/robolectric.go
index 37cac2c..5f46267 100644
--- a/java/robolectric.go
+++ b/java/robolectric.go
@@ -151,8 +151,6 @@
 	r.data = append(r.data, android.PathsForModuleSrc(ctx, r.testProperties.Device_common_data)...)
 	r.data = append(r.data, android.PathsForModuleSrc(ctx, r.testProperties.Device_first_data)...)
 	r.data = append(r.data, android.PathsForModuleSrc(ctx, r.testProperties.Device_first_prefer32_data)...)
-	r.data = append(r.data, android.PathsForModuleSrc(ctx, r.testProperties.Device_first_vendor_data)...)
-	r.data = append(r.data, android.PathsForModuleSrc(ctx, r.testProperties.Device_first_vendor_shared_data)...)
 
 	var ok bool
 	var instrumentedApp *AndroidApp
diff --git a/java/sdk.go b/java/sdk.go
index 4537f19..bb2aa8d 100644
--- a/java/sdk.go
+++ b/java/sdk.go
@@ -66,10 +66,8 @@
 	} else if sdk.FinalOrFutureInt() <= 33 {
 		return JAVA_VERSION_11
 	} else if ctx.Config().TargetsJava21() {
-		// Temporary experimental flag to be able to try and build with
-		// java version 21 options.  The flag, if used, just sets Java
-		// 21 as the default version, leaving any components that
-		// target an older version intact.
+		// Build flag that controls whether Java 21 is used as the
+		// default target version, or Java 17.
 		return JAVA_VERSION_21
 	} else {
 		return JAVA_VERSION_17
diff --git a/java/sdk_library.go b/java/sdk_library.go
index dfbde0e..f6dfcdd 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -1403,6 +1403,7 @@
 		// Even though the source javalib is not used, we need to hide it to prevent duplicate installation rules.
 		// TODO (b/331665856): Implement a principled solution for this.
 		module.HideFromMake()
+		module.SkipInstall()
 	}
 
 	module.stem = proptools.StringDefault(module.overridableProperties.Stem, ctx.ModuleName())
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/kernel/prebuilt_kernel_modules.go b/kernel/prebuilt_kernel_modules.go
index e200ee2..13d6482 100644
--- a/kernel/prebuilt_kernel_modules.go
+++ b/kernel/prebuilt_kernel_modules.go
@@ -32,7 +32,7 @@
 }
 
 func registerKernelBuildComponents(ctx android.RegistrationContext) {
-	ctx.RegisterModuleType("prebuilt_kernel_modules", prebuiltKernelModulesFactory)
+	ctx.RegisterModuleType("prebuilt_kernel_modules", PrebuiltKernelModulesFactory)
 }
 
 type prebuiltKernelModules struct {
@@ -47,6 +47,17 @@
 	// List or filegroup of prebuilt kernel module files. Should have .ko suffix.
 	Srcs []string `android:"path,arch_variant"`
 
+	// List of system_dlkm kernel modules that the local kernel modules depend on.
+	// The deps will be assembled into intermediates directory for running depmod
+	// but will not be added to the current module's installed files.
+	System_deps []string `android:"path,arch_variant"`
+
+	// If false, then srcs will not be included in modules.load.
+	// This feature is used by system_dlkm
+	Load_by_default *bool
+
+	Blocklist_file *string `android:"path"`
+
 	// Kernel version that these modules are for. Kernel modules are installed to
 	// /lib/modules/<kernel_version> directory in the corresponding partition. Default is "".
 	Kernel_version *string
@@ -58,7 +69,7 @@
 // prebuilt_kernel_modules installs a set of prebuilt kernel module files to the correct directory.
 // In addition, this module builds modules.load, modules.dep, modules.softdep and modules.alias
 // using depmod and installs them as well.
-func prebuiltKernelModulesFactory() android.Module {
+func PrebuiltKernelModulesFactory() android.Module {
 	module := &prebuiltKernelModules{}
 	module.AddProperties(&module.properties)
 	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
@@ -81,9 +92,11 @@
 	if !pkm.installable() {
 		pkm.SkipInstall()
 	}
-	modules := android.PathsForModuleSrc(ctx, pkm.properties.Srcs)
 
-	depmodOut := runDepmod(ctx, modules)
+	modules := android.PathsForModuleSrc(ctx, pkm.properties.Srcs)
+	systemModules := android.PathsForModuleSrc(ctx, pkm.properties.System_deps)
+
+	depmodOut := pkm.runDepmod(ctx, modules, systemModules)
 	strippedModules := stripDebugSymbols(ctx, modules)
 
 	installDir := android.PathForModuleInstall(ctx, "lib", "modules")
@@ -98,6 +111,23 @@
 	ctx.InstallFile(installDir, "modules.dep", depmodOut.modulesDep)
 	ctx.InstallFile(installDir, "modules.softdep", depmodOut.modulesSoftdep)
 	ctx.InstallFile(installDir, "modules.alias", depmodOut.modulesAlias)
+	pkm.installBlocklistFile(ctx, installDir)
+
+	ctx.SetOutputFiles(modules, ".modules")
+}
+
+func (pkm *prebuiltKernelModules) installBlocklistFile(ctx android.ModuleContext, installDir android.InstallPath) {
+	if pkm.properties.Blocklist_file == nil {
+		return
+	}
+	blocklistOut := android.PathForModuleOut(ctx, "modules.blocklist")
+
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   processBlocklistFile,
+		Input:  android.PathForModuleSrc(ctx, proptools.String(pkm.properties.Blocklist_file)),
+		Output: blocklistOut,
+	})
+	ctx.InstallFile(installDir, "modules.blocklist", blocklistOut)
 }
 
 var (
@@ -137,35 +167,93 @@
 	modulesAlias   android.OutputPath
 }
 
-func runDepmod(ctx android.ModuleContext, modules android.Paths) depmodOutputs {
+var (
+	// system/lib/modules/foo.ko: system/lib/modules/bar.ko
+	// will be converted to
+	// /system/lib/modules/foo.ko: /system/lib/modules/bar.ko
+	addLeadingSlashToPaths = pctx.AndroidStaticRule("add_leading_slash",
+		blueprint.RuleParams{
+			Command: `sed -e 's|\([^: ]*lib/modules/[^: ]*\)|/\1|g' $in > $out`,
+		},
+	)
+	// Remove empty lines. Raise an exception if line is _not_ formatted as `blocklist $name.ko`
+	processBlocklistFile = pctx.AndroidStaticRule("process_blocklist_file",
+		blueprint.RuleParams{
+			Command: `rm -rf $out && awk <$in > $out` +
+				` '/^#/ { print; next }` +
+				` NF == 0 { next }` +
+				` NF != 2 || $$1 != "blocklist"` +
+				` { print "Invalid blocklist line " FNR ": " $$0 >"/dev/stderr";` +
+				` exit_status = 1; next }` +
+				` { $$1 = $$1; print }` +
+				` END { exit exit_status }'`,
+		},
+	)
+)
+
+// This is the path in soong intermediates where the .ko files will be copied.
+// The layout should match the layout on device so that depmod can create meaningful modules.* files.
+func modulesDirForAndroidDlkm(ctx android.ModuleContext, modulesDir android.OutputPath, system bool) android.OutputPath {
+	if ctx.InstallInSystemDlkm() || system {
+		// The first component can be either system or system_dlkm
+		// system works because /system/lib/modules is a symlink to /system_dlkm/lib/modules.
+		// system was chosen to match the contents of the kati built modules.dep
+		return modulesDir.Join(ctx, "system", "lib", "modules")
+	} else if ctx.InstallInVendorDlkm() {
+		return modulesDir.Join(ctx, "vendor", "lib", "modules")
+	} else if ctx.InstallInOdmDlkm() {
+		return modulesDir.Join(ctx, "odm", "lib", "modules")
+	} else {
+		// not an android dlkm module.
+		return modulesDir
+	}
+}
+
+func (pkm *prebuiltKernelModules) runDepmod(ctx android.ModuleContext, modules android.Paths, systemModules android.Paths) depmodOutputs {
 	baseDir := android.PathForModuleOut(ctx, "depmod").OutputPath
 	fakeVer := "0.0" // depmod demands this anyway
 	modulesDir := baseDir.Join(ctx, "lib", "modules", fakeVer)
+	modulesCpDir := modulesDirForAndroidDlkm(ctx, modulesDir, false)
 
 	builder := android.NewRuleBuilder(pctx, ctx)
 
 	// Copy the module files to a temporary dir
-	builder.Command().Text("rm").Flag("-rf").Text(modulesDir.String())
-	builder.Command().Text("mkdir").Flag("-p").Text(modulesDir.String())
+	builder.Command().Text("rm").Flag("-rf").Text(modulesCpDir.String())
+	builder.Command().Text("mkdir").Flag("-p").Text(modulesCpDir.String())
 	for _, m := range modules {
-		builder.Command().Text("cp").Input(m).Text(modulesDir.String())
+		builder.Command().Text("cp").Input(m).Text(modulesCpDir.String())
+	}
+
+	modulesDirForSystemDlkm := modulesDirForAndroidDlkm(ctx, modulesDir, true)
+	if len(systemModules) > 0 {
+		builder.Command().Text("mkdir").Flag("-p").Text(modulesDirForSystemDlkm.String())
+	}
+	for _, m := range systemModules {
+		builder.Command().Text("cp").Input(m).Text(modulesDirForSystemDlkm.String())
 	}
 
 	// Enumerate modules to load
 	modulesLoad := modulesDir.Join(ctx, "modules.load")
-	var basenames []string
-	for _, m := range modules {
-		basenames = append(basenames, filepath.Base(m.String()))
+	// If Load_by_default is set to false explicitly, create an empty modules.load
+	if pkm.properties.Load_by_default != nil && !*pkm.properties.Load_by_default {
+		builder.Command().Text("rm").Flag("-rf").Text(modulesLoad.String())
+		builder.Command().Text("touch").Output(modulesLoad)
+	} else {
+		var basenames []string
+		for _, m := range modules {
+			basenames = append(basenames, filepath.Base(m.String()))
+		}
+		builder.Command().
+			Text("echo").Flag("\"" + strings.Join(basenames, " ") + "\"").
+			Text("|").Text("tr").Flag("\" \"").Flag("\"\\n\"").
+			Text(">").Output(modulesLoad)
 	}
-	builder.Command().
-		Text("echo").Flag("\"" + strings.Join(basenames, " ") + "\"").
-		Text("|").Text("tr").Flag("\" \"").Flag("\"\\n\"").
-		Text(">").Output(modulesLoad)
 
 	// Run depmod to build modules.dep/softdep/alias files
 	modulesDep := modulesDir.Join(ctx, "modules.dep")
 	modulesSoftdep := modulesDir.Join(ctx, "modules.softdep")
 	modulesAlias := modulesDir.Join(ctx, "modules.alias")
+	builder.Command().Text("mkdir").Flag("-p").Text(modulesDir.String())
 	builder.Command().
 		BuiltTool("depmod").
 		FlagWithArg("-b ", baseDir.String()).
@@ -176,5 +264,16 @@
 
 	builder.Build("depmod", fmt.Sprintf("depmod %s", ctx.ModuleName()))
 
-	return depmodOutputs{modulesLoad, modulesDep, modulesSoftdep, modulesAlias}
+	finalModulesDep := modulesDep
+	// Add a leading slash to paths in modules.dep of android dlkm
+	if ctx.InstallInSystemDlkm() || ctx.InstallInVendorDlkm() || ctx.InstallInOdmDlkm() {
+		finalModulesDep := modulesDep.ReplaceExtension(ctx, "intermediates")
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   addLeadingSlashToPaths,
+			Input:  modulesDep,
+			Output: finalModulesDep,
+		})
+	}
+
+	return depmodOutputs{modulesLoad, finalModulesDep, modulesSoftdep, modulesAlias}
 }
diff --git a/linkerconfig/linkerconfig.go b/linkerconfig/linkerconfig.go
index 05b99fd..d422871 100644
--- a/linkerconfig/linkerconfig.go
+++ b/linkerconfig/linkerconfig.go
@@ -77,7 +77,7 @@
 	output := android.PathForModuleOut(ctx, "linker.config.pb").OutputPath
 
 	builder := android.NewRuleBuilder(pctx, ctx)
-	BuildLinkerConfig(ctx, builder, input, nil, nil, output)
+	BuildLinkerConfig(ctx, builder, android.Paths{input}, nil, nil, output)
 	builder.Build("conv_linker_config", "Generate linker config protobuf "+output.String())
 
 	l.outputFilePath = output
@@ -91,16 +91,18 @@
 }
 
 func BuildLinkerConfig(ctx android.ModuleContext, builder *android.RuleBuilder,
-	input android.Path, provideModules []android.Module, requireModules []android.Module, output android.OutputPath) {
+	inputs android.Paths, provideModules []android.Module, requireModules []android.Module, output android.OutputPath) {
 
 	// First, convert the input json to protobuf format
 	interimOutput := android.PathForModuleOut(ctx, "temp.pb")
-	builder.Command().
+	cmd := builder.Command().
 		BuiltTool("conv_linker_config").
 		Flag("proto").
-		Flag("--force").
-		FlagWithInput("-s ", input).
-		FlagWithOutput("-o ", interimOutput)
+		Flag("--force")
+	for _, input := range inputs {
+		cmd.FlagWithInput("-s ", input)
+	}
+	cmd.FlagWithOutput("-o ", interimOutput)
 
 	// Secondly, if there's provideLibs gathered from provideModules, append them
 	var provideLibs []string
diff --git a/rust/bindgen.go b/rust/bindgen.go
index 3944495..d590579 100644
--- a/rust/bindgen.go
+++ b/rust/bindgen.go
@@ -252,7 +252,7 @@
 
 	// Module defined clang flags and include paths
 	cflags = append(cflags, esc(cflagsProp)...)
-	for _, include := range b.ClangProperties.Local_include_dirs {
+	for _, include := range b.ClangProperties.Local_include_dirs.GetOrDefault(ctx, nil) {
 		cflags = append(cflags, "-I"+android.PathForModuleSrc(ctx, include).String())
 		implicits = append(implicits, android.PathForModuleSrc(ctx, include))
 	}
diff --git a/rust/builder.go b/rust/builder.go
index f469f56..a1e17fc 100644
--- a/rust/builder.go
+++ b/rust/builder.go
@@ -170,6 +170,9 @@
 	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, getTransformProperties(ctx, "rlib"))
 }
 
+// TransformRlibstoStaticlib is assumed to be called from the cc module, and
+// thus needs to reconstruct the common set of flags which need to be passed
+// to the rustc compiler.
 func TransformRlibstoStaticlib(ctx android.ModuleContext, mainSrc android.Path, deps []cc.RustRlibDep,
 	outputFile android.WritablePath) android.Path {
 
diff --git a/rust/config/global.go b/rust/config/global.go
index 68a74c2..7b79fca 100644
--- a/rust/config/global.go
+++ b/rust/config/global.go
@@ -15,6 +15,7 @@
 package config
 
 import (
+	"fmt"
 	"strings"
 
 	"android/soong/android"
@@ -93,6 +94,16 @@
 	}
 )
 
+func RustPath(ctx android.PathContext) string {
+	// I can't see any way to flatten the static variable inside Soong, so this
+	// reproduces the init logic.
+	var RustBase string = RustDefaultBase
+	if override := ctx.Config().Getenv("RUST_PREBUILTS_BASE"); override != "" {
+		RustBase = override
+	}
+	return fmt.Sprintf("%s/%s/%s", RustBase, HostPrebuiltTag(ctx.Config()), GetRustVersion(ctx))
+}
+
 func init() {
 	pctx.SourcePathVariable("RustDefaultBase", RustDefaultBase)
 	pctx.VariableConfigMethod("HostPrebuiltTag", HostPrebuiltTag)
diff --git a/rust/project_json.go b/rust/project_json.go
index 6c1e320..6e8cebe 100644
--- a/rust/project_json.go
+++ b/rust/project_json.go
@@ -19,6 +19,7 @@
 	"fmt"
 
 	"android/soong/android"
+	"android/soong/rust/config"
 )
 
 // This singleton collects Rust crate definitions and generates a JSON file
@@ -44,17 +45,19 @@
 }
 
 type rustProjectCrate struct {
-	DisplayName string            `json:"display_name"`
-	RootModule  string            `json:"root_module"`
-	Edition     string            `json:"edition,omitempty"`
-	Deps        []rustProjectDep  `json:"deps"`
-	Cfg         []string          `json:"cfg"`
-	Env         map[string]string `json:"env"`
-	ProcMacro   bool              `json:"is_proc_macro"`
+	DisplayName    string            `json:"display_name"`
+	RootModule     string            `json:"root_module"`
+	Edition        string            `json:"edition,omitempty"`
+	Deps           []rustProjectDep  `json:"deps"`
+	Cfg            []string          `json:"cfg"`
+	Env            map[string]string `json:"env"`
+	ProcMacro      bool              `json:"is_proc_macro"`
+	ProcMacroDylib *string           `json:"proc_macro_dylib_path"`
 }
 
 type rustProjectJson struct {
-	Crates []rustProjectCrate `json:"crates"`
+	Sysroot string             `json:"sysroot"`
+	Crates  []rustProjectCrate `json:"crates"`
 }
 
 // crateInfo is used during the processing to keep track of the known crates.
@@ -135,16 +138,21 @@
 		return 0, false
 	}
 
-	_, procMacro := rModule.compiler.(*procMacroDecorator)
+	var procMacroDylib *string = nil
+	if procDec, procMacro := rModule.compiler.(*procMacroDecorator); procMacro {
+		procMacroDylib = new(string)
+		*procMacroDylib = procDec.baseCompiler.unstrippedOutputFilePath().String()
+	}
 
 	crate := rustProjectCrate{
-		DisplayName: rModule.Name(),
-		RootModule:  rootModule.String(),
-		Edition:     rModule.compiler.edition(),
-		Deps:        make([]rustProjectDep, 0),
-		Cfg:         make([]string, 0),
-		Env:         make(map[string]string),
-		ProcMacro:   procMacro,
+		DisplayName:    rModule.Name(),
+		RootModule:     rootModule.String(),
+		Edition:        rModule.compiler.edition(),
+		Deps:           make([]rustProjectDep, 0),
+		Cfg:            make([]string, 0),
+		Env:            make(map[string]string),
+		ProcMacro:      procMacroDylib != nil,
+		ProcMacroDylib: procMacroDylib,
 	}
 
 	if rModule.compiler.cargoOutDir().Valid() {
@@ -197,6 +205,8 @@
 		return
 	}
 
+	singleton.project.Sysroot = config.RustPath(ctx)
+
 	singleton.knownCrates = make(map[string]crateInfo)
 	ctx.VisitAllModules(func(module android.Module) {
 		singleton.appendCrateAndDependencies(ctx, module)
diff --git a/rust/rust.go b/rust/rust.go
index 0b26f92..ed38ad7 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -977,6 +977,7 @@
 			// side dependencies. In particular, proc-macros need to be captured in the
 			// host snapshot.
 			mod.HideFromMake()
+			mod.SkipInstall()
 		} else if !mod.installable(apexInfo) {
 			mod.SkipInstall()
 		}
diff --git a/rust/rust_test.go b/rust/rust_test.go
index eeedf3f..767508d 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -494,7 +494,7 @@
 	}
 
 	// Make sure the static lib is included in the ld command
-	if !strings.Contains(libcc_shared_ld.Args["libFlags"], "generated_rust_staticlib/liblibcc_shared_rust_staticlib.a") {
+	if !strings.Contains(libcc_shared_ld.Args["libFlags"], "generated_rust_staticlib/librustlibs.a") {
 		t.Errorf("missing generated static library in linker step libFlags %#v, libFlags: %#v",
 			"libcc_shared.generated_rust_staticlib.a", libcc_shared_ld.Args["libFlags"])
 	}
@@ -511,7 +511,7 @@
 	}
 
 	// Make sure the static lib is included in the cc command
-	if !strings.Contains(ccbin_ld.Args["libFlags"], "generated_rust_staticlib/libccBin_rust_staticlib.a") {
+	if !strings.Contains(ccbin_ld.Args["libFlags"], "generated_rust_staticlib/librustlibs.a") {
 		t.Errorf("missing generated static library in linker step libFlags, expecting %#v, libFlags: %#v",
 			"ccBin.generated_rust_staticlib.a", ccbin_ld.Args["libFlags"])
 	}
diff --git a/scripts/gen_build_prop.py b/scripts/gen_build_prop.py
index e0686ed..0b7780e 100644
--- a/scripts/gen_build_prop.py
+++ b/scripts/gen_build_prop.py
@@ -608,6 +608,8 @@
         build_product_prop(args)
       case "vendor":
         build_vendor_prop(args)
+      case "system_dlkm" | "vendor_dlkm" | "odm_dlkm":
+        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/sdk/Android.bp b/sdk/Android.bp
index f436320..f42b478 100644
--- a/sdk/Android.bp
+++ b/sdk/Android.bp
@@ -18,7 +18,6 @@
         "bp.go",
         "build_release.go",
         "exports.go",
-        "genrule.go",
         "member_trait.go",
         "member_type.go",
         "sdk.go",
@@ -31,7 +30,6 @@
         "cc_sdk_test.go",
         "compat_config_sdk_test.go",
         "exports_test.go",
-        "genrule_test.go",
         "java_sdk_test.go",
         "license_sdk_test.go",
         "member_trait_test.go",
diff --git a/sdk/genrule.go b/sdk/genrule.go
deleted file mode 100644
index 347ab05..0000000
--- a/sdk/genrule.go
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2023 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 sdk
-
-import (
-	"android/soong/android"
-	"android/soong/genrule"
-)
-
-func init() {
-	registerGenRuleBuildComponents(android.InitRegistrationContext)
-}
-
-func registerGenRuleBuildComponents(ctx android.RegistrationContext) {
-	ctx.RegisterModuleType("sdk_genrule", SdkGenruleFactory)
-}
-
-// sdk_genrule_host is a genrule that can depend on sdk and sdk_snapshot module types
-//
-// What this means is that it's a genrule with only the "common_os" variant.
-// sdk modules have 3 variants: host, android, and common_os. The common_os one depends
-// on the host/device ones and packages their result into a final snapshot zip.
-// Genrules probably want access to this snapshot zip when they depend on an sdk module,
-// which means they want to depend on the common_os variant and not the host/android
-// variants.
-func SdkGenruleFactory() android.Module {
-	module := genrule.NewGenRule()
-
-	android.InitCommonOSAndroidMultiTargetsArchModule(module, android.NeitherHostNorDeviceSupported, android.MultilibCommon)
-	android.InitDefaultableModule(module)
-
-	return module
-}
diff --git a/sdk/genrule_test.go b/sdk/genrule_test.go
deleted file mode 100644
index bf67795..0000000
--- a/sdk/genrule_test.go
+++ /dev/null
@@ -1,45 +0,0 @@
-// 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.
-
-package sdk
-
-import (
-	"testing"
-
-	"android/soong/android"
-	"android/soong/genrule"
-	"android/soong/java"
-)
-
-func TestSdkGenrule(t *testing.T) {
-	// Test that a genrule can depend on an sdk if using common_os_srcs
-	bp := `
-				sdk {
-					name: "my_sdk",
-				}
-				genrule {
-					name: "my_regular_genrule",
-					common_os_srcs: [":my_sdk"],
-					out: ["out"],
-					cmd: "cp $(in) $(out)",
-				}
-			`
-	android.GroupFixturePreparers(
-		// if java components aren't registered, the sdk module doesn't create a snapshot for some reason.
-		java.PrepareForTestWithJavaBuildComponents,
-		genrule.PrepareForTestWithGenRuleBuildComponents,
-		PrepareForTestWithSdkBuildComponents,
-		android.FixtureRegisterWithContext(registerGenRuleBuildComponents),
-	).RunTestWithBp(t, bp)
-}
diff --git a/sh/sh_binary.go b/sh/sh_binary.go
index 9c0db73..320e97f 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -15,6 +15,7 @@
 package sh
 
 import (
+	"fmt"
 	"path/filepath"
 	"strings"
 
@@ -45,6 +46,7 @@
 	ctx.RegisterModuleType("sh_binary_host", ShBinaryHostFactory)
 	ctx.RegisterModuleType("sh_test", ShTestFactory)
 	ctx.RegisterModuleType("sh_test_host", ShTestHostFactory)
+	ctx.RegisterModuleType("sh_defaults", ShDefaultsFactory)
 }
 
 // Test fixture preparer that will register most sh build components.
@@ -163,10 +165,14 @@
 
 	// Test options.
 	Test_options android.CommonTestOptions
+
+	// a list of extra test configuration files that should be installed with the module.
+	Extra_test_configs []string `android:"path,arch_variant"`
 }
 
 type ShBinary struct {
 	android.ModuleBase
+	android.DefaultableModuleBase
 
 	properties shBinaryProperties
 
@@ -184,8 +190,9 @@
 
 	installDir android.InstallPath
 
-	data       []android.DataPath
-	testConfig android.Path
+	data             []android.DataPath
+	testConfig       android.Path
+	extraTestConfigs android.Paths
 
 	dataModules map[string]android.Path
 }
@@ -469,6 +476,7 @@
 		HostTemplate:           "${ShellTestConfigTemplate}",
 	})
 
+	s.extraTestConfigs = android.PathsForModuleSrc(ctx, s.testProperties.Extra_test_configs)
 	s.dataModules = make(map[string]android.Path)
 	ctx.VisitDirectDeps(func(dep android.Module) {
 		depTag := ctx.OtherModuleDependencyTag(dep)
@@ -508,6 +516,27 @@
 
 	installedData := ctx.InstallTestData(s.installDir, s.data)
 	s.installedFile = ctx.InstallExecutable(s.installDir, s.outputFilePath.Base(), s.outputFilePath, installedData...)
+
+	mkEntries := s.AndroidMkEntries()[0]
+	android.SetProvider(ctx, tradefed.BaseTestProviderKey, tradefed.BaseTestProviderData{
+		TestcaseRelDataFiles: addArch(ctx.Arch().ArchType.String(), installedData.Paths()),
+		OutputFile:           s.outputFilePath,
+		TestConfig:           s.testConfig,
+		TestSuites:           s.testProperties.Test_suites,
+		IsHost:               false,
+		IsUnitTest:           Bool(s.testProperties.Test_options.Unit_test),
+		MkInclude:            mkEntries.Include,
+		MkAppClass:           mkEntries.Class,
+		InstallDir:           s.installDir,
+	})
+}
+
+func addArch(archType string, paths android.Paths) []string {
+	archRelPaths := []string{}
+	for _, p := range paths {
+		archRelPaths = append(archRelPaths, fmt.Sprintf("%s/%s", archType, p.Rel()))
+	}
+	return archRelPaths
 }
 
 func (s *ShTest) InstallInData() bool {
@@ -531,6 +560,9 @@
 					entries.AddStrings("LOCAL_TEST_DATA_BINS", s.testProperties.Data_bins...)
 				}
 				entries.SetBoolIfTrue("LOCAL_COMPATIBILITY_PER_TESTCASE_DIRECTORY", Bool(s.testProperties.Per_testcase_directory))
+				if len(s.extraTestConfigs) > 0 {
+					entries.AddStrings("LOCAL_EXTRA_FULL_TEST_CONFIGS", s.extraTestConfigs.Strings()...)
+				}
 
 				s.testProperties.Test_options.SetAndroidMkEntries(entries)
 			},
@@ -548,6 +580,7 @@
 	module := &ShBinary{}
 	initShBinaryModule(module)
 	android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
 	return module
 }
 
@@ -557,6 +590,7 @@
 	module := &ShBinary{}
 	initShBinaryModule(module)
 	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
 	return module
 }
 
@@ -567,6 +601,7 @@
 	module.AddProperties(&module.testProperties)
 
 	android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
 	return module
 }
 
@@ -581,6 +616,21 @@
 	}
 
 	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+type ShDefaults struct {
+	android.ModuleBase
+	android.DefaultsModuleBase
+}
+
+func ShDefaultsFactory() android.Module {
+	module := &ShDefaults{}
+
+	module.AddProperties(&shBinaryProperties{}, &TestProperties{})
+	android.InitDefaultsModule(module)
+
 	return module
 }
 
diff --git a/sh/sh_binary_test.go b/sh/sh_binary_test.go
index 37450b0..28f997d 100644
--- a/sh/sh_binary_test.go
+++ b/sh/sh_binary_test.go
@@ -176,6 +176,22 @@
 	android.AssertBoolEquals(t, "LOCAL_IS_UNIT_TEST", true, actualData)
 }
 
+func TestShTestExtraTestConfig(t *testing.T) {
+	result, _ := testShBinary(t, `
+		sh_test {
+			name: "foo",
+			src: "test.sh",
+			filename: "test.sh",
+                        extra_test_configs: ["config1.xml", "config2.xml"],
+		}
+	`)
+
+	mod := result.ModuleForTests("foo", "android_arm64_armv8-a").Module().(*ShTest)
+	entries := android.AndroidMkEntriesForTest(t, result, mod)[0]
+	actualData := entries.EntryMap["LOCAL_EXTRA_FULL_TEST_CONFIGS"]
+	android.AssertStringPathsRelativeToTopEquals(t, "extra_configs", result.Config(), []string{"config1.xml", "config2.xml"}, actualData)
+}
+
 func TestShTestHost_dataDeviceModules(t *testing.T) {
 	ctx, config := testShBinary(t, `
 		sh_test_host {
@@ -294,3 +310,92 @@
 	actualData := entries.EntryMap["LOCAL_TEST_DATA"]
 	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_TEST_DATA", config, expectedData, actualData)
 }
+
+func TestDefaultsForTests(t *testing.T) {
+	ctx, config := testShBinary(t, `
+		sh_defaults {
+			name: "defaults",
+			src: "test.sh",
+			filename: "test.sh",
+			data: [
+				"testdata/data1",
+				"testdata/sub/data2",
+			],
+		}
+		sh_test_host {
+			name: "foo",
+			defaults: ["defaults"],
+			data: [
+				"testdata/more_data",
+			],
+			java_data: [
+				"javalib",
+			],
+		}
+
+		java_library_host {
+			name: "javalib",
+			srcs: [],
+		}
+
+		sh_test {
+			name: "sh-test",
+			defaults: ["defaults"],
+		}
+
+	`)
+	buildOS := ctx.Config().BuildOS.String()
+	mod := ctx.ModuleForTests("foo", buildOS+"_x86_64").Module().(*ShTest)
+	if !mod.Host() {
+		t.Errorf("host bit is not set for a sh_test_host module.")
+	}
+	expectedData := []string{
+		":testdata/data1",
+		":testdata/sub/data2",
+		":testdata/more_data",
+		"out/soong/.intermediates/javalib/" + buildOS + "_common/combined/:javalib.jar",
+	}
+
+	entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
+	actualData := entries.EntryMap["LOCAL_TEST_DATA"]
+	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_TEST_DATA", config, expectedData, actualData)
+
+	// Just the defaults
+	expectedData = []string{
+		":testdata/data1",
+		":testdata/sub/data2",
+	}
+	mod = ctx.ModuleForTests("sh-test", "android_arm64_armv8-a").Module().(*ShTest)
+	entries = android.AndroidMkEntriesForTest(t, ctx, mod)[0]
+	actualData = entries.EntryMap["LOCAL_TEST_DATA"]
+	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_TEST_DATA", config, expectedData, actualData)
+}
+
+func TestDefaultsForBinaries(t *testing.T) {
+	ctx, _ := testShBinary(t, `
+		sh_defaults {
+			name: "defaults",
+			src: "test.sh",
+			filename: "test.sh",
+		}
+		sh_binary_host {
+			name: "the-host-binary",
+			defaults: ["defaults"],
+		}
+		sh_binary{
+			name: "the-binary",
+			defaults: ["defaults"],
+		}
+	`)
+	buildOS := ctx.Config().BuildOS.String()
+	mod := ctx.ModuleForTests("the-host-binary", buildOS+"_x86_64").Module().(*ShBinary)
+	if !mod.Host() {
+		t.Errorf("host bit is not set for a sh_binary_host module.")
+	}
+
+	expectedFilename := "test.sh"
+	android.AssertStringEquals(t, "Filename", expectedFilename, *mod.properties.Filename)
+
+	mod = ctx.ModuleForTests("the-binary", "android_arm64_armv8-a").Module().(*ShBinary)
+	android.AssertStringEquals(t, "Filename", expectedFilename, *mod.properties.Filename)
+}
diff --git a/tests/lib.sh b/tests/lib.sh
index 4c320d0..0e26de5 100644
--- a/tests/lib.sh
+++ b/tests/lib.sh
@@ -91,7 +91,6 @@
 }
 
 function create_mock_soong {
-  create_mock_bazel
   copy_directory build/blueprint
   copy_directory build/soong
   copy_directory build/make
@@ -143,41 +142,6 @@
   USE_RBE=false TARGET_PRODUCT=aosp_arm TARGET_RELEASE=trunk_staging TARGET_BUILD_VARIANT=userdebug build/soong/soong_ui.bash --make-mode --skip-ninja --skip-config --soong-only --skip-soong-tests "$@"
 }
 
-function create_mock_bazel {
-  copy_directory build/bazel
-  copy_directory build/bazel_common_rules
-
-  # This requires pulling more tools into the mock top to build partitions
-  delete_directory build/bazel/examples/partitions
-
-  symlink_directory packages/modules/common/build
-  symlink_directory prebuilts/bazel
-  symlink_directory prebuilts/clang
-  symlink_directory prebuilts/jdk
-  symlink_directory external/bazel-skylib
-  symlink_directory external/bazelbuild-rules_android
-  symlink_directory external/bazelbuild-rules_go
-  symlink_directory external/bazelbuild-rules_license
-  symlink_directory external/bazelbuild-kotlin-rules
-  symlink_directory external/bazelbuild-rules_cc
-  symlink_directory external/bazelbuild-rules_python
-  symlink_directory external/bazelbuild-rules_java
-  symlink_directory external/bazelbuild-rules_rust
-  symlink_directory external/bazelbuild-rules_testing
-  symlink_directory external/rust/crates/tinyjson
-
-  symlink_file WORKSPACE
-  symlink_file BUILD
-}
-
-function run_bazel {
-  # Remove the ninja_build output marker file to communicate to buildbot that this is not a regular Ninja build, and its
-  # output should not be parsed as such.
-  rm -rf out/ninja_build
-
-  build/bazel/bin/bazel "$@"
-}
-
 function run_ninja {
   build/soong/soong_ui.bash --make-mode --skip-config --soong-only --skip-soong-tests "$@"
 }
diff --git a/tradefed/providers.go b/tradefed/providers.go
index 0abac12..0ae841d 100644
--- a/tradefed/providers.go
+++ b/tradefed/providers.go
@@ -9,8 +9,8 @@
 // Data that test_module_config[_host] modules types will need from
 // their dependencies to write out build rules and AndroidMkEntries.
 type BaseTestProviderData struct {
-	// data files and apps for android_test
-	InstalledFiles android.Paths
+	// data files and apps installed for tests, relative to testcases dir.
+	TestcaseRelDataFiles []string
 	// apk for android_test
 	OutputFile android.Path
 	// Either handwritten or generated TF xml.
@@ -28,6 +28,12 @@
 	LocalCertificate string
 	// Indicates if the base module was a unit test.
 	IsUnitTest bool
+	// The .mk file is used AndroidMkEntries for base (soong_java_prebuilt, etc.)
+	MkInclude string
+	// The AppClass to use for the AndroidMkEntries for the base.
+	MkAppClass string
+	// value for LOCAL_MODULE_PATH.  The directory where the module is installed.
+	InstallDir android.InstallPath
 }
 
 var BaseTestProviderKey = blueprint.NewProvider[BaseTestProviderData]()
diff --git a/tradefed_modules/Android.bp b/tradefed_modules/Android.bp
index 9969ae2..a765a05 100644
--- a/tradefed_modules/Android.bp
+++ b/tradefed_modules/Android.bp
@@ -9,13 +9,16 @@
         "blueprint",
         "soong-android",
         "soong-java",
+        "soong-sh",
         "soong-tradefed",
     ],
     srcs: [
         "test_module_config.go",
+        "test_suite.go",
     ],
     testSrcs: [
         "test_module_config_test.go",
+        "test_suite_test.go",
     ],
     pluginFor: ["soong_build"],
 }
diff --git a/tradefed_modules/test_module_config.go b/tradefed_modules/test_module_config.go
index 7a04c19..988352c 100644
--- a/tradefed_modules/test_module_config.go
+++ b/tradefed_modules/test_module_config.go
@@ -196,7 +196,7 @@
 	module := &testModuleConfigModule{}
 
 	module.AddProperties(&module.tradefedProperties)
-	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
 	android.InitDefaultableModule(module)
 
 	return module
@@ -216,13 +216,28 @@
 // Implements android.AndroidMkEntriesProvider
 var _ android.AndroidMkEntriesProvider = (*testModuleConfigModule)(nil)
 
+func (m *testModuleConfigModule) nativeExtraEntries(entries *android.AndroidMkEntries) {
+	// TODO(ron) provider for suffix and STEM?
+	entries.SetString("LOCAL_MODULE_SUFFIX", "")
+	// Should the stem and path use the base name or our module name?
+	entries.SetString("LOCAL_MODULE_STEM", m.provider.OutputFile.Rel())
+	entries.SetPath("LOCAL_MODULE_PATH", m.provider.InstallDir)
+}
+
+func (m *testModuleConfigModule) javaExtraEntries(entries *android.AndroidMkEntries) {
+	// The app_prebuilt_internal.mk files try create a copy of the OutputFile as an .apk.
+	// Normally, this copies the "package.apk" from the intermediate directory here.
+	// To prevent the copy of the large apk and to prevent confusion with the real .apk we
+	// link to, we set the STEM here to a bogus name and we set OutputFile to a small file (our manifest).
+	// We do this so we don't have to add more conditionals to base_rules.mk
+	// soong_java_prebult has the same issue for .jars so use this in both module types.
+	entries.SetString("LOCAL_MODULE_STEM", fmt.Sprintf("UNUSED-%s", *m.Base))
+	entries.SetString("LOCAL_MODULE_TAGS", "tests")
+}
+
 func (m *testModuleConfigModule) AndroidMkEntries() []android.AndroidMkEntries {
-	appClass := "APPS"
-	include := "$(BUILD_SYSTEM)/soong_app_prebuilt.mk"
-	if m.isHost {
-		appClass = "JAVA_LIBRARIES"
-		include = "$(BUILD_SYSTEM)/soong_java_prebuilt.mk"
-	}
+	appClass := m.provider.MkAppClass
+	include := m.provider.MkInclude
 	return []android.AndroidMkEntries{{
 		Class:      appClass,
 		OutputFile: android.OptionalPathForPath(m.manifest),
@@ -231,7 +246,6 @@
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				entries.SetPath("LOCAL_FULL_TEST_CONFIG", m.testConfig)
-				entries.SetString("LOCAL_MODULE_TAGS", "tests")
 				entries.SetString("LOCAL_TEST_MODULE_CONFIG_BASE", *m.Base)
 				if m.provider.LocalSdkVersion != "" {
 					entries.SetString("LOCAL_SDK_VERSION", m.provider.LocalSdkVersion)
@@ -244,13 +258,11 @@
 				entries.AddCompatibilityTestSuites(m.tradefedProperties.Test_suites...)
 				entries.AddStrings("LOCAL_HOST_REQUIRED_MODULES", m.provider.HostRequiredModuleNames...)
 
-				// The app_prebuilt_internal.mk files try create a copy of the OutputFile as an .apk.
-				// Normally, this copies the "package.apk" from the intermediate directory here.
-				// To prevent the copy of the large apk and to prevent confusion with the real .apk we
-				// link to, we set the STEM here to a bogus name and we set OutputFile to a small file (our manifest).
-				// We do this so we don't have to add more conditionals to base_rules.mk
-				// soong_java_prebult has the same issue for .jars so use this in both module types.
-				entries.SetString("LOCAL_MODULE_STEM", fmt.Sprintf("UNUSED-%s", *m.Base))
+				if m.provider.MkAppClass == "NATIVE_TESTS" {
+					m.nativeExtraEntries(entries)
+				} else {
+					m.javaExtraEntries(entries)
+				}
 
 				// In normal java/app modules, the module writes LOCAL_COMPATIBILITY_SUPPORT_FILES
 				// and then base_rules.mk ends up copying each of those dependencies from .intermediates to the install directory.
@@ -357,16 +369,19 @@
 	// FrameworksServicesTests
 	// └── x86_64
 	//    └── FrameworksServicesTests.apk
-	symlinkName := fmt.Sprintf("%s/%s", ctx.DeviceConfig().DeviceArch(), baseApk.Base())
-	// Only android_test, not java_host_test puts the output in the DeviceArch dir.
-	if m.provider.IsHost || ctx.DeviceConfig().DeviceArch() == "" {
-		// testcases/CtsDevicePolicyManagerTestCases
-		// ├── CtsDevicePolicyManagerTestCases.jar
-		symlinkName = baseApk.Base()
+	if m.provider.MkAppClass != "NATIVE_TESTS" {
+		symlinkName := fmt.Sprintf("%s/%s", ctx.DeviceConfig().DeviceArch(), baseApk.Base())
+		// Only android_test, not java_host_test puts the output in the DeviceArch dir.
+		if m.provider.IsHost || ctx.DeviceConfig().DeviceArch() == "" {
+			// testcases/CtsDevicePolicyManagerTestCases
+			// ├── CtsDevicePolicyManagerTestCases.jar
+			symlinkName = baseApk.Base()
+		}
+
+		target := installedBaseRelativeToHere(symlinkName, *m.tradefedProperties.Base)
+		installedApk := ctx.InstallAbsoluteSymlink(installDir, symlinkName, target)
+		m.supportFiles = append(m.supportFiles, installedApk)
 	}
-	target := installedBaseRelativeToHere(symlinkName, *m.tradefedProperties.Base)
-	installedApk := ctx.InstallAbsoluteSymlink(installDir, symlinkName, target)
-	m.supportFiles = append(m.supportFiles, installedApk)
 
 	// 3) Symlink for all data deps
 	// And like this for data files and required modules
@@ -374,8 +389,7 @@
 	// ├── data
 	// │   └── broken_shortcut.xml
 	// ├── JobTestApp.apk
-	for _, f := range m.provider.InstalledFiles {
-		symlinkName := f.Rel()
+	for _, symlinkName := range m.provider.TestcaseRelDataFiles {
 		target := installedBaseRelativeToHere(symlinkName, *m.tradefedProperties.Base)
 		installedPath := ctx.InstallAbsoluteSymlink(installDir, symlinkName, target)
 		m.supportFiles = append(m.supportFiles, installedPath)
@@ -383,10 +397,31 @@
 
 	// 4) Module.config / AndroidTest.xml
 	m.testConfig = m.fixTestConfig(ctx, m.provider.TestConfig)
+
+	// 5) We provide so we can be listed in test_suites.
+	android.SetProvider(ctx, tradefed.BaseTestProviderKey, tradefed.BaseTestProviderData{
+		TestcaseRelDataFiles:    testcaseRel(m.supportFiles.Paths()),
+		OutputFile:              baseApk,
+		TestConfig:              m.testConfig,
+		HostRequiredModuleNames: m.provider.HostRequiredModuleNames,
+		RequiredModuleNames:     m.provider.RequiredModuleNames,
+		TestSuites:              m.tradefedProperties.Test_suites,
+		IsHost:                  m.provider.IsHost,
+		LocalCertificate:        m.provider.LocalCertificate,
+		IsUnitTest:              m.provider.IsUnitTest,
+	})
 }
 
 var _ android.AndroidMkEntriesProvider = (*testModuleConfigHostModule)(nil)
 
+func testcaseRel(paths android.Paths) []string {
+	relPaths := []string{}
+	for _, p := range paths {
+		relPaths = append(relPaths, p.Rel())
+	}
+	return relPaths
+}
+
 // Given a relative path to a file in the current directory or a subdirectory,
 // return a relative path under our sibling directory named `base`.
 // There should be one "../" for each subdir we descend plus one to backup to "base".
diff --git a/tradefed_modules/test_module_config_test.go b/tradefed_modules/test_module_config_test.go
index cf6c7d1..efd4a04 100644
--- a/tradefed_modules/test_module_config_test.go
+++ b/tradefed_modules/test_module_config_test.go
@@ -16,6 +16,7 @@
 import (
 	"android/soong/android"
 	"android/soong/java"
+	"android/soong/sh"
 	"fmt"
 	"strconv"
 	"strings"
@@ -54,6 +55,8 @@
 
 `
 
+const variant = "android_arm64_armv8-a"
+
 // Ensure we create files needed and set the AndroidMkEntries needed
 func TestModuleConfigAndroidTest(t *testing.T) {
 
@@ -62,7 +65,7 @@
 		android.FixtureRegisterWithContext(RegisterTestModuleConfigBuildComponents),
 	).RunTestWithBp(t, bp)
 
-	derived := ctx.ModuleForTests("derived_test", "android_common")
+	derived := ctx.ModuleForTests("derived_test", variant)
 	// Assert there are rules to create these files.
 	derived.Output("test_module_config.manifest")
 	derived.Output("test_config_fixer/derived_test.config")
@@ -88,7 +91,7 @@
 	// And some new derived entries are there.
 	android.AssertArrayString(t, "", entries.EntryMap["LOCAL_MODULE_TAGS"], []string{"tests"})
 
-	android.AssertStringMatches(t, "", entries.EntryMap["LOCAL_FULL_TEST_CONFIG"][0], "derived_test/android_common/test_config_fixer/derived_test.config")
+	android.AssertStringMatches(t, "", entries.EntryMap["LOCAL_FULL_TEST_CONFIG"][0], fmt.Sprintf("derived_test/%s/test_config_fixer/derived_test.config", variant))
 
 	// Check the footer lines.  Our support files should depend on base's support files.
 	convertedActual := make([]string, 5)
@@ -105,6 +108,80 @@
 	})
 }
 
+func TestModuleConfigShTest(t *testing.T) {
+	ctx := android.GroupFixturePreparers(
+		sh.PrepareForTestWithShBuildComponents,
+		android.PrepareForTestWithAndroidBuildComponents,
+		android.FixtureMergeMockFs(android.MockFS{
+			"test.sh":            nil,
+			"testdata/data1":     nil,
+			"testdata/sub/data2": nil,
+		}),
+		android.FixtureRegisterWithContext(RegisterTestModuleConfigBuildComponents),
+	).RunTestWithBp(t, `
+		sh_test {
+			name: "shell_test",
+			src: "test.sh",
+			filename: "test.sh",
+                        test_suites: ["general-tests"],
+			data: [
+				"testdata/data1",
+				"testdata/sub/data2",
+			],
+		}
+                test_module_config {
+                        name: "conch",
+                        base: "shell_test",
+                        test_suites: ["general-tests"],
+                        options: [{name: "SomeName", value: "OptionValue"}],
+                }
+         `)
+	derived := ctx.ModuleForTests("conch", variant) //
+	conch := derived.Module().(*testModuleConfigModule)
+	android.AssertArrayString(t, "TestcaseRelDataFiles", []string{"arm64/testdata/data1", "arm64/testdata/sub/data2"}, conch.provider.TestcaseRelDataFiles)
+	android.AssertStringEquals(t, "Rel OutputFile", "test.sh", conch.provider.OutputFile.Rel())
+
+	// Assert there are rules to create these files.
+	derived.Output("test_module_config.manifest")
+	derived.Output("test_config_fixer/conch.config")
+
+	// Ensure some basic rules exist.
+	entries := android.AndroidMkEntriesForTest(t, ctx.TestContext, derived.Module())[0]
+
+	// Ensure some entries from base are there, specifically support files for data and helper apps.
+	// Do not use LOCAL_COMPATIBILITY_SUPPORT_FILES, but instead use LOCAL_SOONG_INSTALLED_COMPATIBILITY_SUPPORT_FILES
+	android.AssertStringPathsRelativeToTopEquals(t, "support-files", ctx.Config,
+		[]string{"out/soong/target/product/test_device/testcases/conch/arm64/testdata/data1",
+			"out/soong/target/product/test_device/testcases/conch/arm64/testdata/sub/data2"},
+		entries.EntryMap["LOCAL_SOONG_INSTALLED_COMPATIBILITY_SUPPORT_FILES"])
+	android.AssertArrayString(t, "", entries.EntryMap["LOCAL_COMPATIBILITY_SUPPORT_FILES"], []string{})
+
+	android.AssertStringEquals(t, "app class", "NATIVE_TESTS", entries.Class)
+	android.AssertArrayString(t, "required modules", []string{"shell_test"}, entries.EntryMap["LOCAL_REQUIRED_MODULES"])
+	android.AssertArrayString(t, "host required modules", []string{}, entries.EntryMap["LOCAL_HOST_REQUIRED_MODULES"])
+	android.AssertArrayString(t, "cert", []string{}, entries.EntryMap["LOCAL_CERTIFICATE"])
+
+	// And some new derived entries are there.
+	android.AssertArrayString(t, "tags", []string{}, entries.EntryMap["LOCAL_MODULE_TAGS"])
+
+	android.AssertStringMatches(t, "", entries.EntryMap["LOCAL_FULL_TEST_CONFIG"][0],
+		fmt.Sprintf("conch/%s/test_config_fixer/conch.config", variant))
+
+	// Check the footer lines.  Our support files should depend on base's support files.
+	convertedActual := make([]string, 4)
+	for i, e := range entries.FooterLinesForTests() {
+		// AssertStringPathsRelativeToTop doesn't replace both instances
+		convertedActual[i] = strings.Replace(e, ctx.Config.SoongOutDir(), "", 2)
+	}
+	android.AssertArrayString(t, fmt.Sprintf("%s", ctx.Config.SoongOutDir()), convertedActual, []string{
+		"include $(BUILD_SYSTEM)/soong_cc_rust_prebuilt.mk",
+		"/target/product/test_device/testcases/conch/arm64/testdata/data1: /target/product/test_device/testcases/shell_test/arm64/testdata/data1",
+		"/target/product/test_device/testcases/conch/arm64/testdata/sub/data2: /target/product/test_device/testcases/shell_test/arm64/testdata/sub/data2",
+		"",
+	})
+
+}
+
 // Make sure we call test-config-fixer with the right args.
 func TestModuleConfigOptions(t *testing.T) {
 
@@ -114,7 +191,7 @@
 	).RunTestWithBp(t, bp)
 
 	// Check that we generate a rule to make a new AndroidTest.xml/Module.config file.
-	derived := ctx.ModuleForTests("derived_test", "android_common")
+	derived := ctx.ModuleForTests("derived_test", variant)
 	rule_cmd := derived.Rule("fix_test_config").RuleParams.Command
 	android.AssertStringDoesContain(t, "Bad FixConfig rule inputs", rule_cmd,
 		`--test-runner-options='[{"Name":"exclude-filter","Key":"","Value":"android.test.example.devcodelab.DevCodelabTest#testHelloFail"},{"Name":"include-annotation","Key":"","Value":"android.platform.test.annotations.LargeTest"}]'`)
@@ -211,8 +288,7 @@
 	).ExtendWithErrorHandler(
 		android.FixtureExpectsAtLeastOneErrorMatchingPattern("Test options must be given")).
 		RunTestWithBp(t, badBp)
-
-	ctx.ModuleForTests("derived_test", "android_common")
+	ctx.ModuleForTests("derived_test", variant)
 }
 
 func TestModuleConfigMultipleDerivedTestsWriteDistinctMakeEntries(t *testing.T) {
@@ -250,7 +326,7 @@
 	).RunTestWithBp(t, multiBp)
 
 	{
-		derived := ctx.ModuleForTests("derived_test", "android_common")
+		derived := ctx.ModuleForTests("derived_test", variant)
 		entries := android.AndroidMkEntriesForTest(t, ctx.TestContext, derived.Module())[0]
 		// All these should be the same in both derived tests
 		android.AssertStringPathsRelativeToTopEquals(t, "support-files", ctx.Config,
@@ -260,13 +336,13 @@
 			entries.EntryMap["LOCAL_SOONG_INSTALLED_COMPATIBILITY_SUPPORT_FILES"])
 
 		// Except this one, which points to the updated tradefed xml file.
-		android.AssertStringMatches(t, "", entries.EntryMap["LOCAL_FULL_TEST_CONFIG"][0], "derived_test/android_common/test_config_fixer/derived_test.config")
+		android.AssertStringMatches(t, "", entries.EntryMap["LOCAL_FULL_TEST_CONFIG"][0], fmt.Sprintf("derived_test/%s/test_config_fixer/derived_test.config", variant))
 		// And this one, the module name.
 		android.AssertArrayString(t, "", entries.EntryMap["LOCAL_MODULE"], []string{"derived_test"})
 	}
 
 	{
-		derived := ctx.ModuleForTests("another_derived_test", "android_common")
+		derived := ctx.ModuleForTests("another_derived_test", variant)
 		entries := android.AndroidMkEntriesForTest(t, ctx.TestContext, derived.Module())[0]
 		// All these should be the same in both derived tests
 		android.AssertStringPathsRelativeToTopEquals(t, "support-files", ctx.Config,
@@ -275,7 +351,8 @@
 				"out/soong/target/product/test_device/testcases/another_derived_test/data/testfile"},
 			entries.EntryMap["LOCAL_SOONG_INSTALLED_COMPATIBILITY_SUPPORT_FILES"])
 		// Except this one, which points to the updated tradefed xml file.
-		android.AssertStringMatches(t, "", entries.EntryMap["LOCAL_FULL_TEST_CONFIG"][0], "another_derived_test/android_common/test_config_fixer/another_derived_test.config")
+		android.AssertStringMatches(t, "", entries.EntryMap["LOCAL_FULL_TEST_CONFIG"][0],
+			fmt.Sprintf("another_derived_test/%s/test_config_fixer/another_derived_test.config", variant))
 		// And this one, the module name.
 		android.AssertArrayString(t, "", entries.EntryMap["LOCAL_MODULE"], []string{"another_derived_test"})
 	}
diff --git a/tradefed_modules/test_suite.go b/tradefed_modules/test_suite.go
new file mode 100644
index 0000000..00585f5
--- /dev/null
+++ b/tradefed_modules/test_suite.go
@@ -0,0 +1,173 @@
+// 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 tradefed_modules
+
+import (
+	"encoding/json"
+	"path"
+	"path/filepath"
+
+	"android/soong/android"
+	"android/soong/tradefed"
+	"github.com/google/blueprint"
+)
+
+const testSuiteModuleType = "test_suite"
+
+type testSuiteTag struct{
+	blueprint.BaseDependencyTag
+}
+
+type testSuiteManifest struct {
+	Name  string `json:"name"`
+	Files []string `json:"files"`
+}
+
+func init() {
+	RegisterTestSuiteBuildComponents(android.InitRegistrationContext)
+}
+
+func RegisterTestSuiteBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType(testSuiteModuleType, TestSuiteFactory)
+}
+
+var PrepareForTestWithTestSuiteBuildComponents = android.GroupFixturePreparers(
+	android.FixtureRegisterWithContext(RegisterTestSuiteBuildComponents),
+)
+
+type testSuiteProperties struct {
+	Description string
+	Tests []string `android:"path,arch_variant"`
+}
+
+type testSuiteModule struct {
+	android.ModuleBase
+	android.DefaultableModuleBase
+	testSuiteProperties
+}
+
+func (t *testSuiteModule) DepsMutator(ctx android.BottomUpMutatorContext) {
+	for _, test := range t.Tests {
+		if ctx.OtherModuleDependencyVariantExists(ctx.Config().BuildOSCommonTarget.Variations(), test) {
+			// Host tests.
+			ctx.AddVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), testSuiteTag{}, test)
+		} else {
+			// Target tests.
+			ctx.AddDependency(ctx.Module(), testSuiteTag{}, test)
+		}
+	}
+}
+
+func (t *testSuiteModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	suiteName := ctx.ModuleName()
+	modulesByName := make(map[string]android.Module)
+	ctx.WalkDeps(func(child, parent android.Module) bool {
+		// Recurse into test_suite dependencies.
+		if ctx.OtherModuleType(child) == testSuiteModuleType {
+			ctx.Phony(suiteName, android.PathForPhony(ctx, child.Name()))
+			return true
+		}
+
+		// Only write out top level test suite dependencies here.
+		if _, ok := ctx.OtherModuleDependencyTag(child).(testSuiteTag); !ok {
+			return false
+		}
+
+		if !child.InstallInTestcases() {
+			ctx.ModuleErrorf("test_suite only supports modules installed in testcases. %q is not installed in testcases.", child.Name())
+			return false
+		}
+
+		modulesByName[child.Name()] = child
+		return false
+	})
+
+	var files []string
+	for name, module := range modulesByName {
+		// Get the test provider data from the child.
+		tp, ok := android.OtherModuleProvider(ctx, module, tradefed.BaseTestProviderKey)
+		if !ok {
+			// TODO: Consider printing out a list of all module types.
+			ctx.ModuleErrorf("%q is not a test module.", name)
+			continue
+		}
+
+		files = append(files, packageModuleFiles(ctx, suiteName, module, tp)...)
+		ctx.Phony(suiteName, android.PathForPhony(ctx, name))
+	}
+
+	manifestPath := android.PathForSuiteInstall(ctx, suiteName, suiteName+".json")
+	b, err := json.Marshal(testSuiteManifest{Name: suiteName, Files: files})
+	if err != nil {
+		ctx.ModuleErrorf("Failed to marshal manifest: %v", err)
+		return
+	}
+	android.WriteFileRule(ctx, manifestPath, string(b))
+
+	ctx.Phony(suiteName, manifestPath)
+}
+
+func TestSuiteFactory() android.Module {
+	module := &testSuiteModule{}
+	module.AddProperties(&module.testSuiteProperties)
+
+	android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+
+	return module
+}
+
+func packageModuleFiles(ctx android.ModuleContext, suiteName string, module android.Module, tp tradefed.BaseTestProviderData) []string {
+
+	hostOrTarget := "target"
+	if tp.IsHost {
+		hostOrTarget = "host"
+	}
+
+	// suiteRoot at out/soong/packaging/<suiteName>.
+	suiteRoot := android.PathForSuiteInstall(ctx, suiteName)
+
+	var installed android.InstallPaths
+	// Install links to installed files from the module.
+	if installFilesInfo, ok := android.OtherModuleProvider(ctx, module, android.InstallFilesProvider); ok {
+		for _, f := range installFilesInfo.InstallFiles {
+			// rel is anything under .../<partition>, normally under .../testcases.
+			rel := android.Rel(ctx, f.PartitionDir(), f.String())
+
+			// Install the file under <suiteRoot>/<host|target>/<partition>.
+			installDir := suiteRoot.Join(ctx, hostOrTarget, f.Partition(), path.Dir(rel))
+			linkTo, err := filepath.Rel(installDir.String(), f.String())
+			if err != nil {
+				ctx.ModuleErrorf("Failed to get relative path from %s to %s: %v", installDir.String(), f.String(), err)
+				continue
+			}
+			installed = append(installed, ctx.InstallAbsoluteSymlink(installDir, path.Base(rel), linkTo))
+		}
+	}
+
+	// Install config file.
+	if tp.TestConfig != nil {
+		moduleRoot := suiteRoot.Join(ctx, hostOrTarget, "testcases", module.Name())
+		installed = append(installed, ctx.InstallFile(moduleRoot, module.Name() + ".config", tp.TestConfig))
+	}
+
+	// Add to phony and manifest, manifestpaths are relative to suiteRoot.
+	var manifestEntries []string
+	for _, f := range installed {
+		manifestEntries = append(manifestEntries, android.Rel(ctx, suiteRoot.String(), f.String()))
+		ctx.Phony(suiteName, f)
+	}
+	return manifestEntries
+}
diff --git a/tradefed_modules/test_suite_test.go b/tradefed_modules/test_suite_test.go
new file mode 100644
index 0000000..3c0a9eb
--- /dev/null
+++ b/tradefed_modules/test_suite_test.go
@@ -0,0 +1,151 @@
+// 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 tradefed_modules
+
+import (
+	"android/soong/android"
+	"android/soong/java"
+	"encoding/json"
+	"slices"
+	"testing"
+)
+
+func TestTestSuites(t *testing.T) {
+	t.Parallel()
+	ctx := android.GroupFixturePreparers(
+		java.PrepareForTestWithJavaDefaultModules,
+		android.FixtureRegisterWithContext(RegisterTestSuiteBuildComponents),
+	).RunTestWithBp(t, `
+		android_test {
+			name: "TestModule1",
+			sdk_version: "current",
+		}
+
+		android_test {
+			name: "TestModule2",
+			sdk_version: "current",
+		}
+
+		test_suite {
+			name: "my-suite",
+			description: "a test suite",
+			tests: [
+				"TestModule1",
+				"TestModule2",
+			]
+		}
+	`)
+	manifestPath := ctx.ModuleForTests("my-suite", "android_common").Output("out/soong/test_suites/my-suite/my-suite.json")
+	var actual testSuiteManifest
+	if err := json.Unmarshal([]byte(android.ContentFromFileRuleForTests(t, ctx.TestContext, manifestPath)), &actual); err != nil {
+		t.Errorf("failed to unmarshal manifest: %v", err)
+	}
+	slices.Sort(actual.Files)
+
+	expected := testSuiteManifest{
+		Name: "my-suite",
+		Files: []string{
+			"target/testcases/TestModule1/TestModule1.config",
+			"target/testcases/TestModule1/arm64/TestModule1.apk",
+			"target/testcases/TestModule2/TestModule2.config",
+			"target/testcases/TestModule2/arm64/TestModule2.apk",
+		},
+	}
+
+	android.AssertDeepEquals(t, "manifests differ", expected, actual)
+}
+
+func TestTestSuitesWithNested(t *testing.T) {
+	t.Parallel()
+	ctx := android.GroupFixturePreparers(
+		java.PrepareForTestWithJavaDefaultModules,
+		android.FixtureRegisterWithContext(RegisterTestSuiteBuildComponents),
+	).RunTestWithBp(t, `
+		android_test {
+			name: "TestModule1",
+			sdk_version: "current",
+		}
+
+		android_test {
+			name: "TestModule2",
+			sdk_version: "current",
+		}
+
+		android_test {
+			name: "TestModule3",
+			sdk_version: "current",
+		}
+
+		test_suite {
+			name: "my-child-suite",
+			description: "a child test suite",
+			tests: [
+				"TestModule1",
+				"TestModule2",
+			]
+		}
+
+		test_suite {
+			name: "my-all-tests-suite",
+			description: "a parent test suite",
+			tests: [
+				"TestModule1",
+				"TestModule3",
+				"my-child-suite",
+			]
+		}
+	`)
+	manifestPath := ctx.ModuleForTests("my-all-tests-suite", "android_common").Output("out/soong/test_suites/my-all-tests-suite/my-all-tests-suite.json")
+	var actual testSuiteManifest
+	if err := json.Unmarshal([]byte(android.ContentFromFileRuleForTests(t, ctx.TestContext, manifestPath)), &actual); err != nil {
+		t.Errorf("failed to unmarshal manifest: %v", err)
+	}
+	slices.Sort(actual.Files)
+
+	expected := testSuiteManifest{
+		Name: "my-all-tests-suite",
+		Files: []string{
+			"target/testcases/TestModule1/TestModule1.config",
+			"target/testcases/TestModule1/arm64/TestModule1.apk",
+			"target/testcases/TestModule2/TestModule2.config",
+			"target/testcases/TestModule2/arm64/TestModule2.apk",
+			"target/testcases/TestModule3/TestModule3.config",
+			"target/testcases/TestModule3/arm64/TestModule3.apk",
+		},
+	}
+
+	android.AssertDeepEquals(t, "manifests differ", expected, actual)
+}
+
+func TestTestSuitesNotInstalledInTestcases(t *testing.T) {
+	t.Parallel()
+	android.GroupFixturePreparers(
+		java.PrepareForTestWithJavaDefaultModules,
+		android.FixtureRegisterWithContext(RegisterTestSuiteBuildComponents),
+	).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern([]string{
+		`"SomeHostTest" is not installed in testcases`,
+	})).RunTestWithBp(t, `
+			java_test_host {
+				name: "SomeHostTest",
+				srcs: ["a.java"],
+			}
+			test_suite {
+				name: "my-suite",
+				description: "a test suite",
+				tests: [
+					"SomeHostTest",
+				]
+			}
+	`)
+}
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,
+    },
+}