Merge "Revert^2 "cc_baremetal_defaults: Disable SVE"" into main
diff --git a/Android.bp b/Android.bp
index 6db4963..523f55c 100644
--- a/Android.bp
+++ b/Android.bp
@@ -164,6 +164,7 @@
     name: "product_config",
     visibility: [
         "//build/make/target/product/generic",
+        "//build/soong/fsgen",
     ],
 }
 
@@ -171,10 +172,15 @@
     name: "system-build.prop",
     stem: "build.prop",
     product_config: ":product_config",
-    // Currently, only microdroid and cf system image can refer to system-build.prop
+    footer_files: [
+        ":applied_backported_fixes",
+    ],
+    // Currently, only microdroid, Ravenwood, and cf system image can refer to system-build.prop
     visibility: [
-        "//build/make/target/product/generic",
+        "//build/soong/fsgen",
         "//packages/modules/Virtualization/build/microdroid",
+        "//frameworks/base/ravenwood",
+        "//visibility:any_system_partition",
     ],
 }
 
@@ -184,7 +190,10 @@
     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/soong/fsgen",
+    ],
 }
 
 build_prop {
@@ -193,7 +202,10 @@
     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/soong/fsgen",
+    ],
 }
 
 build_prop {
@@ -202,5 +214,49 @@
     device_specific: true,
     product_config: ":product_config",
     relative_install_path: "etc", // odm/etc/build.prop
+    visibility: ["//build/soong/fsgen"],
+}
+
+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"],
+}
+
+build_prop {
+    name: "ramdisk-build.prop",
+    stem: "build.prop",
+    ramdisk: true,
+    product_config: ":product_config",
+    relative_install_path: "etc/ramdisk", // ramdisk/system/etc/ramdisk/build.prop
+    visibility: ["//visibility:private"],
+}
+
+all_apex_certs {
+    name: "all_apex_certs",
+    visibility: [
+        "//cts/tests/tests/security",
+        "//cts/hostsidetests/appsecurity",
+    ],
+}
diff --git a/README.md b/README.md
index ad282a5..6d1a2c5 100644
--- a/README.md
+++ b/README.md
@@ -673,8 +673,7 @@
 SOONG_DELVE=2345 SOONG_DELVE_STEPS='build,modulegraph' m
 ```
 results in only `build` (main build step) and `modulegraph` being run in the debugger.
-The allowed step names are `bp2build_files`, `bp2build_workspace`, `build`,
-`modulegraph`, `queryview`, `soong_docs`.
+The allowed step names are `build`, `soong_docs`.
 
 Note setting or unsetting `SOONG_DELVE` causes a recompilation of `soong_build`. This
 is because in order to debug the binary, it needs to be built with debug
diff --git a/aconfig/Android.bp b/aconfig/Android.bp
index 402cf16..33e04a4 100644
--- a/aconfig/Android.bp
+++ b/aconfig/Android.bp
@@ -17,6 +17,7 @@
         "aconfig_values.go",
         "aconfig_value_set.go",
         "all_aconfig_declarations.go",
+        "all_aconfig_declarations_extension.go",
         "exported_java_aconfig_library.go",
         "init.go",
         "testing.go",
@@ -25,7 +26,24 @@
         "aconfig_declarations_test.go",
         "aconfig_values_test.go",
         "aconfig_value_set_test.go",
-        "all_aconfig_declarations_test.go",
+        "all_aconfig_declarations_extension_test.go",
     ],
     pluginFor: ["soong_build"],
 }
+
+// All FlaggedApi flags associated with platform API.
+// By default this uses the platform APIs associated with android.jar
+// but other verticals/platforms can override via soong config setting.
+all_aconfig_declarations {
+    name: "all_aconfig_declarations",
+    visibility: [
+        "//vendor:__subpackages__", // for vendor extensions
+    ],
+    api_signature_files: [
+        ":frameworks-base-api-current.txt",
+        ":frameworks-base-api-system-current.txt",
+        ":frameworks-base-api-system-server-current.txt",
+        ":frameworks-base-api-module-lib-current.txt",
+    ],
+    finalized_flags_file: ":latest-finalized-flags",
+}
diff --git a/aconfig/aconfig_declarations.go b/aconfig/aconfig_declarations.go
index d9a862c..9a9e568 100644
--- a/aconfig/aconfig_declarations.go
+++ b/aconfig/aconfig_declarations.go
@@ -15,12 +15,12 @@
 package aconfig
 
 import (
+	"android/soong/android"
 	"path/filepath"
 	"slices"
+	"strconv"
 	"strings"
 
-	"android/soong/android"
-
 	"github.com/google/blueprint"
 )
 
@@ -185,6 +185,13 @@
 				defaultPermission = confPerm
 			}
 		}
+		var allowReadWrite bool
+		if requireAllReadOnly, ok := ctx.Config().GetBuildFlag("RELEASE_ACONFIG_REQUIRE_ALL_READ_ONLY"); ok {
+			// The build flag (RELEASE_ACONFIG_REQUIRE_ALL_READ_ONLY) is the negation of the aconfig flag
+			// (allow-read-write) for historical reasons.
+			// Bool build flags are always "" for false, and generally "true" for true.
+			allowReadWrite = requireAllReadOnly == ""
+		}
 		inputFiles := make([]android.Path, len(declarationFiles))
 		copy(inputFiles, declarationFiles)
 		inputFiles = append(inputFiles, valuesFiles[config]...)
@@ -194,6 +201,7 @@
 			"declarations":       android.JoinPathsWithPrefix(declarationFiles, "--declarations "),
 			"values":             joinAndPrefix(" --values ", values[config]),
 			"default-permission": optionalVariable(" --default-permission ", defaultPermission),
+			"allow-read-write":   optionalVariable(" --allow-read-write ", strconv.FormatBool(allowReadWrite)),
 		}
 		if len(module.properties.Container) > 0 {
 			args["container"] = "--container " + module.properties.Container
diff --git a/aconfig/aconfig_declarations_test.go b/aconfig/aconfig_declarations_test.go
index e89cd31..c39008b 100644
--- a/aconfig/aconfig_declarations_test.go
+++ b/aconfig/aconfig_declarations_test.go
@@ -37,7 +37,7 @@
 	`
 	result := runTest(t, android.FixtureExpectsNoErrors, bp)
 
-	module := result.ModuleForTests("module_name", "").Module().(*DeclarationsModule)
+	module := result.ModuleForTests(t, "module_name", "").Module().(*DeclarationsModule)
 
 	// Check that the provider has the right contents
 	depData, _ := android.OtherModuleProvider(result, module, android.AconfigDeclarationsProviderKey)
@@ -66,7 +66,7 @@
 	`
 	result := runTest(t, android.FixtureExpectsNoErrors, bp)
 
-	module := result.ModuleForTests("module_name", "").Module().(*DeclarationsModule)
+	module := result.ModuleForTests(t, "module_name", "").Module().(*DeclarationsModule)
 	depData, _ := android.OtherModuleProvider(result, module, android.AconfigDeclarationsProviderKey)
 	android.AssertBoolEquals(t, "exportable", depData.Exportable, false)
 }
@@ -84,7 +84,7 @@
 	`
 	result := runTest(t, android.FixtureExpectsNoErrors, bp)
 
-	module := result.ModuleForTests("module_name", "")
+	module := result.ModuleForTests(t, "module_name", "")
 	rule := module.Rule("aconfig")
 	android.AssertStringEquals(t, "rule must contain container", rule.Args["container"], "--container com.android.foo")
 }
@@ -204,7 +204,7 @@
 			fixture = fixture.ExtendWithErrorHandler(test.errorHandler)
 		}
 		result := fixture.RunTestWithBp(t, test.bp)
-		module := result.ModuleForTests("module_name", "").Module().(*DeclarationsModule)
+		module := result.ModuleForTests(t, "module_name", "").Module().(*DeclarationsModule)
 		depData, _ := android.OtherModuleProvider(result, module, android.AconfigReleaseDeclarationsProviderKey)
 		expectedKeys := []string{""}
 		for _, rc := range strings.Split(test.buildFlags["RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS"], " ") {
diff --git a/aconfig/aconfig_value_set_test.go b/aconfig/aconfig_value_set_test.go
index 3b7281e..1f04244 100644
--- a/aconfig/aconfig_value_set_test.go
+++ b/aconfig/aconfig_value_set_test.go
@@ -37,7 +37,7 @@
 			`
 	result := runTest(t, android.FixtureExpectsNoErrors, bp)
 
-	module := result.ModuleForTests("module_name", "").Module().(*ValueSetModule)
+	module := result.ModuleForTests(t, "module_name", "").Module().(*ValueSetModule)
 
 	// Check that the provider has the right contents
 	depData, _ := android.OtherModuleProvider(result, module, valueSetProviderKey)
@@ -88,7 +88,7 @@
 
 	checkModuleHasDependency := func(name, variant, dep string) bool {
 		t.Helper()
-		module := result.ModuleForTests(name, variant).Module()
+		module := result.ModuleForTests(t, name, variant).Module()
 		depFound := false
 		result.VisitDirectDeps(module, func(m blueprint.Module) {
 			if m.Name() == dep {
diff --git a/aconfig/aconfig_values_test.go b/aconfig/aconfig_values_test.go
index ddbea57..0bec14b 100644
--- a/aconfig/aconfig_values_test.go
+++ b/aconfig/aconfig_values_test.go
@@ -30,7 +30,7 @@
 			`
 	result := runTest(t, android.FixtureExpectsNoErrors, bp)
 
-	module := result.ModuleForTests("module_name", "").Module().(*ValuesModule)
+	module := result.ModuleForTests(t, "module_name", "").Module().(*ValuesModule)
 
 	// Check that the provider has the right contents
 	depData, _ := android.OtherModuleProvider(result, module, valuesProviderKey)
diff --git a/aconfig/all_aconfig_declarations.go b/aconfig/all_aconfig_declarations.go
index 6ad54da..3d07e16 100644
--- a/aconfig/all_aconfig_declarations.go
+++ b/aconfig/all_aconfig_declarations.go
@@ -19,6 +19,9 @@
 	"slices"
 
 	"android/soong/android"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
 )
 
 // A singleton module that collects all of the aconfig flags declared in the
@@ -28,17 +31,36 @@
 // Note that this is ALL aconfig_declarations modules present in the tree, not just
 // ones that are relevant to the product currently being built, so that that infra
 // doesn't need to pull from multiple builds and merge them.
-func AllAconfigDeclarationsFactory() android.Singleton {
-	return &allAconfigDeclarationsSingleton{releaseMap: make(map[string]allAconfigReleaseDeclarationsSingleton)}
+func AllAconfigDeclarationsFactory() android.SingletonModule {
+	module := &allAconfigDeclarationsSingleton{releaseMap: make(map[string]allAconfigReleaseDeclarationsSingleton)}
+	module.AddProperties(&module.properties)
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	return module
 }
 
+type allAconfigDeclarationsInfo struct {
+	parsedFlagsFile android.Path
+}
+
+var allAconfigDeclarationsInfoProvider = blueprint.NewProvider[allAconfigDeclarationsInfo]()
+
 type allAconfigReleaseDeclarationsSingleton struct {
 	intermediateBinaryProtoPath android.OutputPath
 	intermediateTextProtoPath   android.OutputPath
 }
 
+type ApiSurfaceContributorProperties struct {
+	Api_signature_files  proptools.Configurable[[]string] `android:"arch_variant,path"`
+	Finalized_flags_file string                           `android:"arch_variant,path"`
+}
+
 type allAconfigDeclarationsSingleton struct {
+	android.SingletonModuleBase
+
 	releaseMap map[string]allAconfigReleaseDeclarationsSingleton
+	properties ApiSurfaceContributorProperties
+
+	finalizedFlags android.OutputPath
 }
 
 func (this *allAconfigDeclarationsSingleton) sortedConfigNames() []string {
@@ -50,7 +72,41 @@
 	return names
 }
 
-func (this *allAconfigDeclarationsSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+func GenerateFinalizedFlagsForApiSurface(ctx android.ModuleContext, outputPath android.WritablePath,
+	parsedFlagsFile android.Path, apiSurface ApiSurfaceContributorProperties) {
+
+	apiSignatureFiles := android.Paths{}
+	for _, apiSignatureFile := range apiSurface.Api_signature_files.GetOrDefault(ctx, nil) {
+		if path := android.PathForModuleSrc(ctx, apiSignatureFile); path != nil {
+			apiSignatureFiles = append(apiSignatureFiles, path)
+		}
+	}
+	finalizedFlagsFile := android.PathForModuleSrc(ctx, apiSurface.Finalized_flags_file)
+
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   RecordFinalizedFlagsRule,
+		Inputs: append(apiSignatureFiles, finalizedFlagsFile, parsedFlagsFile),
+		Output: outputPath,
+		Args: map[string]string{
+			"api_signature_files":  android.JoinPathsWithPrefix(apiSignatureFiles, "--api-signature-file "),
+			"finalized_flags_file": "--finalized-flags-file " + finalizedFlagsFile.String(),
+			"parsed_flags_file":    "--parsed-flags-file " + parsedFlagsFile.String(),
+		},
+	})
+}
+
+func (this *allAconfigDeclarationsSingleton) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	parsedFlagsFile := android.PathForIntermediates(ctx, "all_aconfig_declarations.pb")
+	this.finalizedFlags = android.PathForIntermediates(ctx, "finalized-flags.txt")
+	GenerateFinalizedFlagsForApiSurface(ctx, this.finalizedFlags, parsedFlagsFile, this.properties)
+	ctx.Phony("all_aconfig_declarations", this.finalizedFlags)
+
+	android.SetProvider(ctx, allAconfigDeclarationsInfoProvider, allAconfigDeclarationsInfo{
+		parsedFlagsFile: parsedFlagsFile,
+	})
+}
+
+func (this *allAconfigDeclarationsSingleton) GenerateSingletonBuildActions(ctx android.SingletonContext) {
 	for _, rcName := range append([]string{""}, ctx.Config().ReleaseAconfigExtraReleaseConfigs()...) {
 		// Find all of the aconfig_declarations modules
 		var packages = make(map[string]int)
@@ -65,15 +121,16 @@
 		})
 
 		var numOffendingPkg = 0
+		offendingPkgsMessage := ""
 		for pkg, cnt := range packages {
 			if cnt > 1 {
-				fmt.Printf("%d aconfig_declarations found for package %s\n", cnt, pkg)
+				offendingPkgsMessage += fmt.Sprintf("%d aconfig_declarations found for package %s\n", cnt, pkg)
 				numOffendingPkg++
 			}
 		}
 
 		if numOffendingPkg > 0 {
-			panic(fmt.Errorf("Only one aconfig_declarations allowed for each package."))
+			panic("Only one aconfig_declarations allowed for each package.\n" + offendingPkgsMessage)
 		}
 
 		// Generate build action for aconfig (binary proto output)
@@ -105,9 +162,7 @@
 		})
 		ctx.Phony("all_aconfig_declarations_textproto", this.releaseMap[rcName].intermediateTextProtoPath)
 	}
-}
 
-func (this *allAconfigDeclarationsSingleton) MakeVars(ctx android.MakeVarsContext) {
 	for _, rcName := range this.sortedConfigNames() {
 		ctx.DistForGoal("droid", this.releaseMap[rcName].intermediateBinaryProtoPath)
 		for _, goal := range []string{"docs", "droid", "sdk"} {
@@ -115,4 +170,5 @@
 			ctx.DistForGoalWithFilename(goal, this.releaseMap[rcName].intermediateTextProtoPath, assembleFileName(rcName, "flags.textproto"))
 		}
 	}
+	ctx.DistForGoalWithFilename("sdk", this.finalizedFlags, "finalized-flags.txt")
 }
diff --git a/aconfig/all_aconfig_declarations_extension.go b/aconfig/all_aconfig_declarations_extension.go
new file mode 100644
index 0000000..d5a4588
--- /dev/null
+++ b/aconfig/all_aconfig_declarations_extension.go
@@ -0,0 +1,88 @@
+// Copyright 2025 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 aconfig
+
+import (
+	"android/soong/android"
+	"path"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+func AllAconfigDeclarationsExtensionFactory() android.Module {
+	module := &allAconfigDeclarationsExtension{}
+	module.AddProperties(&module.properties)
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	return module
+}
+
+type allAconfigDeclarationsExtensionProperties struct {
+	// all_aconfig_declarations module that this module extends. Defaults to
+	// all_aconfig_declarations.
+	Base *string
+
+	// Directory where the dist artifact should be placed in.
+	Dist_dir *string
+
+	ApiSurfaceContributorProperties
+}
+
+type allAconfigDeclarationsExtension struct {
+	android.ModuleBase
+
+	properties allAconfigDeclarationsExtensionProperties
+
+	finalizedFlags android.ModuleOutPath
+}
+
+type allAconfigDeclarationsDependencyTagStruct struct {
+	blueprint.BaseDependencyTag
+}
+
+var allAconfigDeclarationsDependencyTag allAconfigDeclarationsDependencyTagStruct
+
+func (ext *allAconfigDeclarationsExtension) DepsMutator(ctx android.BottomUpMutatorContext) {
+	ctx.AddDependency(ctx.Module(), allAconfigDeclarationsDependencyTag, proptools.StringDefault(ext.properties.Base, "all_aconfig_declarations"))
+}
+
+func (ext *allAconfigDeclarationsExtension) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+
+	var parsedFlagsFile android.Path
+	ctx.VisitDirectDepsProxyWithTag(allAconfigDeclarationsDependencyTag, func(proxy android.ModuleProxy) {
+		if info, ok := android.OtherModuleProvider(ctx, proxy, allAconfigDeclarationsInfoProvider); ok {
+			parsedFlagsFile = info.parsedFlagsFile
+		} else {
+			ctx.PropertyErrorf("base", "base must provide allAconfigDeclarationsInfo")
+		}
+	})
+
+	ext.finalizedFlags = android.PathForModuleOut(ctx, "finalized-flags.txt")
+
+	GenerateFinalizedFlagsForApiSurface(ctx,
+		ext.finalizedFlags,
+		parsedFlagsFile,
+		ext.properties.ApiSurfaceContributorProperties,
+	)
+
+	ctx.Phony(ctx.ModuleName(), ext.finalizedFlags)
+
+	ctx.DistForGoalWithFilename("sdk", ext.finalizedFlags, path.Join(proptools.String(ext.properties.Dist_dir), "finalized-flags.txt"))
+
+	// This module must not set any provider or call `ctx.SetOutputFiles`!
+	// This module is only used to depend on the singleton module all_aconfig_declarations and
+	// generate the custom finalized-flags.txt file in dist builds, and should not be depended
+	// by other modules.
+}
diff --git a/aconfig/all_aconfig_declarations_extension_test.go b/aconfig/all_aconfig_declarations_extension_test.go
new file mode 100644
index 0000000..5bd99d0
--- /dev/null
+++ b/aconfig/all_aconfig_declarations_extension_test.go
@@ -0,0 +1,50 @@
+// Copyright 2025 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 aconfig
+
+import (
+	"strings"
+	"testing"
+
+	"android/soong/android"
+)
+
+func TestAllAconfigDeclarationsExtension(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithAconfigBuildComponents,
+		android.FixtureMergeMockFs(
+			android.MockFS{
+				"a.txt":     nil,
+				"flags.txt": nil,
+			},
+		),
+	).RunTestWithBp(t, `
+		all_aconfig_declarations {
+			name: "all_aconfig_declarations",
+		}
+
+		all_aconfig_declarations_extension {
+			name: "custom_aconfig_declarations",
+			base: "all_aconfig_declarations",
+			api_signature_files: [
+				"a.txt",
+			],
+			finalized_flags_file: "flags.txt",
+		}
+	`)
+
+	finalizedFlags := result.ModuleForTests(t, "custom_aconfig_declarations", "").Output("finalized-flags.txt")
+	android.AssertStringContainsEquals(t, "must depend on all_aconfig_declarations", strings.Join(finalizedFlags.Inputs.Strings(), " "), "all_aconfig_declarations.pb", true)
+}
diff --git a/aconfig/all_aconfig_declarations_test.go b/aconfig/all_aconfig_declarations_test.go
deleted file mode 100644
index 0b2021e..0000000
--- a/aconfig/all_aconfig_declarations_test.go
+++ /dev/null
@@ -1,48 +0,0 @@
-// 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 aconfig
-
-import (
-	"testing"
-
-	"android/soong/android"
-)
-
-func TestTwoAconfigDeclarationsPerPackage(t *testing.T) {
-	bp := `
-		aconfig_declarations {
-			name: "module_name.foo",
-			package: "com.example.package",
-			container: "com.android.foo",
-			srcs: [
-				"foo.aconfig",
-			],
-		}
-
-		aconfig_declarations {
-			name: "module_name.bar",
-			package: "com.example.package",
-			container: "com.android.foo",
-			srcs: [
-				"bar.aconfig",
-			],
-		}
-	`
-	errMsg := "Only one aconfig_declarations allowed for each package."
-	android.GroupFixturePreparers(
-		PrepareForTestWithAconfigBuildComponents).
-		ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(errMsg)).
-		RunTestWithBp(t, bp)
-}
diff --git a/aconfig/build_flags/build_flags.go b/aconfig/build_flags/build_flags.go
index e878b5a..94e1eb1 100644
--- a/aconfig/build_flags/build_flags.go
+++ b/aconfig/build_flags/build_flags.go
@@ -35,7 +35,7 @@
 type buildFlags struct {
 	android.ModuleBase
 
-	outputPath android.OutputPath
+	outputPath android.Path
 }
 
 func buildFlagsFactory() android.Module {
@@ -48,7 +48,7 @@
 	// Read the build_flags_<partition>.json file generated by soong
 	// 'release-config' command.
 	srcPath := android.PathForOutput(ctx, "release-config", fmt.Sprintf("build_flags_%s.json", m.PartitionTag(ctx.DeviceConfig())))
-	m.outputPath = android.PathForModuleOut(ctx, outJsonFileName).OutputPath
+	outputPath := android.PathForModuleOut(ctx, outJsonFileName)
 
 	// The 'release-config' command is called for every build, and generates the
 	// build_flags_<partition>.json file.
@@ -56,11 +56,12 @@
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   android.CpIfChanged,
 		Input:  srcPath,
-		Output: m.outputPath,
+		Output: outputPath,
 	})
 
 	installPath := android.PathForModuleInstall(ctx, "etc")
-	ctx.InstallFile(installPath, outJsonFileName, m.outputPath)
+	ctx.InstallFile(installPath, outJsonFileName, outputPath)
+	m.outputPath = outputPath
 }
 
 func (m *buildFlags) AndroidMkEntries() []android.AndroidMkEntries {
diff --git a/aconfig/build_flags/build_flags_singleton.go b/aconfig/build_flags/build_flags_singleton.go
index 3b40755..e375d9c 100644
--- a/aconfig/build_flags/build_flags_singleton.go
+++ b/aconfig/build_flags/build_flags_singleton.go
@@ -16,6 +16,7 @@
 
 import (
 	"android/soong/android"
+	"fmt"
 )
 
 // A singleton module that collects all of the build flags declared in the
@@ -110,9 +111,7 @@
 		this.configsBinaryProtoPath,
 		this.configsTextProtoPath,
 	)
-}
 
-func (this *allBuildFlagDeclarationsSingleton) MakeVars(ctx android.MakeVarsContext) {
 	ctx.DistForGoal("droid", this.flagsBinaryProtoPath)
 	for _, goal := range []string{"docs", "droid", "sdk", "release_config_metadata"} {
 		ctx.DistForGoalWithFilename(goal, this.flagsBinaryProtoPath, "build_flags/all_flags.pb")
@@ -120,4 +119,26 @@
 		ctx.DistForGoalWithFilename(goal, this.configsBinaryProtoPath, "build_flags/all_release_config_contributions.pb")
 		ctx.DistForGoalWithFilename(goal, this.configsTextProtoPath, "build_flags/all_release_config_contributions.textproto")
 	}
+
+	if ctx.Config().HasDeviceProduct() {
+		flagsDir := android.PathForOutput(ctx, "release-config")
+		baseAllRelease := fmt.Sprintf("all_release_configs-%s", ctx.Config().DeviceProduct())
+
+		distAllReleaseConfigsArtifact := func(ext string) {
+			ctx.DistForGoalWithFilename(
+				"droid",
+				flagsDir.Join(ctx, fmt.Sprintf("%s.%s", baseAllRelease, ext)),
+				fmt.Sprintf("build_flags/all_release_configs.%s", ext),
+			)
+		}
+
+		distAllReleaseConfigsArtifact("pb")
+		distAllReleaseConfigsArtifact("textproto")
+		distAllReleaseConfigsArtifact("json")
+		ctx.DistForGoalWithFilename(
+			"droid",
+			flagsDir.Join(ctx, fmt.Sprintf("inheritance_graph-%s.dot", ctx.Config().DeviceProduct())),
+			fmt.Sprintf("build_flags/inheritance_graph-%s.dot", ctx.Config().DeviceProduct()),
+		)
+	}
 }
diff --git a/aconfig/codegen/aconfig_declarations_group.go b/aconfig/codegen/aconfig_declarations_group.go
index 13daf47..6811d06 100644
--- a/aconfig/codegen/aconfig_declarations_group.go
+++ b/aconfig/codegen/aconfig_declarations_group.go
@@ -59,7 +59,7 @@
 func AconfigDeclarationsGroupFactory() android.Module {
 	module := &AconfigDeclarationsGroup{}
 	module.AddProperties(&module.properties)
-	android.InitAndroidModule(module)
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	android.InitDefaultableModule(module)
 	return module
 }
diff --git a/aconfig/codegen/aconfig_declarations_group_test.go b/aconfig/codegen/aconfig_declarations_group_test.go
index c69d21f..c822ef8 100644
--- a/aconfig/codegen/aconfig_declarations_group_test.go
+++ b/aconfig/codegen/aconfig_declarations_group_test.go
@@ -67,14 +67,14 @@
 	`)
 
 	// Check if aconfig_declarations_group module depends on the aconfig_library modules
-	java.CheckModuleDependencies(t, result.TestContext, "my_group", "", []string{
+	java.CheckModuleDependencies(t, result.TestContext, "my_group", "android_common", []string{
 		`bar-java`,
 		`foo-java`,
 	})
 
 	// Check if srcjar files are correctly passed to the reverse dependency of
 	// aconfig_declarations_group module
-	bazModule := result.ModuleForTests("baz", "android_common")
+	bazModule := result.ModuleForTests(t, "baz", "android_common")
 	bazJavacSrcjars := bazModule.Rule("javac").Args["srcJars"]
 	errorMessage := "baz javac argument expected to contain srcjar provided by aconfig_declrations_group"
 	android.AssertStringDoesContain(t, errorMessage, bazJavacSrcjars, "foo-java.srcjar")
diff --git a/aconfig/codegen/cc_aconfig_library.go b/aconfig/codegen/cc_aconfig_library.go
index 8c4bfe6..ce37456 100644
--- a/aconfig/codegen/cc_aconfig_library.go
+++ b/aconfig/codegen/cc_aconfig_library.go
@@ -22,7 +22,6 @@
 	"github.com/google/blueprint/proptools"
 
 	"fmt"
-	"strconv"
 	"strings"
 )
 
@@ -32,8 +31,6 @@
 
 var ccDeclarationsTag = ccDeclarationsTagType{}
 
-const baseLibDep = "server_configurable_flags"
-
 const libBaseDep = "libbase"
 const libLogDep = "liblog"
 const libAconfigStorageReadApiCcDep = "libaconfig_storage_read_api_cc"
@@ -86,15 +83,11 @@
 
 	// Add a dependency for the aconfig flags base library if it is not forced read only
 	if mode != "force-read-only" {
-		deps.SharedLibs = append(deps.SharedLibs, baseLibDep)
-
+		deps.SharedLibs = append(deps.SharedLibs, libAconfigStorageReadApiCcDep)
+		deps.SharedLibs = append(deps.SharedLibs, libBaseDep)
+		deps.SharedLibs = append(deps.SharedLibs, libLogDep)
 	}
 
-	// TODO: after storage migration is over, don't add these in force-read-only-mode.
-	deps.SharedLibs = append(deps.SharedLibs, libAconfigStorageReadApiCcDep)
-	deps.SharedLibs = append(deps.SharedLibs, libBaseDep)
-	deps.SharedLibs = append(deps.SharedLibs, libLogDep)
-
 	// TODO: It'd be really nice if we could reexport this library and not make everyone do it.
 
 	return deps
@@ -104,7 +97,7 @@
 	result := cc.GeneratedSource{}
 
 	// Get the values that came from the global RELEASE_ACONFIG_VALUE_SETS flag
-	declarationsModules := ctx.GetDirectDepsWithTag(ccDeclarationsTag)
+	declarationsModules := ctx.GetDirectDepsProxyWithTag(ccDeclarationsTag)
 	if len(declarationsModules) != 1 {
 		panic(fmt.Errorf("Exactly one aconfig_declarations property required"))
 	}
@@ -134,7 +127,7 @@
 
 func (this *CcAconfigLibraryCallbacks) GeneratorBuildActions(ctx cc.ModuleContext, flags cc.Flags, deps cc.PathDeps) {
 	// Get the values that came from the global RELEASE_ACONFIG_VALUE_SETS flag
-	declarationsModules := ctx.GetDirectDepsWithTag(ccDeclarationsTag)
+	declarationsModules := ctx.GetDirectDepsProxyWithTag(ccDeclarationsTag)
 	if len(declarationsModules) != 1 {
 		panic(fmt.Errorf("Exactly one aconfig_declarations property required"))
 	}
@@ -156,7 +149,6 @@
 		Args: map[string]string{
 			"gendir": this.generatedDir.String(),
 			"mode":   mode,
-			"debug":  strconv.FormatBool(ctx.Config().ReleaseReadFromNewStorage()),
 		},
 	})
 
diff --git a/aconfig/codegen/cc_aconfig_library_test.go b/aconfig/codegen/cc_aconfig_library_test.go
index 2f6c1a6..5cb3f8b 100644
--- a/aconfig/codegen/cc_aconfig_library_test.go
+++ b/aconfig/codegen/cc_aconfig_library_test.go
@@ -81,7 +81,7 @@
 			}
 		`, bpMode))
 
-	module := result.ModuleForTests("my_cc_aconfig_library", "android_arm64_armv8-a_shared")
+	module := result.ModuleForTests(t, "my_cc_aconfig_library", "android_arm64_armv8-a_shared")
 	rule := module.Rule("cc_aconfig_library")
 	android.AssertStringEquals(t, "rule must contain test mode", rule.Args["mode"], ruleMode)
 }
@@ -209,9 +209,9 @@
 		cc.PrepareForTestWithCcDefaultModules).
 		ExtendWithErrorHandler(android.FixtureExpectsNoErrors).RunTestWithBp(t, bp)
 
-	module := result.ModuleForTests("my_cc_library", "android_vendor_arm64_armv8-a_shared").Module()
+	module := result.ModuleForTests(t, "my_cc_library", "android_vendor_arm64_armv8-a_shared").Module()
 
-	entry := android.AndroidMkEntriesForTest(t, result.TestContext, module)[0]
+	entry := android.AndroidMkInfoForTest(t, result.TestContext, module).PrimaryInfo
 
 	makeVar := entry.EntryMap["LOCAL_ACONFIG_FILES"]
 	android.EnsureListContainsSuffix(t, makeVar, "my_aconfig_declarations_foo/intermediate.pb")
@@ -254,13 +254,13 @@
 			}
 		`))
 
-	module := result.ModuleForTests("my_cc_aconfig_library", "android_arm64_armv8-a_shared").Module()
-	dependOnBaseLib := false
+	module := result.ModuleForTests(t, "my_cc_aconfig_library", "android_arm64_armv8-a_shared").Module()
+	dependOnReadLib := false
 	result.VisitDirectDeps(module, func(dep blueprint.Module) {
-		if dep.Name() == baseLibDep {
-			dependOnBaseLib = true
+		if dep.Name() == libAconfigStorageReadApiCcDep {
+			dependOnReadLib = true
 		}
 	})
-	android.AssertBoolEquals(t, "should not have dependency on server_configuriable_flags",
-		dependOnBaseLib, false)
+	android.AssertBoolEquals(t, "should not have dependency on libaconfig_storage_read_api_cc",
+		dependOnReadLib, false)
 }
diff --git a/aconfig/codegen/init.go b/aconfig/codegen/init.go
index ed0b3ed..325e367 100644
--- a/aconfig/codegen/init.go
+++ b/aconfig/codegen/init.go
@@ -26,6 +26,7 @@
 	// For java_aconfig_library: Generate java library
 	javaRule = pctx.AndroidStaticRule("java_aconfig_library",
 		blueprint.RuleParams{
+			// LINT.IfChange
 			Command: `rm -rf ${out}.tmp` +
 				` && mkdir -p ${out}.tmp` +
 				` && ${aconfig} create-java-lib` +
@@ -33,14 +34,17 @@
 				`    --cache ${in}` +
 				`    --out ${out}.tmp` +
 				`    --allow-instrumentation ${debug}` +
+				`    --new-exported ${new_exported}` +
+				`		 --check-api-level ${check_api_level}` +
 				` && $soong_zip -write_if_changed -jar -o ${out} -C ${out}.tmp -D ${out}.tmp` +
 				` && rm -rf ${out}.tmp`,
+			// LINT.ThenChange(/aconfig/init.go)
 			CommandDeps: []string{
 				"$aconfig",
 				"$soong_zip",
 			},
 			Restat: true,
-		}, "mode", "debug")
+		}, "mode", "debug", "new_exported", "check_api_level")
 
 	// For cc_aconfig_library: Generate C++ library
 	cppRule = pctx.AndroidStaticRule("cc_aconfig_library",
@@ -50,12 +54,11 @@
 				` && ${aconfig} create-cpp-lib` +
 				`    --mode ${mode}` +
 				`    --cache ${in}` +
-				`    --out ${gendir}` +
-				`    --allow-instrumentation ${debug}`,
+				`    --out ${gendir}`,
 			CommandDeps: []string{
 				"$aconfig",
 			},
-		}, "gendir", "mode", "debug")
+		}, "gendir", "mode")
 
 	// For rust_aconfig_library: Generate Rust library
 	rustRule = pctx.AndroidStaticRule("rust_aconfig_library",
@@ -65,12 +68,11 @@
 				` && ${aconfig} create-rust-lib` +
 				`    --mode ${mode}` +
 				`    --cache ${in}` +
-				`    --allow-instrumentation ${debug}` +
 				`    --out ${gendir}`,
 			CommandDeps: []string{
 				"$aconfig",
 			},
-		}, "gendir", "mode", "debug")
+		}, "gendir", "mode")
 )
 
 func init() {
diff --git a/aconfig/codegen/java_aconfig_library.go b/aconfig/codegen/java_aconfig_library.go
index ebca413..7b9da8e 100644
--- a/aconfig/codegen/java_aconfig_library.go
+++ b/aconfig/codegen/java_aconfig_library.go
@@ -72,7 +72,7 @@
 		module.AddSharedLibrary("aconfig-annotations-lib")
 		// TODO(b/303773055): Remove the annotation after access issue is resolved.
 		module.AddSharedLibrary("unsupportedappusage")
-		module.AddSharedLibrary("aconfig_storage_reader_java")
+		module.AddSharedLibrary("aconfig_storage_stub")
 	}
 }
 
@@ -98,14 +98,24 @@
 		ctx.PropertyErrorf("mode", "exported mode requires its aconfig_declaration has exportable prop true")
 	}
 
+	var newExported bool
+	if useNewExported, ok := ctx.Config().GetBuildFlag("RELEASE_ACONFIG_NEW_EXPORTED"); ok {
+		// The build flag (RELEASE_ACONFIG_REQUIRE_ALL_READ_ONLY) is the negation of the aconfig flag
+		// (allow-read-write) for historical reasons.
+		// Bool build flags are always "" for false, and generally "true" for true.
+		newExported = useNewExported == "true"
+	}
+
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        javaRule,
 		Input:       declarations.IntermediateCacheOutputPath,
 		Output:      srcJarPath,
 		Description: "aconfig.srcjar",
 		Args: map[string]string{
-			"mode":  mode,
-			"debug": strconv.FormatBool(ctx.Config().ReleaseReadFromNewStorage()),
+			"mode":            mode,
+			"debug":           strconv.FormatBool(ctx.Config().ReleaseReadFromNewStorage()),
+			"new_exported":    strconv.FormatBool(newExported),
+			"check_api_level": strconv.FormatBool(ctx.Config().ReleaseAconfigCheckApiLevel()),
 		},
 	})
 
diff --git a/aconfig/codegen/java_aconfig_library_test.go b/aconfig/codegen/java_aconfig_library_test.go
index d8372f3..8854369 100644
--- a/aconfig/codegen/java_aconfig_library_test.go
+++ b/aconfig/codegen/java_aconfig_library_test.go
@@ -57,7 +57,7 @@
 			}
 		`)
 
-	module := result.ModuleForTests("my_module", "android_common").Module()
+	module := result.ModuleForTests(t, "my_module", "android_common").Module()
 
 	entry := android.AndroidMkEntriesForTest(t, result.TestContext, module)[0]
 
@@ -189,7 +189,7 @@
 			}
 		`, bpMode))
 
-	module := result.ModuleForTests("my_java_aconfig_library", "android_common")
+	module := result.ModuleForTests(t, "my_java_aconfig_library", "android_common")
 	rule := module.Rule("java_aconfig_library")
 	android.AssertStringEquals(t, "rule must contain test mode", rule.Args["mode"], ruleMode)
 }
@@ -282,7 +282,7 @@
 			}
 		`)
 
-	module := result.ModuleForTests("my_module", "android_common").Module()
+	module := result.ModuleForTests(t, "my_module", "android_common").Module()
 	entry := android.AndroidMkEntriesForTest(t, result.TestContext, module)[0]
 	makeVar := entry.EntryMap["LOCAL_ACONFIG_FILES"]
 	android.EnsureListContainsSuffix(t, makeVar, "my_aconfig_declarations_foo/intermediate.pb")
diff --git a/aconfig/codegen/rust_aconfig_library.go b/aconfig/codegen/rust_aconfig_library.go
index 4b896c3..53818c2 100644
--- a/aconfig/codegen/rust_aconfig_library.go
+++ b/aconfig/codegen/rust_aconfig_library.go
@@ -2,7 +2,6 @@
 
 import (
 	"fmt"
-	"strconv"
 
 	"android/soong/android"
 	"android/soong/rust"
@@ -83,7 +82,6 @@
 		Args: map[string]string{
 			"gendir": generatedDir.String(),
 			"mode":   mode,
-			"debug":  strconv.FormatBool(ctx.Config().ReleaseReadFromNewStorage()),
 		},
 	})
 	a.BaseSourceProvider.OutputFiles = android.Paths{generatedSource}
@@ -102,7 +100,6 @@
 func (a *aconfigDecorator) SourceProviderDeps(ctx rust.DepsContext, deps rust.Deps) rust.Deps {
 	deps = a.BaseSourceProvider.SourceProviderDeps(ctx, deps)
 	deps.Rustlibs = append(deps.Rustlibs, "libaconfig_storage_read_api")
-	deps.Rustlibs = append(deps.Rustlibs, "libflags_rust")
 	deps.Rustlibs = append(deps.Rustlibs, "liblazy_static")
 	deps.Rustlibs = append(deps.Rustlibs, "liblogger")
 	deps.Rustlibs = append(deps.Rustlibs, "liblog_rust")
diff --git a/aconfig/codegen/rust_aconfig_library_test.go b/aconfig/codegen/rust_aconfig_library_test.go
index 523b464..6701021 100644
--- a/aconfig/codegen/rust_aconfig_library_test.go
+++ b/aconfig/codegen/rust_aconfig_library_test.go
@@ -57,13 +57,13 @@
 			}
 		`))
 
-	sourceVariant := result.ModuleForTests("libmy_rust_aconfig_library", "android_arm64_armv8-a_source")
+	sourceVariant := result.ModuleForTests(t, "libmy_rust_aconfig_library", "android_arm64_armv8-a_source")
 	rule := sourceVariant.Rule("rust_aconfig_library")
 	android.AssertStringEquals(t, "rule must contain production mode", rule.Args["mode"], "production")
 
-	dylibVariant := result.ModuleForTests("libmy_rust_aconfig_library", "android_arm64_armv8-a_dylib")
-	rlibRlibStdVariant := result.ModuleForTests("libmy_rust_aconfig_library", "android_arm64_armv8-a_rlib_rlib-std")
-	rlibDylibStdVariant := result.ModuleForTests("libmy_rust_aconfig_library", "android_arm64_armv8-a_rlib_dylib-std")
+	dylibVariant := result.ModuleForTests(t, "libmy_rust_aconfig_library", "android_arm64_armv8-a_dylib")
+	rlibRlibStdVariant := result.ModuleForTests(t, "libmy_rust_aconfig_library", "android_arm64_armv8-a_rlib_rlib-std")
+	rlibDylibStdVariant := result.ModuleForTests(t, "libmy_rust_aconfig_library", "android_arm64_armv8-a_rlib_dylib-std")
 
 	variants := []android.TestingModule{
 		dylibVariant,
@@ -143,7 +143,7 @@
 			}
 		`, bpMode))
 
-	module := result.ModuleForTests("libmy_rust_aconfig_library", "android_arm64_armv8-a_source")
+	module := result.ModuleForTests(t, "libmy_rust_aconfig_library", "android_arm64_armv8-a_source")
 	rule := module.Rule("rust_aconfig_library")
 	android.AssertStringEquals(t, "rule must contain test mode", rule.Args["mode"], ruleMode)
 }
diff --git a/aconfig/exported_java_aconfig_library.go b/aconfig/exported_java_aconfig_library.go
index a64cac8..63d824a 100644
--- a/aconfig/exported_java_aconfig_library.go
+++ b/aconfig/exported_java_aconfig_library.go
@@ -16,6 +16,7 @@
 
 import (
 	"android/soong/android"
+	"strconv"
 )
 
 func ExportedJavaDeclarationsLibraryFactory() android.Singleton {
@@ -37,6 +38,16 @@
 		cacheFiles = append(cacheFiles, decl.IntermediateCacheOutputPath)
 	})
 
+	var newExported bool
+	if useNewExported, ok := ctx.Config().GetBuildFlag("RELEASE_ACONFIG_NEW_EXPORTED"); ok {
+		newExported = useNewExported == "true"
+	}
+
+	var newStorage bool
+	if useNewStorage, ok := ctx.Config().GetBuildFlag("RELEASE_READ_FROM_NEW_STORAGE"); ok {
+		newStorage = useNewStorage == "true"
+	}
+
 	// Generate build action for aconfig
 	this.intermediatePath = android.PathForIntermediates(ctx, "exported_java_aconfig_library.jar")
 	ctx.Build(pctx, android.BuildParams{
@@ -45,12 +56,12 @@
 		Output:      this.intermediatePath,
 		Description: "exported_java_aconfig_library",
 		Args: map[string]string{
-			"cache_files": android.JoinPathsWithPrefix(cacheFiles, " "),
+			"cache_files":      android.JoinPathsWithPrefix(cacheFiles, " "),
+			"use_new_storage":  strconv.FormatBool(newStorage),
+			"use_new_exported": strconv.FormatBool(newExported),
+			"check_api_level":  strconv.FormatBool(ctx.Config().ReleaseAconfigCheckApiLevel()),
 		},
 	})
 	ctx.Phony("exported_java_aconfig_library", this.intermediatePath)
-}
-
-func (this *exportedJavaDeclarationsLibrarySingleton) MakeVars(ctx android.MakeVarsContext) {
 	ctx.DistForGoalWithFilename("sdk", this.intermediatePath, "android-flags.jar")
 }
diff --git a/aconfig/init.go b/aconfig/init.go
index 6f91d8e..b2fe5a3 100644
--- a/aconfig/init.go
+++ b/aconfig/init.go
@@ -32,6 +32,7 @@
 				` ${declarations}` +
 				` ${values}` +
 				` ${default-permission}` +
+				` ${allow-read-write}` +
 				` --cache ${out}.tmp` +
 				` && ( if cmp -s ${out}.tmp ${out} ; then rm ${out}.tmp ; else mv ${out}.tmp ${out} ; fi )`,
 			//				` --build-id ${release_version}` +
@@ -39,7 +40,7 @@
 				"${aconfig}",
 			},
 			Restat: true,
-		}, "release_version", "package", "container", "declarations", "values", "default-permission")
+		}, "release_version", "package", "container", "declarations", "values", "default-permission", "allow-read-write")
 
 	// For create-device-config-sysprops: Generate aconfig flag value map text file
 	aconfigTextRule = pctx.AndroidStaticRule("aconfig_text",
@@ -69,14 +70,21 @@
 				"${aconfig}",
 			},
 		}, "cache_files")
+	RecordFinalizedFlagsRule = pctx.AndroidStaticRule("RecordFinalizedFlagsRule",
+		blueprint.RuleParams{
+			Command: `${record-finalized-flags} ${parsed_flags_file} ${finalized_flags_file} ${api_signature_files} > ${out}`,
+			CommandDeps: []string{
+				"${record-finalized-flags}",
+			},
+		}, "api_signature_files", "finalized_flags_file", "parsed_flags_file")
 
 	CreateStorageRule = pctx.AndroidStaticRule("aconfig_create_storage",
 		blueprint.RuleParams{
-			Command: `${aconfig} create-storage --container ${container} --file ${file_type} --out ${out} ${cache_files}`,
+			Command: `${aconfig} create-storage --container ${container} --file ${file_type} --out ${out} ${cache_files} --version ${version}`,
 			CommandDeps: []string{
 				"${aconfig}",
 			},
-		}, "container", "file_type", "cache_files")
+		}, "container", "file_type", "cache_files", "version")
 
 	// For exported_java_aconfig_library: Generate a JAR from all
 	// java_aconfig_libraries to be consumed by apps built outside the
@@ -87,31 +95,42 @@
 		// exported flags (only). Finally collect all generated code
 		// into the ${out} JAR file.
 		blueprint.RuleParams{
+			// LINT.IfChange
 			Command: `rm -rf ${out}.tmp` +
 				`&& for cache in ${cache_files}; do ` +
 				`  if [ -n "$$(${aconfig} dump-cache --dedup --cache $$cache --filter=is_exported:true --format='{fully_qualified_name}')" ]; then ` +
-				`    ${aconfig} create-java-lib --cache $$cache --mode=exported --out ${out}.tmp; ` +
+				`    ${aconfig} create-java-lib` +
+				`        --cache $$cache` +
+				`        --mode=exported` +
+				`        --allow-instrumentation ${use_new_storage}` +
+				`        --new-exported ${use_new_exported}` +
+				`        --single-exported-file true` +
+				`        --check-api-level ${check_api_level}` +
+				`        --out ${out}.tmp; ` +
 				`  fi ` +
 				`done` +
 				`&& $soong_zip -write_if_changed -jar -o ${out} -C ${out}.tmp -D ${out}.tmp` +
 				`&& rm -rf ${out}.tmp`,
+			// LINT.ThenChange(/aconfig/codegen/init.go)
 			CommandDeps: []string{
 				"$aconfig",
 				"$soong_zip",
 			},
-		}, "cache_files")
+		}, "cache_files", "use_new_storage", "use_new_exported", "check_api_level")
 )
 
 func init() {
 	RegisterBuildComponents(android.InitRegistrationContext)
 	pctx.HostBinToolVariable("aconfig", "aconfig")
 	pctx.HostBinToolVariable("soong_zip", "soong_zip")
+	pctx.HostBinToolVariable("record-finalized-flags", "record-finalized-flags")
 }
 
 func RegisterBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("aconfig_declarations", DeclarationsFactory)
 	ctx.RegisterModuleType("aconfig_values", ValuesFactory)
 	ctx.RegisterModuleType("aconfig_value_set", ValueSetFactory)
-	ctx.RegisterParallelSingletonType("all_aconfig_declarations", AllAconfigDeclarationsFactory)
+	ctx.RegisterSingletonModuleType("all_aconfig_declarations", AllAconfigDeclarationsFactory)
 	ctx.RegisterParallelSingletonType("exported_java_aconfig_library", ExportedJavaDeclarationsLibraryFactory)
+	ctx.RegisterModuleType("all_aconfig_declarations_extension", AllAconfigDeclarationsExtensionFactory)
 }
diff --git a/aidl_library/Android.bp b/aidl_library/Android.bp
index 07472a4..3c386fb 100644
--- a/aidl_library/Android.bp
+++ b/aidl_library/Android.bp
@@ -20,6 +20,7 @@
     name: "soong-aidl-library",
     pkgPath: "android/soong/aidl_library",
     deps: [
+        "blueprint-depset",
         "soong-android",
     ],
     srcs: [
diff --git a/aidl_library/aidl_library.go b/aidl_library/aidl_library.go
index 0141545..1e0ab30 100644
--- a/aidl_library/aidl_library.go
+++ b/aidl_library/aidl_library.go
@@ -17,6 +17,7 @@
 import (
 	"android/soong/android"
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/depset"
 	"github.com/google/blueprint/proptools"
 )
 
@@ -58,17 +59,17 @@
 	// The direct aidl files of the module
 	Srcs android.Paths
 	// The include dirs to the direct aidl files and those provided from transitive aidl_library deps
-	IncludeDirs android.DepSet[android.Path]
+	IncludeDirs depset.DepSet[android.Path]
 	// The direct hdrs and hdrs from transitive deps
-	Hdrs android.DepSet[android.Path]
+	Hdrs depset.DepSet[android.Path]
 }
 
 // AidlLibraryProvider provides the srcs and the transitive include dirs
 var AidlLibraryProvider = blueprint.NewProvider[AidlLibraryInfo]()
 
 func (lib *AidlLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	includeDirsDepSetBuilder := android.NewDepSetBuilder[android.Path](android.PREORDER)
-	hdrsDepSetBuilder := android.NewDepSetBuilder[android.Path](android.PREORDER)
+	includeDirsDepSetBuilder := depset.NewBuilder[android.Path](depset.PREORDER)
+	hdrsDepSetBuilder := depset.NewBuilder[android.Path](depset.PREORDER)
 
 	if len(lib.properties.Srcs) == 0 && len(lib.properties.Hdrs) == 0 {
 		ctx.ModuleErrorf("at least srcs or hdrs prop must be non-empty")
@@ -100,15 +101,15 @@
 
 	for _, dep := range ctx.GetDirectDepsWithTag(aidlLibraryTag) {
 		if info, ok := android.OtherModuleProvider(ctx, dep, AidlLibraryProvider); ok {
-			includeDirsDepSetBuilder.Transitive(&info.IncludeDirs)
-			hdrsDepSetBuilder.Transitive(&info.Hdrs)
+			includeDirsDepSetBuilder.Transitive(info.IncludeDirs)
+			hdrsDepSetBuilder.Transitive(info.Hdrs)
 		}
 	}
 
 	android.SetProvider(ctx, AidlLibraryProvider, AidlLibraryInfo{
 		Srcs:        srcs,
-		IncludeDirs: *includeDirsDepSetBuilder.Build(),
-		Hdrs:        *hdrsDepSetBuilder.Build(),
+		IncludeDirs: includeDirsDepSetBuilder.Build(),
+		Hdrs:        hdrsDepSetBuilder.Build(),
 	})
 }
 
diff --git a/aidl_library/aidl_library_test.go b/aidl_library/aidl_library_test.go
index 1660456..63a08b9 100644
--- a/aidl_library/aidl_library_test.go
+++ b/aidl_library/aidl_library_test.go
@@ -46,7 +46,7 @@
 		}.AddToFixture(),
 	).RunTest(t).TestContext
 
-	foo := ctx.ModuleForTests("foo", "").Module().(*AidlLibrary)
+	foo := ctx.ModuleForTests(t, "foo", "").Module().(*AidlLibrary)
 	actualInfo, _ := android.OtherModuleProvider(ctx, foo, AidlLibraryProvider)
 
 	android.AssertArrayString(
@@ -95,7 +95,7 @@
 		}.AddToFixture(),
 	).RunTest(t).TestContext
 
-	foo := ctx.ModuleForTests("foo", "").Module().(*AidlLibrary)
+	foo := ctx.ModuleForTests(t, "foo", "").Module().(*AidlLibrary)
 	actualInfo, _ := android.OtherModuleProvider(ctx, foo, AidlLibraryProvider)
 
 	android.AssertArrayString(
diff --git a/android/Android.bp b/android/Android.bp
index eb8c64d..aef18fe 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -8,7 +8,10 @@
     deps: [
         "blueprint",
         "blueprint-bootstrap",
+        "blueprint-depset",
+        "blueprint-gobtools",
         "blueprint-metrics",
+        "blueprint-pool",
         "sbox_proto",
         "soong",
         "soong-android_team_proto",
@@ -29,6 +32,7 @@
     srcs: [
         "aconfig_providers.go",
         "all_teams.go",
+        "android_info.go",
         "androidmk.go",
         "apex.go",
         "apex_contributions.go",
@@ -50,8 +54,8 @@
         "deapexer.go",
         "defaults.go",
         "defs.go",
-        "depset_generic.go",
         "deptag.go",
+        "dirgroup.go",
         "early_module_context.go",
         "expand.go",
         "filegroup.go",
@@ -76,6 +80,7 @@
         "namespace.go",
         "neverallow.go",
         "ninja_deps.go",
+        "nothing.go",
         "notices.go",
         "onceper.go",
         "override_module.go",
@@ -89,11 +94,13 @@
         "prebuilt.go",
         "prebuilt_build_tool.go",
         "product_config.go",
-        "product_config_to_bp.go",
+        "product_packages_file.go",
         "proto.go",
         "provider.go",
         "raw_files.go",
+        "recovery_build_prop.go",
         "register.go",
+        "removed_package.go",
         "rule_builder.go",
         "sandbox.go",
         "sbom.go",
@@ -107,23 +114,25 @@
         "test_asserts.go",
         "test_suites.go",
         "testing.go",
+        "transition.go",
         "util.go",
         "variable.go",
+        "vendor_api_levels.go",
         "vintf_fragment.go",
+        "vintf_data.go",
         "visibility.go",
     ],
     testSrcs: [
         "all_teams_test.go",
         "android_test.go",
         "androidmk_test.go",
-        "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",
         "defaults_test.go",
-        "depset_test.go",
         "deptag_test.go",
         "expand_test.go",
         "filegroup_test.go",
@@ -132,6 +141,7 @@
         "license_kind_test.go",
         "license_test.go",
         "licenses_test.go",
+        "makevars_test.go",
         "module_test.go",
         "mutator_test.go",
         "namespace_test.go",
@@ -150,6 +160,7 @@
         "singleton_module_test.go",
         "soong_config_modules_test.go",
         "test_suites_test.go",
+        "transition_test.go",
         "util_test.go",
         "variable_test.go",
         "vintf_fragment_test.go",
diff --git a/android/aconfig_providers.go b/android/aconfig_providers.go
index b902f8b..b698d24 100644
--- a/android/aconfig_providers.go
+++ b/android/aconfig_providers.go
@@ -92,11 +92,11 @@
 				if asError {
 					ctx.ModuleErrorf(msg)
 				} else {
-					fmt.Printf("WARNING: " + msg)
+					fmt.Print("WARNING: " + msg)
 				}
 			} else {
 				if !asError {
-					fmt.Printf("PASSED: " + msg)
+					fmt.Print("PASSED: " + msg)
 				}
 			}
 		}
@@ -107,7 +107,7 @@
 	mergedAconfigFiles := make(map[string]Paths)
 	mergedModeInfos := make(map[string]ModeInfo)
 
-	ctx.VisitDirectDeps(func(module Module) {
+	ctx.VisitDirectDepsProxy(func(module ModuleProxy) {
 		if aconfig_dep, ok := OtherModuleProvider(ctx, module, CodegenInfoProvider); ok && len(aconfig_dep.ModeInfos) > 0 {
 			maps.Copy(mergedModeInfos, aconfig_dep.ModeInfos)
 		}
diff --git a/android/all_teams.go b/android/all_teams.go
index 01be396..3b20107 100644
--- a/android/all_teams.go
+++ b/android/all_teams.go
@@ -134,9 +134,6 @@
 
 	WriteFileRuleVerbatim(ctx, t.outputPath, string(data))
 	ctx.Phony("all_teams", t.outputPath)
-}
-
-func (t *allTeamsSingleton) MakeVars(ctx MakeVarsContext) {
 	ctx.DistForGoal("all_teams", t.outputPath)
 }
 
diff --git a/android/all_teams_test.go b/android/all_teams_test.go
index fa8c048..3b200f6 100644
--- a/android/all_teams_test.go
+++ b/android/all_teams_test.go
@@ -131,7 +131,7 @@
 
 func getTeamProtoOutput(t *testing.T, ctx *TestResult) *team_proto.AllTeams {
 	teams := new(team_proto.AllTeams)
-	config := ctx.SingletonForTests("all_teams")
+	config := ctx.SingletonForTests(t, "all_teams")
 	allOutputs := config.AllOutputs()
 
 	protoPath := allOutputs[0]
diff --git a/android/android_info.go b/android/android_info.go
new file mode 100644
index 0000000..9a68d10
--- /dev/null
+++ b/android/android_info.go
@@ -0,0 +1,92 @@
+// 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}, "")
+	ctx.SetOutputFiles(Paths{androidInfoTxt}, ".txt")
+}
+
+// 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)
+	InitAndroidArchModule(module, DeviceSupported, MultilibCommon)
+	return module
+}
diff --git a/android/androidmk.go b/android/androidmk.go
index cac2cfe..7d6b056 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -64,7 +64,6 @@
 type AndroidMkData struct {
 	Class           string
 	SubName         string
-	DistFiles       TaggedDistFiles
 	OutputFile      OptionalPath
 	Disabled        bool
 	Include         string
@@ -333,6 +332,25 @@
 	dest string
 }
 
+func (d *distCopy) String() string {
+	if len(d.dest) == 0 {
+		return d.from.String()
+	}
+	return fmt.Sprintf("%s:%s", d.from.String(), d.dest)
+}
+
+type distCopies []distCopy
+
+func (d *distCopies) Strings() (ret []string) {
+	if d == nil {
+		return
+	}
+	for _, dist := range *d {
+		ret = append(ret, dist.String())
+	}
+	return
+}
+
 // Compute the contributions that the module makes to the dist.
 func (a *AndroidMkEntries) getDistContributions(mod blueprint.Module) *distContributions {
 	amod := mod.(Module).base()
@@ -463,18 +481,18 @@
 func generateDistContributionsForMake(distContributions *distContributions) []string {
 	var ret []string
 	for _, d := range distContributions.copiesForGoals {
-		ret = append(ret, fmt.Sprintf(".PHONY: %s\n", d.goals))
+		ret = append(ret, fmt.Sprintf(".PHONY: %s", d.goals))
 		// Create dist-for-goals calls for each of the copy instructions.
 		for _, c := range d.copies {
 			if distContributions.licenseMetadataFile != nil {
 				ret = append(
 					ret,
-					fmt.Sprintf("$(if $(strip $(ALL_TARGETS.%s.META_LIC)),,$(eval ALL_TARGETS.%s.META_LIC := %s))\n",
+					fmt.Sprintf("$(if $(strip $(ALL_TARGETS.%s.META_LIC)),,$(eval ALL_TARGETS.%s.META_LIC := %s))",
 						c.from.String(), c.from.String(), distContributions.licenseMetadataFile.String()))
 			}
 			ret = append(
 				ret,
-				fmt.Sprintf("$(call dist-for-goals,%s,%s:%s)\n", d.goals, c.from.String(), c.dest))
+				fmt.Sprintf("$(call dist-for-goals,%s,%s:%s)", d.goals, c.from.String(), c.dest))
 		}
 	}
 
@@ -523,7 +541,7 @@
 	a.Target_required = append(a.Target_required, amod.TargetRequiredModuleNames()...)
 
 	for _, distString := range a.GetDistForGoals(mod) {
-		fmt.Fprintf(&a.header, distString)
+		fmt.Fprintln(&a.header, distString)
 	}
 
 	fmt.Fprintf(&a.header, "\ninclude $(CLEAR_VARS)  # type: %s, name: %s, variant: %s\n", ctx.ModuleType(mod), base.BaseModuleName(), ctx.ModuleSubDir(mod))
@@ -700,24 +718,29 @@
 
 type androidMkSingleton struct{}
 
-func (c *androidMkSingleton) GenerateBuildActions(ctx SingletonContext) {
-	// Skip if Soong wasn't invoked from Make.
-	if !ctx.Config().KatiEnabled() {
-		return
-	}
-
-	var androidMkModulesList []blueprint.Module
+func allModulesSorted(ctx SingletonContext) []blueprint.Module {
+	var allModules []blueprint.Module
 
 	ctx.VisitAllModulesBlueprint(func(module blueprint.Module) {
-		androidMkModulesList = append(androidMkModulesList, module)
+		allModules = append(allModules, module)
 	})
 
 	// Sort the module list by the module names to eliminate random churns, which may erroneously
 	// invoke additional build processes.
-	sort.SliceStable(androidMkModulesList, func(i, j int) bool {
-		return ctx.ModuleName(androidMkModulesList[i]) < ctx.ModuleName(androidMkModulesList[j])
+	sort.SliceStable(allModules, func(i, j int) bool {
+		return ctx.ModuleName(allModules[i]) < ctx.ModuleName(allModules[j])
 	})
 
+	return allModules
+}
+
+func (c *androidMkSingleton) GenerateBuildActions(ctx SingletonContext) {
+	// If running in soong-only mode, more limited version of this singleton is run as
+	// soong only androidmk singleton
+	if !ctx.Config().KatiEnabled() {
+		return
+	}
+
 	transMk := PathForOutput(ctx, "Android"+String(ctx.Config().productVariables.Make_suffix)+".mk")
 	if ctx.Failed() {
 		return
@@ -725,7 +748,7 @@
 
 	moduleInfoJSON := PathForOutput(ctx, "module-info"+String(ctx.Config().productVariables.Make_suffix)+".json")
 
-	err := translateAndroidMk(ctx, absolutePath(transMk.String()), moduleInfoJSON, androidMkModulesList)
+	err := translateAndroidMk(ctx, absolutePath(transMk.String()), moduleInfoJSON, allModulesSorted(ctx))
 	if err != nil {
 		ctx.Errorf(err.Error())
 	}
@@ -736,6 +759,243 @@
 	})
 }
 
+type soongOnlyAndroidMkSingleton struct {
+	Singleton
+}
+
+func soongOnlyAndroidMkSingletonFactory() Singleton {
+	return &soongOnlyAndroidMkSingleton{}
+}
+
+func (so *soongOnlyAndroidMkSingleton) GenerateBuildActions(ctx SingletonContext) {
+	if !ctx.Config().KatiEnabled() {
+		so.soongOnlyBuildActions(ctx, allModulesSorted(ctx))
+	}
+}
+
+// In soong-only mode, we don't do most of the androidmk stuff. But disted files are still largely
+// defined through the androidmk mechanisms, so this function is an alternate implementation of
+// the androidmk singleton that just focuses on getting the dist contributions
+func (so *soongOnlyAndroidMkSingleton) soongOnlyBuildActions(ctx SingletonContext, mods []blueprint.Module) {
+	allDistContributions, moduleInfoJSONs := getSoongOnlyDataFromMods(ctx, mods)
+
+	for _, provider := range append(makeVarsInitProviders, *getSingletonMakevarsProviders(ctx.Config())...) {
+		mctx := &makeVarsContext{
+			SingletonContext: ctx,
+			pctx:             provider.pctx,
+		}
+		provider.call(mctx)
+		if contribution := distsToDistContributions(mctx.dists); contribution != nil {
+			allDistContributions = append(allDistContributions, *contribution)
+		}
+	}
+
+	singletonDists := getSingletonDists(ctx.Config())
+	singletonDists.lock.Lock()
+	if contribution := distsToDistContributions(singletonDists.dists); contribution != nil {
+		allDistContributions = append(allDistContributions, *contribution)
+	}
+	singletonDists.lock.Unlock()
+
+	// Build module-info.json. Only in builds with HasDeviceProduct(), as we need a named
+	// device to have a TARGET_OUT folder.
+	if ctx.Config().HasDeviceProduct() {
+		preMergePath := PathForOutput(ctx, "module_info_pre_merging.json")
+		moduleInfoJSONPath := pathForInstall(ctx, Android, X86_64, "", "module-info.json")
+		if err := writeModuleInfoJSON(ctx, moduleInfoJSONs, preMergePath); err != nil {
+			ctx.Errorf("%s", err)
+		}
+		builder := NewRuleBuilder(pctx, ctx)
+		builder.Command().
+			BuiltTool("merge_module_info_json").
+			FlagWithOutput("-o ", moduleInfoJSONPath).
+			Input(preMergePath)
+		builder.Build("merge_module_info_json", "merge module info json")
+		ctx.Phony("module-info", moduleInfoJSONPath)
+		ctx.Phony("droidcore-unbundled", moduleInfoJSONPath)
+		allDistContributions = append(allDistContributions, distContributions{
+			copiesForGoals: []*copiesForGoals{{
+				goals: "general-tests droidcore-unbundled",
+				copies: []distCopy{{
+					from: moduleInfoJSONPath,
+					dest: "module-info.json",
+				}},
+			}},
+		})
+	}
+
+	// Build dist.mk for the packaging step to read and generate dist targets
+	distMkFile := absolutePath(filepath.Join(ctx.Config().katiPackageMkDir(), "dist.mk"))
+
+	var goalOutputPairs []string
+	var srcDstPairs []string
+	for _, contributions := range allDistContributions {
+		for _, copiesForGoal := range contributions.copiesForGoals {
+			goals := strings.Fields(copiesForGoal.goals)
+			for _, copy := range copiesForGoal.copies {
+				for _, goal := range goals {
+					goalOutputPairs = append(goalOutputPairs, fmt.Sprintf(" %s:%s", goal, copy.dest))
+				}
+				srcDstPairs = append(srcDstPairs, fmt.Sprintf(" %s:%s", copy.from.String(), copy.dest))
+			}
+		}
+	}
+	// There are duplicates in the lists that we need to remove
+	goalOutputPairs = SortedUniqueStrings(goalOutputPairs)
+	srcDstPairs = SortedUniqueStrings(srcDstPairs)
+	var buf strings.Builder
+	buf.WriteString("DIST_SRC_DST_PAIRS :=")
+	for _, srcDstPair := range srcDstPairs {
+		buf.WriteString(srcDstPair)
+	}
+	buf.WriteString("\nDIST_GOAL_OUTPUT_PAIRS :=")
+	for _, goalOutputPair := range goalOutputPairs {
+		buf.WriteString(goalOutputPair)
+	}
+	buf.WriteString("\n")
+
+	writeValueIfChanged(ctx, distMkFile, buf.String())
+}
+
+func writeValueIfChanged(ctx SingletonContext, path string, value string) {
+	if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil {
+		ctx.Errorf("%s\n", err)
+		return
+	}
+	previousValue := ""
+	rawPreviousValue, err := os.ReadFile(path)
+	if err == nil {
+		previousValue = string(rawPreviousValue)
+	}
+
+	if previousValue != value {
+		if err = os.WriteFile(path, []byte(value), 0666); err != nil {
+			ctx.Errorf("Failed to write: %v", err)
+		}
+	}
+}
+
+func distsToDistContributions(dists []dist) *distContributions {
+	if len(dists) == 0 {
+		return nil
+	}
+
+	copyGoals := []*copiesForGoals{}
+	for _, dist := range dists {
+		for _, goal := range dist.goals {
+			copyGoals = append(copyGoals, &copiesForGoals{
+				goals:  goal,
+				copies: dist.paths,
+			})
+		}
+	}
+
+	return &distContributions{
+		copiesForGoals: copyGoals,
+	}
+}
+
+// getSoongOnlyDataFromMods gathers data from the given modules needed in soong-only builds.
+// Currently, this is the dist contributions, and the module-info.json contents.
+func getSoongOnlyDataFromMods(ctx fillInEntriesContext, mods []blueprint.Module) ([]distContributions, []*ModuleInfoJSON) {
+	var allDistContributions []distContributions
+	var moduleInfoJSONs []*ModuleInfoJSON
+	for _, mod := range mods {
+		if distInfo, ok := OtherModuleProvider(ctx, mod, DistProvider); ok {
+			if contribution := distsToDistContributions(distInfo.Dists); contribution != nil {
+				allDistContributions = append(allDistContributions, *contribution)
+			}
+		}
+
+		if amod, ok := mod.(Module); ok && shouldSkipAndroidMkProcessing(ctx, amod.base()) {
+			continue
+		}
+		if info, ok := OtherModuleProvider(ctx, mod, AndroidMkInfoProvider); ok {
+			// Deep copy the provider info since we need to modify the info later
+			info := deepCopyAndroidMkProviderInfo(info)
+			info.PrimaryInfo.fillInEntries(ctx, mod)
+			if info.PrimaryInfo.disabled() {
+				continue
+			}
+			if moduleInfoJSON, ok := OtherModuleProvider(ctx, mod, ModuleInfoJSONProvider); ok {
+				moduleInfoJSONs = append(moduleInfoJSONs, moduleInfoJSON...)
+			}
+			if contribution := info.PrimaryInfo.getDistContributions(ctx, mod); contribution != nil {
+				allDistContributions = append(allDistContributions, *contribution)
+			}
+			for _, ei := range info.ExtraInfo {
+				ei.fillInEntries(ctx, mod)
+				if ei.disabled() {
+					continue
+				}
+				if contribution := ei.getDistContributions(ctx, mod); contribution != nil {
+					allDistContributions = append(allDistContributions, *contribution)
+				}
+			}
+		} else {
+			if x, ok := mod.(AndroidMkDataProvider); ok {
+				data := x.AndroidMk()
+
+				if data.Include == "" {
+					data.Include = "$(BUILD_PREBUILT)"
+				}
+
+				data.fillInData(ctx, mod)
+				if data.Entries.disabled() {
+					continue
+				}
+				if moduleInfoJSON, ok := OtherModuleProvider(ctx, mod, ModuleInfoJSONProvider); ok {
+					moduleInfoJSONs = append(moduleInfoJSONs, moduleInfoJSON...)
+				}
+				if contribution := data.Entries.getDistContributions(mod); contribution != nil {
+					allDistContributions = append(allDistContributions, *contribution)
+				}
+			}
+			if x, ok := mod.(AndroidMkEntriesProvider); ok {
+				entriesList := x.AndroidMkEntries()
+				for _, entries := range entriesList {
+					entries.fillInEntries(ctx, mod)
+					if entries.disabled() {
+						continue
+					}
+					if moduleInfoJSON, ok := OtherModuleProvider(ctx, mod, ModuleInfoJSONProvider); ok {
+						moduleInfoJSONs = append(moduleInfoJSONs, moduleInfoJSON...)
+					}
+					if contribution := entries.getDistContributions(mod); contribution != nil {
+						allDistContributions = append(allDistContributions, *contribution)
+					}
+				}
+			}
+			if x, ok := mod.(ModuleMakeVarsProvider); ok {
+				mctx := &makeVarsContext{
+					SingletonContext: ctx.(SingletonContext),
+					config:           ctx.Config(),
+					pctx:             pctx,
+				}
+				if !x.Enabled(ctx) {
+					continue
+				}
+				x.MakeVars(mctx)
+				if contribution := distsToDistContributions(mctx.dists); contribution != nil {
+					allDistContributions = append(allDistContributions, *contribution)
+				}
+			}
+			if x, ok := mod.(SingletonMakeVarsProvider); ok {
+				mctx := &makeVarsContext{
+					SingletonContext: ctx.(SingletonContext),
+					config:           ctx.Config(),
+					pctx:             pctx,
+				}
+				x.MakeVars(mctx)
+				if contribution := distsToDistContributions(mctx.dists); contribution != nil {
+					allDistContributions = append(allDistContributions, *contribution)
+				}
+			}
+		}
+	}
+	return allDistContributions, moduleInfoJSONs
+}
+
 func translateAndroidMk(ctx SingletonContext, absMkFile string, moduleInfoJSONPath WritablePath, mods []blueprint.Module) error {
 	buf := &bytes.Buffer{}
 
@@ -807,9 +1067,8 @@
 	// Additional cases here require review for correct license propagation to make.
 	var err error
 
-	if info, ok := ctx.otherModuleProvider(mod, AndroidMkInfoProvider); ok {
-		androidMkEntriesInfos := info.(*AndroidMkProviderInfo)
-		err = translateAndroidMkEntriesInfoModule(ctx, w, moduleInfoJSONs, mod, androidMkEntriesInfos)
+	if info, ok := OtherModuleProvider(ctx, mod, AndroidMkInfoProvider); ok {
+		err = translateAndroidMkEntriesInfoModule(ctx, w, moduleInfoJSONs, mod, info)
 	} else {
 		switch x := mod.(type) {
 		case AndroidMkDataProvider:
@@ -833,7 +1092,6 @@
 	data.Entries = AndroidMkEntries{
 		Class:           data.Class,
 		SubName:         data.SubName,
-		DistFiles:       data.DistFiles,
 		OutputFile:      data.OutputFile,
 		Disabled:        data.Disabled,
 		Include:         data.Include,
@@ -921,7 +1179,7 @@
 
 	if !data.Entries.disabled() {
 		if moduleInfoJSON, ok := OtherModuleProvider(ctx, mod, ModuleInfoJSONProvider); ok {
-			*moduleInfoJSONs = append(*moduleInfoJSONs, moduleInfoJSON)
+			*moduleInfoJSONs = append(*moduleInfoJSONs, moduleInfoJSON...)
 		}
 	}
 
@@ -955,15 +1213,20 @@
 	entriesList := provider.AndroidMkEntries()
 	aconfigUpdateAndroidMkEntries(ctx, mod.(Module), &entriesList)
 
+	moduleInfoJSON, providesModuleInfoJSON := OtherModuleProvider(ctx, mod, ModuleInfoJSONProvider)
+
 	// Any new or special cases here need review to verify correct propagation of license information.
 	for _, entries := range entriesList {
 		entries.fillInEntries(ctx, mod)
 		entries.write(w)
-	}
 
-	if len(entriesList) > 0 && !entriesList[0].disabled() {
-		if moduleInfoJSON, ok := OtherModuleProvider(ctx, mod, ModuleInfoJSONProvider); ok {
-			*moduleInfoJSONs = append(*moduleInfoJSONs, moduleInfoJSON)
+		if providesModuleInfoJSON && !entries.disabled() {
+			// append only the name matching moduleInfoJSON entry
+			for _, m := range moduleInfoJSON {
+				if m.RegisterNameOverride == entries.OverrideName && m.SubName == entries.SubName {
+					*moduleInfoJSONs = append(*moduleInfoJSONs, m)
+				}
+			}
 		}
 	}
 
@@ -1100,6 +1363,10 @@
 	EntryOrder []string
 }
 
+type AndroidMkProviderInfoProducer interface {
+	PrepareAndroidMKProviderInfo(config Config) *AndroidMkProviderInfo
+}
+
 // TODO: rename it to AndroidMkEntriesProvider after AndroidMkEntriesProvider interface is gone.
 var AndroidMkInfoProvider = blueprint.NewProvider[*AndroidMkProviderInfo]()
 
@@ -1126,7 +1393,7 @@
 
 	if !info.PrimaryInfo.disabled() {
 		if moduleInfoJSON, ok := OtherModuleProvider(ctx, mod, ModuleInfoJSONProvider); ok {
-			*moduleInfoJSONs = append(*moduleInfoJSONs, moduleInfoJSON)
+			*moduleInfoJSONs = append(*moduleInfoJSONs, moduleInfoJSON...)
 		}
 	}
 
@@ -1272,7 +1539,7 @@
 		a.HeaderStrings = append(a.HeaderStrings, distString)
 	}
 
-	a.HeaderStrings = append(a.HeaderStrings, fmt.Sprintf("\ninclude $(CLEAR_VARS)  # type: %s, name: %s, variant: %s\n", ctx.ModuleType(mod), base.BaseModuleName(), ctx.ModuleSubDir(mod)))
+	a.HeaderStrings = append(a.HeaderStrings, fmt.Sprintf("\ninclude $(CLEAR_VARS)  # type: %s, name: %s, variant: %s", ctx.ModuleType(mod), base.BaseModuleName(), ctx.ModuleSubDir(mod)))
 
 	// Collect make variable assignment entries.
 	helperInfo.SetString("LOCAL_PATH", ctx.ModuleDir(mod))
@@ -1300,6 +1567,14 @@
 		helperInfo.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", proptools.Bool(base.commonProperties.No_full_install))
 	}
 
+	if info.UncheckedModule {
+		helperInfo.SetBool("LOCAL_DONT_CHECK_MODULE", true)
+	} else if info.CheckbuildTarget != nil {
+		helperInfo.SetPath("LOCAL_CHECKED_MODULE", info.CheckbuildTarget)
+	} else {
+		helperInfo.SetOptionalPath("LOCAL_CHECKED_MODULE", a.OutputFile)
+	}
+
 	if len(info.TestData) > 0 {
 		helperInfo.AddStrings("LOCAL_TEST_DATA", androidMkDataPaths(info.TestData)...)
 	}
@@ -1364,25 +1639,6 @@
 		helperInfo.SetString("LOCAL_IS_HOST_MODULE", "true")
 	}
 
-	prefix := ""
-	if base.ArchSpecific() {
-		switch base.Os().Class {
-		case Host:
-			if base.Target().HostCross {
-				prefix = "HOST_CROSS_"
-			} else {
-				prefix = "HOST_"
-			}
-		case Device:
-			prefix = "TARGET_"
-
-		}
-
-		if base.Arch().ArchType != ctx.Config().Targets[base.Os()][0].Arch.ArchType {
-			prefix = "2ND_" + prefix
-		}
-	}
-
 	if licenseMetadata, ok := OtherModuleProvider(ctx, mod, LicenseMetadataProvider); ok {
 		helperInfo.SetPath("LOCAL_SOONG_LICENSE_METADATA", licenseMetadata.LicenseMetadataPath)
 	}
@@ -1423,8 +1679,8 @@
 		return
 	}
 
-	combinedHeaderString := strings.Join(a.HeaderStrings, "\n")
-	combinedFooterString := strings.Join(a.FooterStrings, "\n")
+	combinedHeaderString := strings.Join(a.HeaderStrings, "\n") + "\n"
+	combinedFooterString := strings.Join(a.FooterStrings, "\n") + "\n"
 	w.Write([]byte(combinedHeaderString))
 	for _, name := range a.EntryOrder {
 		AndroidMkEmitAssignList(w, name, a.EntryMap[name])
diff --git a/android/androidmk_test.go b/android/androidmk_test.go
index c37eeab..0a81fb8 100644
--- a/android/androidmk_test.go
+++ b/android/androidmk_test.go
@@ -144,7 +144,7 @@
 		FixtureWithRootAndroidBp(bp),
 	).RunTest(t)
 
-	module := result.ModuleForTests("foo", "").Module().(*customModule)
+	module := result.ModuleForTests(t, "foo", "").Module().(*customModule)
 	return result.TestContext, module
 }
 
@@ -195,8 +195,7 @@
 $(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic))
 $(call dist-for-goals,my_goal,one.out:one.out)
 $(if $(strip $(ALL_TARGETS.two.out.META_LIC)),,$(eval ALL_TARGETS.two.out.META_LIC := meta_lic))
-$(call dist-for-goals,my_goal,two.out:other.out)
-`, strings.Join(makeOutput, ""))
+$(call dist-for-goals,my_goal,two.out:other.out)`, strings.Join(makeOutput, "\n"))
 }
 
 func TestGetDistForGoals(t *testing.T) {
@@ -235,28 +234,28 @@
 			`
 
 	expectedAndroidMkLines := []string{
-		".PHONY: my_second_goal\n",
-		"$(if $(strip $(ALL_TARGETS.two.out.META_LIC)),,$(eval ALL_TARGETS.two.out.META_LIC := meta_lic))\n",
-		"$(call dist-for-goals,my_second_goal,two.out:two.out)\n",
-		"$(if $(strip $(ALL_TARGETS.three/four.out.META_LIC)),,$(eval ALL_TARGETS.three/four.out.META_LIC := meta_lic))\n",
-		"$(call dist-for-goals,my_second_goal,three/four.out:four.out)\n",
-		".PHONY: my_third_goal\n",
-		"$(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic))\n",
-		"$(call dist-for-goals,my_third_goal,one.out:test/dir/one.out)\n",
-		".PHONY: my_fourth_goal\n",
-		"$(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic))\n",
-		"$(call dist-for-goals,my_fourth_goal,one.out:one.suffix.out)\n",
-		".PHONY: my_fifth_goal\n",
-		"$(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic))\n",
-		"$(call dist-for-goals,my_fifth_goal,one.out:new-name)\n",
-		".PHONY: my_sixth_goal\n",
-		"$(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic))\n",
-		"$(call dist-for-goals,my_sixth_goal,one.out:some/dir/new-name.suffix)\n",
-		".PHONY: my_goal my_other_goal\n",
-		"$(if $(strip $(ALL_TARGETS.two.out.META_LIC)),,$(eval ALL_TARGETS.two.out.META_LIC := meta_lic))\n",
-		"$(call dist-for-goals,my_goal my_other_goal,two.out:two.out)\n",
-		"$(if $(strip $(ALL_TARGETS.three/four.out.META_LIC)),,$(eval ALL_TARGETS.three/four.out.META_LIC := meta_lic))\n",
-		"$(call dist-for-goals,my_goal my_other_goal,three/four.out:four.out)\n",
+		".PHONY: my_second_goal",
+		"$(if $(strip $(ALL_TARGETS.two.out.META_LIC)),,$(eval ALL_TARGETS.two.out.META_LIC := meta_lic))",
+		"$(call dist-for-goals,my_second_goal,two.out:two.out)",
+		"$(if $(strip $(ALL_TARGETS.three/four.out.META_LIC)),,$(eval ALL_TARGETS.three/four.out.META_LIC := meta_lic))",
+		"$(call dist-for-goals,my_second_goal,three/four.out:four.out)",
+		".PHONY: my_third_goal",
+		"$(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic))",
+		"$(call dist-for-goals,my_third_goal,one.out:test/dir/one.out)",
+		".PHONY: my_fourth_goal",
+		"$(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic))",
+		"$(call dist-for-goals,my_fourth_goal,one.out:one.suffix.out)",
+		".PHONY: my_fifth_goal",
+		"$(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic))",
+		"$(call dist-for-goals,my_fifth_goal,one.out:new-name)",
+		".PHONY: my_sixth_goal",
+		"$(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic))",
+		"$(call dist-for-goals,my_sixth_goal,one.out:some/dir/new-name.suffix)",
+		".PHONY: my_goal my_other_goal",
+		"$(if $(strip $(ALL_TARGETS.two.out.META_LIC)),,$(eval ALL_TARGETS.two.out.META_LIC := meta_lic))",
+		"$(call dist-for-goals,my_goal my_other_goal,two.out:two.out)",
+		"$(if $(strip $(ALL_TARGETS.three/four.out.META_LIC)),,$(eval ALL_TARGETS.three/four.out.META_LIC := meta_lic))",
+		"$(call dist-for-goals,my_goal my_other_goal,three/four.out:four.out)",
 	}
 
 	ctx, module := buildContextAndCustomModuleFoo(t, bp)
diff --git a/android/apex.go b/android/apex.go
index 79ab13c..780d091 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -16,9 +16,7 @@
 
 import (
 	"fmt"
-	"reflect"
 	"slices"
-	"sort"
 	"strconv"
 	"strings"
 	"sync"
@@ -56,36 +54,32 @@
 	// to true.
 	UsePlatformApis bool
 
-	// List of Apex variant names that this module is associated with. This initially is the
-	// same as the `ApexVariationName` field.  Then when multiple apex variants are merged in
-	// mergeApexVariations, ApexInfo struct of the merged variant holds the list of apexBundles
-	// that are merged together.
-	InApexVariants []string
-
-	// List of APEX Soong module names that this module is part of. Note that the list includes
-	// different variations of the same APEX. For example, if module `foo` is included in the
-	// apex `com.android.foo`, and also if there is an override_apex module
-	// `com.mycompany.android.foo` overriding `com.android.foo`, then this list contains both
-	// `com.android.foo` and `com.mycompany.android.foo`.  If the APEX Soong module is a
-	// prebuilt, the name here doesn't have the `prebuilt_` prefix.
-	InApexModules []string
-
-	// Pointers to the ApexContents struct each of which is for apexBundle modules that this
-	// module is part of. The ApexContents gives information about which modules the apexBundle
-	// has and whether a module became part of the apexBundle via a direct dependency or not.
-	ApexContents []*ApexContents
-
 	// True if this is for a prebuilt_apex.
 	//
 	// If true then this will customize the apex processing to make it suitable for handling
 	// prebuilt_apex, e.g. it will prevent ApexInfos from being merged together.
 	//
-	// See Prebuilt.ApexInfoMutator for more information.
+	// Unlike the source apex module type the prebuilt_apex module type cannot share compatible variants
+	// across prebuilt_apex modules. That is because there is no way to determine whether two
+	// prebuilt_apex modules that export files for the same module are compatible. e.g. they could have
+	// been built from different source at different times or they could have been built with different
+	// build options that affect the libraries.
+	//
+	// While it may be possible to provide sufficient information to determine whether two prebuilt_apex
+	// modules were compatible it would be a lot of work and would not provide much benefit for a couple
+	// of reasons:
+	//   - The number of prebuilt_apex modules that will be exporting files for the same module will be
+	//     low as the prebuilt_apex only exports files for the direct dependencies that require it and
+	//     very few modules are direct dependencies of multiple prebuilt_apex modules, e.g. there are a
+	//     few com.android.art* apex files that contain the same contents and could export files for the
+	//     same modules but only one of them needs to do so. Contrast that with source apex modules which
+	//     need apex specific variants for every module that contributes code to the apex, whether direct
+	//     or indirect.
+	//   - The build cost of a prebuilt_apex variant is generally low as at worst it will involve some
+	//     extra copying of files. Contrast that with source apex modules that has to build each variant
+	//     from source.
 	ForPrebuiltApex bool
 
-	// Returns the name of the test apexes that this module is included in.
-	TestApexes []string
-
 	// Returns the name of the overridden apex (com.android.foo)
 	BaseApexName string
 
@@ -93,20 +87,34 @@
 	ApexAvailableName string
 }
 
-// AllApexInfo holds the ApexInfo of all apexes that include this module.
-type AllApexInfo struct {
-	ApexInfos []ApexInfo
+func (a ApexInfo) Variation() string {
+	return a.ApexVariationName
+}
+
+// Minimize is called during a transition from a module with a unique variation per apex to a module that should
+// share variations between apexes.  It returns a minimized ApexInfo that removes any apex names and replaces
+// the variation name with one computed from the remaining properties.
+func (a ApexInfo) Minimize() ApexInfo {
+	info := ApexInfo{
+		MinSdkVersion:   a.MinSdkVersion,
+		UsePlatformApis: a.UsePlatformApis,
+	}
+	info.ApexVariationName = info.mergedName()
+	return info
+}
+
+type ApexAvailableInfo struct {
+	// Returns the apex names that this module is available for
+	ApexAvailableFor []string
 }
 
 var ApexInfoProvider = blueprint.NewMutatorProvider[ApexInfo]("apex_mutate")
-var AllApexInfoProvider = blueprint.NewMutatorProvider[*AllApexInfo]("apex_info")
+var ApexAvailableInfoProvider = blueprint.NewMutatorProvider[ApexAvailableInfo]("apex_mutate")
 
 func (i ApexInfo) AddJSONData(d *map[string]interface{}) {
 	(*d)["Apex"] = map[string]interface{}{
 		"ApexVariationName": i.ApexVariationName,
 		"MinSdkVersion":     i.MinSdkVersion,
-		"InApexModules":     i.InApexModules,
-		"InApexVariants":    i.InApexVariants,
 		"ForPrebuiltApex":   i.ForPrebuiltApex,
 	}
 }
@@ -120,6 +128,9 @@
 // thus wouldn't be merged.
 func (i ApexInfo) mergedName() string {
 	name := "apex" + strconv.Itoa(i.MinSdkVersion.FinalOrFutureInt())
+	if i.UsePlatformApis {
+		name += "_p"
+	}
 	return name
 }
 
@@ -129,68 +140,54 @@
 	return i.ApexVariationName == ""
 }
 
-// InApexVariant tells whether this apex variant of the module is part of the given apexVariant or
-// not.
-func (i ApexInfo) InApexVariant(apexVariant string) bool {
-	for _, a := range i.InApexVariants {
-		if a == apexVariant {
-			return true
-		}
-	}
-	return false
-}
-
-func (i ApexInfo) InApexModule(apexModuleName string) bool {
-	for _, a := range i.InApexModules {
-		if a == apexModuleName {
-			return true
-		}
-	}
-	return false
-}
-
 // To satisfy the comparable interface
 func (i ApexInfo) Equal(other any) bool {
 	otherApexInfo, ok := other.(ApexInfo)
 	return ok && i.ApexVariationName == otherApexInfo.ApexVariationName &&
 		i.MinSdkVersion == otherApexInfo.MinSdkVersion &&
 		i.Updatable == otherApexInfo.Updatable &&
-		i.UsePlatformApis == otherApexInfo.UsePlatformApis &&
-		reflect.DeepEqual(i.InApexVariants, otherApexInfo.InApexVariants) &&
-		reflect.DeepEqual(i.InApexModules, otherApexInfo.InApexModules)
+		i.UsePlatformApis == otherApexInfo.UsePlatformApis
 }
 
-// ApexTestForInfo stores the contents of APEXes for which this module is a test - although this
-// module is not part of the APEX - and thus has access to APEX internals.
-type ApexTestForInfo struct {
-	ApexContents []*ApexContents
-}
-
-var ApexTestForInfoProvider = blueprint.NewMutatorProvider[ApexTestForInfo]("apex_test_for")
-
 // ApexBundleInfo contains information about the dependencies of an apex
 type ApexBundleInfo struct {
-	Contents *ApexContents
 }
 
-var ApexBundleInfoProvider = blueprint.NewMutatorProvider[ApexBundleInfo]("apex_info")
+var ApexBundleInfoProvider = blueprint.NewMutatorProvider[ApexBundleInfo]("apex_mutate")
 
-// DepIsInSameApex defines an interface that should be used to determine whether a given dependency
-// should be considered as part of the same APEX as the current module or not. Note: this was
-// extracted from ApexModule to make it easier to define custom subsets of the ApexModule interface
-// and improve code navigation within the IDE.
-type DepIsInSameApex interface {
-	// DepIsInSameApex tests if the other module 'dep' is considered as part of the same APEX as
-	// this module. For example, a static lib dependency usually returns true here, while a
+// DepInSameApexChecker defines an interface that should be used to determine whether a given dependency
+// should be considered as part of the same APEX as the current module or not.
+type DepInSameApexChecker interface {
+	// OutgoingDepIsInSameApex tests if the module depended on via 'tag' is considered as part of
+	// the same APEX as this module. For example, a static lib dependency usually returns true here, while a
 	// shared lib dependency to a stub library returns false.
 	//
 	// This method must not be called directly without first ignoring dependencies whose tags
 	// implement ExcludeFromApexContentsTag. Calls from within the func passed to WalkPayloadDeps()
 	// are fine as WalkPayloadDeps() will ignore those dependencies automatically. Otherwise, use
 	// IsDepInSameApex instead.
-	DepIsInSameApex(ctx BaseModuleContext, dep Module) bool
+	OutgoingDepIsInSameApex(tag blueprint.DependencyTag) bool
+
+	// IncomingDepIsInSameApex tests if this module depended on via 'tag' is considered as part of
+	// the same APEX as the depending module module. For example, a static lib dependency usually
+	// returns true here, while a shared lib dependency to a stub library returns false.
+	//
+	// This method must not be called directly without first ignoring dependencies whose tags
+	// implement ExcludeFromApexContentsTag. Calls from within the func passed to WalkPayloadDeps()
+	// are fine as WalkPayloadDeps() will ignore those dependencies automatically. Otherwise, use
+	// IsDepInSameApex instead.
+	IncomingDepIsInSameApex(tag blueprint.DependencyTag) bool
 }
 
+// DepInSameApexInfo is a provider that wraps around a DepInSameApexChecker that can be
+// used to check if a dependency belongs to the same apex as the module when walking
+// through the dependencies of a module.
+type DepInSameApexInfo struct {
+	Checker DepInSameApexChecker
+}
+
+var DepInSameApexInfoProvider = blueprint.NewMutatorProvider[DepInSameApexInfo]("apex_unique")
+
 func IsDepInSameApex(ctx BaseModuleContext, module, dep Module) bool {
 	depTag := ctx.OtherModuleDependencyTag(dep)
 	if _, ok := depTag.(ExcludeFromApexContentsTag); ok {
@@ -198,7 +195,25 @@
 		// apex as the parent.
 		return false
 	}
-	return module.(DepIsInSameApex).DepIsInSameApex(ctx, dep)
+
+	if !ctx.EqualModules(ctx.Module(), module) {
+		if moduleInfo, ok := OtherModuleProvider(ctx, module, DepInSameApexInfoProvider); ok {
+			if !moduleInfo.Checker.OutgoingDepIsInSameApex(depTag) {
+				return false
+			}
+		}
+	} else {
+		if m, ok := ctx.Module().(ApexModule); ok && !m.GetDepInSameApexChecker().OutgoingDepIsInSameApex(depTag) {
+			return false
+		}
+	}
+	if depInfo, ok := OtherModuleProvider(ctx, dep, DepInSameApexInfoProvider); ok {
+		if !depInfo.Checker.IncomingDepIsInSameApex(depTag) {
+			return false
+		}
+	}
+
+	return true
 }
 
 // ApexModule is the interface that a module type is expected to implement if the module has to be
@@ -216,7 +231,6 @@
 // mergedName) when the two APEXes have the same min_sdk_version requirement.
 type ApexModule interface {
 	Module
-	DepIsInSameApex
 
 	apexModuleBase() *ApexModuleBase
 
@@ -228,15 +242,8 @@
 	// this after apex.apexMutator is run.
 	InAnyApex() bool
 
-	// Returns true if this module is directly in any APEX. Call this AFTER apex.apexMutator is
-	// run.
-	DirectlyInAnyApex() bool
-
-	// NotInPlatform tells whether or not this module is included in an APEX and therefore
-	// shouldn't be exposed to the platform (i.e. outside of the APEX) directly. A module is
-	// considered to be included in an APEX either when there actually is an APEX that
-	// explicitly has the module as its dependency or the module is not available to the
-	// platform, which indicates that the module belongs to at least one or more other APEXes.
+	// NotInPlatform returns true if the module is not available to the platform due to
+	// apex_available being set and not containing "//apex_available:platform".
 	NotInPlatform() bool
 
 	// Tests if this module could have APEX variants. Even when a module type implements
@@ -254,6 +261,12 @@
 	// apex_available property of the module.
 	AvailableFor(what string) bool
 
+	// Returns the apexes that are available for this module, valid values include
+	// "//apex_available:platform", "//apex_available:anyapex" and specific apexes.
+	// There are some differences between this one and the ApexAvailable on
+	// ApexModuleBase for cc, java library and sdkLibraryXml.
+	ApexAvailableFor() []string
+
 	// AlwaysRequiresPlatformApexVariant allows the implementing module to determine whether an
 	// APEX mutator should always be created for it.
 	//
@@ -269,12 +282,6 @@
 	// check-platform-availability mutator in the apex package.
 	SetNotAvailableForPlatform()
 
-	// Returns the list of APEXes that this module is a test for. The module has access to the
-	// private part of the listed APEXes even when it is not included in the APEXes. This by
-	// default returns nil. A module type should override the default implementation. For
-	// example, cc_test module type returns the value of test_for here.
-	TestFor() []string
-
 	// Returns nil (success) if this module should support the given sdk version. Returns an
 	// error if not. No default implementation is provided for this method. A module type
 	// implementing this interface should provide an implementation. A module supports an sdk
@@ -285,6 +292,8 @@
 	// deduping. This is turned on when, for example if use_apex_name_macro is set so that each
 	// apex variant should be built with different macro definitions.
 	UniqueApexVariations() bool
+
+	GetDepInSameApexChecker() DepInSameApexChecker
 }
 
 // Properties that are common to all module types implementing ApexModule interface.
@@ -299,35 +308,15 @@
 	// Default is ["//apex_available:platform"].
 	Apex_available []string
 
-	// See ApexModule.InAnyApex()
-	InAnyApex bool `blueprint:"mutated"`
-
-	// See ApexModule.DirectlyInAnyApex()
-	DirectlyInAnyApex bool `blueprint:"mutated"`
-
-	// AnyVariantDirectlyInAnyApex is true in the primary variant of a module if _any_ variant
-	// of the module is directly in any apex. This includes host, arch, asan, etc. variants. It
-	// is unused in any variant that is not the primary variant. Ideally this wouldn't be used,
-	// as it incorrectly mixes arch variants if only one arch is in an apex, but a few places
-	// depend on it, for example when an ASAN variant is created before the apexMutator. Call
-	// this after apex.apexMutator is run.
-	AnyVariantDirectlyInAnyApex bool `blueprint:"mutated"`
-
 	// See ApexModule.NotAvailableForPlatform()
 	NotAvailableForPlatform bool `blueprint:"mutated"`
 
 	// See ApexModule.UniqueApexVariants()
 	UniqueApexVariationsForDeps bool `blueprint:"mutated"`
-
-	// The test apexes that includes this apex variant
-	TestApexes []string `blueprint:"mutated"`
 }
 
 // Marker interface that identifies dependencies that are excluded from APEX contents.
 //
-// Unless the tag also implements the AlwaysRequireApexVariantTag this will prevent an apex variant
-// from being created for the module.
-//
 // At the moment the sdk.sdkRequirementsMutator relies on the fact that the existing tags which
 // implement this interface do not define dependencies onto members of an sdk_snapshot. If that
 // changes then sdk.sdkRequirementsMutator will need fixing.
@@ -338,27 +327,6 @@
 	ExcludeFromApexContents()
 }
 
-// Marker interface that identifies dependencies that always requires an APEX variant to be created.
-//
-// It is possible for a dependency to require an apex variant but exclude the module from the APEX
-// contents. See sdk.sdkMemberDependencyTag.
-type AlwaysRequireApexVariantTag interface {
-	blueprint.DependencyTag
-
-	// Return true if this tag requires that the target dependency has an apex variant.
-	AlwaysRequireApexVariant() bool
-}
-
-// Marker interface that identifies dependencies that should inherit the DirectlyInAnyApex state
-// from the parent to the child. For example, stubs libraries are marked as DirectlyInAnyApex if
-// their implementation is in an apex.
-type CopyDirectlyInAnyApexTag interface {
-	blueprint.DependencyTag
-
-	// Method that differentiates this interface from others.
-	CopyDirectlyInAnyApex()
-}
-
 // Interface that identifies dependencies to skip Apex dependency check
 type SkipApexAllowedDependenciesCheck interface {
 	// Returns true to skip the Apex dependency check, which limits the allowed dependency in build.
@@ -377,6 +345,61 @@
 	apexInfosLock sync.Mutex // protects apexInfos during parallel apexInfoMutator
 }
 
+func (m *ApexModuleBase) ApexTransitionMutatorSplit(ctx BaseModuleContext) []ApexInfo {
+	return []ApexInfo{{}}
+}
+
+func (m *ApexModuleBase) ApexTransitionMutatorOutgoing(ctx OutgoingTransitionContext, info ApexInfo) ApexInfo {
+	if !ctx.Module().(ApexModule).GetDepInSameApexChecker().OutgoingDepIsInSameApex(ctx.DepTag()) {
+		return ApexInfo{}
+	}
+	return info
+}
+
+func (m *ApexModuleBase) ApexTransitionMutatorIncoming(ctx IncomingTransitionContext, info ApexInfo) ApexInfo {
+	module := ctx.Module().(ApexModule)
+	if !module.CanHaveApexVariants() {
+		return ApexInfo{}
+	}
+
+	if !ctx.Module().(ApexModule).GetDepInSameApexChecker().IncomingDepIsInSameApex(ctx.DepTag()) {
+		return ApexInfo{}
+	}
+
+	if info.ApexVariationName == "" {
+		return ApexInfo{}
+	}
+
+	if !ctx.Module().(ApexModule).UniqueApexVariations() && !m.ApexProperties.UniqueApexVariationsForDeps && !info.ForPrebuiltApex {
+		return info.Minimize()
+	}
+	return info
+}
+
+func (m *ApexModuleBase) ApexTransitionMutatorMutate(ctx BottomUpMutatorContext, info ApexInfo) {
+	SetProvider(ctx, ApexInfoProvider, info)
+
+	module := ctx.Module().(ApexModule)
+	base := module.apexModuleBase()
+
+	platformVariation := info.ApexVariationName == ""
+	if !platformVariation {
+		// Do some validity checks.
+		// TODO(jiyong): is this the right place?
+		base.checkApexAvailableProperty(ctx)
+
+		SetProvider(ctx, ApexAvailableInfoProvider, ApexAvailableInfo{
+			ApexAvailableFor: module.ApexAvailableFor(),
+		})
+	}
+	if platformVariation && !ctx.Host() && !module.AvailableFor(AvailableToPlatform) && module.NotAvailableForPlatform() {
+		// Do not install the module for platform, but still allow it to output
+		// uninstallable AndroidMk entries in certain cases when they have side
+		// effects.  TODO(jiyong): move this routine to somewhere else
+		module.MakeUninstallable()
+	}
+}
+
 // Initializes ApexModuleBase struct. Not calling this (even when inheriting from ApexModuleBase)
 // prevents the module from being mutated for apexBundle.
 func InitApexModule(m ApexModule) {
@@ -405,44 +428,35 @@
 	return CopyOf(availableToPlatformList)
 }
 
+func (m *ApexModuleBase) ApexAvailableFor() []string {
+	return m.ApexAvailable()
+}
+
 // Implements ApexModule
 func (m *ApexModuleBase) BuildForApex(apex ApexInfo) {
 	m.apexInfosLock.Lock()
 	defer m.apexInfosLock.Unlock()
-	for i, v := range m.apexInfos {
-		if v.ApexVariationName == apex.ApexVariationName {
-			if len(apex.InApexModules) != 1 {
-				panic(fmt.Errorf("Newly created apexInfo must be for a single APEX"))
-			}
-			// Even when the ApexVariantNames are the same, the given ApexInfo might
-			// actually be for different APEX. This can happen when an APEX is
-			// overridden via override_apex. For example, there can be two apexes
-			// `com.android.foo` (from the `apex` module type) and
-			// `com.mycompany.android.foo` (from the `override_apex` module type), both
-			// of which has the same ApexVariantName `com.android.foo`. Add the apex
-			// name to the list so that it's not lost.
-			if !InList(apex.InApexModules[0], v.InApexModules) {
-				m.apexInfos[i].InApexModules = append(m.apexInfos[i].InApexModules, apex.InApexModules[0])
-			}
-			return
-		}
+	if slices.ContainsFunc(m.apexInfos, func(existing ApexInfo) bool {
+		return existing.ApexVariationName == apex.ApexVariationName
+	}) {
+		return
 	}
 	m.apexInfos = append(m.apexInfos, apex)
 }
 
 // Implements ApexModule
 func (m *ApexModuleBase) InAnyApex() bool {
-	return m.ApexProperties.InAnyApex
-}
-
-// Implements ApexModule
-func (m *ApexModuleBase) DirectlyInAnyApex() bool {
-	return m.ApexProperties.DirectlyInAnyApex
+	for _, apex_name := range m.ApexProperties.Apex_available {
+		if apex_name != AvailableToPlatform {
+			return true
+		}
+	}
+	return false
 }
 
 // Implements ApexModule
 func (m *ApexModuleBase) NotInPlatform() bool {
-	return m.ApexProperties.AnyVariantDirectlyInAnyApex || !m.AvailableFor(AvailableToPlatform)
+	return !m.AvailableFor(AvailableToPlatform)
 }
 
 // Implements ApexModule
@@ -458,18 +472,6 @@
 }
 
 // Implements ApexModule
-func (m *ApexModuleBase) TestFor() []string {
-	// If needed, this will be overridden by concrete types inheriting
-	// ApexModuleBase
-	return nil
-}
-
-// Returns the test apexes that this module is included in.
-func (m *ApexModuleBase) TestApexes() []string {
-	return m.ApexProperties.TestApexes
-}
-
-// Implements ApexModule
 func (m *ApexModuleBase) UniqueApexVariations() bool {
 	// If needed, this will bel overridden by concrete types inheriting
 	// ApexModuleBase
@@ -477,11 +479,17 @@
 }
 
 // Implements ApexModule
-func (m *ApexModuleBase) DepIsInSameApex(ctx BaseModuleContext, dep Module) bool {
-	// By default, if there is a dependency from A to B, we try to include both in the same
-	// APEX, unless B is explicitly from outside of the APEX (i.e. a stubs lib). Thus, returning
-	// true. This is overridden by some module types like apex.ApexBundle, cc.Module,
-	// java.Module, etc.
+func (m *ApexModuleBase) GetDepInSameApexChecker() DepInSameApexChecker {
+	return BaseDepInSameApexChecker{}
+}
+
+type BaseDepInSameApexChecker struct{}
+
+func (m BaseDepInSameApexChecker) OutgoingDepIsInSameApex(tag blueprint.DependencyTag) bool {
+	return true
+}
+
+func (m BaseDepInSameApexChecker) IncomingDepIsInSameApex(tag blueprint.DependencyTag) bool {
 	return true
 }
 
@@ -519,13 +527,17 @@
 		if strings.HasSuffix(apex_name, ".*") && strings.HasPrefix(what, strings.TrimSuffix(apex_name, "*")) {
 			return true
 		}
+		// TODO b/383863941: Remove once legacy name is no longer used
+		if (apex_name == "com.android.btservices" && what == "com.android.bt") || (apex_name == "com.android.bt" && what == "com.android.btservices") {
+			return true
+		}
 	}
 	return false
 }
 
 // Implements ApexModule
 func (m *ApexModuleBase) AvailableFor(what string) bool {
-	return CheckAvailableForApex(what, m.ApexProperties.Apex_available)
+	return CheckAvailableForApex(what, m.ApexAvailableFor())
 }
 
 // Implements ApexModule
@@ -585,217 +597,14 @@
 	return true
 }
 
-// mergeApexVariations deduplicates apex variations that would build identically into a common
-// variation. It returns the reduced list of variations and a list of aliases from the original
-// variation names to the new variation names.
-func mergeApexVariations(apexInfos []ApexInfo) (merged []ApexInfo, aliases [][2]string) {
-	seen := make(map[string]int)
-	for _, apexInfo := range apexInfos {
-		// If this is for a prebuilt apex then use the actual name of the apex variation to prevent this
-		// from being merged with other ApexInfo. See Prebuilt.ApexInfoMutator for more information.
-		if apexInfo.ForPrebuiltApex {
-			merged = append(merged, apexInfo)
-			continue
-		}
-
-		// Merge the ApexInfo together. If a compatible ApexInfo exists then merge the information from
-		// this one into it, otherwise create a new merged ApexInfo from this one and save it away so
-		// other ApexInfo instances can be merged into it.
-		variantName := apexInfo.ApexVariationName
-		mergedName := apexInfo.mergedName()
-		if index, exists := seen[mergedName]; exists {
-			// Variants having the same mergedName are deduped
-			merged[index].InApexVariants = append(merged[index].InApexVariants, variantName)
-			merged[index].InApexModules = append(merged[index].InApexModules, apexInfo.InApexModules...)
-			merged[index].ApexContents = append(merged[index].ApexContents, apexInfo.ApexContents...)
-			merged[index].Updatable = merged[index].Updatable || apexInfo.Updatable
-			// Platform APIs is allowed for this module only when all APEXes containing
-			// the module are with `use_platform_apis: true`.
-			merged[index].UsePlatformApis = merged[index].UsePlatformApis && apexInfo.UsePlatformApis
-			merged[index].TestApexes = append(merged[index].TestApexes, apexInfo.TestApexes...)
-		} else {
-			seen[mergedName] = len(merged)
-			apexInfo.ApexVariationName = mergedName
-			apexInfo.InApexVariants = CopyOf(apexInfo.InApexVariants)
-			apexInfo.InApexModules = CopyOf(apexInfo.InApexModules)
-			apexInfo.ApexContents = append([]*ApexContents(nil), apexInfo.ApexContents...)
-			apexInfo.TestApexes = CopyOf(apexInfo.TestApexes)
-			merged = append(merged, apexInfo)
-		}
-		aliases = append(aliases, [2]string{variantName, mergedName})
-	}
-	return merged, aliases
-}
-
-// IncomingApexTransition is called by apexTransitionMutator.IncomingTransition on modules that can be in apexes.
-// The incomingVariation can be either the name of an apex if the dependency is coming directly from an apex
-// module, or it can be the name of an apex variation (e.g. apex10000) if it is coming from another module that
-// is in the apex.
-func IncomingApexTransition(ctx IncomingTransitionContext, incomingVariation string) string {
-	module := ctx.Module().(ApexModule)
-	base := module.apexModuleBase()
-
-	var apexInfos []ApexInfo
-	if allApexInfos, ok := ModuleProvider(ctx, AllApexInfoProvider); ok {
-		apexInfos = allApexInfos.ApexInfos
-	}
-
-	// Dependencies from platform variations go to the platform variation.
-	if incomingVariation == "" {
-		return ""
-	}
-
-	if len(apexInfos) == 0 {
-		if ctx.IsAddingDependency() {
-			// If this module has no apex variations we can't do any mapping on the incoming variation, just return it
-			// and let the caller get a "missing variant" error.
-			return incomingVariation
-		} else {
-			// If this module has no apex variations the use the platform variation.
-			return ""
-		}
-	}
-
-	// Convert the list of apex infos into from the AllApexInfoProvider into the merged list
-	// of apex variations and the aliases from apex names to apex variations.
-	var aliases [][2]string
-	if !module.UniqueApexVariations() && !base.ApexProperties.UniqueApexVariationsForDeps {
-		apexInfos, aliases = mergeApexVariations(apexInfos)
-	}
-
-	// Check if the incoming variation matches an apex name, and if so use the corresponding
-	// apex variation.
-	aliasIndex := slices.IndexFunc(aliases, func(alias [2]string) bool {
-		return alias[0] == incomingVariation
-	})
-	if aliasIndex >= 0 {
-		return aliases[aliasIndex][1]
-	}
-
-	// Check if the incoming variation matches an apex variation.
-	apexIndex := slices.IndexFunc(apexInfos, func(info ApexInfo) bool {
-		return info.ApexVariationName == incomingVariation
-	})
-	if apexIndex >= 0 {
-		return incomingVariation
-	}
-
-	return ""
-}
-
-func MutateApexTransition(ctx BaseModuleContext, variation string) {
-	module := ctx.Module().(ApexModule)
-	base := module.apexModuleBase()
-	platformVariation := variation == ""
-
-	var apexInfos []ApexInfo
-	if allApexInfos, ok := ModuleProvider(ctx, AllApexInfoProvider); ok {
-		apexInfos = allApexInfos.ApexInfos
-	}
-
-	// Shortcut
-	if len(apexInfos) == 0 {
-		return
-	}
-
-	// Do some validity checks.
-	// TODO(jiyong): is this the right place?
-	base.checkApexAvailableProperty(ctx)
-
-	if !module.UniqueApexVariations() && !base.ApexProperties.UniqueApexVariationsForDeps {
-		apexInfos, _ = mergeApexVariations(apexInfos)
-	}
-
-	var inApex ApexMembership
-	for _, a := range apexInfos {
-		for _, apexContents := range a.ApexContents {
-			inApex = inApex.merge(apexContents.contents[ctx.ModuleName()])
-		}
-	}
-	base.ApexProperties.InAnyApex = true
-	base.ApexProperties.DirectlyInAnyApex = inApex == directlyInApex
-
-	if platformVariation && !ctx.Host() && !module.AvailableFor(AvailableToPlatform) && module.NotAvailableForPlatform() {
-		// Do not install the module for platform, but still allow it to output
-		// uninstallable AndroidMk entries in certain cases when they have side
-		// effects.  TODO(jiyong): move this routine to somewhere else
-		module.MakeUninstallable()
-	}
-	if !platformVariation {
-		var thisApexInfo ApexInfo
-
-		apexIndex := slices.IndexFunc(apexInfos, func(info ApexInfo) bool {
-			return info.ApexVariationName == variation
-		})
-		if apexIndex >= 0 {
-			thisApexInfo = apexInfos[apexIndex]
-		} else {
-			panic(fmt.Errorf("failed to find apexInfo for incoming variation %q", variation))
-		}
-
-		SetProvider(ctx, ApexInfoProvider, thisApexInfo)
-	}
-
-	// Set the value of TestApexes in every single apex variant.
-	// This allows each apex variant to be aware of the test apexes in the user provided apex_available.
-	var testApexes []string
-	for _, a := range apexInfos {
-		testApexes = append(testApexes, a.TestApexes...)
-	}
-	base.ApexProperties.TestApexes = testApexes
-
-}
-
-func ApexInfoMutator(ctx TopDownMutatorContext, module ApexModule) {
-	base := module.apexModuleBase()
-	if len(base.apexInfos) > 0 {
-		apexInfos := slices.Clone(base.apexInfos)
-		slices.SortFunc(apexInfos, func(a, b ApexInfo) int {
-			return strings.Compare(a.ApexVariationName, b.ApexVariationName)
-		})
-		SetProvider(ctx, AllApexInfoProvider, &AllApexInfo{apexInfos})
-		// base.apexInfos is only needed to propagate the list of apexes from the apex module to its
-		// contents within apexInfoMutator. Clear it so it doesn't accidentally get used later.
-		base.apexInfos = nil
-	}
-}
-
 // UpdateUniqueApexVariationsForDeps sets UniqueApexVariationsForDeps if any dependencies that are
 // in the same APEX have unique APEX variations so that the module can link against the right
 // variant.
 func UpdateUniqueApexVariationsForDeps(mctx BottomUpMutatorContext, am ApexModule) {
-	// anyInSameApex returns true if the two ApexInfo lists contain any values in an
-	// InApexVariants list in common. It is used instead of DepIsInSameApex because it needs to
-	// determine if the dep is in the same APEX due to being directly included, not only if it
-	// is included _because_ it is a dependency.
-	anyInSameApex := func(a, b ApexModule) bool {
-		collectApexes := func(m ApexModule) []string {
-			if allApexInfo, ok := OtherModuleProvider(mctx, m, AllApexInfoProvider); ok {
-				var ret []string
-				for _, info := range allApexInfo.ApexInfos {
-					ret = append(ret, info.InApexVariants...)
-				}
-				return ret
-			}
-			return nil
-		}
-
-		aApexes := collectApexes(a)
-		bApexes := collectApexes(b)
-		sort.Strings(bApexes)
-		for _, aApex := range aApexes {
-			index := sort.SearchStrings(bApexes, aApex)
-			if index < len(bApexes) && bApexes[index] == aApex {
-				return true
-			}
-		}
-		return false
-	}
-
 	// If any of the dependencies requires unique apex variations, so does this module.
 	mctx.VisitDirectDeps(func(dep Module) {
 		if depApexModule, ok := dep.(ApexModule); ok {
-			if anyInSameApex(depApexModule, am) &&
+			if IsDepInSameApex(mctx, am, depApexModule) &&
 				(depApexModule.UniqueApexVariations() ||
 					depApexModule.apexModuleBase().ApexProperties.UniqueApexVariationsForDeps) {
 				am.apexModuleBase().ApexProperties.UniqueApexVariationsForDeps = true
@@ -804,115 +613,6 @@
 	})
 }
 
-// UpdateDirectlyInAnyApex uses the final module to store if any variant of this module is directly
-// in any APEX, and then copies the final value to all the modules. It also copies the
-// DirectlyInAnyApex value to any transitive dependencies with a CopyDirectlyInAnyApexTag
-// dependency tag.
-func UpdateDirectlyInAnyApex(mctx BottomUpMutatorContext, am ApexModule) {
-	base := am.apexModuleBase()
-	// Copy DirectlyInAnyApex and InAnyApex from any transitive dependencies with a
-	// CopyDirectlyInAnyApexTag dependency tag.
-	mctx.WalkDeps(func(child, parent Module) bool {
-		if _, ok := mctx.OtherModuleDependencyTag(child).(CopyDirectlyInAnyApexTag); ok {
-			depBase := child.(ApexModule).apexModuleBase()
-			depBase.apexPropertiesLock.Lock()
-			defer depBase.apexPropertiesLock.Unlock()
-			depBase.ApexProperties.DirectlyInAnyApex = base.ApexProperties.DirectlyInAnyApex
-			depBase.ApexProperties.InAnyApex = base.ApexProperties.InAnyApex
-			return true
-		}
-		return false
-	})
-
-	if base.ApexProperties.DirectlyInAnyApex {
-		// Variants of a module are always visited sequentially in order, so it is safe to
-		// write to another variant of this module. For a BottomUpMutator the
-		// PrimaryModule() is visited first and FinalModule() is visited last.
-		mctx.FinalModule().(ApexModule).apexModuleBase().ApexProperties.AnyVariantDirectlyInAnyApex = true
-	}
-
-	// If this is the FinalModule (last visited module) copy
-	// AnyVariantDirectlyInAnyApex to all the other variants
-	if am == mctx.FinalModule().(ApexModule) {
-		mctx.VisitAllModuleVariants(func(variant Module) {
-			variant.(ApexModule).apexModuleBase().ApexProperties.AnyVariantDirectlyInAnyApex =
-				base.ApexProperties.AnyVariantDirectlyInAnyApex
-		})
-	}
-}
-
-// ApexMembership tells how a module became part of an APEX.
-type ApexMembership int
-
-const (
-	notInApex        ApexMembership = 0
-	indirectlyInApex                = iota
-	directlyInApex
-)
-
-// ApexContents gives an information about member modules of an apexBundle.  Each apexBundle has an
-// apexContents, and modules in that apex have a provider containing the apexContents of each
-// apexBundle they are part of.
-type ApexContents struct {
-	// map from a module name to its membership in this apexBundle
-	contents map[string]ApexMembership
-}
-
-// NewApexContents creates and initializes an ApexContents that is suitable
-// for use with an apex module.
-//   - contents is a map from a module name to information about its membership within
-//     the apex.
-func NewApexContents(contents map[string]ApexMembership) *ApexContents {
-	return &ApexContents{
-		contents: contents,
-	}
-}
-
-// Updates an existing membership by adding a new direct (or indirect) membership
-func (i ApexMembership) Add(direct bool) ApexMembership {
-	if direct || i == directlyInApex {
-		return directlyInApex
-	}
-	return indirectlyInApex
-}
-
-// Merges two membership into one. Merging is needed because a module can be a part of an apexBundle
-// in many different paths. For example, it could be dependend on by the apexBundle directly, but at
-// the same time, there might be an indirect dependency to the module. In that case, the more
-// specific dependency (the direct one) is chosen.
-func (i ApexMembership) merge(other ApexMembership) ApexMembership {
-	if other == directlyInApex || i == directlyInApex {
-		return directlyInApex
-	}
-
-	if other == indirectlyInApex || i == indirectlyInApex {
-		return indirectlyInApex
-	}
-	return notInApex
-}
-
-// Tests whether a module named moduleName is directly included in the apexBundle where this
-// ApexContents is tagged.
-func (ac *ApexContents) DirectlyInApex(moduleName string) bool {
-	return ac.contents[moduleName] == directlyInApex
-}
-
-// Tests whether a module named moduleName is included in the apexBundle where this ApexContent is
-// tagged.
-func (ac *ApexContents) InApex(moduleName string) bool {
-	return ac.contents[moduleName] != notInApex
-}
-
-// Tests whether a module named moduleName is directly depended on by all APEXes in an ApexInfo.
-func DirectlyInAllApexes(apexInfo ApexInfo, moduleName string) bool {
-	for _, contents := range apexInfo.ApexContents {
-		if !contents.DirectlyInApex(moduleName) {
-			return false
-		}
-	}
-	return true
-}
-
 ////////////////////////////////////////////////////////////////////////////////////////////////////
 //Below are routines for extra safety checks.
 //
@@ -939,8 +639,8 @@
 type DepNameToDepInfoMap map[string]ApexModuleDepInfo
 
 type ApexBundleDepsInfo struct {
-	flatListPath OutputPath
-	fullListPath OutputPath
+	flatListPath Path
+	fullListPath Path
 }
 
 type ApexBundleDepsInfoIntf interface {
@@ -977,19 +677,21 @@
 		fmt.Fprintf(&flatContent, "%s\n", toName)
 	}
 
-	d.fullListPath = PathForModuleOut(ctx, "depsinfo", "fulllist.txt").OutputPath
-	WriteFileRule(ctx, d.fullListPath, fullContent.String())
+	fullListPath := PathForModuleOut(ctx, "depsinfo", "fulllist.txt")
+	WriteFileRule(ctx, fullListPath, fullContent.String())
+	d.fullListPath = fullListPath
 
-	d.flatListPath = PathForModuleOut(ctx, "depsinfo", "flatlist.txt").OutputPath
-	WriteFileRule(ctx, d.flatListPath, flatContent.String())
+	flatListPath := PathForModuleOut(ctx, "depsinfo", "flatlist.txt")
+	WriteFileRule(ctx, flatListPath, flatContent.String())
+	d.flatListPath = flatListPath
 
-	ctx.Phony(fmt.Sprintf("%s-depsinfo", ctx.ModuleName()), d.fullListPath, d.flatListPath)
+	ctx.Phony(fmt.Sprintf("%s-depsinfo", ctx.ModuleName()), fullListPath, flatListPath)
 }
 
 // Function called while walking an APEX's payload dependencies.
 //
 // Return true if the `to` module should be visited, false otherwise.
-type PayloadDepsCallback func(ctx BaseModuleContext, from blueprint.Module, to ApexModule, externalDep bool) bool
+type PayloadDepsCallback func(ctx BaseModuleContext, from Module, to ApexModule, externalDep bool) bool
 type WalkPayloadDepsFunc func(ctx BaseModuleContext, do PayloadDepsCallback)
 
 // ModuleWithMinSdkVersionCheck represents a module that implements min_sdk_version checks
@@ -1017,14 +719,14 @@
 		return
 	}
 
-	walk(ctx, func(ctx BaseModuleContext, from blueprint.Module, to ApexModule, externalDep bool) bool {
+	walk(ctx, func(ctx BaseModuleContext, from Module, to ApexModule, externalDep bool) bool {
 		if externalDep {
 			// external deps are outside the payload boundary, which is "stable"
 			// interface. We don't have to check min_sdk_version for external
 			// dependencies.
 			return false
 		}
-		if am, ok := from.(DepIsInSameApex); ok && !am.DepIsInSameApex(ctx, to) {
+		if !IsDepInSameApex(ctx, from, to) {
 			return false
 		}
 		if m, ok := to.(ModuleWithMinSdkVersionCheck); ok {
@@ -1049,8 +751,14 @@
 	})
 }
 
+type MinSdkVersionFromValueContext interface {
+	Config() Config
+	DeviceConfig() DeviceConfig
+	ModuleErrorContext
+}
+
 // Construct ApiLevel object from min_sdk_version string value
-func MinSdkVersionFromValue(ctx EarlyModuleContext, value string) ApiLevel {
+func MinSdkVersionFromValue(ctx MinSdkVersionFromValueContext, value string) ApiLevel {
 	if value == "" {
 		return NoneApiLevel
 	}
@@ -1062,12 +770,6 @@
 	return apiLevel
 }
 
-// Implemented by apexBundle.
-type ApexTestInterface interface {
-	// Return true if the apex bundle is an apex_test
-	IsTestApex() bool
-}
-
 var ApexExportsInfoProvider = blueprint.NewProvider[ApexExportsInfo]()
 
 // ApexExportsInfo contains information about the artifacts provided by apexes to dexpreopt and hiddenapi
@@ -1097,3 +799,21 @@
 	// to generate the mainline module prebuilt.
 	Prebuilt_info_file_path string `json:",omitempty"`
 }
+
+// FragmentInApexTag is embedded into a dependency tag to allow apex modules to annotate
+// their fragments in a way that allows the java bootclasspath modules to traverse from
+// the apex to the fragment.
+type FragmentInApexTag struct{}
+
+func (FragmentInApexTag) isFragmentInApexTag() {}
+
+type isFragmentInApexTagIntf interface {
+	isFragmentInApexTag()
+}
+
+// IsFragmentInApexTag returns true if the dependency tag embeds FragmentInApexTag,
+// signifying that it is a dependency from an apex module to its fragment.
+func IsFragmentInApexTag(tag blueprint.DependencyTag) bool {
+	_, ok := tag.(isFragmentInApexTagIntf)
+	return ok
+}
diff --git a/android/apex_contributions.go b/android/apex_contributions.go
index 4cd8dda..fe7a835 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
@@ -104,19 +104,8 @@
 	AcDepTag = apexContributionsDepTag{}
 )
 
-// Creates a dep to each selected apex_contributions
-func (a *allApexContributions) DepsMutator(ctx BottomUpMutatorContext) {
-	// Skip apex_contributions if BuildApexContributionContents is true
-	// This product config var allows some products in the same family to use mainline modules from source
-	// (e.g. shiba and shiba_fullmte)
-	// Eventually these product variants will have their own release config maps.
-	if !proptools.Bool(ctx.Config().BuildIgnoreApexContributionContents()) {
-		ctx.AddDependency(ctx.Module(), AcDepTag, ctx.Config().AllApexContributions()...)
-	}
-}
-
 // Set PrebuiltSelectionInfoProvider in post deps phase
-func (a *allApexContributions) SetPrebuiltSelectionInfoProvider(ctx BaseModuleContext) {
+func (a *allApexContributions) SetPrebuiltSelectionInfoProvider(ctx BottomUpMutatorContext) {
 	addContentsToProvider := func(p *PrebuiltSelectionInfoMap, m *apexContributions) {
 		for _, content := range m.Contents() {
 			// Verify that the module listed in contents exists in the tree
@@ -135,13 +124,23 @@
 	}
 
 	p := PrebuiltSelectionInfoMap{}
-	ctx.VisitDirectDepsWithTag(AcDepTag, func(child Module) {
-		if m, ok := child.(*apexContributions); ok {
-			addContentsToProvider(&p, m)
-		} else {
-			ctx.ModuleErrorf("%s is not an apex_contributions module\n", child.Name())
+	// Skip apex_contributions if BuildApexContributionContents is true
+	// This product config var allows some products in the same family to use mainline modules from source
+	// (e.g. shiba and shiba_fullmte)
+	// Eventually these product variants will have their own release config maps.
+	if !proptools.Bool(ctx.Config().BuildIgnoreApexContributionContents()) {
+		deps := ctx.AddDependency(ctx.Module(), AcDepTag, ctx.Config().AllApexContributions()...)
+		for _, child := range deps {
+			if child == nil {
+				continue
+			}
+			if m, ok := child.(*apexContributions); ok {
+				addContentsToProvider(&p, m)
+			} else {
+				ctx.ModuleErrorf("%s is not an apex_contributions module\n", child.Name())
+			}
 		}
-	})
+	}
 	SetProvider(ctx, PrebuiltSelectionInfoProvider, p)
 }
 
@@ -191,7 +190,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/apex_test.go b/android/apex_test.go
deleted file mode 100644
index 347bf7d..0000000
--- a/android/apex_test.go
+++ /dev/null
@@ -1,293 +0,0 @@
-// Copyright 2020 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 (
-	"reflect"
-	"testing"
-)
-
-func Test_mergeApexVariations(t *testing.T) {
-	const (
-		ForPrebuiltApex    = true
-		NotForPrebuiltApex = false
-	)
-	tests := []struct {
-		name        string
-		in          []ApexInfo
-		wantMerged  []ApexInfo
-		wantAliases [][2]string
-	}{
-		{
-			name: "single",
-			in: []ApexInfo{
-				{
-					ApexVariationName: "foo",
-					MinSdkVersion:     FutureApiLevel,
-					InApexVariants:    []string{"foo"},
-					InApexModules:     []string{"foo"},
-					ForPrebuiltApex:   NotForPrebuiltApex,
-				},
-			},
-			wantMerged: []ApexInfo{
-				{
-					ApexVariationName: "apex10000",
-					MinSdkVersion:     FutureApiLevel,
-					InApexVariants:    []string{"foo"},
-					InApexModules:     []string{"foo"},
-					ForPrebuiltApex:   NotForPrebuiltApex,
-				},
-			},
-			wantAliases: [][2]string{
-				{"foo", "apex10000"},
-			},
-		},
-		{
-			name: "merge",
-			in: []ApexInfo{
-				{
-					ApexVariationName: "foo",
-					MinSdkVersion:     FutureApiLevel,
-					InApexVariants:    []string{"foo"},
-					InApexModules:     []string{"foo"},
-					ForPrebuiltApex:   NotForPrebuiltApex,
-				},
-				{
-					ApexVariationName: "bar",
-					MinSdkVersion:     FutureApiLevel,
-					InApexVariants:    []string{"bar"},
-					InApexModules:     []string{"bar"},
-					ForPrebuiltApex:   NotForPrebuiltApex,
-				},
-			},
-			wantMerged: []ApexInfo{
-				{
-					ApexVariationName: "apex10000",
-					MinSdkVersion:     FutureApiLevel,
-					InApexVariants:    []string{"foo", "bar"},
-					InApexModules:     []string{"foo", "bar"},
-				}},
-			wantAliases: [][2]string{
-				{"foo", "apex10000"},
-				{"bar", "apex10000"},
-			},
-		},
-		{
-			name: "don't merge version",
-			in: []ApexInfo{
-				{
-					ApexVariationName: "foo",
-					MinSdkVersion:     FutureApiLevel,
-					InApexVariants:    []string{"foo"},
-					InApexModules:     []string{"foo"},
-					ForPrebuiltApex:   NotForPrebuiltApex,
-				},
-				{
-					ApexVariationName: "bar",
-					MinSdkVersion:     uncheckedFinalApiLevel(30),
-					InApexVariants:    []string{"bar"},
-					InApexModules:     []string{"bar"},
-					ForPrebuiltApex:   NotForPrebuiltApex,
-				},
-			},
-			wantMerged: []ApexInfo{
-				{
-					ApexVariationName: "apex10000",
-					MinSdkVersion:     FutureApiLevel,
-					InApexVariants:    []string{"foo"},
-					InApexModules:     []string{"foo"},
-					ForPrebuiltApex:   NotForPrebuiltApex,
-				},
-				{
-					ApexVariationName: "apex30",
-					MinSdkVersion:     uncheckedFinalApiLevel(30),
-					InApexVariants:    []string{"bar"},
-					InApexModules:     []string{"bar"},
-					ForPrebuiltApex:   NotForPrebuiltApex,
-				},
-			},
-			wantAliases: [][2]string{
-				{"foo", "apex10000"},
-				{"bar", "apex30"},
-			},
-		},
-		{
-			name: "merge updatable",
-			in: []ApexInfo{
-				{
-					ApexVariationName: "foo",
-					MinSdkVersion:     FutureApiLevel,
-					InApexVariants:    []string{"foo"},
-					InApexModules:     []string{"foo"},
-					ForPrebuiltApex:   NotForPrebuiltApex,
-				},
-				{
-					ApexVariationName: "bar",
-					MinSdkVersion:     FutureApiLevel,
-					Updatable:         true,
-					InApexVariants:    []string{"bar"},
-					InApexModules:     []string{"bar"},
-					ForPrebuiltApex:   NotForPrebuiltApex,
-				},
-			},
-			wantMerged: []ApexInfo{
-				{
-					ApexVariationName: "apex10000",
-					MinSdkVersion:     FutureApiLevel,
-					Updatable:         true,
-					InApexVariants:    []string{"foo", "bar"},
-					InApexModules:     []string{"foo", "bar"},
-					ForPrebuiltApex:   NotForPrebuiltApex,
-				},
-			},
-			wantAliases: [][2]string{
-				{"foo", "apex10000"},
-				{"bar", "apex10000"},
-			},
-		},
-		{
-			name: "don't merge when for prebuilt_apex",
-			in: []ApexInfo{
-				{
-					ApexVariationName: "foo",
-					MinSdkVersion:     FutureApiLevel,
-					InApexVariants:    []string{"foo"},
-					InApexModules:     []string{"foo"},
-					ForPrebuiltApex:   NotForPrebuiltApex,
-				},
-				{
-					ApexVariationName: "bar",
-					MinSdkVersion:     FutureApiLevel,
-					Updatable:         true,
-					InApexVariants:    []string{"bar"},
-					InApexModules:     []string{"bar"},
-					ForPrebuiltApex:   NotForPrebuiltApex,
-				},
-				// This one should not be merged in with the others because it is for
-				// a prebuilt_apex.
-				{
-					ApexVariationName: "baz",
-					MinSdkVersion:     FutureApiLevel,
-					Updatable:         true,
-					InApexVariants:    []string{"baz"},
-					InApexModules:     []string{"baz"},
-					ForPrebuiltApex:   ForPrebuiltApex,
-				},
-			},
-			wantMerged: []ApexInfo{
-				{
-					ApexVariationName: "apex10000",
-					MinSdkVersion:     FutureApiLevel,
-					Updatable:         true,
-					InApexVariants:    []string{"foo", "bar"},
-					InApexModules:     []string{"foo", "bar"},
-					ForPrebuiltApex:   NotForPrebuiltApex,
-				},
-				{
-					ApexVariationName: "baz",
-					MinSdkVersion:     FutureApiLevel,
-					Updatable:         true,
-					InApexVariants:    []string{"baz"},
-					InApexModules:     []string{"baz"},
-					ForPrebuiltApex:   ForPrebuiltApex,
-				},
-			},
-			wantAliases: [][2]string{
-				{"foo", "apex10000"},
-				{"bar", "apex10000"},
-			},
-		},
-		{
-			name: "merge different UsePlatformApis but don't allow using platform api",
-			in: []ApexInfo{
-				{
-					ApexVariationName: "foo",
-					MinSdkVersion:     FutureApiLevel,
-					InApexVariants:    []string{"foo"},
-					InApexModules:     []string{"foo"},
-					ForPrebuiltApex:   NotForPrebuiltApex,
-				},
-				{
-					ApexVariationName: "bar",
-					MinSdkVersion:     FutureApiLevel,
-					UsePlatformApis:   true,
-					InApexVariants:    []string{"bar"},
-					InApexModules:     []string{"bar"},
-					ForPrebuiltApex:   NotForPrebuiltApex,
-				},
-			},
-			wantMerged: []ApexInfo{
-				{
-					ApexVariationName: "apex10000",
-					MinSdkVersion:     FutureApiLevel,
-					InApexVariants:    []string{"foo", "bar"},
-					InApexModules:     []string{"foo", "bar"},
-					ForPrebuiltApex:   NotForPrebuiltApex,
-				},
-			},
-			wantAliases: [][2]string{
-				{"foo", "apex10000"},
-				{"bar", "apex10000"},
-			},
-		},
-		{
-			name: "merge same UsePlatformApis and allow using platform api",
-			in: []ApexInfo{
-				{
-					ApexVariationName: "foo",
-					MinSdkVersion:     FutureApiLevel,
-					UsePlatformApis:   true,
-					InApexVariants:    []string{"foo"},
-					InApexModules:     []string{"foo"},
-					ForPrebuiltApex:   NotForPrebuiltApex,
-				},
-				{
-					ApexVariationName: "bar",
-					MinSdkVersion:     FutureApiLevel,
-					UsePlatformApis:   true,
-					InApexVariants:    []string{"bar"},
-					InApexModules:     []string{"bar"},
-					ForPrebuiltApex:   NotForPrebuiltApex,
-				},
-			},
-			wantMerged: []ApexInfo{
-				{
-					ApexVariationName: "apex10000",
-					MinSdkVersion:     FutureApiLevel,
-					UsePlatformApis:   true,
-					InApexVariants:    []string{"foo", "bar"},
-					InApexModules:     []string{"foo", "bar"},
-					ForPrebuiltApex:   NotForPrebuiltApex,
-				},
-			},
-			wantAliases: [][2]string{
-				{"foo", "apex10000"},
-				{"bar", "apex10000"},
-			},
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			gotMerged, gotAliases := mergeApexVariations(tt.in)
-			if !reflect.DeepEqual(gotMerged, tt.wantMerged) {
-				t.Errorf("mergeApexVariations() gotMerged = %v, want %v", gotMerged, tt.wantMerged)
-			}
-			if !reflect.DeepEqual(gotAliases, tt.wantAliases) {
-				t.Errorf("mergeApexVariations() gotAliases = %v, want %v", gotAliases, tt.wantAliases)
-			}
-		})
-	}
-}
diff --git a/android/api_levels.go b/android/api_levels.go
index 2b1d01d..b4fa251 100644
--- a/android/api_levels.go
+++ b/android/api_levels.go
@@ -311,7 +311,7 @@
 
 // ApiLevelFrom converts the given string `raw` to an ApiLevel.
 // If `raw` is invalid (empty string, unrecognized codename etc.) it returns an invalid ApiLevel
-func ApiLevelFrom(ctx PathContext, raw string) ApiLevel {
+func ApiLevelFrom(ctx ConfigContext, raw string) ApiLevel {
 	ret, err := ApiLevelFromUser(ctx, raw)
 	if err != nil {
 		return NewInvalidApiLevel(raw)
@@ -333,7 +333,7 @@
 //
 // Inputs that are not "current", known previews, or convertible to an integer
 // will return an error.
-func ApiLevelFromUser(ctx PathContext, raw string) (ApiLevel, error) {
+func ApiLevelFromUser(ctx ConfigContext, raw string) (ApiLevel, error) {
 	return ApiLevelFromUserWithConfig(ctx.Config(), raw)
 }
 
@@ -413,7 +413,7 @@
 // Converts an API level string `raw` into an ApiLevel in the same method as
 // `ApiLevelFromUser`, but the input is assumed to have no errors and any errors
 // will panic instead of returning an error.
-func ApiLevelOrPanic(ctx PathContext, raw string) ApiLevel {
+func ApiLevelOrPanic(ctx ConfigContext, raw string) ApiLevel {
 	value, err := ApiLevelFromUser(ctx, raw)
 	if err != nil {
 		panic(err.Error())
diff --git a/android/arch.go b/android/arch.go
index 1ec971d..3cd6e4b 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -567,10 +567,6 @@
 //	"32": compile for only a single 32-bit Target supported by the OsClass.
 //	"64": compile for only a single 64-bit Target supported by the OsClass.
 //	"common": compile a for a single Target that will work on all Targets supported by the OsClass (for example Java).
-//	"common_first": compile a for a Target that will work on all Targets supported by the OsClass
-//	    (same as "common"), plus a second Target for the preferred Target supported by the OsClass
-//	    (same as "first").  This is used for java_binary that produces a common .jar and a wrapper
-//	    executable script.
 //
 // Once the list of Targets is determined, the module is split into a variant for each Target.
 //
@@ -702,11 +698,9 @@
 		return ""
 	}
 
-	if incomingVariation == "" {
-		multilib, _ := decodeMultilib(ctx, base)
-		if multilib == "common" {
-			return "common"
-		}
+	multilib, _ := decodeMultilib(ctx, base)
+	if multilib == "common" {
+		return "common"
 	}
 	return incomingVariation
 }
@@ -756,8 +750,7 @@
 	// Create a dependency for Darwin Universal binaries from the primary to secondary
 	// architecture. The module itself will be responsible for calling lipo to merge the outputs.
 	if os == Darwin {
-		isUniversalBinary := (allArchInfo.Multilib == "darwin_universal" && len(allArchInfo.Targets) == 2) ||
-			allArchInfo.Multilib == "darwin_universal_common_first" && len(allArchInfo.Targets) == 3
+		isUniversalBinary := allArchInfo.Multilib == "darwin_universal" && len(allArchInfo.Targets) == 2
 		isPrimary := variation == ctx.Config().BuildArch.String()
 		hasSecondaryConfigured := len(ctx.Config().Targets[Darwin]) > 1
 		if isUniversalBinary && isPrimary && hasSecondaryConfigured {
@@ -825,11 +818,7 @@
 		//  !UseTargetVariants, as the module has opted into handling the arch-specific logic on
 		//    its own.
 		if os == Darwin && multilib != "common" && multilib != "32" {
-			if multilib == "common_first" {
-				multilib = "darwin_universal_common_first"
-			} else {
-				multilib = "darwin_universal"
-			}
+			multilib = "darwin_universal"
 		}
 
 		return multilib, ""
@@ -1369,7 +1358,7 @@
 
 // Returns the structs corresponding to the properties specific to the given
 // architecture and OS in archProperties.
-func getArchProperties(ctx BaseMutatorContext, archProperties interface{}, arch Arch, os OsType, nativeBridgeEnabled bool) []reflect.Value {
+func getArchProperties(ctx BaseModuleContext, archProperties interface{}, arch Arch, os OsType, nativeBridgeEnabled bool) []reflect.Value {
 	result := make([]reflect.Value, 0)
 	archPropValues := reflect.ValueOf(archProperties).Elem()
 
@@ -1941,13 +1930,6 @@
 	switch multilib {
 	case "common":
 		buildTargets = getCommonTargets(targets)
-	case "common_first":
-		buildTargets = getCommonTargets(targets)
-		if prefer32 {
-			buildTargets = append(buildTargets, FirstTarget(targets, "lib32", "lib64")...)
-		} else {
-			buildTargets = append(buildTargets, FirstTarget(targets, "lib64", "lib32")...)
-		}
 	case "both":
 		if prefer32 {
 			buildTargets = append(buildTargets, filterMultilibTargets(targets, "lib32")...)
@@ -1978,10 +1960,6 @@
 		// Reverse the targets so that the first architecture can depend on the second
 		// architecture module in order to merge the outputs.
 		ReverseSliceInPlace(buildTargets)
-	case "darwin_universal_common_first":
-		archTargets := filterMultilibTargets(targets, "lib64")
-		ReverseSliceInPlace(archTargets)
-		buildTargets = append(getCommonTargets(targets), archTargets...)
 	default:
 		return nil, fmt.Errorf(`compile_multilib must be "both", "first", "32", "64", "prefer32" or "first_prefer32" found %q`,
 			multilib)
diff --git a/android/arch_test.go b/android/arch_test.go
index 57c9010..adb655f 100644
--- a/android/arch_test.go
+++ b/android/arch_test.go
@@ -432,7 +432,7 @@
 		var ret []string
 		variants := ctx.ModuleVariantsForTests(name)
 		for _, variant := range variants {
-			m := ctx.ModuleForTests(name, variant)
+			m := ctx.ModuleForTests(t, name, variant)
 			if m.Module().Enabled(PanickingConfigAndErrorContext(ctx)) {
 				ret = append(ret, variant)
 			}
@@ -442,7 +442,7 @@
 
 	moduleMultiTargets := func(ctx *TestContext, name string, variant string) []string {
 		var ret []string
-		targets := ctx.ModuleForTests(name, variant).Module().MultiTargets()
+		targets := ctx.ModuleForTests(t, name, variant).Module().MultiTargets()
 		for _, t := range targets {
 			ret = append(ret, t.String())
 		}
@@ -546,7 +546,7 @@
 		var ret []string
 		variants := ctx.ModuleVariantsForTests(name)
 		for _, variant := range variants {
-			m := ctx.ModuleForTests(name, variant)
+			m := ctx.ModuleForTests(t, name, variant)
 			if m.Module().Enabled(PanickingConfigAndErrorContext(ctx)) {
 				ret = append(ret, variant)
 			}
@@ -560,15 +560,7 @@
 				prepareForArchTest,
 				// Test specific preparer
 				OptionalFixturePreparer(tt.preparer),
-				// Prepare for native bridge test
-				FixtureModifyConfig(func(config Config) {
-					config.Targets[Android] = []Target{
-						{Android, Arch{ArchType: X86_64, ArchVariant: "silvermont", Abi: []string{"arm64-v8a"}}, NativeBridgeDisabled, "", "", false},
-						{Android, Arch{ArchType: X86, ArchVariant: "silvermont", Abi: []string{"armeabi-v7a"}}, NativeBridgeDisabled, "", "", false},
-						{Android, Arch{ArchType: Arm64, ArchVariant: "armv8-a", Abi: []string{"arm64-v8a"}}, NativeBridgeEnabled, "x86_64", "arm64", false},
-						{Android, Arch{ArchType: Arm, ArchVariant: "armv7-a-neon", Abi: []string{"armeabi-v7a"}}, NativeBridgeEnabled, "x86", "arm", false},
-					}
-				}),
+				PrepareForNativeBridgeEnabled,
 				FixtureWithRootAndroidBp(bp),
 			).RunTest(t)
 
@@ -766,7 +758,7 @@
 
 			for _, want := range tt.results {
 				t.Run(want.module+"_"+want.variant, func(t *testing.T) {
-					got := result.ModuleForTests(want.module, want.variant).Module().(*testArchPropertiesModule).properties.A
+					got := result.ModuleForTests(t, want.module, want.variant).Module().(*testArchPropertiesModule).properties.A
 					AssertArrayString(t, "arch mutator property", want.property, got)
 				})
 			}
diff --git a/android/base_module_context.go b/android/base_module_context.go
index 670537f..4b90083 100644
--- a/android/base_module_context.go
+++ b/android/base_module_context.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 	"regexp"
+	"slices"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -87,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
@@ -105,15 +110,14 @@
 
 	GetDirectDepsWithTag(tag blueprint.DependencyTag) []Module
 
+	GetDirectDepsProxyWithTag(tag blueprint.DependencyTag) []ModuleProxy
+
 	// 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
-	// the first DependencyTag.
-	GetDirectDep(name string) (blueprint.Module, blueprint.DependencyTag)
+	GetDirectDepProxyWithTag(name string, tag blueprint.DependencyTag) *ModuleProxy
 
 	// VisitDirectDeps 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
@@ -124,24 +128,27 @@
 	// 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
 	// that module and OtherModuleDependencyTag will return a different tag for each.
 	//
-	// The Module passed to the visit function should not be retained outside of the visit function, it may be
+	// The ModuleProxy passed to the visit function should not be retained outside of the visit function, it may be
 	// invalidated by future mutators.
-	VisitDirectDepsProxyAllowDisabled(visit func(proxy Module))
+	VisitDirectDepsProxyAllowDisabled(visit func(proxy ModuleProxy))
 
 	VisitDirectDepsWithTag(tag blueprint.DependencyTag, visit func(Module))
 
+	VisitDirectDepsProxyWithTag(tag blueprint.DependencyTag, visit func(proxy ModuleProxy))
+
 	// VisitDirectDepsIf calls pred for each direct dependency, and if pred returns true calls visit.  If there are
 	// multiple direct dependencies on the same module pred and visit will be called multiple times on that module and
 	// OtherModuleDependencyTag will return a different tag for each.  It skips any
@@ -173,7 +180,7 @@
 	//
 	// The Modules passed to the visit function should not be retained outside of the visit function, they may be
 	// invalidated by future mutators.
-	WalkDepsProxy(visit func(child, parent Module) bool)
+	WalkDepsProxy(visit func(child, parent ModuleProxy) bool)
 
 	// GetWalkPath is supposed to be called in visit function passed in WalkDeps()
 	// and returns a top-down dependency path from a start module to current child module.
@@ -191,12 +198,24 @@
 	// 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))
 
+	// VisitAllModuleVariantProxies calls visit for each variant of the current module.  Variants of a module are always
+	// visited in order by mutators and GenerateBuildActions, so the data created by the current mutator can be read
+	// from all variants if the current module is the last one. Otherwise, care must be taken to not access any
+	// data modified by the current mutator.
+	VisitAllModuleVariantProxies(visit func(proxy ModuleProxy))
+
 	// GetTagPath is supposed to be called in visit function passed in WalkDeps()
 	// and returns a top-down dependency tags path from a start module to current child module.
 	// It has one less entry than GetWalkPath() as it contains the dependency tags that
@@ -235,6 +254,9 @@
 }
 
 func getWrappedModule(module blueprint.Module) blueprint.Module {
+	if mp, isProxy := module.(*ModuleProxy); isProxy {
+		return mp.module
+	}
 	if mp, isProxy := module.(ModuleProxy); isProxy {
 		return mp.module
 	}
@@ -248,9 +270,11 @@
 func (b *baseModuleContext) OtherModuleName(m blueprint.Module) string {
 	return b.bp.OtherModuleName(getWrappedModule(m))
 }
-func (b *baseModuleContext) OtherModuleDir(m blueprint.Module) string { return b.bp.OtherModuleDir(m) }
+func (b *baseModuleContext) OtherModuleDir(m blueprint.Module) string {
+	return b.bp.OtherModuleDir(getWrappedModule(m))
+}
 func (b *baseModuleContext) OtherModuleErrorf(m blueprint.Module, fmt string, args ...interface{}) {
-	b.bp.OtherModuleErrorf(m, fmt, args...)
+	b.bp.OtherModuleErrorf(getWrappedModule(m), fmt, args...)
 }
 func (b *baseModuleContext) OtherModuleDependencyTag(m blueprint.Module) blueprint.DependencyTag {
 	return b.bp.OtherModuleDependencyTag(getWrappedModule(m))
@@ -266,11 +290,15 @@
 	return b.bp.OtherModuleReverseDependencyVariantExists(name)
 }
 func (b *baseModuleContext) OtherModuleType(m blueprint.Module) string {
-	return b.bp.OtherModuleType(m)
+	return b.bp.OtherModuleType(getWrappedModule(m))
 }
 
 func (b *baseModuleContext) otherModuleProvider(m blueprint.Module, provider blueprint.AnyProviderKey) (any, bool) {
-	return b.bp.OtherModuleProvider(m, provider)
+	return b.bp.OtherModuleProvider(getWrappedModule(m), provider)
+}
+
+func (b *baseModuleContext) OtherModuleIsAutoGenerated(m blueprint.Module) bool {
+	return b.bp.OtherModuleIsAutoGenerated(m)
 }
 
 func (b *baseModuleContext) provider(provider blueprint.AnyProviderKey) (any, bool) {
@@ -281,8 +309,18 @@
 	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) GetDirectDepProxyWithTag(name string, tag blueprint.DependencyTag) *ModuleProxy {
+	if module := b.bp.GetDirectDepProxyWithTag(name, tag); module != nil {
+		return &ModuleProxy{*module}
+	}
+	return nil
 }
 
 func (b *baseModuleContext) blueprintBaseModuleContext() blueprint.BaseModuleContext {
@@ -314,6 +352,7 @@
 type AllowDisabledModuleDependency interface {
 	blueprint.DependencyTag
 	AllowDisabledModuleDependency(target Module) bool
+	AllowDisabledModuleDependencyProxy(ctx OtherModuleProviderContext, target ModuleProxy) bool
 }
 
 type AlwaysAllowDisabledModuleDependencyTag struct{}
@@ -322,6 +361,10 @@
 	return true
 }
 
+func (t AlwaysAllowDisabledModuleDependencyTag) AllowDisabledModuleDependencyProxy(OtherModuleProviderContext, ModuleProxy) bool {
+	return true
+}
+
 func (b *baseModuleContext) validateAndroidModule(module blueprint.Module, tag blueprint.DependencyTag, strict bool) Module {
 	aModule, _ := module.(Module)
 
@@ -346,53 +389,52 @@
 	return aModule
 }
 
-type dep struct {
-	mod blueprint.Module
-	tag blueprint.DependencyTag
+func (b *baseModuleContext) validateAndroidModuleProxy(
+	module blueprint.ModuleProxy, tag blueprint.DependencyTag, strict bool) *ModuleProxy {
+	aModule := ModuleProxy{module: module}
+
+	if !strict {
+		return &aModule
+	}
+
+	if !OtherModuleProviderOrDefault(b, module, CommonModuleInfoKey).Enabled {
+		if t, ok := tag.(AllowDisabledModuleDependency); !ok || !t.AllowDisabledModuleDependencyProxy(b, aModule) {
+			if b.Config().AllowMissingDependencies() {
+				b.AddMissingDependencies([]string{b.OtherModuleName(aModule)})
+			} else {
+				b.ModuleErrorf("depends on disabled module %q", b.OtherModuleName(aModule))
+			}
+		}
+		return nil
+	}
+
+	return &aModule
 }
 
-func (b *baseModuleContext) getDirectDepsInternal(name string, tag blueprint.DependencyTag) []dep {
-	var deps []dep
+func (b *baseModuleContext) getDirectDepsInternal(name string, tag blueprint.DependencyTag) []Module {
+	var deps []Module
 	b.VisitDirectDeps(func(module Module) {
 		if module.base().BaseModuleName() == name {
 			returnedTag := b.bp.OtherModuleDependencyTag(module)
 			if tag == nil || returnedTag == tag {
-				deps = append(deps, dep{module, returnedTag})
+				deps = append(deps, module)
 			}
 		}
 	})
 	return deps
 }
 
-func (b *baseModuleContext) getDirectDepInternal(name string, tag blueprint.DependencyTag) (blueprint.Module, blueprint.DependencyTag) {
-	deps := b.getDirectDepsInternal(name, tag)
-	if len(deps) == 1 {
-		return deps[0].mod, deps[0].tag
-	} else if len(deps) >= 2 {
-		panic(fmt.Errorf("Multiple dependencies having same BaseModuleName() %q found from %q",
-			name, b.ModuleName()))
-	} else {
-		return nil, nil
-	}
-}
-
-func (b *baseModuleContext) getDirectDepFirstTag(name string) (blueprint.Module, blueprint.DependencyTag) {
-	foundDeps := b.getDirectDepsInternal(name, nil)
-	deps := map[blueprint.Module]bool{}
-	for _, dep := range foundDeps {
-		deps[dep.mod] = true
-	}
-	if len(deps) == 1 {
-		return foundDeps[0].mod, foundDeps[0].tag
-	} else if len(deps) >= 2 {
-		// this could happen if two dependencies have the same name in different namespaces
-		// TODO(b/186554727): this should not occur if namespaces are handled within
-		// getDirectDepsInternal.
-		panic(fmt.Errorf("Multiple dependencies having same BaseModuleName() %q found from %q",
-			name, b.ModuleName()))
-	} else {
-		return nil, nil
-	}
+func (b *baseModuleContext) getDirectDepsProxyInternal(name string, tag blueprint.DependencyTag) []ModuleProxy {
+	var deps []ModuleProxy
+	b.VisitDirectDepsProxy(func(module ModuleProxy) {
+		if OtherModuleProviderOrDefault(b, module, CommonModuleInfoKey).BaseModuleName == name {
+			returnedTag := b.OtherModuleDependencyTag(module)
+			if tag == nil || returnedTag == tag {
+				deps = append(deps, module)
+			}
+		}
+	})
+	return deps
 }
 
 func (b *baseModuleContext) GetDirectDepsWithTag(tag blueprint.DependencyTag) []Module {
@@ -405,11 +447,14 @@
 	return deps
 }
 
-// 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 the
-// first DependencyTag.
-func (b *baseModuleContext) GetDirectDep(name string) (blueprint.Module, blueprint.DependencyTag) {
-	return b.getDirectDepFirstTag(name)
+func (b *baseModuleContext) GetDirectDepsProxyWithTag(tag blueprint.DependencyTag) []ModuleProxy {
+	var deps []ModuleProxy
+	b.VisitDirectDepsProxy(func(module ModuleProxy) {
+		if b.OtherModuleDependencyTag(module) == tag {
+			deps = append(deps, module)
+		}
+	})
+	return deps
 }
 
 func (b *baseModuleContext) VisitDirectDeps(visit func(Module)) {
@@ -420,30 +465,38 @@
 	})
 }
 
-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 Module)) {
-	b.bp.VisitDirectDepsProxy(func(module blueprint.ModuleProxy) {
-		visit(ModuleProxy{
-			module: module,
-		})
-	})
+func (b *baseModuleContext) VisitDirectDepsProxyAllowDisabled(visit func(proxy ModuleProxy)) {
+	b.bp.VisitDirectDepsProxy(visitProxyAdaptor(visit))
 }
 
 func (b *baseModuleContext) VisitDirectDepsWithTag(tag blueprint.DependencyTag, visit func(Module)) {
 	b.bp.VisitDirectDeps(func(module blueprint.Module) {
 		if b.bp.OtherModuleDependencyTag(module) == tag {
-			if aModule := b.validateAndroidModule(module, b.bp.OtherModuleDependencyTag(module), b.strictVisitDeps); aModule != nil {
+			if aModule := b.validateAndroidModule(module, tag, b.strictVisitDeps); aModule != nil {
 				visit(aModule)
 			}
 		}
 	})
 }
 
+func (b *baseModuleContext) VisitDirectDepsProxyWithTag(tag blueprint.DependencyTag, visit func(proxy ModuleProxy)) {
+	b.bp.VisitDirectDepsProxy(func(module blueprint.ModuleProxy) {
+		if b.bp.OtherModuleDependencyTag(module) == tag {
+			if aModule := b.validateAndroidModuleProxy(module, tag, b.strictVisitDeps); aModule != nil {
+				visit(*aModule)
+			}
+		}
+	})
+}
+
 func (b *baseModuleContext) VisitDirectDepsIf(pred func(Module) bool, visit func(Module)) {
 	b.bp.VisitDirectDepsIf(
 		// pred
@@ -505,7 +558,7 @@
 	})
 }
 
-func (b *baseModuleContext) WalkDepsProxy(visit func(Module, Module) bool) {
+func (b *baseModuleContext) WalkDepsProxy(visit func(ModuleProxy, ModuleProxy) bool) {
 	b.walkPath = []Module{ModuleProxy{blueprint.CreateModuleProxy(b.Module())}}
 	b.tagPath = []blueprint.DependencyTag{}
 	b.bp.WalkDepsProxy(func(child, parent blueprint.ModuleProxy) bool {
@@ -523,7 +576,7 @@
 }
 
 func (b *baseModuleContext) GetWalkPath() []Module {
-	return b.walkPath
+	return slices.Clone(b.walkPath)
 }
 
 func (b *baseModuleContext) GetTagPath() []blueprint.DependencyTag {
@@ -536,6 +589,10 @@
 	})
 }
 
+func (b *baseModuleContext) VisitAllModuleVariantProxies(visit func(ModuleProxy)) {
+	b.bp.VisitAllModuleVariantProxies(visitProxyAdaptor(visit))
+}
+
 func (b *baseModuleContext) PrimaryModule() Module {
 	return b.bp.PrimaryModule().(Module)
 }
@@ -544,6 +601,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 ede93ed..2f71bc0 100644
--- a/android/build_prop.go
+++ b/android/build_prop.go
@@ -15,12 +15,18 @@
 package android
 
 import (
+	"fmt"
+
 	"github.com/google/blueprint/proptools"
 )
 
 func init() {
-	ctx := InitRegistrationContext
-	ctx.RegisterModuleType("build_prop", buildPropFactory)
+	registerBuildPropComponents(InitRegistrationContext)
+}
+
+func registerBuildPropComponents(ctx RegistrationContext) {
+	ctx.RegisterModuleType("build_prop", BuildPropFactory)
+	ctx.RegisterModuleType("android_info", AndroidInfoFactory)
 }
 
 type buildPropProperties struct {
@@ -38,6 +44,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
 }
@@ -47,7 +57,7 @@
 
 	properties buildPropProperties
 
-	outputFilePath OutputPath
+	outputFilePath Path
 	installPath    InstallPath
 }
 
@@ -65,6 +75,12 @@
 		return ctx.Config().ProductPropFiles(ctx)
 	} else if partition == "odm" {
 		return ctx.Config().OdmPropFiles(ctx)
+	} else if partition == "vendor" {
+		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
 }
@@ -95,30 +111,28 @@
 		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"
+	} else if p.InstallInRamdisk() {
+		// From this hardcoding in make:
+		// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/sysprop.mk;l=311;drc=274435657e4682e5cee3fffd11fb301ab32a828d
+		return "bootimage"
 	}
 	return "system"
 }
 
-var validPartitions = []string{
-	"system",
-	"system_ext",
-	"product",
-	"odm",
-}
-
 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")
 	}
 
+	outputFilePath := PathForModuleOut(ctx, "build.prop")
+
 	partition := p.partition(ctx.DeviceConfig())
-	if !InList(partition, validPartitions) {
-		ctx.PropertyErrorf("partition", "unsupported partition %q: only %q are supported", partition, validPartitions)
-		return
-	}
 
 	rule := NewRuleBuilder(pctx, ctx)
 
@@ -145,7 +159,7 @@
 	cmd.FlagWithInput("--product-config=", PathForModuleSrc(ctx, proptools.String(p.properties.Product_config)))
 	cmd.FlagWithArg("--partition=", partition)
 	cmd.FlagForEachInput("--prop-files=", p.propFiles(ctx))
-	cmd.FlagWithOutput("--out=", p.outputFilePath)
+	cmd.FlagWithOutput("--out=", outputFilePath)
 
 	postProcessCmd := rule.Command().BuiltTool("post_process_props")
 	if ctx.DeviceConfig().BuildBrokenDupSysprop() {
@@ -158,17 +172,35 @@
 		// still need to pass an empty string to kernel-version-file-for-uffd-gc
 		postProcessCmd.FlagWithArg("--kernel-version-file-for-uffd-gc ", `""`)
 	}
-	postProcessCmd.Text(p.outputFilePath.String())
+	postProcessCmd.Text(outputFilePath.String())
 	postProcessCmd.Flags(p.properties.Block_list)
 
-	rule.Command().Text("echo").Text(proptools.NinjaAndShellEscape("# end of file")).FlagWithArg(">> ", p.outputFilePath.String())
+	for _, footer := range p.properties.Footer_files {
+		path := PathForModuleSrc(ctx, footer)
+		rule.appendText(outputFilePath, "####################################")
+		rule.appendTextf(outputFilePath, "# Adding footer from %v", footer)
+		rule.appendTextf(outputFilePath, "# with path %v", path)
+		rule.appendText(outputFilePath, "####################################")
+		rule.Command().Text("cat").FlagWithInput("", path).FlagWithArg(">> ", outputFilePath.String())
+	}
+
+	rule.appendText(outputFilePath, "# end of file")
 
 	rule.Build(ctx.ModuleName(), "generating build.prop")
 
 	p.installPath = PathForModuleInstall(ctx, proptools.String(p.properties.Relative_install_path))
-	ctx.InstallFile(p.installPath, p.stem(), p.outputFilePath)
+	ctx.InstallFile(p.installPath, p.stem(), outputFilePath)
 
-	ctx.SetOutputFiles(Paths{p.outputFilePath}, "")
+	ctx.SetOutputFiles(Paths{outputFilePath}, "")
+	p.outputFilePath = outputFilePath
+}
+
+func (r *RuleBuilder) appendText(path ModuleOutPath, text string) {
+	r.Command().Text("echo").Text(proptools.NinjaAndShellEscape(text)).FlagWithArg(">> ", path.String())
+}
+
+func (r *RuleBuilder) appendTextf(path ModuleOutPath, format string, a ...any) {
+	r.appendText(path, fmt.Sprintf(format, a...))
 }
 
 func (p *buildPropModule) AndroidMkEntries() []AndroidMkEntries {
@@ -187,7 +219,7 @@
 // build_prop module generates {partition}/build.prop file. At first common build properties are
 // printed based on Soong config variables. And then prop_files are printed as-is. Finally,
 // post_process_props tool is run to check if the result build.prop is valid or not.
-func buildPropFactory() Module {
+func BuildPropFactory() Module {
 	module := &buildPropModule{}
 	module.AddProperties(&module.properties)
 	InitAndroidArchModule(module, DeviceSupported, MultilibCommon)
diff --git a/android/build_prop_test.go b/android/build_prop_test.go
new file mode 100644
index 0000000..e136a1a
--- /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(t, "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/compliance_metadata.go b/android/compliance_metadata.go
index d28831e..35805a2 100644
--- a/android/compliance_metadata.go
+++ b/android/compliance_metadata.go
@@ -23,6 +23,7 @@
 	"strings"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/gobtools"
 )
 
 var (
@@ -42,6 +43,7 @@
 		STATIC_DEP_FILES       string
 		WHOLE_STATIC_DEPS      string
 		WHOLE_STATIC_DEP_FILES string
+		HEADER_LIBS            string
 		LICENSES               string
 
 		// module_type=package
@@ -70,6 +72,7 @@
 		"static_dep_files",
 		"whole_static_deps",
 		"whole_static_dep_files",
+		"header_libs",
 		"licenses",
 
 		"pkg_default_applicable_licenses",
@@ -105,6 +108,8 @@
 		ComplianceMetadataProp.WHOLE_STATIC_DEPS,
 		// Space separated file paths of whole static dependencies
 		ComplianceMetadataProp.WHOLE_STATIC_DEP_FILES,
+		// Space separated modules name of header libs
+		ComplianceMetadataProp.HEADER_LIBS,
 		ComplianceMetadataProp.LICENSES,
 		// module_type=package
 		ComplianceMetadataProp.PKG_DEFAULT_APPLICABLE_LICENSES,
@@ -146,11 +151,11 @@
 }
 
 func (c *ComplianceMetadataInfo) GobEncode() ([]byte, error) {
-	return blueprint.CustomGobEncode[complianceMetadataInfoGob](c)
+	return gobtools.CustomGobEncode[complianceMetadataInfoGob](c)
 }
 
 func (c *ComplianceMetadataInfo) GobDecode(data []byte) error {
-	return blueprint.CustomGobDecode[complianceMetadataInfoGob](data, c)
+	return gobtools.CustomGobDecode[complianceMetadataInfoGob](data, c)
 }
 
 func (c *ComplianceMetadataInfo) SetStringValue(propertyName string, value string) {
@@ -310,6 +315,11 @@
 	makeMetadataCsv := PathForOutput(ctx, "compliance-metadata", deviceProduct, "make-metadata.csv")
 	makeModulesCsv := PathForOutput(ctx, "compliance-metadata", deviceProduct, "make-modules.csv")
 
+	if !ctx.Config().KatiEnabled() {
+		WriteFileRule(ctx, makeMetadataCsv, "installed_file,module_path,is_soong_module,is_prebuilt_make_module,product_copy_files,kernel_module_copy_files,is_platform_generated,static_libs,whole_static_libs,license_text")
+		WriteFileRule(ctx, makeModulesCsv, "name,module_path,module_class,module_type,static_libs,whole_static_libs,built_files,installed_files")
+	}
+
 	// Import metadata from Make and Soong to sqlite3 database
 	complianceMetadataDb := PathForOutput(ctx, "compliance-metadata", deviceProduct, "compliance-metadata.db")
 	ctx.Build(pctx, BuildParams{
diff --git a/android/config.go b/android/config.go
index 06d71c0..acaad60 100644
--- a/android/config.go
+++ b/android/config.go
@@ -83,8 +83,8 @@
 	OutDir         string
 	SoongOutDir    string
 	SoongVariables string
+	KatiSuffix     string
 
-	BazelQueryViewDir string
 	ModuleGraphFile   string
 	ModuleActionsFile string
 	DocFile           string
@@ -99,11 +99,6 @@
 	// Don't use bazel at all during module analysis.
 	AnalysisNoBazel SoongBuildMode = iota
 
-	// Generate BUILD files which faithfully represent the dependency graph of
-	// blueprint modules. Individual BUILD targets will not, however, faitfhully
-	// express build semantics.
-	GenerateQueryView
-
 	// Create a JSON representation of the module graph and exit.
 	GenerateModuleGraph
 
@@ -291,6 +286,18 @@
 	return c.config.productVariables.GetBuildFlagBool("RELEASE_CREATE_ACONFIG_STORAGE_FILE")
 }
 
+func (c Config) ReleaseUseSystemFeatureBuildFlags() bool {
+	return c.config.productVariables.GetBuildFlagBool("RELEASE_USE_SYSTEM_FEATURE_BUILD_FLAGS")
+}
+
+func (c Config) ReleaseFingerprintAconfigPackages() bool {
+	return c.config.productVariables.GetBuildFlagBool("RELEASE_FINGERPRINT_ACONFIG_PACKAGES")
+}
+
+func (c Config) ReleaseAconfigCheckApiLevel() bool {
+	return c.config.productVariables.GetBuildFlagBool("RELEASE_ACONFIG_CHECK_API_LEVEL")
+}
+
 // A DeviceConfig object represents the configuration for a particular device
 // being built. For now there will only be one of these, but in the future there
 // may be multiple devices being built.
@@ -324,6 +331,9 @@
 	AndroidCommonTarget      Target // the Target for common modules for the Android device
 	AndroidFirstDeviceTarget Target // the first Target for modules for the Android device
 
+	// Flags for Partial Compile, derived from SOONG_PARTIAL_COMPILE.
+	partialCompileFlags partialCompileFlags
+
 	// multilibConflicts for an ArchType is true if there is earlier configured
 	// device architecture with the same multilib value.
 	multilibConflicts map[ArchType]bool
@@ -344,6 +354,7 @@
 	// Changes behavior based on whether Kati runs after soong_build, or if soong_build
 	// runs standalone.
 	katiEnabled bool
+	katiSuffix  string
 
 	captureBuild      bool // true for tests, saves build parameters for each module
 	ignoreEnvironment bool // true for tests, returns empty from all Getenv calls
@@ -373,6 +384,16 @@
 	ensureAllowlistIntegrity bool
 }
 
+type partialCompileFlags struct {
+	// Is partial compilation enabled at all?
+	Enabled bool
+
+	// Whether to use d8 instead of r8
+	Use_d8 bool
+
+	// Add others as needed.
+}
+
 type deviceConfig struct {
 	config *config
 	OncePer
@@ -382,6 +403,91 @@
 	SetDefaultConfig()
 }
 
+// Parse SOONG_PARTIAL_COMPILE.
+//
+// SOONG_PARTIAL_COMPILE determines which features are enabled or disabled in
+// rule generation.  Changing this environment variable causes reanalysis.
+//
+// SOONG_USE_PARTIAL_COMPILE determines whether or not we **use** PARTIAL_COMPILE.
+// Rule generation must support both cases, since changing it does not cause
+// reanalysis.
+//
+// The user-facing documentation shows:
+//
+// - empty or not set: "The current default state"
+// - "true" or "on": enable all stable partial compile features.
+// - "false" or "off": disable partial compile completely.
+//
+// What we actually allow is a comma separated list of tokens, whose first
+// character may be "+" (enable) or "-" (disable).  If neither is present, "+"
+// is assumed.  For example, "on,+use_d8" will enable partial compilation, and
+// additionally set the use_d8 flag (regardless of whether it is opt-in or
+// opt-out).
+//
+// 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.
+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 defaultPartialCompileFlags, nil
+	}
+
+	ret := defaultPartialCompileFlags
+	tokens := strings.Split(strings.ToLower(value), ",")
+	makeVal := func(state string, defaultValue bool) bool {
+		switch state {
+		case "":
+			return defaultValue
+		case "-":
+			return false
+		case "+":
+			return true
+		}
+		return false
+	}
+	for _, tok := range tokens {
+		var state string
+		if len(tok) == 0 {
+			continue
+		}
+		switch tok[0:1] {
+		case "":
+			// Ignore empty tokens.
+			continue
+		case "-", "+":
+			state = tok[0:1]
+			tok = tok[1:]
+		default:
+			// Treat `feature` as `+feature`.
+			state = "+"
+		}
+		switch tok {
+		case "true":
+			ret = defaultPartialCompileFlags
+			ret.Enabled = true
+		case "false":
+			// Set everything to false.
+			ret = partialCompileFlags{}
+		case "enabled":
+			ret.Enabled = makeVal(state, defaultPartialCompileFlags.Enabled)
+		case "use_d8":
+			ret.Use_d8 = makeVal(state, defaultPartialCompileFlags.Use_d8)
+		default:
+			return partialCompileFlags{}, fmt.Errorf("Unknown SOONG_PARTIAL_COMPILE value: %v", tok)
+		}
+	}
+	return ret, nil
+}
+
 func loadConfig(config *config) error {
 	return loadFromConfigFile(&config.productVariables, absolutePath(config.ProductVariablesFileName))
 }
@@ -520,6 +626,7 @@
 		outDir:            cmdArgs.OutDir,
 		soongOutDir:       cmdArgs.SoongOutDir,
 		runGoTests:        cmdArgs.RunGoTests,
+		katiSuffix:        cmdArgs.KatiSuffix,
 		multilibConflicts: make(map[ArchType]bool),
 
 		moduleListFile: cmdArgs.ModuleListFile,
@@ -527,6 +634,8 @@
 
 		buildFromSourceStub: cmdArgs.BuildFromSourceStub,
 	}
+	variant, ok := os.LookupEnv("TARGET_BUILD_VARIANT")
+	isEngBuild := !ok || variant == "eng"
 
 	config.deviceConfig = &deviceConfig{
 		config: config,
@@ -568,6 +677,11 @@
 		return Config{}, err
 	}
 
+	config.partialCompileFlags, err = config.parsePartialCompileFlags(isEngBuild)
+	if err != nil {
+		return Config{}, err
+	}
+
 	// Make the CommonOS OsType available for all products.
 	targets[CommonOS] = []Target{commonTargetMap[CommonOS.Name]}
 
@@ -616,7 +730,6 @@
 			config.BuildMode = mode
 		}
 	}
-	setBuildMode(cmdArgs.BazelQueryViewDir, GenerateQueryView)
 	setBuildMode(cmdArgs.ModuleGraphFile, GenerateModuleGraph)
 	setBuildMode(cmdArgs.DocFile, GenerateDocFile)
 
@@ -662,11 +775,7 @@
 // BlueprintToolLocation returns the directory containing build system tools
 // from Blueprint, like soong_zip and merge_zips.
 func (c *config) HostToolDir() string {
-	if c.KatiEnabled() {
-		return filepath.Join(c.outDir, "host", c.PrebuiltOS(), "bin")
-	} else {
-		return filepath.Join(c.soongOutDir, "host", c.PrebuiltOS(), "bin")
-	}
+	return filepath.Join(c.outDir, "host", c.PrebuiltOS(), "bin")
 }
 
 func (c *config) HostToolPath(ctx PathContext, tool string) Path {
@@ -769,7 +878,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
@@ -999,6 +1108,10 @@
 	return ApiLevelOrPanic(ctx, codename)
 }
 
+func (c *config) PartialCompileFlags() partialCompileFlags {
+	return c.partialCompileFlags
+}
+
 func (c *config) AppsDefaultVersionName() string {
 	return String(c.productVariables.AppsDefaultVersionName)
 }
@@ -1211,7 +1324,16 @@
 }
 
 func (c *config) RunErrorProne() bool {
-	return c.IsEnvTrue("RUN_ERROR_PRONE")
+	return c.IsEnvTrue("RUN_ERROR_PRONE") || c.RunErrorProneInline()
+}
+
+// Returns if the errorprone build should be run "inline", that is, using errorprone as part
+// of the main javac compilation instead of its own separate compilation. This is good for CI
+// but bad for local development, because if you toggle errorprone+inline on/off it will repeatedly
+// clobber java files from the old configuration.
+func (c *config) RunErrorProneInline() bool {
+	value := strings.ToLower(c.Getenv("RUN_ERROR_PRONE"))
+	return c.IsEnvTrue("RUN_ERROR_PRONE_INLINE") || value == "inline"
 }
 
 // XrefCorpusName returns the Kythe cross-reference corpus name.
@@ -1397,6 +1519,10 @@
 	return c.productVariables.GetBuildFlagBool("RELEASE_BOARD_API_LEVEL_FROZEN")
 }
 
+func (c *config) katiPackageMkDir() string {
+	return filepath.Join(c.soongOutDir, "kati_packaging"+c.katiSuffix)
+}
+
 func (c *deviceConfig) Arches() []Arch {
 	var arches []Arch
 	for _, target := range c.config.Targets[Android] {
@@ -1413,6 +1539,13 @@
 	return "64"
 }
 
+func (c *deviceConfig) RecoveryPath() string {
+	if c.config.productVariables.RecoveryPath != nil {
+		return *c.config.productVariables.RecoveryPath
+	}
+	return "recovery"
+}
+
 func (c *deviceConfig) VendorPath() string {
 	if c.config.productVariables.VendorPath != nil {
 		return *c.config.productVariables.VendorPath
@@ -1420,6 +1553,17 @@
 	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)
+}
+
 func (c *deviceConfig) CurrentApiLevelForVendorModules() string {
 	return StringDefault(c.config.productVariables.DeviceCurrentApiLevelForVendorModules, "current")
 }
@@ -1443,6 +1587,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
@@ -1450,6 +1605,10 @@
 	return "product"
 }
 
+func (c *deviceConfig) BuildingProductImage() bool {
+	return proptools.Bool(c.config.productVariables.BuildingProductImage)
+}
+
 func (c *deviceConfig) SystemExtPath() string {
 	if c.config.productVariables.SystemExtPath != nil {
 		return *c.config.productVariables.SystemExtPath
@@ -1457,6 +1616,35 @@
 	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) BuildingRecoveryImage() bool {
+	return proptools.Bool(c.config.productVariables.BuildingRecoveryImage)
+}
+
 func (c *deviceConfig) BtConfigIncludeDir() string {
 	return String(c.config.productVariables.BtConfigIncludeDir)
 }
@@ -1681,8 +1869,8 @@
 	return Bool(c.productVariables.CompressedApex) && !c.UnbundledBuildApps()
 }
 
-func (c *config) ApexTrimEnabled() bool {
-	return Bool(c.productVariables.TrimmedApex)
+func (c *config) DefaultApexPayloadType() string {
+	return StringDefault(c.productVariables.DefaultApexPayloadType, "ext4")
 }
 
 func (c *config) UseSoongSystemImage() bool {
@@ -1991,19 +2179,35 @@
 	return c.productVariables.GetBuildFlagBool("RELEASE_USE_TRANSITIVE_JARS_IN_CLASSPATH")
 }
 
+func (c *config) UseR8FullModeByDefault() bool {
+	return c.productVariables.GetBuildFlagBool("RELEASE_R8_FULL_MODE_BY_DEFAULT")
+}
+
+func (c *config) UseR8OnlyRuntimeVisibleAnnotations() bool {
+	return c.productVariables.GetBuildFlagBool("RELEASE_R8_ONLY_RUNTIME_VISIBLE_ANNOTATIONS")
+}
+
+func (c *config) UseR8StoreStoreFenceConstructorInlining() bool {
+	return c.productVariables.GetBuildFlagBool("RELEASE_R8_STORE_STORE_FENCE_CONSTRUCTOR_INLINING")
+}
+
+func (c *config) UseDexV41() bool {
+	return c.productVariables.GetBuildFlagBool("RELEASE_USE_DEX_V41")
+}
+
 var (
 	mainlineApexContributionBuildFlagsToApexNames = map[string]string{
 		"RELEASE_APEX_CONTRIBUTIONS_ADBD":                    "com.android.adbd",
 		"RELEASE_APEX_CONTRIBUTIONS_ADSERVICES":              "com.android.adservices",
 		"RELEASE_APEX_CONTRIBUTIONS_APPSEARCH":               "com.android.appsearch",
 		"RELEASE_APEX_CONTRIBUTIONS_ART":                     "com.android.art",
-		"RELEASE_APEX_CONTRIBUTIONS_BLUETOOTH":               "com.android.btservices",
+		"RELEASE_APEX_CONTRIBUTIONS_BLUETOOTH":               "com.android.bt",
 		"RELEASE_APEX_CONTRIBUTIONS_CAPTIVEPORTALLOGIN":      "",
 		"RELEASE_APEX_CONTRIBUTIONS_CELLBROADCAST":           "com.android.cellbroadcast",
 		"RELEASE_APEX_CONTRIBUTIONS_CONFIGINFRASTRUCTURE":    "com.android.configinfrastructure",
 		"RELEASE_APEX_CONTRIBUTIONS_CONNECTIVITY":            "com.android.tethering",
 		"RELEASE_APEX_CONTRIBUTIONS_CONSCRYPT":               "com.android.conscrypt",
-		"RELEASE_APEX_CONTRIBUTIONS_CRASHRECOVERY":           "",
+		"RELEASE_APEX_CONTRIBUTIONS_CRASHRECOVERY":           "com.android.crashrecovery",
 		"RELEASE_APEX_CONTRIBUTIONS_DEVICELOCK":              "com.android.devicelock",
 		"RELEASE_APEX_CONTRIBUTIONS_DOCUMENTSUIGOOGLE":       "",
 		"RELEASE_APEX_CONTRIBUTIONS_EXTSERVICES":             "com.android.extservices",
@@ -2014,9 +2218,11 @@
 		"RELEASE_APEX_CONTRIBUTIONS_MODULE_METADATA":         "",
 		"RELEASE_APEX_CONTRIBUTIONS_NETWORKSTACKGOOGLE":      "",
 		"RELEASE_APEX_CONTRIBUTIONS_NEURALNETWORKS":          "com.android.neuralnetworks",
+		"RELEASE_APEX_CONTRIBUTIONS_NFC":                     "com.android.nfcservices",
 		"RELEASE_APEX_CONTRIBUTIONS_ONDEVICEPERSONALIZATION": "com.android.ondevicepersonalization",
 		"RELEASE_APEX_CONTRIBUTIONS_PERMISSION":              "com.android.permission",
 		"RELEASE_APEX_CONTRIBUTIONS_PRIMARY_LIBS":            "",
+		"RELEASE_APEX_CONTRIBUTIONS_PROFILING":               "com.android.profiling",
 		"RELEASE_APEX_CONTRIBUTIONS_REMOTEKEYPROVISIONING":   "com.android.rkpd",
 		"RELEASE_APEX_CONTRIBUTIONS_RESOLV":                  "com.android.resolv",
 		"RELEASE_APEX_CONTRIBUTIONS_SCHEDULING":              "com.android.scheduling",
@@ -2025,6 +2231,7 @@
 		"RELEASE_APEX_CONTRIBUTIONS_STATSD":                  "com.android.os.statsd",
 		"RELEASE_APEX_CONTRIBUTIONS_TELEMETRY_TVP":           "",
 		"RELEASE_APEX_CONTRIBUTIONS_TZDATA":                  "com.android.tzdata",
+		"RELEASE_APEX_CONTRIBUTIONS_UPROBESTATS":             "com.android.uprobestats",
 		"RELEASE_APEX_CONTRIBUTIONS_UWB":                     "com.android.uwb",
 		"RELEASE_APEX_CONTRIBUTIONS_WIFI":                    "com.android.wifi",
 	}
@@ -2090,8 +2297,8 @@
 	return PathsForSource(ctx, c.productVariables.OdmPropFiles)
 }
 
-func (c *config) ExtraAllowedDepsTxt() string {
-	return String(c.productVariables.ExtraAllowedDepsTxt)
+func (c *config) VendorPropFiles(ctx PathContext) Paths {
+	return PathsForSource(ctx, c.productVariables.VendorPropFiles)
 }
 
 func (c *config) EnableUffdGc() string {
@@ -2120,3 +2327,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..4bdf05f 100644
--- a/android/config_test.go
+++ b/android/config_test.go
@@ -77,7 +77,7 @@
 func TestProductConfigAnnotations(t *testing.T) {
 	err := validateConfigAnnotations(&ProductVariables{})
 	if err != nil {
-		t.Errorf(err.Error())
+		t.Error(err.Error())
 	}
 }
 
@@ -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/container.go b/android/container.go
index 2a3777b..5dc97d3 100644
--- a/android/container.go
+++ b/android/container.go
@@ -31,28 +31,25 @@
 // and the corresponding functions are called from [exceptionHandleFunctionsTable] map.
 // ----------------------------------------------------------------------------
 
-type exceptionHandleFunc func(ModuleContext, Module, Module) bool
+type exceptionHandleFunc func(ModuleContext, Module, ModuleProxy) bool
 
 type StubsAvailableModule interface {
 	IsStubsModule() bool
 }
 
 // Returns true if the dependency module is a stubs module
-var depIsStubsModule exceptionHandleFunc = func(_ ModuleContext, _, dep Module) bool {
-	if stubsModule, ok := dep.(StubsAvailableModule); ok {
-		return stubsModule.IsStubsModule()
-	}
-	return false
+var depIsStubsModule exceptionHandleFunc = func(mctx ModuleContext, _ Module, dep ModuleProxy) bool {
+	return OtherModuleProviderOrDefault(mctx, dep, CommonModuleInfoKey).IsStubsModule
 }
 
 // Returns true if the dependency module belongs to any of the apexes.
-var depIsApexModule exceptionHandleFunc = func(mctx ModuleContext, _, dep Module) bool {
+var depIsApexModule exceptionHandleFunc = func(mctx ModuleContext, _ Module, dep ModuleProxy) bool {
 	depContainersInfo, _ := getContainerModuleInfo(mctx, dep)
 	return InList(ApexContainer, depContainersInfo.belongingContainers)
 }
 
 // Returns true if the module and the dependent module belongs to common apexes.
-var belongsToCommonApexes exceptionHandleFunc = func(mctx ModuleContext, m, dep Module) bool {
+var belongsToCommonApexes exceptionHandleFunc = func(mctx ModuleContext, m Module, dep ModuleProxy) bool {
 	mContainersInfo, _ := getContainerModuleInfo(mctx, m)
 	depContainersInfo, _ := getContainerModuleInfo(mctx, dep)
 
@@ -62,7 +59,7 @@
 // Returns true when all apexes that the module belongs to are non updatable.
 // For an apex module to be allowed to depend on a non-apex partition module,
 // all apexes that the module belong to must be non updatable.
-var belongsToNonUpdatableApex exceptionHandleFunc = func(mctx ModuleContext, m, _ Module) bool {
+var belongsToNonUpdatableApex exceptionHandleFunc = func(mctx ModuleContext, m Module, _ ModuleProxy) bool {
 	mContainersInfo, _ := getContainerModuleInfo(mctx, m)
 
 	return !mContainersInfo.UpdatableApex()
@@ -70,7 +67,7 @@
 
 // Returns true if the dependency is added via dependency tags that are not used to tag dynamic
 // dependency tags.
-var depIsNotDynamicDepTag exceptionHandleFunc = func(ctx ModuleContext, m, dep Module) bool {
+var depIsNotDynamicDepTag exceptionHandleFunc = func(ctx ModuleContext, m Module, dep ModuleProxy) bool {
 	mInstallable, _ := m.(InstallableModule)
 	depTag := ctx.OtherModuleDependencyTag(dep)
 	return !InList(depTag, mInstallable.DynamicDependencyTags())
@@ -79,7 +76,7 @@
 // Returns true if the dependency is added via dependency tags that are not used to tag static
 // or dynamic dependency tags. These dependencies do not affect the module in compile time or in
 // runtime, thus are not significant enough to raise an error.
-var depIsNotStaticOrDynamicDepTag exceptionHandleFunc = func(ctx ModuleContext, m, dep Module) bool {
+var depIsNotStaticOrDynamicDepTag exceptionHandleFunc = func(ctx ModuleContext, m Module, dep ModuleProxy) bool {
 	mInstallable, _ := m.(InstallableModule)
 	depTag := ctx.OtherModuleDependencyTag(dep)
 	return !InList(depTag, append(mInstallable.StaticDependencyTags(), mInstallable.DynamicDependencyTags()...))
@@ -93,7 +90,7 @@
 
 	// TODO(b/363016634): Remove from the allowlist when the module is converted
 	// to java_sdk_library and the java_aconfig_library modules depend on the stub.
-	"aconfig_storage_reader_java",
+	"aconfig_storage_stub",
 
 	// framework-res provides core resources essential for building apps and system UI.
 	// This module is implicitly added as a dependency for java modules even when the
@@ -106,7 +103,7 @@
 }
 
 // Returns true when the dependency is globally allowlisted for inter-container dependency
-var depIsGloballyAllowlisted exceptionHandleFunc = func(_ ModuleContext, _, dep Module) bool {
+var depIsGloballyAllowlisted exceptionHandleFunc = func(_ ModuleContext, _ Module, dep ModuleProxy) bool {
 	return InList(dep.Name(), globallyAllowlistedDependencies)
 }
 
@@ -170,8 +167,8 @@
 }
 
 var apexContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool {
-	_, ok := ModuleProvider(mctx, AllApexInfoProvider)
-	return ok
+	// TODO(b/394955484): a module can't determine the apexes it belongs to any more
+	return false
 }
 
 var ctsContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool {
@@ -197,10 +194,11 @@
 
 func determineUnstableModule(mctx ModuleContext) bool {
 	module := mctx.Module()
+
 	unstableModule := module.Name() == "framework-minus-apex"
 	if installable, ok := module.(InstallableModule); ok {
 		for _, staticDepTag := range installable.StaticDependencyTags() {
-			mctx.VisitDirectDepsWithTag(staticDepTag, func(dep Module) {
+			mctx.VisitDirectDepsProxyWithTag(staticDepTag, func(dep ModuleProxy) {
 				if unstableInfo, ok := OtherModuleProvider(mctx, dep, unstableInfoProvider); ok {
 					unstableModule = unstableModule || unstableInfo.ContainsPlatformPrivateApis
 				}
@@ -382,7 +380,7 @@
 
 func (c *ContainersInfo) ApexNames() (ret []string) {
 	for _, apex := range c.belongingApexes {
-		ret = append(ret, apex.InApexModules...)
+		ret = append(ret, apex.BaseApexName)
 	}
 	slices.Sort(ret)
 	return ret
@@ -400,7 +398,7 @@
 
 var ContainersInfoProvider = blueprint.NewProvider[ContainersInfo]()
 
-func satisfyAllowedExceptions(ctx ModuleContext, allowedExceptionLabels []exceptionHandleFuncLabel, m, dep Module) bool {
+func satisfyAllowedExceptions(ctx ModuleContext, allowedExceptionLabels []exceptionHandleFuncLabel, m Module, dep ModuleProxy) bool {
 	for _, label := range allowedExceptionLabels {
 		if exceptionHandleFunctionsTable[label](ctx, m, dep) {
 			return true
@@ -409,7 +407,7 @@
 	return false
 }
 
-func (c *ContainersInfo) GetViolations(mctx ModuleContext, m, dep Module, depInfo ContainersInfo) []string {
+func (c *ContainersInfo) GetViolations(mctx ModuleContext, m Module, dep ModuleProxy, depInfo ContainersInfo) []string {
 	var violations []string
 
 	// Any containers that the module belongs to but the dependency does not belong to must be examined.
@@ -443,19 +441,15 @@
 		}
 	}
 
-	var belongingApexes []ApexInfo
-	if apexInfo, ok := ModuleProvider(ctx, AllApexInfoProvider); ok {
-		belongingApexes = apexInfo.ApexInfos
-	}
-
 	return ContainersInfo{
 		belongingContainers: containers,
-		belongingApexes:     belongingApexes,
+		// TODO(b/394955484): a module can't determine the apexes it belongs to any more
+		belongingApexes: nil,
 	}
 }
 
 func getContainerModuleInfo(ctx ModuleContext, module Module) (ContainersInfo, bool) {
-	if ctx.Module() == module {
+	if ctx.EqualModules(ctx.Module(), module) {
 		return ctx.getContainersInfo(), true
 	}
 
@@ -479,8 +473,8 @@
 func checkContainerViolations(ctx ModuleContext) {
 	if _, ok := ctx.Module().(InstallableModule); ok {
 		containersInfo, _ := getContainerModuleInfo(ctx, ctx.Module())
-		ctx.VisitDirectDeps(func(dep Module) {
-			if !dep.Enabled(ctx) {
+		ctx.VisitDirectDepsProxy(func(dep ModuleProxy) {
+			if !OtherModuleProviderOrDefault(ctx, dep, CommonModuleInfoKey).Enabled {
 				return
 			}
 
diff --git a/android/container_violations.go b/android/container_violations.go
index 4251484..4c6386d 100644
--- a/android/container_violations.go
+++ b/android/container_violations.go
@@ -15,6 +15,10 @@
 package android
 
 var ContainerDependencyViolationAllowlist = map[string][]string{
+	"adservices-service-core": {
+		"gson", // apex [com.android.adservices, com.android.extservices] -> apex [com.android.virt]
+	},
+
 	"android.car-module.impl": {
 		"modules-utils-preconditions", // apex [com.android.car.framework] -> apex [com.android.adservices, com.android.appsearch, com.android.cellbroadcast, com.android.extservices, com.android.ondevicepersonalization, com.android.tethering, com.android.uwb, com.android.wifi, test_com.android.cellbroadcast, test_com.android.wifi]
 	},
@@ -28,22 +32,27 @@
 	},
 
 	"Bluetooth": {
-		"app-compat-annotations",         // apex [com.android.btservices] -> system
-		"framework-bluetooth-pre-jarjar", // apex [com.android.btservices] -> system
+		"app-compat-annotations",         // apex [com.android.bt] -> system
+		"framework-bluetooth-pre-jarjar", // apex [com.android.bt] -> system
 	},
 
 	"bluetooth-nano-protos": {
-		"libprotobuf-java-nano", // apex [com.android.btservices] -> apex [com.android.wifi, test_com.android.wifi]
+		"libprotobuf-java-nano", // apex [com.android.bt] -> apex [com.android.wifi, test_com.android.wifi]
 	},
 
 	"bluetooth.change-ids": {
-		"app-compat-annotations", // apex [com.android.btservices] -> system
+		"app-compat-annotations", // apex [com.android.bt] -> system
 	},
 
 	"CarServiceUpdatable": {
 		"modules-utils-os",                    // apex [com.android.car.framework] -> apex [com.android.permission, test_com.android.permission]
 		"modules-utils-preconditions",         // apex [com.android.car.framework] -> apex [com.android.adservices, com.android.appsearch, com.android.cellbroadcast, com.android.extservices, com.android.ondevicepersonalization, com.android.tethering, com.android.uwb, com.android.wifi, test_com.android.cellbroadcast, test_com.android.wifi]
-		"modules-utils-shell-command-handler", // apex [com.android.car.framework] -> apex [com.android.adservices, com.android.art, com.android.art.debug, com.android.art.testing, com.android.btservices, com.android.configinfrastructure, com.android.mediaprovider, com.android.nfcservices, com.android.permission, com.android.scheduling, com.android.tethering, com.android.uwb, com.android.wifi, test_com.android.mediaprovider, test_com.android.permission, test_com.android.wifi, test_imgdiag_com.android.art, test_jitzygote_com.android.art]
+		"modules-utils-shell-command-handler", // apex [com.android.car.framework] -> apex [com.android.adservices, com.android.art, com.android.art.debug, com.android.art.testing, com.android.bt, com.android.configinfrastructure, com.android.mediaprovider, com.android.nfcservices, com.android.permission, com.android.scheduling, com.android.tethering, com.android.uwb, com.android.wifi, test_com.android.mediaprovider, test_com.android.permission, test_com.android.wifi, test_imgdiag_com.android.art, test_jitzygote_com.android.art]
+	},
+
+	"cellbroadcastreceiver_aconfig_flags_lib": {
+		"ext",       // apex [com.android.cellbroadcast, test_com.android.cellbroadcast] -> system
+		"framework", // apex [com.android.cellbroadcast, test_com.android.cellbroadcast] -> system
 	},
 
 	"connectivity-net-module-utils-bpf": {
@@ -161,6 +170,10 @@
 		"framework", // cts -> unstable
 	},
 
+	"CtsAppFunctionTestCases": {
+		"framework", // cts -> unstable
+	},
+
 	"CtsAppOpsTestCases": {
 		"framework", // cts -> unstable
 	},
@@ -401,10 +414,6 @@
 		"framework", // cts -> unstable
 	},
 
-	"CtsMediaBetterTogetherTestCases": {
-		"framework", // cts -> unstable
-	},
-
 	"CtsMediaCodecTestCases": {
 		"framework", // cts -> unstable
 	},
@@ -465,6 +474,11 @@
 		"framework", // cts -> unstable
 	},
 
+	// TODO(b/387499846): Remove once migrated to sdk_version.
+	"CtsMediaRouterTestCases": {
+		"framework", // cts -> unstable
+	},
+
 	"CtsMediaRouterHostSideTestBluetoothPermissionsApp": {
 		"framework", // cts -> unstable
 	},
@@ -477,6 +491,11 @@
 		"framework", // cts -> unstable
 	},
 
+	// TODO(b/387500109): Remove once migrated to sdk_version.
+	"CtsMediaSessionTestCases": {
+		"framework", // cts -> unstable
+	},
+
 	"CtsMediaV2TestCases": {
 		"framework", // cts -> unstable
 	},
@@ -701,6 +720,10 @@
 		"framework", // cts -> unstable
 	},
 
+	"CtsTvInputTestCases": {
+		"framework", // cts -> unstable
+	},
+
 	"CtsTvTunerTestCases": {
 		"framework", // cts -> unstable
 	},
@@ -807,7 +830,7 @@
 	},
 
 	"devicelockcontroller-lib": {
-		"modules-utils-expresslog", // apex [com.android.devicelock] -> apex [com.android.btservices, com.android.car.framework]
+		"modules-utils-expresslog", // apex [com.android.devicelock] -> apex [com.android.bt, com.android.car.framework]
 	},
 
 	"FederatedCompute": {
@@ -819,7 +842,11 @@
 	},
 
 	"framework-bluetooth.impl": {
-		"app-compat-annotations", // apex [com.android.btservices] -> system
+		"app-compat-annotations", // apex [com.android.bt] -> system
+	},
+
+	"framework-configinfrastructure.impl": {
+		"configinfra_framework_flags_java_lib", // apex [com.android.configinfrastructure] -> system
 	},
 
 	"framework-connectivity-t.impl": {
@@ -827,11 +854,19 @@
 		"framework-connectivity-pre-jarjar", // apex [com.android.tethering] -> system
 	},
 
+	// TODO(b/382743602): Remove "app-compat-annotations" and depend on the stub version jar
+	// TODO(b/382301972): Remove the violations and use jarjar_rename or jarjar_prefix
+	"framework-connectivity-b.impl": {
+		"app-compat-annotations",            // apex [com.android.tethering] -> system
+		"framework-connectivity-pre-jarjar", // apex [com.android.tethering] -> system
+	},
+
 	"framework-connectivity.impl": {
 		"app-compat-annotations", // apex [com.android.tethering] -> system
 	},
 
 	"framework-ondevicepersonalization.impl": {
+		"app-compat-annotations",            // apex [com.android.ondevicepersonalization] -> system
 		"ondevicepersonalization_flags_lib", // apex [com.android.ondevicepersonalization] -> system
 	},
 
@@ -878,10 +913,6 @@
 		"libnativeloader_vendor_shared_lib", // system -> vendor
 	},
 
-	"MctsMediaBetterTogetherTestCases": {
-		"framework", // cts -> unstable
-	},
-
 	"MctsMediaCodecTestCases": {
 		"framework", // cts -> unstable
 	},
@@ -918,6 +949,16 @@
 		"framework", // cts -> unstable
 	},
 
+	// TODO(b/387499846): Remove once migrated to sdk_version.
+	"MctsMediaRouterTestCases": {
+		"framework", // cts -> unstable
+	},
+
+	// TODO(b/387500109): Remove once migrated to sdk_version.
+	"MctsMediaSessionTestCases": {
+		"framework", // cts -> unstable
+	},
+
 	"MctsMediaTranscodingTestCases": {
 		"framework", // cts -> unstable
 	},
@@ -952,7 +993,11 @@
 	},
 
 	"NfcNciApex": {
+		// TODO(b/383782511): Remove the violations once the infra is fixed.
+		"android.nfc.flags-aconfig-java",        // apex [com.android.nfcservices] -> system
 		"android.permission.flags-aconfig-java", // apex [com.android.nfcservices] -> apex [com.android.permission, test_com.android.permission]
+		// TODO(b/383782511): Remove the violations once the infra is fixed.
+		"framework-nfc.impl", // apex [com.android.nfcservices] -> system
 	},
 
 	"okhttp-norepackage": {
@@ -972,7 +1017,7 @@
 	},
 
 	"PlatformProperties": {
-		"sysprop-library-stub-platform", // apex [com.android.btservices, com.android.nfcservices, com.android.tethering, com.android.virt, com.android.wifi, test_com.android.wifi] -> system
+		"sysprop-library-stub-platform", // apex [com.android.bt, com.android.nfcservices, com.android.tethering, com.android.virt, com.android.wifi, test_com.android.wifi] -> system
 	},
 
 	"safety-center-config": {
@@ -1008,8 +1053,8 @@
 	},
 
 	"service-bluetooth-pre-jarjar": {
-		"framework-bluetooth-pre-jarjar", // apex [com.android.btservices] -> system
-		"service-bluetooth.change-ids",   // apex [com.android.btservices] -> system
+		"framework-bluetooth-pre-jarjar", // apex [com.android.bt] -> system
+		"service-bluetooth.change-ids",   // apex [com.android.bt] -> system
 	},
 
 	"service-connectivity": {
@@ -1029,6 +1074,13 @@
 		"framework-connectivity-t-pre-jarjar", // apex [com.android.tethering] -> system
 	},
 
+	// TODO(b/382301972): Remove the violations and use jarjar_rename or jarjar_prefix
+	"service-connectivity-b-pre-jarjar": {
+		"framework-connectivity-pre-jarjar",   // apex [com.android.tethering] -> system
+		"framework-connectivity-b-pre-jarjar", // apex [com.android.tethering] -> system
+		"framework-connectivity-t-pre-jarjar", // apex [com.android.tethering] -> system
+	},
+
 	"service-entitlement": {
 		"auto_value_annotations", // apex [com.android.wifi, test_com.android.wifi] -> apex [com.android.adservices, com.android.extservices, com.android.extservices_tplus]
 	},
diff --git a/android/csuite_config.go b/android/csuite_config.go
index 20bd035..26ad6e1 100644
--- a/android/csuite_config.go
+++ b/android/csuite_config.go
@@ -30,11 +30,11 @@
 type CSuiteConfig struct {
 	ModuleBase
 	properties     csuiteConfigProperties
-	OutputFilePath OutputPath
+	OutputFilePath Path
 }
 
 func (me *CSuiteConfig) GenerateAndroidBuildActions(ctx ModuleContext) {
-	me.OutputFilePath = PathForModuleOut(ctx, me.BaseModuleName()).OutputPath
+	me.OutputFilePath = PathForModuleOut(ctx, me.BaseModuleName())
 }
 
 func (me *CSuiteConfig) AndroidMkEntries() []AndroidMkEntries {
diff --git a/android/csuite_config_test.go b/android/csuite_config_test.go
index b8a176e..7e25aac 100644
--- a/android/csuite_config_test.go
+++ b/android/csuite_config_test.go
@@ -32,7 +32,6 @@
 	if len(variants) > 1 {
 		t.Errorf("expected 1, got %d", len(variants))
 	}
-	outputFilename := result.ModuleForTests(
-		"plain", variants[0]).Module().(*CSuiteConfig).OutputFilePath.Base()
+	outputFilename := result.ModuleForTests(t, "plain", variants[0]).Module().(*CSuiteConfig).OutputFilePath.Base()
 	AssertStringEquals(t, "output file name", "plain", outputFilename)
 }
diff --git a/android/deapexer.go b/android/deapexer.go
index 4049d2b..6d00dcd 100644
--- a/android/deapexer.go
+++ b/android/deapexer.go
@@ -75,8 +75,6 @@
 
 	// map from the name of an exported file from a prebuilt_apex to the path to that file. The
 	// exported file name is the apex relative path, e.g. javalib/core-libart.jar.
-	//
-	// See Prebuilt.ApexInfoMutator for more information.
 	exports map[string]WritablePath
 
 	// name of the java libraries exported from the apex
diff --git a/android/defaults.go b/android/defaults.go
index 3d06c69..0fc1768 100644
--- a/android/defaults.go
+++ b/android/defaults.go
@@ -178,6 +178,7 @@
 	module.AddProperties(
 		&hostAndDeviceProperties{},
 		commonProperties,
+		&baseProperties{},
 		&ApexProperties{},
 		&distProperties{})
 
@@ -273,8 +274,8 @@
 }
 
 func RegisterDefaultsPreArchMutators(ctx RegisterMutatorsContext) {
-	ctx.BottomUp("defaults_deps", defaultsDepsMutator).Parallel()
-	ctx.BottomUp("defaults", defaultsMutator).Parallel()
+	ctx.BottomUp("defaults_deps", defaultsDepsMutator)
+	ctx.BottomUp("defaults", defaultsMutator).UsesCreateModule()
 }
 
 func defaultsDepsMutator(ctx BottomUpMutatorContext) {
diff --git a/android/defaults_test.go b/android/defaults_test.go
index 0ad0fb8..24f1461 100644
--- a/android/defaults_test.go
+++ b/android/defaults_test.go
@@ -123,8 +123,8 @@
 		FixtureWithRootAndroidBp(bp),
 	).RunTest(t)
 
-	missingDefaults := result.ModuleForTests("missing_defaults", "").Output("out")
-	missingTransitiveDefaults := result.ModuleForTests("missing_transitive_defaults", "").Output("out")
+	missingDefaults := result.ModuleForTests(t, "missing_defaults", "").Output("out")
+	missingTransitiveDefaults := result.ModuleForTests(t, "missing_transitive_defaults", "").Output("out")
 
 	AssertSame(t, "missing_defaults rule", ErrorRule, missingDefaults.Rule)
 
diff --git a/android/defs.go b/android/defs.go
index 9f3fb1e..57fcc9b 100644
--- a/android/defs.go
+++ b/android/defs.go
@@ -51,6 +51,14 @@
 		},
 		"cpFlags", "extraCmds")
 
+	// A copy rule wrapped with bash.
+	CpWithBash = pctx.AndroidStaticRule("CpWithBash",
+		blueprint.RuleParams{
+			Command:     "/bin/bash -c \"rm -f $out && cp $cpFlags $cpPreserveSymlinks $in $out$extraCmds\"",
+			Description: "cp $out",
+		},
+		"cpFlags", "extraCmds")
+
 	// A copy rule that doesn't preserve symlinks.
 	CpNoPreserveSymlink = pctx.AndroidStaticRule("CpNoPreserveSymlink",
 		blueprint.RuleParams{
@@ -74,6 +82,14 @@
 		},
 		"cpFlags", "extraCmds")
 
+	// A copy executable rule wrapped with bash
+	CpExecutableWithBash = pctx.AndroidStaticRule("CpExecutableWithBash",
+		blueprint.RuleParams{
+			Command:     "/bin/bash -c \"(rm -f $out && cp $cpFlags $cpPreserveSymlinks $in $out ) && (chmod +x $out$extraCmds )\"",
+			Description: "cp $out",
+		},
+		"cpFlags", "extraCmds")
+
 	// A timestamp touch rule.
 	Touch = pctx.AndroidStaticRule("Touch",
 		blueprint.RuleParams{
@@ -89,6 +105,14 @@
 		},
 		"fromPath")
 
+	// A symlink rule wrapped with bash
+	SymlinkWithBash = pctx.AndroidStaticRule("SymlinkWithBash",
+		blueprint.RuleParams{
+			Command:     "/bin/bash -c \"rm -f $out && ln -sfn $fromPath $out\"",
+			Description: "symlink $out",
+		},
+		"fromPath")
+
 	ErrorRule = pctx.AndroidStaticRule("Error",
 		blueprint.RuleParams{
 			Command:     `echo "$error" && false`,
@@ -119,3 +143,13 @@
 		return ctx.Config().RBEWrapper()
 	})
 }
+
+// CopyFileRule creates a ninja rule to copy path to outPath.
+func CopyFileRule(ctx ModuleContext, path Path, outPath OutputPath) {
+	ctx.Build(pctx, BuildParams{
+		Rule:        Cp,
+		Input:       path,
+		Output:      outPath,
+		Description: "copy " + outPath.Base(),
+	})
+}
diff --git a/android/depset_generic.go b/android/depset_generic.go
deleted file mode 100644
index d04f88b..0000000
--- a/android/depset_generic.go
+++ /dev/null
@@ -1,226 +0,0 @@
-// Copyright 2020 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"
-
-	"github.com/google/blueprint"
-)
-
-// DepSet is designed to be conceptually compatible with Bazel's depsets:
-// https://docs.bazel.build/versions/master/skylark/depsets.html
-
-type DepSetOrder int
-
-const (
-	PREORDER DepSetOrder = iota
-	POSTORDER
-	TOPOLOGICAL
-)
-
-func (o DepSetOrder) String() string {
-	switch o {
-	case PREORDER:
-		return "PREORDER"
-	case POSTORDER:
-		return "POSTORDER"
-	case TOPOLOGICAL:
-		return "TOPOLOGICAL"
-	default:
-		panic(fmt.Errorf("Invalid DepSetOrder %d", o))
-	}
-}
-
-type depSettableType comparable
-
-// A DepSet efficiently stores a slice of an arbitrary type from transitive dependencies without
-// copying. It is stored as a DAG of DepSet nodes, each of which has some direct contents and a list
-// of dependency DepSet nodes.
-//
-// A DepSet has an order that will be used to walk the DAG when ToList() is called.  The order
-// can be POSTORDER, PREORDER, or TOPOLOGICAL.  POSTORDER and PREORDER orders return a postordered
-// or preordered left to right flattened list.  TOPOLOGICAL returns a list that guarantees that
-// elements of children are listed after all of their parents (unless there are duplicate direct
-// elements in the DepSet or any of its transitive dependencies, in which case the ordering of the
-// duplicated element is not guaranteed).
-//
-// A DepSet is created by NewDepSet or NewDepSetBuilder.Build from the slice for direct contents
-// and the *DepSets of dependencies. A DepSet is immutable once created.
-type DepSet[T depSettableType] struct {
-	preorder   bool
-	reverse    bool
-	order      DepSetOrder
-	direct     []T
-	transitive []*DepSet[T]
-}
-
-type depSetGob[T depSettableType] struct {
-	Preorder   bool
-	Reverse    bool
-	Order      DepSetOrder
-	Direct     []T
-	Transitive []*DepSet[T]
-}
-
-func (d *DepSet[T]) ToGob() *depSetGob[T] {
-	return &depSetGob[T]{
-		Preorder:   d.preorder,
-		Reverse:    d.reverse,
-		Order:      d.order,
-		Direct:     d.direct,
-		Transitive: d.transitive,
-	}
-}
-
-func (d *DepSet[T]) FromGob(data *depSetGob[T]) {
-	d.preorder = data.Preorder
-	d.reverse = data.Reverse
-	d.order = data.Order
-	d.direct = data.Direct
-	d.transitive = data.Transitive
-}
-
-func (d *DepSet[T]) GobEncode() ([]byte, error) {
-	return blueprint.CustomGobEncode[depSetGob[T]](d)
-}
-
-func (d *DepSet[T]) GobDecode(data []byte) error {
-	return blueprint.CustomGobDecode[depSetGob[T]](data, d)
-}
-
-// NewDepSet returns an immutable DepSet with the given order, direct and transitive contents.
-func NewDepSet[T depSettableType](order DepSetOrder, direct []T, transitive []*DepSet[T]) *DepSet[T] {
-	var directCopy []T
-	var transitiveCopy []*DepSet[T]
-	for _, t := range transitive {
-		if t.order != order {
-			panic(fmt.Errorf("incompatible order, new DepSet is %s but transitive DepSet is %s",
-				order, t.order))
-		}
-	}
-
-	if order == TOPOLOGICAL {
-		// TOPOLOGICAL is implemented as a postorder traversal followed by reversing the output.
-		// Pre-reverse the inputs here so their order is maintained in the output.
-		directCopy = ReverseSlice(direct)
-		transitiveCopy = ReverseSlice(transitive)
-	} else {
-		directCopy = append([]T(nil), direct...)
-		transitiveCopy = append([]*DepSet[T](nil), transitive...)
-	}
-
-	return &DepSet[T]{
-		preorder:   order == PREORDER,
-		reverse:    order == TOPOLOGICAL,
-		order:      order,
-		direct:     directCopy,
-		transitive: transitiveCopy,
-	}
-}
-
-// DepSetBuilder is used to create an immutable DepSet.
-type DepSetBuilder[T depSettableType] struct {
-	order      DepSetOrder
-	direct     []T
-	transitive []*DepSet[T]
-}
-
-// NewDepSetBuilder returns a DepSetBuilder to create an immutable DepSet with the given order and
-// type, represented by a slice of type that will be in the DepSet.
-func NewDepSetBuilder[T depSettableType](order DepSetOrder) *DepSetBuilder[T] {
-	return &DepSetBuilder[T]{
-		order: order,
-	}
-}
-
-// DirectSlice adds direct contents to the DepSet being built by a DepSetBuilder. Newly added direct
-// contents are to the right of any existing direct contents.
-func (b *DepSetBuilder[T]) DirectSlice(direct []T) *DepSetBuilder[T] {
-	b.direct = append(b.direct, direct...)
-	return b
-}
-
-// Direct adds direct contents to the DepSet being built by a DepSetBuilder. Newly added direct
-// contents are to the right of any existing direct contents.
-func (b *DepSetBuilder[T]) Direct(direct ...T) *DepSetBuilder[T] {
-	b.direct = append(b.direct, direct...)
-	return b
-}
-
-// Transitive adds transitive contents to the DepSet being built by a DepSetBuilder. Newly added
-// transitive contents are to the right of any existing transitive contents.
-func (b *DepSetBuilder[T]) Transitive(transitive ...*DepSet[T]) *DepSetBuilder[T] {
-	for _, t := range transitive {
-		if t.order != b.order {
-			panic(fmt.Errorf("incompatible order, new DepSet is %s but transitive DepSet is %s",
-				b.order, t.order))
-		}
-	}
-	b.transitive = append(b.transitive, transitive...)
-	return b
-}
-
-// Returns the DepSet being built by this DepSetBuilder.  The DepSetBuilder retains its contents
-// for creating more depSets.
-func (b *DepSetBuilder[T]) Build() *DepSet[T] {
-	return NewDepSet(b.order, b.direct, b.transitive)
-}
-
-// walk calls the visit method in depth-first order on a DepSet, preordered if d.preorder is set,
-// otherwise postordered.
-func (d *DepSet[T]) walk(visit func([]T)) {
-	visited := make(map[*DepSet[T]]bool)
-
-	var dfs func(d *DepSet[T])
-	dfs = func(d *DepSet[T]) {
-		visited[d] = true
-		if d.preorder {
-			visit(d.direct)
-		}
-		for _, dep := range d.transitive {
-			if !visited[dep] {
-				dfs(dep)
-			}
-		}
-
-		if !d.preorder {
-			visit(d.direct)
-		}
-	}
-
-	dfs(d)
-}
-
-// ToList returns the DepSet flattened to a list.  The order in the list is based on the order
-// of the DepSet.  POSTORDER and PREORDER orders return a postordered or preordered left to right
-// flattened list.  TOPOLOGICAL returns a list that guarantees that elements of children are listed
-// after all of their parents (unless there are duplicate direct elements in the DepSet or any of
-// its transitive dependencies, in which case the ordering of the duplicated element is not
-// guaranteed).
-func (d *DepSet[T]) ToList() []T {
-	if d == nil {
-		return nil
-	}
-	var list []T
-	d.walk(func(paths []T) {
-		list = append(list, paths...)
-	})
-	list = firstUniqueInPlace(list)
-	if d.reverse {
-		ReverseSliceInPlace(list)
-	}
-	return list
-}
diff --git a/android/depset_test.go b/android/depset_test.go
deleted file mode 100644
index 376dffa..0000000
--- a/android/depset_test.go
+++ /dev/null
@@ -1,295 +0,0 @@
-// Copyright 2020 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"
-	"reflect"
-	"strings"
-	"testing"
-)
-
-func ExampleDepSet_ToList_postordered() {
-	a := NewDepSetBuilder[Path](POSTORDER).Direct(PathForTesting("a")).Build()
-	b := NewDepSetBuilder[Path](POSTORDER).Direct(PathForTesting("b")).Transitive(a).Build()
-	c := NewDepSetBuilder[Path](POSTORDER).Direct(PathForTesting("c")).Transitive(a).Build()
-	d := NewDepSetBuilder[Path](POSTORDER).Direct(PathForTesting("d")).Transitive(b, c).Build()
-
-	fmt.Println(Paths(d.ToList()).Strings())
-	// Output: [a b c d]
-}
-
-func ExampleDepSet_ToList_preordered() {
-	a := NewDepSetBuilder[Path](PREORDER).Direct(PathForTesting("a")).Build()
-	b := NewDepSetBuilder[Path](PREORDER).Direct(PathForTesting("b")).Transitive(a).Build()
-	c := NewDepSetBuilder[Path](PREORDER).Direct(PathForTesting("c")).Transitive(a).Build()
-	d := NewDepSetBuilder[Path](PREORDER).Direct(PathForTesting("d")).Transitive(b, c).Build()
-
-	fmt.Println(Paths(d.ToList()).Strings())
-	// Output: [d b a c]
-}
-
-func ExampleDepSet_ToList_topological() {
-	a := NewDepSetBuilder[Path](TOPOLOGICAL).Direct(PathForTesting("a")).Build()
-	b := NewDepSetBuilder[Path](TOPOLOGICAL).Direct(PathForTesting("b")).Transitive(a).Build()
-	c := NewDepSetBuilder[Path](TOPOLOGICAL).Direct(PathForTesting("c")).Transitive(a).Build()
-	d := NewDepSetBuilder[Path](TOPOLOGICAL).Direct(PathForTesting("d")).Transitive(b, c).Build()
-
-	fmt.Println(Paths(d.ToList()).Strings())
-	// Output: [d b c a]
-}
-
-// Tests based on Bazel's ExpanderTestBase.java to ensure compatibility
-// https://github.com/bazelbuild/bazel/blob/master/src/test/java/com/google/devtools/build/lib/collect/nestedset/ExpanderTestBase.java
-func TestDepSet(t *testing.T) {
-	a := PathForTesting("a")
-	b := PathForTesting("b")
-	c := PathForTesting("c")
-	c2 := PathForTesting("c2")
-	d := PathForTesting("d")
-	e := PathForTesting("e")
-
-	tests := []struct {
-		name                             string
-		depSet                           func(t *testing.T, order DepSetOrder) *DepSet[Path]
-		postorder, preorder, topological []string
-	}{
-		{
-			name: "simple",
-			depSet: func(t *testing.T, order DepSetOrder) *DepSet[Path] {
-				return NewDepSet[Path](order, Paths{c, a, b}, nil)
-			},
-			postorder:   []string{"c", "a", "b"},
-			preorder:    []string{"c", "a", "b"},
-			topological: []string{"c", "a", "b"},
-		},
-		{
-			name: "simpleNoDuplicates",
-			depSet: func(t *testing.T, order DepSetOrder) *DepSet[Path] {
-				return NewDepSet[Path](order, Paths{c, a, a, a, b}, nil)
-			},
-			postorder:   []string{"c", "a", "b"},
-			preorder:    []string{"c", "a", "b"},
-			topological: []string{"c", "a", "b"},
-		},
-		{
-			name: "nesting",
-			depSet: func(t *testing.T, order DepSetOrder) *DepSet[Path] {
-				subset := NewDepSet[Path](order, Paths{c, a, e}, nil)
-				return NewDepSet[Path](order, Paths{b, d}, []*DepSet[Path]{subset})
-			},
-			postorder:   []string{"c", "a", "e", "b", "d"},
-			preorder:    []string{"b", "d", "c", "a", "e"},
-			topological: []string{"b", "d", "c", "a", "e"},
-		},
-		{
-			name: "builderReuse",
-			depSet: func(t *testing.T, order DepSetOrder) *DepSet[Path] {
-				assertEquals := func(t *testing.T, w, g Paths) {
-					t.Helper()
-					if !reflect.DeepEqual(w, g) {
-						t.Errorf("want %q, got %q", w, g)
-					}
-				}
-				builder := NewDepSetBuilder[Path](order)
-				assertEquals(t, nil, builder.Build().ToList())
-
-				builder.Direct(b)
-				assertEquals(t, Paths{b}, builder.Build().ToList())
-
-				builder.Direct(d)
-				assertEquals(t, Paths{b, d}, builder.Build().ToList())
-
-				child := NewDepSetBuilder[Path](order).Direct(c, a, e).Build()
-				builder.Transitive(child)
-				return builder.Build()
-			},
-			postorder:   []string{"c", "a", "e", "b", "d"},
-			preorder:    []string{"b", "d", "c", "a", "e"},
-			topological: []string{"b", "d", "c", "a", "e"},
-		},
-		{
-			name: "builderChaining",
-			depSet: func(t *testing.T, order DepSetOrder) *DepSet[Path] {
-				return NewDepSetBuilder[Path](order).Direct(b).Direct(d).
-					Transitive(NewDepSetBuilder[Path](order).Direct(c, a, e).Build()).Build()
-			},
-			postorder:   []string{"c", "a", "e", "b", "d"},
-			preorder:    []string{"b", "d", "c", "a", "e"},
-			topological: []string{"b", "d", "c", "a", "e"},
-		},
-		{
-			name: "transitiveDepsHandledSeparately",
-			depSet: func(t *testing.T, order DepSetOrder) *DepSet[Path] {
-				subset := NewDepSetBuilder[Path](order).Direct(c, a, e).Build()
-				builder := NewDepSetBuilder[Path](order)
-				// The fact that we add the transitive subset between the Direct(b) and Direct(d)
-				// calls should not change the result.
-				builder.Direct(b)
-				builder.Transitive(subset)
-				builder.Direct(d)
-				return builder.Build()
-			},
-			postorder:   []string{"c", "a", "e", "b", "d"},
-			preorder:    []string{"b", "d", "c", "a", "e"},
-			topological: []string{"b", "d", "c", "a", "e"},
-		},
-		{
-			name: "nestingNoDuplicates",
-			depSet: func(t *testing.T, order DepSetOrder) *DepSet[Path] {
-				subset := NewDepSetBuilder[Path](order).Direct(c, a, e).Build()
-				return NewDepSetBuilder[Path](order).Direct(b, d, e).Transitive(subset).Build()
-			},
-			postorder:   []string{"c", "a", "e", "b", "d"},
-			preorder:    []string{"b", "d", "e", "c", "a"},
-			topological: []string{"b", "d", "c", "a", "e"},
-		},
-		{
-			name: "chain",
-			depSet: func(t *testing.T, order DepSetOrder) *DepSet[Path] {
-				c := NewDepSetBuilder[Path](order).Direct(c).Build()
-				b := NewDepSetBuilder[Path](order).Direct(b).Transitive(c).Build()
-				a := NewDepSetBuilder[Path](order).Direct(a).Transitive(b).Build()
-
-				return a
-			},
-			postorder:   []string{"c", "b", "a"},
-			preorder:    []string{"a", "b", "c"},
-			topological: []string{"a", "b", "c"},
-		},
-		{
-			name: "diamond",
-			depSet: func(t *testing.T, order DepSetOrder) *DepSet[Path] {
-				d := NewDepSetBuilder[Path](order).Direct(d).Build()
-				c := NewDepSetBuilder[Path](order).Direct(c).Transitive(d).Build()
-				b := NewDepSetBuilder[Path](order).Direct(b).Transitive(d).Build()
-				a := NewDepSetBuilder[Path](order).Direct(a).Transitive(b).Transitive(c).Build()
-
-				return a
-			},
-			postorder:   []string{"d", "b", "c", "a"},
-			preorder:    []string{"a", "b", "d", "c"},
-			topological: []string{"a", "b", "c", "d"},
-		},
-		{
-			name: "extendedDiamond",
-			depSet: func(t *testing.T, order DepSetOrder) *DepSet[Path] {
-				d := NewDepSetBuilder[Path](order).Direct(d).Build()
-				e := NewDepSetBuilder[Path](order).Direct(e).Build()
-				b := NewDepSetBuilder[Path](order).Direct(b).Transitive(d).Transitive(e).Build()
-				c := NewDepSetBuilder[Path](order).Direct(c).Transitive(e).Transitive(d).Build()
-				a := NewDepSetBuilder[Path](order).Direct(a).Transitive(b).Transitive(c).Build()
-				return a
-			},
-			postorder:   []string{"d", "e", "b", "c", "a"},
-			preorder:    []string{"a", "b", "d", "e", "c"},
-			topological: []string{"a", "b", "c", "e", "d"},
-		},
-		{
-			name: "extendedDiamondRightArm",
-			depSet: func(t *testing.T, order DepSetOrder) *DepSet[Path] {
-				d := NewDepSetBuilder[Path](order).Direct(d).Build()
-				e := NewDepSetBuilder[Path](order).Direct(e).Build()
-				b := NewDepSetBuilder[Path](order).Direct(b).Transitive(d).Transitive(e).Build()
-				c2 := NewDepSetBuilder[Path](order).Direct(c2).Transitive(e).Transitive(d).Build()
-				c := NewDepSetBuilder[Path](order).Direct(c).Transitive(c2).Build()
-				a := NewDepSetBuilder[Path](order).Direct(a).Transitive(b).Transitive(c).Build()
-				return a
-			},
-			postorder:   []string{"d", "e", "b", "c2", "c", "a"},
-			preorder:    []string{"a", "b", "d", "e", "c", "c2"},
-			topological: []string{"a", "b", "c", "c2", "e", "d"},
-		},
-		{
-			name: "orderConflict",
-			depSet: func(t *testing.T, order DepSetOrder) *DepSet[Path] {
-				child1 := NewDepSetBuilder[Path](order).Direct(a, b).Build()
-				child2 := NewDepSetBuilder[Path](order).Direct(b, a).Build()
-				parent := NewDepSetBuilder[Path](order).Transitive(child1).Transitive(child2).Build()
-				return parent
-			},
-			postorder:   []string{"a", "b"},
-			preorder:    []string{"a", "b"},
-			topological: []string{"b", "a"},
-		},
-		{
-			name: "orderConflictNested",
-			depSet: func(t *testing.T, order DepSetOrder) *DepSet[Path] {
-				a := NewDepSetBuilder[Path](order).Direct(a).Build()
-				b := NewDepSetBuilder[Path](order).Direct(b).Build()
-				child1 := NewDepSetBuilder[Path](order).Transitive(a).Transitive(b).Build()
-				child2 := NewDepSetBuilder[Path](order).Transitive(b).Transitive(a).Build()
-				parent := NewDepSetBuilder[Path](order).Transitive(child1).Transitive(child2).Build()
-				return parent
-			},
-			postorder:   []string{"a", "b"},
-			preorder:    []string{"a", "b"},
-			topological: []string{"b", "a"},
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			t.Run("postorder", func(t *testing.T) {
-				depSet := tt.depSet(t, POSTORDER)
-				if g, w := Paths(depSet.ToList()).Strings(), tt.postorder; !reflect.DeepEqual(g, w) {
-					t.Errorf("expected ToList() = %q, got %q", w, g)
-				}
-			})
-			t.Run("preorder", func(t *testing.T) {
-				depSet := tt.depSet(t, PREORDER)
-				if g, w := Paths(depSet.ToList()).Strings(), tt.preorder; !reflect.DeepEqual(g, w) {
-					t.Errorf("expected ToList() = %q, got %q", w, g)
-				}
-			})
-			t.Run("topological", func(t *testing.T) {
-				depSet := tt.depSet(t, TOPOLOGICAL)
-				if g, w := Paths(depSet.ToList()).Strings(), tt.topological; !reflect.DeepEqual(g, w) {
-					t.Errorf("expected ToList() = %q, got %q", w, g)
-				}
-			})
-		})
-	}
-}
-
-func TestDepSetInvalidOrder(t *testing.T) {
-	orders := []DepSetOrder{POSTORDER, PREORDER, TOPOLOGICAL}
-
-	run := func(t *testing.T, order1, order2 DepSetOrder) {
-		defer func() {
-			if r := recover(); r != nil {
-				if err, ok := r.(error); !ok {
-					t.Fatalf("expected panic error, got %v", err)
-				} else if !strings.Contains(err.Error(), "incompatible order") {
-					t.Fatalf("expected incompatible order error, got %v", err)
-				}
-			}
-		}()
-		NewDepSet(order1, nil, []*DepSet[Path]{NewDepSet[Path](order2, nil, nil)})
-		t.Fatal("expected panic")
-	}
-
-	for _, order1 := range orders {
-		t.Run(order1.String(), func(t *testing.T) {
-			for _, order2 := range orders {
-				t.Run(order2.String(), func(t *testing.T) {
-					if order1 != order2 {
-						run(t, order1, order2)
-					}
-				})
-			}
-		})
-	}
-}
diff --git a/android/deptag_test.go b/android/deptag_test.go
index eb4fa89..9037871 100644
--- a/android/deptag_test.go
+++ b/android/deptag_test.go
@@ -90,10 +90,10 @@
 
 	config := result.Config
 
-	hostFoo := result.ModuleForTests("foo", config.BuildOSCommonTarget.String()).Description("install")
-	hostInstallDep := result.ModuleForTests("install_dep", config.BuildOSCommonTarget.String()).Description("install")
-	hostTransitive := result.ModuleForTests("transitive", config.BuildOSCommonTarget.String()).Description("install")
-	hostDep := result.ModuleForTests("dep", config.BuildOSCommonTarget.String()).Description("install")
+	hostFoo := result.ModuleForTests(t, "foo", config.BuildOSCommonTarget.String()).Description("install")
+	hostInstallDep := result.ModuleForTests(t, "install_dep", config.BuildOSCommonTarget.String()).Description("install")
+	hostTransitive := result.ModuleForTests(t, "transitive", config.BuildOSCommonTarget.String()).Description("install")
+	hostDep := result.ModuleForTests(t, "dep", config.BuildOSCommonTarget.String()).Description("install")
 
 	if g, w := hostFoo.Implicits.Strings(), hostInstallDep.Output.String(); !InList(w, g) {
 		t.Errorf("expected host dependency %q, got %q", w, g)
@@ -111,10 +111,10 @@
 		t.Errorf("expected no host dependency %q, got %q", w, g)
 	}
 
-	deviceFoo := result.ModuleForTests("foo", "android_common").Description("install")
-	deviceInstallDep := result.ModuleForTests("install_dep", "android_common").Description("install")
-	deviceTransitive := result.ModuleForTests("transitive", "android_common").Description("install")
-	deviceDep := result.ModuleForTests("dep", "android_common").Description("install")
+	deviceFoo := result.ModuleForTests(t, "foo", "android_common").Description("install")
+	deviceInstallDep := result.ModuleForTests(t, "install_dep", "android_common").Description("install")
+	deviceTransitive := result.ModuleForTests(t, "transitive", "android_common").Description("install")
+	deviceDep := result.ModuleForTests(t, "dep", "android_common").Description("install")
 
 	if g, w := deviceFoo.OrderOnly.Strings(), deviceInstallDep.Output.String(); !InList(w, g) {
 		t.Errorf("expected device dependency %q, got %q", w, g)
diff --git a/android/dirgroup.go b/android/dirgroup.go
new file mode 100644
index 0000000..62fbaa5
--- /dev/null
+++ b/android/dirgroup.go
@@ -0,0 +1,62 @@
+// 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 (
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+func init() {
+	RegisterDirgroupBuildComponents(InitRegistrationContext)
+}
+
+func RegisterDirgroupBuildComponents(ctx RegistrationContext) {
+	ctx.RegisterModuleType("dirgroup", DirGroupFactory)
+}
+
+type dirGroupProperties struct {
+	// dirs lists directories that will be included in this dirgroup
+	Dirs proptools.Configurable[[]string] `android:"path"`
+}
+
+type dirGroup struct {
+	ModuleBase
+	DefaultableModuleBase
+	properties dirGroupProperties
+}
+
+type DirInfo struct {
+	Dirs DirectoryPaths
+}
+
+var DirProvider = blueprint.NewProvider[DirInfo]()
+
+// dirgroup contains a list of dirs that are referenced by other modules
+// properties using the syntax ":<name>". dirgroup are also be used to export
+// dirs across package boundaries. Currently the only allowed usage is genrule's
+// dir_srcs property.
+func DirGroupFactory() Module {
+	module := &dirGroup{}
+	module.AddProperties(&module.properties)
+	InitAndroidModule(module)
+	InitDefaultableModule(module)
+	return module
+}
+
+func (fg *dirGroup) GenerateAndroidBuildActions(ctx ModuleContext) {
+	dirs := DirectoryPathsForModuleSrc(ctx, fg.properties.Dirs.GetOrDefault(ctx, nil))
+	SetProvider(ctx, DirProvider, DirInfo{Dirs: dirs})
+}
diff --git a/android/early_module_context.go b/android/early_module_context.go
index 11de771..8d28285 100644
--- a/android/early_module_context.go
+++ b/android/early_module_context.go
@@ -21,15 +21,26 @@
 	"github.com/google/blueprint"
 )
 
+// ModuleErrorContext provides only methods to report errors about the current module.
+type ModuleErrorContext interface {
+	// ModuleErrorf reports an error at the line number of the module type in the module definition.
+	ModuleErrorf(fmt string, args ...interface{})
+
+	// PropertyErrorf reports an error at the line number of a property in the module definition.
+	PropertyErrorf(property, fmt string, args ...interface{})
+}
+
 // EarlyModuleContext provides methods that can be called early, as soon as the properties have
 // been parsed into the module and before any mutators have run.
 type EarlyModuleContext interface {
+	ModuleErrorContext
+
 	// Module returns the current module as a Module.  It should rarely be necessary, as the module already has a
 	// reference to itself.
 	Module() Module
 
 	// ModuleName returns the name of the module.  This is generally the value that was returned by Module.Name() when
-	// the module was created, but may have been modified by calls to BaseMutatorContext.Rename.
+	// the module was created, but may have been modified by calls to BottomUpMutatorContext.Rename.
 	ModuleName() string
 
 	// ModuleDir returns the path to the directory that contains the definition of the module.
@@ -49,12 +60,6 @@
 	// Errorf reports an error at the specified position of the module definition file.
 	Errorf(pos scanner.Position, fmt string, args ...interface{})
 
-	// ModuleErrorf reports an error at the line number of the module type in the module definition.
-	ModuleErrorf(fmt string, args ...interface{})
-
-	// PropertyErrorf reports an error at the line number of a property in the module definition.
-	PropertyErrorf(property, fmt string, args ...interface{})
-
 	// OtherModulePropertyErrorf reports an error at the line number of a property in the given module definition.
 	OtherModulePropertyErrorf(module Module, property, fmt string, args ...interface{})
 
@@ -177,7 +182,7 @@
 }
 
 func (e *earlyModuleContext) OtherModulePropertyErrorf(module Module, property string, fmt string, args ...interface{}) {
-	e.EarlyModuleContext.OtherModulePropertyErrorf(module, property, fmt, args...)
+	e.EarlyModuleContext.OtherModulePropertyErrorf(getWrappedModule(module), property, fmt, args...)
 }
 
 func (e *earlyModuleContext) HasMutatorFinished(mutatorName string) bool {
diff --git a/android/filegroup.go b/android/filegroup.go
index ff0f74e..47102b9 100644
--- a/android/filegroup.go
+++ b/android/filegroup.go
@@ -41,6 +41,14 @@
 
 	Exclude_srcs proptools.Configurable[[]string] `android:"path"`
 
+	// Sources that will be included in the filegroup, but any module dependencies will be added
+	// using the device os and the device's first architecture's variant.
+	Device_first_srcs proptools.Configurable[[]string] `android:"path_device_first"`
+
+	// Sources that will be included in the filegroup, but any module dependencies will be added
+	// using the device os and the common architecture's variant.
+	Device_common_srcs proptools.Configurable[[]string] `android:"path_device_common"`
+
 	// The base path to the files.  May be used by other modules to determine which portion
 	// of the path to use.  For example, when a filegroup is used as data in a cc_test rule,
 	// the base path is stripped off the path and the remaining path is used as the
@@ -90,11 +98,12 @@
 }
 
 func (fg *fileGroup) GenerateAndroidBuildActions(ctx ModuleContext) {
-	fg.srcs = PathsForModuleSrcExcludes(ctx, fg.properties.Srcs.GetOrDefault(ctx, nil), fg.properties.Exclude_srcs.GetOrDefault(ctx, nil))
+	srcs := PathsForModuleSrcExcludes(ctx, fg.properties.Srcs.GetOrDefault(ctx, nil), fg.properties.Exclude_srcs.GetOrDefault(ctx, nil))
+	srcs = append(srcs, PathsForModuleSrc(ctx, fg.properties.Device_first_srcs.GetOrDefault(ctx, nil))...)
+	srcs = append(srcs, PathsForModuleSrc(ctx, fg.properties.Device_common_srcs.GetOrDefault(ctx, nil))...)
 	if fg.properties.Path != nil {
-		fg.srcs = PathsWithModuleSrcSubDir(ctx, fg.srcs, String(fg.properties.Path))
+		srcs = PathsWithModuleSrcSubDir(ctx, srcs, String(fg.properties.Path))
 	}
-	SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: fg.srcs.Strings()})
 
 	var aconfigDeclarations []string
 	var intermediateCacheOutputPaths Paths
@@ -108,6 +117,8 @@
 			maps.Copy(modeInfos, dep.ModeInfos)
 		}
 	})
+
+	fg.srcs = srcs
 	SetProvider(ctx, CodegenInfoProvider, CodegenInfo{
 		AconfigDeclarations:          aconfigDeclarations,
 		IntermediateCacheOutputPaths: intermediateCacheOutputPaths,
@@ -139,3 +150,15 @@
 
 	return module
 }
+
+// Collect information for opening IDE project files in java/jdeps.go.
+// Copied from build/soong/genrule/genrule.go
+func (fg *fileGroup) IDEInfo(ctx BaseModuleContext, dpInfo *IdeInfo) {
+	dpInfo.Srcs = append(dpInfo.Srcs, fg.Srcs().Strings()...)
+	for _, src := range fg.properties.Srcs.GetOrDefault(ctx, nil) {
+		if mod, _ := SrcIsModuleWithTag(src); mod != "" {
+			// Register the module name without any tags in `Deps`
+			dpInfo.Deps = append(dpInfo.Deps, mod)
+		}
+	}
+}
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/fixture.go b/android/fixture.go
index 5ad47e8..ea52b95 100644
--- a/android/fixture.go
+++ b/android/fixture.go
@@ -1048,7 +1048,7 @@
 
 // Module returns the module with the specific name and of the specified variant.
 func (r *TestResult) Module(name string, variant string) Module {
-	return r.ModuleForTests(name, variant).Module()
+	return r.ModuleForTests(r.fixture.t, name, variant).Module()
 }
 
 // CollateErrs adds additional errors to the result and returns true if there is more than one
diff --git a/android/hooks.go b/android/hooks.go
index bd2fa5e..5d509a4 100644
--- a/android/hooks.go
+++ b/android/hooks.go
@@ -37,6 +37,7 @@
 	AppendProperties(...interface{})
 	PrependProperties(...interface{})
 	CreateModule(ModuleFactory, ...interface{}) Module
+	CreateModuleInDirectory(ModuleFactory, string, ...interface{}) Module
 
 	registerScopedModuleType(name string, factory blueprint.ModuleFactory)
 	moduleFactories() map[string]blueprint.ModuleFactory
@@ -58,6 +59,16 @@
 	})
 }
 
+func AddLoadHookWithPriority(m blueprint.Module, hook func(LoadHookContext), priority int) {
+	blueprint.AddLoadHookWithPriority(m, func(ctx blueprint.LoadHookContext) {
+		actx := &loadHookContext{
+			earlyModuleContext: m.(Module).base().earlyModuleContextFactory(ctx),
+			bp:                 ctx,
+		}
+		hook(actx)
+	}, priority)
+}
+
 type loadHookContext struct {
 	earlyModuleContext
 	bp     blueprint.LoadHookContext
@@ -89,17 +100,41 @@
 	l.appendPrependHelper(props, proptools.PrependMatchingProperties)
 }
 
-func (l *loadHookContext) createModule(factory blueprint.ModuleFactory, name string, props ...interface{}) blueprint.Module {
-	return l.bp.CreateModule(factory, name, props...)
+func (l *loadHookContext) createModule(factory blueprint.ModuleFactory, name string, props ...interface{}) Module {
+	return bpModuleToModule(l.bp.CreateModule(factory, name, props...))
+}
+
+func (l *loadHookContext) createModuleInDirectory(factory blueprint.ModuleFactory, name, moduleDir string, props ...interface{}) Module {
+	return bpModuleToModule(l.bp.CreateModuleInDirectory(factory, name, moduleDir, props...))
+}
+
+type specifyDirectory struct {
+	specified bool
+	directory string
+}
+
+func doesNotSpecifyDirectory() specifyDirectory {
+	return specifyDirectory{
+		specified: false,
+		directory: "",
+	}
+}
+
+func specifiesDirectory(directory string) specifyDirectory {
+	return specifyDirectory{
+		specified: true,
+		directory: directory,
+	}
 }
 
 type createModuleContext interface {
 	Module() Module
 	HasMutatorFinished(mutatorName string) bool
-	createModule(blueprint.ModuleFactory, string, ...interface{}) blueprint.Module
+	createModule(blueprint.ModuleFactory, string, ...interface{}) Module
+	createModuleInDirectory(blueprint.ModuleFactory, string, string, ...interface{}) Module
 }
 
-func createModule(ctx createModuleContext, factory ModuleFactory, ext string, props ...interface{}) Module {
+func createModule(ctx createModuleContext, factory ModuleFactory, ext string, specifyDirectory specifyDirectory, props ...interface{}) Module {
 	if ctx.HasMutatorFinished("defaults") {
 		// Creating modules late is oftentimes problematic, because they don't have earlier
 		// mutators run on them. Prevent making modules after the defaults mutator has run.
@@ -119,7 +154,12 @@
 	}
 	typeName = typeName + "_" + ext
 
-	module := ctx.createModule(ModuleFactoryAdaptor(factory), typeName, append(inherited, props...)...).(Module)
+	var module Module
+	if specifyDirectory.specified {
+		module = ctx.createModuleInDirectory(ModuleFactoryAdaptor(factory), typeName, specifyDirectory.directory, append(inherited, props...)...).(Module)
+	} else {
+		module = ctx.createModule(ModuleFactoryAdaptor(factory), typeName, append(inherited, props...)...).(Module)
+	}
 
 	if ctx.Module().base().variableProperties != nil && module.base().variableProperties != nil {
 		src := ctx.Module().base().variableProperties
@@ -139,7 +179,11 @@
 }
 
 func (l *loadHookContext) CreateModule(factory ModuleFactory, props ...interface{}) Module {
-	return createModule(l, factory, "_loadHookModule", props...)
+	return createModule(l, factory, "_loadHookModule", doesNotSpecifyDirectory(), props...)
+}
+
+func (l *loadHookContext) CreateModuleInDirectory(factory ModuleFactory, directory string, props ...interface{}) Module {
+	return createModule(l, factory, "_loadHookModule", specifiesDirectory(directory), props...)
 }
 
 func (l *loadHookContext) registerScopedModuleType(name string, factory blueprint.ModuleFactory) {
diff --git a/android/image.go b/android/image.go
index 6e5a551..78343db 100644
--- a/android/image.go
+++ b/android/image.go
@@ -14,44 +14,61 @@
 
 package android
 
+type ImageInterfaceContext interface {
+	ArchModuleContext
+
+	Module() Module
+
+	ModuleErrorf(fmt string, args ...interface{})
+	PropertyErrorf(property, fmt string, args ...interface{})
+
+	DeviceSpecific() bool
+	SocSpecific() bool
+	ProductSpecific() bool
+	SystemExtSpecific() bool
+	Platform() bool
+
+	Config() Config
+}
+
 // ImageInterface is implemented by modules that need to be split by the imageTransitionMutator.
 type ImageInterface interface {
 	// ImageMutatorBegin is called before any other method in the ImageInterface.
-	ImageMutatorBegin(ctx BaseModuleContext)
+	ImageMutatorBegin(ctx ImageInterfaceContext)
 
 	// VendorVariantNeeded should return true if the module needs a vendor variant (installed on the vendor image).
-	VendorVariantNeeded(ctx BaseModuleContext) bool
+	VendorVariantNeeded(ctx ImageInterfaceContext) bool
 
 	// ProductVariantNeeded should return true if the module needs a product variant (installed on the product image).
-	ProductVariantNeeded(ctx BaseModuleContext) bool
+	ProductVariantNeeded(ctx ImageInterfaceContext) bool
 
 	// CoreVariantNeeded should return true if the module needs a core variant (installed on the system image).
-	CoreVariantNeeded(ctx BaseModuleContext) bool
+	CoreVariantNeeded(ctx ImageInterfaceContext) bool
 
 	// RamdiskVariantNeeded should return true if the module needs a ramdisk variant (installed on the
 	// ramdisk partition).
-	RamdiskVariantNeeded(ctx BaseModuleContext) bool
+	RamdiskVariantNeeded(ctx ImageInterfaceContext) bool
 
 	// VendorRamdiskVariantNeeded should return true if the module needs a vendor ramdisk variant (installed on the
 	// vendor ramdisk partition).
-	VendorRamdiskVariantNeeded(ctx BaseModuleContext) bool
+	VendorRamdiskVariantNeeded(ctx ImageInterfaceContext) bool
 
 	// DebugRamdiskVariantNeeded should return true if the module needs a debug ramdisk variant (installed on the
 	// debug ramdisk partition: $(PRODUCT_OUT)/debug_ramdisk).
-	DebugRamdiskVariantNeeded(ctx BaseModuleContext) bool
+	DebugRamdiskVariantNeeded(ctx ImageInterfaceContext) bool
 
 	// RecoveryVariantNeeded should return true if the module needs a recovery variant (installed on the
 	// recovery partition).
-	RecoveryVariantNeeded(ctx BaseModuleContext) bool
+	RecoveryVariantNeeded(ctx ImageInterfaceContext) bool
 
 	// ExtraImageVariations should return a list of the additional variations needed for the module.  After the
 	// variants are created the SetImageVariation method will be called on each newly created variant with the
 	// its variation.
-	ExtraImageVariations(ctx BaseModuleContext) []string
+	ExtraImageVariations(ctx ImageInterfaceContext) []string
 
 	// SetImageVariation is called for each newly created image variant. The receiver is the original
 	// module, "variation" is the name of the newly created variant. "variation" is set on the receiver.
-	SetImageVariation(ctx BaseModuleContext, variation string)
+	SetImageVariation(ctx ImageInterfaceContext, variation string)
 }
 
 const (
@@ -81,16 +98,53 @@
 	DebugRamdiskVariation string = "debug_ramdisk"
 )
 
+type imageInterfaceContextAdapter struct {
+	IncomingTransitionContext
+	kind moduleKind
+}
+
+var _ ImageInterfaceContext = (*imageInterfaceContextAdapter)(nil)
+
+func (e *imageInterfaceContextAdapter) Platform() bool {
+	return e.kind == platformModule
+}
+
+func (e *imageInterfaceContextAdapter) DeviceSpecific() bool {
+	return e.kind == deviceSpecificModule
+}
+
+func (e *imageInterfaceContextAdapter) SocSpecific() bool {
+	return e.kind == socSpecificModule
+}
+
+func (e *imageInterfaceContextAdapter) ProductSpecific() bool {
+	return e.kind == productSpecificModule
+}
+
+func (e *imageInterfaceContextAdapter) SystemExtSpecific() bool {
+	return e.kind == systemExtSpecificModule
+}
+
+// imageMutatorBeginMutator calls ImageMutatorBegin on all modules that may have image variants.
+// This happens right before the imageTransitionMutator runs. It's needed to initialize these
+// modules so that they return the correct results for all the other ImageInterface methods,
+// which the imageTransitionMutator will call. Transition mutators should also not mutate modules
+// (except in their Mutate() function), which this method does, so we run it in a separate mutator
+// first.
+func imageMutatorBeginMutator(ctx BottomUpMutatorContext) {
+	if m, ok := ctx.Module().(ImageInterface); ok && ctx.Os() == Android {
+		m.ImageMutatorBegin(ctx)
+	}
+}
+
 // imageTransitionMutator creates variants for modules that implement the ImageInterface that
 // allow them to build differently for each partition (recovery, core, vendor, etc.).
 type imageTransitionMutator struct{}
 
-func (imageTransitionMutator) Split(ctx BaseModuleContext) []string {
+func getImageVariations(ctx ImageInterfaceContext) []string {
 	var variations []string
 
 	if m, ok := ctx.Module().(ImageInterface); ctx.Os() == Android && ok {
-		m.ImageMutatorBegin(ctx)
-
 		if m.CoreVariantNeeded(ctx) {
 			variations = append(variations, CoreVariation)
 		}
@@ -124,6 +178,10 @@
 	return variations
 }
 
+func (imageTransitionMutator) Split(ctx BaseModuleContext) []string {
+	return getImageVariations(ctx)
+}
+
 func (imageTransitionMutator) OutgoingTransition(ctx OutgoingTransitionContext, sourceVariation string) string {
 	return sourceVariation
 }
@@ -132,6 +190,16 @@
 	if _, ok := ctx.Module().(ImageInterface); ctx.Os() != Android || !ok {
 		return CoreVariation
 	}
+	variations := getImageVariations(&imageInterfaceContextAdapter{
+		IncomingTransitionContext: ctx,
+		kind:                      determineModuleKind(ctx.Module().base(), ctx),
+	})
+	// If there's only 1 possible variation, use that. This is a holdover from when blueprint,
+	// when adding dependencies, would use the only variant of a module regardless of its variations
+	// if only 1 variant existed.
+	if len(variations) == 1 {
+		return variations[0]
+	}
 	return incomingVariation
 }
 
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/license.go b/android/license.go
index 5bffc25..7b4aeeb 100644
--- a/android/license.go
+++ b/android/license.go
@@ -18,6 +18,15 @@
 	"github.com/google/blueprint"
 )
 
+type LicenseInfo struct {
+	PackageName                *string
+	EffectiveLicenseText       NamedPaths
+	EffectiveLicenseKinds      []string
+	EffectiveLicenseConditions []string
+}
+
+var LicenseInfoProvider = blueprint.NewProvider[LicenseInfo]()
+
 type licenseKindDependencyTag struct {
 	blueprint.BaseDependencyTag
 }
@@ -69,16 +78,24 @@
 
 func (m *licenseModule) GenerateAndroidBuildActions(ctx ModuleContext) {
 	// license modules have no licenses, but license_kinds must refer to license_kind modules
-	mergeStringProps(&m.base().commonProperties.Effective_licenses, ctx.ModuleName())
 	namePathProps(&m.base().commonProperties.Effective_license_text, m.properties.Package_name, PathsForModuleSrc(ctx, m.properties.License_text)...)
-	for _, module := range ctx.GetDirectDepsWithTag(licenseKindTag) {
-		if lk, ok := module.(*licenseKindModule); ok {
-			mergeStringProps(&m.base().commonProperties.Effective_license_conditions, lk.properties.Conditions...)
-			mergeStringProps(&m.base().commonProperties.Effective_license_kinds, ctx.OtherModuleName(module))
+	var conditions []string
+	var kinds []string
+	for _, module := range ctx.GetDirectDepsProxyWithTag(licenseKindTag) {
+		if lk, ok := OtherModuleProvider(ctx, module, LicenseKindInfoProvider); ok {
+			conditions = append(conditions, lk.Conditions...)
+			kinds = append(kinds, ctx.OtherModuleName(module))
 		} else {
 			ctx.ModuleErrorf("license_kinds property %q is not a license_kind module", ctx.OtherModuleName(module))
 		}
 	}
+
+	SetProvider(ctx, LicenseInfoProvider, LicenseInfo{
+		PackageName:                m.properties.Package_name,
+		EffectiveLicenseText:       m.base().commonProperties.Effective_license_text,
+		EffectiveLicenseKinds:      SortedUniqueStrings(kinds),
+		EffectiveLicenseConditions: SortedUniqueStrings(conditions),
+	})
 }
 
 func LicenseFactory() Module {
diff --git a/android/license_kind.go b/android/license_kind.go
index 838dedd..1ca6954 100644
--- a/android/license_kind.go
+++ b/android/license_kind.go
@@ -14,6 +14,14 @@
 
 package android
 
+import "github.com/google/blueprint"
+
+type LicenseKindInfo struct {
+	Conditions []string
+}
+
+var LicenseKindInfoProvider = blueprint.NewProvider[LicenseKindInfo]()
+
 func init() {
 	RegisterLicenseKindBuildComponents(InitRegistrationContext)
 }
@@ -43,8 +51,10 @@
 	// Nothing to do.
 }
 
-func (m *licenseKindModule) GenerateAndroidBuildActions(ModuleContext) {
-	// Nothing to do.
+func (m *licenseKindModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	SetProvider(ctx, LicenseKindInfoProvider, LicenseKindInfo{
+		Conditions: m.properties.Conditions,
+	})
 }
 
 func LicenseKindFactory() Module {
diff --git a/android/license_metadata.go b/android/license_metadata.go
index f925638..d15dfa8 100644
--- a/android/license_metadata.go
+++ b/android/license_metadata.go
@@ -19,6 +19,7 @@
 	"strings"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/depset"
 	"github.com/google/blueprint/proptools"
 )
 
@@ -61,10 +62,10 @@
 	var allDepMetadataFiles Paths
 	var allDepMetadataArgs []string
 	var allDepOutputFiles Paths
-	var allDepMetadataDepSets []*DepSet[Path]
+	var allDepMetadataDepSets []depset.DepSet[Path]
 
-	ctx.VisitDirectDeps(func(dep Module) {
-		if !dep.Enabled(ctx) {
+	ctx.VisitDirectDepsProxy(func(dep ModuleProxy) {
+		if !OtherModuleProviderOrDefault(ctx, dep, CommonModuleInfoKey).Enabled {
 			return
 		}
 
@@ -80,7 +81,7 @@
 
 		if info, ok := OtherModuleProvider(ctx, dep, LicenseMetadataProvider); ok {
 			allDepMetadataFiles = append(allDepMetadataFiles, info.LicenseMetadataPath)
-			if isContainer || isInstallDepNeeded(dep, ctx.OtherModuleDependencyTag(dep)) {
+			if isContainer || isInstallDepNeeded(ctx, dep) {
 				allDepMetadataDepSets = append(allDepMetadataDepSets, info.LicenseMetadataDepSet)
 			}
 
@@ -133,7 +134,7 @@
 		JoinWithPrefix(proptools.NinjaAndShellEscapeListIncludingSpaces(base.commonProperties.Effective_license_text.Strings()), "-n "))
 
 	if isContainer {
-		transitiveDeps := Paths(NewDepSet[Path](TOPOLOGICAL, nil, allDepMetadataDepSets).ToList())
+		transitiveDeps := Paths(depset.New[Path](depset.TOPOLOGICAL, nil, allDepMetadataDepSets).ToList())
 		args = append(args,
 			JoinWithPrefix(proptools.NinjaAndShellEscapeListIncludingSpaces(transitiveDeps.Strings()), "-d "))
 		orderOnlyDeps = append(orderOnlyDeps, transitiveDeps...)
@@ -176,7 +177,7 @@
 
 	SetProvider(ctx, LicenseMetadataProvider, &LicenseMetadataInfo{
 		LicenseMetadataPath:   licenseMetadataFile,
-		LicenseMetadataDepSet: NewDepSet(TOPOLOGICAL, Paths{licenseMetadataFile}, allDepMetadataDepSets),
+		LicenseMetadataDepSet: depset.New(depset.TOPOLOGICAL, Paths{licenseMetadataFile}, allDepMetadataDepSets),
 	})
 }
 
@@ -204,7 +205,7 @@
 // LicenseMetadataInfo stores the license metadata path for a module.
 type LicenseMetadataInfo struct {
 	LicenseMetadataPath   Path
-	LicenseMetadataDepSet *DepSet[Path]
+	LicenseMetadataDepSet depset.DepSet[Path]
 }
 
 // licenseAnnotationsFromTag returns the LicenseAnnotations for a tag (if any) converted into
diff --git a/android/licenses.go b/android/licenses.go
index be1eede..32d12c8 100644
--- a/android/licenses.go
+++ b/android/licenses.go
@@ -15,7 +15,10 @@
 package android
 
 import (
+	"fmt"
+	"path/filepath"
 	"reflect"
+	"strings"
 	"sync"
 
 	"github.com/google/blueprint"
@@ -106,19 +109,19 @@
 //
 // This goes before defaults expansion so the defaults can pick up the package default.
 func RegisterLicensesPackageMapper(ctx RegisterMutatorsContext) {
-	ctx.BottomUp("licensesPackageMapper", licensesPackageMapper).Parallel()
+	ctx.BottomUp("licensesPackageMapper", licensesPackageMapper)
 }
 
 // Registers the function that gathers the license dependencies for each module.
 //
 // This goes after defaults expansion so that it can pick up default licenses and before visibility enforcement.
 func RegisterLicensesPropertyGatherer(ctx RegisterMutatorsContext) {
-	ctx.BottomUp("licensesPropertyGatherer", licensesPropertyGatherer).Parallel()
+	ctx.BottomUp("licensesPropertyGatherer", licensesPropertyGatherer)
 }
 
 // Registers the function that verifies the licenses and license_kinds dependency types for each module.
 func RegisterLicensesDependencyChecker(ctx RegisterMutatorsContext) {
-	ctx.BottomUp("licensesPropertyChecker", licensesDependencyChecker).Parallel()
+	ctx.BottomUp("licensesPropertyChecker", licensesDependencyChecker)
 }
 
 // Maps each package to its default applicable licenses.
@@ -155,7 +158,25 @@
 	}
 
 	licenses := getLicenses(ctx, m)
-	ctx.AddVariationDependencies(nil, licensesTag, licenses...)
+
+	var fullyQualifiedLicenseNames []string
+	for _, license := range licenses {
+		fullyQualifiedLicenseName := license
+		if !strings.HasPrefix(license, "//") {
+			licenseModuleDir := ctx.OtherModuleDir(m)
+			for licenseModuleDir != "." && !ctx.OtherModuleExists(fmt.Sprintf("//%s:%s", licenseModuleDir, license)) {
+				licenseModuleDir = filepath.Dir(licenseModuleDir)
+			}
+			if licenseModuleDir == "." {
+				fullyQualifiedLicenseName = license
+			} else {
+				fullyQualifiedLicenseName = fmt.Sprintf("//%s:%s", licenseModuleDir, license)
+			}
+		}
+		fullyQualifiedLicenseNames = append(fullyQualifiedLicenseNames, fullyQualifiedLicenseName)
+	}
+
+	ctx.AddVariationDependencies(nil, licensesTag, fullyQualifiedLicenseNames...)
 }
 
 // Verifies the license and license_kind dependencies are each the correct kind of module.
@@ -206,16 +227,18 @@
 	}
 
 	var licenses []string
-	for _, module := range ctx.GetDirectDepsWithTag(licensesTag) {
-		if l, ok := module.(*licenseModule); ok {
+	var texts NamedPaths
+	var conditions []string
+	var kinds []string
+	for _, module := range ctx.GetDirectDepsProxyWithTag(licensesTag) {
+		if l, ok := OtherModuleProvider(ctx, module, LicenseInfoProvider); ok {
 			licenses = append(licenses, ctx.OtherModuleName(module))
-			if m.base().commonProperties.Effective_package_name == nil && l.properties.Package_name != nil {
-				m.base().commonProperties.Effective_package_name = l.properties.Package_name
+			if m.base().commonProperties.Effective_package_name == nil && l.PackageName != nil {
+				m.base().commonProperties.Effective_package_name = l.PackageName
 			}
-			mergeStringProps(&m.base().commonProperties.Effective_licenses, module.base().commonProperties.Effective_licenses...)
-			mergeNamedPathProps(&m.base().commonProperties.Effective_license_text, module.base().commonProperties.Effective_license_text...)
-			mergeStringProps(&m.base().commonProperties.Effective_license_kinds, module.base().commonProperties.Effective_license_kinds...)
-			mergeStringProps(&m.base().commonProperties.Effective_license_conditions, module.base().commonProperties.Effective_license_conditions...)
+			texts = append(texts, l.EffectiveLicenseText...)
+			kinds = append(kinds, l.EffectiveLicenseKinds...)
+			conditions = append(conditions, l.EffectiveLicenseConditions...)
 		} else {
 			propertyName := "licenses"
 			primaryProperty := m.base().primaryLicensesProperty
@@ -226,17 +249,15 @@
 		}
 	}
 
+	m.base().commonProperties.Effective_license_text = SortedUniqueNamedPaths(texts)
+	m.base().commonProperties.Effective_license_kinds = SortedUniqueStrings(kinds)
+	m.base().commonProperties.Effective_license_conditions = SortedUniqueStrings(conditions)
+
 	// Make the license information available for other modules.
-	licenseInfo := LicenseInfo{
+	licenseInfo := LicensesInfo{
 		Licenses: licenses,
 	}
-	SetProvider(ctx, LicenseInfoProvider, licenseInfo)
-}
-
-// Update a property string array with a distinct union of its values and a list of new values.
-func mergeStringProps(prop *[]string, values ...string) {
-	*prop = append(*prop, values...)
-	*prop = SortedUniqueStrings(*prop)
+	SetProvider(ctx, LicensesInfoProvider, licenseInfo)
 }
 
 // Update a property NamedPath array with a distinct union of its values and a list of new values.
@@ -253,12 +274,6 @@
 	*prop = SortedUniqueNamedPaths(*prop)
 }
 
-// Update a property NamedPath array with a distinct union of its values and a list of new values.
-func mergeNamedPathProps(prop *NamedPaths, values ...NamedPath) {
-	*prop = append(*prop, values...)
-	*prop = SortedUniqueNamedPaths(*prop)
-}
-
 // Get the licenses property falling back to the package default.
 func getLicenses(ctx BaseModuleContext, module Module) []string {
 	if exemptFromRequiredApplicableLicensesProperty(module) {
@@ -315,14 +330,14 @@
 	return true
 }
 
-// LicenseInfo contains information about licenses for a specific module.
-type LicenseInfo struct {
+// LicensesInfo contains information about licenses for a specific module.
+type LicensesInfo struct {
 	// The list of license modules this depends upon, either explicitly or through default package
 	// configuration.
 	Licenses []string
 }
 
-var LicenseInfoProvider = blueprint.NewProvider[LicenseInfo]()
+var LicensesInfoProvider = blueprint.NewProvider[LicensesInfo]()
 
 func init() {
 	RegisterMakeVarsProvider(pctx, licensesMakeVarsProvider)
diff --git a/android/licenses_test.go b/android/licenses_test.go
index 8a81e12..0c371e8 100644
--- a/android/licenses_test.go
+++ b/android/licenses_test.go
@@ -7,15 +7,13 @@
 )
 
 var licensesTests = []struct {
-	name                       string
-	fs                         MockFS
-	expectedErrors             []string
-	effectiveLicenses          map[string][]string
-	effectiveInheritedLicenses map[string][]string
-	effectivePackage           map[string]string
-	effectiveNotices           map[string][]string
-	effectiveKinds             map[string][]string
-	effectiveConditions        map[string][]string
+	name                string
+	fs                  MockFS
+	expectedErrors      []string
+	effectivePackage    map[string]string
+	effectiveNotices    map[string][]string
+	effectiveKinds      map[string][]string
+	effectiveConditions map[string][]string
 }{
 	{
 		name: "invalid module type without licenses property",
@@ -69,11 +67,6 @@
 					licenses: ["top_Apache2"],
 				}`),
 		},
-		effectiveLicenses: map[string][]string{
-			"libexample1": []string{"top_Apache2"},
-			"libnested":   []string{"top_Apache2"},
-			"libother":    []string{"top_Apache2"},
-		},
 		effectiveKinds: map[string][]string{
 			"libexample1": []string{"notice"},
 			"libnested":   []string{"notice"},
@@ -146,18 +139,6 @@
 					deps: ["libexample"],
 				}`),
 		},
-		effectiveLicenses: map[string][]string{
-			"libexample":     []string{"nested_other", "top_other"},
-			"libsamepackage": []string{},
-			"libnested":      []string{},
-			"libother":       []string{},
-		},
-		effectiveInheritedLicenses: map[string][]string{
-			"libexample":     []string{"nested_other", "top_other"},
-			"libsamepackage": []string{"nested_other", "top_other"},
-			"libnested":      []string{"nested_other", "top_other"},
-			"libother":       []string{"nested_other", "top_other"},
-		},
 		effectiveKinds: map[string][]string{
 			"libexample":     []string{"nested_notice", "top_notice"},
 			"libsamepackage": []string{},
@@ -217,20 +198,6 @@
 					deps: ["libexample"],
 				}`),
 		},
-		effectiveLicenses: map[string][]string{
-			"libexample":     []string{"other", "top_nested"},
-			"libsamepackage": []string{},
-			"libnested":      []string{},
-			"libother":       []string{},
-			"liboutsider":    []string{},
-		},
-		effectiveInheritedLicenses: map[string][]string{
-			"libexample":     []string{"other", "top_nested"},
-			"libsamepackage": []string{"other", "top_nested"},
-			"libnested":      []string{"other", "top_nested"},
-			"libother":       []string{"other", "top_nested"},
-			"liboutsider":    []string{"other", "top_nested"},
-		},
 		effectiveKinds: map[string][]string{
 			"libexample":     []string{},
 			"libsamepackage": []string{},
@@ -284,14 +251,6 @@
 					defaults: ["top_defaults"],
 				}`),
 		},
-		effectiveLicenses: map[string][]string{
-			"libexample":  []string{"by_exception_only"},
-			"libdefaults": []string{"notice"},
-		},
-		effectiveInheritedLicenses: map[string][]string{
-			"libexample":  []string{"by_exception_only"},
-			"libdefaults": []string{"notice"},
-		},
 	},
 
 	// Package default_applicable_licenses tests
@@ -326,14 +285,6 @@
 					deps: ["libexample"],
 				}`),
 		},
-		effectiveLicenses: map[string][]string{
-			"libexample":  []string{"top_notice"},
-			"liboutsider": []string{},
-		},
-		effectiveInheritedLicenses: map[string][]string{
-			"libexample":  []string{"top_notice"},
-			"liboutsider": []string{"top_notice"},
-		},
 	},
 	{
 		name: "package default_applicable_licenses not inherited to subpackages",
@@ -369,18 +320,6 @@
 					deps: ["libexample", "libother", "libnested"],
 				}`),
 		},
-		effectiveLicenses: map[string][]string{
-			"libexample":  []string{"top_notice"},
-			"libnested":   []string{"outsider"},
-			"libother":    []string{},
-			"liboutsider": []string{},
-		},
-		effectiveInheritedLicenses: map[string][]string{
-			"libexample":  []string{"top_notice"},
-			"libnested":   []string{"outsider"},
-			"libother":    []string{},
-			"liboutsider": []string{"top_notice", "outsider"},
-		},
 	},
 	{
 		name: "verify that prebuilt dependencies are included",
@@ -409,12 +348,6 @@
 					deps: [":module"],
 				}`),
 		},
-		effectiveLicenses: map[string][]string{
-			"other": []string{},
-		},
-		effectiveInheritedLicenses: map[string][]string{
-			"other": []string{"prebuilt", "top_sources"},
-		},
 	},
 	{
 		name: "verify that prebuilt dependencies are ignored for licenses reasons (preferred)",
@@ -444,13 +377,6 @@
 					deps: [":module"],
 				}`),
 		},
-		effectiveLicenses: map[string][]string{
-			"other": []string{},
-		},
-		effectiveInheritedLicenses: map[string][]string{
-			"module": []string{"prebuilt", "top_sources"},
-			"other":  []string{"prebuilt", "top_sources"},
-		},
 	},
 }
 
@@ -470,10 +396,6 @@
 				ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern(test.expectedErrors)).
 				RunTest(t)
 
-			if test.effectiveLicenses != nil {
-				checkEffectiveLicenses(t, result, test.effectiveLicenses)
-			}
-
 			if test.effectivePackage != nil {
 				checkEffectivePackage(t, result, test.effectivePackage)
 			}
@@ -489,114 +411,10 @@
 			if test.effectiveConditions != nil {
 				checkEffectiveConditions(t, result, test.effectiveConditions)
 			}
-
-			if test.effectiveInheritedLicenses != nil {
-				checkEffectiveInheritedLicenses(t, result, test.effectiveInheritedLicenses)
-			}
 		})
 	}
 }
 
-func checkEffectiveLicenses(t *testing.T, result *TestResult, effectiveLicenses map[string][]string) {
-	actualLicenses := make(map[string][]string)
-	result.Context.Context.VisitAllModules(func(m blueprint.Module) {
-		if _, ok := m.(*licenseModule); ok {
-			return
-		}
-		if _, ok := m.(*licenseKindModule); ok {
-			return
-		}
-		if _, ok := m.(*packageModule); ok {
-			return
-		}
-		module, ok := m.(Module)
-		if !ok {
-			t.Errorf("%q not a module", m.Name())
-			return
-		}
-		base := module.base()
-		if base == nil {
-			return
-		}
-		actualLicenses[m.Name()] = base.commonProperties.Effective_licenses
-	})
-
-	for moduleName, expectedLicenses := range effectiveLicenses {
-		licenses, ok := actualLicenses[moduleName]
-		if !ok {
-			licenses = []string{}
-		}
-		if !compareUnorderedStringArrays(expectedLicenses, licenses) {
-			t.Errorf("effective licenses mismatch for module %q: expected %q, found %q", moduleName, expectedLicenses, licenses)
-		}
-	}
-}
-
-func checkEffectiveInheritedLicenses(t *testing.T, result *TestResult, effectiveInheritedLicenses map[string][]string) {
-	actualLicenses := make(map[string][]string)
-	result.Context.Context.VisitAllModules(func(m blueprint.Module) {
-		if _, ok := m.(*licenseModule); ok {
-			return
-		}
-		if _, ok := m.(*licenseKindModule); ok {
-			return
-		}
-		if _, ok := m.(*packageModule); ok {
-			return
-		}
-		module, ok := m.(Module)
-		if !ok {
-			t.Errorf("%q not a module", m.Name())
-			return
-		}
-		base := module.base()
-		if base == nil {
-			return
-		}
-		inherited := make(map[string]bool)
-		for _, l := range base.commonProperties.Effective_licenses {
-			inherited[l] = true
-		}
-		result.Context.Context.VisitDepsDepthFirst(m, func(c blueprint.Module) {
-			if _, ok := c.(*licenseModule); ok {
-				return
-			}
-			if _, ok := c.(*licenseKindModule); ok {
-				return
-			}
-			if _, ok := c.(*packageModule); ok {
-				return
-			}
-			cmodule, ok := c.(Module)
-			if !ok {
-				t.Errorf("%q not a module", c.Name())
-				return
-			}
-			cbase := cmodule.base()
-			if cbase == nil {
-				return
-			}
-			for _, l := range cbase.commonProperties.Effective_licenses {
-				inherited[l] = true
-			}
-		})
-		actualLicenses[m.Name()] = []string{}
-		for l := range inherited {
-			actualLicenses[m.Name()] = append(actualLicenses[m.Name()], l)
-		}
-	})
-
-	for moduleName, expectedInheritedLicenses := range effectiveInheritedLicenses {
-		licenses, ok := actualLicenses[moduleName]
-		if !ok {
-			licenses = []string{}
-		}
-		if !compareUnorderedStringArrays(expectedInheritedLicenses, licenses) {
-			t.Errorf("effective inherited licenses mismatch for module %q: expected %q, found %q", moduleName, expectedInheritedLicenses, licenses)
-		}
-	}
-}
-
 func checkEffectivePackage(t *testing.T, result *TestResult, effectivePackage map[string]string) {
 	actualPackage := make(map[string]string)
 	result.Context.Context.VisitAllModules(func(m blueprint.Module) {
diff --git a/android/logtags.go b/android/logtags.go
index 7929057..abc37f9 100644
--- a/android/logtags.go
+++ b/android/logtags.go
@@ -14,7 +14,11 @@
 
 package android
 
-import "github.com/google/blueprint"
+import (
+	"strings"
+
+	"github.com/google/blueprint"
+)
 
 func init() {
 	RegisterParallelSingletonType("logtags", LogtagsSingleton)
@@ -46,11 +50,20 @@
 			allLogtags = append(allLogtags, logtagsInfo.Logtags...)
 		}
 	})
+	allLogtags = SortedUniquePaths(allLogtags)
+	filteredLogTags := make([]Path, 0, len(allLogtags))
+	for _, p := range allLogtags {
+		// Logic copied from make:
+		// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=987;drc=0585bb1bcf4c89065adaf709f48acc8b869fd3ce
+		if !strings.HasPrefix(p.String(), "vendor/") && !strings.HasPrefix(p.String(), "device/") && !strings.HasPrefix(p.String(), "out/") {
+			filteredLogTags = append(filteredLogTags, p)
+		}
+	}
 
 	builder := NewRuleBuilder(pctx, ctx)
 	builder.Command().
 		BuiltTool("merge-event-log-tags").
 		FlagWithOutput("-o ", MergedLogtagsPath(ctx)).
-		Inputs(SortedUniquePaths(allLogtags))
+		Inputs(filteredLogTags)
 	builder.Build("all-event-log-tags.txt", "merge logtags")
 }
diff --git a/android/makevars.go b/android/makevars.go
index 8305d8e..45fd0d0 100644
--- a/android/makevars.go
+++ b/android/makevars.go
@@ -185,6 +185,7 @@
 type makeVarsSingleton struct {
 	varsForTesting     []makeVarsVariable
 	installsForTesting []byte
+	lateForTesting     []byte
 }
 
 type makeVarsProvider struct {
@@ -220,7 +221,7 @@
 
 type dist struct {
 	goals []string
-	paths []string
+	paths distCopies
 }
 
 func (s *makeVarsSingleton) GenerateBuildActions(ctx SingletonContext) {
@@ -265,6 +266,11 @@
 		dists = append(dists, mctx.dists...)
 	}
 
+	singletonDists := getSingletonDists(ctx.Config())
+	singletonDists.lock.Lock()
+	dists = append(dists, singletonDists.dists...)
+	singletonDists.lock.Unlock()
+
 	ctx.VisitAllModules(func(m Module) {
 		if provider, ok := m.(ModuleMakeVarsProvider); ok && m.Enabled(ctx) {
 			mctx := &makeVarsContext{
@@ -285,6 +291,10 @@
 			katiVintfManifestInstalls = append(katiVintfManifestInstalls, info.KatiVintfInstalls...)
 			katiSymlinks = append(katiSymlinks, info.KatiSymlinks...)
 		}
+
+		if distInfo, ok := OtherModuleProvider(ctx, m, DistProvider); ok {
+			dists = append(dists, distInfo.Dists...)
+		}
 	})
 
 	compareKatiInstalls := func(a, b katiInstall) int {
@@ -330,7 +340,7 @@
 		return len(a) < len(b)
 	}
 	sort.Slice(dists, func(i, j int) bool {
-		return lessArr(dists[i].goals, dists[j].goals) || lessArr(dists[i].paths, dists[j].paths)
+		return lessArr(dists[i].goals, dists[j].goals) || lessArr(dists[i].paths.Strings(), dists[j].paths.Strings())
 	})
 
 	outBytes := s.writeVars(vars)
@@ -354,6 +364,7 @@
 	if ctx.Config().RunningInsideUnitTest() {
 		s.varsForTesting = vars
 		s.installsForTesting = installsBytes
+		s.lateForTesting = lateOutBytes
 	}
 }
 
@@ -458,7 +469,7 @@
 	for _, dist := range dists {
 		fmt.Fprintf(buf, ".PHONY: %s\n", strings.Join(dist.goals, " "))
 		fmt.Fprintf(buf, "$(call dist-for-goals,%s,%s)\n",
-			strings.Join(dist.goals, " "), strings.Join(dist.paths, " "))
+			strings.Join(dist.goals, " "), strings.Join(dist.paths.Strings(), " "))
 	}
 
 	return buf.Bytes()
@@ -607,7 +618,7 @@
 	c.phonies = append(c.phonies, phony{name, deps})
 }
 
-func (c *makeVarsContext) addDist(goals []string, paths []string) {
+func (c *makeVarsContext) addDist(goals []string, paths []distCopy) {
 	c.dists = append(c.dists, dist{
 		goals: goals,
 		paths: paths,
@@ -647,9 +658,16 @@
 }
 
 func (c *makeVarsContext) DistForGoals(goals []string, paths ...Path) {
-	c.addDist(goals, Paths(paths).Strings())
+	var copies distCopies
+	for _, path := range paths {
+		copies = append(copies, distCopy{
+			from: path,
+			dest: path.Base(),
+		})
+	}
+	c.addDist(goals, copies)
 }
 
 func (c *makeVarsContext) DistForGoalsWithFilename(goals []string, path Path, filename string) {
-	c.addDist(goals, []string{path.String() + ":" + filename})
+	c.addDist(goals, distCopies{{from: path, dest: filename}})
 }
diff --git a/android/makevars_test.go b/android/makevars_test.go
new file mode 100644
index 0000000..387d457
--- /dev/null
+++ b/android/makevars_test.go
@@ -0,0 +1,96 @@
+package android
+
+import (
+	"regexp"
+	"testing"
+)
+
+func TestDistFilesInGenerateAndroidBuildActions(t *testing.T) {
+	result := GroupFixturePreparers(
+		FixtureRegisterWithContext(func(ctx RegistrationContext) {
+			ctx.RegisterModuleType("my_module_type", newDistFileModule)
+			ctx.RegisterParallelSingletonType("my_singleton", newDistFileSingleton)
+			ctx.RegisterParallelSingletonModuleType("my_singleton_module", newDistFileSingletonModule)
+		}),
+		FixtureModifyConfig(SetKatiEnabledForTests),
+		PrepareForTestWithMakevars,
+	).RunTestWithBp(t, `
+	my_module_type {
+		name: "foo",
+	}
+	my_singleton_module {
+		name: "bar"
+	}
+	`)
+
+	lateContents := string(result.SingletonForTests(t, "makevars").Singleton().(*makeVarsSingleton).lateForTesting)
+	matched, err := regexp.MatchString(`call dist-for-goals,my_goal,.*/my_file.txt:my_file.txt\)`, lateContents)
+	if err != nil || !matched {
+		t.Fatalf("Expected a dist of my_file.txt, but got: %s", lateContents)
+	}
+	matched, err = regexp.MatchString(`call dist-for-goals,my_singleton_goal,.*/my_singleton_file.txt:my_singleton_file.txt\)`, lateContents)
+	if err != nil || !matched {
+		t.Fatalf("Expected a dist of my_singleton_file.txt, but got: %s", lateContents)
+	}
+	matched, err = regexp.MatchString(`call dist-for-goals,my_singleton_module_module_goal,.*/my_singleton_module_module_file.txt:my_singleton_module_module_file.txt\)`, lateContents)
+	if err != nil || !matched {
+		t.Fatalf("Expected a dist of my_singleton_module_module_file.txt, but got: %s", lateContents)
+	}
+	matched, err = regexp.MatchString(`call dist-for-goals,my_singleton_module_singleton_goal,.*/my_singleton_module_singleton_file.txt:my_singleton_module_singleton_file.txt\)`, lateContents)
+	if err != nil || !matched {
+		t.Fatalf("Expected a dist of my_singleton_module_singleton_file.txt, but got: %s", lateContents)
+	}
+}
+
+type distFileModule struct {
+	ModuleBase
+}
+
+func newDistFileModule() Module {
+	m := &distFileModule{}
+	InitAndroidModule(m)
+	return m
+}
+
+func (m *distFileModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	out := PathForModuleOut(ctx, "my_file.txt")
+	WriteFileRule(ctx, out, "Hello, world!")
+	ctx.DistForGoal("my_goal", out)
+}
+
+type distFileSingleton struct {
+}
+
+func newDistFileSingleton() Singleton {
+	return &distFileSingleton{}
+}
+
+func (d *distFileSingleton) GenerateBuildActions(ctx SingletonContext) {
+	out := PathForOutput(ctx, "my_singleton_file.txt")
+	WriteFileRule(ctx, out, "Hello, world!")
+	ctx.DistForGoal("my_singleton_goal", out)
+}
+
+type distFileSingletonModule struct {
+	SingletonModuleBase
+}
+
+func newDistFileSingletonModule() SingletonModule {
+	sm := &distFileSingletonModule{}
+	InitAndroidSingletonModule(sm)
+	return sm
+}
+
+// GenerateAndroidBuildActions implements SingletonModule.
+func (d *distFileSingletonModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	out := PathForModuleOut(ctx, "my_singleton_module_module_file.txt")
+	WriteFileRule(ctx, out, "Hello, world!")
+	ctx.DistForGoal("my_singleton_module_module_goal", out)
+}
+
+// GenerateSingletonBuildActions implements SingletonModule.
+func (d *distFileSingletonModule) GenerateSingletonBuildActions(ctx SingletonContext) {
+	out := PathForOutput(ctx, "my_singleton_module_singleton_file.txt")
+	WriteFileRule(ctx, out, "Hello, world!")
+	ctx.DistForGoal("my_singleton_module_singleton_goal", out)
+}
diff --git a/android/module.go b/android/module.go
index 44f7583..55dd119 100644
--- a/android/module.go
+++ b/android/module.go
@@ -24,6 +24,8 @@
 	"strings"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/depset"
+	"github.com/google/blueprint/gobtools"
 	"github.com/google/blueprint/proptools"
 )
 
@@ -79,16 +81,19 @@
 	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()
 	IsReplacedByPrebuilt() bool
 	ExportedToMake() bool
-	EffectiveLicenseKinds() []string
 	EffectiveLicenseFiles() Paths
 
 	AddProperties(props ...interface{})
@@ -111,8 +116,17 @@
 	HostRequiredModuleNames() []string
 	TargetRequiredModuleNames() []string
 	VintfFragmentModuleNames(ctx ConfigurableEvaluatorContext) []string
+	VintfFragments(ctx ConfigurableEvaluatorContext) []string
 
 	ConfigurableEvaluator(ctx ConfigurableEvaluatorContext) proptools.ConfigurableEvaluator
+
+	// The usage of this method is experimental and should not be used outside of fsgen package.
+	// This will be removed once product packaging migration to Soong is complete.
+	DecodeMultilib(ctx ConfigContext) (string, string)
+
+	// WARNING: This should not be used outside build/soong/fsgen
+	// Overrides returns the list of modules which should not be installed if this module is installed.
+	Overrides() []string
 }
 
 // Qualified id for a module
@@ -242,6 +256,8 @@
 	Name *string
 }
 
+// Properties common to all modules inheriting from ModuleBase. These properties are automatically
+// inherited by sub-modules created with ctx.CreateModule()
 type commonProperties struct {
 	// emit build rules for this module
 	//
@@ -300,8 +316,6 @@
 	// Describes the licenses applicable to this module. Must reference license modules.
 	Licenses []string
 
-	// Flattened from direct license dependencies. Equal to Licenses unless particular module adds more.
-	Effective_licenses []string `blueprint:"mutated"`
 	// Override of module name when reporting licenses
 	Effective_package_name *string `blueprint:"mutated"`
 	// Notice files
@@ -323,6 +337,7 @@
 		}
 		Android struct {
 			Compile_multilib *string
+			Enabled          *bool
 		}
 	}
 
@@ -375,6 +390,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"`
 
@@ -384,15 +408,6 @@
 	// VINTF manifest fragments to be installed if this module is installed
 	Vintf_fragments proptools.Configurable[[]string] `android:"path"`
 
-	// names of other modules to install if this module is installed
-	Required proptools.Configurable[[]string] `android:"arch_variant"`
-
-	// names of other modules to install on host if this module is installed
-	Host_required []string `android:"arch_variant"`
-
-	// names of other modules to install on target if this module is installed
-	Target_required []string `android:"arch_variant"`
-
 	// The OsType of artifacts that this module variant is responsible for creating.
 	//
 	// Set by osMutator
@@ -493,6 +508,19 @@
 	Overrides []string
 }
 
+// Properties common to all modules inheriting from ModuleBase. Unlike commonProperties, these
+// properties are NOT automatically inherited by sub-modules created with ctx.CreateModule()
+type baseProperties struct {
+	// names of other modules to install if this module is installed
+	Required proptools.Configurable[[]string] `android:"arch_variant"`
+
+	// names of other modules to install on host if this module is installed
+	Host_required []string `android:"arch_variant"`
+
+	// names of other modules to install on target if this module is installed
+	Target_required []string `android:"arch_variant"`
+}
+
 type distProperties struct {
 	// configuration to distribute output files from this module to the distribution
 	// directory (default: $OUT/dist, configurable with $DIST_DIR)
@@ -539,6 +567,13 @@
 	}
 }
 
+func (t *CommonTestOptions) SetAndroidMkInfoEntries(entries *AndroidMkInfo) {
+	entries.SetBoolIfTrue("LOCAL_IS_UNIT_TEST", Bool(t.Unit_test))
+	if len(t.Tags) > 0 {
+		entries.AddStrings("LOCAL_TEST_OPTIONS_TAGS", t.Tags...)
+	}
+}
+
 // The key to use in TaggedDistFiles when a Dist structure does not specify a
 // tag property. This intentionally does not use "" as the default because that
 // would mean that an empty tag would have a different meaning when used in a dist
@@ -605,10 +640,9 @@
 type Multilib string
 
 const (
-	MultilibBoth        Multilib = "both"
-	MultilibFirst       Multilib = "first"
-	MultilibCommon      Multilib = "common"
-	MultilibCommonFirst Multilib = "common_first"
+	MultilibBoth   Multilib = "both"
+	MultilibFirst  Multilib = "first"
+	MultilibCommon Multilib = "common"
 )
 
 type HostOrDeviceSupported int
@@ -685,6 +719,7 @@
 	m.AddProperties(
 		&base.nameProperties,
 		&base.commonProperties,
+		&base.baseProperties,
 		&base.distProperties)
 
 	initProductVariableModule(m)
@@ -803,6 +838,7 @@
 
 	nameProperties          nameProperties
 	commonProperties        commonProperties
+	baseProperties          baseProperties
 	distProperties          distProperties
 	variableProperties      interface{}
 	hostAndDeviceProperties hostAndDeviceProperties
@@ -959,8 +995,9 @@
 	// 2. `boot_signer` is `required` by modules like `build_image` which is explicitly list as
 	// the top-level build goal (in the shell file that invokes Soong).
 	// 3. `boot_signer` depends on `bouncycastle-unbundled` which is in the missing git project.
-	// 4. aosp_kernel-build-tools invokes soong with `--skip-make`. Therefore, the absence of
-	// ALLOW_MISSING_DEPENDENCIES didn't cause a problem.
+	// 4. aosp_kernel-build-tools invokes soong with `--soong-only`. Therefore, the absence of
+	// ALLOW_MISSING_DEPENDENCIES didn't cause a problem, as previously only make processed required
+	// dependencies.
 	// 5. Now, since Soong understands `required` deps, it tries to build `boot_signer` and the
 	// absence of external/bouncycastle fails the build.
 	//
@@ -1018,7 +1055,7 @@
 	hostTargets = append(hostTargets, ctx.Config().BuildOSCommonTarget)
 
 	if ctx.Device() {
-		for _, depName := range ctx.Module().RequiredModuleNames(ctx) {
+		for _, depName := range append(ctx.Module().RequiredModuleNames(ctx), ctx.Module().VintfFragmentModuleNames(ctx)...) {
 			for _, target := range deviceTargets {
 				addDep(target, depName)
 			}
@@ -1031,7 +1068,7 @@
 	}
 
 	if ctx.Host() {
-		for _, depName := range ctx.Module().RequiredModuleNames(ctx) {
+		for _, depName := range append(ctx.Module().RequiredModuleNames(ctx), ctx.Module().VintfFragmentModuleNames(ctx)...) {
 			for _, target := range hostTargets {
 				// When a host module requires another host module, don't make a
 				// dependency if they have different OSes (i.e. hostcross).
@@ -1054,6 +1091,10 @@
 	InstallAlwaysNeededDependencyTag
 }{}
 
+func IsVintfDepTag(depTag blueprint.DependencyTag) bool {
+	return depTag == vintfDepTag
+}
+
 func addVintfFragmentDeps(ctx BottomUpMutatorContext) {
 	// Vintf manifests in the recovery partition will be ignored.
 	if !ctx.Device() || ctx.Module().InstallInRecovery() {
@@ -1067,7 +1108,12 @@
 
 	modPartition := mod.PartitionTag(deviceConfig)
 	for _, vintf := range vintfModules {
-		if vintfModule, ok := vintf.(*vintfFragmentModule); ok {
+		if vintf == nil {
+			// TODO(b/372091092): Remove this. Having it gives us missing dependency errors instead
+			// of nil pointer dereference errors, but we should resolve the missing dependencies.
+			continue
+		}
+		if vintfModule, ok := vintf.(*VintfFragmentModule); ok {
 			vintfPartition := vintfModule.PartitionTag(deviceConfig)
 			if modPartition != vintfPartition {
 				ctx.ModuleErrorf("Module %q(%q) and Vintf_fragment %q(%q) are installed to different partitions.",
@@ -1348,6 +1394,12 @@
 		if config.SystemExtPath() == "system_ext" {
 			partition = "system_ext"
 		}
+	} else if m.InstallInRamdisk() {
+		partition = "ramdisk"
+	} else if m.InstallInVendorRamdisk() {
+		partition = "vendor_ramdisk"
+	} else if m.InstallInRecovery() {
+		partition = "recovery"
 	}
 	return partition
 }
@@ -1400,6 +1452,7 @@
 func (m *ModuleBase) MakeUninstallable() {
 	m.commonProperties.UninstallableApexPlatformVariant = true
 	m.HideFromMake()
+	m.SkipInstall()
 }
 
 func (m *ModuleBase) ReplacedByPrebuilt() {
@@ -1415,10 +1468,6 @@
 	return m.commonProperties.NamespaceExportedToMake
 }
 
-func (m *ModuleBase) EffectiveLicenseKinds() []string {
-	return m.commonProperties.Effective_license_kinds
-}
-
 func (m *ModuleBase) EffectiveLicenseFiles() Paths {
 	result := make(Paths, 0, len(m.commonProperties.Effective_license_text))
 	for _, p := range m.commonProperties.Effective_license_text {
@@ -1429,15 +1478,16 @@
 
 // computeInstallDeps finds the installed paths of all dependencies that have a dependency
 // tag that is annotated as needing installation via the isInstallDepNeeded method.
-func (m *ModuleBase) computeInstallDeps(ctx ModuleContext) ([]*DepSet[InstallPath], []*DepSet[PackagingSpec]) {
-	var installDeps []*DepSet[InstallPath]
-	var packagingSpecs []*DepSet[PackagingSpec]
-	ctx.VisitDirectDeps(func(dep Module) {
-		if isInstallDepNeeded(dep, ctx.OtherModuleDependencyTag(dep)) {
+func (m *ModuleBase) computeInstallDeps(ctx ModuleContext) ([]depset.DepSet[InstallPath], []depset.DepSet[PackagingSpec]) {
+	var installDeps []depset.DepSet[InstallPath]
+	var packagingSpecs []depset.DepSet[PackagingSpec]
+	ctx.VisitDirectDepsProxy(func(dep ModuleProxy) {
+		if isInstallDepNeeded(ctx, dep) {
 			// Installation is still handled by Make, so anything hidden from Make is not
 			// installable.
 			info := OtherModuleProviderOrDefault(ctx, dep, InstallFilesProvider)
-			if !dep.IsHideFromMake() && !dep.IsSkipInstall() {
+			commonInfo := OtherModuleProviderOrDefault(ctx, dep, CommonModuleInfoKey)
+			if !commonInfo.HideFromMake && !commonInfo.SkipInstall {
 				installDeps = append(installDeps, info.TransitiveInstallFiles)
 			}
 			// Add packaging deps even when the dependency is not installed so that uninstallable
@@ -1451,13 +1501,13 @@
 
 // isInstallDepNeeded returns true if installing the output files of the current module
 // should also install the output files of the given dependency and dependency tag.
-func isInstallDepNeeded(dep Module, tag blueprint.DependencyTag) bool {
+func isInstallDepNeeded(ctx ModuleContext, dep ModuleProxy) bool {
 	// Don't add a dependency from the platform to a library provided by an apex.
-	if dep.base().commonProperties.UninstallableApexPlatformVariant {
+	if OtherModuleProviderOrDefault(ctx, dep, CommonModuleInfoKey).UninstallableApexPlatformVariant {
 		return false
 	}
 	// Only install modules if the dependency tag is an InstallDepNeeded tag.
-	return IsInstallDepNeededTag(tag)
+	return IsInstallDepNeededTag(ctx.OtherModuleDependencyTag(dep))
 }
 
 func (m *ModuleBase) NoAddressSanitizer() bool {
@@ -1512,6 +1562,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
 }
@@ -1562,21 +1624,25 @@
 }
 
 func (m *ModuleBase) RequiredModuleNames(ctx ConfigurableEvaluatorContext) []string {
-	return m.base().commonProperties.Required.GetOrDefault(m.ConfigurableEvaluator(ctx), nil)
+	return m.base().baseProperties.Required.GetOrDefault(m.ConfigurableEvaluator(ctx), nil)
 }
 
 func (m *ModuleBase) HostRequiredModuleNames() []string {
-	return m.base().commonProperties.Host_required
+	return m.base().baseProperties.Host_required
 }
 
 func (m *ModuleBase) TargetRequiredModuleNames() []string {
-	return m.base().commonProperties.Target_required
+	return m.base().baseProperties.Target_required
 }
 
 func (m *ModuleBase) VintfFragmentModuleNames(ctx ConfigurableEvaluatorContext) []string {
 	return m.base().commonProperties.Vintf_fragment_modules.GetOrDefault(m.ConfigurableEvaluator(ctx), nil)
 }
 
+func (m *ModuleBase) VintfFragments(ctx ConfigurableEvaluatorContext) []string {
+	return m.base().commonProperties.Vintf_fragments.GetOrDefault(m.ConfigurableEvaluator(ctx), nil)
+}
+
 func (m *ModuleBase) generateVariantTarget(ctx *moduleContext) {
 	namespacePrefix := ctx.Namespace().id
 	if namespacePrefix != "" {
@@ -1594,37 +1660,42 @@
 func (m *ModuleBase) generateModuleTarget(ctx *moduleContext) {
 	var allInstalledFiles InstallPaths
 	var allCheckbuildTargets Paths
-	ctx.VisitAllModuleVariants(func(module Module) {
-		a := module.base()
+	var alloutputFiles Paths
+	ctx.VisitAllModuleVariantProxies(func(module ModuleProxy) {
 		var checkbuildTarget Path
 		var uncheckedModule bool
-		if a == m {
+		var skipAndroidMkProcessing bool
+		if ctx.EqualModules(m.module, module) {
 			allInstalledFiles = append(allInstalledFiles, ctx.installFiles...)
 			checkbuildTarget = ctx.checkbuildTarget
 			uncheckedModule = ctx.uncheckedModule
+			skipAndroidMkProcessing = shouldSkipAndroidMkProcessing(ctx, m)
 		} else {
 			info := OtherModuleProviderOrDefault(ctx, module, InstallFilesProvider)
 			allInstalledFiles = append(allInstalledFiles, info.InstallFiles...)
 			checkbuildTarget = info.CheckbuildTarget
 			uncheckedModule = info.UncheckedModule
+			skipAndroidMkProcessing = OtherModuleProviderOrDefault(ctx, module, CommonModuleInfoKey).SkipAndroidMkProcessing
+		}
+		if outputFiles, err := outputFilesForModule(ctx, module, ""); err == nil {
+			alloutputFiles = append(alloutputFiles, outputFiles...)
 		}
 		// A module's -checkbuild phony targets should
 		// not be created if the module is not exported to make.
 		// Those could depend on the build target and fail to compile
 		// for the current build target.
-		if (!ctx.Config().KatiEnabled() || !shouldSkipAndroidMkProcessing(ctx, a)) && !uncheckedModule && checkbuildTarget != nil {
+		if (!ctx.Config().KatiEnabled() || !skipAndroidMkProcessing) && !uncheckedModule && checkbuildTarget != nil {
 			allCheckbuildTargets = append(allCheckbuildTargets, checkbuildTarget)
 		}
 	})
 
-	var deps Paths
-
 	var namespacePrefix string
 	nameSpace := ctx.Namespace().Path
 	if nameSpace != "." {
 		namespacePrefix = strings.ReplaceAll(nameSpace, "/", ".") + "-"
 	}
 
+	var deps Paths
 	var info FinalModuleBuildTargetsInfo
 
 	if len(allInstalledFiles) > 0 {
@@ -1640,6 +1711,12 @@
 		deps = append(deps, PathForPhony(ctx, name))
 	}
 
+	if len(alloutputFiles) > 0 {
+		name := namespacePrefix + ctx.ModuleName() + "-outputs"
+		ctx.Phony(name, alloutputFiles...)
+		deps = append(deps, PathForPhony(ctx, name))
+	}
+
 	if len(deps) > 0 {
 		suffix := ""
 		if ctx.Config().KatiEnabled() {
@@ -1647,13 +1724,24 @@
 		}
 
 		ctx.Phony(namespacePrefix+ctx.ModuleName()+suffix, deps...)
+		if ctx.Device() {
+			// Generate a target suffix for use in atest etc.
+			ctx.Phony(namespacePrefix+ctx.ModuleName()+"-target"+suffix, deps...)
+		} else {
+			// Generate a host suffix for use in atest etc.
+			ctx.Phony(namespacePrefix+ctx.ModuleName()+"-host"+suffix, deps...)
+			if ctx.Target().HostCross {
+				// Generate a host-cross suffix for use in atest etc.
+				ctx.Phony(namespacePrefix+ctx.ModuleName()+"-host-cross"+suffix, deps...)
+			}
+		}
 
 		info.BlueprintDir = ctx.ModuleDir()
 		SetProvider(ctx, FinalModuleBuildTargetsProvider, info)
 	}
 }
 
-func determineModuleKind(m *ModuleBase, ctx blueprint.EarlyModuleContext) moduleKind {
+func determineModuleKind(m *ModuleBase, ctx ModuleErrorContext) moduleKind {
 	var socSpecific = Bool(m.commonProperties.Vendor) || Bool(m.commonProperties.Proprietary) || Bool(m.commonProperties.Soc_specific)
 	var deviceSpecific = Bool(m.commonProperties.Device_specific)
 	var productSpecific = Bool(m.commonProperties.Product_specific)
@@ -1764,12 +1852,12 @@
 	KatiInstalls             katiInstalls
 	KatiSymlinks             katiInstalls
 	TestData                 []DataPath
-	TransitivePackagingSpecs *DepSet[PackagingSpec]
+	TransitivePackagingSpecs depset.DepSet[PackagingSpec]
 	LicenseMetadataFile      WritablePath
 
 	// The following fields are private before, make it private again once we have
 	// better solution.
-	TransitiveInstallFiles *DepSet[InstallPath]
+	TransitiveInstallFiles depset.DepSet[InstallPath]
 	// katiInitRcInstalls and katiVintfInstalls track the install rules created by Soong that are
 	// allowed to have duplicates across modules and variants.
 	KatiInitRcInstalls           katiInstalls
@@ -1785,9 +1873,15 @@
 
 var InstallFilesProvider = blueprint.NewProvider[InstallFilesInfo]()
 
+type SourceFilesInfo struct {
+	Srcs Paths
+}
+
+var SourceFilesInfoProvider = blueprint.NewProvider[SourceFilesInfo]()
+
+// FinalModuleBuildTargetsInfo is used by buildTargetSingleton to create checkbuild and
+// per-directory build targets. Only set on the final variant of each module
 type FinalModuleBuildTargetsInfo struct {
-	// Used by buildTargetSingleton to create checkbuild and per-directory build targets
-	// Only set on the final variant of each module
 	InstallTarget    WritablePath
 	CheckbuildTarget WritablePath
 	BlueprintDir     string
@@ -1795,25 +1889,70 @@
 
 var FinalModuleBuildTargetsProvider = blueprint.NewProvider[FinalModuleBuildTargetsInfo]()
 
-type CommonPropertiesProviderData struct {
+type CommonModuleInfo struct {
 	Enabled bool
 	// Whether the module has been replaced by a prebuilt
 	ReplacedByPrebuilt bool
+	// The Target of artifacts that this module variant is responsible for creating.
+	Target                  Target
+	SkipAndroidMkProcessing bool
+	BaseModuleName          string
+	CanHaveApexVariants     bool
+	MinSdkVersion           ApiLevelOrPlatform
+	SdkVersion              string
+	NotAvailableForPlatform bool
+	// There some subtle differences between this one and the one above.
+	NotInPlatform bool
+	// UninstallableApexPlatformVariant is set by MakeUninstallable called by the apex
+	// mutator.  MakeUninstallable also sets HideFromMake.  UninstallableApexPlatformVariant
+	// is used to avoid adding install or packaging dependencies into libraries provided
+	// by apexes.
+	UninstallableApexPlatformVariant bool
+	HideFromMake                     bool
+	SkipInstall                      bool
+	IsStubsModule                    bool
+	Host                             bool
 }
 
-var CommonPropertiesProviderKey = blueprint.NewProvider[CommonPropertiesProviderData]()
-
-type PrebuiltModuleProviderData struct {
-	// Empty for now
+type ApiLevelOrPlatform struct {
+	ApiLevel   *ApiLevel
+	IsPlatform bool
 }
 
-var PrebuiltModuleProviderKey = blueprint.NewProvider[PrebuiltModuleProviderData]()
+var CommonModuleInfoKey = blueprint.NewProvider[CommonModuleInfo]()
 
-type HostToolProviderData struct {
+type PrebuiltModuleInfo struct {
+	SourceExists bool
+	UsePrebuilt  bool
+}
+
+var PrebuiltModuleInfoProvider = blueprint.NewProvider[PrebuiltModuleInfo]()
+
+type HostToolProviderInfo struct {
 	HostToolPath OptionalPath
 }
 
-var HostToolProviderKey = blueprint.NewProvider[HostToolProviderData]()
+var HostToolProviderInfoProvider = blueprint.NewProvider[HostToolProviderInfo]()
+
+type DistInfo struct {
+	Dists []dist
+}
+
+var DistProvider = blueprint.NewProvider[DistInfo]()
+
+type SourceFileGenerator interface {
+	GeneratedSourceFiles() Paths
+	GeneratedHeaderDirs() Paths
+	GeneratedDeps() Paths
+}
+
+type GeneratedSourceInfo struct {
+	GeneratedSourceFiles Paths
+	GeneratedHeaderDirs  Paths
+	GeneratedDeps        Paths
+}
+
+var GeneratedSourceInfoProvider = blueprint.NewProvider[GeneratedSourceInfo]()
 
 func (m *ModuleBase) GenerateBuildActions(blueprintCtx blueprint.ModuleContext) {
 	ctx := &moduleContext{
@@ -1835,7 +1974,7 @@
 	// set the TransitiveInstallFiles to only the transitive dependencies to be used as the dependencies
 	// of installed files of this module.  It will be replaced by a depset including the installed
 	// files of this module at the end for use by modules that depend on this one.
-	ctx.TransitiveInstallFiles = NewDepSet[InstallPath](TOPOLOGICAL, nil, dependencyInstallFiles)
+	ctx.TransitiveInstallFiles = depset.New[InstallPath](depset.TOPOLOGICAL, nil, dependencyInstallFiles)
 
 	// Temporarily continue to call blueprintCtx.GetMissingDependencies() to maintain the previous behavior of never
 	// reporting missing dependency errors in Blueprint when AllowMissingDependencies == true.
@@ -1883,9 +2022,7 @@
 
 	if m.Enabled(ctx) {
 		// ensure all direct android.Module deps are enabled
-		ctx.VisitDirectDeps(func(m Module) {
-			ctx.validateAndroidModule(m, ctx.OtherModuleDependencyTag(m), ctx.baseModuleContext.strictVisitDeps)
-		})
+		ctx.VisitDirectDepsProxy(func(m ModuleProxy) {})
 
 		if m.Device() {
 			// Handle any init.rc and vintf fragment files requested by the module.  All files installed by this
@@ -1987,61 +2124,102 @@
 		ctx.GetMissingDependencies()
 	}
 
-	if m == ctx.FinalModule().(Module).base() {
+	if sourceFileProducer, ok := m.module.(SourceFileProducer); ok {
+		SetProvider(ctx, SourceFilesInfoProvider, SourceFilesInfo{Srcs: sourceFileProducer.Srcs()})
+	}
+
+	if ctx.IsFinalModule(m.module) {
 		m.generateModuleTarget(ctx)
 		if ctx.Failed() {
 			return
 		}
 	}
 
-	ctx.TransitiveInstallFiles = NewDepSet[InstallPath](TOPOLOGICAL, ctx.installFiles, dependencyInstallFiles)
+	ctx.TransitiveInstallFiles = depset.New[InstallPath](depset.TOPOLOGICAL, ctx.installFiles, dependencyInstallFiles)
 	installFiles.TransitiveInstallFiles = ctx.TransitiveInstallFiles
-	installFiles.TransitivePackagingSpecs = NewDepSet[PackagingSpec](TOPOLOGICAL, ctx.packagingSpecs, dependencyPackagingSpecs)
+	installFiles.TransitivePackagingSpecs = depset.New[PackagingSpec](depset.TOPOLOGICAL, ctx.packagingSpecs, dependencyPackagingSpecs)
 
 	SetProvider(ctx, InstallFilesProvider, installFiles)
 	buildLicenseMetadata(ctx, ctx.licenseMetadataFile)
 
-	if ctx.moduleInfoJSON != nil {
-		var installed InstallPaths
-		installed = append(installed, ctx.katiInstalls.InstallPaths()...)
-		installed = append(installed, ctx.katiSymlinks.InstallPaths()...)
-		installed = append(installed, ctx.katiInitRcInstalls.InstallPaths()...)
-		installed = append(installed, ctx.katiVintfInstalls.InstallPaths()...)
-		installedStrings := installed.Strings()
+	if len(ctx.moduleInfoJSON) > 0 {
+		for _, moduleInfoJSON := range ctx.moduleInfoJSON {
+			if moduleInfoJSON.Disabled {
+				continue
+			}
+			var installed InstallPaths
+			installed = append(installed, ctx.katiInstalls.InstallPaths()...)
+			installed = append(installed, ctx.katiSymlinks.InstallPaths()...)
+			installed = append(installed, ctx.katiInitRcInstalls.InstallPaths()...)
+			installed = append(installed, ctx.katiVintfInstalls.InstallPaths()...)
+			installedStrings := installed.Strings()
 
-		var targetRequired, hostRequired []string
-		if ctx.Host() {
-			targetRequired = m.commonProperties.Target_required
-		} else {
-			hostRequired = m.commonProperties.Host_required
-		}
+			var targetRequired, hostRequired []string
+			if ctx.Host() {
+				targetRequired = m.baseProperties.Target_required
+			} else {
+				hostRequired = m.baseProperties.Host_required
+			}
 
-		var data []string
-		for _, d := range ctx.testData {
-			data = append(data, d.ToRelativeInstallPath())
-		}
+			var data []string
+			for _, d := range ctx.testData {
+				data = append(data, d.ToRelativeInstallPath())
+			}
 
-		if ctx.moduleInfoJSON.Uninstallable {
-			installedStrings = nil
-			if len(ctx.moduleInfoJSON.CompatibilitySuites) == 1 && ctx.moduleInfoJSON.CompatibilitySuites[0] == "null-suite" {
-				ctx.moduleInfoJSON.CompatibilitySuites = nil
-				ctx.moduleInfoJSON.TestConfig = nil
-				ctx.moduleInfoJSON.AutoTestConfig = nil
-				data = nil
+			if moduleInfoJSON.Uninstallable {
+				installedStrings = nil
+				if len(moduleInfoJSON.CompatibilitySuites) == 1 && moduleInfoJSON.CompatibilitySuites[0] == "null-suite" {
+					moduleInfoJSON.CompatibilitySuites = nil
+					moduleInfoJSON.TestConfig = nil
+					moduleInfoJSON.AutoTestConfig = nil
+					data = nil
+				}
+			}
+
+			// M(C)TS supports a full test suite and partial per-module MTS test suites, with naming mts-${MODULE}.
+			// To reduce repetition, if we find a partial M(C)TS test suite without an full M(C)TS test suite,
+			// we add the full test suite to our list. This was inherited from
+			// AndroidMkEntries.AddCompatibilityTestSuites.
+			suites := moduleInfoJSON.CompatibilitySuites
+			if PrefixInList(suites, "mts-") && !InList("mts", suites) {
+				suites = append(suites, "mts")
+			}
+			if PrefixInList(suites, "mcts-") && !InList("mcts", suites) {
+				suites = append(suites, "mcts")
+			}
+			moduleInfoJSON.CompatibilitySuites = suites
+
+			required := append(m.RequiredModuleNames(ctx), m.VintfFragmentModuleNames(ctx)...)
+			required = append(required, moduleInfoJSON.ExtraRequired...)
+
+			registerName := moduleInfoJSON.RegisterNameOverride
+			if len(registerName) == 0 {
+				registerName = m.moduleInfoRegisterName(ctx, moduleInfoJSON.SubName)
+			}
+
+			moduleName := moduleInfoJSON.ModuleNameOverride
+			if len(moduleName) == 0 {
+				moduleName = m.BaseModuleName() + moduleInfoJSON.SubName
+			}
+
+			supportedVariants := moduleInfoJSON.SupportedVariantsOverride
+			if moduleInfoJSON.SupportedVariantsOverride == nil {
+				supportedVariants = []string{m.moduleInfoVariant(ctx)}
+			}
+
+			moduleInfoJSON.core = CoreModuleInfoJSON{
+				RegisterName:       registerName,
+				Path:               []string{ctx.ModuleDir()},
+				Installed:          installedStrings,
+				ModuleName:         moduleName,
+				SupportedVariants:  supportedVariants,
+				TargetDependencies: targetRequired,
+				HostDependencies:   hostRequired,
+				Data:               data,
+				Required:           required,
 			}
 		}
 
-		ctx.moduleInfoJSON.core = CoreModuleInfoJSON{
-			RegisterName:       m.moduleInfoRegisterName(ctx, ctx.moduleInfoJSON.SubName),
-			Path:               []string{ctx.ModuleDir()},
-			Installed:          installedStrings,
-			ModuleName:         m.BaseModuleName() + ctx.moduleInfoJSON.SubName,
-			SupportedVariants:  []string{m.moduleInfoVariant(ctx)},
-			TargetDependencies: targetRequired,
-			HostDependencies:   hostRequired,
-			Data:               data,
-			Required:           append(m.RequiredModuleNames(ctx), m.VintfFragmentModuleNames(ctx)...),
-		}
 		SetProvider(ctx, ModuleInfoJSONProvider, ctx.moduleInfoJSON)
 	}
 
@@ -2059,24 +2237,88 @@
 			Phonies: ctx.phonies,
 		})
 	}
+
+	if len(ctx.dists) > 0 {
+		SetProvider(ctx, DistProvider, DistInfo{
+			Dists: ctx.dists,
+		})
+	}
+
 	buildComplianceMetadataProvider(ctx, m)
 
-	commonData := CommonPropertiesProviderData{
-		ReplacedByPrebuilt: m.commonProperties.ReplacedByPrebuilt,
+	commonData := CommonModuleInfo{
+		ReplacedByPrebuilt:               m.commonProperties.ReplacedByPrebuilt,
+		Target:                           m.commonProperties.CompileTarget,
+		SkipAndroidMkProcessing:          shouldSkipAndroidMkProcessing(ctx, m),
+		BaseModuleName:                   m.BaseModuleName(),
+		UninstallableApexPlatformVariant: m.commonProperties.UninstallableApexPlatformVariant,
+		HideFromMake:                     m.commonProperties.HideFromMake,
+		SkipInstall:                      m.commonProperties.SkipInstall,
+		Host:                             m.Host(),
 	}
+	if mm, ok := m.module.(interface {
+		MinSdkVersion(ctx EarlyModuleContext) ApiLevel
+	}); ok {
+		ver := mm.MinSdkVersion(ctx)
+		commonData.MinSdkVersion.ApiLevel = &ver
+	} else if mm, ok := m.module.(interface{ MinSdkVersion() string }); ok {
+		ver := mm.MinSdkVersion()
+		// Compile against the current platform
+		if ver == "" {
+			commonData.MinSdkVersion.IsPlatform = true
+		} else {
+			api := ApiLevelFrom(ctx, ver)
+			commonData.MinSdkVersion.ApiLevel = &api
+		}
+	}
+
+	if mm, ok := m.module.(interface {
+		SdkVersion(ctx EarlyModuleContext) ApiLevel
+	}); ok {
+		ver := mm.SdkVersion(ctx)
+		if !ver.IsNone() {
+			commonData.SdkVersion = ver.String()
+		}
+	} else if mm, ok := m.module.(interface{ SdkVersion() string }); ok {
+		commonData.SdkVersion = mm.SdkVersion()
+	}
+
 	if m.commonProperties.ForcedDisabled {
 		commonData.Enabled = false
 	} else {
 		commonData.Enabled = m.commonProperties.Enabled.GetOrDefault(m.ConfigurableEvaluator(ctx), !m.Os().DefaultDisabled)
 	}
-	SetProvider(ctx, CommonPropertiesProviderKey, commonData)
+	if am, ok := m.module.(ApexModule); ok {
+		commonData.CanHaveApexVariants = am.CanHaveApexVariants()
+		commonData.NotAvailableForPlatform = am.NotAvailableForPlatform()
+		commonData.NotInPlatform = am.NotInPlatform()
+	}
+	if st, ok := m.module.(StubsAvailableModule); ok {
+		commonData.IsStubsModule = st.IsStubsModule()
+	}
+	SetProvider(ctx, CommonModuleInfoKey, commonData)
 	if p, ok := m.module.(PrebuiltInterface); ok && p.Prebuilt() != nil {
-		SetProvider(ctx, PrebuiltModuleProviderKey, PrebuiltModuleProviderData{})
+		SetProvider(ctx, PrebuiltModuleInfoProvider, PrebuiltModuleInfo{
+			SourceExists: p.Prebuilt().SourceExists(),
+			UsePrebuilt:  p.Prebuilt().UsePrebuilt(),
+		})
 	}
 	if h, ok := m.module.(HostToolProvider); ok {
-		SetProvider(ctx, HostToolProviderKey, HostToolProviderData{
+		SetProvider(ctx, HostToolProviderInfoProvider, HostToolProviderInfo{
 			HostToolPath: h.HostToolPath()})
 	}
+
+	if p, ok := m.module.(AndroidMkProviderInfoProducer); ok && !commonData.SkipAndroidMkProcessing {
+		SetProvider(ctx, AndroidMkInfoProvider, p.PrepareAndroidMKProviderInfo(ctx.Config()))
+	}
+
+	if s, ok := m.module.(SourceFileGenerator); ok {
+		SetProvider(ctx, GeneratedSourceInfoProvider, GeneratedSourceInfo{
+			GeneratedSourceFiles: s.GeneratedSourceFiles(),
+			GeneratedHeaderDirs:  s.GeneratedHeaderDirs(),
+			GeneratedDeps:        s.GeneratedDeps(),
+		})
+	}
 }
 
 func SetJarJarPrefixHandler(handler func(ModuleContext)) {
@@ -2100,7 +2342,7 @@
 	arches = slices.DeleteFunc(arches, func(target Target) bool {
 		return target.NativeBridge != ctx.Target().NativeBridge
 	})
-	if len(arches) > 0 && ctx.Arch().ArchType != arches[0].Arch.ArchType {
+	if len(arches) > 0 && ctx.Arch().ArchType != arches[0].Arch.ArchType && ctx.Arch().ArchType != Common {
 		if ctx.Arch().ArchType.Multilib == "lib32" {
 			suffix = "_32"
 		} else {
@@ -2192,11 +2434,11 @@
 }
 
 func (k *katiInstall) GobEncode() ([]byte, error) {
-	return blueprint.CustomGobEncode[katiInstallGob](k)
+	return gobtools.CustomGobEncode[katiInstallGob](k)
 }
 
 func (k *katiInstall) GobDecode(data []byte) error {
-	return blueprint.CustomGobDecode[katiInstallGob](data, k)
+	return gobtools.CustomGobDecode[katiInstallGob](data, k)
 }
 
 type extraFilesZip struct {
@@ -2222,11 +2464,11 @@
 }
 
 func (e *extraFilesZip) GobEncode() ([]byte, error) {
-	return blueprint.CustomGobEncode[extraFilesZipGob](e)
+	return gobtools.CustomGobEncode[extraFilesZipGob](e)
 }
 
 func (e *extraFilesZip) GobDecode(data []byte) error {
-	return blueprint.CustomGobDecode[extraFilesZipGob](data, e)
+	return gobtools.CustomGobDecode[extraFilesZipGob](data, e)
 }
 
 type katiInstalls []katiInstall
@@ -2278,6 +2520,14 @@
 	return proptools.Bool(m.commonProperties.Native_bridge_supported)
 }
 
+func (m *ModuleBase) DecodeMultilib(ctx ConfigContext) (string, string) {
+	return decodeMultilib(ctx, m)
+}
+
+func (m *ModuleBase) Overrides() []string {
+	return m.commonProperties.Overrides
+}
+
 type ConfigContext interface {
 	Config() Config
 }
@@ -2345,9 +2595,13 @@
 			return proptools.ConfigurableValueBool(ctx.Config().BuildFromTextStub())
 		case "debuggable":
 			return proptools.ConfigurableValueBool(ctx.Config().Debuggable())
+		case "eng":
+			return proptools.ConfigurableValueBool(ctx.Config().Eng())
 		case "use_debug_art":
 			// TODO(b/234351700): Remove once ART does not have separated debug APEX
 			return proptools.ConfigurableValueBool(ctx.Config().UseDebugArt())
+		case "selinux_ignore_neverallows":
+			return proptools.ConfigurableValueBool(ctx.Config().SelinuxIgnoreNeverallows())
 		default:
 			// TODO(b/323382414): Might add these on a case-by-case basis
 			ctx.OtherModulePropertyErrorf(m, property, fmt.Sprintf("TODO(b/323382414): Product variable %q is not yet supported in selects", variable))
@@ -2372,6 +2626,8 @@
 					return proptools.ConfigurableValueString(v)
 				case "bool":
 					return proptools.ConfigurableValueBool(v == "true")
+				case "string_list":
+					return proptools.ConfigurableValueStringList(strings.Split(v, " "))
 				default:
 					panic("unhandled soong config variable type: " + ty)
 				}
@@ -2568,7 +2824,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)
@@ -2579,7 +2835,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)
@@ -2612,20 +2868,35 @@
 	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) {
+			// It is the current module, we can access the srcs through interface
+			if sourceFileProducer, ok := module.(SourceFileProducer); ok {
+				return sourceFileProducer.Srcs(), nil
+			}
+		} else if sourceFiles, ok := OtherModuleProvider(octx, module, SourceFilesInfoProvider); 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
@@ -2634,26 +2905,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))
 	}
@@ -2747,6 +3011,9 @@
 func (c *buildTargetSingleton) GenerateBuildActions(ctx SingletonContext) {
 	var checkbuildDeps Paths
 
+	// Create a top level partialcompileclean target for modules to add dependencies to.
+	ctx.Phony("partialcompileclean")
+
 	mmTarget := func(dir string) string {
 		return "MODULES-IN-" + strings.Replace(filepath.Clean(dir), "/", "-", -1)
 	}
diff --git a/android/module_context.go b/android/module_context.go
index 2bf2a8f..f279fd9 100644
--- a/android/module_context.go
+++ b/android/module_context.go
@@ -18,10 +18,13 @@
 	"fmt"
 	"path"
 	"path/filepath"
+	"slices"
 	"strings"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/depset"
 	"github.com/google/blueprint/proptools"
+	"github.com/google/blueprint/uniquelist"
 )
 
 // BuildParameters describes the set of potential parameters to build a Ninja rule.
@@ -73,11 +76,13 @@
 	// Validations is a slice of output path for a validation action. Validation outputs imply lower
 	// non-blocking priority to building non-validation outputs.
 	Validations Paths
-	// Whether to skip outputting a default target statement which will be built by Ninja when no
+	// Whether to output a default target statement which will be built by Ninja when no
 	// targets are specified on Ninja's command line.
 	Default bool
 	// Args is a key value mapping for replacements of variables within the Rule
 	Args map[string]string
+	// PhonyOutput marks this build as `phony_output = true`
+	PhonyOutput bool
 }
 
 type ModuleBuildParams BuildParams
@@ -194,6 +199,9 @@
 	InstallInOdm() bool
 	InstallInProduct() bool
 	InstallInVendor() bool
+	InstallInSystemDlkm() bool
+	InstallInVendorDlkm() bool
+	InstallInOdmDlkm() bool
 	InstallForceOS() (*OsType, *ArchType)
 
 	RequiredModuleNames(ctx ConfigurableEvaluatorContext) []string
@@ -225,6 +233,10 @@
 	// the module-info.json generated by Make, and Make will not generate its own data for this module.
 	ModuleInfoJSON() *ModuleInfoJSON
 
+	// Simiar to ModuleInfoJSON, ExtraModuleInfoJSON also returns a pointer to the ModuleInfoJSON struct.
+	// This should only be called by a module that generates multiple AndroidMkEntries struct.
+	ExtraModuleInfoJSON() *ModuleInfoJSON
+
 	// SetOutputFiles stores the outputFiles to outputFiles property, which is used
 	// to set the OutputFilesProvider later.
 	SetOutputFiles(outputFiles Paths, tag string)
@@ -245,6 +257,24 @@
 	setContainersInfo(info ContainersInfo)
 
 	setAconfigPaths(paths Paths)
+
+	// DistForGoals creates a rule to copy one or more Paths to the artifacts
+	// directory on the build server when any of the specified goals are built.
+	DistForGoal(goal string, paths ...Path)
+
+	// DistForGoalWithFilename creates a rule to copy a Path to the artifacts
+	// directory on the build server with the given filename when the specified
+	// goal is built.
+	DistForGoalWithFilename(goal string, path Path, filename string)
+
+	// DistForGoals creates a rule to copy one or more Paths to the artifacts
+	// directory on the build server when any of the specified goals are built.
+	DistForGoals(goals []string, paths ...Path)
+
+	// DistForGoalsWithFilename creates a rule to copy a Path to the artifacts
+	// directory on the build server with the given filename when any of the
+	// specified goals are built.
+	DistForGoalsWithFilename(goals []string, path Path, filename string)
 }
 
 type moduleContext struct {
@@ -261,7 +291,7 @@
 	// the OutputFilesProvider in GenerateBuildActions
 	outputFiles OutputFilesInfo
 
-	TransitiveInstallFiles *DepSet[InstallPath]
+	TransitiveInstallFiles depset.DepSet[InstallPath]
 
 	// set of dependency module:location mappings used to populate the license metadata for
 	// apex containers.
@@ -290,7 +320,7 @@
 
 	// moduleInfoJSON can be filled out by GenerateAndroidBuildActions to write a JSON file that will
 	// be included in the final module-info.json produced by Make.
-	moduleInfoJSON *ModuleInfoJSON
+	moduleInfoJSON []*ModuleInfoJSON
 
 	// containersInfo stores the information about the containers and the information of the
 	// apexes the module belongs to.
@@ -302,6 +332,8 @@
 	// complianceMetadataInfo is for different module types to dump metadata.
 	// See android.ModuleContext interface.
 	complianceMetadataInfo *ComplianceMetadataInfo
+
+	dists []dist
 }
 
 var _ ModuleContext = &moduleContext{}
@@ -338,7 +370,8 @@
 		OrderOnly:       params.OrderOnly.Strings(),
 		Validations:     params.Validations.Strings(),
 		Args:            params.Args,
-		Optional:        !params.Default,
+		Default:         params.Default,
+		PhonyOutput:     params.PhonyOutput,
 	}
 
 	if params.Depfile != nil {
@@ -434,9 +467,28 @@
 	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 {
+	deps := m.getDirectDepsInternal(name, tag)
+	if len(deps) == 1 {
+		return deps[0]
+	} else if len(deps) >= 2 {
+		panic(fmt.Errorf("Multiple dependencies having same BaseModuleName() %q found from %q",
+			name, m.ModuleName()))
+	} else {
+		return nil
+	}
+}
+
+func (m *moduleContext) GetDirectDepProxyWithTag(name string, tag blueprint.DependencyTag) *ModuleProxy {
+	deps := m.getDirectDepsProxyInternal(name, tag)
+	if len(deps) == 1 {
+		return &deps[0]
+	} else if len(deps) >= 2 {
+		panic(fmt.Errorf("Multiple dependencies having same BaseModuleName() %q found from %q",
+			name, m.ModuleName()))
+	} else {
+		return nil
+	}
 }
 
 func (m *moduleContext) ModuleSubDir() string {
@@ -491,15 +543,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.
@@ -518,6 +578,10 @@
 		return false
 	}
 
+	if m.module.base().commonProperties.HideFromMake {
+		return false
+	}
+
 	if proptools.Bool(m.module.base().commonProperties.No_full_install) {
 		return false
 	}
@@ -550,32 +614,47 @@
 
 func (m *moduleContext) PackageFile(installPath InstallPath, name string, srcPath Path) PackagingSpec {
 	fullInstallPath := installPath.Join(m, name)
-	return m.packageFile(fullInstallPath, srcPath, false)
+	return m.packageFile(fullInstallPath, srcPath, false, false)
 }
 
-func (m *moduleContext) getAconfigPaths() *Paths {
-	return &m.aconfigFilePaths
+func (m *moduleContext) getAconfigPaths() Paths {
+	return m.aconfigFilePaths
 }
 
 func (m *moduleContext) setAconfigPaths(paths Paths) {
 	m.aconfigFilePaths = paths
 }
 
-func (m *moduleContext) packageFile(fullInstallPath InstallPath, srcPath Path, executable bool) PackagingSpec {
+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, requiresFullInstall 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,
 		symlinkTarget:         "",
 		executable:            executable,
-		effectiveLicenseFiles: &licenseFiles,
+		effectiveLicenseFiles: uniquelist.Make(licenseFiles),
 		partition:             fullInstallPath.partition,
 		skipInstall:           m.skipInstall(),
-		aconfigPaths:          m.getAconfigPaths(),
+		aconfigPaths:          uniquelist.Make(m.getAconfigPaths()),
 		archType:              m.target.Arch.ArchType,
-		overrides:             &overrides,
-		owner:                 m.ModuleName(),
+		overrides:             uniquelist.Make(overrides),
+		owner:                 owner,
+		requiresFullInstall:   requiresFullInstall,
+		fullInstallPath:       fullInstallPath,
 	}
 	m.packagingSpecs = append(m.packagingSpecs, spec)
 	return spec
@@ -583,6 +662,9 @@
 
 func (m *moduleContext) installFile(installPath InstallPath, name string, srcPath Path, deps []InstallPath,
 	executable bool, hooks bool, checkbuild bool, extraZip *extraFilesZip) InstallPath {
+	if _, ok := srcPath.(InstallPath); ok {
+		m.ModuleErrorf("Src path cannot be another installed file. Please use a path from source or intermediates instead.")
+	}
 
 	fullInstallPath := installPath.Join(m, name)
 	if hooks {
@@ -591,8 +673,10 @@
 
 	if m.requiresFullInstall() {
 		deps = append(deps, InstallPaths(m.TransitiveInstallFiles.ToList())...)
-		deps = append(deps, m.installedInitRcPaths...)
-		deps = append(deps, m.installedVintfFragmentsPaths...)
+		if m.config.KatiEnabled() {
+			deps = append(deps, m.installedInitRcPaths...)
+			deps = append(deps, m.installedVintfFragmentsPaths...)
+		}
 
 		var implicitDeps, orderOnlyDeps Paths
 
@@ -604,22 +688,24 @@
 			orderOnlyDeps = InstallPaths(deps).Paths()
 		}
 
-		if m.Config().KatiEnabled() {
-			// When creating the install rule in Soong but embedding in Make, write the rule to a
-			// makefile instead of directly to the ninja file so that main.mk can add the
-			// dependencies from the `required` property that are hard to resolve in Soong.
-			m.katiInstalls = append(m.katiInstalls, katiInstall{
-				from:          srcPath,
-				to:            fullInstallPath,
-				implicitDeps:  implicitDeps,
-				orderOnlyDeps: orderOnlyDeps,
-				executable:    executable,
-				extraFiles:    extraZip,
-			})
-		} else {
-			rule := Cp
+		// When creating the install rule in Soong but embedding in Make, write the rule to a
+		// makefile instead of directly to the ninja file so that main.mk can add the
+		// dependencies from the `required` property that are hard to resolve in Soong.
+		// In soong-only builds, the katiInstall will still be created for semi-legacy code paths
+		// such as module-info.json or compliance, but it will not be used for actually installing
+		// the file.
+		m.katiInstalls = append(m.katiInstalls, katiInstall{
+			from:          srcPath,
+			to:            fullInstallPath,
+			implicitDeps:  implicitDeps,
+			orderOnlyDeps: orderOnlyDeps,
+			executable:    executable,
+			extraFiles:    extraZip,
+		})
+		if !m.Config().KatiEnabled() {
+			rule := CpWithBash
 			if executable {
-				rule = CpExecutable
+				rule = CpExecutableWithBash
 			}
 
 			extraCmds := ""
@@ -630,6 +716,17 @@
 				implicitDeps = append(implicitDeps, extraZip.zip)
 			}
 
+			var cpFlags = "-f"
+
+			// If this is a device file, copy while preserving timestamps. This is to support
+			// adb sync in soong-only builds. Because soong-only builds have 2 different staging
+			// directories, the out/target/product one and the out/soong/.intermediates one,
+			// we need to ensure the files in them have the same timestamps so that adb sync doesn't
+			// update the files on device.
+			if strings.Contains(fullInstallPath.String(), "target/product") {
+				cpFlags += " -p"
+			}
+
 			m.Build(pctx, BuildParams{
 				Rule:        rule,
 				Description: "install " + fullInstallPath.Base(),
@@ -637,9 +734,9 @@
 				Input:       srcPath,
 				Implicits:   implicitDeps,
 				OrderOnly:   orderOnlyDeps,
-				Default:     !m.Config().KatiEnabled(),
 				Args: map[string]string{
 					"extraCmds": extraCmds,
+					"cpFlags":   cpFlags,
 				},
 			})
 		}
@@ -647,7 +744,7 @@
 		m.installFiles = append(m.installFiles, fullInstallPath)
 	}
 
-	m.packageFile(fullInstallPath, srcPath, executable)
+	m.packageFile(fullInstallPath, srcPath, executable, m.requiresFullInstall())
 
 	if checkbuild {
 		m.checkbuildFiles = append(m.checkbuildFiles, srcPath)
@@ -666,25 +763,26 @@
 	}
 	if m.requiresFullInstall() {
 
-		if m.Config().KatiEnabled() {
-			// When creating the symlink rule in Soong but embedding in Make, write the rule to a
-			// makefile instead of directly to the ninja file so that main.mk can add the
-			// dependencies from the `required` property that are hard to resolve in Soong.
-			m.katiSymlinks = append(m.katiSymlinks, katiInstall{
-				from: srcPath,
-				to:   fullInstallPath,
-			})
-		} else {
+		// When creating the symlink rule in Soong but embedding in Make, write the rule to a
+		// makefile instead of directly to the ninja file so that main.mk can add the
+		// dependencies from the `required` property that are hard to resolve in Soong.
+		// In soong-only builds, the katiInstall will still be created for semi-legacy code paths
+		// such as module-info.json or compliance, but it will not be used for actually installing
+		// the file.
+		m.katiSymlinks = append(m.katiSymlinks, katiInstall{
+			from: srcPath,
+			to:   fullInstallPath,
+		})
+		if !m.Config().KatiEnabled() {
 			// The symlink doesn't need updating when the target is modified, but we sometimes
 			// have a dependency on a symlink to a binary instead of to the binary directly, and
 			// the mtime of the symlink must be updated when the binary is modified, so use a
 			// normal dependency here instead of an order-only dependency.
 			m.Build(pctx, BuildParams{
-				Rule:        Symlink,
+				Rule:        SymlinkWithBash,
 				Description: "install symlink " + fullInstallPath.Base(),
 				Output:      fullInstallPath,
 				Input:       srcPath,
-				Default:     !m.Config().KatiEnabled(),
 				Args: map[string]string{
 					"fromPath": relPath,
 				},
@@ -694,18 +792,20 @@
 		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,
-		symlinkTarget:    relPath,
-		executable:       false,
-		partition:        fullInstallPath.partition,
-		skipInstall:      m.skipInstall(),
-		aconfigPaths:     m.getAconfigPaths(),
-		archType:         m.target.Arch.ArchType,
-		overrides:        &overrides,
-		owner:            m.ModuleName(),
+		relPathInPackage:    Rel(m, fullInstallPath.PartitionDir(), fullInstallPath.String()),
+		srcPath:             nil,
+		symlinkTarget:       relPath,
+		executable:          false,
+		partition:           fullInstallPath.partition,
+		skipInstall:         m.skipInstall(),
+		aconfigPaths:        uniquelist.Make(m.getAconfigPaths()),
+		archType:            m.target.Arch.ArchType,
+		overrides:           uniquelist.Make(overrides),
+		owner:               owner,
+		requiresFullInstall: m.requiresFullInstall(),
+		fullInstallPath:     fullInstallPath,
 	})
 
 	return fullInstallPath
@@ -718,20 +818,21 @@
 	m.module.base().hooks.runInstallHooks(m, nil, fullInstallPath, true)
 
 	if m.requiresFullInstall() {
-		if m.Config().KatiEnabled() {
-			// When creating the symlink rule in Soong but embedding in Make, write the rule to a
-			// makefile instead of directly to the ninja file so that main.mk can add the
-			// dependencies from the `required` property that are hard to resolve in Soong.
-			m.katiSymlinks = append(m.katiSymlinks, katiInstall{
-				absFrom: absPath,
-				to:      fullInstallPath,
-			})
-		} else {
+		// When creating the symlink rule in Soong but embedding in Make, write the rule to a
+		// makefile instead of directly to the ninja file so that main.mk can add the
+		// dependencies from the `required` property that are hard to resolve in Soong.
+		// In soong-only builds, the katiInstall will still be created for semi-legacy code paths
+		// such as module-info.json or compliance, but it will not be used for actually installing
+		// the file.
+		m.katiSymlinks = append(m.katiSymlinks, katiInstall{
+			absFrom: absPath,
+			to:      fullInstallPath,
+		})
+		if !m.Config().KatiEnabled() {
 			m.Build(pctx, BuildParams{
-				Rule:        Symlink,
+				Rule:        SymlinkWithBash,
 				Description: "install symlink " + fullInstallPath.Base() + " -> " + absPath,
 				Output:      fullInstallPath,
-				Default:     !m.Config().KatiEnabled(),
 				Args: map[string]string{
 					"fromPath": absPath,
 				},
@@ -741,18 +842,20 @@
 		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,
-		symlinkTarget:    absPath,
-		executable:       false,
-		partition:        fullInstallPath.partition,
-		skipInstall:      m.skipInstall(),
-		aconfigPaths:     m.getAconfigPaths(),
-		archType:         m.target.Arch.ArchType,
-		overrides:        &overrides,
-		owner:            m.ModuleName(),
+		relPathInPackage:    Rel(m, fullInstallPath.PartitionDir(), fullInstallPath.String()),
+		srcPath:             nil,
+		symlinkTarget:       absPath,
+		executable:          false,
+		partition:           fullInstallPath.partition,
+		skipInstall:         m.skipInstall(),
+		aconfigPaths:        uniquelist.Make(m.getAconfigPaths()),
+		archType:            m.target.Arch.ArchType,
+		overrides:           uniquelist.Make(overrides),
+		owner:               owner,
+		requiresFullInstall: m.requiresFullInstall(),
+		fullInstallPath:     fullInstallPath,
 	})
 
 	return fullInstallPath
@@ -790,15 +893,29 @@
 }
 
 func (m *moduleContext) ModuleInfoJSON() *ModuleInfoJSON {
-	if moduleInfoJSON := m.moduleInfoJSON; moduleInfoJSON != nil {
-		return moduleInfoJSON
+	if len(m.moduleInfoJSON) == 0 {
+		moduleInfoJSON := &ModuleInfoJSON{}
+		m.moduleInfoJSON = append(m.moduleInfoJSON, moduleInfoJSON)
 	}
+	return m.moduleInfoJSON[0]
+}
+
+func (m *moduleContext) ExtraModuleInfoJSON() *ModuleInfoJSON {
+	if len(m.moduleInfoJSON) == 0 {
+		panic("call ModuleInfoJSON() instead")
+	}
+
 	moduleInfoJSON := &ModuleInfoJSON{}
-	m.moduleInfoJSON = moduleInfoJSON
+	m.moduleInfoJSON = append(m.moduleInfoJSON, moduleInfoJSON)
 	return moduleInfoJSON
 }
 
 func (m *moduleContext) SetOutputFiles(outputFiles Paths, tag string) {
+	for _, outputFile := range outputFiles {
+		if outputFile == nil {
+			panic("outputfiles cannot be nil")
+		}
+	}
 	if tag == "" {
 		if len(m.outputFiles.DefaultOutputFiles) > 0 {
 			m.ModuleErrorf("Module %s default OutputFiles cannot be overwritten", m.ModuleName())
@@ -876,3 +993,32 @@
 func (m *moduleContext) setContainersInfo(info ContainersInfo) {
 	m.containersInfo = info
 }
+
+func (c *moduleContext) DistForGoal(goal string, paths ...Path) {
+	c.DistForGoals([]string{goal}, paths...)
+}
+
+func (c *moduleContext) DistForGoalWithFilename(goal string, path Path, filename string) {
+	c.DistForGoalsWithFilename([]string{goal}, path, filename)
+}
+
+func (c *moduleContext) DistForGoals(goals []string, paths ...Path) {
+	var copies distCopies
+	for _, path := range paths {
+		copies = append(copies, distCopy{
+			from: path,
+			dest: path.Base(),
+		})
+	}
+	c.dists = append(c.dists, dist{
+		goals: slices.Clone(goals),
+		paths: copies,
+	})
+}
+
+func (c *moduleContext) DistForGoalsWithFilename(goals []string, path Path, filename string) {
+	c.dists = append(c.dists, dist{
+		goals: slices.Clone(goals),
+		paths: distCopies{{from: path, dest: filename}},
+	})
+}
diff --git a/android/module_info_json.go b/android/module_info_json.go
index ee552dc..bb309ff 100644
--- a/android/module_info_json.go
+++ b/android/module_info_json.go
@@ -6,6 +6,7 @@
 	"slices"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/gobtools"
 )
 
 type CoreModuleInfoJSON struct {
@@ -20,8 +21,7 @@
 	Required           []string `json:"required,omitempty"`            // $(sort $(ALL_MODULES.$(m).REQUIRED_FROM_TARGET))
 }
 
-type ModuleInfoJSON struct {
-	core                CoreModuleInfoJSON
+type ExtraModuleInfoJSON struct {
 	SubName             string   `json:"-"`
 	Uninstallable       bool     `json:"-"`
 	Class               []string `json:"class,omitempty"`                 // $(sort $(ALL_MODULES.$(m).CLASS))
@@ -34,7 +34,7 @@
 	SrcJars             []string `json:"srcjars,omitempty"`               // $(sort $(ALL_MODULES.$(m).SRCJARS))
 	ClassesJar          []string `json:"classes_jar,omitempty"`           // $(sort $(ALL_MODULES.$(m).CLASSES_JAR))
 	TestMainlineModules []string `json:"test_mainline_modules,omitempty"` // $(sort $(ALL_MODULES.$(m).TEST_MAINLINE_MODULES))
-	IsUnitTest          bool     `json:"is_unit_test,omitempty"`          // $(ALL_MODULES.$(m).IS_UNIT_TEST)
+	IsUnitTest          string   `json:"is_unit_test,omitempty"`          // $(ALL_MODULES.$(m).IS_UNIT_TEST)
 	TestOptionsTags     []string `json:"test_options_tags,omitempty"`     // $(sort $(ALL_MODULES.$(m).TEST_OPTIONS_TAGS))
 	RuntimeDependencies []string `json:"runtime_dependencies,omitempty"`  // $(sort $(ALL_MODULES.$(m).LOCAL_RUNTIME_LIBRARIES))
 	StaticDependencies  []string `json:"static_dependencies,omitempty"`   // $(sort $(ALL_MODULES.$(m).LOCAL_STATIC_LIBRARIES))
@@ -43,6 +43,17 @@
 	CompatibilitySuites []string `json:"compatibility_suites,omitempty"` // $(sort $(ALL_MODULES.$(m).COMPATIBILITY_SUITES))
 	AutoTestConfig      []string `json:"auto_test_config,omitempty"`     // $(ALL_MODULES.$(m).auto_test_config)
 	TestConfig          []string `json:"test_config,omitempty"`          // $(strip $(ALL_MODULES.$(m).TEST_CONFIG) $(ALL_MODULES.$(m).EXTRA_TEST_CONFIGS)
+	ExtraRequired       []string `json:"-"`
+
+	SupportedVariantsOverride []string `json:"-"`
+	Disabled                  bool     `json:"-"`
+	RegisterNameOverride      string   `json:"-"`
+	ModuleNameOverride        string   `json:"-"`
+}
+
+type ModuleInfoJSON struct {
+	core CoreModuleInfoJSON
+	ExtraModuleInfoJSON
 }
 
 //ALL_DEPS.$(LOCAL_MODULE).ALL_DEPS := $(sort \
@@ -60,7 +71,7 @@
 
 type combinedModuleInfoJSON struct {
 	*CoreModuleInfoJSON
-	*ModuleInfoJSON
+	*ExtraModuleInfoJSON
 }
 
 func encodeModuleInfoJSON(w io.Writer, moduleInfoJSON *ModuleInfoJSON) error {
@@ -99,7 +110,35 @@
 	sortAndUnique(&moduleInfoJSONCopy.TestConfig)
 
 	encoder := json.NewEncoder(w)
-	return encoder.Encode(combinedModuleInfoJSON{&moduleInfoJSONCopy.core, &moduleInfoJSONCopy})
+	return encoder.Encode(combinedModuleInfoJSON{&moduleInfoJSONCopy.core, &moduleInfoJSONCopy.ExtraModuleInfoJSON})
 }
 
-var ModuleInfoJSONProvider = blueprint.NewProvider[*ModuleInfoJSON]()
+func (p *ModuleInfoJSON) ToGob() *combinedModuleInfoJSON {
+	return &combinedModuleInfoJSON{
+		CoreModuleInfoJSON:  &p.core,
+		ExtraModuleInfoJSON: &p.ExtraModuleInfoJSON,
+	}
+}
+
+func (p *ModuleInfoJSON) FromGob(data *combinedModuleInfoJSON) {
+	p.core = *data.CoreModuleInfoJSON
+	p.ExtraModuleInfoJSON = *data.ExtraModuleInfoJSON
+}
+
+func (m *ModuleInfoJSON) GobEncode() ([]byte, error) {
+	return gobtools.CustomGobEncode[combinedModuleInfoJSON](m)
+}
+
+func (m *ModuleInfoJSON) GobDecode(data []byte) error {
+	return gobtools.CustomGobDecode[combinedModuleInfoJSON](data, m)
+}
+
+func (m *ModuleInfoJSON) GetInstalled() []string {
+	return m.core.Installed
+}
+
+func (m *ModuleInfoJSON) GetClass() []string {
+	return m.Class
+}
+
+var ModuleInfoJSONProvider = blueprint.NewProvider[[]*ModuleInfoJSON]()
diff --git a/android/module_proxy.go b/android/module_proxy.go
index bc5090e..77abc11 100644
--- a/android/module_proxy.go
+++ b/android/module_proxy.go
@@ -9,6 +9,12 @@
 	module blueprint.ModuleProxy
 }
 
+var _ Module = (*ModuleProxy)(nil)
+
+func (m ModuleProxy) IsNil() bool {
+	return m.module.IsNil()
+}
+
 func (m ModuleProxy) Name() string {
 	return m.module.Name()
 }
@@ -106,6 +112,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 +140,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")
 }
@@ -142,10 +164,6 @@
 	panic("method is not implemented on ModuleProxy")
 }
 
-func (m ModuleProxy) EffectiveLicenseKinds() []string {
-	panic("method is not implemented on ModuleProxy")
-}
-
 func (m ModuleProxy) EffectiveLicenseFiles() Paths {
 	panic("method is not implemented on ModuleProxy")
 }
@@ -171,7 +189,7 @@
 }
 
 func (m ModuleProxy) String() string {
-	return m.module.Name()
+	return m.module.String()
 }
 
 func (m ModuleProxy) qualifiedModuleId(ctx BaseModuleContext) qualifiedModuleName {
@@ -201,3 +219,15 @@
 func (m ModuleProxy) ConfigurableEvaluator(ctx ConfigurableEvaluatorContext) proptools.ConfigurableEvaluator {
 	panic("method is not implemented on ModuleProxy")
 }
+
+func (m ModuleProxy) DecodeMultilib(ctx ConfigContext) (string, string) {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) Overrides() []string {
+	panic("method is not implemented on ModuleProxy")
+}
+
+func (m ModuleProxy) VintfFragments(ctx ConfigurableEvaluatorContext) []string {
+	panic("method is not implemented on ModuleProxy")
+}
diff --git a/android/module_test.go b/android/module_test.go
index d76d9b3..5331e49 100644
--- a/android/module_test.go
+++ b/android/module_test.go
@@ -321,27 +321,27 @@
 		if host {
 			variant = result.Config.BuildOSCommonTarget.String()
 		}
-		return result.ModuleForTests(name, variant)
+		return result.ModuleForTests(t, name, variant)
 	}
 
 	outputRule := func(name string) TestingBuildParams { return module(name, false).Output(name) }
 
 	installRule := func(name string) TestingBuildParams {
-		return module(name, false).Output(filepath.Join("out/soong/target/product/test_device/system", name))
+		return module(name, false).Output(filepath.Join("out/target/product/test_device/system", name))
 	}
 
 	symlinkRule := func(name string) TestingBuildParams {
-		return module(name, false).Output(filepath.Join("out/soong/target/product/test_device/system/symlinks", name))
+		return module(name, false).Output(filepath.Join("out/target/product/test_device/system/symlinks", name))
 	}
 
 	hostOutputRule := func(name string) TestingBuildParams { return module(name, true).Output(name) }
 
 	hostInstallRule := func(name string) TestingBuildParams {
-		return module(name, true).Output(filepath.Join("out/soong/host/linux-x86", name))
+		return module(name, true).Output(filepath.Join("out/host/linux-x86", name))
 	}
 
 	hostSymlinkRule := func(name string) TestingBuildParams {
-		return module(name, true).Output(filepath.Join("out/soong/host/linux-x86/symlinks", name))
+		return module(name, true).Output(filepath.Join("out/host/linux-x86/symlinks", name))
 	}
 
 	assertInputs := func(params TestingBuildParams, inputs ...Path) {
@@ -434,11 +434,12 @@
 	rules := result.InstallMakeRulesForTesting(t)
 
 	module := func(name string, host bool) TestingModule {
+		t.Helper()
 		variant := "android_common"
 		if host {
 			variant = result.Config.BuildOSCommonTarget.String()
 		}
-		return result.ModuleForTests(name, variant)
+		return result.ModuleForTests(t, name, variant)
 	}
 
 	outputRule := func(name string) TestingBuildParams { return module(name, false).Output(name) }
@@ -743,7 +744,7 @@
 				FixtureWithRootAndroidBp(tc.bp),
 			).RunTest(t)
 
-			foo := result.ModuleForTests("foo", "").Module().base()
+			foo := result.ModuleForTests(t, "foo", "").Module().base()
 
 			AssertDeepEquals(t, "foo ", tc.expectedProps, foo.propertiesWithValues())
 		})
@@ -998,6 +999,10 @@
 	return OutputFilesInfo{}
 }
 
+func (p *pathContextAddMissingDependenciesWrapper) EqualModules(m1, m2 Module) bool {
+	return m1 == m2
+}
+
 func TestOutputFileForModule(t *testing.T) {
 	testcases := []struct {
 		name        string
@@ -1074,7 +1079,7 @@
 				PathContext:                PathContextForTesting(config),
 				OtherModuleProviderContext: result.TestContext.OtherModuleProviderAdaptor(),
 			}
-			got := OutputFileForModule(ctx, result.ModuleForTests("test_module", "").Module(), tt.tag)
+			got := OutputFileForModule(ctx, result.ModuleForTests(t, "test_module", "").Module(), tt.tag)
 			AssertPathRelativeToTopEquals(t, "expected output path", tt.expected, got)
 			AssertArrayString(t, "expected missing deps", tt.missingDeps, ctx.missingDeps)
 		})
@@ -1106,3 +1111,14 @@
 			"Module .+ and Vintf_fragment .+ are installed to different partitions.")).
 		RunTestWithBp(t, bp)
 }
+
+func TestInvalidModuleName(t *testing.T) {
+	bp := `
+		deps {
+			name: "fo o",
+		}
+	`
+	prepareForModuleTests.
+		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(`should use a valid name`)).
+		RunTestWithBp(t, bp)
+}
diff --git a/android/mutator.go b/android/mutator.go
index 8265458..12861c0 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -15,9 +15,8 @@
 package android
 
 import (
-	"sync"
-
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/pool"
 )
 
 // Phases:
@@ -67,10 +66,10 @@
 }
 
 type RegisterMutatorsContext interface {
-	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 VariationTransitionMutator) TransitionMutatorHandle
+	InfoBasedTransition(name string, m androidTransitionMutator) TransitionMutatorHandle
 }
 
 type RegisterMutatorFunc func(RegisterMutatorsContext)
@@ -151,12 +150,14 @@
 
 func registerArchMutator(ctx RegisterMutatorsContext) {
 	ctx.Transition("os", &osTransitionMutator{})
+	ctx.BottomUp("image_begin", imageMutatorBeginMutator)
 	ctx.Transition("image", &imageTransitionMutator{})
 	ctx.Transition("arch", &archTransitionMutator{})
 }
 
 var preDeps = []RegisterMutatorFunc{
 	registerArchMutator,
+	RegisterPrebuiltsPreDepsMutators,
 }
 
 var postDeps = []RegisterMutatorFunc{
@@ -192,51 +193,23 @@
 	finalDeps = append(finalDeps, f)
 }
 
-type BaseMutatorContext interface {
-	BaseModuleContext
-
-	// MutatorName returns the name that this mutator was registered with.
-	MutatorName() string
-
-	// Rename all variants of a module.  The new name is not visible to calls to ModuleName,
-	// AddDependency or OtherModuleName until after this mutator pass is complete.
-	Rename(name string)
-
-	// CreateModule creates a new module by calling the factory method for the specified moduleType, and applies
-	// the specified property structs to it as if the properties were set in a blueprint file.
-	CreateModule(ModuleFactory, ...interface{}) Module
-}
-
-type TopDownMutator func(TopDownMutatorContext)
-
-type TopDownMutatorContext interface {
-	BaseMutatorContext
-}
-
-type topDownMutatorContext struct {
-	bp blueprint.TopDownMutatorContext
-	baseModuleContext
-}
-
 type BottomUpMutator func(BottomUpMutatorContext)
 
 type BottomUpMutatorContext interface {
-	BaseMutatorContext
+	BaseModuleContext
 
 	// AddDependency adds a dependency to the given module.  It returns a slice of modules for each
 	// dependency (some entries may be nil).
 	//
-	// If the mutator is parallel (see MutatorHandle.Parallel), this method will pause until the
-	// new dependencies have had the current mutator called on them.  If the mutator is not
-	// parallel this method does not affect the ordering of the current mutator pass, but will
-	// be ordered correctly for all future mutator passes.
-	AddDependency(module blueprint.Module, tag blueprint.DependencyTag, name ...string) []blueprint.Module
+	// This method will pause until the new dependencies have had the current mutator called on them.
+	AddDependency(module blueprint.Module, tag blueprint.DependencyTag, name ...string) []Module
 
 	// AddReverseDependency adds a dependency from the destination to the given module.
 	// Does not affect the ordering of the current mutator pass, but will be ordered
 	// correctly for all future mutator passes.  All reverse dependencies for a destination module are
 	// collected until the end of the mutator pass, sorted by name, and then appended to the destination
-	// module's dependency list.
+	// module's dependency list.  May only  be called by mutators that were marked with
+	// UsesReverseDependencies during registration.
 	AddReverseDependency(module blueprint.Module, tag blueprint.DependencyTag, name string)
 
 	// AddVariationDependencies adds deps as dependencies of the current module, but uses the variations
@@ -244,20 +217,18 @@
 	// each dependency (some entries may be nil).  A variant of the dependency must exist that matches
 	// all the non-local variations of the current module, plus the variations argument.
 	//
-	// If the mutator is parallel (see MutatorHandle.Parallel), this method will pause until the
-	// new dependencies have had the current mutator called on them.  If the mutator is not
-	// parallel this method does not affect the ordering of the current mutator pass, but will
-	// be ordered correctly for all future mutator passes.
-	AddVariationDependencies(variations []blueprint.Variation, tag blueprint.DependencyTag, names ...string) []blueprint.Module
+	// This method will pause until the new dependencies have had the current mutator called on them.
+	AddVariationDependencies(variations []blueprint.Variation, tag blueprint.DependencyTag, names ...string) []Module
 
-	// AddReverseVariationDependencies adds a dependency from the named module to the current
+	// AddReverseVariationDependency adds a dependency from the named module to the current
 	// module. The given variations will be added to the current module's varations, and then the
 	// result will be used to find the correct variation of the depending module, which must exist.
 	//
 	// Does not affect the ordering of the current mutator pass, but will be ordered
 	// correctly for all future mutator passes.  All reverse dependencies for a destination module are
 	// collected until the end of the mutator pass, sorted by name, and then appended to the destination
-	// module's dependency list.
+	// module's dependency list.  May only  be called by mutators that were marked with
+	// UsesReverseDependencies during registration.
 	AddReverseVariationDependency([]blueprint.Variation, blueprint.DependencyTag, string)
 
 	// AddFarVariationDependencies adds deps as dependencies of the current module, but uses the
@@ -269,41 +240,40 @@
 	// Unlike AddVariationDependencies, the variations of the current module are ignored - the
 	// dependency only needs to match the supplied variations.
 	//
-	// If the mutator is parallel (see MutatorHandle.Parallel), this method will pause until the
-	// new dependencies have had the current mutator called on them.  If the mutator is not
-	// parallel this method does not affect the ordering of the current mutator pass, but will
-	// be ordered correctly for all future mutator passes.
-	AddFarVariationDependencies([]blueprint.Variation, blueprint.DependencyTag, ...string) []blueprint.Module
+	// This method will pause until the new dependencies have had the current mutator called on them.
+	AddFarVariationDependencies([]blueprint.Variation, blueprint.DependencyTag, ...string) []Module
 
 	// ReplaceDependencies finds all the variants of the module with the specified name, then
 	// replaces all dependencies onto those variants with the current variant of this module.
-	// Replacements don't take effect until after the mutator pass is finished.
+	// Replacements don't take effect until after the mutator pass is finished.  May only
+	// be called by mutators that were marked with UsesReplaceDependencies during registration.
 	ReplaceDependencies(string)
 
 	// ReplaceDependenciesIf finds all the variants of the module with the specified name, then
 	// replaces all dependencies onto those variants with the current variant of this module
 	// as long as the supplied predicate returns true.
-	// Replacements don't take effect until after the mutator pass is finished.
+	// Replacements don't take effect until after the mutator pass is finished.  May only
+	// be called by mutators that were marked with UsesReplaceDependencies during registration.
 	ReplaceDependenciesIf(string, blueprint.ReplaceDependencyPredicate)
+
+	// Rename all variants of a module.  The new name is not visible to calls to ModuleName,
+	// AddDependency or OtherModuleName until after this mutator pass is complete.  May only be called
+	// by mutators that were marked with UsesRename during registration.
+	Rename(name string)
+
+	// CreateModule creates a new module by calling the factory method for the specified moduleType, and applies
+	// the specified property structs to it as if the properties were set in a blueprint file.  May only
+	// be called by mutators that were marked with UsesCreateModule during registration.
+	CreateModule(ModuleFactory, ...interface{}) Module
 }
 
 // An outgoingTransitionContextImpl and incomingTransitionContextImpl is created for every dependency of every module
-// for each transition mutator.  bottomUpMutatorContext and topDownMutatorContext are created once for every module
-// for every BottomUp or TopDown mutator.  Use a global pool for each to avoid reallocating every time.
+// for each transition mutator.  bottomUpMutatorContext is created once for every module for every BottomUp mutator.
+// Use a global pool for each to avoid reallocating every time.
 var (
-	outgoingTransitionContextPool = sync.Pool{
-		New: func() any { return &outgoingTransitionContextImpl{} },
-	}
-	incomingTransitionContextPool = sync.Pool{
-		New: func() any { return &incomingTransitionContextImpl{} },
-	}
-	bottomUpMutatorContextPool = sync.Pool{
-		New: func() any { return &bottomUpMutatorContext{} },
-	}
-
-	topDownMutatorContextPool = sync.Pool{
-		New: func() any { return &topDownMutatorContext{} },
-	}
+	outgoingTransitionContextPool = pool.New[outgoingTransitionContextImpl]()
+	incomingTransitionContextPool = pool.New[incomingTransitionContextImpl]()
+	bottomUpMutatorContextPool    = pool.New[bottomUpMutatorContext]()
 )
 
 type bottomUpMutatorContext struct {
@@ -314,10 +284,10 @@
 
 // callers must immediately follow the call to this function with defer bottomUpMutatorContextPool.Put(mctx).
 func bottomUpMutatorContextFactory(ctx blueprint.BottomUpMutatorContext, a Module,
-	finalPhase bool) BottomUpMutatorContext {
+	finalPhase bool) *bottomUpMutatorContext {
 
 	moduleContext := a.base().baseModuleContextFactory(ctx)
-	mctx := bottomUpMutatorContextPool.Get().(*bottomUpMutatorContext)
+	mctx := bottomUpMutatorContextPool.Get()
 	*mctx = bottomUpMutatorContext{
 		bp:                ctx,
 		baseModuleContext: moduleContext,
@@ -346,273 +316,38 @@
 	return mutator
 }
 
-type IncomingTransitionContext interface {
-	ArchModuleContext
-	ModuleProviderContext
-
-	// Module returns the target of the dependency edge for which the transition
-	// is being computed
-	Module() Module
-
-	// Config returns the configuration for the build.
-	Config() Config
-
-	DeviceConfig() DeviceConfig
-
-	// IsAddingDependency returns true if the transition is being called while adding a dependency
-	// after the transition mutator has already run, or false if it is being called when the transition
-	// mutator is running.  This should be used sparingly, all uses will have to be removed in order
-	// to support creating variants on demand.
-	IsAddingDependency() bool
-}
-
-type OutgoingTransitionContext interface {
-	ArchModuleContext
-	ModuleProviderContext
-
-	// Module returns the target of the dependency edge for which the transition
-	// is being computed
-	Module() Module
-
-	// DepTag() Returns the dependency tag through which this dependency is
-	// reached
-	DepTag() blueprint.DependencyTag
-
-	// Config returns the configuration for the build.
-	Config() Config
-
-	DeviceConfig() DeviceConfig
-}
-
-// TransitionMutator implements a top-down mechanism where a module tells its
-// direct dependencies what variation they should be built in but the dependency
-// has the final say.
-//
-// When implementing a transition mutator, one needs to implement four methods:
-//   - Split() that tells what variations a module has by itself
-//   - OutgoingTransition() where a module tells what it wants from its
-//     dependency
-//   - IncomingTransition() where a module has the final say about its own
-//     variation
-//   - Mutate() that changes the state of a module depending on its variation
-//
-// That the effective variation of module B when depended on by module A is the
-// composition the outgoing transition of module A and the incoming transition
-// of module B.
-//
-// the outgoing transition should not take the properties of the dependency into
-// account, only those of the module that depends on it. For this reason, the
-// dependency is not even passed into it as an argument. Likewise, the incoming
-// transition should not take the properties of the depending module into
-// account and is thus not informed about it. This makes for a nice
-// decomposition of the decision logic.
-//
-// A given transition mutator only affects its own variation; other variations
-// stay unchanged along the dependency edges.
-//
-// Soong makes sure that all modules are created in the desired variations and
-// that dependency edges are set up correctly. This ensures that "missing
-// variation" errors do not happen and allows for more flexible changes in the
-// value of the variation among dependency edges (as oppposed to bottom-up
-// mutators where if module A in variation X depends on module B and module B
-// has that variation X, A must depend on variation X of B)
-//
-// The limited power of the context objects passed to individual mutators
-// methods also makes it more difficult to shoot oneself in the foot. Complete
-// safety is not guaranteed because no one prevents individual transition
-// mutators from mutating modules in illegal ways and for e.g. Split() or
-// Mutate() to run their own visitations of the transitive dependency of the
-// module and both of these are bad ideas, but it's better than no guardrails at
-// all.
-//
-// This model is pretty close to Bazel's configuration transitions. The mapping
-// between concepts in Soong and Bazel is as follows:
-//   - Module == configured target
-//   - Variant == configuration
-//   - Variation name == configuration flag
-//   - Variation == configuration flag value
-//   - Outgoing transition == attribute transition
-//   - Incoming transition == rule transition
-//
-// The Split() method does not have a Bazel equivalent and Bazel split
-// transitions do not have a Soong equivalent.
-//
-// Mutate() does not make sense in Bazel due to the different models of the
-// two systems: when creating new variations, Soong clones the old module and
-// thus some way is needed to change it state whereas Bazel creates each
-// configuration of a given configured target anew.
-type TransitionMutator interface {
-	// Split returns the set of variations that should be created for a module no
-	// matter who depends on it. Used when Make depends on a particular variation
-	// or when the module knows its variations just based on information given to
-	// it in the Blueprint file. This method should not mutate the module it is
-	// called on.
-	Split(ctx BaseModuleContext) []string
-
-	// OutgoingTransition is called on a module to determine which variation it wants
-	// from its direct dependencies. The dependency itself can override this decision.
-	// This method should not mutate the module itself.
-	OutgoingTransition(ctx OutgoingTransitionContext, sourceVariation string) string
-
-	// IncomingTransition is called on a module to determine which variation it should
-	// be in based on the variation modules that depend on it want. This gives the module
-	// a final say about its own variations. This method should not mutate the module
-	// itself.
-	IncomingTransition(ctx IncomingTransitionContext, incomingVariation string) string
-
-	// Mutate is called after a module was split into multiple variations on each variation.
-	// It should not split the module any further but adding new dependencies is
-	// fine. Unlike all the other methods on TransitionMutator, this method is
-	// allowed to mutate the module.
-	Mutate(ctx BottomUpMutatorContext, variation string)
-}
-
-type androidTransitionMutator struct {
-	finalPhase bool
-	mutator    TransitionMutator
-	name       string
-}
-
-func (a *androidTransitionMutator) Split(ctx blueprint.BaseModuleContext) []string {
-	if a.finalPhase {
-		panic("TransitionMutator not allowed in FinalDepsMutators")
+func (x *registerMutatorsContext) Transition(name string, m VariationTransitionMutator) TransitionMutatorHandle {
+	atm := &androidTransitionMutatorAdapter{
+		finalPhase: x.finalPhase,
+		mutator:    variationTransitionMutatorAdapter{m},
+		name:       name,
 	}
-	if m, ok := ctx.Module().(Module); ok {
-		moduleContext := m.base().baseModuleContextFactory(ctx)
-		return a.mutator.Split(&moduleContext)
-	} else {
-		return []string{""}
+	mutator := &mutator{
+		name:              name,
+		transitionMutator: atm,
 	}
+	x.mutators = append(x.mutators, mutator)
+	return mutator
 }
 
-type outgoingTransitionContextImpl struct {
-	archModuleContext
-	bp blueprint.OutgoingTransitionContext
-}
-
-func (c *outgoingTransitionContextImpl) Module() Module {
-	return c.bp.Module().(Module)
-}
-
-func (c *outgoingTransitionContextImpl) DepTag() blueprint.DependencyTag {
-	return c.bp.DepTag()
-}
-
-func (c *outgoingTransitionContextImpl) Config() Config {
-	return c.bp.Config().(Config)
-}
-
-func (c *outgoingTransitionContextImpl) DeviceConfig() DeviceConfig {
-	return DeviceConfig{c.bp.Config().(Config).deviceConfig}
-}
-
-func (c *outgoingTransitionContextImpl) provider(provider blueprint.AnyProviderKey) (any, bool) {
-	return c.bp.Provider(provider)
-}
-
-func (a *androidTransitionMutator) OutgoingTransition(bpctx blueprint.OutgoingTransitionContext, sourceVariation string) string {
-	if m, ok := bpctx.Module().(Module); ok {
-		ctx := outgoingTransitionContextPool.Get().(*outgoingTransitionContextImpl)
-		defer outgoingTransitionContextPool.Put(ctx)
-		*ctx = outgoingTransitionContextImpl{
-			archModuleContext: m.base().archModuleContextFactory(bpctx),
-			bp:                bpctx,
-		}
-		return a.mutator.OutgoingTransition(ctx, sourceVariation)
-	} else {
-		return ""
-	}
-}
-
-type incomingTransitionContextImpl struct {
-	archModuleContext
-	bp blueprint.IncomingTransitionContext
-}
-
-func (c *incomingTransitionContextImpl) Module() Module {
-	return c.bp.Module().(Module)
-}
-
-func (c *incomingTransitionContextImpl) Config() Config {
-	return c.bp.Config().(Config)
-}
-
-func (c *incomingTransitionContextImpl) DeviceConfig() DeviceConfig {
-	return DeviceConfig{c.bp.Config().(Config).deviceConfig}
-}
-
-func (c *incomingTransitionContextImpl) IsAddingDependency() bool {
-	return c.bp.IsAddingDependency()
-}
-
-func (c *incomingTransitionContextImpl) provider(provider blueprint.AnyProviderKey) (any, bool) {
-	return c.bp.Provider(provider)
-}
-
-func (a *androidTransitionMutator) IncomingTransition(bpctx blueprint.IncomingTransitionContext, incomingVariation string) string {
-	if m, ok := bpctx.Module().(Module); ok {
-		ctx := incomingTransitionContextPool.Get().(*incomingTransitionContextImpl)
-		defer incomingTransitionContextPool.Put(ctx)
-		*ctx = incomingTransitionContextImpl{
-			archModuleContext: m.base().archModuleContextFactory(bpctx),
-			bp:                bpctx,
-		}
-		return a.mutator.IncomingTransition(ctx, incomingVariation)
-	} else {
-		return ""
-	}
-}
-
-func (a *androidTransitionMutator) Mutate(ctx blueprint.BottomUpMutatorContext, variation string) {
-	if am, ok := ctx.Module().(Module); ok {
-		if variation != "" {
-			// TODO: this should really be checking whether the TransitionMutator affected this module, not
-			//  the empty variant, but TransitionMutator has no concept of skipping a module.
-			base := am.base()
-			base.commonProperties.DebugMutators = append(base.commonProperties.DebugMutators, a.name)
-			base.commonProperties.DebugVariations = append(base.commonProperties.DebugVariations, variation)
-		}
-
-		mctx := bottomUpMutatorContextFactory(ctx, am, a.finalPhase)
-		defer bottomUpMutatorContextPool.Put(mctx)
-		a.mutator.Mutate(mctx, variation)
-	}
-}
-
-func (x *registerMutatorsContext) Transition(name string, m TransitionMutator) {
-	atm := &androidTransitionMutator{
+func (x *registerMutatorsContext) InfoBasedTransition(name string, m androidTransitionMutator) TransitionMutatorHandle {
+	atm := &androidTransitionMutatorAdapter{
 		finalPhase: x.finalPhase,
 		mutator:    m,
 		name:       name,
 	}
 	mutator := &mutator{
 		name:              name,
-		transitionMutator: atm}
+		transitionMutator: atm,
+	}
 	x.mutators = append(x.mutators, mutator)
+	return mutator
 }
 
 func (x *registerMutatorsContext) mutatorName(name string) string {
 	return name
 }
 
-func (x *registerMutatorsContext) TopDown(name string, m TopDownMutator) MutatorHandle {
-	f := func(ctx blueprint.TopDownMutatorContext) {
-		if a, ok := ctx.Module().(Module); ok {
-			moduleContext := a.base().baseModuleContextFactory(ctx)
-			actx := topDownMutatorContextPool.Get().(*topDownMutatorContext)
-			defer topDownMutatorContextPool.Put(actx)
-			*actx = topDownMutatorContext{
-				bp:                ctx,
-				baseModuleContext: moduleContext,
-			}
-			m(actx)
-		}
-	}
-	mutator := &mutator{name: x.mutatorName(name), topDownMutator: f}
-	x.mutators = append(x.mutators, mutator)
-	return mutator
-}
-
 func (mutator *mutator) componentName() string {
 	return mutator.name
 }
@@ -622,27 +357,115 @@
 	var handle blueprint.MutatorHandle
 	if mutator.bottomUpMutator != nil {
 		handle = blueprintCtx.RegisterBottomUpMutator(mutator.name, mutator.bottomUpMutator)
-	} 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()
+		}
 	}
-	if mutator.parallel {
-		handle.Parallel()
+
+	// Forward booleans set on the MutatorHandle to the blueprint.MutatorHandle.
+	if mutator.usesRename {
+		handle.UsesRename()
+	}
+	if mutator.usesReverseDependencies {
+		handle.UsesReverseDependencies()
+	}
+	if mutator.usesReplaceDependencies {
+		handle.UsesReplaceDependencies()
+	}
+	if mutator.usesCreateModule {
+		handle.UsesCreateModule()
+	}
+	if mutator.mutatesDependencies {
+		handle.MutatesDependencies()
+	}
+	if mutator.mutatesGlobalState {
+		handle.MutatesGlobalState()
 	}
 }
 
 type MutatorHandle interface {
+	// Parallel sets the mutator to visit modules in parallel while maintaining ordering.  Calling any
+	// method on the mutator context is thread-safe, but the mutator must handle synchronization
+	// for any modifications to global state or any modules outside the one it was invoked on.
+	// Deprecated: all Mutators are parallel by default.
 	Parallel() MutatorHandle
+
+	// UsesRename marks the mutator as using the BottomUpMutatorContext.Rename method, which prevents
+	// coalescing adjacent mutators into a single mutator pass.
+	UsesRename() MutatorHandle
+
+	// UsesReverseDependencies marks the mutator as using the BottomUpMutatorContext.AddReverseDependency
+	// method, which prevents coalescing adjacent mutators into a single mutator pass.
+	UsesReverseDependencies() MutatorHandle
+
+	// UsesReplaceDependencies marks the mutator as using the BottomUpMutatorContext.ReplaceDependencies
+	// method, which prevents coalescing adjacent mutators into a single mutator pass.
+	UsesReplaceDependencies() MutatorHandle
+
+	// UsesCreateModule marks the mutator as using the BottomUpMutatorContext.CreateModule method,
+	// which prevents coalescing adjacent mutators into a single mutator pass.
+	UsesCreateModule() MutatorHandle
+
+	// MutatesDependencies marks the mutator as modifying properties in dependencies, which prevents
+	// coalescing adjacent mutators into a single mutator pass.
+	MutatesDependencies() MutatorHandle
+
+	// MutatesGlobalState marks the mutator as modifying global state, which prevents coalescing
+	// adjacent mutators into a single mutator pass.
+	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 {
-	mutator.parallel = true
+	return mutator
+}
+
+func (mutator *mutator) UsesRename() MutatorHandle {
+	mutator.usesRename = true
+	return mutator
+}
+
+func (mutator *mutator) UsesReverseDependencies() MutatorHandle {
+	mutator.usesReverseDependencies = true
+	return mutator
+}
+
+func (mutator *mutator) UsesReplaceDependencies() MutatorHandle {
+	mutator.usesReplaceDependencies = true
+	return mutator
+}
+
+func (mutator *mutator) UsesCreateModule() MutatorHandle {
+	mutator.usesCreateModule = true
+	return mutator
+}
+
+func (mutator *mutator) MutatesDependencies() MutatorHandle {
+	mutator.mutatesDependencies = true
+	return mutator
+}
+
+func (mutator *mutator) MutatesGlobalState() MutatorHandle {
+	mutator.mutatesGlobalState = true
+	return mutator
+}
+
+func (mutator *mutator) NeverFar() MutatorHandle {
+	mutator.neverFar = true
 	return mutator
 }
 
 func RegisterComponentsMutator(ctx RegisterMutatorsContext) {
-	ctx.BottomUp("component-deps", componentDepsMutator).Parallel()
+	ctx.BottomUp("component-deps", componentDepsMutator)
 }
 
 // A special mutator that runs just prior to the deps mutator to allow the dependencies
@@ -660,59 +483,37 @@
 }
 
 func registerDepsMutator(ctx RegisterMutatorsContext) {
-	ctx.BottomUp("deps", depsMutator).Parallel()
+	ctx.BottomUp("deps", depsMutator).UsesReverseDependencies()
 }
 
-// android.topDownMutatorContext either has to embed blueprint.TopDownMutatorContext, in which case every method that
+// android.bottomUpMutatorContext either has to embed blueprint.BottomUpMutatorContext, in which case every method that
 // has an overridden version in android.BaseModuleContext has to be manually forwarded to BaseModuleContext to avoid
-// ambiguous method errors, or it has to store a blueprint.TopDownMutatorContext non-embedded, in which case every
+// ambiguous method errors, or it has to store a blueprint.BottomUpMutatorContext non-embedded, in which case every
 // non-overridden method has to be forwarded.  There are fewer non-overridden methods, so use the latter.  The following
-// methods forward to the identical blueprint versions for topDownMutatorContext and bottomUpMutatorContext.
-
-func (t *topDownMutatorContext) MutatorName() string {
-	return t.bp.MutatorName()
-}
-
-func (t *topDownMutatorContext) Rename(name string) {
-	t.bp.Rename(name)
-	t.Module().base().commonProperties.DebugName = name
-}
-
-func (t *topDownMutatorContext) createModule(factory blueprint.ModuleFactory, name string, props ...interface{}) blueprint.Module {
-	return t.bp.CreateModule(factory, name, props...)
-}
-
-func (t *topDownMutatorContext) CreateModule(factory ModuleFactory, props ...interface{}) Module {
-	return createModule(t, factory, "_topDownMutatorModule", props...)
-}
-
-func (t *topDownMutatorContext) createModuleWithoutInheritance(factory ModuleFactory, props ...interface{}) Module {
-	module := t.bp.CreateModule(ModuleFactoryAdaptor(factory), "", props...).(Module)
-	return module
-}
-
-func (b *bottomUpMutatorContext) MutatorName() string {
-	return b.bp.MutatorName()
-}
+// methods forward to the identical blueprint versions for bottomUpMutatorContext.
 
 func (b *bottomUpMutatorContext) Rename(name string) {
 	b.bp.Rename(name)
 	b.Module().base().commonProperties.DebugName = name
 }
 
-func (b *bottomUpMutatorContext) createModule(factory blueprint.ModuleFactory, name string, props ...interface{}) blueprint.Module {
-	return b.bp.CreateModule(factory, name, props...)
+func (b *bottomUpMutatorContext) createModule(factory blueprint.ModuleFactory, name string, props ...interface{}) Module {
+	return bpModuleToModule(b.bp.CreateModule(factory, name, props...))
+}
+
+func (b *bottomUpMutatorContext) createModuleInDirectory(factory blueprint.ModuleFactory, name string, _ string, props ...interface{}) Module {
+	panic("createModuleInDirectory is not implemented for bottomUpMutatorContext")
 }
 
 func (b *bottomUpMutatorContext) CreateModule(factory ModuleFactory, props ...interface{}) Module {
-	return createModule(b, factory, "_bottomUpMutatorModule", props...)
+	return createModule(b, factory, "_bottomUpMutatorModule", doesNotSpecifyDirectory(), props...)
 }
 
-func (b *bottomUpMutatorContext) AddDependency(module blueprint.Module, tag blueprint.DependencyTag, name ...string) []blueprint.Module {
+func (b *bottomUpMutatorContext) AddDependency(module blueprint.Module, tag blueprint.DependencyTag, name ...string) []Module {
 	if b.baseModuleContext.checkedMissingDeps() {
 		panic("Adding deps not allowed after checking for missing deps")
 	}
-	return b.bp.AddDependency(module, tag, name...)
+	return bpModulesToModules(b.bp.AddDependency(module, tag, name...))
 }
 
 func (b *bottomUpMutatorContext) AddReverseDependency(module blueprint.Module, tag blueprint.DependencyTag, name string) {
@@ -730,20 +531,20 @@
 }
 
 func (b *bottomUpMutatorContext) AddVariationDependencies(variations []blueprint.Variation, tag blueprint.DependencyTag,
-	names ...string) []blueprint.Module {
+	names ...string) []Module {
 	if b.baseModuleContext.checkedMissingDeps() {
 		panic("Adding deps not allowed after checking for missing deps")
 	}
-	return b.bp.AddVariationDependencies(variations, tag, names...)
+	return bpModulesToModules(b.bp.AddVariationDependencies(variations, tag, names...))
 }
 
 func (b *bottomUpMutatorContext) AddFarVariationDependencies(variations []blueprint.Variation,
-	tag blueprint.DependencyTag, names ...string) []blueprint.Module {
+	tag blueprint.DependencyTag, names ...string) []Module {
 	if b.baseModuleContext.checkedMissingDeps() {
 		panic("Adding deps not allowed after checking for missing deps")
 	}
 
-	return b.bp.AddFarVariationDependencies(variations, tag, names...)
+	return bpModulesToModules(b.bp.AddFarVariationDependencies(variations, tag, names...))
 }
 
 func (b *bottomUpMutatorContext) ReplaceDependencies(name string) {
@@ -759,3 +560,18 @@
 	}
 	b.bp.ReplaceDependenciesIf(name, predicate)
 }
+
+func bpModulesToModules(bpModules []blueprint.Module) []Module {
+	modules := make([]Module, len(bpModules))
+	for i, bpModule := range bpModules {
+		modules[i] = bpModuleToModule(bpModule)
+	}
+	return modules
+}
+
+func bpModuleToModule(bpModule blueprint.Module) Module {
+	if bpModule != nil {
+		return bpModule.(Module)
+	}
+	return nil
+}
diff --git a/android/mutator_test.go b/android/mutator_test.go
index 5d4074a..f7ee7d8 100644
--- a/android/mutator_test.go
+++ b/android/mutator_test.go
@@ -17,6 +17,8 @@
 import (
 	"fmt"
 	"strings"
+	"sync"
+	"sync/atomic"
 	"testing"
 
 	"github.com/google/blueprint"
@@ -52,7 +54,7 @@
 	ctx.AddDependency(ctx.Module(), nil, m.props.Deps_missing_deps...)
 }
 
-func addMissingDependenciesMutator(ctx TopDownMutatorContext) {
+func addMissingDependenciesMutator(ctx BottomUpMutatorContext) {
 	ctx.AddMissingDependencies(ctx.Module().(*mutatorTestModule).props.Mutator_missing_deps)
 }
 
@@ -70,151 +72,17 @@
 		FixtureRegisterWithContext(func(ctx RegistrationContext) {
 			ctx.RegisterModuleType("test", mutatorTestModuleFactory)
 			ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
-				ctx.TopDown("add_missing_dependencies", addMissingDependenciesMutator)
+				ctx.BottomUp("add_missing_dependencies", addMissingDependenciesMutator)
 			})
 		}),
 		FixtureWithRootAndroidBp(bp),
 	).RunTest(t)
 
-	foo := result.ModuleForTests("foo", "").Module().(*mutatorTestModule)
+	foo := result.ModuleForTests(t, "foo", "").Module().(*mutatorTestModule)
 
 	AssertDeepEquals(t, "foo missing deps", []string{"added_missing_dep", "regular_missing_dep"}, foo.missingDeps)
 }
 
-type testTransitionMutator struct {
-	split              func(ctx BaseModuleContext) []string
-	outgoingTransition func(ctx OutgoingTransitionContext, sourceVariation string) string
-	incomingTransition func(ctx IncomingTransitionContext, incomingVariation string) string
-	mutate             func(ctx BottomUpMutatorContext, variation string)
-}
-
-func (t *testTransitionMutator) Split(ctx BaseModuleContext) []string {
-	if t.split != nil {
-		return t.split(ctx)
-	}
-	return []string{""}
-}
-
-func (t *testTransitionMutator) OutgoingTransition(ctx OutgoingTransitionContext, sourceVariation string) string {
-	if t.outgoingTransition != nil {
-		return t.outgoingTransition(ctx, sourceVariation)
-	}
-	return sourceVariation
-}
-
-func (t *testTransitionMutator) IncomingTransition(ctx IncomingTransitionContext, incomingVariation string) string {
-	if t.incomingTransition != nil {
-		return t.incomingTransition(ctx, incomingVariation)
-	}
-	return incomingVariation
-}
-
-func (t *testTransitionMutator) Mutate(ctx BottomUpMutatorContext, variation string) {
-	if t.mutate != nil {
-		t.mutate(ctx, variation)
-	}
-}
-
-func TestModuleString(t *testing.T) {
-	bp := `
-		test {
-			name: "foo",
-		}
-	`
-
-	var moduleStrings []string
-
-	GroupFixturePreparers(
-		FixtureRegisterWithContext(func(ctx RegistrationContext) {
-
-			ctx.PreArchMutators(func(ctx RegisterMutatorsContext) {
-				ctx.Transition("pre_arch", &testTransitionMutator{
-					split: func(ctx BaseModuleContext) []string {
-						moduleStrings = append(moduleStrings, ctx.Module().String())
-						return []string{"a", "b"}
-					},
-				})
-				ctx.TopDown("rename_top_down", func(ctx TopDownMutatorContext) {
-					moduleStrings = append(moduleStrings, ctx.Module().String())
-					ctx.Rename(ctx.Module().base().Name() + "_renamed1")
-				})
-			})
-
-			ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
-				ctx.Transition("pre_deps", &testTransitionMutator{
-					split: func(ctx BaseModuleContext) []string {
-						moduleStrings = append(moduleStrings, ctx.Module().String())
-						return []string{"c", "d"}
-					},
-				})
-			})
-
-			ctx.PostDepsMutators(func(ctx RegisterMutatorsContext) {
-				ctx.Transition("post_deps", &testTransitionMutator{
-					split: func(ctx BaseModuleContext) []string {
-						moduleStrings = append(moduleStrings, ctx.Module().String())
-						return []string{"e", "f"}
-					},
-					outgoingTransition: func(ctx OutgoingTransitionContext, sourceVariation string) string {
-						return ""
-					},
-				})
-				ctx.BottomUp("rename_bottom_up", func(ctx BottomUpMutatorContext) {
-					moduleStrings = append(moduleStrings, ctx.Module().String())
-					ctx.Rename(ctx.Module().base().Name() + "_renamed2")
-				})
-				ctx.BottomUp("final", func(ctx BottomUpMutatorContext) {
-					moduleStrings = append(moduleStrings, ctx.Module().String())
-				})
-			})
-
-			ctx.RegisterModuleType("test", mutatorTestModuleFactory)
-		}),
-		FixtureWithRootAndroidBp(bp),
-	).RunTest(t)
-
-	want := []string{
-		// Initial name.
-		"foo{}",
-
-		// After pre_arch (reversed because rename_top_down is TopDown so it visits in reverse order).
-		"foo{pre_arch:b}",
-		"foo{pre_arch:a}",
-
-		// After rename_top_down (reversed because pre_deps TransitionMutator.Split is TopDown).
-		"foo_renamed1{pre_arch:b}",
-		"foo_renamed1{pre_arch:a}",
-
-		// After pre_deps (reversed because post_deps TransitionMutator.Split is TopDown).
-		"foo_renamed1{pre_arch:b,pre_deps:d}",
-		"foo_renamed1{pre_arch:b,pre_deps:c}",
-		"foo_renamed1{pre_arch:a,pre_deps:d}",
-		"foo_renamed1{pre_arch:a,pre_deps:c}",
-
-		// After post_deps.
-		"foo_renamed1{pre_arch:a,pre_deps:c,post_deps:e}",
-		"foo_renamed1{pre_arch:a,pre_deps:c,post_deps:f}",
-		"foo_renamed1{pre_arch:a,pre_deps:d,post_deps:e}",
-		"foo_renamed1{pre_arch:a,pre_deps:d,post_deps:f}",
-		"foo_renamed1{pre_arch:b,pre_deps:c,post_deps:e}",
-		"foo_renamed1{pre_arch:b,pre_deps:c,post_deps:f}",
-		"foo_renamed1{pre_arch:b,pre_deps:d,post_deps:e}",
-		"foo_renamed1{pre_arch:b,pre_deps:d,post_deps:f}",
-
-		// After rename_bottom_up.
-		"foo_renamed2{pre_arch:a,pre_deps:c,post_deps:e}",
-		"foo_renamed2{pre_arch:a,pre_deps:c,post_deps:f}",
-		"foo_renamed2{pre_arch:a,pre_deps:d,post_deps:e}",
-		"foo_renamed2{pre_arch:a,pre_deps:d,post_deps:f}",
-		"foo_renamed2{pre_arch:b,pre_deps:c,post_deps:e}",
-		"foo_renamed2{pre_arch:b,pre_deps:c,post_deps:f}",
-		"foo_renamed2{pre_arch:b,pre_deps:d,post_deps:e}",
-		"foo_renamed2{pre_arch:b,pre_deps:d,post_deps:f}",
-	}
-
-	AssertDeepEquals(t, "module String() values", want, moduleStrings)
-}
-
 func TestFinalDepsPhase(t *testing.T) {
 	bp := `
 		test {
@@ -228,7 +96,7 @@
 		}
 	`
 
-	finalGot := map[string]int{}
+	finalGot := sync.Map{}
 
 	GroupFixturePreparers(
 		FixtureRegisterWithContext(func(ctx RegistrationContext) {
@@ -259,9 +127,11 @@
 					}
 				})
 				ctx.BottomUp("final", func(ctx BottomUpMutatorContext) {
-					finalGot[ctx.Module().String()] += 1
+					counter, _ := finalGot.LoadOrStore(ctx.Module().String(), &atomic.Int64{})
+					counter.(*atomic.Int64).Add(1)
 					ctx.VisitDirectDeps(func(mod Module) {
-						finalGot[fmt.Sprintf("%s -> %s", ctx.Module().String(), mod)] += 1
+						counter, _ := finalGot.LoadOrStore(fmt.Sprintf("%s -> %s", ctx.Module().String(), mod), &atomic.Int64{})
+						counter.(*atomic.Int64).Add(1)
 					})
 				})
 			})
@@ -284,24 +154,11 @@
 		"foo{variant:b} -> common_dep_2{variant:a}": 1,
 	}
 
-	AssertDeepEquals(t, "final", finalWant, finalGot)
-}
+	finalGotMap := make(map[string]int)
+	finalGot.Range(func(k, v any) bool {
+		finalGotMap[k.(string)] = int(v.(*atomic.Int64).Load())
+		return true
+	})
 
-func TestTransitionMutatorInFinalDeps(t *testing.T) {
-	GroupFixturePreparers(
-		FixtureRegisterWithContext(func(ctx RegistrationContext) {
-			ctx.FinalDepsMutators(func(ctx RegisterMutatorsContext) {
-				ctx.Transition("vars", &testTransitionMutator{
-					split: func(ctx BaseModuleContext) []string {
-						return []string{"a", "b"}
-					},
-				})
-			})
-
-			ctx.RegisterModuleType("test", mutatorTestModuleFactory)
-		}),
-		FixtureWithRootAndroidBp(`test {name: "foo"}`),
-	).
-		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern("not allowed in FinalDepsMutators")).
-		RunTest(t)
+	AssertDeepEquals(t, "final", finalWant, finalGotMap)
 }
diff --git a/android/namespace.go b/android/namespace.go
index ebf85a1..9ba5025 100644
--- a/android/namespace.go
+++ b/android/namespace.go
@@ -332,7 +332,7 @@
 	if isAbs {
 		// if the user gave a fully-qualified name, we don't need to look for other
 		// modules that they might have been referring to
-		return fmt.Errorf(text)
+		return fmt.Errorf("%s", text)
 	}
 
 	// determine which namespaces the module can be found in
@@ -368,7 +368,7 @@
 		text += fmt.Sprintf("\nOr did you mean %q?", guess)
 	}
 
-	return fmt.Errorf(text)
+	return fmt.Errorf("%s", text)
 }
 
 func (r *NameResolver) GetNamespace(ctx blueprint.NamespaceContext) blueprint.Namespace {
@@ -457,7 +457,7 @@
 }
 
 func RegisterNamespaceMutator(ctx RegisterMutatorsContext) {
-	ctx.BottomUp("namespace_deps", namespaceMutator).Parallel()
+	ctx.BottomUp("namespace_deps", namespaceMutator).MutatesGlobalState()
 }
 
 func namespaceMutator(ctx BottomUpMutatorContext) {
diff --git a/android/namespace_test.go b/android/namespace_test.go
index ea51c6e..a183bbf 100644
--- a/android/namespace_test.go
+++ b/android/namespace_test.go
@@ -646,7 +646,7 @@
 		ctx.RegisterModuleType("test_module", newTestModule)
 		ctx.Context.RegisterModuleType("blueprint_test_module", newBlueprintTestModule)
 		ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
-			ctx.BottomUp("rename", renameMutator)
+			ctx.BottomUp("rename", renameMutator).UsesRename()
 		})
 	}),
 )
@@ -683,7 +683,7 @@
 }
 
 func getModule(result *TestResult, moduleName string) TestingModule {
-	return result.ModuleForTests(moduleName, "")
+	return result.ModuleForTests(result.fixture.t, moduleName, "")
 }
 
 func findModuleById(result *TestResult, id string) (module TestingModule) {
@@ -691,7 +691,7 @@
 		testModule, ok := candidate.(*testModule)
 		if ok {
 			if testModule.properties.Id == id {
-				module = newTestingModule(result.config, testModule)
+				module = newTestingModule(result.fixture.t, result.config, testModule)
 			}
 		}
 	}
@@ -709,9 +709,6 @@
 }
 
 func (m *testModule) DepsMutator(ctx BottomUpMutatorContext) {
-	if m.properties.Rename != "" {
-		ctx.Rename(m.properties.Rename)
-	}
 	for _, d := range m.properties.Deps {
 		ctx.AddDependency(ctx.Module(), nil, d)
 	}
diff --git a/android/neverallow.go b/android/neverallow.go
index e135f57..70af2ac 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -44,7 +44,7 @@
 // - it has none of the "Without" properties matched (same rules as above)
 
 func registerNeverallowMutator(ctx RegisterMutatorsContext) {
-	ctx.BottomUp("neverallow", neverallowMutator).Parallel()
+	ctx.BottomUp("neverallow", neverallowMutator)
 }
 
 var neverallows = []Rule{}
@@ -55,11 +55,17 @@
 	AddNeverAllowRules(createJavaDeviceForHostRules()...)
 	AddNeverAllowRules(createCcSdkVariantRules()...)
 	AddNeverAllowRules(createUncompressDexRules()...)
-	AddNeverAllowRules(createInitFirstStageRules()...)
+	AddNeverAllowRules(createInstallInRootAllowingRules()...)
 	AddNeverAllowRules(createProhibitFrameworkAccessRules()...)
 	AddNeverAllowRules(createCcStubsRule())
 	AddNeverAllowRules(createProhibitHeaderOnlyRule())
 	AddNeverAllowRules(createLimitNdkExportRule()...)
+	AddNeverAllowRules(createLimitDirgroupRule()...)
+	AddNeverAllowRules(createFilesystemIsAutoGeneratedRule())
+	AddNeverAllowRules(createKotlinPluginRule()...)
+	AddNeverAllowRules(createPrebuiltEtcBpDefineRule())
+	AddNeverAllowRules(createAutogenRroBpDefineRule())
+	AddNeverAllowRules(createNoSha1HashRule())
 }
 
 // Add a NeverAllow rule to the set of rules to apply.
@@ -225,21 +231,27 @@
 func createUncompressDexRules() []Rule {
 	return []Rule{
 		NeverAllow().
-			NotIn("art").
+			NotIn("art", "cts/hostsidetests/compilation").
 			WithMatcher("uncompress_dex", isSetMatcherInstance).
 			Because("uncompress_dex is only allowed for certain jars for test in art."),
 	}
 }
 
-func createInitFirstStageRules() []Rule {
+func createInstallInRootAllowingRules() []Rule {
 	return []Rule{
 		NeverAllow().
 			Without("name", "init_first_stage_defaults").
 			Without("name", "init_first_stage").
 			Without("name", "init_first_stage.microdroid").
+			Without("name", "librecovery_ui_ext").
 			With("install_in_root", "true").
 			NotModuleType("prebuilt_root").
-			Because("install_in_root is only for init_first_stage."),
+			NotModuleType("prebuilt_vendor").
+			NotModuleType("prebuilt_sbin").
+			NotModuleType("prebuilt_system").
+			NotModuleType("prebuilt_first_stage_ramdisk").
+			NotModuleType("prebuilt_res").
+			Because("install_in_root is only for init_first_stage or librecovery_ui_ext."),
 	}
 }
 
@@ -275,6 +287,111 @@
 	}
 }
 
+func createLimitDirgroupRule() []Rule {
+	reason := "dirgroup module and dir_srcs / keep_gendir property of genrule is allowed only to Trusty build rule."
+	return []Rule{
+		NeverAllow().
+			ModuleType("dirgroup").
+			WithMatcher("visibility", NotInList([]string{"//trusty/vendor/google/aosp/scripts", "//trusty/vendor/google/proprietary/scripts"})).Because(reason),
+		NeverAllow().
+			ModuleType("dirgroup").
+			WithoutMatcher("visibility", InAllowedList([]string{"//trusty/vendor/google/aosp/scripts", "//trusty/vendor/google/proprietary/scripts"})).Because(reason),
+		NeverAllow().
+			ModuleType("genrule").
+			// TODO: remove the 4 below targets once new targets are submitted
+			Without("name", "trusty-arm64.lk.elf.gen").
+			Without("name", "trusty-arm64-virt-test-debug.lk.elf.gen").
+			Without("name", "trusty-x86_64.lk.elf.gen").
+			Without("name", "trusty-x86_64-test.lk.elf.gen").
+			// trusty vm target names moving forward
+			Without("name", "trusty-test_vm-arm64.elf.gen").
+			Without("name", "trusty-test_vm-x86.elf.gen").
+			Without("name", "trusty-security_vm-arm64.elf.gen").
+			Without("name", "trusty-security_vm-x86.elf.gen").
+			Without("name", "trusty-widevine_vm-arm64.elf.gen").
+			Without("name", "trusty-widevine_vm-x86.elf.gen").
+			WithMatcher("dir_srcs", isSetMatcherInstance).Because(reason),
+		NeverAllow().
+			ModuleType("genrule").
+			// TODO: remove the 4 below targets once new targets are submitted
+			Without("name", "trusty-arm64.lk.elf.gen").
+			Without("name", "trusty-arm64-virt-test-debug.lk.elf.gen").
+			Without("name", "trusty-x86_64.lk.elf.gen").
+			Without("name", "trusty-x86_64-test.lk.elf.gen").
+			// trusty vm target names moving forward
+			Without("name", "trusty-test_vm-arm64.elf.gen").
+			Without("name", "trusty-test_vm-x86.elf.gen").
+			Without("name", "trusty-security_vm-arm64.elf.gen").
+			Without("name", "trusty-security_vm-x86.elf.gen").
+			Without("name", "trusty-widevine_vm-arm64.elf.gen").
+			Without("name", "trusty-widevine_vm-x86.elf.gen").
+			With("keep_gendir", "true").Because(reason),
+	}
+}
+
+func createFilesystemIsAutoGeneratedRule() Rule {
+	return NeverAllow().
+		NotIn("build/soong/fsgen").
+		ModuleType("filesystem", "android_system_image").
+		WithMatcher("is_auto_generated", isSetMatcherInstance).
+		Because("is_auto_generated property is only allowed for filesystem modules in build/soong/fsgen directory")
+}
+
+func createNoSha1HashRule() Rule {
+	return NeverAllow().
+		ModuleType("filesystem", "android_filesystem").
+		ModuleType("filesystem", "android_system_image").
+		With("avb_hash_algorithm", "sha1").
+		Because("sha1 is discouraged")
+}
+
+func createKotlinPluginRule() []Rule {
+	kotlinPluginProjectsAllowedList := []string{
+		"external/kotlinc",
+	}
+
+	return []Rule{
+		NeverAllow().
+			NotIn(kotlinPluginProjectsAllowedList...).
+			ModuleType("kotlin_plugin").
+			Because("kotlin_plugin can only be used in allowed projects"),
+	}
+}
+
+// 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_wlc_upt",
+			"prebuilt_odm",
+			"prebuilt_vendor_dlkm",
+			"prebuilt_bt_firmware",
+			"prebuilt_tvservice",
+			"prebuilt_optee",
+			"prebuilt_tvconfig",
+			"prebuilt_vendor",
+			"prebuilt_sbin",
+			"prebuilt_system",
+			"prebuilt_first_stage_ramdisk",
+		).
+		DefinedInBpFile().
+		Because("module type not allowed to be defined in bp file")
+}
+
+func createAutogenRroBpDefineRule() Rule {
+	return NeverAllow().
+		ModuleType(
+			"autogen_runtime_resource_overlay",
+		).
+		DefinedInBpFile().
+		Because("Module type will be autogenerated by soong. Use runtime_resource_overlay instead")
+}
+
 func neverallowMutator(ctx BottomUpMutatorContext) {
 	m, ok := ctx.Module().(Module)
 	if !ok {
@@ -308,6 +425,10 @@
 			continue
 		}
 
+		if !n.appliesToBpDefinedModule(ctx) {
+			continue
+		}
+
 		ctx.ModuleErrorf("violates " + n.String())
 	}
 }
@@ -378,6 +499,18 @@
 	return ".not-in-list(" + strings.Join(m.allowed, ",") + ")"
 }
 
+type InListMatcher struct {
+	allowed []string
+}
+
+func (m *InListMatcher) Test(value string) bool {
+	return InList(value, m.allowed)
+}
+
+func (m *InListMatcher) String() string {
+	return ".in-list(" + strings.Join(m.allowed, ",") + ")"
+}
+
 type isSetMatcher struct{}
 
 func (m *isSetMatcher) Test(value string) bool {
@@ -431,6 +564,8 @@
 
 	WithoutMatcher(properties string, matcher ValueMatcher) Rule
 
+	DefinedInBpFile() Rule
+
 	Because(reason string) Rule
 }
 
@@ -452,6 +587,8 @@
 	unlessProps ruleProperties
 
 	onlyBootclasspathJar bool
+
+	definedInBp bool
 }
 
 // Create a new NeverAllow rule.
@@ -525,6 +662,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
@@ -610,6 +754,9 @@
 }
 
 func (r *rule) appliesToModuleType(moduleType string) bool {
+	// Remove prefix for auto-generated modules
+	moduleType = strings.TrimSuffix(moduleType, "__loadHookModule")
+	moduleType = strings.TrimSuffix(moduleType, "__bottomUpMutatorModule")
 	return (len(r.moduleTypes) == 0 || InList(moduleType, r.moduleTypes)) && !InList(moduleType, r.unlessModuleTypes)
 }
 
@@ -619,6 +766,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}
 }
@@ -635,6 +789,10 @@
 	return &notInListMatcher{allowed}
 }
 
+func InAllowedList(allowed []string) ValueMatcher {
+	return &InListMatcher{allowed}
+}
+
 // assorted utils
 
 func cleanPaths(paths []string) []string {
diff --git a/android/neverallow_test.go b/android/neverallow_test.go
index 192c924..c74d5ff 100644
--- a/android/neverallow_test.go
+++ b/android/neverallow_test.go
@@ -359,6 +359,35 @@
 			`headers_only can only be used for generating framework-minus-apex headers for non-updatable modules`,
 		},
 	},
+	// Test for the rule restricting use of is_auto_generated
+	{
+		name: `"is_auto_generated" outside allowed directory`,
+		fs: map[string][]byte{
+			"a/b/Android.bp": []byte(`
+				filesystem {
+					name: "baaz",
+					is_auto_generated: true,
+				}
+			`),
+		},
+		expectedErrors: []string{
+			`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(
@@ -367,6 +396,8 @@
 		ctx.RegisterModuleType("java_library", newMockJavaLibraryModule)
 		ctx.RegisterModuleType("java_library_host", newMockJavaLibraryModule)
 		ctx.RegisterModuleType("java_device_for_host", newMockJavaLibraryModule)
+		ctx.RegisterModuleType("filesystem", newMockFilesystemModule)
+		ctx.RegisterModuleType("prebuilt_usr_srec", newMockPrebuiltUsrSrecModule)
 	}),
 )
 
@@ -466,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/nothing.go b/android/nothing.go
new file mode 100644
index 0000000..18bf85b
--- /dev/null
+++ b/android/nothing.go
@@ -0,0 +1,34 @@
+// Copyright 2025 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
+
+func init() {
+	RegisterParallelSingletonType("nothing_singleton", nothingSingletonFactory)
+}
+
+func nothingSingletonFactory() Singleton {
+	return &nothingSingleton{}
+}
+
+type nothingSingleton struct{}
+
+func (s *nothingSingleton) GenerateBuildActions(ctx SingletonContext) {
+	rule := NewRuleBuilder(pctx, ctx)
+	rule.SetPhonyOutput()
+	rule.Command().
+		Text("echo Successfully read the makefiles.").
+		ImplicitOutput(PathForPhony(ctx, "nothing"))
+	rule.Build("nothing", "nothing")
+}
diff --git a/android/override_module.go b/android/override_module.go
index f69f963..50ddc9b 100644
--- a/android/override_module.go
+++ b/android/override_module.go
@@ -234,17 +234,18 @@
 // Mutators for override/overridable modules. All the fun happens in these functions. It is critical
 // to keep them in this order and not put any order mutators between them.
 func RegisterOverridePostDepsMutators(ctx RegisterMutatorsContext) {
-	ctx.BottomUp("override_deps", overrideModuleDepsMutator).Parallel()
+	ctx.BottomUp("override_deps", overrideModuleDepsMutator).MutatesDependencies() // modifies deps via addOverride
 	ctx.Transition("override", &overrideTransitionMutator{})
+	ctx.BottomUp("override_apply", overrideApplyMutator).MutatesDependencies()
 	// overridableModuleDepsMutator calls OverridablePropertiesDepsMutator so that overridable modules can
 	// add deps from overridable properties.
-	ctx.BottomUp("overridable_deps", overridableModuleDepsMutator).Parallel()
+	ctx.BottomUp("overridable_deps", overridableModuleDepsMutator)
 	// Because overridableModuleDepsMutator is run after PrebuiltPostDepsMutator,
 	// prebuilt's ReplaceDependencies doesn't affect to those deps added by overridable properties.
 	// By running PrebuiltPostDepsMutator again after overridableModuleDepsMutator, deps via overridable properties
 	// can be replaced with prebuilts.
-	ctx.BottomUp("replace_deps_on_prebuilts_for_overridable_deps_again", PrebuiltPostDepsMutator).Parallel()
-	ctx.BottomUp("replace_deps_on_override", replaceDepsOnOverridingModuleMutator).Parallel()
+	ctx.BottomUp("replace_deps_on_prebuilts_for_overridable_deps_again", PrebuiltPostDepsMutator).UsesReplaceDependencies()
+	ctx.BottomUp("replace_deps_on_override", replaceDepsOnOverridingModuleMutator).UsesReplaceDependencies()
 }
 
 type overrideBaseDependencyTag struct {
@@ -330,6 +331,9 @@
 }
 
 func (overrideTransitionMutator) Mutate(ctx BottomUpMutatorContext, variation string) {
+}
+
+func overrideApplyMutator(ctx BottomUpMutatorContext) {
 	if o, ok := ctx.Module().(OverrideModule); ok {
 		overridableDeps := ctx.GetDirectDepsWithTag(overrideBaseDepTag)
 		if len(overridableDeps) > 1 {
diff --git a/android/packaging.go b/android/packaging.go
index 3c64d56..551fd4c 100644
--- a/android/packaging.go
+++ b/android/packaging.go
@@ -18,10 +18,11 @@
 	"fmt"
 	"path/filepath"
 	"sort"
-	"strings"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/gobtools"
 	"github.com/google/blueprint/proptools"
+	"github.com/google/blueprint/uniquelist"
 )
 
 // PackagingSpec abstracts a request to place a built artifact at a certain path in a package. A
@@ -42,7 +43,7 @@
 	// Whether relPathInPackage should be marked as executable or not
 	executable bool
 
-	effectiveLicenseFiles *Paths
+	effectiveLicenseFiles uniquelist.UniqueList[Path]
 
 	partition string
 
@@ -52,43 +53,54 @@
 	skipInstall bool
 
 	// Paths of aconfig files for the built artifact
-	aconfigPaths *Paths
+	aconfigPaths uniquelist.UniqueList[Path]
 
 	// ArchType of the module which produced this packaging spec
 	archType ArchType
 
 	// List of module names that this packaging spec overrides
-	overrides *[]string
+	overrides uniquelist.UniqueList[string]
 
 	// Name of the module where this packaging spec is output of
 	owner string
+
+	// If the ninja rule creating the FullInstallPath has already been emitted or not. Do not use,
+	// for the soong-only migration.
+	requiresFullInstall bool
+
+	// The path to the installed file in out/target/product. This is for legacy purposes, with
+	// tools that want to interact with these files outside of the build. You should not use it
+	// inside of the build. Will be nil if this module doesn't require a "full install".
+	fullInstallPath InstallPath
 }
 
 type packagingSpecGob struct {
-	RelPathInPackage string
-	SrcPath          Path
-	SymlinkTarget    string
-	Executable       bool
-	Partition        string
-	SkipInstall      bool
-	AconfigPaths     *Paths
-	ArchType         ArchType
-	Overrides        *[]string
-	Owner            string
+	RelPathInPackage      string
+	SrcPath               Path
+	SymlinkTarget         string
+	Executable            bool
+	EffectiveLicenseFiles Paths
+	Partition             string
+	SkipInstall           bool
+	AconfigPaths          Paths
+	ArchType              ArchType
+	Overrides             []string
+	Owner                 string
 }
 
 func (p *PackagingSpec) ToGob() *packagingSpecGob {
 	return &packagingSpecGob{
-		RelPathInPackage: p.relPathInPackage,
-		SrcPath:          p.srcPath,
-		SymlinkTarget:    p.symlinkTarget,
-		Executable:       p.executable,
-		Partition:        p.partition,
-		SkipInstall:      p.skipInstall,
-		AconfigPaths:     p.aconfigPaths,
-		ArchType:         p.archType,
-		Overrides:        p.overrides,
-		Owner:            p.owner,
+		RelPathInPackage:      p.relPathInPackage,
+		SrcPath:               p.srcPath,
+		SymlinkTarget:         p.symlinkTarget,
+		Executable:            p.executable,
+		EffectiveLicenseFiles: p.effectiveLicenseFiles.ToSlice(),
+		Partition:             p.partition,
+		SkipInstall:           p.skipInstall,
+		AconfigPaths:          p.aconfigPaths.ToSlice(),
+		ArchType:              p.archType,
+		Overrides:             p.overrides.ToSlice(),
+		Owner:                 p.owner,
 	}
 }
 
@@ -97,20 +109,21 @@
 	p.srcPath = data.SrcPath
 	p.symlinkTarget = data.SymlinkTarget
 	p.executable = data.Executable
+	p.effectiveLicenseFiles = uniquelist.Make(data.EffectiveLicenseFiles)
 	p.partition = data.Partition
 	p.skipInstall = data.SkipInstall
-	p.aconfigPaths = data.AconfigPaths
+	p.aconfigPaths = uniquelist.Make(data.AconfigPaths)
 	p.archType = data.ArchType
-	p.overrides = data.Overrides
+	p.overrides = uniquelist.Make(data.Overrides)
 	p.owner = data.Owner
 }
 
 func (p *PackagingSpec) GobEncode() ([]byte, error) {
-	return blueprint.CustomGobEncode[packagingSpecGob](p)
+	return gobtools.CustomGobEncode[packagingSpecGob](p)
 }
 
 func (p *PackagingSpec) GobDecode(data []byte) error {
-	return blueprint.CustomGobDecode[packagingSpecGob](data, p)
+	return gobtools.CustomGobDecode[packagingSpecGob](data, p)
 }
 
 func (p *PackagingSpec) Equals(other *PackagingSpec) bool {
@@ -151,23 +164,42 @@
 }
 
 func (p *PackagingSpec) EffectiveLicenseFiles() Paths {
-	if p.effectiveLicenseFiles == nil {
-		return Paths{}
-	}
-	return *p.effectiveLicenseFiles
+	return p.effectiveLicenseFiles.ToSlice()
 }
 
 func (p *PackagingSpec) Partition() string {
 	return p.partition
 }
 
+func (p *PackagingSpec) SetPartition(partition string) {
+	p.partition = partition
+}
+
 func (p *PackagingSpec) SkipInstall() bool {
 	return p.skipInstall
 }
 
 // Paths of aconfig files for the built artifact
 func (p *PackagingSpec) GetAconfigPaths() Paths {
-	return *p.aconfigPaths
+	return p.aconfigPaths.ToSlice()
+}
+
+// The path to the installed file in out/target/product. This is for legacy purposes, with
+// tools that want to interact with these files outside of the build. You should not use it
+// inside of the build. Will be nil if this module doesn't require a "full install".
+func (p *PackagingSpec) FullInstallPath() InstallPath {
+	return p.fullInstallPath
+}
+
+// If the ninja rule creating the FullInstallPath has already been emitted or not. Do not use,
+// for the soong-only migration.
+func (p *PackagingSpec) RequiresFullInstall() bool {
+	return p.requiresFullInstall
+}
+
+// The source file to be copied to the FullInstallPath. Do not use, for the soong-only migration.
+func (p *PackagingSpec) SrcPath() Path {
+	return p.srcPath
 }
 
 type PackageModule interface {
@@ -182,6 +214,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,
@@ -203,38 +236,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 {
@@ -245,55 +289,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)
 				}
 			}
 		}
@@ -302,17 +363,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 {
@@ -356,6 +421,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().
@@ -367,21 +434,58 @@
 	return true
 }
 
+type highPriorityDepTag struct {
+	blueprint.BaseDependencyTag
+	PackagingItemAlwaysDepTag
+}
+
 // 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{}
+		}
+
+		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
-			}
-			ctx.AddFarVariationDependencies(t.Variations(), 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).
+	var depNames []string
+
 	// list of module names overridden
 	var overridden []string
 
@@ -400,8 +504,9 @@
 		return false
 	}
 
-	ctx.VisitDirectDeps(func(child Module) {
-		if pi, ok := ctx.OtherModuleDependencyTag(child).(PackagingItem); !ok || !pi.IsPackagingItem() {
+	ctx.VisitDirectDepsProxy(func(child ModuleProxy) {
+		depTag := ctx.OtherModuleDependencyTag(child)
+		if pi, ok := depTag.(PackagingItem); !ok || !pi.IsPackagingItem() {
 			return
 		}
 		for _, ps := range OtherModuleProviderOrDefault(
@@ -415,24 +520,42 @@
 					continue
 				}
 			}
-			all = append(all, ps)
-			if ps.overrides != nil {
-				overridden = append(overridden, *ps.overrides...)
+
+			if modifier != nil {
+				modifier(&ps)
 			}
+
+			if _, ok := depTag.(highPriorityDepTag); ok {
+				highPriorities = append(highPriorities, ps)
+			} else {
+				regularPriorities = append(regularPriorities, ps)
+			}
+
+			depNames = append(depNames, child.Name())
+			overridden = append(overridden, ps.overrides.ToSlice()...)
 		}
 	})
 
-	// all minus packaging specs that are overridden
-	var filtered []PackagingSpec
-	for _, 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)
 		}
-		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) {
@@ -442,10 +565,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)
 }
@@ -455,12 +598,12 @@
 func (p *PackagingBase) CopySpecsToDir(ctx ModuleContext, builder *RuleBuilder, specs map[string]PackagingSpec, dir WritablePath) (entries []string) {
 	dirsToSpecs := make(map[WritablePath]map[string]PackagingSpec)
 	dirsToSpecs[dir] = specs
-	return p.CopySpecsToDirs(ctx, builder, dirsToSpecs)
+	return p.CopySpecsToDirs(ctx, builder, dirsToSpecs, false)
 }
 
 // CopySpecsToDirs is a helper that will add commands to the rule builder to copy the PackagingSpec
 // entries into corresponding directories.
-func (p *PackagingBase) CopySpecsToDirs(ctx ModuleContext, builder *RuleBuilder, dirsToSpecs map[WritablePath]map[string]PackagingSpec) (entries []string) {
+func (p *PackagingBase) CopySpecsToDirs(ctx ModuleContext, builder *RuleBuilder, dirsToSpecs map[WritablePath]map[string]PackagingSpec, preserveTimestamps bool) (entries []string) {
 	empty := true
 	for _, specs := range dirsToSpecs {
 		if len(specs) > 0 {
@@ -473,10 +616,6 @@
 	}
 
 	seenDir := make(map[string]bool)
-	preparerPath := PathForModuleOut(ctx, "preparer.sh")
-	cmd := builder.Command().Tool(preparerPath)
-	var sb strings.Builder
-	sb.WriteString("set -e\n")
 
 	dirs := make([]WritablePath, 0, len(dirsToSpecs))
 	for dir, _ := range dirsToSpecs {
@@ -495,22 +634,23 @@
 			entries = append(entries, ps.relPathInPackage)
 			if _, ok := seenDir[destDir]; !ok {
 				seenDir[destDir] = true
-				sb.WriteString(fmt.Sprintf("mkdir -p %s\n", destDir))
+				builder.Command().Textf("mkdir -p %s", destDir)
 			}
 			if ps.symlinkTarget == "" {
-				cmd.Implicit(ps.srcPath)
-				sb.WriteString(fmt.Sprintf("cp %s %s\n", ps.srcPath, destPath))
+				cmd := builder.Command().Text("cp")
+				if preserveTimestamps {
+					cmd.Flag("-p")
+				}
+				cmd.Input(ps.srcPath).Text(destPath)
 			} else {
-				sb.WriteString(fmt.Sprintf("ln -sf %s %s\n", ps.symlinkTarget, destPath))
+				builder.Command().Textf("ln -sf %s %s", ps.symlinkTarget, destPath)
 			}
 			if ps.executable {
-				sb.WriteString(fmt.Sprintf("chmod a+x %s\n", destPath))
+				builder.Command().Textf("chmod a+x %s", destPath)
 			}
 		}
 	}
 
-	WriteExecutableFileRuleVerbatim(ctx, preparerPath, sb.String())
-
 	return entries
 }
 
diff --git a/android/packaging_test.go b/android/packaging_test.go
index f5b1020..9c6760c 100644
--- a/android/packaging_test.go
+++ b/android/packaging_test.go
@@ -64,7 +64,7 @@
 	ModuleBase
 	PackagingBase
 	properties struct {
-		Install_deps []string `android:`
+		Install_deps []string
 	}
 	entries []string
 }
diff --git a/android/path_properties.go b/android/path_properties.go
index 6210aee..55a4dc0 100644
--- a/android/path_properties.go
+++ b/android/path_properties.go
@@ -18,6 +18,7 @@
 	"fmt"
 	"reflect"
 
+	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
 
@@ -27,7 +28,7 @@
 // to the output file of the referenced module.
 
 func registerPathDepsMutator(ctx RegisterMutatorsContext) {
-	ctx.BottomUp("pathdeps", pathDepsMutator).Parallel()
+	ctx.BottomUp("pathdeps", pathDepsMutator)
 }
 
 // The pathDepsMutator automatically adds dependencies on any module that is listed with the
@@ -38,20 +39,35 @@
 		// squashed into the real modules.
 		return
 	}
+	if !ctx.Module().Enabled(ctx) {
+		return
+	}
 	props := ctx.Module().base().GetProperties()
 	addPathDepsForProps(ctx, props)
 }
 
 func addPathDepsForProps(ctx BottomUpMutatorContext, props []interface{}) {
 	// Iterate through each property struct of the module extracting the contents of all properties
-	// tagged with `android:"path"`.
+	// tagged with `android:"path"` or one of the variant-specifying tags.
 	var pathProperties []string
+	var pathDeviceFirstProperties []string
+	var pathDeviceFirstPrefer32Properties []string
+	var pathDeviceCommonProperties []string
+	var pathCommonOsProperties []string
 	for _, ps := range props {
-		pathProperties = append(pathProperties, pathPropertiesForPropertyStruct(ctx, ps)...)
+		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")...)
+		pathDeviceCommonProperties = append(pathDeviceCommonProperties, taggedPropertiesForPropertyStruct(ctx, ps, "path_device_common")...)
+		pathCommonOsProperties = append(pathCommonOsProperties, taggedPropertiesForPropertyStruct(ctx, ps, "path_common_os")...)
 	}
 
 	// Remove duplicates to avoid multiple dependencies.
 	pathProperties = FirstUniqueStrings(pathProperties)
+	pathDeviceFirstProperties = FirstUniqueStrings(pathDeviceFirstProperties)
+	pathDeviceFirstPrefer32Properties = FirstUniqueStrings(pathDeviceFirstPrefer32Properties)
+	pathDeviceCommonProperties = FirstUniqueStrings(pathDeviceCommonProperties)
+	pathCommonOsProperties = FirstUniqueStrings(pathCommonOsProperties)
 
 	// Add dependencies to anything that is a module reference.
 	for _, s := range pathProperties {
@@ -59,12 +75,54 @@
 			ctx.AddDependency(ctx.Module(), sourceOrOutputDepTag(m, t), m)
 		}
 	}
+	// For properties tagged "path_device_first", use the first arch device variant when adding
+	// dependencies. This allows host modules to have some properties that add dependencies on
+	// device modules.
+	for _, s := range pathDeviceFirstProperties {
+		if m, t := SrcIsModuleWithTag(s); m != "" {
+			ctx.AddVariationDependencies(ctx.Config().AndroidFirstDeviceTarget.Variations(), sourceOrOutputDepTag(m, t), m)
+		}
+	}
+	// properties tagged path_device_first_prefer32 get the first 32 bit target if one is available,
+	// otherwise they use the first 64 bit target
+	if len(pathDeviceFirstPrefer32Properties) > 0 {
+		var targets []Target
+		if ctx.Config().IgnorePrefer32OnDevice() {
+			targets, _ = decodeMultilibTargets("first", ctx.Config().Targets[Android], false)
+		} else {
+			targets, _ = decodeMultilibTargets("first_prefer32", ctx.Config().Targets[Android], false)
+		}
+		if len(targets) == 0 {
+			ctx.ModuleErrorf("Could not find a first_prefer32 target")
+		} else {
+			for _, s := range pathDeviceFirstPrefer32Properties {
+				if m, t := SrcIsModuleWithTag(s); m != "" {
+					ctx.AddVariationDependencies(targets[0].Variations(), sourceOrOutputDepTag(m, t), m)
+				}
+			}
+		}
+	}
+	// properties tagged "path_device_common" get the device common variant
+	for _, s := range pathDeviceCommonProperties {
+		if m, t := SrcIsModuleWithTag(s); m != "" {
+			ctx.AddVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(), sourceOrOutputDepTag(m, t), m)
+		}
+	}
+	// properties tagged "path_common_os" get the CommonOs variant
+	for _, s := range pathCommonOsProperties {
+		if m, t := SrcIsModuleWithTag(s); m != "" {
+			ctx.AddVariationDependencies([]blueprint.Variation{
+				{Mutator: "os", Variation: "common_os"},
+				{Mutator: "arch", Variation: ""},
+			}, sourceOrOutputDepTag(m, t), m)
+		}
+	}
 }
 
-// pathPropertiesForPropertyStruct uses the indexes of properties that are tagged with
-// android:"path" to extract all their values from a property struct, returning them as a single
+// taggedPropertiesForPropertyStruct uses the indexes of properties that are tagged with
+// android:"tagValue" to extract all their values from a property struct, returning them as a single
 // slice of strings.
-func pathPropertiesForPropertyStruct(ctx BottomUpMutatorContext, ps interface{}) []string {
+func taggedPropertiesForPropertyStruct(ctx BottomUpMutatorContext, ps interface{}, tagValue string) []string {
 	v := reflect.ValueOf(ps)
 	if v.Kind() != reflect.Ptr || v.Elem().Kind() != reflect.Struct {
 		panic(fmt.Errorf("type %s is not a pointer to a struct", v.Type()))
@@ -79,7 +137,7 @@
 	v = v.Elem()
 
 	// Get or create the list of indexes of properties that are tagged with `android:"path"`.
-	pathPropertyIndexes := pathPropertyIndexesForPropertyStruct(ps)
+	pathPropertyIndexes := taggedPropertyIndexesForPropertyStruct(ps, tagValue)
 
 	var ret []string
 
@@ -172,12 +230,20 @@
 
 var pathPropertyIndexesCache OncePer
 
-// pathPropertyIndexesForPropertyStruct returns a list of all of the indexes of properties in
-// property struct type that are tagged with `android:"path"`.  Each index is a []int suitable for
-// passing to reflect.Value.FieldByIndex.  The value is cached in a global cache by type.
-func pathPropertyIndexesForPropertyStruct(ps interface{}) [][]int {
-	key := NewCustomOnceKey(reflect.TypeOf(ps))
+// taggedPropertyIndexesForPropertyStruct returns a list of all of the indexes of properties in
+// property struct type that are tagged with `android:"tagValue"`.  Each index is a []int suitable
+// for passing to reflect.Value.FieldByIndex.  The value is cached in a global cache by type and
+// tagValue.
+func taggedPropertyIndexesForPropertyStruct(ps interface{}, tagValue string) [][]int {
+	type pathPropertyIndexesOnceKey struct {
+		propStructType reflect.Type
+		tagValue       string
+	}
+	key := NewCustomOnceKey(pathPropertyIndexesOnceKey{
+		propStructType: reflect.TypeOf(ps),
+		tagValue:       tagValue,
+	})
 	return pathPropertyIndexesCache.Once(key, func() interface{} {
-		return proptools.PropertyIndexesWithTag(ps, "android", "path")
+		return proptools.PropertyIndexesWithTag(ps, "android", tagValue)
 	}).([][]int)
 }
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 9c2df65..1c0321c 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -24,6 +24,7 @@
 	"strings"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/gobtools"
 	"github.com/google/blueprint/pathtools"
 )
 
@@ -90,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
 }
@@ -116,6 +119,9 @@
 	InstallInOdm() bool
 	InstallInProduct() bool
 	InstallInVendor() bool
+	InstallInSystemDlkm() bool
+	InstallInVendorDlkm() bool
+	InstallInOdmDlkm() bool
 	InstallForceOS() (*OsType, *ArchType)
 }
 
@@ -169,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()
 }
@@ -368,11 +386,11 @@
 }
 
 func (p OptionalPath) GobEncode() ([]byte, error) {
-	return blueprint.CustomGobEncode[optionalPathGob](&p)
+	return gobtools.CustomGobEncode[optionalPathGob](&p)
 }
 
 func (p *OptionalPath) GobDecode(data []byte) error {
-	return blueprint.CustomGobDecode[optionalPathGob](data, p)
+	return gobtools.CustomGobDecode[optionalPathGob](data, p)
 }
 
 // Valid returns whether there is a valid path
@@ -550,6 +568,78 @@
 	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.
+func DirectoryPathsForModuleSrc(ctx ModuleMissingDepsPathContext, paths []string) DirectoryPaths {
+	var ret DirectoryPaths
+
+	for _, path := range paths {
+		if m, t := SrcIsModuleWithTag(path); m != "" {
+			module := GetModuleProxyFromPathDep(ctx, m, t)
+			if module == nil {
+				ctx.ModuleErrorf(`missing dependency on %q, is the property annotated with android:"path"?`, m)
+				continue
+			}
+			if t != "" {
+				ctx.ModuleErrorf("DirProvider dependency %q does not support the tag %q", module, t)
+				continue
+			}
+			mctx, ok := ctx.(OtherModuleProviderContext)
+			if !ok {
+				panic(fmt.Errorf("%s is not an OtherModuleProviderContext", ctx))
+			}
+			if dirProvider, ok := OtherModuleProvider(mctx, *module, DirProvider); ok {
+				ret = append(ret, dirProvider.Dirs...)
+			} else {
+				ReportPathErrorf(ctx, "module %q does not implement DirProvider", module)
+			}
+		} else {
+			p := pathForModuleSrc(ctx, path)
+			if isDir, err := ctx.Config().fs.IsDir(p.String()); err != nil {
+				ReportPathErrorf(ctx, "%s: %s", p, err.Error())
+			} else if !isDir {
+				ReportPathErrorf(ctx, "module directory path %q is not a directory", p)
+			} else {
+				ret = append(ret, &directoryPath{basePath{path: p.path, rel: p.rel}})
+			}
+		}
+	}
+
+	seen := make(map[DirectoryPath]bool, len(ret))
+	for _, path := range ret {
+		if seen[path] {
+			ReportPathErrorf(ctx, "duplicated path %q", path)
+		}
+		seen[path] = true
+	}
+	return ret
+}
+
 // OutputPaths is a slice of OutputPath objects, with helpers to operate on the collection.
 type OutputPaths []OutputPath
 
@@ -581,14 +671,15 @@
 // If the dependency is not found, a missingErrorDependency is returned.
 // If the module dependency is not a SourceFileProducer or OutputFileProducer, appropriate errors will be returned.
 func getPathsFromModuleDep(ctx ModuleWithDepsPathContext, path, moduleName, tag string) (Paths, error) {
-	module := GetModuleFromPathDep(ctx, moduleName, tag)
+	module := GetModuleProxyFromPathDep(ctx, moduleName, tag)
 	if module == nil {
 		return nil, missingDependencyError{[]string{moduleName}}
 	}
-	if aModule, ok := module.(Module); ok && !aModule.Enabled(ctx) {
+	if !OtherModuleProviderOrDefault(ctx, *module, CommonModuleInfoKey).Enabled {
 		return nil, missingDependencyError{[]string{moduleName}}
 	}
-	outputFiles, err := outputFilesForModule(ctx, module, tag)
+
+	outputFiles, err := outputFilesForModule(ctx, *module, tag)
 	if outputFiles != nil && err == nil {
 		return outputFiles, nil
 	} else {
@@ -596,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.
 //
@@ -606,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
@@ -1105,11 +1217,11 @@
 }
 
 func (p basePath) GobEncode() ([]byte, error) {
-	return blueprint.CustomGobEncode[basePathGob](&p)
+	return gobtools.CustomGobEncode[basePathGob](&p)
 }
 
 func (p *basePath) GobDecode(data []byte) error {
-	return blueprint.CustomGobDecode[basePathGob](data, p)
+	return gobtools.CustomGobDecode[basePathGob](data, p)
 }
 
 func (p basePath) Ext() string {
@@ -1241,7 +1353,7 @@
 
 // PathForArbitraryOutput creates a path for the given components. Unlike PathForOutput,
 // the path is relative to the root of the output folder, not the out/soong folder.
-func PathForArbitraryOutput(ctx PathContext, pathComponents ...string) Path {
+func PathForArbitraryOutput(ctx PathContext, pathComponents ...string) WritablePath {
 	path, err := validatePath(pathComponents...)
 	if err != nil {
 		reportPathError(ctx, err)
@@ -1325,33 +1437,6 @@
 	return p.withRel(path)
 }
 
-// OverlayPath returns the overlay for `path' if it exists. This assumes that the
-// SourcePath is the path to a resource overlay directory.
-func (p SourcePath) OverlayPath(ctx ModuleMissingDepsPathContext, path Path) OptionalPath {
-	var relDir string
-	if srcPath, ok := path.(SourcePath); ok {
-		relDir = srcPath.path
-	} else {
-		ReportPathErrorf(ctx, "Cannot find relative path for %s(%s)", reflect.TypeOf(path).Name(), path)
-		// No need to put the error message into the returned path since it has been reported already.
-		return OptionalPath{}
-	}
-	dir := filepath.Join(p.path, relDir)
-	// Use Glob so that we are run again if the directory is added.
-	if pathtools.IsGlob(dir) {
-		ReportPathErrorf(ctx, "Path may not contain a glob: %s", dir)
-	}
-	paths, err := ctx.GlobWithDeps(dir, nil)
-	if err != nil {
-		ReportPathErrorf(ctx, "glob: %s", err.Error())
-		return OptionalPath{}
-	}
-	if len(paths) == 0 {
-		return InvalidOptionalPath(dir + " does not exist")
-	}
-	return OptionalPathForPath(PathForSource(ctx, paths[0]))
-}
-
 // OutputPath is a Path representing an intermediates file path rooted from the build directory
 type OutputPath struct {
 	basePath
@@ -1363,31 +1448,31 @@
 }
 
 type outputPathGob struct {
-	basePath
+	BasePath basePath
 	OutDir   string
 	FullPath string
 }
 
 func (p *OutputPath) ToGob() *outputPathGob {
 	return &outputPathGob{
-		basePath: p.basePath,
+		BasePath: p.basePath,
 		OutDir:   p.outDir,
 		FullPath: p.fullPath,
 	}
 }
 
 func (p *OutputPath) FromGob(data *outputPathGob) {
-	p.basePath = data.basePath
+	p.basePath = data.BasePath
 	p.outDir = data.OutDir
 	p.fullPath = data.FullPath
 }
 
 func (p OutputPath) GobEncode() ([]byte, error) {
-	return blueprint.CustomGobEncode[outputPathGob](&p)
+	return gobtools.CustomGobEncode[outputPathGob](&p)
 }
 
 func (p *OutputPath) GobDecode(data []byte) error {
-	return blueprint.CustomGobDecode[outputPathGob](data, p)
+	return gobtools.CustomGobDecode[outputPathGob](data, p)
 }
 
 func (p OutputPath) withRel(rel string) OutputPath {
@@ -1788,7 +1873,7 @@
 }
 
 type installPathGob struct {
-	basePath
+	BasePath     basePath
 	SoongOutDir  string
 	PartitionDir string
 	Partition    string
@@ -1798,7 +1883,7 @@
 
 func (p *InstallPath) ToGob() *installPathGob {
 	return &installPathGob{
-		basePath:     p.basePath,
+		BasePath:     p.basePath,
 		SoongOutDir:  p.soongOutDir,
 		PartitionDir: p.partitionDir,
 		Partition:    p.partition,
@@ -1808,7 +1893,7 @@
 }
 
 func (p *InstallPath) FromGob(data *installPathGob) {
-	p.basePath = data.basePath
+	p.basePath = data.BasePath
 	p.soongOutDir = data.SoongOutDir
 	p.partitionDir = data.PartitionDir
 	p.partition = data.Partition
@@ -1817,11 +1902,11 @@
 }
 
 func (p InstallPath) GobEncode() ([]byte, error) {
-	return blueprint.CustomGobEncode[installPathGob](&p)
+	return gobtools.CustomGobEncode[installPathGob](&p)
 }
 
 func (p *InstallPath) GobDecode(data []byte) error {
-	return blueprint.CustomGobDecode[installPathGob](data, p)
+	return gobtools.CustomGobDecode[installPathGob](data, p)
 }
 
 // Will panic if called from outside a test environment.
@@ -1991,7 +2076,7 @@
 		reportPathError(ctx, err)
 	}
 
-	base := pathForPartitionInstallDir(ctx, partition, partitionPath, ctx.Config().KatiEnabled())
+	base := pathForPartitionInstallDir(ctx, partition, partitionPath, true)
 	return base.Join(ctx, pathComponents...)
 }
 
@@ -2004,6 +2089,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
@@ -2058,6 +2147,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"
 		}
@@ -2261,6 +2356,9 @@
 	inOdm           bool
 	inProduct       bool
 	inVendor        bool
+	inSystemDlkm    bool
+	inVendorDlkm    bool
+	inOdmDlkm       bool
 	forceOS         *OsType
 	forceArch       *ArchType
 }
@@ -2315,6 +2413,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
 }
@@ -2476,3 +2586,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..b125c4e 100644
--- a/android/paths_test.go
+++ b/android/paths_test.go
@@ -1269,7 +1269,7 @@
 				ExtendWithErrorHandler(errorHandler).
 				RunTest(t)
 
-			m := result.ModuleForTests("foo", "").Module().(*pathForModuleSrcTestModule)
+			m := result.ModuleForTests(t, "foo", "").Module().(*pathForModuleSrcTestModule)
 
 			AssertStringPathsRelativeToTopEquals(t, "srcs", result.Config, test.srcs, m.srcs)
 			AssertStringPathsRelativeToTopEquals(t, "rels", result.Config, test.rels, m.rels)
@@ -1533,13 +1533,13 @@
 		FixtureWithRootAndroidBp(bp),
 	).RunTest(t)
 
-	foo := result.ModuleForTests("foo", "").Module().(*pathForModuleSrcTestModule)
+	foo := result.ModuleForTests(t, "foo", "").Module().(*pathForModuleSrcTestModule)
 
 	AssertArrayString(t, "foo missing deps", []string{"a", "b", "c"}, foo.missingDeps)
 	AssertArrayString(t, "foo srcs", []string{}, foo.srcs)
 	AssertStringEquals(t, "foo src", "", foo.src)
 
-	bar := result.ModuleForTests("bar", "").Module().(*pathForModuleSrcTestModule)
+	bar := result.ModuleForTests(t, "bar", "").Module().(*pathForModuleSrcTestModule)
 
 	AssertArrayString(t, "bar missing deps", []string{"d", "e"}, bar.missingDeps)
 	AssertArrayString(t, "bar srcs", []string{}, bar.srcs)
@@ -1561,7 +1561,7 @@
 
 	t.Run("install for soong", func(t *testing.T) {
 		p := PathForModuleInstall(ctx, "install/path")
-		AssertPathRelativeToTopEquals(t, "install path for soong", "out/soong/target/product/test_device/system/install/path", p)
+		AssertPathRelativeToTopEquals(t, "install path for soong", "out/target/product/test_device/system/install/path", p)
 	})
 	t.Run("install for make", func(t *testing.T) {
 		p := PathForModuleInstall(ctx, "install/path")
@@ -1584,7 +1584,7 @@
 		}
 
 		expected := []string{
-			"out/soong/target/product/test_device/system/install/path",
+			"out/target/product/test_device/system/install/path",
 			"out/soong/output/path",
 			"source/path",
 		}
@@ -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/phony.go b/android/phony.go
index f8db88d..7bdd9d3 100644
--- a/android/phony.go
+++ b/android/phony.go
@@ -15,6 +15,7 @@
 package android
 
 import (
+	"strings"
 	"sync"
 
 	"github.com/google/blueprint"
@@ -68,13 +69,25 @@
 	}
 
 	if !ctx.Config().KatiEnabled() {
+		// In soong-only builds, the phonies can conflict with dist targets that will
+		// be generated in the packaging step. Instead of emitting a blueprint/ninja phony directly,
+		// create a makefile that defines the phonies that will be included in the packaging step.
+		// Make will dedup the phonies there.
+		var buildPhonyFileContents strings.Builder
 		for _, phony := range p.phonyList {
-			ctx.Build(pctx, BuildParams{
-				Rule:      blueprint.Phony,
-				Outputs:   []WritablePath{PathForPhony(ctx, phony)},
-				Implicits: p.phonyMap[phony],
-			})
+			buildPhonyFileContents.WriteString(".PHONY: ")
+			buildPhonyFileContents.WriteString(phony)
+			buildPhonyFileContents.WriteString("\n")
+			buildPhonyFileContents.WriteString(phony)
+			buildPhonyFileContents.WriteString(":")
+			for _, dep := range p.phonyMap[phony] {
+				buildPhonyFileContents.WriteString(" ")
+				buildPhonyFileContents.WriteString(dep.String())
+			}
+			buildPhonyFileContents.WriteString("\n")
 		}
+		buildPhonyFile := PathForOutput(ctx, "soong_phony_targets.mk")
+		writeValueIfChanged(ctx, absolutePath(buildPhonyFile.String()), buildPhonyFileContents.String())
 	}
 }
 
diff --git a/android/prebuilt.go b/android/prebuilt.go
index 4f04d05..6b076b7 100644
--- a/android/prebuilt.go
+++ b/android/prebuilt.go
@@ -28,6 +28,7 @@
 
 func RegisterPrebuiltMutators(ctx RegistrationContext) {
 	ctx.PreArchMutators(RegisterPrebuiltsPreArchMutators)
+	ctx.PreDepsMutators(RegisterPrebuiltsPreDepsMutators)
 	ctx.PostDepsMutators(RegisterPrebuiltsPostDepsMutators)
 }
 
@@ -195,6 +196,10 @@
 	return p.properties.UsePrebuilt
 }
 
+func (p *Prebuilt) SetUsePrebuilt(use bool) {
+	p.properties.UsePrebuilt = use
+}
+
 // Called to provide the srcs value for the prebuilt module.
 //
 // This can be called with a context for any module not just the prebuilt one itself. It can also be
@@ -272,6 +277,25 @@
 	InitPrebuiltModuleWithSrcSupplier(module, srcsSupplier, "srcs")
 }
 
+// InitConfigurablePrebuiltModuleString is the same as InitPrebuiltModule, but uses a
+// Configurable string property instead of a regular list of strings. It only produces a single
+// source file.
+func InitConfigurablePrebuiltModuleString(module PrebuiltInterface, srcs *proptools.Configurable[string], propertyName string) {
+	if srcs == nil {
+		panic(fmt.Errorf("%s must not be nil", propertyName))
+	}
+
+	srcsSupplier := func(ctx BaseModuleContext, _ Module) []string {
+		src := srcs.GetOrDefault(ctx, "")
+		if src == "" {
+			return nil
+		}
+		return []string{src}
+	}
+
+	InitPrebuiltModuleWithSrcSupplier(module, srcsSupplier, propertyName)
+}
+
 func InitSingleSourcePrebuiltModule(module PrebuiltInterface, srcProps interface{}, srcField string) {
 	srcPropsValue := reflect.ValueOf(srcProps).Elem()
 	srcStructField, _ := srcPropsValue.Type().FieldByName(srcField)
@@ -362,10 +386,10 @@
 // the right module. This function is only safe to call after all TransitionMutators
 // have run, e.g. in GenerateAndroidBuildActions.
 func PrebuiltGetPreferred(ctx BaseModuleContext, module Module) Module {
-	if !module.IsReplacedByPrebuilt() {
+	if !OtherModuleProviderOrDefault(ctx, module, CommonModuleInfoKey).ReplacedByPrebuilt {
 		return module
 	}
-	if IsModulePrebuilt(module) {
+	if _, ok := OtherModuleProvider(ctx, module, PrebuiltModuleInfoProvider); ok {
 		// If we're given a prebuilt then assume there's no source module around.
 		return module
 	}
@@ -373,11 +397,11 @@
 	sourceModDepFound := false
 	var prebuiltMod Module
 
-	ctx.WalkDeps(func(child, parent Module) bool {
+	ctx.WalkDepsProxy(func(child, parent ModuleProxy) bool {
 		if prebuiltMod != nil {
 			return false
 		}
-		if parent == ctx.Module() {
+		if ctx.EqualModules(parent, ctx.Module()) {
 			// First level: Only recurse if the module is found as a direct dependency.
 			sourceModDepFound = child == module
 			return sourceModDepFound
@@ -400,13 +424,16 @@
 }
 
 func RegisterPrebuiltsPreArchMutators(ctx RegisterMutatorsContext) {
-	ctx.BottomUp("prebuilt_rename", PrebuiltRenameMutator).Parallel()
+	ctx.BottomUp("prebuilt_rename", PrebuiltRenameMutator).UsesRename()
+}
+
+func RegisterPrebuiltsPreDepsMutators(ctx RegisterMutatorsContext) {
+	ctx.BottomUp("prebuilt_source", PrebuiltSourceDepsMutator).UsesReverseDependencies()
+	ctx.BottomUp("prebuilt_select", PrebuiltSelectModuleMutator)
 }
 
 func RegisterPrebuiltsPostDepsMutators(ctx RegisterMutatorsContext) {
-	ctx.BottomUp("prebuilt_source", PrebuiltSourceDepsMutator).Parallel()
-	ctx.BottomUp("prebuilt_select", PrebuiltSelectModuleMutator).Parallel()
-	ctx.BottomUp("prebuilt_postdeps", PrebuiltPostDepsMutator).Parallel()
+	ctx.BottomUp("prebuilt_postdeps", PrebuiltPostDepsMutator).UsesReplaceDependencies()
 }
 
 // Returns the name of the source module corresponding to a prebuilt module
@@ -449,7 +476,7 @@
 			bmn, _ := m.(baseModuleName)
 			name := bmn.BaseModuleName()
 			if ctx.OtherModuleReverseDependencyVariantExists(name) {
-				ctx.AddReverseDependency(ctx.Module(), PrebuiltDepTag, name)
+				ctx.AddReverseVariationDependency(nil, PrebuiltDepTag, name)
 				p.properties.SourceExists = true
 			}
 		}
@@ -550,6 +577,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
@@ -584,6 +612,13 @@
 	}
 }
 
+func IsDontReplaceSourceWithPrebuiltTag(tag blueprint.DependencyTag) bool {
+	if t, ok := tag.(ReplaceSourceWithPrebuilt); ok {
+		return !t.ReplaceSourceWithPrebuilt()
+	}
+	return false
+}
+
 // PrebuiltPostDepsMutator replaces dependencies on the source module with dependencies on the
 // prebuilt when both modules exist and the prebuilt should be used.  When the prebuilt should not
 // be used, disable installing it.
@@ -620,6 +655,7 @@
 			}
 		} else {
 			m.HideFromMake()
+			m.SkipInstall()
 		}
 	}
 }
@@ -677,7 +713,7 @@
 //
 // Even though this is a cc_prebuilt_library_shared, we create both the variants today
 // https://source.corp.google.com/h/googleplex-android/platform/build/soong/+/e08e32b45a18a77bc3c3e751f730539b1b374f1b:cc/library.go;l=2113-2116;drc=2c4a9779cd1921d0397a12b3d3521f4c9b30d747;bpv=1;bpt=0
-func (p *Prebuilt) variantIsDisabled(ctx BaseMutatorContext, prebuilt Module) bool {
+func (p *Prebuilt) variantIsDisabled(ctx BaseModuleContext, prebuilt Module) bool {
 	return p.srcsSupplier != nil && len(p.srcsSupplier(ctx, prebuilt)) == 0
 }
 
@@ -687,7 +723,7 @@
 
 // usePrebuilt returns true if a prebuilt should be used instead of the source module.  The prebuilt
 // will be used if it is marked "prefer" or if the source module is disabled.
-func (p *Prebuilt) usePrebuilt(ctx BaseMutatorContext, source Module, prebuilt Module) bool {
+func (p *Prebuilt) usePrebuilt(ctx BaseModuleContext, source Module, prebuilt Module) bool {
 	isMainlinePrebuilt := func(prebuilt Module) bool {
 		apex, ok := prebuilt.(apexVariationName)
 		if !ok {
diff --git a/android/prebuilt_test.go b/android/prebuilt_test.go
index 5e4af0b..27a68fb 100644
--- a/android/prebuilt_test.go
+++ b/android/prebuilt_test.go
@@ -335,7 +335,7 @@
 			).RunTestWithBp(t, bp)
 
 			for _, variant := range result.ModuleVariantsForTests("foo") {
-				foo := result.ModuleForTests("foo", variant)
+				foo := result.ModuleForTests(t, "foo", variant)
 				t.Run(foo.Module().Target().Os.String(), func(t *testing.T) {
 					var dependsOnSourceModule, dependsOnPrebuiltModule bool
 					result.VisitDirectDeps(foo.Module(), func(m blueprint.Module) {
@@ -508,11 +508,10 @@
 }
 
 func (p *prebuiltModule) GenerateAndroidBuildActions(ctx ModuleContext) {
-	var src Path
 	if len(p.properties.Srcs) >= 1 {
-		src = p.prebuilt.SingleSourcePath(ctx)
+		src := p.prebuilt.SingleSourcePath(ctx)
+		ctx.SetOutputFiles(Paths{src}, "")
 	}
-	ctx.SetOutputFiles(Paths{src}, "")
 }
 
 func (p *prebuiltModule) Prebuilt() *Prebuilt {
diff --git a/android/product_config.go b/android/product_config.go
index ce3acc9..850f003 100644
--- a/android/product_config.go
+++ b/android/product_config.go
@@ -32,7 +32,7 @@
 		ctx.ModuleErrorf("There can only be one product_config module in build/soong")
 		return
 	}
-	outputFilePath := PathForModuleOut(ctx, p.Name()+".json").OutputPath
+	outputFilePath := PathForModuleOut(ctx, p.Name()+".json")
 
 	// DeviceProduct can be null so calling ctx.Config().DeviceProduct() may cause null dereference
 	targetProduct := proptools.String(ctx.Config().config.productVariables.DeviceProduct)
diff --git a/android/product_config_to_bp.go b/android/product_config_to_bp.go
deleted file mode 100644
index 680328f..0000000
--- a/android/product_config_to_bp.go
+++ /dev/null
@@ -1,35 +0,0 @@
-// 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
-
-func init() {
-	ctx := InitRegistrationContext
-	ctx.RegisterParallelSingletonType("product_config_to_bp_singleton", productConfigToBpSingletonFactory)
-}
-
-type productConfigToBpSingleton struct{}
-
-func (s *productConfigToBpSingleton) GenerateBuildActions(ctx SingletonContext) {
-	// TODO: update content from make-based product config
-	var content string
-	generatedBp := PathForOutput(ctx, "soong_generated_product_config.bp")
-	WriteFileRule(ctx, generatedBp, content)
-	ctx.Phony("product_config_to_bp", generatedBp)
-}
-
-// productConfigToBpSingleton generates a bp file from make-based product config
-func productConfigToBpSingletonFactory() Singleton {
-	return &productConfigToBpSingleton{}
-}
diff --git a/android/product_packages_file.go b/android/product_packages_file.go
new file mode 100644
index 0000000..c7c18a2
--- /dev/null
+++ b/android/product_packages_file.go
@@ -0,0 +1,39 @@
+// 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 (
+	"strings"
+)
+
+func init() {
+	RegisterParallelSingletonType("product_packages_file_singleton", productPackagesFileSingletonFactory)
+}
+
+func productPackagesFileSingletonFactory() Singleton {
+	return &productPackagesFileSingleton{}
+}
+
+type productPackagesFileSingleton struct{}
+
+func (s *productPackagesFileSingleton) GenerateBuildActions(ctx SingletonContext) {
+	// There's no HasDeviceName() function, but the device name and device product should always
+	// both be present or not.
+	if ctx.Config().HasDeviceProduct() {
+		productPackages := ctx.Config().productVariables.PartitionVarsForSoongMigrationOnlyDoNotUse.ProductPackages
+		output := PathForArbitraryOutput(ctx, "target", "product", ctx.Config().DeviceName(), "product_packages.txt")
+		WriteFileRule(ctx, output, strings.Join(productPackages, "\n"))
+	}
+}
diff --git a/android/proto.go b/android/proto.go
index 0d8e097..91d6732 100644
--- a/android/proto.go
+++ b/android/proto.go
@@ -74,14 +74,14 @@
 		flags = append(flags, JoinWithPrefix(rootProtoIncludeDirs.Strings(), "-I"))
 	}
 
-	ctx.VisitDirectDepsWithTag(ProtoPluginDepTag, func(dep Module) {
-		if hostTool, ok := dep.(HostToolProvider); !ok || !hostTool.HostToolPath().Valid() {
+	ctx.VisitDirectDepsProxyWithTag(ProtoPluginDepTag, func(dep ModuleProxy) {
+		if h, ok := OtherModuleProvider(ctx, dep, HostToolProviderInfoProvider); !ok || !h.HostToolPath.Valid() {
 			ctx.PropertyErrorf("proto.plugin", "module %q is not a host tool provider",
 				ctx.OtherModuleName(dep))
 		} else {
 			plugin := String(p.Proto.Plugin)
-			deps = append(deps, hostTool.HostToolPath().Path())
-			flags = append(flags, "--plugin=protoc-gen-"+plugin+"="+hostTool.HostToolPath().String())
+			deps = append(deps, h.HostToolPath.Path())
+			flags = append(flags, "--plugin=protoc-gen-"+plugin+"="+h.HostToolPath.String())
 		}
 	})
 
diff --git a/android/provider.go b/android/provider.go
index 81d17a1..b48fd91 100644
--- a/android/provider.go
+++ b/android/provider.go
@@ -4,8 +4,8 @@
 	"github.com/google/blueprint"
 )
 
-// OtherModuleProviderContext is a helper interface that is a subset of ModuleContext, BottomUpMutatorContext, or
-// TopDownMutatorContext for use in OtherModuleProvider.
+// OtherModuleProviderContext is a helper interface that is a subset of ModuleContext or BottomUpMutatorContext
+// for use in OtherModuleProvider.
 type OtherModuleProviderContext interface {
 	otherModuleProvider(m blueprint.Module, provider blueprint.AnyProviderKey) (any, bool)
 }
@@ -13,7 +13,6 @@
 var _ OtherModuleProviderContext = BaseModuleContext(nil)
 var _ OtherModuleProviderContext = ModuleContext(nil)
 var _ OtherModuleProviderContext = BottomUpMutatorContext(nil)
-var _ OtherModuleProviderContext = TopDownMutatorContext(nil)
 var _ OtherModuleProviderContext = SingletonContext(nil)
 var _ OtherModuleProviderContext = (*TestContext)(nil)
 
@@ -21,8 +20,7 @@
 // returned and the boolean is true.  If it has not been set the zero value of the provider's type  is returned
 // and the boolean is false.  The value returned may be a deep copy of the value originally passed to SetProvider.
 //
-// OtherModuleProviderContext is a helper interface that accepts ModuleContext, BottomUpMutatorContext, or
-// TopDownMutatorContext.
+// OtherModuleProviderContext is a helper interface that accepts ModuleContext or BottomUpMutatorContext.
 func OtherModuleProvider[K any](ctx OtherModuleProviderContext, module blueprint.Module, provider blueprint.ProviderKey[K]) (K, bool) {
 	value, ok := ctx.otherModuleProvider(getWrappedModule(module), provider)
 	if !ok {
@@ -37,8 +35,8 @@
 	return value
 }
 
-// ModuleProviderContext is a helper interface that is a subset of ModuleContext, BottomUpMutatorContext, or
-// TopDownMutatorContext for use in ModuleProvider.
+// ModuleProviderContext is a helper interface that is a subset of ModuleContext or BottomUpMutatorContext
+// for use in ModuleProvider.
 type ModuleProviderContext interface {
 	provider(provider blueprint.AnyProviderKey) (any, bool)
 }
@@ -46,14 +44,12 @@
 var _ ModuleProviderContext = BaseModuleContext(nil)
 var _ ModuleProviderContext = ModuleContext(nil)
 var _ ModuleProviderContext = BottomUpMutatorContext(nil)
-var _ ModuleProviderContext = TopDownMutatorContext(nil)
 
 // ModuleProvider reads the provider for the current module.  If the provider has been set the value is
 // returned and the boolean is true.  If it has not been set the zero value of the provider's type  is returned
 // and the boolean is false.  The value returned may be a deep copy of the value originally passed to SetProvider.
 //
-// ModuleProviderContext is a helper interface that accepts ModuleContext, BottomUpMutatorContext, or
-// TopDownMutatorContext.
+// ModuleProviderContext is a helper interface that accepts ModuleContext or BottomUpMutatorContext.
 func ModuleProvider[K any](ctx ModuleProviderContext, provider blueprint.ProviderKey[K]) (K, bool) {
 	value, ok := ctx.provider(provider)
 	if !ok {
@@ -63,8 +59,8 @@
 	return value.(K), ok
 }
 
-// SetProviderContext is a helper interface that is a subset of ModuleContext, BottomUpMutatorContext, or
-// TopDownMutatorContext for use in SetProvider.
+// SetProviderContext is a helper interface that is a subset of ModuleContext or BottomUpMutatorContext
+// for use in SetProvider.
 type SetProviderContext interface {
 	setProvider(provider blueprint.AnyProviderKey, value any)
 }
@@ -72,15 +68,13 @@
 var _ SetProviderContext = BaseModuleContext(nil)
 var _ SetProviderContext = ModuleContext(nil)
 var _ SetProviderContext = BottomUpMutatorContext(nil)
-var _ SetProviderContext = TopDownMutatorContext(nil)
 
 // SetProvider sets the value for a provider for the current module.  It panics if not called
 // during the appropriate mutator or GenerateBuildActions pass for the provider, if the value
 // is not of the appropriate type, or if the value has already been set.  The value should not
 // be modified after being passed to SetProvider.
 //
-// SetProviderContext is a helper interface that accepts ModuleContext, BottomUpMutatorContext, or
-// TopDownMutatorContext.
+// SetProviderContext is a helper interface that accepts ModuleContext or BottomUpMutatorContext.
 func SetProvider[K any](ctx SetProviderContext, provider blueprint.ProviderKey[K], value K) {
 	ctx.setProvider(provider, value)
 }
diff --git a/android/raw_files.go b/android/raw_files.go
index 9d7f5e8..fd37196 100644
--- a/android/raw_files.go
+++ b/android/raw_files.go
@@ -18,7 +18,6 @@
 	"crypto/sha1"
 	"encoding/hex"
 	"fmt"
-	"github.com/google/blueprint"
 	"io"
 	"io/fs"
 	"os"
@@ -26,25 +25,27 @@
 	"strings"
 	"testing"
 
+	"github.com/google/blueprint"
+
 	"github.com/google/blueprint/proptools"
 )
 
 // WriteFileRule creates a ninja rule to write contents to a file by immediately writing the
 // contents, plus a trailing newline, to a file in out/soong/raw-${TARGET_PRODUCT}, and then creating
 // a ninja rule to copy the file into place.
-func WriteFileRule(ctx BuilderContext, outputFile WritablePath, content string) {
-	writeFileRule(ctx, outputFile, content, true, false)
+func WriteFileRule(ctx BuilderContext, outputFile WritablePath, content string, validations ...Path) {
+	writeFileRule(ctx, outputFile, content, true, false, validations)
 }
 
 // WriteFileRuleVerbatim creates a ninja rule to write contents to a file by immediately writing the
 // contents to a file in out/soong/raw-${TARGET_PRODUCT}, and then creating a ninja rule to copy the file into place.
-func WriteFileRuleVerbatim(ctx BuilderContext, outputFile WritablePath, content string) {
-	writeFileRule(ctx, outputFile, content, false, false)
+func WriteFileRuleVerbatim(ctx BuilderContext, outputFile WritablePath, content string, validations ...Path) {
+	writeFileRule(ctx, outputFile, content, false, false, validations)
 }
 
 // WriteExecutableFileRuleVerbatim is the same as WriteFileRuleVerbatim, but runs chmod +x on the result
-func WriteExecutableFileRuleVerbatim(ctx BuilderContext, outputFile WritablePath, content string) {
-	writeFileRule(ctx, outputFile, content, false, true)
+func WriteExecutableFileRuleVerbatim(ctx BuilderContext, outputFile WritablePath, content string, validations ...Path) {
+	writeFileRule(ctx, outputFile, content, false, true, validations)
 }
 
 // tempFile provides a testable wrapper around a file in out/soong/.temp.  It writes to a temporary file when
@@ -124,7 +125,7 @@
 	return tempFile, hex.EncodeToString(hash.Sum(nil))
 }
 
-func writeFileRule(ctx BuilderContext, outputFile WritablePath, content string, newline bool, executable bool) {
+func writeFileRule(ctx BuilderContext, outputFile WritablePath, content string, newline bool, executable bool, validations Paths) {
 	// Write the contents to a temporary file while computing its hash.
 	tempFile, hash := writeContentToTempFileAndHash(ctx, content, newline)
 
@@ -186,6 +187,7 @@
 		Input:       rawPath,
 		Output:      outputFile,
 		Description: "raw " + outputFile.Base(),
+		Validations: validations,
 	})
 }
 
diff --git a/android/recovery_build_prop.go b/android/recovery_build_prop.go
new file mode 100644
index 0000000..ac7d2ec
--- /dev/null
+++ b/android/recovery_build_prop.go
@@ -0,0 +1,113 @@
+// 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 "github.com/google/blueprint/proptools"
+
+func init() {
+	RegisterModuleType("recovery_build_prop", RecoveryBuildPropModuleFactory)
+}
+
+type recoveryBuildPropProperties struct {
+	// Path to the system build.prop file
+	System_build_prop *string `android:"path"`
+
+	// Path to the vendor build.prop file
+	Vendor_build_prop *string `android:"path"`
+
+	// Path to the odm build.prop file
+	Odm_build_prop *string `android:"path"`
+
+	// Path to the product build.prop file
+	Product_build_prop *string `android:"path"`
+
+	// Path to the system_ext build.prop file
+	System_ext_build_prop *string `android:"path"`
+}
+
+type recoveryBuildPropModule struct {
+	ModuleBase
+	properties recoveryBuildPropProperties
+
+	outputFilePath ModuleOutPath
+
+	installPath InstallPath
+}
+
+func RecoveryBuildPropModuleFactory() Module {
+	module := &recoveryBuildPropModule{}
+	module.AddProperties(&module.properties)
+	InitAndroidArchModule(module, DeviceSupported, MultilibCommon)
+	return module
+}
+
+// Overrides ctx.Module().InstallInRoot().
+// recovery_build_prop module always installs in root so that the prop.default
+// file is installed in recovery/root instead of recovery/root/system
+func (r *recoveryBuildPropModule) InstallInRoot() bool {
+	return true
+}
+
+func (r *recoveryBuildPropModule) appendRecoveryUIProperties(ctx ModuleContext, rule *RuleBuilder) {
+	rule.Command().Text("echo '#' >>").Output(r.outputFilePath)
+	rule.Command().Text("echo '# RECOVERY UI BUILD PROPERTIES' >>").Output(r.outputFilePath)
+	rule.Command().Text("echo '#' >>").Output(r.outputFilePath)
+
+	for propName, val := range ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.PrivateRecoveryUiProperties {
+		if len(val) > 0 {
+			rule.Command().
+				Textf("echo ro.recovery.ui.%s=%s >>", propName, val).
+				Output(r.outputFilePath)
+		}
+	}
+}
+
+func (r *recoveryBuildPropModule) getBuildProps(ctx ModuleContext) Paths {
+	var buildProps Paths
+	for _, buildProp := range []*string{
+		r.properties.System_build_prop,
+		r.properties.Vendor_build_prop,
+		r.properties.Odm_build_prop,
+		r.properties.Product_build_prop,
+		r.properties.System_ext_build_prop,
+	} {
+		if buildProp != nil {
+			if buildPropPath := PathForModuleSrc(ctx, proptools.String(buildProp)); buildPropPath != nil {
+				buildProps = append(buildProps, buildPropPath)
+			}
+		}
+	}
+	return buildProps
+}
+
+func (r *recoveryBuildPropModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	if !r.InstallInRecovery() {
+		ctx.ModuleErrorf("recovery_build_prop module must set `recovery` property to true")
+	}
+	r.outputFilePath = PathForModuleOut(ctx, ctx.ModuleName(), "prop.default")
+
+	// Replicates the logic in https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=2733;drc=0585bb1bcf4c89065adaf709f48acc8b869fd3ce
+	rule := NewRuleBuilder(pctx, ctx)
+	rule.Command().Text("rm").FlagWithOutput("-f ", r.outputFilePath)
+	rule.Command().Text("cat").
+		Inputs(r.getBuildProps(ctx)).
+		Text(">>").
+		Output(r.outputFilePath)
+	r.appendRecoveryUIProperties(ctx, rule)
+
+	rule.Build(ctx.ModuleName(), "generating recovery prop.default")
+	r.installPath = PathForModuleInstall(ctx)
+	ctx.InstallFile(r.installPath, "prop.default", r.outputFilePath)
+}
diff --git a/android/register.go b/android/register.go
index 2ce6025..10c9114 100644
--- a/android/register.go
+++ b/android/register.go
@@ -89,9 +89,15 @@
 type mutator struct {
 	name              string
 	bottomUpMutator   blueprint.BottomUpMutator
-	topDownMutator    blueprint.TopDownMutator
 	transitionMutator blueprint.TransitionMutator
-	parallel          bool
+
+	usesRename              bool
+	usesReverseDependencies bool
+	usesReplaceDependencies bool
+	usesCreateModule        bool
+	mutatesDependencies     bool
+	mutatesGlobalState      bool
+	neverFar                bool
 }
 
 var _ sortableComponent = &mutator{}
@@ -185,6 +191,11 @@
 func collateGloballyRegisteredSingletons() sortableComponents {
 	allSingletons := append(sortableComponents(nil), singletons...)
 	allSingletons = append(allSingletons,
+		// Soong only androidmk is registered later than other singletons in order to collect
+		// dist contributions from other singletons. This singleton is registered just before
+		// phony so that its phony rules can be collected by the phony singleton.
+		singleton{parallel: false, name: "soongonlyandroidmk", factory: soongOnlyAndroidMkSingletonFactory},
+
 		// Register phony just before makevars so it can write out its phony rules as Make rules
 		singleton{parallel: false, name: "phony", factory: phonySingletonFactory},
 
diff --git a/android/removed_package.go b/android/removed_package.go
new file mode 100644
index 0000000..aa54c2a
--- /dev/null
+++ b/android/removed_package.go
@@ -0,0 +1,60 @@
+package android
+
+import (
+	"fmt"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+func init() {
+	InitRegistrationContext.RegisterModuleType("removed_package", removedPackageModuleFactory)
+}
+
+type removedPackageModuleProps struct {
+	// The error message to display when this module is built. This is optional, there is a
+	// reasonable default message.
+	Message *string
+}
+
+type removedPackageModule struct {
+	ModuleBase
+	properties removedPackageModuleProps
+}
+
+// removed_package will cause a build failure when it's included in PRODUCT_PACKAGES. It's needed
+// because currently you can add non-existent packages to PRODUCT_PACKAGES, and the build will
+// not notice/complain, unless you opt-into enforcement via $(call enforce-product-packages-exist).
+// Opting into the enforcement is difficult in some cases, because a package exists on some source
+// trees but not on others. removed_package is an intermediate solution that allows you to remove
+// a package and still get an error if it remains in PRODUCT_PACKAGES somewhere.
+func removedPackageModuleFactory() Module {
+	m := &removedPackageModule{}
+	InitAndroidModule(m)
+	m.AddProperties(&m.properties)
+	return m
+}
+
+var removedPackageRule = pctx.AndroidStaticRule("removed_package", blueprint.RuleParams{
+	Command: "echo $message && false",
+}, "message")
+
+func (m *removedPackageModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	// Unchecked module so that checkbuild doesn't fail
+	ctx.UncheckedModule()
+
+	out := PathForModuleOut(ctx, "out.txt")
+	message := fmt.Sprintf("%s has been removed, and can no longer be used.", ctx.ModuleName())
+	if m.properties.Message != nil {
+		message = *m.properties.Message
+	}
+	ctx.Build(pctx, BuildParams{
+		Rule:   removedPackageRule,
+		Output: out,
+		Args: map[string]string{
+			"message": proptools.ShellEscape(message),
+		},
+	})
+
+	ctx.InstallFile(PathForModuleInstall(ctx, "removed_module"), ctx.ModuleName(), out)
+}
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 56de9cd..db56c3f 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -63,6 +63,7 @@
 	missingDeps      []string
 	args             map[string]string
 	nsjail           bool
+	nsjailKeepGendir bool
 	nsjailBasePath   WritablePath
 	nsjailImplicits  Paths
 }
@@ -208,6 +209,18 @@
 	return r
 }
 
+// By default, nsjail rules truncate outputDir and baseDir before running commands, similar to Sbox
+// rules which always run commands in a fresh sandbox. Calling NsjailKeepGendir keeps outputDir and
+// baseDir as-is, leaving previous artifacts. This is useful when the rules support incremental
+// builds.
+func (r *RuleBuilder) NsjailKeepGendir() *RuleBuilder {
+	if !r.nsjail {
+		panic("NsjailKeepGendir() must be called after Nsjail()")
+	}
+	r.nsjailKeepGendir = true
+	return r
+}
+
 // SandboxTools enables tool sandboxing for the rule by copying any referenced tools into the
 // sandbox.
 func (r *RuleBuilder) SandboxTools() *RuleBuilder {
@@ -488,21 +501,15 @@
 		Inputs(depFiles.Paths())
 }
 
-// BuildWithNinjaVars adds the built command line to the build graph, with dependencies on Inputs and Tools, and output files for
-// Outputs. This function will not escape Ninja variables, so it may be used to write sandbox manifests using Ninja variables.
-func (r *RuleBuilder) BuildWithUnescapedNinjaVars(name string, desc string) {
-	r.build(name, desc, false)
-}
-
 // Build adds the built command line to the build graph, with dependencies on Inputs and Tools, and output files for
 // Outputs.
 func (r *RuleBuilder) Build(name string, desc string) {
-	r.build(name, desc, true)
+	r.build(name, desc)
 }
 
 var sandboxEnvOnceKey = NewOnceKey("sandbox_environment_variables")
 
-func (r *RuleBuilder) build(name string, desc string, ninjaEscapeCommandString bool) {
+func (r *RuleBuilder) build(name string, desc string) {
 	name = ninjaNameEscape(name)
 
 	if len(r.missingDeps) > 0 {
@@ -561,8 +568,17 @@
 	if r.nsjail {
 		var nsjailCmd strings.Builder
 		nsjailPath := r.ctx.Config().PrebuiltBuildTool(r.ctx, "nsjail")
+		if !r.nsjailKeepGendir {
+			nsjailCmd.WriteString("rm -rf ")
+			nsjailCmd.WriteString(r.nsjailBasePath.String())
+			nsjailCmd.WriteRune(' ')
+			nsjailCmd.WriteString(r.outDir.String())
+			nsjailCmd.WriteString(" && ")
+		}
 		nsjailCmd.WriteString("mkdir -p ")
 		nsjailCmd.WriteString(r.nsjailBasePath.String())
+		nsjailCmd.WriteRune(' ')
+		nsjailCmd.WriteString(r.outDir.String())
 		nsjailCmd.WriteString(" && ")
 		nsjailCmd.WriteString(nsjailPath.String())
 		nsjailCmd.WriteRune(' ')
@@ -575,25 +591,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)
 			}
 		}
@@ -608,6 +627,7 @@
 		nsjailCmd.WriteString(" -m none:/tmp:tmpfs:size=1073741824") // 1GB, should be enough
 		nsjailCmd.WriteString(" -D nsjail_build_sandbox")
 		nsjailCmd.WriteString(" --disable_rlimits")
+		nsjailCmd.WriteString(" --skip_setsid") // ABFS relies on process-groups to track file operations
 		nsjailCmd.WriteString(" -q")
 		nsjailCmd.WriteString(" -- ")
 		nsjailCmd.WriteString("/bin/bash -c ")
@@ -761,30 +781,7 @@
 		if err != nil {
 			ReportPathErrorf(r.ctx, "sbox manifest failed to marshal: %q", err)
 		}
-		if ninjaEscapeCommandString {
-			WriteFileRule(r.ctx, r.sboxManifestPath, string(pbText))
-		} else {
-			// We need  to have a rule to write files that is
-			// defined on the RuleBuilder's pctx in order to
-			// write Ninja variables in the string.
-			// The WriteFileRule function above rule can only write
-			// raw strings because it is defined on the android
-			// package's pctx, and it can't access variables defined
-			// in another context.
-			r.ctx.Build(r.pctx, BuildParams{
-				Rule: r.ctx.Rule(r.pctx, "unescapedWriteFile", blueprint.RuleParams{
-					Command:        `rm -rf ${out} && cat ${out}.rsp > ${out}`,
-					Rspfile:        "${out}.rsp",
-					RspfileContent: "${content}",
-					Description:    "write file",
-				}, "content"),
-				Output:      r.sboxManifestPath,
-				Description: "write sbox manifest " + r.sboxManifestPath.Base(),
-				Args: map[string]string{
-					"content": string(pbText),
-				},
-			})
-		}
+		WriteFileRule(r.ctx, r.sboxManifestPath, string(pbText))
 
 		// Generate a new string to use as the command line of the sbox rule.  This uses
 		// a RuleBuilderCommand as a convenience method of building the command line, then
@@ -878,10 +875,20 @@
 		pool = localPool
 	}
 
-	if ninjaEscapeCommandString {
-		commandString = proptools.NinjaEscape(commandString)
+	// If the command length is getting close to linux's maximum, dump it to a file, which allows
+	// for longer commands.
+	if len(commandString) > 100000 {
+		hasher := sha256.New()
+		hasher.Write([]byte(output.String()))
+		script := PathForOutput(r.ctx, "rule_builder_scripts", fmt.Sprintf("%x.sh", hasher.Sum(nil)))
+		commandString = "set -eu\n\n" + commandString + "\n"
+		WriteExecutableFileRuleVerbatim(r.ctx, script, commandString)
+		inputs = append(inputs, script)
+		commandString = script.String()
 	}
 
+	commandString = proptools.NinjaEscape(commandString)
+
 	args_vars := make([]string, len(r.args))
 	i := 0
 	for k, _ := range r.args {
@@ -917,16 +924,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 +959,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 +1325,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/rule_builder_test.go b/android/rule_builder_test.go
index 6a8a964..5f3b9be 100644
--- a/android/rule_builder_test.go
+++ b/android/rule_builder_test.go
@@ -358,7 +358,7 @@
 			"command3 input3 out_local/soong/module/output2 out_local/soong/module/output3 input3 out_local/soong/module/output2",
 		}
 
-		wantDepMergerCommand := "out_local/soong/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer " +
+		wantDepMergerCommand := "out_local/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer " +
 			"out_local/soong/module/DepFile out_local/soong/module/depfile out_local/soong/module/ImplicitDepFile out_local/soong/module/depfile2"
 
 		AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
@@ -388,7 +388,7 @@
 			"command3 input3 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/out/output3 input3 __SBOX_SANDBOX_DIR__/out/output2",
 		}
 
-		wantDepMergerCommand := "out_local/soong/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2"
+		wantDepMergerCommand := "out_local/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2"
 
 		AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
 
@@ -475,10 +475,9 @@
 		Srcs  []string
 		Flags []string
 
-		Restat              bool
-		Sbox                bool
-		Sbox_inputs         bool
-		Unescape_ninja_vars bool
+		Restat      bool
+		Sbox        bool
+		Sbox_inputs bool
 	}
 }
 
@@ -498,7 +497,7 @@
 
 	testRuleBuilder_Build(ctx, in, implicit, orderOnly, validation, t.properties.Flags,
 		out, outDep, outDir,
-		manifestPath, t.properties.Restat, t.properties.Sbox, t.properties.Sbox_inputs, t.properties.Unescape_ninja_vars,
+		manifestPath, t.properties.Restat, t.properties.Sbox, t.properties.Sbox_inputs,
 		rspFile, rspFileContents, rspFile2, rspFileContents2)
 }
 
@@ -523,14 +522,14 @@
 	manifestPath := PathForOutput(ctx, "singleton/sbox.textproto")
 
 	testRuleBuilder_Build(ctx, in, implicit, orderOnly, validation, nil, out, outDep, outDir,
-		manifestPath, true, false, false, false,
+		manifestPath, true, false, false,
 		rspFile, rspFileContents, rspFile2, rspFileContents2)
 }
 
 func testRuleBuilder_Build(ctx BuilderContext, in Paths, implicit, orderOnly, validation Path,
 	flags []string,
 	out, outDep, outDir, manifestPath WritablePath,
-	restat, sbox, sboxInputs, unescapeNinjaVars bool,
+	restat, sbox, sboxInputs bool,
 	rspFile WritablePath, rspFileContents Paths, rspFile2 WritablePath, rspFileContents2 Paths) {
 
 	rule := NewRuleBuilder(pctx_ruleBuilderTest, ctx)
@@ -558,11 +557,7 @@
 		rule.Restat()
 	}
 
-	if unescapeNinjaVars {
-		rule.BuildWithUnescapedNinjaVars("rule", "desc")
-	} else {
-		rule.Build("rule", "desc")
-	}
+	rule.Build("rule", "desc")
 }
 
 var prepareForRuleBuilderTest = FixtureRegisterWithContext(func(ctx RegistrationContext) {
@@ -656,7 +651,7 @@
 		outFile := "out/soong/.intermediates/foo/gen/foo"
 		rspFile := "out/soong/.intermediates/foo/rsp"
 		rspFile2 := "out/soong/.intermediates/foo/rsp2"
-		module := result.ModuleForTests("foo", "")
+		module := result.ModuleForTests(t, "foo", "")
 		check(t, module.Rule("rule"), module.Output(rspFile2),
 			"cp in "+outFile+" @"+rspFile+" @"+rspFile2,
 			outFile, outFile+".d", rspFile, rspFile2, true, nil, nil)
@@ -669,11 +664,11 @@
 		rspFile := filepath.Join(outDir, "rsp")
 		rspFile2 := filepath.Join(outDir, "rsp2")
 		manifest := filepath.Join(outDir, "sbox.textproto")
-		sbox := filepath.Join("out", "soong", "host", result.Config.PrebuiltOS(), "bin/sbox")
+		sbox := filepath.Join("out", "host", result.Config.PrebuiltOS(), "bin/sbox")
 		sandboxPath := shared.TempDirForOutDir("out/soong")
 
 		cmd := sbox + ` --sandbox-path ` + sandboxPath + ` --output-dir ` + sboxOutDir + ` --manifest ` + manifest
-		module := result.ModuleForTests("foo_sbox", "")
+		module := result.ModuleForTests(t, "foo_sbox", "")
 		check(t, module.Output("gen/foo_sbox"), module.Output(rspFile2),
 			cmd, outFile, depFile, rspFile, rspFile2, false, []string{manifest}, []string{sbox})
 	})
@@ -685,12 +680,12 @@
 		rspFile := filepath.Join(outDir, "rsp")
 		rspFile2 := filepath.Join(outDir, "rsp2")
 		manifest := filepath.Join(outDir, "sbox.textproto")
-		sbox := filepath.Join("out", "soong", "host", result.Config.PrebuiltOS(), "bin/sbox")
+		sbox := filepath.Join("out", "host", result.Config.PrebuiltOS(), "bin/sbox")
 		sandboxPath := shared.TempDirForOutDir("out/soong")
 
 		cmd := sbox + ` --sandbox-path ` + sandboxPath + ` --output-dir ` + sboxOutDir + ` --manifest ` + manifest
 
-		module := result.ModuleForTests("foo_sbox_inputs", "")
+		module := result.ModuleForTests(t, "foo_sbox_inputs", "")
 		check(t, module.Output("gen/foo_sbox_inputs"), module.Output(rspFile2),
 			cmd, outFile, depFile, rspFile, rspFile2, false, []string{manifest}, []string{sbox})
 	})
@@ -698,7 +693,7 @@
 		outFile := filepath.Join("out/soong/singleton/gen/baz")
 		rspFile := filepath.Join("out/soong/singleton/rsp")
 		rspFile2 := filepath.Join("out/soong/singleton/rsp2")
-		singleton := result.SingletonForTests("rule_builder_test")
+		singleton := result.SingletonForTests(t, "rule_builder_test")
 		check(t, singleton.Rule("rule"), singleton.Output(rspFile2),
 			"cp in "+outFile+" @"+rspFile+" @"+rspFile2,
 			outFile, outFile+".d", rspFile, rspFile2, true, nil, nil)
@@ -761,14 +756,14 @@
 	for _, test := range testcases {
 		t.Run(test.name, func(t *testing.T) {
 			t.Run("sbox", func(t *testing.T) {
-				gen := result.ModuleForTests(test.name+"_sbox", "")
+				gen := result.ModuleForTests(t, test.name+"_sbox", "")
 				manifest := RuleBuilderSboxProtoForTests(t, result.TestContext, gen.Output("sbox.textproto"))
 				hash := manifest.Commands[0].GetInputHash()
 
 				AssertStringEquals(t, "hash", test.expectedHash, hash)
 			})
 			t.Run("", func(t *testing.T) {
-				gen := result.ModuleForTests(test.name+"", "")
+				gen := result.ModuleForTests(t, test.name+"", "")
 				command := gen.Output("gen/" + test.name).RuleParams.Command
 				if g, w := command, " # hash of input list: "+test.expectedHash; !strings.HasSuffix(g, w) {
 					t.Errorf("Expected command line to end with %q, got %q", w, g)
@@ -777,48 +772,3 @@
 		})
 	}
 }
-
-func TestRuleBuilderWithNinjaVarEscaping(t *testing.T) {
-	bp := `
-		rule_builder_test {
-			name: "foo_sbox_escaped",
-			flags: ["${cmdFlags}"],
-			sbox: true,
-			sbox_inputs: true,
-		}
-		rule_builder_test {
-			name: "foo_sbox_unescaped",
-			flags: ["${cmdFlags}"],
-			sbox: true,
-			sbox_inputs: true,
-			unescape_ninja_vars: true,
-		}
-	`
-	result := GroupFixturePreparers(
-		prepareForRuleBuilderTest,
-		FixtureWithRootAndroidBp(bp),
-	).RunTest(t)
-
-	escapedNinjaMod := result.ModuleForTests("foo_sbox_escaped", "").Output("sbox.textproto")
-	AssertStringEquals(t, "expected rule", "android/soong/android.rawFileCopy", escapedNinjaMod.Rule.String())
-	AssertStringDoesContain(
-		t,
-		"",
-		ContentFromFileRuleForTests(t, result.TestContext, escapedNinjaMod),
-		"${cmdFlags}",
-	)
-
-	unescapedNinjaMod := result.ModuleForTests("foo_sbox_unescaped", "").Rule("unescapedWriteFile")
-	AssertStringDoesContain(
-		t,
-		"",
-		unescapedNinjaMod.BuildParams.Args["content"],
-		"${cmdFlags}",
-	)
-	AssertStringDoesNotContain(
-		t,
-		"",
-		unescapedNinjaMod.BuildParams.Args["content"],
-		"$${cmdFlags}",
-	)
-}
diff --git a/android/sbom.go b/android/sbom.go
index 2a5499e..fc61c41 100644
--- a/android/sbom.go
+++ b/android/sbom.go
@@ -15,9 +15,7 @@
 package android
 
 import (
-	"io"
 	"path/filepath"
-	"strings"
 
 	"github.com/google/blueprint"
 )
@@ -55,21 +53,7 @@
 	if !ctx.Config().HasDeviceProduct() {
 		return
 	}
-	// Get all METADATA files and add them as implicit input
-	metadataFileListFile := PathForArbitraryOutput(ctx, ".module_paths", "METADATA.list")
-	f, err := ctx.Config().fs.Open(metadataFileListFile.String())
-	if err != nil {
-		panic(err)
-	}
-	b, err := io.ReadAll(f)
-	if err != nil {
-		panic(err)
-	}
-	allMetadataFiles := strings.Split(string(b), "\n")
-	implicits := []Path{metadataFileListFile}
-	for _, path := range allMetadataFiles {
-		implicits = append(implicits, PathForSource(ctx, path))
-	}
+	implicits := []Path{}
 	prodVars := ctx.Config().productVariables
 	buildFingerprintFile := PathForArbitraryOutput(ctx, "target", "product", String(prodVars.DeviceName), "build_fingerprint.txt")
 	implicits = append(implicits, buildFingerprintFile)
@@ -100,12 +84,6 @@
 			Inputs: []Path{this.sbomFile},
 			Output: PathForPhony(ctx, "sbom"),
 		})
-	}
-}
-
-func (this *sbomSingleton) MakeVars(ctx MakeVarsContext) {
-	// When building SBOM of products
-	if !ctx.Config().UnbundledBuildApps() {
 		ctx.DistForGoalWithFilename("droid", this.sbomFile, "sbom/sbom.spdx.json")
 	}
 }
diff --git a/android/sdk_version.go b/android/sdk_version.go
index a9b88fb..fa3abaa 100644
--- a/android/sdk_version.go
+++ b/android/sdk_version.go
@@ -123,6 +123,31 @@
 	}
 }
 
+func JavaLibraryNameToSdkKind(name string) (SdkKind, bool) {
+	if name == SdkPublic.DefaultJavaLibraryName() {
+		return SdkPublic, true
+	}
+	if name == SdkSystem.DefaultJavaLibraryName() {
+		return SdkSystem, true
+	}
+	if name == SdkTest.DefaultJavaLibraryName() {
+		return SdkTest, true
+	}
+	if name == SdkTestFrameworksCore.DefaultJavaLibraryName() {
+		return SdkTestFrameworksCore, true
+	}
+	if name == SdkCore.DefaultJavaLibraryName() {
+		return SdkCore, true
+	}
+	if name == SdkModule.DefaultJavaLibraryName() {
+		return SdkModule, true
+	}
+	if name == SdkSystemServer.DefaultJavaLibraryName() {
+		return SdkSystemServer, true
+	}
+	return SdkInvalid, false
+}
+
 func (k SdkKind) DefaultExportableJavaLibraryName() string {
 	switch k {
 	case SdkPublic, SdkSystem, SdkTest, SdkModule, SdkSystemServer:
diff --git a/android/selects_test.go b/android/selects_test.go
index 90d7091..7f20a3d 100644
--- a/android/selects_test.go
+++ b/android/selects_test.go
@@ -1031,6 +1031,54 @@
 				my_string_list: &[]string{"d2", "e2", "f2", "a1", "b1", "c1"},
 			},
 		},
+		{
+			name: "string list variables",
+			bp: `
+my_module_type {
+	name: "foo",
+	my_string_list: ["a"] + select(soong_config_variable("my_namespace", "my_var"), {
+		any @ my_var: my_var,
+		default: [],
+	}),
+}
+`,
+			vendorVars: map[string]map[string]string{
+				"my_namespace": {
+					"my_var": "b c",
+				},
+			},
+			vendorVarTypes: map[string]map[string]string{
+				"my_namespace": {
+					"my_var": "string_list",
+				},
+			},
+			provider: selectsTestProvider{
+				my_string_list: &[]string{"a", "b", "c"},
+			},
+		},
+		{
+			name: "string list variables don't match string matchers",
+			bp: `
+my_module_type {
+	name: "foo",
+	my_string_list: ["a"] + select(soong_config_variable("my_namespace", "my_var"), {
+		"foo": ["b"],
+		default: [],
+	}),
+}
+`,
+			vendorVars: map[string]map[string]string{
+				"my_namespace": {
+					"my_var": "b c",
+				},
+			},
+			vendorVarTypes: map[string]map[string]string{
+				"my_namespace": {
+					"my_var": "string_list",
+				},
+			},
+			expectedError: `Expected all branches of a select on condition soong_config_variable\("my_namespace", "my_var"\) to have type string_list, found string`,
+		},
 	}
 
 	for _, tc := range testCases {
@@ -1070,7 +1118,7 @@
 
 				for moduleName := range tc.providers {
 					expected := tc.providers[moduleName]
-					m := result.ModuleForTests(moduleName, "android_arm64_armv8-a")
+					m := result.ModuleForTests(t, moduleName, "android_arm64_armv8-a")
 					p, _ := OtherModuleProvider(result.testContext.OtherModuleProviderAdaptor(), m.Module(), selectsTestProviderKey)
 					if !reflect.DeepEqual(p, expected) {
 						t.Errorf("Expected:\n  %q\ngot:\n  %q", expected.String(), p.String())
diff --git a/android/singleton.go b/android/singleton.go
index 913bf6a..df22045 100644
--- a/android/singleton.go
+++ b/android/singleton.go
@@ -15,6 +15,9 @@
 package android
 
 import (
+	"slices"
+	"sync"
+
 	"github.com/google/blueprint"
 )
 
@@ -64,6 +67,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 +81,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)
 
@@ -94,6 +100,24 @@
 	// HasMutatorFinished returns true if the given mutator has finished running.
 	// It will panic if given an invalid mutator name.
 	HasMutatorFinished(mutatorName string) bool
+
+	// DistForGoals creates a rule to copy one or more Paths to the artifacts
+	// directory on the build server when any of the specified goals are built.
+	DistForGoal(goal string, paths ...Path)
+
+	// DistForGoalWithFilename creates a rule to copy a Path to the artifacts
+	// directory on the build server with the given filename when the specified
+	// goal is built.
+	DistForGoalWithFilename(goal string, path Path, filename string)
+
+	// DistForGoals creates a rule to copy one or more Paths to the artifacts
+	// directory on the build server when any of the specified goals are built.
+	DistForGoals(goals []string, paths ...Path)
+
+	// DistForGoalsWithFilename creates a rule to copy a Path to the artifacts
+	// directory on the build server with the given filename when any of the
+	// specified goals are built.
+	DistForGoalsWithFilename(goals []string, path Path, filename string)
 }
 
 type singletonAdaptor struct {
@@ -115,6 +139,13 @@
 
 	s.buildParams = sctx.buildParams
 	s.ruleParams = sctx.ruleParams
+
+	if len(sctx.dists) > 0 {
+		dists := getSingletonDists(sctx.Config())
+		dists.lock.Lock()
+		defer dists.lock.Unlock()
+		dists.dists = append(dists.dists, sctx.dists...)
+	}
 }
 
 func (s *singletonAdaptor) BuildParamsForTests() []BuildParams {
@@ -125,6 +156,19 @@
 	return s.ruleParams
 }
 
+var singletonDistsKey = NewOnceKey("singletonDistsKey")
+
+type singletonDistsAndLock struct {
+	dists []dist
+	lock  sync.Mutex
+}
+
+func getSingletonDists(config Config) *singletonDistsAndLock {
+	return config.Once(singletonDistsKey, func() interface{} {
+		return &singletonDistsAndLock{}
+	}).(*singletonDistsAndLock)
+}
+
 type Singleton interface {
 	GenerateBuildActions(SingletonContext)
 }
@@ -134,6 +178,7 @@
 
 	buildParams []BuildParams
 	ruleParams  map[blueprint.Rule]blueprint.RuleParams
+	dists       []dist
 }
 
 func (s *singletonContextAdaptor) blueprintSingletonContext() blueprint.SingletonContext {
@@ -193,7 +238,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 +248,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 +279,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 +307,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 {
@@ -294,3 +357,31 @@
 func (s *singletonContextAdaptor) HasMutatorFinished(mutatorName string) bool {
 	return s.blueprintSingletonContext().HasMutatorFinished(mutatorName)
 }
+func (s *singletonContextAdaptor) DistForGoal(goal string, paths ...Path) {
+	s.DistForGoals([]string{goal}, paths...)
+}
+
+func (s *singletonContextAdaptor) DistForGoalWithFilename(goal string, path Path, filename string) {
+	s.DistForGoalsWithFilename([]string{goal}, path, filename)
+}
+
+func (s *singletonContextAdaptor) DistForGoals(goals []string, paths ...Path) {
+	var copies distCopies
+	for _, path := range paths {
+		copies = append(copies, distCopy{
+			from: path,
+			dest: path.Base(),
+		})
+	}
+	s.dists = append(s.dists, dist{
+		goals: slices.Clone(goals),
+		paths: copies,
+	})
+}
+
+func (s *singletonContextAdaptor) DistForGoalsWithFilename(goals []string, path Path, filename string) {
+	s.dists = append(s.dists, dist{
+		goals: slices.Clone(goals),
+		paths: distCopies{{from: path, dest: filename}},
+	})
+}
diff --git a/android/singleton_module_test.go b/android/singleton_module_test.go
index 3b8c6b2..6f61a3b 100644
--- a/android/singleton_module_test.go
+++ b/android/singleton_module_test.go
@@ -61,7 +61,7 @@
 		FixtureWithRootAndroidBp(bp),
 	).RunTest(t)
 
-	ops := result.ModuleForTests("test_singleton_module", "").Module().(*testSingletonModule).ops
+	ops := result.ModuleForTests(t, "test_singleton_module", "").Module().(*testSingletonModule).ops
 	wantOps := []string{"GenerateAndroidBuildActions", "GenerateSingletonBuildActions", "MakeVars"}
 	AssertDeepEquals(t, "operations", wantOps, ops)
 }
@@ -88,7 +88,7 @@
 		prepareForSingletonModuleTest,
 	).RunTest(t)
 
-	singleton := result.SingletonForTests("test_singleton_module").Singleton()
+	singleton := result.SingletonForTests(t, "test_singleton_module").Singleton()
 	sm := singleton.(*singletonModuleSingletonAdaptor).sm
 	ops := sm.(*testSingletonModule).ops
 	if ops != nil {
diff --git a/android/soong_config_modules_test.go b/android/soong_config_modules_test.go
index 04aafde..f98e02b 100644
--- a/android/soong_config_modules_test.go
+++ b/android/soong_config_modules_test.go
@@ -321,10 +321,10 @@
 					FixtureWithRootAndroidBp(bp),
 				).RunTest(t)
 
-				foo := result.ModuleForTests("foo", "").Module().(*soongConfigTestModule)
+				foo := result.ModuleForTests(t, "foo", "").Module().(*soongConfigTestModule)
 				AssertDeepEquals(t, "foo cflags", tc.fooExpectedFlags, foo.props.Cflags)
 
-				fooDefaults := result.ModuleForTests("foo_with_defaults", "").Module().(*soongConfigTestModule)
+				fooDefaults := result.ModuleForTests(t, "foo_with_defaults", "").Module().(*soongConfigTestModule)
 				AssertDeepEquals(t, "foo_with_defaults cflags", tc.fooDefaultsExpectedFlags, fooDefaults.props.Cflags)
 			})
 		}
@@ -499,8 +499,8 @@
 			).RunTest(t)
 
 			// Make sure that the singleton was created.
-			result.SingletonForTests("test_singleton")
-			m := result.ModuleForTests("wiley", "").module.(*soongConfigTestSingletonModule)
+			result.SingletonForTests(t, "test_singleton")
+			m := result.ModuleForTests(t, "wiley", "").module.(*soongConfigTestSingletonModule)
 			AssertStringEquals(t, "fragments", test.expectedFragments, fmt.Sprintf("%+v", m.props.Fragments))
 		})
 	}
diff --git a/android/team_proto/OWNERS b/android/team_proto/OWNERS
index 2beb4f4..1eb820b 100644
--- a/android/team_proto/OWNERS
+++ b/android/team_proto/OWNERS
@@ -1,5 +1,4 @@
 dariofreni@google.com
 joeo@google.com
 ronish@google.com
-caditya@google.com
 rbraunstein@google.com
diff --git a/android/team_test.go b/android/team_test.go
index ccfcaaa..dcc1c99 100644
--- a/android/team_test.go
+++ b/android/team_test.go
@@ -61,9 +61,9 @@
 	`)
 
 	// Assert the rule from GenerateAndroidBuildActions exists.
-	m := ctx.ModuleForTests("main_test", "")
+	m := ctx.ModuleForTests(t, "main_test", "")
 	AssertStringEquals(t, "msg", m.Module().base().Team(), "someteam")
-	m = ctx.ModuleForTests("tool", "")
+	m = ctx.ModuleForTests(t, "tool", "")
 	AssertStringEquals(t, "msg", m.Module().base().Team(), "team2")
 }
 
diff --git a/android/test_asserts.go b/android/test_asserts.go
index c33ade5..22472c5 100644
--- a/android/test_asserts.go
+++ b/android/test_asserts.go
@@ -33,6 +33,23 @@
 	}
 }
 
+// AssertSame checks if the expected and actual values are equal and if they are not then
+// it reports an error prefixed with the supplied message and including a reason for why it failed.
+func AssertSameArray[T comparable](t *testing.T, message string, expected []T, actual []T) {
+	t.Helper()
+	if len(actual) != len(expected) {
+		t.Errorf("%s: expected %d (%v), actual (%d) %v", message, len(expected), expected, len(actual), actual)
+		return
+	}
+	for i := range actual {
+		if actual[i] != expected[i] {
+			t.Errorf("%s: expected %d-th, %v (%v), actual %v (%v)",
+				message, i, expected[i], expected, actual[i], actual)
+			return
+		}
+	}
+}
+
 // AssertBoolEquals checks if the expected and actual values are equal and if they are not then it
 // reports an error prefixed with the supplied message and including a reason for why it failed.
 func AssertBoolEquals(t *testing.T, message string, expected bool, actual bool) {
diff --git a/android/test_config.go b/android/test_config.go
index f251038..3609e6b 100644
--- a/android/test_config.go
+++ b/android/test_config.go
@@ -45,6 +45,7 @@
 			Platform_version_active_codenames:   []string{"S", "Tiramisu"},
 			DeviceSystemSdkVersions:             []string{"29", "30", "S"},
 			Platform_systemsdk_versions:         []string{"29", "30", "S", "Tiramisu"},
+			VendorApiLevel:                      stringPtr("202404"),
 			AAPTConfig:                          []string{"normal", "large", "xlarge", "hdpi", "xhdpi", "xxhdpi"},
 			AAPTPreferredConfig:                 stringPtr("xhdpi"),
 			AAPTCharacteristics:                 stringPtr("nosdcard"),
diff --git a/android/test_suites.go b/android/test_suites.go
index 936d2b6..18744f1 100644
--- a/android/test_suites.go
+++ b/android/test_suites.go
@@ -58,9 +58,6 @@
 
 	t.ravenwood = ravenwoodTestSuite(ctx, files["ravenwood-tests"])
 	ctx.Phony("ravenwood-tests", t.ravenwood...)
-}
-
-func (t *testSuiteFiles) MakeVars(ctx MakeVarsContext) {
 	ctx.DistForGoal("robolectric-tests", t.robolectric...)
 	ctx.DistForGoal("ravenwood-tests", t.ravenwood...)
 }
diff --git a/android/test_suites_test.go b/android/test_suites_test.go
index db9a34d..bf4de19 100644
--- a/android/test_suites_test.go
+++ b/android/test_suites_test.go
@@ -52,7 +52,7 @@
 		}
 	`)
 
-	config := ctx.SingletonForTests("testsuites")
+	config := ctx.SingletonForTests(t, "testsuites")
 	allOutputs := config.AllOutputs()
 
 	wantContents := map[string]string{
diff --git a/android/testing.go b/android/testing.go
index 7440869..8e38b3b 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -19,6 +19,7 @@
 	"fmt"
 	"path/filepath"
 	"regexp"
+	"runtime"
 	"sort"
 	"strings"
 	"sync"
@@ -189,6 +190,26 @@
 	})
 }
 
+// PrepareForNativeBridgeEnabled sets configuration with targets including:
+// - X86_64 (primary)
+// - X86 (secondary)
+// - Arm64 on X86_64 (native bridge)
+// - Arm on X86 (native bridge)
+var PrepareForNativeBridgeEnabled = FixtureModifyConfig(
+	func(config Config) {
+		config.Targets[Android] = []Target{
+			{Os: Android, Arch: Arch{ArchType: X86_64, ArchVariant: "silvermont", Abi: []string{"arm64-v8a"}},
+				NativeBridge: NativeBridgeDisabled, NativeBridgeHostArchName: "", NativeBridgeRelativePath: ""},
+			{Os: Android, Arch: Arch{ArchType: X86, ArchVariant: "silvermont", Abi: []string{"armeabi-v7a"}},
+				NativeBridge: NativeBridgeDisabled, NativeBridgeHostArchName: "", NativeBridgeRelativePath: ""},
+			{Os: Android, Arch: Arch{ArchType: Arm64, ArchVariant: "armv8-a", Abi: []string{"arm64-v8a"}},
+				NativeBridge: NativeBridgeEnabled, NativeBridgeHostArchName: "x86_64", NativeBridgeRelativePath: "arm64"},
+			{Os: Android, Arch: Arch{ArchType: Arm, ArchVariant: "armv7-a-neon", Abi: []string{"armeabi-v7a"}},
+				NativeBridge: NativeBridgeEnabled, NativeBridgeHostArchName: "x86", NativeBridgeRelativePath: "arm"},
+		}
+	},
+)
+
 func NewTestArchContext(config Config) *TestContext {
 	ctx := NewTestContext(config)
 	ctx.preDeps = append(ctx.preDeps, registerArchMutator)
@@ -509,7 +530,8 @@
 // both have the same value. Both the module and the map are allowed to have
 // extra variations that the other doesn't have. Panics if not exactly one
 // module variant matches.
-func (ctx *TestContext) ModuleVariantForTests(name string, matchVariations map[string]string) TestingModule {
+func (ctx *TestContext) ModuleVariantForTests(t *testing.T, name string, matchVariations map[string]string) TestingModule {
+	t.Helper()
 	modules := []Module{}
 	ctx.VisitAllModules(func(m blueprint.Module) {
 		if ctx.ModuleName(m) == name {
@@ -541,12 +563,12 @@
 		})
 
 		if len(allVariants) == 0 {
-			panic(fmt.Errorf("failed to find module %q. All modules:\n  %s",
-				name, strings.Join(SortedUniqueStrings(allModuleNames), "\n  ")))
+			t.Fatalf("failed to find module %q. All modules:\n  %s",
+				name, strings.Join(SortedUniqueStrings(allModuleNames), "\n  "))
 		} else {
 			sort.Strings(allVariants)
-			panic(fmt.Errorf("failed to find module %q matching %v. All variants:\n  %s",
-				name, matchVariations, strings.Join(allVariants, "\n  ")))
+			t.Fatalf("failed to find module %q matching %v. All variants:\n  %s",
+				name, matchVariations, strings.Join(allVariants, "\n  "))
 		}
 	}
 
@@ -556,14 +578,15 @@
 			moduleStrings = append(moduleStrings, m.String())
 		}
 		sort.Strings(moduleStrings)
-		panic(fmt.Errorf("module %q has more than one variant that match %v:\n  %s",
-			name, matchVariations, strings.Join(moduleStrings, "\n  ")))
+		t.Fatalf("module %q has more than one variant that match %v:\n  %s",
+			name, matchVariations, strings.Join(moduleStrings, "\n  "))
 	}
 
-	return newTestingModule(ctx.config, modules[0])
+	return newTestingModule(t, ctx.config, modules[0])
 }
 
-func (ctx *TestContext) ModuleForTests(name, variant string) TestingModule {
+func (ctx *TestContext) ModuleForTests(t *testing.T, name, variant string) TestingModule {
+	t.Helper()
 	var module Module
 	ctx.VisitAllModules(func(m blueprint.Module) {
 		if ctx.ModuleName(m) == name && ctx.ModuleSubDir(m) == variant {
@@ -584,15 +607,15 @@
 		sort.Strings(allVariants)
 
 		if len(allVariants) == 0 {
-			panic(fmt.Errorf("failed to find module %q. All modules:\n  %s",
-				name, strings.Join(SortedUniqueStrings(allModuleNames), "\n  ")))
+			t.Fatalf("failed to find module %q. All modules:\n  %s",
+				name, strings.Join(SortedUniqueStrings(allModuleNames), "\n  "))
 		} else {
-			panic(fmt.Errorf("failed to find module %q variant %q. All variants:\n  %s",
-				name, variant, strings.Join(allVariants, "\n  ")))
+			t.Fatalf("failed to find module %q variant %q. All variants:\n  %s",
+				name, variant, strings.Join(allVariants, "\n  "))
 		}
 	}
 
-	return newTestingModule(ctx.config, module)
+	return newTestingModule(t, ctx.config, module)
 }
 
 func (ctx *TestContext) ModuleVariantsForTests(name string) []string {
@@ -606,21 +629,24 @@
 }
 
 // SingletonForTests returns a TestingSingleton for the singleton registered with the given name.
-func (ctx *TestContext) SingletonForTests(name string) TestingSingleton {
+func (ctx *TestContext) SingletonForTests(t *testing.T, name string) TestingSingleton {
+	t.Helper()
 	allSingletonNames := []string{}
 	for _, s := range ctx.Singletons() {
 		n := ctx.SingletonName(s)
 		if n == name {
 			return TestingSingleton{
-				baseTestingComponent: newBaseTestingComponent(ctx.config, s.(testBuildProvider)),
+				baseTestingComponent: newBaseTestingComponent(t, ctx.config, s.(testBuildProvider)),
 				singleton:            s.(*singletonAdaptor).Singleton,
 			}
 		}
 		allSingletonNames = append(allSingletonNames, n)
 	}
 
-	panic(fmt.Errorf("failed to find singleton %q."+
-		"\nall singletons: %v", name, allSingletonNames))
+	t.Fatalf("failed to find singleton %q."+
+		"\nall singletons: %v", name, allSingletonNames)
+
+	return TestingSingleton{}
 }
 
 type InstallMakeRule struct {
@@ -630,6 +656,7 @@
 }
 
 func parseMkRules(t *testing.T, config Config, nodes []mkparser.Node) []InstallMakeRule {
+	t.Helper()
 	var rules []InstallMakeRule
 	for _, node := range nodes {
 		if mkParserRule, ok := node.(*mkparser.Rule); ok {
@@ -667,7 +694,8 @@
 }
 
 func (ctx *TestContext) InstallMakeRulesForTesting(t *testing.T) []InstallMakeRule {
-	installs := ctx.SingletonForTests("makevars").Singleton().(*makeVarsSingleton).installsForTesting
+	t.Helper()
+	installs := ctx.SingletonForTests(t, "makevars").Singleton().(*makeVarsSingleton).installsForTesting
 	buf := bytes.NewBuffer(append([]byte(nil), installs...))
 	parser := mkparser.NewParser("makevars", buf)
 
@@ -707,8 +735,9 @@
 //
 // It is necessary to use PrepareForTestAccessingMakeVars in tests that want to call this function.
 // Along with any other preparers needed to add the make vars.
-func (ctx *TestContext) MakeVarsForTesting(filter func(variable MakeVarVariable) bool) []MakeVarVariable {
-	vars := ctx.SingletonForTests("makevars").Singleton().(*makeVarsSingleton).varsForTesting
+func (ctx *TestContext) MakeVarsForTesting(t *testing.T, filter func(variable MakeVarVariable) bool) []MakeVarVariable {
+	t.Helper()
+	vars := ctx.SingletonForTests(t, "makevars").Singleton().(*makeVarsSingleton).varsForTesting
 	result := make([]MakeVarVariable, 0, len(vars))
 	for _, v := range vars {
 		if filter(v) {
@@ -825,12 +854,13 @@
 
 // baseTestingComponent provides functionality common to both TestingModule and TestingSingleton.
 type baseTestingComponent struct {
+	t        *testing.T
 	config   Config
 	provider testBuildProvider
 }
 
-func newBaseTestingComponent(config Config, provider testBuildProvider) baseTestingComponent {
-	return baseTestingComponent{config, provider}
+func newBaseTestingComponent(t *testing.T, config Config, provider testBuildProvider) baseTestingComponent {
+	return baseTestingComponent{t, config, provider}
 }
 
 // A function that will normalize a string containing paths, e.g. ninja command, by replacing
@@ -903,7 +933,7 @@
 func (b baseTestingComponent) buildParamsFromRule(rule string) TestingBuildParams {
 	p, searchRules := b.maybeBuildParamsFromRule(rule)
 	if p.Rule == nil {
-		panic(fmt.Errorf("couldn't find rule %q.\nall rules:\n%s", rule, strings.Join(searchRules, "\n")))
+		b.t.Fatalf("couldn't find rule %q.\nall rules:\n%s", rule, strings.Join(searchRules, "\n"))
 	}
 	return p
 }
@@ -922,7 +952,7 @@
 func (b baseTestingComponent) buildParamsFromDescription(desc string) TestingBuildParams {
 	p, searchedDescriptions := b.maybeBuildParamsFromDescription(desc)
 	if p.Rule == nil {
-		panic(fmt.Errorf("couldn't find description %q\nall descriptions:\n%s", desc, strings.Join(searchedDescriptions, "\n")))
+		b.t.Fatalf("couldn't find description %q\nall descriptions:\n%s", desc, strings.Join(searchedDescriptions, "\n"))
 	}
 	return p
 }
@@ -955,8 +985,8 @@
 func (b baseTestingComponent) buildParamsFromOutput(file string) TestingBuildParams {
 	p, searchedOutputs := b.maybeBuildParamsFromOutput(file)
 	if p.Rule == nil {
-		panic(fmt.Errorf("couldn't find output %q.\nall outputs:\n    %s\n",
-			file, strings.Join(searchedOutputs, "\n    ")))
+		b.t.Fatalf("couldn't find output %q.\nall outputs:\n    %s\n",
+			file, strings.Join(searchedOutputs, "\n    "))
 	}
 	return p
 }
@@ -1019,9 +1049,9 @@
 	module Module
 }
 
-func newTestingModule(config Config, module Module) TestingModule {
+func newTestingModule(t *testing.T, config Config, module Module) TestingModule {
 	return TestingModule{
-		newBaseTestingComponent(config, module),
+		newBaseTestingComponent(t, config, module),
 		module,
 	}
 }
@@ -1131,17 +1161,12 @@
 	config.katiEnabled = true
 }
 
-func SetTrimmedApexEnabledForTests(config Config) {
-	config.productVariables.TrimmedApex = new(bool)
-	*config.productVariables.TrimmedApex = true
-}
-
 func AndroidMkEntriesForTest(t *testing.T, ctx *TestContext, mod blueprint.Module) []AndroidMkEntries {
 	t.Helper()
 	var p AndroidMkEntriesProvider
 	var ok bool
 	if p, ok = mod.(AndroidMkEntriesProvider); !ok {
-		t.Errorf("module does not implement AndroidMkEntriesProvider: " + mod.Name())
+		t.Error("module does not implement AndroidMkEntriesProvider: " + mod.Name())
 	}
 
 	entriesList := p.AndroidMkEntries()
@@ -1152,12 +1177,36 @@
 	return entriesList
 }
 
+func AndroidMkInfoForTest(t *testing.T, ctx *TestContext, mod blueprint.Module) *AndroidMkProviderInfo {
+	if runtime.GOOS == "darwin" && mod.(Module).base().Os() != Darwin {
+		// The AndroidMkInfo provider is not set in this case.
+		t.Skip("AndroidMkInfo provider is not set on darwin")
+	}
+
+	t.Helper()
+	var ok bool
+	if _, ok = mod.(AndroidMkProviderInfoProducer); !ok {
+		t.Error("module does not implement AndroidMkProviderInfoProducer: " + mod.Name())
+	}
+
+	info := OtherModuleProviderOrDefault(ctx, mod, AndroidMkInfoProvider)
+	aconfigUpdateAndroidMkInfos(ctx, mod.(Module), info)
+	info.PrimaryInfo.fillInEntries(ctx, mod)
+	if len(info.ExtraInfo) > 0 {
+		for _, ei := range info.ExtraInfo {
+			ei.fillInEntries(ctx, mod)
+		}
+	}
+
+	return info
+}
+
 func AndroidMkDataForTest(t *testing.T, ctx *TestContext, mod blueprint.Module) AndroidMkData {
 	t.Helper()
 	var p AndroidMkDataProvider
 	var ok bool
 	if p, ok = mod.(AndroidMkDataProvider); !ok {
-		t.Fatalf("module does not implement AndroidMkDataProvider: " + mod.Name())
+		t.Fatal("module does not implement AndroidMkDataProvider: " + mod.Name())
 	}
 	data := p.AndroidMk()
 	data.fillInData(ctx, mod)
diff --git a/android/transition.go b/android/transition.go
new file mode 100644
index 0000000..0677ca1
--- /dev/null
+++ b/android/transition.go
@@ -0,0 +1,410 @@
+// 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 "github.com/google/blueprint"
+
+// TransitionMutator implements a top-down mechanism where a module tells its
+// direct dependencies what variation they should be built in but the dependency
+// has the final say.
+//
+// When implementing a transition mutator, one needs to implement four methods:
+//   - Split() that tells what variations a module has by itself
+//   - OutgoingTransition() where a module tells what it wants from its
+//     dependency
+//   - IncomingTransition() where a module has the final say about its own
+//     variation
+//   - Mutate() that changes the state of a module depending on its variation
+//
+// That the effective variation of module B when depended on by module A is the
+// composition the outgoing transition of module A and the incoming transition
+// of module B.
+//
+// the outgoing transition should not take the properties of the dependency into
+// account, only those of the module that depends on it. For this reason, the
+// dependency is not even passed into it as an argument. Likewise, the incoming
+// transition should not take the properties of the depending module into
+// account and is thus not informed about it. This makes for a nice
+// decomposition of the decision logic.
+//
+// A given transition mutator only affects its own variation; other variations
+// stay unchanged along the dependency edges.
+//
+// Soong makes sure that all modules are created in the desired variations and
+// that dependency edges are set up correctly. This ensures that "missing
+// variation" errors do not happen and allows for more flexible changes in the
+// value of the variation among dependency edges (as oppposed to bottom-up
+// mutators where if module A in variation X depends on module B and module B
+// has that variation X, A must depend on variation X of B)
+//
+// The limited power of the context objects passed to individual mutators
+// methods also makes it more difficult to shoot oneself in the foot. Complete
+// safety is not guaranteed because no one prevents individual transition
+// mutators from mutating modules in illegal ways and for e.g. Split() or
+// Mutate() to run their own visitations of the transitive dependency of the
+// module and both of these are bad ideas, but it's better than no guardrails at
+// all.
+//
+// This model is pretty close to Bazel's configuration transitions. The mapping
+// between concepts in Soong and Bazel is as follows:
+//   - Module == configured target
+//   - Variant == configuration
+//   - Variation name == configuration flag
+//   - Variation == configuration flag value
+//   - Outgoing transition == attribute transition
+//   - Incoming transition == rule transition
+//
+// The Split() method does not have a Bazel equivalent and Bazel split
+// transitions do not have a Soong equivalent.
+//
+// Mutate() does not make sense in Bazel due to the different models of the
+// two systems: when creating new variations, Soong clones the old module and
+// thus some way is needed to change it state whereas Bazel creates each
+// configuration of a given configured target anew.
+type TransitionMutator[T blueprint.TransitionInfo] interface {
+	// Split returns the set of variations that should be created for a module no
+	// matter who depends on it. Used when Make depends on a particular variation
+	// or when the module knows its variations just based on information given to
+	// it in the Blueprint file. This method should not mutate the module it is
+	// called on.
+	Split(ctx BaseModuleContext) []T
+
+	// OutgoingTransition is called on a module to determine which variation it wants
+	// from its direct dependencies. The dependency itself can override this decision.
+	// This method should not mutate the module itself.
+	OutgoingTransition(ctx OutgoingTransitionContext, sourceTransitionInfo T) T
+
+	// IncomingTransition is called on a module to determine which variation it should
+	// be in based on the variation modules that depend on it want. This gives the module
+	// a final say about its own variations. This method should not mutate the module
+	// itself.
+	IncomingTransition(ctx IncomingTransitionContext, incomingTransitionInfo T) T
+
+	// Mutate is called after a module was split into multiple variations on each variation.
+	// It should not split the module any further but adding new dependencies is
+	// fine. Unlike all the other methods on TransitionMutator, this method is
+	// allowed to mutate the module.
+	Mutate(ctx BottomUpMutatorContext, transitionInfo T)
+
+	// TransitionInfoFromVariation is called when adding dependencies with an explicit variation after the
+	// TransitionMutator has already run.  It takes a variation name and returns a TransitionInfo for that
+	// variation.  It may not be possible for some TransitionMutators to generate an appropriate TransitionInfo
+	// if the variation does not contain all the information from the TransitionInfo, in which case the
+	// TransitionMutator can panic in TransitionInfoFromVariation, and adding dependencies with explicit variations
+	// for this TransitionMutator is not supported.
+	TransitionInfoFromVariation(variation string) T
+}
+
+// androidTransitionMutator is a copy of blueprint.TransitionMutator with the context argument types changed
+// from blueprint.BaseModuleContext to BaseModuleContext, etc.
+type androidTransitionMutator interface {
+	Split(ctx BaseModuleContext) []blueprint.TransitionInfo
+	OutgoingTransition(ctx OutgoingTransitionContext, sourceTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo
+	IncomingTransition(ctx IncomingTransitionContext, incomingTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo
+	Mutate(ctx BottomUpMutatorContext, transitionInfo blueprint.TransitionInfo)
+	TransitionInfoFromVariation(variation string) blueprint.TransitionInfo
+}
+
+// VariationTransitionMutator is a simpler version of androidTransitionMutator that passes variation strings instead
+// of a blueprint.TransitionInfo object.
+type VariationTransitionMutator interface {
+	Split(ctx BaseModuleContext) []string
+	OutgoingTransition(ctx OutgoingTransitionContext, sourceVariation string) string
+	IncomingTransition(ctx IncomingTransitionContext, incomingVariation string) string
+	Mutate(ctx BottomUpMutatorContext, variation string)
+}
+
+type IncomingTransitionContext interface {
+	ArchModuleContext
+	ModuleProviderContext
+	ModuleErrorContext
+
+	// Module returns the target of the dependency edge for which the transition
+	// is being computed
+	Module() Module
+
+	// ModuleName returns the name of the module.  This is generally the value that was returned by Module.Name() when
+	// the module was created, but may have been modified by calls to BottomUpMutatorContext.Rename.
+	ModuleName() string
+
+	// DepTag() Returns the dependency tag through which this dependency is
+	// reached
+	DepTag() blueprint.DependencyTag
+
+	// Config returns the configuration for the build.
+	Config() Config
+
+	DeviceConfig() DeviceConfig
+
+	// IsAddingDependency returns true if the transition is being called while adding a dependency
+	// after the transition mutator has already run, or false if it is being called when the transition
+	// mutator is running.  This should be used sparingly, all uses will have to be removed in order
+	// to support creating variants on demand.
+	IsAddingDependency() bool
+}
+
+type OutgoingTransitionContext interface {
+	ArchModuleContext
+	ModuleProviderContext
+
+	// Module returns the target of the dependency edge for which the transition
+	// is being computed
+	Module() Module
+
+	// ModuleName returns the name of the module.  This is generally the value that was returned by Module.Name() when
+	// the module was created, but may have been modified by calls to BottomUpMutatorContext.Rename.
+	ModuleName() string
+
+	// DepTag() Returns the dependency tag through which this dependency is
+	// reached
+	DepTag() blueprint.DependencyTag
+
+	// Config returns the configuration for the build.
+	Config() Config
+
+	DeviceConfig() DeviceConfig
+}
+
+// androidTransitionMutatorAdapter wraps an androidTransitionMutator to convert it to a blueprint.TransitionInfo
+// by converting the blueprint.*Context objects into android.*Context objects.
+type androidTransitionMutatorAdapter struct {
+	finalPhase bool
+	mutator    androidTransitionMutator
+	name       string
+}
+
+func (a *androidTransitionMutatorAdapter) Split(ctx blueprint.BaseModuleContext) []blueprint.TransitionInfo {
+	if a.finalPhase {
+		panic("TransitionMutator not allowed in FinalDepsMutators")
+	}
+	m := ctx.Module().(Module)
+	moduleContext := m.base().baseModuleContextFactory(ctx)
+	return a.mutator.Split(&moduleContext)
+}
+
+func (a *androidTransitionMutatorAdapter) OutgoingTransition(bpctx blueprint.OutgoingTransitionContext,
+	sourceTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {
+	m := bpctx.Module().(Module)
+	ctx := outgoingTransitionContextPool.Get()
+	defer outgoingTransitionContextPool.Put(ctx)
+	*ctx = outgoingTransitionContextImpl{
+		archModuleContext: m.base().archModuleContextFactory(bpctx),
+		bp:                bpctx,
+	}
+	return a.mutator.OutgoingTransition(ctx, sourceTransitionInfo)
+}
+
+func (a *androidTransitionMutatorAdapter) IncomingTransition(bpctx blueprint.IncomingTransitionContext,
+	incomingTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {
+	m := bpctx.Module().(Module)
+	ctx := incomingTransitionContextPool.Get()
+	defer incomingTransitionContextPool.Put(ctx)
+	*ctx = incomingTransitionContextImpl{
+		archModuleContext: m.base().archModuleContextFactory(bpctx),
+		bp:                bpctx,
+	}
+	return a.mutator.IncomingTransition(ctx, incomingTransitionInfo)
+}
+
+func (a *androidTransitionMutatorAdapter) Mutate(ctx blueprint.BottomUpMutatorContext, transitionInfo blueprint.TransitionInfo) {
+	am := ctx.Module().(Module)
+	variation := transitionInfo.Variation()
+	if variation != "" {
+		// TODO: this should really be checking whether the TransitionMutator affected this module, not
+		//  the empty variant, but TransitionMutator has no concept of skipping a module.
+		base := am.base()
+		base.commonProperties.DebugMutators = append(base.commonProperties.DebugMutators, a.name)
+		base.commonProperties.DebugVariations = append(base.commonProperties.DebugVariations, variation)
+	}
+
+	mctx := bottomUpMutatorContextFactory(ctx, am, a.finalPhase)
+	defer bottomUpMutatorContextPool.Put(mctx)
+	a.mutator.Mutate(mctx, transitionInfo)
+}
+
+func (a *androidTransitionMutatorAdapter) TransitionInfoFromVariation(variation string) blueprint.TransitionInfo {
+	return a.mutator.TransitionInfoFromVariation(variation)
+}
+
+// variationTransitionMutatorAdapter wraps a VariationTransitionMutator to convert it to an androidTransitionMutator
+// by wrapping the string info object used by VariationTransitionMutator with variationTransitionInfo to convert it into
+// blueprint.TransitionInfo.
+type variationTransitionMutatorAdapter struct {
+	m VariationTransitionMutator
+}
+
+func (v variationTransitionMutatorAdapter) Split(ctx BaseModuleContext) []blueprint.TransitionInfo {
+	variations := v.m.Split(ctx)
+	transitionInfos := make([]blueprint.TransitionInfo, 0, len(variations))
+	for _, variation := range variations {
+		transitionInfos = append(transitionInfos, variationTransitionInfo{variation})
+	}
+	return transitionInfos
+}
+
+func (v variationTransitionMutatorAdapter) OutgoingTransition(ctx OutgoingTransitionContext,
+	sourceTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {
+
+	sourceVariationTransitionInfo, _ := sourceTransitionInfo.(variationTransitionInfo)
+	outgoingVariation := v.m.OutgoingTransition(ctx, sourceVariationTransitionInfo.variation)
+	return variationTransitionInfo{outgoingVariation}
+}
+
+func (v variationTransitionMutatorAdapter) IncomingTransition(ctx IncomingTransitionContext,
+	incomingTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {
+
+	incomingVariationTransitionInfo, _ := incomingTransitionInfo.(variationTransitionInfo)
+	variation := v.m.IncomingTransition(ctx, incomingVariationTransitionInfo.variation)
+	return variationTransitionInfo{variation}
+}
+
+func (v variationTransitionMutatorAdapter) Mutate(ctx BottomUpMutatorContext, transitionInfo blueprint.TransitionInfo) {
+	variationTransitionInfo, _ := transitionInfo.(variationTransitionInfo)
+	v.m.Mutate(ctx, variationTransitionInfo.variation)
+}
+
+func (v variationTransitionMutatorAdapter) TransitionInfoFromVariation(variation string) blueprint.TransitionInfo {
+	return variationTransitionInfo{variation}
+}
+
+// variationTransitionInfo is a blueprint.TransitionInfo that contains a single variation string.
+type variationTransitionInfo struct {
+	variation string
+}
+
+func (v variationTransitionInfo) Variation() string {
+	return v.variation
+}
+
+// genericTransitionMutatorAdapter wraps a TransitionMutator to convert it to an androidTransitionMutator
+type genericTransitionMutatorAdapter[T blueprint.TransitionInfo] struct {
+	m TransitionMutator[T]
+}
+
+// NewGenericTransitionMutatorAdapter is used to convert a generic TransitionMutator[T] into an androidTransitionMutator
+// that can be passed to RegisterMutatorsContext.InfoBasedTransition.
+func NewGenericTransitionMutatorAdapter[T blueprint.TransitionInfo](m TransitionMutator[T]) androidTransitionMutator {
+	return &genericTransitionMutatorAdapter[T]{m}
+}
+
+func (g *genericTransitionMutatorAdapter[T]) convertTransitionInfoToT(transitionInfo blueprint.TransitionInfo) T {
+	if transitionInfo == nil {
+		var zero T
+		return zero
+	}
+	return transitionInfo.(T)
+}
+
+func (g *genericTransitionMutatorAdapter[T]) Split(ctx BaseModuleContext) []blueprint.TransitionInfo {
+	transitionInfos := g.m.Split(ctx)
+	bpTransitionInfos := make([]blueprint.TransitionInfo, 0, len(transitionInfos))
+	for _, transitionInfo := range transitionInfos {
+		bpTransitionInfos = append(bpTransitionInfos, transitionInfo)
+	}
+	return bpTransitionInfos
+}
+
+func (g *genericTransitionMutatorAdapter[T]) OutgoingTransition(ctx OutgoingTransitionContext, sourceTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {
+	sourceTransitionInfoT := g.convertTransitionInfoToT(sourceTransitionInfo)
+	return g.m.OutgoingTransition(ctx, sourceTransitionInfoT)
+}
+
+func (g *genericTransitionMutatorAdapter[T]) IncomingTransition(ctx IncomingTransitionContext, incomingTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {
+	incomingTransitionInfoT := g.convertTransitionInfoToT(incomingTransitionInfo)
+	return g.m.IncomingTransition(ctx, incomingTransitionInfoT)
+}
+
+func (g *genericTransitionMutatorAdapter[T]) Mutate(ctx BottomUpMutatorContext, transitionInfo blueprint.TransitionInfo) {
+	transitionInfoT := g.convertTransitionInfoToT(transitionInfo)
+	g.m.Mutate(ctx, transitionInfoT)
+}
+
+func (g *genericTransitionMutatorAdapter[T]) TransitionInfoFromVariation(variation string) blueprint.TransitionInfo {
+	return g.m.TransitionInfoFromVariation(variation)
+}
+
+// incomingTransitionContextImpl wraps a blueprint.IncomingTransitionContext to convert it to an
+// IncomingTransitionContext.
+type incomingTransitionContextImpl struct {
+	archModuleContext
+	bp blueprint.IncomingTransitionContext
+}
+
+func (c *incomingTransitionContextImpl) Module() Module {
+	return c.bp.Module().(Module)
+}
+
+func (c *incomingTransitionContextImpl) ModuleName() string {
+	return c.bp.ModuleName()
+}
+
+func (c *incomingTransitionContextImpl) DepTag() blueprint.DependencyTag {
+	return c.bp.DepTag()
+}
+
+func (c *incomingTransitionContextImpl) Config() Config {
+	return c.bp.Config().(Config)
+}
+
+func (c *incomingTransitionContextImpl) DeviceConfig() DeviceConfig {
+	return DeviceConfig{c.bp.Config().(Config).deviceConfig}
+}
+
+func (c *incomingTransitionContextImpl) IsAddingDependency() bool {
+	return c.bp.IsAddingDependency()
+}
+
+func (c *incomingTransitionContextImpl) provider(provider blueprint.AnyProviderKey) (any, bool) {
+	return c.bp.Provider(provider)
+}
+
+func (c *incomingTransitionContextImpl) ModuleErrorf(fmt string, args ...interface{}) {
+	c.bp.ModuleErrorf(fmt, args)
+}
+
+func (c *incomingTransitionContextImpl) PropertyErrorf(property, fmt string, args ...interface{}) {
+	c.bp.PropertyErrorf(property, fmt, args)
+}
+
+// outgoingTransitionContextImpl wraps a blueprint.OutgoingTransitionContext to convert it to an
+// OutgoingTransitionContext.
+type outgoingTransitionContextImpl struct {
+	archModuleContext
+	bp blueprint.OutgoingTransitionContext
+}
+
+func (c *outgoingTransitionContextImpl) Module() Module {
+	return c.bp.Module().(Module)
+}
+
+func (c *outgoingTransitionContextImpl) ModuleName() string {
+	return c.bp.ModuleName()
+}
+
+func (c *outgoingTransitionContextImpl) DepTag() blueprint.DependencyTag {
+	return c.bp.DepTag()
+}
+
+func (c *outgoingTransitionContextImpl) Config() Config {
+	return c.bp.Config().(Config)
+}
+
+func (c *outgoingTransitionContextImpl) DeviceConfig() DeviceConfig {
+	return DeviceConfig{c.bp.Config().(Config).deviceConfig}
+}
+
+func (c *outgoingTransitionContextImpl) provider(provider blueprint.AnyProviderKey) (any, bool) {
+	return c.bp.Provider(provider)
+}
diff --git a/android/transition_test.go b/android/transition_test.go
new file mode 100644
index 0000000..f7618f3
--- /dev/null
+++ b/android/transition_test.go
@@ -0,0 +1,162 @@
+// 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"
+
+type testTransitionMutator struct {
+	split              func(ctx BaseModuleContext) []string
+	outgoingTransition func(ctx OutgoingTransitionContext, sourceVariation string) string
+	incomingTransition func(ctx IncomingTransitionContext, incomingVariation string) string
+	mutate             func(ctx BottomUpMutatorContext, variation string)
+}
+
+func (t *testTransitionMutator) Split(ctx BaseModuleContext) []string {
+	if t.split != nil {
+		return t.split(ctx)
+	}
+	return []string{""}
+}
+
+func (t *testTransitionMutator) OutgoingTransition(ctx OutgoingTransitionContext, sourceVariation string) string {
+	if t.outgoingTransition != nil {
+		return t.outgoingTransition(ctx, sourceVariation)
+	}
+	return sourceVariation
+}
+
+func (t *testTransitionMutator) IncomingTransition(ctx IncomingTransitionContext, incomingVariation string) string {
+	if t.incomingTransition != nil {
+		return t.incomingTransition(ctx, incomingVariation)
+	}
+	return incomingVariation
+}
+
+func (t *testTransitionMutator) Mutate(ctx BottomUpMutatorContext, variation string) {
+	if t.mutate != nil {
+		t.mutate(ctx, variation)
+	}
+}
+
+func TestModuleString(t *testing.T) {
+	bp := `
+		test {
+			name: "foo",
+		}
+	`
+
+	var moduleStrings []string
+
+	GroupFixturePreparers(
+		FixtureRegisterWithContext(func(ctx RegistrationContext) {
+
+			ctx.PreArchMutators(func(ctx RegisterMutatorsContext) {
+				ctx.Transition("pre_arch", &testTransitionMutator{
+					split: func(ctx BaseModuleContext) []string {
+						moduleStrings = append(moduleStrings, ctx.Module().String())
+						return []string{"a", "b"}
+					},
+				})
+			})
+
+			ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
+				ctx.Transition("pre_deps", &testTransitionMutator{
+					split: func(ctx BaseModuleContext) []string {
+						moduleStrings = append(moduleStrings, ctx.Module().String())
+						return []string{"c", "d"}
+					},
+				})
+			})
+
+			ctx.PostDepsMutators(func(ctx RegisterMutatorsContext) {
+				ctx.Transition("post_deps", &testTransitionMutator{
+					split: func(ctx BaseModuleContext) []string {
+						moduleStrings = append(moduleStrings, ctx.Module().String())
+						return []string{"e", "f"}
+					},
+					outgoingTransition: func(ctx OutgoingTransitionContext, sourceVariation string) string {
+						return ""
+					},
+				})
+				ctx.BottomUp("rename_bottom_up", func(ctx BottomUpMutatorContext) {
+					moduleStrings = append(moduleStrings, ctx.Module().String())
+					ctx.Rename(ctx.Module().base().Name() + "_renamed1")
+				}).UsesRename()
+				ctx.BottomUp("final", func(ctx BottomUpMutatorContext) {
+					moduleStrings = append(moduleStrings, ctx.Module().String())
+				})
+			})
+
+			ctx.RegisterModuleType("test", mutatorTestModuleFactory)
+		}),
+		FixtureWithRootAndroidBp(bp),
+	).RunTest(t)
+
+	want := []string{
+		// Initial name.
+		"foo{}",
+
+		// After pre_arch (reversed because rename_top_down is TopDown so it visits in reverse order).
+		"foo{pre_arch:b}",
+		"foo{pre_arch:a}",
+
+		// After pre_deps (reversed because post_deps TransitionMutator.Split is TopDown).
+		"foo{pre_arch:b,pre_deps:d}",
+		"foo{pre_arch:b,pre_deps:c}",
+		"foo{pre_arch:a,pre_deps:d}",
+		"foo{pre_arch:a,pre_deps:c}",
+
+		// After post_deps.
+		"foo{pre_arch:a,pre_deps:c,post_deps:e}",
+		"foo{pre_arch:a,pre_deps:c,post_deps:f}",
+		"foo{pre_arch:a,pre_deps:d,post_deps:e}",
+		"foo{pre_arch:a,pre_deps:d,post_deps:f}",
+		"foo{pre_arch:b,pre_deps:c,post_deps:e}",
+		"foo{pre_arch:b,pre_deps:c,post_deps:f}",
+		"foo{pre_arch:b,pre_deps:d,post_deps:e}",
+		"foo{pre_arch:b,pre_deps:d,post_deps:f}",
+
+		// After rename_bottom_up.
+		"foo_renamed1{pre_arch:a,pre_deps:c,post_deps:e}",
+		"foo_renamed1{pre_arch:a,pre_deps:c,post_deps:f}",
+		"foo_renamed1{pre_arch:a,pre_deps:d,post_deps:e}",
+		"foo_renamed1{pre_arch:a,pre_deps:d,post_deps:f}",
+		"foo_renamed1{pre_arch:b,pre_deps:c,post_deps:e}",
+		"foo_renamed1{pre_arch:b,pre_deps:c,post_deps:f}",
+		"foo_renamed1{pre_arch:b,pre_deps:d,post_deps:e}",
+		"foo_renamed1{pre_arch:b,pre_deps:d,post_deps:f}",
+	}
+
+	AssertDeepEquals(t, "module String() values", want, moduleStrings)
+}
+
+func TestTransitionMutatorInFinalDeps(t *testing.T) {
+	GroupFixturePreparers(
+		FixtureRegisterWithContext(func(ctx RegistrationContext) {
+			ctx.FinalDepsMutators(func(ctx RegisterMutatorsContext) {
+				ctx.Transition("vars", &testTransitionMutator{
+					split: func(ctx BaseModuleContext) []string {
+						return []string{"a", "b"}
+					},
+				})
+			})
+
+			ctx.RegisterModuleType("test", mutatorTestModuleFactory)
+		}),
+		FixtureWithRootAndroidBp(`test {name: "foo"}`),
+	).
+		ExtendWithErrorHandler(FixtureExpectsOneErrorPattern("not allowed in FinalDepsMutators")).
+		RunTest(t)
+}
diff --git a/android/util.go b/android/util.go
index 2d269b7..8591cc6 100644
--- a/android/util.go
+++ b/android/util.go
@@ -213,21 +213,23 @@
 }
 
 // ListSetDifference checks if the two lists contain the same elements. It returns
-// a boolean which is true if there is a difference, and then returns lists of elements
+// a boolean which is true if there is a difference, and then returns lists of unique elements
 // that are in l1 but not l2, and l2 but not l1.
 func ListSetDifference[T comparable](l1, l2 []T) (bool, []T, []T) {
 	listsDiffer := false
+	l1 = firstUnique(l1)
+	l2 = firstUnique(l2)
 	diff1 := []T{}
 	diff2 := []T{}
 	m1 := setFromList(l1)
 	m2 := setFromList(l2)
-	for t := range m1 {
+	for _, t := range l1 {
 		if _, ok := m2[t]; !ok {
 			diff1 = append(diff1, t)
 			listsDiffer = true
 		}
 	}
-	for t := range m2 {
+	for _, t := range l2 {
 		if _, ok := m1[t]; !ok {
 			diff2 = append(diff2, t)
 			listsDiffer = true
@@ -238,8 +240,13 @@
 
 // Returns true if the two lists have common elements.
 func HasIntersection[T comparable](l1, l2 []T) bool {
-	_, a, b := ListSetDifference(l1, l2)
-	return len(a)+len(b) < len(setFromList(l1))+len(setFromList(l2))
+	m1 := setFromList(l1)
+	for _, x := range l2 {
+		if _, ok := m1[x]; ok {
+			return true
+		}
+	}
+	return false
 }
 
 // Returns true if the given string s is prefixed with any string in the given prefix list.
@@ -308,6 +315,20 @@
 	return
 }
 
+// FilterListByPrefixes performs the same splitting as FilterList does, but treats the passed
+// filters as prefixes
+func FilterListByPrefix(list []string, filter []string) (remainder []string, filtered []string) {
+	for _, l := range list {
+		if HasAnyPrefix(l, filter) {
+			filtered = append(filtered, l)
+		} else {
+			remainder = append(remainder, l)
+		}
+	}
+
+	return
+}
+
 // FilterListPred returns the elements of the given list for which the predicate
 // returns true. Order is kept.
 func FilterListPred(list []string, pred func(s string) bool) (filtered []string) {
@@ -660,3 +681,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 417ba89..81999f3 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -29,7 +29,7 @@
 
 func registerVariableBuildComponents(ctx RegistrationContext) {
 	ctx.PreDepsMutators(func(ctx RegisterMutatorsContext) {
-		ctx.BottomUp("variable", VariableMutator).Parallel()
+		ctx.BottomUp("variable", VariableMutator)
 	})
 }
 
@@ -162,6 +162,7 @@
 			Optimize struct {
 				Enabled *bool
 			}
+			Aaptflags []string
 		}
 
 		Uml struct {
@@ -193,6 +194,9 @@
 			Required               []string
 			Vintf_fragment_modules []string
 		}
+		SelinuxIgnoreNeverallows struct {
+			Required []string
+		}
 	} `android:"arch_variant"`
 }
 
@@ -238,7 +242,8 @@
 	DeviceMaxPageSizeSupported            *string  `json:",omitempty"`
 	DeviceNoBionicPageSizeMacro           *bool    `json:",omitempty"`
 
-	VendorApiLevel *string `json:",omitempty"`
+	VendorApiLevel             *string `json:",omitempty"`
+	VendorApiLevelPropOverride *string `json:",omitempty"`
 
 	DeviceSecondaryArch        *string  `json:",omitempty"`
 	DeviceSecondaryArchVariant *string  `json:",omitempty"`
@@ -334,10 +339,21 @@
 	HWASanIncludePaths []string `json:",omitempty"`
 	HWASanExcludePaths []string `json:",omitempty"`
 
-	VendorPath    *string `json:",omitempty"`
-	OdmPath       *string `json:",omitempty"`
-	ProductPath   *string `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"`
+	RecoveryPath          *string `json:",omitempty"`
+	BuildingRecoveryImage *bool   `json:",omitempty"`
 
 	ClangTidy  *bool   `json:",omitempty"`
 	TidyChecks *string `json:",omitempty"`
@@ -395,10 +411,10 @@
 
 	Ndk_abis *bool `json:",omitempty"`
 
-	TrimmedApex                  *bool `json:",omitempty"`
-	ForceApexSymlinkOptimization *bool `json:",omitempty"`
-	CompressedApex               *bool `json:",omitempty"`
-	Aml_abis                     *bool `json:",omitempty"`
+	ForceApexSymlinkOptimization *bool   `json:",omitempty"`
+	CompressedApex               *bool   `json:",omitempty"`
+	DefaultApexPayloadType       *string `json:",omitempty"`
+	Aml_abis                     *bool   `json:",omitempty"`
 
 	DexpreoptGlobalConfig *string `json:",omitempty"`
 
@@ -475,6 +491,8 @@
 
 	ProductManufacturer string `json:",omitempty"`
 	ProductBrand        string `json:",omitempty"`
+	ProductDevice       string `json:",omitempty"`
+	ProductModel        string `json:",omitempty"`
 
 	ReleaseVersion          string   `json:",omitempty"`
 	ReleaseAconfigValueSets []string `json:",omitempty"`
@@ -513,8 +531,10 @@
 	SystemExtPropFiles []string `json:",omitempty"`
 	ProductPropFiles   []string `json:",omitempty"`
 	OdmPropFiles       []string `json:",omitempty"`
+	VendorPropFiles    []string `json:",omitempty"`
 
-	EnableUffdGc *string `json:",omitempty"`
+	EnableUffdGc       *string `json:",omitempty"`
+	BoardKernelVersion *string `json:",omitempty"`
 
 	BoardAvbEnable                         *bool    `json:",omitempty"`
 	BoardAvbSystemAddHashtreeFooterArgs    []string `json:",omitempty"`
@@ -523,13 +543,21 @@
 
 	PartitionVarsForSoongMigrationOnlyDoNotUse PartitionVariables
 
-	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"`
+
+	UseSoongNoticeXML *bool `json:",omitempty"`
 }
 
 type PartitionQualifiedVariablesType struct {
 	BuildingImage               bool   `json:",omitempty"`
+	PrebuiltImage               bool   `json:",omitempty"`
 	BoardErofsCompressor        string `json:",omitempty"`
 	BoardErofsCompressHints     string `json:",omitempty"`
 	BoardErofsPclusterSize      string `json:",omitempty"`
@@ -556,6 +584,19 @@
 	BoardAvbRollbackIndexLocation string `json:",omitempty"`
 }
 
+type BoardSuperPartitionGroupProps struct {
+	GroupSize     string   `json:",omitempty"`
+	PartitionList []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
@@ -576,14 +617,104 @@
 	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"`
+	VendorSecurityPatch     string `json:",omitempty"`
+	OdmSecurityPatch        string `json:",omitempty"`
+	SystemDlkmSecurityPatch string `json:",omitempty"`
+	VendorDlkmSecurityPatch string `json:",omitempty"`
+	OdmDlkmSecurityPatch    string `json:",omitempty"`
 
-	ProductPackages      []string `json:",omitempty"`
-	ProductPackagesDebug []string `json:",omitempty"`
+	BuildingSystemOtherImage bool `json:",omitempty"`
+
+	// Boot image stuff
+	BuildingRamdiskImage              bool     `json:",omitempty"`
+	ProductBuildBootImage             bool     `json:",omitempty"`
+	ProductBuildVendorBootImage       string   `json:",omitempty"`
+	ProductBuildInitBootImage         bool     `json:",omitempty"`
+	BoardUsesRecoveryAsBoot           bool     `json:",omitempty"`
+	BoardPrebuiltBootimage            string   `json:",omitempty"`
+	BoardPrebuiltInitBootimage        string   `json:",omitempty"`
+	BoardBootimagePartitionSize       string   `json:",omitempty"`
+	BoardVendorBootimagePartitionSize string   `json:",omitempty"`
+	BoardInitBootimagePartitionSize   string   `json:",omitempty"`
+	BoardBootHeaderVersion            string   `json:",omitempty"`
+	TargetKernelPath                  string   `json:",omitempty"`
+	BoardUsesGenericKernelImage       bool     `json:",omitempty"`
+	BootSecurityPatch                 string   `json:",omitempty"`
+	InitBootSecurityPatch             string   `json:",omitempty"`
+	BoardIncludeDtbInBootimg          bool     `json:",omitempty"`
+	InternalKernelCmdline             []string `json:",omitempty"`
+	InternalBootconfig                []string `json:",omitempty"`
+	InternalBootconfigFile            string   `json:",omitempty"`
+
+	// Super image stuff
+	ProductUseDynamicPartitions       bool                                     `json:",omitempty"`
+	ProductRetrofitDynamicPartitions  bool                                     `json:",omitempty"`
+	ProductBuildSuperPartition        bool                                     `json:",omitempty"`
+	BoardSuperPartitionSize           string                                   `json:",omitempty"`
+	BoardSuperPartitionMetadataDevice string                                   `json:",omitempty"`
+	BoardSuperPartitionBlockDevices   []string                                 `json:",omitempty"`
+	BoardSuperPartitionGroups         map[string]BoardSuperPartitionGroupProps `json:",omitempty"`
+	ProductVirtualAbOta               bool                                     `json:",omitempty"`
+	ProductVirtualAbOtaRetrofit       bool                                     `json:",omitempty"`
+	ProductVirtualAbCompression       bool                                     `json:",omitempty"`
+	ProductVirtualAbCompressionMethod string                                   `json:",omitempty"`
+	ProductVirtualAbCompressionFactor string                                   `json:",omitempty"`
+	ProductVirtualAbCowVersion        string                                   `json:",omitempty"`
+	AbOtaUpdater                      bool                                     `json:",omitempty"`
+	AbOtaPartitions                   []string                                 `json:",omitempty"`
+	AbOtaKeys                         []string                                 `json:",omitempty"`
+	AbOtaPostInstallConfig            []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"`
+
+	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"`
+
+	VendorRamdiskKernelModules       []string `json:",omitempty"`
+	VendorRamdiskKernelBlocklistFile string   `json:",omitempty"`
+	VendorRamdiskKernelLoadModules   []string `json:",omitempty"`
+	VendorRamdiskKernelOptionsFile   string   `json:",omitempty"`
+
+	ProductFsverityGenerateMetadata bool `json:",omitempty"`
+
+	TargetScreenDensity string `json:",omitempty"`
+
+	PrivateRecoveryUiProperties map[string]string `json:",omitempty"`
+
+	PrebuiltBootloader string `json:",omitempty"`
+
+	ProductFsCasefold    string `json:",omitempty"`
+	ProductQuotaProjid   string `json:",omitempty"`
+	ProductFsCompression string `json:",omitempty"`
+
+	ReleaseToolsExtensionDir string `json:",omitempty"`
 }
 
 func boolPtr(v bool) *bool {
@@ -634,7 +765,6 @@
 		Malloc_zero_contents:         boolPtr(true),
 		Malloc_pattern_fill_contents: boolPtr(false),
 		Safestack:                    boolPtr(false),
-		TrimmedApex:                  boolPtr(false),
 		Build_from_text_stub:         boolPtr(false),
 
 		BootJars:     ConfiguredJarList{apexes: []string{}, jars: []string{}},
diff --git a/android/variable_test.go b/android/variable_test.go
index 73dc052..1d928f2 100644
--- a/android/variable_test.go
+++ b/android/variable_test.go
@@ -299,7 +299,7 @@
 		FixtureWithRootAndroidBp(bp),
 	).RunTest(t)
 
-	foo := result.ModuleForTests("foo", "").Module().(*productVariablesDefaultsTestModule)
+	foo := result.ModuleForTests(t, "foo", "").Module().(*productVariablesDefaultsTestModule)
 
 	want := []string{"defaults", "module", "product_variable_defaults", "product_variable_module"}
 	AssertDeepEquals(t, "foo", want, foo.properties.Foo)
@@ -360,7 +360,7 @@
 		FixtureWithRootAndroidBp(bp),
 	).RunTest(t)
 
-	foo := result.ModuleForTests("foo", "android_arm64_armv8-a").Module().(*productVariablesDefaultsTestModule)
+	foo := result.ModuleForTests(t, "foo", "android_arm64_armv8-a").Module().(*productVariablesDefaultsTestModule)
 
 	want := []string{"module", "arm64"}
 	AssertDeepEquals(t, "foo", want, foo.properties.Foo)
diff --git a/android/vendor_api_levels.go b/android/vendor_api_levels.go
new file mode 100644
index 0000000..d32bc56
--- /dev/null
+++ b/android/vendor_api_levels.go
@@ -0,0 +1,51 @@
+// 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"
+	"strconv"
+)
+
+func getSdkVersionOfVendorApiLevel(apiLevel int) (int, bool) {
+	ok := true
+	sdkVersion := -1
+	switch apiLevel {
+	case 202404:
+		sdkVersion = 35
+	case 202504:
+		sdkVersion = 36
+	case 202604:
+		sdkVersion = 37
+	default:
+		ok = false
+	}
+	return sdkVersion, ok
+}
+
+func GetSdkVersionForVendorApiLevel(vendorApiLevel string) (ApiLevel, error) {
+	vendorApiLevelInt, err := strconv.Atoi(vendorApiLevel)
+	if err != nil {
+		return NoneApiLevel, fmt.Errorf("The vendor API level %q must be able to be parsed as an integer", vendorApiLevel)
+	}
+	if vendorApiLevelInt < 35 {
+		return uncheckedFinalApiLevel(vendorApiLevelInt), nil
+	}
+
+	if sdkInt, ok := getSdkVersionOfVendorApiLevel(vendorApiLevelInt); ok {
+		return uncheckedFinalApiLevel(sdkInt), nil
+	}
+	return NoneApiLevel, fmt.Errorf("Unknown vendor API level %q. Requires updating the map in vendor_api_level.go?", vendorApiLevel)
+}
diff --git a/android/vintf_data.go b/android/vintf_data.go
new file mode 100644
index 0000000..2909817
--- /dev/null
+++ b/android/vintf_data.go
@@ -0,0 +1,172 @@
+// 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 Path
+	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().
+		Implicits(inputPaths).
+		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
+
+	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/android/vintf_fragment.go b/android/vintf_fragment.go
index 329eac9..85beb72 100644
--- a/android/vintf_fragment.go
+++ b/android/vintf_fragment.go
@@ -19,13 +19,14 @@
 	Src string `android:"path"`
 }
 
-type vintfFragmentModule struct {
+type VintfFragmentModule struct {
 	ModuleBase
+	ApexModuleBase
 
 	properties vintfFragmentProperties
 
 	installDirPath InstallPath
-	outputFilePath OutputPath
+	outputFilePath Path
 }
 
 func init() {
@@ -40,16 +41,16 @@
 // Vintf fragment files formerly listed in vintf_fragment property would be transformed into
 // this module type.
 func vintfLibraryFactory() Module {
-	m := &vintfFragmentModule{}
+	m := &VintfFragmentModule{}
 	m.AddProperties(
 		&m.properties,
 	)
-	InitAndroidArchModule(m, DeviceSupported, MultilibFirst)
+	InitAndroidArchModule(m, DeviceSupported, MultilibCommon)
 
 	return m
 }
 
-func (m *vintfFragmentModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+func (m *VintfFragmentModule) GenerateAndroidBuildActions(ctx ModuleContext) {
 	builder := NewRuleBuilder(pctx, ctx)
 	srcVintfFragment := PathForModuleSrc(ctx, m.properties.Src)
 	processedVintfFragment := PathForModuleOut(ctx, srcVintfFragment.Base())
@@ -64,13 +65,17 @@
 	builder.Build("assemble_vintf", "Process vintf fragment "+processedVintfFragment.String())
 
 	m.installDirPath = PathForModuleInstall(ctx, "etc", "vintf", "manifest")
-	m.outputFilePath = processedVintfFragment.OutputPath
+	m.outputFilePath = processedVintfFragment
 
 	ctx.InstallFile(m.installDirPath, processedVintfFragment.Base(), processedVintfFragment)
 }
 
+func (m *VintfFragmentModule) OutputFile() Path {
+	return m.outputFilePath
+}
+
 // Make this module visible to AndroidMK so it can be referenced from modules defined from Android.mk files
-func (m *vintfFragmentModule) AndroidMkEntries() []AndroidMkEntries {
+func (m *VintfFragmentModule) AndroidMkEntries() []AndroidMkEntries {
 	return []AndroidMkEntries{{
 		Class:      "ETC",
 		OutputFile: OptionalPathForPath(m.outputFilePath),
@@ -82,3 +87,11 @@
 		},
 	}}
 }
+
+var _ ApexModule = (*VintfFragmentModule)(nil)
+
+// Implements android.ApexModule
+func (m *VintfFragmentModule) ShouldSupportSdkVersion(ctx BaseModuleContext, sdkVersion ApiLevel) error {
+	// VintfFragmetModule is independent from the SDK version.
+	return nil
+}
diff --git a/android/vintf_fragment_test.go b/android/vintf_fragment_test.go
index 8be534c..7f0078c 100644
--- a/android/vintf_fragment_test.go
+++ b/android/vintf_fragment_test.go
@@ -29,8 +29,8 @@
 
 	testResult := PrepareForTestWithAndroidBuildComponents.RunTestWithBp(t, bp)
 
-	vintfFragmentBuild := testResult.TestContext.ModuleForTests("test_vintf_fragment", "android_arm64_armv8-a").Rule("assemble_vintf")
+	vintfFragmentBuild := testResult.TestContext.ModuleForTests(t, "test_vintf_fragment", "android_common").Rule("assemble_vintf")
 	if !strings.Contains(vintfFragmentBuild.RuleParams.Command, "assemble_vintf") {
-		t.Errorf("Vintf_manifest build command does not process with assemble_vintf : " + vintfFragmentBuild.RuleParams.Command)
+		t.Error("Vintf_manifest build command does not process with assemble_vintf : " + vintfFragmentBuild.RuleParams.Command)
 	}
 }
diff --git a/android/visibility.go b/android/visibility.go
index 61f2200..4837c7d 100644
--- a/android/visibility.go
+++ b/android/visibility.go
@@ -268,7 +268,7 @@
 // The rule checker needs to be registered before defaults expansion to correctly check that
 // //visibility:xxx isn't combined with other packages in the same list in any one module.
 func RegisterVisibilityRuleChecker(ctx RegisterMutatorsContext) {
-	ctx.BottomUp("visibilityRuleChecker", visibilityRuleChecker).Parallel()
+	ctx.BottomUp("visibilityRuleChecker", visibilityRuleChecker)
 }
 
 // Registers the function that gathers the visibility rules for each module.
@@ -278,12 +278,12 @@
 // the complete visibility lists from flat lists and after the package info is gathered to ensure
 // that default_visibility is available.
 func RegisterVisibilityRuleGatherer(ctx RegisterMutatorsContext) {
-	ctx.BottomUp("visibilityRuleGatherer", visibilityRuleGatherer).Parallel()
+	ctx.BottomUp("visibilityRuleGatherer", visibilityRuleGatherer)
 }
 
 // This must be registered after the deps have been resolved.
 func RegisterVisibilityRuleEnforcer(ctx RegisterMutatorsContext) {
-	ctx.BottomUp("visibilityRuleEnforcer", visibilityRuleEnforcer).Parallel()
+	ctx.BottomUp("visibilityRuleEnforcer", visibilityRuleEnforcer)
 }
 
 // Checks the per-module visibility rule lists before defaults expansion.
@@ -529,7 +529,7 @@
 
 		rule := effectiveVisibilityRules(ctx.Config(), depQualified)
 		if !rule.matches(qualified) {
-			ctx.ModuleErrorf("depends on %s which is not visible to this module\nYou may need to add %q to its visibility", depQualified, "//"+ctx.ModuleDir())
+			ctx.ModuleErrorf("depends on %s which is not visible to this module\nYou may need to add %q to its visibility, %#v", depQualified, "//"+ctx.ModuleDir(), ctx.OtherModuleDependencyTag(dep))
 		}
 	})
 }
diff --git a/android/visibility_test.go b/android/visibility_test.go
index 1a2eeca..277be0f 100644
--- a/android/visibility_test.go
+++ b/android/visibility_test.go
@@ -2098,8 +2098,9 @@
 }
 
 type mockFilesystemModuleProperties struct {
-	Partition_type *string
-	Deps           []string
+	Partition_type    *string
+	Deps              []string
+	Is_auto_generated *bool
 }
 
 type mockFilesystemModule struct {
diff --git a/android_sdk/sdk_repo_host_test.go b/android_sdk/sdk_repo_host_test.go
index 0688921..ce84204 100644
--- a/android_sdk/sdk_repo_host_test.go
+++ b/android_sdk/sdk_repo_host_test.go
@@ -44,7 +44,7 @@
 	`)
 
 	// produces "sdk-repo-{OS}-platform-tools.zip"
-	result.ModuleForTests("platform-tools", "linux_glibc_common").Output("sdk-repo-linux-platform-tools.zip")
+	result.ModuleForTests(t, "platform-tools", "linux_glibc_common").Output("sdk-repo-linux-platform-tools.zip")
 }
 
 func TestRemapPackageSpecs(t *testing.T) {
diff --git a/androidmk/parser/parser_test.go b/androidmk/parser/parser_test.go
index e238f8b..21baf6b 100644
--- a/androidmk/parser/parser_test.go
+++ b/androidmk/parser/parser_test.go
@@ -142,7 +142,7 @@
 		t.Fatalf("Unexpected errors while parsing: %v", errs)
 	}
 
-	if got[0].End() < got[len(got) -1].Pos() {
-		t.Errorf("Rule's end (%d) is smaller than directive that inside of rule's start (%v)\n", got[0].End(), got[len(got) -1].Pos())
+	if got[0].End() < got[len(got)-1].Pos() {
+		t.Errorf("Rule's end (%d) is smaller than directive that inside of rule's start (%v)\n", got[0].End(), got[len(got)-1].Pos())
 	}
 }
diff --git a/apex/Android.bp b/apex/Android.bp
index 4848513..870ca7e 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -7,6 +7,7 @@
     pkgPath: "android/soong/apex",
     deps: [
         "blueprint",
+        "blueprint-bpmodify",
         "soong",
         "soong-aconfig",
         "soong-aconfig-codegen",
@@ -33,6 +34,7 @@
         "vndk.go",
     ],
     testSrcs: [
+        "aconfig_test.go",
         "apex_test.go",
         "bootclasspath_fragment_test.go",
         "classpath_element_test.go",
diff --git a/apex/aconfig_test.go b/apex/aconfig_test.go
index bb811f5..0eb8ef4 100644
--- a/apex/aconfig_test.go
+++ b/apex/aconfig_test.go
@@ -33,6 +33,7 @@
 })
 
 func TestValidationAcrossContainersExportedPass(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		name string
 		bp   string
@@ -59,6 +60,7 @@
 					apex_available: [
 						"myapex",
 					],
+					compile_dex: true,
 				}
 				aconfig_declarations {
 					name: "my_aconfig_declarations_foo",
@@ -294,6 +296,7 @@
 	}
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
 			android.GroupFixturePreparers(
 				java.PrepareForTestWithJavaDefaultModules,
 				cc.PrepareForTestWithCcBuildComponents,
@@ -309,6 +312,7 @@
 }
 
 func TestValidationAcrossContainersNotExportedFail(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		name          string
 		expectedError string
@@ -336,6 +340,7 @@
 					apex_available: [
 						"myapex",
 					],
+					compile_dex: true,
 				}
 				aconfig_declarations {
 					name: "my_aconfig_declarations_foo",
@@ -679,7 +684,7 @@
 				}
 				filegroup {
 						name: "my_filegroup_foo_srcjars",
-						srcs: [
+						device_common_srcs: [
 								":my_aconfig_declarations_group_foo{.srcjars}",
 						],
 				}
@@ -709,6 +714,7 @@
 	}
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
 			errorHandler := android.FixtureExpectsNoErrors
 			if test.expectedError != "" {
 				errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(test.expectedError)
@@ -730,6 +736,7 @@
 }
 
 func TestValidationNotPropagateAcrossShared(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		name string
 		bp   string
@@ -756,6 +763,7 @@
 					apex_available: [
 						"myapex",
 					],
+					compile_dex: true,
 				}
 				java_library {
 					name: "my_java_library_foo",
@@ -786,6 +794,7 @@
 	}
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
 			android.GroupFixturePreparers(
 				java.PrepareForTestWithJavaDefaultModules,
 				cc.PrepareForTestWithCcBuildComponents,
diff --git a/apex/androidmk.go b/apex/androidmk.go
index 933682a..3a81ee4 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -226,11 +226,6 @@
 	required = append(required, a.required...)
 	targetRequired = append(targetRequired, a.TargetRequiredModuleNames()...)
 	hostRequired = append(hostRequired, a.HostRequiredModuleNames()...)
-	for _, fi := range a.filesInfo {
-		required = append(required, fi.requiredModuleNames...)
-		targetRequired = append(targetRequired, fi.targetRequiredModuleNames...)
-		hostRequired = append(hostRequired, fi.hostRequiredModuleNames...)
-	}
 	android.AndroidMkEmitAssignList(w, "LOCAL_REQUIRED_MODULES", moduleNames, a.makeModulesToInstall, required)
 	android.AndroidMkEmitAssignList(w, "LOCAL_TARGET_REQUIRED_MODULES", targetRequired)
 	android.AndroidMkEmitAssignList(w, "LOCAL_HOST_REQUIRED_MODULES", hostRequired)
@@ -261,6 +256,7 @@
 				fmt.Fprintln(w, "LOCAL_SOONG_INSTALLED_MODULE :=", a.installedFile.String())
 				fmt.Fprintln(w, "LOCAL_SOONG_INSTALL_PAIRS :=", a.outputFile.String()+":"+a.installedFile.String())
 				fmt.Fprintln(w, "LOCAL_SOONG_INSTALL_SYMLINKS := ", strings.Join(a.compatSymlinks.Strings(), " "))
+				fmt.Fprintln(w, "LOCAL_SOONG_INSTALL_PAIRS +=", a.extraInstalledPairs.String())
 			}
 			fmt.Fprintln(w, "LOCAL_APEX_KEY_PATH := ", a.apexKeysPath.String())
 
@@ -297,7 +293,7 @@
 				fmt.Fprintf(w, "$(call declare-0p-target,%s)\n", a.installedFilesFile.String())
 			}
 			for _, dist := range data.Entries.GetDistForGoals(a) {
-				fmt.Fprintf(w, dist)
+				fmt.Fprintln(w, dist)
 			}
 
 			distCoverageFiles(w, "ndk_apis_usedby_apex", a.nativeApisUsedByModuleFile.String())
diff --git a/apex/apex.go b/apex/apex.go
index d3e7eee..4d0e3f1 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -20,10 +20,12 @@
 	"fmt"
 	"path/filepath"
 	"regexp"
+	"slices"
 	"sort"
 	"strings"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/depset"
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
@@ -55,20 +57,15 @@
 }
 
 func RegisterPreDepsMutators(ctx android.RegisterMutatorsContext) {
-	ctx.BottomUp("apex_vndk_deps", apexVndkDepsMutator).Parallel()
+	ctx.BottomUp("apex_vndk_deps", apexVndkDepsMutator).UsesReverseDependencies()
 }
 
 func RegisterPostDepsMutators(ctx android.RegisterMutatorsContext) {
-	ctx.TopDown("apex_info", apexInfoMutator).Parallel()
-	ctx.BottomUp("apex_unique", apexUniqueVariationsMutator).Parallel()
-	ctx.BottomUp("apex_test_for_deps", apexTestForDepsMutator).Parallel()
-	ctx.BottomUp("apex_test_for", apexTestForMutator).Parallel()
+	ctx.BottomUp("apex_unique", apexUniqueVariationsMutator)
 	// Run mark_platform_availability before the apexMutator as the apexMutator needs to know whether
 	// it should create a platform variant.
-	ctx.BottomUp("mark_platform_availability", markPlatformAvailability).Parallel()
-	ctx.Transition("apex", &apexTransitionMutator{})
-	ctx.BottomUp("apex_directly_in_any", apexDirectlyInAnyMutator).Parallel()
-	ctx.BottomUp("apex_dcla_deps", apexDCLADepsMutator).Parallel()
+	ctx.BottomUp("mark_platform_availability", markPlatformAvailability)
+	ctx.InfoBasedTransition("apex", android.NewGenericTransitionMutatorAdapter(&apexTransitionMutator{}))
 }
 
 type apexBundleProperties struct {
@@ -106,11 +103,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
@@ -150,9 +146,6 @@
 	// Default: true.
 	Installable *bool
 
-	// Deprecated. Do not use. TODO(b/350644693) remove this after removing all usage
-	Use_vndk_as_stable *bool
-
 	// The type of filesystem to use. Either 'ext4', 'f2fs' or 'erofs'. Default 'ext4'.
 	Payload_fs_type *string
 
@@ -160,10 +153,6 @@
 	// Default is false.
 	Ignore_system_library_special_case *bool
 
-	// Whenever apex_payload.img of the APEX should include dm-verity hashtree.
-	// Default value is true.
-	Generate_hashtree *bool
-
 	// Whenever apex_payload.img of the APEX should not be dm-verity signed. Should be only
 	// used in tests.
 	Test_only_unsigned_payload *bool
@@ -292,7 +281,7 @@
 }
 
 // Merge combines another ApexNativeDependencies into this one
-func (a *ResolvedApexNativeDependencies) Merge(ctx android.BaseMutatorContext, b ApexNativeDependencies) {
+func (a *ResolvedApexNativeDependencies) Merge(ctx android.BaseModuleContext, b ApexNativeDependencies) {
 	a.Native_shared_libs = append(a.Native_shared_libs, b.Native_shared_libs.GetOrDefault(ctx, nil)...)
 	a.Jni_libs = append(a.Jni_libs, b.Jni_libs.GetOrDefault(ctx, nil)...)
 	a.Rust_dyn_libs = append(a.Rust_dyn_libs, b.Rust_dyn_libs...)
@@ -425,6 +414,30 @@
 	Min_sdk_version *string
 }
 
+// installPair stores a path to a built object and its install location.  It is used for holding
+// the installation location of the dexpreopt artifacts for system server jars in apexes that need
+// to be installed when the apex is installed.
+type installPair struct {
+	from android.Path
+	to   android.InstallPath
+}
+
+type installPairs []installPair
+
+// String converts a list of installPair structs to the form accepted by LOCAL_SOONG_INSTALL_PAIRS.
+func (p installPairs) String() string {
+	sb := &strings.Builder{}
+	for i, pair := range p {
+		if i != 0 {
+			sb.WriteByte(' ')
+		}
+		sb.WriteString(pair.from.String())
+		sb.WriteByte(':')
+		sb.WriteString(pair.to.String())
+	}
+	return sb.String()
+}
+
 type apexBundle struct {
 	// Inherited structs
 	android.ModuleBase
@@ -437,6 +450,7 @@
 	archProperties        apexArchBundleProperties
 	overridableProperties overridableProperties
 	vndkProperties        apexVndkProperties // only for apex_vndk modules
+	testProperties        apexTestProperties // only for apex_test modules
 
 	///////////////////////////////////////////////////////////////////////////////////////////
 	// Inputs
@@ -464,6 +478,12 @@
 	// GenerateAndroidBuildActions.
 	filesInfo []apexFile
 
+	// List of files that were excluded by the unwanted_transitive_deps property.
+	unwantedTransitiveFilesInfo []apexFile
+
+	// List of files that were excluded due to conflicts with other variants of the same module.
+	duplicateTransitiveFilesInfo []apexFile
+
 	// List of other module names that should be installed when this APEX gets installed (LOCAL_REQUIRED_MODULES).
 	makeModulesToInstall []string
 
@@ -499,6 +519,12 @@
 	// Path where this APEX was installed.
 	installedFile android.InstallPath
 
+	// Extra files that are installed alongside this APEX.
+	extraInstalledFiles android.InstallPaths
+
+	// The source and install locations for extraInstalledFiles for use in LOCAL_SOONG_INSTALL_PAIRS.
+	extraInstalledPairs installPairs
+
 	// fragment for this apex for apexkeys.txt
 	apexKeysPath android.WritablePath
 
@@ -507,7 +533,7 @@
 
 	// Text file having the list of individual files that are included in this APEX. Used for
 	// debugging purpose.
-	installedFilesFile android.WritablePath
+	installedFilesFile android.Path
 
 	// List of module names that this APEX is including (to be shown via *-deps-info target).
 	// Used for debugging purpose.
@@ -573,13 +599,15 @@
 	// Info for Android.mk Module name of `module` in AndroidMk. Note the generated AndroidMk
 	// module for apexFile is named something like <AndroidMk module name>.<apex name>[<apex
 	// suffix>]
-	androidMkModuleName       string             // becomes LOCAL_MODULE
-	class                     apexFileClass      // becomes LOCAL_MODULE_CLASS
-	moduleDir                 string             // becomes LOCAL_PATH
-	requiredModuleNames       []string           // becomes LOCAL_REQUIRED_MODULES
-	targetRequiredModuleNames []string           // becomes LOCAL_TARGET_REQUIRED_MODULES
-	hostRequiredModuleNames   []string           // becomes LOCAL_HOST_REQUIRED_MODULES
-	dataPaths                 []android.DataPath // becomes LOCAL_TEST_DATA
+	androidMkModuleName string             // becomes LOCAL_MODULE
+	class               apexFileClass      // becomes LOCAL_MODULE_CLASS
+	moduleDir           string             // becomes LOCAL_PATH
+	dataPaths           []android.DataPath // becomes LOCAL_TEST_DATA
+
+	// systemServerDexpreoptInstalls stores the list of dexpreopt artifacts for a system server jar.
+	systemServerDexpreoptInstalls []java.DexpreopterInstall
+	// systemServerDexJars stores the list of dexjars for a system server jar.
+	systemServerDexJars android.Paths
 
 	jacocoReportClassesFile android.Path     // only for javalibs and apps
 	lintInfo                *java.LintInfo   // only for javalibs and apps
@@ -610,9 +638,6 @@
 		}
 		ret.moduleDir = ctx.OtherModuleDir(module)
 		ret.partition = module.PartitionTag(ctx.DeviceConfig())
-		ret.requiredModuleNames = module.RequiredModuleNames(ctx)
-		ret.targetRequiredModuleNames = module.TargetRequiredModuleNames()
-		ret.hostRequiredModuleNames = module.HostRequiredModuleNames()
 		ret.multilib = module.Target().Arch.ArchType.Multilib
 	}
 	return ret
@@ -726,7 +751,6 @@
 	androidAppTag  = &dependencyTag{name: "androidApp", payload: true}
 	bpfTag         = &dependencyTag{name: "bpf", payload: true}
 	certificateTag = &dependencyTag{name: "certificate"}
-	dclaTag        = &dependencyTag{name: "dcla"}
 	executableTag  = &dependencyTag{name: "executable", payload: true}
 	fsTag          = &dependencyTag{name: "filesystem", payload: true}
 	bcpfTag        = &dependencyTag{name: "bootclasspathFragment", payload: true, sourceOnly: true, memberType: java.BootclasspathFragmentSdkMemberType}
@@ -739,11 +763,21 @@
 	prebuiltTag     = &dependencyTag{name: "prebuilt", payload: true}
 	rroTag          = &dependencyTag{name: "rro", payload: true}
 	sharedLibTag    = &dependencyTag{name: "sharedLib", payload: true}
-	testForTag      = &dependencyTag{name: "test for"}
 	testTag         = &dependencyTag{name: "test", payload: true}
 	shBinaryTag     = &dependencyTag{name: "shBinary", payload: true}
 )
 
+type fragmentInApexDepTag struct {
+	blueprint.BaseDependencyTag
+	android.FragmentInApexTag
+}
+
+func (fragmentInApexDepTag) ExcludeFromVisibilityEnforcement() {}
+
+// fragmentInApexTag is used by apex modules to depend on their fragments.  Java bootclasspath
+// modules can traverse from the apex to the fragment using android.IsFragmentInApexTag.
+var fragmentInApexTag = fragmentInApexDepTag{}
+
 // TODO(jiyong): shorten this function signature
 func addDependenciesForNativeModules(ctx android.BottomUpMutatorContext, nativeModules ResolvedApexNativeDependencies, target android.Target, imageVariation string) {
 	binVariations := target.Variations()
@@ -833,6 +867,7 @@
 		deps.Merge(ctx, a.properties.Multilib.Both)
 		deps.Merge(ctx, ApexNativeDependencies{
 			Native_shared_libs: a.properties.Native_shared_libs,
+			Rust_dyn_libs:      a.properties.Rust_dyn_libs,
 			Tests:              a.properties.Tests,
 			Jni_libs:           a.properties.Jni_libs,
 		})
@@ -887,18 +922,35 @@
 		}
 	}
 
-	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, fragmentInApexTag, 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...)
+
+	// Add a reverse dependency to all_apex_certs singleton module.
+	// all_apex_certs will use this dependency to collect the certificate of this apex.
+	ctx.AddReverseDependency(ctx.Module(), allApexCertsDepTag, "all_apex_certs")
+
+	// TODO: When all branches contain this singleton module, make this strict
+	// TODO: Add this dependency only for mainline prebuilts and not every prebuilt module
+	if ctx.OtherModuleExists("all_apex_contributions") {
+		ctx.AddDependency(ctx.Module(), android.AcDepTag, "all_apex_contributions")
+	}
 }
 
+type allApexCertsDependencyTag struct {
+	blueprint.DependencyTag
+}
+
+func (_ allApexCertsDependencyTag) ExcludeFromVisibilityEnforcement() {}
+
+var allApexCertsDepTag = allApexCertsDependencyTag{}
+
 // DepsMutator for the overridden properties.
 func (a *apexBundle) OverridablePropertiesDepsMutator(ctx android.BottomUpMutatorContext) {
 	if a.overridableProperties.Allowed_files != nil {
@@ -943,103 +995,29 @@
 	}
 }
 
-func apexDCLADepsMutator(mctx android.BottomUpMutatorContext) {
-	if !mctx.Config().ApexTrimEnabled() {
-		return
-	}
-	if a, ok := mctx.Module().(*apexBundle); ok && a.overridableProperties.Trim_against != nil {
-		commonVariation := mctx.Config().AndroidCommonTarget.Variations()
-		mctx.AddFarVariationDependencies(commonVariation, dclaTag, String(a.overridableProperties.Trim_against))
-	} else if o, ok := mctx.Module().(*OverrideApex); ok {
-		for _, p := range o.GetProperties() {
-			properties, ok := p.(*overridableProperties)
-			if !ok {
-				continue
-			}
-			if properties.Trim_against != nil {
-				commonVariation := mctx.Config().AndroidCommonTarget.Variations()
-				mctx.AddFarVariationDependencies(commonVariation, dclaTag, String(properties.Trim_against))
-			}
-		}
-	}
-}
-
-type DCLAInfo struct {
-	ProvidedLibs []string
-}
-
-var DCLAInfoProvider = blueprint.NewMutatorProvider[DCLAInfo]("apex_info")
-
-var _ ApexInfoMutator = (*apexBundle)(nil)
+var _ ApexTransitionMutator = (*apexBundle)(nil)
 
 func (a *apexBundle) ApexVariationName() string {
 	return a.properties.ApexVariationName
 }
 
-// ApexInfoMutator is responsible for collecting modules that need to have apex variants. They are
-// identified by doing a graph walk starting from an apexBundle. Basically, all the (direct and
-// indirect) dependencies are collected. But a few types of modules that shouldn't be included in
-// the apexBundle (e.g. stub libraries) are not collected. Note that a single module can be depended
-// on by multiple apexBundles. In that case, the module is collected for all of the apexBundles.
-//
-// For each dependency between an apex and an ApexModule an ApexInfo object describing the apex
-// is passed to that module's BuildForApex(ApexInfo) method which collates them all in a list.
-// The apexMutator uses that list to create module variants for the apexes to which it belongs.
-// The relationship between module variants and apexes is not one-to-one as variants will be
-// shared between compatible apexes.
-func (a *apexBundle) ApexInfoMutator(mctx android.TopDownMutatorContext) {
+type generateApexInfoContext interface {
+	android.MinSdkVersionFromValueContext
+	Module() android.Module
+	ModuleName() string
+}
 
+// generateApexInfo returns an android.ApexInfo configuration that should be used for dependencies of this apex.
+func (a *apexBundle) generateApexInfo(ctx generateApexInfoContext) android.ApexInfo {
 	// The VNDK APEX is special. For the APEX, the membership is described in a very different
 	// way. There is no dependency from the VNDK APEX to the VNDK libraries. Instead, VNDK
 	// libraries are self-identified by their vndk.enabled properties. There is no need to run
-	// this mutator for the APEX as nothing will be collected. So, let's return fast.
+	// this mutator for the APEX as nothing will be collected so return an empty ApexInfo.
 	if a.vndkApex {
-		return
+		return android.ApexInfo{}
 	}
 
-	continueApexDepsWalk := func(child, parent android.Module) bool {
-		am, ok := child.(android.ApexModule)
-		if !ok || !am.CanHaveApexVariants() {
-			return false
-		}
-		depTag := mctx.OtherModuleDependencyTag(child)
-
-		// Check to see if the tag always requires that the child module has an apex variant for every
-		// apex variant of the parent module. If it does not then it is still possible for something
-		// else, e.g. the DepIsInSameApex(...) method to decide that a variant is required.
-		if required, ok := depTag.(android.AlwaysRequireApexVariantTag); ok && required.AlwaysRequireApexVariant() {
-			return true
-		}
-		if !android.IsDepInSameApex(mctx, parent, child) {
-			return false
-		}
-
-		// By default, all the transitive dependencies are collected, unless filtered out
-		// above.
-		return true
-	}
-
-	// Records whether a certain module is included in this apexBundle via direct dependency or
-	// inndirect dependency.
-	contents := make(map[string]android.ApexMembership)
-	mctx.WalkDeps(func(child, parent android.Module) bool {
-		if !continueApexDepsWalk(child, parent) {
-			return false
-		}
-		// If the parent is apexBundle, this child is directly depended.
-		_, directDep := parent.(*apexBundle)
-		depName := mctx.OtherModuleName(child)
-		contents[depName] = contents[depName].Add(directDep)
-		return true
-	})
-
-	// The membership information is saved for later access
-	apexContents := android.NewApexContents(contents)
-	android.SetProvider(mctx, android.ApexBundleInfoProvider, android.ApexBundleInfo{
-		Contents: apexContents,
-	})
-
-	minSdkVersion := a.minSdkVersion(mctx)
+	minSdkVersion := a.minSdkVersion(ctx)
 	// When min_sdk_version is not set, the apex is built against FutureApiLevel.
 	if minSdkVersion.IsNone() {
 		minSdkVersion = android.FutureApiLevel
@@ -1048,69 +1026,45 @@
 	// This is the main part of this mutator. Mark the collected dependencies that they need to
 	// be built for this apexBundle.
 
-	apexVariationName := mctx.ModuleName() // could be com.android.foo
-	if overridable, ok := mctx.Module().(android.OverridableModule); ok && overridable.GetOverriddenBy() != "" {
+	apexVariationName := ctx.ModuleName() // could be com.android.foo
+	if a.GetOverriddenBy() != "" {
 		// use the overridden name com.mycompany.android.foo
-		apexVariationName = overridable.GetOverriddenBy()
+		apexVariationName = a.GetOverriddenBy()
 	}
 
-	a.properties.ApexVariationName = apexVariationName
-	testApexes := []string{}
-	if a.testApex {
-		testApexes = []string{apexVariationName}
-	}
 	apexInfo := android.ApexInfo{
 		ApexVariationName: apexVariationName,
 		MinSdkVersion:     minSdkVersion,
 		Updatable:         a.Updatable(),
 		UsePlatformApis:   a.UsePlatformApis(),
-		InApexVariants:    []string{apexVariationName},
-		InApexModules:     []string{a.Name()}, // could be com.mycompany.android.foo
-		ApexContents:      []*android.ApexContents{apexContents},
-		TestApexes:        testApexes,
-		BaseApexName:      mctx.ModuleName(),
+		BaseApexName:      ctx.ModuleName(),
 		ApexAvailableName: proptools.String(a.properties.Apex_available_name),
 	}
-	mctx.WalkDeps(func(child, parent android.Module) bool {
-		if !continueApexDepsWalk(child, parent) {
-			return false
-		}
-		child.(android.ApexModule).BuildForApex(apexInfo) // leave a mark!
-		return true
-	})
-
-	if a.dynamic_common_lib_apex() {
-		android.SetProvider(mctx, DCLAInfoProvider, DCLAInfo{
-			ProvidedLibs: a.properties.Native_shared_libs.GetOrDefault(mctx, nil),
-		})
-	}
+	return apexInfo
 }
 
-type ApexInfoMutator interface {
-	// ApexVariationName returns the name of the APEX variation to use in the apex
-	// mutator etc. It is the same name as ApexInfo.ApexVariationName.
-	ApexVariationName() string
-
-	// ApexInfoMutator implementations must call BuildForApex(ApexInfo) on any modules that are
-	// depended upon by an apex and which require an apex specific variant.
-	ApexInfoMutator(android.TopDownMutatorContext)
+func (a *apexBundle) ApexTransitionMutatorSplit(ctx android.BaseModuleContext) []android.ApexInfo {
+	return []android.ApexInfo{a.generateApexInfo(ctx)}
 }
 
-// apexInfoMutator delegates the work of identifying which modules need an ApexInfo and apex
-// specific variant to modules that support the ApexInfoMutator.
-// It also propagates updatable=true to apps of updatable apexes
-func apexInfoMutator(mctx android.TopDownMutatorContext) {
-	if !mctx.Module().Enabled(mctx) {
-		return
-	}
+func (a *apexBundle) ApexTransitionMutatorOutgoing(ctx android.OutgoingTransitionContext, sourceInfo android.ApexInfo) android.ApexInfo {
+	return sourceInfo
+}
 
-	if a, ok := mctx.Module().(ApexInfoMutator); ok {
-		a.ApexInfoMutator(mctx)
-	}
+func (a *apexBundle) ApexTransitionMutatorIncoming(ctx android.IncomingTransitionContext, outgoingInfo android.ApexInfo) android.ApexInfo {
+	return a.generateApexInfo(ctx)
+}
 
-	if am, ok := mctx.Module().(android.ApexModule); ok {
-		android.ApexInfoMutator(mctx, am)
-	}
+func (a *apexBundle) ApexTransitionMutatorMutate(ctx android.BottomUpMutatorContext, info android.ApexInfo) {
+	android.SetProvider(ctx, android.ApexBundleInfoProvider, android.ApexBundleInfo{})
+	a.properties.ApexVariationName = info.ApexVariationName
+}
+
+type ApexTransitionMutator interface {
+	ApexTransitionMutatorSplit(ctx android.BaseModuleContext) []android.ApexInfo
+	ApexTransitionMutatorOutgoing(ctx android.OutgoingTransitionContext, sourceInfo android.ApexInfo) android.ApexInfo
+	ApexTransitionMutatorIncoming(ctx android.IncomingTransitionContext, outgoingInfo android.ApexInfo) android.ApexInfo
+	ApexTransitionMutatorMutate(ctx android.BottomUpMutatorContext, info android.ApexInfo)
 }
 
 // TODO: b/215736885 Whittle the denylist
@@ -1124,7 +1078,7 @@
 		"com.android.appsearch",
 		"com.android.art",
 		"com.android.art.debug",
-		"com.android.btservices",
+		"com.android.bt",
 		"com.android.cellbroadcast",
 		"com.android.configinfrastructure",
 		"com.android.conscrypt",
@@ -1173,39 +1127,8 @@
 	}
 	if am, ok := mctx.Module().(android.ApexModule); ok {
 		android.UpdateUniqueApexVariationsForDeps(mctx, am)
-	}
-}
-
-// apexTestForDepsMutator checks if this module is a test for an apex. If so, add a dependency on
-// the apex in order to retrieve its contents later.
-// TODO(jiyong): move this to android/apex.go?
-func apexTestForDepsMutator(mctx android.BottomUpMutatorContext) {
-	if !mctx.Module().Enabled(mctx) {
-		return
-	}
-	if am, ok := mctx.Module().(android.ApexModule); ok {
-		if testFor := am.TestFor(); len(testFor) > 0 {
-			mctx.AddFarVariationDependencies([]blueprint.Variation{
-				{Mutator: "os", Variation: am.Target().OsVariation()},
-				{"arch", "common"},
-			}, testForTag, testFor...)
-		}
-	}
-}
-
-// TODO(jiyong): move this to android/apex.go?
-func apexTestForMutator(mctx android.BottomUpMutatorContext) {
-	if !mctx.Module().Enabled(mctx) {
-		return
-	}
-	if _, ok := mctx.Module().(android.ApexModule); ok {
-		var contents []*android.ApexContents
-		for _, testFor := range mctx.GetDirectDepsWithTag(testForTag) {
-			abInfo, _ := android.OtherModuleProvider(mctx, testFor, android.ApexBundleInfoProvider)
-			contents = append(contents, abInfo.Contents)
-		}
-		android.SetProvider(mctx, android.ApexTestForInfoProvider, android.ApexTestForInfo{
-			ApexContents: contents,
+		android.SetProvider(mctx, android.DepInSameApexInfoProvider, android.DepInSameApexInfo{
+			Checker: am.GetDepInSameApexChecker(),
 		})
 	}
 }
@@ -1259,64 +1182,35 @@
 
 type apexTransitionMutator struct{}
 
-func (a *apexTransitionMutator) Split(ctx android.BaseModuleContext) []string {
-	// apexBundle itself is mutated so that it and its dependencies have the same apex variant.
-	if ai, ok := ctx.Module().(ApexInfoMutator); ok && apexModuleTypeRequiresVariant(ai) {
-		if overridable, ok := ctx.Module().(android.OverridableModule); ok && overridable.GetOverriddenBy() != "" {
-			return []string{overridable.GetOverriddenBy()}
-		}
-		return []string{ai.ApexVariationName()}
-	} else if _, ok := ctx.Module().(*OverrideApex); ok {
-		return []string{ctx.ModuleName()}
+func (a *apexTransitionMutator) Split(ctx android.BaseModuleContext) []android.ApexInfo {
+	if ai, ok := ctx.Module().(ApexTransitionMutator); ok {
+		return ai.ApexTransitionMutatorSplit(ctx)
 	}
-	return []string{""}
+	return []android.ApexInfo{{}}
 }
 
-func (a *apexTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
-	return sourceVariation
-}
-
-func (a *apexTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
-	if am, ok := ctx.Module().(android.ApexModule); ok && am.CanHaveApexVariants() {
-		return android.IncomingApexTransition(ctx, incomingVariation)
-	} else if ai, ok := ctx.Module().(ApexInfoMutator); ok {
-		if overridable, ok := ctx.Module().(android.OverridableModule); ok && overridable.GetOverriddenBy() != "" {
-			return overridable.GetOverriddenBy()
-		}
-		return ai.ApexVariationName()
-	} else if _, ok := ctx.Module().(*OverrideApex); ok {
-		return ctx.Module().Name()
+func (a *apexTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceInfo android.ApexInfo) android.ApexInfo {
+	if ai, ok := ctx.Module().(ApexTransitionMutator); ok {
+		return ai.ApexTransitionMutatorOutgoing(ctx, sourceInfo)
 	}
-
-	return ""
+	return android.ApexInfo{}
 }
 
-func (a *apexTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
-	if am, ok := ctx.Module().(android.ApexModule); ok && am.CanHaveApexVariants() {
-		android.MutateApexTransition(ctx, variation)
+func (a *apexTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, outgoingInfo android.ApexInfo) android.ApexInfo {
+	if ai, ok := ctx.Module().(ApexTransitionMutator); ok {
+		return ai.ApexTransitionMutatorIncoming(ctx, outgoingInfo)
+	}
+	return android.ApexInfo{}
+}
+
+func (a *apexTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, info android.ApexInfo) {
+	if ai, ok := ctx.Module().(ApexTransitionMutator); ok {
+		ai.ApexTransitionMutatorMutate(ctx, info)
 	}
 }
 
-// apexModuleTypeRequiresVariant determines whether the module supplied requires an apex specific
-// variant.
-func apexModuleTypeRequiresVariant(module ApexInfoMutator) bool {
-	if a, ok := module.(*apexBundle); ok {
-		// TODO(jiyong): document the reason why the VNDK APEX is an exception here.
-		return !a.vndkApex
-	}
-
-	return true
-}
-
-// See android.UpdateDirectlyInAnyApex
-// TODO(jiyong): move this to android/apex.go?
-func apexDirectlyInAnyMutator(mctx android.BottomUpMutatorContext) {
-	if !mctx.Module().Enabled(mctx) {
-		return
-	}
-	if am, ok := mctx.Module().(android.ApexModule); ok {
-		android.UpdateDirectlyInAnyApex(mctx, am)
-	}
+func (a *apexTransitionMutator) TransitionInfoFromVariation(variation string) android.ApexInfo {
+	panic(fmt.Errorf("adding dependencies on explicit apex variations is not supported"))
 }
 
 const (
@@ -1332,15 +1226,6 @@
 	erofsFsType = "erofs"
 )
 
-var _ android.DepIsInSameApex = (*apexBundle)(nil)
-
-// Implements android.DepInInSameApex
-func (a *apexBundle) DepIsInSameApex(_ android.BaseModuleContext, _ android.Module) bool {
-	// direct deps of an APEX bundle are all part of the APEX bundle
-	// TODO(jiyong): shouldn't we look into the payload field of the dependencyTag?
-	return true
-}
-
 func (a *apexBundle) Exportable() bool {
 	return true
 }
@@ -1394,6 +1279,23 @@
 	return proptools.BoolDefault(a.properties.Platform_apis, false)
 }
 
+type apexValidationType int
+
+const (
+	hostApexVerifier apexValidationType = iota
+	apexSepolicyTests
+)
+
+func (a *apexBundle) skipValidation(validationType apexValidationType) bool {
+	switch validationType {
+	case hostApexVerifier:
+		return proptools.Bool(a.testProperties.Skip_validations.Host_apex_verifier)
+	case apexSepolicyTests:
+		return proptools.Bool(a.testProperties.Skip_validations.Apex_sepolicy_tests)
+	}
+	panic("Unknown validation type")
+}
+
 // getCertString returns the name of the cert that should be used to sign this APEX. This is
 // basically from the "certificate" property, but could be overridden by the device config.
 func (a *apexBundle) getCertString(ctx android.BaseModuleContext) string {
@@ -1416,11 +1318,6 @@
 	return !a.properties.PreventInstall && (a.properties.Installable == nil || proptools.Bool(a.properties.Installable))
 }
 
-// See the generate_hashtree property
-func (a *apexBundle) shouldGenerateHashtree() bool {
-	return proptools.BoolDefault(a.properties.Generate_hashtree, true)
-}
-
 // See the test_only_unsigned_payload property
 func (a *apexBundle) testOnlyShouldSkipPayloadSign() bool {
 	return proptools.Bool(a.properties.Test_only_unsigned_payload)
@@ -1436,19 +1333,6 @@
 	return proptools.BoolDefault(a.properties.Dynamic_common_lib_apex, false)
 }
 
-// See the list of libs to trim
-func (a *apexBundle) libs_to_trim(ctx android.ModuleContext) []string {
-	dclaModules := ctx.GetDirectDepsWithTag(dclaTag)
-	if len(dclaModules) > 1 {
-		panic(fmt.Errorf("expected exactly at most one dcla dependency, got %d", len(dclaModules)))
-	}
-	if len(dclaModules) > 0 {
-		DCLAInfo, _ := android.OtherModuleProvider(ctx, dclaModules[0], DCLAInfoProvider)
-		return DCLAInfo.ProvidedLibs
-	}
-	return []string{}
-}
-
 // These functions are interfacing with cc/sanitizer.go. The entire APEX (along with all of its
 // members) can be sanitized, either forcibly, or by the global configuration. For some of the
 // sanitizers, extra dependencies can be forcibly added as well.
@@ -1494,12 +1378,12 @@
 // apexFileFor<Type> functions below create an apexFile struct for a given Soong module. The
 // returned apexFile saves information about the Soong module that will be used for creating the
 // build rules.
-func apexFileForNativeLibrary(ctx android.BaseModuleContext, ccMod *cc.Module, handleSpecialLibs bool) apexFile {
+func apexFileForNativeLibrary(ctx android.BaseModuleContext, ccMod cc.VersionedLinkableInterface, handleSpecialLibs bool) apexFile {
 	// Decide the APEX-local directory by the multilib of the library In the future, we may
 	// query this to the module.
 	// TODO(jiyong): use the new PackagingSpec
 	var dirInApex string
-	switch ccMod.Arch().ArchType.Multilib {
+	switch ccMod.Multilib() {
 	case "lib32":
 		dirInApex = "lib"
 	case "lib64":
@@ -1526,7 +1410,7 @@
 	dirInApex = filepath.Join(dirInApex, ccMod.RelativeInstallPath())
 
 	fileToCopy := android.OutputFileForModule(ctx, ccMod, "")
-	androidMkModuleName := ccMod.BaseModuleName() + ccMod.Properties.SubName
+	androidMkModuleName := ccMod.BaseModuleName() + ccMod.SubName()
 	return newApexFile(ctx, fileToCopy, androidMkModuleName, dirInApex, nativeSharedLib, ccMod)
 }
 
@@ -1556,25 +1440,6 @@
 	return af
 }
 
-func apexFileForRustLibrary(ctx android.BaseModuleContext, rustm *rust.Module) apexFile {
-	// Decide the APEX-local directory by the multilib of the library
-	// In the future, we may query this to the module.
-	var dirInApex string
-	switch rustm.Arch().ArchType.Multilib {
-	case "lib32":
-		dirInApex = "lib"
-	case "lib64":
-		dirInApex = "lib64"
-	}
-	if rustm.Target().NativeBridge == android.NativeBridgeEnabled {
-		dirInApex = filepath.Join(dirInApex, rustm.Target().NativeBridgeRelativePath)
-	}
-	dirInApex = filepath.Join(dirInApex, rustm.RelativeInstallPath())
-	fileToCopy := android.OutputFileForModule(ctx, rustm, "")
-	androidMkModuleName := rustm.BaseModuleName() + rustm.Properties.SubName
-	return newApexFile(ctx, fileToCopy, androidMkModuleName, dirInApex, nativeSharedLib, rustm)
-}
-
 func apexFileForShBinary(ctx android.BaseModuleContext, sh *sh.ShBinary) apexFile {
 	dirInApex := filepath.Join("bin", sh.SubDir())
 	if sh.Target().NativeBridge == android.NativeBridgeEnabled {
@@ -1598,6 +1463,12 @@
 	return newApexFile(ctx, fileToCopy, depName, dirInApex, etc, config)
 }
 
+func apexFileForVintfFragment(ctx android.BaseModuleContext, vintfFragment *android.VintfFragmentModule) apexFile {
+	dirInApex := filepath.Join("etc", "vintf")
+
+	return newApexFile(ctx, vintfFragment.OutputFile(), vintfFragment.BaseModuleName(), dirInApex, etc, vintfFragment)
+}
+
 // javaModule is an interface to handle all Java modules (java_library, dex_import, etc) in the same
 // way.
 type javaModule interface {
@@ -1628,16 +1499,15 @@
 		af.lintInfo = lintInfo
 	}
 	af.customStem = module.Stem() + ".jar"
+	// Collect any system server dex jars and dexpreopt artifacts for installation alongside the apex.
 	// TODO: b/338641779 - Remove special casing of sdkLibrary once bcpf and sscpf depends
 	// on the implementation library
 	if sdkLib, ok := module.(*java.SdkLibrary); ok {
-		for _, install := range sdkLib.BuiltInstalledForApex() {
-			af.requiredModuleNames = append(af.requiredModuleNames, install.FullModuleName())
-		}
+		af.systemServerDexpreoptInstalls = append(af.systemServerDexpreoptInstalls, sdkLib.ApexSystemServerDexpreoptInstalls()...)
+		af.systemServerDexJars = append(af.systemServerDexJars, sdkLib.ApexSystemServerDexJars()...)
 	} else if dexpreopter, ok := module.(java.DexpreopterInterface); ok {
-		for _, install := range dexpreopter.DexpreoptBuiltInstalledForApex() {
-			af.requiredModuleNames = append(af.requiredModuleNames, install.FullModuleName())
-		}
+		af.systemServerDexpreoptInstalls = append(af.systemServerDexpreoptInstalls, dexpreopter.ApexSystemServerDexpreoptInstalls()...)
+		af.systemServerDexJars = append(af.systemServerDexJars, dexpreopter.ApexSystemServerDexJars()...)
 	}
 	return af
 }
@@ -1771,14 +1641,38 @@
 			return false
 		}
 
-		ai, _ := android.OtherModuleProvider(ctx, child, android.ApexInfoProvider)
-		externalDep := !android.InList(ctx.ModuleName(), ai.InApexVariants)
+		externalDep := !android.IsDepInSameApex(ctx, parent, child)
 
 		// Visit actually
 		return do(ctx, parent, am, externalDep)
 	})
 }
 
+func (a *apexBundle) WalkPayloadDepsProxy(ctx android.BaseModuleContext,
+	do func(ctx android.BaseModuleContext, from, to android.ModuleProxy, externalDep bool) bool) {
+	ctx.WalkDepsProxy(func(child, parent android.ModuleProxy) bool {
+		if !android.OtherModuleProviderOrDefault(ctx, child, android.CommonModuleInfoKey).CanHaveApexVariants {
+			return false
+		}
+		// Filter-out unwanted depedendencies
+		depTag := ctx.OtherModuleDependencyTag(child)
+		if _, ok := depTag.(android.ExcludeFromApexContentsTag); ok {
+			return false
+		}
+		if dt, ok := depTag.(*dependencyTag); ok && !dt.payload {
+			return false
+		}
+		if depTag == android.RequiredDepTag {
+			return false
+		}
+
+		externalDep := !android.IsDepInSameApex(ctx, parent, child)
+
+		// Visit actually
+		return do(ctx, parent, child, externalDep)
+	})
+}
+
 // filesystem type of the apex_payload.img inside the APEX. Currently, ext4 and f2fs are supported.
 type fsType int
 
@@ -1834,7 +1728,8 @@
 }
 
 func (a *apexBundle) setPayloadFsType(ctx android.ModuleContext) {
-	switch proptools.StringDefault(a.properties.Payload_fs_type, ext4FsType) {
+	defaultFsType := ctx.Config().DefaultApexPayloadType()
+	switch proptools.StringDefault(a.properties.Payload_fs_type, defaultFsType) {
 	case ext4FsType:
 		a.payloadFsType = ext4
 	case f2fsFsType:
@@ -1847,7 +1742,13 @@
 }
 
 func (a *apexBundle) isCompressable() bool {
-	return proptools.BoolDefault(a.overridableProperties.Compressible, false) && !a.testApex
+	if a.testApex {
+		return false
+	}
+	if a.payloadFsType == erofs {
+		return false
+	}
+	return proptools.Bool(a.overridableProperties.Compressible)
 }
 
 func (a *apexBundle) commonBuildActions(ctx android.ModuleContext) bool {
@@ -1879,6 +1780,14 @@
 
 	// visitor skips these from this list of module names
 	unwantedTransitiveDeps []string
+
+	// unwantedTransitiveFilesInfo contains files that would have been in the apex
+	// except that they were listed in unwantedTransitiveDeps.
+	unwantedTransitiveFilesInfo []apexFile
+
+	// duplicateTransitiveFilesInfo contains files that would ahve been in the apex
+	// except that another variant of the same module was already in the apex.
+	duplicateTransitiveFilesInfo []apexFile
 }
 
 func (vctx *visitorContext) normalizeFileInfo(mctx android.ModuleContext) {
@@ -1889,6 +1798,7 @@
 		// Needs additional verification for the resulting APEX to ensure that skipped artifacts don't make problems.
 		// For example, DT_NEEDED modules should be found within the APEX unless they are marked in `requiredNativeLibs`.
 		if f.transitiveDep && f.module != nil && android.InList(mctx.OtherModuleName(f.module), vctx.unwantedTransitiveDeps) {
+			vctx.unwantedTransitiveFilesInfo = append(vctx.unwantedTransitiveFilesInfo, f)
 			continue
 		}
 		dest := filepath.Join(f.installDir, f.builtFile.Base())
@@ -1899,6 +1809,8 @@
 				mctx.ModuleErrorf("apex file %v is provided by two different files %v and %v",
 					dest, e.builtFile, f.builtFile)
 				return
+			} else {
+				vctx.duplicateTransitiveFilesInfo = append(vctx.duplicateTransitiveFilesInfo, f)
 			}
 			// If a module is directly included and also transitively depended on
 			// consider it as directly included.
@@ -1913,6 +1825,7 @@
 	for _, v := range encountered {
 		vctx.filesInfo = append(vctx.filesInfo, v)
 	}
+
 	sort.Slice(vctx.filesInfo, func(i, j int) bool {
 		// Sort by destination path so as to ensure consistent ordering even if the source of the files
 		// changes.
@@ -1927,7 +1840,7 @@
 // as the apex.
 func (a *apexBundle) enforcePartitionTagOnApexSystemServerJar(ctx android.ModuleContext) {
 	global := dexpreopt.GetGlobalConfig(ctx)
-	ctx.VisitDirectDepsWithTag(sscpfTag, func(child android.Module) {
+	ctx.VisitDirectDepsProxyWithTag(sscpfTag, func(child android.ModuleProxy) {
 		info, ok := android.OtherModuleProvider(ctx, child, java.LibraryNameToPartitionInfoProvider)
 		if !ok {
 			ctx.ModuleErrorf("Could not find partition info of apex system server jars.")
@@ -1963,8 +1876,8 @@
 			if isJniLib {
 				propertyName = "jni_libs"
 			}
-			switch ch := child.(type) {
-			case *cc.Module:
+
+			if ch, ok := child.(cc.VersionedLinkableInterface); ok {
 				if ch.IsStubs() {
 					ctx.PropertyErrorf(propertyName, "%q is a stub. Remove it from the list.", depName)
 				}
@@ -1978,14 +1891,11 @@
 					vctx.provideNativeLibs = append(vctx.provideNativeLibs, fi.stem())
 				}
 				return true // track transitive dependencies
-			case *rust.Module:
-				fi := apexFileForRustLibrary(ctx, ch)
-				fi.isJniLib = isJniLib
-				vctx.filesInfo = append(vctx.filesInfo, fi)
-				return true // track transitive dependencies
-			default:
-				ctx.PropertyErrorf(propertyName, "%q is not a cc_library or cc_library_shared module", depName)
+			} else {
+				ctx.PropertyErrorf(propertyName,
+					"%q is not a VersionLinkableInterface (e.g. cc_library or rust_ffi module)", depName)
 			}
+
 		case executableTag:
 			switch ch := child.(type) {
 			case *cc.Module:
@@ -2137,12 +2047,11 @@
 	// We cannot use a switch statement on `depTag` here as the checked
 	// tags used below are private (e.g. `cc.sharedDepTag`).
 	if cc.IsSharedDepTag(depTag) || cc.IsRuntimeDepTag(depTag) {
-		if ch, ok := child.(*cc.Module); ok {
+		if ch, ok := child.(cc.VersionedLinkableInterface); ok {
 			af := apexFileForNativeLibrary(ctx, ch, vctx.handleSpecialLibs)
 			af.transitiveDep = true
 
-			abInfo, _ := android.ModuleProvider(ctx, android.ApexBundleInfoProvider)
-			if !abInfo.Contents.DirectlyInApex(depName) && (ch.IsStubs() || ch.HasStubsVariants()) {
+			if ch.IsStubs() || ch.HasStubsVariants() {
 				// If the dependency is a stubs lib, don't include it in this APEX,
 				// but make sure that the lib is installed on the device.
 				// In case no APEX is having the lib, the lib is installed to the system
@@ -2153,9 +2062,10 @@
 				//
 				// Skip the dependency in unbundled builds where the device image is not
 				// being built.
-				if ch.IsStubsImplementationRequired() && !am.DirectlyInAnyApex() && !ctx.Config().UnbundledBuild() {
+				if ch.VersionedInterface().IsStubsImplementationRequired() &&
+					!am.NotInPlatform() && !ctx.Config().UnbundledBuild() {
 					// we need a module name for Make
-					name := ch.ImplementationModuleNameForMake(ctx) + ch.Properties.SubName
+					name := ch.ImplementationModuleNameForMake(ctx) + ch.SubName()
 					if !android.InList(name, a.makeModulesToInstall) {
 						a.makeModulesToInstall = append(a.makeModulesToInstall, name)
 					}
@@ -2174,24 +2084,12 @@
 			// like to record requiredNativeLibs even when
 			// DepIsInSameAPex is false. We also shouldn't do
 			// this for host.
-			//
-			// TODO(jiyong): explain why the same module is passed in twice.
-			// Switching the first am to parent breaks lots of tests.
-			if !android.IsDepInSameApex(ctx, am, am) {
+			if !android.IsDepInSameApex(ctx, parent, am) {
 				return false
 			}
 
 			vctx.filesInfo = append(vctx.filesInfo, af)
 			return true // track transitive dependencies
-		} else if rm, ok := child.(*rust.Module); ok {
-			if !android.IsDepInSameApex(ctx, am, am) {
-				return false
-			}
-
-			af := apexFileForRustLibrary(ctx, rm)
-			af.transitiveDep = true
-			vctx.filesInfo = append(vctx.filesInfo, af)
-			return true // track transitive dependencies
 		}
 	} else if cc.IsHeaderDepTag(depTag) {
 		// nothing
@@ -2210,7 +2108,7 @@
 				return false
 			}
 
-			af := apexFileForRustLibrary(ctx, rustm)
+			af := apexFileForNativeLibrary(ctx, child.(cc.VersionedLinkableInterface), vctx.handleSpecialLibs)
 			af.transitiveDep = true
 			vctx.filesInfo = append(vctx.filesInfo, af)
 			return true // track transitive dependencies
@@ -2250,15 +2148,19 @@
 			ctx.PropertyErrorf("systemserverclasspath_fragments",
 				"systemserverclasspath_fragment content %q of type %q is not supported", depName, ctx.OtherModuleType(child))
 		}
-	} else if _, ok := depTag.(android.CopyDirectlyInAnyApexTag); ok {
-		// nothing
 	} else if depTag == android.DarwinUniversalVariantTag {
 		// nothing
 	} else if depTag == android.RequiredDepTag {
 		// nothing
 	} else if am.CanHaveApexVariants() && am.IsInstallableToApex() {
 		ctx.ModuleErrorf("unexpected tag %s for indirect dependency %q", android.PrettyPrintTag(depTag), depName)
+	} else if android.IsVintfDepTag(depTag) {
+		if vf, ok := child.(*android.VintfFragmentModule); ok {
+			apexFile := apexFileForVintfFragment(ctx, vf)
+			vctx.filesInfo = append(vctx.filesInfo, apexFile)
+		}
 	}
+
 	return false
 }
 
@@ -2343,6 +2245,8 @@
 	// 3) some fields in apexBundle struct are configured
 	a.installDir = android.PathForModuleInstall(ctx, "apex")
 	a.filesInfo = vctx.filesInfo
+	a.unwantedTransitiveFilesInfo = vctx.unwantedTransitiveFilesInfo
+	a.duplicateTransitiveFilesInfo = vctx.duplicateTransitiveFilesInfo
 
 	a.setPayloadFsType(ctx)
 	a.setSystemLibLink(ctx)
@@ -2355,6 +2259,7 @@
 	////////////////////////////////////////////////////////////////////////////////////////////
 	// 4) generate the build rules to create the APEX. This is done in builder.go.
 	a.buildManifest(ctx, vctx.provideNativeLibs, vctx.requireNativeLibs)
+	a.installApexSystemServerFiles(ctx)
 	a.buildApex(ctx)
 	a.buildApexDependencyInfo(ctx)
 	a.buildLintReports(ctx)
@@ -2369,6 +2274,8 @@
 
 	a.setOutputFiles(ctx)
 	a.enforcePartitionTagOnApexSystemServerJar(ctx)
+
+	a.verifyNativeImplementationLibs(ctx)
 }
 
 // Set prebuiltInfoProvider. This will be used by `apex_prebuiltinfo_singleton` to print out a metadata file
@@ -2385,7 +2292,7 @@
 // Apexes built from source retrieve this information by visiting `bootclasspath_fragments`
 // Used by dex_bootjars to generate the boot image
 func (a *apexBundle) provideApexExportsInfo(ctx android.ModuleContext) {
-	ctx.VisitDirectDepsWithTag(bcpfTag, func(child android.Module) {
+	ctx.VisitDirectDepsProxyWithTag(bcpfTag, func(child android.ModuleProxy) {
 		if info, ok := android.OtherModuleProvider(ctx, child, java.BootclasspathFragmentApexContentInfoProvider); ok {
 			exports := android.ApexExportsInfo{
 				ApexName:                      a.ApexVariationName(),
@@ -2416,7 +2323,7 @@
 	}
 	if a.Updatable() {
 		// checking direct deps is sufficient since apex->apk is a direct edge, even when inherited via apex_defaults
-		mctx.VisitDirectDeps(func(module android.Module) {
+		mctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 			if appInfo, ok := android.OtherModuleProvider(mctx, module, java.AppInfoProvider); ok {
 				// ignore android_test_app
 				if !appInfo.TestHelperApp && !appInfo.Updatable {
@@ -2524,10 +2431,14 @@
 	return module
 }
 
-func ApexBundleFactory(testApex bool) android.Module {
-	bundle := newApexBundle()
-	bundle.testApex = testApex
-	return bundle
+type apexTestProperties struct {
+	// Boolean flags for validation checks. Test APEXes can turn on/off individual checks.
+	Skip_validations struct {
+		// Skips `Apex_sepolicy_tests` check if true
+		Apex_sepolicy_tests *bool
+		// Skips `Host_apex_verifier` check if true
+		Host_apex_verifier *bool
+	}
 }
 
 // apex_test is an APEX for testing. The difference from the ordinary apex module type is that
@@ -2535,6 +2446,7 @@
 func TestApexBundleFactory() android.Module {
 	bundle := newApexBundle()
 	bundle.testApex = true
+	bundle.AddProperties(&bundle.testProperties)
 	return bundle
 }
 
@@ -2607,7 +2519,7 @@
 }
 
 // Returns apex's min_sdk_version string value, honoring overrides
-func (a *apexBundle) minSdkVersionValue(ctx android.EarlyModuleContext) string {
+func (a *apexBundle) minSdkVersionValue(ctx android.MinSdkVersionFromValueContext) string {
 	// Only override the minSdkVersion value on Apexes which already specify
 	// a min_sdk_version (it's optional for non-updatable apexes), and that its
 	// min_sdk_version value is lower than the one to override with.
@@ -2631,7 +2543,7 @@
 }
 
 // Returns apex's min_sdk_version ApiLevel, honoring overrides
-func (a *apexBundle) minSdkVersion(ctx android.EarlyModuleContext) android.ApiLevel {
+func (a *apexBundle) minSdkVersion(ctx android.MinSdkVersionFromValueContext) android.ApiLevel {
 	return android.MinSdkVersionFromValue(ctx, a.minSdkVersionValue(ctx))
 }
 
@@ -2642,24 +2554,25 @@
 		return
 	}
 
-	abInfo, _ := android.ModuleProvider(ctx, android.ApexBundleInfoProvider)
+	librariesDirectlyInApex := make(map[string]bool)
+	ctx.VisitDirectDepsProxyWithTag(sharedLibTag, func(dep android.ModuleProxy) {
+		librariesDirectlyInApex[ctx.OtherModuleName(dep)] = true
+	})
 
-	a.WalkPayloadDeps(ctx, func(ctx android.BaseModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
-		if ccm, ok := to.(*cc.Module); ok {
-			apexName := ctx.ModuleName()
-			fromName := ctx.OtherModuleName(from)
-			toName := ctx.OtherModuleName(to)
-
+	a.WalkPayloadDeps(ctx, func(ctx android.BaseModuleContext, from android.Module, to android.ApexModule, externalDep bool) bool {
+		if info, ok := android.OtherModuleProvider(ctx, to, cc.LinkableInfoProvider); ok {
 			// If `to` is not actually in the same APEX as `from` then it does not need
 			// apex_available and neither do any of its dependencies.
-			//
-			// It is ok to call DepIsInSameApex() directly from within WalkPayloadDeps().
-			if am, ok := from.(android.DepIsInSameApex); ok && !am.DepIsInSameApex(ctx, to) {
+			if externalDep {
 				// As soon as the dependency graph crosses the APEX boundary, don't go further.
 				return false
 			}
 
-			// The dynamic linker and crash_dump tool in the runtime APEX is the only
+			apexName := ctx.ModuleName()
+			fromName := ctx.OtherModuleName(from)
+			toName := ctx.OtherModuleName(to)
+
+			// The dynamic linker and crash_dump tool in the runtime APEX is an
 			// exception to this rule. It can't make the static dependencies dynamic
 			// because it can't do the dynamic linking for itself.
 			// Same rule should be applied to linkerconfig, because it should be executed
@@ -2668,12 +2581,20 @@
 				return false
 			}
 
-			isStubLibraryFromOtherApex := ccm.HasStubsVariants() && !abInfo.Contents.DirectlyInApex(toName)
+			// b/389067742 adds libz as an exception to this check. Although libz is
+			// a part of NDK and thus provides a stable interface, it never was the
+			// intention because the upstream zlib provides neither ABI- nor behavior-
+			// stability. Therefore, we want to allow portable components like APEXes to
+			// bundle libz by statically linking to it.
+			if toName == "libz" {
+				return false
+			}
+
+			isStubLibraryFromOtherApex := info.HasStubsVariants && !librariesDirectlyInApex[toName]
 			if isStubLibraryFromOtherApex && !externalDep {
 				ctx.ModuleErrorf("%q required by %q is a native library providing stub. "+
 					"It shouldn't be included in this APEX via static linking. Dependency path: %s", to.String(), fromName, ctx.GetPathString(false))
 			}
-
 		}
 		return true
 	})
@@ -2701,7 +2622,7 @@
 
 // checkClasspathFragments enforces that all classpath fragments in deps generate classpaths.proto config.
 func (a *apexBundle) checkClasspathFragments(ctx android.ModuleContext) {
-	ctx.VisitDirectDeps(func(module android.Module) {
+	ctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 		if tag := ctx.OtherModuleDependencyTag(module); tag == bcpfTag || tag == sscpfTag {
 			info, _ := android.OtherModuleProvider(ctx, module, java.ClasspathFragmentProtoContentInfoProvider)
 			if !info.ClasspathFragmentProtoGenerated {
@@ -2715,16 +2636,12 @@
 func (a *apexBundle) checkJavaStableSdkVersion(ctx android.ModuleContext) {
 	// Visit direct deps only. As long as we guarantee top-level deps are using stable SDKs,
 	// java's checkLinkType guarantees correct usage for transitive deps
-	ctx.VisitDirectDeps(func(module android.Module) {
+	ctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 		tag := ctx.OtherModuleDependencyTag(module)
 		switch tag {
 		case javaLibTag, androidAppTag:
-			if m, ok := module.(interface {
-				CheckStableSdkVersion(ctx android.BaseModuleContext) error
-			}); ok {
-				if err := m.CheckStableSdkVersion(ctx); err != nil {
-					ctx.ModuleErrorf("cannot depend on \"%v\": %v", ctx.OtherModuleName(module), err)
-				}
+			if err := java.CheckStableSdkVersion(ctx, module); err != nil {
+				ctx.ModuleErrorf("cannot depend on \"%v\": %v", ctx.OtherModuleName(module), err)
 			}
 		}
 	})
@@ -2756,7 +2673,7 @@
 		return
 	}
 
-	a.WalkPayloadDeps(ctx, func(ctx android.BaseModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
+	a.WalkPayloadDeps(ctx, func(ctx android.BaseModuleContext, from android.Module, to android.ApexModule, externalDep bool) bool {
 		// As soon as the dependency graph crosses the APEX boundary, don't go further.
 		if externalDep {
 			return false
@@ -2773,17 +2690,8 @@
 		fromName := ctx.OtherModuleName(from)
 		toName := ctx.OtherModuleName(to)
 
-		// If `to` is not actually in the same APEX as `from` then it does not need
-		// apex_available and neither do any of its dependencies.
-		//
-		// It is ok to call DepIsInSameApex() directly from within WalkPayloadDeps().
-		if am, ok := from.(android.DepIsInSameApex); ok && !am.DepIsInSameApex(ctx, to) {
-			// As soon as the dependency graph crosses the APEX boundary, don't go
-			// further.
-			return false
-		}
-
-		if to.AvailableFor(apexName) {
+		if android.CheckAvailableForApex(apexName,
+			android.OtherModuleProviderOrDefault(ctx, to, android.ApexAvailableInfoProvider).ApexAvailableFor) {
 			return true
 		}
 
@@ -2808,12 +2716,12 @@
 
 // checkStaticExecutable ensures that executables in an APEX are not static.
 func (a *apexBundle) checkStaticExecutables(ctx android.ModuleContext) {
-	ctx.VisitDirectDeps(func(module android.Module) {
+	ctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 		if ctx.OtherModuleDependencyTag(module) != executableTag {
 			return
 		}
 
-		if l, ok := module.(cc.LinkableInterface); ok && l.StaticExecutable() {
+		if android.OtherModuleProviderOrDefault(ctx, module, cc.LinkableInfoProvider).StaticExecutable {
 			apex := a.ApexVariationName()
 			exec := ctx.OtherModuleName(module)
 			if isStaticExecutableAllowed(apex, exec) {
@@ -2839,8 +2747,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() {
@@ -2919,6 +2827,104 @@
 	}
 }
 
-func (a *apexBundle) IsTestApex() bool {
-	return a.testApex
+// verifyNativeImplementationLibs compares the list of transitive implementation libraries used to link native
+// libraries in the apex against the list of implementation libraries in the apex, ensuring that none of the
+// libraries in the apex have references to private APIs from outside the apex.
+func (a *apexBundle) verifyNativeImplementationLibs(ctx android.ModuleContext) {
+	var directImplementationLibs android.Paths
+	var transitiveImplementationLibs []depset.DepSet[android.Path]
+
+	if a.properties.IsCoverageVariant {
+		return
+	}
+
+	if a.testApex {
+		return
+	}
+
+	if a.UsePlatformApis() {
+		return
+	}
+
+	checkApexTag := func(tag blueprint.DependencyTag) bool {
+		switch tag {
+		case sharedLibTag, jniLibTag, executableTag, androidAppTag:
+			return true
+		default:
+			return false
+		}
+	}
+
+	checkTransitiveTag := func(tag blueprint.DependencyTag) bool {
+		switch {
+		case cc.IsSharedDepTag(tag), java.IsJniDepTag(tag), rust.IsRlibDepTag(tag), rust.IsDylibDepTag(tag), checkApexTag(tag):
+			return true
+		default:
+			return false
+		}
+	}
+
+	var appEmbeddedJNILibs android.Paths
+	ctx.VisitDirectDepsProxy(func(dep android.ModuleProxy) {
+		tag := ctx.OtherModuleDependencyTag(dep)
+		if !checkApexTag(tag) {
+			return
+		}
+		if tag == sharedLibTag || tag == jniLibTag {
+			outputFile := android.OutputFileForModule(ctx, dep, "")
+			directImplementationLibs = append(directImplementationLibs, outputFile)
+		}
+		if info, ok := android.OtherModuleProvider(ctx, dep, cc.ImplementationDepInfoProvider); ok {
+			transitiveImplementationLibs = append(transitiveImplementationLibs, info.ImplementationDeps)
+		}
+		if info, ok := android.OtherModuleProvider(ctx, dep, java.AppInfoProvider); ok {
+			appEmbeddedJNILibs = append(appEmbeddedJNILibs, info.EmbeddedJNILibs...)
+		}
+	})
+
+	depSet := depset.New(depset.PREORDER, directImplementationLibs, transitiveImplementationLibs)
+	allImplementationLibs := depSet.ToList()
+
+	allFileInfos := slices.Concat(a.filesInfo, a.unwantedTransitiveFilesInfo, a.duplicateTransitiveFilesInfo)
+
+	for _, lib := range allImplementationLibs {
+		inApex := slices.ContainsFunc(allFileInfos, func(fi apexFile) bool {
+			return fi.builtFile == lib
+		})
+		inApkInApex := slices.Contains(appEmbeddedJNILibs, lib)
+
+		if !inApex && !inApkInApex {
+			ctx.ModuleErrorf("library in apex transitively linked against implementation library %q not in apex", lib)
+			var depPath []android.Module
+			ctx.WalkDeps(func(child, parent android.Module) bool {
+				if depPath != nil {
+					return false
+				}
+
+				tag := ctx.OtherModuleDependencyTag(child)
+
+				if parent == ctx.Module() {
+					if !checkApexTag(tag) {
+						return false
+					}
+				}
+
+				if checkTransitiveTag(tag) {
+					if android.OutputFileForModule(ctx, child, "") == lib {
+						depPath = ctx.GetWalkPath()
+					}
+					return true
+				}
+
+				return false
+			})
+			if depPath != nil {
+				ctx.ModuleErrorf("dependency path:")
+				for _, m := range depPath {
+					ctx.ModuleErrorf("   %s", ctx.OtherModuleName(m))
+				}
+				return
+			}
+		}
+	}
 }
diff --git a/apex/apex_singleton.go b/apex/apex_singleton.go
index 00dd446..a8bd984 100644
--- a/apex/apex_singleton.go
+++ b/apex/apex_singleton.go
@@ -59,17 +59,19 @@
 
 	// Diff two given lists while ignoring comments in the allowed deps file.
 	diffAllowedApexDepsInfoRule = pctx.AndroidStaticRule("diffAllowedApexDepsInfoRule", blueprint.RuleParams{
-		Description: "Diff ${allowed_deps_list} and ${new_allowed_deps}",
+		Description: "Diff ${allowed_deps} and ${new_allowed_deps}",
 		Command: `
-			if grep -v -h '^#' ${allowed_deps_list} | sort -u -f| diff -B -u - ${new_allowed_deps}; then
+			if grep -v '^#' ${allowed_deps} | diff -B - ${new_allowed_deps}; then
 			   touch ${out};
 			else
-				echo -e "\n******************************";
+				echo;
+				echo "******************************";
 				echo "ERROR: go/apex-allowed-deps-error contains more information";
 				echo "******************************";
 				echo "Detected changes to allowed dependencies in updatable modules.";
 				echo "To fix and update packages/modules/common/build/allowed_deps.txt, please run:";
-				echo -e "$$ (croot && packages/modules/common/build/update-apex-allowed-deps.sh)\n";
+				echo "$$ (croot && packages/modules/common/build/update-apex-allowed-deps.sh)";
+				echo;
 				echo "When submitting the generated CL, you must include the following information";
 				echo "in the commit message if you are adding a new dependency:";
 				echo "Apex-Size-Increase: Expected binary size increase for affected APEXes (or the size of the .jar / .so file of the new library)";
@@ -78,66 +80,59 @@
 				echo "Test-Info: What’s the testing strategy for the new dependency? Does it have its own tests, and are you adding integration tests? How/when are the tests run?";
 				echo "You do not need OWNERS approval to submit the change, but mainline-modularization@";
 				echo "will periodically review additions and may require changes.";
-				echo -e "******************************\n";
+				echo "******************************";
+				echo;
 				exit 1;
 			fi;
 		`,
-	}, "allowed_deps_list", "new_allowed_deps")
+	}, "allowed_deps", "new_allowed_deps")
 )
 
 func (s *apexDepsInfoSingleton) GenerateBuildActions(ctx android.SingletonContext) {
-	allowedDepsSources := []android.OptionalPath{android.ExistentPathForSource(ctx, "packages/modules/common/build/allowed_deps.txt")}
-	extraAllowedDepsPath := ctx.Config().ExtraAllowedDepsTxt()
-	if extraAllowedDepsPath != "" {
-		allowedDepsSources = append(allowedDepsSources, android.ExistentPathForSource(ctx, extraAllowedDepsPath))
-	}
 	updatableFlatLists := android.Paths{}
 	ctx.VisitAllModules(func(module android.Module) {
 		if binaryInfo, ok := module.(android.ApexBundleDepsInfoIntf); ok {
 			apexInfo, _ := android.OtherModuleProvider(ctx, module, android.ApexInfoProvider)
 			if path := binaryInfo.FlatListPath(); path != nil {
 				if binaryInfo.Updatable() || apexInfo.Updatable {
-					updatableFlatLists = append(updatableFlatLists, path)
+					if strings.HasPrefix(module.String(), "com.android.") {
+						updatableFlatLists = append(updatableFlatLists, path)
+					}
 				}
 			}
 		}
 	})
+
+	allowedDepsSource := android.ExistentPathForSource(ctx, "packages/modules/common/build/allowed_deps.txt")
 	newAllowedDeps := android.PathForOutput(ctx, "apex", "depsinfo", "new-allowed-deps.txt")
 	s.allowedApexDepsInfoCheckResult = android.PathForOutput(ctx, newAllowedDeps.Rel()+".check")
-	hasOneValidDepsPath := false
-	for _, allowedDepsSource := range allowedDepsSources {
-		if allowedDepsSource.Valid() {
-			hasOneValidDepsPath = true
-			updatableFlatLists = append(updatableFlatLists, allowedDepsSource.Path())
-		}
-	}
-	allowedDepsStrList := make([]string, len(allowedDepsSources))
-	for _, value := range allowedDepsSources {
-		allowedDepsStrList = append(allowedDepsStrList, value.String())
-	}
-	allowedDepsListString := strings.Join(allowedDepsStrList, " ")
-	if !hasOneValidDepsPath {
+
+	if !allowedDepsSource.Valid() {
 		// Unbundled projects may not have packages/modules/common/ checked out; ignore those.
 		ctx.Build(pctx, android.BuildParams{
 			Rule:   android.Touch,
 			Output: s.allowedApexDepsInfoCheckResult,
 		})
 	} else {
+		allowedDeps := allowedDepsSource.Path()
+
 		ctx.Build(pctx, android.BuildParams{
 			Rule:   generateApexDepsInfoFilesRule,
-			Inputs: updatableFlatLists,
+			Inputs: append(updatableFlatLists, allowedDeps),
 			Output: newAllowedDeps,
 		})
+
 		ctx.Build(pctx, android.BuildParams{
 			Rule:   diffAllowedApexDepsInfoRule,
 			Input:  newAllowedDeps,
 			Output: s.allowedApexDepsInfoCheckResult,
 			Args: map[string]string{
-				"allowed_deps_list": allowedDepsListString,
-				"new_allowed_deps":  newAllowedDeps.String(),
+				"allowed_deps":     allowedDeps.String(),
+				"new_allowed_deps": newAllowedDeps.String(),
 			},
 		})
 	}
+
 	ctx.Phony("apex-allowed-deps-check", s.allowedApexDepsInfoCheckResult)
 }
 
@@ -180,8 +175,5 @@
 	}
 	a.out = android.PathForOutput(ctx, "prebuilt_info.json")
 	android.WriteFileRule(ctx, a.out, string(j))
-}
-
-func (a *apexPrebuiltInfo) MakeVars(ctx android.MakeVarsContext) {
 	ctx.DistForGoal("droidcore", a.out)
 }
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 1d2f3fb..5519bd2 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -82,34 +82,6 @@
 	return files.AddToFixture()
 }
 
-func withTargets(targets map[android.OsType][]android.Target) android.FixturePreparer {
-	return android.FixtureModifyConfig(func(config android.Config) {
-		for k, v := range targets {
-			config.Targets[k] = v
-		}
-	})
-}
-
-// withNativeBridgeTargets sets configuration with targets including:
-// - X86_64 (primary)
-// - X86 (secondary)
-// - Arm64 on X86_64 (native bridge)
-// - Arm on X86 (native bridge)
-var withNativeBridgeEnabled = android.FixtureModifyConfig(
-	func(config android.Config) {
-		config.Targets[android.Android] = []android.Target{
-			{Os: android.Android, Arch: android.Arch{ArchType: android.X86_64, ArchVariant: "silvermont", Abi: []string{"arm64-v8a"}},
-				NativeBridge: android.NativeBridgeDisabled, NativeBridgeHostArchName: "", NativeBridgeRelativePath: ""},
-			{Os: android.Android, Arch: android.Arch{ArchType: android.X86, ArchVariant: "silvermont", Abi: []string{"armeabi-v7a"}},
-				NativeBridge: android.NativeBridgeDisabled, NativeBridgeHostArchName: "", NativeBridgeRelativePath: ""},
-			{Os: android.Android, Arch: android.Arch{ArchType: android.Arm64, ArchVariant: "armv8-a", Abi: []string{"arm64-v8a"}},
-				NativeBridge: android.NativeBridgeEnabled, NativeBridgeHostArchName: "x86_64", NativeBridgeRelativePath: "arm64"},
-			{Os: android.Android, Arch: android.Arch{ArchType: android.Arm, ArchVariant: "armv7-a-neon", Abi: []string{"armeabi-v7a"}},
-				NativeBridge: android.NativeBridgeEnabled, NativeBridgeHostArchName: "x86", NativeBridgeRelativePath: "arm"},
-		}
-	},
-)
-
 func withManifestPackageNameOverrides(specs []string) android.FixturePreparer {
 	return android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
 		variables.ManifestPackageNameOverrides = specs
@@ -233,6 +205,10 @@
 	"system/sepolicy/apex/myapex-file_contexts": nil,
 })
 
+var prepareForTestWithOtherapex = android.FixtureMergeMockFs(android.MockFS{
+	"system/sepolicy/apex/otherapex-file_contexts": nil,
+})
+
 // ensure that 'result' equals 'expected'
 func ensureEquals(t *testing.T, result string, expected string) {
 	t.Helper()
@@ -316,6 +292,7 @@
 
 // Minimal test
 func TestBasicApex(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex_defaults {
 			name: "myapex-defaults",
@@ -400,7 +377,7 @@
 			name: "foo.rust",
 			srcs: ["foo.rs"],
 			rlibs: ["libfoo.rlib.rust"],
-			rustlibs: ["libfoo.dylib.rust"],
+			rustlibs: ["libfoo.transitive.dylib.rust"],
 			apex_available: ["myapex"],
 		}
 
@@ -427,6 +404,13 @@
 			apex_available: ["myapex"],
 		}
 
+		rust_library_dylib {
+			name: "libfoo.transitive.dylib.rust",
+			srcs: ["foo.rs"],
+			crate_name: "foo",
+			apex_available: ["myapex"],
+		}
+
 		rust_ffi_shared {
 			name: "libfoo.ffi",
 			srcs: ["foo.rs"],
@@ -462,7 +446,7 @@
 				"//apex_available:platform",
 				"myapex",
 			],
-    }
+		}
 
 		cc_library_static {
 			name: "libstatic",
@@ -489,6 +473,7 @@
 				"//apex_available:platform",
 				"myapex",
 			],
+			compile_dex: true,
 		}
 
 		dex_import {
@@ -520,10 +505,10 @@
 		}
 	`)
 
-	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexRule")
+	apexRule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("apexRule")
 
 	// Make sure that Android.mk is created
-	ab := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*apexBundle)
+	ab := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Module().(*apexBundle)
 	data := android.AndroidMkDataForTest(t, ctx, ab)
 	var builder strings.Builder
 	data.Custom(&builder, ab.BaseModuleName(), "TARGET_", "", data)
@@ -554,6 +539,7 @@
 	ensureListContains(t, ctx.ModuleVariantsForTests("myotherjar"), "android_common_apex10000")
 	ensureListContains(t, ctx.ModuleVariantsForTests("libfoo.rlib.rust"), "android_arm64_armv8-a_rlib_dylib-std_apex10000")
 	ensureListContains(t, ctx.ModuleVariantsForTests("libfoo.dylib.rust"), "android_arm64_armv8-a_dylib_apex10000")
+	ensureListContains(t, ctx.ModuleVariantsForTests("libfoo.transitive.dylib.rust"), "android_arm64_armv8-a_dylib_apex10000")
 	ensureListContains(t, ctx.ModuleVariantsForTests("libbar.ffi"), "android_arm64_armv8-a_shared_apex10000")
 	ensureListContains(t, ctx.ModuleVariantsForTests("libfoo.shared_from_rust"), "android_arm64_armv8-a_shared_apex10000")
 
@@ -563,6 +549,7 @@
 	ensureContains(t, copyCmds, "image.apex/javalib/myjar_stem.jar")
 	ensureContains(t, copyCmds, "image.apex/javalib/myjar_dex.jar")
 	ensureContains(t, copyCmds, "image.apex/lib64/libfoo.dylib.rust.dylib.so")
+	ensureContains(t, copyCmds, "image.apex/lib64/libfoo.transitive.dylib.rust.dylib.so")
 	ensureContains(t, copyCmds, "image.apex/lib64/libfoo.ffi.so")
 	ensureContains(t, copyCmds, "image.apex/lib64/libbar.ffi.so")
 	ensureContains(t, copyCmds, "image.apex/lib64/libfoo.shared_from_rust.so")
@@ -598,14 +585,14 @@
 	}
 
 	fullDepsInfo := strings.Split(android.ContentFromFileRuleForTests(t, ctx,
-		ctx.ModuleForTests("myapex", "android_common_myapex").Output("depsinfo/fulllist.txt")), "\n")
+		ctx.ModuleForTests(t, "myapex", "android_common_myapex").Output("depsinfo/fulllist.txt")), "\n")
 	ensureListContains(t, fullDepsInfo, "  myjar(minSdkVersion:(no version)) <- myapex")
 	ensureListContains(t, fullDepsInfo, "  mylib2(minSdkVersion:(no version)) <- mylib")
 	ensureListContains(t, fullDepsInfo, "  myotherjar(minSdkVersion:(no version)) <- myjar")
 	ensureListContains(t, fullDepsInfo, "  mysharedjar(minSdkVersion:(no version)) (external) <- myjar")
 
 	flatDepsInfo := strings.Split(android.ContentFromFileRuleForTests(t, ctx,
-		ctx.ModuleForTests("myapex", "android_common_myapex").Output("depsinfo/flatlist.txt")), "\n")
+		ctx.ModuleForTests(t, "myapex", "android_common_myapex").Output("depsinfo/flatlist.txt")), "\n")
 	ensureListContains(t, flatDepsInfo, "myjar(minSdkVersion:(no version))")
 	ensureListContains(t, flatDepsInfo, "mylib2(minSdkVersion:(no version))")
 	ensureListContains(t, flatDepsInfo, "myotherjar(minSdkVersion:(no version))")
@@ -613,6 +600,7 @@
 }
 
 func TestDefaults(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex_defaults {
 			name: "myapex-defaults",
@@ -655,6 +643,7 @@
 			sdk_version: "none",
 			system_modules: "none",
 			apex_available: [ "myapex" ],
+			compile_dex: true,
 		}
 
 		android_app {
@@ -695,6 +684,7 @@
 }
 
 func TestApexManifest(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -709,7 +699,7 @@
 		}
 	`)
 
-	module := ctx.ModuleForTests("myapex", "android_common_myapex")
+	module := ctx.ModuleForTests(t, "myapex", "android_common_myapex")
 	args := module.Rule("apexRule").Args
 	if manifest := args["manifest"]; manifest != module.Output("apex_manifest.pb").Output.String() {
 		t.Error("manifest should be apex_manifest.pb, but " + manifest)
@@ -717,6 +707,7 @@
 }
 
 func TestApexManifestMinSdkVersion(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex_defaults {
 			name: "my_defaults",
@@ -780,7 +771,7 @@
 		},
 	}
 	for _, tc := range testCases {
-		module := ctx.ModuleForTests(tc.module, "android_common_"+tc.module)
+		module := ctx.ModuleForTests(t, tc.module, "android_common_"+tc.module)
 		args := module.Rule("apexRule").Args
 		optFlags := args["opt_flags"]
 		if !strings.Contains(optFlags, "--min_sdk_version "+tc.minSdkVersion) {
@@ -790,6 +781,7 @@
 }
 
 func TestApexWithDessertSha(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex_defaults {
 			name: "my_defaults",
@@ -853,7 +845,7 @@
 		},
 	}
 	for _, tc := range testCases {
-		module := ctx.ModuleForTests(tc.module, "android_common_"+tc.module)
+		module := ctx.ModuleForTests(t, tc.module, "android_common_"+tc.module)
 		args := module.Rule("apexRule").Args
 		optFlags := args["opt_flags"]
 		if !strings.Contains(optFlags, "--min_sdk_version "+tc.minSdkVersion) {
@@ -863,6 +855,7 @@
 }
 
 func TestFileContexts(t *testing.T) {
+	t.Parallel()
 	for _, vendor := range []bool{true, false} {
 		prop := ""
 		if vendor {
@@ -883,7 +876,7 @@
 			}
 		`)
 
-		rule := ctx.ModuleForTests("myapex", "android_common_myapex").Output("file_contexts")
+		rule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Output("file_contexts")
 		if vendor {
 			android.AssertStringDoesContain(t, "should force-label as vendor_apex_metadata_file",
 				rule.RuleParams.Command,
@@ -897,11 +890,16 @@
 }
 
 func TestApexWithStubs(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
 			key: "myapex.key",
-			native_shared_libs: ["mylib", "mylib3"],
+			native_shared_libs: [
+				"mylib",
+				"mylib3",
+				"libmylib3_rs",
+			],
 			binaries: ["foo.rust"],
 			updatable: false,
 		}
@@ -915,7 +913,14 @@
 		cc_library {
 			name: "mylib",
 			srcs: ["mylib.cpp"],
-			shared_libs: ["mylib2", "mylib3", "my_prebuilt_platform_lib", "my_prebuilt_platform_stub_only_lib"],
+			shared_libs: [
+				"mylib2",
+				"mylib3#impl",
+				"libmylib2_rs",
+				"libmylib3_rs#impl",
+				"my_prebuilt_platform_lib",
+				"my_prebuilt_platform_stub_only_lib",
+			],
 			system_shared_libs: [],
 			stl: "none",
 			apex_available: [ "myapex" ],
@@ -933,6 +938,16 @@
 			},
 		}
 
+		rust_ffi {
+			name: "libmylib2_rs",
+			crate_name: "mylib2",
+			srcs: ["mylib.rs"],
+			stubs: {
+				symbol_file: "mylib2.map.txt",
+				versions: ["1", "2", "3"],
+			},
+		}
+
 		cc_library {
 			name: "mylib3",
 			srcs: ["mylib.cpp"],
@@ -946,6 +961,18 @@
 			apex_available: [ "myapex" ],
 		}
 
+		rust_ffi {
+			name: "libmylib3_rs",
+			crate_name: "mylib3",
+			srcs: ["mylib.rs"],
+			shared_libs: ["mylib4.from_rust"],
+			stubs: {
+				symbol_file: "mylib3.map.txt",
+				versions: ["10", "11", "12"],
+			},
+			apex_available: [ "myapex" ],
+		}
+
 		cc_library {
 			name: "mylib4",
 			srcs: ["mylib.cpp"],
@@ -954,6 +981,14 @@
 			apex_available: [ "myapex" ],
 		}
 
+		cc_library {
+			name: "mylib4.from_rust",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			stl: "none",
+			apex_available: [ "myapex" ],
+		}
+
 		cc_prebuilt_library_shared {
 			name: "my_prebuilt_platform_lib",
 			stubs: {
@@ -975,7 +1010,10 @@
 		rust_binary {
 			name: "foo.rust",
 			srcs: ["foo.rs"],
-			shared_libs: ["libfoo.shared_from_rust"],
+			shared_libs: [
+				"libfoo.shared_from_rust",
+				"libfoo_rs.shared_from_rust",
+			],
 			prefer_rlib: true,
 			apex_available: ["myapex"],
 		}
@@ -990,9 +1028,18 @@
 			},
 		}
 
+		rust_ffi {
+			name: "libfoo_rs.shared_from_rust",
+			crate_name: "foo_rs",
+			srcs: ["mylib.rs"],
+			stubs: {
+				versions: ["10", "11", "12"],
+			},
+		}
+
 	`)
 
-	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexRule")
+	apexRule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
 	// Ensure that direct non-stubs dep is always included
@@ -1000,21 +1047,27 @@
 
 	// Ensure that indirect stubs dep is not included
 	ensureNotContains(t, copyCmds, "image.apex/lib64/mylib2.so")
+	ensureNotContains(t, copyCmds, "image.apex/lib64/libmylib2_rs.so")
 
 	// Ensure that direct stubs dep is included
 	ensureContains(t, copyCmds, "image.apex/lib64/mylib3.so")
+	ensureContains(t, copyCmds, "image.apex/lib64/libmylib3_rs.so")
 
-	mylibLdFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_apex10000").Rule("ld").Args["libFlags"]
+	mylibLdFlags := ctx.ModuleForTests(t, "mylib", "android_arm64_armv8-a_shared_apex10000").Rule("ld").Args["libFlags"]
 
 	// Ensure that mylib is linking with the latest version of stubs for mylib2
 	ensureContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared_current/mylib2.so")
+	ensureContains(t, mylibLdFlags, "libmylib2_rs/android_arm64_armv8-a_shared_current/unstripped/libmylib2_rs.so")
 	// ... and not linking to the non-stub (impl) variant of mylib2
 	ensureNotContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared/mylib2.so")
+	ensureNotContains(t, mylibLdFlags, "libmylib2_rs/android_arm64_armv8-a_shared/unstripped/libmylib2_rs.so")
 
-	// Ensure that mylib is linking with the non-stub (impl) of mylib3 (because mylib3 is in the same apex)
+	// Ensure that mylib is linking with the non-stub (impl) of mylib3 (because the dependency is added with mylib3#impl)
 	ensureContains(t, mylibLdFlags, "mylib3/android_arm64_armv8-a_shared_apex10000/mylib3.so")
+	ensureContains(t, mylibLdFlags, "libmylib3_rs/android_arm64_armv8-a_shared_apex10000/unstripped/libmylib3_rs.so")
 	// .. and not linking to the stubs variant of mylib3
 	ensureNotContains(t, mylibLdFlags, "mylib3/android_arm64_armv8-a_shared_12/mylib3.so")
+	ensureNotContains(t, mylibLdFlags, "libmylib3_rs/android_arm64_armv8-a_shared_12/unstripped/mylib3.so")
 
 	// Comment out this test. Now it fails after the optimization of sharing "cflags" in cc/cc.go
 	// is replaced by sharing of "cFlags" in cc/builder.go.
@@ -1025,49 +1078,58 @@
 	// including the original cflags's "-include mylib.h".
 	//
 	// Ensure that stubs libs are built without -include flags
-	// mylib2Cflags := ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
+	// mylib2Cflags := ctx.ModuleForTests(t, "mylib2", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 	// ensureNotContains(t, mylib2Cflags, "-include ")
 
 	// Ensure that genstub for platform-provided lib is invoked with --systemapi
-	ensureContains(t, ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_shared_3").Rule("genStubSrc").Args["flags"], "--systemapi")
+	ensureContains(t, ctx.ModuleForTests(t, "mylib2", "android_arm64_armv8-a_shared_3").Rule("genStubSrc").Args["flags"], "--systemapi")
+	ensureContains(t, ctx.ModuleForTests(t, "libmylib2_rs", "android_arm64_armv8-a_shared_3").Rule("genStubSrc").Args["flags"], "--systemapi")
 	// Ensure that genstub for apex-provided lib is invoked with --apex
-	ensureContains(t, ctx.ModuleForTests("mylib3", "android_arm64_armv8-a_shared_12").Rule("genStubSrc").Args["flags"], "--apex")
+	ensureContains(t, ctx.ModuleForTests(t, "mylib3", "android_arm64_armv8-a_shared_12").Rule("genStubSrc").Args["flags"], "--apex")
+	ensureContains(t, ctx.ModuleForTests(t, "libmylib3_rs", "android_arm64_armv8-a_shared_12").Rule("genStubSrc").Args["flags"], "--apex")
 
 	ensureExactContents(t, ctx, "myapex", "android_common_myapex", []string{
 		"lib64/mylib.so",
 		"lib64/mylib3.so",
+		"lib64/libmylib3_rs.so",
 		"lib64/mylib4.so",
+		"lib64/mylib4.from_rust.so",
 		"bin/foo.rust",
-		"lib64/libc++.so", // by the implicit dependency from foo.rust
-		"lib64/liblog.so", // by the implicit dependency from foo.rust
+
+		"lib64/libstd.dylib.so", // implicit rust ffi dep
 	})
 
 	// Ensure that stub dependency from a rust module is not included
 	ensureNotContains(t, copyCmds, "image.apex/lib64/libfoo.shared_from_rust.so")
+	ensureNotContains(t, copyCmds, "image.apex/lib64/libfoo_rs.shared_from_rust.so")
 	// The rust module is linked to the stub cc library
-	rustDeps := ctx.ModuleForTests("foo.rust", "android_arm64_armv8-a_apex10000").Rule("rustc").Args["linkFlags"]
+	rustDeps := ctx.ModuleForTests(t, "foo.rust", "android_arm64_armv8-a_apex10000").Rule("rustc").Args["linkFlags"]
 	ensureContains(t, rustDeps, "libfoo.shared_from_rust/android_arm64_armv8-a_shared_current/libfoo.shared_from_rust.so")
+	ensureContains(t, rustDeps, "libfoo_rs.shared_from_rust/android_arm64_armv8-a_shared_current/unstripped/libfoo_rs.shared_from_rust.so")
 	ensureNotContains(t, rustDeps, "libfoo.shared_from_rust/android_arm64_armv8-a_shared/libfoo.shared_from_rust.so")
+	ensureNotContains(t, rustDeps, "libfoo_rs.shared_from_rust/android_arm64_armv8-a_shared/unstripped/libfoo_rs.shared_from_rust.so")
 
-	apexManifestRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexManifestRule")
+	apexManifestRule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("apexManifestRule")
 	ensureListContains(t, names(apexManifestRule.Args["requireNativeLibs"]), "libfoo.shared_from_rust.so")
+	ensureListContains(t, names(apexManifestRule.Args["requireNativeLibs"]), "libfoo_rs.shared_from_rust.so")
 
 	// Ensure that mylib is linking with the latest version of stubs for my_prebuilt_platform_lib
 	ensureContains(t, mylibLdFlags, "my_prebuilt_platform_lib/android_arm64_armv8-a_shared_current/my_prebuilt_platform_lib.so")
 	// ... and not linking to the non-stub (impl) variant of my_prebuilt_platform_lib
 	ensureNotContains(t, mylibLdFlags, "my_prebuilt_platform_lib/android_arm64_armv8-a_shared/my_prebuilt_platform_lib.so")
 	// Ensure that genstub for platform-provided lib is invoked with --systemapi
-	ensureContains(t, ctx.ModuleForTests("my_prebuilt_platform_lib", "android_arm64_armv8-a_shared_3").Rule("genStubSrc").Args["flags"], "--systemapi")
+	ensureContains(t, ctx.ModuleForTests(t, "my_prebuilt_platform_lib", "android_arm64_armv8-a_shared_3").Rule("genStubSrc").Args["flags"], "--systemapi")
 
 	// Ensure that mylib is linking with the latest version of stubs for my_prebuilt_platform_lib
 	ensureContains(t, mylibLdFlags, "my_prebuilt_platform_stub_only_lib/android_arm64_armv8-a_shared_current/my_prebuilt_platform_stub_only_lib.so")
 	// ... and not linking to the non-stub (impl) variant of my_prebuilt_platform_lib
 	ensureNotContains(t, mylibLdFlags, "my_prebuilt_platform_stub_only_lib/android_arm64_armv8-a_shared/my_prebuilt_platform_stub_only_lib.so")
 	// Ensure that genstub for platform-provided lib is invoked with --systemapi
-	ensureContains(t, ctx.ModuleForTests("my_prebuilt_platform_stub_only_lib", "android_arm64_armv8-a_shared_3").Rule("genStubSrc").Args["flags"], "--systemapi")
+	ensureContains(t, ctx.ModuleForTests(t, "my_prebuilt_platform_stub_only_lib", "android_arm64_armv8-a_shared_3").Rule("genStubSrc").Args["flags"], "--systemapi")
 }
 
 func TestApexShouldNotEmbedStubVariant(t *testing.T) {
+	t.Parallel()
 	testApexError(t, `module "myapex" .*: native_shared_libs: "libbar" is a stub`, `
 		apex {
 			name: "myapex",
@@ -1094,6 +1156,7 @@
 }
 
 func TestApexCanUsePrivateApis(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -1113,7 +1176,10 @@
 		cc_library {
 			name: "mylib",
 			srcs: ["mylib.cpp"],
-			shared_libs: ["mylib2"],
+			shared_libs: [
+				"mylib2",
+				"libmylib2_rust"
+			],
 			system_shared_libs: [],
 			stl: "none",
 			apex_available: [ "myapex" ],
@@ -1130,10 +1196,22 @@
 			},
 		}
 
+		rust_ffi {
+			name: "libmylib2_rust",
+			crate_name: "mylib2_rust",
+			srcs: ["mylib.rs"],
+			stubs: {
+				versions: ["1", "2", "3"],
+			},
+		}
+
 		rust_binary {
 			name: "foo.rust",
 			srcs: ["foo.rs"],
-			shared_libs: ["libfoo.shared_from_rust"],
+			shared_libs: [
+				"libfoo.shared_from_rust",
+				"libmylib_rust.shared_from_rust"
+			],
 			prefer_rlib: true,
 			apex_available: ["myapex"],
 		}
@@ -1147,23 +1225,38 @@
 				versions: ["10", "11", "12"],
 			},
 		}
+		rust_ffi {
+			name: "libmylib_rust.shared_from_rust",
+			crate_name: "mylib_rust",
+			srcs: ["mylib.rs"],
+			stubs: {
+				versions: ["1", "2", "3"],
+			},
+		}
+
 	`)
 
-	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexRule")
+	apexRule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
 	// Ensure that indirect stubs dep is not included
 	ensureNotContains(t, copyCmds, "image.apex/lib64/mylib2.so")
+	ensureNotContains(t, copyCmds, "image.apex/lib64/libmylib_rust.so")
+	ensureNotContains(t, copyCmds, "image.apex/lib64/libmylib_rust.shared_from_rust.so")
 	ensureNotContains(t, copyCmds, "image.apex/lib64/libfoo.shared_from_rust.so")
 
 	// Ensure that we are using non-stub variants of mylib2 and libfoo.shared_from_rust (because
 	// of the platform_apis: true)
-	mylibLdFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_apex10000").Rule("ld").Args["libFlags"]
+	mylibLdFlags := ctx.ModuleForTests(t, "mylib", "android_arm64_armv8-a_shared_apex10000_p").Rule("ld").Args["libFlags"]
 	ensureNotContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared_current/mylib2.so")
 	ensureContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared/mylib2.so")
-	rustDeps := ctx.ModuleForTests("foo.rust", "android_arm64_armv8-a_apex10000").Rule("rustc").Args["linkFlags"]
+	ensureNotContains(t, mylibLdFlags, "libmylib2_rust/android_arm64_armv8-a_shared_current/unstripped/libmylib2_rust.so")
+	ensureContains(t, mylibLdFlags, "libmylib2_rust/android_arm64_armv8-a_shared/unstripped/libmylib2_rust.so")
+	rustDeps := ctx.ModuleForTests(t, "foo.rust", "android_arm64_armv8-a_apex10000_p").Rule("rustc").Args["linkFlags"]
 	ensureNotContains(t, rustDeps, "libfoo.shared_from_rust/android_arm64_armv8-a_shared_current/libfoo.shared_from_rust.so")
 	ensureContains(t, rustDeps, "libfoo.shared_from_rust/android_arm64_armv8-a_shared/libfoo.shared_from_rust.so")
+	ensureNotContains(t, rustDeps, "libmylib_rust.shared_from_rust/android_arm64_armv8-a_shared_current/unstripped/libmylib_rust.shared_from_rust.so")
+	ensureContains(t, rustDeps, "libmylib_rust.shared_from_rust/android_arm64_armv8-a_shared/unstripped/libmylib_rust.shared_from_rust.so")
 }
 
 func TestApexWithStubsWithMinSdkVersion(t *testing.T) {
@@ -1172,7 +1265,11 @@
 		apex {
 			name: "myapex",
 			key: "myapex.key",
-			native_shared_libs: ["mylib", "mylib3"],
+			native_shared_libs: [
+				"mylib",
+				"mylib3",
+				"libmylib3_rust",
+			],
 			min_sdk_version: "29",
 		}
 
@@ -1185,7 +1282,12 @@
 		cc_library {
 			name: "mylib",
 			srcs: ["mylib.cpp"],
-			shared_libs: ["mylib2", "mylib3"],
+			shared_libs: [
+				"mylib2",
+				"mylib3#impl",
+				"libmylib2_rust",
+				"libmylib3_rust#impl",
+			],
 			system_shared_libs: [],
 			stl: "none",
 			apex_available: [ "myapex" ],
@@ -1205,6 +1307,17 @@
 			min_sdk_version: "28",
 		}
 
+		rust_ffi {
+			name: "libmylib2_rust",
+			crate_name: "mylib2_rust",
+			srcs: ["mylib.rs"],
+			stubs: {
+				symbol_file: "mylib2.map.txt",
+				versions: ["28", "29", "30", "current"],
+			},
+			min_sdk_version: "28",
+		}
+
 		cc_library {
 			name: "mylib3",
 			srcs: ["mylib.cpp"],
@@ -1219,6 +1332,19 @@
 			min_sdk_version: "28",
 		}
 
+		rust_ffi {
+			name: "libmylib3_rust",
+			crate_name: "mylib3_rust",
+			srcs: ["mylib.rs"],
+			shared_libs: ["libmylib4.from_rust"],
+			stubs: {
+				symbol_file: "mylib3.map.txt",
+				versions: ["28", "29", "30", "current"],
+			},
+			apex_available: [ "myapex" ],
+			min_sdk_version: "28",
+		}
+
 		cc_library {
 			name: "mylib4",
 			srcs: ["mylib.cpp"],
@@ -1227,43 +1353,63 @@
 			apex_available: [ "myapex" ],
 			min_sdk_version: "28",
 		}
+
+		rust_ffi {
+			name: "libmylib4.from_rust",
+			crate_name: "mylib4",
+			srcs: ["mylib.rs"],
+			apex_available: [ "myapex" ],
+			min_sdk_version: "28",
+		}
 	`)
 
-	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexRule")
+	apexRule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
 	// Ensure that direct non-stubs dep is always included
 	ensureContains(t, copyCmds, "image.apex/lib64/mylib.so")
+	ensureContains(t, copyCmds, "image.apex/lib64/mylib3.so")
+	ensureContains(t, copyCmds, "image.apex/lib64/libmylib3_rust.so")
 
 	// Ensure that indirect stubs dep is not included
 	ensureNotContains(t, copyCmds, "image.apex/lib64/mylib2.so")
+	ensureNotContains(t, copyCmds, "image.apex/lib64/libmylib2_rust.so")
 
 	// Ensure that direct stubs dep is included
 	ensureContains(t, copyCmds, "image.apex/lib64/mylib3.so")
 
-	mylibLdFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_apex29").Rule("ld").Args["libFlags"]
+	mylibLdFlags := ctx.ModuleForTests(t, "mylib", "android_arm64_armv8-a_shared_apex29").Rule("ld").Args["libFlags"]
 
 	// Ensure that mylib is linking with the latest version of stub for mylib2
 	ensureContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared_current/mylib2.so")
+	ensureContains(t, mylibLdFlags, "libmylib2_rust/android_arm64_armv8-a_shared_current/unstripped/libmylib2_rust.so")
 	// ... and not linking to the non-stub (impl) variant of mylib2
 	ensureNotContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared/mylib2.so")
+	ensureNotContains(t, mylibLdFlags, "libmylib2_rust/android_arm64_armv8-a_shared/unstripped/libmylib2_rust.so")
 
-	// Ensure that mylib is linking with the non-stub (impl) of mylib3 (because mylib3 is in the same apex)
+	// Ensure that mylib is linking with the non-stub (impl) of mylib3 (because the dependency is added with mylib3#impl)
 	ensureContains(t, mylibLdFlags, "mylib3/android_arm64_armv8-a_shared_apex29/mylib3.so")
+	ensureContains(t, mylibLdFlags, "libmylib3_rust/android_arm64_armv8-a_shared_apex29/unstripped/libmylib3_rust.so")
 	// .. and not linking to the stubs variant of mylib3
 	ensureNotContains(t, mylibLdFlags, "mylib3/android_arm64_armv8-a_shared_29/mylib3.so")
+	ensureNotContains(t, mylibLdFlags, "libmylib3_rust/android_arm64_armv8-a_shared_29/unstripped/libmylib3_rust.so")
 
 	// Ensure that stubs libs are built without -include flags
-	mylib2Cflags := ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_shared_29").Rule("cc").Args["cFlags"]
+	mylib2Cflags := ctx.ModuleForTests(t, "mylib2", "android_arm64_armv8-a_shared_29").Rule("cc").Args["cFlags"]
 	ensureNotContains(t, mylib2Cflags, "-include ")
 
 	// Ensure that genstub is invoked with --systemapi
-	ensureContains(t, ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_shared_29").Rule("genStubSrc").Args["flags"], "--systemapi")
+	ensureContains(t, ctx.ModuleForTests(t, "mylib2", "android_arm64_armv8-a_shared_29").Rule("genStubSrc").Args["flags"], "--systemapi")
+	ensureContains(t, ctx.ModuleForTests(t, "libmylib2_rust", "android_arm64_armv8-a_shared_29").Rule("cc.genStubSrc").Args["flags"], "--systemapi")
 
 	ensureExactContents(t, ctx, "myapex", "android_common_myapex", []string{
 		"lib64/mylib.so",
 		"lib64/mylib3.so",
+		"lib64/libmylib3_rust.so",
 		"lib64/mylib4.so",
+		"lib64/libmylib4.from_rust.so",
+
+		"lib64/libstd.dylib.so", // by the implicit dependency from foo.rust
 	})
 }
 
@@ -1288,7 +1434,10 @@
 		cc_library {
 			name: "mylib",
 			srcs: ["mylib.cpp"],
-			shared_libs: ["libstub"],
+			shared_libs: [
+				"libstub",
+				"libstub_rust",
+			],
 			apex_available: ["myapex"],
 			min_sdk_version: "Z",
 		}
@@ -1302,7 +1451,10 @@
 		apex {
 			name: "otherapex",
 			key: "myapex.key",
-			native_shared_libs: ["libstub"],
+			native_shared_libs: [
+				"libstub",
+				"libstub_rust",
+			],
 			min_sdk_version: "29",
 		}
 
@@ -1316,11 +1468,25 @@
 			min_sdk_version: "29",
 		}
 
+		rust_ffi {
+			name: "libstub_rust",
+			crate_name: "stub_rust",
+			srcs: ["mylib.rs"],
+			stubs: {
+				versions: ["29", "Z", "current"],
+			},
+			apex_available: ["otherapex"],
+			min_sdk_version: "29",
+		}
+
 		// platform module depending on libstub from otherapex should use the latest stub("current")
 		cc_library {
 			name: "libplatform",
 			srcs: ["mylib.cpp"],
-			shared_libs: ["libstub"],
+			shared_libs: [
+				"libstub",
+				"libstub_rust",
+			],
 		}
 	`,
 		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
@@ -1331,19 +1497,26 @@
 	)
 
 	// Ensure that mylib from myapex is built against the latest stub (current)
-	mylibCflags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_static_apex10000").Rule("cc").Args["cFlags"]
+	mylibCflags := ctx.ModuleForTests(t, "mylib", "android_arm64_armv8-a_static_apex10000").Rule("cc").Args["cFlags"]
 	ensureContains(t, mylibCflags, "-D__LIBSTUB_API__=10000 ")
-	mylibLdflags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_apex10000").Rule("ld").Args["libFlags"]
+	// rust stubs do not emit -D__LIBFOO_API__ flags as this is deprecated behavior for cc stubs
+
+	mylibLdflags := ctx.ModuleForTests(t, "mylib", "android_arm64_armv8-a_shared_apex10000").Rule("ld").Args["libFlags"]
 	ensureContains(t, mylibLdflags, "libstub/android_arm64_armv8-a_shared_current/libstub.so ")
+	ensureContains(t, mylibLdflags, "libstub_rust/android_arm64_armv8-a_shared_current/unstripped/libstub_rust.so ")
 
 	// Ensure that libplatform is built against latest stub ("current") of mylib3 from the apex
-	libplatformCflags := ctx.ModuleForTests("libplatform", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
+	libplatformCflags := ctx.ModuleForTests(t, "libplatform", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 	ensureContains(t, libplatformCflags, "-D__LIBSTUB_API__=10000 ") // "current" maps to 10000
-	libplatformLdflags := ctx.ModuleForTests("libplatform", "android_arm64_armv8-a_shared").Rule("ld").Args["libFlags"]
+	// rust stubs do not emit -D__LIBFOO_API__ flags as this is deprecated behavior for cc stubs
+
+	libplatformLdflags := ctx.ModuleForTests(t, "libplatform", "android_arm64_armv8-a_shared").Rule("ld").Args["libFlags"]
 	ensureContains(t, libplatformLdflags, "libstub/android_arm64_armv8-a_shared_current/libstub.so ")
+	ensureContains(t, libplatformLdflags, "libstub_rust/android_arm64_armv8-a_shared_current/unstripped/libstub_rust.so ")
 }
 
 func TestApexWithExplicitStubsDependency(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex2",
@@ -1361,7 +1534,10 @@
 		cc_library {
 			name: "mylib",
 			srcs: ["mylib.cpp"],
-			shared_libs: ["libfoo#10"],
+			shared_libs: [
+				"libfoo#10",
+				"libfoo_rust#10"
+			],
 			static_libs: ["libbaz"],
 			system_shared_libs: [],
 			stl: "none",
@@ -1379,6 +1555,16 @@
 			},
 		}
 
+		rust_ffi {
+			name: "libfoo_rust",
+			crate_name: "foo_rust",
+			srcs: ["mylib.cpp"],
+			shared_libs: ["libbar.from_rust"],
+			stubs: {
+				versions: ["10", "20", "30"],
+			},
+		}
+
 		cc_library {
 			name: "libbar",
 			srcs: ["mylib.cpp"],
@@ -1386,6 +1572,13 @@
 			stl: "none",
 		}
 
+		cc_library {
+			name: "libbar.from_rust",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			stl: "none",
+		}
+
 		cc_library_static {
 			name: "libbaz",
 			srcs: ["mylib.cpp"],
@@ -1396,7 +1589,7 @@
 
 	`)
 
-	apexRule := ctx.ModuleForTests("myapex2", "android_common_myapex2").Rule("apexRule")
+	apexRule := ctx.ModuleForTests(t, "myapex2", "android_common_myapex2").Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
 	// Ensure that direct non-stubs dep is always included
@@ -1404,32 +1597,39 @@
 
 	// Ensure that indirect stubs dep is not included
 	ensureNotContains(t, copyCmds, "image.apex/lib64/libfoo.so")
+	ensureNotContains(t, copyCmds, "image.apex/lib64/libfoo_rust.so")
 
 	// Ensure that dependency of stubs is not included
 	ensureNotContains(t, copyCmds, "image.apex/lib64/libbar.so")
+	ensureNotContains(t, copyCmds, "image.apex/lib64/libbar.from_rust.so")
 
-	mylibLdFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_apex10000").Rule("ld").Args["libFlags"]
+	mylibLdFlags := ctx.ModuleForTests(t, "mylib", "android_arm64_armv8-a_shared_apex10000").Rule("ld").Args["libFlags"]
 
 	// Ensure that mylib is linking with version 10 of libfoo
 	ensureContains(t, mylibLdFlags, "libfoo/android_arm64_armv8-a_shared_10/libfoo.so")
+	ensureContains(t, mylibLdFlags, "libfoo_rust/android_arm64_armv8-a_shared_10/unstripped/libfoo_rust.so")
 	// ... and not linking to the non-stub (impl) variant of libfoo
 	ensureNotContains(t, mylibLdFlags, "libfoo/android_arm64_armv8-a_shared/libfoo.so")
+	ensureNotContains(t, mylibLdFlags, "libfoo_rust/android_arm64_armv8-a_shared/unstripped/libfoo_rust.so")
 
-	libFooStubsLdFlags := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared_10").Rule("ld").Args["libFlags"]
+	libFooStubsLdFlags := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_shared_10").Rule("ld").Args["libFlags"]
+	libFooRustStubsLdFlags := ctx.ModuleForTests(t, "libfoo_rust", "android_arm64_armv8-a_shared_10").Rule("ld").Args["libFlags"]
 
 	// Ensure that libfoo stubs is not linking to libbar (since it is a stubs)
 	ensureNotContains(t, libFooStubsLdFlags, "libbar.so")
+	ensureNotContains(t, libFooRustStubsLdFlags, "libbar.from_rust.so")
 
 	fullDepsInfo := strings.Split(android.ContentFromFileRuleForTests(t, ctx,
-		ctx.ModuleForTests("myapex2", "android_common_myapex2").Output("depsinfo/fulllist.txt")), "\n")
+		ctx.ModuleForTests(t, "myapex2", "android_common_myapex2").Output("depsinfo/fulllist.txt")), "\n")
 	ensureListContains(t, fullDepsInfo, "  libfoo(minSdkVersion:(no version)) (external) <- mylib")
 
 	flatDepsInfo := strings.Split(android.ContentFromFileRuleForTests(t, ctx,
-		ctx.ModuleForTests("myapex2", "android_common_myapex2").Output("depsinfo/flatlist.txt")), "\n")
+		ctx.ModuleForTests(t, "myapex2", "android_common_myapex2").Output("depsinfo/flatlist.txt")), "\n")
 	ensureListContains(t, flatDepsInfo, "libfoo(minSdkVersion:(no version)) (external)")
 }
 
 func TestApexWithRuntimeLibsDependency(t *testing.T) {
+	t.Parallel()
 	/*
 		myapex
 		  |
@@ -1457,7 +1657,11 @@
 			srcs: ["mylib.cpp"],
 			static_libs: ["libstatic"],
 			shared_libs: ["libshared"],
-			runtime_libs: ["libfoo", "libbar"],
+			runtime_libs: [
+				"libfoo",
+				"libbar",
+				"libfoo_rs",
+			],
 			system_shared_libs: [],
 			stl: "none",
 			apex_available: [ "myapex" ],
@@ -1473,6 +1677,15 @@
 			},
 		}
 
+		rust_ffi {
+			name: "libfoo_rs",
+			crate_name: "foo_rs",
+			srcs: ["mylib.rs"],
+			stubs: {
+				versions: ["10", "20", "30"],
+			},
+		}
+
 		cc_library {
 			name: "libbar",
 			srcs: ["mylib.cpp"],
@@ -1516,7 +1729,7 @@
 		}
 	`)
 
-	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexRule")
+	apexRule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
 	// Ensure that direct non-stubs dep is always included
@@ -1524,6 +1737,7 @@
 
 	// Ensure that indirect stubs dep is not included
 	ensureNotContains(t, copyCmds, "image.apex/lib64/libfoo.so")
+	ensureNotContains(t, copyCmds, "image.apex/lib64/libfoo_rs.so")
 
 	// Ensure that runtime_libs dep in included
 	ensureContains(t, copyCmds, "image.apex/lib64/libbar.so")
@@ -1532,9 +1746,10 @@
 
 	ensureNotContains(t, copyCmds, "image.apex/lib64/libstatic_to_runtime.so")
 
-	apexManifestRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexManifestRule")
+	apexManifestRule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("apexManifestRule")
 	ensureListEmpty(t, names(apexManifestRule.Args["provideNativeLibs"]))
 	ensureListContains(t, names(apexManifestRule.Args["requireNativeLibs"]), "libfoo.so")
+	ensureListContains(t, names(apexManifestRule.Args["requireNativeLibs"]), "libfoo_rs.so")
 }
 
 var prepareForTestOfRuntimeApexWithHwasan = android.GroupFixturePreparers(
@@ -1558,6 +1773,7 @@
 )
 
 func TestRuntimeApexShouldInstallHwasanIfLibcDependsOnIt(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(prepareForTestOfRuntimeApexWithHwasan).RunTestWithBp(t, `
 		cc_library {
 			name: "libc",
@@ -1600,7 +1816,7 @@
 		"lib64/bionic/libclang_rt.hwasan-aarch64-android.so",
 	})
 
-	hwasan := ctx.ModuleForTests("libclang_rt.hwasan", "android_arm64_armv8-a_shared")
+	hwasan := ctx.ModuleForTests(t, "libclang_rt.hwasan", "android_arm64_armv8-a_shared")
 
 	installed := hwasan.Description("install libclang_rt.hwasan")
 	ensureContains(t, installed.Output.String(), "/system/lib64/bootstrap/libclang_rt.hwasan-aarch64-android.so")
@@ -1611,6 +1827,7 @@
 }
 
 func TestRuntimeApexShouldInstallHwasanIfHwaddressSanitized(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForTestOfRuntimeApexWithHwasan,
 		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
@@ -1655,7 +1872,7 @@
 		"lib64/bionic/libclang_rt.hwasan-aarch64-android.so",
 	})
 
-	hwasan := ctx.ModuleForTests("libclang_rt.hwasan", "android_arm64_armv8-a_shared")
+	hwasan := ctx.ModuleForTests(t, "libclang_rt.hwasan", "android_arm64_armv8-a_shared")
 
 	installed := hwasan.Description("install libclang_rt.hwasan")
 	ensureContains(t, installed.Output.String(), "/system/lib64/bootstrap/libclang_rt.hwasan-aarch64-android.so")
@@ -1666,6 +1883,7 @@
 }
 
 func TestApexDependsOnLLNDKTransitively(t *testing.T) {
+	t.Parallel()
 	testcases := []struct {
 		name          string
 		minSdkVersion string
@@ -1690,6 +1908,7 @@
 	}
 	for _, tc := range testcases {
 		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
 			ctx := testApex(t, `
 			apex {
 				name: "myapex",
@@ -1736,17 +1955,17 @@
 			})
 
 			// Ensure that LLNDK dep is required
-			apexManifestRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexManifestRule")
+			apexManifestRule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("apexManifestRule")
 			ensureListEmpty(t, names(apexManifestRule.Args["provideNativeLibs"]))
 			ensureListContains(t, names(apexManifestRule.Args["requireNativeLibs"]), "libbar.so")
 
-			mylibLdFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_"+tc.apexVariant).Rule("ld").Args["libFlags"]
+			mylibLdFlags := ctx.ModuleForTests(t, "mylib", "android_arm64_armv8-a_shared_"+tc.apexVariant).Rule("ld").Args["libFlags"]
 			ensureContains(t, mylibLdFlags, "libbar/android_arm64_armv8-a_shared_"+tc.shouldLink+"/libbar.so")
 			for _, ver := range tc.shouldNotLink {
 				ensureNotContains(t, mylibLdFlags, "libbar/android_arm64_armv8-a_shared_"+ver+"/libbar.so")
 			}
 
-			mylibCFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_static_"+tc.apexVariant).Rule("cc").Args["cFlags"]
+			mylibCFlags := ctx.ModuleForTests(t, "mylib", "android_arm64_armv8-a_static_"+tc.apexVariant).Rule("cc").Args["cFlags"]
 			ver := tc.shouldLink
 			if tc.shouldLink == "current" {
 				ver = strconv.Itoa(android.FutureApiLevelInt)
@@ -1757,11 +1976,12 @@
 }
 
 func TestApexWithSystemLibsStubs(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
 			key: "myapex.key",
-			native_shared_libs: ["mylib", "mylib_shared", "libdl", "libm"],
+			native_shared_libs: ["mylib", "mylib_shared", "libdl", "libm", "libmylib_rs"],
 			updatable: false,
 		}
 
@@ -1774,12 +1994,20 @@
 		cc_library {
 			name: "mylib",
 			srcs: ["mylib.cpp"],
-			system_shared_libs: ["libc", "libm"],
-			shared_libs: ["libdl#27"],
+			system_shared_libs: ["libc"],
+			shared_libs: ["libdl#27", "libm#impl"],
 			stl: "none",
 			apex_available: [ "myapex" ],
 		}
 
+		rust_ffi {
+			name: "libmylib_rs",
+			crate_name: "mylib_rs",
+			shared_libs: ["libvers#27", "libm#impl"],
+			srcs: ["mylib.rs"],
+			apex_available: [ "myapex" ],
+		}
+
 		cc_library_shared {
 			name: "mylib_shared",
 			srcs: ["mylib.cpp"],
@@ -1794,22 +2022,40 @@
 			stl: "none",
 			bootstrap: true,
 		}
+
+		rust_ffi {
+			name: "libbootstrap_rs",
+			srcs: ["mylib.cpp"],
+			crate_name: "bootstrap_rs",
+			bootstrap: true,
+		}
+
+		cc_library {
+			name: "libvers",
+			srcs: ["mylib.cpp"],
+			stl: "none",
+			stubs: { versions: ["27","30"] },
+		}
 	`)
 
-	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexRule")
+	apexRule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
-	// Ensure that mylib, libm, libdl are included.
+	// Ensure that mylib, libmylib_rs, libm, libdl, libstd.dylib.so (from Rust) are included.
 	ensureContains(t, copyCmds, "image.apex/lib64/mylib.so")
+	ensureContains(t, copyCmds, "image.apex/lib64/libmylib_rs.so")
 	ensureContains(t, copyCmds, "image.apex/lib64/bionic/libm.so")
 	ensureContains(t, copyCmds, "image.apex/lib64/bionic/libdl.so")
+	ensureContains(t, copyCmds, "image.apex/lib64/libstd.dylib.so")
 
-	// Ensure that libc is not included (since it has stubs and not listed in native_shared_libs)
+	// Ensure that libc and liblog (from Rust) is not included (since it has stubs and not listed in native_shared_libs)
 	ensureNotContains(t, copyCmds, "image.apex/lib64/bionic/libc.so")
+	ensureNotContains(t, copyCmds, "image.apex/lib64/liblog.so")
 
-	mylibLdFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_apex10000").Rule("ld").Args["libFlags"]
-	mylibCFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_static_apex10000").Rule("cc").Args["cFlags"]
-	mylibSharedCFlags := ctx.ModuleForTests("mylib_shared", "android_arm64_armv8-a_shared_apex10000").Rule("cc").Args["cFlags"]
+	mylibLdFlags := ctx.ModuleForTests(t, "mylib", "android_arm64_armv8-a_shared_apex10000").Rule("ld").Args["libFlags"]
+	mylibRsFlags := ctx.ModuleForTests(t, "libmylib_rs", "android_arm64_armv8-a_shared_apex10000").Rule("rustc").Args["linkFlags"]
+	mylibCFlags := ctx.ModuleForTests(t, "mylib", "android_arm64_armv8-a_static_apex10000").Rule("cc").Args["cFlags"]
+	mylibSharedCFlags := ctx.ModuleForTests(t, "mylib_shared", "android_arm64_armv8-a_shared_apex10000").Rule("cc").Args["cFlags"]
 
 	// For dependency to libc
 	// Ensure that mylib is linking with the latest version of stubs
@@ -1841,14 +2087,46 @@
 	ensureContains(t, mylibCFlags, "__LIBDL_API__=27")
 	ensureContains(t, mylibSharedCFlags, "__LIBDL_API__=27")
 
+	// Rust checks
+	// For dependency to libc, liblog
+	// Ensure that libmylib_rs is linking with the latest versions of stubs
+	ensureContains(t, mylibRsFlags, "libc/android_arm64_armv8-a_shared_current/libc.so")
+	ensureContains(t, mylibRsFlags, "liblog/android_arm64_armv8-a_shared_current/liblog.so")
+	// ... and not linking to the non-stub (impl) variants
+	ensureNotContains(t, mylibRsFlags, "libc/android_arm64_armv8-a_shared/libc.so")
+	ensureNotContains(t, mylibRsFlags, "liblog/android_arm64_armv8-a_shared/liblog.so")
+
+	// For libm dependency (explicit)
+	// Ensure that mylib is linking with the non-stub (impl) variant
+	ensureContains(t, mylibRsFlags, "libm/android_arm64_armv8-a_shared_apex10000/libm.so")
+	// ... and not linking to the stub variant
+	ensureNotContains(t, mylibRsFlags, "libm/android_arm64_armv8-a_shared_29/libm.so")
+
+	// For dependency to libvers
+	// (We do not use libdl#27 as Rust links the system libs implicitly and does
+	// not currently have a system_shared_libs equivalent to prevent this)
+	// Ensure that mylib is linking with the specified version of stubs
+	ensureContains(t, mylibRsFlags, "libvers/android_arm64_armv8-a_shared_27/libvers.so")
+	// ... and not linking to the other versions of stubs
+	ensureNotContains(t, mylibRsFlags, "libvers/android_arm64_armv8-a_shared_30/libvers.so")
+	// ... and not linking to the non-stub (impl) variant
+	ensureNotContains(t, mylibRsFlags, "libvers/android_arm64_armv8-a_shared_apex10000/libvers.so")
+
 	// Ensure that libBootstrap is depending on the platform variant of bionic libs
-	libFlags := ctx.ModuleForTests("libBootstrap", "android_arm64_armv8-a_shared").Rule("ld").Args["libFlags"]
+	libFlags := ctx.ModuleForTests(t, "libBootstrap", "android_arm64_armv8-a_shared").Rule("ld").Args["libFlags"]
 	ensureContains(t, libFlags, "libc/android_arm64_armv8-a_shared/libc.so")
 	ensureContains(t, libFlags, "libm/android_arm64_armv8-a_shared/libm.so")
 	ensureContains(t, libFlags, "libdl/android_arm64_armv8-a_shared/libdl.so")
+
+	// Ensure that libbootstrap_rs is depending on the platform variant of bionic libs
+	libRsFlags := ctx.ModuleForTests(t, "libbootstrap_rs", "android_arm64_armv8-a_shared").Rule("rustc").Args["linkFlags"]
+	ensureContains(t, libRsFlags, "libc/android_arm64_armv8-a_shared/libc.so")
+	ensureContains(t, libRsFlags, "libm/android_arm64_armv8-a_shared/libm.so")
+	ensureContains(t, libRsFlags, "libdl/android_arm64_armv8-a_shared/libdl.so")
 }
 
 func TestApexMinSdkVersion_NativeModulesShouldBeBuiltAgainstStubs(t *testing.T) {
+	t.Parallel()
 	// there are three links between liba --> libz.
 	// 1) myapex -> libx -> liba -> libz    : this should be #30 link
 	// 2) otherapex -> liby -> liba -> libz : this should be #30 link
@@ -1894,7 +2172,7 @@
 
 		cc_library {
 			name: "liba",
-			shared_libs: ["libz"],
+			shared_libs: ["libz", "libz_rs"],
 			system_shared_libs: [],
 			stl: "none",
 			apex_available: [
@@ -1912,31 +2190,50 @@
 				versions: ["28", "30"],
 			},
 		}
+
+		rust_ffi {
+			name: "libz_rs",
+			crate_name: "z_rs",
+			srcs: ["foo.rs"],
+			stubs: {
+				versions: ["28", "30"],
+			},
+		}
 	`)
 
 	expectLink := func(from, from_variant, to, to_variant string) {
-		ldArgs := ctx.ModuleForTests(from, "android_arm64_armv8-a_"+from_variant).Rule("ld").Args["libFlags"]
+		ldArgs := ctx.ModuleForTests(t, from, "android_arm64_armv8-a_"+from_variant).Rule("ld").Args["libFlags"]
 		ensureContains(t, ldArgs, "android_arm64_armv8-a_"+to_variant+"/"+to+".so")
 	}
 	expectNoLink := func(from, from_variant, to, to_variant string) {
-		ldArgs := ctx.ModuleForTests(from, "android_arm64_armv8-a_"+from_variant).Rule("ld").Args["libFlags"]
+		ldArgs := ctx.ModuleForTests(t, from, "android_arm64_armv8-a_"+from_variant).Rule("ld").Args["libFlags"]
 		ensureNotContains(t, ldArgs, "android_arm64_armv8-a_"+to_variant+"/"+to+".so")
 	}
 	// platform liba is linked to non-stub version
 	expectLink("liba", "shared", "libz", "shared")
+	expectLink("liba", "shared", "unstripped/libz_rs", "shared")
 	// liba in myapex is linked to current
 	expectLink("liba", "shared_apex29", "libz", "shared_current")
 	expectNoLink("liba", "shared_apex29", "libz", "shared_30")
 	expectNoLink("liba", "shared_apex29", "libz", "shared_28")
 	expectNoLink("liba", "shared_apex29", "libz", "shared")
+	expectLink("liba", "shared_apex29", "unstripped/libz_rs", "shared_current")
+	expectNoLink("liba", "shared_apex29", "unstripped/libz_rs", "shared_30")
+	expectNoLink("liba", "shared_apex29", "unstripped/libz_rs", "shared_28")
+	expectNoLink("liba", "shared_apex29", "unstripped/libz_rs", "shared")
 	// liba in otherapex is linked to current
 	expectLink("liba", "shared_apex30", "libz", "shared_current")
 	expectNoLink("liba", "shared_apex30", "libz", "shared_30")
 	expectNoLink("liba", "shared_apex30", "libz", "shared_28")
 	expectNoLink("liba", "shared_apex30", "libz", "shared")
+	expectLink("liba", "shared_apex30", "unstripped/libz_rs", "shared_current")
+	expectNoLink("liba", "shared_apex30", "unstripped/libz_rs", "shared_30")
+	expectNoLink("liba", "shared_apex30", "unstripped/libz_rs", "shared_28")
+	expectNoLink("liba", "shared_apex30", "unstripped/libz_rs", "shared")
 }
 
 func TestApexMinSdkVersion_SupportsCodeNames(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -1975,11 +2272,11 @@
 	)
 
 	expectLink := func(from, from_variant, to, to_variant string) {
-		ldArgs := ctx.ModuleForTests(from, "android_arm64_armv8-a_"+from_variant).Rule("ld").Args["libFlags"]
+		ldArgs := ctx.ModuleForTests(t, from, "android_arm64_armv8-a_"+from_variant).Rule("ld").Args["libFlags"]
 		ensureContains(t, ldArgs, "android_arm64_armv8-a_"+to_variant+"/"+to+".so")
 	}
 	expectNoLink := func(from, from_variant, to, to_variant string) {
-		ldArgs := ctx.ModuleForTests(from, "android_arm64_armv8-a_"+from_variant).Rule("ld").Args["libFlags"]
+		ldArgs := ctx.ModuleForTests(t, from, "android_arm64_armv8-a_"+from_variant).Rule("ld").Args["libFlags"]
 		ensureNotContains(t, ldArgs, "android_arm64_armv8-a_"+to_variant+"/"+to+".so")
 	}
 	expectLink("libx", "shared_apex10000", "libz", "shared_current")
@@ -1989,6 +2286,7 @@
 }
 
 func TestApexMinSdkVersion_SupportsCodeNames_JavaLibs(t *testing.T) {
+	t.Parallel()
 	testApex(t, `
 		apex {
 			name: "myapex",
@@ -2009,6 +2307,7 @@
 			apex_available: [ "myapex" ],
 			sdk_version: "current",
 			min_sdk_version: "S", // should be okay
+			compile_dex: true,
 		}
 	`,
 		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
@@ -2019,6 +2318,7 @@
 }
 
 func TestApexMinSdkVersion_DefaultsToLatest(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -2052,11 +2352,11 @@
 	`)
 
 	expectLink := func(from, from_variant, to, to_variant string) {
-		ldArgs := ctx.ModuleForTests(from, "android_arm64_armv8-a_"+from_variant).Rule("ld").Args["libFlags"]
+		ldArgs := ctx.ModuleForTests(t, from, "android_arm64_armv8-a_"+from_variant).Rule("ld").Args["libFlags"]
 		ensureContains(t, ldArgs, "android_arm64_armv8-a_"+to_variant+"/"+to+".so")
 	}
 	expectNoLink := func(from, from_variant, to, to_variant string) {
-		ldArgs := ctx.ModuleForTests(from, "android_arm64_armv8-a_"+from_variant).Rule("ld").Args["libFlags"]
+		ldArgs := ctx.ModuleForTests(t, from, "android_arm64_armv8-a_"+from_variant).Rule("ld").Args["libFlags"]
 		ensureNotContains(t, ldArgs, "android_arm64_armv8-a_"+to_variant+"/"+to+".so")
 	}
 	expectLink("libx", "shared_apex10000", "libz", "shared_current")
@@ -2066,6 +2366,7 @@
 }
 
 func TestApexMinSdkVersion_InVendorApex(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -2099,14 +2400,14 @@
 
 	vendorVariant := "android_vendor_arm64_armv8-a"
 
-	mylib := ctx.ModuleForTests("mylib", vendorVariant+"_shared_apex29")
+	mylib := ctx.ModuleForTests(t, "mylib", vendorVariant+"_shared_apex29")
 
 	// Ensure that mylib links with "current" LLNDK
 	libFlags := names(mylib.Rule("ld").Args["libFlags"])
 	ensureListContains(t, libFlags, "out/soong/.intermediates/libbar/"+vendorVariant+"_shared/libbar.so")
 
 	// Ensure that mylib is targeting 29
-	ccRule := ctx.ModuleForTests("mylib", vendorVariant+"_static_apex29").Output("obj/mylib.o")
+	ccRule := ctx.ModuleForTests(t, "mylib", vendorVariant+"_static_apex29").Output("obj/mylib.o")
 	ensureContains(t, ccRule.Args["cFlags"], "-target aarch64-linux-android29")
 
 	// Ensure that the correct variant of crtbegin_so is used.
@@ -2114,11 +2415,92 @@
 	ensureContains(t, crtBegin, "out/soong/.intermediates/"+cc.DefaultCcCommonTestModulesDir+"crtbegin_so/"+vendorVariant+"_apex29/crtbegin_so.o")
 
 	// Ensure that the crtbegin_so used by the APEX is targeting 29
-	cflags := ctx.ModuleForTests("crtbegin_so", vendorVariant+"_apex29").Rule("cc").Args["cFlags"]
+	cflags := ctx.ModuleForTests(t, "crtbegin_so", vendorVariant+"_apex29").Rule("cc").Args["cFlags"]
 	android.AssertStringDoesContain(t, "cflags", cflags, "-target aarch64-linux-android29")
 }
 
-func TestTrackAllowedDeps(t *testing.T) {
+func TestTrackAllowedDepsForAndroidApex(t *testing.T) {
+	t.Parallel()
+	ctx := testApex(t, `
+		apex {
+			name: "com.android.myapex",
+			key: "myapex.key",
+			updatable: true,
+			native_shared_libs: [
+				"mylib",
+				"yourlib",
+			],
+			min_sdk_version: "29",
+		}
+
+		apex {
+			name: "myapex2",
+			key: "myapex.key",
+			updatable: false,
+			native_shared_libs: ["yourlib"],
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_library {
+			name: "mylib",
+			srcs: ["mylib.cpp"],
+			shared_libs: ["libbar", "libbar_rs"],
+			min_sdk_version: "29",
+			apex_available: ["com.android.myapex"],
+		}
+
+		cc_library {
+			name: "libbar",
+			stubs: { versions: ["29", "30"] },
+		}
+
+		rust_ffi {
+			name: "libbar_rs",
+			crate_name: "bar_rs",
+			srcs: ["bar.rs"],
+			stubs: { versions: ["29", "30"] },
+		}
+
+		cc_library {
+			name: "yourlib",
+			srcs: ["mylib.cpp"],
+			min_sdk_version: "29",
+			apex_available: ["com.android.myapex", "myapex2", "//apex_available:platform"],
+		}
+	`, withFiles(android.MockFS{
+		"packages/modules/common/build/allowed_deps.txt": nil,
+	}),
+		android.FixtureMergeMockFs(android.MockFS{
+			"system/sepolicy/apex/com.android.myapex-file_contexts": nil,
+		}))
+
+	depsinfo := ctx.SingletonForTests(t, "apex_depsinfo_singleton")
+	inputs := depsinfo.Rule("generateApexDepsInfoFilesRule").BuildParams.Inputs.Strings()
+	android.AssertStringListContains(t, "updatable com.android.myapex should generate depsinfo file", inputs,
+		"out/soong/.intermediates/com.android.myapex/android_common_com.android.myapex/depsinfo/flatlist.txt")
+	android.AssertStringListDoesNotContain(t, "non-updatable myapex2 should not generate depsinfo file", inputs,
+		"out/soong/.intermediates/myapex2/android_common_myapex2/depsinfo/flatlist.txt")
+
+	myapex := ctx.ModuleForTests(t, "com.android.myapex", "android_common_com.android.myapex")
+	flatlist := strings.Split(android.ContentFromFileRuleForTests(t, ctx,
+		myapex.Output("depsinfo/flatlist.txt")), "\n")
+	android.AssertStringListContains(t, "deps with stubs should be tracked in depsinfo as external dep",
+		flatlist, "libbar(minSdkVersion:(no version)) (external)")
+	android.AssertStringListContains(t, "deps with stubs should be tracked in depsinfo as external dep",
+		flatlist, "libbar_rs(minSdkVersion:(no version)) (external)")
+	android.AssertStringListDoesNotContain(t, "do not track if not available for platform",
+		flatlist, "mylib:(minSdkVersion:29)")
+	android.AssertStringListContains(t, "track platform-available lib",
+		flatlist, "yourlib(minSdkVersion:29)")
+}
+
+func TestNotTrackAllowedDepsForNonAndroidApex(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -2167,173 +2549,19 @@
 		"packages/modules/common/build/allowed_deps.txt": nil,
 	}))
 
-	depsinfo := ctx.SingletonForTests("apex_depsinfo_singleton")
+	depsinfo := ctx.SingletonForTests(t, "apex_depsinfo_singleton")
 	inputs := depsinfo.Rule("generateApexDepsInfoFilesRule").BuildParams.Inputs.Strings()
-	android.AssertStringListContains(t, "updatable myapex should generate depsinfo file", inputs,
+	android.AssertStringListDoesNotContain(t, "updatable myapex should generate depsinfo file", inputs,
 		"out/soong/.intermediates/myapex/android_common_myapex/depsinfo/flatlist.txt")
 	android.AssertStringListDoesNotContain(t, "non-updatable myapex2 should not generate depsinfo file", inputs,
 		"out/soong/.intermediates/myapex2/android_common_myapex2/depsinfo/flatlist.txt")
-
-	myapex := ctx.ModuleForTests("myapex", "android_common_myapex")
-	flatlist := strings.Split(android.ContentFromFileRuleForTests(t, ctx,
-		myapex.Output("depsinfo/flatlist.txt")), "\n")
-	android.AssertStringListContains(t, "deps with stubs should be tracked in depsinfo as external dep",
-		flatlist, "libbar(minSdkVersion:(no version)) (external)")
-	android.AssertStringListDoesNotContain(t, "do not track if not available for platform",
-		flatlist, "mylib:(minSdkVersion:29)")
-	android.AssertStringListContains(t, "track platform-available lib",
-		flatlist, "yourlib(minSdkVersion:29)")
-}
-
-func TestTrackCustomAllowedDepsInvalidDefaultTxt(t *testing.T) {
-	ctx := testApex(t, `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			updatable: true,
-			native_shared_libs: [
-				"mylib",
-				"yourlib",
-			],
-			min_sdk_version: "29",
-		}
-
-		apex {
-			name: "myapex2",
-			key: "myapex.key",
-			updatable: false,
-			native_shared_libs: ["yourlib"],
-		}
-
-		apex_key {
-			name: "myapex.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-
-		cc_library {
-			name: "mylib",
-			srcs: ["mylib.cpp"],
-			shared_libs: ["libbar"],
-			min_sdk_version: "29",
-			apex_available: ["myapex"],
-		}
-
-		cc_library {
-			name: "libbar",
-			stubs: { versions: ["29", "30"] },
-		}
-
-		cc_library {
-			name: "yourlib",
-			srcs: ["mylib.cpp"],
-			min_sdk_version: "29",
-			apex_available: ["myapex", "myapex2", "//apex_available:platform"],
-		}
-	`, withFiles(android.MockFS{
-		"packages/modules/common/build/custom_allowed_deps.txt": nil,
-	}),
-		android.FixtureModifyProductVariables(
-			func(variables android.FixtureProductVariables) {
-				variables.ExtraAllowedDepsTxt = proptools.StringPtr("packages/modules/common/build/custom_allowed_deps.txt")
-			},
-		))
-
-	depsinfo := ctx.SingletonForTests("apex_depsinfo_singleton")
-	inputs := depsinfo.Rule("generateApexDepsInfoFilesRule").BuildParams.Inputs.Strings()
-	android.AssertStringListContains(t, "updatable myapex should generate depsinfo file", inputs,
-		"out/soong/.intermediates/myapex/android_common_myapex/depsinfo/flatlist.txt")
-	android.AssertStringListDoesNotContain(t, "non-updatable myapex2 should not generate depsinfo file", inputs,
-		"out/soong/.intermediates/myapex2/android_common_myapex2/depsinfo/flatlist.txt")
-
-	myapex := ctx.ModuleForTests("myapex", "android_common_myapex")
-	flatlist := strings.Split(android.ContentFromFileRuleForTests(t, ctx,
-		myapex.Output("depsinfo/flatlist.txt")), "\n")
-	android.AssertStringListContains(t, "deps with stubs should be tracked in depsinfo as external dep",
-		flatlist, "libbar(minSdkVersion:(no version)) (external)")
-	android.AssertStringListDoesNotContain(t, "do not track if not available for platform",
-		flatlist, "mylib:(minSdkVersion:29)")
-	android.AssertStringListContains(t, "track platform-available lib",
-		flatlist, "yourlib(minSdkVersion:29)")
-}
-
-func TestTrackCustomAllowedDepsWithDefaultTxt(t *testing.T) {
-	ctx := testApex(t, `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			updatable: true,
-			native_shared_libs: [
-				"mylib",
-				"yourlib",
-			],
-			min_sdk_version: "29",
-		}
-
-		apex {
-			name: "myapex2",
-			key: "myapex.key",
-			updatable: false,
-			native_shared_libs: ["yourlib"],
-		}
-
-		apex_key {
-			name: "myapex.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-
-		cc_library {
-			name: "mylib",
-			srcs: ["mylib.cpp"],
-			shared_libs: ["libbar"],
-			min_sdk_version: "29",
-			apex_available: ["myapex"],
-		}
-
-		cc_library {
-			name: "libbar",
-			stubs: { versions: ["29", "30"] },
-		}
-
-		cc_library {
-			name: "yourlib",
-			srcs: ["mylib.cpp"],
-			min_sdk_version: "29",
-			apex_available: ["myapex", "myapex2", "//apex_available:platform"],
-		}
-	`, withFiles(android.MockFS{
-		"packages/modules/common/build/custom_allowed_deps.txt": nil,
-		"packages/modules/common/build/allowed_deps.txt":        nil,
-	}),
-		android.FixtureModifyProductVariables(
-			func(variables android.FixtureProductVariables) {
-				variables.ExtraAllowedDepsTxt = proptools.StringPtr("packages/modules/common/build/custom_allowed_deps.txt")
-			},
-		))
-
-	depsinfo := ctx.SingletonForTests("apex_depsinfo_singleton")
-	inputs := depsinfo.Rule("generateApexDepsInfoFilesRule").BuildParams.Inputs.Strings()
-	android.AssertStringListContains(t, "updatable myapex should generate depsinfo file", inputs,
-		"out/soong/.intermediates/myapex/android_common_myapex/depsinfo/flatlist.txt")
-	android.AssertStringListDoesNotContain(t, "non-updatable myapex2 should not generate depsinfo file", inputs,
-		"out/soong/.intermediates/myapex2/android_common_myapex2/depsinfo/flatlist.txt")
-
-	myapex := ctx.ModuleForTests("myapex", "android_common_myapex")
-	flatlist := strings.Split(android.ContentFromFileRuleForTests(t, ctx,
-		myapex.Output("depsinfo/flatlist.txt")), "\n")
-	android.AssertStringListContains(t, "deps with stubs should be tracked in depsinfo as external dep",
-		flatlist, "libbar(minSdkVersion:(no version)) (external)")
-	android.AssertStringListDoesNotContain(t, "do not track if not available for platform",
-		flatlist, "mylib:(minSdkVersion:29)")
-	android.AssertStringListContains(t, "track platform-available lib",
-		flatlist, "yourlib(minSdkVersion:29)")
 }
 
 func TestTrackAllowedDeps_SkipWithoutAllowedDepsTxt(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
-			name: "myapex",
+			name: "com.android.myapex",
 			key: "myapex.key",
 			updatable: true,
 			min_sdk_version: "29",
@@ -2344,19 +2572,23 @@
 			public_key: "testkey.avbpubkey",
 			private_key: "testkey.pem",
 		}
-	`)
-	depsinfo := ctx.SingletonForTests("apex_depsinfo_singleton")
+	`,
+		android.FixtureMergeMockFs(android.MockFS{
+			"system/sepolicy/apex/com.android.myapex-file_contexts": nil,
+		}))
+	depsinfo := ctx.SingletonForTests(t, "apex_depsinfo_singleton")
 	if nil != depsinfo.MaybeRule("generateApexDepsInfoFilesRule").Output {
 		t.Error("apex_depsinfo_singleton shouldn't run when allowed_deps.txt doesn't exist")
 	}
 }
 
 func TestPlatformUsesLatestStubsFromApexes(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
 			key: "myapex.key",
-			native_shared_libs: ["libx"],
+			native_shared_libs: ["libx", "libx_rs"],
 			updatable: false,
 		}
 
@@ -2376,9 +2608,19 @@
 			},
 		}
 
+		rust_ffi {
+			name: "libx_rs",
+			crate_name: "x_rs",
+			srcs: ["x.rs"],
+			apex_available: [ "myapex" ],
+			stubs: {
+				versions: ["1", "2"],
+			},
+		}
+
 		cc_library {
 			name: "libz",
-			shared_libs: ["libx"],
+			shared_libs: ["libx", "libx_rs",],
 			system_shared_libs: [],
 			stl: "none",
 		}
@@ -2386,16 +2628,18 @@
 
 	expectLink := func(from, from_variant, to, to_variant string) {
 		t.Helper()
-		ldArgs := ctx.ModuleForTests(from, "android_arm64_armv8-a_"+from_variant).Rule("ld").Args["libFlags"]
+		ldArgs := ctx.ModuleForTests(t, from, "android_arm64_armv8-a_"+from_variant).Rule("ld").Args["libFlags"]
 		ensureContains(t, ldArgs, "android_arm64_armv8-a_"+to_variant+"/"+to+".so")
 	}
 	expectNoLink := func(from, from_variant, to, to_variant string) {
 		t.Helper()
-		ldArgs := ctx.ModuleForTests(from, "android_arm64_armv8-a_"+from_variant).Rule("ld").Args["libFlags"]
+		ldArgs := ctx.ModuleForTests(t, from, "android_arm64_armv8-a_"+from_variant).Rule("ld").Args["libFlags"]
 		ensureNotContains(t, ldArgs, "android_arm64_armv8-a_"+to_variant+"/"+to+".so")
 	}
 	expectLink("libz", "shared", "libx", "shared_current")
 	expectNoLink("libz", "shared", "libx", "shared_2")
+	expectLink("libz", "shared", "unstripped/libx_rs", "shared_current")
+	expectNoLink("libz", "shared", "unstripped/libx_rs", "shared_2")
 	expectNoLink("libz", "shared", "libz", "shared_1")
 	expectNoLink("libz", "shared", "libz", "shared")
 }
@@ -2407,6 +2651,7 @@
 )
 
 func TestQApexesUseLatestStubsInBundledBuildsAndHWASAN(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -2423,11 +2668,18 @@
 
 		cc_library {
 			name: "libx",
-			shared_libs: ["libbar"],
+			shared_libs: ["libbar", "libbar_rs"],
 			apex_available: [ "myapex" ],
 			min_sdk_version: "29",
 		}
 
+		rust_ffi {
+			name: "libbar_rs",
+			crate_name: "bar_rs",
+			srcs: ["bar.rs"],
+			stubs: { versions: ["29", "30"] },
+		}
+
 		cc_library {
 			name: "libbar",
 			stubs: {
@@ -2438,14 +2690,16 @@
 		prepareForTestWithSantitizeHwaddress,
 	)
 	expectLink := func(from, from_variant, to, to_variant string) {
-		ld := ctx.ModuleForTests(from, "android_arm64_armv8-a_"+from_variant).Rule("ld")
+		ld := ctx.ModuleForTests(t, from, "android_arm64_armv8-a_"+from_variant).Rule("ld")
 		libFlags := ld.Args["libFlags"]
 		ensureContains(t, libFlags, "android_arm64_armv8-a_"+to_variant+"/"+to+".so")
 	}
 	expectLink("libx", "shared_hwasan_apex29", "libbar", "shared_current")
+	expectLink("libx", "shared_hwasan_apex29", "unstripped/libbar_rs", "shared_current")
 }
 
 func TestQTargetApexUsesStaticUnwinder(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -2468,14 +2722,15 @@
 	`)
 
 	// ensure apex variant of c++ is linked with static unwinder
-	cm := ctx.ModuleForTests("libc++", "android_arm64_armv8-a_shared_apex29").Module().(*cc.Module)
+	cm := ctx.ModuleForTests(t, "libc++", "android_arm64_armv8-a_shared_apex29").Module().(*cc.Module)
 	ensureListContains(t, cm.Properties.AndroidMkStaticLibs, "libunwind")
 	// note that platform variant is not.
-	cm = ctx.ModuleForTests("libc++", "android_arm64_armv8-a_shared").Module().(*cc.Module)
+	cm = ctx.ModuleForTests(t, "libc++", "android_arm64_armv8-a_shared").Module().(*cc.Module)
 	ensureListNotContains(t, cm.Properties.AndroidMkStaticLibs, "libunwind")
 }
 
 func TestApexMinSdkVersion_ErrorIfIncompatibleVersion(t *testing.T) {
+	t.Parallel()
 	testApexError(t, `module "mylib".*: should support min_sdk_version\(29\)`, `
 		apex {
 			name: "myapex",
@@ -2548,6 +2803,7 @@
 				"myapex",
 			],
 			min_sdk_version: "30",
+			compile_dex: true,
 		}
 	`)
 
@@ -2575,12 +2831,14 @@
 			// Compile against core API surface
 			sdk_version: "core_current",
 			min_sdk_version: "30",
+			compile_dex: true,
 		}
 	`)
 
 }
 
 func TestApexMinSdkVersion_Okay(t *testing.T) {
+	t.Parallel()
 	testApex(t, `
 		apex {
 			name: "myapex",
@@ -2621,6 +2879,7 @@
 			],
 			apex_available: ["myapex"],
 			min_sdk_version: "29",
+			compile_dex: true,
 		}
 
 		java_library {
@@ -2641,6 +2900,7 @@
 }
 
 func TestApexMinSdkVersion_MinApiForArch(t *testing.T) {
+	t.Parallel()
 	// Tests that an apex dependency with min_sdk_version higher than the
 	// min_sdk_version of the apex is allowed as long as the dependency's
 	// min_sdk_version is less than or equal to the api level that the
@@ -2673,6 +2933,7 @@
 }
 
 func TestJavaStableSdkVersion(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		name          string
 		expectedError string
@@ -2698,6 +2959,7 @@
 					srcs: ["foo/bar/MyClass.java"],
 					sdk_version: "test_current",
 					apex_available: ["myapex"],
+					compile_dex: true,
 				}
 			`,
 		},
@@ -2722,6 +2984,7 @@
 					sdk_version: "current",
 					apex_available: ["myapex"],
 					min_sdk_version: "29",
+					compile_dex: true,
 				}
 			`,
 		},
@@ -2745,6 +3008,7 @@
 					srcs: ["foo/bar/MyClass.java"],
 					sdk_version: "test_current",
 					apex_available: ["myapex"],
+					compile_dex: true,
 				}
 			`,
 		},
@@ -2768,6 +3032,7 @@
 					srcs: ["foo/bar/MyClass.java"],
 					sdk_version: "core_platform",
 					apex_available: ["myapex"],
+					compile_dex: true,
 				}
 			`,
 			preparer: java.FixtureUseLegacyCorePlatformApi("myjar-uses-legacy"),
@@ -2796,6 +3061,7 @@
 					sdk_version: "current",
 					apex_available: ["myapex"],
 					static_libs: ["transitive-jar"],
+					compile_dex: true,
 				}
 				java_library {
 					name: "transitive-jar",
@@ -2812,6 +3078,7 @@
 			continue
 		}
 		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
 			errorHandler := android.FixtureExpectsNoErrors
 			if test.expectedError != "" {
 				errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(test.expectedError)
@@ -2870,6 +3137,7 @@
 }
 
 func TestApexMinSdkVersion_ErrorIfDepIsNewer_Java(t *testing.T) {
+	t.Parallel()
 	testApexError(t, `module "bar".*: should support min_sdk_version\(29\) for "myapex"`, `
 		apex {
 			name: "myapex",
@@ -2906,6 +3174,7 @@
 }
 
 func TestApexMinSdkVersion_OkayEvenWhenDepIsNewer_IfItSatisfiesApexMinSdkVersion(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -2920,8 +3189,7 @@
 			private_key: "testkey.pem",
 		}
 
-		// mylib in myapex will link to mylib2#current
-		// mylib in otherapex will link to mylib2(non-stub) in otherapex as well
+		// mylib will link to mylib2#current
 		cc_library {
 			name: "mylib",
 			srcs: ["mylib.cpp"],
@@ -2950,15 +3218,16 @@
 		}
 	`)
 	expectLink := func(from, from_variant, to, to_variant string) {
-		ld := ctx.ModuleForTests(from, "android_arm64_armv8-a_"+from_variant).Rule("ld")
+		ld := ctx.ModuleForTests(t, from, "android_arm64_armv8-a_"+from_variant).Rule("ld")
 		libFlags := ld.Args["libFlags"]
 		ensureContains(t, libFlags, "android_arm64_armv8-a_"+to_variant+"/"+to+".so")
 	}
 	expectLink("mylib", "shared_apex29", "mylib2", "shared_current")
-	expectLink("mylib", "shared_apex30", "mylib2", "shared_apex30")
+	expectLink("mylib", "shared_apex30", "mylib2", "shared_current")
 }
 
 func TestApexMinSdkVersion_WorksWithSdkCodename(t *testing.T) {
+	t.Parallel()
 	withSAsActiveCodeNames := android.FixtureModifyProductVariables(
 		func(variables android.FixtureProductVariables) {
 			variables.Platform_sdk_codename = proptools.StringPtr("S")
@@ -2991,6 +3260,7 @@
 }
 
 func TestApexMinSdkVersion_WorksWithActiveCodenames(t *testing.T) {
+	t.Parallel()
 	withSAsActiveCodeNames := android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
 		variables.Platform_sdk_codename = proptools.StringPtr("S")
 		variables.Platform_version_active_codenames = []string{"S", "T"}
@@ -3023,12 +3293,13 @@
 	`, withSAsActiveCodeNames)
 
 	// ensure libfoo is linked with current version of libbar stub
-	libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared_apex10000")
+	libfoo := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_shared_apex10000")
 	libFlags := libfoo.Rule("ld").Args["libFlags"]
 	ensureContains(t, libFlags, "android_arm64_armv8-a_shared_current/libbar.so")
 }
 
 func TestFilesInSubDir(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -3078,7 +3349,7 @@
 		}
 	`)
 
-	generateFsRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("generateFsConfig")
+	generateFsRule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("generateFsConfig")
 	cmd := generateFsRule.RuleParams.Command
 
 	// Ensure that the subdirectories are all listed
@@ -3098,6 +3369,7 @@
 }
 
 func TestFilesInSubDirWhenNativeBridgeEnabled(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -3142,7 +3414,7 @@
 				},
 			},
 		}
-	`, withNativeBridgeEnabled)
+	`, android.PrepareForNativeBridgeEnabled)
 	ensureExactContents(t, ctx, "myapex", "android_common_myapex", []string{
 		"bin/foo/bar/mybin",
 		"bin/foo/bar/mybin64",
@@ -3156,6 +3428,7 @@
 }
 
 func TestVendorApex(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForApexTest,
 		android.FixtureModifyConfig(android.SetKatiEnabledForTests),
@@ -3190,7 +3463,7 @@
 		"lib64/libc++.so",
 	})
 
-	apexBundle := result.ModuleForTests("myapex", "android_common_myapex").Module().(*apexBundle)
+	apexBundle := result.ModuleForTests(t, "myapex", "android_common_myapex").Module().(*apexBundle)
 	data := android.AndroidMkDataForTest(t, result.TestContext, apexBundle)
 	name := apexBundle.BaseModuleName()
 	prefix := "TARGET_"
@@ -3200,12 +3473,13 @@
 	installPath := "out/target/product/test_device/vendor/apex"
 	ensureContains(t, androidMk, "LOCAL_MODULE_PATH := "+installPath)
 
-	apexManifestRule := result.ModuleForTests("myapex", "android_common_myapex").Rule("apexManifestRule")
+	apexManifestRule := result.ModuleForTests(t, "myapex", "android_common_myapex").Rule("apexManifestRule")
 	requireNativeLibs := names(apexManifestRule.Args["requireNativeLibs"])
 	ensureListNotContains(t, requireNativeLibs, ":vndk")
 }
 
 func TestProductVariant(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -3230,7 +3504,7 @@
 	`)
 
 	cflags := strings.Fields(
-		ctx.ModuleForTests("foo", "android_product_arm64_armv8-a_apex10000").Rule("cc").Args["cFlags"])
+		ctx.ModuleForTests(t, "foo", "android_product_arm64_armv8-a_apex10000").Rule("cc").Args["cFlags"])
 	ensureListContains(t, cflags, "-D__ANDROID_VNDK__")
 	ensureListContains(t, cflags, "-D__ANDROID_APEX__")
 	ensureListContains(t, cflags, "-D__ANDROID_PRODUCT__")
@@ -3238,6 +3512,7 @@
 }
 
 func TestApex_withPrebuiltFirmware(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		name           string
 		additionalProp string
@@ -3247,6 +3522,7 @@
 	}
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
 			ctx := testApex(t, `
 				apex {
 					name: "myapex",
@@ -3275,6 +3551,7 @@
 }
 
 func TestAndroidMk_VendorApexRequired(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -3296,7 +3573,7 @@
 		}
 	`)
 
-	apexBundle := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*apexBundle)
+	apexBundle := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Module().(*apexBundle)
 	data := android.AndroidMkDataForTest(t, ctx, apexBundle)
 	name := apexBundle.BaseModuleName()
 	prefix := "TARGET_"
@@ -3325,7 +3602,7 @@
 		}
 	`)
 
-	apexBundle := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*apexBundle)
+	apexBundle := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Module().(*apexBundle)
 	data := android.AndroidMkDataForTest(t, ctx, apexBundle)
 	name := apexBundle.BaseModuleName()
 	prefix := "TARGET_"
@@ -3337,6 +3614,7 @@
 }
 
 func TestStaticLinking(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -3362,23 +3640,35 @@
 			apex_available: ["myapex"],
 		}
 
+		rust_ffi {
+			name: "libmylib_rs",
+			crate_name: "mylib_rs",
+			srcs: ["mylib.rs"],
+			stubs: {
+				versions: ["1", "2", "3"],
+			},
+			apex_available: ["myapex"],
+		}
+
 		cc_binary {
 			name: "not_in_apex",
 			srcs: ["mylib.cpp"],
-			static_libs: ["mylib"],
+			static_libs: ["mylib", "libmylib_rs"],
 			static_executable: true,
 			system_shared_libs: [],
 			stl: "none",
 		}
 	`)
 
-	ldFlags := ctx.ModuleForTests("not_in_apex", "android_arm64_armv8-a").Rule("ld").Args["libFlags"]
+	ldFlags := ctx.ModuleForTests(t, "not_in_apex", "android_arm64_armv8-a").Rule("ld").Args["libFlags"]
 
 	// Ensure that not_in_apex is linking with the static variant of mylib
 	ensureContains(t, ldFlags, "mylib/android_arm64_armv8-a_static/mylib.a")
+	ensureContains(t, ldFlags, "generated_rust_staticlib/librustlibs.a")
 }
 
 func TestKeys(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex_keytest",
@@ -3416,7 +3706,7 @@
 	`)
 
 	// check the APEX keys
-	keys := ctx.ModuleForTests("myapex.key", "android_common").Module().(*apexKey)
+	keys := ctx.ModuleForTests(t, "myapex.key", "android_common").Module().(*apexKey)
 
 	if keys.publicKeyFile.String() != "vendor/foo/devkeys/testkey.avbpubkey" {
 		t.Errorf("public key %q is not %q", keys.publicKeyFile.String(),
@@ -3428,7 +3718,7 @@
 	}
 
 	// check the APK certs. It should be overridden to myapex.certificate.override
-	certs := ctx.ModuleForTests("myapex_keytest", "android_common_myapex_keytest").Rule("signapk").Args["certificates"]
+	certs := ctx.ModuleForTests(t, "myapex_keytest", "android_common_myapex_keytest").Rule("signapk").Args["certificates"]
 	if certs != "testkey.override.x509.pem testkey.override.pk8" {
 		t.Errorf("cert and private key %q are not %q", certs,
 			"testkey.override.509.pem testkey.override.pk8")
@@ -3436,7 +3726,9 @@
 }
 
 func TestCertificate(t *testing.T) {
+	t.Parallel()
 	t.Run("if unspecified, it defaults to DefaultAppCertificate", func(t *testing.T) {
+		t.Parallel()
 		ctx := testApex(t, `
 			apex {
 				name: "myapex",
@@ -3448,13 +3740,14 @@
 				public_key: "testkey.avbpubkey",
 				private_key: "testkey.pem",
 			}`)
-		rule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("signapk")
+		rule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("signapk")
 		expected := "vendor/foo/devkeys/test.x509.pem vendor/foo/devkeys/test.pk8"
 		if actual := rule.Args["certificates"]; actual != expected {
 			t.Errorf("certificates should be %q, not %q", expected, actual)
 		}
 	})
 	t.Run("override when unspecified", func(t *testing.T) {
+		t.Parallel()
 		ctx := testApex(t, `
 			apex {
 				name: "myapex_keytest",
@@ -3471,13 +3764,14 @@
 				name: "myapex.certificate.override",
 				certificate: "testkey.override",
 			}`)
-		rule := ctx.ModuleForTests("myapex_keytest", "android_common_myapex_keytest").Rule("signapk")
+		rule := ctx.ModuleForTests(t, "myapex_keytest", "android_common_myapex_keytest").Rule("signapk")
 		expected := "testkey.override.x509.pem testkey.override.pk8"
 		if actual := rule.Args["certificates"]; actual != expected {
 			t.Errorf("certificates should be %q, not %q", expected, actual)
 		}
 	})
 	t.Run("if specified as :module, it respects the prop", func(t *testing.T) {
+		t.Parallel()
 		ctx := testApex(t, `
 			apex {
 				name: "myapex",
@@ -3494,13 +3788,14 @@
 				name: "myapex.certificate",
 				certificate: "testkey",
 			}`)
-		rule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("signapk")
+		rule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("signapk")
 		expected := "testkey.x509.pem testkey.pk8"
 		if actual := rule.Args["certificates"]; actual != expected {
 			t.Errorf("certificates should be %q, not %q", expected, actual)
 		}
 	})
 	t.Run("override when specifiec as <:module>", func(t *testing.T) {
+		t.Parallel()
 		ctx := testApex(t, `
 			apex {
 				name: "myapex_keytest",
@@ -3518,13 +3813,14 @@
 				name: "myapex.certificate.override",
 				certificate: "testkey.override",
 			}`)
-		rule := ctx.ModuleForTests("myapex_keytest", "android_common_myapex_keytest").Rule("signapk")
+		rule := ctx.ModuleForTests(t, "myapex_keytest", "android_common_myapex_keytest").Rule("signapk")
 		expected := "testkey.override.x509.pem testkey.override.pk8"
 		if actual := rule.Args["certificates"]; actual != expected {
 			t.Errorf("certificates should be %q, not %q", expected, actual)
 		}
 	})
 	t.Run("if specified as name, finds it from DefaultDevKeyDir", func(t *testing.T) {
+		t.Parallel()
 		ctx := testApex(t, `
 			apex {
 				name: "myapex",
@@ -3536,14 +3832,19 @@
 				name: "myapex.key",
 				public_key: "testkey.avbpubkey",
 				private_key: "testkey.pem",
-			}`)
-		rule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("signapk")
+			}`,
+			android.MockFS{
+				"vendor/foo/devkeys/testkey.x509.pem": nil,
+			}.AddToFixture(),
+		)
+		rule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("signapk")
 		expected := "vendor/foo/devkeys/testkey.x509.pem vendor/foo/devkeys/testkey.pk8"
 		if actual := rule.Args["certificates"]; actual != expected {
 			t.Errorf("certificates should be %q, not %q", expected, actual)
 		}
 	})
 	t.Run("override when specified as <name>", func(t *testing.T) {
+		t.Parallel()
 		ctx := testApex(t, `
 			apex {
 				name: "myapex_keytest",
@@ -3561,7 +3862,7 @@
 				name: "myapex.certificate.override",
 				certificate: "testkey.override",
 			}`)
-		rule := ctx.ModuleForTests("myapex_keytest", "android_common_myapex_keytest").Rule("signapk")
+		rule := ctx.ModuleForTests(t, "myapex_keytest", "android_common_myapex_keytest").Rule("signapk")
 		expected := "testkey.override.x509.pem testkey.override.pk8"
 		if actual := rule.Args["certificates"]; actual != expected {
 			t.Errorf("certificates should be %q, not %q", expected, actual)
@@ -3570,6 +3871,7 @@
 }
 
 func TestMacro(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -3631,38 +3933,39 @@
 	`)
 
 	// non-APEX variant does not have __ANDROID_APEX__ defined
-	mylibCFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
+	mylibCFlags := ctx.ModuleForTests(t, "mylib", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX__")
 
 	// APEX variant has __ANDROID_APEX__ and __ANDROID_APEX__ defined
-	mylibCFlags = ctx.ModuleForTests("mylib", "android_arm64_armv8-a_static_apex10000").Rule("cc").Args["cFlags"]
+	mylibCFlags = ctx.ModuleForTests(t, "mylib", "android_arm64_armv8-a_static_apex10000").Rule("cc").Args["cFlags"]
 	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX__")
 
 	// APEX variant has __ANDROID_APEX__ and __ANDROID_APEX__ defined
-	mylibCFlags = ctx.ModuleForTests("mylib", "android_arm64_armv8-a_static_apex29").Rule("cc").Args["cFlags"]
+	mylibCFlags = ctx.ModuleForTests(t, "mylib", "android_arm64_armv8-a_static_apex29").Rule("cc").Args["cFlags"]
 	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX__")
 
 	// When a cc_library sets use_apex_name_macro: true each apex gets a unique variant and
 	// each variant defines additional macros to distinguish which apex variant it is built for
 
 	// non-APEX variant does not have __ANDROID_APEX__ defined
-	mylibCFlags = ctx.ModuleForTests("mylib3", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
+	mylibCFlags = ctx.ModuleForTests(t, "mylib3", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX__")
 
 	// recovery variant does not set __ANDROID_APEX__
-	mylibCFlags = ctx.ModuleForTests("mylib3", "android_recovery_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
+	mylibCFlags = ctx.ModuleForTests(t, "mylib3", "android_recovery_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX__")
 
 	// non-APEX variant does not have __ANDROID_APEX__ defined
-	mylibCFlags = ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
+	mylibCFlags = ctx.ModuleForTests(t, "mylib2", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX__")
 
 	// recovery variant does not set __ANDROID_APEX__
-	mylibCFlags = ctx.ModuleForTests("mylib2", "android_recovery_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
+	mylibCFlags = ctx.ModuleForTests(t, "mylib2", "android_recovery_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX__")
 }
 
 func TestHeaderLibsDependency(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -3707,7 +4010,7 @@
 		}
 	`)
 
-	cFlags := ctx.ModuleForTests("otherlib", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
+	cFlags := ctx.ModuleForTests(t, "otherlib", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 
 	// Ensure that the include path of the header lib is exported to 'otherlib'
 	ensureContains(t, cFlags, "-Imy_include")
@@ -3739,7 +4042,7 @@
 
 func getFiles(t *testing.T, ctx *android.TestContext, moduleName, variant string) []fileInApex {
 	t.Helper()
-	module := ctx.ModuleForTests(moduleName, variant)
+	module := ctx.ModuleForTests(t, moduleName, variant)
 	apexRule := module.MaybeRule("apexRule")
 	apexDir := "/image.apex/"
 	copyCmds := apexRule.Args["copy_commands"]
@@ -3830,7 +4133,7 @@
 }
 
 func ensureExactDeapexedContents(t *testing.T, ctx *android.TestContext, moduleName string, variant string, files []string) {
-	deapexer := ctx.ModuleForTests(moduleName, variant).Description("deapex")
+	deapexer := ctx.ModuleForTests(t, moduleName, variant).Description("deapex")
 	outputs := make([]string, 0, len(deapexer.ImplicitOutputs)+1)
 	if deapexer.Output != nil {
 		outputs = append(outputs, deapexer.Output.String())
@@ -3866,6 +4169,7 @@
 }
 
 func TestVndkApexVersion(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex_vndk {
 			name: "com.android.vndk.v27",
@@ -3935,6 +4239,7 @@
 }
 
 func TestVndkApexNameRule(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex_vndk {
 			name: "com.android.vndk.v29",
@@ -3957,7 +4262,7 @@
 		}`+vndkLibrariesTxtFiles("28", "29"))
 
 	assertApexName := func(expected, moduleName string) {
-		module := ctx.ModuleForTests(moduleName, "android_common")
+		module := ctx.ModuleForTests(t, moduleName, "android_common")
 		apexManifestRule := module.Rule("apexManifestRule")
 		ensureContains(t, apexManifestRule.Args["opt"], "-v name "+expected)
 	}
@@ -3967,6 +4272,7 @@
 }
 
 func TestVndkApexDoesntSupportNativeBridgeSupported(t *testing.T) {
+	t.Parallel()
 	testApexError(t, `module "com.android.vndk.v30" .*: native_bridge_supported: .* doesn't support native bridge binary`, `
 		apex_vndk {
 			name: "com.android.vndk.v30",
@@ -3997,6 +4303,7 @@
 }
 
 func TestVndkApexWithBinder32(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex_vndk {
 			name: "com.android.vndk.v27",
@@ -4051,11 +4358,20 @@
 			"libvndk27binder32.so": nil,
 		}),
 		withBinder32bit,
-		withTargets(map[android.OsType][]android.Target{
-			android.Android: {
-				{Os: android.Android, Arch: android.Arch{ArchType: android.Arm, ArchVariant: "armv7-a-neon", Abi: []string{"armeabi-v7a"}},
-					NativeBridge: android.NativeBridgeDisabled, NativeBridgeHostArchName: "", NativeBridgeRelativePath: ""},
-			},
+		android.FixtureModifyConfig(func(config android.Config) {
+			target := android.Target{
+				Os: android.Android,
+				Arch: android.Arch{
+					ArchType:    android.Arm,
+					ArchVariant: "armv7-a-neon",
+					Abi:         []string{"armeabi-v7a"},
+				},
+				NativeBridge:             android.NativeBridgeDisabled,
+				NativeBridgeHostArchName: "",
+				NativeBridgeRelativePath: "",
+			}
+			config.Targets[android.Android] = []android.Target{target}
+			config.AndroidFirstDeviceTarget = target
 		}),
 	)
 
@@ -4066,6 +4382,7 @@
 }
 
 func TestDependenciesInApexManifest(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex_nodep",
@@ -4173,25 +4490,25 @@
 	var apexManifestRule android.TestingBuildParams
 	var provideNativeLibs, requireNativeLibs []string
 
-	apexManifestRule = ctx.ModuleForTests("myapex_nodep", "android_common_myapex_nodep").Rule("apexManifestRule")
+	apexManifestRule = ctx.ModuleForTests(t, "myapex_nodep", "android_common_myapex_nodep").Rule("apexManifestRule")
 	provideNativeLibs = names(apexManifestRule.Args["provideNativeLibs"])
 	requireNativeLibs = names(apexManifestRule.Args["requireNativeLibs"])
 	ensureListEmpty(t, provideNativeLibs)
 	ensureListEmpty(t, requireNativeLibs)
 
-	apexManifestRule = ctx.ModuleForTests("myapex_dep", "android_common_myapex_dep").Rule("apexManifestRule")
+	apexManifestRule = ctx.ModuleForTests(t, "myapex_dep", "android_common_myapex_dep").Rule("apexManifestRule")
 	provideNativeLibs = names(apexManifestRule.Args["provideNativeLibs"])
 	requireNativeLibs = names(apexManifestRule.Args["requireNativeLibs"])
 	ensureListEmpty(t, provideNativeLibs)
 	ensureListContains(t, requireNativeLibs, "libfoo.so")
 
-	apexManifestRule = ctx.ModuleForTests("myapex_provider", "android_common_myapex_provider").Rule("apexManifestRule")
+	apexManifestRule = ctx.ModuleForTests(t, "myapex_provider", "android_common_myapex_provider").Rule("apexManifestRule")
 	provideNativeLibs = names(apexManifestRule.Args["provideNativeLibs"])
 	requireNativeLibs = names(apexManifestRule.Args["requireNativeLibs"])
 	ensureListContains(t, provideNativeLibs, "libfoo.so")
 	ensureListEmpty(t, requireNativeLibs)
 
-	apexManifestRule = ctx.ModuleForTests("myapex_selfcontained", "android_common_myapex_selfcontained").Rule("apexManifestRule")
+	apexManifestRule = ctx.ModuleForTests(t, "myapex_selfcontained", "android_common_myapex_selfcontained").Rule("apexManifestRule")
 	provideNativeLibs = names(apexManifestRule.Args["provideNativeLibs"])
 	requireNativeLibs = names(apexManifestRule.Args["requireNativeLibs"])
 	ensureListContains(t, provideNativeLibs, "libbar.so")
@@ -4199,6 +4516,7 @@
 }
 
 func TestOverrideApexManifestDefaultVersion(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -4227,12 +4545,13 @@
 		"OVERRIDE_APEX_MANIFEST_DEFAULT_VERSION": "1234",
 	}))
 
-	module := ctx.ModuleForTests("myapex", "android_common_myapex")
+	module := ctx.ModuleForTests(t, "myapex", "android_common_myapex")
 	apexManifestRule := module.Rule("apexManifestRule")
 	ensureContains(t, apexManifestRule.Args["default_version"], "1234")
 }
 
 func TestCompileMultilibProp(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		compileMultiLibProp string
 		containedLibs       []string
@@ -4290,7 +4609,7 @@
 			}
 		`, testCase.compileMultiLibProp),
 		)
-		module := ctx.ModuleForTests("myapex", "android_common_myapex")
+		module := ctx.ModuleForTests(t, "myapex", "android_common_myapex")
 		apexRule := module.Rule("apexRule")
 		copyCmds := apexRule.Args["copy_commands"]
 		for _, containedLib := range testCase.containedLibs {
@@ -4303,6 +4622,7 @@
 }
 
 func TestNonTestApex(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -4329,7 +4649,7 @@
 		}
 	`)
 
-	module := ctx.ModuleForTests("myapex", "android_common_myapex")
+	module := ctx.ModuleForTests(t, "myapex", "android_common_myapex")
 	apexRule := module.Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
@@ -4349,13 +4669,14 @@
 	// Ensure that the platform variant ends with _shared
 	ensureListContains(t, ctx.ModuleVariantsForTests("mylib_common"), "android_arm64_armv8-a_shared")
 
-	if !ctx.ModuleForTests("mylib_common", "android_arm64_armv8-a_shared_apex10000").Module().(*cc.Module).InAnyApex() {
+	if !ctx.ModuleForTests(t, "mylib_common", "android_arm64_armv8-a_shared_apex10000").Module().(*cc.Module).InAnyApex() {
 		t.Log("Found mylib_common not in any apex!")
 		t.Fail()
 	}
 }
 
 func TestTestApex(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex_test {
 			name: "myapex",
@@ -4383,7 +4704,7 @@
 		}
 	`)
 
-	module := ctx.ModuleForTests("myapex", "android_common_myapex")
+	module := ctx.ModuleForTests(t, "myapex", "android_common_myapex")
 	apexRule := module.Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
@@ -4405,6 +4726,7 @@
 }
 
 func TestLibzVendorIsntStable(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 	apex {
 		name: "myapex",
@@ -4456,7 +4778,7 @@
 		ensureExactContents(t, ctx, "myapex", "android_common_myapex", []string{
 			"bin/mybin",
 		})
-		apexManifestRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexManifestRule")
+		apexManifestRule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("apexManifestRule")
 		android.AssertStringEquals(t, "should require libz", apexManifestRule.Args["requireNativeLibs"], "libz.so")
 	}
 	// libz doesn't provide stubs for vendor variant.
@@ -4465,12 +4787,13 @@
 			"bin/mybin",
 			"lib64/libz.so",
 		})
-		apexManifestRule := ctx.ModuleForTests("myvendorapex", "android_common_myvendorapex").Rule("apexManifestRule")
+		apexManifestRule := ctx.ModuleForTests(t, "myvendorapex", "android_common_myvendorapex").Rule("apexManifestRule")
 		android.AssertStringEquals(t, "should not require libz", apexManifestRule.Args["requireNativeLibs"], "")
 	}
 }
 
 func TestApexWithTarget(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -4539,7 +4862,7 @@
 		}
 	`)
 
-	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexRule")
+	apexRule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
 	// Ensure that main rule creates an output
@@ -4562,6 +4885,7 @@
 }
 
 func TestApexWithArch(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -4623,7 +4947,7 @@
 		}
 	`)
 
-	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexRule")
+	apexRule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
 	// Ensure that apex variant is created for the direct dep
@@ -4637,6 +4961,7 @@
 }
 
 func TestApexWithShBinary(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -4660,13 +4985,14 @@
 		}
 	`)
 
-	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexRule")
+	apexRule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
 	ensureContains(t, copyCmds, "image.apex/bin/script/myscript.sh")
 }
 
 func TestApexInVariousPartition(t *testing.T) {
+	t.Parallel()
 	testcases := []struct {
 		propName, partition string
 	}{
@@ -4679,6 +5005,7 @@
 	}
 	for _, tc := range testcases {
 		t.Run(tc.propName+":"+tc.partition, func(t *testing.T) {
+			t.Parallel()
 			ctx := testApex(t, `
 				apex {
 					name: "myapex",
@@ -4694,8 +5021,8 @@
 				}
 			`)
 
-			apex := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*apexBundle)
-			expected := "out/soong/target/product/test_device/" + tc.partition + "/apex"
+			apex := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Module().(*apexBundle)
+			expected := "out/target/product/test_device/" + tc.partition + "/apex"
 			actual := apex.installDir.RelativeToTop().String()
 			if actual != expected {
 				t.Errorf("wrong install path. expected %q. actual %q", expected, actual)
@@ -4705,6 +5032,7 @@
 }
 
 func TestFileContexts_FindInDefaultLocationIfNotSet(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -4718,12 +5046,13 @@
 			private_key: "testkey.pem",
 		}
 	`)
-	module := ctx.ModuleForTests("myapex", "android_common_myapex")
+	module := ctx.ModuleForTests(t, "myapex", "android_common_myapex")
 	rule := module.Output("file_contexts")
 	ensureContains(t, rule.RuleParams.Command, "cat system/sepolicy/apex/myapex-file_contexts")
 }
 
 func TestFileContexts_ShouldBeUnderSystemSepolicyForSystemApexes(t *testing.T) {
+	t.Parallel()
 	testApexError(t, `"myapex" .*: file_contexts: should be under system/sepolicy`, `
 		apex {
 			name: "myapex",
@@ -4743,6 +5072,7 @@
 }
 
 func TestFileContexts_ProductSpecificApexes(t *testing.T) {
+	t.Parallel()
 	testApexError(t, `"myapex" .*: file_contexts: cannot find`, `
 		apex {
 			name: "myapex",
@@ -4776,12 +5106,13 @@
 	`, withFiles(map[string][]byte{
 		"product_specific_file_contexts": nil,
 	}))
-	module := ctx.ModuleForTests("myapex", "android_common_myapex")
+	module := ctx.ModuleForTests(t, "myapex", "android_common_myapex")
 	rule := module.Output("file_contexts")
 	ensureContains(t, rule.RuleParams.Command, "cat product_specific_file_contexts")
 }
 
 func TestFileContexts_SetViaFileGroup(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -4804,12 +5135,13 @@
 	`, withFiles(map[string][]byte{
 		"product_specific_file_contexts": nil,
 	}))
-	module := ctx.ModuleForTests("myapex", "android_common_myapex")
+	module := ctx.ModuleForTests(t, "myapex", "android_common_myapex")
 	rule := module.Output("file_contexts")
 	ensureContains(t, rule.RuleParams.Command, "cat product_specific_file_contexts")
 }
 
 func TestApexKeyFromOtherModule(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex_key {
 			name: "myapex.key",
@@ -4829,7 +5161,7 @@
 		}
 	`)
 
-	apex_key := ctx.ModuleForTests("myapex.key", "android_common").Module().(*apexKey)
+	apex_key := ctx.ModuleForTests(t, "myapex.key", "android_common").Module().(*apexKey)
 	expected_pubkey := "testkey2.avbpubkey"
 	actual_pubkey := apex_key.publicKeyFile.String()
 	if actual_pubkey != expected_pubkey {
@@ -4843,6 +5175,7 @@
 }
 
 func TestPrebuilt(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		prebuilt_apex {
 			name: "myapex",
@@ -4857,7 +5190,7 @@
 		}
 	`)
 
-	testingModule := ctx.ModuleForTests("myapex", "android_common_myapex")
+	testingModule := ctx.ModuleForTests(t, "myapex", "android_common_prebuilt_myapex")
 	prebuilt := testingModule.Module().(*Prebuilt)
 
 	expectedInput := "myapex-arm64.apex"
@@ -4877,7 +5210,8 @@
 }
 
 func TestPrebuiltMissingSrc(t *testing.T) {
-	testApexError(t, `module "myapex" variant "android_common_myapex".*: prebuilt_apex does not support "arm64_armv8-a"`, `
+	t.Parallel()
+	testApexError(t, `module "myapex" variant "android_common_prebuilt_myapex".*: prebuilt_apex does not support "arm64_armv8-a"`, `
 		prebuilt_apex {
 			name: "myapex",
 		}
@@ -4885,6 +5219,7 @@
 }
 
 func TestPrebuiltFilenameOverride(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		prebuilt_apex {
 			name: "myapex",
@@ -4893,7 +5228,7 @@
 		}
 	`)
 
-	testingModule := ctx.ModuleForTests("myapex", "android_common_myapex")
+	testingModule := ctx.ModuleForTests(t, "myapex", "android_common_prebuilt_myapex")
 	p := testingModule.Module().(*Prebuilt)
 
 	expected := "notmyapex.apex"
@@ -4908,6 +5243,7 @@
 }
 
 func TestApexSetFilenameOverride(t *testing.T) {
+	t.Parallel()
 	testApex(t, `
 		apex_set {
  			name: "com.company.android.myapex",
@@ -4915,7 +5251,7 @@
 			set: "company-myapex.apks",
       filename: "com.company.android.myapex.apex"
 		}
-	`).ModuleForTests("com.company.android.myapex", "android_common_com.android.myapex")
+	`).ModuleForTests(t, "com.company.android.myapex", "android_common_prebuilt_com.android.myapex")
 
 	testApex(t, `
 		apex_set {
@@ -4924,7 +5260,7 @@
 			set: "company-myapex.apks",
       filename: "com.company.android.myapex.capex"
 		}
-	`).ModuleForTests("com.company.android.myapex", "android_common_com.android.myapex")
+	`).ModuleForTests(t, "com.company.android.myapex", "android_common_prebuilt_com.android.myapex")
 
 	testApexError(t, `filename should end in .apex or .capex for apex_set`, `
 		apex_set {
@@ -4937,6 +5273,7 @@
 }
 
 func TestPrebuiltOverrides(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		prebuilt_apex {
 			name: "myapex.prebuilt",
@@ -4947,7 +5284,7 @@
 		}
 	`)
 
-	testingModule := ctx.ModuleForTests("myapex.prebuilt", "android_common_myapex.prebuilt")
+	testingModule := ctx.ModuleForTests(t, "myapex.prebuilt", "android_common_prebuilt_myapex.prebuilt")
 	p := testingModule.Module().(*Prebuilt)
 
 	expected := []string{"myapex"}
@@ -4963,13 +5300,14 @@
 }
 
 func TestPrebuiltApexName(t *testing.T) {
+	t.Parallel()
 	testApex(t, `
 		prebuilt_apex {
 			name: "com.company.android.myapex",
 			apex_name: "com.android.myapex",
 			src: "company-myapex-arm.apex",
 		}
-	`).ModuleForTests("com.company.android.myapex", "android_common_com.android.myapex")
+	`).ModuleForTests(t, "com.company.android.myapex", "android_common_prebuilt_com.android.myapex")
 
 	testApex(t, `
 		apex_set {
@@ -4977,10 +5315,11 @@
 			apex_name: "com.android.myapex",
 			set: "company-myapex.apks",
 		}
-	`).ModuleForTests("com.company.android.myapex", "android_common_com.android.myapex")
+	`).ModuleForTests(t, "com.company.android.myapex", "android_common_prebuilt_com.android.myapex")
 }
 
 func TestPrebuiltApexNameWithPlatformBootclasspath(t *testing.T) {
+	t.Parallel()
 	_ = android.GroupFixturePreparers(
 		java.PrepareForTestWithJavaDefaultModules,
 		PrepareForTestWithApexBuildComponents,
@@ -5002,6 +5341,12 @@
 				exported_bootclasspath_fragments: ["art-bootclasspath-fragment"],
 			}
 
+			prebuilt_apex {
+				name: "com.android.art",
+				src: "com.android.art-arm.apex",
+				exported_bootclasspath_fragments: ["art-bootclasspath-fragment"],
+			}
+
 			prebuilt_bootclasspath_fragment {
 				name: "art-bootclasspath-fragment",
 				image_name: "art",
@@ -5031,6 +5376,7 @@
 }
 
 func TestBootDexJarsFromSourcesAndPrebuilts(t *testing.T) {
+	t.Parallel()
 	preparer := android.GroupFixturePreparers(
 		java.FixtureConfigureApexBootJars("myapex:libfoo", "myapex:libbar"),
 		// Make sure that the frameworks/base/Android.bp file exists as otherwise hidden API encoding
@@ -5054,7 +5400,7 @@
 
 	checkHiddenAPIIndexFromClassesInputs := func(t *testing.T, ctx *android.TestContext, expectedIntermediateInputs string) {
 		t.Helper()
-		platformBootclasspath := ctx.ModuleForTests("platform-bootclasspath", "android_common")
+		platformBootclasspath := ctx.ModuleForTests(t, "platform-bootclasspath", "android_common")
 		var rule android.TestingBuildParams
 
 		rule = platformBootclasspath.Output("hiddenapi-monolithic/index-from-classes.csv")
@@ -5063,7 +5409,7 @@
 
 	checkHiddenAPIIndexFromFlagsInputs := func(t *testing.T, ctx *android.TestContext, expectedIntermediateInputs string) {
 		t.Helper()
-		platformBootclasspath := ctx.ModuleForTests("platform-bootclasspath", "android_common")
+		platformBootclasspath := ctx.ModuleForTests(t, "platform-bootclasspath", "android_common")
 		var rule android.TestingBuildParams
 
 		rule = platformBootclasspath.Output("hiddenapi-index.csv")
@@ -5076,6 +5422,7 @@
 	}
 
 	t.Run("prebuilt only", func(t *testing.T) {
+		t.Parallel()
 		bp := `
 		prebuilt_apex {
 			name: "myapex",
@@ -5132,11 +5479,12 @@
 		checkHiddenAPIIndexFromFlagsInputs(t, ctx, `
 			my-bootclasspath-fragment/index.csv
 			out/soong/.intermediates/frameworks/base/boot/platform-bootclasspath/android_common/hiddenapi-monolithic/index-from-classes.csv
-			out/soong/.intermediates/packages/modules/com.android.art/art-bootclasspath-fragment/android_common_apex10000/modular-hiddenapi/index.csv
+			out/soong/.intermediates/packages/modules/com.android.art/art-bootclasspath-fragment/android_common_com.android.art/modular-hiddenapi/index.csv
 		`)
 	})
 
 	t.Run("apex_set only", func(t *testing.T) {
+		t.Parallel()
 		bp := `
 		apex_set {
 			name: "myapex",
@@ -5204,10 +5552,10 @@
 		checkHiddenAPIIndexFromFlagsInputs(t, ctx, `
 			my-bootclasspath-fragment/index.csv
 			out/soong/.intermediates/frameworks/base/boot/platform-bootclasspath/android_common/hiddenapi-monolithic/index-from-classes.csv
-			out/soong/.intermediates/packages/modules/com.android.art/art-bootclasspath-fragment/android_common_apex10000/modular-hiddenapi/index.csv
+			out/soong/.intermediates/packages/modules/com.android.art/art-bootclasspath-fragment/android_common_com.android.art/modular-hiddenapi/index.csv
 		`)
 
-		myApex := ctx.ModuleForTests("myapex", "android_common_myapex").Module()
+		myApex := ctx.ModuleForTests(t, "myapex", "android_common_prebuilt_myapex").Module()
 
 		overrideNames := []string{
 			"",
@@ -5227,6 +5575,7 @@
 	})
 
 	t.Run("prebuilt with source library preferred", func(t *testing.T) {
+		t.Parallel()
 		bp := `
 		prebuilt_apex {
 			name: "myapex",
@@ -5290,7 +5639,7 @@
 		// prebuilt_apex module always depends on the prebuilt, and so it doesn't
 		// find the dex boot jar in it. We either need to disable the source libfoo
 		// or make the prebuilt libfoo preferred.
-		testDexpreoptWithApexes(t, bp, "module libfoo does not provide a dex boot jar", preparer, fragment)
+		testDexpreoptWithApexes(t, bp, `module "platform-bootclasspath" variant ".*": module libfoo{.*} does not provide a dex jar`, preparer, fragment)
 		// dexbootjar check is skipped if AllowMissingDependencies is true
 		preparerAllowMissingDeps := android.GroupFixturePreparers(
 			preparer,
@@ -5300,6 +5649,7 @@
 	})
 
 	t.Run("prebuilt library preferred with source", func(t *testing.T) {
+		t.Parallel()
 		bp := `
 		apex {
 			name: "myapex",
@@ -5325,6 +5675,7 @@
 
 		prebuilt_apex {
 			name: "myapex",
+			prefer: true,
 			arch: {
 				arm64: {
 					src: "myapex-arm64.apex",
@@ -5397,11 +5748,12 @@
 		checkHiddenAPIIndexFromFlagsInputs(t, ctx, `
 			my-bootclasspath-fragment/index.csv
 			out/soong/.intermediates/frameworks/base/boot/platform-bootclasspath/android_common/hiddenapi-monolithic/index-from-classes.csv
-			out/soong/.intermediates/packages/modules/com.android.art/art-bootclasspath-fragment/android_common_apex10000/modular-hiddenapi/index.csv
+			out/soong/.intermediates/packages/modules/com.android.art/art-bootclasspath-fragment/android_common_com.android.art/modular-hiddenapi/index.csv
 		`)
 	})
 
 	t.Run("prebuilt with source apex preferred", func(t *testing.T) {
+		t.Parallel()
 		bp := `
 		apex {
 			name: "myapex",
@@ -5495,11 +5847,12 @@
 		checkHiddenAPIIndexFromFlagsInputs(t, ctx, `
 			out/soong/.intermediates/frameworks/base/boot/platform-bootclasspath/android_common/hiddenapi-monolithic/index-from-classes.csv
 			out/soong/.intermediates/my-bootclasspath-fragment/android_common_myapex/modular-hiddenapi/index.csv
-			out/soong/.intermediates/packages/modules/com.android.art/art-bootclasspath-fragment/android_common_apex10000/modular-hiddenapi/index.csv
+			out/soong/.intermediates/packages/modules/com.android.art/art-bootclasspath-fragment/android_common_com.android.art/modular-hiddenapi/index.csv
 		`)
 	})
 
 	t.Run("prebuilt preferred with source apex disabled", func(t *testing.T) {
+		t.Parallel()
 		bp := `
 		apex {
 			name: "myapex",
@@ -5606,11 +5959,12 @@
 		checkHiddenAPIIndexFromFlagsInputs(t, ctx, `
 			my-bootclasspath-fragment/index.csv
 			out/soong/.intermediates/frameworks/base/boot/platform-bootclasspath/android_common/hiddenapi-monolithic/index-from-classes.csv
-			out/soong/.intermediates/packages/modules/com.android.art/art-bootclasspath-fragment/android_common_apex10000/modular-hiddenapi/index.csv
+			out/soong/.intermediates/packages/modules/com.android.art/art-bootclasspath-fragment/android_common_com.android.art/modular-hiddenapi/index.csv
 		`)
 	})
 
 	t.Run("Co-existing unflagged apexes should create a duplicate module error", func(t *testing.T) {
+		t.Parallel()
 		bp := `
 		// Source
 		apex {
@@ -5690,6 +6044,7 @@
 }
 
 func TestApexWithTests(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex_test {
 			name: "myapex",
@@ -5721,7 +6076,6 @@
 			relative_install_path: "test",
 			shared_libs: ["mylib"],
 			system_shared_libs: [],
-			static_executable: true,
 			stl: "none",
 			data: [":fg"],
 		}
@@ -5741,7 +6095,7 @@
 		}
 	`)
 
-	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexRule")
+	apexRule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
 	// Ensure that test dep (and their transitive dependencies) are copied into apex.
@@ -5753,7 +6107,7 @@
 	ensureContains(t, copyCmds, "image.apex/bin/test/bar/baz")
 
 	// Ensure the module is correctly translated.
-	bundle := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*apexBundle)
+	bundle := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Module().(*apexBundle)
 	data := android.AndroidMkDataForTest(t, ctx, bundle)
 	name := bundle.BaseModuleName()
 	prefix := "TARGET_"
@@ -5765,6 +6119,7 @@
 }
 
 func TestErrorsIfDepsAreNotEnabled(t *testing.T) {
+	t.Parallel()
 	testApexError(t, `module "myapex" .* depends on disabled module "libfoo"`, `
 		apex {
 			name: "myapex",
@@ -5806,6 +6161,7 @@
 			system_modules: "none",
 			enabled: false,
 			apex_available: ["myapex"],
+			compile_dex: true,
 		}
 	`)
 }
@@ -5833,13 +6189,14 @@
 		}
 	`)
 
-	module := ctx.ModuleForTests("myapex", "android_common_myapex")
+	module := ctx.ModuleForTests(t, "myapex", "android_common_myapex")
 	apexRule := module.Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 	ensureContains(t, copyCmds, "image.apex/javalib/myjavaimport.jar")
 }
 
 func TestApexWithApps(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -5898,7 +6255,7 @@
 		}
 	`)
 
-	module := ctx.ModuleForTests("myapex", "android_common_myapex")
+	module := ctx.ModuleForTests(t, "myapex", "android_common_myapex")
 	apexRule := module.Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
@@ -5906,14 +6263,14 @@
 	ensureContains(t, copyCmds, "image.apex/priv-app/AppFooPriv@TEST.BUILD_ID/AppFooPriv.apk")
 	ensureContains(t, copyCmds, "image.apex/etc/permissions/privapp_allowlist_com.android.AppFooPriv.xml")
 
-	appZipRule := ctx.ModuleForTests("AppFoo", "android_common_apex10000").Description("zip jni libs")
+	appZipRule := ctx.ModuleForTests(t, "AppFoo", "android_common_apex10000").Description("zip jni libs")
 	// JNI libraries are uncompressed
 	if args := appZipRule.Args["jarArgs"]; !strings.Contains(args, "-L 0") {
 		t.Errorf("jni libs are not uncompressed for AppFoo")
 	}
 	// JNI libraries including transitive deps are
 	for _, jni := range []string{"libjni", "libfoo"} {
-		jniOutput := ctx.ModuleForTests(jni, "android_arm64_armv8-a_sdk_shared_apex10000").Module().(*cc.Module).OutputFile().RelativeToTop()
+		jniOutput := ctx.ModuleForTests(t, jni, "android_arm64_armv8-a_sdk_shared_apex10000").Module().(*cc.Module).OutputFile().RelativeToTop()
 		// ... embedded inside APK (jnilibs.zip)
 		ensureListContains(t, appZipRule.Implicits.Strings(), jniOutput.String())
 		// ... and not directly inside the APEX
@@ -5934,6 +6291,7 @@
 }
 
 func TestApexWithAppImportBuildId(t *testing.T) {
+	t.Parallel()
 	invalidBuildIds := []string{"../", "a b", "a/b", "a/b/../c", "/a"}
 	for _, id := range invalidBuildIds {
 		message := fmt.Sprintf("Unable to use build id %s as filename suffix", id)
@@ -5964,6 +6322,7 @@
 }
 
 func TestApexWithAppImports(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -6004,7 +6363,7 @@
 		}
 	`)
 
-	module := ctx.ModuleForTests("myapex", "android_common_myapex")
+	module := ctx.ModuleForTests(t, "myapex", "android_common_myapex")
 	apexRule := module.Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
@@ -6013,6 +6372,7 @@
 }
 
 func TestApexWithAppImportsPrefer(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -6055,6 +6415,7 @@
 }
 
 func TestApexWithTestHelperApp(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -6080,7 +6441,7 @@
 
 	`)
 
-	module := ctx.ModuleForTests("myapex", "android_common_myapex")
+	module := ctx.ModuleForTests(t, "myapex", "android_common_myapex")
 	apexRule := module.Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
@@ -6088,6 +6449,7 @@
 }
 
 func TestApexPropertiesShouldBeDefaultable(t *testing.T) {
+	t.Parallel()
 	// libfoo's apex_available comes from cc_defaults
 	testApexError(t, `requires "libfoo" that doesn't list the APEX under 'apex_available'.`, `
 	apex {
@@ -6124,6 +6486,7 @@
 }
 
 func TestApexAvailable_DirectDep(t *testing.T) {
+	t.Parallel()
 	// libfoo is not available to myapex, but only to otherapex
 	testApexError(t, "requires \"libfoo\" that doesn't list the APEX under 'apex_available'.", `
 	apex {
@@ -6242,6 +6605,7 @@
 }
 
 func TestApexAvailable_IndirectDep(t *testing.T) {
+	t.Parallel()
 	// libbbaz is an indirect dep
 	testApexError(t, `requires "libbaz" that doesn't list the APEX under 'apex_available'.\n\nDependency path:
 .*via tag apex\.dependencyTag\{"sharedLib"\}
@@ -6372,6 +6736,7 @@
 }
 
 func TestApexAvailable_IndirectStaticDep(t *testing.T) {
+	t.Parallel()
 	testApex(t, `
 	apex {
 		name: "myapex",
@@ -6438,6 +6803,7 @@
 }
 
 func TestApexAvailable_InvalidApexName(t *testing.T) {
+	t.Parallel()
 	testApexError(t, "\"otherapex\" is not a valid module name", `
 	apex {
 		name: "myapex",
@@ -6499,7 +6865,9 @@
 }
 
 func TestApexAvailable_ApexAvailableNameWithVersionCodeError(t *testing.T) {
+	t.Parallel()
 	t.Run("negative variant_version produces error", func(t *testing.T) {
+		t.Parallel()
 		testApexError(t, "expected an integer between 0-9; got -1", `
 			apex {
 				name: "myapex",
@@ -6517,6 +6885,7 @@
 	})
 
 	t.Run("variant_version greater than 9 produces error", func(t *testing.T) {
+		t.Parallel()
 		testApexError(t, "expected an integer between 0-9; got 10", `
 			apex {
 				name: "myapex",
@@ -6535,6 +6904,7 @@
 }
 
 func TestApexAvailable_ApexAvailableNameWithVersionCode(t *testing.T) {
+	t.Parallel()
 	context := android.GroupFixturePreparers(
 		android.PrepareForIntegrationTestWithAndroid,
 		PrepareForTestWithApexBuildComponents,
@@ -6569,14 +6939,14 @@
 		}
 	`)
 
-	fooManifestRule := result.ModuleForTests("foo", "android_common_foo").Rule("apexManifestRule")
+	fooManifestRule := result.ModuleForTests(t, "foo", "android_common_foo").Rule("apexManifestRule")
 	fooExpectedDefaultVersion := testDefaultUpdatableModuleVersion
 	fooActualDefaultVersion := fooManifestRule.Args["default_version"]
 	if fooActualDefaultVersion != fooExpectedDefaultVersion {
 		t.Errorf("expected to find defaultVersion %q; got %q", fooExpectedDefaultVersion, fooActualDefaultVersion)
 	}
 
-	barManifestRule := result.ModuleForTests("bar", "android_common_bar").Rule("apexManifestRule")
+	barManifestRule := result.ModuleForTests(t, "bar", "android_common_bar").Rule("apexManifestRule")
 	defaultVersionInt, _ := strconv.Atoi(testDefaultUpdatableModuleVersion)
 	barExpectedDefaultVersion := fmt.Sprint(defaultVersionInt + 3)
 	barActualDefaultVersion := barManifestRule.Args["default_version"]
@@ -6584,7 +6954,7 @@
 		t.Errorf("expected to find defaultVersion %q; got %q", barExpectedDefaultVersion, barActualDefaultVersion)
 	}
 
-	overrideBarManifestRule := result.ModuleForTests("bar", "android_common_myoverrideapex_myoverrideapex").Rule("apexManifestRule")
+	overrideBarManifestRule := result.ModuleForTests(t, "bar", "android_common_myoverrideapex_myoverrideapex").Rule("apexManifestRule")
 	overrideBarActualDefaultVersion := overrideBarManifestRule.Args["default_version"]
 	if overrideBarActualDefaultVersion != barExpectedDefaultVersion {
 		t.Errorf("expected to find defaultVersion %q; got %q", barExpectedDefaultVersion, barActualDefaultVersion)
@@ -6592,7 +6962,9 @@
 }
 
 func TestApexAvailable_ApexAvailableName(t *testing.T) {
+	t.Parallel()
 	t.Run("using name of apex that sets apex_available_name is not allowed", func(t *testing.T) {
+		t.Parallel()
 		testApexError(t, "Consider adding \"myapex\" to 'apex_available' property of \"AppFoo\"", `
 			apex {
 				name: "myapex_sminus",
@@ -6626,6 +6998,7 @@
 	})
 
 	t.Run("apex_available_name allows module to be used in two different apexes", func(t *testing.T) {
+		t.Parallel()
 		testApex(t, `
 			apex {
 				name: "myapex_sminus",
@@ -6659,6 +7032,7 @@
 	})
 
 	t.Run("override_apexes work with apex_available_name", func(t *testing.T) {
+		t.Parallel()
 		testApex(t, `
 			override_apex {
 				name: "myoverrideapex_sminus",
@@ -6712,6 +7086,7 @@
 }
 
 func TestApexAvailable_ApexAvailableNameWithOverrides(t *testing.T) {
+	t.Parallel()
 	context := android.GroupFixturePreparers(
 		android.PrepareForIntegrationTestWithAndroid,
 		PrepareForTestWithApexBuildComponents,
@@ -6769,6 +7144,7 @@
 }
 
 func TestApexAvailable_CheckForPlatform(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 	apex {
 		name: "myapex",
@@ -6818,20 +7194,21 @@
 
 	// libfoo shouldn't be available to platform even though it has "//apex_available:platform",
 	// because it depends on libbar which isn't available to platform
-	libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Module().(*cc.Module)
+	libfoo := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_shared").Module().(*cc.Module)
 	if libfoo.NotAvailableForPlatform() != true {
 		t.Errorf("%q shouldn't be available to platform", libfoo.String())
 	}
 
 	// libfoo2 however can be available to platform because it depends on libbaz which provides
 	// stubs
-	libfoo2 := ctx.ModuleForTests("libfoo2", "android_arm64_armv8-a_shared").Module().(*cc.Module)
+	libfoo2 := ctx.ModuleForTests(t, "libfoo2", "android_arm64_armv8-a_shared").Module().(*cc.Module)
 	if libfoo2.NotAvailableForPlatform() == true {
 		t.Errorf("%q should be available to platform", libfoo2.String())
 	}
 }
 
 func TestApexAvailable_CreatedForApex(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 	apex {
 		name: "myapex",
@@ -6856,17 +7233,18 @@
 		},
 	}`)
 
-	libfooShared := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Module().(*cc.Module)
+	libfooShared := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_shared").Module().(*cc.Module)
 	if libfooShared.NotAvailableForPlatform() != true {
 		t.Errorf("%q shouldn't be available to platform", libfooShared.String())
 	}
-	libfooStatic := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_static").Module().(*cc.Module)
+	libfooStatic := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_static").Module().(*cc.Module)
 	if libfooStatic.NotAvailableForPlatform() != false {
 		t.Errorf("%q should be available to platform", libfooStatic.String())
 	}
 }
 
 func TestApexAvailable_PrefixMatch(t *testing.T) {
+	t.Parallel()
 
 	for _, tc := range []struct {
 		name          string
@@ -6904,6 +7282,7 @@
 		},
 	} {
 		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
 			errorHandler := android.FixtureExpectsNoErrors
 			if tc.expectedError != "" {
 				errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(tc.expectedError)
@@ -6959,7 +7338,81 @@
 	`)
 }
 
+func TestApexValidation_UsesProperPartitionTag(t *testing.T) {
+	t.Parallel()
+	ctx := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			updatable: false,
+			vendor: true,
+		}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+	`, android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+		// vendor path should not affect "partition tag"
+		variables.VendorPath = proptools.StringPtr("system/vendor")
+	}))
+
+	module := ctx.ModuleForTests(t, "myapex", "android_common_myapex")
+	android.AssertStringEquals(t, "partition tag for host_apex_verifier",
+		"vendor",
+		module.Output("host_apex_verifier.timestamp").Args["partition_tag"])
+	android.AssertStringEquals(t, "partition tag for apex_sepolicy_tests",
+		"vendor",
+		module.Output("apex_sepolicy_tests.timestamp").Args["partition_tag"])
+}
+
+func TestApexValidation_TestApexCanSkipInitRcCheck(t *testing.T) {
+	t.Parallel()
+	ctx := testApex(t, `
+		apex_test {
+			name: "myapex",
+			key: "myapex.key",
+			skip_validations: {
+				host_apex_verifier: true,
+			},
+			updatable: false,
+		}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+	`)
+
+	validations := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("signapk").Validations.Strings()
+	if android.SuffixInList(validations, "host_apex_verifier.timestamp") {
+		t.Error("should not run host_apex_verifier")
+	}
+}
+
+func TestApexValidation_TestApexCheckInitRc(t *testing.T) {
+	t.Parallel()
+	ctx := testApex(t, `
+		apex_test {
+			name: "myapex",
+			key: "myapex.key",
+			updatable: false,
+		}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+	`)
+
+	validations := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("signapk").Validations.Strings()
+	if !android.SuffixInList(validations, "host_apex_verifier.timestamp") {
+		t.Error("should run host_apex_verifier")
+	}
+}
+
 func TestOverrideApex(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -7056,8 +7509,8 @@
 		}
 	`, withManifestPackageNameOverrides([]string{"myapex:com.android.myapex"}))
 
-	originalVariant := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(android.OverridableModule)
-	overriddenVariant := ctx.ModuleForTests("myapex", "android_common_override_myapex_override_myapex").Module().(android.OverridableModule)
+	originalVariant := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Module().(android.OverridableModule)
+	overriddenVariant := ctx.ModuleForTests(t, "myapex", "android_common_override_myapex_override_myapex").Module().(android.OverridableModule)
 	if originalVariant.GetOverriddenBy() != "" {
 		t.Errorf("GetOverriddenBy should be empty, but was %q", originalVariant.GetOverriddenBy())
 	}
@@ -7065,7 +7518,7 @@
 		t.Errorf("GetOverriddenBy should be \"override_myapex\", but was %q", overriddenVariant.GetOverriddenBy())
 	}
 
-	module := ctx.ModuleForTests("myapex", "android_common_override_myapex_override_myapex")
+	module := ctx.ModuleForTests(t, "myapex", "android_common_override_myapex_override_myapex")
 	apexRule := module.Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
@@ -7110,6 +7563,7 @@
 }
 
 func TestMinSdkVersionOverride(t *testing.T) {
+	t.Parallel()
 	// Override from 29 to 31
 	minSdkOverride31 := "31"
 	ctx := testApex(t, `
@@ -7155,7 +7609,7 @@
 
 	`, withApexGlobalMinSdkVersionOverride(&minSdkOverride31))
 
-	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexRule")
+	apexRule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
 	// Ensure that direct non-stubs dep is always included
@@ -7169,6 +7623,7 @@
 }
 
 func TestMinSdkVersionOverrideToLowerVersionNoOp(t *testing.T) {
+	t.Parallel()
 	// Attempt to override from 31 to 29, should be a NOOP
 	minSdkOverride29 := "29"
 	ctx := testApex(t, `
@@ -7214,7 +7669,7 @@
 
 	`, withApexGlobalMinSdkVersionOverride(&minSdkOverride29))
 
-	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexRule")
+	apexRule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
 	// Ensure that direct non-stubs dep is always included
@@ -7228,6 +7683,7 @@
 }
 
 func TestLegacyAndroid10Support(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -7252,18 +7708,17 @@
 		}
 	`, withUnbundledBuild)
 
-	module := ctx.ModuleForTests("myapex", "android_common_myapex")
+	module := ctx.ModuleForTests(t, "myapex", "android_common_myapex")
 	args := module.Rule("apexRule").Args
 	ensureContains(t, args["opt_flags"], "--manifest_json "+module.Output("apex_manifest.json").Output.String())
-	ensureNotContains(t, args["opt_flags"], "--no_hashtree")
 
 	// The copies of the libraries in the apex should have one more dependency than
 	// the ones outside the apex, namely the unwinder. Ideally we should check
 	// the dependency names directly here but for some reason the names are blank in
 	// this test.
 	for _, lib := range []string{"libc++", "mylib"} {
-		apexImplicits := ctx.ModuleForTests(lib, "android_arm64_armv8-a_shared_apex29").Rule("ld").Implicits
-		nonApexImplicits := ctx.ModuleForTests(lib, "android_arm64_armv8-a_shared").Rule("ld").Implicits
+		apexImplicits := ctx.ModuleForTests(t, lib, "android_arm64_armv8-a_shared_apex29").Rule("ld").Implicits
+		nonApexImplicits := ctx.ModuleForTests(t, lib, "android_arm64_armv8-a_shared").Rule("ld").Implicits
 		if len(apexImplicits) != len(nonApexImplicits)+1 {
 			t.Errorf("%q missing unwinder dep", lib)
 		}
@@ -7288,6 +7743,7 @@
 }
 
 func TestJavaSDKLibrary(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -7321,12 +7777,13 @@
 		"etc/permissions/foo.xml",
 	})
 	// Permission XML should point to the activated path of impl jar of java_sdk_library
-	sdkLibrary := ctx.ModuleForTests("foo.xml", "android_common_myapex").Output("foo.xml")
+	sdkLibrary := ctx.ModuleForTests(t, "foo.xml", "android_common_myapex").Output("foo.xml")
 	contents := android.ContentFromFileRuleForTests(t, ctx, sdkLibrary)
 	ensureMatches(t, contents, "<library\\n\\s+name=\\\"foo\\\"\\n\\s+file=\\\"/apex/myapex/javalib/foo.jar\\\"")
 }
 
 func TestJavaSDKLibraryOverrideApexes(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		override_apex {
 			name: "mycompanyapex",
@@ -7361,12 +7818,13 @@
 	// Permission XML should point to the activated path of impl jar of java_sdk_library.
 	// Since override variants (com.mycompany.android.foo) are installed in the same package as the overridden variant
 	// (com.android.foo), the filepath should not contain override apex name.
-	sdkLibrary := ctx.ModuleForTests("foo.xml", "android_common_mycompanyapex").Output("foo.xml")
+	sdkLibrary := ctx.ModuleForTests(t, "foo.xml", "android_common_mycompanyapex").Output("foo.xml")
 	contents := android.ContentFromFileRuleForTests(t, ctx, sdkLibrary)
 	ensureMatches(t, contents, "<library\\n\\s+name=\\\"foo\\\"\\n\\s+file=\\\"/apex/myapex/javalib/foo.jar\\\"")
 }
 
 func TestJavaSDKLibrary_WithinApex(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -7388,6 +7846,7 @@
 			apex_available: ["myapex"],
 			sdk_version: "none",
 			system_modules: "none",
+			compile_dex: true,
 		}
 
 		java_library {
@@ -7397,6 +7856,7 @@
 			apex_available: ["myapex"],
 			sdk_version: "none",
 			system_modules: "none",
+			compile_dex: true,
 		}
 
 		prebuilt_apis {
@@ -7413,13 +7873,14 @@
 	})
 
 	// The bar library should depend on the implementation jar.
-	barLibrary := ctx.ModuleForTests("bar", "android_common_myapex").Rule("javac")
+	barLibrary := ctx.ModuleForTests(t, "bar", "android_common_apex10000").Rule("javac")
 	if expected, actual := `^-classpath [^:]*/turbine-combined/foo\.jar$`, barLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
 		t.Errorf("expected %q, found %#q", expected, actual)
 	}
 }
 
 func TestJavaSDKLibrary_CrossBoundary(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -7464,13 +7925,14 @@
 	})
 
 	// The bar library should depend on the stubs jar.
-	barLibrary := ctx.ModuleForTests("bar", "android_common").Rule("javac")
+	barLibrary := ctx.ModuleForTests(t, "bar", "android_common").Rule("javac")
 	if expected, actual := `^-classpath [^:]*/turbine-combined/foo\.stubs\.jar$`, barLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
 		t.Errorf("expected %q, found %#q", expected, actual)
 	}
 }
 
 func TestJavaSDKLibrary_ImportPreferred(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		prebuilt_apis {
 			name: "sdk",
@@ -7504,6 +7966,7 @@
 			apex_available: ["myapex"],
 			sdk_version: "none",
 			system_modules: "none",
+			compile_dex: true,
 		}
 `),
 			"source/a.java":          nil,
@@ -7525,6 +7988,7 @@
 			public: {
 				enabled: true,
 			},
+			compile_dex: true,
 		}
 `),
 			"prebuilt/a.jar": nil,
@@ -7541,6 +8005,7 @@
 			public: {
 				jars: ["a.jar"],
 			},
+			compile_dex: true,
 		}
 `),
 		}), withFiles(filesForSdkLibrary),
@@ -7554,13 +8019,14 @@
 	})
 
 	// The bar library should depend on the implementation jar.
-	barLibrary := ctx.ModuleForTests("bar", "android_common_myapex").Rule("javac")
+	barLibrary := ctx.ModuleForTests(t, "bar", "android_common_apex10000").Rule("javac")
 	if expected, actual := `^-classpath [^:]*/turbine-combined/foo\.jar$`, barLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
 		t.Errorf("expected %q, found %#q", expected, actual)
 	}
 }
 
 func TestJavaSDKLibrary_ImportOnly(t *testing.T) {
+	t.Parallel()
 	testApexError(t, `java_libs: "foo" is not configured to be compiled into dex`, `
 		apex {
 			name: "myapex",
@@ -7588,6 +8054,7 @@
 }
 
 func TestCompatConfig(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForApexTest,
 		java.PrepareForTestWithPlatformCompatConfig,
@@ -7617,6 +8084,7 @@
 			sdk_version: "none",
 			system_modules: "none",
 			apex_available: [ "myapex" ],
+			compile_dex: true,
 		}
 
 		// Make sure that a preferred prebuilt does not affect the apex contents.
@@ -7634,6 +8102,7 @@
 }
 
 func TestNoDupeApexFiles(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		android.PrepareForTestWithAndroidBuildComponents,
 		PrepareForTestWithApexBuildComponents,
@@ -7670,6 +8139,7 @@
 }
 
 func TestApexUnwantedTransitiveDeps(t *testing.T) {
+	t.Parallel()
 	bp := `
 	apex {
 		name: "myapex",
@@ -7706,6 +8176,7 @@
 }
 
 func TestRejectNonInstallableJavaLibrary(t *testing.T) {
+	t.Parallel()
 	testApexError(t, `"myjar" is not configured to be compiled into dex`, `
 		apex {
 			name: "myapex",
@@ -7731,46 +8202,8 @@
 	`)
 }
 
-func TestCarryRequiredModuleNames(t *testing.T) {
-	ctx := testApex(t, `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			native_shared_libs: ["mylib"],
-			updatable: false,
-		}
-
-		apex_key {
-			name: "myapex.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-
-		cc_library {
-			name: "mylib",
-			srcs: ["mylib.cpp"],
-			system_shared_libs: [],
-			stl: "none",
-			required: ["a", "b"],
-			host_required: ["c", "d"],
-			target_required: ["e", "f"],
-			apex_available: [ "myapex" ],
-		}
-	`)
-
-	apexBundle := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*apexBundle)
-	data := android.AndroidMkDataForTest(t, ctx, apexBundle)
-	name := apexBundle.BaseModuleName()
-	prefix := "TARGET_"
-	var builder strings.Builder
-	data.Custom(&builder, name, prefix, "", data)
-	androidMk := builder.String()
-	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES := mylib.myapex:64 a b\n")
-	ensureContains(t, androidMk, "LOCAL_HOST_REQUIRED_MODULES := c d\n")
-	ensureContains(t, androidMk, "LOCAL_TARGET_REQUIRED_MODULES := e f\n")
-}
-
 func TestSymlinksFromApexToSystem(t *testing.T) {
+	t.Parallel()
 	bp := `
 		apex {
 			name: "myapex",
@@ -7851,6 +8284,7 @@
 				"//apex_available:platform",
 			],
 			min_sdk_version: "33",
+			compile_dex: true,
 		}
 
 		java_library {
@@ -7925,6 +8359,7 @@
 }
 
 func TestSymlinksFromApexToSystemRequiredModuleNames(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -7963,7 +8398,7 @@
 		}
 	`)
 
-	apexBundle := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*apexBundle)
+	apexBundle := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Module().(*apexBundle)
 	data := android.AndroidMkDataForTest(t, ctx, apexBundle)
 	var builder strings.Builder
 	data.Custom(&builder, apexBundle.BaseModuleName(), "TARGET_", "", data)
@@ -7977,6 +8412,7 @@
 }
 
 func TestApexWithJniLibs(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -8048,7 +8484,7 @@
 
 	`)
 
-	rule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexManifestRule")
+	rule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("apexManifestRule")
 	// Notice mylib2.so (transitive dep) is not added as a jni_lib
 	ensureEquals(t, rule.Args["opt"], "-a jniLibs libfoo.rust.so mylib.so mylib3.so")
 	ensureExactContents(t, ctx, "myapex", "android_common_myapex", []string{
@@ -8057,39 +8493,14 @@
 		"lib64/mylib2.so",
 		"lib64/mylib3.so",
 		"lib64/libfoo.rust.so",
-		"lib64/libc++.so", // auto-added to libfoo.rust by Soong
-		"lib64/liblog.so", // auto-added to libfoo.rust by Soong
 	})
 
 	// b/220397949
 	ensureListContains(t, names(rule.Args["requireNativeLibs"]), "libfoo.shared_from_rust.so")
 }
 
-func TestApexMutatorsDontRunIfDisabled(t *testing.T) {
-	ctx := testApex(t, `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			updatable: false,
-		}
-		apex_key {
-			name: "myapex.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-	`,
-		android.FixtureModifyConfig(func(config android.Config) {
-			delete(config.Targets, android.Android)
-			config.AndroidCommonTarget = android.Target{}
-		}),
-	)
-
-	if expected, got := []string{""}, ctx.ModuleVariantsForTests("myapex"); !reflect.DeepEqual(expected, got) {
-		t.Errorf("Expected variants: %v, but got: %v", expected, got)
-	}
-}
-
 func TestAppBundle(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -8113,7 +8524,7 @@
 		}
 		`, withManifestPackageNameOverrides([]string{"AppFoo:com.android.foo"}))
 
-	bundleConfigRule := ctx.ModuleForTests("myapex", "android_common_myapex").Output("bundle_config.json")
+	bundleConfigRule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Output("bundle_config.json")
 	content := android.ContentFromFileRuleForTests(t, ctx, bundleConfigRule)
 
 	ensureContains(t, content, `"compression":{"uncompressed_glob":["apex_payload.img","apex_manifest.*"]}`)
@@ -8121,6 +8532,7 @@
 }
 
 func TestAppSetBundle(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -8139,7 +8551,7 @@
 			name: "AppSet",
 			set: "AppSet.apks",
 		}`)
-	mod := ctx.ModuleForTests("myapex", "android_common_myapex")
+	mod := ctx.ModuleForTests(t, "myapex", "android_common_myapex")
 	bundleConfigRule := mod.Output("bundle_config.json")
 	content := android.ContentFromFileRuleForTests(t, ctx, bundleConfigRule)
 	ensureContains(t, content, `"compression":{"uncompressed_glob":["apex_payload.img","apex_manifest.*"]}`)
@@ -8173,21 +8585,22 @@
 	ctx := testApex(t, bp, prepareForTestWithSantitizeHwaddress)
 
 	// Check that the extractor produces the correct output file from the correct input file.
-	extractorOutput := "out/soong/.intermediates/myapex/android_common_myapex/extracted/myapex.hwasan.apks"
+	extractorOutput := "out/soong/.intermediates/myapex/android_common_prebuilt_myapex/extracted/myapex.hwasan.apks"
 
-	m := ctx.ModuleForTests("myapex", "android_common_myapex")
+	m := ctx.ModuleForTests(t, "myapex", "android_common_prebuilt_myapex")
 	extractedApex := m.Output(extractorOutput)
 
 	android.AssertArrayString(t, "extractor input", []string{"myapex.hwasan.apks"}, extractedApex.Inputs.Strings())
 
 	// Ditto for the apex.
-	m = ctx.ModuleForTests("myapex", "android_common_myapex")
-	copiedApex := m.Output("out/soong/.intermediates/myapex/android_common_myapex/foo_v2.apex")
+	m = ctx.ModuleForTests(t, "myapex", "android_common_prebuilt_myapex")
+	copiedApex := m.Output("out/soong/.intermediates/myapex/android_common_prebuilt_myapex/foo_v2.apex")
 
 	android.AssertStringEquals(t, "myapex input", extractorOutput, copiedApex.Input.String())
 }
 
 func TestApexSetApksModuleAssignment(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex_set {
 			name: "myapex",
@@ -8200,10 +8613,10 @@
 		}
 	`)
 
-	m := ctx.ModuleForTests("myapex", "android_common_myapex")
+	m := ctx.ModuleForTests(t, "myapex", "android_common_prebuilt_myapex")
 
 	// Check that the extractor produces the correct apks file from the input module
-	extractorOutput := "out/soong/.intermediates/myapex/android_common_myapex/extracted/myapex.apks"
+	extractorOutput := "out/soong/.intermediates/myapex/android_common_prebuilt_myapex/extracted/myapex.apks"
 	extractedApex := m.Output(extractorOutput)
 
 	android.AssertArrayString(t, "extractor input", []string{"myapex.apks"}, extractedApex.Inputs.Strings())
@@ -8266,6 +8679,7 @@
 }
 
 func TestUpdatable_should_set_min_sdk_version(t *testing.T) {
+	t.Parallel()
 	testApexError(t, `"myapex" .*: updatable: updatable APEXes should set min_sdk_version`, `
 		apex {
 			name: "myapex",
@@ -8282,6 +8696,7 @@
 }
 
 func TestUpdatableDefault_should_set_min_sdk_version(t *testing.T) {
+	t.Parallel()
 	testApexError(t, `"myapex" .*: updatable: updatable APEXes should set min_sdk_version`, `
 		apex {
 			name: "myapex",
@@ -8297,6 +8712,7 @@
 }
 
 func TestUpdatable_should_not_set_generate_classpaths_proto(t *testing.T) {
+	t.Parallel()
 	testApexError(t, `"mysystemserverclasspathfragment" .* it must not set generate_classpaths_proto to false`, `
 		apex {
 			name: "myapex",
@@ -8341,8 +8757,10 @@
 }
 
 func TestDexpreoptAccessDexFilesFromPrebuiltApex(t *testing.T) {
+	t.Parallel()
 	preparer := java.FixtureConfigureApexBootJars("myapex:libfoo")
 	t.Run("prebuilt no source", func(t *testing.T) {
+		t.Parallel()
 		fragment := java.ApexVariantReference{
 			Apex:   proptools.StringPtr("myapex"),
 			Module: proptools.StringPtr("my-bootclasspath-fragment"),
@@ -8428,6 +8846,7 @@
 }
 
 func TestApexPermittedPackagesRules(t *testing.T) {
+	t.Parallel()
 	testcases := []struct {
 		name                 string
 		expectedError        string
@@ -8447,6 +8866,7 @@
 					apex_available: ["myapex"],
 					sdk_version: "none",
 					system_modules: "none",
+					compile_dex: true,
 				}
 				java_library {
 					name: "nonbcp_lib2",
@@ -8455,6 +8875,7 @@
 					permitted_packages: ["a.b"],
 					sdk_version: "none",
 					system_modules: "none",
+					compile_dex: true,
 				}
 				apex {
 					name: "myapex",
@@ -8480,6 +8901,7 @@
 					permitted_packages: ["foo.bar"],
 					sdk_version: "none",
 					system_modules: "none",
+					compile_dex: true,
 				}
 				java_library {
 					name: "bcp_lib2",
@@ -8488,6 +8910,7 @@
 					permitted_packages: ["foo.bar", "bar.baz"],
 					sdk_version: "none",
 					system_modules: "none",
+					compile_dex: true,
 				}
 				apex {
 					name: "myapex",
@@ -8518,6 +8941,7 @@
 					sdk_version: "none",
 					min_sdk_version: "29",
 					system_modules: "none",
+					compile_dex: true,
 				}
 				java_library {
 					name: "bcp_lib_unrestricted",
@@ -8527,6 +8951,7 @@
 					sdk_version: "none",
 					min_sdk_version: "29",
 					system_modules: "none",
+					compile_dex: true,
 				}
 				apex {
 					name: "myapex",
@@ -8547,205 +8972,20 @@
 	}
 	for _, tc := range testcases {
 		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
 			rules := createBcpPermittedPackagesRules(tc.bcpPermittedPackages)
 			testBootJarPermittedPackagesRules(t, tc.expectedError, tc.bp, tc.bootJars, rules)
 		})
 	}
 }
 
-func TestTestFor(t *testing.T) {
-	ctx := testApex(t, `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			native_shared_libs: ["mylib", "myprivlib"],
-			updatable: false,
-		}
-
-		apex_key {
-			name: "myapex.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-
-		cc_library {
-			name: "mylib",
-			srcs: ["mylib.cpp"],
-			system_shared_libs: [],
-			stl: "none",
-			stubs: {
-				versions: ["1"],
-			},
-			apex_available: ["myapex"],
-		}
-
-		cc_library {
-			name: "myprivlib",
-			srcs: ["mylib.cpp"],
-			system_shared_libs: [],
-			stl: "none",
-			apex_available: ["myapex"],
-		}
-
-
-		cc_test {
-			name: "mytest",
-			gtest: false,
-			srcs: ["mylib.cpp"],
-			system_shared_libs: [],
-			stl: "none",
-			shared_libs: ["mylib", "myprivlib", "mytestlib"],
-			test_for: ["myapex"]
-		}
-
-		cc_library {
-			name: "mytestlib",
-			srcs: ["mylib.cpp"],
-			system_shared_libs: [],
-			shared_libs: ["mylib", "myprivlib"],
-			stl: "none",
-			test_for: ["myapex"],
-		}
-
-		cc_benchmark {
-			name: "mybench",
-			srcs: ["mylib.cpp"],
-			system_shared_libs: [],
-			shared_libs: ["mylib", "myprivlib"],
-			stl: "none",
-			test_for: ["myapex"],
-		}
-	`)
-
-	ensureLinkedLibIs := func(mod, variant, linkedLib, expectedVariant string) {
-		ldFlags := strings.Split(ctx.ModuleForTests(mod, variant).Rule("ld").Args["libFlags"], " ")
-		mylibLdFlags := android.FilterListPred(ldFlags, func(s string) bool { return strings.HasPrefix(s, linkedLib) })
-		android.AssertArrayString(t, "unexpected "+linkedLib+" link library for "+mod, []string{linkedLib + expectedVariant}, mylibLdFlags)
-	}
-
-	// These modules are tests for the apex, therefore are linked to the
-	// actual implementation of mylib instead of its stub.
-	ensureLinkedLibIs("mytest", "android_arm64_armv8-a", "out/soong/.intermediates/mylib/", "android_arm64_armv8-a_shared/mylib.so")
-	ensureLinkedLibIs("mytestlib", "android_arm64_armv8-a_shared", "out/soong/.intermediates/mylib/", "android_arm64_armv8-a_shared/mylib.so")
-	ensureLinkedLibIs("mybench", "android_arm64_armv8-a", "out/soong/.intermediates/mylib/", "android_arm64_armv8-a_shared/mylib.so")
-}
-
-func TestIndirectTestFor(t *testing.T) {
-	ctx := testApex(t, `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			native_shared_libs: ["mylib", "myprivlib"],
-			updatable: false,
-		}
-
-		apex_key {
-			name: "myapex.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-
-		cc_library {
-			name: "mylib",
-			srcs: ["mylib.cpp"],
-			system_shared_libs: [],
-			stl: "none",
-			stubs: {
-				versions: ["1"],
-			},
-			apex_available: ["myapex"],
-		}
-
-		cc_library {
-			name: "myprivlib",
-			srcs: ["mylib.cpp"],
-			system_shared_libs: [],
-			stl: "none",
-			shared_libs: ["mylib"],
-			apex_available: ["myapex"],
-		}
-
-		cc_library {
-			name: "mytestlib",
-			srcs: ["mylib.cpp"],
-			system_shared_libs: [],
-			shared_libs: ["myprivlib"],
-			stl: "none",
-			test_for: ["myapex"],
-		}
-	`)
-
-	ensureLinkedLibIs := func(mod, variant, linkedLib, expectedVariant string) {
-		ldFlags := strings.Split(ctx.ModuleForTests(mod, variant).Rule("ld").Args["libFlags"], " ")
-		mylibLdFlags := android.FilterListPred(ldFlags, func(s string) bool { return strings.HasPrefix(s, linkedLib) })
-		android.AssertArrayString(t, "unexpected "+linkedLib+" link library for "+mod, []string{linkedLib + expectedVariant}, mylibLdFlags)
-	}
-
-	// The platform variant of mytestlib links to the platform variant of the
-	// internal myprivlib.
-	ensureLinkedLibIs("mytestlib", "android_arm64_armv8-a_shared", "out/soong/.intermediates/myprivlib/", "android_arm64_armv8-a_shared/myprivlib.so")
-
-	// The platform variant of myprivlib links to the platform variant of mylib
-	// and bypasses its stubs.
-	ensureLinkedLibIs("myprivlib", "android_arm64_armv8-a_shared", "out/soong/.intermediates/mylib/", "android_arm64_armv8-a_shared/mylib.so")
-}
-
-func TestTestForForLibInOtherApex(t *testing.T) {
-	// This case is only allowed for known overlapping APEXes, i.e. the ART APEXes.
-	_ = testApex(t, `
-		apex {
-			name: "com.android.art",
-			key: "myapex.key",
-			native_shared_libs: ["libnativebridge"],
-			updatable: false,
-		}
-
-		apex {
-			name: "com.android.art.debug",
-			key: "myapex.key",
-			native_shared_libs: ["libnativebridge", "libnativebrdige_test"],
-			updatable: false,
-		}
-
-		apex_key {
-			name: "myapex.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-
-		cc_library {
-			name: "libnativebridge",
-			srcs: ["libnativebridge.cpp"],
-			system_shared_libs: [],
-			stl: "none",
-			stubs: {
-				versions: ["1"],
-			},
-			apex_available: ["com.android.art", "com.android.art.debug"],
-		}
-
-		cc_library {
-			name: "libnativebrdige_test",
-			srcs: ["mylib.cpp"],
-			system_shared_libs: [],
-			shared_libs: ["libnativebridge"],
-			stl: "none",
-			apex_available: ["com.android.art.debug"],
-			test_for: ["com.android.art"],
-		}
-	`,
-		android.MockFS{
-			"system/sepolicy/apex/com.android.art-file_contexts":       nil,
-			"system/sepolicy/apex/com.android.art.debug-file_contexts": nil,
-		}.AddToFixture())
-}
-
 // TODO(jungjw): Move this to proptools
 func intPtr(i int) *int {
 	return &i
 }
 
 func TestApexSet(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex_set {
 			name: "myapex",
@@ -8765,7 +9005,7 @@
 		}),
 	)
 
-	m := ctx.ModuleForTests("myapex", "android_common_myapex")
+	m := ctx.ModuleForTests(t, "myapex", "android_common_prebuilt_myapex")
 
 	// Check extract_apks tool parameters.
 	extractedApex := m.Output("extracted/myapex.apks")
@@ -8780,7 +9020,7 @@
 		t.Errorf("Unexpected abis parameter - expected %q vs actual %q", expected, actual)
 	}
 
-	m = ctx.ModuleForTests("myapex", "android_common_myapex")
+	m = ctx.ModuleForTests(t, "myapex", "android_common_prebuilt_myapex")
 	a := m.Module().(*ApexSet)
 	expectedOverrides := []string{"foo"}
 	actualOverrides := android.AndroidMkEntriesForTest(t, ctx, a)[0].EntryMap["LOCAL_OVERRIDES_MODULES"]
@@ -8790,6 +9030,7 @@
 }
 
 func TestApexSet_NativeBridge(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex_set {
 			name: "myapex",
@@ -8806,7 +9047,7 @@
 		}),
 	)
 
-	m := ctx.ModuleForTests("myapex", "android_common_myapex")
+	m := ctx.ModuleForTests(t, "myapex", "android_common_prebuilt_myapex")
 
 	// Check extract_apks tool parameters. No native bridge arch expected
 	extractedApex := m.Output("extracted/myapex.apks")
@@ -8814,6 +9055,7 @@
 }
 
 func TestNoStaticLinkingToStubsLib(t *testing.T) {
+	t.Parallel()
 	testApexError(t, `.*required by "mylib" is a native library providing stub.*`, `
 		apex {
 			name: "myapex",
@@ -8851,6 +9093,7 @@
 }
 
 func TestApexKeysTxt(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -8866,12 +9109,13 @@
 		}
 	`)
 
-	myapex := ctx.ModuleForTests("myapex", "android_common_myapex")
+	myapex := ctx.ModuleForTests(t, "myapex", "android_common_myapex")
 	content := android.ContentFromFileRuleForTests(t, ctx, myapex.Output("apexkeys.txt"))
 	ensureContains(t, content, `name="myapex.apex" public_key="vendor/foo/devkeys/testkey.avbpubkey" private_key="vendor/foo/devkeys/testkey.pem" container_certificate="vendor/foo/devkeys/test.x509.pem" container_private_key="vendor/foo/devkeys/test.pk8" partition="system" sign_tool="sign_myapex"`)
 }
 
 func TestApexKeysTxtOverrides(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -8908,14 +9152,15 @@
 	`)
 
 	content := android.ContentFromFileRuleForTests(t, ctx,
-		ctx.ModuleForTests("myapex", "android_common_myapex").Output("apexkeys.txt"))
+		ctx.ModuleForTests(t, "myapex", "android_common_myapex").Output("apexkeys.txt"))
 	ensureContains(t, content, `name="myapex.apex" public_key="vendor/foo/devkeys/testkey.avbpubkey" private_key="vendor/foo/devkeys/testkey.pem" container_certificate="vendor/foo/devkeys/test.x509.pem" container_private_key="vendor/foo/devkeys/test.pk8" partition="system" sign_tool="sign_myapex"`)
 	content = android.ContentFromFileRuleForTests(t, ctx,
-		ctx.ModuleForTests("myapex_set", "android_common_myapex_set").Output("apexkeys.txt"))
+		ctx.ModuleForTests(t, "myapex_set", "android_common_prebuilt_myapex_set").Output("apexkeys.txt"))
 	ensureContains(t, content, `name="myapex_set.apex" public_key="PRESIGNED" private_key="PRESIGNED" container_certificate="PRESIGNED" container_private_key="PRESIGNED" partition="system"`)
 }
 
 func TestAllowedFiles(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -8960,18 +9205,19 @@
 			`),
 	}))
 
-	rule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("diffApexContentRule")
+	rule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("diffApexContentRule")
 	if expected, actual := "allowed.txt", rule.Args["allowed_files_file"]; expected != actual {
 		t.Errorf("allowed_files_file: expected %q but got %q", expected, actual)
 	}
 
-	rule2 := ctx.ModuleForTests("myapex", "android_common_override_myapex_override_myapex").Rule("diffApexContentRule")
+	rule2 := ctx.ModuleForTests(t, "myapex", "android_common_override_myapex_override_myapex").Rule("diffApexContentRule")
 	if expected, actual := "sub/allowed.txt", rule2.Args["allowed_files_file"]; expected != actual {
 		t.Errorf("allowed_files_file: expected %q but got %q", expected, actual)
 	}
 }
 
 func TestNonPreferredPrebuiltDependency(t *testing.T) {
+	t.Parallel()
 	testApex(t, `
 		apex {
 			name: "myapex",
@@ -9008,6 +9254,7 @@
 }
 
 func TestCompressedApex(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -9026,14 +9273,14 @@
 		}),
 	)
 
-	compressRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("compressRule")
+	compressRule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("compressRule")
 	ensureContains(t, compressRule.Output.String(), "myapex.capex.unsigned")
 
-	signApkRule := ctx.ModuleForTests("myapex", "android_common_myapex").Description("sign compressedApex")
+	signApkRule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Description("sign compressedApex")
 	ensureEquals(t, signApkRule.Input.String(), compressRule.Output.String())
 
 	// Make sure output of bundle is .capex
-	ab := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*apexBundle)
+	ab := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Module().(*apexBundle)
 	ensureContains(t, ab.outputFile.String(), "myapex.capex")
 
 	// Verify android.mk rules
@@ -9044,9 +9291,38 @@
 	ensureContains(t, androidMk, "LOCAL_MODULE_STEM := myapex.capex\n")
 }
 
+func TestCompressedApexIsDisabledWhenUsingErofs(t *testing.T) {
+	t.Parallel()
+	ctx := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			compressible: true,
+			updatable: false,
+			payload_fs_type: "erofs",
+		}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+	`,
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			variables.CompressedApex = proptools.BoolPtr(true)
+		}),
+	)
+
+	compressRule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").MaybeRule("compressRule")
+	if compressRule.Rule != nil {
+		t.Error("erofs apex should not be compressed")
+	}
+}
+
 func TestApexSet_ShouldRespectCompressedApexFlag(t *testing.T) {
+	t.Parallel()
 	for _, compressionEnabled := range []bool{true, false} {
 		t.Run(fmt.Sprintf("compressionEnabled=%v", compressionEnabled), func(t *testing.T) {
+			t.Parallel()
 			ctx := testApex(t, `
 				apex_set {
 					name: "com.company.android.myapex",
@@ -9058,7 +9334,7 @@
 			}),
 			)
 
-			build := ctx.ModuleForTests("com.company.android.myapex", "android_common_com.android.myapex").Output("com.company.android.myapex.apex")
+			build := ctx.ModuleForTests(t, "com.company.android.myapex", "android_common_prebuilt_com.android.myapex").Output("com.company.android.myapex.apex")
 			if compressionEnabled {
 				ensureEquals(t, build.Rule.String(), "android/soong/android.Cp")
 			} else {
@@ -9069,6 +9345,7 @@
 }
 
 func TestPreferredPrebuiltSharedLibDep(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -9109,7 +9386,7 @@
 		}
 	`)
 
-	ab := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*apexBundle)
+	ab := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Module().(*apexBundle)
 	data := android.AndroidMkDataForTest(t, ctx, ab)
 	var builder strings.Builder
 	data.Custom(&builder, ab.BaseModuleName(), "TARGET_", "", data)
@@ -9121,6 +9398,7 @@
 }
 
 func TestExcludeDependency(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -9158,19 +9436,20 @@
 	`)
 
 	// Check if mylib is linked to mylib2 for the non-apex target
-	ldFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared").Rule("ld").Args["libFlags"]
+	ldFlags := ctx.ModuleForTests(t, "mylib", "android_arm64_armv8-a_shared").Rule("ld").Args["libFlags"]
 	ensureContains(t, ldFlags, "mylib2/android_arm64_armv8-a_shared/mylib2.so")
 
 	// Make sure that the link doesn't occur for the apex target
-	ldFlags = ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_apex10000").Rule("ld").Args["libFlags"]
+	ldFlags = ctx.ModuleForTests(t, "mylib", "android_arm64_armv8-a_shared_apex10000").Rule("ld").Args["libFlags"]
 	ensureNotContains(t, ldFlags, "mylib2/android_arm64_armv8-a_shared_apex10000/mylib2.so")
 
 	// It shouldn't appear in the copy cmd as well.
-	copyCmds := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexRule").Args["copy_commands"]
+	copyCmds := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("apexRule").Args["copy_commands"]
 	ensureNotContains(t, copyCmds, "image.apex/lib64/mylib2.so")
 }
 
 func TestPrebuiltStubLibDep(t *testing.T) {
+	t.Parallel()
 	bpBase := `
 		apex {
 			name: "myapex",
@@ -9263,13 +9542,15 @@
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
 			for _, otherApexEnabled := range test.otherApexEnabled {
 				t.Run("otherapex_enabled_"+otherApexEnabled, func(t *testing.T) {
+					t.Parallel()
 					ctx := testApex(t, fmt.Sprintf(bpBase, otherApexEnabled)+test.stublibBp)
 
 					type modAndMkEntries struct {
 						mod       *cc.Module
-						mkEntries android.AndroidMkEntries
+						mkEntries android.AndroidMkInfo
 					}
 					entries := []*modAndMkEntries{}
 
@@ -9279,11 +9560,14 @@
 							if !strings.HasPrefix(variant, "android_arm64_armv8-a_shared") {
 								continue
 							}
-							mod := ctx.ModuleForTests(modName, variant).Module().(*cc.Module)
+							mod := ctx.ModuleForTests(t, modName, variant).Module().(*cc.Module)
 							if !mod.Enabled(android.PanickingConfigAndErrorContext(ctx)) || mod.IsHideFromMake() {
 								continue
 							}
-							for _, ent := range android.AndroidMkEntriesForTest(t, ctx, mod) {
+							info := android.AndroidMkInfoForTest(t, ctx, mod)
+							ents := []android.AndroidMkInfo{info.PrimaryInfo}
+							ents = append(ents, info.ExtraInfo...)
+							for _, ent := range ents {
 								if ent.Disabled {
 									continue
 								}
@@ -9332,6 +9616,7 @@
 }
 
 func TestApexJavaCoverage(t *testing.T) {
+	t.Parallel()
 	bp := `
 		apex {
 			name: "myapex",
@@ -9398,18 +9683,19 @@
 	).RunTest(t)
 
 	// Make sure jacoco ran on both mylib and mybootclasspathlib
-	if result.ModuleForTests("mylib", "android_common_apex10000").MaybeRule("jacoco").Rule == nil {
+	if result.ModuleForTests(t, "mylib", "android_common_apex10000").MaybeRule("jacoco").Rule == nil {
 		t.Errorf("Failed to find jacoco rule for mylib")
 	}
-	if result.ModuleForTests("mybootclasspathlib", "android_common_apex10000").MaybeRule("jacoco").Rule == nil {
+	if result.ModuleForTests(t, "mybootclasspathlib", "android_common_apex10000").MaybeRule("jacoco").Rule == nil {
 		t.Errorf("Failed to find jacoco rule for mybootclasspathlib")
 	}
-	if result.ModuleForTests("mysystemserverclasspathlib", "android_common_apex10000").MaybeRule("jacoco").Rule == nil {
+	if result.ModuleForTests(t, "mysystemserverclasspathlib", "android_common_apex10000").MaybeRule("jacoco").Rule == nil {
 		t.Errorf("Failed to find jacoco rule for mysystemserverclasspathlib")
 	}
 }
 
 func TestProhibitStaticExecutable(t *testing.T) {
+	t.Parallel()
 	testApexError(t, `executable mybin is static`, `
 		apex {
 			name: "myapex",
@@ -9461,6 +9747,7 @@
 }
 
 func TestAndroidMk_DexpreoptBuiltInstalledForApex(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -9485,15 +9772,25 @@
 		dexpreopt.FixtureSetApexSystemServerJars("myapex:foo"),
 	)
 
-	apexBundle := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*apexBundle)
+	apexBundle := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Module().(*apexBundle)
 	data := android.AndroidMkDataForTest(t, ctx, apexBundle)
 	var builder strings.Builder
 	data.Custom(&builder, apexBundle.BaseModuleName(), "TARGET_", "", data)
 	androidMk := builder.String()
-	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES := foo.myapex foo-dexpreopt-arm64-apex@myapex@javalib@foo.jar@classes.odex foo-dexpreopt-arm64-apex@myapex@javalib@foo.jar@classes.vdex\n")
+	out := ctx.Config().OutDir()
+	ensureContains(t, androidMk, "LOCAL_SOONG_INSTALL_PAIRS += "+
+		filepath.Join(out, "soong/.intermediates/foo/android_common_apex10000/dexpreopt/foo/oat/arm64/javalib.odex")+
+		":"+
+		filepath.Join(out, "target/product/test_device/system/framework/oat/arm64/apex@myapex@javalib@foo.jar@classes.odex")+
+		" "+
+		filepath.Join(out, "soong/.intermediates/foo/android_common_apex10000/dexpreopt/foo/oat/arm64/javalib.vdex")+
+		":"+
+		filepath.Join(out, "target/product/test_device/system/framework/oat/arm64/apex@myapex@javalib@foo.jar@classes.vdex")+
+		"\n")
 }
 
 func TestAndroidMk_RequiredModules(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -9524,7 +9821,7 @@
 		}
 	`)
 
-	apexBundle := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*apexBundle)
+	apexBundle := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Module().(*apexBundle)
 	data := android.AndroidMkDataForTest(t, ctx, apexBundle)
 	var builder strings.Builder
 	data.Custom(&builder, apexBundle.BaseModuleName(), "TARGET_", "", data)
@@ -9533,6 +9830,7 @@
 }
 
 func TestAndroidMk_RequiredDeps(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -9547,7 +9845,7 @@
 		}
 	`)
 
-	bundle := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*apexBundle)
+	bundle := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Module().(*apexBundle)
 	bundle.makeModulesToInstall = append(bundle.makeModulesToInstall, "foo")
 	data := android.AndroidMkDataForTest(t, ctx, bundle)
 	var builder strings.Builder
@@ -9557,6 +9855,7 @@
 }
 
 func TestApexOutputFileProducer(t *testing.T) {
+	t.Parallel()
 	for _, tc := range []struct {
 		name          string
 		ref           string
@@ -9574,6 +9873,7 @@
 		},
 	} {
 		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
 			ctx := testApex(t, `
 					apex {
 						name: "myapex",
@@ -9597,7 +9897,7 @@
 				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
 					variables.CompressedApex = proptools.BoolPtr(true)
 				}))
-			javaTest := ctx.ModuleForTests(tc.name, "android_common").Module().(*java.Test)
+			javaTest := ctx.ModuleForTests(t, tc.name, "android_common").Module().(*java.Test)
 			data := android.AndroidMkEntriesForTest(t, ctx, javaTest)[0].EntryMap["LOCAL_COMPATIBILITY_SUPPORT_FILES"]
 			android.AssertStringPathsRelativeToTopEquals(t, "data", ctx.Config(), tc.expected_data, data)
 		})
@@ -9605,6 +9905,7 @@
 }
 
 func TestSdkLibraryCanHaveHigherMinSdkVersion(t *testing.T) {
+	t.Parallel()
 	preparer := android.GroupFixturePreparers(
 		PrepareForTestWithApexBuildComponents,
 		prepareForTestWithMyapex,
@@ -9617,6 +9918,7 @@
 
 	// Test java_sdk_library in bootclasspath_fragment may define higher min_sdk_version than the apex
 	t.Run("bootclasspath_fragment jar has higher min_sdk_version than apex", func(t *testing.T) {
+		t.Parallel()
 		preparer.RunTestWithBp(t, `
 			apex {
 				name: "myapex",
@@ -9673,6 +9975,7 @@
 
 	// Test java_sdk_library in systemserverclasspath_fragment may define higher min_sdk_version than the apex
 	t.Run("systemserverclasspath_fragment jar has higher min_sdk_version than apex", func(t *testing.T) {
+		t.Parallel()
 		preparer.RunTestWithBp(t, `
 			apex {
 				name: "myapex",
@@ -9722,6 +10025,7 @@
 	})
 
 	t.Run("bootclasspath_fragment jar must set min_sdk_version", func(t *testing.T) {
+		t.Parallel()
 		preparer.
 			RunTestWithBp(t, `
 				apex {
@@ -9760,6 +10064,7 @@
 	})
 
 	t.Run("systemserverclasspath_fragment jar must set min_sdk_version", func(t *testing.T) {
+		t.Parallel()
 		preparer.ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "mysystemserverclasspathlib".*must set min_sdk_version`)).
 			RunTestWithBp(t, `
 				apex {
@@ -9795,7 +10100,7 @@
 
 // Verifies that the APEX depends on all the Make modules in the list.
 func ensureContainsRequiredDeps(t *testing.T, ctx *android.TestContext, moduleName, variant string, deps []string) {
-	a := ctx.ModuleForTests(moduleName, variant).Module().(*apexBundle)
+	a := ctx.ModuleForTests(t, moduleName, variant).Module().(*apexBundle)
 	for _, dep := range deps {
 		android.AssertStringListContains(t, "", a.makeModulesToInstall, dep)
 	}
@@ -9803,13 +10108,14 @@
 
 // Verifies that the APEX does not depend on any of the Make modules in the list.
 func ensureDoesNotContainRequiredDeps(t *testing.T, ctx *android.TestContext, moduleName, variant string, deps []string) {
-	a := ctx.ModuleForTests(moduleName, variant).Module().(*apexBundle)
+	a := ctx.ModuleForTests(t, moduleName, variant).Module().(*apexBundle)
 	for _, dep := range deps {
 		android.AssertStringListDoesNotContain(t, "", a.makeModulesToInstall, dep)
 	}
 }
 
 func TestApexStrictUpdtabilityLint(t *testing.T) {
+	t.Parallel()
 	bpTemplate := `
 		apex {
 			name: "myapex",
@@ -9831,6 +10137,7 @@
 			},
 			sdk_version: "current",
 			min_sdk_version: "29",
+			compile_dex: true,
 		}
 		`
 	fs := android.MockFS{
@@ -9881,6 +10188,7 @@
 
 	for _, testCase := range testCases {
 		t.Run(testCase.testCaseName, func(t *testing.T) {
+			t.Parallel()
 			fixtures := []android.FixturePreparer{}
 			baselineProperty := ""
 			if testCase.lintFileExists {
@@ -9908,8 +10216,8 @@
 				}
 			}
 
-			myjavalib := result.ModuleForTests("myjavalib", "android_common_apex29")
-			apex := result.ModuleForTests("myapex", "android_common_myapex")
+			myjavalib := result.ModuleForTests(t, "myjavalib", "android_common_apex29")
+			apex := result.ModuleForTests(t, "myapex", "android_common_myapex")
 			apexStrictUpdatabilityCheck := apex.MaybeOutput("lint_strict_updatability_check.stamp")
 			javalibStrictUpdatabilityCheck := myjavalib.MaybeOutput("lint_strict_updatability_check.stamp")
 
@@ -9921,6 +10229,7 @@
 
 // checks transtive deps of an apex coming from bootclasspath_fragment
 func TestApexStrictUpdtabilityLintBcpFragmentDeps(t *testing.T) {
+	t.Parallel()
 	bp := `
 		apex {
 			name: "myapex",
@@ -9957,7 +10266,7 @@
 	}
 
 	result := testApex(t, bp, dexpreopt.FixtureSetApexBootJars("myapex:myjavalib"), fs.AddToFixture())
-	apex := result.ModuleForTests("myapex", "android_common_myapex")
+	apex := result.ModuleForTests(t, "myapex", "android_common_myapex")
 	apexStrictUpdatabilityCheck := apex.Output("lint_strict_updatability_check.stamp")
 	android.AssertStringDoesContain(t, "strict updatability check rule for myapex",
 		apexStrictUpdatabilityCheck.RuleParams.Command, "--disallowed_issues NewApi")
@@ -9966,6 +10275,7 @@
 }
 
 func TestApexLintBcpFragmentSdkLibDeps(t *testing.T) {
+	t.Parallel()
 	bp := `
 		apex {
 			name: "myapex",
@@ -10008,7 +10318,7 @@
 		android.FixtureMergeMockFs(fs),
 	).RunTestWithBp(t, bp)
 
-	myapex := result.ModuleForTests("myapex", "android_common_myapex")
+	myapex := result.ModuleForTests(t, "myapex", "android_common_myapex")
 	lintReportInputs := strings.Join(myapex.Output("lint-report-xml.zip").Inputs.Strings(), " ")
 	android.AssertStringDoesContain(t,
 		"myapex lint report expected to contain that of the sdk library impl lib as an input",
@@ -10017,6 +10327,7 @@
 
 // updatable apexes should propagate updatable=true to its apps
 func TestUpdatableApexEnforcesAppUpdatability(t *testing.T) {
+	t.Parallel()
 	bp := `
 		apex {
 			name: "myapex",
@@ -10045,61 +10356,8 @@
 		RunTestWithBp(t, bp)
 }
 
-func TestTrimmedApex(t *testing.T) {
-	bp := `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			native_shared_libs: ["libfoo","libbaz"],
-			min_sdk_version: "29",
-			trim_against: "mydcla",
-    }
-		apex {
-			name: "mydcla",
-			key: "myapex.key",
-			native_shared_libs: ["libfoo","libbar"],
-			min_sdk_version: "29",
-			file_contexts: ":myapex-file_contexts",
-			dynamic_common_lib_apex: true,
-		}
-		apex_key {
-			name: "myapex.key",
-		}
-		cc_library {
-			name: "libfoo",
-			shared_libs: ["libc"],
-			apex_available: ["myapex","mydcla"],
-			min_sdk_version: "29",
-		}
-		cc_library {
-			name: "libbar",
-			shared_libs: ["libc"],
-			apex_available: ["myapex","mydcla"],
-			min_sdk_version: "29",
-		}
-		cc_library {
-			name: "libbaz",
-			shared_libs: ["libc"],
-			apex_available: ["myapex","mydcla"],
-			min_sdk_version: "29",
-		}
-		`
-	ctx := testApex(t, bp)
-	module := ctx.ModuleForTests("myapex", "android_common_myapex")
-	apexRule := module.MaybeRule("apexRule")
-	if apexRule.Rule == nil {
-		t.Errorf("Expecting regular apex rule but a non regular apex rule found")
-	}
-
-	ctx = testApex(t, bp, android.FixtureModifyConfig(android.SetTrimmedApexEnabledForTests))
-	trimmedApexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("TrimmedApexRule")
-	libs_to_trim := trimmedApexRule.Args["libs_to_trim"]
-	android.AssertStringDoesContain(t, "missing lib to trim", libs_to_trim, "libfoo")
-	android.AssertStringDoesContain(t, "missing lib to trim", libs_to_trim, "libbar")
-	android.AssertStringDoesNotContain(t, "unexpected libs in the libs to trim", libs_to_trim, "libbaz")
-}
-
 func TestCannedFsConfig(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -10112,7 +10370,7 @@
 			public_key: "testkey.avbpubkey",
 			private_key: "testkey.pem",
 		}`)
-	mod := ctx.ModuleForTests("myapex", "android_common_myapex")
+	mod := ctx.ModuleForTests(t, "myapex", "android_common_myapex")
 	generateFsRule := mod.Rule("generateFsConfig")
 	cmd := generateFsRule.RuleParams.Command
 
@@ -10120,6 +10378,7 @@
 }
 
 func TestCannedFsConfig_HasCustomConfig(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -10133,7 +10392,7 @@
 			public_key: "testkey.avbpubkey",
 			private_key: "testkey.pem",
 		}`)
-	mod := ctx.ModuleForTests("myapex", "android_common_myapex")
+	mod := ctx.ModuleForTests(t, "myapex", "android_common_myapex")
 	generateFsRule := mod.Rule("generateFsConfig")
 	cmd := generateFsRule.RuleParams.Command
 
@@ -10142,6 +10401,7 @@
 }
 
 func TestStubLibrariesMultipleApexViolation(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		desc          string
 		hasStubs      bool
@@ -10166,9 +10426,9 @@
 			expectedError: "Stub libraries should have a single apex_available.*myapex.*otherapex",
 		},
 		{
-			desc:          "stub library can be available to a core apex and a test apex",
+			desc:          "stub library can be available to a core apex and a test apex using apex_available_name",
 			hasStubs:      true,
-			apexAvailable: `["myapex", "test_myapex"]`,
+			apexAvailable: `["myapex"]`,
 		},
 	}
 	bpTemplate := `
@@ -10193,29 +10453,33 @@
 			key: "apex.key",
 			updatable: false,
 			native_shared_libs: ["libfoo"],
+			apex_available_name: "myapex",
 		}
 		apex_key {
 			name: "apex.key",
 		}
 	`
 	for _, tc := range testCases {
-		stubs := ""
-		if tc.hasStubs {
-			stubs = `stubs: {symbol_file: "libfoo.map.txt"},`
-		}
-		bp := fmt.Sprintf(bpTemplate, stubs, tc.apexAvailable)
-		mockFsFixturePreparer := android.FixtureModifyMockFS(func(fs android.MockFS) {
-			fs["system/sepolicy/apex/test_myapex-file_contexts"] = nil
+		t.Run(tc.desc, func(t *testing.T) {
+			stubs := ""
+			if tc.hasStubs {
+				stubs = `stubs: {symbol_file: "libfoo.map.txt"},`
+			}
+			bp := fmt.Sprintf(bpTemplate, stubs, tc.apexAvailable)
+			mockFsFixturePreparer := android.FixtureModifyMockFS(func(fs android.MockFS) {
+				fs["system/sepolicy/apex/test_myapex-file_contexts"] = nil
+			})
+			if tc.expectedError == "" {
+				testApex(t, bp, mockFsFixturePreparer)
+			} else {
+				testApexError(t, tc.expectedError, bp, mockFsFixturePreparer)
+			}
 		})
-		if tc.expectedError == "" {
-			testApex(t, bp, mockFsFixturePreparer)
-		} else {
-			testApexError(t, tc.expectedError, bp, mockFsFixturePreparer)
-		}
 	}
 }
 
 func TestFileSystemShouldSkipApexLibraries(t *testing.T) {
+	t.Parallel()
 	context := android.GroupFixturePreparers(
 		android.PrepareForIntegrationTestWithAndroid,
 		cc.PrepareForIntegrationTestWithCc,
@@ -10229,7 +10493,10 @@
 			deps: [
 				"libfoo",
 			],
-			linker_config_src: "linker.config.json",
+			linker_config: {
+				gen_linker_config: true,
+				linker_config_srcs: ["linker.config.json"],
+			},
 		}
 
 		cc_library {
@@ -10260,7 +10527,7 @@
 		}
 	`)
 
-	inputs := result.ModuleForTests("myfilesystem", "android_common").Output("myfilesystem.img").Implicits
+	inputs := result.ModuleForTests(t, "myfilesystem", "android_common").Output("myfilesystem.img").Implicits
 	android.AssertStringListDoesNotContain(t, "filesystem should not have libbar",
 		inputs.Strings(),
 		"out/soong/.intermediates/libbar/android_arm64_armv8-a_shared/libbar.so")
@@ -10285,6 +10552,7 @@
 `
 
 func TestAconfigFilesJavaDeps(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, apex_default_bp+`
 		apex {
 			name: "myapex",
@@ -10307,6 +10575,7 @@
 			apex_available: [
 				"myapex",
 			],
+			compile_dex: true,
 		}
 
 		java_library {
@@ -10318,6 +10587,7 @@
 			apex_available: [
 				"myapex",
 			],
+			compile_dex: true,
 		}
 
 		aconfig_declarations {
@@ -10351,7 +10621,7 @@
 		}
 	`)
 
-	mod := ctx.ModuleForTests("myapex", "android_common_myapex")
+	mod := ctx.ModuleForTests(t, "myapex", "android_common_myapex")
 	s := mod.Rule("apexRule").Args["copy_commands"]
 	copyCmds := regexp.MustCompile(" *&& *").Split(s, -1)
 	if len(copyCmds) != 14 {
@@ -10376,6 +10646,7 @@
 }
 
 func TestAconfigFilesJavaAndCcDeps(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, apex_default_bp+`
 		apex {
 			name: "myapex",
@@ -10403,6 +10674,7 @@
 			apex_available: [
 				"myapex",
 			],
+			compile_dex: true,
 		}
 
 		cc_library {
@@ -10488,7 +10760,7 @@
 		}
 	`)
 
-	mod := ctx.ModuleForTests("myapex", "android_common_myapex")
+	mod := ctx.ModuleForTests(t, "myapex", "android_common_myapex")
 	s := mod.Rule("apexRule").Args["copy_commands"]
 	copyCmds := regexp.MustCompile(" *&& *").Split(s, -1)
 	if len(copyCmds) != 18 {
@@ -10514,6 +10786,7 @@
 }
 
 func TestAconfigFilesRustDeps(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, apex_default_bp+`
 		apex {
 			name: "myapex",
@@ -10533,15 +10806,6 @@
 		}
 
 		rust_library {
-			name: "libflags_rust", // test mock
-			crate_name: "flags_rust",
-			srcs: ["lib.rs"],
-			apex_available: [
-				"myapex",
-			],
-		}
-
-		rust_library {
 			name: "liblazy_static", // test mock
 			crate_name: "lazy_static",
 			srcs: ["src/lib.rs"],
@@ -10658,11 +10922,11 @@
 		}
 	`)
 
-	mod := ctx.ModuleForTests("myapex", "android_common_myapex")
+	mod := ctx.ModuleForTests(t, "myapex", "android_common_myapex")
 	s := mod.Rule("apexRule").Args["copy_commands"]
 	copyCmds := regexp.MustCompile(" *&& *").Split(s, -1)
-	if len(copyCmds) != 38 {
-		t.Fatalf("Expected 38 commands, got %d in:\n%s", len(copyCmds), s)
+	if len(copyCmds) != 32 {
+		t.Fatalf("Expected 32 commands, got %d in:\n%s", len(copyCmds), s)
 	}
 
 	ensureListContainsMatch(t, copyCmds, "^cp -f .*/aconfig_flags.pb .*/image.apex/etc/aconfig_flags.pb")
@@ -10705,6 +10969,7 @@
 }
 
 func TestAconfigFilesOnlyMatchCurrentApex(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, apex_default_bp+`
 		apex {
 			name: "myapex",
@@ -10727,6 +10992,7 @@
 			apex_available: [
 				"myapex",
 			],
+			compile_dex: true,
 		}
 
 		java_library {
@@ -10738,6 +11004,7 @@
 			apex_available: [
 				"myapex",
 			],
+			compile_dex: true,
 		}
 
 		aconfig_declarations {
@@ -10771,7 +11038,7 @@
 		}
 	`)
 
-	mod := ctx.ModuleForTests("myapex", "android_common_myapex")
+	mod := ctx.ModuleForTests(t, "myapex", "android_common_myapex")
 	combineAconfigRule := mod.Rule("All_aconfig_declarations_dump")
 	s := " " + combineAconfigRule.Args["cache_files"]
 	aconfigArgs := regexp.MustCompile(" --cache ").Split(s, -1)[1:]
@@ -10789,6 +11056,7 @@
 }
 
 func TestAconfigFilesRemoveDuplicates(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, apex_default_bp+`
 		apex {
 			name: "myapex",
@@ -10811,6 +11079,7 @@
 			apex_available: [
 				"myapex",
 			],
+			compile_dex: true,
 		}
 
 		java_library {
@@ -10822,6 +11091,7 @@
 			apex_available: [
 				"myapex",
 			],
+			compile_dex: true,
 		}
 
 		aconfig_declarations {
@@ -10848,7 +11118,7 @@
 		}
 	`)
 
-	mod := ctx.ModuleForTests("myapex", "android_common_myapex")
+	mod := ctx.ModuleForTests(t, "myapex", "android_common_myapex")
 	combineAconfigRule := mod.Rule("All_aconfig_declarations_dump")
 	s := " " + combineAconfigRule.Args["cache_files"]
 	aconfigArgs := regexp.MustCompile(" --cache ").Split(s, -1)[1:]
@@ -10868,9 +11138,10 @@
 // Test that the boot jars come from the _selected_ apex prebuilt
 // RELEASE_APEX_CONTIRBUTIONS_* build flags will be used to select the correct prebuilt for a specific release config
 func TestBootDexJarsMultipleApexPrebuilts(t *testing.T) {
+	t.Parallel()
 	checkBootDexJarPath := func(t *testing.T, ctx *android.TestContext, stem string, bootDexJarPath string) {
 		t.Helper()
-		s := ctx.ModuleForTests("dex_bootjars", "android_common")
+		s := ctx.ModuleForTests(t, "dex_bootjars", "android_common")
 		foundLibfooJar := false
 		base := stem + ".jar"
 		for _, output := range s.AllOutputs() {
@@ -10888,14 +11159,14 @@
 	// Check that the boot jars of the selected apex are run through boot_jars_package_check
 	// This validates that the jars on the bootclasspath do not contain packages outside an allowlist
 	checkBootJarsPackageCheck := func(t *testing.T, ctx *android.TestContext, expectedBootJar string) {
-		platformBcp := ctx.ModuleForTests("platform-bootclasspath", "android_common")
+		platformBcp := ctx.ModuleForTests(t, "platform-bootclasspath", "android_common")
 		bootJarsCheckRule := platformBcp.Rule("boot_jars_package_check")
 		android.AssertStringMatches(t, "Could not find the correct boot dex jar in package check rule", bootJarsCheckRule.RuleParams.Command, "build/soong/scripts/check_boot_jars/package_allowed_list.txt.*"+expectedBootJar)
 	}
 
 	// Check that the boot jars used to generate the monolithic hiddenapi flags come from the selected apex
 	checkBootJarsForMonolithicHiddenapi := func(t *testing.T, ctx *android.TestContext, expectedBootJar string) {
-		monolithicHiddenapiFlagsCmd := ctx.ModuleForTests("platform-bootclasspath", "android_common").Output("out/soong/hiddenapi/hiddenapi-stub-flags.txt").RuleParams.Command
+		monolithicHiddenapiFlagsCmd := ctx.ModuleForTests(t, "platform-bootclasspath", "android_common").Output("out/soong/hiddenapi/hiddenapi-stub-flags.txt").RuleParams.Command
 		android.AssertStringMatches(t, "Could not find the correct boot dex jar in monolithic hiddenapi flags generation command", monolithicHiddenapiFlagsCmd, "--boot-dex="+expectedBootJar)
 	}
 
@@ -11005,17 +11276,17 @@
 		{
 			desc:                      "Source apex com.android.foo is selected, bootjar should come from source java library",
 			selectedApexContributions: "foo.source.contributions",
-			expectedBootJar:           "out/soong/.intermediates/foo-bootclasspath-fragment/android_common_apex10000/hiddenapi-modular/encoded/framework-foo.jar",
+			expectedBootJar:           "out/soong/.intermediates/foo-bootclasspath-fragment/android_common_com.android.foo/hiddenapi-modular/encoded/framework-foo.jar",
 		},
 		{
 			desc:                      "Prebuilt apex prebuilt_com.android.foo is selected, profile should come from .prof deapexed from the prebuilt",
 			selectedApexContributions: "foo.prebuilt.contributions",
-			expectedBootJar:           "out/soong/.intermediates/prebuilt_com.android.foo/android_common_com.android.foo/deapexer/javalib/framework-foo.jar",
+			expectedBootJar:           "out/soong/.intermediates/prebuilt_com.android.foo/android_common_prebuilt_com.android.foo/deapexer/javalib/framework-foo.jar",
 		},
 		{
 			desc:                      "Prebuilt apex prebuilt_com.android.foo.v2 is selected, profile should come from .prof deapexed from the prebuilt",
 			selectedApexContributions: "foo.prebuilt.v2.contributions",
-			expectedBootJar:           "out/soong/.intermediates/com.android.foo.v2/android_common_com.android.foo/deapexer/javalib/framework-foo.jar",
+			expectedBootJar:           "out/soong/.intermediates/com.android.foo.v2/android_common_prebuilt_com.android.foo/deapexer/javalib/framework-foo.jar",
 		},
 	}
 
@@ -11056,21 +11327,22 @@
 // Test that product packaging installs the selected mainline module (either source or a specific prebuilt)
 // RELEASE_APEX_CONTIRBUTIONS_* build flags will be used to select the correct prebuilt for a specific release config
 func TestInstallationRulesForMultipleApexPrebuilts(t *testing.T) {
+	t.Parallel()
 	// for a mainline module family, check that only the flagged soong module is visible to make
 	checkHideFromMake := func(t *testing.T, ctx *android.TestContext, visibleModuleName string, hiddenModuleNames []string) {
 		variation := func(moduleName string) string {
-			ret := "android_common_com.android.foo"
+			ret := "android_common_prebuilt_com.android.foo"
 			if moduleName == "com.google.android.foo" {
-				ret = "android_common_com.google.android.foo_com.google.android.foo"
+				ret = "android_common_com.google.android.foo"
 			}
 			return ret
 		}
 
-		visibleModule := ctx.ModuleForTests(visibleModuleName, variation(visibleModuleName)).Module()
+		visibleModule := ctx.ModuleForTests(t, visibleModuleName, variation(visibleModuleName)).Module()
 		android.AssertBoolEquals(t, "Apex "+visibleModuleName+" selected using apex_contributions should be visible to make", false, visibleModule.IsHideFromMake())
 
 		for _, hiddenModuleName := range hiddenModuleNames {
-			hiddenModule := ctx.ModuleForTests(hiddenModuleName, variation(hiddenModuleName)).Module()
+			hiddenModule := ctx.ModuleForTests(t, hiddenModuleName, variation(hiddenModuleName)).Module()
 			android.AssertBoolEquals(t, "Apex "+hiddenModuleName+" not selected using apex_contributions should be hidden from make", true, hiddenModule.IsHideFromMake())
 
 		}
@@ -11201,23 +11473,24 @@
 
 // Test that product packaging installs the selected mainline module in workspaces withtout source mainline module
 func TestInstallationRulesForMultipleApexPrebuiltsWithoutSource(t *testing.T) {
+	t.Parallel()
 	// for a mainline module family, check that only the flagged soong module is visible to make
 	checkHideFromMake := func(t *testing.T, ctx *android.TestContext, visibleModuleNames []string, hiddenModuleNames []string) {
 		variation := func(moduleName string) string {
 			ret := "android_common_com.android.adservices"
-			if moduleName == "com.google.android.foo" {
-				ret = "android_common_com.google.android.foo_com.google.android.foo"
+			if moduleName == "com.google.android.adservices" || moduleName == "com.google.android.adservices.v2" {
+				ret = "android_common_prebuilt_com.android.adservices"
 			}
 			return ret
 		}
 
 		for _, visibleModuleName := range visibleModuleNames {
-			visibleModule := ctx.ModuleForTests(visibleModuleName, variation(visibleModuleName)).Module()
+			visibleModule := ctx.ModuleForTests(t, visibleModuleName, variation(visibleModuleName)).Module()
 			android.AssertBoolEquals(t, "Apex "+visibleModuleName+" selected using apex_contributions should be visible to make", false, visibleModule.IsHideFromMake())
 		}
 
 		for _, hiddenModuleName := range hiddenModuleNames {
-			hiddenModule := ctx.ModuleForTests(hiddenModuleName, variation(hiddenModuleName)).Module()
+			hiddenModule := ctx.ModuleForTests(t, hiddenModuleName, variation(hiddenModuleName)).Module()
 			android.AssertBoolEquals(t, "Apex "+hiddenModuleName+" not selected using apex_contributions should be hidden from make", true, hiddenModule.IsHideFromMake())
 
 		}
@@ -11281,13 +11554,13 @@
 			expectedHiddenModuleNames:  []string{"com.google.android.adservices", "com.google.android.adservices.v2"},
 		},
 		{
-			desc:                       "Prebuilt apex prebuilt_com.android.foo is selected",
+			desc:                       "Prebuilt apex prebuilt_com.android.adservices is selected",
 			selectedApexContributions:  "adservices.prebuilt.contributions",
 			expectedVisibleModuleNames: []string{"com.android.adservices", "com.google.android.adservices"},
 			expectedHiddenModuleNames:  []string{"com.google.android.adservices.v2"},
 		},
 		{
-			desc:                       "Prebuilt apex prebuilt_com.android.foo.v2 is selected",
+			desc:                       "Prebuilt apex prebuilt_com.android.adservices.v2 is selected",
 			selectedApexContributions:  "adservices.prebuilt.v2.contributions",
 			expectedVisibleModuleNames: []string{"com.android.adservices", "com.google.android.adservices.v2"},
 			expectedHiddenModuleNames:  []string{"com.google.android.adservices"},
@@ -11308,6 +11581,7 @@
 }
 
 func TestAconfifDeclarationsValidation(t *testing.T) {
+	t.Parallel()
 	aconfigDeclarationLibraryString := func(moduleNames []string) (ret string) {
 		for _, moduleName := range moduleNames {
 			ret += fmt.Sprintf(`
@@ -11341,7 +11615,7 @@
 		}
 		filegroup {
 			name: "qux-filegroup",
-			srcs: [
+			device_common_srcs: [
 				":qux-lib{.generated_srcjars}",
 			],
 		}
@@ -11388,7 +11662,7 @@
 		}
 	`+aconfigDeclarationLibraryString([]string{"bar", "baz", "qux", "quux"}))
 
-	m := result.ModuleForTests("foo.stubs.source", "android_common")
+	m := result.ModuleForTests(t, "foo.stubs.source", "android_common")
 	outDir := "out/soong/.intermediates"
 
 	// Arguments passed to aconfig to retrieve the state of the flags defined in the
@@ -11424,6 +11698,7 @@
 }
 
 func TestMultiplePrebuiltsWithSameBase(t *testing.T) {
+	t.Parallel()
 	ctx := testApex(t, `
 		apex {
 			name: "myapex",
@@ -11452,7 +11727,7 @@
 		"packages/modules/common/build/allowed_deps.txt": nil,
 	}))
 
-	ab := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*apexBundle)
+	ab := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Module().(*apexBundle)
 	data := android.AndroidMkDataForTest(t, ctx, ab)
 	var builder strings.Builder
 	data.Custom(&builder, ab.BaseModuleName(), "TARGET_", "", data)
@@ -11463,6 +11738,7 @@
 }
 
 func TestApexMinSdkVersionOverride(t *testing.T) {
+	t.Parallel()
 	checkMinSdkVersion := func(t *testing.T, module android.TestingModule, expectedMinSdkVersion string) {
 		args := module.Rule("apexRule").Args
 		optFlags := args["opt_flags"]
@@ -11498,6 +11774,7 @@
 			apex_available: ["com.android.apex30"],
 			min_sdk_version: "30",
 			sdk_version: "current",
+			compile_dex: true,
 		}
 
 		override_apex {
@@ -11522,23 +11799,24 @@
 	}),
 	)
 
-	baseModule := ctx.ModuleForTests("com.android.apex30", "android_common_com.android.apex30")
+	baseModule := ctx.ModuleForTests(t, "com.android.apex30", "android_common_com.android.apex30")
 	checkMinSdkVersion(t, baseModule, "30")
 
 	// Override module, but uses same min_sdk_version
-	overridingModuleSameMinSdkVersion := ctx.ModuleForTests("com.android.apex30", "android_common_com.mycompany.android.apex30_com.mycompany.android.apex30")
-	javalibApex30Variant := ctx.ModuleForTests("javalib", "android_common_apex30")
+	overridingModuleSameMinSdkVersion := ctx.ModuleForTests(t, "com.android.apex30", "android_common_com.mycompany.android.apex30_com.mycompany.android.apex30")
+	javalibApex30Variant := ctx.ModuleForTests(t, "javalib", "android_common_apex30")
 	checkMinSdkVersion(t, overridingModuleSameMinSdkVersion, "30")
 	checkHasDep(t, ctx, overridingModuleSameMinSdkVersion.Module(), javalibApex30Variant.Module())
 
 	// Override module, uses different min_sdk_version
-	overridingModuleDifferentMinSdkVersion := ctx.ModuleForTests("com.android.apex30", "android_common_com.mycompany.android.apex31_com.mycompany.android.apex31")
-	javalibApex31Variant := ctx.ModuleForTests("javalib", "android_common_apex31")
+	overridingModuleDifferentMinSdkVersion := ctx.ModuleForTests(t, "com.android.apex30", "android_common_com.mycompany.android.apex31_com.mycompany.android.apex31")
+	javalibApex31Variant := ctx.ModuleForTests(t, "javalib", "android_common_apex31")
 	checkMinSdkVersion(t, overridingModuleDifferentMinSdkVersion, "31")
 	checkHasDep(t, ctx, overridingModuleDifferentMinSdkVersion.Module(), javalibApex31Variant.Module())
 }
 
 func TestOverrideApexWithPrebuiltApexPreferred(t *testing.T) {
+	t.Parallel()
 	context := android.GroupFixturePreparers(
 		android.PrepareForIntegrationTestWithAndroid,
 		PrepareForTestWithApexBuildComponents,
@@ -11570,10 +11848,11 @@
 		}
 	`)
 
-	java.CheckModuleHasDependency(t, res.TestContext, "myoverrideapex", "android_common_myoverrideapex_myoverrideapex", "foo")
+	java.CheckModuleHasDependency(t, res.TestContext, "myoverrideapex", "android_common_myoverrideapex", "foo")
 }
 
 func TestUpdatableApexMinSdkVersionCurrent(t *testing.T) {
+	t.Parallel()
 	testApexError(t, `"myapex" .*: updatable: updatable APEXes should not set min_sdk_version to current. Please use a finalized API level or a recognized in-development codename`, `
 		apex {
 			name: "myapex",
@@ -11591,6 +11870,7 @@
 }
 
 func TestPrebuiltStubNoinstall(t *testing.T) {
+	t.Parallel()
 	testFunc := func(t *testing.T, expectLibfooOnSystemLib bool, fs android.MockFS) {
 		result := android.GroupFixturePreparers(
 			prepareForApexTest,
@@ -11599,7 +11879,7 @@
 			android.FixtureMergeMockFs(fs),
 		).RunTest(t)
 
-		ldRule := result.ModuleForTests("installedlib", "android_arm64_armv8-a_shared").Rule("ld")
+		ldRule := result.ModuleForTests(t, "installedlib", "android_arm64_armv8-a_shared").Rule("ld")
 		android.AssertStringDoesContain(t, "", ldRule.Args["libFlags"], "android_arm64_armv8-a_shared_current/libfoo.so")
 
 		installRules := result.InstallMakeRulesForTesting(t)
@@ -11670,6 +11950,7 @@
 	`)
 
 	t.Run("prebuilt stub (without source): no install", func(t *testing.T) {
+		t.Parallel()
 		testFunc(
 			t,
 			/*expectLibfooOnSystemLib=*/ false,
@@ -11694,6 +11975,7 @@
 	`)
 
 	t.Run("prebuilt stub (with disabled source): no install", func(t *testing.T) {
+		t.Parallel()
 		testFunc(
 			t,
 			/*expectLibfooOnSystemLib=*/ false,
@@ -11709,6 +11991,7 @@
 }
 
 func TestSdkLibraryTransitiveClassLoaderContext(t *testing.T) {
+	t.Parallel()
 	// This test case tests that listing the impl lib instead of the top level java_sdk_library
 	// in libs of android_app and java_library does not lead to class loader context device/host
 	// path mismatch errors.
@@ -11783,6 +12066,7 @@
 				"com.android.foo30",
 			],
 			sdk_version: "core_current",
+			compile_dex: true,
 		}
 
 		java_library {
@@ -11818,6 +12102,7 @@
 
 // If an apex sets system_ext_specific: true, its systemserverclasspath libraries must set this property as well.
 func TestApexSSCPJarMustBeInSamePartitionAsApex(t *testing.T) {
+	t.Parallel()
 	testApexError(t, `foo is an apex systemserver jar, but its partition does not match the partition of its containing apex`, `
 		apex {
 			name: "myapex",
@@ -11860,3 +12145,74 @@
 		dexpreopt.FixtureSetApexSystemServerJars("myapex:foo"),
 	)
 }
+
+// partitions should not package the artifacts that are included inside the apex.
+func TestFilesystemWithApexDeps(t *testing.T) {
+	t.Parallel()
+	result := testApex(t, `
+		android_filesystem {
+			name: "myfilesystem",
+			deps: ["myapex"],
+		}
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			binaries: ["binfoo"],
+			native_shared_libs: ["libfoo"],
+			apps: ["appfoo"],
+			updatable: false,
+		}
+		apex_key {
+			name: "myapex.key",
+		}
+		cc_binary {
+			name: "binfoo",
+			apex_available: ["myapex"],
+		}
+		cc_library {
+			name: "libfoo",
+			apex_available: ["myapex"],
+		}
+		android_app {
+			name: "appfoo",
+			sdk_version: "current",
+			apex_available: ["myapex"],
+		}
+	`, filesystem.PrepareForTestWithFilesystemBuildComponents)
+
+	partition := result.ModuleForTests(t, "myfilesystem", "android_common")
+	fileList := android.ContentFromFileRuleForTests(t, result, partition.Output("fileList"))
+	android.AssertDeepEquals(t, "filesystem with apex", "apex/myapex.apex\n", fileList)
+}
+
+func TestVintfFragmentInApex(t *testing.T) {
+	t.Parallel()
+	ctx := testApex(t, apex_default_bp+`
+		apex {
+			name: "myapex",
+			manifest: ":myapex.manifest",
+			androidManifest: ":myapex.androidmanifest",
+			key: "myapex.key",
+			binaries: [ "mybin" ],
+			updatable: false,
+		}
+
+		cc_binary {
+			name: "mybin",
+			srcs: ["mybin.cpp"],
+			vintf_fragment_modules: ["my_vintf_fragment.xml"],
+			apex_available: [ "myapex" ],
+		}
+
+		vintf_fragment {
+			name: "my_vintf_fragment.xml",
+			src: "my_vintf_fragment.xml",
+		}
+	`)
+
+	generateFsRule := ctx.ModuleForTests(t, "myapex", "android_common_myapex").Rule("generateFsConfig")
+	cmd := generateFsRule.RuleParams.Command
+
+	// Ensure that vintf fragment file is being installed
+	ensureContains(t, cmd, "/etc/vintf/my_vintf_fragment.xml ")
+}
diff --git a/apex/bootclasspath_fragment_test.go b/apex/bootclasspath_fragment_test.go
index e44d3f5..97644e6 100644
--- a/apex/bootclasspath_fragment_test.go
+++ b/apex/bootclasspath_fragment_test.go
@@ -47,6 +47,7 @@
 )
 
 func TestBootclasspathFragments_FragmentDependency(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForTestWithBootclasspathFragment,
 		// Configure some libraries in the art bootclasspath_fragment and platform_bootclasspath.
@@ -178,6 +179,7 @@
 }
 
 func TestBootclasspathFragmentInArtApex(t *testing.T) {
+	t.Parallel()
 	commonPreparer := android.GroupFixturePreparers(
 		prepareForTestWithBootclasspathFragment,
 		prepareForTestWithArtApex,
@@ -240,6 +242,7 @@
 					apex_available: [
 						"com.android.art",
 					],
+					min_sdk_version: "33",
 				}
 			`, content)
 		}
@@ -289,6 +292,7 @@
 					apex_available: [
 						"com.android.art",
 					],
+					min_sdk_version: "33",
 					compile_dex: true,
 				}
 			`, content, prefer)
@@ -298,6 +302,7 @@
 	}
 
 	t.Run("boot image files from source", func(t *testing.T) {
+		t.Parallel()
 		result := android.GroupFixturePreparers(
 			commonPreparer,
 
@@ -317,6 +322,7 @@
 		})
 
 		java.CheckModuleDependencies(t, result.TestContext, "com.android.art", "android_common_com.android.art", []string{
+			`all_apex_contributions`,
 			`art-bootclasspath-fragment`,
 			`com.android.art.key`,
 			`dex2oatd`,
@@ -324,11 +330,12 @@
 
 		// Make sure that the source bootclasspath_fragment copies its dex files to the predefined
 		// locations for the art image.
-		module := result.ModuleForTests("dex_bootjars", "android_common")
+		module := result.ModuleForTests(t, "dex_bootjars", "android_common")
 		checkCopiesToPredefinedLocationForArt(t, result.Config, module, "bar", "foo")
 	})
 
 	t.Run("boot image files from source of override apex", func(t *testing.T) {
+		t.Parallel()
 		result := android.GroupFixturePreparers(
 			commonPreparer,
 
@@ -349,6 +356,7 @@
 	})
 
 	t.Run("generate boot image profile even if dexpreopt is disabled", func(t *testing.T) {
+		t.Parallel()
 		result := android.GroupFixturePreparers(
 			commonPreparer,
 
@@ -369,6 +377,7 @@
 	})
 
 	t.Run("boot image disable generate profile", func(t *testing.T) {
+		t.Parallel()
 		result := android.GroupFixturePreparers(
 			commonPreparer,
 
@@ -387,6 +396,7 @@
 	})
 
 	t.Run("boot image files with preferred prebuilt", func(t *testing.T) {
+		t.Parallel()
 		result := android.GroupFixturePreparers(
 			commonPreparer,
 
@@ -411,26 +421,29 @@
 			java.FixtureSetBootImageInstallDirOnDevice("art", "apex/com.android.art/javalib"),
 		).RunTest(t)
 
-		ensureExactDeapexedContents(t, result.TestContext, "prebuilt_com.android.art", "android_common_com.android.art", []string{
+		ensureExactDeapexedContents(t, result.TestContext, "prebuilt_com.android.art", "android_common_prebuilt_com.android.art", []string{
 			"etc/boot-image.prof",
 			"javalib/bar.jar",
 			"javalib/foo.jar",
 		})
 
 		java.CheckModuleDependencies(t, result.TestContext, "com.android.art", "android_common_com.android.art", []string{
+			`all_apex_contributions`,
 			`art-bootclasspath-fragment`,
 			`com.android.art.key`,
 			`dex2oatd`,
+			`prebuilt_art-bootclasspath-fragment`,
 			`prebuilt_com.android.art`,
 		})
 
 		// Make sure that the prebuilt bootclasspath_fragment copies its dex files to the predefined
 		// locations for the art image.
-		module := result.ModuleForTests("dex_bootjars", "android_common")
+		module := result.ModuleForTests(t, "dex_bootjars", "android_common")
 		checkCopiesToPredefinedLocationForArt(t, result.Config, module, "bar", "foo")
 	})
 
 	t.Run("source with inconsistency between config and contents", func(t *testing.T) {
+		t.Parallel()
 		android.GroupFixturePreparers(
 			commonPreparer,
 
@@ -444,6 +457,7 @@
 	})
 
 	t.Run("prebuilt with inconsistency between config and contents", func(t *testing.T) {
+		t.Parallel()
 		android.GroupFixturePreparers(
 			commonPreparer,
 
@@ -457,6 +471,7 @@
 	})
 
 	t.Run("preferred prebuilt with inconsistency between config and contents", func(t *testing.T) {
+		t.Parallel()
 		android.GroupFixturePreparers(
 			commonPreparer,
 
@@ -473,6 +488,7 @@
 	})
 
 	t.Run("source preferred and prebuilt with inconsistency between config and contents", func(t *testing.T) {
+		t.Parallel()
 		android.GroupFixturePreparers(
 			commonPreparer,
 
@@ -491,6 +507,7 @@
 }
 
 func TestBootclasspathFragmentInPrebuiltArtApex(t *testing.T) {
+	t.Parallel()
 	preparers := android.GroupFixturePreparers(
 		prepareForTestWithBootclasspathFragment,
 		prepareForTestWithArtApex,
@@ -571,22 +588,23 @@
 	`
 
 	t.Run("disabled alternative APEX", func(t *testing.T) {
+		t.Parallel()
 		result := preparers.RunTestWithBp(t, fmt.Sprintf(bp, "enabled: false,"))
 
-		java.CheckModuleDependencies(t, result.TestContext, "com.android.art", "android_common_com.android.art", []string{
+		java.CheckModuleDependencies(t, result.TestContext, "com.android.art", "android_common_prebuilt_com.android.art", []string{
 			`all_apex_contributions`,
 			`dex2oatd`,
 			`prebuilt_art-bootclasspath-fragment`,
 		})
 
-		java.CheckModuleDependencies(t, result.TestContext, "art-bootclasspath-fragment", "android_common_com.android.art", []string{
+		java.CheckModuleDependencies(t, result.TestContext, "art-bootclasspath-fragment", "android_common_prebuilt_com.android.art", []string{
 			`all_apex_contributions`,
 			`dex2oatd`,
 			`prebuilt_bar`,
 			`prebuilt_foo`,
 		})
 
-		module := result.ModuleForTests("dex_bootjars", "android_common")
+		module := result.ModuleForTests(t, "dex_bootjars", "android_common")
 		checkCopiesToPredefinedLocationForArt(t, result.Config, module, "bar", "foo")
 	})
 }
@@ -614,6 +632,7 @@
 }
 
 func TestBootclasspathFragmentContentsNoName(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForTestWithBootclasspathFragment,
 		prepareForTestWithMyapex,
@@ -684,17 +703,18 @@
 	})
 
 	java.CheckModuleDependencies(t, result.TestContext, "myapex", "android_common_myapex", []string{
+		`all_apex_contributions`,
 		`dex2oatd`,
 		`myapex.key`,
 		`mybootclasspathfragment`,
 	})
 
-	apex := result.ModuleForTests("myapex", "android_common_myapex")
+	apex := result.ModuleForTests(t, "myapex", "android_common_myapex")
 	apexRule := apex.Rule("apexRule")
 	copyCommands := apexRule.Args["copy_commands"]
 
 	// Make sure that the fragment provides the hidden API encoded dex jars to the APEX.
-	fragment := result.Module("mybootclasspathfragment", "android_common_apex10000")
+	fragment := result.Module("mybootclasspathfragment", "android_common_myapex")
 
 	info, _ := android.OtherModuleProvider(result, fragment, java.BootclasspathFragmentApexContentInfoProvider)
 
@@ -710,8 +730,8 @@
 		android.AssertStringDoesContain(t, name+" apex copy command", copyCommands, expectedCopyCommand)
 	}
 
-	checkFragmentExportedDexJar("foo", "out/soong/.intermediates/mybootclasspathfragment/android_common_apex10000/hiddenapi-modular/encoded/foo.jar")
-	checkFragmentExportedDexJar("bar", "out/soong/.intermediates/mybootclasspathfragment/android_common_apex10000/hiddenapi-modular/encoded/bar.jar")
+	checkFragmentExportedDexJar("foo", "out/soong/.intermediates/mybootclasspathfragment/android_common_myapex/hiddenapi-modular/encoded/foo.jar")
+	checkFragmentExportedDexJar("bar", "out/soong/.intermediates/mybootclasspathfragment/android_common_myapex/hiddenapi-modular/encoded/bar.jar")
 }
 
 func getDexJarPath(result *android.TestResult, name string) string {
@@ -722,6 +742,7 @@
 // TestBootclasspathFragment_HiddenAPIList checks to make sure that the correct parameters are
 // passed to the hiddenapi list tool.
 func TestBootclasspathFragment_HiddenAPIList(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForTestWithBootclasspathFragment,
 		prepareForTestWithArtApex,
@@ -841,10 +862,10 @@
 		}
 	`)
 
-	java.CheckModuleDependencies(t, result.TestContext, "mybootclasspathfragment", "android_common_apex10000", []string{
+	java.CheckModuleDependencies(t, result.TestContext, "mybootclasspathfragment", "android_common_myapex", []string{
 		"all_apex_contributions",
-		"art-bootclasspath-fragment",
 		"bar",
+		"com.android.art",
 		"dex2oatd",
 		"foo",
 	})
@@ -856,7 +877,7 @@
 	quuzModuleLibStubs := getDexJarPath(result, "quuz.stubs.exportable.module_lib")
 
 	// Make sure that the fragment uses the quuz stub dex jars when generating the hidden API flags.
-	fragment := result.ModuleForTests("mybootclasspathfragment", "android_common_apex10000")
+	fragment := result.ModuleForTests(t, "mybootclasspathfragment", "android_common_myapex")
 
 	rule := fragment.Rule("modularHiddenAPIStubFlagsFile")
 	command := rule.RuleParams.Command
@@ -877,6 +898,7 @@
 // additional_stubs: ["android-non-updatable"] causes the source android-non-updatable modules to be
 // added to the hiddenapi list tool.
 func TestBootclasspathFragment_AndroidNonUpdatable_FromSource(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForTestWithBootclasspathFragment,
 		prepareForTestWithArtApex,
@@ -1013,14 +1035,14 @@
 		}
 	`)
 
-	java.CheckModuleDependencies(t, result.TestContext, "mybootclasspathfragment", "android_common_apex10000", []string{
+	java.CheckModuleDependencies(t, result.TestContext, "mybootclasspathfragment", "android_common_myapex", []string{
 		"all_apex_contributions",
 		"android-non-updatable.stubs",
 		"android-non-updatable.stubs.module_lib",
 		"android-non-updatable.stubs.system",
 		"android-non-updatable.stubs.test",
-		"art-bootclasspath-fragment",
 		"bar",
+		"com.android.art",
 		"dex2oatd",
 		"foo",
 	})
@@ -1032,7 +1054,7 @@
 
 	// Make sure that the fragment uses the android-non-updatable modules when generating the hidden
 	// API flags.
-	fragment := result.ModuleForTests("mybootclasspathfragment", "android_common_apex10000")
+	fragment := result.ModuleForTests(t, "mybootclasspathfragment", "android_common_myapex")
 
 	rule := fragment.Rule("modularHiddenAPIStubFlagsFile")
 	command := rule.RuleParams.Command
@@ -1050,6 +1072,7 @@
 }
 
 func TestBootclasspathFragment_AndroidNonUpdatable_FromText(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForTestWithBootclasspathFragment,
 		prepareForTestWithArtApex,
@@ -1186,14 +1209,14 @@
 		}
 	`)
 
-	java.CheckModuleDependencies(t, result.TestContext, "mybootclasspathfragment", "android_common_apex10000", []string{
+	java.CheckModuleDependencies(t, result.TestContext, "mybootclasspathfragment", "android_common_myapex", []string{
 		"all_apex_contributions",
 		"android-non-updatable.stubs",
 		"android-non-updatable.stubs.system",
 		"android-non-updatable.stubs.test",
 		"android-non-updatable.stubs.test_module_lib",
-		"art-bootclasspath-fragment",
 		"bar",
+		"com.android.art",
 		"dex2oatd",
 		"foo",
 	})
@@ -1202,7 +1225,7 @@
 
 	// Make sure that the fragment uses the android-non-updatable modules when generating the hidden
 	// API flags.
-	fragment := result.ModuleForTests("mybootclasspathfragment", "android_common_apex10000")
+	fragment := result.ModuleForTests(t, "mybootclasspathfragment", "android_common_myapex")
 
 	rule := fragment.Rule("modularHiddenAPIStubFlagsFile")
 	command := rule.RuleParams.Command
@@ -1217,6 +1240,7 @@
 // setting additional_stubs: ["android-non-updatable"] causes the prebuilt android-non-updatable
 // modules to be added to the hiddenapi list tool.
 func TestBootclasspathFragment_AndroidNonUpdatable_AlwaysUsePrebuiltSdks(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForTestWithBootclasspathFragment,
 		java.PrepareForTestWithDexpreopt,
@@ -1340,10 +1364,10 @@
 		}
 	`)
 
-	java.CheckModuleDependencies(t, result.TestContext, "mybootclasspathfragment", "android_common_apex10000", []string{
+	java.CheckModuleDependencies(t, result.TestContext, "mybootclasspathfragment", "android_common_myapex", []string{
 		"all_apex_contributions",
-		"art-bootclasspath-fragment",
 		"bar",
+		"com.android.art",
 		"dex2oatd",
 		"foo",
 		"prebuilt_sdk_module-lib_current_android-non-updatable",
@@ -1359,7 +1383,7 @@
 
 	// Make sure that the fragment uses the android-non-updatable modules when generating the hidden
 	// API flags.
-	fragment := result.ModuleForTests("mybootclasspathfragment", "android_common_apex10000")
+	fragment := result.ModuleForTests(t, "mybootclasspathfragment", "android_common_myapex")
 
 	rule := fragment.Rule("modularHiddenAPIStubFlagsFile")
 	command := rule.RuleParams.Command
@@ -1377,6 +1401,7 @@
 }
 
 func TestBootclasspathFragmentProtoContainsMinSdkVersion(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForTestWithBootclasspathFragment,
 		prepareForTestWithMyapex,
@@ -1441,7 +1466,7 @@
 		}
 	`)
 
-	fragment := result.ModuleForTests("mybootclasspathfragment", "android_common_apex10000")
+	fragment := result.ModuleForTests(t, "mybootclasspathfragment", "android_common_myapex")
 	classPathProtoContent := android.ContentFromFileRuleForTests(t, result.TestContext, fragment.Output("bootclasspath.pb.textproto"))
 	// foo
 	ensureContains(t, classPathProtoContent, `jars {
diff --git a/apex/builder.go b/apex/builder.go
index 371d7d5..8427719 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -20,12 +20,14 @@
 	"path"
 	"path/filepath"
 	"runtime"
+	"slices"
 	"sort"
 	"strconv"
 	"strings"
 
 	"android/soong/aconfig"
 	"android/soong/android"
+	"android/soong/dexpreopt"
 	"android/soong/java"
 
 	"github.com/google/blueprint"
@@ -43,7 +45,6 @@
 	pctx.Import("android/soong/java")
 	pctx.HostBinToolVariable("apexer", "apexer")
 	pctx.HostBinToolVariable("apexer_with_DCLA_preprocessing", "apexer_with_DCLA_preprocessing")
-	pctx.HostBinToolVariable("apexer_with_trim_preprocessing", "apexer_with_trim_preprocessing")
 
 	// ART minimal builds (using the master-art manifest) do not have the "frameworks/base"
 	// projects, and hence cannot build 'aapt2'. Use the SDK prebuilt instead.
@@ -71,9 +72,10 @@
 	pctx.HostBinToolVariable("extract_apks", "extract_apks")
 	pctx.HostBinToolVariable("make_f2fs", "make_f2fs")
 	pctx.HostBinToolVariable("sload_f2fs", "sload_f2fs")
-	pctx.HostBinToolVariable("make_erofs", "make_erofs")
+	pctx.HostBinToolVariable("make_erofs", "mkfs.erofs")
 	pctx.HostBinToolVariable("apex_compression_tool", "apex_compression_tool")
 	pctx.HostBinToolVariable("dexdeps", "dexdeps")
+	pctx.HostBinToolVariable("apex_ls", "apex-ls")
 	pctx.HostBinToolVariable("apex_sepolicy_tests", "apex_sepolicy_tests")
 	pctx.HostBinToolVariable("deapexer", "deapexer")
 	pctx.HostBinToolVariable("debugfs_static", "debugfs_static")
@@ -173,34 +175,6 @@
 	}, "tool_path", "image_dir", "copy_commands", "file_contexts", "canned_fs_config", "key",
 		"opt_flags", "manifest", "is_DCLA")
 
-	TrimmedApexRule = pctx.StaticRule("TrimmedApexRule", blueprint.RuleParams{
-		Command: `rm -rf ${image_dir} && mkdir -p ${image_dir} && ` +
-			`(. ${out}.copy_commands) && ` +
-			`APEXER_TOOL_PATH=${tool_path} ` +
-			`${apexer_with_trim_preprocessing} ` +
-			`--apexer ${apexer} ` +
-			`--canned_fs_config ${canned_fs_config} ` +
-			`--manifest ${manifest} ` +
-			`--libs_to_trim ${libs_to_trim} ` +
-			`${image_dir} ` +
-			`${out} ` +
-			`-- ` +
-			`--include_build_info ` +
-			`--force ` +
-			`--payload_type image ` +
-			`--key ${key} ` +
-			`--file_contexts ${file_contexts} ` +
-			`${opt_flags} `,
-		CommandDeps: []string{"${apexer_with_trim_preprocessing}", "${apexer}", "${avbtool}", "${e2fsdroid}",
-			"${merge_zips}", "${mke2fs}", "${resize2fs}", "${sefcontext_compile}", "${make_f2fs}",
-			"${sload_f2fs}", "${make_erofs}", "${soong_zip}", "${zipalign}", "${aapt2}",
-			"prebuilts/sdk/current/public/android.jar"},
-		Rspfile:        "${out}.copy_commands",
-		RspfileContent: "${copy_commands}",
-		Description:    "APEX ${image_dir} => ${out}",
-	}, "tool_path", "image_dir", "copy_commands", "file_contexts", "canned_fs_config", "key",
-		"opt_flags", "manifest", "libs_to_trim")
-
 	apexProtoConvertRule = pctx.AndroidStaticRule("apexProtoConvertRule",
 		blueprint.RuleParams{
 			Command:     `${aapt2} convert --output-format proto $in -o $out`,
@@ -225,7 +199,7 @@
 		Command: `diff --unchanged-group-format='' \` +
 			`--changed-group-format='%<' \` +
 			`${image_content_file} ${allowed_files_file} || (` +
-			`echo -e "New unexpected files were added to ${apex_module_name}." ` +
+			`echo "New unexpected files were added to ${apex_module_name}." ` +
 			` "To fix the build run following command:" && ` +
 			`echo "system/apex/tools/update_allowed_list.sh ${allowed_files_file} ${image_content_file}" && ` +
 			`exit 1); touch ${out}`,
@@ -239,11 +213,11 @@
 	}, "image_dir", "readelf")
 
 	apexSepolicyTestsRule = pctx.StaticRule("apexSepolicyTestsRule", blueprint.RuleParams{
-		Command: `${deapexer} --debugfs_path ${debugfs_static} list -Z ${in} > ${out}.fc` +
-			` && ${apex_sepolicy_tests} -f ${out}.fc && touch ${out}`,
-		CommandDeps: []string{"${apex_sepolicy_tests}", "${deapexer}", "${debugfs_static}"},
+		Command: `${apex_ls} -Z ${in} > ${out}.fc` +
+			` && ${apex_sepolicy_tests} -f ${out}.fc --partition ${partition_tag} && touch ${out}`,
+		CommandDeps: []string{"${apex_sepolicy_tests}", "${apex_ls}"},
 		Description: "run apex_sepolicy_tests",
-	})
+	}, "partition_tag")
 
 	apexLinkerconfigValidationRule = pctx.StaticRule("apexLinkerconfigValidationRule", blueprint.RuleParams{
 		Command:     `${conv_linker_config} validate --type apex ${image_dir} && touch ${out}`,
@@ -253,10 +227,10 @@
 
 	apexHostVerifierRule = pctx.StaticRule("apexHostVerifierRule", blueprint.RuleParams{
 		Command: `${host_apex_verifier} --deapexer=${deapexer} --debugfs=${debugfs_static} ` +
-			`--fsckerofs=${fsck_erofs} --apex=${in} && touch ${out}`,
+			`--fsckerofs=${fsck_erofs} --apex=${in} --partition_tag=${partition_tag} && touch ${out}`,
 		CommandDeps: []string{"${host_apex_verifier}", "${deapexer}", "${debugfs_static}", "${fsck_erofs}"},
 		Description: "run host_apex_verifier",
-	})
+	}, "partition_tag")
 
 	assembleVintfRule = pctx.StaticRule("assembleVintfRule", blueprint.RuleParams{
 		Command:     `rm -f $out && VINTF_IGNORE_TARGET_FCM_VERSION=true ${assemble_vintf} -i $in -o $out`,
@@ -304,6 +278,12 @@
 		})
 		files = append(files, newApexFile(ctx, apexAconfigFile, "aconfig_flags", "etc", etc, nil))
 
+		// To enable fingerprint, we need to have v2 storage files. The default version is 1.
+		storageFilesVersion := 1
+		if ctx.Config().ReleaseFingerprintAconfigPackages() {
+			storageFilesVersion = 2
+		}
+
 		for _, info := range createStorageInfo {
 			outputFile := android.PathForModuleOut(ctx, info.Output_file)
 			ctx.Build(pctx, android.BuildParams{
@@ -315,6 +295,7 @@
 					"container":   ctx.ModuleName(),
 					"file_type":   info.File_type,
 					"cache_files": android.JoinPathsWithPrefix(aconfigFiles, "--cache "),
+					"version":     strconv.Itoa(storageFilesVersion),
 				},
 			})
 			files = append(files, newApexFile(ctx, outputFile, info.File_type, "etc", etc, nil))
@@ -366,8 +347,8 @@
 		if err != nil {
 			ctx.ModuleErrorf("expected RELEASE_DEFAULT_UPDATABLE_MODULE_VERSION to be an int, but got %s", defaultVersion)
 		}
-		if defaultVersionInt%10 != 0 {
-			ctx.ModuleErrorf("expected RELEASE_DEFAULT_UPDATABLE_MODULE_VERSION to end in a zero, but got %s", defaultVersion)
+		if defaultVersionInt%10 != 0 && defaultVersionInt%10 != 9 {
+			ctx.ModuleErrorf("expected RELEASE_DEFAULT_UPDATABLE_MODULE_VERSION to end in a zero or a nine, but got %s", defaultVersion)
 		}
 		variantVersion := []rune(*a.properties.Variant_version)
 		if len(variantVersion) != 1 || variantVersion[0] < '0' || variantVersion[0] > '9' {
@@ -417,7 +398,7 @@
 // file for this APEX which is either from /systme/sepolicy/apex/<apexname>-file_contexts or from
 // the file_contexts property of this APEX. This is to make sure that the manifest file is correctly
 // labeled as system_file or vendor_apex_metadata_file.
-func (a *apexBundle) buildFileContexts(ctx android.ModuleContext) android.OutputPath {
+func (a *apexBundle) buildFileContexts(ctx android.ModuleContext) android.Path {
 	var fileContexts android.Path
 	var fileContextsDir string
 	isFileContextsModule := false
@@ -426,8 +407,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)
 	}
@@ -470,13 +453,13 @@
 	}
 
 	rule.Build("file_contexts."+a.Name(), "Generate file_contexts")
-	return output.OutputPath
+	return output
 }
 
 // buildInstalledFilesFile creates a build rule for the installed-files.txt file where the list of
 // files included in this APEX is shown. The text file is dist'ed so that people can see what's
 // included in the APEX without actually downloading and extracting it.
-func (a *apexBundle) buildInstalledFilesFile(ctx android.ModuleContext, builtApex android.Path, imageDir android.Path) android.OutputPath {
+func (a *apexBundle) buildInstalledFilesFile(ctx android.ModuleContext, builtApex android.Path, imageDir android.Path) android.Path {
 	output := android.PathForModuleOut(ctx, "installed-files.txt")
 	rule := android.NewRuleBuilder(pctx, ctx)
 	rule.Command().
@@ -486,12 +469,12 @@
 		Text(" | sort -nr > ").
 		Output(output)
 	rule.Build("installed-files."+a.Name(), "Installed files")
-	return output.OutputPath
+	return output
 }
 
 // buildBundleConfig creates a build rule for the bundle config file that will control the bundle
 // creation process.
-func (a *apexBundle) buildBundleConfig(ctx android.ModuleContext) android.OutputPath {
+func (a *apexBundle) buildBundleConfig(ctx android.ModuleContext) android.Path {
 	output := android.PathForModuleOut(ctx, "bundle_config.json")
 
 	type ApkConfig struct {
@@ -536,7 +519,7 @@
 
 	android.WriteFileRule(ctx, output, string(j))
 
-	return output.OutputPath
+	return output
 }
 
 func markManifestTestOnly(ctx android.ModuleContext, androidManifestFile android.Path) android.Path {
@@ -545,9 +528,10 @@
 	})
 }
 
-func isVintfFragment(fi apexFile) bool {
+func shouldApplyAssembleVintf(fi apexFile) bool {
 	isVintfFragment, _ := path.Match("etc/vintf/*", fi.path())
-	return isVintfFragment
+	_, fromVintfFragmentModule := fi.module.(*android.VintfFragmentModule)
+	return isVintfFragment && !fromVintfFragmentModule
 }
 
 func runAssembleVintf(ctx android.ModuleContext, vintfFragment android.Path) android.Path {
@@ -561,6 +545,64 @@
 	return processed
 }
 
+// installApexSystemServerFiles installs dexpreopt and dexjar files for system server classpath entries
+// provided by the apex.  They are installed here instead of in library module because there may be multiple
+// variants of the library, generally one for the "main" apex and another with a different min_sdk_version
+// for the Android Go version of the apex.  Both variants would attempt to install to the same locations,
+// and the library variants cannot determine which one should.  The apex module is better equipped to determine
+// if it is "selected".
+// This assumes that the jars produced by different min_sdk_version values are identical, which is currently
+// true but may not be true if the min_sdk_version difference between the variants spans version that changed
+// the dex format.
+func (a *apexBundle) installApexSystemServerFiles(ctx android.ModuleContext) {
+	// If performInstalls is set this module is responsible for creating the install rules.
+	performInstalls := a.GetOverriddenBy() == "" && !a.testApex && a.installable()
+	// TODO(b/234351700): Remove once ART does not have separated debug APEX, or make the selection
+	// explicit in the ART Android.bp files.
+	if ctx.Config().UseDebugArt() {
+		if ctx.ModuleName() == "com.android.art" {
+			performInstalls = false
+		}
+	} else {
+		if ctx.ModuleName() == "com.android.art.debug" {
+			performInstalls = false
+		}
+	}
+
+	psi := android.PrebuiltSelectionInfoMap{}
+	ctx.VisitDirectDeps(func(am android.Module) {
+		if info, exists := android.OtherModuleProvider(ctx, am, android.PrebuiltSelectionInfoProvider); exists {
+			psi = info
+		}
+	})
+
+	if len(psi.GetSelectedModulesForApiDomain(ctx.ModuleName())) > 0 {
+		performInstalls = false
+	}
+
+	for _, fi := range a.filesInfo {
+		for _, install := range fi.systemServerDexpreoptInstalls {
+			var installedFile android.InstallPath
+			if performInstalls {
+				installedFile = ctx.InstallFile(install.InstallDirOnDevice, install.InstallFileOnDevice, install.OutputPathOnHost)
+			} else {
+				// Another module created the install rules, but this module should still depend on
+				// the installed locations.
+				installedFile = install.InstallDirOnDevice.Join(ctx, install.InstallFileOnDevice)
+			}
+			a.extraInstalledFiles = append(a.extraInstalledFiles, installedFile)
+			a.extraInstalledPairs = append(a.extraInstalledPairs, installPair{install.OutputPathOnHost, installedFile})
+		}
+		if performInstalls {
+			for _, dexJar := range fi.systemServerDexJars {
+				// Copy the system server dex jar to a predefined location where dex2oat will find it.
+				android.CopyFileRule(ctx, dexJar,
+					android.PathForOutput(ctx, dexpreopt.SystemServerDexjarsDir, dexJar.Base()))
+			}
+		}
+	}
+}
+
 // buildApex creates build rules to build an APEX using apexer.
 func (a *apexBundle) buildApex(ctx android.ModuleContext) {
 	suffix := imageApexSuffix
@@ -571,7 +613,7 @@
 
 	imageDir := android.PathForModuleOut(ctx, "image"+suffix)
 
-	installSymbolFiles := (!ctx.Config().KatiEnabled() || a.ExportedToMake()) && a.installable()
+	installSymbolFiles := a.ExportedToMake() && a.installable()
 
 	// set of dependency module:location mappings
 	installMapSet := make(map[string]bool)
@@ -598,7 +640,7 @@
 			copyCommands = append(copyCommands, "ln -sfn "+pathOnDevice+" "+destPath)
 		} else {
 			// Copy the file into APEX
-			if !a.testApex && isVintfFragment(fi) {
+			if !a.testApex && shouldApplyAssembleVintf(fi) {
 				// copy the output of assemble_vintf instead of the original
 				vintfFragment := runAssembleVintf(ctx, fi.builtFile)
 				copyCommands = append(copyCommands, "cp -f "+vintfFragment.String()+" "+destPath)
@@ -621,6 +663,7 @@
 				}
 			} else {
 				if installSymbolFiles {
+					// store installedPath. symlinks might be created if required.
 					installedPath = ctx.InstallFile(apexDir.Join(ctx, fi.installDir), fi.stem(), fi.builtFile)
 				}
 			}
@@ -790,18 +833,6 @@
 	implicitInputs = append(implicitInputs, noticeAssetPath)
 	optFlags = append(optFlags, "--assets_dir "+filepath.Dir(noticeAssetPath.String()))
 
-	// Apexes which are supposed to be installed in builtin dirs(/system, etc)
-	// don't need hashtree for activation. Therefore, by removing hashtree from
-	// apex bundle (filesystem image in it, to be specific), we can save storage.
-	needHashTree := moduleMinSdkVersion.LessThanOrEqualTo(android.SdkVersion_Android10) ||
-		a.shouldGenerateHashtree()
-	if ctx.Config().ApexCompressionEnabled() && a.isCompressable() {
-		needHashTree = true
-	}
-	if !needHashTree {
-		optFlags = append(optFlags, "--no_hashtree")
-	}
-
 	if a.testOnlyShouldSkipPayloadSign() {
 		optFlags = append(optFlags, "--unsigned_payload")
 	}
@@ -830,24 +861,6 @@
 				"opt_flags":        strings.Join(optFlags, " "),
 			},
 		})
-	} else if ctx.Config().ApexTrimEnabled() && len(a.libs_to_trim(ctx)) > 0 {
-		ctx.Build(pctx, android.BuildParams{
-			Rule:        TrimmedApexRule,
-			Implicits:   implicitInputs,
-			Output:      unsignedOutputFile,
-			Description: "apex",
-			Args: map[string]string{
-				"tool_path":        outHostBinDir + ":" + prebuiltSdkToolsBinDir,
-				"image_dir":        imageDir.String(),
-				"copy_commands":    strings.Join(copyCommands, " && "),
-				"manifest":         a.manifestPbOut.String(),
-				"file_contexts":    fileContexts.String(),
-				"canned_fs_config": cannedFsConfig.String(),
-				"key":              a.privateKeyFile.String(),
-				"opt_flags":        strings.Join(optFlags, " "),
-				"libs_to_trim":     strings.Join(a.libs_to_trim(ctx), ","),
-			},
-		})
 	} else {
 		ctx.Build(pctx, android.BuildParams{
 			Rule:        apexRule,
@@ -966,17 +979,16 @@
 		args["outCommaList"] = signedOutputFile.String()
 	}
 	var validations android.Paths
-	validations = append(validations, runApexLinkerconfigValidation(ctx, unsignedOutputFile.OutputPath, imageDir.OutputPath))
-	// TODO(b/279688635) deapexer supports [ext4]
-	if !a.testApex && suffix == imageApexSuffix && ext4 == a.payloadFsType {
-		validations = append(validations, runApexSepolicyTests(ctx, unsignedOutputFile.OutputPath))
+	validations = append(validations, runApexLinkerconfigValidation(ctx, unsignedOutputFile, imageDir))
+	if !a.skipValidation(apexSepolicyTests) && android.InList(a.payloadFsType, []fsType{ext4, erofs}) {
+		validations = append(validations, runApexSepolicyTests(ctx, a, unsignedOutputFile))
 	}
 	if !a.testApex && len(a.properties.Unwanted_transitive_deps) > 0 {
 		validations = append(validations,
-			runApexElfCheckerUnwanted(ctx, unsignedOutputFile.OutputPath, a.properties.Unwanted_transitive_deps))
+			runApexElfCheckerUnwanted(ctx, unsignedOutputFile, a.properties.Unwanted_transitive_deps))
 	}
-	if !a.testApex && android.InList(a.payloadFsType, []fsType{ext4, erofs}) {
-		validations = append(validations, runApexHostVerifier(ctx, unsignedOutputFile.OutputPath))
+	if !a.skipValidation(hostApexVerifier) && android.InList(a.payloadFsType, []fsType{ext4, erofs}) {
+		validations = append(validations, runApexHostVerifier(ctx, a, unsignedOutputFile))
 	}
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        rule,
@@ -1034,9 +1046,9 @@
 		a.SkipInstall()
 	}
 
+	installDeps := slices.Concat(a.compatSymlinks, a.extraInstalledFiles)
 	// Install to $OUT/soong/{target,host}/.../apex.
-	a.installedFile = ctx.InstallFile(a.installDir, a.Name()+installSuffix, a.outputFile,
-		a.compatSymlinks...)
+	a.installedFile = ctx.InstallFile(a.installDir, a.Name()+installSuffix, a.outputFile, installDeps...)
 
 	// installed-files.txt is dist'ed
 	a.installedFilesFile = a.buildInstalledFilesFile(ctx, a.outputFile, imageDir)
@@ -1093,7 +1105,7 @@
 	}
 
 	depInfos := android.DepNameToDepInfoMap{}
-	a.WalkPayloadDeps(ctx, func(ctx android.BaseModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
+	a.WalkPayloadDeps(ctx, func(ctx android.BaseModuleContext, from android.Module, to android.ApexModule, externalDep bool) bool {
 		if from.Name() == to.Name() {
 			// This can happen for cc.reuseObjTag. We are not interested in tracking this.
 			// As soon as the dependency graph crosses the APEX boundary, don't go further.
@@ -1102,7 +1114,7 @@
 
 		// Skip dependencies that are only available to APEXes; they are developed with updatability
 		// in mind and don't need manual approval.
-		if to.(android.ApexModule).NotAvailableForPlatform() {
+		if android.OtherModuleProviderOrDefault(ctx, to, android.CommonModuleInfoKey).NotAvailableForPlatform {
 			return !externalDep
 		}
 
@@ -1120,18 +1132,9 @@
 			depInfos[to.Name()] = info
 		} else {
 			toMinSdkVersion := "(no version)"
-			if m, ok := to.(interface {
-				MinSdkVersion(ctx android.EarlyModuleContext) android.ApiLevel
-			}); ok {
-				if v := m.MinSdkVersion(ctx); !v.IsNone() {
-					toMinSdkVersion = v.String()
-				}
-			} else if m, ok := to.(interface{ MinSdkVersion() string }); ok {
-				// TODO(b/175678607) eliminate the use of MinSdkVersion returning
-				// string
-				if v := m.MinSdkVersion(); v != "" {
-					toMinSdkVersion = v
-				}
+			if info, ok := android.OtherModuleProvider(ctx, to, android.CommonModuleInfoKey); ok &&
+				!info.MinSdkVersion.IsPlatform && info.MinSdkVersion.ApiLevel != nil {
+				toMinSdkVersion = info.MinSdkVersion.ApiLevel.String()
 			}
 			depInfos[to.Name()] = android.ApexModuleDepInfo{
 				To:            to.Name(),
@@ -1179,7 +1182,7 @@
 	a.lintReports = java.BuildModuleLintReportZips(ctx, depSets, validations)
 }
 
-func (a *apexBundle) buildCannedFsConfig(ctx android.ModuleContext) android.OutputPath {
+func (a *apexBundle) buildCannedFsConfig(ctx android.ModuleContext) android.Path {
 	var readOnlyPaths = []string{"apex_manifest.json", "apex_manifest.pb"}
 	var executablePaths []string // this also includes dirs
 	var appSetDirs []string
@@ -1243,10 +1246,10 @@
 	cmd.Text(")").FlagWithOutput("> ", cannedFsConfig)
 	builder.Build("generateFsConfig", fmt.Sprintf("Generating canned fs config for %s", a.BaseModuleName()))
 
-	return cannedFsConfig.OutputPath
+	return cannedFsConfig
 }
 
-func runApexLinkerconfigValidation(ctx android.ModuleContext, apexFile android.OutputPath, imageDir android.OutputPath) android.Path {
+func runApexLinkerconfigValidation(ctx android.ModuleContext, apexFile android.Path, imageDir android.Path) android.Path {
 	timestamp := android.PathForModuleOut(ctx, "apex_linkerconfig_validation.timestamp")
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   apexLinkerconfigValidationRule,
@@ -1259,21 +1262,40 @@
 	return timestamp
 }
 
+// Can't use PartitionTag() because PartitionTag() returns the partition this module is actually
+// installed (e.g. PartitionTag() may return "system" for vendor apex when vendor is linked to /system/vendor)
+func (a *apexBundle) partition() string {
+	if a.SocSpecific() {
+		return "vendor"
+	} else if a.DeviceSpecific() {
+		return "odm"
+	} else if a.ProductSpecific() {
+		return "product"
+	} else if a.SystemExtSpecific() {
+		return "system_ext"
+	} else {
+		return "system"
+	}
+}
+
 // Runs apex_sepolicy_tests
 //
-// $ deapexer list -Z {apex_file} > {file_contexts}
+// $ apex-ls -Z {apex_file} > {file_contexts}
 // $ apex_sepolicy_tests -f {file_contexts}
-func runApexSepolicyTests(ctx android.ModuleContext, apexFile android.OutputPath) android.Path {
-	timestamp := android.PathForModuleOut(ctx, "sepolicy_tests.timestamp")
+func runApexSepolicyTests(ctx android.ModuleContext, a *apexBundle, apexFile android.Path) android.Path {
+	timestamp := android.PathForModuleOut(ctx, "apex_sepolicy_tests.timestamp")
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   apexSepolicyTestsRule,
 		Input:  apexFile,
 		Output: timestamp,
+		Args: map[string]string{
+			"partition_tag": a.partition(),
+		},
 	})
 	return timestamp
 }
 
-func runApexElfCheckerUnwanted(ctx android.ModuleContext, apexFile android.OutputPath, unwanted []string) android.Path {
+func runApexElfCheckerUnwanted(ctx android.ModuleContext, apexFile android.Path, unwanted []string) android.Path {
 	timestamp := android.PathForModuleOut(ctx, "apex_elf_unwanted.timestamp")
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   apexElfCheckerUnwantedRule,
@@ -1287,12 +1309,15 @@
 	return timestamp
 }
 
-func runApexHostVerifier(ctx android.ModuleContext, apexFile android.OutputPath) android.Path {
+func runApexHostVerifier(ctx android.ModuleContext, a *apexBundle, apexFile android.Path) android.Path {
 	timestamp := android.PathForModuleOut(ctx, "host_apex_verifier.timestamp")
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   apexHostVerifierRule,
 		Input:  apexFile,
 		Output: timestamp,
+		Args: map[string]string{
+			"partition_tag": a.partition(),
+		},
 	})
 	return timestamp
 }
diff --git a/apex/classpath_element_test.go b/apex/classpath_element_test.go
index 9e1ac94..c2f2fc5 100644
--- a/apex/classpath_element_test.go
+++ b/apex/classpath_element_test.go
@@ -40,14 +40,12 @@
 var _ java.ClasspathElementContext = (*testClasspathElementContext)(nil)
 
 func TestCreateClasspathElements(t *testing.T) {
+	t.Parallel()
 	preparer := android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
 		prepareForTestWithArtApex,
 		prepareForTestWithMyapex,
-		// For otherapex.
-		android.FixtureMergeMockFs(android.MockFS{
-			"system/sepolicy/apex/otherapex-file_contexts": nil,
-		}),
+		prepareForTestWithOtherapex,
 		java.PrepareForTestWithJavaSdkLibraryFiles,
 		java.FixtureWithLastReleaseApis("foo", "othersdklibrary"),
 		java.FixtureConfigureApexBootJars("myapex:bar"),
@@ -200,15 +198,13 @@
 
 	result := preparer.RunTest(t)
 
-	artFragment := result.Module("art-bootclasspath-fragment", "android_common_apex10000")
+	artFragment := result.Module("art-bootclasspath-fragment", "android_common_com.android.art")
 	artBaz := result.Module("baz", "android_common_apex10000")
 	artQuuz := result.Module("quuz", "android_common_apex10000")
 
-	myFragment := result.Module("mybootclasspath-fragment", "android_common_apex10000")
+	myFragment := result.Module("mybootclasspath-fragment", "android_common_myapex")
 	myBar := result.Module("bar", "android_common_apex10000")
 
-	other := result.Module("othersdklibrary", "android_common_apex10000")
-
 	otherApexLibrary := result.Module("otherapexlibrary", "android_common_apex10000")
 
 	platformFoo := result.Module("quuz", "android_common")
@@ -240,8 +236,13 @@
 
 	// Verify that CreateClasspathElements works when given valid input.
 	t.Run("art:baz, art:quuz, my:bar, foo", func(t *testing.T) {
+		t.Parallel()
 		ctx := newCtx()
-		elements := java.CreateClasspathElements(ctx, []android.Module{artBaz, artQuuz, myBar, platformFoo}, []android.Module{artFragment, myFragment})
+		elements := java.CreateClasspathElements(ctx,
+			[]android.Module{artBaz, artQuuz, myBar, platformFoo},
+			[]android.Module{artFragment, myFragment},
+			map[android.Module]string{artBaz: "com.android.art", artQuuz: "com.android.art", myBar: "myapex"},
+			map[string]android.Module{"com.android.art": artFragment, "myapex": myFragment})
 		expectedElements := java.ClasspathElements{
 			expectFragmentElement(artFragment, artBaz, artQuuz),
 			expectFragmentElement(myFragment, myBar),
@@ -250,29 +251,16 @@
 		assertElementsEquals(t, "elements", expectedElements, elements)
 	})
 
-	// Verify that CreateClasspathElements detects when an apex has multiple fragments.
-	t.Run("multiple fragments for same apex", func(t *testing.T) {
-		ctx := newCtx()
-		elements := java.CreateClasspathElements(ctx, []android.Module{}, []android.Module{artFragment, artFragment})
-		android.FailIfNoMatchingErrors(t, "apex com.android.art has multiple fragments, art-bootclasspath-fragment{.*} and art-bootclasspath-fragment{.*}", ctx.errs)
-		expectedElements := java.ClasspathElements{}
-		assertElementsEquals(t, "elements", expectedElements, elements)
-	})
-
-	// Verify that CreateClasspathElements detects when a library is in multiple fragments.
-	t.Run("library from multiple fragments", func(t *testing.T) {
-		ctx := newCtx()
-		elements := java.CreateClasspathElements(ctx, []android.Module{other}, []android.Module{artFragment, myFragment})
-		android.FailIfNoMatchingErrors(t, "library othersdklibrary{.*} is in two separate fragments, art-bootclasspath-fragment{.*} and mybootclasspath-fragment{.*}", ctx.errs)
-		expectedElements := java.ClasspathElements{}
-		assertElementsEquals(t, "elements", expectedElements, elements)
-	})
-
 	// Verify that CreateClasspathElements detects when a fragment's contents are not contiguous and
 	// are separated by a library from another fragment.
 	t.Run("discontiguous separated by fragment", func(t *testing.T) {
+		t.Parallel()
 		ctx := newCtx()
-		elements := java.CreateClasspathElements(ctx, []android.Module{artBaz, myBar, artQuuz, platformFoo}, []android.Module{artFragment, myFragment})
+		elements := java.CreateClasspathElements(ctx,
+			[]android.Module{artBaz, myBar, artQuuz, platformFoo},
+			[]android.Module{artFragment, myFragment},
+			map[android.Module]string{artBaz: "com.android.art", artQuuz: "com.android.art", myBar: "myapex"},
+			map[string]android.Module{"com.android.art": artFragment, "myapex": myFragment})
 		expectedElements := java.ClasspathElements{
 			expectFragmentElement(artFragment, artBaz, artQuuz),
 			expectFragmentElement(myFragment, myBar),
@@ -285,8 +273,13 @@
 	// Verify that CreateClasspathElements detects when a fragment's contents are not contiguous and
 	// are separated by a standalone library.
 	t.Run("discontiguous separated by library", func(t *testing.T) {
+		t.Parallel()
 		ctx := newCtx()
-		elements := java.CreateClasspathElements(ctx, []android.Module{artBaz, platformFoo, artQuuz, myBar}, []android.Module{artFragment, myFragment})
+		elements := java.CreateClasspathElements(ctx,
+			[]android.Module{artBaz, platformFoo, artQuuz, myBar},
+			[]android.Module{artFragment, myFragment},
+			map[android.Module]string{artBaz: "com.android.art", artQuuz: "com.android.art", myBar: "myapex"},
+			map[string]android.Module{"com.android.art": artFragment, "myapex": myFragment})
 		expectedElements := java.ClasspathElements{
 			expectFragmentElement(artFragment, artBaz, artQuuz),
 			expectLibraryElement(platformFoo),
@@ -300,8 +293,13 @@
 	// indicates it is from an apex the supplied fragments list does not contain a fragment for that
 	// apex.
 	t.Run("no fragment for apex", func(t *testing.T) {
+		t.Parallel()
 		ctx := newCtx()
-		elements := java.CreateClasspathElements(ctx, []android.Module{artBaz, otherApexLibrary}, []android.Module{artFragment})
+		elements := java.CreateClasspathElements(ctx,
+			[]android.Module{artBaz, otherApexLibrary},
+			[]android.Module{artFragment},
+			map[android.Module]string{artBaz: "com.android.art", otherApexLibrary: "otherapex"},
+			map[string]android.Module{"com.android.art": artFragment})
 		expectedElements := java.ClasspathElements{
 			expectFragmentElement(artFragment, artBaz),
 		}
diff --git a/apex/container_test.go b/apex/container_test.go
index d28b1a6..b19e050 100644
--- a/apex/container_test.go
+++ b/apex/container_test.go
@@ -15,10 +15,11 @@
 package apex
 
 import (
-	"android/soong/android"
-	"android/soong/java"
 	"fmt"
 	"testing"
+
+	"android/soong/android"
+	"android/soong/java"
 )
 
 var checkContainerMatch = func(t *testing.T, name string, container string, expected bool, actual bool) {
@@ -27,6 +28,8 @@
 }
 
 func TestApexDepsContainers(t *testing.T) {
+	t.Parallel()
+	t.Skip("TODO(b/394955484): this probably has to be moved to a check by the apex")
 	result := android.GroupFixturePreparers(
 		prepareForApexTest,
 		java.PrepareForTestWithJavaSdkLibraryFiles,
@@ -154,7 +157,7 @@
 	}
 
 	for _, c := range testcases {
-		m := result.ModuleForTests(c.moduleName, c.variant)
+		m := result.ModuleForTests(t, c.moduleName, c.variant)
 		containers, _ := android.OtherModuleProvider(result.TestContext.OtherModuleProviderAdaptor(), m.Module(), android.ContainersInfoProvider)
 		belongingContainers := containers.BelongingContainers()
 		checkContainerMatch(t, c.moduleName, "system", c.isSystemContainer, android.InList(android.SystemContainer, belongingContainers))
@@ -163,6 +166,8 @@
 }
 
 func TestNonUpdatableApexDepsContainers(t *testing.T) {
+	t.Parallel()
+	t.Skip("TODO(b/394955484): this probably has to be moved to a check by the apex")
 	result := android.GroupFixturePreparers(
 		prepareForApexTest,
 		java.PrepareForTestWithJavaSdkLibraryFiles,
@@ -268,7 +273,7 @@
 	}
 
 	for _, c := range testcases {
-		m := result.ModuleForTests(c.moduleName, c.variant)
+		m := result.ModuleForTests(t, c.moduleName, c.variant)
 		containers, _ := android.OtherModuleProvider(result.TestContext.OtherModuleProviderAdaptor(), m.Module(), android.ContainersInfoProvider)
 		belongingContainers := containers.BelongingContainers()
 		checkContainerMatch(t, c.moduleName, "system", c.isSystemContainer, android.InList(android.SystemContainer, belongingContainers))
@@ -277,6 +282,8 @@
 }
 
 func TestUpdatableAndNonUpdatableApexesIdenticalMinSdkVersion(t *testing.T) {
+	t.Parallel()
+	t.Skip("TODO(b/394955484): this probably has to be moved to a check by the apex")
 	result := android.GroupFixturePreparers(
 		prepareForApexTest,
 		java.PrepareForTestWithJavaSdkLibraryFiles,
@@ -326,10 +333,11 @@
 			],
 			min_sdk_version: "30",
 			sdk_version: "current",
+			compile_dex: true,
 		}
 	`)
 
-	fooApexVariant := result.ModuleForTests("foo", "android_common_apex30")
+	fooApexVariant := result.ModuleForTests(t, "foo", "android_common_apex30")
 	containers, _ := android.OtherModuleProvider(result.TestContext.OtherModuleProviderAdaptor(), fooApexVariant.Module(), android.ContainersInfoProvider)
 	belongingContainers := containers.BelongingContainers()
 	checkContainerMatch(t, "foo", "system", true, android.InList(android.SystemContainer, belongingContainers))
diff --git a/apex/dexpreopt_bootjars_test.go b/apex/dexpreopt_bootjars_test.go
index 4feade8..2c7c459 100644
--- a/apex/dexpreopt_bootjars_test.go
+++ b/apex/dexpreopt_bootjars_test.go
@@ -151,7 +151,7 @@
 	}
 	result := fixture.RunTestWithBp(t, fmt.Sprintf(bp, preferPrebuilt))
 
-	dexBootJars := result.ModuleForTests("dex_bootjars", "android_common")
+	dexBootJars := result.ModuleForTests(t, "dex_bootjars", "android_common")
 	rule := dexBootJars.Output(ruleFile)
 
 	inputs := rule.Implicits.Strings()
@@ -168,6 +168,7 @@
 }
 
 func TestDexpreoptBootJarsWithSourceArtApex(t *testing.T) {
+	t.Parallel()
 	ruleFile := "out/soong/dexpreopt_arm64/dex_bootjars/android/system/framework/arm64/boot.art"
 
 	expectedInputs := []string{
@@ -175,7 +176,7 @@
 		"out/soong/dexpreopt_arm64/dex_bootjars_input/foo.jar",
 		"out/soong/dexpreopt_arm64/dex_bootjars_input/bar.jar",
 		"out/soong/dexpreopt_arm64/dex_bootjars_input/baz.jar",
-		"out/soong/.intermediates/art-bootclasspath-fragment/android_common_apex10000/art-bootclasspath-fragment/boot.prof",
+		"out/soong/.intermediates/art-bootclasspath-fragment/android_common_com.android.art/art-bootclasspath-fragment/boot.prof",
 		"out/soong/.intermediates/default/java/dex_bootjars/android_common/boot/boot.prof",
 		"out/soong/dexpreopt/uffd_gc_flag.txt",
 	}
@@ -206,6 +207,7 @@
 // The only difference is that the ART profile should be deapexed from the prebuilt APEX. Other
 // inputs and outputs should be the same as above.
 func TestDexpreoptBootJarsWithPrebuiltArtApex(t *testing.T) {
+	t.Parallel()
 	ruleFile := "out/soong/dexpreopt_arm64/dex_bootjars/android/system/framework/arm64/boot.art"
 
 	expectedInputs := []string{
@@ -213,7 +215,7 @@
 		"out/soong/dexpreopt_arm64/dex_bootjars_input/foo.jar",
 		"out/soong/dexpreopt_arm64/dex_bootjars_input/bar.jar",
 		"out/soong/dexpreopt_arm64/dex_bootjars_input/baz.jar",
-		"out/soong/.intermediates/prebuilt_com.android.art/android_common_com.android.art/deapexer/etc/boot-image.prof",
+		"out/soong/.intermediates/prebuilt_com.android.art/android_common_prebuilt_com.android.art/deapexer/etc/boot-image.prof",
 		"out/soong/.intermediates/default/java/dex_bootjars/android_common/boot/boot.prof",
 		"out/soong/dexpreopt/uffd_gc_flag.txt",
 	}
@@ -243,6 +245,7 @@
 
 // Changes to the boot.zip structure may break the ART APK scanner.
 func TestDexpreoptBootZip(t *testing.T) {
+	t.Parallel()
 	ruleFile := "boot.zip"
 
 	ctx := android.PathContextForTesting(android.TestArchConfig("", nil, "", nil))
@@ -271,6 +274,7 @@
 // Multiple ART apexes might exist in the tree.
 // The profile should correspond to the apex selected using release build flags
 func TestDexpreoptProfileWithMultiplePrebuiltArtApexes(t *testing.T) {
+	t.Parallel()
 	ruleFile := "out/soong/dexpreopt_arm64/dex_bootjars/android/system/framework/arm64/boot.art"
 	bp := `
 		// Platform.
@@ -392,17 +396,17 @@
 		{
 			desc:                         "Source apex com.android.art is selected, profile should come from source java library",
 			selectedArtApexContributions: "art.source.contributions",
-			expectedProfile:              "out/soong/.intermediates/art-bootclasspath-fragment/android_common_apex10000/art-bootclasspath-fragment/boot.prof",
+			expectedProfile:              "out/soong/.intermediates/art-bootclasspath-fragment/android_common_com.android.art/art-bootclasspath-fragment/boot.prof",
 		},
 		{
 			desc:                         "Prebuilt apex prebuilt_com.android.art is selected, profile should come from .prof deapexed from the prebuilt",
 			selectedArtApexContributions: "art.prebuilt.contributions",
-			expectedProfile:              "out/soong/.intermediates/prebuilt_com.android.art/android_common_com.android.art/deapexer/etc/boot-image.prof",
+			expectedProfile:              "out/soong/.intermediates/prebuilt_com.android.art/android_common_prebuilt_com.android.art/deapexer/etc/boot-image.prof",
 		},
 		{
 			desc:                         "Prebuilt apex prebuilt_com.android.art.v2 is selected, profile should come from .prof deapexed from the prebuilt",
 			selectedArtApexContributions: "art.prebuilt.v2.contributions",
-			expectedProfile:              "out/soong/.intermediates/com.android.art.v2/android_common_com.android.art/deapexer/etc/boot-image.prof",
+			expectedProfile:              "out/soong/.intermediates/com.android.art.v2/android_common_prebuilt_com.android.art/deapexer/etc/boot-image.prof",
 		},
 	}
 	for _, tc := range testCases {
@@ -415,7 +419,7 @@
 			android.PrepareForTestWithBuildFlag("RELEASE_APEX_CONTRIBUTIONS_ART", tc.selectedArtApexContributions),
 		).RunTestWithBp(t, bp)
 
-		dexBootJars := result.ModuleForTests("dex_bootjars", "android_common")
+		dexBootJars := result.ModuleForTests(t, "dex_bootjars", "android_common")
 		rule := dexBootJars.Output(ruleFile)
 
 		inputs := rule.Implicits.Strings()
@@ -425,6 +429,7 @@
 
 // Check that dexpreopt works with Google mainline prebuilts even in workspaces where source is missing
 func TestDexpreoptWithMainlinePrebuiltNoSource(t *testing.T) {
+	t.Parallel()
 	bp := `
 		// Platform.
 
diff --git a/apex/key.go b/apex/key.go
index e4214f0..1622c65 100644
--- a/apex/key.go
+++ b/apex/key.go
@@ -18,6 +18,7 @@
 	"fmt"
 
 	"android/soong/android"
+	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
 
@@ -29,6 +30,7 @@
 
 func registerApexKeyBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("apex_key", ApexKeyFactory)
+	ctx.RegisterParallelSingletonModuleType("all_apex_certs", allApexCertsFactory)
 }
 
 type apexKey struct {
@@ -155,3 +157,64 @@
 	android.WriteFileRuleVerbatim(ctx, path, entry.String())
 	return path
 }
+
+var (
+	pemToDer = pctx.AndroidStaticRule("pem_to_der",
+		blueprint.RuleParams{
+			Command:     `openssl x509 -inform PEM -outform DER -in $in -out $out`,
+			Description: "Convert certificate from PEM to DER format",
+		},
+	)
+)
+
+// all_apex_certs is a singleton module that collects the certs of all apexes in the tree.
+// It provides two types of output files
+// 1. .pem: This is usually the checked-in x509 certificate in PEM format
+// 2. .der: This is DER format of the certificate, and is generated from the PEM certificate using `openssl x509`
+func allApexCertsFactory() android.SingletonModule {
+	m := &allApexCerts{}
+	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
+	return m
+}
+
+type allApexCerts struct {
+	android.SingletonModuleBase
+}
+
+func (_ *allApexCerts) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	var avbpubkeys android.Paths
+	var certificatesPem android.Paths
+	ctx.VisitDirectDeps(func(m android.Module) {
+		if apex, ok := m.(*apexBundle); ok {
+			pem, _ := apex.getCertificateAndPrivateKey(ctx)
+			if !android.ExistentPathForSource(ctx, pem.String()).Valid() {
+				if ctx.Config().AllowMissingDependencies() {
+					return
+				} else {
+					ctx.ModuleErrorf("Path %s is not valid\n", pem.String())
+				}
+			}
+			certificatesPem = append(certificatesPem, pem)
+			// avbpubkey for signing the apex payload
+			avbpubkeys = append(avbpubkeys, apex.publicKeyFile)
+		}
+	})
+	certificatesPem = android.SortedUniquePaths(certificatesPem) // For hermiticity
+	avbpubkeys = android.SortedUniquePaths(avbpubkeys)           // For hermiticity
+	var certificatesDer android.Paths
+	for index, certificatePem := range certificatesPem {
+		certificateDer := android.PathForModuleOut(ctx, fmt.Sprintf("x509.%v.der", index))
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   pemToDer,
+			Input:  certificatePem,
+			Output: certificateDer,
+		})
+		certificatesDer = append(certificatesDer, certificateDer)
+	}
+	ctx.SetOutputFiles(certificatesPem, ".pem")
+	ctx.SetOutputFiles(certificatesDer, ".der")
+	ctx.SetOutputFiles(avbpubkeys, ".avbpubkey")
+}
+
+func (_ *allApexCerts) GenerateSingletonBuildActions(ctx android.SingletonContext) {
+}
diff --git a/apex/platform_bootclasspath_test.go b/apex/platform_bootclasspath_test.go
index f4da31e..d79af86 100644
--- a/apex/platform_bootclasspath_test.go
+++ b/apex/platform_bootclasspath_test.go
@@ -23,7 +23,6 @@
 	"android/soong/dexpreopt"
 	"android/soong/java"
 
-	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
 
@@ -36,6 +35,7 @@
 )
 
 func TestPlatformBootclasspath_Fragments(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
 		prepareForTestWithMyapex,
@@ -164,12 +164,12 @@
 		android.AssertPathsRelativeToTopEquals(t, message, expected, info.FlagsFilesByCategory[category])
 	}
 
-	android.AssertPathsRelativeToTopEquals(t, "annotation flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex30/modular-hiddenapi/annotation-flags.csv"}, info.AnnotationFlagsPaths)
-	android.AssertPathsRelativeToTopEquals(t, "metadata flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex30/modular-hiddenapi/metadata.csv"}, info.MetadataPaths)
-	android.AssertPathsRelativeToTopEquals(t, "index flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex30/modular-hiddenapi/index.csv"}, info.IndexPaths)
+	android.AssertPathsRelativeToTopEquals(t, "annotation flags", []string{"out/soong/.intermediates/bar-fragment/android_common_myapex/modular-hiddenapi/annotation-flags.csv"}, info.AnnotationFlagsPaths)
+	android.AssertPathsRelativeToTopEquals(t, "metadata flags", []string{"out/soong/.intermediates/bar-fragment/android_common_myapex/modular-hiddenapi/metadata.csv"}, info.MetadataPaths)
+	android.AssertPathsRelativeToTopEquals(t, "index flags", []string{"out/soong/.intermediates/bar-fragment/android_common_myapex/modular-hiddenapi/index.csv"}, info.IndexPaths)
 
-	android.AssertArrayString(t, "stub flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex30/modular-hiddenapi/filtered-stub-flags.csv:out/soong/.intermediates/bar-fragment/android_common_apex30/modular-hiddenapi/signature-patterns.csv"}, info.StubFlagSubsets.RelativeToTop())
-	android.AssertArrayString(t, "all flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex30/modular-hiddenapi/filtered-flags.csv:out/soong/.intermediates/bar-fragment/android_common_apex30/modular-hiddenapi/signature-patterns.csv"}, info.FlagSubsets.RelativeToTop())
+	android.AssertArrayString(t, "stub flags", []string{"out/soong/.intermediates/bar-fragment/android_common_myapex/modular-hiddenapi/filtered-stub-flags.csv:out/soong/.intermediates/bar-fragment/android_common_myapex/modular-hiddenapi/signature-patterns.csv"}, info.StubFlagSubsets.RelativeToTop())
+	android.AssertArrayString(t, "all flags", []string{"out/soong/.intermediates/bar-fragment/android_common_myapex/modular-hiddenapi/filtered-flags.csv:out/soong/.intermediates/bar-fragment/android_common_myapex/modular-hiddenapi/signature-patterns.csv"}, info.FlagSubsets.RelativeToTop())
 }
 
 // TestPlatformBootclasspath_LegacyPrebuiltFragment verifies that the
@@ -178,6 +178,7 @@
 //
 // TODO: Remove once all prebuilts use the filtered_... properties.
 func TestPlatformBootclasspath_LegacyPrebuiltFragment(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
 		java.FixtureConfigureApexBootJars("myapex:foo"),
@@ -238,11 +239,12 @@
 	pbcp := result.Module("myplatform-bootclasspath", "android_common")
 	info, _ := android.OtherModuleProvider(result, pbcp, java.MonolithicHiddenAPIInfoProvider)
 
-	android.AssertArrayString(t, "stub flags", []string{"prebuilt-stub-flags.csv:out/soong/.intermediates/mybootclasspath-fragment/android_common_myapex/modular-hiddenapi/signature-patterns.csv"}, info.StubFlagSubsets.RelativeToTop())
-	android.AssertArrayString(t, "all flags", []string{"prebuilt-all-flags.csv:out/soong/.intermediates/mybootclasspath-fragment/android_common_myapex/modular-hiddenapi/signature-patterns.csv"}, info.FlagSubsets.RelativeToTop())
+	android.AssertArrayString(t, "stub flags", []string{"prebuilt-stub-flags.csv:out/soong/.intermediates/mybootclasspath-fragment/android_common_prebuilt_myapex/modular-hiddenapi/signature-patterns.csv"}, info.StubFlagSubsets.RelativeToTop())
+	android.AssertArrayString(t, "all flags", []string{"prebuilt-all-flags.csv:out/soong/.intermediates/mybootclasspath-fragment/android_common_prebuilt_myapex/modular-hiddenapi/signature-patterns.csv"}, info.FlagSubsets.RelativeToTop())
 }
 
 func TestPlatformBootclasspathDependencies(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
 		prepareForTestWithArtApex,
@@ -385,7 +387,7 @@
 	})
 
 	// Make sure that the myplatform-bootclasspath has the correct dependencies.
-	CheckModuleDependencies(t, result.TestContext, "myplatform-bootclasspath", "android_common", []string{
+	java.CheckPlatformBootclasspathDependencies(t, result.TestContext, "myplatform-bootclasspath", "android_common", []string{
 		// source vs prebuilt selection metadata module
 		`platform:all_apex_contributions`,
 
@@ -398,17 +400,17 @@
 		// Needed for generating the boot image.
 		`platform:dex2oatd`,
 
-		// The configured contents of BootJars.
-		`com.android.art:baz`,
-		`com.android.art:quuz`,
+		// The configured contents of BootJars, via their apexes if necessary.
+		`platform:com.android.art`,
+		`platform:com.android.art`,
 		`platform:foo`,
 
-		// The configured contents of ApexBootJars.
-		`myapex:bar`,
+		// The configured contents of ApexBootJars, via their apex.
+		`platform:myapex`,
 
-		// The fragments.
-		`com.android.art:art-bootclasspath-fragment`,
-		`myapex:my-bootclasspath-fragment`,
+		// The fragments via their apexes.
+		`platform:com.android.art`,
+		`platform:myapex`,
 
 		// Impl lib of sdk_library for transitive srcjar generation
 		`platform:foo.impl`,
@@ -418,6 +420,7 @@
 // TestPlatformBootclasspath_AlwaysUsePrebuiltSdks verifies that the build does not fail when
 // AlwaysUsePrebuiltSdk() returns true.
 func TestPlatformBootclasspath_AlwaysUsePrebuiltSdks(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
 		prepareForTestWithMyapex,
@@ -425,7 +428,7 @@
 		// of AlwaysUsePrebuiltsSdk(). The second is a normal library that is unaffected. The order
 		// matters, so that the dependencies resolved by the platform_bootclasspath matches the
 		// configured list.
-		java.FixtureConfigureApexBootJars("myapex:foo", "myapex:bar"),
+		java.FixtureConfigureApexBootJars("myapex:foo"),
 		java.PrepareForTestWithJavaSdkLibraryFiles,
 		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
 			variables.Always_use_prebuilt_sdks = proptools.BoolPtr(true)
@@ -475,6 +478,7 @@
 			name: "myapex",
 			src: "myapex.apex",
 			exported_bootclasspath_fragments: ["mybootclasspath-fragment"],
+			prefer: true,
 		}
 
 		// A prebuilt java_sdk_library_import that is not preferred by default but will be preferred
@@ -540,12 +544,11 @@
 
 	java.CheckPlatformBootclasspathModules(t, result, "myplatform-bootclasspath", []string{
 		// The configured contents of BootJars.
-		"myapex:prebuilt_foo",
-		"myapex:bar",
+		"prebuilt_myapex:prebuilt_foo",
 	})
 
 	// Make sure that the myplatform-bootclasspath has the correct dependencies.
-	CheckModuleDependencies(t, result.TestContext, "myplatform-bootclasspath", "android_common", []string{
+	java.CheckPlatformBootclasspathDependencies(t, result.TestContext, "myplatform-bootclasspath", "android_common", []string{
 		// source vs prebuilt selection metadata module
 		`platform:all_apex_contributions`,
 
@@ -557,43 +560,22 @@
 		// Not a prebuilt as no prebuilt existed when it was added.
 		"platform:legacy.core.platform.api.stubs.exportable",
 
-		// The platform_bootclasspath intentionally adds dependencies on both source and prebuilt
-		// modules when available as it does not know which one will be preferred.
-		"myapex:foo",
-		"myapex:prebuilt_foo",
+		// The prebuilt library via the apex.
+		"platform:prebuilt_myapex",
 
-		// Only a source module exists.
-		"myapex:bar",
-
-		// The fragments.
-		"myapex:mybootclasspath-fragment",
-		"myapex:prebuilt_mybootclasspath-fragment",
+		// The fragments via the apex.
+		"platform:prebuilt_myapex",
 
 		// Impl lib of sdk_library for transitive srcjar generation
 		"platform:foo.impl",
 	})
 }
 
-// CheckModuleDependencies checks the dependencies of the selected module against the expected list.
-//
-// The expected list must be a list of strings of the form "<apex>:<module>", where <apex> is the
-// name of the apex, or platform is it is not part of an apex and <module> is the module name.
-func CheckModuleDependencies(t *testing.T, ctx *android.TestContext, name, variant string, expected []string) {
-	t.Helper()
-	module := ctx.ModuleForTests(name, variant).Module()
-	modules := []android.Module{}
-	ctx.VisitDirectDeps(module, func(m blueprint.Module) {
-		modules = append(modules, m.(android.Module))
-	})
-
-	pairs := java.ApexNamePairsFromModules(ctx, modules)
-	android.AssertDeepEquals(t, "module dependencies", expected, pairs)
-}
-
 // TestPlatformBootclasspath_IncludesRemainingApexJars verifies that any apex boot jar is present in
 // platform_bootclasspath's classpaths.proto config, if the apex does not generate its own config
 // by setting generate_classpaths_proto property to false.
 func TestPlatformBootclasspath_IncludesRemainingApexJars(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
 		prepareForTestWithMyapex,
@@ -648,18 +630,19 @@
 		true,         // proto should be generated
 		"myapex:foo", // apex doesn't generate its own config, so must be in platform_bootclasspath
 		"bootclasspath.pb",
-		"out/soong/target/product/test_device/system/etc/classpaths",
+		"out/target/product/test_device/system/etc/classpaths",
 	)
 }
 
 func TestBootJarNotInApex(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
 		PrepareForTestWithApexBuildComponents,
 		prepareForTestWithMyapex,
 		java.FixtureConfigureApexBootJars("myapex:foo"),
 	).ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
-		`dependency "foo" of "myplatform-bootclasspath" missing variant`)).
+		`module "myplatform-bootclasspath" variant ".*": failed to find module "foo" in apex "myapex"`)).
 		RunTestWithBp(t, `
 			apex {
 				name: "myapex",
@@ -699,6 +682,7 @@
 }
 
 func TestBootFragmentNotInApex(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
 		PrepareForTestWithApexBuildComponents,
@@ -742,6 +726,7 @@
 }
 
 func TestNonBootJarInFragment(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
 		PrepareForTestWithApexBuildComponents,
@@ -800,6 +785,7 @@
 
 // Skip bcp_fragment content validation of source apexes if prebuilts are active.
 func TestNonBootJarInPrebuilts(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		description               string
 		selectedApexContributions string
@@ -922,6 +908,7 @@
 
 // Source and prebuilt apex provide different set of boot jars
 func TestNonBootJarMissingInPrebuiltFragment(t *testing.T) {
+	t.Parallel()
 	bp := `
 		apex {
 			name: "myapex",
diff --git a/apex/prebuilt.go b/apex/prebuilt.go
index 9cd5688..3daa4f8 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -15,6 +15,7 @@
 package apex
 
 import (
+	"slices"
 	"strconv"
 	"strings"
 
@@ -59,10 +60,12 @@
 	// Properties common to both prebuilt_apex and apex_set.
 	prebuiltCommonProperties *PrebuiltCommonProperties
 
-	installDir      android.InstallPath
-	installFilename string
-	installedFile   android.InstallPath
-	outputApex      android.WritablePath
+	installDir          android.InstallPath
+	installFilename     string
+	installedFile       android.InstallPath
+	extraInstalledFiles android.InstallPaths
+	extraInstalledPairs installPairs
+	outputApex          android.WritablePath
 
 	// fragment for this apex for apexkeys.txt
 	apexKeysPath android.WritablePath
@@ -70,8 +73,12 @@
 	// Installed locations of symlinks for backward compatibility.
 	compatSymlinks android.InstallPaths
 
-	hostRequired        []string
-	requiredModuleNames []string
+	// systemServerDexpreoptInstalls stores the list of dexpreopt artifacts for a system server jar.
+	systemServerDexpreoptInstalls []java.DexpreopterInstall
+
+	// systemServerDexJars stores the list of dexjars for system server jars in the prebuilt for use when
+	// dexpreopting system server jars that are later in the system server classpath.
+	systemServerDexJars android.Paths
 }
 
 type sanitizedPrebuilt interface {
@@ -188,9 +195,8 @@
 // initApexFilesForAndroidMk initializes the prebuiltCommon.requiredModuleNames field with the install only deps of the prebuilt apex
 func (p *prebuiltCommon) initApexFilesForAndroidMk(ctx android.ModuleContext) {
 	// If this apex contains a system server jar, then the dexpreopt artifacts should be added as required
-	for _, install := range p.Dexpreopter.DexpreoptBuiltInstalledForApex() {
-		p.requiredModuleNames = append(p.requiredModuleNames, install.FullModuleName())
-	}
+	p.systemServerDexpreoptInstalls = append(p.systemServerDexpreoptInstalls, p.Dexpreopter.ApexSystemServerDexpreoptInstalls()...)
+	p.systemServerDexJars = append(p.systemServerDexJars, p.Dexpreopter.ApexSystemServerDexJars()...)
 }
 
 // If this prebuilt has system server jar, create the rules to dexpreopt it and install it alongside the prebuilt apex
@@ -218,38 +224,58 @@
 	}
 }
 
-func (p *prebuiltCommon) addRequiredModules(entries *android.AndroidMkEntries) {
-	entries.AddStrings("LOCAL_REQUIRED_MODULES", p.requiredModuleNames...)
+// installApexSystemServerFiles installs dexpreopt files for system server classpath entries
+// provided by the apex.  They are installed here instead of in library module because there may be multiple
+// variants of the library, generally one for the "main" apex and another with a different min_sdk_version
+// for the Android Go version of the apex.  Both variants would attempt to install to the same locations,
+// and the library variants cannot determine which one should.  The apex module is better equipped to determine
+// if it is "selected".
+// This assumes that the jars produced by different min_sdk_version values are identical, which is currently
+// true but may not be true if the min_sdk_version difference between the variants spans version that changed
+// the dex format.
+func (p *prebuiltCommon) installApexSystemServerFiles(ctx android.ModuleContext) {
+	performInstalls := android.IsModulePreferred(ctx.Module())
+
+	for _, install := range p.systemServerDexpreoptInstalls {
+		var installedFile android.InstallPath
+		if performInstalls {
+			installedFile = ctx.InstallFile(install.InstallDirOnDevice, install.InstallFileOnDevice, install.OutputPathOnHost)
+		} else {
+			installedFile = install.InstallDirOnDevice.Join(ctx, install.InstallFileOnDevice)
+		}
+		p.extraInstalledFiles = append(p.extraInstalledFiles, installedFile)
+		p.extraInstalledPairs = append(p.extraInstalledPairs, installPair{install.OutputPathOnHost, installedFile})
+	}
+
+	for _, dexJar := range p.systemServerDexJars {
+		// Copy the system server dex jar to a predefined location where dex2oat will find it.
+		android.CopyFileRule(ctx, dexJar,
+			android.PathForOutput(ctx, dexpreopt.SystemServerDexjarsDir, dexJar.Base()))
+	}
 }
 
 func (p *prebuiltCommon) AndroidMkEntries() []android.AndroidMkEntries {
 	entriesList := []android.AndroidMkEntries{
 		{
-			Class:         "ETC",
-			OutputFile:    android.OptionalPathForPath(p.outputApex),
-			Include:       "$(BUILD_PREBUILT)",
-			Host_required: p.hostRequired,
+			Class:      "ETC",
+			OutputFile: android.OptionalPathForPath(p.outputApex),
+			Include:    "$(BUILD_PREBUILT)",
 			ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 				func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 					entries.SetString("LOCAL_MODULE_PATH", p.installDir.String())
 					entries.SetString("LOCAL_MODULE_STEM", p.installFilename)
 					entries.SetPath("LOCAL_SOONG_INSTALLED_MODULE", p.installedFile)
-					entries.SetString("LOCAL_SOONG_INSTALL_PAIRS", p.outputApex.String()+":"+p.installedFile.String())
+					installPairs := append(installPairs{{p.outputApex, p.installedFile}}, p.extraInstalledPairs...)
+					entries.SetString("LOCAL_SOONG_INSTALL_PAIRS", installPairs.String())
 					entries.AddStrings("LOCAL_SOONG_INSTALL_SYMLINKS", p.compatSymlinks.Strings()...)
 					entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", !p.installable())
 					entries.AddStrings("LOCAL_OVERRIDES_MODULES", p.prebuiltCommonProperties.Overrides...)
 					entries.SetString("LOCAL_APEX_KEY_PATH", p.apexKeysPath.String())
-					p.addRequiredModules(entries)
 				},
 			},
 		},
 	}
 
-	// Add the dexpreopt artifacts to androidmk
-	for _, install := range p.Dexpreopter.DexpreoptBuiltInstalledForApex() {
-		entriesList = append(entriesList, install.ToMakeEntries())
-	}
-
 	return entriesList
 }
 
@@ -265,6 +291,7 @@
 	for _, dep := range p.prebuiltCommonProperties.Exported_bootclasspath_fragments {
 		prebuiltDep := android.PrebuiltNameFromSource(dep)
 		ctx.AddDependency(module, exportedBootclasspathFragmentTag, prebuiltDep)
+		ctx.AddDependency(module, fragmentInApexTag, prebuiltDep)
 	}
 
 	for _, dep := range p.prebuiltCommonProperties.Exported_systemserverclasspath_fragments {
@@ -274,109 +301,47 @@
 }
 
 // Implements android.DepInInSameApex
-func (p *prebuiltCommon) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
-	tag := ctx.OtherModuleDependencyTag(dep)
+func (m *prebuiltCommon) GetDepInSameApexChecker() android.DepInSameApexChecker {
+	return ApexPrebuiltDepInSameApexChecker{}
+}
+
+type ApexPrebuiltDepInSameApexChecker struct {
+	android.BaseDepInSameApexChecker
+}
+
+func (m ApexPrebuiltDepInSameApexChecker) OutgoingDepIsInSameApex(tag blueprint.DependencyTag) bool {
 	_, ok := tag.(exportedDependencyTag)
 	return ok
 }
 
-// apexInfoMutator marks any modules for which this apex exports a file as requiring an apex
-// specific variant and checks that they are supported.
-//
-// The apexMutator will ensure that the ApexInfo objects passed to BuildForApex(ApexInfo) are
-// associated with the apex specific variant using the ApexInfoProvider for later retrieval.
-//
-// Unlike the source apex module type the prebuilt_apex module type cannot share compatible variants
-// across prebuilt_apex modules. That is because there is no way to determine whether two
-// prebuilt_apex modules that export files for the same module are compatible. e.g. they could have
-// been built from different source at different times or they could have been built with different
-// build options that affect the libraries.
-//
-// While it may be possible to provide sufficient information to determine whether two prebuilt_apex
-// modules were compatible it would be a lot of work and would not provide much benefit for a couple
-// of reasons:
-//   - The number of prebuilt_apex modules that will be exporting files for the same module will be
-//     low as the prebuilt_apex only exports files for the direct dependencies that require it and
-//     very few modules are direct dependencies of multiple prebuilt_apex modules, e.g. there are a
-//     few com.android.art* apex files that contain the same contents and could export files for the
-//     same modules but only one of them needs to do so. Contrast that with source apex modules which
-//     need apex specific variants for every module that contributes code to the apex, whether direct
-//     or indirect.
-//   - The build cost of a prebuilt_apex variant is generally low as at worst it will involve some
-//     extra copying of files. Contrast that with source apex modules that has to build each variant
-//     from source.
-func (p *prebuiltCommon) apexInfoMutator(mctx android.TopDownMutatorContext) {
-
-	// Collect direct dependencies into contents.
-	contents := make(map[string]android.ApexMembership)
-
-	// Collect the list of dependencies.
-	var dependencies []android.ApexModule
-	mctx.WalkDeps(func(child, parent android.Module) bool {
-		// If the child is not in the same apex as the parent then exit immediately and do not visit
-		// any of the child's dependencies.
-		if !android.IsDepInSameApex(mctx, parent, child) {
-			return false
-		}
-
-		tag := mctx.OtherModuleDependencyTag(child)
-		depName := mctx.OtherModuleName(child)
+func (p *prebuiltCommon) checkExportedDependenciesArePrebuilts(ctx android.ModuleContext) {
+	ctx.VisitDirectDeps(func(dep android.Module) {
+		tag := ctx.OtherModuleDependencyTag(dep)
+		depName := ctx.OtherModuleName(dep)
 		if exportedTag, ok := tag.(exportedDependencyTag); ok {
 			propertyName := exportedTag.name
 
 			// It is an error if the other module is not a prebuilt.
-			if !android.IsModulePrebuilt(child) {
-				mctx.PropertyErrorf(propertyName, "%q is not a prebuilt module", depName)
-				return false
+			if !android.IsModulePrebuilt(dep) {
+				ctx.PropertyErrorf(propertyName, "%q is not a prebuilt module", depName)
 			}
 
 			// It is an error if the other module is not an ApexModule.
-			if _, ok := child.(android.ApexModule); !ok {
-				mctx.PropertyErrorf(propertyName, "%q is not usable within an apex", depName)
-				return false
+			if _, ok := dep.(android.ApexModule); !ok {
+				ctx.PropertyErrorf(propertyName, "%q is not usable within an apex", depName)
 			}
 		}
 
-		// Ignore any modules that do not implement ApexModule as they cannot have an APEX specific
-		// variant.
-		if _, ok := child.(android.ApexModule); !ok {
-			return false
-		}
-
-		// Strip off the prebuilt_ prefix if present before storing content to ensure consistent
-		// behavior whether there is a corresponding source module present or not.
-		depName = android.RemoveOptionalPrebuiltPrefix(depName)
-
-		// Remember if this module was added as a direct dependency.
-		direct := parent == mctx.Module()
-		contents[depName] = contents[depName].Add(direct)
-
-		// Add the module to the list of dependencies that need to have an APEX variant.
-		dependencies = append(dependencies, child.(android.ApexModule))
-
-		return true
 	})
+}
 
-	// Create contents for the prebuilt_apex and store it away for later use.
-	apexContents := android.NewApexContents(contents)
-	android.SetProvider(mctx, android.ApexBundleInfoProvider, android.ApexBundleInfo{
-		Contents: apexContents,
-	})
-
-	// Create an ApexInfo for the prebuilt_apex.
-	apexVariationName := p.ApexVariationName()
-	apexInfo := android.ApexInfo{
-		ApexVariationName: apexVariationName,
-		InApexVariants:    []string{apexVariationName},
-		InApexModules:     []string{p.BaseModuleName()}, // BaseModuleName() to avoid the prebuilt_ prefix.
-		ApexContents:      []*android.ApexContents{apexContents},
+// generateApexInfo returns an android.ApexInfo configuration suitable for dependencies of this apex.
+func (p *prebuiltCommon) generateApexInfo(ctx generateApexInfoContext) android.ApexInfo {
+	return android.ApexInfo{
+		ApexVariationName: "prebuilt_" + p.ApexVariationName(),
+		BaseApexName:      p.ApexVariationName(),
 		ForPrebuiltApex:   true,
 	}
-
-	// Mark the dependencies of this module as requiring a variant for this module.
-	for _, am := range dependencies {
-		am.BuildForApex(apexInfo)
-	}
 }
 
 type Prebuilt struct {
@@ -386,7 +351,7 @@
 
 	inputApex android.Path
 
-	provenanceMetaDataFile android.OutputPath
+	provenanceMetaDataFile android.Path
 }
 
 type ApexFileProperties struct {
@@ -580,10 +545,22 @@
 	p.prebuiltApexContentsDeps(ctx)
 }
 
-var _ ApexInfoMutator = (*Prebuilt)(nil)
+var _ ApexTransitionMutator = (*Prebuilt)(nil)
 
-func (p *Prebuilt) ApexInfoMutator(mctx android.TopDownMutatorContext) {
-	p.apexInfoMutator(mctx)
+func (p *Prebuilt) ApexTransitionMutatorSplit(ctx android.BaseModuleContext) []android.ApexInfo {
+	return []android.ApexInfo{p.generateApexInfo(ctx)}
+}
+
+func (p *Prebuilt) ApexTransitionMutatorOutgoing(ctx android.OutgoingTransitionContext, sourceInfo android.ApexInfo) android.ApexInfo {
+	return sourceInfo
+}
+
+func (p *Prebuilt) ApexTransitionMutatorIncoming(ctx android.IncomingTransitionContext, outgoingInfo android.ApexInfo) android.ApexInfo {
+	return p.generateApexInfo(ctx)
+}
+
+func (p *Prebuilt) ApexTransitionMutatorMutate(ctx android.BottomUpMutatorContext, info android.ApexInfo) {
+	android.SetProvider(ctx, android.ApexBundleInfoProvider, android.ApexBundleInfo{})
 }
 
 // creates the build rules to deapex the prebuilt, and returns a deapexerInfo
@@ -649,6 +626,8 @@
 		validateApexClasspathFragments(ctx)
 	}
 
+	p.checkExportedDependenciesArePrebuilts(ctx)
+
 	p.apexKeysPath = writeApexKeys(ctx, p)
 	// TODO(jungjw): Check the key validity.
 	p.inputApex = android.PathForModuleSrc(ctx, p.properties.prebuiltApexSelector(ctx, ctx.Module()))
@@ -690,27 +669,19 @@
 	}
 
 	if p.installable() {
-		p.installedFile = ctx.InstallFile(p.installDir, p.installFilename, p.inputApex, p.compatSymlinks...)
+		p.installApexSystemServerFiles(ctx)
+		installDeps := slices.Concat(p.compatSymlinks, p.extraInstalledFiles)
+		p.installedFile = ctx.InstallFile(p.installDir, p.installFilename, p.inputApex, installDeps...)
 		p.provenanceMetaDataFile = provenance.GenerateArtifactProvenanceMetaData(ctx, p.inputApex, p.installedFile)
 	}
 
 	ctx.SetOutputFiles(android.Paths{p.outputApex}, "")
 }
 
-func (p *Prebuilt) ProvenanceMetaDataFile() android.OutputPath {
+func (p *Prebuilt) ProvenanceMetaDataFile() android.Path {
 	return p.provenanceMetaDataFile
 }
 
-// prebuiltApexExtractorModule is a private module type that is only created by the prebuilt_apex
-// module. It extracts the correct apex to use and makes it available for use by apex_set.
-type prebuiltApexExtractorModule struct {
-	android.ModuleBase
-
-	properties ApexExtractorProperties
-
-	extractedApex android.WritablePath
-}
-
 // extract registers the build actions to extract an apex from .apks file
 // returns the path of the extracted apex
 func extract(ctx android.ModuleContext, apexSet android.Path, prerelease *bool) android.Path {
@@ -817,10 +788,22 @@
 	a.prebuiltApexContentsDeps(ctx)
 }
 
-var _ ApexInfoMutator = (*ApexSet)(nil)
+var _ ApexTransitionMutator = (*ApexSet)(nil)
 
-func (a *ApexSet) ApexInfoMutator(mctx android.TopDownMutatorContext) {
-	a.apexInfoMutator(mctx)
+func (a *ApexSet) ApexTransitionMutatorSplit(ctx android.BaseModuleContext) []android.ApexInfo {
+	return []android.ApexInfo{a.generateApexInfo(ctx)}
+}
+
+func (a *ApexSet) ApexTransitionMutatorOutgoing(ctx android.OutgoingTransitionContext, sourceInfo android.ApexInfo) android.ApexInfo {
+	return sourceInfo
+}
+
+func (a *ApexSet) ApexTransitionMutatorIncoming(ctx android.IncomingTransitionContext, outgoingInfo android.ApexInfo) android.ApexInfo {
+	return a.generateApexInfo(ctx)
+}
+
+func (a *ApexSet) ApexTransitionMutatorMutate(ctx android.BottomUpMutatorContext, info android.ApexInfo) {
+	android.SetProvider(ctx, android.ApexBundleInfoProvider, android.ApexBundleInfo{})
 }
 
 func (a *ApexSet) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -877,7 +860,8 @@
 
 	a.installDir = android.PathForModuleInstall(ctx, "apex")
 	if a.installable() {
-		a.installedFile = ctx.InstallFile(a.installDir, a.installFilename, a.outputApex)
+		a.installApexSystemServerFiles(ctx)
+		a.installedFile = ctx.InstallFile(a.installDir, a.installFilename, a.outputApex, a.extraInstalledFiles...)
 	}
 
 	// in case that apex_set replaces source apex (using prefer: prop)
@@ -889,11 +873,3 @@
 
 	ctx.SetOutputFiles(android.Paths{a.outputApex}, "")
 }
-
-type systemExtContext struct {
-	android.ModuleContext
-}
-
-func (*systemExtContext) SystemExtSpecific() bool {
-	return true
-}
diff --git a/apex/systemserver_classpath_fragment_test.go b/apex/systemserver_classpath_fragment_test.go
index acb3649..cf7ea8a 100644
--- a/apex/systemserver_classpath_fragment_test.go
+++ b/apex/systemserver_classpath_fragment_test.go
@@ -29,6 +29,7 @@
 )
 
 func TestSystemserverclasspathFragmentContents(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForTestWithSystemserverclasspathFragment,
 		prepareForTestWithMyapex,
@@ -107,6 +108,7 @@
 	})
 
 	java.CheckModuleDependencies(t, ctx, "myapex", "android_common_myapex", []string{
+		`all_apex_contributions`,
 		`dex2oatd`,
 		`myapex.key`,
 		`mysystemserverclasspathfragment`,
@@ -118,6 +120,7 @@
 }
 
 func TestSystemserverclasspathFragmentNoGeneratedProto(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForTestWithSystemserverclasspathFragment,
 		prepareForTestWithMyapex,
@@ -164,6 +167,7 @@
 	})
 
 	java.CheckModuleDependencies(t, result.TestContext, "myapex", "android_common_myapex", []string{
+		`all_apex_contributions`,
 		`dex2oatd`,
 		`myapex.key`,
 		`mysystemserverclasspathfragment`,
@@ -171,6 +175,7 @@
 }
 
 func TestSystemServerClasspathFragmentWithContentNotInMake(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		prepareForTestWithSystemserverclasspathFragment,
 		prepareForTestWithMyapex,
@@ -222,6 +227,7 @@
 }
 
 func TestPrebuiltSystemserverclasspathFragmentContents(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForTestWithSystemserverclasspathFragment,
 		prepareForTestWithMyapex,
@@ -274,19 +280,19 @@
 
 	ctx := result.TestContext
 
-	java.CheckModuleDependencies(t, ctx, "myapex", "android_common_myapex", []string{
+	java.CheckModuleDependencies(t, ctx, "myapex", "android_common_prebuilt_myapex", []string{
 		`all_apex_contributions`,
 		`dex2oatd`,
 		`prebuilt_mysystemserverclasspathfragment`,
 	})
 
-	java.CheckModuleDependencies(t, ctx, "mysystemserverclasspathfragment", "android_common_myapex", []string{
+	java.CheckModuleDependencies(t, ctx, "mysystemserverclasspathfragment", "android_common_prebuilt_myapex", []string{
 		`all_apex_contributions`,
 		`prebuilt_bar`,
 		`prebuilt_foo`,
 	})
 
-	ensureExactDeapexedContents(t, ctx, "myapex", "android_common_myapex", []string{
+	ensureExactDeapexedContents(t, ctx, "myapex", "android_common_prebuilt_myapex", []string{
 		"javalib/foo.jar",
 		"javalib/bar.jar",
 		"javalib/bar.jar.prof",
@@ -297,6 +303,7 @@
 }
 
 func TestSystemserverclasspathFragmentStandaloneContents(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForTestWithSystemserverclasspathFragment,
 		prepareForTestWithMyapex,
@@ -380,6 +387,7 @@
 }
 
 func TestPrebuiltStandaloneSystemserverclasspathFragmentContents(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForTestWithSystemserverclasspathFragment,
 		prepareForTestWithMyapex,
@@ -432,13 +440,13 @@
 
 	ctx := result.TestContext
 
-	java.CheckModuleDependencies(t, ctx, "mysystemserverclasspathfragment", "android_common_myapex", []string{
+	java.CheckModuleDependencies(t, ctx, "mysystemserverclasspathfragment", "android_common_prebuilt_myapex", []string{
 		`all_apex_contributions`,
 		`prebuilt_bar`,
 		`prebuilt_foo`,
 	})
 
-	ensureExactDeapexedContents(t, ctx, "myapex", "android_common_myapex", []string{
+	ensureExactDeapexedContents(t, ctx, "myapex", "android_common_prebuilt_myapex", []string{
 		"javalib/foo.jar",
 		"javalib/bar.jar",
 		"javalib/bar.jar.prof",
@@ -449,7 +457,7 @@
 }
 
 func assertProfileGuided(t *testing.T, ctx *android.TestContext, moduleName string, variant string, expected bool) {
-	dexpreopt := ctx.ModuleForTests(moduleName, variant).Rule("dexpreopt")
+	dexpreopt := ctx.ModuleForTests(t, moduleName, variant).Rule("dexpreopt")
 	actual := strings.Contains(dexpreopt.RuleParams.Command, "--profile-file=")
 	if expected != actual {
 		t.Fatalf("Expected profile-guided to be %v, got %v", expected, actual)
@@ -457,7 +465,7 @@
 }
 
 func assertProfileGuidedPrebuilt(t *testing.T, ctx *android.TestContext, apexName string, moduleName string, expected bool) {
-	dexpreopt := ctx.ModuleForTests(apexName, "android_common_"+apexName).Rule("dexpreopt." + moduleName)
+	dexpreopt := ctx.ModuleForTests(t, apexName, "android_common_prebuilt_"+apexName).Rule("dexpreopt." + moduleName)
 	actual := strings.Contains(dexpreopt.RuleParams.Command, "--profile-file=")
 	if expected != actual {
 		t.Fatalf("Expected profile-guided to be %v, got %v", expected, actual)
diff --git a/apex/testing.go b/apex/testing.go
index 63c5b69..a22f640 100644
--- a/apex/testing.go
+++ b/apex/testing.go
@@ -22,6 +22,9 @@
 	android.FixtureRegisterWithContext(registerApexBuildComponents),
 	android.FixtureRegisterWithContext(registerApexKeyBuildComponents),
 	android.FixtureRegisterWithContext(registerApexDepsInfoComponents),
+	android.FixtureAddTextFile("all_apex_certs/Android.bp", `
+		all_apex_certs { name: "all_apex_certs" }
+	`),
 	// Additional files needed in tests that disallow non-existent source files.
 	// This includes files that are needed by all, or at least most, instances of an apex module type.
 	android.MockFS{
@@ -30,6 +33,8 @@
 		"build/soong/scripts/gen_ndk_backedby_apex.sh":         nil,
 		// Needed by prebuilt_apex.
 		"build/soong/scripts/unpack-prebuilt-apex.sh": nil,
+		// Needed by all_apex_certs
+		"build/make/target/product/security/testkey.x509.pem": nil,
 	}.AddToFixture(),
 	android.PrepareForTestWithBuildFlag("RELEASE_DEFAULT_UPDATABLE_MODULE_VERSION", testDefaultUpdatableModuleVersion),
 )
diff --git a/apex/vndk.go b/apex/vndk.go
index 5e630c0..d88808b 100644
--- a/apex/vndk.go
+++ b/apex/vndk.go
@@ -95,7 +95,11 @@
 				// level for the primary architecture.
 				a.Disable()
 			} else {
-				mctx.AddDependency(mctx.Module(), prebuiltTag, cc.VndkLibrariesTxtModules(vndkVersion, mctx)...)
+				mctx.AddVariationDependencies(
+					mctx.Config().AndroidFirstDeviceTarget.Variations(),
+					prebuiltTag,
+					cc.VndkLibrariesTxtModules(vndkVersion, mctx)...,
+				)
 			}
 		}
 	}
diff --git a/bin/get_build_vars b/bin/get_build_vars
new file mode 100755
index 0000000..aa887c7
--- /dev/null
+++ b/bin/get_build_vars
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+# 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.
+
+# Get the value of build variables.  The values are printed in a format suitable
+# for use in the import_build_vars function in build/make/shell_utils.sh
+#
+# For absolute variables, prefix the variable name with a '/'
+
+# Common script utilities
+source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../../make/shell_utils.sh
+
+require_top
+
+$TOP/build/soong/soong_ui.bash --dumpvars-mode \
+    --vars="$(printf '%s\n' "$@" | grep -v '^/')" \
+    --abs-vars="$(printf '%s\n' "$@" | grep '^/' | sed 's:^/::')" \
+    --var-prefix= \
+    --abs-var-prefix=
+
+exit $?
diff --git a/bin/mm b/bin/mm
index 6461b1e..6f1c934 100755
--- a/bin/mm
+++ b/bin/mm
@@ -19,6 +19,6 @@
 
 require_top
 
-_wrap_build "$TOP/build/soong/soong_ui.bash" --build-mode --modules-in-a-dir-no-deps --dir="$(pwd)" "$@"
+_wrap_build "$TOP/build/soong/soong_ui.bash" --build-mode --modules-in-a-dir --dir="$(pwd)" "$@"
 
 exit $?
diff --git a/bin/mmm b/bin/mmm
index ab3a632..d9190e5 100755
--- a/bin/mmm
+++ b/bin/mmm
@@ -19,6 +19,6 @@
 
 require_top
 
-_wrap_build "$TOP/build/soong/soong_ui.bash" --build-mode --modules-in-dirs-no-deps --dir="$(pwd)" "$@"
+_wrap_build "$TOP/build/soong/soong_ui.bash" --build-mode --modules-in-dirs --dir="$(pwd)" "$@"
 
 exit $?
diff --git a/bin/soongdbg b/bin/soongdbg
index 98d31eb..dad5137 100755
--- a/bin/soongdbg
+++ b/bin/soongdbg
@@ -393,12 +393,42 @@
                 print(f"    dep:      {d.id}")
 
 
+class StarCommand:
+    help = "Print the dependencies and reverse dependencies of a module"
+
+    def args(self, parser):
+        parser.add_argument("module", nargs="+",
+                            help="Module to print dependencies of")
+        parser.add_argument("--depth", type=int, required=True,
+                            help="max depth of dependencies")
+        print_args(parser)
+
+    def run(self, args):
+        graph = load_graph()
+        nodes = set()
+        err = False
+        for id in args.module:
+            root = graph.nodes.get(id)
+            if not root:
+                sys.stderr.write(f"error: Can't find root: {id}\n")
+                err = True
+                continue
+            get_deps(nodes, root, args.depth, False, set(args.tag))
+            nodes.remove(root) # Remove it so get_deps doesn't bail out
+            get_deps(nodes, root, args.depth, True, set(args.tag))
+        if err:
+            sys.exit(1)
+        print_nodes(args, nodes, new_module_formatter(args))
+
+
+
 COMMANDS = {
     "between": BetweenCommand(),
     "deps": DepsCommand(),
     "id": IdCommand(),
     "json": JsonCommand(),
     "query": QueryCommand(),
+    "star": StarCommand(),
 }
 
 
@@ -420,13 +450,17 @@
 
 
 def main():
+    global SOONG_DEBUG_DATA_FILENAME
     parser = argparse.ArgumentParser()
+    parser.add_argument("-f", "--debug-file", nargs=1, help="location of the debug info file",
+                        default=[SOONG_DEBUG_DATA_FILENAME])
     subparsers = parser.add_subparsers(required=True, dest="command")
     for name in sorted(COMMANDS.keys()):
         command = COMMANDS[name]
         subparser = subparsers.add_parser(name, help=command.help)
         command.args(subparser)
     args = parser.parse_args()
+    SOONG_DEBUG_DATA_FILENAME = args.debug_file[0]
     COMMANDS[args.command].run(args)
     sys.exit(0)
 
diff --git a/bloaty/bloaty.go b/bloaty/bloaty.go
index 8ecea98..26f2aa8 100644
--- a/bloaty/bloaty.go
+++ b/bloaty/bloaty.go
@@ -105,13 +105,11 @@
 		}
 	})
 
+	protoFilenamePath := android.PathForOutput(ctx, protoFilename)
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   bloatyMerger,
 		Inputs: android.SortedUniquePaths(deps),
-		Output: android.PathForOutput(ctx, protoFilename),
+		Output: protoFilenamePath,
 	})
-}
-
-func (singleton *sizesSingleton) MakeVars(ctx android.MakeVarsContext) {
-	ctx.DistForGoalWithFilename("checkbuild", android.PathForOutput(ctx, protoFilename), protoFilename)
+	ctx.DistForGoalWithFilename("checkbuild", protoFilenamePath, protoFilename)
 }
diff --git a/bp2build/Android.bp b/bp2build/Android.bp
deleted file mode 100644
index 28c0268..0000000
--- a/bp2build/Android.bp
+++ /dev/null
@@ -1,41 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-bootstrap_go_package {
-    name: "soong-bp2build",
-    pkgPath: "android/soong/bp2build",
-    srcs: [
-        "androidbp_to_build_templates.go",
-        "build_conversion.go",
-        "bzl_conversion.go",
-        "constants.go",
-        "conversion.go",
-    ],
-    deps: [
-        "blueprint-bootstrap",
-        "soong-aidl-library",
-        "soong-aconfig",
-        "soong-android",
-        "soong-android-allowlists",
-        "soong-android-soongconfig",
-        "soong-apex",
-        "soong-cc",
-        "soong-cc-config",
-        "soong-etc",
-        "soong-genrule",
-        "soong-linkerconfig",
-        "soong-python",
-        "soong-rust",
-        "soong-sh",
-        "soong-shared",
-        "soong-starlark-format",
-        "soong-ui-metrics",
-    ],
-    testSrcs: [
-        "conversion_test.go",
-    ],
-    pluginFor: [
-        "soong_build",
-    ],
-}
diff --git a/bp2build/androidbp_to_build_templates.go b/bp2build/androidbp_to_build_templates.go
deleted file mode 100644
index 9b21c32..0000000
--- a/bp2build/androidbp_to_build_templates.go
+++ /dev/null
@@ -1,131 +0,0 @@
-// Copyright 2020 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 bp2build
-
-const (
-	// The default `load` preamble for every generated queryview BUILD file.
-	soongModuleLoad = `package(default_visibility = ["//visibility:public"])
-load("//build/bazel/queryview_rules:soong_module.bzl", "soong_module")
-
-`
-
-	// A macro call in the BUILD file representing a Soong module, with space
-	// for expanding more attributes.
-	soongModuleTargetTemplate = `soong_module(
-    name = "%s",
-    soong_module_name = "%s",
-    soong_module_type = "%s",
-    soong_module_variant = "%s",
-    soong_module_deps = %s,
-%s)`
-
-	ruleTargetTemplate = `%s(
-    name = "%s",
-%s)`
-
-	unnamedRuleTargetTemplate = `%s(
-%s)`
-
-	// A simple provider to mark and differentiate Soong module rule shims from
-	// regular Bazel rules. Every Soong module rule shim returns a
-	// SoongModuleInfo provider, and can only depend on rules returning
-	// SoongModuleInfo in the `soong_module_deps` attribute.
-	providersBzl = `SoongModuleInfo = provider(
-    fields = {
-        "name": "Name of module",
-        "type": "Type of module",
-        "variant": "Variant of module",
-    },
-)
-`
-
-	// The soong_module rule implementation in a .bzl file.
-	soongModuleBzl = `
-%s
-
-load("//build/bazel/queryview_rules:providers.bzl", "SoongModuleInfo")
-
-def _generic_soong_module_impl(ctx):
-    return [
-        SoongModuleInfo(
-            name = ctx.attr.soong_module_name,
-            type = ctx.attr.soong_module_type,
-            variant = ctx.attr.soong_module_variant,
-        ),
-    ]
-
-generic_soong_module = rule(
-    implementation = _generic_soong_module_impl,
-    attrs = {
-        "soong_module_name": attr.string(mandatory = True),
-        "soong_module_type": attr.string(mandatory = True),
-        "soong_module_variant": attr.string(),
-        "soong_module_deps": attr.label_list(providers = [SoongModuleInfo]),
-    },
-)
-
-soong_module_rule_map = {
-%s}
-
-_SUPPORTED_TYPES = ["bool", "int", "string"]
-
-def _is_supported_type(value):
-    if type(value) in _SUPPORTED_TYPES:
-        return True
-    elif type(value) == "list":
-        supported = True
-        for v in value:
-            supported = supported and type(v) in _SUPPORTED_TYPES
-        return supported
-    else:
-        return False
-
-# soong_module is a macro that supports arbitrary kwargs, and uses soong_module_type to
-# expand to the right underlying shim.
-def soong_module(name, soong_module_type, **kwargs):
-    soong_module_rule = soong_module_rule_map.get(soong_module_type)
-
-    if soong_module_rule == None:
-        # This module type does not have an existing rule to map to, so use the
-        # generic_soong_module rule instead.
-        generic_soong_module(
-            name = name,
-            soong_module_type = soong_module_type,
-            soong_module_name = kwargs.pop("soong_module_name", ""),
-            soong_module_variant = kwargs.pop("soong_module_variant", ""),
-            soong_module_deps = kwargs.pop("soong_module_deps", []),
-        )
-    else:
-        supported_kwargs = dict()
-        for key, value in kwargs.items():
-            if _is_supported_type(value):
-                supported_kwargs[key] = value
-        soong_module_rule(
-            name = name,
-            **supported_kwargs,
-        )
-`
-
-	// A rule shim for representing a Soong module type and its properties.
-	moduleRuleShim = `
-def _%[1]s_impl(ctx):
-    return [SoongModuleInfo()]
-
-%[1]s = rule(
-    implementation = _%[1]s_impl,
-    attrs = %[2]s
-)
-`
-)
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
deleted file mode 100644
index 18213a8..0000000
--- a/bp2build/build_conversion.go
+++ /dev/null
@@ -1,618 +0,0 @@
-// Copyright 2020 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 bp2build
-
-/*
-For shareable/common functionality for conversion from soong-module to build files
-for queryview/bp2build
-*/
-
-import (
-	"fmt"
-	"reflect"
-	"sort"
-	"strings"
-
-	"android/soong/android"
-	"android/soong/starlark_fmt"
-	"github.com/google/blueprint"
-	"github.com/google/blueprint/proptools"
-)
-
-type BazelAttributes struct {
-	Attrs map[string]string
-}
-
-type BazelLoadSymbol struct {
-	// The name of the symbol in the file being loaded
-	symbol string
-	// The name the symbol wil have in this file. Can be left blank to use the same name as symbol.
-	alias string
-}
-
-type BazelLoad struct {
-	file    string
-	symbols []BazelLoadSymbol
-}
-
-type BazelTarget struct {
-	name        string
-	packageName string
-	content     string
-	ruleClass   string
-	loads       []BazelLoad
-}
-
-// Label is the fully qualified Bazel label constructed from the BazelTarget's
-// package name and target name.
-func (t BazelTarget) Label() string {
-	if t.packageName == "." {
-		return "//:" + t.name
-	} else {
-		return "//" + t.packageName + ":" + t.name
-	}
-}
-
-// PackageName returns the package of the Bazel target.
-// Defaults to root of tree.
-func (t BazelTarget) PackageName() string {
-	if t.packageName == "" {
-		return "."
-	}
-	return t.packageName
-}
-
-// BazelTargets is a typedef for a slice of BazelTarget objects.
-type BazelTargets []BazelTarget
-
-func (targets BazelTargets) packageRule() *BazelTarget {
-	for _, target := range targets {
-		if target.ruleClass == "package" {
-			return &target
-		}
-	}
-	return nil
-}
-
-// sort a list of BazelTargets in-place, by name, and by generated/handcrafted types.
-func (targets BazelTargets) sort() {
-	sort.Slice(targets, func(i, j int) bool {
-		return targets[i].name < targets[j].name
-	})
-}
-
-// String returns the string representation of BazelTargets, without load
-// statements (use LoadStatements for that), since the targets are usually not
-// adjacent to the load statements at the top of the BUILD file.
-func (targets BazelTargets) String() string {
-	var res strings.Builder
-	for i, target := range targets {
-		if target.ruleClass != "package" {
-			res.WriteString(target.content)
-		}
-		if i != len(targets)-1 {
-			res.WriteString("\n\n")
-		}
-	}
-	return res.String()
-}
-
-// LoadStatements return the string representation of the sorted and deduplicated
-// Starlark rule load statements needed by a group of BazelTargets.
-func (targets BazelTargets) LoadStatements() string {
-	// First, merge all the load statements from all the targets onto one list
-	bzlToLoadedSymbols := map[string][]BazelLoadSymbol{}
-	for _, target := range targets {
-		for _, load := range target.loads {
-		outer:
-			for _, symbol := range load.symbols {
-				alias := symbol.alias
-				if alias == "" {
-					alias = symbol.symbol
-				}
-				for _, otherSymbol := range bzlToLoadedSymbols[load.file] {
-					otherAlias := otherSymbol.alias
-					if otherAlias == "" {
-						otherAlias = otherSymbol.symbol
-					}
-					if symbol.symbol == otherSymbol.symbol && alias == otherAlias {
-						continue outer
-					} else if alias == otherAlias {
-						panic(fmt.Sprintf("Conflicting destination (%s) for loads of %s and %s", alias, symbol.symbol, otherSymbol.symbol))
-					}
-				}
-				bzlToLoadedSymbols[load.file] = append(bzlToLoadedSymbols[load.file], symbol)
-			}
-		}
-	}
-
-	var loadStatements strings.Builder
-	for i, bzl := range android.SortedKeys(bzlToLoadedSymbols) {
-		symbols := bzlToLoadedSymbols[bzl]
-		loadStatements.WriteString("load(\"")
-		loadStatements.WriteString(bzl)
-		loadStatements.WriteString("\", ")
-		sort.Slice(symbols, func(i, j int) bool {
-			if symbols[i].symbol < symbols[j].symbol {
-				return true
-			}
-			return symbols[i].alias < symbols[j].alias
-		})
-		for j, symbol := range symbols {
-			if symbol.alias != "" && symbol.alias != symbol.symbol {
-				loadStatements.WriteString(symbol.alias)
-				loadStatements.WriteString(" = ")
-			}
-			loadStatements.WriteString("\"")
-			loadStatements.WriteString(symbol.symbol)
-			loadStatements.WriteString("\"")
-			if j != len(symbols)-1 {
-				loadStatements.WriteString(", ")
-			}
-		}
-		loadStatements.WriteString(")")
-		if i != len(bzlToLoadedSymbols)-1 {
-			loadStatements.WriteString("\n")
-		}
-	}
-	return loadStatements.String()
-}
-
-type bpToBuildContext interface {
-	ModuleName(module blueprint.Module) string
-	ModuleDir(module blueprint.Module) string
-	ModuleSubDir(module blueprint.Module) string
-	ModuleType(module blueprint.Module) string
-
-	VisitAllModules(visit func(blueprint.Module))
-	VisitDirectDeps(module blueprint.Module, visit func(blueprint.Module))
-}
-
-type CodegenContext struct {
-	config         android.Config
-	context        *android.Context
-	mode           CodegenMode
-	additionalDeps []string
-	topDir         string
-}
-
-func (ctx *CodegenContext) Mode() CodegenMode {
-	return ctx.mode
-}
-
-// CodegenMode is an enum to differentiate code-generation modes.
-type CodegenMode int
-
-const (
-	// QueryView - generate BUILD files with targets representing fully mutated
-	// Soong modules, representing the fully configured Soong module graph with
-	// variants and dependency edges.
-	//
-	// This mode is used for discovering and introspecting the existing Soong
-	// module graph.
-	QueryView CodegenMode = iota
-)
-
-func (mode CodegenMode) String() string {
-	switch mode {
-	case QueryView:
-		return "QueryView"
-	default:
-		return fmt.Sprintf("%d", mode)
-	}
-}
-
-// AddNinjaFileDeps adds dependencies on the specified files to be added to the ninja manifest. The
-// primary builder will be rerun whenever the specified files are modified. Allows us to fulfill the
-// PathContext interface in order to add dependencies on hand-crafted BUILD files. Note: must also
-// call AdditionalNinjaDeps and add them manually to the ninja file.
-func (ctx *CodegenContext) AddNinjaFileDeps(deps ...string) {
-	ctx.additionalDeps = append(ctx.additionalDeps, deps...)
-}
-
-// AdditionalNinjaDeps returns additional ninja deps added by CodegenContext
-func (ctx *CodegenContext) AdditionalNinjaDeps() []string {
-	return ctx.additionalDeps
-}
-
-func (ctx *CodegenContext) Config() android.Config    { return ctx.config }
-func (ctx *CodegenContext) Context() *android.Context { return ctx.context }
-
-// NewCodegenContext creates a wrapper context that conforms to PathContext for
-// writing BUILD files in the output directory.
-func NewCodegenContext(config android.Config, context *android.Context, mode CodegenMode, topDir string) *CodegenContext {
-	return &CodegenContext{
-		context: context,
-		config:  config,
-		mode:    mode,
-		topDir:  topDir,
-	}
-}
-
-// props is an unsorted map. This function ensures that
-// the generated attributes are sorted to ensure determinism.
-func propsToAttributes(props map[string]string) string {
-	var attributes string
-	for _, propName := range android.SortedKeys(props) {
-		attributes += fmt.Sprintf("    %s = %s,\n", propName, props[propName])
-	}
-	return attributes
-}
-
-type conversionResults struct {
-	buildFileToTargets    map[string]BazelTargets
-	moduleNameToPartition map[string]string
-}
-
-func (r conversionResults) BuildDirToTargets() map[string]BazelTargets {
-	return r.buildFileToTargets
-}
-
-func GenerateBazelTargets(ctx *CodegenContext, generateFilegroups bool) (conversionResults, []error) {
-	ctx.Context().BeginEvent("GenerateBazelTargets")
-	defer ctx.Context().EndEvent("GenerateBazelTargets")
-	buildFileToTargets := make(map[string]BazelTargets)
-
-	dirs := make(map[string]bool)
-	moduleNameToPartition := make(map[string]string)
-
-	var errs []error
-
-	bpCtx := ctx.Context()
-	bpCtx.VisitAllModules(func(m blueprint.Module) {
-		dir := bpCtx.ModuleDir(m)
-		dirs[dir] = true
-
-		var targets []BazelTarget
-
-		switch ctx.Mode() {
-		case QueryView:
-			// Blocklist certain module types from being generated.
-			if canonicalizeModuleType(bpCtx.ModuleType(m)) == "package" {
-				// package module name contain slashes, and thus cannot
-				// be mapped cleanly to a bazel label.
-				return
-			}
-			t, err := generateSoongModuleTarget(bpCtx, m)
-			if err != nil {
-				errs = append(errs, err)
-			}
-			targets = append(targets, t)
-		default:
-			errs = append(errs, fmt.Errorf("Unknown code-generation mode: %s", ctx.Mode()))
-			return
-		}
-
-		for _, target := range targets {
-			targetDir := target.PackageName()
-			buildFileToTargets[targetDir] = append(buildFileToTargets[targetDir], target)
-		}
-	})
-
-	if len(errs) > 0 {
-		return conversionResults{}, errs
-	}
-
-	if generateFilegroups {
-		// Add a filegroup target that exposes all sources in the subtree of this package
-		// NOTE: This also means we generate a BUILD file for every Android.bp file (as long as it has at least one module)
-		//
-		// This works because: https://bazel.build/reference/be/functions#exports_files
-		// "As a legacy behaviour, also files mentioned as input to a rule are exported with the
-		// default visibility until the flag --incompatible_no_implicit_file_export is flipped. However, this behavior
-		// should not be relied upon and actively migrated away from."
-		//
-		// TODO(b/198619163): We should change this to export_files(glob(["**/*"])) instead, but doing that causes these errors:
-		// "Error in exports_files: generated label '//external/avb:avbtool' conflicts with existing py_binary rule"
-		// So we need to solve all the "target ... is both a rule and a file" warnings first.
-		for dir := range dirs {
-			buildFileToTargets[dir] = append(buildFileToTargets[dir], BazelTarget{
-				name:      "bp2build_all_srcs",
-				content:   `filegroup(name = "bp2build_all_srcs", srcs = glob(["**/*"]), tags = ["manual"])`,
-				ruleClass: "filegroup",
-			})
-		}
-	}
-
-	return conversionResults{
-		buildFileToTargets:    buildFileToTargets,
-		moduleNameToPartition: moduleNameToPartition,
-	}, errs
-}
-
-// Convert a module and its deps and props into a Bazel macro/rule
-// representation in the BUILD file.
-func generateSoongModuleTarget(ctx bpToBuildContext, m blueprint.Module) (BazelTarget, error) {
-	props, err := getBuildProperties(ctx, m)
-
-	// TODO(b/163018919): DirectDeps can have duplicate (module, variant)
-	// items, if the modules are added using different DependencyTag. Figure
-	// out the implications of that.
-	depLabels := map[string]bool{}
-	if aModule, ok := m.(android.Module); ok {
-		ctx.VisitDirectDeps(aModule, func(depModule blueprint.Module) {
-			depLabels[qualifiedTargetLabel(ctx, depModule)] = true
-		})
-	}
-
-	for p := range ignoredPropNames {
-		delete(props.Attrs, p)
-	}
-	attributes := propsToAttributes(props.Attrs)
-
-	depLabelList := "[\n"
-	for depLabel := range depLabels {
-		depLabelList += fmt.Sprintf("        %q,\n", depLabel)
-	}
-	depLabelList += "    ]"
-
-	targetName := targetNameWithVariant(ctx, m)
-	return BazelTarget{
-		name:        targetName,
-		packageName: ctx.ModuleDir(m),
-		content: fmt.Sprintf(
-			soongModuleTargetTemplate,
-			targetName,
-			ctx.ModuleName(m),
-			canonicalizeModuleType(ctx.ModuleType(m)),
-			ctx.ModuleSubDir(m),
-			depLabelList,
-			attributes),
-	}, err
-}
-
-func getBuildProperties(ctx bpToBuildContext, m blueprint.Module) (BazelAttributes, error) {
-	// TODO: this omits properties for blueprint modules (blueprint_go_binary,
-	// bootstrap_go_binary, bootstrap_go_package), which will have to be handled separately.
-	if aModule, ok := m.(android.Module); ok {
-		return extractModuleProperties(aModule.GetProperties(), false)
-	}
-
-	return BazelAttributes{}, nil
-}
-
-// Generically extract module properties and types into a map, keyed by the module property name.
-func extractModuleProperties(props []interface{}, checkForDuplicateProperties bool) (BazelAttributes, error) {
-	ret := map[string]string{}
-
-	// Iterate over this android.Module's property structs.
-	for _, properties := range props {
-		propertiesValue := reflect.ValueOf(properties)
-		// Check that propertiesValue is a pointer to the Properties struct, like
-		// *cc.BaseLinkerProperties or *java.CompilerProperties.
-		//
-		// propertiesValue can also be type-asserted to the structs to
-		// manipulate internal props, if needed.
-		if isStructPtr(propertiesValue.Type()) {
-			structValue := propertiesValue.Elem()
-			ok, err := extractStructProperties(structValue, 0)
-			if err != nil {
-				return BazelAttributes{}, err
-			}
-			for k, v := range ok {
-				if existing, exists := ret[k]; checkForDuplicateProperties && exists {
-					return BazelAttributes{}, fmt.Errorf(
-						"%s (%v) is present in properties whereas it should be consolidated into a commonAttributes",
-						k, existing)
-				}
-				ret[k] = v
-			}
-		} else {
-			return BazelAttributes{},
-				fmt.Errorf(
-					"properties must be a pointer to a struct, got %T",
-					propertiesValue.Interface())
-		}
-	}
-
-	return BazelAttributes{
-		Attrs: ret,
-	}, nil
-}
-
-func isStructPtr(t reflect.Type) bool {
-	return t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct
-}
-
-// prettyPrint a property value into the equivalent Starlark representation
-// recursively.
-func prettyPrint(propertyValue reflect.Value, indent int, emitZeroValues bool) (string, error) {
-	if !emitZeroValues && isZero(propertyValue) {
-		// A property value being set or unset actually matters -- Soong does set default
-		// values for unset properties, like system_shared_libs = ["libc", "libm", "libdl"] at
-		// https://cs.android.com/android/platform/superproject/+/main:build/soong/cc/linker.go;l=281-287;drc=f70926eef0b9b57faf04c17a1062ce50d209e480
-		//
-		// In Bazel-parlance, we would use "attr.<type>(default = <default
-		// value>)" to set the default value of unset attributes. In the cases
-		// where the bp2build converter didn't set the default value within the
-		// mutator when creating the BazelTargetModule, this would be a zero
-		// value. For those cases, we return an empty string so we don't
-		// unnecessarily generate empty values.
-		return "", nil
-	}
-
-	switch propertyValue.Kind() {
-	case reflect.String:
-		return fmt.Sprintf("\"%v\"", escapeString(propertyValue.String())), nil
-	case reflect.Bool:
-		return starlark_fmt.PrintBool(propertyValue.Bool()), nil
-	case reflect.Int, reflect.Uint, reflect.Int64:
-		return fmt.Sprintf("%v", propertyValue.Interface()), nil
-	case reflect.Ptr:
-		return prettyPrint(propertyValue.Elem(), indent, emitZeroValues)
-	case reflect.Slice:
-		elements := make([]string, 0, propertyValue.Len())
-		for i := 0; i < propertyValue.Len(); i++ {
-			val, err := prettyPrint(propertyValue.Index(i), indent, emitZeroValues)
-			if err != nil {
-				return "", err
-			}
-			if val != "" {
-				elements = append(elements, val)
-			}
-		}
-		return starlark_fmt.PrintList(elements, indent, func(s string) string {
-			return "%s"
-		}), nil
-
-	case reflect.Struct:
-		// Sort and print the struct props by the key.
-		structProps, err := extractStructProperties(propertyValue, indent)
-
-		if err != nil {
-			return "", err
-		}
-
-		if len(structProps) == 0 {
-			return "", nil
-		}
-		return starlark_fmt.PrintDict(structProps, indent), nil
-	case reflect.Interface:
-		// TODO(b/164227191): implement pretty print for interfaces.
-		// Interfaces are used for for arch, multilib and target properties.
-		return "", nil
-	case reflect.Map:
-		if v, ok := propertyValue.Interface().(map[string]string); ok {
-			return starlark_fmt.PrintStringStringDict(v, indent), nil
-		}
-		return "", fmt.Errorf("bp2build expects map of type map[string]string for field: %s", propertyValue)
-	default:
-		return "", fmt.Errorf(
-			"unexpected kind for property struct field: %s", propertyValue.Kind())
-	}
-}
-
-// Converts a reflected property struct value into a map of property names and property values,
-// which each property value correctly pretty-printed and indented at the right nest level,
-// since property structs can be nested. In Starlark, nested structs are represented as nested
-// dicts: https://docs.bazel.build/skylark/lib/dict.html
-func extractStructProperties(structValue reflect.Value, indent int) (map[string]string, error) {
-	if structValue.Kind() != reflect.Struct {
-		return map[string]string{}, fmt.Errorf("Expected a reflect.Struct type, but got %s", structValue.Kind())
-	}
-
-	var err error
-
-	ret := map[string]string{}
-	structType := structValue.Type()
-	for i := 0; i < structValue.NumField(); i++ {
-		field := structType.Field(i)
-		if shouldSkipStructField(field) {
-			continue
-		}
-
-		fieldValue := structValue.Field(i)
-		if isZero(fieldValue) {
-			// Ignore zero-valued fields
-			continue
-		}
-
-		// if the struct is embedded (anonymous), flatten the properties into the containing struct
-		if field.Anonymous {
-			if field.Type.Kind() == reflect.Ptr {
-				fieldValue = fieldValue.Elem()
-			}
-			if fieldValue.Type().Kind() == reflect.Struct {
-				propsToMerge, err := extractStructProperties(fieldValue, indent)
-				if err != nil {
-					return map[string]string{}, err
-				}
-				for prop, value := range propsToMerge {
-					ret[prop] = value
-				}
-				continue
-			}
-		}
-
-		propertyName := proptools.PropertyNameForField(field.Name)
-		var prettyPrintedValue string
-		prettyPrintedValue, err = prettyPrint(fieldValue, indent+1, false)
-		if err != nil {
-			return map[string]string{}, fmt.Errorf(
-				"Error while parsing property: %q. %s",
-				propertyName,
-				err)
-		}
-		if prettyPrintedValue != "" {
-			ret[propertyName] = prettyPrintedValue
-		}
-	}
-
-	return ret, nil
-}
-
-func isZero(value reflect.Value) bool {
-	switch value.Kind() {
-	case reflect.Func, reflect.Map, reflect.Slice:
-		return value.IsNil()
-	case reflect.Array:
-		valueIsZero := true
-		for i := 0; i < value.Len(); i++ {
-			valueIsZero = valueIsZero && isZero(value.Index(i))
-		}
-		return valueIsZero
-	case reflect.Struct:
-		valueIsZero := true
-		for i := 0; i < value.NumField(); i++ {
-			valueIsZero = valueIsZero && isZero(value.Field(i))
-		}
-		return valueIsZero
-	case reflect.Ptr:
-		if !value.IsNil() {
-			return isZero(reflect.Indirect(value))
-		} else {
-			return true
-		}
-	// Always print bool/strings, if you want a bool/string attribute to be able to take the default value, use a
-	// pointer instead
-	case reflect.Bool, reflect.String:
-		return false
-	default:
-		if !value.IsValid() {
-			return true
-		}
-		zeroValue := reflect.Zero(value.Type())
-		result := value.Interface() == zeroValue.Interface()
-		return result
-	}
-}
-
-func escapeString(s string) string {
-	s = strings.ReplaceAll(s, "\\", "\\\\")
-
-	// b/184026959: Reverse the application of some common control sequences.
-	// These must be generated literally in the BUILD file.
-	s = strings.ReplaceAll(s, "\t", "\\t")
-	s = strings.ReplaceAll(s, "\n", "\\n")
-	s = strings.ReplaceAll(s, "\r", "\\r")
-
-	return strings.ReplaceAll(s, "\"", "\\\"")
-}
-
-func targetNameWithVariant(c bpToBuildContext, logicModule blueprint.Module) string {
-	name := ""
-	if c.ModuleSubDir(logicModule) != "" {
-		// TODO(b/162720883): Figure out a way to drop the "--" variant suffixes.
-		name = c.ModuleName(logicModule) + "--" + c.ModuleSubDir(logicModule)
-	} else {
-		name = c.ModuleName(logicModule)
-	}
-
-	return strings.Replace(name, "//", "", 1)
-}
-
-func qualifiedTargetLabel(c bpToBuildContext, logicModule blueprint.Module) string {
-	return fmt.Sprintf("//%s:%s", c.ModuleDir(logicModule), targetNameWithVariant(c, logicModule))
-}
diff --git a/bp2build/bzl_conversion.go b/bp2build/bzl_conversion.go
deleted file mode 100644
index e774fdf..0000000
--- a/bp2build/bzl_conversion.go
+++ /dev/null
@@ -1,237 +0,0 @@
-// Copyright 2020 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 bp2build
-
-import (
-	"android/soong/android"
-	"fmt"
-	"reflect"
-	"runtime"
-	"sort"
-	"strings"
-
-	"github.com/google/blueprint/proptools"
-)
-
-var (
-	// An allowlist of prop types that are surfaced from module props to rule
-	// attributes. (nested) dictionaries are notably absent here, because while
-	// Soong supports multi value typed and nested dictionaries, Bazel's rule
-	// attr() API supports only single-level string_dicts.
-	allowedPropTypes = map[string]bool{
-		"int":         true, // e.g. 42
-		"bool":        true, // e.g. True
-		"string_list": true, // e.g. ["a", "b"]
-		"string":      true, // e.g. "a"
-	}
-)
-
-type rule struct {
-	name  string
-	attrs string
-}
-
-type RuleShim struct {
-	// The rule class shims contained in a bzl file. e.g. ["cc_object", "cc_library", ..]
-	rules []string
-
-	// The generated string content of the bzl file.
-	content string
-}
-
-// Create <module>.bzl containing Bazel rule shims for every module type available in Soong and
-// user-specified Go plugins.
-//
-// This function reuses documentation generation APIs to ensure parity between modules-as-docs
-// and modules-as-code, including the names and types of morule properties.
-func CreateRuleShims(moduleTypeFactories map[string]android.ModuleFactory) map[string]RuleShim {
-	ruleShims := map[string]RuleShim{}
-	for pkg, rules := range generateRules(moduleTypeFactories) {
-		shim := RuleShim{
-			rules: make([]string, 0, len(rules)),
-		}
-		shim.content = "load(\"//build/bazel/queryview_rules:providers.bzl\", \"SoongModuleInfo\")\n"
-
-		bzlFileName := strings.ReplaceAll(pkg, "android/soong/", "")
-		bzlFileName = strings.ReplaceAll(bzlFileName, ".", "_")
-		bzlFileName = strings.ReplaceAll(bzlFileName, "/", "_")
-
-		for _, r := range rules {
-			shim.content += fmt.Sprintf(moduleRuleShim, r.name, r.attrs)
-			shim.rules = append(shim.rules, r.name)
-		}
-		sort.Strings(shim.rules)
-		ruleShims[bzlFileName] = shim
-	}
-	return ruleShims
-}
-
-// Generate the content of soong_module.bzl with the rule shim load statements
-// and mapping of module_type to rule shim map for every module type in Soong.
-func generateSoongModuleBzl(bzlLoads map[string]RuleShim) string {
-	var loadStmts string
-	var moduleRuleMap string
-	for _, bzlFileName := range android.SortedKeys(bzlLoads) {
-		loadStmt := "load(\"//build/bazel/queryview_rules:"
-		loadStmt += bzlFileName
-		loadStmt += ".bzl\""
-		ruleShim := bzlLoads[bzlFileName]
-		for _, rule := range ruleShim.rules {
-			loadStmt += fmt.Sprintf(", %q", rule)
-			moduleRuleMap += "    \"" + rule + "\": " + rule + ",\n"
-		}
-		loadStmt += ")\n"
-		loadStmts += loadStmt
-	}
-
-	return fmt.Sprintf(soongModuleBzl, loadStmts, moduleRuleMap)
-}
-
-func generateRules(moduleTypeFactories map[string]android.ModuleFactory) map[string][]rule {
-	// TODO: add shims for bootstrap/blueprint go modules types
-
-	rules := make(map[string][]rule)
-	// TODO: allow registration of a bzl rule when registring a factory
-	for _, moduleType := range android.SortedKeys(moduleTypeFactories) {
-		factory := moduleTypeFactories[moduleType]
-		factoryName := runtime.FuncForPC(reflect.ValueOf(factory).Pointer()).Name()
-		pkg := strings.Split(factoryName, ".")[0]
-		attrs := `{
-        "soong_module_name": attr.string(mandatory = True),
-        "soong_module_variant": attr.string(),
-        "soong_module_deps": attr.label_list(providers = [SoongModuleInfo]),
-`
-		attrs += getAttributes(factory)
-		attrs += "    },"
-
-		r := rule{
-			name:  canonicalizeModuleType(moduleType),
-			attrs: attrs,
-		}
-
-		rules[pkg] = append(rules[pkg], r)
-	}
-	return rules
-}
-
-type property struct {
-	name             string
-	starlarkAttrType string
-	properties       []property
-}
-
-const (
-	attributeIndent = "        "
-)
-
-func (p *property) attributeString() string {
-	if !shouldGenerateAttribute(p.name) {
-		return ""
-	}
-
-	if _, ok := allowedPropTypes[p.starlarkAttrType]; !ok {
-		// a struct -- let's just comment out sub-props
-		s := fmt.Sprintf(attributeIndent+"# %s start\n", p.name)
-		for _, nestedP := range p.properties {
-			s += "# " + nestedP.attributeString()
-		}
-		s += fmt.Sprintf(attributeIndent+"# %s end\n", p.name)
-		return s
-	}
-	return fmt.Sprintf(attributeIndent+"%q: attr.%s(),\n", p.name, p.starlarkAttrType)
-}
-
-func extractPropertyDescriptionsFromStruct(structType reflect.Type) []property {
-	properties := make([]property, 0)
-	for i := 0; i < structType.NumField(); i++ {
-		field := structType.Field(i)
-		if shouldSkipStructField(field) {
-			continue
-		}
-		subProps := extractPropertyDescriptions(field.Name, field.Type)
-		// if the struct is embedded (anonymous), flatten the properties into the containing struct
-		if field.Anonymous {
-			for _, prop := range subProps {
-				properties = append(properties, prop.properties...)
-			}
-		} else {
-			properties = append(properties, subProps...)
-		}
-	}
-	return properties
-}
-
-func extractPropertyDescriptions(name string, t reflect.Type) []property {
-	name = proptools.PropertyNameForField(name)
-
-	// TODO: handle android:paths tags, they should be changed to label types
-
-	starlarkAttrType := fmt.Sprintf("%s", t.Name())
-	props := make([]property, 0)
-
-	switch t.Kind() {
-	case reflect.Bool, reflect.String:
-		// do nothing
-	case reflect.Uint, reflect.Int, reflect.Int64:
-		starlarkAttrType = "int"
-	case reflect.Slice:
-		if t.Elem().Kind() != reflect.String {
-			// TODO: handle lists of non-strings (currently only list of Dist)
-			return []property{}
-		}
-		starlarkAttrType = "string_list"
-	case reflect.Struct:
-		props = extractPropertyDescriptionsFromStruct(t)
-	case reflect.Ptr:
-		return extractPropertyDescriptions(name, t.Elem())
-	case reflect.Interface:
-		// Interfaces are used for for arch, multilib and target properties, which are handled at runtime.
-		// These will need to be handled in a bazel-specific version of the arch mutator.
-		return []property{}
-	}
-
-	prop := property{
-		name:             name,
-		starlarkAttrType: starlarkAttrType,
-		properties:       props,
-	}
-
-	return []property{prop}
-}
-
-func getPropertyDescriptions(props []interface{}) []property {
-	// there may be duplicate properties, e.g. from defaults libraries
-	propertiesByName := make(map[string]property)
-	for _, p := range props {
-		for _, prop := range extractPropertyDescriptionsFromStruct(reflect.ValueOf(p).Elem().Type()) {
-			propertiesByName[prop.name] = prop
-		}
-	}
-
-	properties := make([]property, 0, len(propertiesByName))
-	for _, key := range android.SortedKeys(propertiesByName) {
-		properties = append(properties, propertiesByName[key])
-	}
-
-	return properties
-}
-
-func getAttributes(factory android.ModuleFactory) string {
-	attrs := ""
-	for _, p := range getPropertyDescriptions(factory().GetProperties()) {
-		attrs += p.attributeString()
-	}
-	return attrs
-}
diff --git a/bp2build/constants.go b/bp2build/constants.go
deleted file mode 100644
index 76ba106..0000000
--- a/bp2build/constants.go
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright 2020 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 bp2build
-
-var (
-	// When both a BUILD and BUILD.bazel file are exist in the same package, the BUILD.bazel file will
-	// be preferred for use within a Bazel build.
-
-	// The file name used for automatically generated files.
-	GeneratedBuildFileName = "BUILD.bazel"
-)
diff --git a/bp2build/conversion.go b/bp2build/conversion.go
deleted file mode 100644
index 9f1aa09..0000000
--- a/bp2build/conversion.go
+++ /dev/null
@@ -1,120 +0,0 @@
-package bp2build
-
-import (
-	"reflect"
-	"strings"
-
-	"android/soong/android"
-	"github.com/google/blueprint/proptools"
-)
-
-type BazelFile struct {
-	Dir      string
-	Basename string
-	Contents string
-}
-
-func CreateBazelFiles(ruleShims map[string]RuleShim, buildToTargets map[string]BazelTargets, mode CodegenMode) []BazelFile {
-	var files []BazelFile
-
-	if mode == QueryView {
-		// Write top level WORKSPACE.
-		files = append(files, newFile("", "WORKSPACE", ""))
-
-		// Used to denote that the top level directory is a package.
-		files = append(files, newFile("", GeneratedBuildFileName, ""))
-
-		files = append(files, newFile(bazelRulesSubDir, GeneratedBuildFileName, ""))
-
-		// These files are only used for queryview.
-		files = append(files, newFile(bazelRulesSubDir, "providers.bzl", providersBzl))
-
-		for bzlFileName, ruleShim := range ruleShims {
-			files = append(files, newFile(bazelRulesSubDir, bzlFileName+".bzl", ruleShim.content))
-		}
-		files = append(files, newFile(bazelRulesSubDir, "soong_module.bzl", generateSoongModuleBzl(ruleShims)))
-	}
-
-	files = append(files, createBuildFiles(buildToTargets, mode)...)
-
-	return files
-}
-
-func createBuildFiles(buildToTargets map[string]BazelTargets, mode CodegenMode) []BazelFile {
-	files := make([]BazelFile, 0, len(buildToTargets))
-	for _, dir := range android.SortedKeys(buildToTargets) {
-		targets := buildToTargets[dir]
-		targets.sort()
-
-		var content string
-		if mode == QueryView {
-			content = soongModuleLoad
-		}
-		if content != "" {
-			// If there are load statements, add a couple of newlines.
-			content += "\n\n"
-		}
-		content += targets.String()
-		files = append(files, newFile(dir, GeneratedBuildFileName, content))
-	}
-	return files
-}
-
-func newFile(dir, basename, content string) BazelFile {
-	return BazelFile{
-		Dir:      dir,
-		Basename: basename,
-		Contents: content,
-	}
-}
-
-const (
-	bazelRulesSubDir = "build/bazel/queryview_rules"
-)
-
-var (
-	// Certain module property names are blocklisted/ignored here, for the reasons commented.
-	ignoredPropNames = map[string]bool{
-		"name":               true, // redundant, since this is explicitly generated for every target
-		"from":               true, // reserved keyword
-		"in":                 true, // reserved keyword
-		"size":               true, // reserved for tests
-		"arch":               true, // interface prop type is not supported yet.
-		"multilib":           true, // interface prop type is not supported yet.
-		"target":             true, // interface prop type is not supported yet.
-		"visibility":         true, // Bazel has native visibility semantics. Handle later.
-		"features":           true, // There is already a built-in attribute 'features' which cannot be overridden.
-		"for":                true, // reserved keyword, b/233579439
-		"versions_with_info": true, // TODO(b/245730552) struct properties not fully supported
-	}
-)
-
-func shouldGenerateAttribute(prop string) bool {
-	return !ignoredPropNames[prop]
-}
-
-func shouldSkipStructField(field reflect.StructField) bool {
-	if field.PkgPath != "" && !field.Anonymous {
-		// Skip unexported fields. Some properties are
-		// internal to Soong only, and these fields do not have PkgPath.
-		return true
-	}
-	// fields with tag `blueprint:"mutated"` are exported to enable modification in mutators, etc.
-	// but cannot be set in a .bp file
-	if proptools.HasTag(field, "blueprint", "mutated") {
-		return true
-	}
-	return false
-}
-
-// FIXME(b/168089390): In Bazel, rules ending with "_test" needs to be marked as
-// testonly = True, forcing other rules that depend on _test rules to also be
-// marked as testonly = True. This semantic constraint is not present in Soong.
-// To work around, rename "*_test" rules to "*_test_".
-func canonicalizeModuleType(moduleName string) string {
-	if strings.HasSuffix(moduleName, "_test") {
-		return moduleName + "_"
-	}
-
-	return moduleName
-}
diff --git a/bp2build/conversion_test.go b/bp2build/conversion_test.go
deleted file mode 100644
index 2f806fa..0000000
--- a/bp2build/conversion_test.go
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2020 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 bp2build
-
-import (
-	"sort"
-	"testing"
-)
-
-type bazelFilepath struct {
-	dir      string
-	basename string
-}
-
-func TestCreateBazelFiles_QueryView_AddsTopLevelFiles(t *testing.T) {
-	files := CreateBazelFiles(map[string]RuleShim{}, map[string]BazelTargets{}, QueryView)
-	expectedFilePaths := []bazelFilepath{
-		{
-			dir:      "",
-			basename: "BUILD.bazel",
-		},
-		{
-			dir:      "",
-			basename: "WORKSPACE",
-		},
-		{
-			dir:      bazelRulesSubDir,
-			basename: "BUILD.bazel",
-		},
-		{
-			dir:      bazelRulesSubDir,
-			basename: "providers.bzl",
-		},
-		{
-			dir:      bazelRulesSubDir,
-			basename: "soong_module.bzl",
-		},
-	}
-
-	// Compare number of files
-	if a, e := len(files), len(expectedFilePaths); a != e {
-		t.Errorf("Expected %d files, got %d", e, a)
-	}
-
-	// Sort the files to be deterministic
-	sort.Slice(files, func(i, j int) bool {
-		if dir1, dir2 := files[i].Dir, files[j].Dir; dir1 == dir2 {
-			return files[i].Basename < files[j].Basename
-		} else {
-			return dir1 < dir2
-		}
-	})
-
-	// Compare the file contents
-	for i := range files {
-		actualFile, expectedFile := files[i], expectedFilePaths[i]
-
-		if actualFile.Dir != expectedFile.dir || actualFile.Basename != expectedFile.basename {
-			t.Errorf("Did not find expected file %s/%s", actualFile.Dir, actualFile.Basename)
-		} else if actualFile.Basename == "BUILD.bazel" || actualFile.Basename == "WORKSPACE" {
-			if actualFile.Contents != "" {
-				t.Errorf("Expected %s to have no content.", actualFile)
-			}
-		} else if actualFile.Contents == "" {
-			t.Errorf("Contents of %s unexpected empty.", actualFile)
-		}
-	}
-}
diff --git a/bpf/bpf.go b/bpf/bpf.go
index 8679821..3f34382 100644
--- a/bpf/bpf.go
+++ b/bpf/bpf.go
@@ -109,41 +109,41 @@
 
 var _ android.ImageInterface = (*bpf)(nil)
 
-func (bpf *bpf) ImageMutatorBegin(ctx android.BaseModuleContext) {}
+func (bpf *bpf) ImageMutatorBegin(ctx android.ImageInterfaceContext) {}
 
-func (bpf *bpf) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
+func (bpf *bpf) VendorVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return proptools.Bool(bpf.properties.Vendor)
 }
 
-func (bpf *bpf) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
+func (bpf *bpf) ProductVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return false
 }
 
-func (bpf *bpf) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
+func (bpf *bpf) CoreVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return !proptools.Bool(bpf.properties.Vendor)
 }
 
-func (bpf *bpf) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (bpf *bpf) RamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return false
 }
 
-func (bpf *bpf) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (bpf *bpf) VendorRamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return false
 }
 
-func (bpf *bpf) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (bpf *bpf) DebugRamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return false
 }
 
-func (bpf *bpf) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
+func (bpf *bpf) RecoveryVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return false
 }
 
-func (bpf *bpf) ExtraImageVariations(ctx android.BaseModuleContext) []string {
+func (bpf *bpf) ExtraImageVariations(ctx android.ImageInterfaceContext) []string {
 	return nil
 }
 
-func (bpf *bpf) SetImageVariation(ctx android.BaseModuleContext, variation string) {
+func (bpf *bpf) SetImageVariation(ctx android.ImageInterfaceContext, variation string) {
 	bpf.properties.VendorInternal = variation == "vendor"
 }
 
@@ -230,8 +230,6 @@
 		ctx.PackageFile(installDir, obj.Base(), obj)
 	}
 
-	android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: srcs.Strings()})
-
 	ctx.SetOutputFiles(bpf.objs, "")
 }
 
diff --git a/bpf/libbpf/libbpf_prog.go b/bpf/libbpf/libbpf_prog.go
index ac61510..44013e5 100644
--- a/bpf/libbpf/libbpf_prog.go
+++ b/bpf/libbpf/libbpf_prog.go
@@ -104,41 +104,41 @@
 
 var _ android.ImageInterface = (*libbpfProg)(nil)
 
-func (libbpf *libbpfProg) ImageMutatorBegin(ctx android.BaseModuleContext) {}
+func (libbpf *libbpfProg) ImageMutatorBegin(ctx android.ImageInterfaceContext) {}
 
-func (libbpf *libbpfProg) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
+func (libbpf *libbpfProg) VendorVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return false
 }
 
-func (libbpf *libbpfProg) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
+func (libbpf *libbpfProg) ProductVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return false
 }
 
-func (libbpf *libbpfProg) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
+func (libbpf *libbpfProg) CoreVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return true
 }
 
-func (libbpf *libbpfProg) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (libbpf *libbpfProg) RamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return false
 }
 
-func (libbpf *libbpfProg) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (libbpf *libbpfProg) VendorRamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return false
 }
 
-func (libbpf *libbpfProg) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (libbpf *libbpfProg) DebugRamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return false
 }
 
-func (libbpf *libbpfProg) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
+func (libbpf *libbpfProg) RecoveryVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return false
 }
 
-func (libbpf *libbpfProg) ExtraImageVariations(ctx android.BaseModuleContext) []string {
+func (libbpf *libbpfProg) ExtraImageVariations(ctx android.ImageInterfaceContext) []string {
 	return nil
 }
 
-func (libbpf *libbpfProg) SetImageVariation(ctx android.BaseModuleContext, variation string) {
+func (libbpf *libbpfProg) SetImageVariation(ctx android.ImageInterfaceContext, variation string) {
 }
 
 func (libbpf *libbpfProg) DepsMutator(ctx android.BottomUpMutatorContext) {
@@ -158,7 +158,8 @@
 		"-Wall",
 		"-Werror",
 		"-Wextra",
-
+		// Flag to assist with the transition to libbpf
+		"-DENABLE_LIBBPF",
 		"-isystem bionic/libc/include",
 		"-isystem bionic/libc/kernel/uapi",
 		// The architecture doesn't matter here, but asm/types.h is included by linux/types.h.
@@ -205,7 +206,7 @@
 		if strings.ContainsRune(src.Base(), '_') {
 			ctx.ModuleErrorf("invalid character '_' in source name")
 		}
-		obj := android.ObjPathWithExt(ctx, "unstripped", src, "o")
+		obj := android.ObjPathWithExt(ctx, "unstripped", src, "bpf")
 
 		ctx.Build(pctx, android.BuildParams{
 			Rule:      libbpfProgCcRule,
@@ -218,7 +219,7 @@
 			},
 		})
 
-		objStripped := android.ObjPathWithExt(ctx, "", src, "o")
+		objStripped := android.ObjPathWithExt(ctx, "", src, "bpf")
 		ctx.Build(pctx, android.BuildParams{
 			Rule:   libbpfProgStripRule,
 			Input:  obj,
@@ -230,7 +231,7 @@
 		libbpf.objs = append(libbpf.objs, objStripped.WithoutRel())
 	}
 
-	installDir := android.PathForModuleInstall(ctx, "etc", "bpf/libbpf")
+	installDir := android.PathForModuleInstall(ctx, "etc", "bpf")
 	if len(libbpf.properties.Relative_install_path) > 0 {
 		installDir = installDir.Join(ctx, libbpf.properties.Relative_install_path)
 	}
@@ -238,8 +239,6 @@
 		ctx.PackageFile(installDir, obj.Base(), obj)
 	}
 
-	android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: srcs.Strings()})
-
 	ctx.SetOutputFiles(libbpf.objs, "")
 }
 
@@ -251,7 +250,7 @@
 			fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir)
 			fmt.Fprintln(w)
 			var localModulePath string
-			localModulePath = "LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/bpf/libbpf"
+			localModulePath = "LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/bpf"
 			if len(libbpf.properties.Relative_install_path) > 0 {
 				localModulePath += "/" + libbpf.properties.Relative_install_path
 			}
diff --git a/bpf/libbpf/libbpf_prog_test.go b/bpf/libbpf/libbpf_prog_test.go
index f4f5167..2b3b378 100644
--- a/bpf/libbpf/libbpf_prog_test.go
+++ b/bpf/libbpf/libbpf_prog_test.go
@@ -41,14 +41,15 @@
 func TestLibbpfProgDataDependency(t *testing.T) {
 	bp := `
 		libbpf_prog {
-			name: "bpf.o",
+			name: "bpf.bpf",
 			srcs: ["bpf.c"],
 		}
 
 		cc_test {
 			name: "vts_test_binary_bpf_module",
+			compile_multilib: "first",
 			srcs: ["BpfTest.cpp"],
-			data: [":bpf.o"],
+			data: [":bpf.bpf"],
 			gtest: false,
 		}
 	`
@@ -59,7 +60,7 @@
 func TestLibbpfProgSourceName(t *testing.T) {
 	bp := `
 		libbpf_prog {
-			name: "bpf_invalid_name.o",
+			name: "bpf_invalid_name.bpf",
 			srcs: ["bpf_invalid_name.c"],
 		}
 	`
diff --git a/build_test.bash b/build_test.bash
deleted file mode 100755
index defdd82..0000000
--- a/build_test.bash
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/bin/bash -eu
-#
-# Copyright 2017 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.
-
-#
-# This file is used in our continous build infrastructure to run a variety of
-# tests related to the build system.
-#
-# Currently, it's used to build and run multiproduct_kati, so it'll attempt
-# to build ninja files for every product in the tree. I expect this to
-# evolve as we find interesting things to test or track performance for.
-#
-
-# Products that are broken or otherwise don't work with multiproduct_kati
-SKIPPED_PRODUCTS=(
-    # These products are for soong-only builds, and will fail the kati stage.
-    linux_bionic
-    mainline_sdk
-    ndk
-
-    # New architecture bringup, fails without ALLOW_MISSING_DEPENDENCIES=true
-    aosp_riscv64
-)
-
-# To track how long we took to startup.
-case $(uname -s) in
-  Darwin)
-    export TRACE_BEGIN_SOONG=`$T/prebuilts/build-tools/path/darwin-x86/date +%s%3N`
-    ;;
-  *)
-    export TRACE_BEGIN_SOONG=$(date +%s%N)
-    ;;
-esac
-
-# Remove BUILD_NUMBER so that incremental builds on build servers don't
-# re-read makefiles every time.
-unset BUILD_NUMBER
-
-export TOP=$(cd $(dirname ${BASH_SOURCE[0]})/../..; PWD= /bin/pwd)
-cd "${TOP}"
-source "${TOP}/build/soong/scripts/microfactory.bash"
-
-case $(uname) in
-  Linux)
-    if [[ -f /lib/x86_64-linux-gnu/libSegFault.so ]]; then
-      export LD_PRELOAD=/lib/x86_64-linux-gnu/libSegFault.so
-      export SEGFAULT_USE_ALTSTACK=1
-    fi
-    ulimit -a
-    ;;
-esac
-
-echo
-echo "Free disk space:"
-# Ignore df errors because it errors out on gvfsd file systems
-# but still displays most of the useful info we need
-df -h || true
-
-echo
-echo "Running Bazel smoke test..."
-STANDALONE_BAZEL=true "${TOP}/build/bazel/bin/bazel" --batch --max_idle_secs=1 help
-
-echo
-echo "Running Soong test..."
-soong_build_go multiproduct_kati android/soong/cmd/multiproduct_kati
-exec "$(getoutdir)/multiproduct_kati" --skip-products "$(echo "${SKIPPED_PRODUCTS[@]-}" | tr ' ' ',')" "$@"
diff --git a/cc/Android.bp b/cc/Android.bp
index 88a793c..3b29ae8 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -7,6 +7,7 @@
     pkgPath: "android/soong/cc",
     deps: [
         "blueprint",
+        "blueprint-depset",
         "blueprint-pathtools",
         "soong",
         "soong-aconfig",
@@ -16,7 +17,6 @@
         "soong-etc",
         "soong-fuzz",
         "soong-genrule",
-        "soong-testing",
         "soong-tradefed",
     ],
     srcs: [
@@ -27,6 +27,7 @@
         "builder.go",
         "cc.go",
         "ccdeps.go",
+        "cc_preprocess_no_configuration.go",
         "check.go",
         "coverage.go",
         "gen.go",
@@ -88,6 +89,7 @@
     testSrcs: [
         "afdo_test.go",
         "binary_test.go",
+        "cc_preprocess_no_configuration_test.go",
         "cc_test.go",
         "cc_test_only_property_test.go",
         "cmake_snapshot_test.go",
@@ -120,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/afdo.go b/cc/afdo.go
index 14d105e..828e494 100644
--- a/cc/afdo.go
+++ b/cc/afdo.go
@@ -65,8 +65,8 @@
 }
 
 func getFdoProfilePathFromDep(ctx ModuleContext) string {
-	fdoProfileDeps := ctx.GetDirectDepsWithTag(FdoProfileTag)
-	if len(fdoProfileDeps) > 0 && fdoProfileDeps[0] != nil {
+	fdoProfileDeps := ctx.GetDirectDepsProxyWithTag(FdoProfileTag)
+	if len(fdoProfileDeps) > 0 {
 		if info, ok := android.OtherModuleProvider(ctx, fdoProfileDeps[0], FdoProfileProvider); ok {
 			return info.Path.String()
 		}
@@ -163,7 +163,7 @@
 		}
 
 		// TODO(b/324141705): this is designed to prevent propagating AFDO from static libraries that have afdo: true set, but
-		//  it should be m.static() && !m.staticBinary() so that static binaries use AFDO variants of dependencies.
+		//  it should be m.staticLibrary() so that static binaries use AFDO variants of dependencies.
 		if m.static() {
 			return ""
 		}
@@ -188,7 +188,7 @@
 		if variation == "" {
 			// The empty variation is either a module that has enabled AFDO for itself, or the non-AFDO
 			// variant of a dependency.
-			if m.afdo.afdoEnabled() && !(m.static() && !m.staticBinary()) && !m.Host() {
+			if m.afdo.afdoEnabled() && !m.staticLibrary() && !m.Host() {
 				m.afdo.addDep(ctx, ctx.ModuleName())
 			}
 		} else {
diff --git a/cc/afdo_test.go b/cc/afdo_test.go
index 0679d13..d2d5584 100644
--- a/cc/afdo_test.go
+++ b/cc/afdo_test.go
@@ -94,9 +94,9 @@
 	afdoLtoLdFlag := "-Wl,-plugin-opt,-import-instr-limit=40"
 	noAfdoLtoLdFlag := "-Wl,-plugin-opt,-import-instr-limit=5"
 
-	libTest := result.ModuleForTests("libTest", "android_arm64_armv8-a_shared")
-	libFooAfdoVariant := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static_afdo-libTest")
-	libBarAfdoVariant := result.ModuleForTests("libBar", "android_arm64_armv8-a_static_afdo-libTest")
+	libTest := result.ModuleForTests(t, "libTest", "android_arm64_armv8-a_shared")
+	libFooAfdoVariant := result.ModuleForTests(t, "libFoo", "android_arm64_armv8-a_static_afdo-libTest")
+	libBarAfdoVariant := result.ModuleForTests(t, "libBar", "android_arm64_armv8-a_static_afdo-libTest")
 
 	// Check cFlags of afdo-enabled module and the afdo-variant of its static deps
 	cFlags := libTest.Rule("cc").Args["cFlags"]
@@ -138,8 +138,8 @@
 	}
 
 	// Verify non-afdo variant exists and doesn't contain afdo
-	libFoo := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static")
-	libBar := result.ModuleForTests("libBar", "android_arm64_armv8-a_static")
+	libFoo := result.ModuleForTests(t, "libFoo", "android_arm64_armv8-a_static")
+	libBar := result.ModuleForTests(t, "libBar", "android_arm64_armv8-a_static")
 
 	cFlags = libFoo.Rule("cc").Args["cFlags"]
 	if strings.Contains(cFlags, profileSampleCFlag) {
@@ -166,9 +166,9 @@
 	}
 
 	// Verify that the arm variant does not have FDO since the fdo_profile module only has a profile for arm64
-	libTest32 := result.ModuleForTests("libTest", "android_arm_armv7-a-neon_shared")
-	libFooAfdoVariant32 := result.ModuleForTests("libFoo", "android_arm_armv7-a-neon_static_afdo-libTest_lto-thin")
-	libBarAfdoVariant32 := result.ModuleForTests("libBar", "android_arm_armv7-a-neon_static_afdo-libTest_lto-thin")
+	libTest32 := result.ModuleForTests(t, "libTest", "android_arm_armv7-a-neon_shared")
+	libFooAfdoVariant32 := result.ModuleForTests(t, "libFoo", "android_arm_armv7-a-neon_static_afdo-libTest_lto-thin")
+	libBarAfdoVariant32 := result.ModuleForTests(t, "libBar", "android_arm_armv7-a-neon_static_afdo-libTest_lto-thin")
 
 	cFlags = libTest32.Rule("cc").Args["cFlags"]
 	if strings.Contains(cFlags, profileSampleCFlag) {
@@ -215,9 +215,9 @@
 	}
 
 	// Verify that the host variants don't enable afdo
-	libTestHost := result.ModuleForTests("libTest", result.Config.BuildOSTarget.String()+"_shared")
-	libFooHost := result.ModuleForTests("libFoo", result.Config.BuildOSTarget.String()+"_static_lto-thin")
-	libBarHost := result.ModuleForTests("libBar", result.Config.BuildOSTarget.String()+"_static_lto-thin")
+	libTestHost := result.ModuleForTests(t, "libTest", result.Config.BuildOSTarget.String()+"_shared")
+	libFooHost := result.ModuleForTests(t, "libFoo", result.Config.BuildOSTarget.String()+"_static_lto-thin")
+	libBarHost := result.ModuleForTests(t, "libBar", result.Config.BuildOSTarget.String()+"_static_lto-thin")
 
 	cFlags = libTestHost.Rule("cc").Args["cFlags"]
 	if strings.Contains(cFlags, profileSampleCFlag) {
@@ -301,9 +301,9 @@
 		}.AddToFixture(),
 	).RunTestWithBp(t, bp)
 
-	libTest := result.ModuleForTests("libTest", "android_arm64_armv8-a_shared").Module()
-	libFoo := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static")
-	libBar := result.ModuleForTests("libBar", "android_arm64_armv8-a_static").Module()
+	libTest := result.ModuleForTests(t, "libTest", "android_arm64_armv8-a_shared").Module()
+	libFoo := result.ModuleForTests(t, "libFoo", "android_arm64_armv8-a_static")
+	libBar := result.ModuleForTests(t, "libBar", "android_arm64_armv8-a_static").Module()
 
 	if !hasDirectDep(result, libTest, libFoo.Module()) {
 		t.Errorf("libTest missing dependency on non-afdo variant of libFoo")
@@ -412,13 +412,13 @@
 		}.AddToFixture(),
 	).RunTestWithBp(t, bp)
 
-	fooArm := result.ModuleForTests("foo", "android_arm_armv7-a-neon_shared")
+	fooArm := result.ModuleForTests(t, "foo", "android_arm_armv7-a-neon_shared")
 	fooArmCFlags := fooArm.Rule("cc").Args["cFlags"]
 	if w := "-fprofile-sample-use=afdo_profiles_package/foo_arm.afdo"; !strings.Contains(fooArmCFlags, w) {
 		t.Errorf("Expected 'foo' to enable afdo, but did not find %q in cflags %q", w, fooArmCFlags)
 	}
 
-	fooArm64 := result.ModuleForTests("foo", "android_arm64_armv8-a_shared")
+	fooArm64 := result.ModuleForTests(t, "foo", "android_arm64_armv8-a_shared")
 	fooArm64CFlags := fooArm64.Rule("cc").Args["cFlags"]
 	if w := "-fprofile-sample-use=afdo_profiles_package/foo_arm64.afdo"; !strings.Contains(fooArm64CFlags, w) {
 		t.Errorf("Expected 'foo' to enable afdo, but did not find %q in cflags %q", w, fooArm64CFlags)
@@ -476,11 +476,11 @@
 	expectedCFlagLibTest := "-fprofile-sample-use=afdo_profiles_package/libTest.afdo"
 	expectedCFlagLibBar := "-fprofile-sample-use=afdo_profiles_package/libBar.afdo"
 
-	libTest := result.ModuleForTests("libTest", "android_arm64_armv8-a_shared")
-	libFooAfdoVariantWithLibTest := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static_afdo-libTest")
+	libTest := result.ModuleForTests(t, "libTest", "android_arm64_armv8-a_shared")
+	libFooAfdoVariantWithLibTest := result.ModuleForTests(t, "libFoo", "android_arm64_armv8-a_static_afdo-libTest")
 
-	libBar := result.ModuleForTests("libBar", "android_arm64_armv8-a_shared")
-	libFooAfdoVariantWithLibBar := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static_afdo-libBar")
+	libBar := result.ModuleForTests(t, "libBar", "android_arm64_armv8-a_shared")
+	libFooAfdoVariantWithLibBar := result.ModuleForTests(t, "libFoo", "android_arm64_armv8-a_static_afdo-libBar")
 
 	// Check cFlags of afdo-enabled module and the afdo-variant of its static deps
 	cFlags := libTest.Rule("cc").Args["cFlags"]
@@ -543,9 +543,9 @@
 	// -funique-internal-linkage-names.
 	expectedCFlag := "-funique-internal-linkage-names"
 
-	libTest := result.ModuleForTests("libTest", "android_arm64_armv8-a_shared")
-	libFooAfdoVariant := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static_afdo-libTest")
-	libBarAfdoVariant := result.ModuleForTests("libBar", "android_arm64_armv8-a_static_afdo-libTest")
+	libTest := result.ModuleForTests(t, "libTest", "android_arm64_armv8-a_shared")
+	libFooAfdoVariant := result.ModuleForTests(t, "libFoo", "android_arm64_armv8-a_static_afdo-libTest")
+	libBarAfdoVariant := result.ModuleForTests(t, "libBar", "android_arm64_armv8-a_static_afdo-libTest")
 
 	// Check cFlags of afdo-enabled module and the afdo-variant of its static deps
 	cFlags := libTest.Rule("cc").Args["cFlags"]
@@ -572,8 +572,8 @@
 	}
 
 	// Verify non-afdo variant exists and doesn't contain afdo
-	libFoo := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static")
-	libBar := result.ModuleForTests("libBar", "android_arm64_armv8-a_static")
+	libFoo := result.ModuleForTests(t, "libFoo", "android_arm64_armv8-a_static")
+	libBar := result.ModuleForTests(t, "libBar", "android_arm64_armv8-a_static")
 
 	cFlags = libFoo.Rule("cc").Args["cFlags"]
 	if strings.Contains(cFlags, expectedCFlag) {
diff --git a/cc/androidmk.go b/cc/androidmk.go
index 6966f76..03f229e 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -36,7 +36,7 @@
 type AndroidMkContext interface {
 	BaseModuleName() string
 	Target() android.Target
-	subAndroidMk(*android.AndroidMkEntries, interface{})
+	subAndroidMk(android.Config, *android.AndroidMkInfo, interface{})
 	Arch() android.Arch
 	Os() android.OsType
 	Host() bool
@@ -48,112 +48,124 @@
 	InRecovery() bool
 	NotInPlatform() bool
 	InVendorOrProduct() bool
+	ArchSpecific() bool
 }
 
-type subAndroidMkProvider interface {
-	AndroidMkEntries(AndroidMkContext, *android.AndroidMkEntries)
+type subAndroidMkProviderInfoProducer interface {
+	prepareAndroidMKProviderInfo(android.Config, AndroidMkContext, *android.AndroidMkInfo)
 }
 
-func (c *Module) subAndroidMk(entries *android.AndroidMkEntries, obj interface{}) {
+type subAndroidMkFooterInfoProducer interface {
+	prepareAndroidMKFooterInfo(android.Config, AndroidMkContext, *android.AndroidMkInfo)
+}
+
+func (c *Module) subAndroidMk(config android.Config, entries *android.AndroidMkInfo, obj interface{}) {
 	if c.subAndroidMkOnce == nil {
-		c.subAndroidMkOnce = make(map[subAndroidMkProvider]bool)
+		c.subAndroidMkOnce = make(map[subAndroidMkProviderInfoProducer]bool)
 	}
-	if androidmk, ok := obj.(subAndroidMkProvider); ok {
+	if androidmk, ok := obj.(subAndroidMkProviderInfoProducer); ok {
 		if !c.subAndroidMkOnce[androidmk] {
 			c.subAndroidMkOnce[androidmk] = true
-			androidmk.AndroidMkEntries(c, entries)
+			androidmk.prepareAndroidMKProviderInfo(config, c, entries)
 		}
 	}
 }
 
-func (c *Module) AndroidMkEntries() []android.AndroidMkEntries {
+var _ android.AndroidMkProviderInfoProducer = (*Module)(nil)
+
+func (c *Module) PrepareAndroidMKProviderInfo(config android.Config) *android.AndroidMkProviderInfo {
 	if c.hideApexVariantFromMake || c.Properties.HideFromMake {
-		return []android.AndroidMkEntries{{
-			Disabled: true,
-		}}
+		return &android.AndroidMkProviderInfo{
+			PrimaryInfo: android.AndroidMkInfo{
+				Disabled: true,
+			},
+		}
 	}
 
-	entries := android.AndroidMkEntries{
-		OutputFile: c.outputFile,
-		// TODO(jiyong): add the APEXes providing shared libs to the required
-		// modules Currently, adding c.Properties.ApexesProvidingSharedLibs is
-		// causing multiple ART APEXes (com.android.art and com.android.art.debug)
-		// to be installed. And this is breaking some older devices (like marlin)
-		// where system.img is small.
-		Required:     c.Properties.AndroidMkRuntimeLibs,
-		OverrideName: c.BaseModuleName(),
-		Include:      "$(BUILD_SYSTEM)/soong_cc_rust_prebuilt.mk",
-
-		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
-			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				if len(c.Properties.Logtags) > 0 {
-					entries.AddStrings("LOCAL_SOONG_LOGTAGS_FILES", c.logtagsPaths.Strings()...)
-				}
-				// Note: Pass the exact value of AndroidMkSystemSharedLibs to the Make
-				// world, even if it is an empty list. In the Make world,
-				// LOCAL_SYSTEM_SHARED_LIBRARIES defaults to "none", which is expanded
-				// to the default list of system shared libs by the build system.
-				// Soong computes the exact list of system shared libs, so we have to
-				// override the default value when the list of libs is actually empty.
-				entries.SetString("LOCAL_SYSTEM_SHARED_LIBRARIES", strings.Join(c.Properties.AndroidMkSystemSharedLibs, " "))
-				if len(c.Properties.AndroidMkSharedLibs) > 0 {
-					entries.AddStrings("LOCAL_SHARED_LIBRARIES", c.Properties.AndroidMkSharedLibs...)
-				}
-				if len(c.Properties.AndroidMkRuntimeLibs) > 0 {
-					entries.AddStrings("LOCAL_RUNTIME_LIBRARIES", c.Properties.AndroidMkRuntimeLibs...)
-				}
-				entries.SetString("LOCAL_SOONG_LINK_TYPE", c.makeLinkType)
-				if c.InVendor() {
-					entries.SetBool("LOCAL_IN_VENDOR", true)
-				} else if c.InProduct() {
-					entries.SetBool("LOCAL_IN_PRODUCT", true)
-				}
-				if c.Properties.SdkAndPlatformVariantVisibleToMake {
-					// Add the unsuffixed name to SOONG_SDK_VARIANT_MODULES so that Make can rewrite
-					// dependencies to the .sdk suffix when building a module that uses the SDK.
-					entries.SetString("SOONG_SDK_VARIANT_MODULES",
-						"$(SOONG_SDK_VARIANT_MODULES) $(patsubst %.sdk,%,$(LOCAL_MODULE))")
-				}
-				entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", c.IsSkipInstall())
-			},
-		},
-		ExtraFooters: []android.AndroidMkExtraFootersFunc{
-			func(w io.Writer, name, prefix, moduleDir string) {
-				if c.Properties.IsSdkVariant && c.Properties.SdkAndPlatformVariantVisibleToMake &&
-					c.CcLibraryInterface() && c.Shared() {
-					// Using the SDK variant as a JNI library needs a copy of the .so that
-					// is not named .sdk.so so that it can be packaged into the APK with
-					// the right name.
-					fmt.Fprintln(w, "$(eval $(call copy-one-file,",
-						"$(LOCAL_BUILT_MODULE),",
-						"$(patsubst %.sdk.so,%.so,$(LOCAL_BUILT_MODULE))))")
-				}
-			},
+	providerData := android.AndroidMkProviderInfo{
+		PrimaryInfo: android.AndroidMkInfo{
+			OutputFile:   c.outputFile,
+			Required:     c.Properties.AndroidMkRuntimeLibs,
+			OverrideName: c.BaseModuleName(),
+			Include:      "$(BUILD_SYSTEM)/soong_cc_rust_prebuilt.mk",
+			EntryMap:     make(map[string][]string),
 		},
 	}
 
+	entries := &providerData.PrimaryInfo
+	if len(c.Properties.Logtags) > 0 {
+		entries.AddStrings("LOCAL_SOONG_LOGTAGS_FILES", c.logtagsPaths.Strings()...)
+	}
+	// Note: Pass the exact value of AndroidMkSystemSharedLibs to the Make
+	// world, even if it is an empty list. In the Make world,
+	// LOCAL_SYSTEM_SHARED_LIBRARIES defaults to "none", which is expanded
+	// to the default list of system shared libs by the build system.
+	// Soong computes the exact list of system shared libs, so we have to
+	// override the default value when the list of libs is actually empty.
+	entries.SetString("LOCAL_SYSTEM_SHARED_LIBRARIES", strings.Join(c.Properties.AndroidMkSystemSharedLibs, " "))
+	if len(c.Properties.AndroidMkSharedLibs) > 0 {
+		entries.AddStrings("LOCAL_SHARED_LIBRARIES", c.Properties.AndroidMkSharedLibs...)
+	}
+	if len(c.Properties.AndroidMkRuntimeLibs) > 0 {
+		entries.AddStrings("LOCAL_RUNTIME_LIBRARIES", c.Properties.AndroidMkRuntimeLibs...)
+	}
+	entries.SetString("LOCAL_SOONG_LINK_TYPE", c.makeLinkType)
+	if c.InVendor() {
+		entries.SetBool("LOCAL_IN_VENDOR", true)
+	} else if c.InProduct() {
+		entries.SetBool("LOCAL_IN_PRODUCT", true)
+	}
+	if c.Properties.SdkAndPlatformVariantVisibleToMake {
+		// Add the unsuffixed name to SOONG_SDK_VARIANT_MODULES so that Make can rewrite
+		// dependencies to the .sdk suffix when building a module that uses the SDK.
+		entries.SetString("SOONG_SDK_VARIANT_MODULES",
+			"$(SOONG_SDK_VARIANT_MODULES) $(patsubst %.sdk,%,$(LOCAL_MODULE))")
+	}
+	entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", c.IsSkipInstall())
+
 	for _, feature := range c.features {
-		c.subAndroidMk(&entries, feature)
+		c.subAndroidMk(config, entries, feature)
 	}
 
-	c.subAndroidMk(&entries, c.compiler)
-	c.subAndroidMk(&entries, c.linker)
+	c.subAndroidMk(config, entries, c.compiler)
+	c.subAndroidMk(config, entries, c.linker)
 	if c.sanitize != nil {
-		c.subAndroidMk(&entries, c.sanitize)
+		c.subAndroidMk(config, entries, c.sanitize)
 	}
-	c.subAndroidMk(&entries, c.installer)
+	c.subAndroidMk(config, entries, c.installer)
 
 	entries.SubName += c.Properties.SubName
 
-	return []android.AndroidMkEntries{entries}
+	// The footer info comes at the last step, previously it was achieved by
+	// calling some extra footer function that were added earlier. Because we no
+	// longer use these extra footer functions, we need to put this step at the
+	// last one.
+	if c.Properties.IsSdkVariant && c.Properties.SdkAndPlatformVariantVisibleToMake &&
+		c.CcLibraryInterface() && c.Shared() {
+		// Using the SDK variant as a JNI library needs a copy of the .so that
+		// is not named .sdk.so so that it can be packaged into the APK with
+		// the right name.
+		entries.FooterStrings = []string{
+			fmt.Sprintf("%s %s %s", "$(eval $(call copy-one-file,",
+				"$(LOCAL_BUILT_MODULE),",
+				"$(patsubst %.sdk.so,%.so,$(LOCAL_BUILT_MODULE))))")}
+	}
+
+	for _, obj := range []interface{}{c.compiler, c.linker, c.sanitize, c.installer} {
+		if obj == nil {
+			continue
+		}
+		if p, ok := obj.(subAndroidMkFooterInfoProducer); ok {
+			p.prepareAndroidMKFooterInfo(config, c, entries)
+		}
+	}
+
+	return &providerData
 }
 
-func androidMkWriteExtraTestConfigs(extraTestConfigs android.Paths, entries *android.AndroidMkEntries) {
+func androidMkWriteExtraTestConfigs(extraTestConfigs android.Paths, entries *android.AndroidMkInfo) {
 	if len(extraTestConfigs) > 0 {
-		entries.ExtraEntries = append(entries.ExtraEntries,
-			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.AddStrings("LOCAL_EXTRA_FULL_TEST_CONFIGS", extraTestConfigs.Strings()...)
-			})
+		entries.AddStrings("LOCAL_EXTRA_FULL_TEST_CONFIGS", extraTestConfigs.Strings()...)
 	}
 }
 
@@ -169,7 +181,7 @@
 	return overrides
 }
 
-func (library *libraryDecorator) androidMkWriteExportedFlags(entries *android.AndroidMkEntries) {
+func (library *libraryDecorator) androidMkWriteExportedFlags(entries *android.AndroidMkInfo) {
 	var exportedFlags []string
 	var includeDirs android.Paths
 	var systemIncludeDirs android.Paths
@@ -200,7 +212,7 @@
 	}
 }
 
-func (library *libraryDecorator) androidMkEntriesWriteAdditionalDependenciesForSourceAbiDiff(entries *android.AndroidMkEntries) {
+func (library *libraryDecorator) androidMkEntriesWriteAdditionalDependenciesForSourceAbiDiff(entries *android.AndroidMkInfo) {
 	if !library.static() {
 		entries.AddPaths("LOCAL_ADDITIONAL_DEPENDENCIES", library.sAbiDiff)
 	}
@@ -213,23 +225,21 @@
 	}
 }
 
-func (library *libraryDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
+func (library *libraryDecorator) prepareAndroidMKProviderInfo(config android.Config, ctx AndroidMkContext, entries *android.AndroidMkInfo) {
 	if library.static() {
 		entries.Class = "STATIC_LIBRARIES"
 	} else if library.shared() {
 		entries.Class = "SHARED_LIBRARIES"
-		entries.ExtraEntries = append(entries.ExtraEntries, func(_ android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-			entries.SetString("LOCAL_SOONG_TOC", library.toc().String())
-			if !library.buildStubs() && library.unstrippedOutputFile != nil {
-				entries.SetString("LOCAL_SOONG_UNSTRIPPED_BINARY", library.unstrippedOutputFile.String())
-			}
-			if len(library.Properties.Overrides) > 0 {
-				entries.SetString("LOCAL_OVERRIDES_MODULES", strings.Join(makeOverrideModuleNames(ctx, library.Properties.Overrides), " "))
-			}
-			if len(library.postInstallCmds) > 0 {
-				entries.SetString("LOCAL_POST_INSTALL_CMD", strings.Join(library.postInstallCmds, "&& "))
-			}
-		})
+		entries.SetString("LOCAL_SOONG_TOC", library.toc().String())
+		if !library.BuildStubs() && library.unstrippedOutputFile != nil {
+			entries.SetString("LOCAL_SOONG_UNSTRIPPED_BINARY", library.unstrippedOutputFile.String())
+		}
+		if len(library.Properties.Overrides) > 0 {
+			entries.SetString("LOCAL_OVERRIDES_MODULES", strings.Join(makeOverrideModuleNames(ctx, library.Properties.Overrides), " "))
+		}
+		if len(library.postInstallCmds) > 0 {
+			entries.SetString("LOCAL_POST_INSTALL_CMD", strings.Join(library.postInstallCmds, "&& "))
+		}
 	} else if library.header() {
 		entries.Class = "HEADER_LIBRARIES"
 	}
@@ -238,34 +248,30 @@
 		entries.DistFiles = android.MakeDefaultDistFiles(library.distFile)
 	}
 
-	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		library.androidMkWriteExportedFlags(entries)
-		library.androidMkEntriesWriteAdditionalDependenciesForSourceAbiDiff(entries)
+	library.androidMkWriteExportedFlags(entries)
+	library.androidMkEntriesWriteAdditionalDependenciesForSourceAbiDiff(entries)
 
-		if entries.OutputFile.Valid() {
-			_, _, ext := android.SplitFileExt(entries.OutputFile.Path().Base())
-			entries.SetString("LOCAL_BUILT_MODULE_STEM", "$(LOCAL_MODULE)"+ext)
-		}
+	if entries.OutputFile.Valid() {
+		_, _, ext := android.SplitFileExt(entries.OutputFile.Path().Base())
+		entries.SetString("LOCAL_BUILT_MODULE_STEM", "$(LOCAL_MODULE)"+ext)
+	}
 
-		if library.coverageOutputFile.Valid() {
-			entries.SetString("LOCAL_PREBUILT_COVERAGE_ARCHIVE", library.coverageOutputFile.String())
-		}
-	})
+	if library.coverageOutputFile.Valid() {
+		entries.SetString("LOCAL_PREBUILT_COVERAGE_ARCHIVE", library.coverageOutputFile.String())
+	}
 
-	if library.shared() && !library.buildStubs() {
-		ctx.subAndroidMk(entries, library.baseInstaller)
+	if library.shared() && !library.BuildStubs() {
+		ctx.subAndroidMk(config, entries, library.baseInstaller)
 	} else {
-		if library.buildStubs() && library.stubsVersion() != "" {
-			entries.SubName = "." + library.stubsVersion()
+		if library.BuildStubs() && library.StubsVersion() != "" {
+			entries.SubName = "." + library.StubsVersion()
 		}
-		entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-			// library.makeUninstallable() depends on this to bypass HideFromMake() for
-			// static libraries.
-			entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true)
-			if library.buildStubs() {
-				entries.SetBool("LOCAL_NO_NOTICE_FILE", true)
-			}
-		})
+		// library.makeUninstallable() depends on this to bypass HideFromMake() for
+		// static libraries.
+		entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true)
+		if library.BuildStubs() {
+			entries.SetBool("LOCAL_NO_NOTICE_FILE", true)
+		}
 	}
 	// If a library providing a stub is included in an APEX, the private APIs of the library
 	// is accessible only inside the APEX. From outside of the APEX, clients can only use the
@@ -275,220 +281,222 @@
 	// very early stage in the boot process).
 	if len(library.Properties.Stubs.Versions) > 0 && !ctx.Host() && ctx.NotInPlatform() &&
 		!ctx.InRamdisk() && !ctx.InVendorRamdisk() && !ctx.InRecovery() && !ctx.InVendorOrProduct() && !ctx.static() {
-		if library.buildStubs() && library.isLatestStubVersion() {
+		if library.BuildStubs() && library.isLatestStubVersion() {
 			entries.SubName = ""
 		}
-		if !library.buildStubs() {
+		if !library.BuildStubs() {
 			entries.SubName = ".bootstrap"
 		}
 	}
 }
 
-func (object *objectLinker) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
+func (object *objectLinker) prepareAndroidMKProviderInfo(config android.Config, ctx AndroidMkContext, entries *android.AndroidMkInfo) {
 	entries.Class = "STATIC_LIBRARIES"
-	entries.ExtraFooters = append(entries.ExtraFooters,
-		func(w io.Writer, name, prefix, moduleDir string) {
-			out := entries.OutputFile.Path()
-			varname := fmt.Sprintf("SOONG_%sOBJECT_%s%s", prefix, name, entries.SubName)
-
-			fmt.Fprintf(w, "\n%s := %s\n", varname, out.String())
-			fmt.Fprintln(w, ".KATI_READONLY: "+varname)
-		})
 }
 
-func (test *testDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
-	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		if len(test.InstallerProperties.Test_suites) > 0 {
-			entries.AddCompatibilityTestSuites(test.InstallerProperties.Test_suites...)
+func (object *objectLinker) prepareAndroidMKFooterInfo(config android.Config, ctx AndroidMkContext, entries *android.AndroidMkInfo) {
+	out := entries.OutputFile.Path()
+	name := ctx.BaseModuleName()
+	if entries.OverrideName != "" {
+		name = entries.OverrideName
+	}
+
+	prefix := ""
+	if ctx.ArchSpecific() {
+		switch ctx.Os().Class {
+		case android.Host:
+			if ctx.Target().HostCross {
+				prefix = "HOST_CROSS_"
+			} else {
+				prefix = "HOST_"
+			}
+		case android.Device:
+			prefix = "TARGET_"
+
 		}
-	})
+
+		if ctx.Arch().ArchType != config.Targets[ctx.Os()][0].Arch.ArchType {
+			prefix = "2ND_" + prefix
+		}
+	}
+
+	varname := fmt.Sprintf("SOONG_%sOBJECT_%s%s", prefix, name, entries.SubName)
+
+	entries.FooterStrings = append(entries.FooterStrings,
+		fmt.Sprintf("\n%s := %s\n.KATI_READONLY: %s", varname, out.String(), varname))
 }
 
-func (binary *binaryDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
-	ctx.subAndroidMk(entries, binary.baseInstaller)
+func (test *testDecorator) prepareAndroidMKProviderInfo(config android.Config, ctx AndroidMkContext, entries *android.AndroidMkInfo) {
+	if len(test.InstallerProperties.Test_suites) > 0 {
+		entries.AddCompatibilityTestSuites(test.InstallerProperties.Test_suites...)
+	}
+}
+
+func (binary *binaryDecorator) prepareAndroidMKProviderInfo(config android.Config, ctx AndroidMkContext, entries *android.AndroidMkInfo) {
+	ctx.subAndroidMk(config, entries, binary.baseInstaller)
 
 	entries.Class = "EXECUTABLES"
 	entries.DistFiles = binary.distFiles
-	entries.ExtraEntries = append(entries.ExtraEntries, func(_ android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		entries.SetString("LOCAL_SOONG_UNSTRIPPED_BINARY", binary.unstrippedOutputFile.String())
-		if len(binary.symlinks) > 0 {
-			entries.AddStrings("LOCAL_MODULE_SYMLINKS", binary.symlinks...)
-		}
+	entries.SetString("LOCAL_SOONG_UNSTRIPPED_BINARY", binary.unstrippedOutputFile.String())
+	if len(binary.symlinks) > 0 {
+		entries.AddStrings("LOCAL_MODULE_SYMLINKS", binary.symlinks...)
+	}
 
-		if binary.coverageOutputFile.Valid() {
-			entries.SetString("LOCAL_PREBUILT_COVERAGE_ARCHIVE", binary.coverageOutputFile.String())
-		}
+	if binary.coverageOutputFile.Valid() {
+		entries.SetString("LOCAL_PREBUILT_COVERAGE_ARCHIVE", binary.coverageOutputFile.String())
+	}
 
-		if len(binary.Properties.Overrides) > 0 {
-			entries.SetString("LOCAL_OVERRIDES_MODULES", strings.Join(makeOverrideModuleNames(ctx, binary.Properties.Overrides), " "))
-		}
-		if len(binary.postInstallCmds) > 0 {
-			entries.SetString("LOCAL_POST_INSTALL_CMD", strings.Join(binary.postInstallCmds, "&& "))
-		}
-	})
+	if len(binary.Properties.Overrides) > 0 {
+		entries.SetString("LOCAL_OVERRIDES_MODULES", strings.Join(makeOverrideModuleNames(ctx, binary.Properties.Overrides), " "))
+	}
+	if len(binary.postInstallCmds) > 0 {
+		entries.SetString("LOCAL_POST_INSTALL_CMD", strings.Join(binary.postInstallCmds, "&& "))
+	}
 }
 
-func (benchmark *benchmarkDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
-	ctx.subAndroidMk(entries, benchmark.binaryDecorator)
+func (benchmark *benchmarkDecorator) prepareAndroidMKProviderInfo(config android.Config, ctx AndroidMkContext, entries *android.AndroidMkInfo) {
+	ctx.subAndroidMk(config, entries, benchmark.binaryDecorator)
 	entries.Class = "NATIVE_TESTS"
-	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		if len(benchmark.Properties.Test_suites) > 0 {
-			entries.AddCompatibilityTestSuites(benchmark.Properties.Test_suites...)
-		}
-		if benchmark.testConfig != nil {
-			entries.SetString("LOCAL_FULL_TEST_CONFIG", benchmark.testConfig.String())
-		}
-		entries.SetBool("LOCAL_NATIVE_BENCHMARK", true)
-		if !BoolDefault(benchmark.Properties.Auto_gen_config, true) {
-			entries.SetBool("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", true)
-		}
-	})
+	if len(benchmark.Properties.Test_suites) > 0 {
+		entries.AddCompatibilityTestSuites(benchmark.Properties.Test_suites...)
+	}
+	if benchmark.testConfig != nil {
+		entries.SetString("LOCAL_FULL_TEST_CONFIG", benchmark.testConfig.String())
+	}
+	entries.SetBool("LOCAL_NATIVE_BENCHMARK", true)
+	if !BoolDefault(benchmark.Properties.Auto_gen_config, true) {
+		entries.SetBool("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", true)
+	}
 }
 
-func (test *testBinary) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
-	ctx.subAndroidMk(entries, test.binaryDecorator)
-	ctx.subAndroidMk(entries, test.testDecorator)
+func (test *testBinary) prepareAndroidMKProviderInfo(config android.Config, ctx AndroidMkContext, entries *android.AndroidMkInfo) {
+	ctx.subAndroidMk(config, entries, test.binaryDecorator)
+	ctx.subAndroidMk(config, entries, test.testDecorator)
 
 	entries.Class = "NATIVE_TESTS"
-	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		if test.testConfig != nil {
-			entries.SetString("LOCAL_FULL_TEST_CONFIG", test.testConfig.String())
-		}
-		if !BoolDefault(test.Properties.Auto_gen_config, true) {
-			entries.SetBool("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", true)
-		}
-		entries.AddStrings("LOCAL_TEST_MAINLINE_MODULES", test.Properties.Test_mainline_modules...)
+	if test.testConfig != nil {
+		entries.SetString("LOCAL_FULL_TEST_CONFIG", test.testConfig.String())
+	}
+	if !BoolDefault(test.Properties.Auto_gen_config, true) {
+		entries.SetBool("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", true)
+	}
+	entries.AddStrings("LOCAL_TEST_MAINLINE_MODULES", test.Properties.Test_mainline_modules...)
 
-		entries.SetBoolIfTrue("LOCAL_COMPATIBILITY_PER_TESTCASE_DIRECTORY", Bool(test.Properties.Per_testcase_directory))
-		if len(test.Properties.Data_bins) > 0 {
-			entries.AddStrings("LOCAL_TEST_DATA_BINS", test.Properties.Data_bins...)
-		}
+	entries.SetBoolIfTrue("LOCAL_COMPATIBILITY_PER_TESTCASE_DIRECTORY", Bool(test.Properties.Per_testcase_directory))
+	if len(test.Properties.Data_bins) > 0 {
+		entries.AddStrings("LOCAL_TEST_DATA_BINS", test.Properties.Data_bins...)
+	}
 
-		test.Properties.Test_options.CommonTestOptions.SetAndroidMkEntries(entries)
-	})
+	test.Properties.Test_options.CommonTestOptions.SetAndroidMkInfoEntries(entries)
 
 	androidMkWriteExtraTestConfigs(test.extraTestConfigs, entries)
 }
 
-func (fuzz *fuzzBinary) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
-	ctx.subAndroidMk(entries, fuzz.binaryDecorator)
+func (fuzz *fuzzBinary) prepareAndroidMKProviderInfo(config android.Config, ctx AndroidMkContext, entries *android.AndroidMkInfo) {
+	ctx.subAndroidMk(config, entries, fuzz.binaryDecorator)
 
-	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		entries.SetBool("LOCAL_IS_FUZZ_TARGET", true)
-		if fuzz.installedSharedDeps != nil {
-			// TOOD: move to install dep
-			entries.AddStrings("LOCAL_FUZZ_INSTALLED_SHARED_DEPS", fuzz.installedSharedDeps...)
-		}
-	})
+	entries.SetBool("LOCAL_IS_FUZZ_TARGET", true)
+	if fuzz.installedSharedDeps != nil {
+		// TOOD: move to install dep
+		entries.AddStrings("LOCAL_FUZZ_INSTALLED_SHARED_DEPS", fuzz.installedSharedDeps...)
+	}
 }
 
-func (test *testLibrary) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
-	ctx.subAndroidMk(entries, test.libraryDecorator)
-	ctx.subAndroidMk(entries, test.testDecorator)
+func (test *testLibrary) prepareAndroidMKProviderInfo(config android.Config, ctx AndroidMkContext, entries *android.AndroidMkInfo) {
+	ctx.subAndroidMk(config, entries, test.libraryDecorator)
+	ctx.subAndroidMk(config, entries, test.testDecorator)
 }
 
-func (installer *baseInstaller) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
+func (installer *baseInstaller) prepareAndroidMKProviderInfo(config android.Config, ctx AndroidMkContext, entries *android.AndroidMkInfo) {
 	if installer.path == (android.InstallPath{}) {
 		return
 	}
 
-	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		path, file := filepath.Split(installer.path.String())
-		stem, suffix, _ := android.SplitFileExt(file)
-		entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
-		entries.SetString("LOCAL_MODULE_PATH", path)
-		entries.SetString("LOCAL_MODULE_STEM", stem)
-	})
+	path, file := filepath.Split(installer.path.String())
+	stem, suffix, _ := android.SplitFileExt(file)
+	entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
+	entries.SetString("LOCAL_MODULE_PATH", path)
+	entries.SetString("LOCAL_MODULE_STEM", stem)
 }
 
-func (c *stubDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
+func (c *stubDecorator) prepareAndroidMKProviderInfo(config android.Config, ctx AndroidMkContext, entries *android.AndroidMkInfo) {
 	entries.SubName = ndkLibrarySuffix + "." + c.apiLevel.String()
 	entries.Class = "SHARED_LIBRARIES"
 
-	if !c.buildStubs() {
+	if !c.BuildStubs() {
 		entries.Disabled = true
 		return
 	}
 
-	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		path, file := filepath.Split(c.installPath.String())
-		stem, suffix, _ := android.SplitFileExt(file)
-		entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
-		entries.SetString("LOCAL_MODULE_PATH", path)
-		entries.SetString("LOCAL_MODULE_STEM", stem)
-		entries.SetBool("LOCAL_NO_NOTICE_FILE", true)
-		if c.parsedCoverageXmlPath.String() != "" {
-			entries.SetString("SOONG_NDK_API_XML", "$(SOONG_NDK_API_XML) "+c.parsedCoverageXmlPath.String())
-		}
-		entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true) // Stubs should not be installed
-	})
+	path, file := filepath.Split(c.installPath.String())
+	stem, suffix, _ := android.SplitFileExt(file)
+	entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
+	entries.SetString("LOCAL_MODULE_PATH", path)
+	entries.SetString("LOCAL_MODULE_STEM", stem)
+	entries.SetBool("LOCAL_NO_NOTICE_FILE", true)
+	if c.parsedCoverageXmlPath.String() != "" {
+		entries.SetString("SOONG_NDK_API_XML", "$(SOONG_NDK_API_XML) "+c.parsedCoverageXmlPath.String())
+	}
+	entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true) // Stubs should not be installed
 }
 
-func (c *vndkPrebuiltLibraryDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
+func (c *vndkPrebuiltLibraryDecorator) prepareAndroidMKProviderInfo(config android.Config, ctx AndroidMkContext, entries *android.AndroidMkInfo) {
 	entries.Class = "SHARED_LIBRARIES"
 
 	entries.SubName = c.androidMkSuffix
 
-	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		c.libraryDecorator.androidMkWriteExportedFlags(entries)
+	c.libraryDecorator.androidMkWriteExportedFlags(entries)
 
-		// Specifying stem is to pass check_elf_files when vendor modules link against vndk prebuilt.
-		// We can't use install path because VNDKs are not installed. Instead, Srcs is directly used.
-		_, file := filepath.Split(c.properties.Srcs[0])
-		stem, suffix, ext := android.SplitFileExt(file)
-		entries.SetString("LOCAL_BUILT_MODULE_STEM", "$(LOCAL_MODULE)"+ext)
-		entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
-		entries.SetString("LOCAL_MODULE_STEM", stem)
+	// Specifying stem is to pass check_elf_files when vendor modules link against vndk prebuilt.
+	// We can't use install path because VNDKs are not installed. Instead, Srcs is directly used.
+	_, file := filepath.Split(c.properties.Srcs[0])
+	stem, suffix, ext := android.SplitFileExt(file)
+	entries.SetString("LOCAL_BUILT_MODULE_STEM", "$(LOCAL_MODULE)"+ext)
+	entries.SetString("LOCAL_MODULE_SUFFIX", suffix)
+	entries.SetString("LOCAL_MODULE_STEM", stem)
 
-		if c.tocFile.Valid() {
-			entries.SetString("LOCAL_SOONG_TOC", c.tocFile.String())
-		}
+	if c.tocFile.Valid() {
+		entries.SetString("LOCAL_SOONG_TOC", c.tocFile.String())
+	}
 
-		// VNDK libraries available to vendor are not installed because
-		// they are packaged in VNDK APEX and installed by APEX packages (apex/apex.go)
-		entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true)
-	})
+	// VNDK libraries available to vendor are not installed because
+	// they are packaged in VNDK APEX and installed by APEX packages (apex/apex.go)
+	entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true)
 }
 
-func (p *prebuiltLinker) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
-	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		if p.properties.Check_elf_files != nil {
-			entries.SetBool("LOCAL_CHECK_ELF_FILES", *p.properties.Check_elf_files)
-		} else {
-			// soong_cc_rust_prebuilt.mk does not include check_elf_file.mk by default
-			// because cc_library_shared and cc_binary use soong_cc_rust_prebuilt.mk as well.
-			// In order to turn on prebuilt ABI checker, set `LOCAL_CHECK_ELF_FILES` to
-			// true if `p.properties.Check_elf_files` is not specified.
-			entries.SetBool("LOCAL_CHECK_ELF_FILES", true)
-		}
-	})
+func (p *prebuiltLinker) prepareAndroidMKProviderInfo(config android.Config, ctx AndroidMkContext, entries *android.AndroidMkInfo) {
+	if p.properties.Check_elf_files != nil {
+		entries.SetBool("LOCAL_CHECK_ELF_FILES", *p.properties.Check_elf_files)
+	} else {
+		// soong_cc_rust_prebuilt.mk does not include check_elf_file.mk by default
+		// because cc_library_shared and cc_binary use soong_cc_rust_prebuilt.mk as well.
+		// In order to turn on prebuilt ABI checker, set `LOCAL_CHECK_ELF_FILES` to
+		// true if `p.properties.Check_elf_files` is not specified.
+		entries.SetBool("LOCAL_CHECK_ELF_FILES", true)
+	}
 }
 
-func (p *prebuiltLibraryLinker) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
-	ctx.subAndroidMk(entries, p.libraryDecorator)
+func (p *prebuiltLibraryLinker) prepareAndroidMKProviderInfo(config android.Config, ctx AndroidMkContext, entries *android.AndroidMkInfo) {
+	ctx.subAndroidMk(config, entries, p.libraryDecorator)
 	if p.shared() {
-		ctx.subAndroidMk(entries, &p.prebuiltLinker)
+		ctx.subAndroidMk(config, entries, &p.prebuiltLinker)
 		androidMkWritePrebuiltOptions(p.baseLinker, entries)
 	}
 }
 
-func (p *prebuiltBinaryLinker) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
-	ctx.subAndroidMk(entries, p.binaryDecorator)
-	ctx.subAndroidMk(entries, &p.prebuiltLinker)
+func (p *prebuiltBinaryLinker) prepareAndroidMKProviderInfo(config android.Config, ctx AndroidMkContext, entries *android.AndroidMkInfo) {
+	ctx.subAndroidMk(config, entries, p.binaryDecorator)
+	ctx.subAndroidMk(config, entries, &p.prebuiltLinker)
 	androidMkWritePrebuiltOptions(p.baseLinker, entries)
 }
 
-func androidMkWritePrebuiltOptions(linker *baseLinker, entries *android.AndroidMkEntries) {
+func androidMkWritePrebuiltOptions(linker *baseLinker, entries *android.AndroidMkInfo) {
 	allow := linker.Properties.Allow_undefined_symbols
 	if allow != nil {
-		entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-			entries.SetBool("LOCAL_ALLOW_UNDEFINED_SYMBOLS", *allow)
-		})
+		entries.SetBool("LOCAL_ALLOW_UNDEFINED_SYMBOLS", *allow)
 	}
 	ignore := linker.Properties.Ignore_max_page_size
 	if ignore != nil {
-		entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-			entries.SetBool("LOCAL_IGNORE_MAX_PAGE_SIZE", *ignore)
-		})
+		entries.SetBool("LOCAL_IGNORE_MAX_PAGE_SIZE", *ignore)
 	}
 }
diff --git a/cc/api_level.go b/cc/api_level.go
index 69a0d3a..deca723 100644
--- a/cc/api_level.go
+++ b/cc/api_level.go
@@ -41,12 +41,25 @@
 	}
 }
 
-func nativeApiLevelFromUser(ctx android.BaseModuleContext,
-	raw string) (android.ApiLevel, error) {
+// Native API levels cannot be less than the MinApiLevelForArch. This function
+// sets the lower bound of the API level with the MinApiLevelForArch.
+func nativeClampedApiLevel(ctx android.BaseModuleContext,
+	apiLevel android.ApiLevel) android.ApiLevel {
 
 	min := MinApiForArch(ctx, ctx.Arch().ArchType)
+
+	if apiLevel.LessThan(min) {
+		return min
+	}
+
+	return apiLevel
+}
+
+func NativeApiLevelFromUser(ctx android.BaseModuleContext,
+	raw string) (android.ApiLevel, error) {
+
 	if raw == "minimum" {
-		return min, nil
+		return MinApiForArch(ctx, ctx.Arch().ArchType), nil
 	}
 
 	value, err := android.ApiLevelFromUser(ctx, raw)
@@ -54,16 +67,13 @@
 		return android.NoneApiLevel, err
 	}
 
-	if value.LessThan(min) {
-		return min, nil
-	}
-
-	return value, nil
+	return nativeClampedApiLevel(ctx, value), nil
 }
 
 func nativeApiLevelOrPanic(ctx android.BaseModuleContext,
 	raw string) android.ApiLevel {
-	value, err := nativeApiLevelFromUser(ctx, raw)
+
+	value, err := NativeApiLevelFromUser(ctx, raw)
 	if err != nil {
 		panic(err.Error())
 	}
diff --git a/cc/binary.go b/cc/binary.go
index 2ac9a45..c4791c5 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -426,7 +426,7 @@
 	validations = append(validations, objs.tidyDepFiles...)
 	linkerDeps = append(linkerDeps, flags.LdFlagsDeps...)
 
-	if generatedLib := generateRustStaticlib(ctx, deps.RustRlibDeps); generatedLib != nil {
+	if generatedLib := GenerateRustStaticlib(ctx, deps.RustRlibDeps); generatedLib != nil {
 		deps.StaticLibs = append(deps.StaticLibs, generatedLib)
 	}
 
@@ -505,7 +505,7 @@
 	// The original path becomes a symlink to the corresponding file in the
 	// runtime APEX.
 	translatedArch := ctx.Target().NativeBridge == android.NativeBridgeEnabled
-	if InstallToBootstrap(ctx.baseModuleName(), ctx.Config()) && !ctx.Host() && ctx.directlyInAnyApex() &&
+	if InstallToBootstrap(ctx.baseModuleName(), ctx.Config()) && !ctx.Host() && !ctx.isSdkVariant() &&
 		!translatedArch && ctx.apexVariationName() == "" && !ctx.inRamdisk() && !ctx.inRecovery() &&
 		!ctx.inVendorRamdisk() {
 
diff --git a/cc/binary_test.go b/cc/binary_test.go
index 3e18940..4f001d7 100644
--- a/cc/binary_test.go
+++ b/cc/binary_test.go
@@ -29,7 +29,7 @@
 			linker_scripts: ["foo.ld", "bar.ld"],
 		}`)
 
-	binFoo := result.ModuleForTests("foo", "android_arm64_armv8-a").Rule("ld")
+	binFoo := result.ModuleForTests(t, "foo", "android_arm64_armv8-a").Rule("ld")
 
 	android.AssertStringListContains(t, "missing dependency on linker_scripts",
 		binFoo.Implicits.Strings(), "foo.ld")
diff --git a/cc/builder.go b/cc/builder.go
index cd535c1..f4f8596 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -472,15 +472,25 @@
 }
 
 // Generate rules for compiling multiple .c, .cpp, or .S files to individual .o files
-func transformSourceToObj(ctx ModuleContext, subdir string, srcFiles, noTidySrcs, timeoutTidySrcs android.Paths,
-	flags builderFlags, pathDeps android.Paths, cFlagsDeps android.Paths) Objects {
+func transformSourceToObj(ctx android.ModuleContext, subdir string, srcFiles, noTidySrcs, timeoutTidySrcs android.Paths,
+	flags builderFlags, pathDeps android.Paths, cFlagsDeps android.Paths, sharedFlags *SharedFlags) Objects {
+
+	// Not all source files produce a .o; a Rust source provider
+	// may provide both a .c and a .rs file (e.g. rust_bindgen).
+	srcObjFiles := android.Paths{}
+	for _, src := range srcFiles {
+		if src.Ext() != ".rs" {
+			srcObjFiles = append(srcObjFiles, src)
+		}
+	}
+
 	// Source files are one-to-one with tidy, coverage, or kythe files, if enabled.
-	objFiles := make(android.Paths, len(srcFiles))
+	objFiles := make(android.Paths, len(srcObjFiles))
 	var tidyFiles android.Paths
 	noTidySrcsMap := make(map[string]bool)
 	var tidyVars string
 	if flags.tidy {
-		tidyFiles = make(android.Paths, 0, len(srcFiles))
+		tidyFiles = make(android.Paths, 0, len(srcObjFiles))
 		for _, path := range noTidySrcs {
 			noTidySrcsMap[path.String()] = true
 		}
@@ -495,11 +505,11 @@
 	}
 	var coverageFiles android.Paths
 	if flags.gcovCoverage {
-		coverageFiles = make(android.Paths, 0, len(srcFiles))
+		coverageFiles = make(android.Paths, 0, len(srcObjFiles))
 	}
 	var kytheFiles android.Paths
 	if flags.emitXrefs && ctx.Module() == ctx.PrimaryModule() {
-		kytheFiles = make(android.Paths, 0, len(srcFiles))
+		kytheFiles = make(android.Paths, 0, len(srcObjFiles))
 	}
 
 	// Produce fully expanded flags for use by C tools, C compiles, C++ tools, C++ compiles, and asm compiles
@@ -548,16 +558,14 @@
 
 	var sAbiDumpFiles android.Paths
 	if flags.sAbiDump {
-		sAbiDumpFiles = make(android.Paths, 0, len(srcFiles))
+		sAbiDumpFiles = make(android.Paths, 0, len(srcObjFiles))
 	}
 
 	// Multiple source files have build rules usually share the same cFlags or tidyFlags.
-	// Define only one version in this module and share it in multiple build rules.
-	// To simplify the code, the shared variables are all named as $flags<nnn>.
-	shared := ctx.getSharedFlags()
-
+	// SharedFlags provides one version for this module and shares it in multiple build rules.
+	// To simplify the code, the SharedFlags variables are all named as $flags<nnn>.
 	// Share flags only when there are multiple files or tidy rules.
-	var hasMultipleRules = len(srcFiles) > 1 || flags.tidy
+	var hasMultipleRules = len(srcObjFiles) > 1 || flags.tidy
 
 	var shareFlags = func(kind string, flags string) string {
 		if !hasMultipleRules || len(flags) < 60 {
@@ -566,17 +574,17 @@
 			return flags
 		}
 		mapKey := kind + flags
-		n, ok := shared.flagsMap[mapKey]
+		n, ok := sharedFlags.FlagsMap[mapKey]
 		if !ok {
-			shared.numSharedFlags += 1
-			n = strconv.Itoa(shared.numSharedFlags)
-			shared.flagsMap[mapKey] = n
+			sharedFlags.NumSharedFlags += 1
+			n = strconv.Itoa(sharedFlags.NumSharedFlags)
+			sharedFlags.FlagsMap[mapKey] = n
 			ctx.Variable(pctx, kind+n, flags)
 		}
 		return "$" + kind + n
 	}
 
-	for i, srcFile := range srcFiles {
+	for i, srcFile := range srcObjFiles {
 		objFile := android.ObjPathWithExt(ctx, subdir, srcFile, "o")
 
 		objFiles[i] = objFile
@@ -809,7 +817,7 @@
 }
 
 // Generate a Rust staticlib from a list of rlibDeps. Returns nil if TransformRlibstoStaticlib is nil or rlibDeps is empty.
-func generateRustStaticlib(ctx android.ModuleContext, rlibDeps []RustRlibDep) android.Path {
+func GenerateRustStaticlib(ctx android.ModuleContext, rlibDeps []RustRlibDep) android.Path {
 	if TransformRlibstoStaticlib == nil && len(rlibDeps) > 0 {
 		// This should only be reachable if a module defines Rust deps in static_libs and
 		// soong-rust hasn't been loaded alongside soong-cc (e.g. in soong-cc tests).
@@ -821,7 +829,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{}
 
@@ -853,6 +861,27 @@
 	return strings.Join(lines, "\n")
 }
 
+func BuildRustStubs(ctx android.ModuleContext, outputFile android.ModuleOutPath,
+	stubObjs Objects, ccFlags Flags) {
+
+	// Instantiate paths
+	sharedLibs := android.Paths{}
+	staticLibs := android.Paths{}
+	lateStaticLibs := android.Paths{}
+	wholeStaticLibs := android.Paths{}
+	deps := android.Paths{}
+	implicitOutputs := android.WritablePaths{}
+	validations := android.Paths{}
+	crtBegin := android.Paths{}
+	crtEnd := android.Paths{}
+	groupLate := false
+
+	builderFlags := flagsToBuilderFlags(ccFlags)
+	transformObjToDynamicBinary(ctx, stubObjs.objFiles, sharedLibs, staticLibs,
+		lateStaticLibs, wholeStaticLibs, deps, crtBegin, crtEnd,
+		groupLate, builderFlags, outputFile, implicitOutputs, validations)
+}
+
 // Generate a rule for compiling multiple .o files, plus static libraries, whole static libraries,
 // and shared libraries, to a shared library (.so) or dynamic executable
 func transformObjToDynamicBinary(ctx android.ModuleContext,
@@ -945,13 +974,18 @@
 func transformDumpToLinkedDump(ctx android.ModuleContext, sAbiDumps android.Paths, soFile android.Path,
 	baseName string, exportedIncludeDirs []string, symbolFile android.OptionalPath,
 	excludedSymbolVersions, excludedSymbolTags, includedSymbolTags []string,
-	api string, isLlndk bool) android.Path {
+	api string, commonGlobalIncludes bool) android.Path {
 
 	outputFile := android.PathForModuleOut(ctx, baseName+".lsdump")
 
 	implicits := android.Paths{soFile}
 	symbolFilterStr := "-so " + soFile.String()
 	exportedHeaderFlags := android.JoinWithPrefix(exportedIncludeDirs, "-I")
+	// If this library does not export any include directory, do not append the flags
+	// so that the ABI tool dumps everything without filtering by the include directories.
+	if commonGlobalIncludes && len(exportedIncludeDirs) > 0 {
+		exportedHeaderFlags += " ${config.CommonGlobalIncludes}"
+	}
 
 	if symbolFile.Valid() {
 		implicits = append(implicits, symbolFile.Path())
@@ -966,9 +1000,6 @@
 	for _, tag := range includedSymbolTags {
 		symbolFilterStr += " --include-symbol-tag " + tag
 	}
-	if isLlndk {
-		symbolFilterStr += " --symbol-tag-policy MatchTagOnly"
-	}
 	apiLevelsJson := android.GetApiLevelsJson(ctx)
 	implicits = append(implicits, apiLevelsJson)
 	symbolFilterStr += " --api-map " + apiLevelsJson.String()
diff --git a/cc/cc.go b/cc/cc.go
index 1b7624d..36e336b 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -19,23 +19,161 @@
 // is handled in builder.go
 
 import (
+	"errors"
 	"fmt"
 	"io"
+	"slices"
 	"strconv"
 	"strings"
 
-	"android/soong/testing"
-
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/depset"
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/aidl_library"
 	"android/soong/android"
 	"android/soong/cc/config"
 	"android/soong/fuzz"
-	"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]()
+
+type AidlInterfaceInfo struct {
+	// list of aidl_interface sources
+	Sources []string
+	// root directory of AIDL sources
+	AidlRoot string
+	// AIDL backend language (e.g. "cpp", "ndk")
+	Lang string
+	// list of flags passed to AIDL generator
+	Flags []string
+}
+
+type CompilerInfo struct {
+	Srcs android.Paths
+	// list of module-specific flags that will be used for C and C++ compiles.
+	Cflags               []string
+	AidlInterfaceInfo    AidlInterfaceInfo
+	LibraryDecoratorInfo *LibraryDecoratorInfo
+}
+
+type LinkerInfo struct {
+	WholeStaticLibs []string
+	// list of modules that should be statically linked into this module.
+	StaticLibs []string
+	// list of modules that should be dynamically linked into this module.
+	SharedLibs []string
+	// list of modules that should only provide headers for this module.
+	HeaderLibs               []string
+	ImplementationModuleName *string
+
+	BinaryDecoratorInfo    *BinaryDecoratorInfo
+	LibraryDecoratorInfo   *LibraryDecoratorInfo
+	TestBinaryInfo         *TestBinaryInfo
+	BenchmarkDecoratorInfo *BenchmarkDecoratorInfo
+	ObjectLinkerInfo       *ObjectLinkerInfo
+	StubDecoratorInfo      *StubDecoratorInfo
+}
+
+type BinaryDecoratorInfo struct{}
+type LibraryDecoratorInfo struct {
+	ExportIncludeDirs []string
+	InjectBsslHash    bool
+}
+
+type SnapshotInfo struct {
+	SnapshotAndroidMkSuffix string
+}
+
+type TestBinaryInfo struct {
+	Gtest bool
+}
+type BenchmarkDecoratorInfo struct{}
+
+type StubDecoratorInfo struct{}
+
+type ObjectLinkerInfo struct{}
+
+type LibraryInfo struct {
+	BuildStubs bool
+}
+
+// Common info about the cc module.
+type CcInfo struct {
+	IsPrebuilt             bool
+	CmakeSnapshotSupported bool
+	HasLlndkStubs          bool
+	CompilerInfo           *CompilerInfo
+	LinkerInfo             *LinkerInfo
+	SnapshotInfo           *SnapshotInfo
+	LibraryInfo            *LibraryInfo
+}
+
+var CcInfoProvider = blueprint.NewProvider[*CcInfo]()
+
+type LinkableInfo struct {
+	// StaticExecutable returns true if this is a binary module with "static_executable: true".
+	StaticExecutable     bool
+	Static               bool
+	Shared               bool
+	HasStubsVariants     bool
+	StubsVersion         string
+	IsStubs              bool
+	UnstrippedOutputFile android.Path
+	OutputFile           android.OptionalPath
+	CoverageFiles        android.Paths
+	// CoverageOutputFile returns the output archive of gcno coverage information files.
+	CoverageOutputFile android.OptionalPath
+	SAbiDumpFiles      android.Paths
+	// Partition returns the partition string for this module.
+	Partition            string
+	CcLibrary            bool
+	CcLibraryInterface   bool
+	RustLibraryInterface bool
+	// CrateName returns the crateName for a Rust library
+	CrateName string
+	// DepFlags returns a slice of Rustc string flags
+	ExportedCrateLinkDirs []string
+	// This can be different from the one on CommonModuleInfo
+	BaseModuleName       string
+	HasNonSystemVariants bool
+	IsLlndk              bool
+	// True if the library is in the configs known NDK list.
+	IsNdk             bool
+	InVendorOrProduct bool
+	// SubName returns the modules SubName, used for image and NDK/SDK variations.
+	SubName             string
+	InRamdisk           bool
+	OnlyInRamdisk       bool
+	InVendorRamdisk     bool
+	OnlyInVendorRamdisk bool
+	InRecovery          bool
+	OnlyInRecovery      bool
+	Installable         *bool
+	// RelativeInstallPath returns the relative install path for this module.
+	RelativeInstallPath string
+	// TODO(b/362509506): remove this once all apex_exclude uses are switched to stubs.
+	RustApexExclude bool
+	// Bootstrap tests if this module is allowed to use non-APEX version of libraries.
+	Bootstrap bool
+}
+
+var LinkableInfoProvider = blueprint.NewProvider[*LinkableInfo]()
+
 func init() {
 	RegisterCCBuildComponents(android.InitRegistrationContext)
 
@@ -48,10 +186,10 @@
 
 	ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
 		ctx.Transition("sdk", &sdkTransitionMutator{})
-		ctx.BottomUp("llndk", llndkMutator).Parallel()
+		ctx.BottomUp("llndk", llndkMutator)
 		ctx.Transition("link", &linkageTransitionMutator{})
 		ctx.Transition("version", &versionTransitionMutator{})
-		ctx.BottomUp("begin", BeginMutator).Parallel()
+		ctx.BottomUp("begin", BeginMutator)
 	})
 
 	ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
@@ -59,10 +197,10 @@
 			san.registerMutators(ctx)
 		}
 
-		ctx.BottomUp("sanitize_runtime_deps", sanitizerRuntimeDepsMutator).Parallel()
-		ctx.BottomUp("sanitize_runtime", sanitizerRuntimeMutator).Parallel()
+		ctx.BottomUp("sanitize_runtime_deps", sanitizerRuntimeDepsMutator)
+		ctx.BottomUp("sanitize_runtime", sanitizerRuntimeMutator)
 
-		ctx.BottomUp("fuzz_deps", fuzzMutatorDeps)
+		ctx.Transition("fuzz", &fuzzTransitionMutator{})
 
 		ctx.Transition("coverage", &coverageTransitionMutator{})
 
@@ -72,8 +210,8 @@
 
 		ctx.Transition("lto", &ltoTransitionMutator{})
 
-		ctx.BottomUp("check_linktype", checkLinkTypeMutator).Parallel()
-		ctx.BottomUp("double_loadable", checkDoubleLoadableLibraries).Parallel()
+		ctx.BottomUp("check_linktype", checkLinkTypeMutator)
+		ctx.BottomUp("double_loadable", checkDoubleLoadableLibraries)
 	})
 
 	ctx.PostApexMutators(func(ctx android.RegisterMutatorsContext) {
@@ -119,9 +257,10 @@
 
 	ObjFiles []string
 
-	GeneratedSources []string
-	GeneratedHeaders []string
-	GeneratedDeps    []string
+	GeneratedSources            []string
+	GeneratedHeaders            []string
+	DeviceFirstGeneratedHeaders []string
+	GeneratedDeps               []string
 
 	ReexportGeneratedHeaders []string
 
@@ -169,7 +308,7 @@
 	RustRlibDeps []RustRlibDep
 
 	// Transitive static library dependencies of static libraries for use in ordering.
-	TranstiveStaticLibrariesForOrdering *android.DepSet[android.Path]
+	TranstiveStaticLibrariesForOrdering depset.DepSet[android.Path]
 
 	// Paths to .o files
 	Objs Objects
@@ -213,6 +352,9 @@
 	// LLNDK headers for the ABI checker to check LLNDK implementation library.
 	LlndkIncludeDirs       android.Paths
 	LlndkSystemIncludeDirs android.Paths
+
+	directImplementationDeps     android.Paths
+	transitiveImplementationDeps []depset.DepSet[android.Path]
 }
 
 // LocalOrGlobalFlags contains flags that need to have values set globally by the build system or locally by the module
@@ -313,15 +455,14 @@
 	// If true, always create an sdk variant and don't create a platform variant.
 	Sdk_variant_only *bool
 
-	AndroidMkSharedLibs       []string `blueprint:"mutated"`
-	AndroidMkStaticLibs       []string `blueprint:"mutated"`
-	AndroidMkRlibs            []string `blueprint:"mutated"`
-	AndroidMkRuntimeLibs      []string `blueprint:"mutated"`
-	AndroidMkWholeStaticLibs  []string `blueprint:"mutated"`
-	AndroidMkHeaderLibs       []string `blueprint:"mutated"`
-	HideFromMake              bool     `blueprint:"mutated"`
-	PreventInstall            bool     `blueprint:"mutated"`
-	ApexesProvidingSharedLibs []string `blueprint:"mutated"`
+	AndroidMkSharedLibs      []string `blueprint:"mutated"`
+	AndroidMkStaticLibs      []string `blueprint:"mutated"`
+	AndroidMkRlibs           []string `blueprint:"mutated"`
+	AndroidMkRuntimeLibs     []string `blueprint:"mutated"`
+	AndroidMkWholeStaticLibs []string `blueprint:"mutated"`
+	AndroidMkHeaderLibs      []string `blueprint:"mutated"`
+	HideFromMake             bool     `blueprint:"mutated"`
+	PreventInstall           bool     `blueprint:"mutated"`
 
 	// Set by DepsMutator.
 	AndroidMkSystemSharedLibs []string `blueprint:"mutated"`
@@ -389,11 +530,6 @@
 	// variant to have a ".sdk" suffix.
 	SdkAndPlatformVariantVisibleToMake bool `blueprint:"mutated"`
 
-	// List of APEXes that this module has private access to for testing purpose. The module
-	// can depend on libraries that are not exported by the APEXes and use private symbols
-	// from the exported libraries.
-	Test_for []string `android:"arch_variant"`
-
 	Target struct {
 		Platform struct {
 			// List of modules required by the core variant.
@@ -490,13 +626,13 @@
 type ModuleContextIntf interface {
 	static() bool
 	staticBinary() bool
+	staticLibrary() bool
 	testBinary() bool
 	testLibrary() bool
 	header() bool
 	binary() bool
 	object() bool
 	toolchain() config.Toolchain
-	canUseSdk() bool
 	useSdk() bool
 	sdkVersion() string
 	minSdkVersion() string
@@ -520,22 +656,20 @@
 	isFuzzer() bool
 	isNDKStubLibrary() bool
 	useClangLld(actx ModuleContext) bool
-	isForPlatform() bool
 	apexVariationName() string
-	apexSdkVersion() android.ApiLevel
 	bootstrap() bool
 	nativeCoverage() bool
-	directlyInAnyApex() bool
 	isPreventInstall() bool
 	isCfiAssemblySupportEnabled() bool
 	getSharedFlags() *SharedFlags
 	notInPlatform() bool
 	optimizeForSize() bool
+	getOrCreateMakeVarsInfo() *CcMakeVarsInfo
 }
 
 type SharedFlags struct {
-	numSharedFlags int
-	flagsMap       map[string]string
+	NumSharedFlags int
+	FlagsMap       map[string]string
 }
 
 type ModuleContext interface {
@@ -639,10 +773,6 @@
 	installInRoot() bool
 }
 
-type xref interface {
-	XrefCcFiles() android.Paths
-}
-
 type overridable interface {
 	overriddenModules() []string
 }
@@ -709,6 +839,7 @@
 
 	reexportFlags       bool
 	explicitlyVersioned bool
+	explicitlyImpl      bool
 	dataLib             bool
 	ndk                 bool
 
@@ -797,13 +928,18 @@
 	dataLibDepTag         = dependencyTag{name: "data lib"}
 	dataBinDepTag         = dependencyTag{name: "data bin"}
 	runtimeDepTag         = installDependencyTag{name: "runtime lib"}
-	stubImplDepTag        = dependencyTag{name: "stub_impl"}
+	StubImplDepTag        = dependencyTag{name: "stub_impl"}
 	JniFuzzLibTag         = dependencyTag{name: "jni_fuzz_lib_tag"}
 	FdoProfileTag         = dependencyTag{name: "fdo_profile"}
 	aidlLibraryTag        = dependencyTag{name: "aidl_library"}
 	llndkHeaderLibTag     = dependencyTag{name: "llndk_header_lib"}
 )
 
+func IsExplicitImplSharedDepTag(depTag blueprint.DependencyTag) bool {
+	ccLibDepTag, ok := depTag.(libraryDependencyTag)
+	return ok && ccLibDepTag.shared() && ccLibDepTag.explicitlyImpl
+}
+
 func IsSharedDepTag(depTag blueprint.DependencyTag) bool {
 	ccLibDepTag, ok := depTag.(libraryDependencyTag)
 	return ok && ccLibDepTag.shared()
@@ -823,6 +959,11 @@
 	return depTag == runtimeDepTag
 }
 
+func ExcludeInApexDepTag(depTag blueprint.DependencyTag) bool {
+	ccLibDepTag, ok := depTag.(libraryDependencyTag)
+	return ok && ccLibDepTag.excludeInApex
+}
+
 // Module contains the properties and members used by all C/C++ module types, and implements
 // the blueprint.Module interface.  It delegates to compiler, linker, and installer interfaces
 // to construct the output file.  Behavior can be customized with a Customizer, or "decorator",
@@ -845,9 +986,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
@@ -878,7 +1020,7 @@
 
 	cachedToolchain config.Toolchain
 
-	subAndroidMkOnce map[subAndroidMkProvider]bool
+	subAndroidMkOnce map[subAndroidMkProviderInfoProducer]bool
 
 	// Flags used to compile this module
 	flags Flags
@@ -890,12 +1032,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 +1049,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{}{
@@ -944,7 +1088,6 @@
 		"IsLlndk":                c.IsLlndk(),
 		"IsVendorPublicLibrary":  c.IsVendorPublicLibrary(),
 		"ApexSdkVersion":         c.apexSdkVersion,
-		"TestFor":                c.TestFor(),
 		"AidlSrcs":               c.hasAidl,
 		"LexSrcs":                c.hasLex,
 		"ProtoSrcs":              c.hasProto,
@@ -1044,7 +1187,21 @@
 	return String(c.Properties.Min_sdk_version)
 }
 
-func (c *Module) isCrt() bool {
+func (c *Module) SetSdkVersion(s string) {
+	c.Properties.Sdk_version = StringPtr(s)
+}
+
+func (c *Module) SetMinSdkVersion(s string) {
+	c.Properties.Min_sdk_version = StringPtr(s)
+}
+
+func (c *Module) SetStl(s string) {
+	if c.stl != nil {
+		c.stl.Properties.Stl = StringPtr(s)
+	}
+}
+
+func (c *Module) IsCrt() bool {
 	if linker, ok := c.linker.(*objectLinker); ok {
 		return linker.isCrt()
 	}
@@ -1052,7 +1209,7 @@
 }
 
 func (c *Module) SplitPerApiLevel() bool {
-	return c.canUseSdk() && c.isCrt()
+	return CanUseSdk(c) && c.IsCrt()
 }
 
 func (c *Module) AlwaysSdk() bool {
@@ -1072,7 +1229,7 @@
 }
 
 func (c *Module) CcLibraryInterface() bool {
-	if _, ok := c.linker.(libraryInterface); ok {
+	if c.library != nil {
 		return true
 	}
 	return false
@@ -1166,11 +1323,6 @@
 	return false
 }
 
-func (c *Module) IsRustFFI() bool {
-	// cc modules are not Rust modules
-	return false
-}
-
 func (c *Module) Module() android.Module {
 	return c
 }
@@ -1190,6 +1342,13 @@
 
 var _ LinkableInterface = (*Module)(nil)
 
+func (c *Module) VersionedInterface() VersionedInterface {
+	if c.library != nil {
+		return c.library
+	}
+	return nil
+}
+
 func (c *Module) UnstrippedOutputFile() android.Path {
 	if c.linker != nil {
 		return c.linker.unstrippedOutputFilePath()
@@ -1274,13 +1433,13 @@
 	return c.Properties.VndkVersion != ""
 }
 
-func (c *Module) canUseSdk() bool {
-	return c.Os() == android.Android && c.Target().NativeBridge == android.NativeBridgeDisabled &&
+func CanUseSdk(c LinkableInterface) bool {
+	return c.Module().Target().Os == android.Android && c.Target().NativeBridge == android.NativeBridgeDisabled &&
 		!c.InVendorOrProduct() && !c.InRamdisk() && !c.InRecovery() && !c.InVendorRamdisk()
 }
 
 func (c *Module) UseSdk() bool {
-	if c.canUseSdk() {
+	if CanUseSdk(c) {
 		return String(c.Properties.Sdk_version) != ""
 	}
 	return false
@@ -1299,13 +1458,13 @@
 }
 
 func (m *Module) NeedsLlndkVariants() bool {
-	lib := moduleLibraryInterface(m)
-	return lib != nil && (lib.hasLLNDKStubs() || lib.hasLLNDKHeaders())
+	lib := moduleVersionedInterface(m)
+	return lib != nil && (lib.HasLLNDKStubs() || lib.HasLLNDKHeaders())
 }
 
 func (m *Module) NeedsVendorPublicLibraryVariants() bool {
-	lib := moduleLibraryInterface(m)
-	return lib != nil && (lib.hasVendorPublicLibrary())
+	lib := moduleVersionedInterface(m)
+	return lib != nil && (lib.HasVendorPublicLibrary())
 }
 
 // IsVendorPublicLibrary returns true for vendor public libraries.
@@ -1325,13 +1484,13 @@
 }
 
 func (c *Module) HasLlndkStubs() bool {
-	lib := moduleLibraryInterface(c)
-	return lib != nil && lib.hasLLNDKStubs()
+	lib := moduleVersionedInterface(c)
+	return lib != nil && lib.HasLLNDKStubs()
 }
 
 func (c *Module) StubsVersion() string {
-	if lib, ok := c.linker.(versionedInterface); ok {
-		return lib.stubsVersion()
+	if lib, ok := c.linker.(VersionedInterface); ok {
+		return lib.StubsVersion()
 	}
 	panic(fmt.Errorf("StubsVersion called on non-versioned module: %q", c.BaseModuleName()))
 }
@@ -1340,7 +1499,7 @@
 // and does not set llndk.vendor_available: false.
 func (c *Module) isImplementationForLLNDKPublic() bool {
 	library, _ := c.library.(*libraryDecorator)
-	return library != nil && library.hasLLNDKStubs() &&
+	return library != nil && library.HasLLNDKStubs() &&
 		!Bool(library.Properties.Llndk.Private)
 }
 
@@ -1379,21 +1538,25 @@
 
 func (c *Module) IsStubs() bool {
 	if lib := c.library; lib != nil {
-		return lib.buildStubs()
+		return lib.BuildStubs()
 	}
 	return false
 }
 
 func (c *Module) HasStubsVariants() bool {
 	if lib := c.library; lib != nil {
-		return lib.hasStubsVariants()
+		return lib.HasStubsVariants()
 	}
 	return false
 }
 
+func (c *Module) RustApexExclude() bool {
+	return false
+}
+
 func (c *Module) IsStubsImplementationRequired() bool {
 	if lib := c.library; lib != nil {
-		return lib.isStubsImplementationRequired()
+		return lib.IsStubsImplementationRequired()
 	}
 	return false
 }
@@ -1402,21 +1565,21 @@
 // the implementation.  If it is an implementation library it returns its own name.
 func (c *Module) ImplementationModuleName(ctx android.BaseModuleContext) string {
 	name := ctx.OtherModuleName(c)
-	if versioned, ok := c.linker.(versionedInterface); ok {
-		name = versioned.implementationModuleName(name)
+	if versioned, ok := c.linker.(VersionedInterface); ok {
+		name = versioned.ImplementationModuleName(name)
 	}
 	return name
 }
 
-// Similar to ImplementationModuleName, but uses the Make variant of the module
+// Similar to ImplementationModuleNameByCtx, but uses the Make variant of the module
 // name as base name, for use in AndroidMk output. E.g. for a prebuilt module
 // where the Soong name is prebuilt_foo, this returns foo (which works in Make
 // under the premise that the prebuilt module overrides its source counterpart
 // if it is exposed to Make).
 func (c *Module) ImplementationModuleNameForMake(ctx android.BaseModuleContext) string {
 	name := c.BaseModuleName()
-	if versioned, ok := c.linker.(versionedInterface); ok {
-		name = versioned.implementationModuleName(name)
+	if versioned, ok := c.linker.(VersionedInterface); ok {
+		name = versioned.ImplementationModuleName(name)
 	}
 	return name
 }
@@ -1455,10 +1618,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)
@@ -1500,6 +1659,10 @@
 	return ctx.mod.staticBinary()
 }
 
+func (ctx *moduleContextImpl) staticLibrary() bool {
+	return ctx.mod.staticLibrary()
+}
+
 func (ctx *moduleContextImpl) testBinary() bool {
 	return ctx.mod.testBinary()
 }
@@ -1524,10 +1687,6 @@
 	return ctx.mod.OptimizeForSize()
 }
 
-func (ctx *moduleContextImpl) canUseSdk() bool {
-	return ctx.mod.canUseSdk()
-}
-
 func (ctx *moduleContextImpl) useSdk() bool {
 	return ctx.mod.UseSdk()
 }
@@ -1539,22 +1698,23 @@
 	return ""
 }
 
-func (ctx *moduleContextImpl) minSdkVersion() string {
-	ver := ctx.mod.MinSdkVersion()
-	if ver == "apex_inherit" && !ctx.isForPlatform() {
-		ver = ctx.apexSdkVersion().String()
+func MinSdkVersion(mod VersionedLinkableInterface, ctxIsForPlatform bool, device bool,
+	platformSdkVersion string) string {
+
+	ver := mod.MinSdkVersion()
+	if ver == "apex_inherit" && !ctxIsForPlatform {
+		ver = mod.ApexSdkVersion().String()
 	}
 	if ver == "apex_inherit" || ver == "" {
-		ver = ctx.sdkVersion()
+		ver = mod.SdkVersion()
 	}
 
-	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()
-			}
+	if device {
+		// 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 (mod.InVendor() || mod.InProduct()) && (ver == "" || ver == "current") {
+			ver = platformSdkVersion
 		}
 	}
 
@@ -1568,19 +1728,19 @@
 	// support such an old version. The version is set to the later version in case when the
 	// non-sdk variant is for the platform, or the min_sdk_version of the containing APEX if
 	// it's for an APEX.
-	if ctx.mod.isCrt() && !ctx.isSdkVariant() {
-		if ctx.isForPlatform() {
+	if mod.IsCrt() && !mod.IsSdkVariant() {
+		if ctxIsForPlatform {
 			ver = strconv.Itoa(android.FutureApiLevelInt)
 		} else { // for apex
-			ver = ctx.apexSdkVersion().String()
+			ver = mod.ApexSdkVersion().String()
 			if ver == "" { // in case when min_sdk_version was not set by the APEX
-				ver = ctx.sdkVersion()
+				ver = mod.SdkVersion()
 			}
 		}
 	}
 
 	// Also make sure that minSdkVersion is not greater than sdkVersion, if they are both numbers
-	sdkVersionInt, err := strconv.Atoi(ctx.sdkVersion())
+	sdkVersionInt, err := strconv.Atoi(mod.SdkVersion())
 	minSdkVersionInt, err2 := strconv.Atoi(ver)
 	if err == nil && err2 == nil {
 		if sdkVersionInt < minSdkVersionInt {
@@ -1590,6 +1750,14 @@
 	return ver
 }
 
+func (ctx *moduleContextImpl) minSdkVersion() string {
+	platformSdkVersion := ""
+	if ctx.ctx.Device() {
+		platformSdkVersion = ctx.ctx.Config().PlatformSdkVersion().String()
+	}
+	return MinSdkVersion(ctx.mod, CtxIsForPlatform(ctx.ctx), ctx.ctx.Device(), platformSdkVersion)
+}
+
 func (ctx *moduleContextImpl) isSdkVariant() bool {
 	return ctx.mod.IsSdkVariant()
 }
@@ -1653,8 +1821,8 @@
 	return ctx.mod.BaseModuleName()
 }
 
-func (ctx *moduleContextImpl) isForPlatform() bool {
-	apexInfo, _ := android.ModuleProvider(ctx.ctx, android.ApexInfoProvider)
+func CtxIsForPlatform(ctx android.BaseModuleContext) bool {
+	apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
 	return apexInfo.IsForPlatform()
 }
 
@@ -1663,10 +1831,6 @@
 	return apexInfo.ApexVariationName
 }
 
-func (ctx *moduleContextImpl) apexSdkVersion() android.ApiLevel {
-	return ctx.mod.apexSdkVersion
-}
-
 func (ctx *moduleContextImpl) bootstrap() bool {
 	return ctx.mod.Bootstrap()
 }
@@ -1675,19 +1839,15 @@
 	return ctx.mod.nativeCoverage()
 }
 
-func (ctx *moduleContextImpl) directlyInAnyApex() bool {
-	return ctx.mod.DirectlyInAnyApex()
-}
-
 func (ctx *moduleContextImpl) isPreventInstall() bool {
 	return ctx.mod.Properties.PreventInstall
 }
 
 func (ctx *moduleContextImpl) getSharedFlags() *SharedFlags {
 	shared := &ctx.mod.sharedFlags
-	if shared.flagsMap == nil {
-		shared.numSharedFlags = 0
-		shared.flagsMap = make(map[string]string)
+	if shared.FlagsMap == nil {
+		shared.NumSharedFlags = 0
+		shared.FlagsMap = make(map[string]string)
 	}
 	return shared
 }
@@ -1700,6 +1860,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,
@@ -1744,6 +1911,14 @@
 	return name
 }
 
+func (c *Module) Multilib() string {
+	return c.Arch().ArchType.Multilib
+}
+
+func (c *Module) ApexSdkVersion() android.ApiLevel {
+	return c.apexSdkVersion
+}
+
 func (c *Module) Symlinks() []string {
 	if p, ok := c.installer.(interface {
 		symlinkList() []string
@@ -1861,7 +2036,7 @@
 // Returns true if a stub library could be installed in multiple apexes
 func (c *Module) stubLibraryMultipleApexViolation(ctx android.ModuleContext) bool {
 	// If this is not an apex variant, no check necessary
-	if !c.InAnyApex() {
+	if info, ok := android.ModuleProvider(ctx, android.ApexInfoProvider); !ok || info.IsForPlatform() {
 		return false
 	}
 	// If this is not a stub library, no check necessary
@@ -1874,13 +2049,13 @@
 		return false
 	}
 
-	_, aaWithoutTestApexes, _ := android.ListSetDifference(c.ApexAvailable(), c.TestApexes())
 	// Stub libraries should not have more than one apex_available
-	if len(aaWithoutTestApexes) > 1 {
+	apexAvailable := android.FirstUniqueStrings(c.ApexAvailable())
+	if len(apexAvailable) > 1 {
 		return true
 	}
 	// Stub libraries should not use the wildcard
-	if aaWithoutTestApexes[0] == android.AvailableToAnyApex {
+	if apexAvailable[0] == android.AvailableToAnyApex {
 		return true
 	}
 	// Default: no violation
@@ -2023,9 +2198,6 @@
 		if ctx.Failed() {
 			return
 		}
-		c.kytheFiles = objs.kytheFiles
-		c.objFiles = objs.objFiles
-		c.tidyFiles = objs.tidyFiles
 	}
 
 	if c.linker != nil {
@@ -2036,12 +2208,11 @@
 		c.outputFile = android.OptionalPathForPath(outputFile)
 
 		c.maybeUnhideFromMake()
-	}
-	if c.testModule {
-		android.SetProvider(ctx, testing.TestModuleProviderKey, testing.TestModuleProviderData{})
-	}
 
-	android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: deps.GeneratedSources.Strings()})
+		android.SetProvider(ctx, ImplementationDepInfoProvider, &ImplementationDepInfo{
+			ImplementationDeps: depset.New(depset.PREORDER, deps.directImplementationDeps, deps.transitiveImplementationDeps),
+		})
+	}
 
 	if Bool(c.Properties.Cmake_snapshot_supported) {
 		android.SetProvider(ctx, cmakeSnapshotSourcesProvider, android.GlobFiles(ctx, ctx.ModuleDir()+"/**/*", nil))
@@ -2093,7 +2264,142 @@
 		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)
+	}
+
+	linkableInfo := CreateCommonLinkableInfo(ctx, c)
+	if lib, ok := c.linker.(VersionedInterface); ok {
+		linkableInfo.StubsVersion = lib.StubsVersion()
+	}
+	if c.linker != nil {
+		if library, ok := c.linker.(libraryInterface); ok {
+			linkableInfo.Static = library.static()
+			linkableInfo.Shared = library.shared()
+			linkableInfo.CoverageFiles = library.objs().coverageFiles
+			linkableInfo.SAbiDumpFiles = library.objs().sAbiDumpFiles
+		}
+	}
+	android.SetProvider(ctx, LinkableInfoProvider, linkableInfo)
+
+	ccInfo := CcInfo{
+		IsPrebuilt:             c.IsPrebuilt(),
+		CmakeSnapshotSupported: proptools.Bool(c.Properties.Cmake_snapshot_supported),
+		HasLlndkStubs:          c.HasLlndkStubs(),
+	}
+	if c.compiler != nil {
+		cflags := c.compiler.baseCompilerProps().Cflags
+		ccInfo.CompilerInfo = &CompilerInfo{
+			Srcs:   c.compiler.(CompiledInterface).Srcs(),
+			Cflags: cflags.GetOrDefault(ctx, nil),
+			AidlInterfaceInfo: AidlInterfaceInfo{
+				Sources:  c.compiler.baseCompilerProps().AidlInterface.Sources,
+				AidlRoot: c.compiler.baseCompilerProps().AidlInterface.AidlRoot,
+				Lang:     c.compiler.baseCompilerProps().AidlInterface.Lang,
+				Flags:    c.compiler.baseCompilerProps().AidlInterface.Flags,
+			},
+		}
+		switch decorator := c.compiler.(type) {
+		case *libraryDecorator:
+			ccInfo.CompilerInfo.LibraryDecoratorInfo = &LibraryDecoratorInfo{
+				ExportIncludeDirs: decorator.flagExporter.Properties.Export_include_dirs.GetOrDefault(ctx, nil),
+			}
+		}
+	}
+	if c.linker != nil {
+		baseLinkerProps := c.linker.baseLinkerProps()
+		ccInfo.LinkerInfo = &LinkerInfo{
+			WholeStaticLibs: baseLinkerProps.Whole_static_libs.GetOrDefault(ctx, nil),
+			StaticLibs:      baseLinkerProps.Static_libs.GetOrDefault(ctx, nil),
+			SharedLibs:      baseLinkerProps.Shared_libs.GetOrDefault(ctx, nil),
+			HeaderLibs:      baseLinkerProps.Header_libs.GetOrDefault(ctx, nil),
+		}
+		switch decorator := c.linker.(type) {
+		case *binaryDecorator:
+			ccInfo.LinkerInfo.BinaryDecoratorInfo = &BinaryDecoratorInfo{}
+		case *libraryDecorator:
+			ccInfo.LinkerInfo.LibraryDecoratorInfo = &LibraryDecoratorInfo{
+				InjectBsslHash: Bool(c.linker.(*libraryDecorator).Properties.Inject_bssl_hash),
+			}
+		case *testBinary:
+			ccInfo.LinkerInfo.TestBinaryInfo = &TestBinaryInfo{
+				Gtest: decorator.testDecorator.gtest(),
+			}
+		case *benchmarkDecorator:
+			ccInfo.LinkerInfo.BenchmarkDecoratorInfo = &BenchmarkDecoratorInfo{}
+		case *objectLinker:
+			ccInfo.LinkerInfo.ObjectLinkerInfo = &ObjectLinkerInfo{}
+		case *stubDecorator:
+			ccInfo.LinkerInfo.StubDecoratorInfo = &StubDecoratorInfo{}
+		}
+
+		if s, ok := c.linker.(SnapshotInterface); ok {
+			ccInfo.SnapshotInfo = &SnapshotInfo{
+				SnapshotAndroidMkSuffix: s.SnapshotAndroidMkSuffix(),
+			}
+		}
+		if v, ok := c.linker.(VersionedInterface); ok {
+			name := v.ImplementationModuleName(ctx.OtherModuleName(c))
+			ccInfo.LinkerInfo.ImplementationModuleName = &name
+		}
+	}
+	if c.library != nil {
+		ccInfo.LibraryInfo = &LibraryInfo{
+			BuildStubs: c.library.BuildStubs(),
+		}
+	}
+	android.SetProvider(ctx, CcInfoProvider, &ccInfo)
+
 	c.setOutputFiles(ctx)
+
+	if c.makeVarsInfo != nil {
+		android.SetProvider(ctx, CcMakeVarsInfoProvider, c.makeVarsInfo)
+	}
+}
+
+func CreateCommonLinkableInfo(ctx android.ModuleContext, mod VersionedLinkableInterface) *LinkableInfo {
+	return &LinkableInfo{
+		StaticExecutable:     mod.StaticExecutable(),
+		HasStubsVariants:     mod.HasStubsVariants(),
+		OutputFile:           mod.OutputFile(),
+		UnstrippedOutputFile: mod.UnstrippedOutputFile(),
+		CoverageOutputFile:   mod.CoverageOutputFile(),
+		Partition:            mod.Partition(),
+		IsStubs:              mod.IsStubs(),
+		CcLibrary:            mod.CcLibrary(),
+		CcLibraryInterface:   mod.CcLibraryInterface(),
+		RustLibraryInterface: mod.RustLibraryInterface(),
+		BaseModuleName:       mod.BaseModuleName(),
+		IsLlndk:              mod.IsLlndk(),
+		IsNdk:                mod.IsNdk(ctx.Config()),
+		HasNonSystemVariants: mod.HasNonSystemVariants(),
+		SubName:              mod.SubName(),
+		InVendorOrProduct:    mod.InVendorOrProduct(),
+		InRamdisk:            mod.InRamdisk(),
+		OnlyInRamdisk:        mod.OnlyInRamdisk(),
+		InVendorRamdisk:      mod.InVendorRamdisk(),
+		OnlyInVendorRamdisk:  mod.OnlyInVendorRamdisk(),
+		InRecovery:           mod.InRecovery(),
+		OnlyInRecovery:       mod.OnlyInRecovery(),
+		Installable:          mod.Installable(),
+		RelativeInstallPath:  mod.RelativeInstallPath(),
+		// TODO(b/362509506): remove this once all apex_exclude uses are switched to stubs.
+		RustApexExclude: mod.RustApexExclude(),
+		Bootstrap:       mod.Bootstrap(),
+	}
+}
+
+func setOutputFilesIfNotEmpty(ctx ModuleContext, files android.Paths, tag string) {
+	if len(files) > 0 {
+		ctx.SetOutputFiles(files, tag)
+	}
 }
 
 func (c *Module) setOutputFiles(ctx ModuleContext) {
@@ -2111,25 +2417,42 @@
 func buildComplianceMetadataInfo(ctx ModuleContext, c *Module, deps PathDeps) {
 	// Dump metadata that can not be done in android/compliance-metadata.go
 	complianceMetadataInfo := ctx.ComplianceMetadataInfo()
-	complianceMetadataInfo.SetStringValue(android.ComplianceMetadataProp.IS_STATIC_LIB, strconv.FormatBool(ctx.static()))
+	complianceMetadataInfo.SetStringValue(android.ComplianceMetadataProp.IS_STATIC_LIB, strconv.FormatBool(ctx.static() || ctx.ModuleType() == "cc_object"))
 	complianceMetadataInfo.SetStringValue(android.ComplianceMetadataProp.BUILT_FILES, c.outputFile.String())
 
 	// Static deps
-	staticDeps := ctx.GetDirectDepsWithTag(StaticDepTag(false))
+	staticDeps := ctx.GetDirectDepsProxyWithTag(StaticDepTag(false))
 	staticDepNames := make([]string, 0, len(staticDeps))
 	for _, dep := range staticDeps {
 		staticDepNames = append(staticDepNames, dep.Name())
 	}
+	// Process CrtBegin and CrtEnd as static libs
+	ctx.VisitDirectDepsProxy(func(dep android.ModuleProxy) {
+		depName := ctx.OtherModuleName(dep)
+		depTag := ctx.OtherModuleDependencyTag(dep)
+		switch depTag {
+		case CrtBeginDepTag:
+			staticDepNames = append(staticDepNames, depName)
+		case CrtEndDepTag:
+			staticDepNames = append(staticDepNames, depName)
+		}
+	})
 
-	staticDepPaths := make([]string, 0, len(deps.StaticLibs))
+	staticDepPaths := make([]string, 0, len(deps.StaticLibs)+len(deps.CrtBegin)+len(deps.CrtEnd))
 	for _, dep := range deps.StaticLibs {
 		staticDepPaths = append(staticDepPaths, dep.String())
 	}
+	for _, dep := range deps.CrtBegin {
+		staticDepPaths = append(staticDepPaths, dep.String())
+	}
+	for _, dep := range deps.CrtEnd {
+		staticDepPaths = append(staticDepPaths, dep.String())
+	}
 	complianceMetadataInfo.SetListValue(android.ComplianceMetadataProp.STATIC_DEPS, android.FirstUniqueStrings(staticDepNames))
 	complianceMetadataInfo.SetListValue(android.ComplianceMetadataProp.STATIC_DEP_FILES, android.FirstUniqueStrings(staticDepPaths))
 
 	// Whole static deps
-	wholeStaticDeps := ctx.GetDirectDepsWithTag(StaticDepTag(true))
+	wholeStaticDeps := ctx.GetDirectDepsProxyWithTag(StaticDepTag(true))
 	wholeStaticDepNames := make([]string, 0, len(wholeStaticDeps))
 	for _, dep := range wholeStaticDeps {
 		wholeStaticDepNames = append(wholeStaticDepNames, dep.Name())
@@ -2141,6 +2464,14 @@
 	}
 	complianceMetadataInfo.SetListValue(android.ComplianceMetadataProp.WHOLE_STATIC_DEPS, android.FirstUniqueStrings(wholeStaticDepNames))
 	complianceMetadataInfo.SetListValue(android.ComplianceMetadataProp.WHOLE_STATIC_DEP_FILES, android.FirstUniqueStrings(wholeStaticDepPaths))
+
+	// Header libs
+	headerLibDeps := ctx.GetDirectDepsProxyWithTag(HeaderDepTag())
+	headerLibDepNames := make([]string, 0, len(headerLibDeps))
+	for _, dep := range headerLibDeps {
+		headerLibDepNames = append(headerLibDepNames, dep.Name())
+	}
+	complianceMetadataInfo.SetListValue(android.ComplianceMetadataProp.HEADER_LIBS, android.FirstUniqueStrings(headerLibDepNames))
 }
 
 func (c *Module) maybeUnhideFromMake() {
@@ -2169,6 +2500,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()
 	}
@@ -2219,7 +2551,7 @@
 		c.orderfile.begin(ctx)
 	}
 	if ctx.useSdk() && c.IsSdkVariant() {
-		version, err := nativeApiLevelFromUser(ctx, ctx.sdkVersion())
+		version, err := NativeApiLevelFromUser(ctx, ctx.sdkVersion())
 		if err != nil {
 			ctx.PropertyErrorf("sdk_version", err.Error())
 			c.Properties.Sdk_version = nil
@@ -2257,6 +2589,10 @@
 	deps.RuntimeLibs = android.LastUniqueStrings(deps.RuntimeLibs)
 	deps.LlndkHeaderLibs = android.LastUniqueStrings(deps.LlndkHeaderLibs)
 
+	if err := checkConflictingExplicitVersions(deps.SharedLibs); err != nil {
+		ctx.PropertyErrorf("shared_libs", "%s", err.Error())
+	}
+
 	for _, lib := range deps.ReexportSharedLibHeaders {
 		if !inList(lib, deps.SharedLibs) {
 			ctx.PropertyErrorf("export_shared_lib_headers", "Shared library not in shared_libs: '%s'", lib)
@@ -2284,6 +2620,26 @@
 	return deps
 }
 
+func checkConflictingExplicitVersions(libs []string) error {
+	withoutVersion := func(s string) string {
+		name, _ := StubsLibNameAndVersion(s)
+		return name
+	}
+	var errs []error
+	for i, lib := range libs {
+		libName := withoutVersion(lib)
+		libsToCompare := libs[i+1:]
+		j := slices.IndexFunc(libsToCompare, func(s string) bool {
+			return withoutVersion(s) == libName
+		})
+		if j >= 0 {
+			errs = append(errs, fmt.Errorf("duplicate shared libraries with different explicit versions: %q and %q",
+				lib, libsToCompare[j]))
+		}
+	}
+	return errors.Join(errs...)
+}
+
 func (c *Module) beginMutator(actx android.BottomUpMutatorContext) {
 	ctx := &baseModuleContext{
 		BaseModuleContext: actx,
@@ -2345,9 +2701,19 @@
 
 	if version != "" && canBeOrLinkAgainstVersionVariants(mod) {
 		// Version is explicitly specified. i.e. libFoo#30
+		if version == "impl" {
+			version = ""
+		}
 		variations = append(variations, blueprint.Variation{Mutator: "version", Variation: version})
 		if tag, ok := depTag.(libraryDependencyTag); ok {
 			tag.explicitlyVersioned = true
+			if version == "" {
+				tag.explicitlyImpl = true
+			}
+			// depTag is an interface that contains a concrete non-pointer struct.  That makes the local
+			// tag variable a copy of the contents of depTag, and updating it doesn't change depTag.  Reassign
+			// the modified copy to depTag.
+			depTag = tag
 		} else {
 			panic(fmt.Errorf("Unexpected dependency tag: %T", depTag))
 		}
@@ -2609,6 +2975,11 @@
 		actx.AddDependency(c, depTag, gen)
 	}
 
+	for _, gen := range deps.DeviceFirstGeneratedHeaders {
+		depTag := genHeaderDepTag
+		actx.AddVariationDependencies(ctx.Config().AndroidFirstDeviceTarget.Variations(), depTag, gen)
+	}
+
 	crtVariations := GetCrtVariations(ctx, c)
 	actx.AddVariationDependencies(crtVariations, objDepTag, deps.ObjFiles...)
 	for _, crt := range deps.CrtBegin {
@@ -2697,6 +3068,7 @@
 		// Recovery code is not NDK
 		return
 	}
+	// Change this to LinkableInterface if Rust gets NDK support, which stubDecorators are for
 	if c, ok := to.(*Module); ok {
 		if c.StubDecorator() {
 			// These aren't real libraries, but are the stub shared libraries that are included in
@@ -2803,7 +3175,7 @@
 		if depTag == staticVariantTag {
 			return false
 		}
-		if depTag == stubImplDepTag {
+		if depTag == StubImplDepTag {
 			return false
 		}
 		if depTag == android.RequiredDepTag {
@@ -2834,7 +3206,7 @@
 	}
 	if module, ok := ctx.Module().(*Module); ok {
 		if lib, ok := module.linker.(*libraryDecorator); ok && lib.shared() {
-			if lib.hasLLNDKStubs() {
+			if lib.HasLLNDKStubs() {
 				ctx.WalkDeps(check)
 			}
 		}
@@ -2878,7 +3250,7 @@
 
 	skipModuleList := map[string]bool{}
 
-	ctx.VisitDirectDeps(func(dep android.Module) {
+	ctx.VisitDirectDepsProxy(func(dep android.ModuleProxy) {
 		depName := ctx.OtherModuleName(dep)
 		depTag := ctx.OtherModuleDependencyTag(dep)
 
@@ -2887,8 +3259,17 @@
 			return
 		}
 
+		var ccInfo *CcInfo
+		v, hasCcInfo := android.OtherModuleProvider(ctx, dep, CcInfoProvider)
+		if hasCcInfo {
+			ccInfo = v
+		}
+		linkableInfo, hasLinkableInfo := android.OtherModuleProvider(ctx, dep, LinkableInfoProvider)
 		if depTag == android.DarwinUniversalVariantTag {
-			depPaths.DarwinSecondArchOutput = dep.(*Module).OutputFile()
+			if !hasCcInfo {
+				panic(fmt.Errorf("dep is not a cc module: %s", dep.String()))
+			}
+			depPaths.DarwinSecondArchOutput = linkableInfo.OutputFile
 			return
 		}
 
@@ -2901,34 +3282,32 @@
 			}
 		}
 
-		ccDep, ok := dep.(LinkableInterface)
-		if !ok {
-
+		if !hasLinkableInfo {
 			// handling for a few module types that aren't cc Module but that are also supported
+			genRule, ok := android.OtherModuleProvider(ctx, dep, android.GeneratedSourceInfoProvider)
 			switch depTag {
 			case genSourceDepTag:
-				if genRule, ok := dep.(genrule.SourceFileGenerator); ok {
+				if ok {
 					depPaths.GeneratedSources = append(depPaths.GeneratedSources,
-						genRule.GeneratedSourceFiles()...)
+						genRule.GeneratedSourceFiles...)
 				} else {
 					ctx.ModuleErrorf("module %q is not a gensrcs or genrule", depName)
 				}
 				// Support exported headers from a generated_sources dependency
 				fallthrough
 			case genHeaderDepTag, genHeaderExportDepTag:
-				if genRule, ok := dep.(genrule.SourceFileGenerator); ok {
+				if ok {
 					depPaths.GeneratedDeps = append(depPaths.GeneratedDeps,
-						genRule.GeneratedDeps()...)
-					dirs := genRule.GeneratedHeaderDirs()
+						genRule.GeneratedDeps...)
+					dirs := genRule.GeneratedHeaderDirs
 					depPaths.IncludeDirs = append(depPaths.IncludeDirs, dirs...)
 					if depTag == genHeaderExportDepTag {
 						depPaths.ReexportedDirs = append(depPaths.ReexportedDirs, dirs...)
 						depPaths.ReexportedGeneratedHeaders = append(depPaths.ReexportedGeneratedHeaders,
-							genRule.GeneratedSourceFiles()...)
-						depPaths.ReexportedDeps = append(depPaths.ReexportedDeps, genRule.GeneratedDeps()...)
+							genRule.GeneratedSourceFiles...)
+						depPaths.ReexportedDeps = append(depPaths.ReexportedDeps, genRule.GeneratedDeps...)
 						// Add these re-exported flags to help header-abi-dumper to infer the abi exported by a library.
 						c.sabi.Properties.ReexportedIncludes = append(c.sabi.Properties.ReexportedIncludes, dirs.Strings()...)
-
 					}
 				} else {
 					ctx.ModuleErrorf("module %q is not a genrule", depName)
@@ -2949,13 +3328,14 @@
 			return
 		}
 
-		if dep.Target().Os != ctx.Os() {
+		commonInfo := android.OtherModuleProviderOrDefault(ctx, dep, android.CommonModuleInfoKey)
+		if commonInfo.Target.Os != ctx.Os() {
 			ctx.ModuleErrorf("OS mismatch between %q (%s) and %q (%s)", ctx.ModuleName(), ctx.Os().Name, depName, dep.Target().Os.Name)
 			return
 		}
-		if dep.Target().Arch.ArchType != ctx.Arch().ArchType {
+		if commonInfo.Target.Arch.ArchType != ctx.Arch().ArchType {
 			ctx.ModuleErrorf("Arch mismatch between %q(%v) and %q(%v)",
-				ctx.ModuleName(), ctx.Arch().ArchType, depName, dep.Target().Arch.ArchType)
+				ctx.ModuleName(), ctx.Arch().ArchType, depName, commonInfo.Target.Arch.ArchType)
 			return
 		}
 
@@ -2964,7 +3344,7 @@
 			// The reuseObjTag dependency still exists because the LinkageMutator runs before the
 			// version mutator, so the stubs variant is created from the shared variant that
 			// already has the reuseObjTag dependency on the static variant.
-			if !c.library.buildStubs() {
+			if !c.library.BuildStubs() {
 				staticAnalogue, _ := android.OtherModuleProvider(ctx, dep, StaticLibraryInfoProvider)
 				objs := staticAnalogue.ReuseObjects
 				depPaths.Objs = depPaths.Objs.Append(objs)
@@ -2980,7 +3360,7 @@
 			depPaths.LlndkSystemIncludeDirs = append(depPaths.LlndkSystemIncludeDirs, depExporterInfo.SystemIncludeDirs...)
 		}
 
-		linkFile := ccDep.OutputFile()
+		linkFile := linkableInfo.OutputFile
 
 		if libDepTag, ok := depTag.(libraryDependencyTag); ok {
 			// Only use static unwinder for legacy (min_sdk_version = 29) apexes (b/144430859)
@@ -3034,6 +3414,16 @@
 				linkFile = android.OptionalPathForPath(sharedLibraryInfo.SharedLibrary)
 				depFile = sharedLibraryInfo.TableOfContents
 
+				if !sharedLibraryInfo.IsStubs {
+					// TODO(b/362509506): remove this additional check once all apex_exclude uses are switched to stubs.
+					if !linkableInfo.RustApexExclude {
+						depPaths.directImplementationDeps = append(depPaths.directImplementationDeps, android.OutputFileForModule(ctx, dep, ""))
+						if info, ok := android.OtherModuleProvider(ctx, dep, ImplementationDepInfoProvider); ok {
+							depPaths.transitiveImplementationDeps = append(depPaths.transitiveImplementationDeps, info.ImplementationDeps)
+						}
+					}
+				}
+
 				ptr = &depPaths.SharedLibs
 				switch libDepTag.Order {
 				case earlyLibraryDependency:
@@ -3051,8 +3441,8 @@
 				}
 
 			case libDepTag.static():
-				if ccDep.RustLibraryInterface() {
-					rlibDep := RustRlibDep{LibPath: linkFile.Path(), CrateName: ccDep.CrateName(), LinkDirs: ccDep.ExportedCrateLinkDirs()}
+				if linkableInfo.RustLibraryInterface {
+					rlibDep := RustRlibDep{LibPath: linkFile.Path(), CrateName: linkableInfo.CrateName, LinkDirs: linkableInfo.ExportedCrateLinkDirs}
 					depPaths.RustRlibDeps = append(depPaths.RustRlibDeps, rlibDep)
 					depPaths.IncludeDirs = append(depPaths.IncludeDirs, depExporterInfo.IncludeDirs...)
 					if libDepTag.wholeStatic {
@@ -3129,8 +3519,8 @@
 				}
 			}
 
-			if libDepTag.static() && !libDepTag.wholeStatic && !ccDep.RustLibraryInterface() {
-				if !ccDep.CcLibraryInterface() || !ccDep.Static() {
+			if libDepTag.static() && !libDepTag.wholeStatic && !linkableInfo.RustLibraryInterface {
+				if !linkableInfo.CcLibraryInterface || !linkableInfo.Static {
 					ctx.ModuleErrorf("module %q not a static library", depName)
 					return
 				}
@@ -3138,16 +3528,15 @@
 				// When combining coverage files for shared libraries and executables, coverage files
 				// in static libraries act as if they were whole static libraries. The same goes for
 				// source based Abi dump files.
-				if c, ok := ccDep.(*Module); ok {
-					staticLib := c.linker.(libraryInterface)
+				if hasCcInfo {
 					depPaths.StaticLibObjs.coverageFiles = append(depPaths.StaticLibObjs.coverageFiles,
-						staticLib.objs().coverageFiles...)
+						linkableInfo.CoverageFiles...)
 					depPaths.StaticLibObjs.sAbiDumpFiles = append(depPaths.StaticLibObjs.sAbiDumpFiles,
-						staticLib.objs().sAbiDumpFiles...)
+						linkableInfo.SAbiDumpFiles...)
 				} else {
 					// Handle non-CC modules here
 					depPaths.StaticLibObjs.coverageFiles = append(depPaths.StaticLibObjs.coverageFiles,
-						ccDep.CoverageFiles()...)
+						linkableInfo.CoverageFiles...)
 				}
 			}
 
@@ -3193,30 +3582,18 @@
 					c.sabi.Properties.ReexportedSystemIncludes, depExporterInfo.SystemIncludeDirs.Strings()...)
 			}
 
-			makeLibName := MakeLibName(ctx, c, ccDep, ccDep.BaseModuleName()) + libDepTag.makeSuffix
+			makeLibName := MakeLibName(ccInfo, linkableInfo, &commonInfo, linkableInfo.BaseModuleName) + libDepTag.makeSuffix
 			switch {
 			case libDepTag.header():
 				c.Properties.AndroidMkHeaderLibs = append(
 					c.Properties.AndroidMkHeaderLibs, makeLibName)
 			case libDepTag.shared():
-				if lib := moduleLibraryInterface(dep); lib != nil {
-					if lib.buildStubs() && dep.(android.ApexModule).InAnyApex() {
-						// Add the dependency to the APEX(es) providing the library so that
-						// m <module> can trigger building the APEXes as well.
-						depApexInfo, _ := android.OtherModuleProvider(ctx, dep, android.ApexInfoProvider)
-						for _, an := range depApexInfo.InApexVariants {
-							c.Properties.ApexesProvidingSharedLibs = append(
-								c.Properties.ApexesProvidingSharedLibs, an)
-						}
-					}
-				}
-
 				// Note: the order of libs in this list is not important because
 				// they merely serve as Make dependencies and do not affect this lib itself.
 				c.Properties.AndroidMkSharedLibs = append(
 					c.Properties.AndroidMkSharedLibs, makeLibName)
 			case libDepTag.static():
-				if !ccDep.RustLibraryInterface() {
+				if !linkableInfo.RustLibraryInterface {
 					if libDepTag.wholeStatic {
 						c.Properties.AndroidMkWholeStaticLibs = append(
 							c.Properties.AndroidMkWholeStaticLibs, makeLibName)
@@ -3232,7 +3609,7 @@
 			switch depTag {
 			case runtimeDepTag:
 				c.Properties.AndroidMkRuntimeLibs = append(
-					c.Properties.AndroidMkRuntimeLibs, MakeLibName(ctx, c, ccDep, ccDep.BaseModuleName())+libDepTag.makeSuffix)
+					c.Properties.AndroidMkRuntimeLibs, MakeLibName(ccInfo, linkableInfo, &commonInfo, linkableInfo.BaseModuleName)+libDepTag.makeSuffix)
 			case objDepTag:
 				depPaths.Objs.objFiles = append(depPaths.Objs.objFiles, linkFile.Path())
 			case CrtBeginDepTag:
@@ -3272,27 +3649,30 @@
 	return depPaths
 }
 
-func ShouldUseStubForApex(ctx android.ModuleContext, dep android.Module) bool {
-	depName := ctx.OtherModuleName(dep)
-	thisModule, ok := ctx.Module().(android.ApexModule)
-	if !ok {
-		panic(fmt.Errorf("Not an APEX module: %q", ctx.ModuleName()))
-	}
-
+func ShouldUseStubForApex(ctx android.ModuleContext, parent android.Module, dep android.ModuleProxy) bool {
 	inVendorOrProduct := false
 	bootstrap := false
-	if linkable, ok := ctx.Module().(LinkableInterface); !ok {
-		panic(fmt.Errorf("Not a Linkable module: %q", ctx.ModuleName()))
+	if ctx.EqualModules(ctx.Module(), parent) {
+		if linkable, ok := parent.(LinkableInterface); !ok {
+			ctx.ModuleErrorf("Not a Linkable module: %q", ctx.ModuleName())
+		} else {
+			inVendorOrProduct = linkable.InVendorOrProduct()
+			bootstrap = linkable.Bootstrap()
+		}
 	} else {
-		inVendorOrProduct = linkable.InVendorOrProduct()
-		bootstrap = linkable.Bootstrap()
+		if linkable, ok := android.OtherModuleProvider(ctx, parent, LinkableInfoProvider); !ok {
+			ctx.ModuleErrorf("Not a Linkable module: %q", ctx.ModuleName())
+		} else {
+			inVendorOrProduct = linkable.InVendorOrProduct
+			bootstrap = linkable.Bootstrap
+		}
 	}
 
-	apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
+	apexInfo, _ := android.OtherModuleProvider(ctx, parent, android.ApexInfoProvider)
 
 	useStubs := false
 
-	if lib := moduleLibraryInterface(dep); lib.buildStubs() && inVendorOrProduct { // LLNDK
+	if android.OtherModuleProviderOrDefault(ctx, dep, LinkableInfoProvider).IsStubs && inVendorOrProduct { // LLNDK
 		if !apexInfo.IsForPlatform() {
 			// For platform libraries, use current version of LLNDK
 			// If this is for use_vendor apex we will apply the same rules
@@ -3304,41 +3684,12 @@
 		// platform APIs, use stubs only when it is from an APEX (and not from
 		// platform) However, for host, ramdisk, vendor_ramdisk, recovery or
 		// bootstrap modules, always link to non-stub variant
-		isNotInPlatform := dep.(android.ApexModule).NotInPlatform()
+		isNotInPlatform := android.OtherModuleProviderOrDefault(ctx, dep, android.CommonModuleInfoKey).NotInPlatform
 
 		useStubs = isNotInPlatform && !bootstrap
-
-		if useStubs {
-			// Another exception: if this module is a test for an APEX, then
-			// it is linked with the non-stub variant of a module in the APEX
-			// as if this is part of the APEX.
-			testFor, _ := android.ModuleProvider(ctx, android.ApexTestForInfoProvider)
-			for _, apexContents := range testFor.ApexContents {
-				if apexContents.DirectlyInApex(depName) {
-					useStubs = false
-					break
-				}
-			}
-		}
-		if useStubs {
-			// Yet another exception: If this module and the dependency are
-			// available to the same APEXes then skip stubs between their
-			// platform variants. This complements the test_for case above,
-			// which avoids the stubs on a direct APEX library dependency, by
-			// avoiding stubs for indirect test dependencies as well.
-			//
-			// TODO(b/183882457): This doesn't work if the two libraries have
-			// only partially overlapping apex_available. For that test_for
-			// modules would need to be split into APEX variants and resolved
-			// separately for each APEX they have access to.
-			if android.AvailableToSameApexes(thisModule, dep.(android.ApexModule)) {
-				useStubs = false
-			}
-		}
 	} else {
-		// If building for APEX, use stubs when the parent is in any APEX that
-		// the child is not in.
-		useStubs = !android.DirectlyInAllApexes(apexInfo, depName)
+		// If building for APEX, always use stubs (can be bypassed by depending on <dep>#impl)
+		useStubs = true
 	}
 
 	return useStubs
@@ -3351,7 +3702,7 @@
 // library bar which provides stable interface and exists in the platform, foo uses the stub variant
 // of bar. If bar doesn't provide a stable interface (i.e. buildStubs() == false) or is in the
 // same APEX as foo, the non-stub variant of bar is used.
-func ChooseStubOrImpl(ctx android.ModuleContext, dep android.Module) (SharedLibraryInfo, FlagExporterInfo) {
+func ChooseStubOrImpl(ctx android.ModuleContext, dep android.ModuleProxy) (SharedLibraryInfo, FlagExporterInfo) {
 	depTag := ctx.OtherModuleDependencyTag(dep)
 	libDepTag, ok := depTag.(libraryDependencyTag)
 	if !ok || !libDepTag.shared() {
@@ -3364,7 +3715,7 @@
 
 	if !libDepTag.explicitlyVersioned && len(sharedLibraryStubsInfo.SharedStubLibraries) > 0 {
 		// when to use (unspecified) stubs, use the latest one.
-		if ShouldUseStubForApex(ctx, dep) {
+		if ShouldUseStubForApex(ctx, ctx.Module(), dep) {
 			stubs := sharedLibraryStubsInfo.SharedStubLibraries
 			toUse := stubs[len(stubs)-1]
 			sharedLibraryInfo = toUse.SharedLibraryInfo
@@ -3376,19 +3727,17 @@
 
 // orderStaticModuleDeps rearranges the order of the static library dependencies of the module
 // to match the topological order of the dependency tree, including any static analogues of
-// direct shared libraries.  It returns the ordered static dependencies, and an android.DepSet
+// direct shared libraries.  It returns the ordered static dependencies, and a depset.DepSet
 // of the transitive dependencies.
-func orderStaticModuleDeps(staticDeps []StaticLibraryInfo, sharedDeps []SharedLibraryInfo) (ordered android.Paths, transitive *android.DepSet[android.Path]) {
-	transitiveStaticLibsBuilder := android.NewDepSetBuilder[android.Path](android.TOPOLOGICAL)
+func orderStaticModuleDeps(staticDeps []StaticLibraryInfo, sharedDeps []SharedLibraryInfo) (ordered android.Paths, transitive depset.DepSet[android.Path]) {
+	transitiveStaticLibsBuilder := depset.NewBuilder[android.Path](depset.TOPOLOGICAL)
 	var staticPaths android.Paths
 	for _, staticDep := range staticDeps {
 		staticPaths = append(staticPaths, staticDep.StaticLibrary)
 		transitiveStaticLibsBuilder.Transitive(staticDep.TransitiveStaticLibrariesForOrdering)
 	}
 	for _, sharedDep := range sharedDeps {
-		if sharedDep.TransitiveStaticLibrariesForOrdering != nil {
-			transitiveStaticLibsBuilder.Transitive(sharedDep.TransitiveStaticLibrariesForOrdering)
-		}
+		transitiveStaticLibsBuilder.Transitive(sharedDep.TransitiveStaticLibrariesForOrdering)
 	}
 	transitiveStaticLibs := transitiveStaticLibsBuilder.Build()
 
@@ -3414,32 +3763,29 @@
 	return libName
 }
 
-func MakeLibName(ctx android.ModuleContext, c LinkableInterface, ccDep LinkableInterface, depName string) string {
+func MakeLibName(ccInfo *CcInfo, linkableInfo *LinkableInfo, commonInfo *android.CommonModuleInfo, depName string) string {
 	libName := BaseLibName(depName)
-	ccDepModule, _ := ccDep.(*Module)
-	isLLndk := ccDepModule != nil && ccDepModule.IsLlndk()
-	nonSystemVariantsExist := ccDep.HasNonSystemVariants() || isLLndk
+	isLLndk := ccInfo != nil && linkableInfo.IsLlndk
+	nonSystemVariantsExist := linkableInfo.HasNonSystemVariants || isLLndk
 
-	if ccDepModule != nil {
+	if ccInfo != nil {
 		// Use base module name for snapshots when exporting to Makefile.
-		if snapshotPrebuilt, ok := ccDepModule.linker.(SnapshotInterface); ok {
-			baseName := ccDepModule.BaseModuleName()
-
-			return baseName + snapshotPrebuilt.SnapshotAndroidMkSuffix()
+		if ccInfo.SnapshotInfo != nil {
+			return linkableInfo.BaseModuleName + ccInfo.SnapshotInfo.SnapshotAndroidMkSuffix
 		}
 	}
 
-	if ccDep.InVendorOrProduct() && nonSystemVariantsExist {
+	if linkableInfo.InVendorOrProduct && nonSystemVariantsExist {
 		// The vendor and product modules in Make will have been renamed to not conflict with the
 		// core module, so update the dependency name here accordingly.
-		return libName + ccDep.SubName()
-	} else if ccDep.InRamdisk() && !ccDep.OnlyInRamdisk() {
+		return libName + linkableInfo.SubName
+	} else if linkableInfo.InRamdisk && !linkableInfo.OnlyInRamdisk {
 		return libName + RamdiskSuffix
-	} else if ccDep.InVendorRamdisk() && !ccDep.OnlyInVendorRamdisk() {
+	} else if linkableInfo.InVendorRamdisk && !linkableInfo.OnlyInVendorRamdisk {
 		return libName + VendorRamdiskSuffix
-	} else if ccDep.InRecovery() && !ccDep.OnlyInRecovery() {
+	} else if linkableInfo.InRecovery && !linkableInfo.OnlyInRecovery {
 		return libName + RecoverySuffix
-	} else if ccDep.Target().NativeBridge == android.NativeBridgeEnabled {
+	} else if commonInfo.Target.NativeBridge == android.NativeBridgeEnabled {
 		return libName + NativeBridgeSuffix
 	} else {
 		return libName
@@ -3503,6 +3849,15 @@
 	return false
 }
 
+func (c *Module) staticLibrary() bool {
+	if static, ok := c.linker.(interface {
+		staticLibrary() bool
+	}); ok {
+		return static.staticLibrary()
+	}
+	return false
+}
+
 func (c *Module) staticBinary() bool {
 	if static, ok := c.linker.(interface {
 		staticBinary() bool
@@ -3567,6 +3922,10 @@
 	return false
 }
 
+func (c *Module) ForceDisableSanitizers() {
+	c.sanitize.Properties.ForceDisable = true
+}
+
 func (c *Module) StaticExecutable() bool {
 	if b, ok := c.linker.(*binaryDecorator); ok {
 		return b.static()
@@ -3622,23 +3981,24 @@
 	if lib := c.library; lib != nil {
 		// Stub libs and prebuilt libs in a versioned SDK are not
 		// installable to APEX even though they are shared libs.
-		return lib.shared() && !lib.buildStubs()
+		return lib.shared() && !lib.BuildStubs()
 	}
 	return false
 }
 
 func (c *Module) AvailableFor(what string) bool {
-	if linker, ok := c.linker.(interface {
-		availableFor(string) bool
-	}); ok {
-		return c.ApexModuleBase.AvailableFor(what) || linker.availableFor(what)
-	} else {
-		return c.ApexModuleBase.AvailableFor(what)
-	}
+	return android.CheckAvailableForApex(what, c.ApexAvailableFor())
 }
 
-func (c *Module) TestFor() []string {
-	return c.Properties.Test_for
+func (c *Module) ApexAvailableFor() []string {
+	list := c.ApexModuleBase.ApexAvailable()
+	if linker, ok := c.linker.(interface {
+		apexAvailable() []string
+	}); ok {
+		list = append(list, linker.apexAvailable()...)
+	}
+
+	return android.FirstUniqueStrings(list)
 }
 
 func (c *Module) EverInstallable() bool {
@@ -3696,36 +4056,24 @@
 var _ android.ApexModule = (*Module)(nil)
 
 // Implements android.ApexModule
-func (c *Module) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
-	depTag := ctx.OtherModuleDependencyTag(dep)
-	libDepTag, isLibDepTag := depTag.(libraryDependencyTag)
-
-	if cc, ok := dep.(*Module); ok {
-		if cc.HasStubsVariants() {
-			if isLibDepTag && libDepTag.shared() {
-				// dynamic dep to a stubs lib crosses APEX boundary
-				return false
-			}
-			if IsRuntimeDepTag(depTag) {
-				// runtime dep to a stubs lib also crosses APEX boundary
-				return false
-			}
-		}
-		if cc.IsLlndk() {
-			return false
-		}
-		if isLibDepTag && c.static() && libDepTag.shared() {
-			// shared_lib dependency from a static lib is considered as crossing
-			// the APEX boundary because the dependency doesn't actually is
-			// linked; the dependency is used only during the compilation phase.
-			return false
-		}
-
-		if isLibDepTag && libDepTag.excludeInApex {
-			return false
-		}
+func (c *Module) GetDepInSameApexChecker() android.DepInSameApexChecker {
+	return CcDepInSameApexChecker{
+		Static:           c.static(),
+		HasStubsVariants: c.HasStubsVariants(),
+		IsLlndk:          c.IsLlndk(),
+		Host:             c.Host(),
 	}
-	if depTag == stubImplDepTag {
+}
+
+type CcDepInSameApexChecker struct {
+	Static           bool
+	HasStubsVariants bool
+	IsLlndk          bool
+	Host             bool
+}
+
+func (c CcDepInSameApexChecker) OutgoingDepIsInSameApex(depTag blueprint.DependencyTag) bool {
+	if depTag == StubImplDepTag {
 		// We don't track from an implementation library to its stubs.
 		return false
 	}
@@ -3735,6 +4083,43 @@
 		// APEX.
 		return false
 	}
+
+	libDepTag, isLibDepTag := depTag.(libraryDependencyTag)
+	if isLibDepTag && c.Static && libDepTag.shared() {
+		// shared_lib dependency from a static lib is considered as crossing
+		// the APEX boundary because the dependency doesn't actually is
+		// linked; the dependency is used only during the compilation phase.
+		return false
+	}
+
+	if isLibDepTag && libDepTag.excludeInApex {
+		return false
+	}
+
+	return true
+}
+
+func (c CcDepInSameApexChecker) IncomingDepIsInSameApex(depTag blueprint.DependencyTag) bool {
+	if c.Host {
+		return false
+	}
+	if c.HasStubsVariants {
+		if IsSharedDepTag(depTag) && !IsExplicitImplSharedDepTag(depTag) {
+			// dynamic dep to a stubs lib crosses APEX boundary
+			return false
+		}
+		if IsRuntimeDepTag(depTag) {
+			// runtime dep to a stubs lib also crosses APEX boundary
+			return false
+		}
+		if IsHeaderDepTag(depTag) {
+			return false
+		}
+	}
+	if c.IsLlndk {
+		return false
+	}
+
 	return true
 }
 
@@ -3858,7 +4243,6 @@
 type Defaults struct {
 	android.ModuleBase
 	android.DefaultsModuleBase
-	android.ApexModuleBase
 }
 
 // cc_defaults provides a set of properties that can be inherited by other cc
@@ -3924,9 +4308,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_preprocess_no_configuration.go b/cc/cc_preprocess_no_configuration.go
new file mode 100644
index 0000000..3d4b077
--- /dev/null
+++ b/cc/cc_preprocess_no_configuration.go
@@ -0,0 +1,115 @@
+// 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 cc
+
+import (
+	"android/soong/android"
+	"slices"
+	"strings"
+)
+
+func init() {
+	RegisterCCPreprocessNoConfiguration(android.InitRegistrationContext)
+}
+
+func RegisterCCPreprocessNoConfiguration(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("cc_preprocess_no_configuration", ccPreprocessNoConfigurationFactory)
+}
+
+// cc_preprocess_no_configuration modules run the c preprocessor on a single input source file.
+// They also have "no configuration", meaning they don't have an arch or os associated with them,
+// they should be thought of as pure textual transformations of the input file. In some cases this
+// is good, in others you might want to do different transformations depending on what arch the
+// result will be compiled in, in which case you can use cc_object instead of this module.
+func ccPreprocessNoConfigurationFactory() android.Module {
+	m := &ccPreprocessNoConfiguration{}
+	m.AddProperties(&m.properties)
+	android.InitAndroidModule(m)
+	return m
+}
+
+type ccPreprocessNoConfigurationProps struct {
+	// Called Srcs for consistency with the other cc module types, but only accepts 1 input source
+	// file.
+	Srcs []string `android:"path"`
+	// The flags to pass to the c compiler. Must include -E in order to enable preprocessing-only
+	// mode.
+	Cflags []string `android:"path"`
+}
+
+type ccPreprocessNoConfiguration struct {
+	android.ModuleBase
+	properties ccPreprocessNoConfigurationProps
+}
+
+func (m *ccPreprocessNoConfiguration) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	srcs := android.PathsForModuleSrc(ctx, m.properties.Srcs)
+	if len(srcs) != 1 {
+		ctx.PropertyErrorf("Srcs", "cc_preprocess_no_configuration only accepts 1 source file, found: %v", srcs.Strings())
+		return
+	}
+	src := srcs[0]
+
+	hasE := false
+	for _, cflag := range m.properties.Cflags {
+		if cflag == "-E" {
+			hasE = true
+			break
+		} else if cflag == "-P" || strings.HasPrefix(cflag, "-D") {
+			// do nothing, allow it
+		} else {
+			ctx.PropertyErrorf("Cflags", "cc_preprocess_no_configuration only allows -D and -P flags, found: %q", cflag)
+			return
+		}
+	}
+	if !hasE {
+		ctx.PropertyErrorf("Cflags", "cc_preprocess_no_configuration must have a -E cflag")
+		return
+	}
+
+	cflags := slices.Clone(m.properties.Cflags)
+
+	// Match behavior of other cc modules:
+	// https://cs.android.com/android/platform/superproject/main/+/main:build/soong/cc/compiler.go;l=422;drc=7297f05ee8cda422ccb32c4af4d9d715d6bac10e
+	cflags = append(cflags, "-I"+ctx.ModuleDir())
+
+	var ccCmd string
+	switch src.Ext() {
+	case ".c":
+		ccCmd = "clang"
+	case ".cpp", ".cc", ".cxx", ".mm":
+		ccCmd = "clang++"
+	default:
+		ctx.PropertyErrorf("srcs", "File %s has unknown extension. Supported extensions: .c, .cpp, .cc, .cxx, .mm", src)
+		return
+	}
+
+	ccCmd = "${config.ClangBin}/" + ccCmd
+
+	outFile := android.PathForModuleOut(ctx, src.Base())
+
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        cc,
+		Description: ccCmd + " " + src.Rel(),
+		Output:      outFile,
+		Input:       src,
+		Args: map[string]string{
+			"cFlags": strings.Join(cflags, " "),
+			"ccCmd":  ccCmd,
+		},
+	})
+
+	ctx.SetOutputFiles([]android.Path{outFile}, "")
+}
diff --git a/cc/cc_preprocess_no_configuration_test.go b/cc/cc_preprocess_no_configuration_test.go
new file mode 100644
index 0000000..f09c44a
--- /dev/null
+++ b/cc/cc_preprocess_no_configuration_test.go
@@ -0,0 +1,43 @@
+// Copyright 2019 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 cc
+
+import (
+	"android/soong/android"
+	"testing"
+)
+
+func TestCcPreprocessNoConfiguration(t *testing.T) {
+	bp := `
+	cc_preprocess_no_configuration {
+		name: "foo",
+		srcs: ["main.cc"],
+		cflags: ["-E", "-DANDROID"],
+	}
+	`
+
+	fixture := android.GroupFixturePreparers(
+		android.PrepareForIntegrationTestWithAndroid,
+		android.FixtureRegisterWithContext(RegisterCCPreprocessNoConfiguration),
+		android.FixtureAddTextFile("foo/bar/Android.bp", bp),
+	)
+
+	result := fixture.RunTest(t)
+
+	foo := result.ModuleForTests(t, "foo", "")
+	actual := foo.Rule("cc").Args["cFlags"]
+	expected := "-E -DANDROID -Ifoo/bar"
+	android.AssertStringEquals(t, "cflags should be correct", expected, actual)
+}
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 3f3347b..2c06924 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -20,6 +20,7 @@
 	"reflect"
 	"regexp"
 	"runtime"
+	"slices"
 	"strings"
 	"testing"
 
@@ -39,9 +40,6 @@
 
 var prepareForCcTest = android.GroupFixturePreparers(
 	PrepareForIntegrationTestWithCc,
-	android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
-		variables.VendorApiLevel = StringPtr("202404")
-	}),
 )
 
 var apexVariationName = "apex28"
@@ -162,7 +160,7 @@
 		}
 	`)
 
-	ld := ctx.ModuleForTests("libTest", vendorVariant).Rule("ld")
+	ld := ctx.ModuleForTests(t, "libTest", vendorVariant).Rule("ld")
 	var objs []string
 	for _, o := range ld.Inputs {
 		objs = append(objs, o.Base())
@@ -173,7 +171,7 @@
 }
 
 func checkInstallPartition(t *testing.T, ctx *android.TestContext, name, variant, expected string) {
-	mod := ctx.ModuleForTests(name, variant).Module().(*Module)
+	mod := ctx.ModuleForTests(t, name, variant).Module().(*Module)
 	partitionDefined := false
 	checkPartition := func(specific bool, partition string) {
 		if specific {
@@ -313,7 +311,7 @@
 	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 
 	ctx := testCcWithConfig(t, config)
-	testingModule := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon")
+	testingModule := ctx.ModuleForTests(t, "main_test", "android_arm_armv7-a-neon")
 	testBinary := testingModule.Module().(*Module).linker.(*testBinary)
 	outputFiles := testingModule.OutputFiles(ctx, t, "")
 	if len(outputFiles) != 1 {
@@ -365,7 +363,7 @@
 	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 
 	ctx := testCcWithConfig(t, config)
-	testingModule := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon")
+	testingModule := ctx.ModuleForTests(t, "main_test", "android_arm_armv7-a-neon")
 	module := testingModule.Module()
 	testBinary := module.(*Module).linker.(*testBinary)
 	outputFiles := testingModule.OutputFiles(ctx, t, "")
@@ -381,7 +379,7 @@
 	if !strings.HasSuffix(outputPath, "/main_test") {
 		t.Errorf("expected test output file to be 'main_test', but was '%s'", outputPath)
 	}
-	entries := android.AndroidMkEntriesForTest(t, ctx, module)[0]
+	entries := android.AndroidMkInfoForTest(t, ctx, module).PrimaryInfo
 	if !strings.HasSuffix(entries.EntryMap["LOCAL_TEST_DATA"][0], ":test_lib.so:foo/bar/baz") {
 		t.Errorf("expected LOCAL_TEST_DATA to end with `:test_lib.so:foo/bar/baz`,"+
 			" but was '%s'", entries.EntryMap["LOCAL_TEST_DATA"][0])
@@ -407,9 +405,9 @@
 	`
 
 	ctx := prepareForCcTest.RunTestWithBp(t, bp).TestContext
-	module := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon").Module()
+	module := ctx.ModuleForTests(t, "main_test", "android_arm_armv7-a-neon").Module()
 
-	entries := android.AndroidMkEntriesForTest(t, ctx, module)[0]
+	entries := android.AndroidMkInfoForTest(t, ctx, module).PrimaryInfo
 	compatEntries := entries.EntryMap["LOCAL_COMPATIBILITY_SUITE"]
 	if len(compatEntries) != 2 {
 		t.Errorf("expected two elements in LOCAL_COMPATIBILITY_SUITE. got %d", len(compatEntries))
@@ -439,9 +437,9 @@
 	`
 
 	ctx := prepareForCcTest.RunTestWithBp(t, bp).TestContext
-	module := ctx.ModuleForTests("main_test_lib", "android_arm_armv7-a-neon_shared").Module()
+	module := ctx.ModuleForTests(t, "main_test_lib", "android_arm_armv7-a-neon_shared").Module()
 
-	entries := android.AndroidMkEntriesForTest(t, ctx, module)[0]
+	entries := android.AndroidMkInfoForTest(t, ctx, module).PrimaryInfo
 	compatEntries := entries.EntryMap["LOCAL_COMPATIBILITY_SUITE"]
 	if len(compatEntries) != 2 {
 		t.Errorf("expected two elements in LOCAL_COMPATIBILITY_SUITE. got %d", len(compatEntries))
@@ -670,7 +668,7 @@
 	}
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
-			module := ctx.ModuleForTests(test.name, test.variant).Module().(*Module)
+			module := ctx.ModuleForTests(t, test.name, test.variant).Module().(*Module)
 			assertString(t, module.makeLinkType, test.expected)
 		})
 	}
@@ -863,10 +861,10 @@
 	`)
 
 	variant := "android_arm64_armv8-a_static"
-	moduleA := ctx.ModuleForTests("a", variant).Module().(*Module)
+	moduleA := ctx.ModuleForTests(t, "a", variant).Module().(*Module)
 	staticLibInfo, _ := android.OtherModuleProvider(ctx, moduleA, StaticLibraryInfoProvider)
 	actual := android.Paths(staticLibInfo.TransitiveStaticLibrariesForOrdering.ToList()).RelativeToTop()
-	expected := GetOutputPaths(ctx, variant, []string{"a", "c", "b", "d"})
+	expected := GetOutputPaths(t, ctx, variant, []string{"a", "c", "b", "d"})
 
 	if !reflect.DeepEqual(actual, expected) {
 		t.Errorf("staticDeps orderings were not propagated correctly"+
@@ -899,10 +897,10 @@
 	`)
 
 	variant := "android_arm64_armv8-a_static"
-	moduleA := ctx.ModuleForTests("a", variant).Module().(*Module)
+	moduleA := ctx.ModuleForTests(t, "a", variant).Module().(*Module)
 	staticLibInfo, _ := android.OtherModuleProvider(ctx, moduleA, StaticLibraryInfoProvider)
 	actual := android.Paths(staticLibInfo.TransitiveStaticLibrariesForOrdering.ToList()).RelativeToTop()
-	expected := GetOutputPaths(ctx, variant, []string{"a", "c", "b"})
+	expected := GetOutputPaths(t, ctx, variant, []string{"a", "c", "b"})
 
 	if !reflect.DeepEqual(actual, expected) {
 		t.Errorf("staticDeps orderings did not account for shared libs"+
@@ -1006,12 +1004,12 @@
 	}
 	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"])
+	params := result.ModuleForTests(t, "libllndk", "android_vendor_arm_armv7-a-neon_shared").Description("generate stub")
+	android.AssertSame(t, "use Vendor API level for default stubs", "35", params.Args["apiLevel"])
 
 	checkExportedIncludeDirs := func(module, variant string, expectedSystemDirs []string, expectedDirs ...string) {
 		t.Helper()
-		m := result.ModuleForTests(module, variant).Module()
+		m := result.ModuleForTests(t, module, variant).Module()
 		f, _ := android.OtherModuleProvider(result, m, FlagExporterInfoProvider)
 		android.AssertPathsRelativeToTopEquals(t, "exported include dirs for "+module+"["+variant+"]",
 			expectedDirs, f.IncludeDirs)
@@ -1032,14 +1030,14 @@
 
 	checkAbiLinkerIncludeDirs := func(module string) {
 		t.Helper()
-		coreModule := result.ModuleForTests(module, coreVariant)
+		coreModule := result.ModuleForTests(t, module, coreVariant)
 		abiCheckFlags := ""
 		for _, output := range coreModule.AllOutputs() {
 			if strings.HasSuffix(output, ".so.llndk.lsdump") {
 				abiCheckFlags = coreModule.Output(output).Args["exportedHeaderFlags"]
 			}
 		}
-		vendorModule := result.ModuleForTests(module, vendorVariant).Module()
+		vendorModule := result.ModuleForTests(t, module, vendorVariant).Module()
 		vendorInfo, _ := android.OtherModuleProvider(result, vendorModule, FlagExporterInfoProvider)
 		vendorDirs := android.Concat(vendorInfo.IncludeDirs, vendorInfo.SystemIncludeDirs)
 		android.AssertStringEquals(t, module+" has different exported include dirs for vendor variant and ABI check",
@@ -1080,7 +1078,7 @@
 	`)
 
 	// _static variant is used since _shared reuses *.o from the static variant
-	cc := ctx.ModuleForTests("libvendor", "android_vendor_arm_armv7-a-neon_static").Rule("cc")
+	cc := ctx.ModuleForTests(t, "libvendor", "android_vendor_arm_armv7-a-neon_static").Rule("cc")
 	cflags := cc.Args["cFlags"]
 	if !strings.Contains(cflags, "-Imy_include") {
 		t.Errorf("cflags for libvendor must contain -Imy_include, but was %#v.", cflags)
@@ -1191,33 +1189,33 @@
 	// runtime_libs for core variants use the module names without suffixes.
 	variant := "android_arm64_armv8-a_shared"
 
-	module := ctx.ModuleForTests("libvendor_available1", variant).Module().(*Module)
+	module := ctx.ModuleForTests(t, "libvendor_available1", variant).Module().(*Module)
 	checkRuntimeLibs(t, []string{"liball_available"}, module)
 
-	module = ctx.ModuleForTests("libproduct_available1", variant).Module().(*Module)
+	module = ctx.ModuleForTests(t, "libproduct_available1", variant).Module().(*Module)
 	checkRuntimeLibs(t, []string{"liball_available"}, module)
 
-	module = ctx.ModuleForTests("libcore", variant).Module().(*Module)
+	module = ctx.ModuleForTests(t, "libcore", variant).Module().(*Module)
 	checkRuntimeLibs(t, []string{"liball_available"}, module)
 
 	// runtime_libs for vendor variants have '.vendor' suffixes if the modules have both core
 	// and vendor variants.
 	variant = "android_vendor_arm64_armv8-a_shared"
 
-	module = ctx.ModuleForTests("libvendor_available1", variant).Module().(*Module)
+	module = ctx.ModuleForTests(t, "libvendor_available1", variant).Module().(*Module)
 	checkRuntimeLibs(t, []string{"liball_available.vendor"}, module)
 
-	module = ctx.ModuleForTests("libvendor2", variant).Module().(*Module)
+	module = ctx.ModuleForTests(t, "libvendor2", variant).Module().(*Module)
 	checkRuntimeLibs(t, []string{"liball_available.vendor", "libvendor1", "libproduct_vendor.vendor"}, module)
 
 	// runtime_libs for product variants have '.product' suffixes if the modules have both core
 	// and product variants.
 	variant = "android_product_arm64_armv8-a_shared"
 
-	module = ctx.ModuleForTests("libproduct_available1", variant).Module().(*Module)
+	module = ctx.ModuleForTests(t, "libproduct_available1", variant).Module().(*Module)
 	checkRuntimeLibs(t, []string{"liball_available.product"}, module)
 
-	module = ctx.ModuleForTests("libproduct2", variant).Module().(*Module)
+	module = ctx.ModuleForTests(t, "libproduct2", variant).Module().(*Module)
 	checkRuntimeLibs(t, []string{"liball_available.product", "libproduct1", "libproduct_vendor"}, module)
 }
 
@@ -1226,11 +1224,11 @@
 	ctx := testCc(t, runtimeLibAndroidBp)
 
 	variant := "android_arm64_armv8-a_shared"
-	module := ctx.ModuleForTests("libvendor_available2", variant).Module().(*Module)
+	module := ctx.ModuleForTests(t, "libvendor_available2", variant).Module().(*Module)
 	checkRuntimeLibs(t, []string{"liball_available"}, module)
 
 	variant = "android_vendor_arm64_armv8-a_shared"
-	module = ctx.ModuleForTests("libvendor_available2", variant).Module().(*Module)
+	module = ctx.ModuleForTests(t, "libvendor_available2", variant).Module().(*Module)
 	checkRuntimeLibs(t, nil, module)
 }
 
@@ -1263,12 +1261,12 @@
 
 	// Check the shared version of lib2.
 	variant := "android_arm64_armv8-a_shared"
-	module := ctx.ModuleForTests("lib2", variant).Module().(*Module)
+	module := ctx.ModuleForTests(t, "lib2", variant).Module().(*Module)
 	checkStaticLibs(t, []string{"lib1", "libc++demangle", "libclang_rt.builtins"}, module)
 
 	// Check the static version of lib2.
 	variant = "android_arm64_armv8-a_static"
-	module = ctx.ModuleForTests("lib2", variant).Module().(*Module)
+	module = ctx.ModuleForTests(t, "lib2", variant).Module().(*Module)
 	// libc++_static is linked additionally.
 	checkStaticLibs(t, []string{"lib1", "libc++_static", "libc++demangle", "libclang_rt.builtins"}, module)
 }
@@ -1389,7 +1387,7 @@
 		t.Errorf("multilib was set to 32 for librecovery32, but its variants has %s.", arm64)
 	}
 
-	recoveryModule := ctx.ModuleForTests("libHalInRecovery", recoveryVariant).Module().(*Module)
+	recoveryModule := ctx.ModuleForTests(t, "libHalInRecovery", recoveryVariant).Module().(*Module)
 	if !recoveryModule.Platform() {
 		t.Errorf("recovery variant of libHalInRecovery must not specific to device, soc, or product")
 	}
@@ -1414,7 +1412,7 @@
 	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 
 	ctx := testCcWithConfig(t, config)
-	testingModule := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon")
+	testingModule := ctx.ModuleForTests(t, "main_test", "android_arm_armv7-a-neon")
 	module := testingModule.Module()
 	testBinary := module.(*Module).linker.(*testBinary)
 	outputFiles := testingModule.OutputFiles(ctx, t, "")
@@ -1430,7 +1428,7 @@
 	if !strings.HasSuffix(outputPath, "/main_test") {
 		t.Errorf("expected test output file to be 'main_test', but was '%s'", outputPath)
 	}
-	entries := android.AndroidMkEntriesForTest(t, ctx, module)[0]
+	entries := android.AndroidMkInfoForTest(t, ctx, module).PrimaryInfo
 	if !strings.HasSuffix(entries.EntryMap["LOCAL_TEST_DATA"][0], ":test_lib.so:foo/bar/baz") {
 		t.Errorf("expected LOCAL_TEST_DATA to end with `:test_lib.so:foo/bar/baz`,"+
 			" but was '%s'", entries.EntryMap["LOCAL_TEST_DATA"][0])
@@ -1489,14 +1487,14 @@
 		}
 	}
 
-	libBarLinkRule := ctx.ModuleForTests("libBar", "android_arm64_armv8-a_shared").Rule("ld")
+	libBarLinkRule := ctx.ModuleForTests(t, "libBar", "android_arm64_armv8-a_shared").Rule("ld")
 	libFlags := libBarLinkRule.Args["libFlags"]
 	libFoo1StubPath := "libFoo/android_arm64_armv8-a_shared_1/libFoo.so"
 	if !strings.Contains(libFlags, libFoo1StubPath) {
 		t.Errorf("%q is not found in %q", libFoo1StubPath, libFlags)
 	}
 
-	libBarCompileRule := ctx.ModuleForTests("libBar", "android_arm64_armv8-a_shared").Rule("cc")
+	libBarCompileRule := ctx.ModuleForTests(t, "libBar", "android_arm64_armv8-a_shared").Rule("cc")
 	cFlags := libBarCompileRule.Args["cFlags"]
 	libFoo1VersioningMacro := "-D__LIBFOO_API__=1"
 	if !strings.Contains(cFlags, libFoo1VersioningMacro) {
@@ -1504,111 +1502,6 @@
 	}
 }
 
-func TestStubsForLibraryInMultipleApexes(t *testing.T) {
-	t.Parallel()
-	ctx := testCc(t, `
-		cc_library_shared {
-			name: "libFoo",
-			srcs: ["foo.c"],
-			stubs: {
-				symbol_file: "foo.map.txt",
-				versions: ["current"],
-			},
-			apex_available: ["bar", "a1"],
-		}
-
-		cc_library_shared {
-			name: "libBar",
-			srcs: ["bar.c"],
-			shared_libs: ["libFoo"],
-			apex_available: ["a1"],
-		}
-
-		cc_library_shared {
-			name: "libA1",
-			srcs: ["a1.c"],
-			shared_libs: ["libFoo"],
-			apex_available: ["a1"],
-		}
-
-		cc_library_shared {
-			name: "libBarA1",
-			srcs: ["bara1.c"],
-			shared_libs: ["libFoo"],
-			apex_available: ["bar", "a1"],
-		}
-
-		cc_library_shared {
-			name: "libAnyApex",
-			srcs: ["anyApex.c"],
-			shared_libs: ["libFoo"],
-			apex_available: ["//apex_available:anyapex"],
-		}
-
-		cc_library_shared {
-			name: "libBaz",
-			srcs: ["baz.c"],
-			shared_libs: ["libFoo"],
-			apex_available: ["baz"],
-		}
-
-		cc_library_shared {
-			name: "libQux",
-			srcs: ["qux.c"],
-			shared_libs: ["libFoo"],
-			apex_available: ["qux", "bar"],
-		}`)
-
-	variants := ctx.ModuleVariantsForTests("libFoo")
-	expectedVariants := []string{
-		"android_arm64_armv8-a_shared",
-		"android_arm64_armv8-a_shared_current",
-		"android_arm_armv7-a-neon_shared",
-		"android_arm_armv7-a-neon_shared_current",
-	}
-	variantsMismatch := false
-	if len(variants) != len(expectedVariants) {
-		variantsMismatch = true
-	} else {
-		for _, v := range expectedVariants {
-			if !inList(v, variants) {
-				variantsMismatch = false
-			}
-		}
-	}
-	if variantsMismatch {
-		t.Errorf("variants of libFoo expected:\n")
-		for _, v := range expectedVariants {
-			t.Errorf("%q\n", v)
-		}
-		t.Errorf(", but got:\n")
-		for _, v := range variants {
-			t.Errorf("%q\n", v)
-		}
-	}
-
-	linkAgainstFoo := []string{"libBarA1"}
-	linkAgainstFooStubs := []string{"libBar", "libA1", "libBaz", "libQux", "libAnyApex"}
-
-	libFooPath := "libFoo/android_arm64_armv8-a_shared/libFoo.so"
-	for _, lib := range linkAgainstFoo {
-		libLinkRule := ctx.ModuleForTests(lib, "android_arm64_armv8-a_shared").Rule("ld")
-		libFlags := libLinkRule.Args["libFlags"]
-		if !strings.Contains(libFlags, libFooPath) {
-			t.Errorf("%q: %q is not found in %q", lib, libFooPath, libFlags)
-		}
-	}
-
-	libFooStubPath := "libFoo/android_arm64_armv8-a_shared_current/libFoo.so"
-	for _, lib := range linkAgainstFooStubs {
-		libLinkRule := ctx.ModuleForTests(lib, "android_arm64_armv8-a_shared").Rule("ld")
-		libFlags := libLinkRule.Args["libFlags"]
-		if !strings.Contains(libFlags, libFooStubPath) {
-			t.Errorf("%q: %q is not found in %q", lib, libFooStubPath, libFlags)
-		}
-	}
-}
-
 func TestVersioningMacro(t *testing.T) {
 	t.Parallel()
 	for _, tc := range []struct{ moduleName, expected string }{
@@ -1657,7 +1550,7 @@
 		}`)
 
 	variant := "android_arm64_armv8-a_static"
-	arRule := ctx.ModuleForTests("baz", variant).Rule("ar")
+	arRule := ctx.ModuleForTests(t, "baz", variant).Rule("ar")
 
 	// For static libraries, the object files of a whole static dep are included in the archive
 	// directly
@@ -1698,7 +1591,7 @@
 		}`)
 
 	variant := "android_arm64_armv8-a_shared"
-	linkRule := ctx.ModuleForTests("baz", variant).Rule("ld")
+	linkRule := ctx.ModuleForTests(t, "baz", variant).Rule("ld")
 	libFlags := linkRule.Args["libFlags"]
 	// When dynamically linking, we expect static dependencies to be found on the command line
 	if expected := "foo.a"; !strings.Contains(libFlags, expected) {
@@ -1730,7 +1623,7 @@
 		}`)
 
 	variant := "android_arm64_armv8-a"
-	binModuleRule := ctx.ModuleForTests("static_test", variant).Rule("ld")
+	binModuleRule := ctx.ModuleForTests(t, "static_test", variant).Rule("ld")
 	libFlags := binModuleRule.Args["libFlags"]
 	systemStaticLibs := []string{"libc.a", "libm.a"}
 	for _, lib := range systemStaticLibs {
@@ -1773,9 +1666,9 @@
 			},
 		}`)
 
-	mybin := ctx.ModuleForTests("mybin", "android_arm64_armv8-a").Rule("ld")
+	mybin := ctx.ModuleForTests(t, "mybin", "android_arm64_armv8-a").Rule("ld")
 	actual := mybin.Implicits[:2]
-	expected := GetOutputPaths(ctx, "android_arm64_armv8-a_static", []string{"libfooB", "libfooC"})
+	expected := GetOutputPaths(t, ctx, "android_arm64_armv8-a_static", []string{"libfooB", "libfooC"})
 
 	if !reflect.DeepEqual(actual, expected) {
 		t.Errorf("staticDeps orderings were not propagated correctly"+
@@ -1882,7 +1775,7 @@
 
 	checkPcGuardFlag := func(
 		modName string, variantName string, shouldHave bool) {
-		cc := ctx.ModuleForTests(modName, variantName).Rule("cc")
+		cc := ctx.ModuleForTests(t, modName, variantName).Rule("cc")
 
 		cFlags, ok := cc.Args["cFlags"]
 		if !ok {
@@ -1903,15 +1796,15 @@
 
 	moduleName = "afl_fuzz_static_lib"
 	checkPcGuardFlag(moduleName, variant+"_static", false)
-	checkPcGuardFlag(moduleName, variant+"_static_fuzzer", true)
+	checkPcGuardFlag(moduleName, variant+"_static_fuzzer_afl", true)
 
 	moduleName = "second_static_lib"
 	checkPcGuardFlag(moduleName, variant+"_static", false)
-	checkPcGuardFlag(moduleName, variant+"_static_fuzzer", true)
+	checkPcGuardFlag(moduleName, variant+"_static_fuzzer_afl", true)
 
-	ctx.ModuleForTests("afl_fuzz_shared_lib",
+	ctx.ModuleForTests(t, "afl_fuzz_shared_lib",
 		"android_arm64_armv8-a_shared").Rule("cc")
-	ctx.ModuleForTests("afl_fuzz_shared_lib",
+	ctx.ModuleForTests(t, "afl_fuzz_shared_lib",
 		"android_arm64_armv8-a_shared_fuzzer").Rule("cc")
 }
 
@@ -1940,7 +1833,7 @@
 		}`)
 
 	variant := "android_arm64_armv8-a_fuzzer"
-	ctx.ModuleForTests("fuzz_smoke_test", variant).Rule("cc")
+	ctx.ModuleForTests(t, "fuzz_smoke_test", variant).Rule("cc")
 }
 
 func assertString(t *testing.T, got, expected string) {
@@ -2004,24 +1897,24 @@
 			defaults: ["defaults"],
 		}`)
 
-	shared := ctx.ModuleForTests("libshared", "android_arm64_armv8-a_shared").Rule("ld")
+	shared := ctx.ModuleForTests(t, "libshared", "android_arm64_armv8-a_shared").Rule("ld")
 	if g, w := pathsToBase(shared.Inputs), []string{"foo.o", "baz.o"}; !reflect.DeepEqual(w, g) {
 		t.Errorf("libshared ld rule wanted %q, got %q", w, g)
 	}
-	bothShared := ctx.ModuleForTests("libboth", "android_arm64_armv8-a_shared").Rule("ld")
+	bothShared := ctx.ModuleForTests(t, "libboth", "android_arm64_armv8-a_shared").Rule("ld")
 	if g, w := pathsToBase(bothShared.Inputs), []string{"foo.o", "baz.o"}; !reflect.DeepEqual(w, g) {
 		t.Errorf("libboth ld rule wanted %q, got %q", w, g)
 	}
-	binary := ctx.ModuleForTests("binary", "android_arm64_armv8-a").Rule("ld")
+	binary := ctx.ModuleForTests(t, "binary", "android_arm64_armv8-a").Rule("ld")
 	if g, w := pathsToBase(binary.Inputs), []string{"foo.o"}; !reflect.DeepEqual(w, g) {
 		t.Errorf("binary ld rule wanted %q, got %q", w, g)
 	}
 
-	static := ctx.ModuleForTests("libstatic", "android_arm64_armv8-a_static").Rule("ar")
+	static := ctx.ModuleForTests(t, "libstatic", "android_arm64_armv8-a_static").Rule("ar")
 	if g, w := pathsToBase(static.Inputs), []string{"foo.o", "bar.o"}; !reflect.DeepEqual(w, g) {
 		t.Errorf("libstatic ar rule wanted %q, got %q", w, g)
 	}
-	bothStatic := ctx.ModuleForTests("libboth", "android_arm64_armv8-a_static").Rule("ar")
+	bothStatic := ctx.ModuleForTests(t, "libboth", "android_arm64_armv8-a_static").Rule("ar")
 	if g, w := pathsToBase(bothStatic.Inputs), []string{"foo.o", "bar.o"}; !reflect.DeepEqual(w, g) {
 		t.Errorf("libboth ar rule wanted %q, got %q", w, g)
 	}
@@ -2080,12 +1973,12 @@
 		android.PrepareForTestWithAllowMissingDependencies,
 	).RunTestWithBp(t, bp)
 
-	libbar := result.ModuleForTests("libbar", "android_arm64_armv8-a_static").Output("libbar.a")
+	libbar := result.ModuleForTests(t, "libbar", "android_arm64_armv8-a_static").Output("libbar.a")
 	android.AssertDeepEquals(t, "libbar rule", android.ErrorRule, libbar.Rule)
 
 	android.AssertStringDoesContain(t, "libbar error", libbar.Args["error"], "missing dependencies: libmissing")
 
-	libfoo := result.ModuleForTests("libfoo", "android_arm64_armv8-a_static").Output("libfoo.a")
+	libfoo := result.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_static").Output("libfoo.a")
 	android.AssertStringListContains(t, "libfoo.a dependencies", libfoo.Inputs.Strings(), libbar.Output.String())
 }
 
@@ -2132,11 +2025,11 @@
 	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	ctx := testCcWithConfig(t, config)
 
-	hostBin := ctx.ModuleForTests("bin", config.BuildOSTarget.String()).Description("install")
-	hostShared := ctx.ModuleForTests("libshared", config.BuildOSTarget.String()+"_shared").Description("install")
-	hostRuntime := ctx.ModuleForTests("libruntime", config.BuildOSTarget.String()+"_shared").Description("install")
-	hostTransitive := ctx.ModuleForTests("libtransitive", config.BuildOSTarget.String()+"_shared").Description("install")
-	hostTool := ctx.ModuleForTests("tool", config.BuildOSTarget.String()).Description("install")
+	hostBin := ctx.ModuleForTests(t, "bin", config.BuildOSTarget.String()).Description("install")
+	hostShared := ctx.ModuleForTests(t, "libshared", config.BuildOSTarget.String()+"_shared").Description("install")
+	hostRuntime := ctx.ModuleForTests(t, "libruntime", config.BuildOSTarget.String()+"_shared").Description("install")
+	hostTransitive := ctx.ModuleForTests(t, "libtransitive", config.BuildOSTarget.String()+"_shared").Description("install")
+	hostTool := ctx.ModuleForTests(t, "tool", config.BuildOSTarget.String()).Description("install")
 
 	if g, w := hostBin.Implicits.Strings(), hostShared.Output.String(); !android.InList(w, g) {
 		t.Errorf("expected host bin dependency %q, got %q", w, g)
@@ -2158,10 +2051,10 @@
 		t.Errorf("expected no host bin dependency %q, got %q", w, g)
 	}
 
-	deviceBin := ctx.ModuleForTests("bin", "android_arm64_armv8-a").Description("install")
-	deviceShared := ctx.ModuleForTests("libshared", "android_arm64_armv8-a_shared").Description("install")
-	deviceTransitive := ctx.ModuleForTests("libtransitive", "android_arm64_armv8-a_shared").Description("install")
-	deviceRuntime := ctx.ModuleForTests("libruntime", "android_arm64_armv8-a_shared").Description("install")
+	deviceBin := ctx.ModuleForTests(t, "bin", "android_arm64_armv8-a").Description("install")
+	deviceShared := ctx.ModuleForTests(t, "libshared", "android_arm64_armv8-a_shared").Description("install")
+	deviceTransitive := ctx.ModuleForTests(t, "libtransitive", "android_arm64_armv8-a_shared").Description("install")
+	deviceRuntime := ctx.ModuleForTests(t, "libruntime", "android_arm64_armv8-a_shared").Description("install")
 
 	if g, w := deviceBin.OrderOnly.Strings(), deviceShared.Output.String(); !android.InList(w, g) {
 		t.Errorf("expected device bin dependency %q, got %q", w, g)
@@ -2211,7 +2104,7 @@
 			srcs: ["foo.c"],
 		}`)
 
-	cFlags := ctx.ModuleForTests("libclient", "android_arm64_armv8-a_shared").Rule("cc").Args["cFlags"]
+	cFlags := ctx.ModuleForTests(t, "libclient", "android_arm64_armv8-a_shared").Rule("cc").Args["cFlags"]
 
 	if !strings.Contains(cFlags, "-Iinclude/libbar") {
 		t.Errorf("expected %q in cflags, got %q", "-Iinclude/libbar", cFlags)
@@ -2251,7 +2144,7 @@
 		}.AddToFixture(),
 	).RunTest(t).TestContext
 
-	libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_static")
+	libfoo := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_static")
 
 	android.AssertPathsRelativeToTopEquals(
 		t,
@@ -2299,7 +2192,7 @@
 		}
 	`)
 
-	libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_static")
+	libfoo := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_static")
 	manifest := android.RuleBuilderSboxProtoForTests(t, ctx.TestContext, libfoo.Output("aidl.sbox.textproto"))
 	aidlCommand := manifest.Commands[0].GetCommand()
 	expectedAidlFlag := "-Werror"
@@ -2350,7 +2243,7 @@
 					`+tc.sdkVersion+`
 				}
 			`)
-			libfoo := ctx.ModuleForTests("libfoo", tc.variant)
+			libfoo := ctx.ModuleForTests(t, "libfoo", tc.variant)
 			manifest := android.RuleBuilderSboxProtoForTests(t, ctx, libfoo.Output("aidl.sbox.textproto"))
 			aidlCommand := manifest.Commands[0].GetCommand()
 			expectedAidlFlag := "--min_sdk_version=" + tc.expected
@@ -2419,7 +2312,7 @@
 			min_sdk_version: "29",
 		}`)
 
-	cFlags := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Rule("cc").Args["cFlags"]
+	cFlags := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_shared").Rule("cc").Args["cFlags"]
 	android.AssertStringDoesContain(t, "min sdk version", cFlags, "-target aarch64-linux-android29")
 }
 
@@ -2439,7 +2332,7 @@
 		}),
 	).RunTestWithBp(t, bp)
 	ctx := result.TestContext
-	cFlags := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Rule("cc").Args["cFlags"]
+	cFlags := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_shared").Rule("cc").Args["cFlags"]
 	android.AssertStringDoesContain(t, "min sdk version", cFlags, "-target aarch64-linux-android31")
 }
 
@@ -2546,7 +2439,7 @@
 			export_generated_headers: ["genrule_bar"],
 		}
 		`)
-		foo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Module()
+		foo := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_shared").Module()
 		checkIncludeDirs(t, ctx, foo,
 			expectedIncludeDirs(`
 				foo/standard
@@ -2557,7 +2450,7 @@
 			expectedOrderOnlyDeps(`.intermediates/genrule_foo/gen/generated_headers/foo/generated_header.h`),
 		)
 
-		bar := ctx.ModuleForTests("libbar", "android_arm64_armv8-a_shared").Module()
+		bar := ctx.ModuleForTests(t, "libbar", "android_arm64_armv8-a_shared").Module()
 		checkIncludeDirs(t, ctx, bar,
 			expectedIncludeDirs(`
 				bar/standard
@@ -2590,7 +2483,7 @@
 			export_generated_headers: ["genrule_bar"],
 		}
 		`)
-		foo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Module()
+		foo := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_shared").Module()
 		checkIncludeDirs(t, ctx, foo,
 			expectedIncludeDirs(`
 				foo/standard
@@ -2601,7 +2494,7 @@
 			expectedOrderOnlyDeps(`.intermediates/genrule_foo/gen/generated_headers/foo/generated_header.h`),
 		)
 
-		bar := ctx.ModuleForTests("libbar", "android_arm64_armv8-a_shared").Module()
+		bar := ctx.ModuleForTests(t, "libbar", "android_arm64_armv8-a_shared").Module()
 		checkIncludeDirs(t, ctx, bar,
 			expectedIncludeDirs(`
 				bar/standard
@@ -2647,7 +2540,7 @@
 			}
 		}
 		`).TestContext
-		foo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Module()
+		foo := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_shared").Module()
 		checkIncludeDirs(t, ctx, foo,
 			expectedIncludeDirs(`
 				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/aidl
@@ -2687,7 +2580,7 @@
 			}
 		}
 		`)
-		foo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Module()
+		foo := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_shared").Module()
 		checkIncludeDirs(t, ctx, foo,
 			expectedIncludeDirs(`
 				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/proto
@@ -2714,7 +2607,7 @@
 			],
 		}
 		`)
-		foo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Module()
+		foo := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_shared").Module()
 		checkIncludeDirs(t, ctx, foo,
 			expectedIncludeDirs(`
 				.intermediates/libfoo/android_arm64_armv8-a_shared/gen/sysprop/include
@@ -2733,6 +2626,11 @@
 
 func TestIncludeDirectoryOrdering(t *testing.T) {
 	t.Parallel()
+
+	expectedPlatformFlags := []string{
+		"-nostdlibinc",
+	}
+
 	baseExpectedFlags := []string{
 		"${config.ArmThumbCflags}",
 		"${config.ArmCflags}",
@@ -2742,10 +2640,18 @@
 		"${config.ArmToolchainCflags}",
 		"${config.ArmArmv7ANeonCflags}",
 		"${config.ArmGenericCflags}",
+	}
+
+	expectedTargetNDKFlags := []string{
 		"-target",
 		"armv7a-linux-androideabi21",
 	}
 
+	expectedTargetPlatformFlags := []string{
+		"-target",
+		"armv7a-linux-androideabi10000",
+	}
+
 	expectedIncludes := []string{
 		"external/foo/android_arm_export_include_dirs",
 		"external/foo/lib32_export_include_dirs",
@@ -2773,6 +2679,9 @@
 		"external/foo/libarm",
 		"external/foo/lib32",
 		"external/foo/libandroid_arm",
+	}
+
+	expectedNDKSTLIncludes := []string{
 		"defaults/cc/common/ndk_libc++_shared_include_dirs",
 	}
 
@@ -2783,38 +2692,92 @@
 	cstd := []string{"-std=gnu17", "-std=conly"}
 	cppstd := []string{"-std=gnu++20", "-std=cpp", "-fno-rtti"}
 
-	lastIncludes := []string{
-		"out/soong/ndk/sysroot/usr/include",
-		"out/soong/ndk/sysroot/usr/include/arm-linux-androideabi",
+	lastNDKFlags := []string{
+		"--sysroot",
+		"out/soong/ndk/sysroot",
 	}
 
-	combineSlices := func(slices ...[]string) []string {
-		var ret []string
-		for _, s := range slices {
-			ret = append(ret, s...)
-		}
-		return ret
+	lastPlatformIncludes := []string{
+		"${config.CommonGlobalIncludes}",
 	}
 
 	testCases := []struct {
-		name     string
-		src      string
-		expected []string
+		name             string
+		src              string
+		expectedNDK      []string
+		expectedPlatform []string
 	}{
 		{
-			name:     "c",
-			src:      "foo.c",
-			expected: combineSlices(baseExpectedFlags, conly, expectedIncludes, cflags, cstd, lastIncludes, []string{"${config.NoOverrideGlobalCflags}", "${config.NoOverrideExternalGlobalCflags}"}),
+			name: "c",
+			src:  "foo.c",
+			expectedNDK: slices.Concat(
+				baseExpectedFlags,
+				expectedTargetNDKFlags,
+				conly,
+				expectedIncludes,
+				expectedNDKSTLIncludes,
+				cflags,
+				cstd,
+				lastNDKFlags,
+				[]string{"${config.NoOverrideGlobalCflags}", "${config.NoOverrideExternalGlobalCflags}"},
+			),
+			expectedPlatform: slices.Concat(
+				expectedPlatformFlags,
+				baseExpectedFlags,
+				expectedTargetPlatformFlags,
+				conly,
+				expectedIncludes,
+				cflags,
+				cstd,
+				lastPlatformIncludes,
+				[]string{"${config.NoOverrideGlobalCflags}", "${config.NoOverrideExternalGlobalCflags}"},
+			),
 		},
 		{
-			name:     "cc",
-			src:      "foo.cc",
-			expected: combineSlices(baseExpectedFlags, cppOnly, expectedIncludes, cflags, cppstd, lastIncludes, []string{"${config.NoOverrideGlobalCflags}", "${config.NoOverrideExternalGlobalCflags}"}),
+			name: "cc",
+			src:  "foo.cc",
+			expectedNDK: slices.Concat(
+				baseExpectedFlags,
+				expectedTargetNDKFlags,
+				cppOnly,
+				expectedIncludes,
+				expectedNDKSTLIncludes,
+				cflags,
+				cppstd,
+				lastNDKFlags,
+				[]string{"${config.NoOverrideGlobalCflags}", "${config.NoOverrideExternalGlobalCflags}"},
+			),
+			expectedPlatform: slices.Concat(
+				expectedPlatformFlags,
+				baseExpectedFlags,
+				expectedTargetPlatformFlags,
+				cppOnly,
+				expectedIncludes,
+				cflags,
+				cppstd,
+				lastPlatformIncludes,
+				[]string{"${config.NoOverrideGlobalCflags}", "${config.NoOverrideExternalGlobalCflags}"},
+			),
 		},
 		{
-			name:     "assemble",
-			src:      "foo.s",
-			expected: combineSlices(baseExpectedFlags, []string{"${config.CommonGlobalAsflags}"}, expectedIncludes, lastIncludes),
+			name: "assemble",
+			src:  "foo.s",
+			expectedNDK: slices.Concat(
+				baseExpectedFlags,
+				expectedTargetNDKFlags,
+				[]string{"${config.CommonGlobalAsflags}"},
+				expectedIncludes,
+				expectedNDKSTLIncludes,
+				lastNDKFlags,
+			),
+			expectedPlatform: slices.Concat(
+				expectedPlatformFlags,
+				baseExpectedFlags,
+				expectedTargetPlatformFlags,
+				[]string{"${config.CommonGlobalAsflags}"},
+				expectedIncludes,
+				lastPlatformIncludes,
+			),
 		},
 	}
 
@@ -2909,25 +2872,34 @@
 		`, lib, lib)
 			}
 
-			ctx := android.GroupFixturePreparers(
-				PrepareForIntegrationTestWithCc,
-				android.FixtureAddTextFile("external/foo/Android.bp", bp),
-			).RunTest(t)
-			cflags := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_sdk_static").Output("obj/external/foo/foo.o").Args["cFlags"]
+			runTest := func(t *testing.T, variant string, expected []string) {
+				ctx := android.GroupFixturePreparers(
+					PrepareForIntegrationTestWithCc,
+					android.FixtureAddTextFile("external/foo/Android.bp", bp),
+				).RunTest(t)
+				cflags := ctx.ModuleForTests(t, "libfoo", variant).Output("obj/external/foo/foo.o").Args["cFlags"]
 
-			var includes []string
-			flags := strings.Split(cflags, " ")
-			for _, flag := range flags {
-				if strings.HasPrefix(flag, "-I") {
-					includes = append(includes, strings.TrimPrefix(flag, "-I"))
-				} else if flag == "-isystem" {
-					// skip isystem, include next
-				} else if len(flag) > 0 {
-					includes = append(includes, flag)
+				var includes []string
+				flags := strings.Split(cflags, " ")
+				for _, flag := range flags {
+					if strings.HasPrefix(flag, "-I") {
+						includes = append(includes, strings.TrimPrefix(flag, "-I"))
+					} else if flag == "-isystem" {
+						// skip isystem, include next
+					} else if len(flag) > 0 {
+						includes = append(includes, flag)
+					}
 				}
+
+				android.AssertArrayString(t, "includes", expected, includes)
 			}
 
-			android.AssertArrayString(t, "includes", tc.expected, includes)
+			t.Run("platform", func(t *testing.T) {
+				runTest(t, "android_arm_armv7-a-neon_static", tc.expectedPlatform)
+			})
+			t.Run("ndk", func(t *testing.T) {
+				runTest(t, "android_arm_armv7-a-neon_sdk_static", tc.expectedNDK)
+			})
 		})
 	}
 
@@ -2959,7 +2931,7 @@
 			srcs: ["foo.c"],
 		}`)
 
-	cFlags := ctx.ModuleForTests("libclient", "android_arm64_armv8-a_shared").Rule("cc").Args["cFlags"]
+	cFlags := ctx.ModuleForTests(t, "libclient", "android_arm64_armv8-a_shared").Rule("cc").Args["cFlags"]
 
 	if !strings.Contains(cFlags, "${config.NoOverride64GlobalCflags}") {
 		t.Errorf("expected %q in cflags, got %q", "${config.NoOverride64GlobalCflags}", cFlags)
@@ -3123,7 +3095,7 @@
  `
 	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	ctx := testCcWithConfig(t, config)
-	testingModule := ctx.ModuleForTests("test_lib", "android_arm_armv7-a-neon_shared")
+	testingModule := ctx.ModuleForTests(t, "test_lib", "android_arm_armv7-a-neon_shared")
 	outputFile := testingModule.OutputFiles(ctx, t, "stripped_all")
 	if !strings.HasSuffix(outputFile.Strings()[0], "/stripped_all/test_lib.so") {
 		t.Errorf("Unexpected output file: %s", outputFile.Strings()[0])
@@ -3168,8 +3140,8 @@
 		if imageVariant != "core" {
 			imageVariantStr = "_" + imageVariant
 		}
-		binFooModule := ctx.ModuleForTests("binfoo", "android"+imageVariantStr+"_arm64_armv8-a").Module()
-		libBarModule := ctx.ModuleForTests("libbar", "android"+imageVariantStr+"_arm64_armv8-a_shared").Module()
+		binFooModule := ctx.ModuleForTests(t, "binfoo", "android"+imageVariantStr+"_arm64_armv8-a").Module()
+		libBarModule := ctx.ModuleForTests(t, "libbar", "android"+imageVariantStr+"_arm64_armv8-a_shared").Module()
 		android.AssertBoolEquals(t, "binfoo should have dependency on libbar with image variant "+imageVariant, true, hasDep(binFooModule, libBarModule))
 	}
 
@@ -3178,7 +3150,7 @@
 	testDepWithVariant("product")
 }
 
-func TestVendorSdkVersion(t *testing.T) {
+func TestVendorOrProductVariantUsesPlatformSdkVersionAsDefault(t *testing.T) {
 	t.Parallel()
 
 	bp := `
@@ -3186,31 +3158,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(t, 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) {
@@ -3229,15 +3199,32 @@
 		}
 	`)
 
-	module := ctx.ModuleForTests("lib_no_clang_verify", "android_arm64_armv8-a_shared")
+	module := ctx.ModuleForTests(t, "lib_no_clang_verify", "android_arm64_armv8-a_shared")
 
 	cFlags_no_cv := module.Rule("cc").Args["cFlags"]
 	if strings.Contains(cFlags_no_cv, "-Xclang") || strings.Contains(cFlags_no_cv, "-verify") {
 		t.Errorf("expected %q not in cflags, got %q", "-Xclang -verify", cFlags_no_cv)
 	}
 
-	cFlags_cv := ctx.ModuleForTests("lib_clang_verify", "android_arm64_armv8-a_shared").Rule("cc").Args["cFlags"]
+	cFlags_cv := ctx.ModuleForTests(t, "lib_clang_verify", "android_arm64_armv8-a_shared").Rule("cc").Args["cFlags"]
 	if strings.Contains(cFlags_cv, "-Xclang") && strings.Contains(cFlags_cv, "-verify") {
 		t.Errorf("expected %q in cflags, got %q", "-Xclang -verify", cFlags_cv)
 	}
 }
+
+func TestCheckConflictingExplicitVersions(t *testing.T) {
+	PrepareForIntegrationTestWithCc.
+		ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(
+			`shared_libs: duplicate shared libraries with different explicit versions: "libbar" and "libbar#impl"`,
+		)).
+		RunTestWithBp(t, `
+			cc_library {
+				name: "libfoo",
+				shared_libs: ["libbar", "libbar#impl"],
+			}
+
+			cc_library {
+				name: "libbar",
+			}
+		`)
+}
diff --git a/cc/cc_test_only_property_test.go b/cc/cc_test_only_property_test.go
index 972e86b..a178cad 100644
--- a/cc/cc_test_only_property_test.go
+++ b/cc/cc_test_only_property_test.go
@@ -173,7 +173,7 @@
 
 func getTeamProtoOutput(t *testing.T, ctx *android.TestResult) *team_proto.AllTeams {
 	teams := new(team_proto.AllTeams)
-	config := ctx.SingletonForTests("all_teams")
+	config := ctx.SingletonForTests(t, "all_teams")
 	allOutputs := config.AllOutputs()
 
 	protoPath := allOutputs[0]
diff --git a/cc/ccdeps.go b/cc/ccdeps.go
index 469fe31..4247778 100644
--- a/cc/ccdeps.go
+++ b/cc/ccdeps.go
@@ -41,8 +41,6 @@
 	outputPath android.Path
 }
 
-var _ android.SingletonMakeVarsProvider = (*ccdepsGeneratorSingleton)(nil)
-
 const (
 	ccdepsJsonFileName = "module_bp_cc_deps.json"
 	cClang             = "clang"
@@ -114,13 +112,6 @@
 		Rule:   android.Touch,
 		Output: ccfpath,
 	})
-}
-
-func (c *ccdepsGeneratorSingleton) MakeVars(ctx android.MakeVarsContext) {
-	if c.outputPath == nil {
-		return
-	}
-
 	ctx.DistForGoal("general-tests", c.outputPath)
 }
 
diff --git a/cc/cmake_module_aidl.txt b/cc/cmake_module_aidl.txt
index 84755a3..3622648 100644
--- a/cc/cmake_module_aidl.txt
+++ b/cc/cmake_module_aidl.txt
@@ -1,11 +1,11 @@
 # <<.M.Name>>
 
-<<setList .M.Name "_SRCS" "" (getAidlSources .M)>>
+<<setList .M.Name "_SRCS" "" (getAidlSources .CcInfo)>>
 
-<<setList .M.Name "_AIDLFLAGS" "" (getCompilerProperties .M).AidlInterface.Flags>>
+<<setList .M.Name "_AIDLFLAGS" "" (getAidlInterface .CcInfo).Flags>>
 
-add_aidl_library(<<.M.Name>> <<(getCompilerProperties .M).AidlInterface.Lang>>
-    "${ANDROID_BUILD_TOP}/<<.Ctx.OtherModuleDir .M>>/<<(getCompilerProperties .M).AidlInterface.AidlRoot>>"
+add_aidl_library(<<.M.Name>> <<(getAidlInterface .CcInfo).Lang>>
+    "${ANDROID_BUILD_TOP}/<<.Ctx.OtherModuleDir .M>>/<<(getAidlInterface .CcInfo).AidlRoot>>"
     "${<<.M.Name>>_SRCS}"
     "${<<.M.Name>>_AIDLFLAGS}")
 add_library(android::<<.M.Name>> ALIAS <<.M.Name>>)
diff --git a/cc/cmake_module_cc.txt b/cc/cmake_module_cc.txt
index 0f6e62f..a57e053 100644
--- a/cc/cmake_module_cc.txt
+++ b/cc/cmake_module_cc.txt
@@ -1,14 +1,14 @@
-<<$srcs := getSources .M>>
-<<$includeDirs := getIncludeDirs .Ctx .M>>
-<<$cflags := getCflagsProperty .Ctx .M>>
+<<$srcs := getSources .Ctx .CcInfo>>
+<<$includeDirs := getIncludeDirs .Ctx .M .CcInfo>>
+<<$cflags := getCflagsProperty .Ctx .CcInfo>>
 <<$deps := mapLibraries .Ctx .M (concat5
-(getWholeStaticLibsProperty .Ctx .M)
-(getStaticLibsProperty .Ctx .M)
-(getSharedLibsProperty .Ctx .M)
-(getHeaderLibsProperty .Ctx .M)
-(getExtraLibs .M)
+(getWholeStaticLibsProperty .Ctx .CcInfo)
+(getStaticLibsProperty .Ctx .CcInfo)
+(getSharedLibsProperty .Ctx .CcInfo)
+(getHeaderLibsProperty .Ctx .CcInfo)
+(getExtraLibs .CcInfo)
 ) .Pprop.LibraryMapping>>
-<<$moduleType := getModuleType .M>>
+<<$moduleType := getModuleType .CcInfo>>
 <<$moduleTypeCmake := "executable">>
 <<if eq $moduleType "library">>
 <<$moduleTypeCmake = "library">>
diff --git a/cc/cmake_snapshot.go b/cc/cmake_snapshot.go
index 61fa46d..3f6a01d 100644
--- a/cc/cmake_snapshot.go
+++ b/cc/cmake_snapshot.go
@@ -196,39 +196,31 @@
 
 			return list.String()
 		},
-		"getSources": func(m *Module) android.Paths {
-			return m.compiler.(CompiledInterface).Srcs()
+		"getSources": func(ctx android.ModuleContext, info *CcInfo) android.Paths {
+			return info.CompilerInfo.Srcs
 		},
 		"getModuleType": getModuleType,
-		"getCompilerProperties": func(m *Module) BaseCompilerProperties {
-			return m.compiler.baseCompilerProps()
+		"getAidlInterface": func(info *CcInfo) AidlInterfaceInfo {
+			return info.CompilerInfo.AidlInterfaceInfo
 		},
-		"getCflagsProperty": func(ctx android.ModuleContext, m *Module) []string {
-			prop := m.compiler.baseCompilerProps().Cflags
-			return prop.GetOrDefault(ctx, nil)
+		"getCflagsProperty": func(ctx android.ModuleContext, info *CcInfo) []string {
+			return info.CompilerInfo.Cflags
 		},
-		"getLinkerProperties": func(m *Module) BaseLinkerProperties {
-			return m.linker.baseLinkerProps()
+		"getWholeStaticLibsProperty": func(ctx android.ModuleContext, info *CcInfo) []string {
+			return info.LinkerInfo.WholeStaticLibs
 		},
-		"getWholeStaticLibsProperty": func(ctx android.ModuleContext, m *Module) []string {
-			prop := m.linker.baseLinkerProps().Whole_static_libs
-			return prop.GetOrDefault(ctx, nil)
+		"getStaticLibsProperty": func(ctx android.ModuleContext, info *CcInfo) []string {
+			return info.LinkerInfo.StaticLibs
 		},
-		"getStaticLibsProperty": func(ctx android.ModuleContext, m *Module) []string {
-			prop := m.linker.baseLinkerProps().Static_libs
-			return prop.GetOrDefault(ctx, nil)
+		"getSharedLibsProperty": func(ctx android.ModuleContext, info *CcInfo) []string {
+			return info.LinkerInfo.SharedLibs
 		},
-		"getSharedLibsProperty": func(ctx android.ModuleContext, m *Module) []string {
-			prop := m.linker.baseLinkerProps().Shared_libs
-			return prop.GetOrDefault(ctx, nil)
-		},
-		"getHeaderLibsProperty": func(ctx android.ModuleContext, m *Module) []string {
-			prop := m.linker.baseLinkerProps().Header_libs
-			return prop.GetOrDefault(ctx, nil)
+		"getHeaderLibsProperty": func(ctx android.ModuleContext, info *CcInfo) []string {
+			return info.LinkerInfo.HeaderLibs
 		},
 		"getExtraLibs":   getExtraLibs,
 		"getIncludeDirs": getIncludeDirs,
-		"mapLibraries": func(ctx android.ModuleContext, m *Module, libs []string, mapping map[string]LibraryMappingProperty) []string {
+		"mapLibraries": func(ctx android.ModuleContext, m android.ModuleProxy, libs []string, mapping map[string]LibraryMappingProperty) []string {
 			var mappedLibs []string
 			for _, lib := range libs {
 				mappedLib, exists := mapping[lib]
@@ -249,8 +241,8 @@
 			mappedLibs = slices.Compact(mappedLibs)
 			return mappedLibs
 		},
-		"getAidlSources": func(m *Module) []string {
-			aidlInterface := m.compiler.baseCompilerProps().AidlInterface
+		"getAidlSources": func(info *CcInfo) []string {
+			aidlInterface := info.CompilerInfo.AidlInterfaceInfo
 			aidlRoot := aidlInterface.AidlRoot + string(filepath.Separator)
 			if aidlInterface.AidlRoot == "" {
 				aidlRoot = ""
@@ -340,14 +332,14 @@
 	moduleDirs := map[string][]string{}
 	sourceFiles := map[string]android.Path{}
 	visitedModules := map[string]bool{}
-	var pregeneratedModules []*Module
-	ctx.WalkDeps(func(dep_a android.Module, parent android.Module) bool {
-		moduleName := ctx.OtherModuleName(dep_a)
+	var pregeneratedModules []android.ModuleProxy
+	ctx.WalkDepsProxy(func(dep, parent android.ModuleProxy) bool {
+		moduleName := ctx.OtherModuleName(dep)
 		if visited := visitedModules[moduleName]; visited {
 			return false // visit only once
 		}
 		visitedModules[moduleName] = true
-		dep, ok := dep_a.(*Module)
+		ccInfo, ok := android.OtherModuleProvider(ctx, dep, CcInfoProvider)
 		if !ok {
 			return false // not a cc module
 		}
@@ -363,15 +355,15 @@
 		if slices.Contains(ignoredSystemLibs, moduleName) {
 			return false // system libs built in-tree for Android
 		}
-		if dep.IsPrebuilt() {
+		if ccInfo.IsPrebuilt {
 			return false // prebuilts are not supported
 		}
-		if dep.compiler == nil {
+		if ccInfo.CompilerInfo == nil {
 			return false // unsupported module type
 		}
-		isAidlModule := dep.compiler.baseCompilerProps().AidlInterface.Lang != ""
+		isAidlModule := ccInfo.CompilerInfo.AidlInterfaceInfo.Lang != ""
 
-		if !proptools.Bool(dep.Properties.Cmake_snapshot_supported) {
+		if !ccInfo.CmakeSnapshotSupported {
 			ctx.OtherModulePropertyErrorf(dep, "cmake_snapshot_supported",
 				"CMake snapshots not supported, despite being a dependency for %s",
 				ctx.OtherModuleName(parent))
@@ -389,12 +381,14 @@
 		}
 		moduleFragment := executeTemplate(templateToUse, &templateBuffer, struct {
 			Ctx      *android.ModuleContext
-			M        *Module
+			M        android.ModuleProxy
+			CcInfo   *CcInfo
 			Snapshot *CmakeSnapshot
 			Pprop    *cmakeProcessedProperties
 		}{
 			&ctx,
 			dep,
+			ccInfo,
 			m,
 			&pprop,
 		})
@@ -415,7 +409,7 @@
 	// Enumerate sources for pregenerated modules
 	if m.Properties.Include_sources {
 		for _, dep := range pregeneratedModules {
-			if !proptools.Bool(dep.Properties.Cmake_snapshot_supported) {
+			if !android.OtherModuleProviderOrDefault(ctx, dep, CcInfoProvider).CmakeSnapshotSupported {
 				ctx.OtherModulePropertyErrorf(dep, "cmake_snapshot_supported",
 					"Pregenerated CMake snapshots not supported, despite being requested for %s",
 					ctx.ModuleName())
@@ -484,14 +478,14 @@
 	// Packaging all make files into the zip file
 	makefilesRspFile := android.PathForModuleObj(ctx, ctx.ModuleName()+"_makefiles.rsp")
 	zipCmd.
-		FlagWithArg("-C ", android.PathForModuleGen(ctx).OutputPath.String()).
+		FlagWithArg("-C ", android.PathForModuleGen(ctx).String()).
 		FlagWithRspFileInputList("-r ", makefilesRspFile, makefilesList)
 
 	// Packaging all prebuilts into the zip file
 	if len(m.Properties.Prebuilts) > 0 {
 		var prebuiltsList android.Paths
 
-		ctx.VisitDirectDepsWithTag(cmakeSnapshotPrebuiltTag, func(dep android.Module) {
+		ctx.VisitDirectDepsProxyWithTag(cmakeSnapshotPrebuiltTag, func(dep android.ModuleProxy) {
 			for _, file := range android.OtherModuleProviderOrDefault(
 				ctx, dep, android.InstallFilesProvider).InstallFiles {
 				prebuiltsList = append(prebuiltsList, file)
@@ -523,40 +517,37 @@
 	}}
 }
 
-func getModuleType(m *Module) string {
-	switch m.linker.(type) {
-	case *binaryDecorator:
+func getModuleType(info *CcInfo) string {
+	if info.LinkerInfo.BinaryDecoratorInfo != nil {
 		return "executable"
-	case *libraryDecorator:
+	} else if info.LinkerInfo.LibraryDecoratorInfo != nil {
 		return "library"
-	case *testBinary:
+	} else if info.LinkerInfo.TestBinaryInfo != nil || info.LinkerInfo.BenchmarkDecoratorInfo != nil {
 		return "test"
-	case *benchmarkDecorator:
-		return "test"
+	} else if info.LinkerInfo.ObjectLinkerInfo != nil {
+		return "object"
 	}
-	panic(fmt.Sprintf("Unexpected module type: %T", m.linker))
+	panic(fmt.Sprintf("Unexpected module type for LinkerInfo"))
 }
 
-func getExtraLibs(m *Module) []string {
-	switch decorator := m.linker.(type) {
-	case *testBinary:
-		if decorator.testDecorator.gtest() {
+func getExtraLibs(info *CcInfo) []string {
+	if info.LinkerInfo.TestBinaryInfo != nil {
+		if info.LinkerInfo.TestBinaryInfo.Gtest {
 			return []string{
 				"libgtest",
 				"libgtest_main",
 			}
 		}
-	case *benchmarkDecorator:
+	} else if info.LinkerInfo.BenchmarkDecoratorInfo != nil {
 		return []string{"libgoogle-benchmark"}
 	}
 	return nil
 }
 
-func getIncludeDirs(ctx android.ModuleContext, m *Module) []string {
+func getIncludeDirs(ctx android.ModuleContext, m android.ModuleProxy, info *CcInfo) []string {
 	moduleDir := ctx.OtherModuleDir(m) + string(filepath.Separator)
-	switch decorator := m.compiler.(type) {
-	case *libraryDecorator:
-		return sliceWithPrefix(moduleDir, decorator.flagExporter.Properties.Export_include_dirs.GetOrDefault(ctx, nil))
+	if info.CompilerInfo.LibraryDecoratorInfo != nil {
+		return sliceWithPrefix(moduleDir, info.CompilerInfo.LibraryDecoratorInfo.ExportIncludeDirs)
 	}
 	return nil
 }
diff --git a/cc/cmake_snapshot_test.go b/cc/cmake_snapshot_test.go
index b6f4369..d08096a 100644
--- a/cc/cmake_snapshot_test.go
+++ b/cc/cmake_snapshot_test.go
@@ -49,7 +49,7 @@
 		t.Skip("CMake snapshots are only supported on Linux")
 	}
 
-	snapshotModule := result.ModuleForTests("foo", "linux_glibc_x86_64")
+	snapshotModule := result.ModuleForTests(t, "foo", "linux_glibc_x86_64")
 
 	wasGenerated(t, &snapshotModule, "CMakeLists.txt", "rawFileCopy")
 	wasGenerated(t, &snapshotModule, "foo.zip", "")
@@ -77,7 +77,7 @@
 		t.Skip("CMake snapshots are only supported on Linux")
 	}
 
-	snapshotModule := result.ModuleForTests("foo", "linux_glibc_x86_64")
+	snapshotModule := result.ModuleForTests(t, "foo", "linux_glibc_x86_64")
 
 	wasGenerated(t, &snapshotModule, "some/module/CMakeLists.txt", "rawFileCopy")
 }
@@ -110,7 +110,7 @@
 		t.Skip("CMake snapshots are only supported on Linux")
 	}
 
-	snapshotModule := result.ModuleForTests("foo", "linux_glibc_x86_64")
+	snapshotModule := result.ModuleForTests(t, "foo", "linux_glibc_x86_64")
 
 	wasGenerated(t, &snapshotModule, "CMakeLists.txt", "rawFileCopy")
 	wasGenerated(t, &snapshotModule, "foo.zip", "")
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 022b712..949603e 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.
@@ -100,6 +100,11 @@
 	// of genrule modules.
 	Generated_headers proptools.Configurable[[]string] `android:"arch_variant,variant_prepend"`
 
+	// Same as generated_headers, but the dependencies will be added based on the first supported
+	// arch variant and the device os variant. This can be useful for creating a host tool that
+	// embeds a copy of a device tool, that it then extracts and pushes to a device at runtime.
+	Device_first_generated_headers proptools.Configurable[[]string] `android:"arch_variant,variant_prepend"`
+
 	// pass -frtti instead of -fno-rtti
 	Rtti *bool `android:"arch_variant"`
 
@@ -294,6 +299,7 @@
 	deps.GeneratedSources = append(deps.GeneratedSources, compiler.Properties.Generated_sources...)
 	deps.GeneratedSources = removeListFromList(deps.GeneratedSources, compiler.Properties.Exclude_generated_sources)
 	deps.GeneratedHeaders = append(deps.GeneratedHeaders, compiler.Properties.Generated_headers.GetOrDefault(ctx, nil)...)
+	deps.DeviceFirstGeneratedHeaders = append(deps.DeviceFirstGeneratedHeaders, compiler.Properties.Device_first_generated_headers.GetOrDefault(ctx, nil)...)
 	deps.AidlLibs = append(deps.AidlLibs, compiler.Properties.Aidl.Libs...)
 
 	android.ProtoDeps(ctx, &compiler.Proto)
@@ -318,6 +324,10 @@
 	getNamedMapForConfig(ctx.Config(), key).Store(module, true)
 }
 
+func requiresGlobalIncludes(ctx ModuleContext) bool {
+	return !(ctx.useSdk() || ctx.InVendorOrProduct()) || ctx.Host()
+}
+
 func useGnuExtensions(gnuExtensions *bool) bool {
 	return proptools.BoolDefault(gnuExtensions, true)
 }
@@ -354,6 +364,29 @@
 	}
 }
 
+func AddTargetFlags(ctx android.ModuleContext, flags Flags, tc config.Toolchain, version string, bpf bool) Flags {
+	target := "-target " + tc.ClangTriple()
+	if ctx.Os().Class == android.Device {
+		if version == "" || version == "current" {
+			target += strconv.Itoa(android.FutureApiLevelInt)
+		} else {
+			apiLevel := nativeApiLevelOrPanic(ctx, version)
+			target += strconv.Itoa(apiLevel.FinalOrFutureInt())
+		}
+	}
+
+	// bpf targets don't need the default target triple. b/308826679
+	if bpf {
+		target = "--target=bpf"
+	}
+
+	flags.Global.CFlags = append(flags.Global.CFlags, target)
+	flags.Global.AsFlags = append(flags.Global.AsFlags, target)
+	flags.Global.LdFlags = append(flags.Global.LdFlags, target)
+
+	return flags
+}
+
 // Create a Flags struct that collects the compile flags from global values,
 // per-target values, module type values, and per-module Blueprints properties
 func (compiler *baseCompiler) compilerFlags(ctx ModuleContext, flags Flags, deps PathDeps) Flags {
@@ -361,7 +394,7 @@
 	modulePath := ctx.ModuleDir()
 
 	reuseObjs := false
-	if len(ctx.GetDirectDepsWithTag(reuseObjTag)) > 0 {
+	if len(ctx.GetDirectDepsProxyWithTag(reuseObjTag)) > 0 {
 		reuseObjs = true
 	}
 
@@ -405,13 +438,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)
@@ -423,21 +456,21 @@
 		flags.Local.YasmFlags = append(flags.Local.YasmFlags, "-I"+modulePath)
 	}
 
-	if !(ctx.useSdk() || ctx.InVendorOrProduct()) || ctx.Host() {
+	if requiresGlobalIncludes(ctx) {
 		flags.SystemIncludeFlags = append(flags.SystemIncludeFlags,
 			"${config.CommonGlobalIncludes}",
 			tc.IncludeFlags())
 	}
 
 	if ctx.useSdk() {
-		// TODO: Switch to --sysroot.
 		// The NDK headers are installed to a common sysroot. While a more
 		// typical Soong approach would be to only make the headers for the
 		// library you're using available, we're trying to emulate the NDK
 		// behavior here, and the NDK always has all the NDK headers available.
 		flags.SystemIncludeFlags = append(flags.SystemIncludeFlags,
-			"-isystem "+getCurrentIncludePath(ctx).String(),
-			"-isystem "+getCurrentIncludePath(ctx).Join(ctx, config.NDKTriple(tc)).String())
+			"--sysroot "+getNdkSysrootBase(ctx).String())
+	} else if ctx.Device() {
+		flags.Global.CommonFlags = append(flags.Global.CFlags, "-nostdlibinc")
 	}
 
 	if ctx.InVendorOrProduct() {
@@ -507,25 +540,7 @@
 	flags.Local.ConlyFlags = config.ClangFilterUnknownCflags(flags.Local.ConlyFlags)
 	flags.Local.LdFlags = config.ClangFilterUnknownCflags(flags.Local.LdFlags)
 
-	target := "-target " + tc.ClangTriple()
-	if ctx.Os().Class == android.Device {
-		version := ctx.minSdkVersion()
-		if version == "" || version == "current" {
-			target += strconv.Itoa(android.FutureApiLevelInt)
-		} else {
-			apiLevel := nativeApiLevelOrPanic(ctx, version)
-			target += strconv.Itoa(apiLevel.FinalOrFutureInt())
-		}
-	}
-
-	// bpf targets don't need the default target triple. b/308826679
-	if proptools.Bool(compiler.Properties.Bpf_target) {
-		target = "--target=bpf"
-	}
-
-	flags.Global.CFlags = append(flags.Global.CFlags, target)
-	flags.Global.AsFlags = append(flags.Global.AsFlags, target)
-	flags.Global.LdFlags = append(flags.Global.LdFlags, target)
+	flags = AddTargetFlags(ctx, flags, tc, ctx.minSdkVersion(), Bool(compiler.Properties.Bpf_target))
 
 	hod := "Host"
 	if ctx.Os().Class == android.Device {
@@ -679,10 +694,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...)
 			}
@@ -695,7 +710,9 @@
 
 	if ctx.optimizeForSize() {
 		flags.Local.CFlags = append(flags.Local.CFlags, "-Oz")
-		flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,-mllvm,-enable-ml-inliner=release")
+		if !ctx.Config().IsEnvFalse("THINLTO_USE_MLGO") {
+			flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,-mllvm,-enable-ml-inliner=release")
+		}
 	}
 
 	// Exclude directories from manual binder interface allowed list.
@@ -777,7 +794,7 @@
 	objs := compileObjs(ctx, buildFlags, "", srcs,
 		append(android.PathsForModuleSrc(ctx, compiler.Properties.Tidy_disabled_srcs), compiler.generatedSources...),
 		android.PathsForModuleSrc(ctx, compiler.Properties.Tidy_timeout_srcs),
-		pathDeps, compiler.cFlagsDeps)
+		pathDeps, compiler.cFlagsDeps, ctx.getSharedFlags())
 
 	if ctx.Failed() {
 		return Objects{}
@@ -787,10 +804,12 @@
 }
 
 // Compile a list of source files into objects a specified subdirectory
-func compileObjs(ctx ModuleContext, flags builderFlags, subdir string,
-	srcFiles, noTidySrcs, timeoutTidySrcs, pathDeps android.Paths, cFlagsDeps android.Paths) Objects {
+func compileObjs(ctx android.ModuleContext, flags builderFlags, subdir string,
+	srcFiles, noTidySrcs, timeoutTidySrcs, pathDeps android.Paths, cFlagsDeps android.Paths,
+	sharedFlags *SharedFlags) Objects {
 
-	return transformSourceToObj(ctx, subdir, srcFiles, noTidySrcs, timeoutTidySrcs, flags, pathDeps, cFlagsDeps)
+	return transformSourceToObj(ctx, subdir, srcFiles, noTidySrcs, timeoutTidySrcs, flags, pathDeps, cFlagsDeps,
+		sharedFlags)
 }
 
 // Properties for rust_bindgen related to generating rust bindings.
@@ -799,7 +818,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/OWNERS b/cc/config/OWNERS
index c78b6d5..2668fdd 100644
--- a/cc/config/OWNERS
+++ b/cc/config/OWNERS
@@ -1,3 +1,2 @@
 per-file vndk.go = smoreland@google.com, victoryang@google.com
-per-file clang.go,global.go,tidy.go = appujee@google.com, pirama@google.com, srhines@google.com, yabinc@google.com, yikong@google.com, zijunzhao@google.com
-
+per-file clang.go,global.go,tidy.go = appujee@google.com, pirama@google.com, srhines@google.com, yabinc@google.com, yikong@google.com, rprichard@google.com, sharjeelkhan@google.com
\ No newline at end of file
diff --git a/cc/config/darwin_host.go b/cc/config/darwin_host.go
index 1783f49..716965a 100644
--- a/cc/config/darwin_host.go
+++ b/cc/config/darwin_host.go
@@ -54,6 +54,7 @@
 		"12",
 		"13",
 		"14",
+		"15",
 	}
 
 	darwinAvailableLibraries = append(
diff --git a/cc/config/global.go b/cc/config/global.go
index 9d3de6d..7bea124 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.
@@ -172,7 +175,6 @@
 		"-Werror=address",
 		"-Werror=sequence-point",
 		"-Werror=format-security",
-		"-nostdlibinc",
 	}
 
 	commonGlobalLldflags = []string{
@@ -236,12 +238,14 @@
 	// opting into the warning.
 	noOverrideGlobalCflags = []string{
 		"-Werror=bool-operation",
+		"-Werror=dangling",
 		"-Werror=format-insufficient-args",
 		"-Werror=implicit-int-float-conversion",
 		"-Werror=int-in-bool-context",
 		"-Werror=int-to-pointer-cast",
 		"-Werror=pointer-to-int-cast",
 		"-Werror=xor-used-as-pow",
+		"-Wimplicit-int-float-conversion",
 		// http://b/161386391 for -Wno-void-pointer-to-enum-cast
 		"-Wno-void-pointer-to-enum-cast",
 		// http://b/161386391 for -Wno-void-pointer-to-int-cast
@@ -286,16 +290,12 @@
 		// New warnings to be fixed after clang-r468909
 		"-Wno-error=deprecated-builtins", // http://b/241601211
 		"-Wno-error=deprecated",          // in external/googletest/googletest
-		// Disabling until the warning is fixed in libc++abi header files b/366180429
-		"-Wno-deprecated-dynamic-exception-spec",
-		// New warnings to be fixed after clang-r475365
-		"-Wno-error=enum-constexpr-conversion", // http://b/243964282
 		// New warnings to be fixed after clang-r522817
 		"-Wno-error=invalid-offsetof",
-		"-Wno-error=thread-safety-reference-return",
 
 		// Allow using VLA CXX extension.
 		"-Wno-vla-cxx-extension",
+		"-Wno-cast-function-type-mismatch",
 	}
 
 	noOverride64GlobalCflags = []string{}
@@ -363,8 +363,6 @@
 		"-Wno-array-parameter",
 		"-Wno-gnu-offsetof-extensions",
 		"-Wno-pessimizing-move",
-		// TODO: Enable this warning http://b/315245071
-		"-Wno-fortify-source",
 	}
 
 	llvmNextExtraCommonGlobalCflags = []string{
@@ -384,8 +382,8 @@
 
 	// prebuilts/clang default settings.
 	ClangDefaultBase         = "prebuilts/clang/host"
-	ClangDefaultVersion      = "clang-r530567"
-	ClangDefaultShortVersion = "19"
+	ClangDefaultVersion      = "clang-r547379"
+	ClangDefaultShortVersion = "20"
 
 	// Directories with warnings from Android.bp files.
 	WarningAllowedProjects = []string{
diff --git a/cc/config/x86_linux_host.go b/cc/config/x86_linux_host.go
index 287967c..c070050 100644
--- a/cc/config/x86_linux_host.go
+++ b/cc/config/x86_linux_host.go
@@ -41,7 +41,6 @@
 	}
 
 	linuxMuslCflags = []string{
-		"-D_LIBCPP_HAS_MUSL_LIBC",
 		"-DANDROID_HOST_MUSL",
 		"-nostdlibinc",
 		"--sysroot /dev/null",
diff --git a/cc/config/x86_windows_host.go b/cc/config/x86_windows_host.go
index a4d43b9..505ddfa 100644
--- a/cc/config/x86_windows_host.go
+++ b/cc/config/x86_windows_host.go
@@ -106,10 +106,13 @@
 	}
 
 	windowsAvailableLibraries = addPrefix([]string{
+		"bcrypt",
+		"dbghelp",
 		"gdi32",
 		"imagehlp",
 		"iphlpapi",
 		"netapi32",
+		"ntdll",
 		"oleaut32",
 		"ole32",
 		"opengl32",
diff --git a/cc/coverage.go b/cc/coverage.go
index a7618dd..757641c 100644
--- a/cc/coverage.go
+++ b/cc/coverage.go
@@ -145,7 +145,7 @@
 	// Even if we don't have coverage enabled, if any of our object files were compiled
 	// with coverage, then we need to add --coverage to our ldflags.
 	if !cov.linkCoverage {
-		if ctx.static() && !ctx.staticBinary() {
+		if ctx.staticLibrary() {
 			// For static libraries, the only thing that changes our object files
 			// are included whole static libraries, so check to see if any of
 			// those have coverage enabled.
@@ -273,8 +273,6 @@
 
 type coverageTransitionMutator struct{}
 
-var _ android.TransitionMutator = (*coverageTransitionMutator)(nil)
-
 func (c coverageTransitionMutator) Split(ctx android.BaseModuleContext) []string {
 	if c, ok := ctx.Module().(*Module); ok && c.coverage != nil {
 		if c.coverage.Properties.NeedCoverageVariant {
@@ -354,10 +352,10 @@
 	}
 }
 
-func parseSymbolFileForAPICoverage(ctx ModuleContext, symbolFile string) android.ModuleOutPath {
+func ParseSymbolFileForAPICoverage(ctx android.ModuleContext, symbolFile string) android.ModuleOutPath {
 	apiLevelsJson := android.GetApiLevelsJson(ctx)
 	symbolFilePath := android.PathForModuleSrc(ctx, symbolFile)
-	outputFile := ctx.baseModuleName() + ".xml"
+	outputFile := ctx.Module().(LinkableInterface).BaseModuleName() + ".xml"
 	parsedApiCoveragePath := android.PathForModuleOut(ctx, outputFile)
 	rule := android.NewRuleBuilder(pctx, ctx)
 	rule.Command().
diff --git a/cc/fuzz.go b/cc/fuzz.go
index 3f21bc6..a8e4cb7 100644
--- a/cc/fuzz.go
+++ b/cc/fuzz.go
@@ -57,38 +57,76 @@
 	return []interface{}{&fuzzer.Properties}
 }
 
-func fuzzMutatorDeps(mctx android.BottomUpMutatorContext) {
-	currentModule, ok := mctx.Module().(*Module)
+// fuzzTransitionMutator creates variants to propagate the FuzzFramework value down to dependencies.
+type fuzzTransitionMutator struct{}
+
+func (f *fuzzTransitionMutator) Split(ctx android.BaseModuleContext) []string {
+	return []string{""}
+}
+
+func (f *fuzzTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+	m, ok := ctx.Module().(*Module)
+	if !ok {
+		return ""
+	}
+
+	if m.fuzzer == nil {
+		return ""
+	}
+
+	if m.sanitize == nil {
+		return ""
+	}
+
+	isFuzzerPointer := m.sanitize.getSanitizerBoolPtr(Fuzzer)
+	if isFuzzerPointer == nil || !*isFuzzerPointer {
+		return ""
+	}
+
+	if m.fuzzer.Properties.FuzzFramework != "" {
+		return m.fuzzer.Properties.FuzzFramework.Variant()
+	}
+
+	return sourceVariation
+}
+
+func (f *fuzzTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
+	m, ok := ctx.Module().(*Module)
+	if !ok {
+		return ""
+	}
+
+	if m.fuzzer == nil {
+		return ""
+	}
+
+	if m.sanitize == nil {
+		return ""
+	}
+
+	isFuzzerPointer := m.sanitize.getSanitizerBoolPtr(Fuzzer)
+	if isFuzzerPointer == nil || !*isFuzzerPointer {
+		return ""
+	}
+
+	return incomingVariation
+}
+
+func (f *fuzzTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
+	m, ok := ctx.Module().(*Module)
 	if !ok {
 		return
 	}
 
-	if currentModule.fuzzer == nil {
+	if m.fuzzer == nil {
 		return
 	}
 
-	mctx.WalkDeps(func(child android.Module, parent android.Module) bool {
-		c, ok := child.(*Module)
-		if !ok {
-			return false
-		}
-
-		if c.sanitize == nil {
-			return false
-		}
-
-		isFuzzerPointer := c.sanitize.getSanitizerBoolPtr(Fuzzer)
-		if isFuzzerPointer == nil || !*isFuzzerPointer {
-			return false
-		}
-
-		if c.fuzzer == nil {
-			return false
-		}
-
-		c.fuzzer.Properties.FuzzFramework = currentModule.fuzzer.Properties.FuzzFramework
-		return true
-	})
+	if variation != "" {
+		m.fuzzer.Properties.FuzzFramework = fuzz.FrameworkFromVariant(variation)
+		m.SetHideFromMake()
+		m.SetPreventInstall()
+	}
 }
 
 // cc_fuzz creates a host/device fuzzer binary. Host binaries can be found at
@@ -173,29 +211,30 @@
 	moduleInfoJSON.Class = []string{"EXECUTABLES"}
 }
 
-// IsValidSharedDependency takes a module and determines if it is a unique shared library
+// isValidSharedDependency takes a module and determines if it is a unique shared library
 // that should be installed in the fuzz target output directories. This function
 // returns true, unless:
 //   - The module is not an installable shared library, or
 //   - The module is a header or stub, or
 //   - The module is a prebuilt and its source is available, or
 //   - The module is a versioned member of an SDK snapshot.
-func IsValidSharedDependency(dependency android.Module) bool {
+func isValidSharedDependency(ctx android.ModuleContext, dependency android.ModuleProxy) bool {
 	// TODO(b/144090547): We should be parsing these modules using
 	// ModuleDependencyTag instead of the current brute-force checking.
 
-	linkable, ok := dependency.(LinkableInterface)
-	if !ok || !linkable.CcLibraryInterface() {
+	linkable, ok := android.OtherModuleProvider(ctx, dependency, LinkableInfoProvider)
+	if !ok || !linkable.CcLibraryInterface {
 		// Discard non-linkables.
 		return false
 	}
 
-	if !linkable.Shared() {
+	if !linkable.Shared {
 		// Discard static libs.
 		return false
 	}
 
-	if lib := moduleLibraryInterface(dependency); lib != nil && lib.buildStubs() && linkable.CcLibrary() {
+	ccInfo, hasCcInfo := android.OtherModuleProvider(ctx, dependency, CcInfoProvider)
+	if hasCcInfo && ccInfo.LibraryInfo != nil && ccInfo.LibraryInfo.BuildStubs && linkable.CcLibrary {
 		// Discard stubs libs (only CCLibrary variants). Prebuilt libraries should not
 		// be excluded on the basis of they're not CCLibrary()'s.
 		return false
@@ -204,13 +243,13 @@
 	// We discarded module stubs libraries above, but the LLNDK prebuilts stubs
 	// libraries must be handled differently - by looking for the stubDecorator.
 	// Discard LLNDK prebuilts stubs as well.
-	if ccLibrary, isCcLibrary := dependency.(*Module); isCcLibrary {
-		if _, isLLndkStubLibrary := ccLibrary.linker.(*stubDecorator); isLLndkStubLibrary {
+	if hasCcInfo {
+		if ccInfo.LinkerInfo != nil && ccInfo.LinkerInfo.StubDecoratorInfo != nil {
 			return false
 		}
 		// Discard installable:false libraries because they are expected to be absent
 		// in runtime.
-		if !proptools.BoolDefault(ccLibrary.Installable(), true) {
+		if !proptools.BoolDefault(linkable.Installable, true) {
 			return false
 		}
 	}
@@ -218,7 +257,7 @@
 	// If the same library is present both as source and a prebuilt we must pick
 	// only one to avoid a conflict. Always prefer the source since the prebuilt
 	// probably won't be built with sanitizers enabled.
-	if prebuilt := android.GetEmbeddedPrebuilt(dependency); prebuilt != nil && prebuilt.SourceExists() {
+	if prebuilt, ok := android.OtherModuleProvider(ctx, dependency, android.PrebuiltModuleInfoProvider); ok && prebuilt.SourceExists {
 		return false
 	}
 
@@ -250,7 +289,7 @@
 }
 
 func (fuzzBin *fuzzBinary) install(ctx ModuleContext, file android.Path) {
-	fuzzBin.fuzzPackagedModule = PackageFuzzModule(ctx, fuzzBin.fuzzPackagedModule, pctx)
+	fuzzBin.fuzzPackagedModule = PackageFuzzModule(ctx, fuzzBin.fuzzPackagedModule)
 
 	installBase := "fuzz"
 
@@ -307,10 +346,13 @@
 	fuzzBin.binaryDecorator.baseInstaller.install(ctx, file)
 }
 
-func PackageFuzzModule(ctx android.ModuleContext, fuzzPackagedModule fuzz.FuzzPackagedModule, pctx android.PackageContext) fuzz.FuzzPackagedModule {
+func PackageFuzzModule(ctx android.ModuleContext, fuzzPackagedModule fuzz.FuzzPackagedModule) fuzz.FuzzPackagedModule {
 	fuzzPackagedModule.Corpus = android.PathsForModuleSrc(ctx, fuzzPackagedModule.FuzzProperties.Corpus)
+	fuzzPackagedModule.Corpus = append(fuzzPackagedModule.Corpus, android.PathsForModuleSrc(ctx, fuzzPackagedModule.FuzzProperties.Device_common_corpus)...)
 
 	fuzzPackagedModule.Data = android.PathsForModuleSrc(ctx, fuzzPackagedModule.FuzzProperties.Data)
+	fuzzPackagedModule.Data = append(fuzzPackagedModule.Data, android.PathsForModuleSrc(ctx, fuzzPackagedModule.FuzzProperties.Device_common_data)...)
+	fuzzPackagedModule.Data = append(fuzzPackagedModule.Data, android.PathsForModuleSrc(ctx, fuzzPackagedModule.FuzzProperties.Device_first_data)...)
 
 	if fuzzPackagedModule.FuzzProperties.Dictionary != nil {
 		fuzzPackagedModule.Dictionary = android.PathForModuleSrc(ctx, *fuzzPackagedModule.FuzzProperties.Dictionary)
@@ -566,17 +608,17 @@
 // VisitDirectDeps is used first to avoid incorrectly using the core libraries (sanitizer
 // runtimes, libc, libdl, etc.) from a dependency. This may cause issues when dependencies
 // have explicit sanitizer tags, as we may get a dependency on an unsanitized libc, etc.
-func CollectAllSharedDependencies(ctx android.ModuleContext) (android.RuleBuilderInstalls, []android.Module) {
+func CollectAllSharedDependencies(ctx android.ModuleContext) (android.RuleBuilderInstalls, []android.ModuleProxy) {
 	seen := make(map[string]bool)
 	recursed := make(map[string]bool)
-	deps := []android.Module{}
+	deps := []android.ModuleProxy{}
 
 	var sharedLibraries android.RuleBuilderInstalls
 
 	// Enumerate the first level of dependencies, as we discard all non-library
 	// modules in the BFS loop below.
-	ctx.VisitDirectDeps(func(dep android.Module) {
-		if !IsValidSharedDependency(dep) {
+	ctx.VisitDirectDepsProxy(func(dep android.ModuleProxy) {
+		if !isValidSharedDependency(ctx, dep) {
 			return
 		}
 		sharedLibraryInfo, hasSharedLibraryInfo := android.OtherModuleProvider(ctx, dep, SharedLibraryInfoProvider)
@@ -594,19 +636,21 @@
 		sharedLibraries = append(sharedLibraries, ruleBuilderInstall)
 	})
 
-	ctx.WalkDeps(func(child, parent android.Module) bool {
+	ctx.WalkDepsProxy(func(child, _ android.ModuleProxy) bool {
 
 		// If this is a Rust module which is not rust_ffi_shared, we still want to bundle any transitive
-		// shared dependencies (even for rust_ffi_rlib or rust_ffi_static)
-		if rustmod, ok := child.(LinkableInterface); ok && rustmod.RustLibraryInterface() && !rustmod.Shared() {
-			if recursed[ctx.OtherModuleName(child)] {
-				return false
+		// shared dependencies (even for rust_ffi_static)
+		if info, ok := android.OtherModuleProvider(ctx, child, LinkableInfoProvider); ok {
+			if info.RustLibraryInterface && !info.Shared {
+				if recursed[ctx.OtherModuleName(child)] {
+					return false
+				}
+				recursed[ctx.OtherModuleName(child)] = true
+				return true
 			}
-			recursed[ctx.OtherModuleName(child)] = true
-			return true
 		}
 
-		if !IsValidSharedDependency(child) {
+		if !isValidSharedDependency(ctx, child) {
 			return false
 		}
 		sharedLibraryInfo, hasSharedLibraryInfo := android.OtherModuleProvider(ctx, child, SharedLibraryInfoProvider)
diff --git a/cc/gen_test.go b/cc/gen_test.go
index 439f0a9..dde0dcf 100644
--- a/cc/gen_test.go
+++ b/cc/gen_test.go
@@ -33,8 +33,8 @@
 			],
 		}`)
 
-		aidl := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Rule("aidl")
-		libfoo := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Module().(*Module)
+		aidl := ctx.ModuleForTests(t, "libfoo", "android_arm_armv7-a-neon_shared").Rule("aidl")
+		libfoo := ctx.ModuleForTests(t, "libfoo", "android_arm_armv7-a-neon_shared").Module().(*Module)
 
 		expected := "-I" + filepath.Dir(aidl.Output.String())
 		actual := android.StringsRelativeToTop(ctx.Config(), libfoo.flags.Local.CommonFlags)
@@ -59,9 +59,9 @@
 			],
 		}`)
 
-		aidl := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Rule("aidl")
-		aidlManifest := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Output("aidl.sbox.textproto")
-		libfoo := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Module().(*Module)
+		aidl := ctx.ModuleForTests(t, "libfoo", "android_arm_armv7-a-neon_shared").Rule("aidl")
+		aidlManifest := ctx.ModuleForTests(t, "libfoo", "android_arm_armv7-a-neon_shared").Output("aidl.sbox.textproto")
+		libfoo := ctx.ModuleForTests(t, "libfoo", "android_arm_armv7-a-neon_shared").Module().(*Module)
 
 		if !inList("-I"+filepath.Dir(aidl.Output.String()), android.StringsRelativeToTop(ctx.Config(), libfoo.flags.Local.CommonFlags)) {
 			t.Errorf("missing aidl includes in global flags")
@@ -84,7 +84,7 @@
 		}`)
 
 		outDir := "out/soong/.intermediates/libsysprop/android_arm64_armv8-a_static/gen"
-		syspropBuildParams := ctx.ModuleForTests("libsysprop", "android_arm64_armv8-a_static").Rule("sysprop")
+		syspropBuildParams := ctx.ModuleForTests(t, "libsysprop", "android_arm64_armv8-a_static").Rule("sysprop")
 
 		android.AssertStringEquals(t, "header output directory does not match", outDir+"/sysprop/include/path/to", syspropBuildParams.Args["headerOutDir"])
 		android.AssertStringEquals(t, "public output directory does not match", outDir+"/sysprop/public/include/path/to", syspropBuildParams.Args["publicOutDir"])
diff --git a/cc/genrule.go b/cc/genrule.go
index fe3b127..bd6c5f1 100644
--- a/cc/genrule.go
+++ b/cc/genrule.go
@@ -77,41 +77,41 @@
 
 var _ android.ImageInterface = (*GenruleExtraProperties)(nil)
 
-func (g *GenruleExtraProperties) ImageMutatorBegin(ctx android.BaseModuleContext) {}
+func (g *GenruleExtraProperties) ImageMutatorBegin(ctx android.ImageInterfaceContext) {}
 
-func (g *GenruleExtraProperties) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
+func (g *GenruleExtraProperties) VendorVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return Bool(g.Vendor_available) || Bool(g.Odm_available) || ctx.SocSpecific() || ctx.DeviceSpecific()
 }
 
-func (g *GenruleExtraProperties) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
+func (g *GenruleExtraProperties) ProductVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return Bool(g.Product_available) || ctx.ProductSpecific()
 }
 
-func (g *GenruleExtraProperties) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
+func (g *GenruleExtraProperties) CoreVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return !(ctx.SocSpecific() || ctx.DeviceSpecific() || ctx.ProductSpecific())
 }
 
-func (g *GenruleExtraProperties) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (g *GenruleExtraProperties) RamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return Bool(g.Ramdisk_available)
 }
 
-func (g *GenruleExtraProperties) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (g *GenruleExtraProperties) VendorRamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return Bool(g.Vendor_ramdisk_available)
 }
 
-func (g *GenruleExtraProperties) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (g *GenruleExtraProperties) DebugRamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return false
 }
 
-func (g *GenruleExtraProperties) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
+func (g *GenruleExtraProperties) RecoveryVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	// If the build is using a snapshot, the recovery variant under AOSP directories
 	// is not needed.
 	return Bool(g.Recovery_available)
 }
 
-func (g *GenruleExtraProperties) ExtraImageVariations(ctx android.BaseModuleContext) []string {
+func (g *GenruleExtraProperties) ExtraImageVariations(ctx android.ImageInterfaceContext) []string {
 	return nil
 }
 
-func (g *GenruleExtraProperties) SetImageVariation(ctx android.BaseModuleContext, variation string) {
+func (g *GenruleExtraProperties) SetImageVariation(ctx android.ImageInterfaceContext, variation string) {
 }
diff --git a/cc/genrule_test.go b/cc/genrule_test.go
index 9a8049b..438eb98 100644
--- a/cc/genrule_test.go
+++ b/cc/genrule_test.go
@@ -64,13 +64,13 @@
 		t.Fatal(errs)
 	}
 
-	gen := ctx.ModuleForTests("gen", "android_arm_armv7-a-neon").Output("out_arm")
+	gen := ctx.ModuleForTests(t, "gen", "android_arm_armv7-a-neon").Output("out_arm")
 	expected := []string{"foo"}
 	if !reflect.DeepEqual(expected, gen.Implicits.Strings()[:len(expected)]) {
 		t.Errorf(`want arm inputs %v, got %v`, expected, gen.Implicits.Strings())
 	}
 
-	gen = ctx.ModuleForTests("gen", "android_arm64_armv8-a").Output("out_arm")
+	gen = ctx.ModuleForTests(t, "gen", "android_arm64_armv8-a").Output("out_arm")
 	expected = []string{"bar"}
 	if !reflect.DeepEqual(expected, gen.Implicits.Strings()[:len(expected)]) {
 		t.Errorf(`want arm64 inputs %v, got %v`, expected, gen.Implicits.Strings())
@@ -105,7 +105,7 @@
 		`
 	ctx := testCc(t, bp)
 
-	gen := ctx.ModuleForTests("gen", "android_arm_armv7-a-neon").Output("out")
+	gen := ctx.ModuleForTests(t, "gen", "android_arm_armv7-a-neon").Output("out")
 	expected := []string{"libboth.so", "libshared.so", "libstatic.a"}
 	var got []string
 	for _, input := range gen.Implicits {
@@ -178,7 +178,7 @@
 				PrepareForIntegrationTestWithCc,
 				android.OptionalFixturePreparer(tt.preparer),
 			).RunTestWithBp(t, bp)
-			gen := result.ModuleForTests("gen", tt.variant)
+			gen := result.ModuleForTests(t, "gen", tt.variant)
 			sboxProto := android.RuleBuilderSboxProtoForTests(t, result.TestContext, gen.Output("genrule.sbox.textproto"))
 			cmd := *sboxProto.Commands[0].Command
 			android.AssertStringDoesContain(t, "incorrect CC_ARCH", cmd, "CC_ARCH="+tt.arch+" ")
@@ -236,7 +236,7 @@
 	}
 	`
 	result := PrepareForIntegrationTestWithCc.RunTestWithBp(t, bp)
-	gen_32bit := result.ModuleForTests("gen", "android_arm_armv7-a-neon").OutputFiles(result.TestContext, t, "")
+	gen_32bit := result.ModuleForTests(t, "gen", "android_arm_armv7-a-neon").OutputFiles(result.TestContext, t, "")
 	android.AssertPathsEndWith(t,
 		"genrule_out",
 		[]string{
@@ -245,7 +245,7 @@
 		gen_32bit,
 	)
 
-	gen_64bit := result.ModuleForTests("gen", "android_arm64_armv8-a").OutputFiles(result.TestContext, t, "")
+	gen_64bit := result.ModuleForTests(t, "gen", "android_arm64_armv8-a").OutputFiles(result.TestContext, t, "")
 	android.AssertPathsEndWith(t,
 		"genrule_out",
 		[]string{
diff --git a/cc/image.go b/cc/image.go
index 7594a08..9766af3 100644
--- a/cc/image.go
+++ b/cc/image.go
@@ -177,10 +177,7 @@
 	IsSnapshotPrebuilt() bool
 
 	// SnapshotVersion returns the snapshot version for this module.
-	SnapshotVersion(mctx android.BaseModuleContext) string
-
-	// SdkVersion returns the SDK version for this module.
-	SdkVersion() string
+	SnapshotVersion(mctx android.ImageInterfaceContext) string
 
 	// ExtraVariants returns the list of extra variants this module requires.
 	ExtraVariants() []string
@@ -209,7 +206,7 @@
 
 var _ ImageMutatableModule = (*Module)(nil)
 
-func (m *Module) ImageMutatorBegin(mctx android.BaseModuleContext) {
+func (m *Module) ImageMutatorBegin(mctx android.ImageInterfaceContext) {
 	MutateImage(mctx, m)
 }
 
@@ -273,7 +270,7 @@
 	m.Properties.VendorVariantNeeded = b
 }
 
-func (m *Module) SnapshotVersion(mctx android.BaseModuleContext) string {
+func (m *Module) SnapshotVersion(mctx android.ImageInterfaceContext) string {
 	if snapshot, ok := m.linker.(SnapshotInterface); ok {
 		return snapshot.Version()
 	} else {
@@ -291,7 +288,7 @@
 }
 
 // MutateImage handles common image mutations for ImageMutatableModule interfaces.
-func MutateImage(mctx android.BaseModuleContext, m ImageMutatableModule) {
+func MutateImage(mctx android.ImageInterfaceContext, m ImageMutatableModule) {
 	// Validation check
 	vendorSpecific := mctx.SocSpecific() || mctx.DeviceSpecific()
 	productSpecific := mctx.ProductSpecific()
@@ -370,7 +367,7 @@
 		if m.HasProductVariant() {
 			productVariantNeeded = true
 		}
-	} else if vendorSpecific && m.SdkVersion() == "" {
+	} else if vendorSpecific {
 		// This will be available in /vendor (or /odm) only
 		vendorVariantNeeded = true
 	} else {
@@ -380,7 +377,7 @@
 		coreVariantNeeded = true
 	}
 
-	if coreVariantNeeded && productSpecific && m.SdkVersion() == "" {
+	if coreVariantNeeded && productSpecific {
 		// The module has "product_specific: true" that does not create core variant.
 		coreVariantNeeded = false
 		productVariantNeeded = true
@@ -431,35 +428,35 @@
 	}
 }
 
-func (c *Module) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
+func (c *Module) VendorVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return c.Properties.VendorVariantNeeded
 }
 
-func (c *Module) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
+func (c *Module) ProductVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return c.Properties.ProductVariantNeeded
 }
 
-func (c *Module) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
+func (c *Module) CoreVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return c.Properties.CoreVariantNeeded
 }
 
-func (c *Module) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (c *Module) RamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return c.Properties.RamdiskVariantNeeded
 }
 
-func (c *Module) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (c *Module) VendorRamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return c.Properties.VendorRamdiskVariantNeeded
 }
 
-func (c *Module) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (c *Module) DebugRamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return false
 }
 
-func (c *Module) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
+func (c *Module) RecoveryVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return c.Properties.RecoveryVariantNeeded
 }
 
-func (c *Module) ExtraImageVariations(ctx android.BaseModuleContext) []string {
+func (c *Module) ExtraImageVariations(ctx android.ImageInterfaceContext) []string {
 	return c.Properties.ExtraVersionedImageVariations
 }
 
@@ -513,7 +510,7 @@
 	}
 }
 
-func (c *Module) SetImageVariation(ctx android.BaseModuleContext, variant string) {
+func (c *Module) SetImageVariation(ctx android.ImageInterfaceContext, variant string) {
 	if variant == android.RamdiskVariation {
 		c.MakeAsPlatform()
 		squashRamdiskSrcs(c)
diff --git a/cc/installer.go b/cc/installer.go
index 30f9612..d7d8c6d 100644
--- a/cc/installer.go
+++ b/cc/installer.go
@@ -107,6 +107,10 @@
 	installer.installDeps = append(installer.installDeps, installedData...)
 }
 
+func (installer *baseInstaller) installStandaloneTestDep(ctx ModuleContext, standaloneTestDep android.PackagingSpec) {
+	installer.installTestData(ctx, []android.DataPath{{SrcPath: standaloneTestDep.ToGob().SrcPath, RelativeInstallPath: "standalone-libs"}})
+}
+
 func (installer *baseInstaller) everInstallable() bool {
 	// Most cc modules are installable.
 	return true
diff --git a/cc/library.go b/cc/library.go
index 988a7fa..532b7e9 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -25,14 +25,18 @@
 	"sync"
 
 	"android/soong/android"
+	"android/soong/cc/config"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/depset"
 	"github.com/google/blueprint/pathtools"
 	"github.com/google/blueprint/proptools"
 )
 
 // LibraryProperties is a collection of properties shared by cc library rules/cc.
 type LibraryProperties struct {
+	// local file name to pass to the linker as -exported_symbols_list
+	Exported_symbols_list *string `android:"path,arch_variant"`
 	// local file name to pass to the linker as -unexported_symbols_list
 	Unexported_symbols_list *string `android:"path,arch_variant"`
 	// local file name to pass to the linker as -force_symbols_not_weak_list
@@ -61,21 +65,7 @@
 	Static_ndk_lib *bool
 
 	// Generate stubs to make this library accessible to APEXes.
-	Stubs struct {
-		// Relative path to the symbol map. The symbol map provides the list of
-		// symbols that are exported for stubs variant of this library.
-		Symbol_file *string `android:"path,arch_variant"`
-
-		// List versions to generate stubs libs for. The version name "current" is always
-		// implicitly added.
-		Versions []string
-
-		// Whether to not require the implementation of the library to be installed if a
-		// client of the stubs is installed. Defaults to true; set to false if the
-		// implementation is made available by some other means, e.g. in a Microdroid
-		// virtual machine.
-		Implementation_installable *bool
-	} `android:"arch_variant"`
+	Stubs StubsProperties `android:"arch_variant"`
 
 	// set the name of the output
 	Stem *string `android:"arch_variant"`
@@ -124,6 +114,22 @@
 	Vendor_public_library vendorPublicLibraryProperties
 }
 
+type StubsProperties struct {
+	// Relative path to the symbol map. The symbol map provides the list of
+	// symbols that are exported for stubs variant of this library.
+	Symbol_file *string `android:"path,arch_variant"`
+
+	// List versions to generate stubs libs for. The version name "current" is always
+	// implicitly added.
+	Versions []string
+
+	// Whether to not require the implementation of the library to be installed if a
+	// client of the stubs is installed. Defaults to true; set to false if the
+	// implementation is made available by some other means, e.g. in a Microdroid
+	// virtual machine.
+	Implementation_installable *bool
+}
+
 // StaticProperties is a properties stanza to affect only attributes of the "static" variants of a
 // library module.
 type StaticProperties struct {
@@ -454,11 +460,15 @@
 	return props
 }
 
-// linkerFlags takes a Flags struct and augments it to contain linker flags that are defined by this
-// library, or that are implied by attributes of this library (such as whether this library is a
-// shared library).
-func (library *libraryDecorator) linkerFlags(ctx ModuleContext, flags Flags) Flags {
-	flags = library.baseLinker.linkerFlags(ctx, flags)
+func CommonLibraryLinkerFlags(ctx android.ModuleContext, flags Flags,
+	toolchain config.Toolchain, libName string) Flags {
+
+	mod, ok := ctx.Module().(LinkableInterface)
+
+	if !ok {
+		ctx.ModuleErrorf("trying to add linker flags to a non-LinkableInterface module.")
+		return flags
+	}
 
 	// MinGW spits out warnings about -fPIC even for -fpie?!) being ignored because
 	// all code is position independent, and then those warnings get promoted to
@@ -466,27 +476,18 @@
 	if !ctx.Windows() {
 		flags.Global.CFlags = append(flags.Global.CFlags, "-fPIC")
 	}
-
-	if library.static() {
-		flags.Local.CFlags = append(flags.Local.CFlags, library.StaticProperties.Static.Cflags.GetOrDefault(ctx, nil)...)
-	} else if library.shared() {
-		flags.Local.CFlags = append(flags.Local.CFlags, library.SharedProperties.Shared.Cflags.GetOrDefault(ctx, nil)...)
-	}
-
-	if library.shared() {
-		libName := library.getLibName(ctx)
+	if mod.Shared() {
 		var f []string
-		if ctx.toolchain().Bionic() {
+		if toolchain.Bionic() {
 			f = append(f,
 				"-nostdlib",
 				"-Wl,--gc-sections",
 			)
 		}
-
 		if ctx.Darwin() {
 			f = append(f,
 				"-dynamiclib",
-				"-install_name @rpath/"+libName+flags.Toolchain.ShlibSuffix(),
+				"-install_name @rpath/"+libName+toolchain.ShlibSuffix(),
 			)
 			if ctx.Arch().ArchType == android.X86 {
 				f = append(f,
@@ -496,16 +497,30 @@
 		} else {
 			f = append(f, "-shared")
 			if !ctx.Windows() {
-				f = append(f, "-Wl,-soname,"+libName+flags.Toolchain.ShlibSuffix())
+				f = append(f, "-Wl,-soname,"+libName+toolchain.ShlibSuffix())
 			}
 		}
-
 		flags.Global.LdFlags = append(flags.Global.LdFlags, f...)
 	}
 
 	return flags
 }
 
+// linkerFlags takes a Flags struct and augments it to contain linker flags that are defined by this
+// library, or that are implied by attributes of this library (such as whether this library is a
+// shared library).
+func (library *libraryDecorator) linkerFlags(ctx ModuleContext, flags Flags) Flags {
+	flags = library.baseLinker.linkerFlags(ctx, flags)
+	flags = CommonLibraryLinkerFlags(ctx, flags, ctx.toolchain(), library.getLibName(ctx))
+	if library.static() {
+		flags.Local.CFlags = append(flags.Local.CFlags, library.StaticProperties.Static.Cflags.GetOrDefault(ctx, nil)...)
+	} else if library.shared() {
+		flags.Local.CFlags = append(flags.Local.CFlags, library.SharedProperties.Shared.Cflags.GetOrDefault(ctx, nil)...)
+	}
+
+	return flags
+}
+
 // compilerFlags takes a Flags and augments it to contain compile flags from global values,
 // per-target values, module type values, per-module Blueprints properties, extra flags from
 // `flags`, and generated sources from `deps`.
@@ -524,7 +539,7 @@
 		// Wipe all the module-local properties, leaving only the global properties.
 		flags.Local = LocalOrGlobalFlags{}
 	}
-	if library.buildStubs() {
+	if library.BuildStubs() {
 		// Remove -include <file> when compiling stubs. Otherwise, the force included
 		// headers might cause conflicting types error with the symbols in the
 		// generated stubs source code. e.g.
@@ -543,7 +558,7 @@
 		flags.Local.CommonFlags = removeInclude(flags.Local.CommonFlags)
 		flags.Local.CFlags = removeInclude(flags.Local.CFlags)
 
-		flags = addStubLibraryCompilerFlags(flags)
+		flags = AddStubLibraryCompilerFlags(flags)
 	}
 	return flags
 }
@@ -564,35 +579,37 @@
 }
 
 func (library *libraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects {
+	sharedFlags := ctx.getSharedFlags()
+
 	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()
+		// Get the matching SDK version for the vendor API level.
+		version, err := android.GetSdkVersionForVendorApiLevel(ctx.Config().VendorApiLevel())
+		if err != nil {
+			panic(err)
 		}
+
 		// This is the vendor variant of an LLNDK library, build the LLNDK stubs.
-		nativeAbiResult := parseNativeAbiDefinition(ctx,
+		nativeAbiResult := ParseNativeAbiDefinition(ctx,
 			String(library.Properties.Llndk.Symbol_file),
-			android.ApiLevelOrPanic(ctx, vendorApiLevel), "--llndk")
-		objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
+			nativeClampedApiLevel(ctx, version), "--llndk")
+		objs := CompileStubLibrary(ctx, flags, nativeAbiResult.StubSrc, sharedFlags)
 		if !Bool(library.Properties.Llndk.Unversioned) {
 			library.versionScriptPath = android.OptionalPathForPath(
-				nativeAbiResult.versionScript)
+				nativeAbiResult.VersionScript)
 		}
 		return objs
 	}
 	if ctx.IsVendorPublicLibrary() {
-		nativeAbiResult := parseNativeAbiDefinition(ctx,
+		nativeAbiResult := ParseNativeAbiDefinition(ctx,
 			String(library.Properties.Vendor_public_library.Symbol_file),
 			android.FutureApiLevel, "")
-		objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
+		objs := CompileStubLibrary(ctx, flags, nativeAbiResult.StubSrc, sharedFlags)
 		if !Bool(library.Properties.Vendor_public_library.Unversioned) {
-			library.versionScriptPath = android.OptionalPathForPath(nativeAbiResult.versionScript)
+			library.versionScriptPath = android.OptionalPathForPath(nativeAbiResult.VersionScript)
 		}
 		return objs
 	}
-	if library.buildStubs() {
+	if library.BuildStubs() {
 		return library.compileModuleLibApiStubs(ctx, flags, deps)
 	}
 
@@ -613,10 +630,17 @@
 	}
 	if library.sabi.shouldCreateSourceAbiDump() {
 		dirs := library.exportedIncludeDirsForAbiCheck(ctx)
-		flags.SAbiFlags = make([]string, 0, len(dirs))
+		flags.SAbiFlags = make([]string, 0, len(dirs)+1)
 		for _, dir := range dirs {
 			flags.SAbiFlags = append(flags.SAbiFlags, "-I"+dir)
 		}
+		// If this library does not export any include directory, do not append the flags
+		// so that the ABI tool dumps everything without filtering by the include directories.
+		// requiresGlobalIncludes returns whether this library can include CommonGlobalIncludes.
+		// If the library cannot include them, it cannot export them.
+		if len(dirs) > 0 && requiresGlobalIncludes(ctx) {
+			flags.SAbiFlags = append(flags.SAbiFlags, "${config.CommonGlobalIncludes}")
+		}
 		totalLength := len(srcs) + len(deps.GeneratedSources) +
 			len(sharedSrcs) + len(staticSrcs)
 		if totalLength > 0 {
@@ -632,18 +656,65 @@
 		objs = objs.Append(compileObjs(ctx, buildFlags, android.DeviceStaticLibrary, srcs,
 			android.PathsForModuleSrc(ctx, library.StaticProperties.Static.Tidy_disabled_srcs),
 			android.PathsForModuleSrc(ctx, library.StaticProperties.Static.Tidy_timeout_srcs),
-			library.baseCompiler.pathDeps, library.baseCompiler.cFlagsDeps))
+			library.baseCompiler.pathDeps, library.baseCompiler.cFlagsDeps, sharedFlags))
 	} else if library.shared() {
 		srcs := android.PathsForModuleSrc(ctx, sharedSrcs)
 		objs = objs.Append(compileObjs(ctx, buildFlags, android.DeviceSharedLibrary, srcs,
 			android.PathsForModuleSrc(ctx, library.SharedProperties.Shared.Tidy_disabled_srcs),
 			android.PathsForModuleSrc(ctx, library.SharedProperties.Shared.Tidy_timeout_srcs),
-			library.baseCompiler.pathDeps, library.baseCompiler.cFlagsDeps))
+			library.baseCompiler.pathDeps, library.baseCompiler.cFlagsDeps, sharedFlags))
 	}
 
 	return objs
 }
 
+type ApiStubsParams struct {
+	NotInPlatform  bool
+	IsNdk          bool
+	BaseModuleName string
+	ModuleName     string
+}
+
+// GetApiStubsFlags calculates the genstubFlags string to pass to ParseNativeAbiDefinition
+func GetApiStubsFlags(api ApiStubsParams) string {
+	var flag string
+
+	// b/239274367 --apex and --systemapi filters symbols tagged with # apex and #
+	// systemapi, respectively. The former is for symbols defined in platform libraries
+	// and the latter is for symbols defined in APEXes.
+	// A single library can contain either # apex or # systemapi, but not both.
+	// The stub generator (ndkstubgen) is additive, so passing _both_ of these to it should be a no-op.
+	// However, having this distinction helps guard accidental
+	// promotion or demotion of API and also helps the API review process b/191371676
+	if api.NotInPlatform {
+		flag = "--apex"
+	} else {
+		flag = "--systemapi"
+	}
+
+	// b/184712170, unless the lib is an NDK library, exclude all public symbols from
+	// the stub so that it is mandated that all symbols are explicitly marked with
+	// either apex or systemapi.
+	if !api.IsNdk &&
+		// the symbol files of libclang libs are autogenerated and do not contain systemapi tags
+		// TODO (spandandas): Update mapfile.py to include #systemapi tag on all symbols
+		!strings.Contains(api.ModuleName, "libclang_rt") {
+		flag = flag + " --no-ndk"
+	}
+
+	// TODO(b/361303067): Remove this special case if bionic/ projects are added to ART development branches.
+	if isBionic(api.BaseModuleName) {
+		// set the flags explicitly for bionic libs.
+		// this is necessary for development in minimal branches which does not contain bionic/*.
+		// In such minimal branches, e.g. on the prebuilt libc stubs
+		// 1. IsNdk will return false (since the ndk_library definition for libc does not exist)
+		// 2. NotInPlatform will return true (since the source com.android.runtime does not exist)
+		flag = "--apex"
+	}
+
+	return flag
+}
+
 // Compile stubs for the API surface between platform and apex
 // This method will be used by source and prebuilt cc module types.
 func (library *libraryDecorator) compileModuleLibApiStubs(ctx ModuleContext, flags Flags, deps PathDeps) Objects {
@@ -651,56 +722,34 @@
 	if library.Properties.Stubs.Symbol_file == nil {
 		return Objects{}
 	}
+
 	symbolFile := String(library.Properties.Stubs.Symbol_file)
 	library.stubsSymbolFilePath = android.PathForModuleSrc(ctx, symbolFile)
-	// b/239274367 --apex and --systemapi filters symbols tagged with # apex and #
-	// systemapi, respectively. The former is for symbols defined in platform libraries
-	// and the latter is for symbols defined in APEXes.
-	// A single library can contain either # apex or # systemapi, but not both.
-	// The stub generator (ndkstubgen) is additive, so passing _both_ of these to it should be a no-op.
-	// However, having this distinction helps guard accidental
-	// promotion or demotion of API and also helps the API review process b/191371676
-	var flag string
-	if ctx.Module().(android.ApexModule).NotInPlatform() {
-		flag = "--apex"
-	} else {
-		flag = "--systemapi"
+
+	apiParams := ApiStubsParams{
+		NotInPlatform:  ctx.notInPlatform(),
+		IsNdk:          ctx.Module().(*Module).IsNdk(ctx.Config()),
+		BaseModuleName: ctx.baseModuleName(),
+		ModuleName:     ctx.ModuleName(),
 	}
-	// b/184712170, unless the lib is an NDK library, exclude all public symbols from
-	// the stub so that it is mandated that all symbols are explicitly marked with
-	// either apex or systemapi.
-	if !ctx.Module().(*Module).IsNdk(ctx.Config()) &&
-		// the symbol files of libclang libs are autogenerated and do not contain systemapi tags
-		// TODO (spandandas): Update mapfile.py to include #systemapi tag on all symbols
-		!strings.Contains(ctx.ModuleName(), "libclang_rt") {
-		flag = flag + " --no-ndk"
-	}
-	// TODO(b/361303067): Remove this special case if bionic/ projects are added to ART development branches.
-	if isBionic(ctx.baseModuleName()) {
-		// set the flags explicitly for bionic libs.
-		// this is necessary for development in minimal branches which does not contain bionic/*.
-		// In such minimal branches, e.g. on the prebuilt libc stubs
-		// 1. IsNdk will return false (since the ndk_library definition for libc does not exist)
-		// 2. NotInPlatform will return true (since the source com.android.runtime does not exist)
-		flag = "--apex"
-	}
-	nativeAbiResult := parseNativeAbiDefinition(ctx, symbolFile,
+	flag := GetApiStubsFlags(apiParams)
+
+	nativeAbiResult := ParseNativeAbiDefinition(ctx, symbolFile,
 		android.ApiLevelOrPanic(ctx, library.MutatedProperties.StubsVersion), flag)
-	objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
+	objs := CompileStubLibrary(ctx, flags, nativeAbiResult.StubSrc, ctx.getSharedFlags())
 
 	library.versionScriptPath = android.OptionalPathForPath(
-		nativeAbiResult.versionScript)
-
+		nativeAbiResult.VersionScript)
 	// Parse symbol file to get API list for coverage
-	if library.stubsVersion() == "current" && ctx.PrimaryArch() && !ctx.inRecovery() && !ctx.inProduct() && !ctx.inVendor() {
-		library.apiListCoverageXmlPath = parseSymbolFileForAPICoverage(ctx, symbolFile)
+	if library.StubsVersion() == "current" && ctx.PrimaryArch() && !ctx.inRecovery() && !ctx.inProduct() && !ctx.inVendor() {
+		library.apiListCoverageXmlPath = ParseSymbolFileForAPICoverage(ctx, symbolFile)
 	}
 
 	return objs
 }
 
 type libraryInterface interface {
-	versionedInterface
+	VersionedInterface
 
 	static() bool
 	shared() bool
@@ -722,33 +771,51 @@
 	// Write LOCAL_ADDITIONAL_DEPENDENCIES for ABI diff
 	androidMkWriteAdditionalDependenciesForSourceAbiDiff(w io.Writer)
 
-	availableFor(string) bool
+	apexAvailable() []string
 
-	getAPIListCoverageXMLPath() android.ModuleOutPath
+	setAPIListCoverageXMLPath(out android.ModuleOutPath)
+	symbolsFile() *string
+	setSymbolFilePath(path android.Path)
+	setVersionScriptPath(path android.OptionalPath)
 
 	installable() *bool
 }
 
-type versionedInterface interface {
-	buildStubs() bool
-	setBuildStubs(isLatest bool)
-	hasStubsVariants() bool
-	isStubsImplementationRequired() bool
-	setStubsVersion(string)
-	stubsVersion() string
+func (library *libraryDecorator) symbolsFile() *string {
+	return library.Properties.Stubs.Symbol_file
+}
 
-	stubsVersions(ctx android.BaseModuleContext) []string
-	setAllStubsVersions([]string)
-	allStubsVersions() []string
+func (library *libraryDecorator) setSymbolFilePath(path android.Path) {
+	library.stubsSymbolFilePath = path
+}
 
-	implementationModuleName(name string) string
-	hasLLNDKStubs() bool
-	hasLLNDKHeaders() bool
-	hasVendorPublicLibrary() bool
+func (library *libraryDecorator) setVersionScriptPath(path android.OptionalPath) {
+	library.versionScriptPath = path
+}
+
+type VersionedInterface interface {
+	BuildStubs() bool
+	SetBuildStubs(isLatest bool)
+	HasStubsVariants() bool
+	IsStubsImplementationRequired() bool
+	SetStubsVersion(string)
+	StubsVersion() string
+
+	StubsVersions(ctx android.BaseModuleContext) []string
+	SetAllStubsVersions([]string)
+	AllStubsVersions() []string
+
+	ImplementationModuleName(name string) string
+	HasLLNDKStubs() bool
+	HasLLNDKHeaders() bool
+	HasVendorPublicLibrary() bool
+	IsLLNDKMovedToApex() bool
+
+	GetAPIListCoverageXMLPath() android.ModuleOutPath
 }
 
 var _ libraryInterface = (*libraryDecorator)(nil)
-var _ versionedInterface = (*libraryDecorator)(nil)
+var _ VersionedInterface = (*libraryDecorator)(nil)
 
 func (library *libraryDecorator) getLibNameHelper(baseModuleName string, inVendor bool, inProduct bool) string {
 	name := library.libName
@@ -797,9 +864,9 @@
 	library.baseLinker.linkerInit(ctx)
 	// Let baseLinker know whether this variant is for stubs or not, so that
 	// it can omit things that are not required for linking stubs.
-	library.baseLinker.dynamicProperties.BuildStubs = library.buildStubs()
+	library.baseLinker.dynamicProperties.BuildStubs = library.BuildStubs()
 
-	if library.buildStubs() {
+	if library.BuildStubs() {
 		macroNames := versioningMacroNamesList(ctx.Config())
 		myName := versioningMacroName(ctx.ModuleName())
 		versioningMacroNamesListMutex.Lock()
@@ -958,8 +1025,8 @@
 		moduleInfoJSON.Uninstallable = true
 	}
 
-	if library.buildStubs() && library.stubsVersion() != "" {
-		moduleInfoJSON.SubName += "." + library.stubsVersion()
+	if library.BuildStubs() && library.StubsVersion() != "" {
+		moduleInfoJSON.SubName += "." + library.StubsVersion()
 	}
 
 	// If a library providing a stub is included in an APEX, the private APIs of the library
@@ -970,10 +1037,10 @@
 	// very early stage in the boot process).
 	if len(library.Properties.Stubs.Versions) > 0 && !ctx.Host() && ctx.notInPlatform() &&
 		!ctx.inRamdisk() && !ctx.inVendorRamdisk() && !ctx.inRecovery() && !ctx.useVndk() && !ctx.static() {
-		if library.buildStubs() && library.isLatestStubVersion() {
+		if library.BuildStubs() && library.isLatestStubVersion() {
 			moduleInfoJSON.SubName = ""
 		}
-		if !library.buildStubs() {
+		if !library.BuildStubs() {
 			moduleInfoJSON.SubName = ".bootstrap"
 		}
 	}
@@ -1017,7 +1084,7 @@
 			Objects:                      library.objects,
 			WholeStaticLibsFromPrebuilts: library.wholeStaticLibsFromPrebuilts,
 
-			TransitiveStaticLibrariesForOrdering: android.NewDepSetBuilder[android.Path](android.TOPOLOGICAL).
+			TransitiveStaticLibrariesForOrdering: depset.NewBuilder[android.Path](depset.TOPOLOGICAL).
 				Direct(outputFile).
 				Transitive(deps.TranstiveStaticLibrariesForOrdering).
 				Build(),
@@ -1047,10 +1114,14 @@
 	linkerDeps = append(linkerDeps, flags.LdFlagsDeps...)
 	linkerDeps = append(linkerDeps, ndkSharedLibDeps(ctx)...)
 
+	exportedSymbols := ctx.ExpandOptionalSource(library.Properties.Exported_symbols_list, "exported_symbols_list")
 	unexportedSymbols := ctx.ExpandOptionalSource(library.Properties.Unexported_symbols_list, "unexported_symbols_list")
 	forceNotWeakSymbols := ctx.ExpandOptionalSource(library.Properties.Force_symbols_not_weak_list, "force_symbols_not_weak_list")
 	forceWeakSymbols := ctx.ExpandOptionalSource(library.Properties.Force_symbols_weak_list, "force_symbols_weak_list")
 	if !ctx.Darwin() {
+		if exportedSymbols.Valid() {
+			ctx.PropertyErrorf("exported_symbols_list", "Only supported on Darwin")
+		}
 		if unexportedSymbols.Valid() {
 			ctx.PropertyErrorf("unexported_symbols_list", "Only supported on Darwin")
 		}
@@ -1061,6 +1132,10 @@
 			ctx.PropertyErrorf("force_symbols_weak_list", "Only supported on Darwin")
 		}
 	} else {
+		if exportedSymbols.Valid() {
+			flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,-exported_symbols_list,"+exportedSymbols.String())
+			linkerDeps = append(linkerDeps, exportedSymbols.Path())
+		}
 		if unexportedSymbols.Valid() {
 			flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,-unexported_symbols_list,"+unexportedSymbols.String())
 			linkerDeps = append(linkerDeps, unexportedSymbols.Path())
@@ -1108,7 +1183,7 @@
 
 	stripFlags := flagsToStripFlags(flags)
 	needsStrip := library.stripper.NeedsStrip(ctx)
-	if library.buildStubs() {
+	if library.BuildStubs() {
 		// No need to strip stubs libraries
 		needsStrip = false
 	}
@@ -1162,7 +1237,7 @@
 	linkerDeps = append(linkerDeps, deps.SharedLibsDeps...)
 	linkerDeps = append(linkerDeps, deps.LateSharedLibsDeps...)
 
-	if generatedLib := generateRustStaticlib(ctx, deps.RustRlibDeps); generatedLib != nil && !library.buildStubs() {
+	if generatedLib := GenerateRustStaticlib(ctx, deps.RustRlibDeps); generatedLib != nil && !library.BuildStubs() {
 		if ctx.Module().(*Module).WholeRustStaticlib {
 			deps.WholeStaticLibs = append(deps.WholeStaticLibs, generatedLib)
 		} else {
@@ -1182,8 +1257,8 @@
 	library.coverageOutputFile = transformCoverageFilesToZip(ctx, objs, library.getLibName(ctx))
 	library.linkSAbiDumpFiles(ctx, deps, objs, fileName, unstrippedOutputFile)
 
-	var transitiveStaticLibrariesForOrdering *android.DepSet[android.Path]
-	if static := ctx.GetDirectDepsWithTag(staticVariantTag); len(static) > 0 {
+	var transitiveStaticLibrariesForOrdering depset.DepSet[android.Path]
+	if static := ctx.GetDirectDepsProxyWithTag(staticVariantTag); len(static) > 0 {
 		s, _ := android.OtherModuleProvider(ctx, static[0], StaticLibraryInfoProvider)
 		transitiveStaticLibrariesForOrdering = s.TransitiveStaticLibrariesForOrdering
 	}
@@ -1193,17 +1268,18 @@
 		SharedLibrary:                        unstrippedOutputFile,
 		TransitiveStaticLibrariesForOrdering: transitiveStaticLibrariesForOrdering,
 		Target:                               ctx.Target(),
+		IsStubs:                              library.BuildStubs(),
 	})
 
-	addStubDependencyProviders(ctx)
+	AddStubDependencyProviders(ctx)
 
 	return unstrippedOutputFile
 }
 
 // Visits the stub variants of the library and returns a struct containing the stub .so paths
-func addStubDependencyProviders(ctx ModuleContext) []SharedStubLibrary {
+func AddStubDependencyProviders(ctx android.BaseModuleContext) []SharedStubLibrary {
 	stubsInfo := []SharedStubLibrary{}
-	stubs := ctx.GetDirectDepsWithTag(stubImplDepTag)
+	stubs := ctx.GetDirectDepsProxyWithTag(StubImplDepTag)
 	if len(stubs) > 0 {
 		for _, stub := range stubs {
 			stubInfo, ok := android.OtherModuleProvider(ctx, stub, SharedLibraryInfoProvider)
@@ -1212,8 +1288,11 @@
 				continue
 			}
 			flagInfo, _ := android.OtherModuleProvider(ctx, stub, FlagExporterInfoProvider)
+			if _, ok = android.OtherModuleProvider(ctx, stub, CcInfoProvider); !ok {
+				panic(fmt.Errorf("stub is not a cc module %s", stub))
+			}
 			stubsInfo = append(stubsInfo, SharedStubLibrary{
-				Version:           moduleLibraryInterface(stub).stubsVersion(),
+				Version:           android.OtherModuleProviderOrDefault(ctx, stub, LinkableInfoProvider).StubsVersion,
 				SharedLibraryInfo: stubInfo,
 				FlagExporterInfo:  flagInfo,
 			})
@@ -1221,10 +1300,11 @@
 		if len(stubsInfo) > 0 {
 			android.SetProvider(ctx, SharedLibraryStubsProvider, SharedLibraryStubsInfo{
 				SharedStubLibraries: stubsInfo,
-				IsLLNDK:             ctx.IsLlndk(),
+				IsLLNDK:             ctx.Module().(LinkableInterface).IsLlndk(),
 			})
 		}
 	}
+
 	return stubsInfo
 }
 
@@ -1241,7 +1321,7 @@
 }
 
 func (library *libraryDecorator) nativeCoverage() bool {
-	if library.header() || library.buildStubs() {
+	if library.header() || library.BuildStubs() {
 		return false
 	}
 	return true
@@ -1288,15 +1368,16 @@
 func (library *libraryDecorator) linkLlndkSAbiDumpFiles(ctx ModuleContext,
 	deps PathDeps, sAbiDumpFiles android.Paths, soFile android.Path, libFileName string,
 	excludeSymbolVersions, excludeSymbolTags []string,
-	vendorApiLevel string) android.Path {
-	// NDK symbols in version 34 are LLNDK symbols. Those in version 35 are not.
+	sdkVersionForVendorApiLevel string) android.Path {
+	// Though LLNDK is implemented in system, the callers in vendor cannot include CommonGlobalIncludes,
+	// so commonGlobalIncludes is false.
 	return transformDumpToLinkedDump(ctx,
 		sAbiDumpFiles, soFile, libFileName+".llndk",
 		library.llndkIncludeDirsForAbiCheck(ctx, deps),
 		android.OptionalPathForModuleSrc(ctx, library.Properties.Llndk.Symbol_file),
 		append([]string{"*_PLATFORM", "*_PRIVATE"}, excludeSymbolVersions...),
 		append([]string{"platform-only"}, excludeSymbolTags...),
-		[]string{"llndk=" + vendorApiLevel}, "34", true /* isLlndk */)
+		[]string{"llndk"}, sdkVersionForVendorApiLevel, false /* commonGlobalIncludes */)
 }
 
 func (library *libraryDecorator) linkApexSAbiDumpFiles(ctx ModuleContext,
@@ -1309,7 +1390,7 @@
 		android.OptionalPathForModuleSrc(ctx, library.Properties.Stubs.Symbol_file),
 		append([]string{"*_PLATFORM", "*_PRIVATE"}, excludeSymbolVersions...),
 		append([]string{"platform-only"}, excludeSymbolTags...),
-		[]string{"apex", "systemapi"}, sdkVersion, false /* isLlndk */)
+		[]string{"apex", "systemapi"}, sdkVersion, requiresGlobalIncludes(ctx))
 }
 
 func getRefAbiDumpFile(ctx android.ModuleInstallPathContext,
@@ -1371,6 +1452,11 @@
 		extraFlags = append(extraFlags,
 			"-allow-unreferenced-changes",
 			"-allow-unreferenced-elf-symbol-changes")
+		// The functions in standard libraries are not always declared in the headers.
+		// Allow them to be added or removed without changing the symbols.
+		if isBionic(ctx.ModuleName()) {
+			extraFlags = append(extraFlags, "-allow-adding-removing-referenced-apis")
+		}
 	}
 	if isLlndk {
 		extraFlags = append(extraFlags, "-consider-opaque-types-different")
@@ -1447,7 +1533,7 @@
 			android.OptionalPathForModuleSrc(ctx, library.symbolFileForAbiCheck(ctx)),
 			headerAbiChecker.Exclude_symbol_versions,
 			headerAbiChecker.Exclude_symbol_tags,
-			[]string{} /* includeSymbolTags */, currSdkVersion, false /* isLlndk */)
+			[]string{} /* includeSymbolTags */, currSdkVersion, requiresGlobalIncludes(ctx))
 
 		var llndkDump, apexVariantDump android.Path
 		tags := classifySourceAbiDump(ctx.Module().(*Module))
@@ -1455,14 +1541,19 @@
 		for _, tag := range tags {
 			if tag == llndkLsdumpTag && currVendorVersion != "" {
 				if llndkDump == nil {
+					sdkVersion, err := android.GetSdkVersionForVendorApiLevel(currVendorVersion)
+					if err != nil {
+						ctx.ModuleErrorf("Cannot create %s llndk dump: %s", fileName, err)
+						return
+					}
 					// TODO(b/323447559): Evaluate if replacing sAbiDumpFiles with implDump is faster
 					llndkDump = library.linkLlndkSAbiDumpFiles(ctx,
 						deps, objs.sAbiDumpFiles, soFile, fileName,
 						headerAbiChecker.Exclude_symbol_versions,
 						headerAbiChecker.Exclude_symbol_tags,
-						currVendorVersion)
+						nativeClampedApiLevel(ctx, sdkVersion).String())
 				}
-				addLsdumpPath(string(tag) + ":" + llndkDump.String())
+				addLsdumpPath(ctx.Config(), string(tag)+":"+llndkDump.String())
 			} else if tag == apexLsdumpTag {
 				if apexVariantDump == nil {
 					apexVariantDump = library.linkApexSAbiDumpFiles(ctx,
@@ -1471,12 +1562,12 @@
 						headerAbiChecker.Exclude_symbol_tags,
 						currSdkVersion)
 				}
-				addLsdumpPath(string(tag) + ":" + apexVariantDump.String())
+				addLsdumpPath(ctx.Config(), string(tag)+":"+apexVariantDump.String())
 			} else {
 				if tag.dirName() == "" {
 					optInTags = append(optInTags, tag)
 				}
-				addLsdumpPath(string(tag) + ":" + implDump.String())
+				addLsdumpPath(ctx.Config(), string(tag)+":"+implDump.String())
 			}
 		}
 
@@ -1701,9 +1792,9 @@
 }
 
 func (library *libraryDecorator) exportVersioningMacroIfNeeded(ctx android.BaseModuleContext) {
-	if library.buildStubs() && library.stubsVersion() != "" && !library.skipAPIDefine {
+	if library.BuildStubs() && library.StubsVersion() != "" && !library.skipAPIDefine {
 		name := versioningMacroName(ctx.Module().(*Module).ImplementationModuleName(ctx))
-		apiLevel, err := android.ApiLevelFromUser(ctx, library.stubsVersion())
+		apiLevel, err := android.ApiLevelFromUser(ctx, library.StubsVersion())
 		if err != nil {
 			ctx.ModuleErrorf("Can't export version macro: %s", err.Error())
 		}
@@ -1754,21 +1845,17 @@
 
 func (library *libraryDecorator) install(ctx ModuleContext, file android.Path) {
 	if library.shared() {
-		if library.hasStubsVariants() && !ctx.Host() && ctx.directlyInAnyApex() {
+		translatedArch := ctx.Target().NativeBridge == android.NativeBridgeEnabled
+		if library.HasStubsVariants() && !ctx.Host() && !ctx.isSdkVariant() &&
+			InstallToBootstrap(ctx.baseModuleName(), ctx.Config()) && !library.BuildStubs() &&
+			!translatedArch && !ctx.inRamdisk() && !ctx.inVendorRamdisk() && !ctx.inRecovery() {
 			// Bionic libraries (e.g. libc.so) is installed to the bootstrap subdirectory.
 			// The original path becomes a symlink to the corresponding file in the
 			// runtime APEX.
-			translatedArch := ctx.Target().NativeBridge == android.NativeBridgeEnabled
-			if InstallToBootstrap(ctx.baseModuleName(), ctx.Config()) && !library.buildStubs() &&
-				!translatedArch && !ctx.inRamdisk() && !ctx.inVendorRamdisk() && !ctx.inRecovery() {
-				if ctx.Device() {
-					library.installSymlinkToRuntimeApex(ctx, file)
-				}
-				library.baseInstaller.subDir = "bootstrap"
+			if ctx.Device() {
+				library.installSymlinkToRuntimeApex(ctx, file)
 			}
-		} else if ctx.directlyInAnyApex() && ctx.IsLlndk() && !isBionic(ctx.baseModuleName()) {
-			// Skip installing LLNDK (non-bionic) libraries moved to APEX.
-			ctx.Module().HideFromMake()
+			library.baseInstaller.subDir = "bootstrap"
 		}
 
 		library.baseInstaller.install(ctx, file)
@@ -1777,7 +1864,7 @@
 	if Bool(library.Properties.Static_ndk_lib) && library.static() &&
 		!ctx.InVendorOrProduct() && !ctx.inRamdisk() && !ctx.inVendorRamdisk() && !ctx.inRecovery() && ctx.Device() &&
 		library.baseLinker.sanitize.isUnsanitizedVariant() &&
-		ctx.isForPlatform() && !ctx.isPreventInstall() {
+		CtxIsForPlatform(ctx) && !ctx.isPreventInstall() {
 		installPath := getUnversionedLibraryInstallPath(ctx).Join(ctx, file.Base())
 
 		ctx.ModuleBuild(pctx, android.ModuleBuildParams{
@@ -1797,12 +1884,17 @@
 	return library.shared() || library.static()
 }
 
-// static returns true if this library is for a "static' variant.
+// static returns true if this library is for a "static" variant.
 func (library *libraryDecorator) static() bool {
 	return library.MutatedProperties.VariantIsStatic
 }
 
-// shared returns true if this library is for a "shared' variant.
+// staticLibrary returns true if this library is for a "static"" variant.
+func (library *libraryDecorator) staticLibrary() bool {
+	return library.static()
+}
+
+// shared returns true if this library is for a "shared" variant.
 func (library *libraryDecorator) shared() bool {
 	return library.MutatedProperties.VariantIsShared
 }
@@ -1842,27 +1934,32 @@
 	library.MutatedProperties.BuildStatic = false
 }
 
-// hasLLNDKStubs returns true if this cc_library module has a variant that will build LLNDK stubs.
-func (library *libraryDecorator) hasLLNDKStubs() bool {
+// HasLLNDKStubs returns true if this cc_library module has a variant that will build LLNDK stubs.
+func (library *libraryDecorator) HasLLNDKStubs() bool {
 	return String(library.Properties.Llndk.Symbol_file) != ""
 }
 
 // hasLLNDKStubs returns true if this cc_library module has a variant that will build LLNDK stubs.
-func (library *libraryDecorator) hasLLNDKHeaders() bool {
+func (library *libraryDecorator) HasLLNDKHeaders() bool {
 	return Bool(library.Properties.Llndk.Llndk_headers)
 }
 
-// hasVendorPublicLibrary returns true if this cc_library module has a variant that will build
+// IsLLNDKMovedToApex returns true if this cc_library module sets the llndk.moved_to_apex property.
+func (library *libraryDecorator) IsLLNDKMovedToApex() bool {
+	return Bool(library.Properties.Llndk.Moved_to_apex)
+}
+
+// HasVendorPublicLibrary returns true if this cc_library module has a variant that will build
 // vendor public library stubs.
-func (library *libraryDecorator) hasVendorPublicLibrary() bool {
+func (library *libraryDecorator) HasVendorPublicLibrary() bool {
 	return String(library.Properties.Vendor_public_library.Symbol_file) != ""
 }
 
-func (library *libraryDecorator) implementationModuleName(name string) string {
+func (library *libraryDecorator) ImplementationModuleName(name string) string {
 	return name
 }
 
-func (library *libraryDecorator) buildStubs() bool {
+func (library *libraryDecorator) BuildStubs() bool {
 	return library.MutatedProperties.BuildStubs
 }
 
@@ -1870,7 +1967,7 @@
 	if props := library.getHeaderAbiCheckerProperties(ctx.Module().(*Module)); props.Symbol_file != nil {
 		return props.Symbol_file
 	}
-	if library.hasStubsVariants() && library.Properties.Stubs.Symbol_file != nil {
+	if library.HasStubsVariants() && library.Properties.Stubs.Symbol_file != nil {
 		return library.Properties.Stubs.Symbol_file
 	}
 	// TODO(b/309880485): Distinguish platform, NDK, LLNDK, and APEX version scripts.
@@ -1880,35 +1977,35 @@
 	return nil
 }
 
-func (library *libraryDecorator) hasStubsVariants() bool {
+func (library *libraryDecorator) HasStubsVariants() bool {
 	// Just having stubs.symbol_file is enough to create a stub variant. In that case
 	// the stub for the future API level is created.
 	return library.Properties.Stubs.Symbol_file != nil ||
 		len(library.Properties.Stubs.Versions) > 0
 }
 
-func (library *libraryDecorator) isStubsImplementationRequired() bool {
+func (library *libraryDecorator) IsStubsImplementationRequired() bool {
 	return BoolDefault(library.Properties.Stubs.Implementation_installable, true)
 }
 
-func (library *libraryDecorator) stubsVersions(ctx android.BaseModuleContext) []string {
-	if !library.hasStubsVariants() {
+func (library *libraryDecorator) StubsVersions(ctx android.BaseModuleContext) []string {
+	if !library.HasStubsVariants() {
 		return nil
 	}
 
-	if library.hasLLNDKStubs() && ctx.Module().(*Module).InVendorOrProduct() {
+	if library.HasLLNDKStubs() && ctx.Module().(*Module).InVendorOrProduct() {
 		// LLNDK libraries only need a single stubs variant (""), which is
 		// added automatically in createVersionVariations().
 		return nil
 	}
 
 	// Future API level is implicitly added if there isn't
-	versions := addCurrentVersionIfNotPresent(library.Properties.Stubs.Versions)
-	normalizeVersions(ctx, versions)
+	versions := AddCurrentVersionIfNotPresent(library.Properties.Stubs.Versions)
+	NormalizeVersions(ctx, versions)
 	return versions
 }
 
-func addCurrentVersionIfNotPresent(vers []string) []string {
+func AddCurrentVersionIfNotPresent(vers []string) []string {
 	if inList(android.FutureApiLevel.String(), vers) {
 		return vers
 	}
@@ -1921,24 +2018,24 @@
 	return append(vers, android.FutureApiLevel.String())
 }
 
-func (library *libraryDecorator) setStubsVersion(version string) {
+func (library *libraryDecorator) SetStubsVersion(version string) {
 	library.MutatedProperties.StubsVersion = version
 }
 
-func (library *libraryDecorator) stubsVersion() string {
+func (library *libraryDecorator) StubsVersion() string {
 	return library.MutatedProperties.StubsVersion
 }
 
-func (library *libraryDecorator) setBuildStubs(isLatest bool) {
+func (library *libraryDecorator) SetBuildStubs(isLatest bool) {
 	library.MutatedProperties.BuildStubs = true
 	library.MutatedProperties.IsLatestVersion = isLatest
 }
 
-func (library *libraryDecorator) setAllStubsVersions(versions []string) {
+func (library *libraryDecorator) SetAllStubsVersions(versions []string) {
 	library.MutatedProperties.AllStubsVersions = versions
 }
 
-func (library *libraryDecorator) allStubsVersions() []string {
+func (library *libraryDecorator) AllStubsVersions() []string {
 	return library.MutatedProperties.AllStubsVersions
 }
 
@@ -1946,17 +2043,15 @@
 	return library.MutatedProperties.IsLatestVersion
 }
 
-func (library *libraryDecorator) availableFor(what string) bool {
+func (library *libraryDecorator) apexAvailable() []string {
 	var list []string
 	if library.static() {
 		list = library.StaticProperties.Static.Apex_available
 	} else if library.shared() {
 		list = library.SharedProperties.Shared.Apex_available
 	}
-	if len(list) == 0 {
-		return false
-	}
-	return android.CheckAvailableForApex(what, list)
+
+	return list
 }
 
 func (library *libraryDecorator) installable() *bool {
@@ -1969,7 +2064,7 @@
 }
 
 func (library *libraryDecorator) makeUninstallable(mod *Module) {
-	if library.static() && library.buildStatic() && !library.buildStubs() {
+	if library.static() && library.buildStatic() && !library.BuildStubs() {
 		// If we're asked to make a static library uninstallable we don't do
 		// anything since AndroidMkEntries always sets LOCAL_UNINSTALLABLE_MODULE
 		// for these entries. This is done to still get the make targets for NOTICE
@@ -1983,10 +2078,14 @@
 	return library.path.Partition()
 }
 
-func (library *libraryDecorator) getAPIListCoverageXMLPath() android.ModuleOutPath {
+func (library *libraryDecorator) GetAPIListCoverageXMLPath() android.ModuleOutPath {
 	return library.apiListCoverageXmlPath
 }
 
+func (library *libraryDecorator) setAPIListCoverageXMLPath(xml android.ModuleOutPath) {
+	library.apiListCoverageXmlPath = xml
+}
+
 func (library *libraryDecorator) overriddenModules() []string {
 	return library.Properties.Overrides
 }
@@ -2101,7 +2200,7 @@
 		} else {
 			// Header only
 		}
-	} else if library, ok := ctx.Module().(LinkableInterface); ok && (library.CcLibraryInterface() || library.RustLibraryInterface()) {
+	} else if library, ok := ctx.Module().(LinkableInterface); ok && (library.CcLibraryInterface()) {
 		// Non-cc.Modules may need an empty variant for their mutators.
 		variations := []string{}
 		if library.NonCcVariants() {
@@ -2130,6 +2229,9 @@
 }
 
 func (linkageTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+	if ctx.DepTag() == android.PrebuiltDepTag {
+		return sourceVariation
+	}
 	return ""
 }
 
@@ -2156,7 +2258,7 @@
 		}
 		buildStatic := library.BuildStaticVariant() && !isLLNDK
 		buildShared := library.BuildSharedVariant()
-		if library.BuildRlibVariant() && library.IsRustFFI() && !buildStatic && (incomingVariation == "static" || incomingVariation == "") {
+		if library.BuildRlibVariant() && !buildStatic && (incomingVariation == "static" || incomingVariation == "") {
 			// Rust modules do not build static libs, but rlibs are used as if they
 			// were via `static_libs`. Thus we need to alias the BuildRlibVariant
 			// to "static" for Rust FFI libraries.
@@ -2186,11 +2288,13 @@
 			library.setStatic()
 			if !library.buildStatic() {
 				library.disablePrebuilt()
+				ctx.Module().(*Module).Prebuilt().SetUsePrebuilt(false)
 			}
 		} else if variation == "shared" {
 			library.setShared()
 			if !library.buildShared() {
 				library.disablePrebuilt()
+				ctx.Module().(*Module).Prebuilt().SetUsePrebuilt(false)
 			}
 		}
 	} else if library, ok := ctx.Module().(LinkableInterface); ok && library.CcLibraryInterface() {
@@ -2213,10 +2317,10 @@
 	}
 }
 
-// normalizeVersions modifies `versions` in place, so that each raw version
+// NormalizeVersions modifies `versions` in place, so that each raw version
 // string becomes its normalized canonical form.
 // Validates that the versions in `versions` are specified in least to greatest order.
-func normalizeVersions(ctx android.BaseModuleContext, versions []string) {
+func NormalizeVersions(ctx android.BaseModuleContext, versions []string) {
 	var previous android.ApiLevel
 	for i, v := range versions {
 		ver, err := android.ApiLevelFromUser(ctx, v)
@@ -2233,7 +2337,7 @@
 }
 
 func perApiVersionVariations(mctx android.BaseModuleContext, minSdkVersion string) []string {
-	from, err := nativeApiLevelFromUser(mctx, minSdkVersion)
+	from, err := NativeApiLevelFromUser(mctx, minSdkVersion)
 	if err != nil {
 		mctx.PropertyErrorf("min_sdk_version", err.Error())
 		return []string{""}
@@ -2261,25 +2365,25 @@
 		module.CcLibraryInterface() && module.Shared()
 }
 
-func moduleLibraryInterface(module blueprint.Module) libraryInterface {
-	if m, ok := module.(*Module); ok {
-		return m.library
+func moduleVersionedInterface(module blueprint.Module) VersionedInterface {
+	if m, ok := module.(VersionedLinkableInterface); ok {
+		return m.VersionedInterface()
 	}
 	return nil
 }
 
 // setStubsVersions normalizes the versions in the Stubs.Versions property into MutatedProperties.AllStubsVersions.
-func setStubsVersions(mctx android.BaseModuleContext, library libraryInterface, module *Module) {
-	if !library.buildShared() || !canBeVersionVariant(module) {
+func setStubsVersions(mctx android.BaseModuleContext, module VersionedLinkableInterface) {
+	if !module.BuildSharedVariant() || !canBeVersionVariant(module) {
 		return
 	}
-	versions := library.stubsVersions(mctx)
+	versions := module.VersionedInterface().StubsVersions(mctx)
 	if mctx.Failed() {
 		return
 	}
 	// Set the versions on the pre-mutated module so they can be read by any llndk modules that
 	// depend on the implementation library and haven't been mutated yet.
-	library.setAllStubsVersions(versions)
+	module.VersionedInterface().SetAllStubsVersions(versions)
 }
 
 // versionTransitionMutator splits a module into the mandatory non-stubs variant
@@ -2290,20 +2394,22 @@
 	if ctx.Os() != android.Android {
 		return []string{""}
 	}
-
-	m, ok := ctx.Module().(*Module)
-	if library := moduleLibraryInterface(ctx.Module()); library != nil && canBeVersionVariant(m) {
-		setStubsVersions(ctx, library, m)
-
-		return append(slices.Clone(library.allStubsVersions()), "")
-	} else if ok && m.SplitPerApiLevel() && m.IsSdkVariant() {
-		return perApiVersionVariations(ctx, m.MinSdkVersion())
+	if m, ok := ctx.Module().(VersionedLinkableInterface); ok {
+		if m.CcLibraryInterface() && canBeVersionVariant(m) {
+			setStubsVersions(ctx, m)
+			return append(slices.Clone(m.VersionedInterface().AllStubsVersions()), "")
+		} else if m.SplitPerApiLevel() && m.IsSdkVariant() {
+			return perApiVersionVariations(ctx, m.MinSdkVersion())
+		}
 	}
 
 	return []string{""}
 }
 
 func (versionTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+	if ctx.DepTag() == android.PrebuiltDepTag {
+		return sourceVariation
+	}
 	return ""
 }
 
@@ -2311,11 +2417,11 @@
 	if ctx.Os() != android.Android {
 		return ""
 	}
-	m, ok := ctx.Module().(*Module)
-	if library := moduleLibraryInterface(ctx.Module()); library != nil && canBeVersionVariant(m) {
+	m, ok := ctx.Module().(VersionedLinkableInterface)
+	if library := moduleVersionedInterface(ctx.Module()); library != nil && canBeVersionVariant(m) {
 		if incomingVariation == "latest" {
 			latestVersion := ""
-			versions := library.allStubsVersions()
+			versions := library.AllStubsVersions()
 			if len(versions) > 0 {
 				latestVersion = versions[len(versions)-1]
 			}
@@ -2340,39 +2446,39 @@
 		return
 	}
 
-	m, ok := ctx.Module().(*Module)
-	if library := moduleLibraryInterface(ctx.Module()); library != nil && canBeVersionVariant(m) {
+	m, ok := ctx.Module().(VersionedLinkableInterface)
+	if library := moduleVersionedInterface(ctx.Module()); library != nil && canBeVersionVariant(m) {
 		isLLNDK := m.IsLlndk()
 		isVendorPublicLibrary := m.IsVendorPublicLibrary()
 
 		if variation != "" || isLLNDK || isVendorPublicLibrary {
 			// A stubs or LLNDK stubs variant.
-			if m.sanitize != nil {
-				m.sanitize.Properties.ForceDisable = true
+			if sm, ok := ctx.Module().(PlatformSanitizeable); ok && sm.SanitizePropDefined() {
+				sm.ForceDisableSanitizers()
 			}
-			if m.stl != nil {
-				m.stl.Properties.Stl = StringPtr("none")
-			}
-			m.Properties.PreventInstall = true
-			lib := moduleLibraryInterface(m)
-			allStubsVersions := library.allStubsVersions()
+			m.SetStl("none")
+			m.SetPreventInstall()
+			allStubsVersions := m.VersionedInterface().AllStubsVersions()
 			isLatest := len(allStubsVersions) > 0 && variation == allStubsVersions[len(allStubsVersions)-1]
-			lib.setBuildStubs(isLatest)
+			m.VersionedInterface().SetBuildStubs(isLatest)
 		}
 		if variation != "" {
 			// A non-LLNDK stubs module is hidden from make
-			library.setStubsVersion(variation)
-			m.Properties.HideFromMake = true
+			m.VersionedInterface().SetStubsVersion(variation)
+			m.SetHideFromMake()
 		} else {
 			// A non-LLNDK implementation module has a dependency to all stubs versions
-			for _, version := range library.allStubsVersions() {
-				ctx.AddVariationDependencies([]blueprint.Variation{{"version", version}},
-					stubImplDepTag, ctx.ModuleName())
+			for _, version := range m.VersionedInterface().AllStubsVersions() {
+				ctx.AddVariationDependencies(
+					[]blueprint.Variation{
+						{Mutator: "version", Variation: version},
+						{Mutator: "link", Variation: "shared"}},
+					StubImplDepTag, ctx.ModuleName())
 			}
 		}
 	} else if ok && m.SplitPerApiLevel() && m.IsSdkVariant() {
-		m.Properties.Sdk_version = StringPtr(variation)
-		m.Properties.Min_sdk_version = StringPtr(variation)
+		m.SetSdkVersion(variation)
+		m.SetMinSdkVersion(variation)
 	}
 }
 
@@ -2384,13 +2490,12 @@
 	inject *bool, fileName string) android.ModuleOutPath {
 	// TODO(b/137267623): Remove this in favor of a cc_genrule when they support operating on shared libraries.
 	injectBoringSSLHash := Bool(inject)
-	ctx.VisitDirectDeps(func(dep android.Module) {
+	ctx.VisitDirectDepsProxy(func(dep android.ModuleProxy) {
 		if tag, ok := ctx.OtherModuleDependencyTag(dep).(libraryDependencyTag); ok && tag.static() {
-			if cc, ok := dep.(*Module); ok {
-				if library, ok := cc.linker.(*libraryDecorator); ok {
-					if Bool(library.Properties.Inject_bssl_hash) {
-						injectBoringSSLHash = true
-					}
+			if ccInfo, ok := android.OtherModuleProvider(ctx, dep, CcInfoProvider); ok &&
+				ccInfo.LinkerInfo != nil && ccInfo.LinkerInfo.LibraryDecoratorInfo != nil {
+				if ccInfo.LinkerInfo.LibraryDecoratorInfo.InjectBsslHash {
+					injectBoringSSLHash = true
 				}
 			}
 		}
diff --git a/cc/library_headers_test.go b/cc/library_headers_test.go
index 1924b2f..88ccd43 100644
--- a/cc/library_headers_test.go
+++ b/cc/library_headers_test.go
@@ -41,12 +41,12 @@
 			ctx := testCc(t, fmt.Sprintf(bp, headerModule))
 
 			// test if header search paths are correctly added
-			cc := ctx.ModuleForTests("lib", "android_arm64_armv8-a_static").Rule("cc")
+			cc := ctx.ModuleForTests(t, "lib", "android_arm64_armv8-a_static").Rule("cc")
 			android.AssertStringDoesContain(t, "cFlags for lib module", cc.Args["cFlags"], " -Imy_include ")
 
 			// Test that there's a valid AndroidMk entry.
-			headers := ctx.ModuleForTests("headers", "android_arm64_armv8-a").Module()
-			e := android.AndroidMkEntriesForTest(t, ctx, headers)[0]
+			headers := ctx.ModuleForTests(t, "headers", "android_arm64_armv8-a").Module()
+			e := android.AndroidMkInfoForTest(t, ctx, headers).PrimaryInfo
 
 			// This duplicates the tests done in AndroidMkEntries.write. It would be
 			// better to test its output, but there are no test functions that capture that.
@@ -80,9 +80,9 @@
 	for _, prebuiltPreferred := range []bool{false, true} {
 		t.Run(fmt.Sprintf("prebuilt prefer %t", prebuiltPreferred), func(t *testing.T) {
 			ctx := testCc(t, fmt.Sprintf(bp, prebuiltPreferred))
-			lib := ctx.ModuleForTests("lib", "android_arm64_armv8-a_static")
-			sourceDep := ctx.ModuleForTests("headers", "android_arm64_armv8-a")
-			prebuiltDep := ctx.ModuleForTests("prebuilt_headers", "android_arm64_armv8-a")
+			lib := ctx.ModuleForTests(t, "lib", "android_arm64_armv8-a_static")
+			sourceDep := ctx.ModuleForTests(t, "headers", "android_arm64_armv8-a")
+			prebuiltDep := ctx.ModuleForTests(t, "prebuilt_headers", "android_arm64_armv8-a")
 			hasSourceDep := false
 			hasPrebuiltDep := false
 			ctx.VisitDirectDeps(lib.Module(), func(dep blueprint.Module) {
diff --git a/cc/library_sdk_member.go b/cc/library_sdk_member.go
index af3658d..d1440ea 100644
--- a/cc/library_sdk_member.go
+++ b/cc/library_sdk_member.go
@@ -546,7 +546,7 @@
 		specifiedDeps = ccModule.linker.linkerSpecifiedDeps(ctx.SdkModuleContext(), ccModule, specifiedDeps)
 
 		if lib := ccModule.library; lib != nil {
-			if !lib.hasStubsVariants() {
+			if !lib.HasStubsVariants() {
 				// Propagate dynamic dependencies for implementation libs, but not stubs.
 				p.SharedLibs = specifiedDeps.sharedLibs
 			} else {
@@ -554,8 +554,8 @@
 				// ccModule.StubsVersion()) if the module is versioned. 2. Ensure that all
 				// the versioned stub libs are retained in the prebuilt tree; currently only
 				// the stub corresponding to ccModule.StubsVersion() is.
-				p.StubsVersions = lib.allStubsVersions()
-				if lib.buildStubs() && ccModule.stubsSymbolFilePath() == nil {
+				p.StubsVersions = lib.AllStubsVersions()
+				if lib.BuildStubs() && ccModule.stubsSymbolFilePath() == nil {
 					ctx.ModuleErrorf("Could not determine symbol_file")
 				} else {
 					p.StubsSymbolFilePath = ccModule.stubsSymbolFilePath()
diff --git a/cc/library_test.go b/cc/library_test.go
index 2ed2d76..8b7fed2 100644
--- a/cc/library_test.go
+++ b/cc/library_test.go
@@ -30,8 +30,8 @@
 			srcs: ["foo.c", "baz.o"],
 		}`)
 
-		libfooShared := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Rule("ld")
-		libfooStatic := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_static").Output("libfoo.a")
+		libfooShared := ctx.ModuleForTests(t, "libfoo", "android_arm_armv7-a-neon_shared").Rule("ld")
+		libfooStatic := ctx.ModuleForTests(t, "libfoo", "android_arm_armv7-a-neon_static").Output("libfoo.a")
 
 		if len(libfooShared.Inputs) != 2 {
 			t.Fatalf("unexpected inputs to libfoo shared: %#v", libfooShared.Inputs.Strings())
@@ -59,8 +59,8 @@
 			},
 		}`)
 
-		libfooShared := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Rule("ld")
-		libfooStatic := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_static").Output("libfoo.a")
+		libfooShared := ctx.ModuleForTests(t, "libfoo", "android_arm_armv7-a-neon_shared").Rule("ld")
+		libfooStatic := ctx.ModuleForTests(t, "libfoo", "android_arm_armv7-a-neon_static").Output("libfoo.a")
 
 		if len(libfooShared.Inputs) != 1 {
 			t.Fatalf("unexpected inputs to libfoo shared: %#v", libfooShared.Inputs.Strings())
@@ -85,8 +85,8 @@
 			},
 		}`)
 
-		libfooShared := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Rule("ld")
-		libfooStatic := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_static").Output("libfoo.a")
+		libfooShared := ctx.ModuleForTests(t, "libfoo", "android_arm_armv7-a-neon_shared").Rule("ld")
+		libfooStatic := ctx.ModuleForTests(t, "libfoo", "android_arm_armv7-a-neon_static").Output("libfoo.a")
 
 		if len(libfooShared.Inputs) != 2 {
 			t.Fatalf("unexpected inputs to libfoo shared: %#v", libfooShared.Inputs.Strings())
@@ -111,8 +111,8 @@
 			},
 		}`)
 
-		libfooShared := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Rule("ld")
-		libfooStatic := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_static").Output("libfoo.a")
+		libfooShared := ctx.ModuleForTests(t, "libfoo", "android_arm_armv7-a-neon_shared").Rule("ld")
+		libfooStatic := ctx.ModuleForTests(t, "libfoo", "android_arm_armv7-a-neon_static").Output("libfoo.a")
 
 		if len(libfooShared.Inputs) != 1 {
 			t.Fatalf("unexpected inputs to libfoo shared: %#v", libfooShared.Inputs.Strings())
@@ -137,8 +137,8 @@
 			},
 		}`)
 
-		libfooShared := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Rule("ld")
-		libfooStatic := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_static").Output("libfoo.a")
+		libfooShared := ctx.ModuleForTests(t, "libfoo", "android_arm_armv7-a-neon_shared").Rule("ld")
+		libfooStatic := ctx.ModuleForTests(t, "libfoo", "android_arm_armv7-a-neon_static").Output("libfoo.a")
 
 		if len(libfooShared.Inputs) != 1 {
 			t.Fatalf("unexpected inputs to libfoo shared: %#v", libfooShared.Inputs.Strings())
@@ -168,8 +168,8 @@
 			},
 		}`)
 
-		libfooShared := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Rule("ld")
-		libfooStatic := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_static").Output("libfoo.a")
+		libfooShared := ctx.ModuleForTests(t, "libfoo", "android_arm_armv7-a-neon_shared").Rule("ld")
+		libfooStatic := ctx.ModuleForTests(t, "libfoo", "android_arm_armv7-a-neon_static").Output("libfoo.a")
 
 		if len(libfooShared.Inputs) != 3 {
 			t.Fatalf("unexpected inputs to libfoo shared: %#v", libfooShared.Inputs.Strings())
@@ -183,7 +183,7 @@
 			t.Errorf("static objects not reused for shared library")
 		}
 
-		libfoo := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Module().(*Module)
+		libfoo := ctx.ModuleForTests(t, "libfoo", "android_arm_armv7-a-neon_shared").Module().(*Module)
 		if !inList("-DGOOGLE_PROTOBUF_NO_RTTI", libfoo.flags.Local.CFlags) {
 			t.Errorf("missing protobuf cflags")
 		}
@@ -254,7 +254,7 @@
 			version_script: "foo.map.txt",
 		}`)
 
-	libfoo := result.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Rule("ld")
+	libfoo := result.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_shared").Rule("ld")
 
 	android.AssertStringListContains(t, "missing dependency on version_script",
 		libfoo.Implicits.Strings(), "foo.map.txt")
@@ -272,7 +272,7 @@
 			dynamic_list: "foo.dynamic.txt",
 		}`)
 
-	libfoo := result.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Rule("ld")
+	libfoo := result.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_shared").Rule("ld")
 
 	android.AssertStringListContains(t, "missing dependency on dynamic_list",
 		libfoo.Implicits.Strings(), "foo.dynamic.txt")
@@ -312,14 +312,14 @@
 		}
 	`)
 
-	libdirect := result.ModuleForTests("libdirect", "android_arm64_armv8-a_static").Rule("arWithLibs")
-	libtransitive := result.ModuleForTests("libtransitive", "android_arm64_armv8-a_static").Rule("arWithLibs")
+	libdirect := result.ModuleForTests(t, "libdirect", "android_arm64_armv8-a_static").Rule("arWithLibs")
+	libtransitive := result.ModuleForTests(t, "libtransitive", "android_arm64_armv8-a_static").Rule("arWithLibs")
 
-	libdirectWithSrcs := result.ModuleForTests("libdirect_with_srcs", "android_arm64_armv8-a_static").Rule("arWithLibs")
-	libtransitiveWithSrcs := result.ModuleForTests("libtransitive_with_srcs", "android_arm64_armv8-a_static").Rule("arWithLibs")
+	libdirectWithSrcs := result.ModuleForTests(t, "libdirect_with_srcs", "android_arm64_armv8-a_static").Rule("arWithLibs")
+	libtransitiveWithSrcs := result.ModuleForTests(t, "libtransitive_with_srcs", "android_arm64_armv8-a_static").Rule("arWithLibs")
 
-	barObj := result.ModuleForTests("libdirect_with_srcs", "android_arm64_armv8-a_static").Rule("cc")
-	bazObj := result.ModuleForTests("libtransitive_with_srcs", "android_arm64_armv8-a_static").Rule("cc")
+	barObj := result.ModuleForTests(t, "libdirect_with_srcs", "android_arm64_armv8-a_static").Rule("cc")
+	bazObj := result.ModuleForTests(t, "libtransitive_with_srcs", "android_arm64_armv8-a_static").Rule("cc")
 
 	android.AssertStringListContains(t, "missing dependency on foo.a",
 		libdirect.Inputs.Strings(), "foo.a")
diff --git a/cc/linkable.go b/cc/linkable.go
index 1672366..337b459 100644
--- a/cc/linkable.go
+++ b/cc/linkable.go
@@ -5,6 +5,7 @@
 	"android/soong/fuzz"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/depset"
 )
 
 // PlatformSanitizeable is an interface for sanitizing platform modules.
@@ -52,6 +53,9 @@
 
 	// SanitizableDepTagChecker returns a SantizableDependencyTagChecker function type.
 	SanitizableDepTagChecker() SantizableDependencyTagChecker
+
+	// ForceDisableSanitizers sets the ForceDisable sanitize property
+	ForceDisableSanitizers()
 }
 
 // SantizableDependencyTagChecker functions check whether or not a dependency
@@ -62,6 +66,30 @@
 // implementation should handle tags from both.
 type SantizableDependencyTagChecker func(tag blueprint.DependencyTag) bool
 
+type VersionedLinkableInterface interface {
+	LinkableInterface
+	android.ApexModule
+
+	// VersionedInterface returns the VersionedInterface for this module
+	// (e.g. c.library), or nil if this is module is not a VersionedInterface.
+	VersionedInterface() VersionedInterface
+
+	// HasStubsVariants true if this module is a stub or has a sibling variant
+	// that is a stub.
+	HasStubsVariants() bool
+
+	// SetStl sets the stl property for CC modules. Does not panic if for other module types.
+	SetStl(string)
+	SetSdkVersion(string)
+	SetMinSdkVersion(version string)
+	ApexSdkVersion() android.ApiLevel
+	ImplementationModuleNameForMake(ctx android.BaseModuleContext) string
+
+	// RustApexExclude returns ApexExclude() for Rust modules; always returns false for all non-Rust modules.
+	// TODO(b/362509506): remove this once all apex_exclude uses are switched to stubs.
+	RustApexExclude() bool
+}
+
 // LinkableInterface is an interface for a type of module that is linkable in a C++ library.
 type LinkableInterface interface {
 	android.Module
@@ -101,9 +129,6 @@
 	IsPrebuilt() bool
 	Toc() android.OptionalPath
 
-	// IsRustFFI returns true if this is a Rust FFI library.
-	IsRustFFI() bool
-
 	// IsFuzzModule returns true if this a *_fuzz module.
 	IsFuzzModule() bool
 
@@ -140,18 +165,12 @@
 	// IsLlndk returns true for both LLNDK (public) and LLNDK-private libs.
 	IsLlndk() bool
 
-	// HasLlndkStubs returns true if this library has a variant that will build LLNDK stubs.
-	HasLlndkStubs() bool
-
 	// NeedsLlndkVariants returns true if this module has LLNDK stubs or provides LLNDK headers.
 	NeedsLlndkVariants() bool
 
 	// NeedsVendorPublicLibraryVariants returns true if this module has vendor public library stubs.
 	NeedsVendorPublicLibraryVariants() bool
 
-	//StubsVersion returns the stubs version for this module.
-	StubsVersion() string
-
 	// UseVndk returns true if the module is using VNDK libraries instead of the libraries in /system/lib or /system/lib64.
 	// "product" and "vendor" variant modules return true for this function.
 	// When BOARD_VNDK_VERSION is set, vendor variants of "vendor_available: true", "vendor: true",
@@ -180,6 +199,7 @@
 	MinSdkVersion() string
 	AlwaysSdk() bool
 	IsSdkVariant() bool
+	Multilib() string
 
 	SplitPerApiLevel() bool
 
@@ -248,6 +268,7 @@
 
 	// FuzzModule returns the fuzz.FuzzModule associated with the module.
 	FuzzModuleStruct() fuzz.FuzzModule
+	IsCrt() bool
 }
 
 var (
@@ -316,10 +337,12 @@
 	SharedLibrary android.Path
 	Target        android.Target
 
-	TableOfContents android.OptionalPath
+	TableOfContents    android.OptionalPath
+	IsStubs            bool
+	ImplementationDeps depset.DepSet[string]
 
 	// should be obtained from static analogue
-	TransitiveStaticLibrariesForOrdering *android.DepSet[android.Path]
+	TransitiveStaticLibrariesForOrdering depset.DepSet[android.Path]
 }
 
 var SharedLibraryInfoProvider = blueprint.NewProvider[SharedLibraryInfo]()
@@ -361,7 +384,7 @@
 	// This isn't the actual transitive DepSet, shared library dependencies have been
 	// converted into static library analogues.  It is only used to order the static
 	// library dependencies that were specified for the current module.
-	TransitiveStaticLibrariesForOrdering *android.DepSet[android.Path]
+	TransitiveStaticLibrariesForOrdering depset.DepSet[android.Path]
 }
 
 var StaticLibraryInfoProvider = blueprint.NewProvider[StaticLibraryInfo]()
@@ -385,3 +408,9 @@
 }
 
 var FlagExporterInfoProvider = blueprint.NewProvider[FlagExporterInfo]()
+
+var ImplementationDepInfoProvider = blueprint.NewProvider[*ImplementationDepInfo]()
+
+type ImplementationDepInfo struct {
+	ImplementationDeps depset.DepSet[android.Path]
+}
diff --git a/cc/linker.go b/cc/linker.go
index 1efacad..f854937 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -85,7 +85,7 @@
 
 	// list of header libraries to re-export include directories from. Entries must be
 	// present in header_libs.
-	Export_header_lib_headers []string `android:"arch_variant"`
+	Export_header_lib_headers proptools.Configurable[[]string] `android:"arch_variant,variant_prepend"`
 
 	// list of generated headers to re-export include directories from. Entries must be
 	// present in generated_headers.
@@ -235,13 +235,16 @@
 	// Generate compact dynamic relocation table, default true.
 	Pack_relocations *bool `android:"arch_variant"`
 
-	// local file name to pass to the linker as --version-script
+	// local file name to pass to the linker as --version-script.  Not supported on darwin, and will fail to build
+	// if provided to the darwin variant of a module.
 	Version_script *string `android:"path,arch_variant"`
 
-	// local file name to pass to the linker as --dynamic-list
+	// local file name to pass to the linker as --dynamic-list.  Not supported on darwin, and will fail to build
+	// if provided to the darwin variant of a module.
 	Dynamic_list *string `android:"path,arch_variant"`
 
-	// local files to pass to the linker as --script
+	// local files to pass to the linker as --script.  Not supported on darwin or windows, and will fail to build
+	// if provided to the darwin or windows variant of a module.
 	Linker_scripts []string `android:"path,arch_variant"`
 
 	// list of static libs that should not be used to build this module
@@ -302,7 +305,7 @@
 	deps.SharedLibs = append(deps.SharedLibs, linker.Properties.Shared_libs.GetOrDefault(ctx, nil)...)
 	deps.RuntimeLibs = append(deps.RuntimeLibs, linker.Properties.Runtime_libs...)
 
-	deps.ReexportHeaderLibHeaders = append(deps.ReexportHeaderLibHeaders, linker.Properties.Export_header_lib_headers...)
+	deps.ReexportHeaderLibHeaders = append(deps.ReexportHeaderLibHeaders, linker.Properties.Export_header_lib_headers.GetOrDefault(ctx, nil)...)
 	deps.ReexportStaticLibHeaders = append(deps.ReexportStaticLibHeaders, linker.Properties.Export_static_lib_headers...)
 	deps.ReexportSharedLibHeaders = append(deps.ReexportSharedLibHeaders, linker.Properties.Export_shared_lib_headers...)
 	deps.ReexportGeneratedHeaders = append(deps.ReexportGeneratedHeaders, linker.Properties.Export_generated_headers...)
@@ -454,7 +457,7 @@
 	if ctx.minSdkVersion() == "current" {
 		return true
 	}
-	parsedSdkVersion, err := nativeApiLevelFromUser(ctx, ctx.minSdkVersion())
+	parsedSdkVersion, err := NativeApiLevelFromUser(ctx, ctx.minSdkVersion())
 	if err != nil {
 		ctx.PropertyErrorf("min_sdk_version",
 			"Invalid min_sdk_version value (must be int or current): %q",
@@ -468,16 +471,77 @@
 
 // ModuleContext extends BaseModuleContext
 // BaseModuleContext should know if LLD is used?
-func (linker *baseLinker) linkerFlags(ctx ModuleContext, flags Flags) Flags {
-	toolchain := ctx.toolchain()
-
+func CommonLinkerFlags(ctx android.ModuleContext, flags Flags, useClangLld bool,
+	toolchain config.Toolchain, allow_undefined_symbols bool) Flags {
 	hod := "Host"
 	if ctx.Os().Class == android.Device {
 		hod = "Device"
 	}
 
-	if linker.useClangLld(ctx) {
+	mod, ok := ctx.Module().(LinkableInterface)
+	if !ok {
+		ctx.ModuleErrorf("trying to add CommonLinkerFlags to non-LinkableInterface module.")
+		return flags
+	}
+	if useClangLld {
 		flags.Global.LdFlags = append(flags.Global.LdFlags, fmt.Sprintf("${config.%sGlobalLldflags}", hod))
+	} else {
+		flags.Global.LdFlags = append(flags.Global.LdFlags, fmt.Sprintf("${config.%sGlobalLdflags}", hod))
+	}
+
+	if allow_undefined_symbols {
+		if ctx.Darwin() {
+			// darwin defaults to treating undefined symbols as errors
+			flags.Global.LdFlags = append(flags.Global.LdFlags, "-Wl,-undefined,dynamic_lookup")
+		}
+	} else if !ctx.Darwin() && !ctx.Windows() {
+		flags.Global.LdFlags = append(flags.Global.LdFlags, "-Wl,--no-undefined")
+	}
+
+	if useClangLld {
+		flags.Global.LdFlags = append(flags.Global.LdFlags, toolchain.Lldflags())
+	} else {
+		flags.Global.LdFlags = append(flags.Global.LdFlags, toolchain.Ldflags())
+	}
+
+	if !toolchain.Bionic() && ctx.Os() != android.LinuxMusl {
+		if !ctx.Windows() {
+			// Add -ldl, -lpthread, -lm and -lrt to host builds to match the default behavior of device
+			// builds
+			flags.Global.LdFlags = append(flags.Global.LdFlags,
+				"-ldl",
+				"-lpthread",
+				"-lm",
+			)
+			if !ctx.Darwin() {
+				flags.Global.LdFlags = append(flags.Global.LdFlags, "-lrt")
+			}
+		}
+	}
+	staticLib := mod.CcLibraryInterface() && mod.Static()
+	if ctx.Host() && !ctx.Windows() && !staticLib {
+		flags.Global.LdFlags = append(flags.Global.LdFlags, RpathFlags(ctx)...)
+	}
+
+	flags.Global.LdFlags = append(flags.Global.LdFlags, toolchain.ToolchainLdflags())
+	return flags
+}
+
+func (linker *baseLinker) linkerFlags(ctx ModuleContext, flags Flags) Flags {
+	toolchain := ctx.toolchain()
+	allow_undefined_symbols := Bool(linker.Properties.Allow_undefined_symbols)
+
+	flags = CommonLinkerFlags(ctx, flags, linker.useClangLld(ctx), toolchain,
+		allow_undefined_symbols)
+
+	if !toolchain.Bionic() && ctx.Os() != android.LinuxMusl {
+		CheckBadHostLdlibs(ctx, "host_ldlibs", linker.Properties.Host_ldlibs)
+		flags.Local.LdFlags = append(flags.Local.LdFlags, linker.Properties.Host_ldlibs...)
+	}
+
+	CheckBadLinkerFlags(ctx, "ldflags", linker.Properties.Ldflags)
+
+	if linker.useClangLld(ctx) {
 		if !BoolDefault(linker.Properties.Pack_relocations, packRelocationsDefault) {
 			flags.Global.LdFlags = append(flags.Global.LdFlags, "-Wl,--pack-dyn-relocs=none")
 		} else if ctx.Device() {
@@ -495,53 +559,10 @@
 				flags.Global.LdFlags = append(flags.Global.LdFlags, "-Wl,--pack-dyn-relocs=android")
 			}
 		}
-	} else {
-		flags.Global.LdFlags = append(flags.Global.LdFlags, fmt.Sprintf("${config.%sGlobalLdflags}", hod))
 	}
-	if Bool(linker.Properties.Allow_undefined_symbols) {
-		if ctx.Darwin() {
-			// darwin defaults to treating undefined symbols as errors
-			flags.Global.LdFlags = append(flags.Global.LdFlags, "-Wl,-undefined,dynamic_lookup")
-		}
-	} else if !ctx.Darwin() && !ctx.Windows() {
-		flags.Global.LdFlags = append(flags.Global.LdFlags, "-Wl,--no-undefined")
-	}
-
-	if linker.useClangLld(ctx) {
-		flags.Global.LdFlags = append(flags.Global.LdFlags, toolchain.Lldflags())
-	} else {
-		flags.Global.LdFlags = append(flags.Global.LdFlags, toolchain.Ldflags())
-	}
-
-	if !ctx.toolchain().Bionic() && ctx.Os() != android.LinuxMusl {
-		CheckBadHostLdlibs(ctx, "host_ldlibs", linker.Properties.Host_ldlibs)
-
-		flags.Local.LdFlags = append(flags.Local.LdFlags, linker.Properties.Host_ldlibs...)
-
-		if !ctx.Windows() {
-			// Add -ldl, -lpthread, -lm and -lrt to host builds to match the default behavior of device
-			// builds
-			flags.Global.LdFlags = append(flags.Global.LdFlags,
-				"-ldl",
-				"-lpthread",
-				"-lm",
-			)
-			if !ctx.Darwin() {
-				flags.Global.LdFlags = append(flags.Global.LdFlags, "-lrt")
-			}
-		}
-	}
-
-	CheckBadLinkerFlags(ctx, "ldflags", linker.Properties.Ldflags)
 
 	flags.Local.LdFlags = append(flags.Local.LdFlags, proptools.NinjaAndShellEscapeList(linker.Properties.Ldflags)...)
 
-	if ctx.Host() && !ctx.Windows() && !ctx.static() {
-		flags.Global.LdFlags = append(flags.Global.LdFlags, RpathFlags(ctx)...)
-	}
-
-	flags.Global.LdFlags = append(flags.Global.LdFlags, toolchain.ToolchainLdflags())
-
 	// Version_script is not needed when linking stubs lib where the version
 	// script is created from the symbol map file.
 	if !linker.dynamicProperties.BuildStubs {
@@ -560,7 +581,7 @@
 
 		if versionScript.Valid() {
 			if ctx.Darwin() {
-				ctx.PropertyErrorf("version_script", "Not supported on Darwin")
+				ctx.AddMissingDependencies([]string{"version_script_not_supported_on_darwin"})
 			} else {
 				flags.Local.LdFlags = append(flags.Local.LdFlags,
 					config.VersionScriptFlagPrefix+versionScript.String())
@@ -578,7 +599,7 @@
 		dynamicList := android.OptionalPathForModuleSrc(ctx, linker.Properties.Dynamic_list)
 		if dynamicList.Valid() {
 			if ctx.Darwin() {
-				ctx.PropertyErrorf("dynamic_list", "Not supported on Darwin")
+				ctx.AddMissingDependencies([]string{"dynamic_list_not_supported_on_darwin"})
 			} else {
 				flags.Local.LdFlags = append(flags.Local.LdFlags,
 					"-Wl,--dynamic-list,"+dynamicList.String())
@@ -587,13 +608,17 @@
 		}
 
 		linkerScriptPaths := android.PathsForModuleSrc(ctx, linker.Properties.Linker_scripts)
-		if len(linkerScriptPaths) > 0 && (ctx.Darwin() || ctx.Windows()) {
-			ctx.PropertyErrorf("linker_scripts", "Only supported for ELF files")
-		} else {
-			for _, linkerScriptPath := range linkerScriptPaths {
-				flags.Local.LdFlags = append(flags.Local.LdFlags,
-					"-Wl,--script,"+linkerScriptPath.String())
-				flags.LdFlagsDeps = append(flags.LdFlagsDeps, linkerScriptPath)
+		if len(linkerScriptPaths) > 0 {
+			if ctx.Darwin() {
+				ctx.AddMissingDependencies([]string{"linker_scripts_not_supported_on_darwin"})
+			} else if ctx.Windows() {
+				ctx.PropertyErrorf("linker_scripts", "Only supported for ELF files")
+			} else {
+				for _, linkerScriptPath := range linkerScriptPaths {
+					flags.Local.LdFlags = append(flags.Local.LdFlags,
+						"-Wl,--script,"+linkerScriptPath.String())
+					flags.LdFlagsDeps = append(flags.LdFlagsDeps, linkerScriptPath)
+				}
 			}
 		}
 	}
diff --git a/cc/llndk_library.go b/cc/llndk_library.go
index c7950f9..5586576 100644
--- a/cc/llndk_library.go
+++ b/cc/llndk_library.go
@@ -57,28 +57,58 @@
 	// if true, make this module available to provide headers to other modules that set
 	// llndk.symbol_file.
 	Llndk_headers *bool
+
+	// moved_to_apex marks this module has having been distributed through an apex module.
+	Moved_to_apex *bool
 }
 
 func makeLlndkVars(ctx android.MakeVarsContext) {
-	// Make uses LLNDK_MOVED_TO_APEX_LIBRARIES to avoid installing libraries on /system if
-	// they been moved to an apex.
-	movedToApexLlndkLibraries := make(map[string]bool)
-	ctx.VisitAllModules(func(module android.Module) {
-		if library := moduleLibraryInterface(module); library != nil && library.hasLLNDKStubs() {
-			// Skip bionic libs, they are handled in different manner
-			name := library.implementationModuleName(module.(*Module).BaseModuleName())
-			if module.(android.ApexModule).DirectlyInAnyApex() && !isBionic(name) {
-				movedToApexLlndkLibraries[name] = true
-			}
-		}
-	})
 
-	ctx.Strict("LLNDK_MOVED_TO_APEX_LIBRARIES",
-		strings.Join(android.SortedKeys(movedToApexLlndkLibraries), " "))
 }
 
 func init() {
 	RegisterLlndkLibraryTxtType(android.InitRegistrationContext)
+	android.RegisterParallelSingletonType("movedToApexLlndkLibraries", movedToApexLlndkLibrariesFactory)
+}
+
+func movedToApexLlndkLibrariesFactory() android.Singleton {
+	return &movedToApexLlndkLibraries{}
+}
+
+type movedToApexLlndkLibraries struct {
+	movedToApexLlndkLibraries []string
+}
+
+func (s *movedToApexLlndkLibraries) GenerateBuildActions(ctx android.SingletonContext) {
+	// Make uses LLNDK_MOVED_TO_APEX_LIBRARIES to generate the linker config.
+	movedToApexLlndkLibrariesMap := make(map[string]bool)
+	ctx.VisitAllModules(func(module android.Module) {
+		if library := moduleVersionedInterface(module); library != nil && library.HasLLNDKStubs() {
+			if library.IsLLNDKMovedToApex() {
+				name := library.ImplementationModuleName(module.(*Module).BaseModuleName())
+				movedToApexLlndkLibrariesMap[name] = true
+			}
+		}
+	})
+	s.movedToApexLlndkLibraries = android.SortedKeys(movedToApexLlndkLibrariesMap)
+
+	var sb strings.Builder
+	for i, l := range s.movedToApexLlndkLibraries {
+		if i > 0 {
+			sb.WriteRune(' ')
+		}
+		sb.WriteString(l)
+		sb.WriteString(".so")
+	}
+	android.WriteFileRule(ctx, MovedToApexLlndkLibrariesFile(ctx), sb.String())
+}
+
+func MovedToApexLlndkLibrariesFile(ctx android.PathContext) android.WritablePath {
+	return android.PathForIntermediates(ctx, "moved_to_apex_llndk_libraries.txt")
+}
+
+func (s *movedToApexLlndkLibraries) MakeVars(ctx android.MakeVarsContext) {
+	ctx.Strict("LLNDK_MOVED_TO_APEX_LIBRARIES", strings.Join(s.movedToApexLlndkLibraries, " "))
 }
 
 func RegisterLlndkLibraryTxtType(ctx android.RegistrationContext) {
@@ -193,10 +223,10 @@
 	lib, isLib := m.linker.(*libraryDecorator)
 	prebuiltLib, isPrebuiltLib := m.linker.(*prebuiltLibraryLinker)
 
-	if m.InVendorOrProduct() && isLib && lib.hasLLNDKStubs() {
+	if m.InVendorOrProduct() && isLib && lib.HasLLNDKStubs() {
 		m.VendorProperties.IsLLNDK = true
 	}
-	if m.InVendorOrProduct() && isPrebuiltLib && prebuiltLib.hasLLNDKStubs() {
+	if m.InVendorOrProduct() && isPrebuiltLib && prebuiltLib.HasLLNDKStubs() {
 		m.VendorProperties.IsLLNDK = true
 	}
 
diff --git a/cc/lto.go b/cc/lto.go
index f3af7d2..4444152 100644
--- a/cc/lto.go
+++ b/cc/lto.go
@@ -110,7 +110,7 @@
 		var ltoLdFlags []string
 
 		// Do not perform costly LTO optimizations for Eng builds.
-		if Bool(lto.Properties.Lto_O0) || ctx.optimizeForSize() || ctx.Config().Eng() {
+		if Bool(lto.Properties.Lto_O0) || ctx.Config().Eng() {
 			ltoLdFlags = append(ltoLdFlags, "-Wl,--lto-O0")
 		}
 
diff --git a/cc/lto_test.go b/cc/lto_test.go
index e4b5a3a..3fb1f3c 100644
--- a/cc/lto_test.go
+++ b/cc/lto_test.go
@@ -70,24 +70,24 @@
 
 	result := LTOPreparer.RunTestWithBp(t, bp)
 
-	libLto := result.ModuleForTests("lto_enabled", "android_arm64_armv8-a_shared").Module()
+	libLto := result.ModuleForTests(t, "lto_enabled", "android_arm64_armv8-a_shared").Module()
 
-	libFoo := result.ModuleForTests("foo", "android_arm64_armv8-a_static").Module()
+	libFoo := result.ModuleForTests(t, "foo", "android_arm64_armv8-a_static").Module()
 	if !hasDep(result, libLto, libFoo) {
 		t.Errorf("'lto_enabled' missing dependency on the default variant of 'foo'")
 	}
 
-	libBaz := result.ModuleForTests("baz", "android_arm64_armv8-a_static").Module()
+	libBaz := result.ModuleForTests(t, "baz", "android_arm64_armv8-a_static").Module()
 	if !hasDep(result, libFoo, libBaz) {
 		t.Errorf("'foo' missing dependency on the default variant of transitive dep 'baz'")
 	}
 
-	libNeverLto := result.ModuleForTests("lib_never_lto", "android_arm64_armv8-a_static").Module()
+	libNeverLto := result.ModuleForTests(t, "lib_never_lto", "android_arm64_armv8-a_static").Module()
 	if !hasDep(result, libLto, libNeverLto) {
 		t.Errorf("'lto_enabled' missing dependency on the default variant of 'lib_never_lto'")
 	}
 
-	libBar := result.ModuleForTests("bar", "android_arm64_armv8-a_shared").Module()
+	libBar := result.ModuleForTests(t, "bar", "android_arm64_armv8-a_shared").Module()
 	if !hasDep(result, libLto, libBar) {
 		t.Errorf("'lto_enabled' missing dependency on the default variant of 'bar'")
 	}
@@ -138,15 +138,15 @@
 
 	result := LTOPreparer.RunTestWithBp(t, bp)
 
-	libRoot := result.ModuleForTests("root", "android_arm64_armv8-a_shared").Module()
-	libRootLtoNever := result.ModuleForTests("root_no_lto", "android_arm64_armv8-a_shared").Module()
+	libRoot := result.ModuleForTests(t, "root", "android_arm64_armv8-a_shared").Module()
+	libRootLtoNever := result.ModuleForTests(t, "root_no_lto", "android_arm64_armv8-a_shared").Module()
 
-	libFoo := result.ModuleForTests("foo", "android_arm64_armv8-a_static")
+	libFoo := result.ModuleForTests(t, "foo", "android_arm64_armv8-a_static")
 	if !hasDep(result, libRoot, libFoo.Module()) {
 		t.Errorf("'root' missing dependency on the default variant of 'foo'")
 	}
 
-	libFooNoLto := result.ModuleForTests("foo", "android_arm64_armv8-a_static_lto-none")
+	libFooNoLto := result.ModuleForTests(t, "foo", "android_arm64_armv8-a_static_lto-none")
 	if !hasDep(result, libRootLtoNever, libFooNoLto.Module()) {
 		t.Errorf("'root_no_lto' missing dependency on the lto_none variant of 'foo'")
 	}
@@ -156,7 +156,7 @@
 		t.Errorf("'foo' expected to have flags %q, but got %q", w, libFooCFlags)
 	}
 
-	libBaz := result.ModuleForTests("baz", "android_arm64_armv8-a_static")
+	libBaz := result.ModuleForTests(t, "baz", "android_arm64_armv8-a_static")
 	if !hasDep(result, libFoo.Module(), libBaz.Module()) {
 		t.Errorf("'foo' missing dependency on the default variant of transitive dep 'baz'")
 	}
@@ -187,8 +187,8 @@
 	}`
 	result := LTOPreparer.RunTestWithBp(t, bp)
 
-	libFooWithLto := result.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Rule("ld")
-	libFooWithoutLto := result.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Rule("ld")
+	libFooWithLto := result.ModuleForTests(t, "libfoo", "android_arm_armv7-a-neon_shared").Rule("ld")
+	libFooWithoutLto := result.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_shared").Rule("ld")
 
 	android.AssertStringDoesContain(t, "missing flag for LTO in variant that expects it",
 		libFooWithLto.Args["ldFlags"], "-flto=thin")
@@ -215,8 +215,8 @@
 
 	result := LTOPreparer.RunTestWithBp(t, bp)
 
-	libFoo := result.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Rule("ld")
-	libBar := result.ModuleForTests("runtime_libbar", "android_arm_armv7-a-neon_shared").Rule("ld")
+	libFoo := result.ModuleForTests(t, "libfoo", "android_arm_armv7-a-neon_shared").Rule("ld")
+	libBar := result.ModuleForTests(t, "runtime_libbar", "android_arm_armv7-a-neon_shared").Rule("ld")
 
 	android.AssertStringDoesContain(t, "missing flag for LTO in LTO enabled library",
 		libFoo.Args["ldFlags"], "-flto=thin")
diff --git a/cc/makevars.go b/cc/makevars.go
index c9352a4..c945102 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,27 +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, " "))
@@ -178,19 +175,19 @@
 	sort.Strings(ndkKnownLibs)
 	ctx.Strict("NDK_KNOWN_LIBS", strings.Join(ndkKnownLibs, " "))
 
-	hostTargets := ctx.Config().Targets[ctx.Config().BuildOS]
-	makeVarsToolchain(ctx, "", hostTargets[0])
-	if len(hostTargets) > 1 {
-		makeVarsToolchain(ctx, "2ND_", hostTargets[1])
+	if hostTargets := ctx.Config().Targets[ctx.Config().BuildOS]; len(hostTargets) > 0 {
+		makeVarsToolchain(ctx, "", hostTargets[0])
+		if len(hostTargets) > 1 {
+			makeVarsToolchain(ctx, "2ND_", hostTargets[1])
+		}
 	}
 
-	deviceTargets := ctx.Config().Targets[android.Android]
-	makeVarsToolchain(ctx, "", deviceTargets[0])
-	if len(deviceTargets) > 1 {
-		makeVarsToolchain(ctx, "2ND_", deviceTargets[1])
+	if deviceTargets := ctx.Config().Targets[android.Android]; len(deviceTargets) > 0 {
+		makeVarsToolchain(ctx, "", deviceTargets[0])
+		if len(deviceTargets) > 1 {
+			makeVarsToolchain(ctx, "2ND_", deviceTargets[1])
+		}
 	}
-
-	makeLlndkVars(ctx)
 }
 
 func makeVarsToolchain(ctx android.MakeVarsContext, secondPrefix string,
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index 01551ab..1f0fc07 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -133,22 +133,20 @@
 	unversionedUntil android.ApiLevel
 }
 
-var _ versionedInterface = (*stubDecorator)(nil)
+var _ VersionedInterface = (*stubDecorator)(nil)
 
 func shouldUseVersionScript(ctx BaseModuleContext, stub *stubDecorator) bool {
 	return stub.apiLevel.GreaterThanOrEqualTo(stub.unversionedUntil)
 }
 
-func (stub *stubDecorator) implementationModuleName(name string) string {
+func (stub *stubDecorator) ImplementationModuleName(name string) string {
 	return strings.TrimSuffix(name, ndkLibrarySuffix)
 }
 
 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())
 		}
 	}
@@ -157,7 +155,7 @@
 	return versionStrs
 }
 
-func (this *stubDecorator) stubsVersions(ctx android.BaseModuleContext) []string {
+func (this *stubDecorator) StubsVersions(ctx android.BaseModuleContext) []string {
 	if !ctx.Module().Enabled(ctx) {
 		return nil
 	}
@@ -165,7 +163,7 @@
 		ctx.Module().Disable()
 		return nil
 	}
-	firstVersion, err := nativeApiLevelFromUser(ctx,
+	firstVersion, err := NativeApiLevelFromUser(ctx,
 		String(this.properties.First_version))
 	if err != nil {
 		ctx.PropertyErrorf("first_version", err.Error())
@@ -175,10 +173,10 @@
 }
 
 func (this *stubDecorator) initializeProperties(ctx BaseModuleContext) bool {
-	this.apiLevel = nativeApiLevelOrPanic(ctx, this.stubsVersion())
+	this.apiLevel = nativeApiLevelOrPanic(ctx, this.StubsVersion())
 
 	var err error
-	this.firstVersion, err = nativeApiLevelFromUser(ctx,
+	this.firstVersion, err = NativeApiLevelFromUser(ctx,
 		String(this.properties.First_version))
 	if err != nil {
 		ctx.PropertyErrorf("first_version", err.Error())
@@ -186,7 +184,7 @@
 	}
 
 	str := proptools.StringDefault(this.properties.Unversioned_until, "minimum")
-	this.unversionedUntil, err = nativeApiLevelFromUser(ctx, str)
+	this.unversionedUntil, err = NativeApiLevelFromUser(ctx, str)
 	if err != nil {
 		ctx.PropertyErrorf("unversioned_until", err.Error())
 		return false
@@ -238,7 +236,7 @@
 	pctx.StaticVariable("StubLibraryCompilerFlags", strings.Join(stubLibraryCompilerFlags, " "))
 }
 
-func addStubLibraryCompilerFlags(flags Flags) Flags {
+func AddStubLibraryCompilerFlags(flags Flags) Flags {
 	flags.Global.CFlags = append(flags.Global.CFlags, stubLibraryCompilerFlags...)
 	// All symbols in the stubs library should be visible.
 	if inList("-fvisibility=hidden", flags.Local.CFlags) {
@@ -249,17 +247,17 @@
 
 func (stub *stubDecorator) compilerFlags(ctx ModuleContext, flags Flags, deps PathDeps) Flags {
 	flags = stub.baseCompiler.compilerFlags(ctx, flags, deps)
-	return addStubLibraryCompilerFlags(flags)
+	return AddStubLibraryCompilerFlags(flags)
 }
 
-type ndkApiOutputs struct {
-	stubSrc       android.ModuleGenPath
-	versionScript android.ModuleGenPath
+type NdkApiOutputs struct {
+	StubSrc       android.ModuleGenPath
+	VersionScript android.ModuleGenPath
 	symbolList    android.ModuleGenPath
 }
 
-func parseNativeAbiDefinition(ctx ModuleContext, symbolFile string,
-	apiLevel android.ApiLevel, genstubFlags string) ndkApiOutputs {
+func ParseNativeAbiDefinition(ctx android.ModuleContext, symbolFile string,
+	apiLevel android.ApiLevel, genstubFlags string) NdkApiOutputs {
 
 	stubSrcPath := android.PathForModuleGen(ctx, "stub.c")
 	versionScriptPath := android.PathForModuleGen(ctx, "stub.map")
@@ -281,37 +279,36 @@
 		},
 	})
 
-	return ndkApiOutputs{
-		stubSrc:       stubSrcPath,
-		versionScript: versionScriptPath,
+	return NdkApiOutputs{
+		StubSrc:       stubSrcPath,
+		VersionScript: versionScriptPath,
 		symbolList:    symbolListPath,
 	}
 }
 
-func compileStubLibrary(ctx ModuleContext, flags Flags, src android.Path) Objects {
+func CompileStubLibrary(ctx android.ModuleContext, flags Flags, src android.Path, sharedFlags *SharedFlags) Objects {
 	// libc/libm stubs libraries end up mismatching with clang's internal definition of these
 	// functions (which have noreturn attributes and other things). Because we just want to create a
 	// stub with symbol definitions, and types aren't important in C, ignore the mismatch.
 	flags.Local.ConlyFlags = append(flags.Local.ConlyFlags, "-fno-builtin")
 	return compileObjs(ctx, flagsToBuilderFlags(flags), "",
-		android.Paths{src}, nil, nil, nil, nil)
+		android.Paths{src}, nil, nil, nil, nil, sharedFlags)
 }
 
 func (this *stubDecorator) findImplementationLibrary(ctx ModuleContext) android.Path {
-	dep := ctx.GetDirectDepWithTag(strings.TrimSuffix(ctx.ModuleName(), ndkLibrarySuffix),
+	dep := ctx.GetDirectDepProxyWithTag(strings.TrimSuffix(ctx.ModuleName(), ndkLibrarySuffix),
 		stubImplementation)
 	if dep == nil {
-		ctx.ModuleErrorf("Could not find implementation for stub")
+		ctx.ModuleErrorf("Could not find implementation for stub: ")
 		return nil
 	}
-	impl, ok := dep.(*Module)
-	if !ok {
+	if _, ok := android.OtherModuleProvider(ctx, *dep, CcInfoProvider); !ok {
 		ctx.ModuleErrorf("Implementation for stub is not correct module type")
 		return nil
 	}
-	output := impl.UnstrippedOutputFile()
+	output := android.OtherModuleProviderOrDefault(ctx, *dep, LinkableInfoProvider).UnstrippedOutputFile
 	if output == nil {
-		ctx.ModuleErrorf("implementation module (%s) has no output", impl)
+		ctx.ModuleErrorf("implementation module (%s) has no output", *dep)
 		return nil
 	}
 
@@ -330,6 +327,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 +348,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 +385,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 +438,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)
 	}
 }
 
@@ -476,7 +489,7 @@
 		ctx.PropertyErrorf("symbol_file", "must end with .map.txt")
 	}
 
-	if !c.buildStubs() {
+	if !c.BuildStubs() {
 		// NDK libraries have no implementation variant, nothing to do
 		return Objects{}
 	}
@@ -487,17 +500,17 @@
 	}
 
 	symbolFile := String(c.properties.Symbol_file)
-	nativeAbiResult := parseNativeAbiDefinition(ctx, symbolFile, c.apiLevel, "")
-	objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc)
-	c.versionScriptPath = nativeAbiResult.versionScript
+	nativeAbiResult := ParseNativeAbiDefinition(ctx, symbolFile, c.apiLevel, "")
+	objs := CompileStubLibrary(ctx, flags, nativeAbiResult.StubSrc, ctx.getSharedFlags())
+	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)
 		}
 	}
 	if c.apiLevel.IsCurrent() && ctx.PrimaryArch() {
-		c.parsedCoverageXmlPath = parseSymbolFileForAPICoverage(ctx, symbolFile)
+		c.parsedCoverageXmlPath = ParseSymbolFileForAPICoverage(ctx, symbolFile)
 	}
 	return objs
 }
@@ -528,7 +541,7 @@
 func (stub *stubDecorator) link(ctx ModuleContext, flags Flags, deps PathDeps,
 	objs Objects) android.Path {
 
-	if !stub.buildStubs() {
+	if !stub.BuildStubs() {
 		// NDK libraries have no implementation variant, nothing to do
 		return nil
 	}
diff --git a/cc/ndk_sysroot.go b/cc/ndk_sysroot.go
index 92da172..a5f014b 100644
--- a/cc/ndk_sysroot.go
+++ b/cc/ndk_sysroot.go
@@ -245,7 +245,7 @@
 		}
 
 		if m, ok := module.(*Module); ok {
-			if installer, ok := m.installer.(*stubDecorator); ok && m.library.buildStubs() {
+			if installer, ok := m.installer.(*stubDecorator); ok && m.library.BuildStubs() {
 				installPaths = append(installPaths, installer.installPath)
 			}
 
diff --git a/cc/ndk_test.go b/cc/ndk_test.go
index f20d3c6..8574bf1 100644
--- a/cc/ndk_test.go
+++ b/cc/ndk_test.go
@@ -50,7 +50,7 @@
 	}
 	`
 	ctx := prepareForCcTest.RunTestWithBp(t, bp)
-	libfoo := ctx.ModuleForTests("libfoo.ndk", "android_arm64_armv8-a_sdk_shared")
-	libfoo_headers := ctx.ModuleForTests("libfoo_headers", "")
+	libfoo := ctx.ModuleForTests(t, "libfoo.ndk", "android_arm64_armv8-a_sdk_shared")
+	libfoo_headers := ctx.ModuleForTests(t, "libfoo_headers", "")
 	android.AssertBoolEquals(t, "Could not find headers of ndk_library", true, isDep(ctx, libfoo.Module(), libfoo_headers.Module()))
 }
diff --git a/cc/ndkstubgen/test_ndkstubgen.py b/cc/ndkstubgen/test_ndkstubgen.py
index 22f31d9..6c24b4f 100755
--- a/cc/ndkstubgen/test_ndkstubgen.py
+++ b/cc/ndkstubgen/test_ndkstubgen.py
@@ -473,16 +473,17 @@
             VERSION_35 { # introduced=35
                 global:
                     wiggle;
-                    waggle;
-                    waggle; # llndk=202404
-                    bubble; # llndk=202404
-                    duddle;
-                    duddle; # llndk=202504
+                    waggle; # llndk
             } VERSION_34;
+            VERSION_36 { # introduced=36
+                global:
+                    abc;
+                    xyz; # llndk
+            } VERSION_35;
         """))
         f = copy(self.filter)
         f.llndk = True
-        f.api = 202404
+        f.api = 35
         parser = symbolfile.SymbolFileParser(input_file, {}, f)
         versions = parser.parse()
 
@@ -497,8 +498,8 @@
         expected_src = textwrap.dedent("""\
             void foo() {}
             void bar() {}
+            void wiggle() {}
             void waggle() {}
-            void bubble() {}
         """)
         self.assertEqual(expected_src, src_file.getvalue())
 
@@ -510,8 +511,8 @@
             };
             VERSION_35 {
                 global:
+                    wiggle;
                     waggle;
-                    bubble;
             } VERSION_34;
         """)
         self.assertEqual(expected_version, version_file.getvalue())
@@ -521,15 +522,15 @@
             LIBANDROID {
                 global:
                     foo; # introduced=34
-                    bar; # introduced=35
-                    bar; # llndk=202404
-                    baz; # introduced=35
+                    bar; # introduced=35 llndk
+                    baz; # introduced=V
+                    qux; # introduced=36
             };
         """))
         f = copy(self.filter)
         f.llndk = True
-        f.api = 202404
-        parser = symbolfile.SymbolFileParser(input_file, {}, f)
+        f.api = 35
+        parser = symbolfile.SymbolFileParser(input_file, {'V': 35}, f)
         versions = parser.parse()
 
         src_file = io.StringIO()
@@ -543,6 +544,7 @@
         expected_src = textwrap.dedent("""\
             void foo() {}
             void bar() {}
+            void baz() {}
         """)
         self.assertEqual(expected_src, src_file.getvalue())
 
@@ -551,6 +553,7 @@
                 global:
                     foo;
                     bar;
+                    baz;
             };
         """)
         self.assertEqual(expected_version, version_file.getvalue())
diff --git a/cc/object.go b/cc/object.go
index c89520a..bbfca94 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -159,7 +159,7 @@
 	// isForPlatform is terribly named and actually means isNotApex.
 	if Bool(object.Properties.Crt) &&
 		!Bool(object.Properties.Exclude_from_ndk_sysroot) && ctx.useSdk() &&
-		ctx.isSdkVariant() && ctx.isForPlatform() {
+		ctx.isSdkVariant() && CtxIsForPlatform(ctx) {
 
 		output = getVersionedLibraryInstallPath(ctx,
 			nativeApiLevelOrPanic(ctx, ctx.sdkVersion())).Join(ctx, outputName)
diff --git a/cc/object_test.go b/cc/object_test.go
index 004dfd3..6d82265 100644
--- a/cc/object_test.go
+++ b/cc/object_test.go
@@ -46,13 +46,13 @@
 
 	ctx := prepareForCcTest.RunTestWithBp(t, bp)
 	for _, v := range variants {
-		cflags := ctx.ModuleForTests("crt_foo", v.variant).Rule("cc").Args["cFlags"]
+		cflags := ctx.ModuleForTests(t, "crt_foo", v.variant).Rule("cc").Args["cFlags"]
 		expected := "-target aarch64-linux-android" + v.num + " "
 		android.AssertStringDoesContain(t, "cflag", cflags, expected)
 	}
 	ctx = prepareForCcTest.RunTestWithBp(t, bp)
 	android.AssertStringDoesContain(t, "cflag",
-		ctx.ModuleForTests("crt_foo", "android_vendor_arm64_armv8-a").Rule("cc").Args["cFlags"],
+		ctx.ModuleForTests(t, "crt_foo", "android_vendor_arm64_armv8-a").Rule("cc").Args["cFlags"],
 		"-target aarch64-linux-android10000 ")
 }
 
@@ -69,13 +69,13 @@
 
 	// Sdk variant uses the crt object of the matching min_sdk_version
 	variant := "android_arm64_armv8-a_sdk"
-	crt := ctx.ModuleForTests("bin", variant).Rule("ld").Args["crtBegin"]
+	crt := ctx.ModuleForTests(t, "bin", variant).Rule("ld").Args["crtBegin"]
 	android.AssertStringDoesContain(t, "crt dep of sdk variant", crt,
 		"29/crtbegin_dynamic.o")
 
 	// platform variant uses the crt object built for platform
 	variant = "android_arm64_armv8-a"
-	crt = ctx.ModuleForTests("bin", variant).Rule("ld").Args["crtBegin"]
+	crt = ctx.ModuleForTests(t, "bin", variant).Rule("ld").Args["crtBegin"]
 	android.AssertStringDoesContain(t, "crt dep of platform variant", crt,
 		variant+"/crtbegin_dynamic.o")
 }
@@ -147,7 +147,7 @@
 			ctx := PrepareForIntegrationTestWithCc.RunTestWithBp(t, bp)
 			android.AssertPathRelativeToTopEquals(t, "expected output file foo.o",
 				fmt.Sprintf("out/soong/.intermediates/%s/android_arm64_armv8-a/foo.o", testcase.moduleName),
-				ctx.ModuleForTests(testcase.moduleName, "android_arm64_armv8-a").Output("foo.o").Output)
+				ctx.ModuleForTests(t, testcase.moduleName, "android_arm64_armv8-a").Output("foo.o").Output)
 		})
 	}
 
diff --git a/cc/orderfile.go b/cc/orderfile.go
index 38b8905..c07a098 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{}
 }
@@ -157,7 +153,7 @@
 	}
 
 	// Currently, we are not enabling orderfiles to begin from static libraries
-	if ctx.static() && !ctx.staticBinary() {
+	if ctx.staticLibrary() {
 		return
 	}
 
diff --git a/cc/orderfile_test.go b/cc/orderfile_test.go
index 3486f96..41253ad 100644
--- a/cc/orderfile_test.go
+++ b/cc/orderfile_test.go
@@ -41,7 +41,7 @@
 
 	expectedCFlag := "-forder-file-instrumentation"
 
-	libTest := result.ModuleForTests("libTest", "android_arm64_armv8-a_shared")
+	libTest := result.ModuleForTests(t, "libTest", "android_arm64_armv8-a_shared")
 
 	// Check cFlags of orderfile-enabled module
 	cFlags := libTest.Rule("cc").Args["cFlags"]
@@ -77,7 +77,7 @@
 
 	expectedCFlag := "-Wl,--symbol-ordering-file=toolchain/pgo-profiles/orderfiles/libTest.orderfile"
 
-	libTest := result.ModuleForTests("libTest", "android_arm64_armv8-a_shared")
+	libTest := result.ModuleForTests(t, "libTest", "android_arm64_armv8-a_shared")
 
 	// Check ldFlags of orderfile-enabled module
 	ldFlags := libTest.Rule("ld").Args["ldFlags"]
@@ -106,7 +106,7 @@
 
 	expectedCFlag := "-forder-file-instrumentation"
 
-	test := result.ModuleForTests("test", "android_arm64_armv8-a")
+	test := result.ModuleForTests(t, "test", "android_arm64_armv8-a")
 
 	// Check cFlags of orderfile-enabled module
 	cFlags := test.Rule("cc").Args["cFlags"]
@@ -142,7 +142,7 @@
 
 	expectedCFlag := "-Wl,--symbol-ordering-file=toolchain/pgo-profiles/orderfiles/test.orderfile"
 
-	test := result.ModuleForTests("test", "android_arm64_armv8-a")
+	test := result.ModuleForTests(t, "test", "android_arm64_armv8-a")
 
 	// Check ldFlags of orderfile-enabled module
 	ldFlags := test.Rule("ld").Args["ldFlags"]
@@ -185,7 +185,7 @@
 	expectedCFlag := "-forder-file-instrumentation"
 
 	// Check cFlags of orderfile-enabled module
-	libTest := result.ModuleForTests("libTest", "android_arm64_armv8-a_shared")
+	libTest := result.ModuleForTests(t, "libTest", "android_arm64_armv8-a_shared")
 
 	cFlags := libTest.Rule("cc").Args["cFlags"]
 	if !strings.Contains(cFlags, expectedCFlag) {
@@ -193,8 +193,8 @@
 	}
 
 	// Check cFlags of orderfile variant static libraries
-	libFooOfVariant := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static_orderfile")
-	libBarOfVariant := result.ModuleForTests("libBar", "android_arm64_armv8-a_static_orderfile")
+	libFooOfVariant := result.ModuleForTests(t, "libFoo", "android_arm64_armv8-a_static_orderfile")
+	libBarOfVariant := result.ModuleForTests(t, "libBar", "android_arm64_armv8-a_static_orderfile")
 
 	cFlags = libFooOfVariant.Rule("cc").Args["cFlags"]
 	if !strings.Contains(cFlags, expectedCFlag) {
@@ -216,8 +216,8 @@
 	}
 
 	// Check cFlags of the non-orderfile variant static libraries
-	libFoo := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static")
-	libBar := result.ModuleForTests("libBar", "android_arm64_armv8-a_static")
+	libFoo := result.ModuleForTests(t, "libFoo", "android_arm64_armv8-a_static")
+	libBar := result.ModuleForTests(t, "libBar", "android_arm64_armv8-a_static")
 
 	cFlags = libFoo.Rule("cc").Args["cFlags"]
 	if strings.Contains(cFlags, expectedCFlag) {
@@ -274,15 +274,15 @@
 	expectedCFlag := "-Wl,--symbol-ordering-file=toolchain/pgo-profiles/orderfiles/test.orderfile"
 
 	// Check ldFlags of orderfile-enabled module
-	libTest := result.ModuleForTests("libTest", "android_arm64_armv8-a_shared")
+	libTest := result.ModuleForTests(t, "libTest", "android_arm64_armv8-a_shared")
 
 	ldFlags := libTest.Rule("ld").Args["ldFlags"]
 	if !strings.Contains(ldFlags, expectedCFlag) {
 		t.Errorf("Expected 'libTest' to load orderfile, but did not find %q in ldFlags %q", expectedCFlag, ldFlags)
 	}
 
-	libFoo := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static")
-	libBar := result.ModuleForTests("libBar", "android_arm64_armv8-a_static")
+	libFoo := result.ModuleForTests(t, "libFoo", "android_arm64_armv8-a_static")
+	libBar := result.ModuleForTests(t, "libBar", "android_arm64_armv8-a_static")
 
 	// Check dependency edge from orderfile-enabled module to non-orderfile variant static libraries
 	if !hasDirectDep(result, libTest.Module(), libFoo.Module()) {
@@ -343,7 +343,7 @@
 	expectedCFlag := "-forder-file-instrumentation"
 
 	// Check cFlags of orderfile-enabled module
-	libTest := result.ModuleForTests("libTest", "android_arm64_armv8-a_shared")
+	libTest := result.ModuleForTests(t, "libTest", "android_arm64_armv8-a_shared")
 
 	cFlags := libTest.Rule("cc").Args["cFlags"]
 	if !strings.Contains(cFlags, expectedCFlag) {
@@ -351,8 +351,8 @@
 	}
 
 	// Check cFlags of the static and shared libraries
-	libFoo := result.ModuleForTests("libFoo", "android_arm64_armv8-a_shared")
-	libBar := result.ModuleForTests("libBar", "android_arm64_armv8-a_static")
+	libFoo := result.ModuleForTests(t, "libFoo", "android_arm64_armv8-a_shared")
+	libBar := result.ModuleForTests(t, "libBar", "android_arm64_armv8-a_static")
 
 	cFlags = libFoo.Rule("cc").Args["cFlags"]
 	if strings.Contains(cFlags, expectedCFlag) {
@@ -423,7 +423,7 @@
 	expectedCFlag := "-forder-file-instrumentation"
 
 	// Check cFlags of module
-	libTest := result.ModuleForTests("libTest", "android_arm64_armv8-a_static")
+	libTest := result.ModuleForTests(t, "libTest", "android_arm64_armv8-a_static")
 
 	cFlags := libTest.Rule("cc").Args["cFlags"]
 	if strings.Contains(cFlags, expectedCFlag) {
@@ -431,8 +431,8 @@
 	}
 
 	// Check cFlags of the static libraries
-	libFoo := result.ModuleForTests("libFoo", "android_arm64_armv8-a_static")
-	libBar := result.ModuleForTests("libBar", "android_arm64_armv8-a_static")
+	libFoo := result.ModuleForTests(t, "libFoo", "android_arm64_armv8-a_static")
+	libBar := result.ModuleForTests(t, "libBar", "android_arm64_armv8-a_static")
 
 	cFlags = libFoo.Rule("cc").Args["cFlags"]
 	if strings.Contains(cFlags, expectedCFlag) {
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index 299fb51..70ee5e3 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -16,8 +16,8 @@
 
 import (
 	"path/filepath"
-	"strings"
 
+	"github.com/google/blueprint/depset"
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
@@ -114,27 +114,11 @@
 
 	// TODO(ccross): verify shared library dependencies
 	srcs := p.prebuiltSrcs(ctx)
-	stubInfo := addStubDependencyProviders(ctx)
+	stubInfo := AddStubDependencyProviders(ctx)
 
 	// Stub variants will create a stub .so file from stub .c files
-	if p.buildStubs() && objs.objFiles != nil {
+	if p.BuildStubs() && objs.objFiles != nil {
 		// TODO (b/275273834): Make objs.objFiles == nil a hard error when the symbol files have been added to module sdk.
-
-		// The map.txt files of libclang_rt.* contain version information, but the checked in .so files do not.
-		// e.g. libclang_rt.* libs impl
-		// $ nm -D prebuilts/../libclang_rt.hwasan-aarch64-android.so
-		// __hwasan_init
-
-		// stubs generated from .map.txt
-		// $ nm -D out/soong/.intermediates/../<stubs>/libclang_rt.hwasan-aarch64-android.so
-		// __hwasan_init@@LIBCLANG_RT_ASAN
-
-		// Special-case libclang_rt.* libs to account for this discrepancy.
-		// TODO (spandandas): Remove this special case https://r.android.com/3236596 has been submitted, and a new set of map.txt
-		// files of libclang_rt.* libs have been generated.
-		if strings.Contains(ctx.ModuleName(), "libclang_rt.") {
-			p.versionScriptPath = android.OptionalPathForPath(nil)
-		}
 		return p.linkShared(ctx, flags, deps, objs)
 	}
 
@@ -156,7 +140,7 @@
 		}
 
 		if p.static() {
-			depSet := android.NewDepSetBuilder[android.Path](android.TOPOLOGICAL).Direct(in).Build()
+			depSet := depset.NewBuilder[android.Path](depset.TOPOLOGICAL).Direct(in).Build()
 			android.SetProvider(ctx, StaticLibraryInfoProvider, StaticLibraryInfo{
 				StaticLibrary: in,
 
@@ -220,6 +204,7 @@
 				Target:        ctx.Target(),
 
 				TableOfContents: p.tocFile,
+				IsStubs:         p.BuildStubs(),
 			})
 
 			return outputFile
@@ -231,6 +216,7 @@
 		android.SetProvider(ctx, SharedLibraryInfoProvider, SharedLibraryInfo{
 			SharedLibrary: latestStub,
 			Target:        ctx.Target(),
+			IsStubs:       true,
 		})
 
 		return latestStub
@@ -282,7 +268,7 @@
 }
 
 // Implements versionedInterface
-func (p *prebuiltLibraryLinker) implementationModuleName(name string) string {
+func (p *prebuiltLibraryLinker) ImplementationModuleName(name string) string {
 	return android.RemoveOptionalPrebuiltPrefix(name)
 }
 
@@ -312,7 +298,7 @@
 }
 
 func (p *prebuiltLibraryLinker) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects {
-	if p.buildStubs() && p.stubsVersion() != "" {
+	if p.BuildStubs() && p.StubsVersion() != "" {
 		return p.compileModuleLibApiStubs(ctx, flags, deps)
 	}
 	return Objects{}
diff --git a/cc/prebuilt_test.go b/cc/prebuilt_test.go
index acbbabc..af68ca6 100644
--- a/cc/prebuilt_test.go
+++ b/cc/prebuilt_test.go
@@ -50,6 +50,7 @@
 		cc_prebuilt_library_shared {
 			name: "liba",
 			srcs: ["liba.so"],
+			prefer: true,
 		}
 
 		cc_library {
@@ -59,6 +60,7 @@
 		cc_prebuilt_library_static {
 			name: "libb",
 			srcs: ["libb.a"],
+			prefer: true,
 		}
 
 		cc_library_shared {
@@ -114,21 +116,21 @@
 	})
 
 	// Verify that all the modules exist and that their dependencies were connected correctly
-	liba := ctx.ModuleForTests("liba", "android_arm64_armv8-a_shared").Module()
-	libb := ctx.ModuleForTests("libb", "android_arm64_armv8-a_static").Module()
-	libd := ctx.ModuleForTests("libd", "android_arm64_armv8-a_shared").Module()
-	libe := ctx.ModuleForTests("libe", "android_arm64_armv8-a_static").Module()
-	libfStatic := ctx.ModuleForTests("libf", "android_arm64_armv8-a_static").Module()
-	libfShared := ctx.ModuleForTests("libf", "android_arm64_armv8-a_shared").Module()
-	crtx := ctx.ModuleForTests("crtx", "android_arm64_armv8-a").Module()
+	liba := ctx.ModuleForTests(t, "liba", "android_arm64_armv8-a_shared").Module()
+	libb := ctx.ModuleForTests(t, "libb", "android_arm64_armv8-a_static").Module()
+	libd := ctx.ModuleForTests(t, "libd", "android_arm64_armv8-a_shared").Module()
+	libe := ctx.ModuleForTests(t, "libe", "android_arm64_armv8-a_static").Module()
+	libfStatic := ctx.ModuleForTests(t, "libf", "android_arm64_armv8-a_static").Module()
+	libfShared := ctx.ModuleForTests(t, "libf", "android_arm64_armv8-a_shared").Module()
+	crtx := ctx.ModuleForTests(t, "crtx", "android_arm64_armv8-a").Module()
 
-	prebuiltLiba := ctx.ModuleForTests("prebuilt_liba", "android_arm64_armv8-a_shared").Module()
-	prebuiltLibb := ctx.ModuleForTests("prebuilt_libb", "android_arm64_armv8-a_static").Module()
-	prebuiltLibd := ctx.ModuleForTests("prebuilt_libd", "android_arm64_armv8-a_shared").Module()
-	prebuiltLibe := ctx.ModuleForTests("prebuilt_libe", "android_arm64_armv8-a_static").Module()
-	prebuiltLibfStatic := ctx.ModuleForTests("prebuilt_libf", "android_arm64_armv8-a_static").Module()
-	prebuiltLibfShared := ctx.ModuleForTests("prebuilt_libf", "android_arm64_armv8-a_shared").Module()
-	prebuiltCrtx := ctx.ModuleForTests("prebuilt_crtx", "android_arm64_armv8-a").Module()
+	prebuiltLiba := ctx.ModuleForTests(t, "prebuilt_liba", "android_arm64_armv8-a_shared").Module()
+	prebuiltLibb := ctx.ModuleForTests(t, "prebuilt_libb", "android_arm64_armv8-a_static").Module()
+	prebuiltLibd := ctx.ModuleForTests(t, "prebuilt_libd", "android_arm64_armv8-a_shared").Module()
+	prebuiltLibe := ctx.ModuleForTests(t, "prebuilt_libe", "android_arm64_armv8-a_static").Module()
+	prebuiltLibfStatic := ctx.ModuleForTests(t, "prebuilt_libf", "android_arm64_armv8-a_static").Module()
+	prebuiltLibfShared := ctx.ModuleForTests(t, "prebuilt_libf", "android_arm64_armv8-a_shared").Module()
+	prebuiltCrtx := ctx.ModuleForTests(t, "prebuilt_crtx", "android_arm64_armv8-a").Module()
 
 	hasDep := func(m android.Module, wantDep android.Module) bool {
 		t.Helper()
@@ -169,9 +171,9 @@
 		t.Errorf("crtx missing dependency on prebuilt_crtx")
 	}
 
-	entries := android.AndroidMkEntriesForTest(t, ctx, prebuiltLiba)[0]
+	entries := android.AndroidMkInfoForTest(t, ctx, prebuiltLiba).PrimaryInfo
 	android.AssertStringEquals(t, "unexpected LOCAL_SOONG_MODULE_TYPE", "cc_prebuilt_library_shared", entries.EntryMap["LOCAL_SOONG_MODULE_TYPE"][0])
-	entries = android.AndroidMkEntriesForTest(t, ctx, prebuiltLibb)[0]
+	entries = android.AndroidMkInfoForTest(t, ctx, prebuiltLibb).PrimaryInfo
 	android.AssertStringEquals(t, "unexpected LOCAL_SOONG_MODULE_TYPE", "cc_prebuilt_library_static", entries.EntryMap["LOCAL_SOONG_MODULE_TYPE"][0])
 }
 
@@ -188,7 +190,7 @@
 		"libf.so": nil,
 	})
 
-	shared := ctx.ModuleForTests("libtest", "android_arm64_armv8-a_shared").Module().(*Module)
+	shared := ctx.ModuleForTests(t, "libtest", "android_arm64_armv8-a_shared").Module().(*Module)
 	assertString(t, shared.OutputFile().Path().Base(), "libtest.so")
 }
 
@@ -202,7 +204,7 @@
 		"libf.a": nil,
 	})
 
-	static := ctx.ModuleForTests("libtest", "android_arm64_armv8-a_static").Module().(*Module)
+	static := ctx.ModuleForTests(t, "libtest", "android_arm64_armv8-a_static").Module().(*Module)
 	assertString(t, static.OutputFile().Path().Base(), "libf.a")
 }
 
@@ -225,10 +227,10 @@
 		"libf.so": nil,
 	})
 
-	shared := ctx.ModuleForTests("libtest", "android_arm64_armv8-a_shared").Module().(*Module)
+	shared := ctx.ModuleForTests(t, "libtest", "android_arm64_armv8-a_shared").Module().(*Module)
 	assertString(t, shared.OutputFile().Path().Base(), "libtest.so")
 
-	static := ctx.ModuleForTests("libtest", "android_arm64_armv8-a_static").Module().(*Module)
+	static := ctx.ModuleForTests(t, "libtest", "android_arm64_armv8-a_static").Module().(*Module)
 	assertString(t, static.OutputFile().Path().Base(), "libf.a")
 }
 
@@ -252,10 +254,10 @@
 		"libfoo.so": nil,
 	})
 
-	static := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_static").Module().(*Module)
+	static := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_static").Module().(*Module)
 	assertString(t, static.OutputFile().Path().Base(), "libfoo.a")
 
-	shared := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Module().(*Module)
+	shared := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_shared").Module().(*Module)
 	assertString(t, shared.OutputFile().Path().Base(), "libbar.so")
 }
 
@@ -273,7 +275,7 @@
 		"libfoo.so": nil,
 	})
 
-	shared := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Module().(*Module)
+	shared := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_shared").Module().(*Module)
 	assertString(t, shared.OutputFile().Path().Base(), "libbar.so")
 }
 
@@ -310,7 +312,7 @@
 		"foo":       nil,
 	})
 
-	fooRule := ctx.ModuleForTests("foo", "linux_glibc_x86_64").Rule("Symlink")
+	fooRule := ctx.ModuleForTests(t, "foo", "linux_glibc_x86_64").Rule("Symlink")
 	assertString(t, fooRule.Output.String(), "out/soong/.intermediates/foo/linux_glibc_x86_64/foo")
 	assertString(t, fooRule.Args["fromPath"], "$$PWD/linux_glibc_x86_64/bin/foo")
 
@@ -353,16 +355,16 @@
 	// Without SANITIZE_TARGET.
 	ctx := testPrebuilt(t, bp, fs)
 
-	shared_rule := ctx.ModuleForTests("libtest", "android_arm64_armv8-a_shared").Rule("android/soong/cc.strip")
+	shared_rule := ctx.ModuleForTests(t, "libtest", "android_arm64_armv8-a_shared").Rule("android/soong/cc.strip")
 	assertString(t, shared_rule.Input.String(), "libf.so")
 
-	static := ctx.ModuleForTests("libtest", "android_arm64_armv8-a_static").Module().(*Module)
+	static := ctx.ModuleForTests(t, "libtest", "android_arm64_armv8-a_static").Module().(*Module)
 	assertString(t, static.OutputFile().Path().Base(), "libf.a")
 
-	shared_rule2 := ctx.ModuleForTests("libtest_shared", "android_arm64_armv8-a_shared").Rule("android/soong/cc.strip")
+	shared_rule2 := ctx.ModuleForTests(t, "libtest_shared", "android_arm64_armv8-a_shared").Rule("android/soong/cc.strip")
 	assertString(t, shared_rule2.Input.String(), "libf.so")
 
-	static2 := ctx.ModuleForTests("libtest_static", "android_arm64_armv8-a_static").Module().(*Module)
+	static2 := ctx.ModuleForTests(t, "libtest_static", "android_arm64_armv8-a_static").Module().(*Module)
 	assertString(t, static2.OutputFile().Path().Base(), "libf.a")
 
 	// With SANITIZE_TARGET=hwaddress
@@ -372,16 +374,16 @@
 		}),
 	)
 
-	shared_rule = ctx.ModuleForTests("libtest", "android_arm64_armv8-a_shared_hwasan").Rule("android/soong/cc.strip")
+	shared_rule = ctx.ModuleForTests(t, "libtest", "android_arm64_armv8-a_shared_hwasan").Rule("android/soong/cc.strip")
 	assertString(t, shared_rule.Input.String(), "hwasan/libf.so")
 
-	static = ctx.ModuleForTests("libtest", "android_arm64_armv8-a_static_hwasan").Module().(*Module)
+	static = ctx.ModuleForTests(t, "libtest", "android_arm64_armv8-a_static_hwasan").Module().(*Module)
 	assertString(t, static.OutputFile().Path().Base(), "libf.hwasan.a")
 
-	shared_rule2 = ctx.ModuleForTests("libtest_shared", "android_arm64_armv8-a_shared_hwasan").Rule("android/soong/cc.strip")
+	shared_rule2 = ctx.ModuleForTests(t, "libtest_shared", "android_arm64_armv8-a_shared_hwasan").Rule("android/soong/cc.strip")
 	assertString(t, shared_rule2.Input.String(), "hwasan/libf.so")
 
-	static2 = ctx.ModuleForTests("libtest_static", "android_arm64_armv8-a_static_hwasan").Module().(*Module)
+	static2 = ctx.ModuleForTests(t, "libtest_static", "android_arm64_armv8-a_static_hwasan").Module().(*Module)
 	assertString(t, static2.OutputFile().Path().Base(), "libf.hwasan.a")
 }
 
@@ -392,7 +394,7 @@
 	srcs: [],
 }`
 	ctx := testPrebuilt(t, bp, map[string][]byte{})
-	mod := ctx.ModuleForTests("bintest", "android_arm64_armv8-a").Module().(*Module)
+	mod := ctx.ModuleForTests(t, "bintest", "android_arm64_armv8-a").Module().(*Module)
 	android.AssertBoolEquals(t, `expected no srcs to yield no output file`, false, mod.OutputFile().Valid())
 }
 
@@ -482,21 +484,21 @@
 			"libbar.so": nil,
 			"crtx.o":    nil,
 		}, preparer)
-		libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Module()
-		expectedDependency := ctx.ModuleForTests(tc.expectedDependencyName, "android_arm64_armv8-a_shared").Module()
+		libfoo := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_shared").Module()
+		expectedDependency := ctx.ModuleForTests(t, tc.expectedDependencyName, "android_arm64_armv8-a_shared").Module()
 		android.AssertBoolEquals(t, fmt.Sprintf("expected dependency from %s to %s\n", libfoo.Name(), tc.expectedDependencyName), true, hasDep(ctx, libfoo, expectedDependency))
 		// check that LOCAL_SHARED_LIBRARIES contains libbar and not libbar.v<N>
-		entries := android.AndroidMkEntriesForTest(t, ctx, libfoo)[0]
+		entries := android.AndroidMkInfoForTest(t, ctx, libfoo).PrimaryInfo
 		android.AssertStringListContains(t, "Version should not be present in LOCAL_SHARED_LIBRARIES", entries.EntryMap["LOCAL_SHARED_LIBRARIES"], "libbar")
 
 		// check installation rules
 		// the selected soong module should be exported to make
-		libbar := ctx.ModuleForTests(tc.expectedDependencyName, "android_arm64_armv8-a_shared").Module()
+		libbar := ctx.ModuleForTests(t, tc.expectedDependencyName, "android_arm64_armv8-a_shared").Module()
 		android.AssertBoolEquals(t, fmt.Sprintf("dependency %s should be exported to make\n", expectedDependency), true, !libbar.IsHideFromMake())
 
 		// check LOCAL_MODULE of the selected module name
 		// the prebuilt should have the same LOCAL_MODULE when exported to make
-		entries = android.AndroidMkEntriesForTest(t, ctx, libbar)[0]
+		entries = android.AndroidMkInfoForTest(t, ctx, libbar).PrimaryInfo
 		android.AssertStringEquals(t, "unexpected LOCAL_MODULE", "libbar", entries.EntryMap["LOCAL_MODULE"][0])
 	}
 }
@@ -583,8 +585,8 @@
 		if tc.expectedErr != "" {
 			return // the fixture will assert that the excepted err has been raised
 		}
-		libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Module()
-		expectedDependency := ctx.ModuleForTests(tc.expectedDependencyName, "android_arm64_armv8-a_shared").Module()
+		libfoo := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_shared").Module()
+		expectedDependency := ctx.ModuleForTests(t, tc.expectedDependencyName, "android_arm64_armv8-a_shared").Module()
 		android.AssertBoolEquals(t, fmt.Sprintf("expected dependency from %s to %s\n", libfoo.Name(), tc.expectedDependencyName), true, hasDep(ctx, libfoo, expectedDependency))
 	}
 }
@@ -636,8 +638,8 @@
 		"libbar.so": nil,
 		"crtx.o":    nil,
 	}, preparer)
-	libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Module()
-	sourceLibBar := ctx.ModuleForTests("libbar", "android_arm64_armv8-a_static").Module()
+	libfoo := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_shared").Module()
+	sourceLibBar := ctx.ModuleForTests(t, "libbar", "android_arm64_armv8-a_static").Module()
 	// Even though the prebuilt is listed in apex_contributions, the prebuilt does not have a static variant.
 	// Therefore source of libbar should be used.
 	android.AssertBoolEquals(t, fmt.Sprintf("expected dependency from libfoo to source libbar"), true, hasDep(ctx, libfoo, sourceLibBar))
diff --git a/cc/proto_test.go b/cc/proto_test.go
index a905ea8..47b7e17 100644
--- a/cc/proto_test.go
+++ b/cc/proto_test.go
@@ -29,7 +29,7 @@
 			srcs: ["a.proto"],
 		}`)
 
-		proto := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Output("proto/a.pb.cc")
+		proto := ctx.ModuleForTests(t, "libfoo", "android_arm_armv7-a-neon_shared").Output("proto/a.pb.cc")
 
 		if cmd := proto.RuleParams.Command; !strings.Contains(cmd, "--cpp_out=") {
 			t.Errorf("expected '--cpp_out' in %q", cmd)
@@ -53,8 +53,8 @@
 
 		buildOS := ctx.Config().BuildOS.String()
 
-		proto := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Output("proto/a.pb.cc")
-		foobar := ctx.ModuleForTests("protoc-gen-foobar", buildOS+"_x86_64")
+		proto := ctx.ModuleForTests(t, "libfoo", "android_arm_armv7-a-neon_shared").Output("proto/a.pb.cc")
+		foobar := ctx.ModuleForTests(t, "protoc-gen-foobar", buildOS+"_x86_64")
 
 		cmd := proto.RuleParams.Command
 		if w := "--foobar_out="; !strings.Contains(cmd, w) {
@@ -85,8 +85,8 @@
 
 		buildOS := ctx.Config().BuildOS.String()
 
-		proto := ctx.ModuleForTests("libgrpc", "android_arm_armv7-a-neon_shared").Output("proto/a.grpc.pb.cc")
-		grpcCppPlugin := ctx.ModuleForTests("protoc-gen-grpc-cpp-plugin", buildOS+"_x86_64")
+		proto := ctx.ModuleForTests(t, "libgrpc", "android_arm_armv7-a-neon_shared").Output("proto/a.grpc.pb.cc")
+		grpcCppPlugin := ctx.ModuleForTests(t, "protoc-gen-grpc-cpp-plugin", buildOS+"_x86_64")
 
 		cmd := proto.RuleParams.Command
 		if w := "--grpc-cpp-plugin_out="; !strings.Contains(cmd, w) {
diff --git a/cc/sabi.go b/cc/sabi.go
index 2caf0d4..06ab6ec 100644
--- a/cc/sabi.go
+++ b/cc/sabi.go
@@ -22,10 +22,16 @@
 )
 
 var (
-	lsdumpPaths     []string
 	lsdumpPathsLock sync.Mutex
+	lsdumpKey       = android.NewOnceKey("lsdump")
 )
 
+func lsdumpPaths(config android.Config) *[]string {
+	return config.Once(lsdumpKey, func() any {
+		return &[]string{}
+	}).(*[]string)
+}
+
 type lsdumpTag string
 
 const (
@@ -131,7 +137,7 @@
 		if m.isImplementationForLLNDKPublic() {
 			result = append(result, llndkLsdumpTag)
 		}
-		if m.library.hasStubsVariants() {
+		if m.library.HasStubsVariants() {
 			result = append(result, apexLsdumpTag)
 		}
 		if headerAbiChecker.enabled() {
@@ -291,8 +297,9 @@
 
 // Add an entry to the global list of lsdump. The list is exported to a Make variable by
 // `cc.makeVarsProvider`.
-func addLsdumpPath(lsdumpPath string) {
+func addLsdumpPath(config android.Config, lsdumpPath string) {
+	lsdumpPaths := lsdumpPaths(config)
 	lsdumpPathsLock.Lock()
 	defer lsdumpPathsLock.Unlock()
-	lsdumpPaths = append(lsdumpPaths, lsdumpPath)
+	*lsdumpPaths = append(*lsdumpPaths, lsdumpPath)
 }
diff --git a/cc/sabi_test.go b/cc/sabi_test.go
index 6b8cc17..3d2a98c 100644
--- a/cc/sabi_test.go
+++ b/cc/sabi_test.go
@@ -48,16 +48,16 @@
 		PrepareForTestWithCcDefaultModules,
 	).RunTestWithBp(t, bp)
 
-	libsabiStatic := result.ModuleForTests("libsabi", "android_arm64_armv8-a_static_sabi")
+	libsabiStatic := result.ModuleForTests(t, "libsabi", "android_arm64_armv8-a_static_sabi")
 	sabiObjSDump := libsabiStatic.Output("obj/sabi.sdump")
 
-	libDirect := result.ModuleForTests("libdirect", "android_arm64_armv8-a_static_sabi")
+	libDirect := result.ModuleForTests(t, "libdirect", "android_arm64_armv8-a_static_sabi")
 	directObjSDump := libDirect.Output("obj/direct.sdump")
 
-	libTransitive := result.ModuleForTests("libtransitive", "android_arm64_armv8-a_static_sabi")
+	libTransitive := result.ModuleForTests(t, "libtransitive", "android_arm64_armv8-a_static_sabi")
 	transitiveObjSDump := libTransitive.Output("obj/transitive.sdump")
 
-	libsabiShared := result.ModuleForTests("libsabi", "android_arm64_armv8-a_shared")
+	libsabiShared := result.ModuleForTests(t, "libsabi", "android_arm64_armv8-a_shared")
 	sabiLink := libsabiShared.Rule("sAbiLink")
 
 	android.AssertStringListContains(t, "sabi link inputs", sabiLink.Inputs.Strings(), sabiObjSDump.Output.String())
diff --git a/cc/sanitize.go b/cc/sanitize.go
index 85fdb02..e7598e7 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -79,7 +79,7 @@
 
 	minimalRuntimeFlags = []string{"-fsanitize-minimal-runtime", "-fno-sanitize-trap=integer,undefined",
 		"-fno-sanitize-recover=integer,undefined"}
-	memtagStackCommonFlags = []string{"-march=armv8-a+memtag"}
+	memtagStackCommonFlags = []string{"-Xclang -target-feature -Xclang +mte"}
 	memtagStackLlvmFlags   = []string{"-dom-tree-reachability-max-bbs-to-explore=128"}
 
 	hostOnlySanitizeFlags   = []string{"-fno-sanitize-recover=all"}
@@ -176,7 +176,7 @@
 	switch t {
 	case cfi, Hwasan, Asan, tsan, Fuzzer, scs, Memtag_stack:
 		sanitizer := &sanitizerSplitMutator{t}
-		ctx.BottomUp(t.variationName()+"_markapexes", sanitizer.markSanitizableApexesMutator).Parallel()
+		ctx.BottomUp(t.variationName()+"_markapexes", sanitizer.markSanitizableApexesMutator)
 		ctx.Transition(t.variationName(), sanitizer)
 	case Memtag_heap, Memtag_globals, intOverflow:
 		// do nothing
@@ -992,7 +992,7 @@
 	return flags
 }
 
-func (s *sanitize) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
+func (s *sanitize) prepareAndroidMKProviderInfo(config android.Config, ctx AndroidMkContext, entries *android.AndroidMkInfo) {
 	// Add a suffix for cfi/hwasan/scs-enabled static/header libraries to allow surfacing
 	// both the sanitized and non-sanitized variants to make without a name conflict.
 	if entries.Class == "STATIC_LIBRARIES" || entries.Class == "HEADER_LIBRARIES" {
@@ -1504,9 +1504,6 @@
 
 		if Bool(sanProps.Memtag_globals) {
 			sanitizers = append(sanitizers, "memtag-globals")
-			// TODO(mitchp): For now, enable memtag-heap with memtag-globals because the linker
-			// isn't new enough (https://reviews.llvm.org/differential/changeset/?ref=4243566).
-			sanitizers = append(sanitizers, "memtag-heap")
 		}
 
 		if Bool(sanProps.Fuzzer) {
@@ -1813,7 +1810,7 @@
 type sanitizerLibrariesTxtModule struct {
 	android.ModuleBase
 
-	outputFile android.OutputPath
+	outputFile android.Path
 }
 
 var _ etc.PrebuiltEtcModule = (*sanitizerLibrariesTxtModule)(nil)
@@ -1830,10 +1827,7 @@
 
 type sanitizerLibraryDependencyTag struct {
 	blueprint.BaseDependencyTag
-}
-
-func (t sanitizerLibraryDependencyTag) AllowDisabledModuleDependency(target android.Module) bool {
-	return true
+	android.AlwaysAllowDisabledModuleDependencyTag
 }
 
 var _ android.AllowDisabledModuleDependency = (*sanitizerLibraryDependencyTag)(nil)
@@ -1899,20 +1893,23 @@
 func (txt *sanitizerLibrariesTxtModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	filename := txt.Name()
 
-	txt.outputFile = android.PathForModuleOut(ctx, filename).OutputPath
-	android.WriteFileRule(ctx, txt.outputFile, txt.getSanitizerLibs(ctx))
+	outputFile := android.PathForModuleOut(ctx, filename)
+	android.WriteFileRule(ctx, outputFile, txt.getSanitizerLibs(ctx))
 
 	installPath := android.PathForModuleInstall(ctx, "etc")
-	ctx.InstallFile(installPath, filename, txt.outputFile)
+	ctx.InstallFile(installPath, filename, outputFile)
 
-	ctx.SetOutputFiles(android.Paths{txt.outputFile}, "")
+	ctx.SetOutputFiles(android.Paths{outputFile}, "")
+	txt.outputFile = outputFile
 }
 
-func (txt *sanitizerLibrariesTxtModule) AndroidMkEntries() []android.AndroidMkEntries {
-	return []android.AndroidMkEntries{{
-		Class:      "ETC",
-		OutputFile: android.OptionalPathForPath(txt.outputFile),
-	}}
+func (txt *sanitizerLibrariesTxtModule) PrepareAndroidMKProviderInfo(config android.Config) *android.AndroidMkProviderInfo {
+	return &android.AndroidMkProviderInfo{
+		PrimaryInfo: android.AndroidMkInfo{
+			Class:      "ETC",
+			OutputFile: android.OptionalPathForPath(txt.outputFile),
+		},
+	}
 }
 
 // PrebuiltEtcModule interface
diff --git a/cc/sanitize_test.go b/cc/sanitize_test.go
index a1cfb5c..543e808 100644
--- a/cc/sanitize_test.go
+++ b/cc/sanitize_test.go
@@ -222,31 +222,31 @@
 		staticAsanVariant := staticVariant + "_asan"
 
 		// The binaries, one with asan and one without
-		binWithAsan := result.ModuleForTests("bin_with_asan", asanVariant)
-		binNoAsan := result.ModuleForTests("bin_no_asan", variant)
+		binWithAsan := result.ModuleForTests(t, "bin_with_asan", asanVariant)
+		binNoAsan := result.ModuleForTests(t, "bin_no_asan", variant)
 
 		// Shared libraries that don't request asan
-		libShared := result.ModuleForTests("libshared", sharedVariant)
-		libTransitive := result.ModuleForTests("libtransitive", sharedVariant)
+		libShared := result.ModuleForTests(t, "libshared", sharedVariant)
+		libTransitive := result.ModuleForTests(t, "libtransitive", sharedVariant)
 
 		// Shared library that requests asan
-		libAsan := result.ModuleForTests("libasan", sharedAsanVariant)
+		libAsan := result.ModuleForTests(t, "libasan", sharedAsanVariant)
 
 		// Static library that uses an asan variant for bin_with_asan and a non-asan variant
 		// for bin_no_asan.
-		libStaticAsanVariant := result.ModuleForTests("libstatic", staticAsanVariant)
-		libStaticNoAsanVariant := result.ModuleForTests("libstatic", staticVariant)
+		libStaticAsanVariant := result.ModuleForTests(t, "libstatic", staticAsanVariant)
+		libStaticNoAsanVariant := result.ModuleForTests(t, "libstatic", staticVariant)
 
 		// Static library that never uses asan.
-		libNoAsan := result.ModuleForTests("libnoasan", staticVariant)
+		libNoAsan := result.ModuleForTests(t, "libnoasan", staticVariant)
 
 		// Static library that specifies asan
-		libStaticAsan := result.ModuleForTests("libstatic_asan", staticAsanVariant)
-		libStaticAsanNoAsanVariant := result.ModuleForTests("libstatic_asan", staticVariant)
+		libStaticAsan := result.ModuleForTests(t, "libstatic_asan", staticAsanVariant)
+		libStaticAsanNoAsanVariant := result.ModuleForTests(t, "libstatic_asan", staticVariant)
 
-		libAsanSharedRuntime := result.ModuleForTests("libclang_rt.asan", sharedVariant)
-		libAsanStaticRuntime := result.ModuleForTests("libclang_rt.asan.static", staticVariant)
-		libAsanStaticCxxRuntime := result.ModuleForTests("libclang_rt.asan_cxx.static", staticVariant)
+		libAsanSharedRuntime := result.ModuleForTests(t, "libclang_rt.asan", sharedVariant)
+		libAsanStaticRuntime := result.ModuleForTests(t, "libclang_rt.asan.static", staticVariant)
+		libAsanStaticCxxRuntime := result.ModuleForTests(t, "libclang_rt.asan_cxx.static", staticVariant)
 
 		expectSharedLinkDep(t, ctx, binWithAsan, libShared)
 		expectSharedLinkDep(t, ctx, binWithAsan, libAsan)
@@ -386,15 +386,15 @@
 		sharedTsanVariant := sharedVariant + "_tsan"
 
 		// The binaries, one with tsan and one without
-		binWithTsan := result.ModuleForTests("bin_with_tsan", tsanVariant)
-		binNoTsan := result.ModuleForTests("bin_no_tsan", variant)
+		binWithTsan := result.ModuleForTests(t, "bin_with_tsan", tsanVariant)
+		binNoTsan := result.ModuleForTests(t, "bin_no_tsan", variant)
 
 		// Shared libraries that don't request tsan
-		libShared := result.ModuleForTests("libshared", sharedVariant)
-		libTransitive := result.ModuleForTests("libtransitive", sharedVariant)
+		libShared := result.ModuleForTests(t, "libshared", sharedVariant)
+		libTransitive := result.ModuleForTests(t, "libtransitive", sharedVariant)
 
 		// Shared library that requests tsan
-		libTsan := result.ModuleForTests("libtsan", sharedTsanVariant)
+		libTsan := result.ModuleForTests(t, "libtsan", sharedTsanVariant)
 
 		expectSharedLinkDep(t, ctx, binWithTsan, libShared)
 		expectSharedLinkDep(t, ctx, binWithTsan, libTsan)
@@ -479,16 +479,16 @@
 		staticVariant := variant + "_static"
 
 		// The binaries, one with ubsan and one without
-		binWithUbsan := result.ModuleForTests("bin_with_ubsan", variant)
-		binNoUbsan := result.ModuleForTests("bin_no_ubsan", variant)
+		binWithUbsan := result.ModuleForTests(t, "bin_with_ubsan", variant)
+		binNoUbsan := result.ModuleForTests(t, "bin_no_ubsan", variant)
 
 		// Static libraries that don't request ubsan
-		libStatic := result.ModuleForTests("libstatic", staticVariant)
-		libTransitive := result.ModuleForTests("libtransitive", staticVariant)
+		libStatic := result.ModuleForTests(t, "libstatic", staticVariant)
+		libTransitive := result.ModuleForTests(t, "libtransitive", staticVariant)
 
-		libUbsan := result.ModuleForTests("libubsan", staticVariant)
+		libUbsan := result.ModuleForTests(t, "libubsan", staticVariant)
 
-		libUbsanMinimal := result.ModuleForTests("libclang_rt.ubsan_minimal", staticVariant)
+		libUbsanMinimal := result.ModuleForTests(t, "libclang_rt.ubsan_minimal", staticVariant)
 
 		expectStaticLinkDep(t, ctx, binWithUbsan, libStatic)
 		expectStaticLinkDep(t, ctx, binWithUbsan, libUbsan)
@@ -610,31 +610,31 @@
 		staticFuzzerVariant := staticVariant + "_fuzzer"
 
 		// The binaries, one with fuzzer and one without
-		binWithFuzzer := result.ModuleForTests("bin_with_fuzzer", fuzzerVariant)
-		binNoFuzzer := result.ModuleForTests("bin_no_fuzzer", variant)
+		binWithFuzzer := result.ModuleForTests(t, "bin_with_fuzzer", fuzzerVariant)
+		binNoFuzzer := result.ModuleForTests(t, "bin_no_fuzzer", variant)
 
 		// Shared libraries that don't request fuzzer
-		libShared := result.ModuleForTests("libshared", sharedVariant)
-		libTransitive := result.ModuleForTests("libtransitive", sharedVariant)
+		libShared := result.ModuleForTests(t, "libshared", sharedVariant)
+		libTransitive := result.ModuleForTests(t, "libtransitive", sharedVariant)
 
 		// Shared libraries that don't request fuzzer
-		libSharedFuzzer := result.ModuleForTests("libshared", sharedFuzzerVariant)
-		libTransitiveFuzzer := result.ModuleForTests("libtransitive", sharedFuzzerVariant)
+		libSharedFuzzer := result.ModuleForTests(t, "libshared", sharedFuzzerVariant)
+		libTransitiveFuzzer := result.ModuleForTests(t, "libtransitive", sharedFuzzerVariant)
 
 		// Shared library that requests fuzzer
-		libFuzzer := result.ModuleForTests("libfuzzer", sharedFuzzerVariant)
+		libFuzzer := result.ModuleForTests(t, "libfuzzer", sharedFuzzerVariant)
 
 		// Static library that uses an fuzzer variant for bin_with_fuzzer and a non-fuzzer variant
 		// for bin_no_fuzzer.
-		libStaticFuzzerVariant := result.ModuleForTests("libstatic", staticFuzzerVariant)
-		libStaticNoFuzzerVariant := result.ModuleForTests("libstatic", staticVariant)
+		libStaticFuzzerVariant := result.ModuleForTests(t, "libstatic", staticFuzzerVariant)
+		libStaticNoFuzzerVariant := result.ModuleForTests(t, "libstatic", staticVariant)
 
 		// Static library that never uses fuzzer.
-		libNoFuzzer := result.ModuleForTests("libnofuzzer", staticVariant)
+		libNoFuzzer := result.ModuleForTests(t, "libnofuzzer", staticVariant)
 
 		// Static library that specifies fuzzer
-		libStaticFuzzer := result.ModuleForTests("libstatic_fuzzer", staticFuzzerVariant)
-		libStaticFuzzerNoFuzzerVariant := result.ModuleForTests("libstatic_fuzzer", staticVariant)
+		libStaticFuzzer := result.ModuleForTests(t, "libstatic_fuzzer", staticFuzzerVariant)
+		libStaticFuzzerNoFuzzerVariant := result.ModuleForTests(t, "libstatic_fuzzer", staticVariant)
 
 		expectSharedLinkDep(t, ctx, binWithFuzzer, libSharedFuzzer)
 		expectSharedLinkDep(t, ctx, binWithFuzzer, libFuzzer)
@@ -781,16 +781,16 @@
 		staticVariant := variant + "_static"
 		sharedVariant := variant + "_shared"
 
-		minimalRuntime := result.ModuleForTests("libclang_rt.ubsan_minimal", staticVariant)
-		standaloneRuntime := result.ModuleForTests("libclang_rt.ubsan_standalone.static", staticVariant)
+		minimalRuntime := result.ModuleForTests(t, "libclang_rt.ubsan_minimal", staticVariant)
+		standaloneRuntime := result.ModuleForTests(t, "libclang_rt.ubsan_standalone.static", staticVariant)
 
 		// The binaries, one with ubsan and one without
-		binWithUbsan := result.ModuleForTests("bin_with_ubsan", variant)
-		binDependsUbsan := result.ModuleForTests("bin_depends_ubsan_static", variant)
-		libSharedUbsan := result.ModuleForTests("libsharedubsan", sharedVariant)
-		binDependsUbsanShared := result.ModuleForTests("bin_depends_ubsan_shared", variant)
-		binNoUbsan := result.ModuleForTests("bin_no_ubsan", variant)
-		staticBin := result.ModuleForTests("static_bin_with_ubsan_dep", variant)
+		binWithUbsan := result.ModuleForTests(t, "bin_with_ubsan", variant)
+		binDependsUbsan := result.ModuleForTests(t, "bin_depends_ubsan_static", variant)
+		libSharedUbsan := result.ModuleForTests(t, "libsharedubsan", sharedVariant)
+		binDependsUbsanShared := result.ModuleForTests(t, "bin_depends_ubsan_shared", variant)
+		binNoUbsan := result.ModuleForTests(t, "bin_no_ubsan", variant)
+		staticBin := result.ModuleForTests(t, "static_bin_with_ubsan_dep", variant)
 
 		android.AssertStringListContains(t, "missing libclang_rt.ubsan_minimal in bin_with_ubsan static libs",
 			strings.Split(binWithUbsan.Rule("ld").Args["libFlags"], " "),
@@ -979,67 +979,67 @@
 	).RunTest(t)
 	ctx := result.TestContext
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_no_override", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_async", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_sync", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_override_default_sync", variant), None)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_no_override", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_async", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_sync", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_override_default_sync", variant), None)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_no_override", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_async", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_disable", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_no_override", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_async", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_disable", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_sync", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_override_default_sync", variant), Async)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_no_override", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_async", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_disable", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_sync", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_override_default_sync", variant), Async)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_override_default_sync", variant), Sync)
 
 	// should sanitize: { diag: { memtag: true } } result in Sync instead of None here?
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_no_override", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_override_default_async", variant), Sync)
 	// should sanitize: { diag: { memtag: true } } result in Sync instead of None here?
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_no_override", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_async", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_override_default_sync", variant), Sync)
 }
 
 func TestSanitizeMemtagHeapWithSanitizeDevice(t *testing.T) {
@@ -1055,66 +1055,66 @@
 	).RunTest(t)
 	ctx := result.TestContext
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_no_override", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_async", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_sync", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_override_default_sync", variant), None)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_no_override", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_async", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_sync", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_override_default_sync", variant), None)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_no_override", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_async", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_disable", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_no_override", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_async", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_disable", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_sync", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_override_default_sync", variant), Async)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_no_override", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_async", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_disable", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_sync", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_override_default_sync", variant), Async)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_override_default_async", variant), Sync)
 	// should sanitize: { diag: { memtag: true } } result in Sync instead of None here?
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_no_override", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_async", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_override_default_sync", variant), Sync)
 }
 
 func TestSanitizeMemtagHeapWithSanitizeDeviceDiag(t *testing.T) {
@@ -1131,66 +1131,66 @@
 	).RunTest(t)
 	ctx := result.TestContext
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_no_override", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_async", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_sync", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_override_default_sync", variant), None)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_no_override", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_async", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_sync", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_override_default_sync", variant), None)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_no_override", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_async", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_disable", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_sync", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_override_default_sync", variant), Async)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_no_override", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_async", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_disable", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_sync", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_override_default_sync", variant), Async)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_override_default_async", variant), Sync)
 	// should sanitize: { diag: { memtag: true } } result in Sync instead of None here?
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_override_default_sync", variant), Sync)
 }
 
 func TestCfi(t *testing.T) {
@@ -1250,14 +1250,14 @@
 	cfi_suffix := "_cfi"
 	static_suffix := "_static"
 
-	sharedWithCfiLib := result.ModuleForTests("shared_with_cfi", buildOs+shared_suffix+cfi_suffix)
-	sharedNoCfiLib := result.ModuleForTests("shared_no_cfi", buildOs+shared_suffix)
-	staticWithCfiLib := result.ModuleForTests("static_dep_with_cfi", buildOs+static_suffix)
-	staticWithCfiLibCfiVariant := result.ModuleForTests("static_dep_with_cfi", buildOs+static_suffix+cfi_suffix)
-	staticNoCfiLib := result.ModuleForTests("static_dep_no_cfi", buildOs+static_suffix)
-	staticNoCfiLibCfiVariant := result.ModuleForTests("static_dep_no_cfi", buildOs+static_suffix+cfi_suffix)
-	sharedRdepNoCfi := result.ModuleForTests("shared_rdep_no_cfi", buildOs+shared_suffix)
-	staticDepWithCfi2Lib := result.ModuleForTests("static_dep_with_cfi_2", buildOs+static_suffix)
+	sharedWithCfiLib := result.ModuleForTests(t, "shared_with_cfi", buildOs+shared_suffix+cfi_suffix)
+	sharedNoCfiLib := result.ModuleForTests(t, "shared_no_cfi", buildOs+shared_suffix)
+	staticWithCfiLib := result.ModuleForTests(t, "static_dep_with_cfi", buildOs+static_suffix)
+	staticWithCfiLibCfiVariant := result.ModuleForTests(t, "static_dep_with_cfi", buildOs+static_suffix+cfi_suffix)
+	staticNoCfiLib := result.ModuleForTests(t, "static_dep_no_cfi", buildOs+static_suffix)
+	staticNoCfiLibCfiVariant := result.ModuleForTests(t, "static_dep_no_cfi", buildOs+static_suffix+cfi_suffix)
+	sharedRdepNoCfi := result.ModuleForTests(t, "shared_rdep_no_cfi", buildOs+shared_suffix)
+	staticDepWithCfi2Lib := result.ModuleForTests(t, "static_dep_with_cfi_2", buildOs+static_suffix)
 
 	// Confirm assumptions about propagation of CFI enablement
 	expectStaticLinkDep(t, ctx, sharedWithCfiLib, staticWithCfiLibCfiVariant)
diff --git a/cc/sdk_test.go b/cc/sdk_test.go
index 61925e3..664a7c6 100644
--- a/cc/sdk_test.go
+++ b/cc/sdk_test.go
@@ -81,16 +81,16 @@
 
 	ctx := testCc(t, bp)
 
-	libsdkNDK := ctx.ModuleForTests("libsdk", "android_arm64_armv8-a_sdk_shared")
-	libsdkPlatform := ctx.ModuleForTests("libsdk", "android_arm64_armv8-a_shared")
-	libsdkdepNDK := ctx.ModuleForTests("libsdkdep", "android_arm64_armv8-a_sdk_shared")
-	libsdkdepPlatform := ctx.ModuleForTests("libsdkdep", "android_arm64_armv8-a_shared")
-	libplatform := ctx.ModuleForTests("libplatform", "android_arm64_armv8-a_shared")
-	platformbinary := ctx.ModuleForTests("platformbinary", "android_arm64_armv8-a")
-	sdkbinary := ctx.ModuleForTests("sdkbinary", "android_arm64_armv8-a_sdk")
+	libsdkNDK := ctx.ModuleForTests(t, "libsdk", "android_arm64_armv8-a_sdk_shared")
+	libsdkPlatform := ctx.ModuleForTests(t, "libsdk", "android_arm64_armv8-a_shared")
+	libsdkdepNDK := ctx.ModuleForTests(t, "libsdkdep", "android_arm64_armv8-a_sdk_shared")
+	libsdkdepPlatform := ctx.ModuleForTests(t, "libsdkdep", "android_arm64_armv8-a_shared")
+	libplatform := ctx.ModuleForTests(t, "libplatform", "android_arm64_armv8-a_shared")
+	platformbinary := ctx.ModuleForTests(t, "platformbinary", "android_arm64_armv8-a")
+	sdkbinary := ctx.ModuleForTests(t, "sdkbinary", "android_arm64_armv8-a_sdk")
 
-	libcxxNDK := ctx.ModuleForTests("ndk_libc++_shared", "android_arm64_armv8-a_sdk_shared")
-	libcxxPlatform := ctx.ModuleForTests("libc++", "android_arm64_armv8-a_shared")
+	libcxxNDK := ctx.ModuleForTests(t, "ndk_libc++_shared", "android_arm64_armv8-a_sdk_shared")
+	libcxxPlatform := ctx.ModuleForTests(t, "libc++", "android_arm64_armv8-a_shared")
 
 	assertDep(t, libsdkNDK, libsdkdepNDK)
 	assertDep(t, libsdkPlatform, libsdkdepPlatform)
diff --git a/cc/strip.go b/cc/strip.go
index b1f34bb..a950df8 100644
--- a/cc/strip.go
+++ b/cc/strip.go
@@ -23,10 +23,8 @@
 // StripProperties defines the type of stripping applied to the module.
 type StripProperties struct {
 	Strip struct {
-		// none forces all stripping to be disabled.
-		// Device modules default to stripping enabled leaving mini debuginfo.
-		// Host modules default to stripping disabled, but can be enabled by setting any other
-		// strip boolean property.
+		// Device and host modules default to stripping enabled leaving mini debuginfo.
+		// This can be disabled by setting none to true.
 		None *bool `android:"arch_variant"`
 
 		// all forces stripping everything, including the mini debug info.
@@ -52,11 +50,7 @@
 // NeedsStrip determines if stripping is required for a module.
 func (stripper *Stripper) NeedsStrip(actx android.ModuleContext) bool {
 	forceDisable := Bool(stripper.StripProperties.Strip.None)
-	defaultEnable := (!actx.Config().KatiEnabled() || actx.Device())
-	forceEnable := Bool(stripper.StripProperties.Strip.All) ||
-		Bool(stripper.StripProperties.Strip.Keep_symbols) ||
-		Bool(stripper.StripProperties.Strip.Keep_symbols_and_debug_frame)
-	return !forceDisable && (forceEnable || defaultEnable)
+	return !forceDisable
 }
 
 func (stripper *Stripper) strip(actx android.ModuleContext, in android.Path, out android.ModuleOutPath,
diff --git a/cc/stub_library.go b/cc/stub_library.go
index e746a33..9d7b5bc 100644
--- a/cc/stub_library.go
+++ b/cc/stub_library.go
@@ -26,20 +26,24 @@
 	android.RegisterParallelSingletonType("stublibraries", stubLibrariesSingleton)
 }
 
+func stubLibrariesSingleton() android.Singleton {
+	return &stubLibraries{}
+}
+
 type stubLibraries struct {
-	stubLibraryMap       map[string]bool
-	stubVendorLibraryMap map[string]bool
+	stubLibraries       []string
+	vendorStubLibraries []string
 
 	apiListCoverageXmlPaths []string
 }
 
 // Check if the module defines stub, or itself is stub
-func IsStubTarget(m *Module) bool {
-	return m.IsStubs() || m.HasStubsVariants()
+func IsStubTarget(info *LinkableInfo) bool {
+	return info != nil && (info.IsStubs || info.HasStubsVariants)
 }
 
 // Get target file name to be installed from this module
-func getInstalledFileName(ctx android.SingletonContext, m *Module) string {
+func getInstalledFileName(ctx android.SingletonContext, m LinkableInterface) string {
 	for _, ps := range android.OtherModuleProviderOrDefault(
 		ctx, m.Module(), android.InstallFilesProvider).PackagingSpecs {
 		if name := ps.FileName(); name != "" {
@@ -51,36 +55,39 @@
 
 func (s *stubLibraries) GenerateBuildActions(ctx android.SingletonContext) {
 	// Visit all generated soong modules and store stub library file names.
+	stubLibraryMap := make(map[string]bool)
+	vendorStubLibraryMap := make(map[string]bool)
 	ctx.VisitAllModules(func(module android.Module) {
-		if m, ok := module.(*Module); ok {
-			if IsStubTarget(m) {
+		if m, ok := module.(VersionedLinkableInterface); ok {
+			if IsStubTarget(android.OtherModuleProviderOrDefault(ctx, m, LinkableInfoProvider)) {
 				if name := getInstalledFileName(ctx, m); name != "" {
-					s.stubLibraryMap[name] = true
+					stubLibraryMap[name] = true
 					if m.InVendor() {
-						s.stubVendorLibraryMap[name] = true
+						vendorStubLibraryMap[name] = true
 					}
 				}
 			}
-			if m.library != nil && android.IsModulePreferred(m) {
-				if p := m.library.getAPIListCoverageXMLPath().String(); p != "" {
+			if m.CcLibraryInterface() && android.IsModulePreferred(m) {
+				if p := m.VersionedInterface().GetAPIListCoverageXMLPath().String(); p != "" {
 					s.apiListCoverageXmlPaths = append(s.apiListCoverageXmlPaths, p)
 				}
 			}
 		}
 	})
+	s.stubLibraries = android.SortedKeys(stubLibraryMap)
+	s.vendorStubLibraries = android.SortedKeys(vendorStubLibraryMap)
+
+	android.WriteFileRule(ctx, StubLibrariesFile(ctx), strings.Join(s.stubLibraries, " "))
 }
 
-func stubLibrariesSingleton() android.Singleton {
-	return &stubLibraries{
-		stubLibraryMap:       make(map[string]bool),
-		stubVendorLibraryMap: make(map[string]bool),
-	}
+func StubLibrariesFile(ctx android.PathContext) android.WritablePath {
+	return android.PathForIntermediates(ctx, "stub_libraries.txt")
 }
 
 func (s *stubLibraries) MakeVars(ctx android.MakeVarsContext) {
 	// Convert stub library file names into Makefile variable.
-	ctx.Strict("STUB_LIBRARIES", strings.Join(android.SortedKeys(s.stubLibraryMap), " "))
-	ctx.Strict("SOONG_STUB_VENDOR_LIBRARIES", strings.Join(android.SortedKeys(s.stubVendorLibraryMap), " "))
+	ctx.Strict("STUB_LIBRARIES", strings.Join(s.stubLibraries, " "))
+	ctx.Strict("SOONG_STUB_VENDOR_LIBRARIES", strings.Join(s.vendorStubLibraries, " "))
 
 	// Export the list of API XML files to Make.
 	sort.Strings(s.apiListCoverageXmlPaths)
diff --git a/cc/symbolfile/__init__.py b/cc/symbolfile/__init__.py
index 4553616..f2bd186 100644
--- a/cc/symbolfile/__init__.py
+++ b/cc/symbolfile/__init__.py
@@ -103,24 +103,13 @@
     @property
     def has_llndk_tags(self) -> bool:
         """Returns True if any LL-NDK tags are set."""
-        for tag in self.tags:
-            if tag == 'llndk' or tag.startswith('llndk='):
-                return True
-        return False
+        return 'llndk' in self.tags
 
     @property
     def has_platform_only_tags(self) -> bool:
         """Returns True if any platform-only tags are set."""
         return 'platform-only' in self.tags
 
-    def copy_introduced_from(self, tags: Tags) -> None:
-        """Copies introduced= or introduced-*= tags."""
-        for tag in tags:
-            if tag.startswith('introduced=') or tag.startswith('introduced-'):
-                name, _ = split_tag(tag)
-                if not any(self_tag.startswith(name + '=') for self_tag in self.tags):
-                    self.tags += (tag,)
-
 
 @dataclass
 class Symbol:
@@ -158,8 +147,6 @@
     """Returns true if this tag has an API level that may need decoding."""
     if tag.startswith('llndk-deprecated='):
         return True
-    if tag.startswith('llndk='):
-        return True
     if tag.startswith('introduced='):
         return True
     if tag.startswith('introduced-'):
@@ -245,21 +232,19 @@
         self.systemapi = systemapi
         self.ndk = ndk
 
+    def _symbol_in_arch_api(self, tags: Tags) -> bool:
+        if not symbol_in_arch(tags, self.arch):
+            return True
+        if not symbol_in_api(tags, self.arch, self.api):
+            return True
+        return False
+
     def _should_omit_tags(self, tags: Tags) -> bool:
         """Returns True if the tagged object should be omitted.
 
         This defines the rules shared between version tagging and symbol tagging.
         """
-        # LLNDK mode/tags follow the similar filtering except that API level checking
-        # is based llndk= instead of introduced=.
-        if self.llndk:
-            if tags.has_mode_tags and not tags.has_llndk_tags:
-                return True
-            if not symbol_in_arch(tags, self.arch):
-                return True
-            if not symbol_in_llndk_api(tags, self.arch, self.api):
-                return True
-            return False
+        # The apex and llndk tags will only exclude APIs from other modes. If in
         # APEX or LLNDK mode and neither tag is provided, we fall back to the
         # default behavior because all NDK symbols are implicitly available to
         # APEX and LLNDK.
@@ -268,12 +253,10 @@
                 return False
             if self.systemapi and tags.has_systemapi_tags:
                 return False
+            if self.llndk and tags.has_llndk_tags:
+                return self._symbol_in_arch_api(tags)
             return True
-        if not symbol_in_arch(tags, self.arch):
-            return True
-        if not symbol_in_api(tags, self.arch, self.api):
-            return True
-        return False
+        return self._symbol_in_arch_api(tags)
 
     def should_omit_version(self, version: Version) -> bool:
         """Returns True if the version section should be omitted.
@@ -286,10 +269,6 @@
             return True
         if version.tags.has_platform_only_tags:
             return True
-        # Include all versions when targeting LLNDK because LLNDK symbols are self-versioned.
-        # Empty version block will be handled separately.
-        if self.llndk:
-            return False
         return self._should_omit_tags(version.tags)
 
     def should_omit_symbol(self, symbol: Symbol) -> bool:
@@ -302,6 +281,7 @@
 
         return self._should_omit_tags(symbol.tags)
 
+
 def symbol_in_arch(tags: Tags, arch: Arch) -> bool:
     """Returns true if the symbol is present for the given architecture."""
     has_arch_tags = False
@@ -316,14 +296,6 @@
     # for the tagged architectures.
     return not has_arch_tags
 
-def symbol_in_llndk_api(tags: Iterable[Tag], arch: Arch, api: int) -> bool:
-    """Returns true if the symbol is present for the given LLNDK API level."""
-    # Check llndk= first.
-    for tag in tags:
-        if tag.startswith('llndk='):
-            return api >= int(get_tag_value(tag))
-    # If not, we keep old behavior: NDK symbols in <= 34 are LLNDK symbols.
-    return symbol_in_api(tags, arch, 34)
 
 def symbol_in_api(tags: Iterable[Tag], arch: Arch, api: int) -> bool:
     """Returns true if the symbol is present for the given API level."""
@@ -400,7 +372,6 @@
                     f'Unexpected contents at top level: {self.current_line}')
 
         self.check_no_duplicate_symbols(versions)
-        self.check_llndk_introduced(versions)
         return versions
 
     def check_no_duplicate_symbols(self, versions: Iterable[Version]) -> None:
@@ -429,31 +400,6 @@
             raise MultiplyDefinedSymbolError(
                 sorted(list(multiply_defined_symbols)))
 
-    def check_llndk_introduced(self, versions: Iterable[Version]) -> None:
-        """Raises errors when llndk= is missing for new llndk symbols."""
-        if not self.filter.llndk:
-            return
-
-        def assert_llndk_with_version(tags: Tags,  name: str) -> None:
-            has_llndk_introduced = False
-            for tag in tags:
-                if tag.startswith('llndk='):
-                    has_llndk_introduced = True
-                    break
-            if not has_llndk_introduced:
-                raise ParseError(f'{name}: missing version. `llndk=yyyymm`')
-
-        arch = self.filter.arch
-        for version in versions:
-            # llndk symbols >= introduced=35 should be tagged
-            # explicitly with llndk=yyyymm.
-            for symbol in version.symbols:
-                if not symbol.tags.has_llndk_tags:
-                    continue
-                if symbol_in_api(symbol.tags, arch, 34):
-                    continue
-                assert_llndk_with_version(symbol.tags, symbol.name)
-
     def parse_version(self) -> Version:
         """Parses a single version section and returns a Version object."""
         assert self.current_line is not None
@@ -487,9 +433,7 @@
                 else:
                     raise ParseError('Unknown visiblity label: ' + visibility)
             elif global_scope and not cpp_symbols:
-                symbol = self.parse_symbol()
-                symbol.tags.copy_introduced_from(tags)
-                symbols.append(symbol)
+                symbols.append(self.parse_symbol())
             else:
                 # We're in a hidden scope or in 'extern "C++"' block. Ignore
                 # everything.
diff --git a/cc/symbolfile/test_symbolfile.py b/cc/symbolfile/test_symbolfile.py
index 8b412b9..14bb737 100644
--- a/cc/symbolfile/test_symbolfile.py
+++ b/cc/symbolfile/test_symbolfile.py
@@ -344,45 +344,6 @@
         self.assertInclude(f_llndk, s_none)
         self.assertInclude(f_llndk, s_llndk)
 
-    def test_omit_llndk_versioned(self) -> None:
-        f_ndk = self.filter
-        f_ndk.api = 35
-
-        f_llndk = copy(f_ndk)
-        f_llndk.llndk = True
-        f_llndk.api = 202404
-
-        s = Symbol('foo', Tags())
-        s_llndk = Symbol('foo', Tags.from_strs(['llndk']))
-        s_llndk_202404 = Symbol('foo', Tags.from_strs(['llndk=202404']))
-        s_34 = Symbol('foo', Tags.from_strs(['introduced=34']))
-        s_34_llndk = Symbol('foo', Tags.from_strs(['introduced=34', 'llndk']))
-        s_35 = Symbol('foo', Tags.from_strs(['introduced=35']))
-        s_35_llndk_202404 = Symbol('foo', Tags.from_strs(['introduced=35', 'llndk=202404']))
-        s_35_llndk_202504 = Symbol('foo', Tags.from_strs(['introduced=35', 'llndk=202504']))
-
-        # When targeting NDK, omit LLNDK tags
-        self.assertInclude(f_ndk, s)
-        self.assertOmit(f_ndk, s_llndk)
-        self.assertOmit(f_ndk, s_llndk_202404)
-        self.assertInclude(f_ndk, s_34)
-        self.assertOmit(f_ndk, s_34_llndk)
-        self.assertInclude(f_ndk, s_35)
-        self.assertOmit(f_ndk, s_35_llndk_202404)
-        self.assertOmit(f_ndk, s_35_llndk_202504)
-
-        # When targeting LLNDK, old symbols without any mode tags are included as LLNDK
-        self.assertInclude(f_llndk, s)
-        # When targeting LLNDK, old symbols with #llndk are included as LLNDK
-        self.assertInclude(f_llndk, s_llndk)
-        self.assertInclude(f_llndk, s_llndk_202404)
-        self.assertInclude(f_llndk, s_34)
-        self.assertInclude(f_llndk, s_34_llndk)
-        # When targeting LLNDK, new symbols(>=35) should be tagged with llndk-introduced=.
-        self.assertOmit(f_llndk, s_35)
-        self.assertInclude(f_llndk, s_35_llndk_202404)
-        self.assertOmit(f_llndk, s_35_llndk_202504)
-
     def test_omit_apex(self) -> None:
         f_none = self.filter
         f_apex = copy(f_none)
@@ -494,8 +455,8 @@
         # should_omit_tags() can differently based on introduced API level when treating
         # LLNDK-available symbols.
         expected_symbols = [
-            Symbol('baz', Tags.from_strs(['introduced=35'])),
-            Symbol('qux', Tags.from_strs(['apex', 'llndk', 'introduced=35'])),
+            Symbol('baz', Tags()),
+            Symbol('qux', Tags.from_strs(['apex', 'llndk'])),
         ]
         self.assertEqual(expected_symbols, version.symbols)
 
@@ -643,19 +604,6 @@
         ]
         self.assertEqual(expected_symbols, version.symbols)
 
-    def test_parse_llndk_version_is_missing(self) -> None:
-        input_file = io.StringIO(textwrap.dedent("""\
-            VERSION_1 { # introduced=35
-                foo;
-                bar; # llndk
-            };
-        """))
-        f = copy(self.filter)
-        f.llndk = True
-        parser = symbolfile.SymbolFileParser(input_file, {}, f)
-        with self.assertRaises(symbolfile.ParseError):
-            parser.parse()
-
 
 def main() -> None:
     suite = unittest.TestLoader().loadTestsFromName(__name__)
diff --git a/cc/test.go b/cc/test.go
index f5bb761..b3b2ae8 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -15,10 +15,12 @@
 package cc
 
 import (
-	"github.com/google/blueprint/proptools"
 	"path/filepath"
 	"strconv"
 
+	"github.com/google/blueprint/depset"
+	"github.com/google/blueprint/proptools"
+
 	"android/soong/android"
 	"android/soong/tradefed"
 )
@@ -28,8 +30,8 @@
 	// if set, build against the gtest library. Defaults to true.
 	Gtest *bool
 
-	// if set, use the isolated gtest runner. Defaults to true if gtest is also true and the arch is Windows, false
-	// otherwise.
+	// if set, use the isolated gtest runner. Defaults to false.
+	// Isolation is not supported on Windows.
 	Isolated *bool
 }
 
@@ -82,6 +84,16 @@
 	// the test
 	Data []string `android:"path,arch_variant"`
 
+	// Same as data, but adds dependencies on modules using the device's os variant, and common
+	// architecture's variant. Can be useful to add device-built apps to the data of a host
+	// test.
+	Device_common_data []string `android:"path_device_common"`
+
+	// Same as data, but adds dependencies on modules using the device's os variant, and the
+	// device's first architecture's variant. Can be useful to add device-built apps to the data
+	// of a host test.
+	Device_first_data []string `android:"path_device_first"`
+
 	// list of shared library modules that should be installed alongside the test
 	Data_libs []string `android:"arch_variant"`
 
@@ -117,6 +129,13 @@
 
 	// Install the test into a folder named for the module in all test suites.
 	Per_testcase_directory *bool
+
+	// Install the test's dependencies into a folder named standalone-libs relative to the
+	// test's installation path. ld-library-path will be set to this path in the test's
+	// auto-generated config. This way the dependencies can be used by the test without having
+	// to manually install them to the device. See more details in
+	// go/standalone-native-device-tests.
+	Standalone_test *bool
 }
 
 func init() {
@@ -187,8 +206,8 @@
 	return BoolDefault(test.LinkerProperties.Gtest, true)
 }
 
-func (test *testDecorator) isolated(ctx android.EarlyModuleContext) bool {
-	return BoolDefault(test.LinkerProperties.Isolated, false)
+func (test *testDecorator) isolated(ctx android.BaseModuleContext) bool {
+	return BoolDefault(test.LinkerProperties.Isolated, false) && !ctx.Windows()
 }
 
 // NOTE: Keep this in sync with cc/cc_test.bzl#gtest_copts
@@ -324,33 +343,35 @@
 
 func (test *testBinary) install(ctx ModuleContext, file android.Path) {
 	dataSrcPaths := android.PathsForModuleSrc(ctx, test.Properties.Data)
+	dataSrcPaths = append(dataSrcPaths, android.PathsForModuleSrc(ctx, test.Properties.Device_common_data)...)
+	dataSrcPaths = append(dataSrcPaths, android.PathsForModuleSrc(ctx, test.Properties.Device_first_data)...)
 
 	for _, dataSrcPath := range dataSrcPaths {
 		test.data = append(test.data, android.DataPath{SrcPath: dataSrcPath})
 	}
 
-	ctx.VisitDirectDepsWithTag(dataLibDepTag, func(dep android.Module) {
+	ctx.VisitDirectDepsProxyWithTag(dataLibDepTag, func(dep android.ModuleProxy) {
 		depName := ctx.OtherModuleName(dep)
-		linkableDep, ok := dep.(LinkableInterface)
+		linkableDep, ok := android.OtherModuleProvider(ctx, dep, LinkableInfoProvider)
 		if !ok {
 			ctx.ModuleErrorf("data_lib %q is not a LinkableInterface module", depName)
 		}
-		if linkableDep.OutputFile().Valid() {
+		if linkableDep.OutputFile.Valid() {
 			test.data = append(test.data,
-				android.DataPath{SrcPath: linkableDep.OutputFile().Path(),
-					RelativeInstallPath: linkableDep.RelativeInstallPath()})
+				android.DataPath{SrcPath: linkableDep.OutputFile.Path(),
+					RelativeInstallPath: linkableDep.RelativeInstallPath})
 		}
 	})
-	ctx.VisitDirectDepsWithTag(dataBinDepTag, func(dep android.Module) {
+	ctx.VisitDirectDepsProxyWithTag(dataBinDepTag, func(dep android.ModuleProxy) {
 		depName := ctx.OtherModuleName(dep)
-		linkableDep, ok := dep.(LinkableInterface)
+		linkableDep, ok := android.OtherModuleProvider(ctx, dep, LinkableInfoProvider)
 		if !ok {
 			ctx.ModuleErrorf("data_bin %q is not a LinkableInterface module", depName)
 		}
-		if linkableDep.OutputFile().Valid() {
+		if linkableDep.OutputFile.Valid() {
 			test.data = append(test.data,
-				android.DataPath{SrcPath: linkableDep.OutputFile().Path(),
-					RelativeInstallPath: linkableDep.RelativeInstallPath()})
+				android.DataPath{SrcPath: linkableDep.OutputFile.Path(),
+					RelativeInstallPath: linkableDep.RelativeInstallPath})
 		}
 	})
 
@@ -367,6 +388,7 @@
 		TestInstallBase:        testInstallBase,
 		DeviceTemplate:         "${NativeTestConfigTemplate}",
 		HostTemplate:           "${NativeHostTestConfigTemplate}",
+		StandaloneTest:         test.Properties.Standalone_test,
 	})
 
 	test.extraTestConfigs = android.PathsForModuleSrc(ctx, test.Properties.Test_options.Extra_test_configs)
@@ -384,8 +406,48 @@
 		test.Properties.Test_options.Unit_test = proptools.BoolPtr(true)
 	}
 
+	if !ctx.Config().KatiEnabled() { // TODO(spandandas): Remove the special case for kati
+		// Install the test config in testcases/ directory for atest.
+		c, ok := ctx.Module().(*Module)
+		if !ok {
+			ctx.ModuleErrorf("Not a cc_test module")
+		}
+		// Install configs in the root of $PRODUCT_OUT/testcases/$module
+		testCases := android.PathForModuleInPartitionInstall(ctx, "testcases", ctx.ModuleName()+c.SubName())
+		if ctx.PrimaryArch() {
+			if test.testConfig != nil {
+				ctx.InstallFile(testCases, ctx.ModuleName()+".config", test.testConfig)
+			}
+			for _, extraTestConfig := range test.extraTestConfigs {
+				ctx.InstallFile(testCases, extraTestConfig.Base(), extraTestConfig)
+			}
+		}
+		// Install tests and data in arch specific subdir $PRODUCT_OUT/testcases/$module/$arch
+		testCases = testCases.Join(ctx, ctx.Target().Arch.ArchType.String())
+		ctx.InstallTestData(testCases, test.data)
+		ctx.InstallFile(testCases, file.Base(), file)
+	}
+
 	test.binaryDecorator.baseInstaller.installTestData(ctx, test.data)
 	test.binaryDecorator.baseInstaller.install(ctx, file)
+	if Bool(test.Properties.Standalone_test) {
+		packagingSpecsBuilder := depset.NewBuilder[android.PackagingSpec](depset.TOPOLOGICAL)
+
+		ctx.VisitDirectDeps(func(dep android.Module) {
+			deps := android.OtherModuleProviderOrDefault(ctx, dep, android.InstallFilesProvider)
+			packagingSpecsBuilder.Transitive(deps.TransitivePackagingSpecs)
+		})
+
+		for _, standaloneTestDep := range packagingSpecsBuilder.Build().ToList() {
+			if standaloneTestDep.ToGob().SrcPath == nil {
+				continue
+			}
+			if standaloneTestDep.SkipInstall() {
+				continue
+			}
+			test.binaryDecorator.baseInstaller.installStandaloneTestDep(ctx, standaloneTestDep)
+		}
+	}
 }
 
 func getTestInstallBase(useVendor bool) string {
diff --git a/cc/test_data_test.go b/cc/test_data_test.go
index a621166..2c03ca4 100644
--- a/cc/test_data_test.go
+++ b/cc/test_data_test.go
@@ -132,7 +132,7 @@
 			_, errs = ctx.PrepareBuildActions(config)
 			android.FailIfErrored(t, errs)
 
-			foo := ctx.ModuleForTests("foo", "")
+			foo := ctx.ModuleForTests(t, "foo", "")
 
 			got := foo.Module().(*testDataTest).data
 			if len(got) != len(test.data) {
diff --git a/cc/testing.go b/cc/testing.go
index 14a6b7a..69ae11d 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -193,7 +193,7 @@
 			},
 			apex_available: [
 				"//apex_available:platform",
-				"myapex"
+				"//apex_available:anyapex",
 			],
 			llndk: {
 				symbol_file: "libm.map.txt",
@@ -253,7 +253,7 @@
 			},
 			apex_available: [
 				"//apex_available:platform",
-				"myapex"
+				"//apex_available:anyapex",
 			],
 			llndk: {
 				symbol_file: "libdl.map.txt",
@@ -708,7 +708,7 @@
 
 func checkSnapshotIncludeExclude(t *testing.T, ctx *android.TestContext, singleton android.TestingSingleton, moduleName, snapshotFilename, subDir, variant string, include bool, fake bool) {
 	t.Helper()
-	mod := ctx.ModuleForTests(moduleName, variant)
+	mod := ctx.ModuleForTests(t, moduleName, variant)
 	outputFiles := mod.OutputFiles(ctx, t, "")
 	if len(outputFiles) != 1 {
 		t.Errorf("%q must have single output\n", moduleName)
@@ -750,9 +750,10 @@
 	checkSnapshotIncludeExclude(t, ctx, singleton, moduleName, snapshotFilename, subDir, variant, true, true)
 }
 
-func GetOutputPaths(ctx *android.TestContext, variant string, moduleNames []string) (paths android.Paths) {
+func GetOutputPaths(t *testing.T, ctx *android.TestContext, variant string, moduleNames []string) (paths android.Paths) {
+	t.Helper()
 	for _, moduleName := range moduleNames {
-		module := ctx.ModuleForTests(moduleName, variant).Module().(*Module)
+		module := ctx.ModuleForTests(t, moduleName, variant).Module().(*Module)
 		output := module.outputFile.Path().RelativeToTop()
 		paths = append(paths, output)
 	}
diff --git a/cc/tidy.go b/cc/tidy.go
index ec1e8a2..2373658 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.CommonModuleInfoKey).Target.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/cc/tidy_test.go b/cc/tidy_test.go
index 9481778..afc12b8 100644
--- a/cc/tidy_test.go
+++ b/cc/tidy_test.go
@@ -83,7 +83,7 @@
 		variant := "android_arm64_armv8-a_shared"
 		ctx := testCc(t, test.bp)
 		t.Run("caseTidyFlags", func(t *testing.T) {
-			flags := ctx.ModuleForTests(test.libName, variant).Rule("clangTidy").Args["tidyFlags"]
+			flags := ctx.ModuleForTests(t, test.libName, variant).Rule("clangTidy").Args["tidyFlags"]
 			for _, flag := range test.flags {
 				if !strings.Contains(flags, flag) {
 					t.Errorf("tidyFlags %v for %s does not contain %s.", flags, test.libName, flag)
@@ -143,7 +143,7 @@
 		variant := "android_arm64_armv8-a_shared"
 		for _, test := range testCases {
 			libName := fmt.Sprintf("libfoo_%d", test.libNumber)
-			flags := ctx.ModuleForTests(libName, variant).Rule("clangTidy").Args["tidyFlags"]
+			flags := ctx.ModuleForTests(t, libName, variant).Rule("clangTidy").Args["tidyFlags"]
 			splitFlags := strings.Split(flags, " ")
 			foundCheckFlag := false
 			for _, flag := range splitFlags {
@@ -231,7 +231,7 @@
 				checkLibraryRule := func(foo, variant, ruleName string) {
 					libName := fmt.Sprintf("lib%s_%d", foo, n)
 					tidyFile := "out/soong/.intermediates/" + libName + "/" + variant + "/obj/" + foo + ".tidy"
-					depFiles := ctx.ModuleForTests(libName, variant).Rule(ruleName).Validations.Strings()
+					depFiles := ctx.ModuleForTests(t, libName, variant).Rule(ruleName).Validations.Strings()
 					if test.needTidyFile[n] {
 						android.AssertStringListContains(t, libName+" needs .tidy file", depFiles, tidyFile)
 					} else {
@@ -262,7 +262,7 @@
 	ctx := android.GroupFixturePreparers(prepareForCcTest, android.FixtureMergeEnv(testEnv)).RunTestWithBp(t, bp)
 
 	t.Run("tidy should be only run for source code, not for generated code", func(t *testing.T) {
-		depFiles := ctx.ModuleForTests("libfoo", variant).Rule("ld").Validations.Strings()
+		depFiles := ctx.ModuleForTests(t, "libfoo", variant).Rule("ld").Validations.Strings()
 
 		tidyFileForCpp := "out/soong/.intermediates/libfoo/" + variant + "/obj/foo_src.tidy"
 
diff --git a/cc/vendor_public_library_test.go b/cc/vendor_public_library_test.go
index 7385f2b..797bb25 100644
--- a/cc/vendor_public_library_test.go
+++ b/cc/vendor_public_library_test.go
@@ -70,32 +70,32 @@
 
 	// test if header search paths are correctly added
 	// _static variant is used since _shared reuses *.o from the static variant
-	cc := ctx.ModuleForTests("libsystem", strings.Replace(coreVariant, "_shared", "_static", 1)).Rule("cc")
+	cc := ctx.ModuleForTests(t, "libsystem", strings.Replace(coreVariant, "_shared", "_static", 1)).Rule("cc")
 	cflags := cc.Args["cFlags"]
 	if !strings.Contains(cflags, "-Imy_include") {
 		t.Errorf("cflags for libsystem must contain -Imy_include, but was %#v.", cflags)
 	}
 
 	// test if libsystem is linked to the stub
-	ld := ctx.ModuleForTests("libsystem", coreVariant).Rule("ld")
+	ld := ctx.ModuleForTests(t, "libsystem", coreVariant).Rule("ld")
 	libflags := ld.Args["libFlags"]
-	stubPaths := GetOutputPaths(ctx, coreVariant, []string{"libvendorpublic"})
+	stubPaths := GetOutputPaths(t, ctx, coreVariant, []string{"libvendorpublic"})
 	if !strings.Contains(libflags, stubPaths[0].String()) {
 		t.Errorf("libflags for libsystem must contain %#v, but was %#v", stubPaths[0], libflags)
 	}
 
 	// test if libsystem is linked to the stub
-	ld = ctx.ModuleForTests("libproduct", productVariant).Rule("ld")
+	ld = ctx.ModuleForTests(t, "libproduct", productVariant).Rule("ld")
 	libflags = ld.Args["libFlags"]
-	stubPaths = GetOutputPaths(ctx, productVariant, []string{"libvendorpublic"})
+	stubPaths = GetOutputPaths(t, ctx, productVariant, []string{"libvendorpublic"})
 	if !strings.Contains(libflags, stubPaths[0].String()) {
 		t.Errorf("libflags for libproduct must contain %#v, but was %#v", stubPaths[0], libflags)
 	}
 
 	// test if libvendor is linked to the real shared lib
-	ld = ctx.ModuleForTests("libvendor", vendorVariant).Rule("ld")
+	ld = ctx.ModuleForTests(t, "libvendor", vendorVariant).Rule("ld")
 	libflags = ld.Args["libFlags"]
-	stubPaths = GetOutputPaths(ctx, vendorVariant, []string{"libvendorpublic"})
+	stubPaths = GetOutputPaths(t, ctx, vendorVariant, []string{"libvendorpublic"})
 
 	if !strings.Contains(libflags, stubPaths[0].String()) {
 		t.Errorf("libflags for libvendor must contain %#v, but was %#v", stubPaths[0], libflags)
diff --git a/cc/vndk_prebuilt.go b/cc/vndk_prebuilt.go
index e7dff40..4a2adf0 100644
--- a/cc/vndk_prebuilt.go
+++ b/cc/vndk_prebuilt.go
@@ -166,6 +166,7 @@
 			Target:        ctx.Target(),
 
 			TableOfContents: p.tocFile,
+			IsStubs:         false,
 		})
 
 		p.libraryDecorator.flagExporter.setProvider(ctx)
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..65ef881
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta/main.go
@@ -0,0 +1,100 @@
+// 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"
+	"regexp"
+
+	fid_lib "android/soong/cmd/find_input_delta/find_input_delta_lib"
+)
+
+var fileSepRegex = regexp.MustCompile("[^[:space:]]+")
+
+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")
+	}
+	// Drop any extra file names that arrived in `target`.
+	target = fileSepRegex.FindString(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, fileSepRegex.FindAllString(string(data), -1)...)
+	}
+
+	// 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)
+	}
+
+	metrics_dir := os.Getenv("SOONG_METRICS_AGGREGATION_DIR")
+	out_dir := os.Getenv("OUT_DIR")
+	if metrics_dir != "" {
+		if err = file_list.WriteMetrics(metrics_dir, out_dir); err != nil {
+			panic(err)
+		}
+	}
+}
diff --git a/testing/code_metadata_internal_proto/Android.bp b/cmd/find_input_delta/find_input_delta_lib/Android.bp
similarity index 63%
copy from testing/code_metadata_internal_proto/Android.bp
copy to cmd/find_input_delta/find_input_delta_lib/Android.bp
index 396e44f..ef9c65b 100644
--- a/testing/code_metadata_internal_proto/Android.bp
+++ b/cmd/find_input_delta/find_input_delta_lib/Android.bp
@@ -1,4 +1,4 @@
-// Copyright 2022 Google Inc. All rights reserved.
+// 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.
@@ -17,17 +17,20 @@
 }
 
 bootstrap_go_package {
-    name: "soong-testing-code_metadata_internal_proto",
-    pkgPath: "android/soong/testing/code_metadata_internal_proto",
+    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",
+        "android-archive-zip",
+        "blueprint-pathtools",
     ],
     srcs: [
-        "code_metadata_internal.pb.go",
-    ],
-    visibility: [
-        "//build/make/tools/metadata",
-        "//build/soong:__subpackages__",
+        "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..f1d588b
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta_lib/file_list.go
@@ -0,0 +1,184 @@
+// 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 (
+	"fmt"
+	"hash/fnv"
+	"io"
+	"os"
+	"path/filepath"
+	"strings"
+	"text/template"
+
+	fid_exp "android/soong/cmd/find_input_delta/find_input_delta_proto"
+	"google.golang.org/protobuf/encoding/protowire"
+	"google.golang.org/protobuf/proto"
+)
+
+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
+
+	// Map of file_extension:counts
+	ExtCountMap map[string]*FileCounts
+
+	// Total number of added/changed/deleted files.
+	TotalDelta uint32
+}
+
+// The maximum number of files that will be recorded by name.
+var MaxFilesRecorded uint32 = 50
+
+type FileCounts struct {
+	Additions uint32
+	Deletions uint32
+	Changes   uint32
+}
+
+func FileListFactory(name string) *FileList {
+	return &FileList{
+		Name:        name,
+		ExtCountMap: make(map[string]*FileCounts),
+	}
+}
+
+func (fl *FileList) addFile(name string) {
+	fl.Additions = append(fl.Additions, name)
+	fl.TotalDelta += 1
+	ext := filepath.Ext(name)
+	if _, ok := fl.ExtCountMap[ext]; !ok {
+		fl.ExtCountMap[ext] = &FileCounts{}
+	}
+	fl.ExtCountMap[ext].Additions += 1
+}
+
+func (fl *FileList) deleteFile(name string) {
+	fl.Deletions = append(fl.Deletions, name)
+	fl.TotalDelta += 1
+	ext := filepath.Ext(name)
+	if _, ok := fl.ExtCountMap[ext]; !ok {
+		fl.ExtCountMap[ext] = &FileCounts{}
+	}
+	fl.ExtCountMap[ext].Deletions += 1
+}
+
+func (fl *FileList) changeFile(name string, ch *FileList) {
+	fl.Changes = append(fl.Changes, *ch)
+	fl.TotalDelta += 1
+	ext := filepath.Ext(name)
+	if _, ok := fl.ExtCountMap[ext]; !ok {
+		fl.ExtCountMap[ext] = &FileCounts{}
+	}
+	fl.ExtCountMap[ext].Changes += 1
+}
+
+// Write a SoongExecutionMetrics FileList proto to `dir`.
+//
+// Path
+// Prune any paths that
+// begin with `pruneDir` (usually ${OUT_DIR}).  The file is only written if any
+// non-pruned changes are present.
+func (fl *FileList) WriteMetrics(dir, pruneDir string) (err error) {
+	if dir == "" {
+		return fmt.Errorf("No directory given")
+	}
+	var needed bool
+
+	if !strings.HasSuffix(pruneDir, "/") {
+		pruneDir += "/"
+	}
+
+	// Hash the dir and `fl.Name` to simplify scanning the metrics
+	// aggregation directory.
+	h := fnv.New128()
+	h.Write([]byte(dir + " " + fl.Name + ".FileList"))
+	path := fmt.Sprintf("%x.pb", h.Sum([]byte{}))
+	path = filepath.Join(dir, path[0:2], path[2:])
+
+	var msg = &fid_exp.FileList{Name: proto.String(fl.Name)}
+	for _, a := range fl.Additions {
+		if strings.HasPrefix(a, pruneDir) {
+			continue
+		}
+		msg.Additions = append(msg.Additions, a)
+		needed = true
+	}
+	for _, ch := range fl.Changes {
+		if strings.HasPrefix(ch.Name, pruneDir) {
+			continue
+		}
+		msg.Changes = append(msg.Changes, ch.Name)
+		needed = true
+	}
+	for _, d := range fl.Deletions {
+		if strings.HasPrefix(d, pruneDir) {
+			continue
+		}
+		msg.Deletions = append(msg.Deletions, d)
+		needed = true
+	}
+	if !needed {
+		return nil
+	}
+	data := protowire.AppendVarint(
+		[]byte{},
+		protowire.EncodeTag(
+			protowire.Number(fid_exp.FieldNumbers_FIELD_NUMBERS_FILE_LIST),
+			protowire.BytesType))
+	size := uint64(proto.Size(msg))
+	data = protowire.AppendVarint(data, size)
+	data, err = proto.MarshalOptions{UseCachedSize: true}.MarshalAppend(data, msg)
+	if err != nil {
+		return err
+	}
+
+	err = os.MkdirAll(filepath.Dir(path), 0777)
+	if err != nil {
+		return err
+	}
+
+	return os.WriteFile(path, data, 0644)
+}
+
+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..09a8aa6
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta_lib/fs.go
@@ -0,0 +1,37 @@
+// 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/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)
+}
+
+// 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..bf4f866
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta_lib/internal_state.go
@@ -0,0 +1,148 @@
+// 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"
+	"regexp"
+	"slices"
+
+	fid_proto "android/soong/cmd/find_input_delta/find_input_delta_proto_internal"
+	"android/soong/third_party/zip"
+	"github.com/google/blueprint/pathtools"
+	"google.golang.org/protobuf/proto"
+)
+
+// 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 {
+			if errors.Is(err, fs.ErrNotExist) {
+				continue
+			}
+			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 {
+			// NOTE: When we find it useful, we can parallelize the file inspection for speed.
+			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
+}
+
+// We ignore any suffix digit caused by sharding.
+var InspectExtsZipRegexp = regexp.MustCompile("\\.(jar|apex|apk)[0-9]*$")
+
+// 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) {
+	if InspectExtsZipRegexp.Match([]byte(name)) {
+		return inspectZipFileContents(name)
+	}
+	return nil, nil
+}
+
+func inspectZipFileContents(name string) ([]*fid_proto.PartialCompileInput, error) {
+	rc, err := zip.OpenReader(name)
+	if err != nil {
+		return nil, err
+	}
+	ret := []*fid_proto.PartialCompileInput{}
+	for _, v := range rc.File {
+		pci := &fid_proto.PartialCompileInput{
+			Name:      proto.String(v.Name),
+			MtimeNsec: proto.Int64(v.ModTime().UnixNano()),
+			Hash:      proto.String(fmt.Sprintf("%08x", v.CRC32)),
+		}
+		ret = append(ret, pci)
+		// We do not support nested inspection.
+	}
+	return ret, nil
+}
+
+func WriteState(s *fid_proto.PartialCompileInputs, path string) error {
+	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 := FileListFactory(name)
+	PriorMap := make(map[string]*fid_proto.PartialCompileInput, len(prior))
+	// We know that the lists are properly sorted, so we can simply compare them.
+	for _, v := range prior {
+		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.addFile(name)
+		} else if !proto.Equal(PriorMap[name], v) {
+			// Changed file
+			fl.changeFile(name, CompareInputFiles(PriorMap[name].GetContents(), v.GetContents(), name))
+		}
+	}
+	for _, v := range prior {
+		name := v.GetName()
+		if _, ok := otherMap[name]; !ok {
+			// Deleted file
+			fl.deleteFile(name)
+		}
+	}
+	return fl
+}
diff --git a/cmd/find_input_delta/find_input_delta_lib/internal_state_test.go b/cmd/find_input_delta/find_input_delta_lib/internal_state_test.go
new file mode 100644
index 0000000..c168d5a
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta_lib/internal_state_test.go
@@ -0,0 +1,283 @@
+// 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 each add modify delete",
+			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"},
+			},
+		},
+		{
+			Name:   "interior one each add modify delete",
+			Target: "bar",
+			Prior: &fid_proto.PartialCompileInputs{
+				InputFiles: []*fid_proto.PartialCompileInput{
+					protoFile("file1", 405, "", []*fid_proto.PartialCompileInput{
+						protoFile("innerC", 400, "crc32:11111111", nil),
+						protoFile("innerD", 400, "crc32:44444444", nil),
+					}),
+				},
+			},
+			New: &fid_proto.PartialCompileInputs{
+				InputFiles: []*fid_proto.PartialCompileInput{
+					protoFile("file1", 505, "", []*fid_proto.PartialCompileInput{
+						protoFile("innerA", 400, "crc32:55555555", nil),
+						protoFile("innerC", 500, "crc32:66666666", nil),
+					}),
+				},
+			},
+			Expected: &FileList{
+				Name: "bar",
+				Changes: []FileList{FileList{
+					Name:      "file1",
+					Additions: []string{"innerA"},
+					Changes:   []FileList{FileList{Name: "innerC"}},
+					Deletions: []string{"innerD"},
+				}},
+			},
+		},
+	}
+	for _, tc := range testCases {
+		actual := CompareInternalState(tc.Prior, tc.New, tc.Target)
+		if !tc.Expected.Equal(actual) {
+			t.Errorf("%s: expected %v, actual %v", tc.Name, tc.Expected, actual)
+		}
+	}
+}
+
+func TestCompareInspectExtsZipRegexp(t *testing.T) {
+	testCases := []struct {
+		Name     string
+		Expected bool
+	}{
+		{Name: ".jar", Expected: true},
+		{Name: ".jar5", Expected: true},
+		{Name: ".apex", Expected: true},
+		{Name: ".apex9", Expected: true},
+		{Name: ".apexx", Expected: false},
+		{Name: ".apk", Expected: true},
+		{Name: ".apk3", Expected: true},
+		{Name: ".go", Expected: false},
+	}
+	for _, tc := range testCases {
+		actual := InspectExtsZipRegexp.Match([]byte(tc.Name))
+		if tc.Expected != actual {
+			t.Errorf("%s: expected %v, actual %v", tc.Name, tc.Expected, actual)
+		}
+	}
+}
diff --git a/testing/code_metadata_internal_proto/Android.bp b/cmd/find_input_delta/find_input_delta_proto/Android.bp
similarity index 70%
copy from testing/code_metadata_internal_proto/Android.bp
copy to cmd/find_input_delta/find_input_delta_proto/Android.bp
index 396e44f..1a05b9e 100644
--- a/testing/code_metadata_internal_proto/Android.bp
+++ b/cmd/find_input_delta/find_input_delta_proto/Android.bp
@@ -1,4 +1,4 @@
-// Copyright 2022 Google Inc. All rights reserved.
+// 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.
@@ -17,17 +17,13 @@
 }
 
 bootstrap_go_package {
-    name: "soong-testing-code_metadata_internal_proto",
-    pkgPath: "android/soong/testing/code_metadata_internal_proto",
+    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: [
-        "code_metadata_internal.pb.go",
-    ],
-    visibility: [
-        "//build/make/tools/metadata",
-        "//build/soong:__subpackages__",
+        "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..b6adc45
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta_proto/file_list.pb.go
@@ -0,0 +1,257 @@
+//
+// 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 find_input_delta_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 FieldNumbers int32
+
+const (
+	FieldNumbers_FIELD_NUMBERS_UNSPECIFIED FieldNumbers = 0
+	FieldNumbers_FIELD_NUMBERS_FILE_LIST   FieldNumbers = 1
+)
+
+// Enum value maps for FieldNumbers.
+var (
+	FieldNumbers_name = map[int32]string{
+		0: "FIELD_NUMBERS_UNSPECIFIED",
+		1: "FIELD_NUMBERS_FILE_LIST",
+	}
+	FieldNumbers_value = map[string]int32{
+		"FIELD_NUMBERS_UNSPECIFIED": 0,
+		"FIELD_NUMBERS_FILE_LIST":   1,
+	}
+)
+
+func (x FieldNumbers) Enum() *FieldNumbers {
+	p := new(FieldNumbers)
+	*p = x
+	return p
+}
+
+func (x FieldNumbers) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (FieldNumbers) Descriptor() protoreflect.EnumDescriptor {
+	return file_file_list_proto_enumTypes[0].Descriptor()
+}
+
+func (FieldNumbers) Type() protoreflect.EnumType {
+	return &file_file_list_proto_enumTypes[0]
+}
+
+func (x FieldNumbers) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Do not use.
+func (x *FieldNumbers) UnmarshalJSON(b []byte) error {
+	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
+	if err != nil {
+		return err
+	}
+	*x = FieldNumbers(num)
+	return nil
+}
+
+// Deprecated: Use FieldNumbers.Descriptor instead.
+func (FieldNumbers) EnumDescriptor() ([]byte, []int) {
+	return file_file_list_proto_rawDescGZIP(), []int{0}
+}
+
+type FileList struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The name of the output file (Ninja target).
+	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 changed files.
+	Changes []string `protobuf:"bytes,3,rep,name=changes" json:"changes,omitempty"`
+	// The deleted files.
+	Deletions []string `protobuf:"bytes,4,rep,name=deletions" json:"deletions,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) GetChanges() []string {
+	if x != nil {
+		return x.Changes
+	}
+	return nil
+}
+
+func (x *FileList) GetDeletions() []string {
+	if x != nil {
+		return x.Deletions
+	}
+	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, 0x74, 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,
+	0x18, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09,
+	0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x65, 0x6c,
+	0x65, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x64, 0x65,
+	0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2a, 0x4a, 0x0a, 0x0c, 0x46, 0x69, 0x65, 0x6c, 0x64,
+	0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x1d, 0x0a, 0x19, 0x46, 0x49, 0x45, 0x4c, 0x44,
+	0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49,
+	0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x46, 0x49, 0x45, 0x4c, 0x44, 0x5f,
+	0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x53, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x5f, 0x4c, 0x49, 0x53,
+	0x54, 0x10, 0x01, 0x42, 0x3b, 0x5a, 0x39, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73,
+	0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e,
+	0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x2f, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69,
+	0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+}
+
+var (
+	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_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_file_list_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_file_list_proto_goTypes = []interface{}{
+	(FieldNumbers)(0), // 0: android.find_input_delta_proto.FieldNumbers
+	(*FileList)(nil),  // 1: android.find_input_delta_proto.FileList
+}
+var file_file_list_proto_depIdxs = []int32{
+	0, // [0:0] is the sub-list for method output_type
+	0, // [0:0] is the sub-list for method input_type
+	0, // [0:0] is the sub-list for extension type_name
+	0, // [0:0] is the sub-list for extension extendee
+	0, // [0:0] 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:      1,
+			NumMessages:   1,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_file_list_proto_goTypes,
+		DependencyIndexes: file_file_list_proto_depIdxs,
+		EnumInfos:         file_file_list_proto_enumTypes,
+		MessageInfos:      file_file_list_proto_msgTypes,
+	}.Build()
+	File_file_list_proto = out.File
+	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..5309d66
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta_proto/file_list.proto
@@ -0,0 +1,37 @@
+//
+// 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/cmd/find_input_delta/find_input_delta_proto";
+
+enum FieldNumbers {
+  FIELD_NUMBERS_UNSPECIFIED = 0;
+  FIELD_NUMBERS_FILE_LIST = 1;
+}
+
+message FileList {
+  // The name of the output file (Ninja target).
+  optional string name = 1;
+
+  // The added files.
+  repeated string additions = 2;
+
+  // The changed files.
+  repeated string changes = 3;
+
+  // The deleted files.
+  repeated string deletions = 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 100755
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/testing/code_metadata_internal_proto/Android.bp b/cmd/find_input_delta/find_input_delta_proto_internal/Android.bp
similarity index 70%
rename from testing/code_metadata_internal_proto/Android.bp
rename to cmd/find_input_delta/find_input_delta_proto_internal/Android.bp
index 396e44f..00ba9ff 100644
--- a/testing/code_metadata_internal_proto/Android.bp
+++ b/cmd/find_input_delta/find_input_delta_proto_internal/Android.bp
@@ -1,4 +1,4 @@
-// Copyright 2022 Google Inc. All rights reserved.
+// 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.
@@ -17,17 +17,13 @@
 }
 
 bootstrap_go_package {
-    name: "soong-testing-code_metadata_internal_proto",
-    pkgPath: "android/soong/testing/code_metadata_internal_proto",
+    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: [
-        "code_metadata_internal.pb.go",
-    ],
-    visibility: [
-        "//build/make/tools/metadata",
-        "//build/soong:__subpackages__",
+        "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..c5b048b
--- /dev/null
+++ b/cmd/find_input_delta/find_input_delta_proto_internal/internal_state.pb.go
@@ -0,0 +1,272 @@
+//
+// 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 find_input_delta_proto_internal
+
+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.  For crc32 hashes, this will be 8 hex digits.
+	Hash *string `protobuf:"bytes,3,opt,name=hash" json:"hash,omitempty"`
+	// Contents of the file, if the file was inspected (such as jar files, etc).
+	Contents []*PartialCompileInput `protobuf:"bytes,4,rep,name=contents" json:"contents,omitempty"`
+}
+
+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, 0x27, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e,
+	0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61,
+	0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x22,
+	0x75, 0x0a, 0x14, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c,
+	0x65, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x73, 0x12, 0x5d, 0x0a, 0x0b, 0x69, 0x6e, 0x70, 0x75, 0x74,
+	0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3c, 0x2e, 0x61,
+	0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75,
+	0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5f, 0x69, 0x6e,
+	0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x43, 0x6f,
+	0x6d, 0x70, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x0a, 0x69, 0x6e, 0x70, 0x75,
+	0x74, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x22, 0xb6, 0x01, 0x0a, 0x13, 0x50, 0x61, 0x72, 0x74, 0x69,
+	0x61, 0x6c, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x12, 0x12,
+	0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
+	0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6e, 0x73, 0x65, 0x63,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x6d, 0x74, 0x69, 0x6d, 0x65, 0x4e, 0x73, 0x65,
+	0x63, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x58, 0x0a, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74,
+	0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x3c, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69,
+	0x64, 0x2e, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x6c,
+	0x74, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61,
+	0x6c, 0x2e, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65,
+	0x49, 0x6e, 0x70, 0x75, 0x74, 0x52, 0x08, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x73, 0x42,
+	0x40, 0x5a, 0x3e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67,
+	0x2f, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x6c, 0x74,
+	0x61, 0x2f, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x6c,
+	0x74, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61,
+	0x6c,
+}
+
+var (
+	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_internal.PartialCompileInputs
+	(*PartialCompileInput)(nil),  // 1: android.find_input_delta_proto_internal.PartialCompileInput
+}
+var file_internal_state_proto_depIdxs = []int32{
+	1, // 0: android.find_input_delta_proto_internal.PartialCompileInputs.input_files:type_name -> android.find_input_delta_proto_internal.PartialCompileInput
+	1, // 1: android.find_input_delta_proto_internal.PartialCompileInput.contents:type_name -> android.find_input_delta_proto_internal.PartialCompileInput
+	2, // [2:2] is the sub-list for method output_type
+	2, // [2:2] is the sub-list for method input_type
+	2, // [2:2] is the sub-list for extension type_name
+	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..54c90cc
--- /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_internal;
+option go_package = "android/soong/find_input_delta/find_input_delta_proto_internal";
+
+// 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.  For crc32 hashes, this will be 8 hex digits.
+  optional string hash = 3;
+
+  // Contents of the file, if the file was inspected (such as jar files, etc).
+  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 100755
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/javac_wrapper/javac_wrapper.go b/cmd/javac_wrapper/javac_wrapper.go
index 4679906..49bcedf 100644
--- a/cmd/javac_wrapper/javac_wrapper.go
+++ b/cmd/javac_wrapper/javac_wrapper.go
@@ -200,6 +200,9 @@
 
 var warningFilters = []*regexp.Regexp{
 	regexp.MustCompile(`bootstrap class path not set in conjunction with -source`),
+	regexp.MustCompile(`source value 8 is obsolete and will be removed in a future release`),
+	regexp.MustCompile(`target value 8 is obsolete and will be removed in a future release`),
+	regexp.MustCompile(`To suppress warnings about obsolete options, use -Xlint:-options.`),
 }
 
 var filters = []*regexp.Regexp{
diff --git a/cmd/javac_wrapper/javac_wrapper_test.go b/cmd/javac_wrapper/javac_wrapper_test.go
index ad23001..5e26786 100644
--- a/cmd/javac_wrapper/javac_wrapper_test.go
+++ b/cmd/javac_wrapper/javac_wrapper_test.go
@@ -90,6 +90,15 @@
 `,
 		out: "\n\x1b[1m\x1b[35mwarning:\x1b[0m\x1b[1m foo\x1b[0m\n1 warning\n",
 	},
+	{
+		in: `
+warning: [options] source value 8 is obsolete and will be removed in a future release
+warning: [options] target value 8 is obsolete and will be removed in a future release
+warning: [options] To suppress warnings about obsolete options, use -Xlint:-options.
+3 warnings
+`,
+		out: "\n",
+	},
 }
 
 func TestJavacColorize(t *testing.T) {
diff --git a/cmd/kotlinc_incremental/Android.bp b/cmd/kotlinc_incremental/Android.bp
new file mode 100644
index 0000000..7816553
--- /dev/null
+++ b/cmd/kotlinc_incremental/Android.bp
@@ -0,0 +1,65 @@
+//
+// Copyright (C) 2025 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 {
+    default_team: "trendy_team_system_ui_please_use_a_more_specific_subteam_if_possible_",
+    default_applicable_licenses: [
+        "Android-Apache-2.0",
+        "Kotlin_Incremental_license",
+    ],
+}
+
+license {
+    name: "Kotlin_Incremental_license",
+    visibility: [":__subpackages__"],
+    license_kinds: ["legacy_proprietary"],
+}
+
+java_library_host {
+    name: "kotlin-incremental-client-lib",
+    srcs: [
+        "src/com/**/*.kt",
+    ],
+    static_libs: [
+        "kotlin-compiler-embeddable",
+        "kotlin-compiler-runner",
+        "kotlin-daemon-client",
+    ],
+
+    plugins: [],
+
+    kotlincflags: [
+        "-Werror",
+    ],
+}
+
+java_binary_host {
+    name: "kotlin-incremental-client",
+    manifest: "kotlin-incremental-client.mf",
+    static_libs: ["kotlin-incremental-client-lib"],
+}
+
+java_test_host {
+    name: "kotlin-incremental-client-tests",
+    srcs: [
+        "tests/src/com/**/*.kt",
+    ],
+    static_libs: [
+        "kotlin-incremental-client-lib",
+        "junit",
+        "truth",
+    ],
+}
diff --git a/cmd/kotlinc_incremental/kotlin-incremental-client.mf b/cmd/kotlinc_incremental/kotlin-incremental-client.mf
new file mode 100644
index 0000000..b84c86a
--- /dev/null
+++ b/cmd/kotlinc_incremental/kotlin-incremental-client.mf
@@ -0,0 +1 @@
+Main-Class: com.android.kotlin.compiler.client.MainKt
diff --git a/cmd/kotlinc_incremental/src/com/android/kotlin/compiler/client/Main.kt b/cmd/kotlinc_incremental/src/com/android/kotlin/compiler/client/Main.kt
new file mode 100644
index 0000000..4938641
--- /dev/null
+++ b/cmd/kotlinc_incremental/src/com/android/kotlin/compiler/client/Main.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2025 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 com.android.kotlin.compiler.client
+
+fun main(args: Array<String>) {
+  println("compiling")
+}
diff --git a/cmd/kotlinc_incremental/tests/src/com/android/kotlin/compiler/client/MainTest.kt b/cmd/kotlinc_incremental/tests/src/com/android/kotlin/compiler/client/MainTest.kt
new file mode 100644
index 0000000..3354aa4
--- /dev/null
+++ b/cmd/kotlinc_incremental/tests/src/com/android/kotlin/compiler/client/MainTest.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2025 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 com.android.kotlin.compiler.client
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class MainTest {
+    @Test
+    fun testMain() {
+        assertThat(true).isTrue()
+    }
+}
\ No newline at end of file
diff --git a/cmd/multiproduct_kati/Android.bp b/cmd/multiproduct_kati/Android.bp
deleted file mode 100644
index 20ca2a3..0000000
--- a/cmd/multiproduct_kati/Android.bp
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2017 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"],
-}
-
-blueprint_go_binary {
-    name: "multiproduct_kati",
-    deps: [
-        "soong-ui-logger",
-        "soong-ui-signal",
-        "soong-ui-terminal",
-        "soong-ui-tracer",
-        "soong-zip",
-    ],
-    srcs: [
-        "main.go",
-    ],
-    testSrcs: [
-        "main_test.go",
-    ],
-    linux: {
-        srcs: [
-            "main_linux.go",
-        ],
-    },
-    darwin: {
-        srcs: [
-            "main_darwin.go",
-        ],
-    },
-}
diff --git a/cmd/multiproduct_kati/main.go b/cmd/multiproduct_kati/main.go
deleted file mode 100644
index c3b0381..0000000
--- a/cmd/multiproduct_kati/main.go
+++ /dev/null
@@ -1,598 +0,0 @@
-// Copyright 2017 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 (
-	"bufio"
-	"context"
-	"flag"
-	"fmt"
-	"io"
-	"io/ioutil"
-	"log"
-	"os"
-	"os/exec"
-	"path/filepath"
-	"regexp"
-	"runtime"
-	"strings"
-	"sync"
-	"syscall"
-	"time"
-
-	"android/soong/ui/logger"
-	"android/soong/ui/signal"
-	"android/soong/ui/status"
-	"android/soong/ui/terminal"
-	"android/soong/ui/tracer"
-	"android/soong/zip"
-)
-
-var numJobs = flag.Int("j", 0, "number of parallel jobs [0=autodetect]")
-
-var keepArtifacts = flag.Bool("keep", false, "keep archives of artifacts")
-var incremental = flag.Bool("incremental", false, "run in incremental mode (saving intermediates)")
-
-var outDir = flag.String("out", "", "path to store output directories (defaults to tmpdir under $OUT when empty)")
-var alternateResultDir = flag.Bool("dist", false, "write select results to $DIST_DIR (or <out>/dist when empty)")
-
-var bazelMode = flag.Bool("bazel-mode", false, "use bazel for analysis of certain modules")
-var bazelModeStaging = flag.Bool("bazel-mode-staging", false, "use bazel for analysis of certain near-ready modules")
-
-var onlyConfig = flag.Bool("only-config", false, "Only run product config (not Soong or Kati)")
-var onlySoong = flag.Bool("only-soong", false, "Only run product config and Soong (not Kati)")
-
-var buildVariant = flag.String("variant", "eng", "build variant to use")
-
-var shardCount = flag.Int("shard-count", 1, "split the products into multiple shards (to spread the build onto multiple machines, etc)")
-var shard = flag.Int("shard", 1, "1-indexed shard to execute")
-
-var skipProducts multipleStringArg
-var includeProducts multipleStringArg
-
-func init() {
-	flag.Var(&skipProducts, "skip-products", "comma-separated list of products to skip (known failures, etc)")
-	flag.Var(&includeProducts, "products", "comma-separated list of products to build")
-}
-
-// multipleStringArg is a flag.Value that takes comma separated lists and converts them to a
-// []string.  The argument can be passed multiple times to append more values.
-type multipleStringArg []string
-
-func (m *multipleStringArg) String() string {
-	return strings.Join(*m, `, `)
-}
-
-func (m *multipleStringArg) Set(s string) error {
-	*m = append(*m, strings.Split(s, ",")...)
-	return nil
-}
-
-const errorLeadingLines = 20
-const errorTrailingLines = 20
-
-func errMsgFromLog(filename string) string {
-	if filename == "" {
-		return ""
-	}
-
-	data, err := ioutil.ReadFile(filename)
-	if err != nil {
-		return ""
-	}
-
-	lines := strings.Split(strings.TrimSpace(string(data)), "\n")
-	if len(lines) > errorLeadingLines+errorTrailingLines+1 {
-		lines[errorLeadingLines] = fmt.Sprintf("... skipping %d lines ...",
-			len(lines)-errorLeadingLines-errorTrailingLines)
-
-		lines = append(lines[:errorLeadingLines+1],
-			lines[len(lines)-errorTrailingLines:]...)
-	}
-	var buf strings.Builder
-	for _, line := range lines {
-		buf.WriteString("> ")
-		buf.WriteString(line)
-		buf.WriteString("\n")
-	}
-	return buf.String()
-}
-
-// TODO(b/70370883): This tool uses a lot of open files -- over the default
-// soft limit of 1024 on some systems. So bump up to the hard limit until I fix
-// the algorithm.
-func setMaxFiles(log logger.Logger) {
-	var limits syscall.Rlimit
-
-	err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limits)
-	if err != nil {
-		log.Println("Failed to get file limit:", err)
-		return
-	}
-
-	log.Verbosef("Current file limits: %d soft, %d hard", limits.Cur, limits.Max)
-	if limits.Cur == limits.Max {
-		return
-	}
-
-	limits.Cur = limits.Max
-	err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limits)
-	if err != nil {
-		log.Println("Failed to increase file limit:", err)
-	}
-}
-
-func inList(str string, list []string) bool {
-	for _, other := range list {
-		if str == other {
-			return true
-		}
-	}
-	return false
-}
-
-func copyFile(from, to string) error {
-	fromFile, err := os.Open(from)
-	if err != nil {
-		return err
-	}
-	defer fromFile.Close()
-
-	toFile, err := os.Create(to)
-	if err != nil {
-		return err
-	}
-	defer toFile.Close()
-
-	_, err = io.Copy(toFile, fromFile)
-	return err
-}
-
-type mpContext struct {
-	Logger logger.Logger
-	Status status.ToolStatus
-
-	SoongUi     string
-	MainOutDir  string
-	MainLogsDir string
-}
-
-func findNamedProducts(soongUi string, log logger.Logger) []string {
-	cmd := exec.Command(soongUi, "--dumpvars-mode", "--vars=all_named_products")
-	output, err := cmd.Output()
-	if err != nil {
-		log.Fatalf("Cannot determine named products: %v", err)
-	}
-
-	rx := regexp.MustCompile(`^all_named_products='(.*)'$`)
-	match := rx.FindStringSubmatch(strings.TrimSpace(string(output)))
-	return strings.Fields(match[1])
-}
-
-// ensureEmptyFileExists ensures that the containing directory exists, and the
-// specified file exists. If it doesn't exist, it will write an empty file.
-func ensureEmptyFileExists(file string, log logger.Logger) {
-	if _, err := os.Stat(file); os.IsNotExist(err) {
-		f, err := os.Create(file)
-		if err != nil {
-			log.Fatalf("Error creating %s: %q\n", file, err)
-		}
-		f.Close()
-	} else if err != nil {
-		log.Fatalf("Error checking %s: %q\n", file, err)
-	}
-}
-
-func outDirBase() string {
-	outDirBase := os.Getenv("OUT_DIR")
-	if outDirBase == "" {
-		return "out"
-	} else {
-		return outDirBase
-	}
-}
-
-func distDir(outDir string) string {
-	if distDir := os.Getenv("DIST_DIR"); distDir != "" {
-		return filepath.Clean(distDir)
-	} else {
-		return filepath.Join(outDir, "dist")
-	}
-}
-
-func forceAnsiOutput() bool {
-	value := os.Getenv("SOONG_UI_ANSI_OUTPUT")
-	return value == "1" || value == "y" || value == "yes" || value == "on" || value == "true"
-}
-
-func getBazelArg() string {
-	count := 0
-	str := ""
-	if *bazelMode {
-		count++
-		str = "--bazel-mode"
-	}
-	if *bazelModeStaging {
-		count++
-		str = "--bazel-mode-staging"
-	}
-
-	if count > 1 {
-		// Can't set more than one
-		fmt.Errorf("Only one bazel mode is permitted to be set.")
-		os.Exit(1)
-	}
-
-	return str
-}
-
-func main() {
-	stdio := terminal.StdioImpl{}
-
-	output := terminal.NewStatusOutput(stdio.Stdout(), "", false, false,
-		forceAnsiOutput())
-	log := logger.New(output)
-	defer log.Cleanup()
-
-	for _, v := range os.Environ() {
-		log.Println("Environment: " + v)
-	}
-
-	log.Printf("Argv: %v\n", os.Args)
-
-	flag.Parse()
-
-	_, cancel := context.WithCancel(context.Background())
-	defer cancel()
-
-	trace := tracer.New(log)
-	defer trace.Close()
-
-	stat := &status.Status{}
-	defer stat.Finish()
-	stat.AddOutput(output)
-
-	var failures failureCount
-	stat.AddOutput(&failures)
-
-	signal.SetupSignals(log, cancel, func() {
-		trace.Close()
-		log.Cleanup()
-		stat.Finish()
-	})
-
-	soongUi := "build/soong/soong_ui.bash"
-
-	var outputDir string
-	if *outDir != "" {
-		outputDir = *outDir
-	} else {
-		name := "multiproduct"
-		if !*incremental {
-			name += "-" + time.Now().Format("20060102150405")
-		}
-		outputDir = filepath.Join(outDirBase(), name)
-	}
-
-	log.Println("Output directory:", outputDir)
-
-	// The ninja_build file is used by our buildbots to understand that the output
-	// can be parsed as ninja output.
-	if err := os.MkdirAll(outputDir, 0777); err != nil {
-		log.Fatalf("Failed to create output directory: %v", err)
-	}
-	ensureEmptyFileExists(filepath.Join(outputDir, "ninja_build"), log)
-
-	logsDir := filepath.Join(outputDir, "logs")
-	os.MkdirAll(logsDir, 0777)
-
-	var configLogsDir string
-	if *alternateResultDir {
-		configLogsDir = filepath.Join(distDir(outDirBase()), "logs")
-	} else {
-		configLogsDir = outputDir
-	}
-
-	log.Println("Logs dir: " + configLogsDir)
-
-	os.MkdirAll(configLogsDir, 0777)
-	log.SetOutput(filepath.Join(configLogsDir, "soong.log"))
-	trace.SetOutput(filepath.Join(configLogsDir, "build.trace"))
-
-	var jobs = *numJobs
-	if jobs < 1 {
-		jobs = runtime.NumCPU() / 4
-
-		ramGb := int(detectTotalRAM() / (1024 * 1024 * 1024))
-		if ramJobs := ramGb / 40; ramGb > 0 && jobs > ramJobs {
-			jobs = ramJobs
-		}
-
-		if jobs < 1 {
-			jobs = 1
-		}
-	}
-	log.Verbosef("Using %d parallel jobs", jobs)
-
-	setMaxFiles(log)
-
-	allProducts := findNamedProducts(soongUi, log)
-	var productsList []string
-
-	if len(includeProducts) > 0 {
-		var missingProducts []string
-		for _, product := range includeProducts {
-			if inList(product, allProducts) {
-				productsList = append(productsList, product)
-			} else {
-				missingProducts = append(missingProducts, product)
-			}
-		}
-		if len(missingProducts) > 0 {
-			log.Fatalf("Products don't exist: %s\n", missingProducts)
-		}
-	} else {
-		productsList = allProducts
-	}
-
-	finalProductsList := make([]string, 0, len(productsList))
-	skipProduct := func(p string) bool {
-		for _, s := range skipProducts {
-			if p == s {
-				return true
-			}
-		}
-		return false
-	}
-	for _, product := range productsList {
-		if !skipProduct(product) {
-			finalProductsList = append(finalProductsList, product)
-		} else {
-			log.Verbose("Skipping: ", product)
-		}
-	}
-
-	if *shard < 1 {
-		log.Fatalf("--shard value must be >= 1, not %d\n", *shard)
-	} else if *shardCount < 1 {
-		log.Fatalf("--shard-count value must be >= 1, not %d\n", *shardCount)
-	} else if *shard > *shardCount {
-		log.Fatalf("--shard (%d) must not be greater than --shard-count (%d)\n", *shard,
-			*shardCount)
-	} else if *shardCount > 1 {
-		finalProductsList = splitList(finalProductsList, *shardCount)[*shard-1]
-	}
-
-	log.Verbose("Got product list: ", finalProductsList)
-
-	s := stat.StartTool()
-	s.SetTotalActions(len(finalProductsList))
-
-	mpCtx := &mpContext{
-		Logger:      log,
-		Status:      s,
-		SoongUi:     soongUi,
-		MainOutDir:  outputDir,
-		MainLogsDir: logsDir,
-	}
-
-	products := make(chan string, len(productsList))
-	go func() {
-		defer close(products)
-		for _, product := range finalProductsList {
-			products <- product
-		}
-	}()
-
-	var wg sync.WaitGroup
-	for i := 0; i < jobs; i++ {
-		wg.Add(1)
-		// To smooth out the spikes in memory usage, skew the
-		// initial starting time of the jobs by a small amount.
-		time.Sleep(15 * time.Second)
-		go func() {
-			defer wg.Done()
-			for {
-				select {
-				case product := <-products:
-					if product == "" {
-						return
-					}
-					runSoongUiForProduct(mpCtx, product)
-				}
-			}
-		}()
-	}
-	wg.Wait()
-
-	if *alternateResultDir {
-		args := zip.ZipArgs{
-			FileArgs: []zip.FileArg{
-				{GlobDir: logsDir, SourcePrefixToStrip: logsDir},
-			},
-			OutputFilePath:   filepath.Join(distDir(outDirBase()), "logs.zip"),
-			NumParallelJobs:  runtime.NumCPU(),
-			CompressionLevel: 5,
-		}
-		log.Printf("Logs zip: %v\n", args.OutputFilePath)
-		if err := zip.Zip(args); err != nil {
-			log.Fatalf("Error zipping logs: %v", err)
-		}
-	}
-
-	s.Finish()
-
-	if failures.count == 1 {
-		log.Fatal("1 failure")
-	} else if failures.count > 1 {
-		log.Fatalf("%d failures %q", failures.count, failures.fails)
-	} else {
-		fmt.Fprintln(output, "Success")
-	}
-}
-
-func cleanupAfterProduct(outDir, productZip string) {
-	if *keepArtifacts {
-		args := zip.ZipArgs{
-			FileArgs: []zip.FileArg{
-				{
-					GlobDir:             outDir,
-					SourcePrefixToStrip: outDir,
-				},
-			},
-			OutputFilePath:   productZip,
-			NumParallelJobs:  runtime.NumCPU(),
-			CompressionLevel: 5,
-		}
-		if err := zip.Zip(args); err != nil {
-			log.Fatalf("Error zipping artifacts: %v", err)
-		}
-	}
-	if !*incremental {
-		os.RemoveAll(outDir)
-	}
-}
-
-func runSoongUiForProduct(mpctx *mpContext, product string) {
-	outDir := filepath.Join(mpctx.MainOutDir, product)
-	logsDir := filepath.Join(mpctx.MainLogsDir, product)
-	productZip := filepath.Join(mpctx.MainOutDir, product+".zip")
-	consoleLogPath := filepath.Join(logsDir, "std.log")
-
-	if err := os.MkdirAll(outDir, 0777); err != nil {
-		mpctx.Logger.Fatalf("Error creating out directory: %v", err)
-	}
-	if err := os.MkdirAll(logsDir, 0777); err != nil {
-		mpctx.Logger.Fatalf("Error creating log directory: %v", err)
-	}
-
-	consoleLogFile, err := os.Create(consoleLogPath)
-	if err != nil {
-		mpctx.Logger.Fatalf("Error creating console log file: %v", err)
-	}
-	defer consoleLogFile.Close()
-
-	consoleLogWriter := bufio.NewWriter(consoleLogFile)
-	defer consoleLogWriter.Flush()
-
-	args := []string{"--make-mode", "--skip-soong-tests", "--skip-ninja"}
-
-	if !*keepArtifacts {
-		args = append(args, "--empty-ninja-file")
-	}
-
-	if *onlyConfig {
-		args = append(args, "--config-only")
-	} else if *onlySoong {
-		args = append(args, "--soong-only")
-	}
-
-	bazelStr := getBazelArg()
-	if bazelStr != "" {
-		args = append(args, bazelStr)
-	}
-
-	cmd := exec.Command(mpctx.SoongUi, args...)
-	cmd.Stdout = consoleLogWriter
-	cmd.Stderr = consoleLogWriter
-	cmd.Env = append(os.Environ(),
-		"OUT_DIR="+outDir,
-		"TARGET_PRODUCT="+product,
-		"TARGET_BUILD_VARIANT="+*buildVariant,
-		"TARGET_BUILD_TYPE=release",
-		"TARGET_BUILD_APPS=",
-		"TARGET_BUILD_UNBUNDLED=",
-		"USE_RBE=false") // Disabling RBE saves ~10 secs per product
-
-	if *alternateResultDir {
-		cmd.Env = append(cmd.Env,
-			"DIST_DIR="+filepath.Join(distDir(outDirBase()), "products/"+product))
-	}
-
-	action := &status.Action{
-		Description: product,
-		Outputs:     []string{product},
-	}
-
-	mpctx.Status.StartAction(action)
-	defer cleanupAfterProduct(outDir, productZip)
-
-	before := time.Now()
-	err = cmd.Run()
-
-	if !*onlyConfig && !*onlySoong {
-		katiBuildNinjaFile := filepath.Join(outDir, "build-"+product+".ninja")
-		if after, err := os.Stat(katiBuildNinjaFile); err == nil && after.ModTime().After(before) {
-			err := copyFile(consoleLogPath, filepath.Join(filepath.Dir(consoleLogPath), "std_full.log"))
-			if err != nil {
-				log.Fatalf("Error copying log file: %s", err)
-			}
-		}
-	}
-	var errOutput string
-	if err == nil {
-		errOutput = ""
-	} else {
-		errOutput = errMsgFromLog(consoleLogPath)
-	}
-
-	mpctx.Status.FinishAction(status.ActionResult{
-		Action: action,
-		Error:  err,
-		Output: errOutput,
-	})
-}
-
-type failureCount struct {
-	count int
-	fails []string
-}
-
-func (f *failureCount) StartAction(action *status.Action, counts status.Counts) {}
-
-func (f *failureCount) FinishAction(result status.ActionResult, counts status.Counts) {
-	if result.Error != nil {
-		f.count += 1
-		f.fails = append(f.fails, result.Action.Description)
-	}
-}
-
-func (f *failureCount) Message(level status.MsgLevel, message string) {
-	if level >= status.ErrorLvl {
-		f.count += 1
-	}
-}
-
-func (f *failureCount) Flush() {}
-
-func (f *failureCount) Write(p []byte) (int, error) {
-	// discard writes
-	return len(p), nil
-}
-
-func splitList(list []string, shardCount int) (ret [][]string) {
-	each := len(list) / shardCount
-	extra := len(list) % shardCount
-	for i := 0; i < shardCount; i++ {
-		count := each
-		if extra > 0 {
-			count += 1
-			extra -= 1
-		}
-		ret = append(ret, list[:count])
-		list = list[count:]
-	}
-	return
-}
diff --git a/cmd/multiproduct_kati/main_darwin.go b/cmd/multiproduct_kati/main_darwin.go
deleted file mode 100644
index 3d1b12a..0000000
--- a/cmd/multiproduct_kati/main_darwin.go
+++ /dev/null
@@ -1,20 +0,0 @@
-// Copyright 2017 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
-
-func detectTotalRAM() uint64 {
-	// unimplemented stub on darwin
-	return 0
-}
diff --git a/cmd/multiproduct_kati/main_test.go b/cmd/multiproduct_kati/main_test.go
deleted file mode 100644
index 263a124..0000000
--- a/cmd/multiproduct_kati/main_test.go
+++ /dev/null
@@ -1,93 +0,0 @@
-// Copyright 2019 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 (
-	"fmt"
-	"reflect"
-	"testing"
-)
-
-func TestSplitList(t *testing.T) {
-	testcases := []struct {
-		inputCount int
-		shardCount int
-		want       [][]string
-	}{
-		{
-			inputCount: 1,
-			shardCount: 1,
-			want:       [][]string{{"1"}},
-		},
-		{
-			inputCount: 1,
-			shardCount: 2,
-			want:       [][]string{{"1"}, {}},
-		},
-		{
-			inputCount: 4,
-			shardCount: 2,
-			want:       [][]string{{"1", "2"}, {"3", "4"}},
-		},
-		{
-			inputCount: 19,
-			shardCount: 10,
-			want: [][]string{
-				{"1", "2"},
-				{"3", "4"},
-				{"5", "6"},
-				{"7", "8"},
-				{"9", "10"},
-				{"11", "12"},
-				{"13", "14"},
-				{"15", "16"},
-				{"17", "18"},
-				{"19"},
-			},
-		},
-		{
-			inputCount: 15,
-			shardCount: 10,
-			want: [][]string{
-				{"1", "2"},
-				{"3", "4"},
-				{"5", "6"},
-				{"7", "8"},
-				{"9", "10"},
-				{"11"},
-				{"12"},
-				{"13"},
-				{"14"},
-				{"15"},
-			},
-		},
-	}
-
-	for _, tc := range testcases {
-		t.Run(fmt.Sprintf("%d/%d", tc.inputCount, tc.shardCount), func(t *testing.T) {
-			input := []string{}
-			for i := 1; i <= tc.inputCount; i++ {
-				input = append(input, fmt.Sprintf("%d", i))
-			}
-
-			got := splitList(input, tc.shardCount)
-
-			if !reflect.DeepEqual(got, tc.want) {
-				t.Errorf("unexpected result for splitList([]string{...%d...}, %d):\nwant: %v\n got: %v\n",
-					tc.inputCount, tc.shardCount, tc.want, got)
-			}
-		})
-	}
-}
diff --git a/cmd/release_config/build_flag/main.go b/cmd/release_config/build_flag/main.go
index 5d183ee..5d103cc 100644
--- a/cmd/release_config/build_flag/main.go
+++ b/cmd/release_config/build_flag/main.go
@@ -90,6 +90,9 @@
 	if !ok {
 		return "", fmt.Errorf("%s not found in %s", name, config.Name)
 	}
+	if fa.Redacted {
+		return "==REDACTED==", nil
+	}
 	return rc_lib.MarshalValue(fa.Value), nil
 }
 
@@ -329,7 +332,7 @@
 		return err
 	}
 	updatedFiles = append(updatedFiles, flagPath)
-	fmt.Printf("Added/Updated: %s\n", strings.Join(updatedFiles, " "))
+	fmt.Printf("\033[1mAdded/Updated: %s\033[0m\n", strings.Join(updatedFiles, " "))
 	return nil
 }
 
diff --git a/cmd/release_config/release_config/main.go b/cmd/release_config/release_config/main.go
index d06b2b7..7013d6b 100644
--- a/cmd/release_config/release_config/main.go
+++ b/cmd/release_config/release_config/main.go
@@ -95,7 +95,7 @@
 	if allMake {
 		// Write one makefile per release config, using the canonical release name.
 		for _, c := range configs.GetSortedReleaseConfigs() {
-			if c.Name != targetRelease {
+			if c.Name != targetRelease && !c.DisallowLunchUse {
 				makefilePath = filepath.Join(outputDir, fmt.Sprintf("release_config-%s-%s.varmk", product, c.Name))
 				err = config.WriteMakefile(makefilePath, c.Name, configs)
 				if err != nil {
diff --git a/cmd/release_config/release_config_lib/flag_artifact.go b/cmd/release_config/release_config_lib/flag_artifact.go
index 51c02d2..f493e1e 100644
--- a/cmd/release_config/release_config_lib/flag_artifact.go
+++ b/cmd/release_config/release_config_lib/flag_artifact.go
@@ -84,8 +84,10 @@
 
 func (fas *FlagArtifacts) SortedFlagNames() []string {
 	var names []string
-	for k, _ := range *fas {
-		names = append(names, k)
+	for k, v := range *fas {
+		if !v.Redacted {
+			names = append(names, k)
+		}
 	}
 	slices.Sort(names)
 	return names
@@ -99,6 +101,9 @@
 	if namespace := fa.FlagDeclaration.GetNamespace(); namespace != "" {
 		ret.Namespace = proto.String(namespace)
 	}
+	if bugs := fa.FlagDeclaration.GetBugs(); bugs != nil {
+		ret.Bugs = bugs
+	}
 	if description := fa.FlagDeclaration.GetDescription(); description != "" {
 		ret.Description = proto.String(description)
 	}
@@ -180,15 +185,20 @@
 //	error: any error encountered
 func (fa *FlagArtifact) UpdateValue(flagValue FlagValue) error {
 	name := *flagValue.proto.Name
-	fa.Traces = append(fa.Traces, &rc_proto.Tracepoint{Source: proto.String(flagValue.path), Value: flagValue.proto.Value})
-	if flagValue.proto.GetRedacted() {
-		fa.Redacted = true
-		fmt.Printf("Redacting flag %s in %s\n", name, flagValue.path)
-		return nil
+	redacted := flagValue.proto.GetRedacted()
+	if redacted {
+		fa.Redact()
+		flagValue.proto.Value = fa.Value
+		warnf("Redacting flag %s in %s\n", name, flagValue.path)
+	} else {
+		// If we are assigning a value, then the flag is no longer redacted.
+		fa.Redacted = false
 	}
+	fa.Traces = append(fa.Traces, &rc_proto.Tracepoint{Source: proto.String(flagValue.path), Value: flagValue.proto.Value})
 	if fa.Value.GetObsolete() {
 		return fmt.Errorf("Attempting to set obsolete flag %s. Trace=%v", name, fa.Traces)
 	}
+
 	var newValue *rc_proto.Value
 	switch val := flagValue.proto.Value.Val.(type) {
 	case *rc_proto.Value_StringValue:
@@ -210,6 +220,11 @@
 	return nil
 }
 
+func (fa *FlagArtifact) Redact() {
+	fa.Redacted = true
+	fa.Value = &rc_proto.Value{Val: &rc_proto.Value_StringValue{StringValue: "*REDACTED*"}}
+}
+
 // Marshal the FlagArtifact into a flag_artifact message.
 func (fa *FlagArtifact) Marshal() (*rc_proto.FlagArtifact, error) {
 	if fa.Redacted {
diff --git a/cmd/release_config/release_config_lib/release_config.go b/cmd/release_config/release_config_lib/release_config.go
index ee71336..873f2fc 100644
--- a/cmd/release_config/release_config_lib/release_config.go
+++ b/cmd/release_config/release_config_lib/release_config.go
@@ -21,7 +21,6 @@
 	"path/filepath"
 	"regexp"
 	"slices"
-	"sort"
 	"strings"
 
 	rc_proto "android/soong/cmd/release_config/release_config_proto"
@@ -68,6 +67,9 @@
 	// overrides. Build flag value overrides are an error.
 	AconfigFlagsOnly bool
 
+	// True if this release config is not allowed as TARGET_RELEASE.
+	DisallowLunchUse bool
+
 	// Unmarshalled flag artifacts
 	FlagArtifacts FlagArtifacts
 
@@ -86,6 +88,31 @@
 	// Prior stage(s) for flag advancement (during development).
 	// Once a flag has met criteria in a prior stage, it can advance to this one.
 	PriorStagesMap map[string]bool
+
+	// What type of release config is this?  This should never be
+	// ReleaseConfigType_CONFIG_TYPE_UNSPECIFIED.
+	ReleaseConfigType rc_proto.ReleaseConfigType
+}
+
+// If true, this is a proper release config that can be used in "lunch".
+func (config *ReleaseConfig) isConfigListable() bool {
+	// Do not list disallowed release configs.
+	if config.DisallowLunchUse {
+		return false
+	}
+	// Logic based on ReleaseConfigType.
+	switch config.ReleaseConfigType {
+	case rc_proto.ReleaseConfigType_RELEASE_CONFIG:
+		return true
+	}
+
+	return false
+}
+
+// If true, this ReleaseConfigType may only inherit from a ReleaseConfig of the
+// same ReleaseConfigType.
+var ReleaseConfigInheritanceDenyMap = map[rc_proto.ReleaseConfigType]bool{
+	rc_proto.ReleaseConfigType_BUILD_VARIANT: true,
 }
 
 func ReleaseConfigFactory(name string, index int) (c *ReleaseConfig) {
@@ -98,6 +125,10 @@
 }
 
 func (config *ReleaseConfig) InheritConfig(iConfig *ReleaseConfig) error {
+	if config.ReleaseConfigType != iConfig.ReleaseConfigType && ReleaseConfigInheritanceDenyMap[config.ReleaseConfigType] {
+		return fmt.Errorf("Release config %s (type '%s') cannot inherit from %s (type '%s')",
+			config.Name, config.ReleaseConfigType, iConfig.Name, iConfig.ReleaseConfigType)
+	}
 	for f := range iConfig.FilesUsedMap {
 		config.FilesUsedMap[f] = true
 	}
@@ -107,6 +138,9 @@
 		if !ok {
 			return fmt.Errorf("Could not inherit flag %s from %s", name, iConfig.Name)
 		}
+		if fa.Redacted {
+			myFa.Redact()
+		}
 		if name == "RELEASE_ACONFIG_VALUE_SETS" {
 			// If there is a value assigned, add the trace.
 			if len(fa.Value.GetStringValue()) > 0 {
@@ -152,10 +186,10 @@
 	contributionsToApply := []*ReleaseConfigContribution{}
 	myInherits := []string{}
 	myInheritsSet := make(map[string]bool)
-	// If there is a "root" release config, it is the start of every inheritance chain.
-	_, err = configs.GetReleaseConfig("root")
-	if err == nil && !isRoot {
-		config.InheritNames = append([]string{"root"}, config.InheritNames...)
+	if config.ReleaseConfigType == rc_proto.ReleaseConfigType_RELEASE_CONFIG {
+		if _, err = configs.GetReleaseConfigStrict("root"); err == nil {
+			config.InheritNames = append([]string{"root"}, config.InheritNames...)
+		}
 	}
 	for _, inherit := range config.InheritNames {
 		if _, ok := myInheritsSet[inherit]; ok {
@@ -166,6 +200,13 @@
 		}
 		myInherits = append(myInherits, inherit)
 		myInheritsSet[inherit] = true
+		// TODO: there are some configs that rely on vgsbr being
+		// present on branches where it isn't. Once the broken configs
+		// are fixed, we can be more strict.  In the meantime, they
+		// will wind up inheriting `trunk_stable` instead of the
+		// non-existent (alias) that they reference today.  Once fixed,
+		// this becomes:
+		//    iConfig, err := configs.GetReleaseConfigStrict(inherit)
 		iConfig, err := configs.GetReleaseConfig(inherit)
 		if err != nil {
 			return err
@@ -261,11 +302,38 @@
 			if err := fa.UpdateValue(*value); err != nil {
 				return err
 			}
-			if fa.Redacted {
-				delete(config.FlagArtifacts, name)
+		}
+	}
+
+	if config.ReleaseConfigType == rc_proto.ReleaseConfigType_RELEASE_CONFIG {
+		inheritBuildVariant := func() error {
+			build_variant := os.Getenv("TARGET_BUILD_VARIANT")
+			if build_variant == "" || config.Name == build_variant {
+				return nil
+			}
+			variant, err := configs.GetReleaseConfigStrict(build_variant)
+			if err != nil {
+				// Failure to find the build-variant release config is
+				// not an error.
+				return nil
+			}
+			if variant.ReleaseConfigType != rc_proto.ReleaseConfigType_BUILD_VARIANT {
+				return nil
+			}
+			if err = variant.GenerateReleaseConfig(configs); err != nil {
+				return err
+			}
+			return config.InheritConfig(variant)
+		}
+
+		useVariant, ok := config.FlagArtifacts["RELEASE_BUILD_USE_VARIANT_FLAGS"]
+		if ok && MarshalValue(useVariant.Value) != "" {
+			if err = inheritBuildVariant(); err != nil {
+				return err
 			}
 		}
 	}
+
 	// Now remove any duplicates from the actual value of RELEASE_ACONFIG_VALUE_SETS
 	myAconfigValueSets := []string{}
 	myAconfigValueSetsMap := map[string]bool{}
@@ -313,6 +381,10 @@
 		if err != nil {
 			return err
 		}
+		// Redacted flags return nil when rendered.
+		if artifact == nil {
+			continue
+		}
 		for _, container := range v.FlagDeclaration.Containers {
 			if _, ok := config.PartitionBuildFlags[container]; !ok {
 				config.PartitionBuildFlags[container] = &rc_proto.FlagArtifacts{}
@@ -325,12 +397,7 @@
 		OtherNames: config.OtherNames,
 		Flags: func() []*rc_proto.FlagArtifact {
 			ret := []*rc_proto.FlagArtifact{}
-			flagNames := []string{}
-			for k := range config.FlagArtifacts {
-				flagNames = append(flagNames, k)
-			}
-			sort.Strings(flagNames)
-			for _, flagName := range flagNames {
+			for _, flagName := range config.FlagArtifacts.SortedFlagNames() {
 				flag := config.FlagArtifacts[flagName]
 				ret = append(ret, &rc_proto.FlagArtifact{
 					FlagDeclaration: flag.FlagDeclaration,
@@ -340,11 +407,13 @@
 			}
 			return ret
 		}(),
-		AconfigValueSets: myAconfigValueSets,
-		Inherits:         myInherits,
-		Directories:      directories,
-		ValueDirectories: valueDirectories,
-		PriorStages:      SortedMapKeys(config.PriorStagesMap),
+		AconfigValueSets:  myAconfigValueSets,
+		Inherits:          myInherits,
+		Directories:       directories,
+		ValueDirectories:  valueDirectories,
+		PriorStages:       SortedMapKeys(config.PriorStagesMap),
+		ReleaseConfigType: config.ReleaseConfigType.Enum(),
+		DisallowLunchUse:  proto.Bool(config.DisallowLunchUse),
 	}
 
 	config.compileInProgress = false
@@ -365,7 +434,7 @@
 		}
 	}
 	for _, rcName := range extraAconfigReleaseConfigs {
-		rc, err := configs.GetReleaseConfig(rcName)
+		rc, err := configs.GetReleaseConfigStrict(rcName)
 		if err != nil {
 			return err
 		}
@@ -421,6 +490,9 @@
 	}
 	// As it stands this list is not per-product, but conceptually it is, and will be.
 	data += fmt.Sprintf("ALL_RELEASE_CONFIGS_FOR_PRODUCT :=$= %s\n", strings.Join(configs.GetAllReleaseNames(), " "))
+	if config.DisallowLunchUse {
+		data += fmt.Sprintf("_disallow_lunch_use :=$= true\n")
+	}
 	data += fmt.Sprintf("_used_files := %s\n", strings.Join(config.GetSortedFileList(), " "))
 	data += fmt.Sprintf("_ALL_RELEASE_FLAGS :=$= %s\n", strings.Join(names, " "))
 	for _, pName := range pNames {
diff --git a/cmd/release_config/release_config_lib/release_configs.go b/cmd/release_config/release_config_lib/release_configs.go
index 831ec02..b0f8cb7 100644
--- a/cmd/release_config/release_config_lib/release_configs.go
+++ b/cmd/release_config/release_config_lib/release_configs.go
@@ -361,11 +361,24 @@
 		if fmt.Sprintf("%s.textproto", name) != filepath.Base(path) {
 			return fmt.Errorf("%s incorrectly declares release config %s", path, name)
 		}
+		releaseConfigType := releaseConfigContribution.proto.ReleaseConfigType
+		if releaseConfigType == nil {
+			if name == "root" {
+				releaseConfigType = rc_proto.ReleaseConfigType_EXPLICIT_INHERITANCE_CONFIG.Enum()
+			} else {
+				releaseConfigType = rc_proto.ReleaseConfigType_RELEASE_CONFIG.Enum()
+			}
+		}
 		if _, ok := configs.ReleaseConfigs[name]; !ok {
 			configs.ReleaseConfigs[name] = ReleaseConfigFactory(name, ConfigDirIndex)
+			configs.ReleaseConfigs[name].ReleaseConfigType = *releaseConfigType
 		}
 		config := configs.ReleaseConfigs[name]
+		if config.ReleaseConfigType != *releaseConfigType {
+			return fmt.Errorf("%s mismatching ReleaseConfigType value %s", path, *releaseConfigType)
+		}
 		config.FilesUsedMap[path] = true
+		config.DisallowLunchUse = config.DisallowLunchUse || releaseConfigContribution.proto.GetDisallowLunchUse()
 		inheritNames := make(map[string]bool)
 		for _, inh := range config.InheritNames {
 			inheritNames[inh] = true
@@ -410,6 +423,14 @@
 }
 
 func (configs *ReleaseConfigs) GetReleaseConfig(name string) (*ReleaseConfig, error) {
+	return configs.getReleaseConfig(name, configs.allowMissing)
+}
+
+func (configs *ReleaseConfigs) GetReleaseConfigStrict(name string) (*ReleaseConfig, error) {
+	return configs.getReleaseConfig(name, false)
+}
+
+func (configs *ReleaseConfigs) getReleaseConfig(name string, allow_missing bool) (*ReleaseConfig, error) {
 	trace := []string{name}
 	for target, ok := configs.Aliases[name]; ok; target, ok = configs.Aliases[name] {
 		name = *target
@@ -418,7 +439,7 @@
 	if config, ok := configs.ReleaseConfigs[name]; ok {
 		return config, nil
 	}
-	if configs.allowMissing {
+	if allow_missing {
 		if config, ok := configs.ReleaseConfigs["trunk_staging"]; ok {
 			return config, nil
 		}
@@ -429,8 +450,10 @@
 func (configs *ReleaseConfigs) GetAllReleaseNames() []string {
 	var allReleaseNames []string
 	for _, v := range configs.ReleaseConfigs {
-		allReleaseNames = append(allReleaseNames, v.Name)
-		allReleaseNames = append(allReleaseNames, v.OtherNames...)
+		if v.isConfigListable() {
+			allReleaseNames = append(allReleaseNames, v.Name)
+			allReleaseNames = append(allReleaseNames, v.OtherNames...)
+		}
 	}
 	slices.Sort(allReleaseNames)
 	return allReleaseNames
@@ -467,7 +490,7 @@
 		dirName := filepath.Dir(contrib.path)
 		for k, names := range contrib.FlagValueDirs {
 			for _, rcName := range names {
-				if config, err := configs.GetReleaseConfig(rcName); err == nil {
+				if config, err := configs.getReleaseConfig(rcName, false); err == nil {
 					rcPath := filepath.Join(dirName, "release_configs", fmt.Sprintf("%s.textproto", config.Name))
 					if _, err := os.Stat(rcPath); err != nil {
 						errors = append(errors, fmt.Sprintf("%s exists but %s does not contribute to %s",
diff --git a/cmd/release_config/release_config_proto/build_flags_common.pb.go b/cmd/release_config/release_config_proto/build_flags_common.pb.go
index f8ad38f..7b52b07 100644
--- a/cmd/release_config/release_config_proto/build_flags_common.pb.go
+++ b/cmd/release_config/release_config_proto/build_flags_common.pb.go
@@ -15,7 +15,7 @@
 
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.33.0
+// 	protoc-gen-go v1.30.0
 // 	protoc        v3.21.12
 // source: build_flags_common.proto
 
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..0029f99 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
@@ -15,7 +15,7 @@
 
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.33.0
+// 	protoc-gen-go v1.30.0
 // 	protoc        v3.21.12
 // source: build_flags_declarations.proto
 
@@ -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_out.pb.go b/cmd/release_config/release_config_proto/build_flags_out.pb.go
index 60ba2e1..21a37a9 100644
--- a/cmd/release_config/release_config_proto/build_flags_out.pb.go
+++ b/cmd/release_config/release_config_proto/build_flags_out.pb.go
@@ -15,7 +15,7 @@
 
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.33.0
+// 	protoc-gen-go v1.30.0
 // 	protoc        v3.21.12
 // source: build_flags_out.proto
 
@@ -235,6 +235,12 @@
 	// config.  The listed directories contain at least a `release_config` message
 	// for this release config.
 	ValueDirectories []string `protobuf:"bytes,8,rep,name=value_directories,json=valueDirectories" json:"value_directories,omitempty"`
+	// The ReleaseConfigType of this release config.
+	ReleaseConfigType *ReleaseConfigType `protobuf:"varint,9,opt,name=release_config_type,json=releaseConfigType,enum=android.release_config_proto.ReleaseConfigType" json:"release_config_type,omitempty"`
+	// Whether to disallow this release config as TARGET_RELEASE.
+	// If true, this release config can only be inherited, it cannot be used
+	// directly in a build.
+	DisallowLunchUse *bool `protobuf:"varint,10,opt,name=disallow_lunch_use,json=disallowLunchUse" json:"disallow_lunch_use,omitempty"`
 }
 
 func (x *ReleaseConfigArtifact) Reset() {
@@ -325,6 +331,20 @@
 	return nil
 }
 
+func (x *ReleaseConfigArtifact) GetReleaseConfigType() ReleaseConfigType {
+	if x != nil && x.ReleaseConfigType != nil {
+		return *x.ReleaseConfigType
+	}
+	return ReleaseConfigType_CONFIG_TYPE_UNSPECIFIED
+}
+
+func (x *ReleaseConfigArtifact) GetDisallowLunchUse() bool {
+	if x != nil && x.DisallowLunchUse != nil {
+		return *x.DisallowLunchUse
+	}
+	return false
+}
+
 type ReleaseConfigsArtifact struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -425,7 +445,7 @@
 	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, 0x41, 0x72, 0x74, 0x69, 0x66,
 	0x61, 0x63, 0x74, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x52, 0x0e, 0x66, 0x6c, 0x61, 0x67,
-	0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x22, 0xda, 0x02, 0x0a, 0x15, 0x52,
+	0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x22, 0xe9, 0x03, 0x0a, 0x15, 0x52,
 	0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 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, 0x1f, 0x0a, 0x0b, 0x6f, 0x74, 0x68, 0x65,
@@ -446,41 +466,50 @@
 	0x72, 0x69, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x67, 0x65, 0x73, 0x12, 0x2b, 0x0a, 0x11, 0x76, 0x61,
 	0x6c, 0x75, 0x65, 0x5f, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18,
 	0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x44, 0x69, 0x72, 0x65,
-	0x63, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x52, 0x0e, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x61, 0x72,
-	0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x22, 0xde, 0x03, 0x0a, 0x16, 0x52, 0x65, 0x6c, 0x65,
-	0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61,
-	0x63, 0x74, 0x12, 0x5a, 0x0a, 0x0e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f,
-	0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 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, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52,
-	0x0d, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x67,
-	0x0a, 0x15, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f,
-	0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 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, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61,
-	0x63, 0x74, 0x52, 0x13, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65,
-	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x12, 0x85, 0x01, 0x0a, 0x17, 0x72, 0x65, 0x6c, 0x65,
-	0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x5f,
-	0x6d, 0x61, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x4e, 0x2e, 0x61, 0x6e, 0x64, 0x72,
+	0x63, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x5f, 0x0a, 0x13, 0x72, 0x65, 0x6c, 0x65, 0x61,
+	0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x09,
+	0x20, 0x01, 0x28, 0x0e, 0x32, 0x2f, 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, 0x43, 0x6f, 0x6e, 0x66, 0x69,
+	0x67, 0x54, 0x79, 0x70, 0x65, 0x52, 0x11, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x43, 0x6f,
+	0x6e, 0x66, 0x69, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x64, 0x69, 0x73, 0x61,
+	0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x6c, 0x75, 0x6e, 0x63, 0x68, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x0a,
+	0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x64, 0x69, 0x73, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x4c, 0x75,
+	0x6e, 0x63, 0x68, 0x55, 0x73, 0x65, 0x52, 0x0e, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x61, 0x72, 0x74,
+	0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x22, 0xde, 0x03, 0x0a, 0x16, 0x52, 0x65, 0x6c, 0x65, 0x61,
+	0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63,
+	0x74, 0x12, 0x5a, 0x0a, 0x0e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e,
+	0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x33, 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,
-	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x2e,
-	0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x61, 0x70,
-	0x73, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x14, 0x72, 0x65, 0x6c, 0x65, 0x61,
-	0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x61, 0x70, 0x73, 0x4d, 0x61, 0x70, 0x1a,
-	0x77, 0x0a, 0x19, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-	0x4d, 0x61, 0x70, 0x73, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03,
-	0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x44,
-	0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 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, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x61, 0x70, 0x52, 0x05, 0x76,
-	0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 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,
+	0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x0d,
+	0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x67, 0x0a,
+	0x15, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63,
+	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x33, 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, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63,
+	0x74, 0x52, 0x13, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x43,
+	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x12, 0x85, 0x01, 0x0a, 0x17, 0x72, 0x65, 0x6c, 0x65, 0x61,
+	0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x6d, 0x61, 0x70, 0x73, 0x5f, 0x6d,
+	0x61, 0x70, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x4e, 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, 0x43,
+	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x2e, 0x52,
+	0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x61, 0x70, 0x73,
+	0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x14, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73,
+	0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x61, 0x70, 0x73, 0x4d, 0x61, 0x70, 0x1a, 0x77,
+	0x0a, 0x19, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d,
+	0x61, 0x70, 0x73, 0x4d, 0x61, 0x70, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b,
+	0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x44, 0x0a,
+	0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 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, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4d, 0x61, 0x70, 0x52, 0x05, 0x76, 0x61,
+	0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 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 (
@@ -505,7 +534,8 @@
 	nil,                            // 5: android.release_config_proto.ReleaseConfigsArtifact.ReleaseConfigMapsMapEntry
 	(*Value)(nil),                  // 6: android.release_config_proto.Value
 	(*FlagDeclaration)(nil),        // 7: android.release_config_proto.FlagDeclaration
-	(*ReleaseConfigMap)(nil),       // 8: android.release_config_proto.ReleaseConfigMap
+	(ReleaseConfigType)(0),         // 8: android.release_config_proto.ReleaseConfigType
+	(*ReleaseConfigMap)(nil),       // 9: android.release_config_proto.ReleaseConfigMap
 }
 var file_build_flags_out_proto_depIdxs = []int32{
 	6,  // 0: android.release_config_proto.Tracepoint.value:type_name -> android.release_config_proto.Value
@@ -514,15 +544,16 @@
 	0,  // 3: android.release_config_proto.FlagArtifact.traces:type_name -> android.release_config_proto.Tracepoint
 	1,  // 4: android.release_config_proto.FlagArtifacts.flags:type_name -> android.release_config_proto.FlagArtifact
 	1,  // 5: android.release_config_proto.ReleaseConfigArtifact.flags:type_name -> android.release_config_proto.FlagArtifact
-	3,  // 6: android.release_config_proto.ReleaseConfigsArtifact.release_config:type_name -> android.release_config_proto.ReleaseConfigArtifact
-	3,  // 7: android.release_config_proto.ReleaseConfigsArtifact.other_release_configs:type_name -> android.release_config_proto.ReleaseConfigArtifact
-	5,  // 8: android.release_config_proto.ReleaseConfigsArtifact.release_config_maps_map:type_name -> android.release_config_proto.ReleaseConfigsArtifact.ReleaseConfigMapsMapEntry
-	8,  // 9: android.release_config_proto.ReleaseConfigsArtifact.ReleaseConfigMapsMapEntry.value:type_name -> android.release_config_proto.ReleaseConfigMap
-	10, // [10:10] is the sub-list for method output_type
-	10, // [10:10] is the sub-list for method input_type
-	10, // [10:10] is the sub-list for extension type_name
-	10, // [10:10] is the sub-list for extension extendee
-	0,  // [0:10] is the sub-list for field type_name
+	8,  // 6: android.release_config_proto.ReleaseConfigArtifact.release_config_type:type_name -> android.release_config_proto.ReleaseConfigType
+	3,  // 7: android.release_config_proto.ReleaseConfigsArtifact.release_config:type_name -> android.release_config_proto.ReleaseConfigArtifact
+	3,  // 8: android.release_config_proto.ReleaseConfigsArtifact.other_release_configs:type_name -> android.release_config_proto.ReleaseConfigArtifact
+	5,  // 9: android.release_config_proto.ReleaseConfigsArtifact.release_config_maps_map:type_name -> android.release_config_proto.ReleaseConfigsArtifact.ReleaseConfigMapsMapEntry
+	9,  // 10: android.release_config_proto.ReleaseConfigsArtifact.ReleaseConfigMapsMapEntry.value:type_name -> android.release_config_proto.ReleaseConfigMap
+	11, // [11:11] is the sub-list for method output_type
+	11, // [11:11] is the sub-list for method input_type
+	11, // [11:11] is the sub-list for extension type_name
+	11, // [11:11] is the sub-list for extension extendee
+	0,  // [0:11] is the sub-list for field type_name
 }
 
 func init() { file_build_flags_out_proto_init() }
diff --git a/cmd/release_config/release_config_proto/build_flags_out.proto b/cmd/release_config/release_config_proto/build_flags_out.proto
index 2ab62d1..8b53464 100644
--- a/cmd/release_config/release_config_proto/build_flags_out.proto
+++ b/cmd/release_config/release_config_proto/build_flags_out.proto
@@ -96,6 +96,14 @@
   // config.  The listed directories contain at least a `release_config` message
   // for this release config.
   repeated string value_directories = 8;
+
+  // The ReleaseConfigType of this release config.
+  optional ReleaseConfigType release_config_type = 9;
+
+  // Whether to disallow this release config as TARGET_RELEASE.
+  // If true, this release config can only be inherited, it cannot be used
+  // directly in a build.
+  optional bool disallow_lunch_use = 10;
 }
 
 message ReleaseConfigsArtifact {
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..9fc4ea4 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
@@ -15,7 +15,7 @@
 
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.33.0
+// 	protoc-gen-go v1.30.0
 // 	protoc        v3.21.12
 // source: build_flags_src.proto
 
@@ -35,6 +35,76 @@
 	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
 )
 
+type ReleaseConfigType int32
+
+const (
+	// This is treated as `RELEASE_CONFIG`.
+	ReleaseConfigType_CONFIG_TYPE_UNSPECIFIED ReleaseConfigType = 0
+	// This is a normal release config.  This is the only ReleaseConfigType with
+	// implicit inheritance.
+	ReleaseConfigType_RELEASE_CONFIG ReleaseConfigType = 1
+	// Same as RELEASE_CONFIG, except no implicit inheritance happens.
+	// This is the "root" release config.
+	ReleaseConfigType_EXPLICIT_INHERITANCE_CONFIG ReleaseConfigType = 2
+	// This is a release config applied based on the TARGET_BUILD_VARIANT
+	// environment variable, if the build flag RELEASE_BUILD_USE_VARIANT_FLAGS is
+	// enabled.
+	ReleaseConfigType_BUILD_VARIANT ReleaseConfigType = 3
+)
+
+// Enum value maps for ReleaseConfigType.
+var (
+	ReleaseConfigType_name = map[int32]string{
+		0: "CONFIG_TYPE_UNSPECIFIED",
+		1: "RELEASE_CONFIG",
+		2: "EXPLICIT_INHERITANCE_CONFIG",
+		3: "BUILD_VARIANT",
+	}
+	ReleaseConfigType_value = map[string]int32{
+		"CONFIG_TYPE_UNSPECIFIED":     0,
+		"RELEASE_CONFIG":              1,
+		"EXPLICIT_INHERITANCE_CONFIG": 2,
+		"BUILD_VARIANT":               3,
+	}
+)
+
+func (x ReleaseConfigType) Enum() *ReleaseConfigType {
+	p := new(ReleaseConfigType)
+	*p = x
+	return p
+}
+
+func (x ReleaseConfigType) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (ReleaseConfigType) Descriptor() protoreflect.EnumDescriptor {
+	return file_build_flags_src_proto_enumTypes[0].Descriptor()
+}
+
+func (ReleaseConfigType) Type() protoreflect.EnumType {
+	return &file_build_flags_src_proto_enumTypes[0]
+}
+
+func (x ReleaseConfigType) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Do not use.
+func (x *ReleaseConfigType) UnmarshalJSON(b []byte) error {
+	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
+	if err != nil {
+		return err
+	}
+	*x = ReleaseConfigType(num)
+	return nil
+}
+
+// Deprecated: Use ReleaseConfigType.Descriptor instead.
+func (ReleaseConfigType) EnumDescriptor() ([]byte, []int) {
+	return file_build_flags_src_proto_rawDescGZIP(), []int{0}
+}
+
 type Value struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -159,6 +229,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 +293,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
@@ -329,6 +408,12 @@
 	// Prior stage(s) for flag advancement (during development).
 	// Once a flag has met criteria in a prior stage, it can advance to this one.
 	PriorStages []string `protobuf:"bytes,5,rep,name=prior_stages,json=priorStages" json:"prior_stages,omitempty"`
+	// The ReleaseConfigType of this release config.
+	ReleaseConfigType *ReleaseConfigType `protobuf:"varint,6,opt,name=release_config_type,json=releaseConfigType,enum=android.release_config_proto.ReleaseConfigType" json:"release_config_type,omitempty"`
+	// Whether to disallow this release config as TARGET_RELEASE.
+	// If true, this release config can only be inherited, it cannot be used
+	// directly in a build.
+	DisallowLunchUse *bool `protobuf:"varint,7,opt,name=disallow_lunch_use,json=disallowLunchUse" json:"disallow_lunch_use,omitempty"`
 }
 
 func (x *ReleaseConfig) Reset() {
@@ -398,6 +483,20 @@
 	return nil
 }
 
+func (x *ReleaseConfig) GetReleaseConfigType() ReleaseConfigType {
+	if x != nil && x.ReleaseConfigType != nil {
+		return *x.ReleaseConfigType
+	}
+	return ReleaseConfigType_CONFIG_TYPE_UNSPECIFIED
+}
+
+func (x *ReleaseConfig) GetDisallowLunchUse() bool {
+	if x != nil && x.DisallowLunchUse != nil {
+		return *x.DisallowLunchUse
+	}
+	return false
+}
+
 // Any aliases.  These are used for continuous integration builder config.
 type ReleaseAlias struct {
 	state         protoimpl.MessageState
@@ -541,62 +640,79 @@
 	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, 0xcd, 0x02, 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, 0x12, 0x5f, 0x0a, 0x13, 0x72, 0x65, 0x6c, 0x65,
+	0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18,
+	0x06, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2f, 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, 0x43, 0x6f, 0x6e, 0x66,
+	0x69, 0x67, 0x54, 0x79, 0x70, 0x65, 0x52, 0x11, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x43,
+	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x54, 0x79, 0x70, 0x65, 0x12, 0x2c, 0x0a, 0x12, 0x64, 0x69, 0x73,
+	0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x6c, 0x75, 0x6e, 0x63, 0x68, 0x5f, 0x75, 0x73, 0x65, 0x18,
+	0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x64, 0x69, 0x73, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x4c,
+	0x75, 0x6e, 0x63, 0x68, 0x55, 0x73, 0x65, 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, 0x2a,
+	0x78, 0x0a, 0x11, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
+	0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x5f, 0x54,
+	0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10,
+	0x00, 0x12, 0x12, 0x0a, 0x0e, 0x52, 0x45, 0x4c, 0x45, 0x41, 0x53, 0x45, 0x5f, 0x43, 0x4f, 0x4e,
+	0x46, 0x49, 0x47, 0x10, 0x01, 0x12, 0x1f, 0x0a, 0x1b, 0x45, 0x58, 0x50, 0x4c, 0x49, 0x43, 0x49,
+	0x54, 0x5f, 0x49, 0x4e, 0x48, 0x45, 0x52, 0x49, 0x54, 0x41, 0x4e, 0x43, 0x45, 0x5f, 0x43, 0x4f,
+	0x4e, 0x46, 0x49, 0x47, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x5f,
+	0x56, 0x41, 0x52, 0x49, 0x41, 0x4e, 0x54, 0x10, 0x03, 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 (
@@ -611,26 +727,29 @@
 	return file_build_flags_src_proto_rawDescData
 }
 
+var file_build_flags_src_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
 var file_build_flags_src_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
 var file_build_flags_src_proto_goTypes = []interface{}{
-	(*Value)(nil),            // 0: android.release_config_proto.Value
-	(*FlagDeclaration)(nil),  // 1: android.release_config_proto.FlagDeclaration
-	(*FlagValue)(nil),        // 2: android.release_config_proto.FlagValue
-	(*ReleaseConfig)(nil),    // 3: android.release_config_proto.ReleaseConfig
-	(*ReleaseAlias)(nil),     // 4: android.release_config_proto.ReleaseAlias
-	(*ReleaseConfigMap)(nil), // 5: android.release_config_proto.ReleaseConfigMap
-	(Workflow)(0),            // 6: android.release_config_proto.Workflow
+	(ReleaseConfigType)(0),   // 0: android.release_config_proto.ReleaseConfigType
+	(*Value)(nil),            // 1: android.release_config_proto.Value
+	(*FlagDeclaration)(nil),  // 2: android.release_config_proto.FlagDeclaration
+	(*FlagValue)(nil),        // 3: android.release_config_proto.FlagValue
+	(*ReleaseConfig)(nil),    // 4: android.release_config_proto.ReleaseConfig
+	(*ReleaseAlias)(nil),     // 5: android.release_config_proto.ReleaseAlias
+	(*ReleaseConfigMap)(nil), // 6: android.release_config_proto.ReleaseConfigMap
+	(Workflow)(0),            // 7: android.release_config_proto.Workflow
 }
 var file_build_flags_src_proto_depIdxs = []int32{
-	0, // 0: android.release_config_proto.FlagDeclaration.value:type_name -> android.release_config_proto.Value
-	6, // 1: android.release_config_proto.FlagDeclaration.workflow:type_name -> android.release_config_proto.Workflow
-	0, // 2: android.release_config_proto.FlagValue.value:type_name -> android.release_config_proto.Value
-	4, // 3: android.release_config_proto.ReleaseConfigMap.aliases:type_name -> android.release_config_proto.ReleaseAlias
-	4, // [4:4] is the sub-list for method output_type
-	4, // [4:4] is the sub-list for method input_type
-	4, // [4:4] is the sub-list for extension type_name
-	4, // [4:4] is the sub-list for extension extendee
-	0, // [0:4] is the sub-list for field type_name
+	1, // 0: android.release_config_proto.FlagDeclaration.value:type_name -> android.release_config_proto.Value
+	7, // 1: android.release_config_proto.FlagDeclaration.workflow:type_name -> android.release_config_proto.Workflow
+	1, // 2: android.release_config_proto.FlagValue.value:type_name -> android.release_config_proto.Value
+	0, // 3: android.release_config_proto.ReleaseConfig.release_config_type:type_name -> android.release_config_proto.ReleaseConfigType
+	5, // 4: android.release_config_proto.ReleaseConfigMap.aliases:type_name -> android.release_config_proto.ReleaseAlias
+	5, // [5:5] is the sub-list for method output_type
+	5, // [5:5] is the sub-list for method input_type
+	5, // [5:5] is the sub-list for extension type_name
+	5, // [5:5] is the sub-list for extension extendee
+	0, // [0:5] is the sub-list for field type_name
 }
 
 func init() { file_build_flags_src_proto_init() }
@@ -724,13 +843,14 @@
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_build_flags_src_proto_rawDesc,
-			NumEnums:      0,
+			NumEnums:      1,
 			NumMessages:   6,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
 		GoTypes:           file_build_flags_src_proto_goTypes,
 		DependencyIndexes: file_build_flags_src_proto_depIdxs,
+		EnumInfos:         file_build_flags_src_proto_enumTypes,
 		MessageInfos:      file_build_flags_src_proto_msgTypes,
 	}.Build()
 	File_build_flags_src_proto = out.File
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..2cc1a84 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;
@@ -98,6 +98,24 @@
   optional bool redacted = 202;
 }
 
+enum ReleaseConfigType {
+  // This is treated as `RELEASE_CONFIG`.
+  CONFIG_TYPE_UNSPECIFIED = 0;
+
+  // This is a normal release config.  This is the only ReleaseConfigType with
+  // implicit inheritance.
+  RELEASE_CONFIG = 1;
+
+  // Same as RELEASE_CONFIG, except no implicit inheritance happens.
+  // This is the "root" release config.
+  EXPLICIT_INHERITANCE_CONFIG = 2;
+
+  // This is a release config applied based on the TARGET_BUILD_VARIANT
+  // environment variable, if the build flag RELEASE_BUILD_USE_VARIANT_FLAGS is
+  // enabled.
+  BUILD_VARIANT = 3;
+}
+
 // This replaces $(call declare-release-config).
 message ReleaseConfig {
   // The name of the release config.
@@ -117,6 +135,14 @@
   // Prior stage(s) for flag advancement (during development).
   // Once a flag has met criteria in a prior stage, it can advance to this one.
   repeated string prior_stages = 5;
+
+  // The ReleaseConfigType of this release config.
+  optional ReleaseConfigType release_config_type = 6;
+
+  // Whether to disallow this release config as TARGET_RELEASE.
+  // If true, this release config can only be inherited, it cannot be used
+  // directly in a build.
+  optional bool disallow_lunch_use = 7;
 }
 
 // Any aliases.  These are used for continuous integration builder config.
diff --git a/cmd/release_config/release_config_proto/regen.sh b/cmd/release_config/release_config_proto/regen.sh
old mode 100644
new mode 100755
diff --git a/cmd/release_config/release_config_proto/release_configs_contributions.pb.go b/cmd/release_config/release_config_proto/release_configs_contributions.pb.go
index 54854f1..339e4ba 100644
--- a/cmd/release_config/release_config_proto/release_configs_contributions.pb.go
+++ b/cmd/release_config/release_config_proto/release_configs_contributions.pb.go
@@ -15,7 +15,7 @@
 
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.33.0
+// 	protoc-gen-go v1.30.0
 // 	protoc        v3.21.12
 // source: release_configs_contributions.proto
 
diff --git a/cmd/soong_build/Android.bp b/cmd/soong_build/Android.bp
index 72af3e0..d8f563b 100644
--- a/cmd/soong_build/Android.bp
+++ b/cmd/soong_build/Android.bp
@@ -26,13 +26,11 @@
         "soong",
         "soong-android",
         "soong-provenance",
-        "soong-bp2build",
         "soong-ui-metrics_proto",
     ],
     srcs: [
         "main.go",
         "writedocs.go",
-        "queryview.go",
     ],
     primaryBuilder: true,
 }
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index 5b1ae54..cd4e9bd 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -26,7 +26,6 @@
 
 	"android/soong/android"
 	"android/soong/android/allowlists"
-	"android/soong/bp2build"
 	"android/soong/shared"
 
 	"github.com/google/blueprint"
@@ -65,6 +64,7 @@
 	flag.StringVar(&usedEnvFile, "used_env", "", "File containing used environment variables")
 	flag.StringVar(&cmdlineArgs.OutDir, "out", "", "the ninja builddir directory")
 	flag.StringVar(&cmdlineArgs.ModuleListFile, "l", "", "file that lists filepaths to parse")
+	flag.StringVar(&cmdlineArgs.KatiSuffix, "kati_suffix", "", "the suffix for kati and ninja files, so that different configurations don't clobber each other")
 
 	// Debug flags
 	flag.StringVar(&delveListen, "delve_listen", "", "Delve port to listen on for debugging")
@@ -78,7 +78,6 @@
 	flag.StringVar(&cmdlineArgs.ModuleGraphFile, "module_graph_file", "", "JSON module graph file to output")
 	flag.StringVar(&cmdlineArgs.ModuleActionsFile, "module_actions_file", "", "JSON file to output inputs/outputs of actions of modules")
 	flag.StringVar(&cmdlineArgs.DocFile, "soong_docs", "", "build documentation file to output")
-	flag.StringVar(&cmdlineArgs.BazelQueryViewDir, "bazel_queryview_dir", "", "path to the bazel queryview directory relative to --top")
 	flag.StringVar(&cmdlineArgs.OutFile, "o", "build.ninja", "the Ninja file to output")
 	flag.StringVar(&cmdlineArgs.SoongVariables, "soong_variables", "soong.variables", "the file contains all build variables")
 	flag.BoolVar(&cmdlineArgs.EmptyNinjaFile, "empty-ninja-file", false, "write out a 0-byte ninja file")
@@ -121,16 +120,6 @@
 	return false
 }
 
-// Run the code-generation phase to convert BazelTargetModules to BUILD files.
-func runQueryView(queryviewDir, queryviewMarker string, ctx *android.Context) {
-	ctx.EventHandler.Begin("queryview")
-	defer ctx.EventHandler.End("queryview")
-	codegenContext := bp2build.NewCodegenContext(ctx.Config(), ctx, bp2build.QueryView, topDir)
-	err := createBazelWorkspace(codegenContext, shared.JoinPath(topDir, queryviewDir), false)
-	maybeQuit(err, "")
-	touch(shared.JoinPath(topDir, queryviewMarker))
-}
-
 func writeNinjaHint(ctx *android.Context) error {
 	ctx.BeginEvent("ninja_hint")
 	defer ctx.EndEvent("ninja_hint")
@@ -283,7 +272,7 @@
 	switch ctx.Config().BuildMode {
 	case android.GenerateModuleGraph:
 		stopBefore = bootstrap.StopBeforeWriteNinja
-	case android.GenerateQueryView, android.GenerateDocFile:
+	case android.GenerateDocFile:
 		stopBefore = bootstrap.StopBeforePrepareBuildActions
 	default:
 		stopBefore = bootstrap.DoEverything
@@ -294,10 +283,6 @@
 
 	// Convert the Soong module graph into Bazel BUILD files.
 	switch ctx.Config().BuildMode {
-	case android.GenerateQueryView:
-		queryviewMarkerFile := cmdlineArgs.BazelQueryViewDir + ".marker"
-		runQueryView(cmdlineArgs.BazelQueryViewDir, queryviewMarkerFile, ctx)
-		return queryviewMarkerFile, ninjaDeps
 	case android.GenerateModuleGraph:
 		writeJsonModuleGraphAndActions(ctx, cmdlineArgs)
 		return cmdlineArgs.ModuleGraphFile, ninjaDeps
diff --git a/cmd/soong_build/queryview.go b/cmd/soong_build/queryview.go
deleted file mode 100644
index eafd67a..0000000
--- a/cmd/soong_build/queryview.go
+++ /dev/null
@@ -1,112 +0,0 @@
-// Copyright 2020 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 (
-	"io/fs"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-
-	"android/soong/android"
-	"android/soong/bp2build"
-)
-
-// A helper function to generate a Read-only Bazel workspace in outDir
-func createBazelWorkspace(ctx *bp2build.CodegenContext, outDir string, generateFilegroups bool) error {
-	os.RemoveAll(outDir)
-	ruleShims := bp2build.CreateRuleShims(android.ModuleTypeFactories())
-
-	res, err := bp2build.GenerateBazelTargets(ctx, generateFilegroups)
-	if err != nil {
-		panic(err)
-	}
-
-	filesToWrite := bp2build.CreateBazelFiles(ruleShims, res.BuildDirToTargets(), ctx.Mode())
-	bazelRcFiles, err2 := CopyBazelRcFiles()
-	if err2 != nil {
-		return err2
-	}
-	filesToWrite = append(filesToWrite, bazelRcFiles...)
-	for _, f := range filesToWrite {
-		if err := writeReadOnlyFile(outDir, f); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
-// CopyBazelRcFiles creates BazelFiles for all the bazelrc files under
-// build/bazel. They're needed because the rc files are still read when running
-// queryview, so they have to be in the queryview workspace.
-func CopyBazelRcFiles() ([]bp2build.BazelFile, error) {
-	result := make([]bp2build.BazelFile, 0)
-	err := filepath.WalkDir(filepath.Join(topDir, "build/bazel"), func(path string, info fs.DirEntry, err error) error {
-		if filepath.Ext(path) == ".bazelrc" {
-			contents, err := os.ReadFile(path)
-			if err != nil {
-				return err
-			}
-			path, err = filepath.Rel(topDir, path)
-			if err != nil {
-				return err
-			}
-			result = append(result, bp2build.BazelFile{
-				Dir:      filepath.Dir(path),
-				Basename: filepath.Base(path),
-				Contents: string(contents),
-			})
-		}
-		return nil
-	})
-	return result, err
-}
-
-// The auto-conversion directory should be read-only, sufficient for bazel query. The files
-// are not intended to be edited by end users.
-func writeReadOnlyFile(dir string, f bp2build.BazelFile) error {
-	dir = filepath.Join(dir, f.Dir)
-	if err := createDirectoryIfNonexistent(dir); err != nil {
-		return err
-	}
-	pathToFile := filepath.Join(dir, f.Basename)
-
-	// 0444 is read-only
-	err := ioutil.WriteFile(pathToFile, []byte(f.Contents), 0444)
-
-	return err
-}
-
-func writeReadWriteFile(dir string, f bp2build.BazelFile) error {
-	dir = filepath.Join(dir, f.Dir)
-	if err := createDirectoryIfNonexistent(dir); err != nil {
-		return err
-	}
-	pathToFile := filepath.Join(dir, f.Basename)
-
-	// 0644 is read-write
-	err := ioutil.WriteFile(pathToFile, []byte(f.Contents), 0644)
-
-	return err
-}
-
-func createDirectoryIfNonexistent(dir string) error {
-	if _, err := os.Stat(dir); os.IsNotExist(err) {
-		return os.MkdirAll(dir, os.ModePerm)
-	} else {
-		return err
-	}
-}
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index 2d3156a..4f6de82 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -27,6 +27,7 @@
 
 	"android/soong/shared"
 	"android/soong/ui/build"
+	"android/soong/ui/execution_metrics"
 	"android/soong/ui/logger"
 	"android/soong/ui/metrics"
 	"android/soong/ui/signal"
@@ -170,14 +171,16 @@
 		stat.Finish()
 	})
 	criticalPath := status.NewCriticalPath()
+	emet := execution_metrics.NewExecutionMetrics(log)
 	buildCtx := build.Context{ContextImpl: &build.ContextImpl{
-		Context:      ctx,
-		Logger:       log,
-		Metrics:      met,
-		Tracer:       trace,
-		Writer:       output,
-		Status:       stat,
-		CriticalPath: criticalPath,
+		Context:          ctx,
+		Logger:           log,
+		Metrics:          met,
+		ExecutionMetrics: emet,
+		Tracer:           trace,
+		Writer:           output,
+		Status:           stat,
+		CriticalPath:     criticalPath,
 	}}
 
 	freshConfig := func() build.Config {
@@ -194,6 +197,7 @@
 	rbeMetricsFile := filepath.Join(logsDir, c.logsPrefix+"rbe_metrics.pb")
 	soongBuildMetricsFile := filepath.Join(logsDir, c.logsPrefix+"soong_build_metrics.pb")
 	buildTraceFile := filepath.Join(logsDir, c.logsPrefix+"build.trace.gz")
+	executionMetricsFile := filepath.Join(logsDir, c.logsPrefix+"execution_metrics.pb")
 
 	metricsFiles := []string{
 		buildErrorFile,        // build error strings
@@ -204,9 +208,15 @@
 	}
 
 	defer func() {
+		emet.Finish(buildCtx)
 		stat.Finish()
 		criticalPath.WriteToMetrics(met)
 		met.Dump(soongMetricsFile)
+		emet.Dump(executionMetricsFile, args)
+		// If there are execution metrics, upload them.
+		if _, err := os.Stat(executionMetricsFile); err == nil {
+			metricsFiles = append(metricsFiles, executionMetricsFile)
+		}
 		if !config.SkipMetricsUpload() {
 			build.UploadMetrics(buildCtx, config, c.simpleOutput, buildStarted, metricsFiles...)
 		}
@@ -251,11 +261,10 @@
 	buildErrorFile := filepath.Join(logsDir, logsPrefix+"build_error")
 	rbeMetricsFile := filepath.Join(logsDir, logsPrefix+"rbe_metrics.pb")
 	soongMetricsFile := filepath.Join(logsDir, logsPrefix+"soong_metrics")
-	bp2buildMetricsFile := filepath.Join(logsDir, logsPrefix+"bp2build_metrics.pb")
 	soongBuildMetricsFile := filepath.Join(logsDir, logsPrefix+"soong_build_metrics.pb")
 
 	//Delete the stale metrics files
-	staleFileSlice := []string{buildErrorFile, rbeMetricsFile, soongMetricsFile, bp2buildMetricsFile, soongBuildMetricsFile}
+	staleFileSlice := []string{buildErrorFile, rbeMetricsFile, soongMetricsFile, soongBuildMetricsFile}
 	if err := deleteStaleMetrics(staleFileSlice); err != nil {
 		log.Fatalln(err)
 	}
@@ -329,7 +338,7 @@
 			ctx.Fatal(err)
 		}
 
-		fmt.Println(build.Banner(varData))
+		fmt.Println(build.Banner(config, varData))
 	} else {
 		varData, err := build.DumpMakeVars(ctx, config, nil, []string{varName})
 		if err != nil {
@@ -405,7 +414,7 @@
 
 	for _, name := range vars {
 		if name == "report_config" {
-			fmt.Printf("%sreport_config='%s'\n", *varPrefix, build.Banner(varData))
+			fmt.Printf("%sreport_config='%s'\n", *varPrefix, build.Banner(config, varData))
 		} else {
 			fmt.Printf("%s%s='%s'\n", *varPrefix, name, varData[name])
 		}
@@ -462,20 +471,6 @@
 		description: "Build action: build from the top of the source tree.",
 		action:      build.BUILD_MODULES,
 	}, {
-		// This is redirecting to mma build command behaviour. Once it has soaked for a
-		// while, the build command is deleted from here once it has been removed from the
-		// envsetup.sh.
-		name:        "modules-in-a-dir-no-deps",
-		description: "Build action: builds all of the modules in the current directory without their dependencies.",
-		action:      build.BUILD_MODULES_IN_A_DIRECTORY,
-	}, {
-		// This is redirecting to mmma build command behaviour. Once it has soaked for a
-		// while, the build command is deleted from here once it has been removed from the
-		// envsetup.sh.
-		name:        "modules-in-dirs-no-deps",
-		description: "Build action: builds all of the modules in the supplied directories without their dependencies.",
-		action:      build.BUILD_MODULES_IN_DIRECTORIES,
-	}, {
 		name:        "modules-in-a-dir",
 		description: "Build action: builds all of the modules in the current directory and their dependencies.",
 		action:      build.BUILD_MODULES_IN_A_DIRECTORY,
diff --git a/testing/code_metadata_internal_proto/Android.bp b/cmd/source_tree_size/Android.bp
similarity index 64%
copy from testing/code_metadata_internal_proto/Android.bp
copy to cmd/source_tree_size/Android.bp
index 396e44f..97d29c6 100644
--- a/testing/code_metadata_internal_proto/Android.bp
+++ b/cmd/source_tree_size/Android.bp
@@ -1,4 +1,4 @@
-// Copyright 2022 Google Inc. All rights reserved.
+// 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.
@@ -16,18 +16,13 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-bootstrap_go_package {
-    name: "soong-testing-code_metadata_internal_proto",
-    pkgPath: "android/soong/testing/code_metadata_internal_proto",
-    deps: [
-        "golang-protobuf-reflect-protoreflect",
-        "golang-protobuf-runtime-protoimpl",
-    ],
+blueprint_go_binary {
+    name: "source_tree_size",
     srcs: [
-        "code_metadata_internal.pb.go",
+        "source_tree.pb.go",
+        "source_tree_size.go",
     ],
-    visibility: [
-        "//build/make/tools/metadata",
-        "//build/soong:__subpackages__",
+    deps: [
+        "golang-protobuf-runtime-protoimpl",
     ],
 }
diff --git a/cmd/source_tree_size/regen.sh b/cmd/source_tree_size/regen.sh
new file mode 100755
index 0000000..b0f1c80
--- /dev/null
+++ b/cmd/source_tree_size/regen.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+aprotoc --go_out=paths=source_relative:. source_tree.proto
+
diff --git a/cmd/source_tree_size/source_tree.pb.go b/cmd/source_tree_size/source_tree.pb.go
new file mode 100644
index 0000000..da8cba5
--- /dev/null
+++ b/cmd/source_tree_size/source_tree.pb.go
@@ -0,0 +1,230 @@
+// 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.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.30.0
+// 	protoc        v3.21.12
+// source: source_tree.proto
+
+package main
+
+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 SourceFile struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Path      *string `protobuf:"bytes,1,opt,name=path" json:"path,omitempty"`
+	SizeBytes *int32  `protobuf:"varint,2,opt,name=size_bytes,json=sizeBytes" json:"size_bytes,omitempty"`
+}
+
+func (x *SourceFile) Reset() {
+	*x = SourceFile{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_source_tree_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SourceFile) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SourceFile) ProtoMessage() {}
+
+func (x *SourceFile) ProtoReflect() protoreflect.Message {
+	mi := &file_source_tree_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 SourceFile.ProtoReflect.Descriptor instead.
+func (*SourceFile) Descriptor() ([]byte, []int) {
+	return file_source_tree_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *SourceFile) GetPath() string {
+	if x != nil && x.Path != nil {
+		return *x.Path
+	}
+	return ""
+}
+
+func (x *SourceFile) GetSizeBytes() int32 {
+	if x != nil && x.SizeBytes != nil {
+		return *x.SizeBytes
+	}
+	return 0
+}
+
+type SourceTree struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	Files []*SourceFile `protobuf:"bytes,1,rep,name=files" json:"files,omitempty"`
+}
+
+func (x *SourceTree) Reset() {
+	*x = SourceTree{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_source_tree_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SourceTree) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SourceTree) ProtoMessage() {}
+
+func (x *SourceTree) ProtoReflect() protoreflect.Message {
+	mi := &file_source_tree_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 SourceTree.ProtoReflect.Descriptor instead.
+func (*SourceTree) Descriptor() ([]byte, []int) {
+	return file_source_tree_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *SourceTree) GetFiles() []*SourceFile {
+	if x != nil {
+		return x.Files
+	}
+	return nil
+}
+
+var File_source_tree_proto protoreflect.FileDescriptor
+
+var file_source_tree_proto_rawDesc = []byte{
+	0x0a, 0x11, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x74, 0x72, 0x65, 0x65, 0x2e, 0x70, 0x72,
+	0x6f, 0x74, 0x6f, 0x12, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65,
+	0x73, 0x22, 0x3f, 0x0a, 0x0a, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x12,
+	0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70,
+	0x61, 0x74, 0x68, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x62, 0x79, 0x74, 0x65,
+	0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x73, 0x69, 0x7a, 0x65, 0x42, 0x79, 0x74,
+	0x65, 0x73, 0x22, 0x3c, 0x0a, 0x0a, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x54, 0x72, 0x65, 0x65,
+	0x12, 0x2e, 0x0a, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
+	0x18, 0x2e, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x2e, 0x53,
+	0x6f, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x52, 0x05, 0x66, 0x69, 0x6c, 0x65, 0x73,
+	0x42, 0x08, 0x5a, 0x06, 0x2e, 0x2f, 0x6d, 0x61, 0x69, 0x6e,
+}
+
+var (
+	file_source_tree_proto_rawDescOnce sync.Once
+	file_source_tree_proto_rawDescData = file_source_tree_proto_rawDesc
+)
+
+func file_source_tree_proto_rawDescGZIP() []byte {
+	file_source_tree_proto_rawDescOnce.Do(func() {
+		file_source_tree_proto_rawDescData = protoimpl.X.CompressGZIP(file_source_tree_proto_rawDescData)
+	})
+	return file_source_tree_proto_rawDescData
+}
+
+var file_source_tree_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_source_tree_proto_goTypes = []interface{}{
+	(*SourceFile)(nil), // 0: source_files.SourceFile
+	(*SourceTree)(nil), // 1: source_files.SourceTree
+}
+var file_source_tree_proto_depIdxs = []int32{
+	0, // 0: source_files.SourceTree.files:type_name -> source_files.SourceFile
+	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_source_tree_proto_init() }
+func file_source_tree_proto_init() {
+	if File_source_tree_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_source_tree_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SourceFile); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_source_tree_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SourceTree); 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_source_tree_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   2,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_source_tree_proto_goTypes,
+		DependencyIndexes: file_source_tree_proto_depIdxs,
+		MessageInfos:      file_source_tree_proto_msgTypes,
+	}.Build()
+	File_source_tree_proto = out.File
+	file_source_tree_proto_rawDesc = nil
+	file_source_tree_proto_goTypes = nil
+	file_source_tree_proto_depIdxs = nil
+}
diff --git a/cmd/multiproduct_kati/main_linux.go b/cmd/source_tree_size/source_tree.proto
similarity index 61%
rename from cmd/multiproduct_kati/main_linux.go
rename to cmd/source_tree_size/source_tree.proto
index db74496..1a5b89f 100644
--- a/cmd/multiproduct_kati/main_linux.go
+++ b/cmd/source_tree_size/source_tree.proto
@@ -1,10 +1,10 @@
-// Copyright 2017 Google Inc. All rights reserved.
+// 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
+//   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,
@@ -12,17 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package main
+syntax = "proto2";
 
-import (
-	"syscall"
-)
+package source_files;
+option go_package = "./main";
 
-func detectTotalRAM() uint64 {
-	var info syscall.Sysinfo_t
-	err := syscall.Sysinfo(&info)
-	if err != nil {
-		panic(err)
-	}
-	return info.Totalram * uint64(info.Unit)
+message SourceFile {
+  optional string path = 1;
+  optional int32 size_bytes = 2;
+}
+
+message SourceTree {
+  repeated SourceFile files = 1;
 }
diff --git a/cmd/source_tree_size/source_tree_size.go b/cmd/source_tree_size/source_tree_size.go
new file mode 100644
index 0000000..8b2a4cf
--- /dev/null
+++ b/cmd/source_tree_size/source_tree_size.go
@@ -0,0 +1,126 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"path"
+	"path/filepath"
+	"sort"
+	"strings"
+	"sync"
+
+	"google.golang.org/protobuf/proto"
+)
+
+var root string
+var repo string
+var outDir string
+var channel chan SourceFile
+var waiter sync.WaitGroup
+var sourceTree SourceTree
+
+func normalizeOutDir(outDir, root string) string {
+	if len(outDir) == 0 {
+		return ""
+	}
+	if filepath.IsAbs(outDir) {
+		if strings.HasPrefix(outDir, root) {
+			// Absolute path inside root, use it
+			return outDir
+		} else {
+			// Not inside root, we don't care about it
+			return ""
+		}
+	} else {
+		// Relative path inside root, make it absolute
+		return root + outDir
+	}
+}
+
+func walkDir(dir string) {
+	defer waiter.Done()
+
+	visit := func(path string, info os.FileInfo, err error) error {
+		name := info.Name()
+
+		// Repo git projects are symlinks.  A real directory called .git counts as checked in
+		// (and is very likely to be wasted space)
+		if info.Mode().Type()&os.ModeSymlink != 0 && name == ".git" {
+			return nil
+		}
+
+		// Skip .repo and out
+		if info.IsDir() && (path == repo || path == outDir) {
+			return filepath.SkipDir
+		}
+
+		if info.IsDir() && path != dir {
+			waiter.Add(1)
+			go walkDir(path)
+			return filepath.SkipDir
+		}
+
+		if !info.IsDir() {
+			sourcePath := strings.TrimPrefix(path, root)
+			file := SourceFile{
+				Path:      proto.String(sourcePath),
+				SizeBytes: proto.Int32(42),
+			}
+			channel <- file
+		}
+		return nil
+
+	}
+	filepath.Walk(dir, visit)
+}
+
+func main() {
+	var outputFile string
+	flag.StringVar(&outputFile, "o", "", "The file to write")
+	flag.StringVar(&outDir, "out_dir", "out", "The out directory")
+	flag.Parse()
+
+	if outputFile == "" {
+		fmt.Fprintf(os.Stderr, "source_tree_size: Missing argument: -o\n")
+		os.Exit(1)
+	}
+
+	root, _ = os.Getwd()
+	if root[len(root)-1] != '/' {
+		root += "/"
+	}
+
+	outDir = normalizeOutDir(outDir, root)
+	repo = path.Join(root, ".repo")
+
+	// The parallel scanning reduces the run time by about a minute
+	channel = make(chan SourceFile)
+	waiter.Add(1)
+	go walkDir(root)
+	go func() {
+		waiter.Wait()
+		close(channel)
+	}()
+	for sourceFile := range channel {
+		sourceTree.Files = append(sourceTree.Files, &sourceFile)
+	}
+
+	// Sort the results, for a stable output
+	sort.Slice(sourceTree.Files, func(i, j int) bool {
+		return *sourceTree.Files[i].Path < *sourceTree.Files[j].Path
+	})
+
+	// Flatten and write
+	buf, err := proto.Marshal(&sourceTree)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Couldn't marshal protobuf\n")
+		os.Exit(1)
+	}
+	err = ioutil.WriteFile(outputFile, buf, 0644)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Error writing file: %v\n", err)
+		os.Exit(1)
+	}
+}
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..25f6f86 100644
--- a/compliance/Android.bp
+++ b/compliance/Android.bp
@@ -34,6 +34,41 @@
     name: "notice_xml_system",
     partition_name: "system",
     visibility: [
-        "//build/make/target/product/generic",
+        "//visibility:any_system_partition",
     ],
 }
+
+notice_xml {
+    name: "notice_xml_system_ext",
+    partition_name: "system_ext",
+}
+
+notice_xml {
+    name: "notice_xml_system_dlkm",
+    partition_name: "system_dlkm",
+}
+
+notice_xml {
+    name: "notice_xml_product",
+    partition_name: "product",
+}
+
+notice_xml {
+    name: "notice_xml_odm",
+    partition_name: "odm",
+}
+
+notice_xml {
+    name: "notice_xml_odm_dlkm",
+    partition_name: "odm_dlkm",
+}
+
+notice_xml {
+    name: "notice_xml_vendor",
+    partition_name: "vendor",
+}
+
+notice_xml {
+    name: "notice_xml_vendor_dlkm",
+    partition_name: "vendor_dlkm",
+}
diff --git a/compliance/notice.go b/compliance/notice.go
index 4fc83ab..c5b0fbe 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 {
@@ -71,12 +71,17 @@
 }
 
 func (nx *NoticeXmlModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	prodVars := ctx.Config().ProductVariables()
+	buildFingerprintFile := android.PathForArbitraryOutput(ctx, "target", "product", android.String(prodVars.DeviceName), "build_fingerprint.txt")
+	implicits := []android.Path{buildFingerprintFile}
+
 	output := android.PathForModuleOut(ctx, "NOTICE.xml.gz")
 	metadataDb := android.PathForOutput(ctx, "compliance-metadata", ctx.Config().DeviceProduct(), "compliance-metadata.db")
 	ctx.Build(pctx, android.BuildParams{
-		Rule:   genNoticeXmlRule,
-		Input:  metadataDb,
-		Output: output,
+		Rule:      genNoticeXmlRule,
+		Input:     metadataDb,
+		Implicits: implicits,
+		Output:    output,
 		Args: map[string]string{
 			"productOut": filepath.Join(ctx.Config().OutDir(), "target", "product", ctx.Config().DeviceName()),
 			"soongOut":   ctx.Config().SoongOutDir(),
@@ -86,9 +91,9 @@
 
 	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)
+	if android.Bool(ctx.Config().ProductVariables().UseSoongNoticeXML) {
+		installPath := android.PathForModuleInPartitionInstall(ctx, nx.props.Partition_name, "etc")
+		ctx.InstallFile(installPath, "NOTICE.xml.gz", nx.outputFile)
 	}
 }
 
diff --git a/compliance/notice_test.go b/compliance/notice_test.go
index 6187e53..e8578ec 100644
--- a/compliance/notice_test.go
+++ b/compliance/notice_test.go
@@ -35,4 +35,4 @@
 
 	m := result.Module("notice_xml_system", "android_arm64_armv8-a").(*NoticeXmlModule)
 	android.AssertStringEquals(t, "output file", "NOTICE.xml.gz", m.outputFile.Base())
-}
\ No newline at end of file
+}
diff --git a/cuj/Android.bp b/cuj/Android.bp
deleted file mode 100644
index f9cfdc1..0000000
--- a/cuj/Android.bp
+++ /dev/null
@@ -1,17 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-blueprint_go_binary {
-    name: "cuj_tests",
-    deps: [
-        "soong-ui-build",
-        "soong-ui-logger",
-        "soong-ui-signal",
-        "soong-ui-terminal",
-        "soong-ui-tracer",
-    ],
-    srcs: [
-        "cuj.go",
-    ],
-}
diff --git a/cuj/cuj.go b/cuj/cuj.go
deleted file mode 100644
index de6f10d..0000000
--- a/cuj/cuj.go
+++ /dev/null
@@ -1,239 +0,0 @@
-// Copyright 2019 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.
-
-// This executable runs a series of build commands to test and benchmark some critical user journeys.
-package main
-
-import (
-	"context"
-	"fmt"
-	"os"
-	"path/filepath"
-	"strconv"
-	"strings"
-	"time"
-
-	"android/soong/ui/build"
-	"android/soong/ui/logger"
-	"android/soong/ui/metrics"
-	"android/soong/ui/signal"
-	"android/soong/ui/status"
-	"android/soong/ui/terminal"
-	"android/soong/ui/tracer"
-)
-
-type Test struct {
-	name   string
-	args   []string
-	before func() error
-
-	results TestResults
-}
-
-type TestResults struct {
-	metrics *metrics.Metrics
-	err     error
-}
-
-// Run runs a single build command.  It emulates the "m" command line by calling into Soong UI directly.
-func (t *Test) Run(logsDir string) {
-	output := terminal.NewStatusOutput(os.Stdout, "", false, false, false)
-
-	log := logger.New(output)
-	defer log.Cleanup()
-
-	ctx, cancel := context.WithCancel(context.Background())
-	defer cancel()
-
-	trace := tracer.New(log)
-	defer trace.Close()
-
-	met := metrics.New()
-
-	stat := &status.Status{}
-	defer stat.Finish()
-	stat.AddOutput(output)
-	stat.AddOutput(trace.StatusTracer())
-
-	signal.SetupSignals(log, cancel, func() {
-		trace.Close()
-		log.Cleanup()
-		stat.Finish()
-	})
-
-	buildCtx := build.Context{ContextImpl: &build.ContextImpl{
-		Context: ctx,
-		Logger:  log,
-		Metrics: met,
-		Tracer:  trace,
-		Writer:  output,
-		Status:  stat,
-	}}
-
-	defer logger.Recover(func(err error) {
-		t.results.err = err
-	})
-
-	config := build.NewConfig(buildCtx, t.args...)
-	build.SetupOutDir(buildCtx, config)
-
-	os.MkdirAll(logsDir, 0777)
-	log.SetOutput(filepath.Join(logsDir, "soong.log"))
-	trace.SetOutput(filepath.Join(logsDir, "build.trace"))
-	stat.AddOutput(status.NewVerboseLog(log, filepath.Join(logsDir, "verbose.log")))
-	stat.AddOutput(status.NewErrorLog(log, filepath.Join(logsDir, "error.log")))
-	stat.AddOutput(status.NewProtoErrorLog(log, filepath.Join(logsDir, "build_error")))
-	stat.AddOutput(status.NewCriticalPathLogger(log, nil))
-
-	defer met.Dump(filepath.Join(logsDir, "soong_metrics"))
-
-	if start, ok := os.LookupEnv("TRACE_BEGIN_SOONG"); ok {
-		if !strings.HasSuffix(start, "N") {
-			if start_time, err := strconv.ParseUint(start, 10, 64); err == nil {
-				log.Verbosef("Took %dms to start up.",
-					time.Since(time.Unix(0, int64(start_time))).Nanoseconds()/time.Millisecond.Nanoseconds())
-				buildCtx.CompleteTrace(metrics.RunSetupTool, "startup", start_time, uint64(time.Now().UnixNano()))
-			}
-		}
-
-		if executable, err := os.Executable(); err == nil {
-			trace.ImportMicrofactoryLog(filepath.Join(filepath.Dir(executable), "."+filepath.Base(executable)+".trace"))
-		}
-	}
-
-	f := build.NewSourceFinder(buildCtx, config)
-	defer f.Shutdown()
-	build.FindSources(buildCtx, config, f)
-
-	build.Build(buildCtx, config)
-
-	t.results.metrics = met
-}
-
-// Touch the Intent.java file to cause a rebuild of the frameworks to monitor the
-// incremental build speed as mentioned b/152046247. Intent.java file was chosen
-// as it is a key component of the framework and is often modified.
-func touchIntentFile() error {
-	const intentFileName = "frameworks/base/core/java/android/content/Intent.java"
-	currentTime := time.Now().Local()
-	return os.Chtimes(intentFileName, currentTime, currentTime)
-}
-
-func main() {
-	outDir := os.Getenv("OUT_DIR")
-	if outDir == "" {
-		outDir = "out"
-	}
-
-	cujDir := filepath.Join(outDir, "cuj_tests")
-
-	wd, _ := os.Getwd()
-	os.Setenv("TOP", wd)
-	// Use a subdirectory for the out directory for the tests to keep them isolated.
-	os.Setenv("OUT_DIR", filepath.Join(cujDir, "out"))
-
-	// Each of these tests is run in sequence without resetting the output tree.  The state of the output tree will
-	// affect each successive test.  To maintain the validity of the benchmarks across changes, care must be taken
-	// to avoid changing the state of the tree when a test is run.  This is most easily accomplished by adding tests
-	// at the end.
-	tests := []Test{
-		{
-			// Reset the out directory to get reproducible results.
-			name: "clean",
-			args: []string{"clean"},
-		},
-		{
-			// Parse the build files.
-			name: "nothing",
-			args: []string{"nothing"},
-		},
-		{
-			// Parse the build files again to monitor issues like globs rerunning.
-			name: "nothing_rebuild",
-			args: []string{"nothing"},
-		},
-		{
-			// Parse the build files again, this should always be very short.
-			name: "nothing_rebuild_twice",
-			args: []string{"nothing"},
-		},
-		{
-			// Build the framework as a common developer task and one that keeps getting longer.
-			name: "framework",
-			args: []string{"framework"},
-		},
-		{
-			// Build the framework again to make sure it doesn't rebuild anything.
-			name: "framework_rebuild",
-			args: []string{"framework"},
-		},
-		{
-			// Build the framework again to make sure it doesn't rebuild anything even if it did the second time.
-			name: "framework_rebuild_twice",
-			args: []string{"framework"},
-		},
-		{
-			// Scenario major_inc_build (b/152046247): tracking build speed of major incremental build.
-			name: "major_inc_build_droid",
-			args: []string{"droid"},
-		},
-		{
-			name:   "major_inc_build_framework_minus_apex_after_droid_build",
-			args:   []string{"framework-minus-apex"},
-			before: touchIntentFile,
-		},
-		{
-			name:   "major_inc_build_framework_after_droid_build",
-			args:   []string{"framework"},
-			before: touchIntentFile,
-		},
-		{
-			name:   "major_inc_build_sync_after_droid_build",
-			args:   []string{"sync"},
-			before: touchIntentFile,
-		},
-		{
-			name:   "major_inc_build_droid_rebuild",
-			args:   []string{"droid"},
-			before: touchIntentFile,
-		},
-		{
-			name:   "major_inc_build_update_api_after_droid_rebuild",
-			args:   []string{"update-api"},
-			before: touchIntentFile,
-		},
-	}
-
-	cujMetrics := metrics.NewCriticalUserJourneysMetrics()
-	defer cujMetrics.Dump(filepath.Join(cujDir, "logs", "cuj_metrics.pb"))
-
-	for i, t := range tests {
-		logsSubDir := fmt.Sprintf("%02d_%s", i, t.name)
-		logsDir := filepath.Join(cujDir, "logs", logsSubDir)
-		if t.before != nil {
-			if err := t.before(); err != nil {
-				fmt.Printf("error running before function on test %q: %v\n", t.name, err)
-				break
-			}
-		}
-		t.Run(logsDir)
-		if t.results.err != nil {
-			fmt.Printf("error running test %q: %s\n", t.name, t.results.err)
-			break
-		}
-		if t.results.metrics != nil {
-			cujMetrics.Add(t.name, t.results.metrics)
-		}
-	}
-}
diff --git a/cuj/run_cuj_tests.sh b/cuj/run_cuj_tests.sh
deleted file mode 100755
index a746bd5..0000000
--- a/cuj/run_cuj_tests.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/bin/bash -e
-
-readonly UNAME="$(uname)"
-case "$UNAME" in
-Linux)
-    readonly OS='linux'
-    ;;
-Darwin)
-    readonly OS='darwin'
-    ;;
-*)
-    echo "Unsupported OS '$UNAME'"
-    exit 1
-    ;;
-esac
-
-readonly ANDROID_TOP="$(cd $(dirname $0)/../../..; pwd)"
-cd "$ANDROID_TOP"
-
-export OUT_DIR="${OUT_DIR:-out}"
-
-build/soong/soong_ui.bash --make-mode "${OUT_DIR}/host/${OS}-x86/bin/cuj_tests"
-
-"${OUT_DIR}/host/${OS}-x86/bin/cuj_tests" || true
-
-if [ -n "${DIST_DIR}" ]; then
-  cp -r "${OUT_DIR}/cuj_tests/logs" "${DIST_DIR}"
-fi
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index 84d4f10..c5cafb1 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -462,6 +462,12 @@
 	return target.IsReplacedByPrebuilt()
 }
 
+func (d dex2oatDependencyTag) AllowDisabledModuleDependencyProxy(
+	ctx android.OtherModuleProviderContext, target android.ModuleProxy) bool {
+	return android.OtherModuleProviderOrDefault(
+		ctx, target, android.CommonModuleInfoKey).ReplacedByPrebuilt
+}
+
 // Dex2oatDepTag represents the dependency onto the dex2oatd module. It is added to any module that
 // needs dexpreopting and so it makes no sense for it to be checked for visibility or included in
 // the apex.
@@ -501,36 +507,37 @@
 	// final variants, while prebuilt_postdeps needs to run before many of the
 	// PostDeps mutators, like the APEX mutators). Hence we need to dig out the
 	// prebuilt explicitly here instead.
-	var dex2oatModule android.Module
-	ctx.WalkDeps(func(child, parent android.Module) bool {
-		if parent == ctx.Module() && ctx.OtherModuleDependencyTag(child) == Dex2oatDepTag {
+	var dex2oatModule android.ModuleProxy
+	ctx.WalkDepsProxy(func(child, parent android.ModuleProxy) bool {
+		prebuiltInfo, isPrebuilt := android.OtherModuleProvider(ctx, child, android.PrebuiltModuleInfoProvider)
+		if ctx.EqualModules(parent, ctx.Module()) && ctx.OtherModuleDependencyTag(child) == Dex2oatDepTag {
 			// Found the source module, or prebuilt module that has replaced the source.
 			dex2oatModule = child
-			if android.IsModulePrebuilt(child) {
+			if isPrebuilt {
 				return false // If it's the prebuilt we're done.
 			} else {
 				return true // Recurse to check if the source has a prebuilt dependency.
 			}
 		}
-		if parent == dex2oatModule && ctx.OtherModuleDependencyTag(child) == android.PrebuiltDepTag {
-			if p := android.GetEmbeddedPrebuilt(child); p != nil && p.UsePrebuilt() {
+		if ctx.EqualModules(parent, dex2oatModule) && ctx.OtherModuleDependencyTag(child) == android.PrebuiltDepTag {
+			if isPrebuilt && prebuiltInfo.UsePrebuilt {
 				dex2oatModule = child // Found a prebuilt that should be used.
 			}
 		}
 		return false
 	})
 
-	if dex2oatModule == nil {
+	if dex2oatModule.IsNil() {
 		// If this happens there's probably a missing call to AddToolDeps in DepsMutator.
 		panic(fmt.Sprintf("Failed to lookup %s dependency", dex2oatBin))
 	}
 
-	dex2oatPath := dex2oatModule.(android.HostToolProvider).HostToolPath()
-	if !dex2oatPath.Valid() {
+	dex2oatPath, ok := android.OtherModuleProvider(ctx, dex2oatModule, android.HostToolProviderInfoProvider)
+	if !ok || !dex2oatPath.HostToolPath.Valid() {
 		panic(fmt.Sprintf("Failed to find host tool path in %s", dex2oatModule))
 	}
 
-	return dex2oatPath.Path()
+	return dex2oatPath.HostToolPath.Path()
 }
 
 // createGlobalSoongConfig creates a GlobalSoongConfig from the current context.
@@ -712,6 +719,16 @@
 	} else if global.EnableUffdGc == "default" {
 		// Generated by `build/make/core/Makefile`.
 		kernelVersionFile := android.PathForOutput(ctx, "dexpreopt/kernel_version_for_uffd_gc.txt")
+		if !ctx.Config().KatiEnabled() {
+			// In soong-only mode, we need to generate the kernel_version_for_uffd_gc.txt with kernel version
+			kernelVersion := android.String(ctx.Config().ProductVariables().BoardKernelVersion)
+			if kernelVersion == "" {
+				// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=5382;drc=66783fca85911af9da48d9b4f35a61b3873023e9
+				panic("BOARD_KERNEL_VERSION is not set. Build team will convert more stuff from Make to Soong to support this scenario.")
+			}
+			android.WriteFileRule(ctx, kernelVersionFile, kernelVersion)
+		}
+
 		// Determine the UFFD GC flag by the kernel version file.
 		rule := android.NewRuleBuilder(pctx, ctx)
 		rule.Command().
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index 7a39fa1..e882470 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -46,13 +46,14 @@
 
 const SystemPartition = "/system/"
 const SystemOtherPartition = "/system_other/"
+const SystemServerDexjarsDir = "system_server_dexjars"
 
 var DexpreoptRunningInSoong = false
 
 // GenerateDexpreoptRule generates a set of commands that will preopt a module based on a GlobalConfig and a
 // ModuleConfig.  The produced files and their install locations will be available through rule.Installs().
 func GenerateDexpreoptRule(ctx android.BuilderContext, globalSoong *GlobalSoongConfig,
-	global *GlobalConfig, module *ModuleConfig, productPackages android.Path, copyApexSystemServerJarDex bool) (
+	global *GlobalConfig, module *ModuleConfig, productPackages android.Path) (
 	rule *android.RuleBuilder, err error) {
 
 	defer func() {
@@ -83,7 +84,7 @@
 
 	if !dexpreoptDisabled(ctx, global, module) {
 		if valid, err := validateClassLoaderContext(module.ClassLoaderContexts); err != nil {
-			android.ReportPathErrorf(ctx, err.Error())
+			android.ReportPathErrorf(ctx, "%s", err.Error())
 		} else if valid {
 			fixClassLoaderContext(module.ClassLoaderContexts)
 
@@ -94,7 +95,7 @@
 
 			for archIdx, _ := range module.Archs {
 				dexpreoptCommand(ctx, globalSoong, global, module, rule, archIdx, profile, appImage,
-					generateDM, productPackages, copyApexSystemServerJarDex)
+					generateDM, productPackages)
 			}
 		}
 	}
@@ -231,7 +232,7 @@
 
 func dexpreoptCommand(ctx android.BuilderContext, globalSoong *GlobalSoongConfig,
 	global *GlobalConfig, module *ModuleConfig, rule *android.RuleBuilder, archIdx int,
-	profile android.WritablePath, appImage bool, generateDM bool, productPackages android.Path, copyApexSystemServerJarDex bool) {
+	profile android.WritablePath, appImage bool, generateDM bool, productPackages android.Path) {
 
 	arch := module.Archs[archIdx]
 
@@ -279,19 +280,6 @@
 			clcTarget = append(clcTarget, GetSystemServerDexLocation(ctx, global, lib))
 		}
 
-		if DexpreoptRunningInSoong && copyApexSystemServerJarDex {
-			// Copy the system server jar to a predefined location where dex2oat will find it.
-			dexPathHost := SystemServerDexJarHostPath(ctx, module.Name)
-			rule.Command().Text("mkdir -p").Flag(filepath.Dir(dexPathHost.String()))
-			rule.Command().Text("cp -f").Input(module.DexPath).Output(dexPathHost)
-		} else {
-			// For Make modules the copy rule is generated in the makefiles, not in dexpreopt.sh.
-			// This is necessary to expose the rule to Ninja, otherwise it has rules that depend on
-			// the jar (namely, dexpreopt commands for all subsequent system server jars that have
-			// this one in their class loader context), but no rule that creates it (because Ninja
-			// cannot see the rule in the generated dexpreopt.sh script).
-		}
-
 		clcHostString := "PCL[" + strings.Join(clcHost.Strings(), ":") + "]"
 		clcTargetString := "PCL[" + strings.Join(clcTarget, ":") + "]"
 
@@ -581,11 +569,11 @@
 func SystemServerDexJarHostPath(ctx android.PathContext, jar string) android.OutputPath {
 	if DexpreoptRunningInSoong {
 		// Soong module, just use the default output directory $OUT/soong.
-		return android.PathForOutput(ctx, "system_server_dexjars", jar+".jar")
+		return android.PathForOutput(ctx, SystemServerDexjarsDir, jar+".jar")
 	} else {
 		// Make module, default output directory is $OUT (passed via the "null config" created
 		// by dexpreopt_gen). Append Soong subdirectory to match Soong module paths.
-		return android.PathForOutput(ctx, "soong", "system_server_dexjars", jar+".jar")
+		return android.PathForOutput(ctx, "soong", SystemServerDexjarsDir, jar+".jar")
 	}
 }
 
diff --git a/dexpreopt/dexpreopt_gen/dexpreopt_gen.go b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
index 7512005..8033b48 100644
--- a/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
+++ b/dexpreopt/dexpreopt_gen/dexpreopt_gen.go
@@ -205,9 +205,8 @@
 			panic(err)
 		}
 	}
-	cpApexSscpServerJar := false // dexpreopt_gen operates on make modules, and since sscp libraries are in soong, this should be a noop
 	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(
-		ctx, globalSoong, global, module, android.PathForTesting(productPackagesPath), cpApexSscpServerJar)
+		ctx, globalSoong, global, module, android.PathForTesting(productPackagesPath))
 	if err != nil {
 		panic(err)
 	}
diff --git a/dexpreopt/dexpreopt_test.go b/dexpreopt/dexpreopt_test.go
index 7b0f51f..500b2ff 100644
--- a/dexpreopt/dexpreopt_test.go
+++ b/dexpreopt/dexpreopt_test.go
@@ -15,9 +15,10 @@
 package dexpreopt
 
 import (
-	"android/soong/android"
 	"fmt"
 	"testing"
+
+	"android/soong/android"
 )
 
 func testSystemModuleConfig(ctx android.PathContext, name string) *ModuleConfig {
@@ -103,7 +104,7 @@
 	module := testSystemModuleConfig(ctx, "test")
 	productPackages := android.PathForTesting("product_packages.txt")
 
-	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages, true)
+	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -163,7 +164,7 @@
 	for _, test := range tests {
 		global.PatternsOnSystemOther = test.patterns
 		for _, mt := range test.moduleTests {
-			rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, mt.module, productPackages, true)
+			rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, mt.module, productPackages)
 			if err != nil {
 				t.Fatal(err)
 			}
@@ -198,7 +199,7 @@
 	global.ApexSystemServerJars = android.CreateTestConfiguredJarList(
 		[]string{"com.android.apex1:service-A"})
 
-	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages, true)
+	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -210,15 +211,6 @@
 
 	android.AssertStringEquals(t, "installs", wantInstalls.String(), rule.Installs().String())
 
-	android.AssertStringListContains(t, "apex sscp jar copy", rule.Outputs().Strings(), "out/soong/system_server_dexjars/service-A.jar")
-
-	// rule with apex sscp cp as false
-	rule, err = GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages, false)
-	if err != nil {
-		t.Fatal(err)
-	}
-	android.AssertStringListDoesNotContain(t, "apex sscp jar copy", rule.Outputs().Strings(), "out/soong/system_server_dexjars/service-A.jar")
-
 	// cleanup the global variable for test
 	DexpreoptRunningInSoong = oldDexpreoptRunningInSoong
 }
@@ -241,7 +233,7 @@
 	global.ApexSystemServerJars = android.CreateTestConfiguredJarList(
 		[]string{"com.android.apex1:service-A"})
 
-	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages, true)
+	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -253,15 +245,6 @@
 
 	android.AssertStringEquals(t, "installs", wantInstalls.String(), rule.Installs().String())
 
-	android.AssertStringListContains(t, "apex sscp jar copy", rule.Outputs().Strings(), "out/soong/system_server_dexjars/service-A.jar")
-
-	// rule with apex sscp cp as false
-	rule, err = GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages, false)
-	if err != nil {
-		t.Fatal(err)
-	}
-	android.AssertStringListDoesNotContain(t, "apex sscp jar copy", rule.Outputs().Strings(), "out/soong/system_server_dexjars/service-A.jar")
-
 	// cleanup the global variable for test
 	DexpreoptRunningInSoong = oldDexpreoptRunningInSoong
 }
@@ -277,7 +260,7 @@
 	global.StandaloneSystemServerJars = android.CreateTestConfiguredJarList(
 		[]string{"platform:service-A"})
 
-	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages, true)
+	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -301,7 +284,7 @@
 	global.StandaloneSystemServerJars = android.CreateTestConfiguredJarList(
 		[]string{"system_ext:service-A"})
 
-	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages, true)
+	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -325,7 +308,7 @@
 	global.ApexStandaloneSystemServerJars = android.CreateTestConfiguredJarList(
 		[]string{"com.android.apex1:service-A"})
 
-	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages, true)
+	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -348,7 +331,7 @@
 
 	module.ProfileClassListing = android.OptionalPathForPath(android.PathForTesting("profile"))
 
-	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages, true)
+	rule, err := GenerateDexpreoptRule(ctx, globalSoong, global, module, productPackages)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -394,7 +377,7 @@
 			result := preparers.RunTest(t)
 			ctx := result.TestContext
 
-			ctx.SingletonForTests("dexpreopt-soong-config").Output("out/soong/dexpreopt/uffd_gc_flag.txt")
+			ctx.SingletonForTests(t, "dexpreopt-soong-config").Output("out/soong/dexpreopt/uffd_gc_flag.txt")
 		})
 	}
 }
@@ -403,6 +386,7 @@
 	preparers := android.GroupFixturePreparers(
 		PrepareForTestWithFakeDex2oatd,
 		PrepareForTestWithDexpreoptConfig,
+		android.FixtureModifyConfig(android.SetKatiEnabledForTests),
 		FixtureSetEnableUffdGc("default"),
 	)
 
@@ -410,7 +394,7 @@
 	ctx := result.TestContext
 	config := ctx.Config()
 
-	rule := ctx.SingletonForTests("dexpreopt-soong-config").Rule("dexpreopt_uffd_gc_flag")
+	rule := ctx.SingletonForTests(t, "dexpreopt-soong-config").Rule("dexpreopt_uffd_gc_flag")
 
 	android.AssertStringDoesContain(t, "", rule.RuleParams.Command, "construct_uffd_gc_flag")
 	android.AssertStringPathsRelativeToTopEquals(t, "", config, []string{
diff --git a/docs/perf.md b/docs/perf.md
index 5b53c8d..446ba9d 100644
--- a/docs/perf.md
+++ b/docs/perf.md
@@ -61,8 +61,7 @@
 
 saves CPU profile for each Soong invocation in /tmp/foo._step_ file, where
 _step_ is Soong execution step. The main step is `build`. The others as
-`bp2build_files`, `bp2build_workspace`, `modulegraph`, `queryview`,
-`api_bp2build`, `soong_docs` (not all of them necessarily run during the build).
+`soong_docs` (not all of them necessarily run during the build).
 The profiles can be inspected with `go tool pprof` from the command line or
 with _Run>Open Profiler Snapshot_ in IntelliJ IDEA.
 
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/Android.bp b/etc/Android.bp
index 8e043b8..e92437e 100644
--- a/etc/Android.bp
+++ b/etc/Android.bp
@@ -12,6 +12,7 @@
     ],
     srcs: [
         "adb_keys.go",
+        "avbpubkey.go",
         "install_symlink.go",
         "otacerts_zip.go",
         "prebuilt_etc.go",
diff --git a/etc/adb_keys.go b/etc/adb_keys.go
index 1bce2f1..cfcb1d5 100644
--- a/etc/adb_keys.go
+++ b/etc/adb_keys.go
@@ -24,7 +24,7 @@
 
 type AdbKeysModule struct {
 	android.ModuleBase
-	outputPath  android.OutputPath
+	outputPath  android.Path
 	installPath android.InstallPath
 }
 
@@ -36,21 +36,26 @@
 
 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.Disable()
 		m.SkipInstall()
 		return
 	}
 
-	m.outputPath = android.PathForModuleOut(ctx, "adb_keys").OutputPath
+	outputPath := android.PathForModuleOut(ctx, "adb_keys")
 	input := android.ExistentPathForSource(ctx, android.String(productVariables.AdbKeys))
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   android.Cp,
-		Output: m.outputPath,
+		Output: outputPath,
 		Input:  input.Path(),
 	})
-	m.installPath = android.PathForModuleInPartitionInstall(ctx, ctx.DeviceConfig().ProductPath(), "etc/security")
-	ctx.InstallFile(m.installPath, "adb_keys", m.outputPath)
+	m.installPath = android.PathForModuleInstall(ctx, "etc/security")
+	ctx.InstallFile(m.installPath, "adb_keys", outputPath)
+	m.outputPath = outputPath
 }
 
 func (m *AdbKeysModule) AndroidMkEntries() []android.AndroidMkEntries {
diff --git a/etc/avbpubkey.go b/etc/avbpubkey.go
new file mode 100644
index 0000000..dc242ce
--- /dev/null
+++ b/etc/avbpubkey.go
@@ -0,0 +1,85 @@
+// Copyright 2024 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package etc
+
+import (
+	"android/soong/android"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+func init() {
+	android.RegisterModuleType("avbpubkey", AvbpubkeyModuleFactory)
+	pctx.HostBinToolVariable("avbtool", "avbtool")
+}
+
+type avbpubkeyProperty struct {
+	Private_key *string `android:"path"`
+}
+
+type AvbpubkeyModule struct {
+	android.ModuleBase
+
+	properties avbpubkeyProperty
+
+	outputPath  android.WritablePath
+	installPath android.InstallPath
+}
+
+func AvbpubkeyModuleFactory() android.Module {
+	module := &AvbpubkeyModule{}
+	module.AddProperties(&module.properties)
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	return module
+}
+
+var avbPubKeyRule = pctx.AndroidStaticRule("avbpubkey",
+	blueprint.RuleParams{
+		Command: `${avbtool} extract_public_key --key ${in} --output ${out}.tmp` +
+			` && ( if cmp -s ${out}.tmp ${out} ; then rm ${out}.tmp ; else mv ${out}.tmp ${out} ; fi )`,
+		CommandDeps: []string{"${avbtool}"},
+		Restat:      true,
+		Description: "Extracting system_other avb key",
+	})
+
+func (m *AvbpubkeyModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	if !m.ProductSpecific() {
+		ctx.ModuleErrorf("avbpubkey module type must set product_specific to true")
+	}
+
+	m.outputPath = android.PathForModuleOut(ctx, ctx.ModuleName(), "system_other.avbpubkey")
+
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   avbPubKeyRule,
+		Input:  android.PathForModuleSrc(ctx, proptools.String(m.properties.Private_key)),
+		Output: m.outputPath,
+	})
+
+	m.installPath = android.PathForModuleInstall(ctx, "etc/security/avb")
+	ctx.InstallFile(m.installPath, "system_other.avbpubkey", m.outputPath)
+}
+
+func (m *AvbpubkeyModule) AndroidMkEntries() []android.AndroidMkEntries {
+	if m.IsSkipInstall() {
+		return []android.AndroidMkEntries{}
+	}
+
+	return []android.AndroidMkEntries{
+		{
+			Class:      "ETC",
+			OutputFile: android.OptionalPathForPath(m.outputPath),
+		}}
+}
diff --git a/etc/install_symlink.go b/etc/install_symlink.go
index 2182b86..aa33445 100644
--- a/etc/install_symlink.go
+++ b/etc/install_symlink.go
@@ -26,6 +26,7 @@
 
 func RegisterInstallSymlinkBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("install_symlink", InstallSymlinkFactory)
+	ctx.RegisterModuleType("install_symlink_host", InstallSymlinkHostFactory)
 }
 
 // install_symlink can be used to install an symlink with an arbitrary target to an arbitrary path
@@ -37,6 +38,14 @@
 	return module
 }
 
+// install_symlink can be used to install an symlink to an arbitrary path on the host.
+func InstallSymlinkHostFactory() android.Module {
+	module := &InstallSymlink{}
+	module.AddProperties(&module.properties)
+	android.InitAndroidMultiTargetsArchModule(module, android.HostSupported, android.MultilibCommon)
+	return module
+}
+
 type InstallSymlinkProperties struct {
 	// Where to install this symlink, relative to the partition it's installed on.
 	// Which partition it's installed on can be controlled by the vendor, system_ext, ramdisk, etc.
diff --git a/etc/install_symlink_test.go b/etc/install_symlink_test.go
index d7165e5..dce6873 100644
--- a/etc/install_symlink_test.go
+++ b/etc/install_symlink_test.go
@@ -39,7 +39,7 @@
 		t.Fatalf("expected 1 variant, got %#v", foo_variants)
 	}
 
-	foo := result.ModuleForTests("foo", "android_common").Module()
+	foo := result.ModuleForTests(t, "foo", "android_common").Module()
 	androidMkEntries := android.AndroidMkEntriesForTest(t, result.TestContext, foo)
 	if len(androidMkEntries) != 1 {
 		t.Fatalf("expected 1 androidmkentry, got %d", len(androidMkEntries))
@@ -70,7 +70,7 @@
 		t.Fatalf("expected 1 variant, got %#v", foo_variants)
 	}
 
-	foo := result.ModuleForTests("foo", "android_common").Module()
+	foo := result.ModuleForTests(t, "foo", "android_common").Module()
 	androidMkEntries := android.AndroidMkEntriesForTest(t, result.TestContext, foo)
 	if len(androidMkEntries) != 1 {
 		t.Fatalf("expected 1 androidmkentry, got %d", len(androidMkEntries))
@@ -133,3 +133,39 @@
 		}
 	`)
 }
+
+var prepareForInstallSymlinkHostTest = android.GroupFixturePreparers(
+	android.PrepareForTestWithAndroidBuildComponents,
+	android.FixtureRegisterWithContext(RegisterInstallSymlinkBuildComponents),
+)
+
+func TestInstallSymlinkHostBasic(t *testing.T) {
+	result := prepareForInstallSymlinkHostTest.RunTestWithBp(t, `
+		install_symlink_host {
+			name: "foo",
+			installed_location: "bin/foo",
+			symlink_target: "aa/bb/cc",
+		}
+	`)
+
+	buildOS := result.Config.BuildOS.String()
+	foo := result.ModuleForTests(t, "foo", buildOS+"_common").Module()
+
+	androidMkEntries := android.AndroidMkEntriesForTest(t, result.TestContext, foo)
+	if len(androidMkEntries) != 1 {
+		t.Fatalf("expected 1 androidmkentry, got %d", len(androidMkEntries))
+	}
+
+	symlinks := androidMkEntries[0].EntryMap["LOCAL_SOONG_INSTALL_SYMLINKS"]
+	if len(symlinks) != 1 {
+		t.Fatalf("Expected 1 symlink, got %d", len(symlinks))
+	}
+
+	if !strings.HasSuffix(symlinks[0], "bin/foo") {
+		t.Fatalf("Expected symlink install path to end in bin/foo, got: %s", symlinks[0])
+	}
+
+	if !strings.Contains(symlinks[0], "host") {
+		t.Fatalf("Expected symlink install path to contain `host`, got: %s", symlinks[0])
+	}
+}
diff --git a/etc/otacerts_zip.go b/etc/otacerts_zip.go
index b6f175a..53ddc50 100644
--- a/etc/otacerts_zip.go
+++ b/etc/otacerts_zip.go
@@ -44,7 +44,7 @@
 	android.ModuleBase
 
 	properties otacertsZipProperties
-	outputPath android.OutputPath
+	outputPath android.Path
 }
 
 // otacerts_zip collects key files defined in PRODUCT_DEFAULT_DEV_CERTIFICATE
@@ -61,41 +61,41 @@
 
 var _ android.ImageInterface = (*otacertsZipModule)(nil)
 
-func (m *otacertsZipModule) ImageMutatorBegin(ctx android.BaseModuleContext) {}
+func (m *otacertsZipModule) ImageMutatorBegin(ctx android.ImageInterfaceContext) {}
 
-func (m *otacertsZipModule) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
+func (m *otacertsZipModule) VendorVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return false
 }
 
-func (m *otacertsZipModule) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
+func (m *otacertsZipModule) ProductVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return false
 }
 
-func (m *otacertsZipModule) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
+func (m *otacertsZipModule) CoreVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return !m.ModuleBase.InstallInRecovery()
 }
 
-func (m *otacertsZipModule) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (m *otacertsZipModule) RamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return false
 }
 
-func (m *otacertsZipModule) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (m *otacertsZipModule) VendorRamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return false
 }
 
-func (m *otacertsZipModule) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (m *otacertsZipModule) DebugRamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return false
 }
 
-func (m *otacertsZipModule) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
+func (m *otacertsZipModule) RecoveryVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return proptools.Bool(m.properties.Recovery_available) || m.ModuleBase.InstallInRecovery()
 }
 
-func (m *otacertsZipModule) ExtraImageVariations(ctx android.BaseModuleContext) []string {
+func (m *otacertsZipModule) ExtraImageVariations(ctx android.ImageInterfaceContext) []string {
 	return nil
 }
 
-func (m *otacertsZipModule) SetImageVariation(ctx android.BaseModuleContext, variation string) {
+func (m *otacertsZipModule) SetImageVariation(ctx android.ImageInterfaceContext, variation string) {
 }
 
 func (m *otacertsZipModule) InRecovery() bool {
@@ -111,17 +111,21 @@
 	return proptools.StringDefault(m.properties.Filename, "otacerts.zip")
 }
 
+func (m *otacertsZipModule) onlyInRecovery() bool {
+	return m.ModuleBase.InstallInRecovery()
+}
+
 func (m *otacertsZipModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	// Read .x509.pem file defined in PRODUCT_DEFAULT_DEV_CERTIFICATE or the default test key.
 	pem, _ := ctx.Config().DefaultAppCertificate(ctx)
 	// Read .x509.pem files listed  in PRODUCT_EXTRA_OTA_KEYS or PRODUCT_EXTRA_RECOVERY_KEYS.
 	extras := ctx.Config().ExtraOtaKeys(ctx, m.InRecovery())
 	srcPaths := append([]android.SourcePath{pem}, extras...)
-	m.outputPath = android.PathForModuleOut(ctx, m.outputFileName()).OutputPath
+	outputPath := android.PathForModuleOut(ctx, m.outputFileName())
 
 	rule := android.NewRuleBuilder(pctx, ctx)
 	cmd := rule.Command().BuiltTool("soong_zip").
-		FlagWithOutput("-o ", m.outputPath).
+		FlagWithOutput("-o ", outputPath).
 		Flag("-j ").
 		Flag("-symlinks=false ")
 	for _, src := range srcPaths {
@@ -130,12 +134,13 @@
 	rule.Build(ctx.ModuleName(), "Generating the otacerts zip file")
 
 	installPath := android.PathForModuleInstall(ctx, "etc", proptools.String(m.properties.Relative_install_path))
-	ctx.InstallFile(installPath, m.outputFileName(), m.outputPath)
+	ctx.InstallFile(installPath, m.outputFileName(), outputPath)
+	m.outputPath = outputPath
 }
 
 func (m *otacertsZipModule) AndroidMkEntries() []android.AndroidMkEntries {
 	nameSuffix := ""
-	if m.InRecovery() {
+	if m.InRecovery() && !m.onlyInRecovery() {
 		nameSuffix = ".recovery"
 	}
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index fc6d1f7..bf54c09 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -59,12 +59,32 @@
 	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", 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_vendor", PrebuiltVendorFactory)
+	ctx.RegisterModuleType("prebuilt_sbin", PrebuiltSbinFactory)
+	ctx.RegisterModuleType("prebuilt_system", PrebuiltSystemFactory)
+	ctx.RegisterModuleType("prebuilt_first_stage_ramdisk", PrebuiltFirstStageRamdiskFactory)
 
 	ctx.RegisterModuleType("prebuilt_defaults", defaultsFactory)
 
@@ -72,15 +92,22 @@
 
 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"`
 
 	// Source files of this prebuilt. Can reference a genrule type module with the ":module" syntax.
-	// Mutually exclusive with src. When used, filename_from_src is set to true.
+	// Mutually exclusive with src. When used, filename_from_src is set to true unless dsts is also
+	// set. May use globs in filenames.
 	Srcs proptools.Configurable[[]string] `android:"path,arch_variant"`
 
+	// Destination files of this prebuilt. Requires srcs to be used and causes srcs not to implicitly
+	// set filename_from_src. This can be used to install each source file to a different directory
+	// and/or change filenames when files are installed. Must be exactly one entry per source file,
+	// which means care must be taken if srcs has globs.
+	Dsts proptools.Configurable[[]string] `android:"path,arch_variant"`
+
 	// Optional name for the installed file. If unspecified, name of the module is used as the file
 	// name. Only available when using a single source (src).
 	Filename *string `android:"arch_variant"`
@@ -114,6 +141,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 {
@@ -149,7 +179,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.
@@ -158,7 +188,7 @@
 	subdirProperties prebuiltSubdirProperties
 
 	sourceFilePaths android.Paths
-	outputFilePaths android.OutputPaths
+	outputFilePaths android.WritablePaths
 	// The base install location, e.g. "etc" for prebuilt_etc, "usr/share" for prebuilt_usr_share.
 	installDirBase               string
 	installDirBase64             string
@@ -166,7 +196,7 @@
 	// The base install location when soc_specific property is set to true, e.g. "firmware" for
 	// prebuilt_firmware.
 	socInstallDirBase      string
-	installDirPath         android.InstallPath
+	installDirPaths        []android.InstallPath
 	additionalDependencies *android.Paths
 
 	usedSrcsProperty bool
@@ -229,30 +259,30 @@
 
 var _ android.ImageInterface = (*PrebuiltEtc)(nil)
 
-func (p *PrebuiltEtc) ImageMutatorBegin(ctx android.BaseModuleContext) {}
+func (p *PrebuiltEtc) ImageMutatorBegin(ctx android.ImageInterfaceContext) {}
 
-func (p *PrebuiltEtc) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
+func (p *PrebuiltEtc) VendorVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return false
 }
 
-func (p *PrebuiltEtc) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
+func (p *PrebuiltEtc) ProductVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return false
 }
 
-func (p *PrebuiltEtc) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
+func (p *PrebuiltEtc) CoreVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return !p.ModuleBase.InstallInRecovery() && !p.ModuleBase.InstallInRamdisk() &&
 		!p.ModuleBase.InstallInVendorRamdisk() && !p.ModuleBase.InstallInDebugRamdisk()
 }
 
-func (p *PrebuiltEtc) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (p *PrebuiltEtc) RamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return proptools.Bool(p.properties.Ramdisk_available) || p.ModuleBase.InstallInRamdisk()
 }
 
-func (p *PrebuiltEtc) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (p *PrebuiltEtc) VendorRamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return proptools.Bool(p.properties.Vendor_ramdisk_available) || p.ModuleBase.InstallInVendorRamdisk()
 }
 
-func (p *PrebuiltEtc) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (p *PrebuiltEtc) DebugRamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return proptools.Bool(p.properties.Debug_ramdisk_available) || p.ModuleBase.InstallInDebugRamdisk()
 }
 
@@ -260,15 +290,15 @@
 	return proptools.Bool(p.rootProperties.Install_in_root)
 }
 
-func (p *PrebuiltEtc) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
+func (p *PrebuiltEtc) RecoveryVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return proptools.Bool(p.properties.Recovery_available) || p.ModuleBase.InstallInRecovery()
 }
 
-func (p *PrebuiltEtc) ExtraImageVariations(ctx android.BaseModuleContext) []string {
+func (p *PrebuiltEtc) ExtraImageVariations(ctx android.ImageInterfaceContext) []string {
 	return nil
 }
 
-func (p *PrebuiltEtc) SetImageVariation(ctx android.BaseModuleContext, variation string) {
+func (p *PrebuiltEtc) SetImageVariation(ctx android.ImageInterfaceContext, variation string) {
 }
 
 func (p *PrebuiltEtc) SourceFilePath(ctx android.ModuleContext) android.Path {
@@ -279,7 +309,10 @@
 }
 
 func (p *PrebuiltEtc) InstallDirPath() android.InstallPath {
-	return p.installDirPath
+	if len(p.installDirPaths) != 1 {
+		panic(fmt.Errorf("InstallDirPath not available on multi-source prebuilt %q", p.Name()))
+	}
+	return p.installDirPaths[0]
 }
 
 // This allows other derivative modules (e.g. prebuilt_etc_xml) to perform
@@ -288,7 +321,7 @@
 	p.additionalDependencies = &paths
 }
 
-func (p *PrebuiltEtc) OutputFile() android.OutputPath {
+func (p *PrebuiltEtc) OutputFile() android.Path {
 	if p.usedSrcsProperty {
 		panic(fmt.Errorf("OutputFile not available on multi-source prebuilt %q", p.Name()))
 	}
@@ -338,12 +371,20 @@
 	if srcProperty.IsPresent() && len(srcsProperty) > 0 {
 		ctx.PropertyErrorf("src", "src is set. Cannot set srcs")
 	}
+	dstsProperty := p.properties.Dsts.GetOrDefault(ctx, nil)
+	if len(dstsProperty) > 0 && len(srcsProperty) == 0 {
+		ctx.PropertyErrorf("dsts", "dsts is set. Must use srcs")
+	}
 
 	// Check that `sub_dir` and `relative_install_path` are not set at the same time.
 	if p.subdirProperties.Sub_dir != nil && p.subdirProperties.Relative_install_path != nil {
 		ctx.PropertyErrorf("sub_dir", "relative_install_path is set. Cannot set sub_dir")
 	}
-	p.installDirPath = android.PathForModuleInstall(ctx, p.installBaseDir(ctx), p.SubDir())
+	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)
@@ -373,16 +414,17 @@
 			ctx.PropertyErrorf("filename", "filename cannot contain separator '/'")
 			return
 		}
-		p.outputFilePaths = android.OutputPaths{android.PathForModuleOut(ctx, filename).OutputPath}
+		p.outputFilePaths = android.WritablePaths{android.PathForModuleOut(ctx, filename)}
 
 		ip := installProperties{
 			filename:       filename,
 			sourceFilePath: p.sourceFilePaths[0],
 			outputFilePath: p.outputFilePaths[0],
-			installDirPath: p.installDirPath,
+			installDirPath: baseInstallDirPath,
 			symlinks:       p.properties.Symlinks,
 		}
 		installs = append(installs, ip)
+		p.installDirPaths = append(p.installDirPaths, baseInstallDirPath)
 	} else if len(srcsProperty) > 0 {
 		p.usedSrcsProperty = true
 		if filename != "" {
@@ -392,20 +434,39 @@
 			ctx.PropertyErrorf("symlinks", "symlinks cannot be set when using srcs")
 		}
 		if p.properties.Filename_from_src != nil {
-			ctx.PropertyErrorf("filename_from_src", "filename_from_src is implicitly set to true when using srcs")
+			if len(dstsProperty) > 0 {
+				ctx.PropertyErrorf("filename_from_src", "dsts is set. Cannot set filename_from_src")
+			} else {
+				ctx.PropertyErrorf("filename_from_src", "filename_from_src is implicitly set to true when using srcs")
+			}
 		}
 		p.sourceFilePaths = android.PathsForModuleSrc(ctx, srcsProperty)
-		for _, src := range p.sourceFilePaths {
-			filename := src.Base()
-			output := android.PathForModuleOut(ctx, filename).OutputPath
+		if len(dstsProperty) > 0 && len(p.sourceFilePaths) != len(dstsProperty) {
+			ctx.PropertyErrorf("dsts", "Must have one entry in dsts per source file")
+		}
+		for i, src := range p.sourceFilePaths {
+			var filename string
+			var installDirPath android.InstallPath
+
+			if len(dstsProperty) > 0 {
+				var dstdir string
+
+				dstdir, filename = filepath.Split(dstsProperty[i])
+				installDirPath = baseInstallDirPath.Join(ctx, dstdir)
+			} else {
+				filename = src.Base()
+				installDirPath = baseInstallDirPath
+			}
+			output := android.PathForModuleOut(ctx, filename)
 			ip := installProperties{
 				filename:       filename,
 				sourceFilePath: src,
 				outputFilePath: output,
-				installDirPath: p.installDirPath,
+				installDirPath: installDirPath,
 			}
 			p.outputFilePaths = append(p.outputFilePaths, output)
 			installs = append(installs, ip)
+			p.installDirPaths = append(p.installDirPaths, installDirPath)
 		}
 	} else if ctx.Config().AllowMissingDependencies() {
 		// If no srcs was set and AllowMissingDependencies is enabled then
@@ -416,14 +477,15 @@
 		if filename == "" {
 			filename = ctx.ModuleName()
 		}
-		p.outputFilePaths = android.OutputPaths{android.PathForModuleOut(ctx, filename).OutputPath}
+		p.outputFilePaths = android.WritablePaths{android.PathForModuleOut(ctx, filename)}
 		ip := installProperties{
 			filename:       filename,
 			sourceFilePath: p.sourceFilePaths[0],
 			outputFilePath: p.outputFilePaths[0],
-			installDirPath: p.installDirPath,
+			installDirPath: baseInstallDirPath,
 		}
 		installs = append(installs, ip)
+		p.installDirPaths = append(p.installDirPaths, baseInstallDirPath)
 	} else {
 		ctx.PropertyErrorf("src", "missing prebuilt source file")
 		return
@@ -437,13 +499,25 @@
 		ip.addInstallRules(ctx)
 	}
 
+	p.updateModuleInfoJSON(ctx)
+
 	ctx.SetOutputFiles(p.outputFilePaths.Paths(), "")
 }
 
+func (p *PrebuiltEtc) updateModuleInfoJSON(ctx android.ModuleContext) {
+	moduleInfoJSON := ctx.ModuleInfoJSON()
+	moduleInfoJSON.Class = []string{"ETC"}
+	if p.makeClass != "" {
+		moduleInfoJSON.Class = []string{p.makeClass}
+	}
+	moduleInfoJSON.SystemSharedLibs = []string{"none"}
+	moduleInfoJSON.Tags = []string{"optional"}
+}
+
 type installProperties struct {
 	filename       string
 	sourceFilePath android.Path
-	outputFilePath android.OutputPath
+	outputFilePath android.WritablePath
 	installDirPath android.InstallPath
 	symlinks       []string
 }
@@ -493,7 +567,7 @@
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				entries.SetString("LOCAL_MODULE_TAGS", "optional")
-				entries.SetString("LOCAL_MODULE_PATH", p.installDirPath.String())
+				entries.SetString("LOCAL_MODULE_PATH", p.installDirPaths[0].String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", p.outputFilePaths[0].Base())
 				if len(p.properties.Symlinks) > 0 {
 					entries.AddStrings("LOCAL_MODULE_SYMLINKS", p.properties.Symlinks...)
@@ -515,6 +589,7 @@
 	p.installDirBase = dirBase
 	p.AddProperties(&p.properties)
 	p.AddProperties(&p.subdirProperties)
+	p.AddProperties(&p.rootProperties)
 }
 
 func InitPrebuiltRootModule(p *PrebuiltEtc) {
@@ -549,7 +624,7 @@
 
 	module.AddProperties(props...)
 	module.AddProperties(
-		&prebuiltEtcProperties{},
+		&PrebuiltEtcProperties{},
 		&prebuiltSubdirProperties{},
 	)
 
@@ -681,12 +756,23 @@
 	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{}
 	InitPrebuiltEtcModule(module, "fonts")
 	// This module is device-only
-	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	android.InitDefaultableModule(module)
 	return module
 }
@@ -753,3 +839,193 @@
 	android.InitDefaultableModule(module)
 	return module
 }
+
+// prebuilt_media installs media files in <partition>/media directory.
+func PrebuiltMediaFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "media")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+// prebuilt_voicepack installs voice pack files in <partition>/tts directory.
+func PrebuiltVoicepackFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "tts")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+// prebuilt_bin installs files in <partition>/bin directory.
+func PrebuiltBinaryFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "bin")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+// prebuilt_wallpaper installs image files in <partition>/wallpaper directory.
+func PrebuiltWallpaperFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "wallpaper")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	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
+}
+
+// prebuilt_vendor installs files in <partition>/vendor directory.
+func PrebuiltVendorFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "vendor")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+// prebuilt_sbin installs files in <partition>/sbin directory.
+func PrebuiltSbinFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "sbin")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+// prebuilt_system installs files in <partition>/system directory.
+func PrebuiltSystemFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "system")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+// prebuilt_first_stage_ramdisk installs files in <partition>/first_stage_ramdisk directory.
+func PrebuiltFirstStageRamdiskFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "first_stage_ramdisk")
+	// 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 e739afe..eb722b4 100644
--- a/etc/prebuilt_etc_test.go
+++ b/etc/prebuilt_etc_test.go
@@ -119,6 +119,113 @@
 	android.AssertStringEquals(t, "output file path", "foo.conf", p.outputFilePaths[2].Base())
 }
 
+func TestPrebuiltEtcDsts(t *testing.T) {
+	result := prepareForPrebuiltEtcTest.RunTestWithBp(t, `
+		prebuilt_etc {
+			name: "foo",
+			srcs: ["foo.conf", "bar.conf"],
+			dsts: ["foodir/foo.conf", "bardir/extradir/different.name"],
+		}
+	`)
+
+	p := result.Module("foo", "android_arm64_armv8-a").(*PrebuiltEtc)
+	android.AssertStringEquals(t, "output file path", "foo.conf", p.outputFilePaths[0].Base())
+	android.AssertStringEquals(t, "output file path", "different.name", p.outputFilePaths[1].Base())
+
+	expectedPaths := [...]string{
+		"out/target/product/test_device/system/etc/foodir",
+		"out/target/product/test_device/system/etc/bardir/extradir",
+	}
+	android.AssertPathRelativeToTopEquals(t, "install dir", expectedPaths[0], p.installDirPaths[0])
+	android.AssertPathRelativeToTopEquals(t, "install dir", expectedPaths[1], p.installDirPaths[1])
+}
+
+func TestPrebuiltEtcDstsPlusRelativeInstallPath(t *testing.T) {
+	result := prepareForPrebuiltEtcTest.RunTestWithBp(t, `
+		prebuilt_etc {
+			name: "foo",
+			srcs: ["foo.conf", "bar.conf"],
+			dsts: ["foodir/foo.conf", "bardir/extradir/different.name"],
+			relative_install_path: "somewhere",
+		}
+	`)
+
+	p := result.Module("foo", "android_arm64_armv8-a").(*PrebuiltEtc)
+	android.AssertStringEquals(t, "output file path", "foo.conf", p.outputFilePaths[0].Base())
+	android.AssertStringEquals(t, "output file path", "different.name", p.outputFilePaths[1].Base())
+
+	expectedPaths := [...]string{
+		"out/target/product/test_device/system/etc/somewhere/foodir",
+		"out/target/product/test_device/system/etc/somewhere/bardir/extradir",
+	}
+	android.AssertPathRelativeToTopEquals(t, "install dir", expectedPaths[0], p.installDirPaths[0])
+	android.AssertPathRelativeToTopEquals(t, "install dir", expectedPaths[1], p.installDirPaths[1])
+}
+
+func TestPrebuiltEtcDstsSrcGlob(t *testing.T) {
+	result := prepareForPrebuiltEtcTest.RunTestWithBp(t, `
+		prebuilt_etc {
+			name: "foo",
+			srcs: ["*.conf"],
+			dsts: ["a.conf", "b.conf", "c.conf"],
+		}
+	`)
+
+	p := result.Module("foo", "android_arm64_armv8-a").(*PrebuiltEtc)
+	android.AssertStringEquals(t, "output file path", "a.conf", p.outputFilePaths[0].Base())
+	android.AssertStringEquals(t, "output file path", "b.conf", p.outputFilePaths[1].Base())
+	android.AssertStringEquals(t, "output file path", "c.conf", p.outputFilePaths[2].Base())
+}
+
+func TestPrebuiltEtcDstsSrcGlobDstsTooShort(t *testing.T) {
+	prepareForPrebuiltEtcTest.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern("Must have one entry in dsts per source file")).
+		RunTestWithBp(t, `
+			prebuilt_etc {
+				name: "foo",
+				srcs: ["*.conf"],
+				dsts: ["a.conf", "b.conf"],
+			}
+		`)
+}
+
+func TestPrebuiltEtcDstsSrcGlobDstsTooLong(t *testing.T) {
+	prepareForPrebuiltEtcTest.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern("Must have one entry in dsts per source file")).
+		RunTestWithBp(t, `
+			prebuilt_etc {
+				name: "foo",
+				srcs: ["*.conf"],
+				dsts: ["a.conf", "b.conf", "c.conf", "d.conf"],
+			}
+		`)
+}
+
+func TestPrebuiltEtcCannotDstsWithSrc(t *testing.T) {
+	prepareForPrebuiltEtcTest.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern("dsts is set. Must use srcs")).
+		RunTestWithBp(t, `
+			prebuilt_etc {
+				name: "foo.conf",
+				src: "foo.conf",
+				dsts: ["a.conf"],
+			}
+		`)
+}
+
+func TestPrebuiltEtcCannotDstsWithFilenameFromSrc(t *testing.T) {
+	prepareForPrebuiltEtcTest.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern("dsts is set. Cannot set filename_from_src")).
+		RunTestWithBp(t, `
+			prebuilt_etc {
+				name: "foo.conf",
+				srcs: ["foo.conf"],
+				dsts: ["a.conf"],
+				filename_from_src: true,
+			}
+		`)
+}
+
 func TestPrebuiltEtcAndroidMk(t *testing.T) {
 	result := prepareForPrebuiltEtcTest.RunTestWithBp(t, `
 		prebuilt_etc {
@@ -164,8 +271,8 @@
 	`)
 
 	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
-	expected := "out/soong/target/product/test_device/system/etc/bar"
-	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
+	expected := "out/target/product/test_device/system/etc/bar"
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
 }
 
 func TestPrebuiltEtcCannotSetRelativeInstallPathAndSubDir(t *testing.T) {
@@ -217,7 +324,7 @@
 	`)
 
 	android.AssertStringEquals(t, "expected error rule", "android/soong/android.Error",
-		result.ModuleForTests("foo.conf", "android_arm64_armv8-a").Output("foo.conf").Rule.String())
+		result.ModuleForTests(t, "foo.conf", "android_arm64_armv8-a").Output("foo.conf").Rule.String())
 }
 
 func TestPrebuiltRootInstallDirPath(t *testing.T) {
@@ -230,8 +337,8 @@
 	`)
 
 	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
-	expected := "out/soong/target/product/test_device/system"
-	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
+	expected := "out/target/product/test_device/system"
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
 }
 
 func TestPrebuiltRootInstallDirPathValidate(t *testing.T) {
@@ -255,8 +362,8 @@
 	`)
 
 	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
-	expected := "out/soong/target/product/test_device/root/avb"
-	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
+	expected := "out/target/product/test_device/root/avb"
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
 }
 
 func TestPrebuiltAvdInstallDirPathValidate(t *testing.T) {
@@ -279,8 +386,8 @@
 	`)
 
 	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
-	expected := "out/soong/target/product/test_device/system/usr/share/bar"
-	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
+	expected := "out/target/product/test_device/system/usr/share/bar"
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
 }
 
 func TestPrebuiltUserShareHostInstallDirPath(t *testing.T) {
@@ -294,8 +401,8 @@
 
 	buildOS := result.Config.BuildOS.String()
 	p := result.Module("foo.conf", buildOS+"_common").(*PrebuiltEtc)
-	expected := filepath.Join("out/soong/host", result.Config.PrebuiltOS(), "usr", "share", "bar")
-	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
+	expected := filepath.Join("out/host", result.Config.PrebuiltOS(), "usr", "share", "bar")
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
 }
 
 func TestPrebuiltPrebuiltUserHyphenDataInstallDirPath(t *testing.T) {
@@ -308,8 +415,8 @@
 	`)
 
 	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
-	expected := "out/soong/target/product/test_device/system/usr/hyphen-data/bar"
-	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
+	expected := "out/target/product/test_device/system/usr/hyphen-data/bar"
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
 }
 
 func TestPrebuiltPrebuiltUserKeyLayoutInstallDirPath(t *testing.T) {
@@ -322,8 +429,8 @@
 	`)
 
 	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
-	expected := "out/soong/target/product/test_device/system/usr/keylayout/bar"
-	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
+	expected := "out/target/product/test_device/system/usr/keylayout/bar"
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
 }
 
 func TestPrebuiltPrebuiltUserKeyCharsInstallDirPath(t *testing.T) {
@@ -336,8 +443,8 @@
 	`)
 
 	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
-	expected := "out/soong/target/product/test_device/system/usr/keychars/bar"
-	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
+	expected := "out/target/product/test_device/system/usr/keychars/bar"
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
 }
 
 func TestPrebuiltPrebuiltUserIdcInstallDirPath(t *testing.T) {
@@ -350,8 +457,8 @@
 	`)
 
 	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
-	expected := "out/soong/target/product/test_device/system/usr/idc/bar"
-	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
+	expected := "out/target/product/test_device/system/usr/idc/bar"
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
 }
 
 func TestPrebuiltFontInstallDirPath(t *testing.T) {
@@ -362,9 +469,9 @@
 		}
 	`)
 
-	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
-	expected := "out/soong/target/product/test_device/system/fonts"
-	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
+	p := result.Module("foo.conf", "android_common").(*PrebuiltEtc)
+	expected := "out/target/product/test_device/system/fonts"
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
 }
 
 func TestPrebuiltOverlayInstallDirPath(t *testing.T) {
@@ -376,12 +483,12 @@
 	`)
 
 	p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
-	expected := "out/soong/target/product/test_device/system/overlay"
-	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
+	expected := "out/target/product/test_device/system/overlay"
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
 }
 
 func TestPrebuiltFirmwareDirPath(t *testing.T) {
-	targetPath := "out/soong/target/product/test_device"
+	targetPath := "out/target/product/test_device"
 	tests := []struct {
 		description  string
 		config       string
@@ -409,13 +516,13 @@
 		t.Run(tt.description, func(t *testing.T) {
 			result := prepareForPrebuiltEtcTest.RunTestWithBp(t, tt.config)
 			p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
-			android.AssertPathRelativeToTopEquals(t, "install dir", tt.expectedPath, p.installDirPath)
+			android.AssertPathRelativeToTopEquals(t, "install dir", tt.expectedPath, p.installDirPaths[0])
 		})
 	}
 }
 
 func TestPrebuiltDSPDirPath(t *testing.T) {
-	targetPath := "out/soong/target/product/test_device"
+	targetPath := "out/target/product/test_device"
 	tests := []struct {
 		description  string
 		config       string
@@ -443,13 +550,13 @@
 		t.Run(tt.description, func(t *testing.T) {
 			result := prepareForPrebuiltEtcTest.RunTestWithBp(t, tt.config)
 			p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
-			android.AssertPathRelativeToTopEquals(t, "install dir", tt.expectedPath, p.installDirPath)
+			android.AssertPathRelativeToTopEquals(t, "install dir", tt.expectedPath, p.installDirPaths[0])
 		})
 	}
 }
 
 func TestPrebuiltRFSADirPath(t *testing.T) {
-	targetPath := "out/soong/target/product/test_device"
+	targetPath := "out/target/product/test_device"
 	tests := []struct {
 		description  string
 		config       string
@@ -477,7 +584,22 @@
 		t.Run(tt.description, func(t *testing.T) {
 			result := prepareForPrebuiltEtcTest.RunTestWithBp(t, tt.config)
 			p := result.Module("foo.conf", "android_arm64_armv8-a").(*PrebuiltEtc)
-			android.AssertPathRelativeToTopEquals(t, "install dir", tt.expectedPath, p.installDirPath)
+			android.AssertPathRelativeToTopEquals(t, "install dir", tt.expectedPath, p.installDirPaths[0])
 		})
 	}
 }
+
+func TestPrebuiltMediaAutoDirPath(t *testing.T) {
+	result := prepareForPrebuiltEtcTest.RunTestWithBp(t, `
+		prebuilt_media {
+			name: "foo",
+			src: "Alarm_Beep_01.ogg",
+			product_specific: true,
+			relative_install_path: "alarms"
+		}
+	`)
+
+	p := result.Module("foo", "android_common").(*PrebuiltEtc)
+	expected := "out/target/product/test_device/product/media/alarms"
+	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPaths[0])
+}
diff --git a/filesystem/Android.bp b/filesystem/Android.bp
index a08f7cf..cb76df2 100644
--- a/filesystem/Android.bp
+++ b/filesystem/Android.bp
@@ -16,14 +16,19 @@
     ],
     srcs: [
         "aconfig_files.go",
+        "android_device.go",
+        "android_device_product_out.go",
         "avb_add_hash_footer.go",
         "avb_gen_vbmeta_image.go",
         "bootimg.go",
+        "bootconfig.go",
         "filesystem.go",
         "fsverity_metadata.go",
         "logical_partition.go",
         "raw_binary.go",
+        "super_image.go",
         "system_image.go",
+        "system_other.go",
         "vbmeta.go",
         "testing.go",
     ],
diff --git a/filesystem/aconfig_files.go b/filesystem/aconfig_files.go
index 608fccd..6d03402 100644
--- a/filesystem/aconfig_files.go
+++ b/filesystem/aconfig_files.go
@@ -16,64 +16,94 @@
 
 import (
 	"android/soong/android"
-	"strings"
+	"strconv"
 
+	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
 
-func (f *filesystem) buildAconfigFlagsFiles(ctx android.ModuleContext, builder *android.RuleBuilder, specs map[string]android.PackagingSpec, dir android.OutputPath) {
+type installedAconfigFlagsInfo struct {
+	aconfigFiles android.Paths
+}
+
+var installedAconfigFlagsProvider = blueprint.NewProvider[installedAconfigFlagsInfo]()
+
+type importAconfigDepDag struct {
+	blueprint.BaseDependencyTag
+}
+
+var importAconfigDependencyTag = interPartitionDepTag{}
+
+func (f *filesystem) buildAconfigFlagsFiles(
+	ctx android.ModuleContext,
+	builder *android.RuleBuilder,
+	specs map[string]android.PackagingSpec,
+	dir android.OutputPath,
+	fullInstallPaths *[]FullInstallPathInfo,
+) {
+	var caches []android.Path
+	for _, ps := range specs {
+		caches = append(caches, ps.GetAconfigPaths()...)
+	}
+
+	ctx.VisitDirectDepsWithTag(importAconfigDependencyTag, func(m android.Module) {
+		info, ok := android.OtherModuleProvider(ctx, m, installedAconfigFlagsProvider)
+		if !ok {
+			ctx.ModuleErrorf("expected dependency %s to have an installedAconfigFlagsProvider", m.Name())
+			return
+		}
+		caches = append(caches, info.aconfigFiles...)
+	})
+	caches = android.SortedUniquePaths(caches)
+
+	android.SetProvider(ctx, installedAconfigFlagsProvider, installedAconfigFlagsInfo{
+		aconfigFiles: caches,
+	})
+
 	if !proptools.Bool(f.properties.Gen_aconfig_flags_pb) {
 		return
 	}
 
-	aconfigFlagsBuilderPath := android.PathForModuleOut(ctx, "aconfig_flags_builder.sh")
-	aconfigToolPath := ctx.Config().HostToolPath(ctx, "aconfig")
-	cmd := builder.Command().Tool(aconfigFlagsBuilderPath).Implicit(aconfigToolPath)
-
-	var caches []string
-	for _, ps := range specs {
-		cmd.Implicits(ps.GetAconfigPaths())
-		caches = append(caches, ps.GetAconfigPaths().Strings()...)
-	}
-	caches = android.SortedUniqueStrings(caches)
-
-	var sbCaches strings.Builder
-	for _, cache := range caches {
-		sbCaches.WriteString("  --cache ")
-		sbCaches.WriteString(cache)
-		sbCaches.WriteString(" \\\n")
-	}
-	sbCaches.WriteRune('\n')
-
-	var sb strings.Builder
-	sb.WriteString("set -e\n")
+	container := f.PartitionType()
 
 	installAconfigFlagsPath := dir.Join(ctx, "etc", "aconfig_flags.pb")
-	sb.WriteString(aconfigToolPath.String())
-	sb.WriteString(" dump-cache --dedup --format protobuf --out ")
-	sb.WriteString(installAconfigFlagsPath.String())
-	sb.WriteString(" \\\n")
-	sb.WriteString(sbCaches.String())
-	cmd.ImplicitOutput(installAconfigFlagsPath)
+	cmd := builder.Command().
+		BuiltTool("aconfig").
+		Text(" dump-cache --dedup --format protobuf --out").
+		Output(installAconfigFlagsPath).
+		Textf("--filter container:%s+state:ENABLED", container).
+		Textf("--filter container:%s+permission:READ_WRITE", container)
+	for _, cache := range caches {
+		cmd.FlagWithInput("--cache ", cache)
+	}
+	*fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{
+		FullInstallPath: android.PathForModuleInPartitionInstall(ctx, f.PartitionType(), "etc/aconfig_flags.pb"),
+		SourcePath:      installAconfigFlagsPath,
+	})
 	f.appendToEntry(ctx, installAconfigFlagsPath)
 
 	installAconfigStorageDir := dir.Join(ctx, "etc", "aconfig")
-	sb.WriteString("mkdir -p ")
-	sb.WriteString(installAconfigStorageDir.String())
-	sb.WriteRune('\n')
+	builder.Command().Text("mkdir -p").Text(installAconfigStorageDir.String())
+
+	// To enable fingerprint, we need to have v2 storage files. The default version is 1.
+	storageFilesVersion := 1
+	if ctx.Config().ReleaseFingerprintAconfigPackages() {
+		storageFilesVersion = 2
+	}
 
 	generatePartitionAconfigStorageFile := func(fileType, fileName string) {
 		outputPath := installAconfigStorageDir.Join(ctx, fileName)
-		sb.WriteString(aconfigToolPath.String())
-		sb.WriteString(" create-storage --container ")
-		sb.WriteString(f.PartitionType())
-		sb.WriteString(" --file ")
-		sb.WriteString(fileType)
-		sb.WriteString(" --out ")
-		sb.WriteString(outputPath.String())
-		sb.WriteString(" \\\n")
-		sb.WriteString(sbCaches.String())
-		cmd.ImplicitOutput(outputPath)
+		builder.Command().
+			BuiltTool("aconfig").
+			FlagWithArg("create-storage --container ", container).
+			FlagWithArg("--file ", fileType).
+			FlagWithOutput("--out ", outputPath).
+			FlagWithArg("--cache ", installAconfigFlagsPath.String()).
+			FlagWithArg("--version ", strconv.Itoa(storageFilesVersion))
+		*fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{
+			FullInstallPath: android.PathForModuleInPartitionInstall(ctx, f.PartitionType(), "etc/aconfig", fileName),
+			SourcePath:      outputPath,
+		})
 		f.appendToEntry(ctx, outputPath)
 	}
 
@@ -83,6 +113,4 @@
 		generatePartitionAconfigStorageFile("flag_val", "flag.val")
 		generatePartitionAconfigStorageFile("flag_info", "flag.info")
 	}
-
-	android.WriteExecutableFileRuleVerbatim(ctx, aconfigFlagsBuilderPath, sb.String())
 }
diff --git a/filesystem/android_device.go b/filesystem/android_device.go
new file mode 100644
index 0000000..8d7f92f
--- /dev/null
+++ b/filesystem/android_device.go
@@ -0,0 +1,526 @@
+// 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 filesystem
+
+import (
+	"fmt"
+	"strings"
+	"sync/atomic"
+
+	"android/soong/android"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+type PartitionNameProperties struct {
+	// Name of the super partition filesystem module
+	Super_partition_name *string
+	// Name of the boot partition filesystem module
+	Boot_partition_name *string
+	// Name of the vendor boot partition filesystem module
+	Vendor_boot_partition_name *string
+	// Name of the init boot partition filesystem module
+	Init_boot_partition_name *string
+	// Name of the system partition filesystem module
+	System_partition_name *string
+	// Name of the system_ext partition filesystem module
+	System_ext_partition_name *string
+	// Name of the product partition filesystem module
+	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
+	// Name of the recovery partition filesystem module
+	Recovery_partition_name *string
+	// The vbmeta partition and its "chained" partitions
+	Vbmeta_partitions []string
+	// Name of the userdata partition filesystem module
+	Userdata_partition_name *string
+	// Name of the system_dlkm partition filesystem module
+	System_dlkm_partition_name *string
+	// Name of the vendor_dlkm partition filesystem module
+	Vendor_dlkm_partition_name *string
+	// Name of the odm_dlkm partition filesystem module
+	Odm_dlkm_partition_name *string
+}
+
+type DeviceProperties struct {
+	// Path to the prebuilt bootloader that would be copied to PRODUCT_OUT
+	Bootloader *string `android:"path"`
+	// Path to android-info.txt file containing board specific info.
+	Android_info *string `android:"path"`
+	// If this is the "main" android_device target for the build, i.e. the one that gets built
+	// when running a plain `m` command. Currently, this is the autogenerated android_device module
+	// in soong-only builds, but in the future when we check in android_device modules, the main
+	// one will be determined based on the lunch product. TODO: Figure out how to make this
+	// blueprint:"mutated" and still set it from filesystem_creator
+	Main_device *bool
+
+	Ab_ota_updater            *bool
+	Ab_ota_partitions         []string
+	Ab_ota_keys               []string
+	Ab_ota_postinstall_config []string
+
+	Ramdisk_node_list      *string `android:"path"`
+	Releasetools_extension *string `android:"path"`
+}
+
+type androidDevice struct {
+	android.ModuleBase
+
+	partitionProps PartitionNameProperties
+
+	deviceProps DeviceProperties
+
+	allImagesZip android.Path
+}
+
+func AndroidDeviceFactory() android.Module {
+	module := &androidDevice{}
+	module.AddProperties(&module.partitionProps, &module.deviceProps)
+	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	return module
+}
+
+var numMainAndroidDevicesOnceKey android.OnceKey = android.NewOnceKey("num_auto_generated_anroid_devices")
+
+type partitionDepTagType struct {
+	blueprint.BaseDependencyTag
+}
+
+type superPartitionDepTagType struct {
+	blueprint.BaseDependencyTag
+}
+type targetFilesMetadataDepTagType struct {
+	blueprint.BaseDependencyTag
+}
+
+var superPartitionDepTag superPartitionDepTagType
+var filesystemDepTag partitionDepTagType
+var targetFilesMetadataDepTag targetFilesMetadataDepTagType
+
+func (a *androidDevice) DepsMutator(ctx android.BottomUpMutatorContext) {
+	addDependencyIfDefined := func(dep *string) {
+		if dep != nil {
+			ctx.AddDependency(ctx.Module(), filesystemDepTag, proptools.String(dep))
+		}
+	}
+
+	if a.partitionProps.Super_partition_name != nil {
+		ctx.AddDependency(ctx.Module(), superPartitionDepTag, *a.partitionProps.Super_partition_name)
+	}
+	addDependencyIfDefined(a.partitionProps.Boot_partition_name)
+	addDependencyIfDefined(a.partitionProps.Init_boot_partition_name)
+	addDependencyIfDefined(a.partitionProps.Vendor_boot_partition_name)
+	addDependencyIfDefined(a.partitionProps.System_partition_name)
+	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)
+	addDependencyIfDefined(a.partitionProps.System_dlkm_partition_name)
+	addDependencyIfDefined(a.partitionProps.Vendor_dlkm_partition_name)
+	addDependencyIfDefined(a.partitionProps.Odm_dlkm_partition_name)
+	addDependencyIfDefined(a.partitionProps.Recovery_partition_name)
+	for _, vbmetaPartition := range a.partitionProps.Vbmeta_partitions {
+		ctx.AddDependency(ctx.Module(), filesystemDepTag, vbmetaPartition)
+	}
+	a.addDepsForTargetFilesMetadata(ctx)
+}
+
+func (a *androidDevice) addDepsForTargetFilesMetadata(ctx android.BottomUpMutatorContext) {
+	ctx.AddFarVariationDependencies(ctx.Config().BuildOSTarget.Variations(), targetFilesMetadataDepTag, "liblz4") // host variant
+}
+
+func (a *androidDevice) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	if proptools.Bool(a.deviceProps.Main_device) {
+		numMainAndroidDevices := ctx.Config().Once(numMainAndroidDevicesOnceKey, func() interface{} {
+			return &atomic.Int32{}
+		}).(*atomic.Int32)
+		total := numMainAndroidDevices.Add(1)
+		if total > 1 {
+			// There should only be 1 main android_device module. That one will be
+			// made the default thing to build in soong-only builds.
+			ctx.ModuleErrorf("There cannot be more than 1 main android_device module")
+		}
+	}
+
+	a.buildTargetFilesZip(ctx)
+	var deps []android.Path
+	if proptools.String(a.partitionProps.Super_partition_name) != "" {
+		superImage := ctx.GetDirectDepProxyWithTag(*a.partitionProps.Super_partition_name, superPartitionDepTag)
+		if info, ok := android.OtherModuleProvider(ctx, superImage, SuperImageProvider); ok {
+			assertUnset := func(prop *string, propName string) {
+				if prop != nil && *prop != "" {
+					ctx.PropertyErrorf(propName, "Cannot be set because it's already part of the super image")
+				}
+			}
+			for _, subPartitionType := range android.SortedKeys(info.SubImageInfo) {
+				switch subPartitionType {
+				case "system":
+					assertUnset(a.partitionProps.System_partition_name, "system_partition_name")
+				case "system_ext":
+					assertUnset(a.partitionProps.System_ext_partition_name, "system_ext_partition_name")
+				case "system_dlkm":
+					assertUnset(a.partitionProps.System_dlkm_partition_name, "system_dlkm_partition_name")
+				case "system_other":
+					// TODO
+				case "product":
+					assertUnset(a.partitionProps.Product_partition_name, "product_partition_name")
+				case "vendor":
+					assertUnset(a.partitionProps.Vendor_partition_name, "vendor_partition_name")
+				case "vendor_dlkm":
+					assertUnset(a.partitionProps.Vendor_dlkm_partition_name, "vendor_dlkm_partition_name")
+				case "odm":
+					assertUnset(a.partitionProps.Odm_partition_name, "odm_partition_name")
+				case "odm_dlkm":
+					assertUnset(a.partitionProps.Odm_dlkm_partition_name, "odm_dlkm_partition_name")
+				default:
+					ctx.ModuleErrorf("Unsupported sub-partition of super partition: %q", subPartitionType)
+				}
+			}
+
+			deps = append(deps, info.SuperImage)
+		} else {
+			ctx.ModuleErrorf("Expected super image dep to provide SuperImageProvider")
+		}
+	}
+	ctx.VisitDirectDepsProxyWithTag(filesystemDepTag, func(m android.ModuleProxy) {
+		imageOutput, ok := android.OtherModuleProvider(ctx, m, android.OutputFilesProvider)
+		if !ok {
+			ctx.ModuleErrorf("Partition module %s doesn't set OutputfilesProvider", m.Name())
+		}
+		if len(imageOutput.DefaultOutputFiles) != 1 {
+			ctx.ModuleErrorf("Partition module %s should provide exact 1 output file", m.Name())
+		}
+		deps = append(deps, imageOutput.DefaultOutputFiles[0])
+	})
+
+	allImagesZip := android.PathForModuleOut(ctx, "all_images.zip")
+	allImagesZipBuilder := android.NewRuleBuilder(pctx, ctx)
+	cmd := allImagesZipBuilder.Command().BuiltTool("soong_zip").Flag("--sort_entries")
+	for _, dep := range deps {
+		cmd.FlagWithArg("-e ", dep.Base())
+		cmd.FlagWithInput("-f ", dep)
+	}
+	cmd.FlagWithOutput("-o ", allImagesZip)
+	allImagesZipBuilder.Build("soong_all_images_zip", "all_images.zip")
+	a.allImagesZip = allImagesZip
+
+	allImagesStamp := android.PathForModuleOut(ctx, "all_images_stamp")
+	var validations android.Paths
+	if !ctx.Config().KatiEnabled() && proptools.Bool(a.deviceProps.Main_device) {
+		// In soong-only builds, build this module by default.
+		// This is the analogue to this make code:
+		// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/main.mk;l=1396;drc=6595459cdd8164a6008335f6372c9f97b9094060
+		ctx.Phony("droidcore-unbundled", allImagesStamp)
+
+		deps = append(deps, a.copyFilesToProductOutForSoongOnly(ctx))
+	}
+
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        android.Touch,
+		Output:      allImagesStamp,
+		Implicits:   deps,
+		Validations: validations,
+	})
+
+	// Checkbuilding it causes soong to make a phony, so you can say `m <module name>`
+	ctx.CheckbuildFile(allImagesStamp)
+
+	a.setVbmetaPhonyTargets(ctx)
+
+	a.distFiles(ctx)
+}
+
+func (a *androidDevice) distFiles(ctx android.ModuleContext) {
+	if !ctx.Config().KatiEnabled() {
+		if proptools.Bool(a.deviceProps.Main_device) {
+			fsInfoMap := a.getFsInfos(ctx)
+			for _, partition := range android.SortedKeys(fsInfoMap) {
+				fsInfo := fsInfoMap[partition]
+				if fsInfo.InstalledFiles.Json != nil {
+					ctx.DistForGoal("droidcore-unbundled", fsInfo.InstalledFiles.Json)
+				}
+				if fsInfo.InstalledFiles.Txt != nil {
+					ctx.DistForGoal("droidcore-unbundled", fsInfo.InstalledFiles.Txt)
+				}
+			}
+		}
+	}
+
+}
+
+func (a *androidDevice) MakeVars(ctx android.MakeVarsModuleContext) {
+	if proptools.Bool(a.deviceProps.Main_device) {
+		ctx.StrictRaw("SOONG_ONLY_ALL_IMAGES_ZIP", a.allImagesZip.String())
+	}
+}
+
+// Helper structs for target_files.zip creation
+type targetFilesZipCopy struct {
+	srcModule  *string
+	destSubdir string
+}
+
+type targetFilesystemZipCopy struct {
+	fsInfo     FilesystemInfo
+	destSubdir string
+}
+
+func (a *androidDevice) buildTargetFilesZip(ctx android.ModuleContext) {
+	targetFilesDir := android.PathForModuleOut(ctx, "target_files_dir")
+	targetFilesZip := android.PathForModuleOut(ctx, "target_files.zip")
+
+	builder := android.NewRuleBuilder(pctx, ctx)
+	builder.Command().Textf("rm -rf %s", targetFilesDir.String())
+	builder.Command().Textf("mkdir -p %s", targetFilesDir.String())
+	toCopy := []targetFilesZipCopy{
+		targetFilesZipCopy{a.partitionProps.System_partition_name, "SYSTEM"},
+		targetFilesZipCopy{a.partitionProps.System_ext_partition_name, "SYSTEM_EXT"},
+		targetFilesZipCopy{a.partitionProps.Product_partition_name, "PRODUCT"},
+		targetFilesZipCopy{a.partitionProps.Vendor_partition_name, "VENDOR"},
+		targetFilesZipCopy{a.partitionProps.Odm_partition_name, "ODM"},
+		targetFilesZipCopy{a.partitionProps.System_dlkm_partition_name, "SYSTEM_DLKM"},
+		targetFilesZipCopy{a.partitionProps.Vendor_dlkm_partition_name, "VENDOR_DLKM"},
+		targetFilesZipCopy{a.partitionProps.Odm_dlkm_partition_name, "ODM_DLKM"},
+		targetFilesZipCopy{a.partitionProps.Init_boot_partition_name, "BOOT/RAMDISK"},
+		targetFilesZipCopy{a.partitionProps.Init_boot_partition_name, "INIT_BOOT/RAMDISK"},
+		targetFilesZipCopy{a.partitionProps.Vendor_boot_partition_name, "VENDOR_BOOT/RAMDISK"},
+	}
+	// TODO: Handle cases where recovery files are copied to BOOT/ or RECOVERY/
+	// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=6211-6219?q=core%2FMakefile&ss=android%2Fplatform%2Fsuperproject%2Fmain
+	if ctx.DeviceConfig().BoardMoveRecoveryResourcesToVendorBoot() {
+		toCopy = append(toCopy, targetFilesZipCopy{a.partitionProps.Recovery_partition_name, "VENDOR_BOOT/RAMDISK"})
+	}
+
+	filesystemsToCopy := []targetFilesystemZipCopy{}
+	for _, zipCopy := range toCopy {
+		if zipCopy.srcModule == nil {
+			continue
+		}
+		filesystemsToCopy = append(
+			filesystemsToCopy,
+			targetFilesystemZipCopy{a.getFilesystemInfo(ctx, *zipCopy.srcModule), zipCopy.destSubdir},
+		)
+	}
+	// Get additional filesystems from super_partition dependency
+	if a.partitionProps.Super_partition_name != nil {
+		superPartition := ctx.GetDirectDepProxyWithTag(*a.partitionProps.Super_partition_name, superPartitionDepTag)
+		if info, ok := android.OtherModuleProvider(ctx, superPartition, SuperImageProvider); ok {
+			for _, partition := range android.SortedStringKeys(info.SubImageInfo) {
+				filesystemsToCopy = append(
+					filesystemsToCopy,
+					targetFilesystemZipCopy{info.SubImageInfo[partition], strings.ToUpper(partition)},
+				)
+			}
+		} else {
+			ctx.ModuleErrorf("Super partition %s does set SuperImageProvider\n", superPartition.Name())
+		}
+	}
+
+	for _, toCopy := range filesystemsToCopy {
+		rootDirString := toCopy.fsInfo.RootDir.String()
+		if toCopy.destSubdir == "SYSTEM" {
+			rootDirString = rootDirString + "/system"
+		}
+		builder.Command().Textf("mkdir -p %s/%s", targetFilesDir.String(), toCopy.destSubdir)
+		builder.Command().
+			BuiltTool("acp").
+			Textf("-rd %s/. %s/%s", rootDirString, targetFilesDir, toCopy.destSubdir).
+			Implicit(toCopy.fsInfo.Output) // so that the staging dir is built
+
+		if toCopy.destSubdir == "SYSTEM" {
+			// Create the ROOT partition in target_files.zip
+			builder.Command().Textf("rsync --links --exclude=system/* %s/ -r %s/ROOT", toCopy.fsInfo.RootDir, targetFilesDir.String())
+		}
+	}
+	// Copy cmdline, kernel etc. files of boot images
+	if a.partitionProps.Vendor_boot_partition_name != nil {
+		bootImg := ctx.GetDirectDepProxyWithTag(proptools.String(a.partitionProps.Vendor_boot_partition_name), filesystemDepTag)
+		bootImgInfo, _ := android.OtherModuleProvider(ctx, bootImg, BootimgInfoProvider)
+		builder.Command().Textf("echo %s > %s/VENDOR_BOOT/cmdline", proptools.ShellEscape(strings.Join(bootImgInfo.Cmdline, " ")), targetFilesDir)
+		builder.Command().Textf("echo %s > %s/VENDOR_BOOT/vendor_cmdline", proptools.ShellEscape(strings.Join(bootImgInfo.Cmdline, " ")), targetFilesDir)
+		if bootImgInfo.Dtb != nil {
+			builder.Command().Textf("cp ").Input(bootImgInfo.Dtb).Textf(" %s/VENDOR_BOOT/dtb", targetFilesDir)
+		}
+		if bootImgInfo.Bootconfig != nil {
+			builder.Command().Textf("cp ").Input(bootImgInfo.Bootconfig).Textf(" %s/VENDOR_BOOT/vendor_bootconfig", targetFilesDir)
+		}
+	}
+	if a.partitionProps.Boot_partition_name != nil {
+		bootImg := ctx.GetDirectDepProxyWithTag(proptools.String(a.partitionProps.Boot_partition_name), filesystemDepTag)
+		bootImgInfo, _ := android.OtherModuleProvider(ctx, bootImg, BootimgInfoProvider)
+		builder.Command().Textf("echo %s > %s/BOOT/cmdline", proptools.ShellEscape(strings.Join(bootImgInfo.Cmdline, " ")), targetFilesDir)
+		if bootImgInfo.Dtb != nil {
+			builder.Command().Textf("cp ").Input(bootImgInfo.Dtb).Textf(" %s/BOOT/dtb", targetFilesDir)
+		}
+		if bootImgInfo.Kernel != nil {
+			builder.Command().Textf("cp ").Input(bootImgInfo.Kernel).Textf(" %s/BOOT/kernel", targetFilesDir)
+			// Even though kernel is not used to build vendor_boot, copy the kernel to VENDOR_BOOT to match the behavior of make packaging.
+			builder.Command().Textf("cp ").Input(bootImgInfo.Kernel).Textf(" %s/VENDOR_BOOT/kernel", targetFilesDir)
+		}
+		if bootImgInfo.Bootconfig != nil {
+			builder.Command().Textf("cp ").Input(bootImgInfo.Bootconfig).Textf(" %s/BOOT/bootconfig", targetFilesDir)
+		}
+	}
+
+	if a.deviceProps.Android_info != nil {
+		builder.Command().Textf("mkdir -p %s/OTA", targetFilesDir)
+		builder.Command().Textf("cp ").Input(android.PathForModuleSrc(ctx, *a.deviceProps.Android_info)).Textf(" %s/OTA/android-info.txt", targetFilesDir)
+	}
+
+	a.copyImagesToTargetZip(ctx, builder, targetFilesDir)
+	a.copyMetadataToTargetZip(ctx, builder, targetFilesDir)
+
+	builder.Command().
+		BuiltTool("soong_zip").
+		Text("-d").
+		FlagWithOutput("-o ", targetFilesZip).
+		FlagWithArg("-C ", targetFilesDir.String()).
+		FlagWithArg("-D ", targetFilesDir.String()).
+		Text("-sha256")
+	builder.Build("target_files_"+ctx.ModuleName(), "Build target_files.zip")
+}
+
+func (a *androidDevice) copyImagesToTargetZip(ctx android.ModuleContext, builder *android.RuleBuilder, targetFilesDir android.WritablePath) {
+	// Create an IMAGES/ subdirectory
+	builder.Command().Textf("mkdir -p %s/IMAGES", targetFilesDir.String())
+	if a.deviceProps.Bootloader != nil {
+		builder.Command().Textf("cp ").Input(android.PathForModuleSrc(ctx, proptools.String(a.deviceProps.Bootloader))).Textf(" %s/IMAGES/bootloader", targetFilesDir.String())
+	}
+	// Copy the filesystem ,boot and vbmeta img files to IMAGES/
+	ctx.VisitDirectDepsProxyWithTag(filesystemDepTag, func(child android.ModuleProxy) {
+		if strings.Contains(child.Name(), "recovery") {
+			return // skip recovery.img to match the make packaging behavior
+		}
+		if info, ok := android.OtherModuleProvider(ctx, child, BootimgInfoProvider); ok {
+			// Check Boot img first so that the boot.img is copied and not its dep ramdisk.img
+			builder.Command().Textf("cp ").Input(info.Output).Textf(" %s/IMAGES/", targetFilesDir.String())
+		} else if info, ok := android.OtherModuleProvider(ctx, child, FilesystemProvider); ok {
+			builder.Command().Textf("cp ").Input(info.Output).Textf(" %s/IMAGES/", targetFilesDir.String())
+		} else if info, ok := android.OtherModuleProvider(ctx, child, vbmetaPartitionProvider); ok {
+			builder.Command().Textf("cp ").Input(info.Output).Textf(" %s/IMAGES/", targetFilesDir.String())
+		} else {
+			ctx.ModuleErrorf("Module %s does not provide an .img file output for target_files.zip", child.Name())
+		}
+	})
+
+	if a.partitionProps.Super_partition_name != nil {
+		superPartition := ctx.GetDirectDepProxyWithTag(*a.partitionProps.Super_partition_name, superPartitionDepTag)
+		if info, ok := android.OtherModuleProvider(ctx, superPartition, SuperImageProvider); ok {
+			for _, partition := range android.SortedKeys(info.SubImageInfo) {
+				if info.SubImageInfo[partition].OutputHermetic != nil {
+					builder.Command().Textf("cp ").Input(info.SubImageInfo[partition].OutputHermetic).Textf(" %s/IMAGES/", targetFilesDir.String())
+				}
+				if info.SubImageInfo[partition].MapFile != nil {
+					builder.Command().Textf("cp ").Input(info.SubImageInfo[partition].MapFile).Textf(" %s/IMAGES/", targetFilesDir.String())
+				}
+			}
+		} else {
+			ctx.ModuleErrorf("Super partition %s does set SuperImageProvider\n", superPartition.Name())
+		}
+	}
+}
+
+func (a *androidDevice) copyMetadataToTargetZip(ctx android.ModuleContext, builder *android.RuleBuilder, targetFilesDir android.WritablePath) {
+	// Create a META/ subdirectory
+	builder.Command().Textf("mkdir -p %s/META", targetFilesDir.String())
+	if proptools.Bool(a.deviceProps.Ab_ota_updater) {
+		ctx.VisitDirectDepsProxyWithTag(targetFilesMetadataDepTag, func(child android.ModuleProxy) {
+			info, _ := android.OtherModuleProvider(ctx, child, android.OutputFilesProvider)
+			builder.Command().Textf("cp").Inputs(info.DefaultOutputFiles).Textf(" %s/META/", targetFilesDir.String())
+		})
+		builder.Command().Textf("cp").Input(android.PathForSource(ctx, "external/zucchini/version_info.h")).Textf(" %s/META/zucchini_config.txt", targetFilesDir.String())
+		builder.Command().Textf("cp").Input(android.PathForSource(ctx, "system/update_engine/update_engine.conf")).Textf(" %s/META/update_engine_config.txt", targetFilesDir.String())
+		if a.getFsInfos(ctx)["system"].ErofsCompressHints != nil {
+			builder.Command().Textf("cp").Input(a.getFsInfos(ctx)["system"].ErofsCompressHints).Textf(" %s/META/erofs_default_compress_hints.txt", targetFilesDir.String())
+		}
+		// ab_partitions.txt
+		abPartitionsSorted := android.SortedUniqueStrings(a.deviceProps.Ab_ota_partitions)
+		abPartitionsSortedString := proptools.ShellEscape(strings.Join(abPartitionsSorted, "\\n"))
+		builder.Command().Textf("echo -e").Flag(abPartitionsSortedString).Textf(" > %s/META/ab_partitions.txt", targetFilesDir.String())
+		// otakeys.txt
+		abOtaKeysSorted := android.SortedUniqueStrings(a.deviceProps.Ab_ota_keys)
+		abOtaKeysSortedString := proptools.ShellEscape(strings.Join(abOtaKeysSorted, "\\n"))
+		builder.Command().Textf("echo -e").Flag(abOtaKeysSortedString).Textf(" > %s/META/otakeys.txt", targetFilesDir.String())
+		// postinstall_config.txt
+		abOtaPostInstallConfigString := proptools.ShellEscape(strings.Join(a.deviceProps.Ab_ota_postinstall_config, "\\n"))
+		builder.Command().Textf("echo -e").Flag(abOtaPostInstallConfigString).Textf(" > %s/META/postinstall_config.txt", targetFilesDir.String())
+		// selinuxfc
+		if a.getFsInfos(ctx)["system"].SelinuxFc != nil {
+			builder.Command().Textf("cp").Input(a.getFsInfos(ctx)["system"].SelinuxFc).Textf(" %s/META/file_contexts.bin", targetFilesDir.String())
+		}
+	}
+	// Copy $partition_filesystem_config.txt
+	fsInfos := a.getFsInfos(ctx)
+	for _, partition := range android.SortedKeys(fsInfos) {
+		if fsInfos[partition].FilesystemConfig == nil {
+			continue
+		}
+		if android.InList(partition, []string{"userdata"}) {
+			continue
+		}
+		builder.Command().Textf("cp").Input(fsInfos[partition].FilesystemConfig).Textf(" %s/META/%s", targetFilesDir.String(), a.filesystemConfigNameForTargetFiles(partition))
+	}
+	// Copy ramdisk_node_list
+	if ramdiskNodeList := android.PathForModuleSrc(ctx, proptools.String(a.deviceProps.Ramdisk_node_list)); ramdiskNodeList != nil {
+		builder.Command().Textf("cp").Input(ramdiskNodeList).Textf(" %s/META/", targetFilesDir.String())
+	}
+	// Copy releasetools.py
+	if releaseTools := android.PathForModuleSrc(ctx, proptools.String(a.deviceProps.Releasetools_extension)); releaseTools != nil {
+		builder.Command().Textf("cp").Input(releaseTools).Textf(" %s/META/", targetFilesDir.String())
+	}
+}
+
+// Filenames for the partition specific fs_config files.
+// Hardcode the ramdisk files to their boot image prefix
+func (a *androidDevice) filesystemConfigNameForTargetFiles(partition string) string {
+	name := partition + "_filesystem_config.txt"
+	if partition == "system" {
+		name = "filesystem_config.txt"
+	} else if partition == "ramdisk" {
+		name = "init_boot_filesystem_config.txt"
+	}
+	return name
+}
+
+func (a *androidDevice) getFilesystemInfo(ctx android.ModuleContext, depName string) FilesystemInfo {
+	fsMod := ctx.GetDirectDepProxyWithTag(depName, filesystemDepTag)
+	fsInfo, ok := android.OtherModuleProvider(ctx, fsMod, FilesystemProvider)
+	if !ok {
+		ctx.ModuleErrorf("Expected dependency %s to be a filesystem", depName)
+	}
+	return fsInfo
+}
+
+func (a *androidDevice) setVbmetaPhonyTargets(ctx android.ModuleContext) {
+	if !proptools.Bool(a.deviceProps.Main_device) {
+		return
+	}
+
+	if !ctx.Config().KatiEnabled() {
+		for _, vbmetaPartitionName := range a.partitionProps.Vbmeta_partitions {
+			img := ctx.GetDirectDepProxyWithTag(vbmetaPartitionName, filesystemDepTag)
+			if provider, ok := android.OtherModuleProvider(ctx, img, vbmetaPartitionProvider); ok {
+				// make generates `vbmetasystemimage` phony target instead of `vbmeta_systemimage` phony target.
+				partitionName := strings.ReplaceAll(provider.Name, "_", "")
+				ctx.Phony(fmt.Sprintf("%simage", partitionName), provider.Output)
+			}
+		}
+	}
+}
diff --git a/filesystem/android_device_product_out.go b/filesystem/android_device_product_out.go
new file mode 100644
index 0000000..7d37f1e
--- /dev/null
+++ b/filesystem/android_device_product_out.go
@@ -0,0 +1,260 @@
+// Copyright (C) 2025 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 filesystem
+
+import (
+	"android/soong/android"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+var (
+	copyStagingDirRule = pctx.AndroidStaticRule("copy_staging_dir", blueprint.RuleParams{
+		Command: "rsync -a --checksum $dir/ $dest && touch $out",
+	}, "dir", "dest")
+)
+
+func (a *androidDevice) copyToProductOut(ctx android.ModuleContext, builder *android.RuleBuilder, src android.Path, dest string) {
+	destPath := android.PathForModuleInPartitionInstall(ctx, "").Join(ctx, dest)
+	builder.Command().Text("rsync").Flag("-a").Flag("--checksum").Input(src).Text(destPath.String())
+}
+
+func (a *androidDevice) copyFilesToProductOutForSoongOnly(ctx android.ModuleContext) android.Path {
+	filesystemInfos := a.getFsInfos(ctx)
+
+	var deps android.Paths
+	var depsNoImg android.Paths // subset of deps without any img files. used for sbom creation.
+
+	for _, partition := range android.SortedKeys(filesystemInfos) {
+		info := filesystemInfos[partition]
+		imgInstallPath := android.PathForModuleInPartitionInstall(ctx, "", partition+".img")
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   android.Cp,
+			Input:  info.Output,
+			Output: imgInstallPath,
+		})
+
+		// Make it so doing `m <moduleName>` or `m <partitionType>image` will copy the files to
+		// PRODUCT_OUT
+		if partition == "system_ext" {
+			partition = "systemext"
+		}
+		partition = partition + "image"
+		ctx.Phony(info.ModuleName, imgInstallPath)
+		ctx.Phony(partition, imgInstallPath)
+		for _, fip := range info.FullInstallPaths {
+			// TODO: Directories. But maybe they're not necessary? Adevice doesn't care
+			// about empty directories, still need to check if adb sync does.
+			if !fip.IsDir {
+				if !fip.RequiresFullInstall {
+					// Some modules set requires_full_install: false, which causes their staging
+					// directory file to not be installed. This is usually because the file appears
+					// in both PRODUCT_COPY_FILES and a soong module for the handwritten soong system
+					// image. In this case, that module's installed files would conflict with the
+					// PRODUCT_COPY_FILES. However, in soong-only builds, we don't automatically
+					// create rules for PRODUCT_COPY_FILES unless they're needed in the partition.
+					// So in that case, nothing is creating the installed path. Create them now
+					// if that's the case.
+					if fip.SymlinkTarget == "" {
+						ctx.Build(pctx, android.BuildParams{
+							Rule:   android.CpWithBash,
+							Input:  fip.SourcePath,
+							Output: fip.FullInstallPath,
+							Args: map[string]string{
+								// Preserve timestamps for adb sync, so that this installed file's
+								// timestamp matches the timestamp in the filesystem's intermediate
+								// staging dir
+								"cpFlags": "-p",
+							},
+						})
+					} else {
+						ctx.Build(pctx, android.BuildParams{
+							Rule:   android.SymlinkWithBash,
+							Output: fip.FullInstallPath,
+							Args: map[string]string{
+								"fromPath": fip.SymlinkTarget,
+							},
+						})
+					}
+				}
+				ctx.Phony(info.ModuleName, fip.FullInstallPath)
+				ctx.Phony(partition, fip.FullInstallPath)
+				deps = append(deps, fip.FullInstallPath)
+				depsNoImg = append(depsNoImg, fip.FullInstallPath)
+				ctx.Phony("sync_"+partition, fip.FullInstallPath)
+				ctx.Phony("sync", fip.FullInstallPath)
+			}
+		}
+
+		deps = append(deps, imgInstallPath)
+	}
+
+	a.createComplianceMetadataTimestamp(ctx, depsNoImg)
+
+	// List all individual files to be copied to PRODUCT_OUT here
+	if a.deviceProps.Bootloader != nil {
+		bootloaderInstallPath := android.PathForModuleInPartitionInstall(ctx, "", "bootloader")
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   android.Cp,
+			Input:  android.PathForModuleSrc(ctx, *a.deviceProps.Bootloader),
+			Output: bootloaderInstallPath,
+		})
+		deps = append(deps, bootloaderInstallPath)
+	}
+
+	copyBootImg := func(prop *string, type_ string) {
+		if proptools.String(prop) != "" {
+			partition := ctx.GetDirectDepWithTag(*prop, filesystemDepTag)
+			if info, ok := android.OtherModuleProvider(ctx, partition, BootimgInfoProvider); ok {
+				installPath := android.PathForModuleInPartitionInstall(ctx, "", type_+".img")
+				ctx.Build(pctx, android.BuildParams{
+					Rule:   android.Cp,
+					Input:  info.Output,
+					Output: installPath,
+				})
+				deps = append(deps, installPath)
+			} else {
+				ctx.ModuleErrorf("%s does not set BootimgInfo\n", *prop)
+			}
+		}
+	}
+
+	copyBootImg(a.partitionProps.Init_boot_partition_name, "init_boot")
+	copyBootImg(a.partitionProps.Boot_partition_name, "boot")
+	copyBootImg(a.partitionProps.Vendor_boot_partition_name, "vendor_boot")
+
+	for _, vbmetaModName := range a.partitionProps.Vbmeta_partitions {
+		partition := ctx.GetDirectDepWithTag(vbmetaModName, filesystemDepTag)
+		if info, ok := android.OtherModuleProvider(ctx, partition, vbmetaPartitionProvider); ok {
+			installPath := android.PathForModuleInPartitionInstall(ctx, "", info.Name+".img")
+			ctx.Build(pctx, android.BuildParams{
+				Rule:   android.Cp,
+				Input:  info.Output,
+				Output: installPath,
+			})
+			deps = append(deps, installPath)
+		} else {
+			ctx.ModuleErrorf("%s does not set vbmetaPartitionProvider\n", vbmetaModName)
+		}
+	}
+
+	if proptools.String(a.partitionProps.Super_partition_name) != "" {
+		partition := ctx.GetDirectDepWithTag(*a.partitionProps.Super_partition_name, superPartitionDepTag)
+		if info, ok := android.OtherModuleProvider(ctx, partition, SuperImageProvider); ok {
+			installPath := android.PathForModuleInPartitionInstall(ctx, "", "super.img")
+			ctx.Build(pctx, android.BuildParams{
+				Rule:   android.Cp,
+				Input:  info.SuperImage,
+				Output: installPath,
+			})
+			deps = append(deps, installPath)
+		} else {
+			ctx.ModuleErrorf("%s does not set SuperImageProvider\n", *a.partitionProps.Super_partition_name)
+		}
+	}
+
+	if proptools.String(a.deviceProps.Android_info) != "" {
+		installPath := android.PathForModuleInPartitionInstall(ctx, "", "android_info.txt")
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   android.Cp,
+			Input:  android.PathForModuleSrc(ctx, *a.deviceProps.Android_info),
+			Output: installPath,
+		})
+		deps = append(deps, installPath)
+	}
+
+	copyToProductOutTimestamp := android.PathForModuleOut(ctx, "product_out_copy_timestamp")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:      android.Touch,
+		Output:    copyToProductOutTimestamp,
+		Implicits: deps,
+	})
+
+	emptyFile := android.PathForModuleOut(ctx, "empty_file")
+	android.WriteFileRule(ctx, emptyFile, "")
+
+	// TODO: We don't have these tests building in soong yet. Add phonies for them so that CI builds
+	// that try to build them don't error out.
+	ctx.Phony("continuous_instrumentation_tests", emptyFile)
+	ctx.Phony("continuous_native_tests", emptyFile)
+	ctx.Phony("device-tests", emptyFile)
+	ctx.Phony("device-platinum-tests", emptyFile)
+	ctx.Phony("platform_tests", emptyFile)
+
+	return copyToProductOutTimestamp
+}
+
+// createComplianceMetadataTimestampForSoongOnly creates a timestamp file in m --soong-only
+// this timestamp file depends on installed files of the main `android_device`.
+// Any changes to installed files of the main `android_device` will retrigger SBOM generation
+func (a *androidDevice) createComplianceMetadataTimestamp(ctx android.ModuleContext, installedFiles android.Paths) {
+	ctx.Build(pctx, android.BuildParams{
+		Rule:      android.Touch,
+		Implicits: installedFiles,
+		Output:    android.PathForOutput(ctx, "compliance-metadata", ctx.Config().DeviceProduct(), "installed_files.stamp"),
+	})
+}
+
+// Returns a mapping from partition type -> FilesystemInfo. This includes filesystems that are
+// nested inside of other partitions, such as the partitions inside super.img, or ramdisk inside
+// of boot.
+func (a *androidDevice) getFsInfos(ctx android.ModuleContext) map[string]FilesystemInfo {
+	type propToType struct {
+		prop *string
+		ty   string
+	}
+
+	filesystemInfos := make(map[string]FilesystemInfo)
+
+	partitionDefinitions := []propToType{
+		propToType{a.partitionProps.System_partition_name, "system"},
+		propToType{a.partitionProps.System_ext_partition_name, "system_ext"},
+		propToType{a.partitionProps.Product_partition_name, "product"},
+		propToType{a.partitionProps.Vendor_partition_name, "vendor"},
+		propToType{a.partitionProps.Odm_partition_name, "odm"},
+		propToType{a.partitionProps.Recovery_partition_name, "recovery"},
+		propToType{a.partitionProps.System_dlkm_partition_name, "system_dlkm"},
+		propToType{a.partitionProps.Vendor_dlkm_partition_name, "vendor_dlkm"},
+		propToType{a.partitionProps.Odm_dlkm_partition_name, "odm_dlkm"},
+		propToType{a.partitionProps.Userdata_partition_name, "userdata"},
+		// filesystemInfo from init_boot and vendor_boot actually are re-exports of the ramdisk
+		// images inside of them
+		propToType{a.partitionProps.Init_boot_partition_name, "ramdisk"},
+		propToType{a.partitionProps.Vendor_boot_partition_name, "vendor_ramdisk"},
+	}
+	for _, partitionDefinition := range partitionDefinitions {
+		if proptools.String(partitionDefinition.prop) != "" {
+			partition := ctx.GetDirectDepWithTag(*partitionDefinition.prop, filesystemDepTag)
+			if info, ok := android.OtherModuleProvider(ctx, partition, FilesystemProvider); ok {
+				filesystemInfos[partitionDefinition.ty] = info
+			} else {
+				ctx.ModuleErrorf("Super partition %s does not set FilesystemProvider\n", partition.Name())
+			}
+		}
+	}
+	if a.partitionProps.Super_partition_name != nil {
+		superPartition := ctx.GetDirectDepWithTag(*a.partitionProps.Super_partition_name, superPartitionDepTag)
+		if info, ok := android.OtherModuleProvider(ctx, superPartition, SuperImageProvider); ok {
+			for partition := range info.SubImageInfo {
+				filesystemInfos[partition] = info.SubImageInfo[partition]
+			}
+		} else {
+			ctx.ModuleErrorf("Super partition %s does not set SuperImageProvider\n", superPartition.Name())
+		}
+	}
+
+	return filesystemInfos
+}
diff --git a/filesystem/avb_add_hash_footer.go b/filesystem/avb_add_hash_footer.go
index 469f1fb..f32993c 100644
--- a/filesystem/avb_add_hash_footer.go
+++ b/filesystem/avb_add_hash_footer.go
@@ -29,7 +29,7 @@
 
 	properties avbAddHashFooterProperties
 
-	output     android.OutputPath
+	output     android.Path
 	installDir android.InstallPath
 }
 
@@ -46,7 +46,7 @@
 
 type avbAddHashFooterProperties struct {
 	// Source file of this image. Can reference a genrule type module with the ":module" syntax.
-	Src *string `android:"path,arch_variant"`
+	Src proptools.Configurable[string] `android:"path,arch_variant,replace_instead_of_append"`
 
 	// Set the name of the output. Defaults to <module_name>.img.
 	Filename *string
@@ -91,14 +91,15 @@
 
 func (a *avbAddHashFooter) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	builder := android.NewRuleBuilder(pctx, ctx)
+	src := a.properties.Src.GetOrDefault(ctx, "")
 
-	if a.properties.Src == nil {
+	if src == "" {
 		ctx.PropertyErrorf("src", "missing source file")
 		return
 	}
-	input := android.PathForModuleSrc(ctx, proptools.String(a.properties.Src))
-	a.output = android.PathForModuleOut(ctx, a.installFileName()).OutputPath
-	builder.Command().Text("cp").Input(input).Output(a.output)
+	input := android.PathForModuleSrc(ctx, src)
+	output := android.PathForModuleOut(ctx, a.installFileName())
+	builder.Command().Text("cp").Input(input).Output(output)
 
 	cmd := builder.Command().BuiltTool("avbtool").Text("add_hash_footer")
 
@@ -141,12 +142,13 @@
 		cmd.Flag(fmt.Sprintf(" --rollback_index %d", rollbackIndex))
 	}
 
-	cmd.FlagWithOutput("--image ", a.output)
+	cmd.FlagWithOutput("--image ", output)
 
 	builder.Build("avbAddHashFooter", fmt.Sprintf("avbAddHashFooter %s", ctx.ModuleName()))
 
 	a.installDir = android.PathForModuleInstall(ctx, "etc")
-	ctx.InstallFile(a.installDir, a.installFileName(), a.output)
+	ctx.InstallFile(a.installDir, a.installFileName(), output)
+	a.output = output
 }
 
 func addAvbProp(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, prop avbProp) {
diff --git a/filesystem/avb_gen_vbmeta_image.go b/filesystem/avb_gen_vbmeta_image.go
index a7fd782..0669a2c 100644
--- a/filesystem/avb_gen_vbmeta_image.go
+++ b/filesystem/avb_gen_vbmeta_image.go
@@ -28,7 +28,7 @@
 
 	properties avbGenVbmetaImageProperties
 
-	output     android.OutputPath
+	output     android.Path
 	installDir android.InstallPath
 }
 
@@ -78,11 +78,12 @@
 	}
 	cmd.FlagWithArg("--salt ", proptools.String(a.properties.Salt))
 
-	a.output = android.PathForModuleOut(ctx, a.installFileName()).OutputPath
-	cmd.FlagWithOutput("--output_vbmeta_image ", a.output)
+	output := android.PathForModuleOut(ctx, a.installFileName())
+	cmd.FlagWithOutput("--output_vbmeta_image ", output)
 	builder.Build("avbGenVbmetaImage", fmt.Sprintf("avbGenVbmetaImage %s", ctx.ModuleName()))
 
-	ctx.SetOutputFiles([]android.Path{a.output}, "")
+	ctx.SetOutputFiles([]android.Path{output}, "")
+	a.output = output
 }
 
 var _ android.AndroidMkEntriesProvider = (*avbGenVbmetaImage)(nil)
diff --git a/filesystem/bootconfig.go b/filesystem/bootconfig.go
new file mode 100644
index 0000000..b125824
--- /dev/null
+++ b/filesystem/bootconfig.go
@@ -0,0 +1,80 @@
+// 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 filesystem
+
+import (
+	"android/soong/android"
+	"strings"
+
+	"github.com/google/blueprint/proptools"
+)
+
+func init() {
+	android.RegisterModuleType("bootconfig", BootconfigModuleFactory)
+	pctx.Import("android/soong/android")
+}
+
+type bootconfigProperty struct {
+	// List of bootconfig parameters that will be written as a line separated list in the output
+	// file.
+	Boot_config []string
+	// Path to the file that contains the list of bootconfig parameters. This will be appended
+	// to the output file, after the entries in boot_config.
+	Boot_config_file *string `android:"path"`
+}
+
+type BootconfigModule struct {
+	android.ModuleBase
+
+	properties bootconfigProperty
+}
+
+// bootconfig module generates the `vendor-bootconfig.img` file, which lists the bootconfig
+// parameters and can be passed as a `--vendor_bootconfig` value in mkbootimg invocation.
+func BootconfigModuleFactory() android.Module {
+	module := &BootconfigModule{}
+	module.AddProperties(&module.properties)
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	return module
+}
+
+func (m *BootconfigModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	bootConfig := m.properties.Boot_config
+	bootConfigFileStr := proptools.String(m.properties.Boot_config_file)
+	if len(bootConfig) == 0 && len(bootConfigFileStr) == 0 {
+		return
+	}
+
+	var bootConfigFile android.Path
+	if len(bootConfigFileStr) > 0 {
+		bootConfigFile = android.PathForModuleSrc(ctx, bootConfigFileStr)
+	}
+
+	outputPath := android.PathForModuleOut(ctx, ctx.ModuleName(), "vendor-bootconfig.img")
+	bootConfigOutput := android.PathForModuleOut(ctx, ctx.ModuleName(), "bootconfig.txt")
+	android.WriteFileRule(ctx, bootConfigOutput, strings.Join(bootConfig, "\n"))
+
+	bcFiles := android.Paths{bootConfigOutput}
+	if bootConfigFile != nil {
+		bcFiles = append(bcFiles, bootConfigFile)
+	}
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        android.Cat,
+		Description: "concatenate bootconfig parameters",
+		Inputs:      bcFiles,
+		Output:      outputPath,
+	})
+	ctx.SetOutputFiles(android.Paths{outputPath}, "")
+}
diff --git a/filesystem/bootimg.go b/filesystem/bootimg.go
index e796ab9..6d6c15c 100644
--- a/filesystem/bootimg.go
+++ b/filesystem/bootimg.go
@@ -26,19 +26,21 @@
 )
 
 func init() {
-	android.RegisterModuleType("bootimg", bootimgFactory)
+	android.RegisterModuleType("bootimg", BootimgFactory)
 }
 
 type bootimg struct {
 	android.ModuleBase
 
-	properties bootimgProperties
+	properties BootimgProperties
 
-	output     android.OutputPath
+	output     android.Path
 	installDir android.InstallPath
+
+	bootImageType bootImageType
 }
 
-type bootimgProperties struct {
+type BootimgProperties struct {
 	// Set the name of the output. Defaults to <module_name>.img.
 	Stem *string
 
@@ -56,9 +58,13 @@
 	// https://source.android.com/devices/bootloader/boot-image-header
 	Header_version *string
 
-	// Determines if this image is for the vendor_boot partition. Default is false. Refer to
-	// https://source.android.com/devices/bootloader/partitions/vendor-boot-partitions
-	Vendor_boot *bool
+	// Determines the specific type of boot image this module is building. Can be boot,
+	// vendor_boot or init_boot. Defaults to boot.
+	// Refer to https://source.android.com/devices/bootloader/partitions/vendor-boot-partitions
+	// for vendor_boot.
+	// Refer to https://source.android.com/docs/core/architecture/partitions/generic-boot for
+	// init_boot.
+	Boot_image_type *string
 
 	// Optional kernel commandline arguments
 	Cmdline []string `android:"arch_variant"`
@@ -67,22 +73,90 @@
 	// and `header_version` is greater than or equal to 4.
 	Bootconfig *string `android:"arch_variant,path"`
 
+	// The size of the partition on the device. It will be a build error if this built partition
+	// image exceeds this size.
+	Partition_size *int64
+
 	// When set to true, sign the image with avbtool. Default is false.
 	Use_avb *bool
 
+	// This can either be "default", or "make_legacy". "make_legacy" will sign the boot image
+	// like how build/make/core/Makefile does, to get bit-for-bit backwards compatibility. But
+	// we may want to reconsider if it's necessary to have two modes in the future. The default
+	// is "default"
+	Avb_mode *string
+
 	// Name of the partition stored in vbmeta desc. Defaults to the name of this module.
 	Partition_name *string
 
 	// Path to the private key that avbtool will use to sign this filesystem image.
 	// TODO(jiyong): allow apex_key to be specified here
-	Avb_private_key *string `android:"path"`
+	Avb_private_key *string `android:"path_device_first"`
 
 	// Hash and signing algorithm for avbtool. Default is SHA256_RSA4096.
 	Avb_algorithm *string
+
+	// The index used to prevent rollback of the image on device.
+	Avb_rollback_index *int64
+
+	// Rollback index location of this image. Must be 0, 1, 2, etc.
+	Avb_rollback_index_location *int64
+
+	// The security patch passed to as the com.android.build.<type>.security_patch avb property.
+	// Replacement for the make variables BOOT_SECURITY_PATCH / INIT_BOOT_SECURITY_PATCH.
+	Security_patch *string
+}
+
+type bootImageType int
+
+const (
+	unsupported bootImageType = iota
+	boot
+	vendorBoot
+	initBoot
+)
+
+func toBootImageType(ctx android.ModuleContext, bootImageType string) bootImageType {
+	switch bootImageType {
+	case "boot":
+		return boot
+	case "vendor_boot":
+		return vendorBoot
+	case "init_boot":
+		return initBoot
+	default:
+		ctx.ModuleErrorf("Unknown boot_image_type %s. Must be one of \"boot\", \"vendor_boot\", or \"init_boot\"", bootImageType)
+	}
+	return unsupported
+}
+
+func (b bootImageType) String() string {
+	switch b {
+	case boot:
+		return "boot"
+	case vendorBoot:
+		return "vendor_boot"
+	case initBoot:
+		return "init_boot"
+	default:
+		panic("unknown boot image type")
+	}
+}
+
+func (b bootImageType) isBoot() bool {
+	return b == boot
+}
+
+func (b bootImageType) isVendorBoot() bool {
+	return b == vendorBoot
+}
+
+func (b bootImageType) isInitBoot() bool {
+	return b == initBoot
 }
 
 // bootimg is the image for the boot partition. It consists of header, kernel, ramdisk, and dtb.
-func bootimgFactory() android.Module {
+func BootimgFactory() android.Module {
 	module := &bootimg{}
 	module.AddProperties(&module.properties)
 	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
@@ -112,50 +186,144 @@
 }
 
 func (b *bootimg) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	vendor := proptools.Bool(b.properties.Vendor_boot)
-	unsignedOutput := b.buildBootImage(ctx, vendor)
+	b.bootImageType = toBootImageType(ctx, proptools.StringDefault(b.properties.Boot_image_type, "boot"))
+	if b.bootImageType == unsupported {
+		return
+	}
 
+	kernelProp := proptools.String(b.properties.Kernel_prebuilt)
+	if b.bootImageType.isVendorBoot() && kernelProp != "" {
+		ctx.PropertyErrorf("kernel_prebuilt", "vendor_boot partition can't have kernel")
+		return
+	}
+	if b.bootImageType.isBoot() && kernelProp == "" {
+		ctx.PropertyErrorf("kernel_prebuilt", "boot partition must have kernel")
+		return
+	}
+
+	unsignedOutput := b.buildBootImage(ctx, b.getKernelPath(ctx))
+
+	output := unsignedOutput
 	if proptools.Bool(b.properties.Use_avb) {
-		b.output = b.signImage(ctx, unsignedOutput)
-	} else {
-		b.output = unsignedOutput
+		// This bootimg module supports 2 modes of avb signing. It is not clear to this author
+		// why there are differences, but one of them is to match the behavior of make-built boot
+		// images.
+		switch proptools.StringDefault(b.properties.Avb_mode, "default") {
+		case "default":
+			output = b.signImage(ctx, unsignedOutput)
+		case "make_legacy":
+			output = b.addAvbFooter(ctx, unsignedOutput, b.getKernelPath(ctx))
+		default:
+			ctx.PropertyErrorf("avb_mode", `Unknown value for avb_mode, expected "default" or "make_legacy", got: %q`, *b.properties.Avb_mode)
+		}
 	}
 
 	b.installDir = android.PathForModuleInstall(ctx, "etc")
-	ctx.InstallFile(b.installDir, b.installFileName(), b.output)
+	ctx.InstallFile(b.installDir, b.installFileName(), output)
 
-	ctx.SetOutputFiles([]android.Path{b.output}, "")
+	ctx.SetOutputFiles([]android.Path{output}, "")
+	b.output = output
+
+	// Set the Filesystem info of the ramdisk dependency.
+	// `android_device` will use this info to package `target_files.zip`
+	if ramdisk := proptools.String(b.properties.Ramdisk_module); ramdisk != "" {
+		ramdiskModule := ctx.GetDirectDepWithTag(ramdisk, bootimgRamdiskDep)
+		fsInfo, _ := android.OtherModuleProvider(ctx, ramdiskModule, FilesystemProvider)
+		android.SetProvider(ctx, FilesystemProvider, fsInfo)
+	}
+
+	// Set BootimgInfo for building target_files.zip
+	android.SetProvider(ctx, BootimgInfoProvider, BootimgInfo{
+		Cmdline:    b.properties.Cmdline,
+		Kernel:     b.getKernelPath(ctx),
+		Dtb:        b.getDtbPath(ctx),
+		Bootconfig: b.getBootconfigPath(ctx),
+		Output:     output,
+	})
+
+	extractedPublicKey := android.PathForModuleOut(ctx, b.partitionName()+".avbpubkey")
+	if b.properties.Avb_private_key != nil {
+		key := android.PathForModuleSrc(ctx, proptools.String(b.properties.Avb_private_key))
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   extractPublicKeyRule,
+			Input:  key,
+			Output: extractedPublicKey,
+		})
+	}
+	var ril int
+	if b.properties.Avb_rollback_index_location != nil {
+		ril = proptools.Int(b.properties.Avb_rollback_index_location)
+	}
+
+	android.SetProvider(ctx, vbmetaPartitionProvider, vbmetaPartitionInfo{
+		Name:                  b.bootImageType.String(),
+		RollbackIndexLocation: ril,
+		PublicKey:             extractedPublicKey,
+		Output:                output,
+	})
 }
 
-func (b *bootimg) buildBootImage(ctx android.ModuleContext, vendor bool) android.OutputPath {
-	output := android.PathForModuleOut(ctx, "unsigned", b.installFileName()).OutputPath
+var BootimgInfoProvider = blueprint.NewProvider[BootimgInfo]()
+
+type BootimgInfo struct {
+	Cmdline    []string
+	Kernel     android.Path
+	Dtb        android.Path
+	Bootconfig android.Path
+	Output     android.Path
+}
+
+func (b *bootimg) getKernelPath(ctx android.ModuleContext) android.Path {
+	var kernelPath android.Path
+	kernelName := proptools.String(b.properties.Kernel_prebuilt)
+	if kernelName != "" {
+		kernelPath = android.PathForModuleSrc(ctx, kernelName)
+	}
+	return kernelPath
+}
+
+func (b *bootimg) getDtbPath(ctx android.ModuleContext) android.Path {
+	var dtbPath android.Path
+	dtbName := proptools.String(b.properties.Dtb_prebuilt)
+	if dtbName != "" {
+		dtbPath = android.PathForModuleSrc(ctx, dtbName)
+	}
+	return dtbPath
+}
+
+func (b *bootimg) getBootconfigPath(ctx android.ModuleContext) android.Path {
+	var bootconfigPath android.Path
+	bootconfigName := proptools.String(b.properties.Bootconfig)
+	if bootconfigName != "" {
+		bootconfigPath = android.PathForModuleSrc(ctx, bootconfigName)
+	}
+	return bootconfigPath
+}
+
+func (b *bootimg) buildBootImage(ctx android.ModuleContext, kernel android.Path) android.Path {
+	output := android.PathForModuleOut(ctx, "unsigned", b.installFileName())
 
 	builder := android.NewRuleBuilder(pctx, ctx)
 	cmd := builder.Command().BuiltTool("mkbootimg")
 
-	kernel := proptools.String(b.properties.Kernel_prebuilt)
-	if vendor && kernel != "" {
-		ctx.PropertyErrorf("kernel_prebuilt", "vendor_boot partition can't have kernel")
-		return output
-	}
-	if !vendor && kernel == "" {
-		ctx.PropertyErrorf("kernel_prebuilt", "boot partition must have kernel")
-		return output
-	}
-	if kernel != "" {
-		cmd.FlagWithInput("--kernel ", android.PathForModuleSrc(ctx, kernel))
+	if kernel != nil {
+		cmd.FlagWithInput("--kernel ", kernel)
 	}
 
-	dtbName := proptools.String(b.properties.Dtb_prebuilt)
-	if dtbName != "" {
-		dtb := android.PathForModuleSrc(ctx, dtbName)
-		cmd.FlagWithInput("--dtb ", dtb)
+	// These arguments are passed for boot.img and init_boot.img generation
+	if b.bootImageType.isBoot() || b.bootImageType.isInitBoot() {
+		cmd.FlagWithArg("--os_version ", ctx.Config().PlatformVersionLastStable())
+		cmd.FlagWithArg("--os_patch_level ", ctx.Config().PlatformSecurityPatch())
+	}
+
+	if b.getDtbPath(ctx) != nil {
+		cmd.FlagWithInput("--dtb ", b.getDtbPath(ctx))
 	}
 
 	cmdline := strings.Join(b.properties.Cmdline, " ")
 	if cmdline != "" {
 		flag := "--cmdline "
-		if vendor {
+		if b.bootImageType.isVendorBoot() {
 			flag = "--vendor_cmdline "
 		}
 		cmd.FlagWithArg(flag, proptools.ShellEscapeIncludingSpaces(cmdline))
@@ -182,7 +350,7 @@
 		ramdisk := ctx.GetDirectDepWithTag(ramdiskName, bootimgRamdiskDep)
 		if filesystem, ok := ramdisk.(*filesystem); ok {
 			flag := "--ramdisk "
-			if vendor {
+			if b.bootImageType.isVendorBoot() {
 				flag = "--vendor_ramdisk "
 			}
 			cmd.FlagWithInput(flag, filesystem.OutputPath())
@@ -194,7 +362,7 @@
 
 	bootconfig := proptools.String(b.properties.Bootconfig)
 	if bootconfig != "" {
-		if !vendor {
+		if !b.bootImageType.isVendorBoot() {
 			ctx.PropertyErrorf("bootconfig", "requires vendor_boot: true")
 			return output
 		}
@@ -205,20 +373,85 @@
 		cmd.FlagWithInput("--vendor_bootconfig ", android.PathForModuleSrc(ctx, bootconfig))
 	}
 
+	// Output flag for boot.img and init_boot.img
 	flag := "--output "
-	if vendor {
+	if b.bootImageType.isVendorBoot() {
 		flag = "--vendor_boot "
 	}
 	cmd.FlagWithOutput(flag, output)
 
+	if b.properties.Partition_size != nil {
+		assertMaxImageSize(builder, output, *b.properties.Partition_size, proptools.Bool(b.properties.Use_avb))
+	}
+
 	builder.Build("build_bootimg", fmt.Sprintf("Creating %s", b.BaseModuleName()))
 	return output
 }
 
-func (b *bootimg) signImage(ctx android.ModuleContext, unsignedImage android.OutputPath) android.OutputPath {
+func (b *bootimg) addAvbFooter(ctx android.ModuleContext, unsignedImage android.Path, kernel android.Path) android.Path {
+	output := android.PathForModuleOut(ctx, b.installFileName())
+	builder := android.NewRuleBuilder(pctx, ctx)
+	builder.Command().Text("cp").Input(unsignedImage).Output(output)
+	cmd := builder.Command().BuiltTool("avbtool").
+		Text("add_hash_footer").
+		FlagWithInput("--image ", output)
+
+	if b.properties.Partition_size != nil {
+		cmd.FlagWithArg("--partition_size ", strconv.FormatInt(*b.properties.Partition_size, 10))
+	} else {
+		cmd.Flag("--dynamic_partition_size")
+	}
+
+	// If you don't provide a salt, avbtool will use random bytes for the salt.
+	// This is bad for determinism (cached builds and diff tests are affected), so instead,
+	// we try to provide a salt. The requirements for a salt are not very clear, one aspect of it
+	// is that if it's unpredictable, attackers trying to change the contents of a partition need
+	// to find a new hash collision every release, because the salt changed.
+	if kernel != nil {
+		cmd.Textf(`--salt $(sha256sum "%s" | cut -d " " -f 1)`, kernel.String())
+		cmd.Implicit(kernel)
+	} else {
+		cmd.Textf(`--salt $(sha256sum "%s" "%s" | cut -d " " -f 1 | tr -d '\n')`, ctx.Config().BuildNumberFile(ctx), ctx.Config().Getenv("BUILD_DATETIME_FILE"))
+		cmd.OrderOnly(ctx.Config().BuildNumberFile(ctx))
+	}
+
+	cmd.FlagWithArg("--partition_name ", b.bootImageType.String())
+
+	if b.properties.Avb_algorithm != nil {
+		cmd.FlagWithArg("--algorithm ", proptools.NinjaAndShellEscape(*b.properties.Avb_algorithm))
+	}
+
+	if b.properties.Avb_private_key != nil {
+		key := android.PathForModuleSrc(ctx, proptools.String(b.properties.Avb_private_key))
+		cmd.FlagWithInput("--key ", key)
+	}
+
+	if !b.bootImageType.isVendorBoot() {
+		cmd.FlagWithArg("--prop ", proptools.NinjaAndShellEscape(fmt.Sprintf(
+			"com.android.build.%s.os_version:%s", b.bootImageType.String(), ctx.Config().PlatformVersionLastStable())))
+	}
+
+	fingerprintFile := ctx.Config().BuildFingerprintFile(ctx)
+	cmd.FlagWithArg("--prop ", fmt.Sprintf("com.android.build.%s.fingerprint:$(cat %s)", b.bootImageType.String(), fingerprintFile.String()))
+	cmd.OrderOnly(fingerprintFile)
+
+	if b.properties.Security_patch != nil {
+		cmd.FlagWithArg("--prop ", proptools.NinjaAndShellEscape(fmt.Sprintf(
+			"com.android.build.%s.security_patch:%s", b.bootImageType.String(), *b.properties.Security_patch)))
+	}
+
+	if b.properties.Avb_rollback_index != nil {
+		cmd.FlagWithArg("--rollback_index ", strconv.FormatInt(*b.properties.Avb_rollback_index, 10))
+	}
+
+	builder.Build("add_avb_footer", fmt.Sprintf("Adding avb footer to %s", b.BaseModuleName()))
+	return output
+}
+
+func (b *bootimg) signImage(ctx android.ModuleContext, unsignedImage android.Path) android.Path {
 	propFile, toolDeps := b.buildPropFile(ctx)
 
-	output := android.PathForModuleOut(ctx, b.installFileName()).OutputPath
+	output := android.PathForModuleOut(ctx, b.installFileName())
 	builder := android.NewRuleBuilder(pctx, ctx)
 	builder.Command().Text("cp").Input(unsignedImage).Output(output)
 	builder.Command().BuiltTool("verity_utils").
@@ -230,16 +463,7 @@
 	return output
 }
 
-// Calculates avb_salt from some input for deterministic output.
-func (b *bootimg) salt() string {
-	var input []string
-	input = append(input, b.properties.Cmdline...)
-	input = append(input, proptools.StringDefault(b.properties.Partition_name, b.Name()))
-	input = append(input, proptools.String(b.properties.Header_version))
-	return sha1sum(input)
-}
-
-func (b *bootimg) buildPropFile(ctx android.ModuleContext) (propFile android.OutputPath, toolDeps android.Paths) {
+func (b *bootimg) buildPropFile(ctx android.ModuleContext) (android.Path, android.Paths) {
 	var sb strings.Builder
 	var deps android.Paths
 	addStr := func(name string, value string) {
@@ -259,9 +483,8 @@
 	addStr("avb_add_hash_footer_args", "") // TODO(jiyong): add --rollback_index
 	partitionName := proptools.StringDefault(b.properties.Partition_name, b.Name())
 	addStr("partition_name", partitionName)
-	addStr("avb_salt", b.salt())
 
-	propFile = android.PathForModuleOut(ctx, "prop").OutputPath
+	propFile := android.PathForModuleOut(ctx, "prop")
 	android.WriteFileRule(ctx, propFile, sb.String())
 	return propFile, deps
 }
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index 09d8fba..f84993d 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -20,11 +20,14 @@
 	"io"
 	"path/filepath"
 	"slices"
+	"sort"
 	"strconv"
 	"strings"
 
 	"android/soong/android"
 	"android/soong/cc"
+	"android/soong/java"
+	"android/soong/linkerconfig"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
@@ -32,6 +35,9 @@
 
 func init() {
 	registerBuildComponents(android.InitRegistrationContext)
+	registerMutators(android.InitRegistrationContext)
+	pctx.HostBinToolVariable("fileslist", "fileslist")
+	pctx.HostBinToolVariable("fs_config", "fs_config")
 }
 
 func registerBuildComponents(ctx android.RegistrationContext) {
@@ -44,6 +50,35 @@
 	ctx.RegisterModuleType("avb_gen_vbmeta_image_defaults", avbGenVbmetaImageDefaultsFactory)
 }
 
+func registerMutators(ctx android.RegistrationContext) {
+	ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
+		ctx.BottomUp("add_autogenerated_rro_deps", addAutogeneratedRroDeps)
+	})
+}
+
+var (
+	// Remember to add referenced files to implicits!
+	textFileProcessorRule = pctx.AndroidStaticRule("text_file_processing", blueprint.RuleParams{
+		Command:     "build/soong/scripts/text_file_processor.py $in $out",
+		CommandDeps: []string{"build/soong/scripts/text_file_processor.py"},
+	})
+
+	// Remember to add the output image file as an implicit dependency!
+	installedFilesJsonRule = pctx.AndroidStaticRule("installed_files_json", blueprint.RuleParams{
+		Command:     `${fileslist} ${rootDir} > ${out}`,
+		CommandDeps: []string{"${fileslist}"},
+	}, "rootDir")
+
+	installedFilesTxtRule = pctx.AndroidStaticRule("installed_files_txt", blueprint.RuleParams{
+		Command:     `build/make/tools/fileslist_util.py -c ${in} > ${out}`,
+		CommandDeps: []string{"build/make/tools/fileslist_util.py"},
+	})
+	fsConfigRule = pctx.AndroidStaticRule("fs_config_rule", blueprint.RuleParams{
+		Command:     `(cd ${rootDir}; find . -type d | sed 's,$$,/,'; find . \! -type d) | cut -c 3- | sort | sed 's,^,${prefix},' | ${fs_config} -C -D ${rootDir} -R "${prefix}" > ${out}`,
+		CommandDeps: []string{"${fs_config}"},
+	}, "rootDir", "prefix")
+)
+
 type filesystem struct {
 	android.ModuleBase
 	android.PackagingBase
@@ -51,26 +86,48 @@
 
 	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
+	output     android.Path
 	installDir android.InstallPath
 
-	fileListFile android.OutputPath
+	fileListFile android.Path
 
 	// Keeps the entries installed from this filesystem
 	entries []string
+
+	filesystemBuilder filesystemBuilder
+
+	selinuxFc android.Path
 }
 
-type symlinkDefinition struct {
+type filesystemBuilder interface {
+	BuildLinkerConfigFile(ctx android.ModuleContext, builder *android.RuleBuilder, rebasedDir android.OutputPath, fullInstallPaths *[]FullInstallPathInfo)
+	// 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)
+
+	// Function to check if the filesystem should not use `vintf_fragments` property,
+	// but use `vintf_fragment` module type instead
+	ShouldUseVintfFragmentModuleOnly() bool
+}
+
+var _ filesystemBuilder = (*filesystem)(nil)
+
+type SymlinkDefinition struct {
 	Target *string
 	Name   *string
 }
 
+// CopyWithNamePrefix returns a new [SymlinkDefinition] with prefix added to Name.
+func (s *SymlinkDefinition) CopyWithNamePrefix(prefix string) SymlinkDefinition {
+	return SymlinkDefinition{
+		Target: s.Target,
+		Name:   proptools.StringPtr(filepath.Join(prefix, proptools.String(s.Name))),
+	}
+}
+
 type FilesystemProperties struct {
 	// When set to true, sign the image with avbtool. Default is false.
 	Use_avb *bool
@@ -83,16 +140,25 @@
 	Avb_algorithm *string
 
 	// Hash algorithm used for avbtool (for descriptors). This is passed as hash_algorithm to
-	// avbtool. Default used by avbtool is sha1.
+	// avbtool. Default is sha256.
 	Avb_hash_algorithm *string
 
+	// The security patch passed to as the com.android.build.<type>.security_patch avb property.
+	Security_patch *string
+
+	// Whether or not to use forward-error-correction codes when signing with AVB. Defaults to true.
+	Use_fec *bool
+
 	// The index used to prevent rollback of the image. Only used if use_avb is true.
 	Rollback_index *int64
 
+	// Rollback index location of this image. Must be 1, 2, 3, etc.
+	Rollback_index_location *int64
+
 	// Name of the partition stored in vbmeta desc. Defaults to the name of this module.
 	Partition_name *string
 
-	// Type of the filesystem. Currently, ext4, cpio, and compressed_cpio are supported. Default
+	// Type of the filesystem. Currently, ext4, erofs, cpio, and compressed_cpio are supported. Default
 	// is ext4.
 	Type *string
 
@@ -100,9 +166,13 @@
 	// checks, and will be used in the future for API surface checks.
 	Partition_type *string
 
-	// file_contexts file to make image. Currently, only ext4 is supported.
+	// file_contexts file to make image. Currently, only ext4 is supported. These file contexts
+	// will be compiled with sefcontext_compile
 	File_contexts *string `android:"path"`
 
+	// The selinux file contexts, after having already run them through sefcontext_compile
+	Precompiled_file_contexts *string `android:"path"`
+
 	// Base directory relative to root, to which deps are installed, e.g. "system". Default is "."
 	// (root).
 	Base_dir *string
@@ -110,8 +180,13 @@
 	// Directories to be created under root. e.g. /dev, /proc, etc.
 	Dirs proptools.Configurable[[]string]
 
+	// List of filesystem modules to include in creating the partition. The root directory of
+	// the provided filesystem modules are included in creating the partition.
+	// This is only supported for cpio and compressed cpio filesystem types.
+	Include_files_of []string
+
 	// Symbolic links to be created under root with "ln -sf <target> <name>".
-	Symlinks []symlinkDefinition
+	Symlinks []SymlinkDefinition
 
 	// Seconds since unix epoch to override timestamps of file entries
 	Fake_timestamp *string
@@ -123,12 +198,6 @@
 	// Mount point for this image. Default is "/"
 	Mount_point *string
 
-	// If set to the name of a partition ("system", "vendor", etc), this filesystem module
-	// will also include the contents of the make-built staging directories. If any soong
-	// modules would be installed to the same location as a make module, they will overwrite
-	// the make version.
-	Include_make_built_files string
-
 	// When set, builds etc/event-log-tags file by merging logtags from all dependencies.
 	// Default is false
 	Build_logtags *bool
@@ -136,6 +205,11 @@
 	// Install aconfig_flags.pb file for the modules installed in this partition.
 	Gen_aconfig_flags_pb *bool
 
+	// List of names of other filesystem partitions to import their aconfig flags from.
+	// This is used for the system partition to import system_ext's aconfig flags, as currently
+	// those are considered one "container": aosp/3261300
+	Import_aconfig_flags_from []string
+
 	Fsverity fsverityProperties
 
 	// If this property is set to true, the filesystem will call ctx.UncheckedModule(), causing
@@ -143,6 +217,71 @@
 	// build modules, where we want to emit some not-yet-working filesystems and we don't want them
 	// to be built.
 	Unchecked_module *bool `blueprint:"mutated"`
+
+	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
+
+	// Path to the dev nodes description file. This is only needed for building the ramdisk
+	// partition and should not be explicitly specified.
+	Dev_nodes_description_file *string `android:"path" blueprint:"mutated"`
+
+	// Additional dependencies used for building android products
+	Android_filesystem_deps AndroidFilesystemDeps
+
+	// Name of the output. Default is $(module_name).img
+	Stem *string
+
+	// The size of the partition on the device. It will be a build error if this built partition
+	// image exceeds this size.
+	Partition_size *int64
+
+	// Whether to format f2fs and ext4 in a way that supports casefolding
+	Support_casefolding *bool
+
+	// Whether to format f2fs and ext4 in a way that supports project quotas
+	Support_project_quota *bool
+
+	// Whether to enable per-file compression in f2fs
+	Enable_compression *bool
+}
+
+type AndroidFilesystemDeps struct {
+	System     *string
+	System_ext *string
+}
+
+// Additional properties required to generate erofs FS partitions.
+type ErofsProperties struct {
+	// Compressor and Compression level passed to mkfs.erofs. e.g. (lz4hc,9)
+	// Please see external/erofs-utils/README for complete documentation.
+	Compressor *string
+
+	// Used as --compress-hints for mkfs.erofs
+	Compress_hints *string `android:"path"`
+
+	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
@@ -152,7 +291,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
 }
@@ -161,75 +300,334 @@
 	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)
+
+	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
+		filesystemModule.setDevNodesDescriptionProp()
+	})
 }
 
-var dependencyTag = struct {
+type depTag struct {
 	blueprint.BaseDependencyTag
 	android.PackagingItemAlwaysDepTag
-}{}
+}
+
+var dependencyTag = depTag{}
+
+type depTagWithVisibilityEnforcementBypass struct {
+	depTag
+}
+
+type interPartitionDepTag struct {
+	blueprint.BaseDependencyTag
+}
+
+var interPartitionDependencyTag = interPartitionDepTag{}
+
+var interPartitionInstallDependencyTag = interPartitionDepTag{}
+
+var _ android.ExcludeFromVisibilityEnforcementTag = (*depTagWithVisibilityEnforcementBypass)(nil)
+
+func (t depTagWithVisibilityEnforcementBypass) ExcludeFromVisibilityEnforcement() {}
+
+var dependencyTagWithVisibilityEnforcementBypass = depTagWithVisibilityEnforcementBypass{}
+
+// ramdiskDevNodesDescription is the name of the filegroup module that provides the file that
+// contains the description of dev nodes added to the CPIO archive for the ramdisk partition.
+const ramdiskDevNodesDescription = "ramdisk_node_list"
+
+func (f *filesystem) setDevNodesDescriptionProp() {
+	if proptools.String(f.properties.Partition_name) == "ramdisk" {
+		f.properties.Dev_nodes_description_file = proptools.StringPtr(":" + ramdiskDevNodesDescription)
+	}
+}
 
 func (f *filesystem) DepsMutator(ctx android.BottomUpMutatorContext) {
-	f.AddDeps(ctx, dependencyTag)
+	if proptools.Bool(f.properties.Is_auto_generated) {
+		f.AddDeps(ctx, dependencyTagWithVisibilityEnforcementBypass)
+	} else {
+		f.AddDeps(ctx, dependencyTag)
+	}
+	if f.properties.Android_filesystem_deps.System != nil {
+		ctx.AddDependency(ctx.Module(), interPartitionDependencyTag, proptools.String(f.properties.Android_filesystem_deps.System))
+	}
+	if f.properties.Android_filesystem_deps.System_ext != nil {
+		ctx.AddDependency(ctx.Module(), interPartitionDependencyTag, proptools.String(f.properties.Android_filesystem_deps.System_ext))
+	}
+	for _, partition := range f.properties.Import_aconfig_flags_from {
+		ctx.AddDependency(ctx.Module(), importAconfigDependencyTag, partition)
+	}
+	for _, partition := range f.properties.Include_files_of {
+		ctx.AddDependency(ctx.Module(), interPartitionInstallDependencyTag, partition)
+	}
 }
 
 type fsType int
 
 const (
 	ext4Type fsType = iota
+	erofsType
+	f2fsType
 	compressedCpioType
 	cpioType // uncompressed
 	unknown
 )
 
+func (fs fsType) IsUnknown() bool {
+	return fs == unknown
+}
+
+type InstalledFilesStruct struct {
+	Txt  android.Path
+	Json android.Path
+}
+
 type FilesystemInfo struct {
+	// The built filesystem image
+	Output android.Path
+	// An additional hermetic filesystem image.
+	// e.g. this will contain inodes with pinned timestamps.
+	// This will be copied to target_files.zip
+	OutputHermetic android.Path
 	// A text file containing the list of paths installed on the partition.
 	FileListFile android.Path
+	// The root staging directory used to build the output filesystem. If consuming this, make sure
+	// to add a dependency on the Output file, as you cannot add dependencies on directories
+	// in ninja.
+	RootDir android.Path
+	// The rebased staging directory used to build the output filesystem. If consuming this, make
+	// sure to add a dependency on the Output file, as you cannot add dependencies on directories
+	// in ninja. In many cases this is the same as RootDir, only in the system partition is it
+	// different. There, it points to the "system" sub-directory of RootDir.
+	RebasedDir android.Path
+	// A text file with block data of the .img file
+	// This is an implicit output of `build_image`
+	MapFile android.Path
+	// Name of the module that produced this FilesystemInfo origionally. (though it may be
+	// re-exported by super images or boot images)
+	ModuleName string
+	// The property file generated by this module and passed to build_image.
+	// It's exported here so that system_other can reuse system's property file.
+	BuildImagePropFile android.Path
+	// Paths to all the tools referenced inside of the build image property file.
+	BuildImagePropFileDeps android.Paths
+	// Packaging specs to be installed on the system_other image, for the initial boot's dexpreopt.
+	SpecsForSystemOther map[string]android.PackagingSpec
+
+	FullInstallPaths []FullInstallPathInfo
+
+	// Installed files list
+	InstalledFiles InstalledFilesStruct
+
+	// Path to compress hints file for erofs filesystems
+	// This will be nil for other fileystems like ext4
+	ErofsCompressHints android.Path
+
+	SelinuxFc android.Path
+
+	FilesystemConfig android.Path
+}
+
+// FullInstallPathInfo contains information about the "full install" paths of all the files
+// inside this partition. The full install paths are the files installed in
+// out/target/product/<device>/<partition>. This is essentially legacy behavior, maintained for
+// tools like adb sync and adevice, but we should update them to query the build system for the
+// installed files no matter where they are.
+type FullInstallPathInfo struct {
+	// RequiresFullInstall tells us if the origional module did the install to FullInstallPath
+	// already. If it's false, the android_device module needs to emit the install rule.
+	RequiresFullInstall bool
+	// The "full install" paths for the files in this filesystem. This is the paths in the
+	// out/target/product/<device>/<partition> folder. They're not used by this filesystem,
+	// but can be depended on by the top-level android_device module to cause the staging
+	// directories to be built.
+	FullInstallPath android.InstallPath
+
+	// The file that's copied to FullInstallPath. May be nil if SymlinkTarget is set or IsDir is
+	// true.
+	SourcePath android.Path
+
+	// The target of the symlink, if this file is a symlink.
+	SymlinkTarget string
+
+	// If this file is a directory. Only used for empty directories, which are mostly mount points.
+	IsDir bool
 }
 
 var FilesystemProvider = blueprint.NewProvider[FilesystemInfo]()
 
-func (f *filesystem) fsType(ctx android.ModuleContext) fsType {
-	typeStr := proptools.StringDefault(f.properties.Type, "ext4")
+type FilesystemDefaultsInfo 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.
+	PartitionType string
+}
+
+var FilesystemDefaultsInfoProvider = blueprint.NewProvider[FilesystemDefaultsInfo]()
+
+func GetFsTypeFromString(ctx android.EarlyModuleContext, typeStr string) fsType {
 	switch typeStr {
 	case "ext4":
 		return ext4Type
+	case "erofs":
+		return erofsType
+	case "f2fs":
+		return f2fsType
 	case "compressed_cpio":
 		return compressedCpioType
 	case "cpio":
 		return cpioType
 	default:
-		ctx.PropertyErrorf("type", "%q not supported", typeStr)
 		return unknown
 	}
 }
 
+func (f *filesystem) fsType(ctx android.ModuleContext) fsType {
+	typeStr := proptools.StringDefault(f.properties.Type, "ext4")
+	fsType := GetFsTypeFromString(ctx, typeStr)
+	if fsType == unknown {
+		ctx.PropertyErrorf("type", "%q not supported", typeStr)
+	}
+	return fsType
+}
+
 func (f *filesystem) installFileName() string {
-	return f.BaseModuleName() + ".img"
+	return proptools.StringDefault(f.properties.Stem, f.BaseModuleName()+".img")
 }
 
 func (f *filesystem) partitionName() string {
 	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.
-	return !ps.SkipInstall()
+	if ps.SkipInstall() {
+		return false
+	}
+	// "apex" is a fake partition used to install files in out/target/product/<device>/apex/.
+	// Don't include these files in the partition. We should also look into removing the following
+	// TODO to check the PackagingSpec's partition against this filesystem's partition for all
+	// modules, not just autogenerated ones, which will fix this as well.
+	if ps.Partition() == "apex" {
+		return false
+	}
+	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()))
+	}
+}
+
+func buildInstalledFiles(ctx android.ModuleContext, partition string, rootDir android.Path, image android.Path) (txt android.ModuleOutPath, json android.ModuleOutPath) {
+	fileName := "installed-files"
+	if len(partition) > 0 {
+		fileName += fmt.Sprintf("-%s", partition)
+	}
+	txt = android.PathForModuleOut(ctx, fmt.Sprintf("%s.txt", fileName))
+	json = android.PathForModuleOut(ctx, fmt.Sprintf("%s.json", fileName))
+
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        installedFilesJsonRule,
+		Implicit:    image,
+		Output:      json,
+		Description: "Installed file list json",
+		Args: map[string]string{
+			"rootDir": rootDir.String(),
+		},
+	})
+
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        installedFilesTxtRule,
+		Input:       json,
+		Output:      txt,
+		Description: "Installed file list txt",
+	})
+
+	return txt, json
 }
 
 var pctx = android.NewPackageContext("android/soong/filesystem")
 
 func (f *filesystem) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	validatePartitionType(ctx, f)
+	if f.filesystemBuilder.ShouldUseVintfFragmentModuleOnly() {
+		f.validateVintfFragments(ctx)
+	}
+
+	if len(f.properties.Include_files_of) > 0 && !android.InList(f.fsType(ctx), []fsType{compressedCpioType, cpioType}) {
+		ctx.PropertyErrorf("include_files_of", "include_files_of is only supported for cpio and compressed cpio filesystem types.")
+	}
+
+	rootDir := android.PathForModuleOut(ctx, f.rootDirString()).OutputPath
+	rebasedDir := rootDir
+	if f.properties.Base_dir != nil {
+		rebasedDir = rootDir.Join(ctx, *f.properties.Base_dir)
+	}
+	builder := android.NewRuleBuilder(pctx, ctx)
+
+	// Wipe the root dir to get rid of leftover files from prior builds
+	builder.Command().Textf("rm -rf %s && mkdir -p %s", rootDir, rootDir)
+	specs := f.gatherFilteredPackagingSpecs(ctx)
+
+	var fullInstallPaths []FullInstallPathInfo
+	for _, spec := range specs {
+		fullInstallPaths = append(fullInstallPaths, FullInstallPathInfo{
+			FullInstallPath:     spec.FullInstallPath(),
+			RequiresFullInstall: spec.RequiresFullInstall(),
+			SourcePath:          spec.SrcPath(),
+			SymlinkTarget:       spec.ToGob().SymlinkTarget,
+		})
+	}
+
+	f.entries = f.copyPackagingSpecs(ctx, builder, specs, rootDir, rebasedDir)
+	f.buildNonDepsFiles(ctx, builder, rootDir, rebasedDir, &fullInstallPaths)
+	f.buildFsverityMetadataFiles(ctx, builder, specs, rootDir, rebasedDir, &fullInstallPaths)
+	f.buildEventLogtagsFile(ctx, builder, rebasedDir, &fullInstallPaths)
+	f.buildAconfigFlagsFiles(ctx, builder, specs, rebasedDir, &fullInstallPaths)
+	f.filesystemBuilder.BuildLinkerConfigFile(ctx, builder, rebasedDir, &fullInstallPaths)
+	// Assemeble the staging dir and output a timestamp
+	builder.Command().Text("touch").Output(f.fileystemStagingDirTimestamp(ctx))
+	builder.Build("assemble_filesystem_staging_dir", fmt.Sprintf("Assemble filesystem staging dir %s", f.BaseModuleName()))
+
+	// Create a new rule builder for build_image
+	builder = android.NewRuleBuilder(pctx, ctx)
+	var mapFile android.Path
+	var outputHermetic android.WritablePath
+	var buildImagePropFile android.Path
+	var buildImagePropFileDeps android.Paths
 	switch f.fsType(ctx) {
-	case ext4Type:
-		f.output = f.buildImageUsingBuildImage(ctx)
+	case ext4Type, erofsType, f2fsType:
+		buildImagePropFile, buildImagePropFileDeps = f.buildPropFile(ctx)
+		output := android.PathForModuleOut(ctx, f.installFileName())
+		f.buildImageUsingBuildImage(ctx, builder, buildImageParams{rootDir, buildImagePropFile, buildImagePropFileDeps, output})
+		f.output = output
+		// Create the hermetic img file using a separate rule builder so that it can be built independently
+		hermeticBuilder := android.NewRuleBuilder(pctx, ctx)
+		outputHermetic = android.PathForModuleOut(ctx, "for_target_files", f.installFileName())
+		propFileHermetic := f.propFileForHermeticImg(ctx, hermeticBuilder, buildImagePropFile)
+		f.buildImageUsingBuildImage(ctx, hermeticBuilder, buildImageParams{rootDir, propFileHermetic, buildImagePropFileDeps, outputHermetic})
+		mapFile = f.getMapFile(ctx)
 	case compressedCpioType:
-		f.output = f.buildCpioImage(ctx, true)
+		f.output = f.buildCpioImage(ctx, builder, rootDir, true)
 	case cpioType:
-		f.output = f.buildCpioImage(ctx, false)
+		f.output = f.buildCpioImage(ctx, builder, rootDir, false)
 	default:
 		return
 	}
@@ -238,20 +636,153 @@
 	ctx.InstallFile(f.installDir, f.installFileName(), f.output)
 	ctx.SetOutputFiles([]android.Path{f.output}, "")
 
-	f.fileListFile = android.PathForModuleOut(ctx, "fileList").OutputPath
-	android.WriteFileRule(ctx, f.fileListFile, f.installedFilesList())
+	if f.partitionName() == "recovery" {
+		rootDir = rootDir.Join(ctx, "root")
+	}
 
-	android.SetProvider(ctx, FilesystemProvider, FilesystemInfo{
-		FileListFile: f.fileListFile,
-	})
+	fileListFile := android.PathForModuleOut(ctx, "fileList")
+	android.WriteFileRule(ctx, fileListFile, f.installedFilesList())
+
+	partitionName := f.partitionName()
+	if partitionName == "system" {
+		partitionName = ""
+	}
+	installedFileTxt, installedFileJson := buildInstalledFiles(ctx, partitionName, rootDir, f.output)
+
+	var erofsCompressHints android.Path
+	if f.properties.Erofs.Compress_hints != nil {
+		erofsCompressHints = android.PathForModuleSrc(ctx, *f.properties.Erofs.Compress_hints)
+	}
+
+	fsInfo := FilesystemInfo{
+		Output:                 f.output,
+		OutputHermetic:         outputHermetic,
+		FileListFile:           fileListFile,
+		RootDir:                rootDir,
+		RebasedDir:             rebasedDir,
+		MapFile:                mapFile,
+		ModuleName:             ctx.ModuleName(),
+		BuildImagePropFile:     buildImagePropFile,
+		BuildImagePropFileDeps: buildImagePropFileDeps,
+		SpecsForSystemOther:    f.systemOtherFiles(ctx),
+		FullInstallPaths:       fullInstallPaths,
+		InstalledFiles: InstalledFilesStruct{
+			Txt:  installedFileTxt,
+			Json: installedFileJson,
+		},
+		ErofsCompressHints: erofsCompressHints,
+		SelinuxFc:          f.selinuxFc,
+		FilesystemConfig:   f.generateFilesystemConfig(ctx, rootDir, rebasedDir),
+	}
+
+	android.SetProvider(ctx, FilesystemProvider, fsInfo)
+
+	f.fileListFile = fileListFile
 
 	if proptools.Bool(f.properties.Unchecked_module) {
 		ctx.UncheckedModule()
 	}
+
+	f.setVbmetaPartitionProvider(ctx)
 }
 
-func (f *filesystem) appendToEntry(ctx android.ModuleContext, installedFile android.OutputPath) {
-	partitionBaseDir := android.PathForModuleOut(ctx, "root", f.partitionName()).String() + "/"
+func (f *filesystem) fileystemStagingDirTimestamp(ctx android.ModuleContext) android.WritablePath {
+	return android.PathForModuleOut(ctx, "staging_dir.timestamp")
+}
+
+func (f *filesystem) generateFilesystemConfig(ctx android.ModuleContext, rootDir android.Path, rebasedDir android.Path) android.Path {
+	rootDirString := rootDir.String()
+	prefix := f.partitionName() + "/"
+	if f.partitionName() == "system" {
+		rootDirString = rebasedDir.String()
+	}
+	if f.partitionName() == "ramdisk" || f.partitionName() == "recovery" {
+		// Hardcoded to match make behavior.
+		// https://cs.android.com/android/_/android/platform/build/+/2a0ef42a432d4da00201e8eb7697dcaa68fd2389:core/Makefile;l=6957-6962;drc=9ea8ad9232cef4d0a24d70133b1b9d2ce2defe5f;bpv=1;bpt=0
+		prefix = ""
+	}
+	out := android.PathForModuleOut(ctx, "filesystem_config.txt")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   fsConfigRule,
+		Input:  f.fileystemStagingDirTimestamp(ctx), // assemble the staging directory
+		Output: out,
+		Args: map[string]string{
+			"rootDir": rootDirString,
+			"prefix":  prefix,
+		},
+	})
+	return out
+}
+
+func (f *filesystem) setVbmetaPartitionProvider(ctx android.ModuleContext) {
+	var extractedPublicKey android.ModuleOutPath
+	if f.properties.Avb_private_key != nil {
+		key := android.PathForModuleSrc(ctx, proptools.String(f.properties.Avb_private_key))
+		extractedPublicKey = android.PathForModuleOut(ctx, f.partitionName()+".avbpubkey")
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   extractPublicKeyRule,
+			Input:  key,
+			Output: extractedPublicKey,
+		})
+	}
+
+	var ril int
+	if f.properties.Rollback_index_location != nil {
+		ril = proptools.Int(f.properties.Rollback_index_location)
+	}
+
+	android.SetProvider(ctx, vbmetaPartitionProvider, vbmetaPartitionInfo{
+		Name:                  f.partitionName(),
+		RollbackIndexLocation: ril,
+		PublicKey:             extractedPublicKey,
+		Output:                f.output,
+	})
+}
+
+func (f *filesystem) getMapFile(ctx android.ModuleContext) android.WritablePath {
+	// create the filepath by replacing the extension of the corresponding img file
+	return android.PathForModuleOut(ctx, f.installFileName()).ReplaceExtension(ctx, "map")
+}
+
+func (f *filesystem) validateVintfFragments(ctx android.ModuleContext) {
+	visitedModule := map[string]bool{}
+	packagingSpecs := f.gatherFilteredPackagingSpecs(ctx)
+
+	moduleInFileSystem := func(mod android.Module) bool {
+		for _, ps := range android.OtherModuleProviderOrDefault(
+			ctx, mod, android.InstallFilesProvider).PackagingSpecs {
+			if _, ok := packagingSpecs[ps.RelPathInPackage()]; ok {
+				return true
+			}
+		}
+		return false
+	}
+
+	ctx.WalkDeps(func(child, parent android.Module) bool {
+		if visitedModule[child.Name()] {
+			return false
+		}
+		if !moduleInFileSystem(child) {
+			visitedModule[child.Name()] = true
+			return true
+		}
+		if vintfFragments := child.VintfFragments(ctx); vintfFragments != nil {
+			ctx.PropertyErrorf(
+				"vintf_fragments",
+				"Module %s is referenced by soong-defined filesystem %s with property vintf_fragments(%s) in use."+
+					" Use vintf_fragment_modules property instead.",
+				child.Name(),
+				f.BaseModuleName(),
+				strings.Join(vintfFragments, ", "),
+			)
+		}
+		visitedModule[child.Name()] = true
+		return true
+	})
+}
+
+func (f *filesystem) appendToEntry(ctx android.ModuleContext, installedFile android.Path) {
+	partitionBaseDir := android.PathForModuleOut(ctx, f.rootDirString(), proptools.String(f.properties.Base_dir)).String() + "/"
 
 	relPath, inTargetPartition := strings.CutPrefix(installedFile.String(), partitionBaseDir)
 	if inTargetPartition {
@@ -271,12 +802,12 @@
 		ctx.PropertyErrorf("partition_type", "partition_type must be one of %s, found: %s", validPartitions, p.PartitionType())
 	}
 
-	ctx.VisitDirectDepsWithTag(android.DefaultsDepTag, func(m android.Module) {
-		if fdm, ok := m.(*filesystemDefaults); ok {
-			if p.PartitionType() != fdm.PartitionType() {
+	ctx.VisitDirectDepsProxyWithTag(android.DefaultsDepTag, func(m android.ModuleProxy) {
+		if fdm, ok := android.OtherModuleProvider(ctx, m, FilesystemDefaultsInfoProvider); ok {
+			if p.PartitionType() != fdm.PartitionType {
 				ctx.PropertyErrorf("partition_type",
 					"%s doesn't match with the partition type %s of the filesystem default module %s",
-					p.PartitionType(), fdm.PartitionType(), m.Name())
+					p.PartitionType(), fdm.PartitionType, m.Name())
 			}
 		}
 	})
@@ -284,11 +815,36 @@
 
 // Copy extra files/dirs that are not from the `deps` property to `rootDir`, checking for conflicts with files
 // already in `rootDir`.
-func (f *filesystem) buildNonDepsFiles(ctx android.ModuleContext, builder *android.RuleBuilder, rootDir android.OutputPath) {
+func (f *filesystem) buildNonDepsFiles(
+	ctx android.ModuleContext,
+	builder *android.RuleBuilder,
+	rootDir android.OutputPath,
+	rebasedDir android.OutputPath,
+	fullInstallPaths *[]FullInstallPathInfo,
+) {
+	rebasedPrefix, err := filepath.Rel(rootDir.String(), rebasedDir.String())
+	if err != nil || strings.HasPrefix(rebasedPrefix, "../") {
+		panic("rebasedDir could not be made relative to rootDir")
+	}
+	if !strings.HasSuffix(rebasedPrefix, "/") {
+		rebasedPrefix += "/"
+	}
+	if rebasedPrefix == "./" {
+		rebasedPrefix = ""
+	}
+
 	// create dirs and symlinks
 	for _, dir := range f.properties.Dirs.GetOrDefault(ctx, nil) {
 		// OutputPath.Join verifies dir
 		builder.Command().Text("mkdir -p").Text(rootDir.Join(ctx, dir).String())
+		// Only add the fullInstallPath logic for files in the rebased dir. The root dir
+		// is harder to install to.
+		if strings.HasPrefix(dir, rebasedPrefix) {
+			*fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{
+				FullInstallPath: android.PathForModuleInPartitionInstall(ctx, f.PartitionType(), strings.TrimPrefix(dir, rebasedPrefix)),
+				IsDir:           true,
+			})
+		}
 	}
 
 	for _, symlink := range f.properties.Symlinks {
@@ -311,25 +867,20 @@
 		builder.Command().Text("mkdir -p").Text(filepath.Dir(dst.String()))
 		builder.Command().Text("ln -sf").Text(proptools.ShellEscape(target)).Text(dst.String())
 		f.appendToEntry(ctx, dst)
+		// Only add the fullInstallPath logic for files in the rebased dir. The root dir
+		// is harder to install to.
+		if strings.HasPrefix(name, rebasedPrefix) {
+			*fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{
+				FullInstallPath: android.PathForModuleInPartitionInstall(ctx, f.PartitionType(), strings.TrimPrefix(name, rebasedPrefix)),
+				SymlinkTarget:   target,
+			})
+		}
 	}
 
-	// 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())
-		}
+	// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=2835;drc=b186569ef00ff2f2a1fab28aedc75ebc32bcd67b
+	if f.partitionName() == "recovery" {
+		builder.Command().Text("mkdir -p").Text(rootDir.Join(ctx, "root/linkerconfig").String())
+		builder.Command().Text("touch").Text(rootDir.Join(ctx, "root/linkerconfig/ld.config.txt").String())
 	}
 }
 
@@ -349,82 +900,85 @@
 	dirsToSpecs[rootDir] = rootDirSpecs
 	dirsToSpecs[rebasedDir] = rebasedDirSpecs
 
-	return f.CopySpecsToDirs(ctx, builder, dirsToSpecs)
+	// Preserve timestamps for adb sync, so that this staging dir file matches the timestamp in the
+	// out/target/product staging directory.
+	return f.CopySpecsToDirs(ctx, builder, dirsToSpecs, true)
 }
 
-func (f *filesystem) copyFilesToProductOut(ctx android.ModuleContext, builder *android.RuleBuilder, rebasedDir android.OutputPath) {
-	if f.Name() != ctx.Config().SoongDefinedSystemImage() {
-		return
-	}
-	installPath := android.PathForModuleInPartitionInstall(ctx, f.partitionName())
-	builder.Command().Textf("cp -prf %s/* %s", rebasedDir, installPath)
+func (f *filesystem) rootDirString() string {
+	return f.partitionName()
 }
 
-func (f *filesystem) buildImageUsingBuildImage(ctx android.ModuleContext) android.OutputPath {
-	rootDir := android.PathForModuleOut(ctx, "root").OutputPath
-	rebasedDir := rootDir
-	if f.properties.Base_dir != nil {
-		rebasedDir = rootDir.Join(ctx, *f.properties.Base_dir)
-	}
-	builder := android.NewRuleBuilder(pctx, ctx)
-	// Wipe the root dir to get rid of leftover files from prior builds
-	builder.Command().Textf("rm -rf %s && mkdir -p %s", rootDir, rootDir)
-	specs := f.gatherFilteredPackagingSpecs(ctx)
-	f.entries = f.copyPackagingSpecs(ctx, builder, specs, rootDir, rebasedDir)
+type buildImageParams struct {
+	// inputs
+	rootDir  android.OutputPath
+	propFile android.Path
+	toolDeps android.Paths
+	// outputs
+	output android.WritablePath
+}
 
-	f.buildNonDepsFiles(ctx, builder, rootDir)
-	f.addMakeBuiltFiles(ctx, builder, rootDir)
-	f.buildFsverityMetadataFiles(ctx, builder, specs, rootDir, rebasedDir)
-	f.buildEventLogtagsFile(ctx, builder, rebasedDir)
-	f.buildAconfigFlagsFiles(ctx, builder, specs, rebasedDir)
-	f.copyFilesToProductOut(ctx, builder, rebasedDir)
-
+func (f *filesystem) buildImageUsingBuildImage(
+	ctx android.ModuleContext,
+	builder *android.RuleBuilder,
+	params buildImageParams) {
 	// run host_init_verifier
 	// Ideally we should have a concept of pluggable linters that verify the generated image.
 	// While such concept is not implement this will do.
 	// TODO(b/263574231): substitute with pluggable linter.
 	builder.Command().
 		BuiltTool("host_init_verifier").
-		FlagWithArg("--out_system=", rootDir.String()+"/system")
+		FlagWithArg("--out_system=", params.rootDir.String()+"/system")
 
-	propFile, toolDeps := f.buildPropFile(ctx)
-	output := android.PathForModuleOut(ctx, f.installFileName()).OutputPath
-	builder.Command().BuiltTool("build_image").
-		Text(rootDir.String()). // input directory
-		Input(propFile).
-		Implicits(toolDeps).
-		Output(output).
-		Text(rootDir.String()) // directory where to find fs_config_files|dirs
+	// Most of the time, if build_image were to call a host tool, it accepts the path to the
+	// host tool in a field in the prop file. However, it doesn't have that option for fec, which
+	// it expects to just be on the PATH. Add fec to the PATH.
+	fec := ctx.Config().HostToolPath(ctx, "fec")
+	pathToolDirs := []string{filepath.Dir(fec.String())}
+
+	builder.Command().
+		Textf("PATH=%s:$PATH", strings.Join(pathToolDirs, ":")).
+		BuiltTool("build_image").
+		Text(params.rootDir.String()). // input directory
+		Input(params.propFile).
+		Implicits(params.toolDeps).
+		Implicit(fec).
+		Implicit(f.fileystemStagingDirTimestamp(ctx)). // assemble the staging directory
+		Output(params.output).
+		Text(params.rootDir.String()) // directory where to find fs_config_files|dirs
+
+	if f.properties.Partition_size != nil {
+		assertMaxImageSize(builder, params.output, *f.properties.Partition_size, false)
+	}
 
 	// rootDir is not deleted. Might be useful for quick inspection.
-	builder.Build("build_filesystem_image", fmt.Sprintf("Creating filesystem %s", f.BaseModuleName()))
-
-	return output
+	builder.Build("build_"+params.output.String(), fmt.Sprintf("Creating filesystem %s", f.BaseModuleName()))
 }
 
-func (f *filesystem) buildFileContexts(ctx android.ModuleContext) android.OutputPath {
+func (f *filesystem) propFileForHermeticImg(ctx android.ModuleContext, builder *android.RuleBuilder, inputPropFile android.Path) android.Path {
+	propFilePinnedTimestamp := android.PathForModuleOut(ctx, "for_target_files", "prop")
+	builder.Command().Textf("cat").Input(inputPropFile).Flag(">").Output(propFilePinnedTimestamp).
+		Textf(" && echo use_fixed_timestamp=true >> %s", propFilePinnedTimestamp).
+		Textf(" && echo block_list=%s >> %s", f.getMapFile(ctx).String(), propFilePinnedTimestamp) // mapfile will be an implicit output
+	builder.Command().Text("touch").Output(f.getMapFile(ctx))
+	return propFilePinnedTimestamp
+}
+
+func (f *filesystem) buildFileContexts(ctx android.ModuleContext) android.Path {
 	builder := android.NewRuleBuilder(pctx, ctx)
 	fcBin := android.PathForModuleOut(ctx, "file_contexts.bin")
 	builder.Command().BuiltTool("sefcontext_compile").
 		FlagWithOutput("-o ", fcBin).
 		Input(android.PathForModuleSrc(ctx, proptools.String(f.properties.File_contexts)))
 	builder.Build("build_filesystem_file_contexts", fmt.Sprintf("Creating filesystem file contexts for %s", f.BaseModuleName()))
-	return fcBin.OutputPath
+	return fcBin
 }
 
-// Calculates avb_salt from entry list (sorted) for deterministic output.
-func (f *filesystem) salt() string {
-	return sha1sum(f.entries)
-}
-
-func (f *filesystem) buildPropFile(ctx android.ModuleContext) (propFile android.OutputPath, toolDeps android.Paths) {
+func (f *filesystem) buildPropFile(ctx android.ModuleContext) (android.Path, android.Paths) {
 	var deps android.Paths
-	var propFileString strings.Builder
+	var lines []string
 	addStr := func(name string, value string) {
-		propFileString.WriteString(name)
-		propFileString.WriteRune('=')
-		propFileString.WriteString(value)
-		propFileString.WriteRune('\n')
+		lines = append(lines, fmt.Sprintf("%s=%s", name, value))
 	}
 	addPath := func(name string, path android.Path) {
 		addStr(name, path.String())
@@ -434,9 +988,13 @@
 	// Type string that build_image.py accepts.
 	fsTypeStr := func(t fsType) string {
 		switch t {
-		// TODO(jiyong): add more types like f2fs, erofs, etc.
+		// TODO(372522486): add more types like f2fs, erofs, etc.
 		case ext4Type:
 			return "ext4"
+		case erofsType:
+			return "erofs"
+		case f2fsType:
+			return "f2fs"
 		}
 		panic(fmt.Errorf("unsupported fs type %v", t))
 	}
@@ -455,13 +1013,17 @@
 		addPath("avb_avbtool", ctx.Config().HostToolPath(ctx, "avbtool"))
 		algorithm := proptools.StringDefault(f.properties.Avb_algorithm, "SHA256_RSA4096")
 		addStr("avb_algorithm", algorithm)
-		key := android.PathForModuleSrc(ctx, proptools.String(f.properties.Avb_private_key))
-		addPath("avb_key_path", key)
-		addStr("partition_name", f.partitionName())
-		avb_add_hashtree_footer_args := "--do_not_generate_fec"
-		if hashAlgorithm := proptools.String(f.properties.Avb_hash_algorithm); hashAlgorithm != "" {
-			avb_add_hashtree_footer_args += " --hash_algorithm " + hashAlgorithm
+		if f.properties.Avb_private_key != nil {
+			key := android.PathForModuleSrc(ctx, *f.properties.Avb_private_key)
+			addPath("avb_key_path", key)
 		}
+		addStr("partition_name", f.partitionName())
+		avb_add_hashtree_footer_args := ""
+		if !proptools.BoolDefault(f.properties.Use_fec, true) {
+			avb_add_hashtree_footer_args += " --do_not_generate_fec"
+		}
+		hashAlgorithm := proptools.StringDefault(f.properties.Avb_hash_algorithm, "sha256")
+		avb_add_hashtree_footer_args += " --hash_algorithm " + hashAlgorithm
 		if f.properties.Rollback_index != nil {
 			rollbackIndex := proptools.Int(f.properties.Rollback_index)
 			if rollbackIndex < 0 {
@@ -469,29 +1031,133 @@
 			}
 			avb_add_hashtree_footer_args += " --rollback_index " + strconv.Itoa(rollbackIndex)
 		}
-		securityPatchKey := "com.android.build." + f.partitionName() + ".security_patch"
-		securityPatchValue := ctx.Config().PlatformSecurityPatch()
-		avb_add_hashtree_footer_args += " --prop " + securityPatchKey + ":" + securityPatchValue
+		avb_add_hashtree_footer_args += fmt.Sprintf(" --prop com.android.build.%s.os_version:%s", f.partitionName(), ctx.Config().PlatformVersionLastStable())
+		// We're not going to add BuildFingerPrintFile as a dep. If it changed, it's likely because
+		// the build number changed, and we don't want to trigger rebuilds solely based on the build
+		// number.
+		avb_add_hashtree_footer_args += fmt.Sprintf(" --prop com.android.build.%s.fingerprint:{CONTENTS_OF:%s}", f.partitionName(), ctx.Config().BuildFingerprintFile(ctx))
+		if f.properties.Security_patch != nil && proptools.String(f.properties.Security_patch) != "" {
+			avb_add_hashtree_footer_args += fmt.Sprintf(" --prop com.android.build.%s.security_patch:%s", f.partitionName(), proptools.String(f.properties.Security_patch))
+		}
 		addStr("avb_add_hashtree_footer_args", avb_add_hashtree_footer_args)
-		addStr("avb_salt", f.salt())
 	}
 
-	if proptools.String(f.properties.File_contexts) != "" {
-		addPath("selinux_fc", f.buildFileContexts(ctx))
+	if f.properties.File_contexts != nil && f.properties.Precompiled_file_contexts != nil {
+		ctx.ModuleErrorf("file_contexts and precompiled_file_contexts cannot both be set")
+	} else if f.properties.File_contexts != nil {
+		f.selinuxFc = f.buildFileContexts(ctx)
+	} else if f.properties.Precompiled_file_contexts != nil {
+		f.selinuxFc = android.PathForModuleSrc(ctx, *f.properties.Precompiled_file_contexts)
+	}
+	if f.selinuxFc != nil {
+		addPath("selinux_fc", f.selinuxFc)
 	}
 	if timestamp := proptools.String(f.properties.Fake_timestamp); timestamp != "" {
 		addStr("timestamp", timestamp)
+	} else if ctx.Config().Getenv("USE_FIXED_TIMESTAMP_IMG_FILES") == "true" {
+		addStr("use_fixed_timestamp", "true")
 	}
+
 	if uuid := proptools.String(f.properties.Uuid); uuid != "" {
 		addStr("uuid", uuid)
 		addStr("hash_seed", uuid)
 	}
-	propFile = android.PathForModuleOut(ctx, "prop").OutputPath
-	android.WriteFileRuleVerbatim(ctx, propFile, propFileString.String())
+
+	// Disable sparse only when partition size is not defined. disable_sparse has the same
+	// effect as <partition name>_disable_sparse.
+	if f.properties.Partition_size == nil {
+		addStr("disable_sparse", "true")
+	}
+
+	fst := f.fsType(ctx)
+	switch fst {
+	case erofsType:
+		// Add erofs properties
+		addStr("erofs_default_compressor", proptools.StringDefault(f.properties.Erofs.Compressor, "lz4hc,9"))
+		if f.properties.Erofs.Compress_hints != nil {
+			src := android.PathForModuleSrc(ctx, *f.properties.Erofs.Compress_hints)
+			addPath("erofs_default_compress_hints", src)
+		}
+		if proptools.BoolDefault(f.properties.Erofs.Sparse, true) {
+			// 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")
+		}
+	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))
+
+	if f.properties.Partition_size != nil {
+		addStr("partition_size", strconv.FormatInt(*f.properties.Partition_size, 10))
+	}
+
+	if proptools.BoolDefault(f.properties.Support_casefolding, false) {
+		addStr("needs_casefold", "1")
+	}
+
+	if proptools.BoolDefault(f.properties.Support_project_quota, false) {
+		addStr("needs_projid", "1")
+	}
+
+	if proptools.BoolDefault(f.properties.Enable_compression, false) {
+		addStr("needs_compress", "1")
+	}
+
+	sort.Strings(lines)
+
+	propFilePreProcessing := android.PathForModuleOut(ctx, "prop_pre_processing")
+	android.WriteFileRule(ctx, propFilePreProcessing, strings.Join(lines, "\n"))
+	propFile := android.PathForModuleOut(ctx, "prop")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   textFileProcessorRule,
+		Input:  propFilePreProcessing,
+		Output: propFile,
+	})
 	return propFile, deps
 }
 
-func (f *filesystem) buildCpioImage(ctx android.ModuleContext, compressed bool) android.OutputPath {
+// 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 includeFilesRootDir(ctx android.ModuleContext) (rootDirs android.Paths, partitions android.Paths) {
+	ctx.VisitDirectDepsWithTag(interPartitionInstallDependencyTag, func(m android.Module) {
+		if fsProvider, ok := android.OtherModuleProvider(ctx, m, FilesystemProvider); ok {
+			rootDirs = append(rootDirs, fsProvider.RootDir)
+			partitions = append(partitions, fsProvider.Output)
+		} else {
+			ctx.PropertyErrorf("include_files_of", "only filesystem modules can be listed in "+
+				"include_files_of but %s is not a filesystem module", m.Name())
+		}
+	})
+	return rootDirs, partitions
+}
+
+func (f *filesystem) buildCpioImage(
+	ctx android.ModuleContext,
+	builder *android.RuleBuilder,
+	rootDir android.OutputPath,
+	compressed bool,
+) android.Path {
 	if proptools.Bool(f.properties.Use_avb) {
 		ctx.PropertyErrorf("use_avb", "signing compresed cpio image using avbtool is not supported."+
 			"Consider adding this to bootimg module and signing the entire boot image.")
@@ -501,31 +1167,22 @@
 		ctx.PropertyErrorf("file_contexts", "file_contexts is not supported for compressed cpio image.")
 	}
 
-	if f.properties.Include_make_built_files != "" {
-		ctx.PropertyErrorf("include_make_built_files", "include_make_built_files is not supported for compressed cpio image.")
-	}
+	rootDirs, partitions := includeFilesRootDir(ctx)
 
-	rootDir := android.PathForModuleOut(ctx, "root").OutputPath
-	rebasedDir := rootDir
-	if f.properties.Base_dir != nil {
-		rebasedDir = rootDir.Join(ctx, *f.properties.Base_dir)
-	}
-	builder := android.NewRuleBuilder(pctx, ctx)
-	// Wipe the root dir to get rid of leftover files from prior builds
-	builder.Command().Textf("rm -rf %s && mkdir -p %s", rootDir, rootDir)
-	specs := f.gatherFilteredPackagingSpecs(ctx)
-	f.entries = f.copyPackagingSpecs(ctx, builder, specs, rootDir, rebasedDir)
-
-	f.buildNonDepsFiles(ctx, builder, rootDir)
-	f.buildFsverityMetadataFiles(ctx, builder, specs, rootDir, rebasedDir)
-	f.buildEventLogtagsFile(ctx, builder, rebasedDir)
-	f.buildAconfigFlagsFiles(ctx, builder, specs, rebasedDir)
-	f.copyFilesToProductOut(ctx, builder, rebasedDir)
-
-	output := android.PathForModuleOut(ctx, f.installFileName()).OutputPath
+	output := android.PathForModuleOut(ctx, f.installFileName())
 	cmd := builder.Command().
 		BuiltTool("mkbootfs").
+		Implicit(f.fileystemStagingDirTimestamp(ctx)).
 		Text(rootDir.String()) // input directory
+
+	for i := range len(rootDirs) {
+		cmd.Text(rootDirs[i].String())
+	}
+	cmd.Implicits(partitions)
+
+	if nodeList := f.properties.Dev_nodes_description_file; nodeList != nil {
+		cmd.FlagWithInput("-n ", android.PathForModuleSrc(ctx, proptools.String(nodeList)))
+	}
 	if compressed {
 		cmd.Text("|").
 			BuiltTool("lz4").
@@ -555,62 +1212,62 @@
 	"vendor_dlkm",
 	"odm_dlkm",
 	"system_dlkm",
+	"ramdisk",
+	"vendor_ramdisk",
+	"recovery",
 }
 
-func (f *filesystem) addMakeBuiltFiles(ctx android.ModuleContext, builder *android.RuleBuilder, rootDir android.Path) {
-	partition := f.properties.Include_make_built_files
-	if partition == "" {
-		return
-	}
-	if !slices.Contains(validPartitions, partition) {
-		ctx.PropertyErrorf("include_make_built_files", "Expected one of %#v, found %q", validPartitions, partition)
-		return
-	}
-	stampFile := fmt.Sprintf("target/product/%s/obj/PACKAGING/%s_intermediates/staging_dir.stamp", ctx.Config().DeviceName(), partition)
-	fileListFile := fmt.Sprintf("target/product/%s/obj/PACKAGING/%s_intermediates/file_list.txt", ctx.Config().DeviceName(), partition)
-	stagingDir := fmt.Sprintf("target/product/%s/%s", ctx.Config().DeviceName(), partition)
-
-	builder.Command().BuiltTool("merge_directories").
-		Implicit(android.PathForArbitraryOutput(ctx, stampFile)).
-		Text("--ignore-duplicates").
-		FlagWithInput("--file-list", android.PathForArbitraryOutput(ctx, fileListFile)).
-		Text(rootDir.String()).
-		Text(android.PathForArbitraryOutput(ctx, stagingDir).String())
-}
-
-func (f *filesystem) buildEventLogtagsFile(ctx android.ModuleContext, builder *android.RuleBuilder, rebasedDir android.OutputPath) {
+func (f *filesystem) buildEventLogtagsFile(
+	ctx android.ModuleContext,
+	builder *android.RuleBuilder,
+	rebasedDir android.OutputPath,
+	fullInstallPaths *[]FullInstallPathInfo,
+) {
 	if !proptools.Bool(f.properties.Build_logtags) {
 		return
 	}
 
-	logtagsFilePaths := make(map[string]bool)
-	ctx.WalkDeps(func(child, parent android.Module) bool {
-		if logtagsInfo, ok := android.OtherModuleProvider(ctx, child, android.LogtagsProviderKey); ok {
-			for _, path := range logtagsInfo.Logtags {
-				logtagsFilePaths[path.String()] = true
-			}
-		}
-		return true
-	})
-
-	if len(logtagsFilePaths) == 0 {
-		return
-	}
-
 	etcPath := rebasedDir.Join(ctx, "etc")
 	eventLogtagsPath := etcPath.Join(ctx, "event-log-tags")
 	builder.Command().Text("mkdir").Flag("-p").Text(etcPath.String())
-	cmd := builder.Command().BuiltTool("merge-event-log-tags").
-		FlagWithArg("-o ", eventLogtagsPath.String()).
-		FlagWithInput("-m ", android.MergedLogtagsPath(ctx))
+	builder.Command().Text("cp").Input(android.MergedLogtagsPath(ctx)).Text(eventLogtagsPath.String())
 
-	for _, path := range android.SortedKeys(logtagsFilePaths) {
-		cmd.Text(path)
-	}
+	*fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{
+		FullInstallPath: android.PathForModuleInPartitionInstall(ctx, f.PartitionType(), "etc", "event-log-tags"),
+		SourcePath:      android.MergedLogtagsPath(ctx),
+	})
 
 	f.appendToEntry(ctx, eventLogtagsPath)
 }
 
+func (f *filesystem) BuildLinkerConfigFile(
+	ctx android.ModuleContext,
+	builder *android.RuleBuilder,
+	rebasedDir android.OutputPath,
+	fullInstallPaths *[]FullInstallPathInfo,
+) {
+	if !proptools.Bool(f.properties.Linker_config.Gen_linker_config) {
+		return
+	}
+
+	provideModules, _ := f.getLibsForLinkerConfig(ctx)
+	intermediateOutput := android.PathForModuleOut(ctx, "linker.config.pb")
+	linkerconfig.BuildLinkerConfig(ctx, android.PathsForModuleSrc(ctx, f.properties.Linker_config.Linker_config_srcs), provideModules, nil, intermediateOutput)
+	output := rebasedDir.Join(ctx, "etc", "linker.config.pb")
+	builder.Command().Text("cp").Input(intermediateOutput).Output(output)
+
+	*fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{
+		FullInstallPath: android.PathForModuleInPartitionInstall(ctx, f.PartitionType(), "etc", "linker.config.pb"),
+		SourcePath:      intermediateOutput,
+	})
+
+	f.appendToEntry(ctx, output)
+}
+
+func (f *filesystem) ShouldUseVintfFragmentModuleOnly() bool {
+	return false
+}
+
 type partition interface {
 	PartitionType() string
 }
@@ -666,8 +1323,21 @@
 // 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)
-	return specs
+	return f.PackagingBase.GatherPackagingSpecsWithFilterAndModifier(ctx, f.filesystemBuilder.FilterPackagingSpec, f.filesystemBuilder.ModifyPackagingSpec)
+}
+
+// Dexpreopt files are installed to system_other. Collect the packaingSpecs for the dexpreopt files
+// from this partition to export to the system_other partition later.
+func (f *filesystem) systemOtherFiles(ctx android.ModuleContext) map[string]android.PackagingSpec {
+	filter := func(spec android.PackagingSpec) bool {
+		// For some reason system_other packaging specs don't set the partition field.
+		return strings.HasPrefix(spec.RelPathInPackage(), "system_other/")
+	}
+	modifier := func(spec *android.PackagingSpec) {
+		spec.SetRelPathInPackage(strings.TrimPrefix(spec.RelPathInPackage(), "system_other/"))
+		spec.SetPartition("system_other")
+	}
+	return f.PackagingBase.GatherPackagingSpecsWithFilterAndModifier(ctx, filter, modifier)
 }
 
 func sha1sum(values []string) string {
@@ -692,13 +1362,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
@@ -718,4 +1382,120 @@
 
 func (f *filesystemDefaults) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	validatePartitionType(ctx, f)
+	android.SetProvider(ctx, FilesystemDefaultsInfoProvider, FilesystemDefaultsInfo{
+		PartitionType: f.PartitionType(),
+	})
+}
+
+// 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.ModuleProxy, []android.ModuleProxy) {
+	// we need "Module"s for packaging items
+	modulesInPackageByModule := make(map[android.ModuleProxy]bool)
+	modulesInPackageByName := make(map[string]bool)
+
+	deps := f.gatherFilteredPackagingSpecs(ctx)
+	ctx.WalkDepsProxy(func(child, parent android.ModuleProxy) bool {
+		if !android.OtherModuleProviderOrDefault(ctx, child, android.CommonModuleInfoKey).Enabled {
+			return false
+		}
+		for _, ps := range android.OtherModuleProviderOrDefault(
+			ctx, child, android.InstallFilesProvider).PackagingSpecs {
+			if _, ok := deps[ps.RelPathInPackage()]; ok && ps.Partition() == f.PartitionType() {
+				modulesInPackageByModule[child] = true
+				modulesInPackageByName[child.Name()] = true
+				return true
+			}
+		}
+		return true
+	})
+
+	provideModules := make([]android.ModuleProxy, 0, len(modulesInPackageByModule))
+	for mod := range modulesInPackageByModule {
+		provideModules = append(provideModules, mod)
+	}
+
+	var requireModules []android.ModuleProxy
+	ctx.WalkDepsProxy(func(child, parent android.ModuleProxy) bool {
+		if !android.OtherModuleProviderOrDefault(ctx, child, android.CommonModuleInfoKey).Enabled {
+			return false
+		}
+		_, 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
+}
+
+// Checks that the given file doesn't exceed the given size, and will also print a warning
+// if it's nearing the maximum size. Equivalent to assert-max-image-size in make:
+// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/definitions.mk;l=3455;drc=993c4de29a02a6accd60ceaaee153307e1a18d10
+func assertMaxImageSize(builder *android.RuleBuilder, image android.Path, maxSize int64, addAvbLater bool) {
+	if addAvbLater {
+		// The value 69632 is derived from MAX_VBMETA_SIZE + MAX_FOOTER_SIZE in avbtool.
+		// Logic copied from make:
+		// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=228;drc=a6a0007ef24e16c0b79f439beac4a118416717e6
+		maxSize -= 69632
+	}
+	cmd := builder.Command()
+	cmd.Textf(`file="%s"; maxsize="%d";`+
+		`total=$(stat -c "%%s" "$file" | tr -d '\n');`+
+		`if [ "$total" -gt "$maxsize" ]; then `+
+		`  echo "error: $file too large ($total > $maxsize)";`+
+		`  false;`+
+		`elif [ "$total" -gt $((maxsize - 32768)) ]; then `+
+		`  echo "WARNING: $file approaching size limit ($total now; limit $maxsize)";`+
+		`fi`,
+		image, maxSize)
+	cmd.Implicit(image)
+}
+
+// addAutogeneratedRroDeps walks the transitive closure of vendor and product partitions.
+// It visits apps installed in system and system_ext partitions, and adds the autogenerated
+// RRO modules to its own deps.
+func addAutogeneratedRroDeps(ctx android.BottomUpMutatorContext) {
+	f, ok := ctx.Module().(*filesystem)
+	if !ok {
+		return
+	}
+	thisPartition := f.PartitionType()
+	if thisPartition != "vendor" && thisPartition != "product" {
+		if f.properties.Android_filesystem_deps.System != nil {
+			ctx.PropertyErrorf("android_filesystem_deps.system", "only vendor or product partitions can use android_filesystem_deps")
+		}
+		if f.properties.Android_filesystem_deps.System_ext != nil {
+			ctx.PropertyErrorf("android_filesystem_deps.system_ext", "only vendor or product partitions can use android_filesystem_deps")
+		}
+		return
+	}
+	ctx.WalkDeps(func(child, parent android.Module) bool {
+		depTag := ctx.OtherModuleDependencyTag(child)
+		if parent.Name() == f.Name() && depTag != interPartitionDependencyTag {
+			return false // This is a module listed in deps of vendor/product filesystem
+		}
+		if vendorOverlay := java.AutogeneratedRroModuleName(ctx, child.Name(), "vendor"); ctx.OtherModuleExists(vendorOverlay) && thisPartition == "vendor" {
+			ctx.AddFarVariationDependencies(nil, dependencyTagWithVisibilityEnforcementBypass, vendorOverlay)
+		}
+		if productOverlay := java.AutogeneratedRroModuleName(ctx, child.Name(), "product"); ctx.OtherModuleExists(productOverlay) && thisPartition == "product" {
+			ctx.AddFarVariationDependencies(nil, dependencyTagWithVisibilityEnforcementBypass, productOverlay)
+		}
+		return true
+	})
+}
+
+func (f *filesystem) MakeVars(ctx android.MakeVarsModuleContext) {
+	if f.Name() == ctx.Config().SoongDefinedSystemImage() {
+		ctx.StrictRaw("SOONG_DEFINED_SYSTEM_IMAGE_PATH", f.output.String())
+	}
 }
diff --git a/filesystem/filesystem_test.go b/filesystem/filesystem_test.go
index 8c0d111..6d0b490 100644
--- a/filesystem/filesystem_test.go
+++ b/filesystem/filesystem_test.go
@@ -118,9 +118,9 @@
 	`)
 
 	// produces "myfilesystem.img"
-	result.ModuleForTests("myfilesystem", "android_common").Output("myfilesystem.img")
+	result.ModuleForTests(t, "myfilesystem", "android_common").Output("myfilesystem.img")
 
-	fs := result.ModuleForTests("myfilesystem", "android_common").Module().(*filesystem)
+	fs := result.ModuleForTests(t, "myfilesystem", "android_common").Module().(*filesystem)
 	expected := []string{
 		"app/myapp/myapp.apk",
 		"bin/foo",
@@ -136,31 +136,19 @@
 	}
 }
 
-func TestIncludeMakeBuiltFiles(t *testing.T) {
-	result := fixture.RunTestWithBp(t, `
-		android_filesystem {
-			name: "myfilesystem",
-			include_make_built_files: "system",
-		}
-	`)
-
-	output := result.ModuleForTests("myfilesystem", "android_common").Output("myfilesystem.img")
-
-	stampFile := "out/target/product/test_device/obj/PACKAGING/system_intermediates/staging_dir.stamp"
-	fileListFile := "out/target/product/test_device/obj/PACKAGING/system_intermediates/file_list.txt"
-	android.AssertStringListContains(t, "deps of filesystem must include the staging dir stamp file", output.Implicits.Strings(), stampFile)
-	android.AssertStringListContains(t, "deps of filesystem must include the staging dir file list", output.Implicits.Strings(), fileListFile)
-}
-
 func TestFileSystemFillsLinkerConfigWithStubLibs(t *testing.T) {
 	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 {
@@ -175,13 +163,15 @@
 		}
 	`)
 
-	module := result.ModuleForTests("myfilesystem", "android_common")
-	output := module.Output("system/etc/linker.config.pb")
+	module := result.ModuleForTests(t, "myfilesystem", "android_common")
+	output := module.Output("out/soong/.intermediates/myfilesystem/android_common/linker.config.pb")
+
+	linkerConfigCommand := output.RuleParams.Command
 
 	android.AssertStringDoesContain(t, "linker.config.pb should have libfoo",
-		output.RuleParams.Command, "libfoo.so")
+		linkerConfigCommand, "libfoo.so")
 	android.AssertStringDoesNotContain(t, "linker.config.pb should not have libbar",
-		output.RuleParams.Command, "libbar.so")
+		linkerConfigCommand, "libbar.so")
 }
 
 func registerComponent(ctx android.RegistrationContext) {
@@ -223,7 +213,10 @@
 					deps: ["foo"],
 				},
 			},
-			linker_config_src: "linker.config.json",
+			linker_config: {
+				gen_linker_config: true,
+				linker_config_srcs: ["linker.config.json"],
+			},
 		}
 		component {
 			name: "foo",
@@ -231,8 +224,8 @@
 		}
 	`)
 
-	module := result.ModuleForTests("myfilesystem", "android_common").Module().(*systemImage)
-	android.AssertDeepEquals(t, "entries should have foo only", []string{"components/foo"}, module.entries)
+	module := result.ModuleForTests(t, "myfilesystem", "android_common").Module().(*systemImage)
+	android.AssertDeepEquals(t, "entries should have foo and not bar", []string{"components/foo", "etc/linker.config.pb"}, module.entries)
 }
 
 func TestAvbGenVbmetaImage(t *testing.T) {
@@ -243,7 +236,7 @@
 			partition_name: "input_partition_name",
 			salt: "2222",
 		}`)
-	cmd := result.ModuleForTests("input_hashdesc", "android_arm64_armv8-a").Rule("avbGenVbmetaImage").RuleParams.Command
+	cmd := result.ModuleForTests(t, "input_hashdesc", "android_arm64_armv8-a").Rule("avbGenVbmetaImage").RuleParams.Command
 	android.AssertStringDoesContain(t, "Can't find correct --partition_name argument",
 		cmd, "--partition_name input_partition_name")
 	android.AssertStringDoesContain(t, "Can't find --do_not_append_vbmeta_image",
@@ -283,7 +276,7 @@
 			include_descriptors_from_images: ["input_hashdesc"],
 		}
 	`)
-	cmd := result.ModuleForTests("myfooter", "android_arm64_armv8-a").Rule("avbAddHashFooter").RuleParams.Command
+	cmd := result.ModuleForTests(t, "myfooter", "android_arm64_armv8-a").Rule("avbAddHashFooter").RuleParams.Command
 	android.AssertStringDoesContain(t, "Can't find correct --partition_name argument",
 		cmd, "--partition_name mypartition")
 	android.AssertStringDoesContain(t, "Can't find correct --key argument",
@@ -318,7 +311,10 @@
 			deps: [
 				"libfoo",
 			],
-			linker_config_src: "linker.config.json",
+			linker_config: {
+				gen_linker_config: true,
+				linker_config_srcs: ["linker.config.json"],
+			},
 		}
 
 		cc_library {
@@ -335,8 +331,8 @@
 		}
 	`)
 
-	filesystem := result.ModuleForTests("myfilesystem", "android_common_cov")
-	inputs := filesystem.Output("myfilesystem.img").Implicits
+	filesystem := result.ModuleForTests(t, "myfilesystem", "android_common_cov")
+	inputs := filesystem.Output("staging_dir.timestamp").Implicits
 	android.AssertStringListContains(t, "filesystem should have libfoo(cov)",
 		inputs.Strings(),
 		"out/soong/.intermediates/libfoo/android_arm64_armv8-a_shared_cov/libfoo.so")
@@ -344,8 +340,8 @@
 		inputs.Strings(),
 		"out/soong/.intermediates/libbar/android_arm64_armv8-a_shared_cov/libbar.so")
 
-	filesystemOutput := filesystem.Output("myfilesystem.img").Output
-	prebuiltInput := result.ModuleForTests("prebuilt", "android_arm64_armv8-a").Rule("Cp").Input
+	filesystemOutput := filesystem.OutputFiles(result.TestContext, t, "")[0]
+	prebuiltInput := result.ModuleForTests(t, "prebuilt", "android_arm64_armv8-a").Rule("Cp").Input
 	if filesystemOutput != prebuiltInput {
 		t.Error("prebuilt should use cov variant of filesystem")
 	}
@@ -407,7 +403,7 @@
 		}
 	`)
 
-	fs := result.ModuleForTests("system", "android_common").Module().(*systemImage)
+	fs := result.ModuleForTests(t, "system", "android_common").Module().(*systemImage)
 	expected := []string{
 		"bin/foo",
 		"lib/libbar.so",
@@ -487,7 +483,7 @@
 		}
 	`)
 
-	fs := result.ModuleForTests("fs", "android_common").Module().(*filesystem)
+	fs := result.ModuleForTests(t, "fs", "android_common").Module().(*filesystem)
 	expected := []string{
 		"bin/foo",
 		"lib64/libbar.so",
@@ -550,7 +546,7 @@
 		},
 	}
 	for _, c := range testcases {
-		fs := result.ModuleForTests(c.fsName, "android_common").Module().(*filesystem)
+		fs := result.ModuleForTests(t, c.fsName, "android_common").Module().(*filesystem)
 		for _, e := range c.expected {
 			android.AssertStringListContains(t, "missing entry", fs.entries, e)
 		}
@@ -559,3 +555,217 @@
 		}
 	}
 }
+
+func TestErofsPartition(t *testing.T) {
+	result := fixture.RunTestWithBp(t, `
+		android_filesystem {
+			name: "erofs_partition",
+			type: "erofs",
+			erofs: {
+				compressor: "lz4hc,9",
+				compress_hints: "compress_hints.txt",
+			},
+			deps: ["binfoo"],
+		}
+
+		cc_binary {
+			name: "binfoo",
+		}
+	`)
+
+	partition := result.ModuleForTests(t, "erofs_partition", "android_common")
+	buildImageConfig := android.ContentFromFileRuleForTests(t, result.TestContext, partition.Output("prop_pre_processing"))
+	android.AssertStringDoesContain(t, "erofs fs type", buildImageConfig, "fs_type=erofs")
+	android.AssertStringDoesContain(t, "erofs fs type compress algorithm", buildImageConfig, "erofs_default_compressor=lz4hc,9")
+	android.AssertStringDoesContain(t, "erofs fs type compress hint", buildImageConfig, "erofs_default_compress_hints=compress_hints.txt")
+	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(t, "f2fs_partition", "android_common")
+	buildImageConfig := android.ContentFromFileRuleForTests(t, result.TestContext, partition.Output("prop_pre_processing"))
+	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) {
+	t.Skip() // TODO (spandandas): Re-enable this
+	result := fixture.RunTestWithBp(t, `
+		android_filesystem {
+			name: "myfilesystem",
+			deps: ["binfoo"],
+			partition_type: "system_ext",
+		}
+
+		cc_binary {
+			name: "binfoo",
+			shared_libs: ["libfoo"],
+			system_ext_specific: true,
+		}
+		cc_library_shared {
+			name: "libfoo", // installed in system/
+		}
+	`)
+
+	partition := result.ModuleForTests(t, "myfilesystem", "android_common")
+	fileList := android.ContentFromFileRuleForTests(t, result.TestContext, partition.Output("fileList"))
+	android.AssertDeepEquals(t, "filesystem with dependencies on different partition", "bin/binfoo\n", fileList)
+}
+
+// If a cc_library is listed in `deps`, and it has a shared and static variant, then the shared variant
+// should be installed.
+func TestUseSharedVariationOfNativeLib(t *testing.T) {
+	result := fixture.RunTestWithBp(t, `
+		android_filesystem {
+			name: "myfilesystem",
+			deps: ["libfoo"],
+		}
+		// cc_library will create a static and shared variant.
+		cc_library {
+			name: "libfoo",
+		}
+	`)
+
+	partition := result.ModuleForTests(t, "myfilesystem", "android_common")
+	fileList := android.ContentFromFileRuleForTests(t, result.TestContext, partition.Output("fileList"))
+	android.AssertDeepEquals(t, "cc_library listed in deps",
+		"lib64/bootstrap/libc.so\nlib64/bootstrap/libdl.so\nlib64/bootstrap/libm.so\nlib64/libc++.so\nlib64/libc.so\nlib64/libdl.so\nlib64/libfoo.so\nlib64/libm.so\n",
+		fileList)
+}
+
+// binfoo1 overrides binbar. transitive deps of binbar should not be installed.
+func TestDoNotInstallTransitiveDepOfOverriddenModule(t *testing.T) {
+	result := fixture.RunTestWithBp(t, `
+android_filesystem {
+    name: "myfilesystem",
+    deps: ["binfoo1", "libfoo2", "binbar"],
+}
+cc_binary {
+    name: "binfoo1",
+    shared_libs: ["libfoo"],
+    overrides: ["binbar"],
+}
+cc_library {
+    name: "libfoo",
+}
+cc_library {
+    name: "libfoo2",
+    overrides: ["libfoo"],
+}
+// binbar gets overridden by binfoo1
+// therefore, libbar should not be installed
+cc_binary {
+    name: "binbar",
+    shared_libs: ["libbar"]
+}
+cc_library {
+    name: "libbar",
+}
+	`)
+
+	partition := result.ModuleForTests(t, "myfilesystem", "android_common")
+	fileList := android.ContentFromFileRuleForTests(t, result.TestContext, partition.Output("fileList"))
+	android.AssertDeepEquals(t, "Shared library dep of overridden binary should not be installed",
+		"bin/binfoo1\nlib64/bootstrap/libc.so\nlib64/bootstrap/libdl.so\nlib64/bootstrap/libm.so\nlib64/libc++.so\nlib64/libc.so\nlib64/libdl.so\nlib64/libfoo2.so\nlib64/libm.so\n",
+		fileList)
+}
+
+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(t, "myfilesystem", "android_common").Output("out/soong/.intermediates/myfilesystem/android_common/linker.config.pb").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(t, "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)
+}
+
+func TestRamdiskPartitionSetsDevNodes(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		fixture,
+		android.FixtureMergeMockFs(android.MockFS{
+			"ramdisk_node_list": nil,
+		}),
+	).RunTestWithBp(t, `
+		android_filesystem {
+			name: "ramdisk_filesystem",
+			partition_name: "ramdisk",
+		}
+		filegroup {
+			name: "ramdisk_node_list",
+			srcs: ["ramdisk_node_list"],
+		}
+	`)
+
+	android.AssertBoolEquals(
+		t,
+		"Generated ramdisk image expected to depend on \"ramdisk_node_list\" module",
+		true,
+		java.CheckModuleHasDependency(t, result.TestContext, "ramdisk_filesystem", "android_common", "ramdisk_node_list"),
+	)
+}
diff --git a/filesystem/fsverity_metadata.go b/filesystem/fsverity_metadata.go
index d7bb654..a3a2086 100644
--- a/filesystem/fsverity_metadata.go
+++ b/filesystem/fsverity_metadata.go
@@ -15,10 +15,13 @@
 package filesystem
 
 import (
+	"fmt"
 	"path/filepath"
 	"strings"
 
 	"android/soong/android"
+
+	"github.com/google/blueprint/proptools"
 )
 
 type fsverityProperties struct {
@@ -26,13 +29,13 @@
 	// will be generated and included to the filesystem image.
 	// etc/security/fsverity/BuildManifest.apk will also be generated which contains information
 	// about generated .fsv_meta files.
-	Inputs []string
+	Inputs proptools.Configurable[[]string]
 
 	// APK libraries to link against, for etc/security/fsverity/BuildManifest.apk
-	Libs []string `android:"path"`
+	Libs proptools.Configurable[[]string] `android:"path"`
 }
 
-func (f *filesystem) writeManifestGeneratorListFile(ctx android.ModuleContext, outputPath android.OutputPath, matchedSpecs []android.PackagingSpec, rebasedDir android.OutputPath) {
+func (f *filesystem) writeManifestGeneratorListFile(ctx android.ModuleContext, outputPath android.WritablePath, matchedSpecs []android.PackagingSpec, rebasedDir android.OutputPath) {
 	var buf strings.Builder
 	for _, spec := range matchedSpecs {
 		buf.WriteString(rebasedDir.Join(ctx, spec.RelPathInPackage()).String())
@@ -41,9 +44,16 @@
 	android.WriteFileRuleVerbatim(ctx, outputPath, buf.String())
 }
 
-func (f *filesystem) buildFsverityMetadataFiles(ctx android.ModuleContext, builder *android.RuleBuilder, specs map[string]android.PackagingSpec, rootDir android.OutputPath, rebasedDir android.OutputPath) {
+func (f *filesystem) buildFsverityMetadataFiles(
+	ctx android.ModuleContext,
+	builder *android.RuleBuilder,
+	specs map[string]android.PackagingSpec,
+	rootDir android.OutputPath,
+	rebasedDir android.OutputPath,
+	fullInstallPaths *[]FullInstallPathInfo,
+) {
 	match := func(path string) bool {
-		for _, pattern := range f.properties.Fsverity.Inputs {
+		for _, pattern := range f.properties.Fsverity.Inputs.GetOrDefault(ctx, nil) {
 			if matched, err := filepath.Match(pattern, path); matched {
 				return true
 			} else if err != nil {
@@ -65,115 +75,110 @@
 		return
 	}
 
-	fsverityBuilderPath := android.PathForModuleOut(ctx, "fsverity_builder.sh")
-	metadataGeneratorPath := ctx.Config().HostToolPath(ctx, "fsverity_metadata_generator")
 	fsverityPath := ctx.Config().HostToolPath(ctx, "fsverity")
 
-	cmd := builder.Command().Tool(fsverityBuilderPath)
-
 	// STEP 1: generate .fsv_meta
 	var sb strings.Builder
 	sb.WriteString("set -e\n")
-	cmd.Implicit(metadataGeneratorPath).Implicit(fsverityPath)
 	for _, spec := range matchedSpecs {
 		// srcPath is copied by CopySpecsToDir()
 		srcPath := rebasedDir.Join(ctx, spec.RelPathInPackage())
 		destPath := rebasedDir.Join(ctx, spec.RelPathInPackage()+".fsv_meta")
-		sb.WriteString(metadataGeneratorPath.String())
-		sb.WriteString(" --fsverity-path ")
-		sb.WriteString(fsverityPath.String())
-		sb.WriteString(" --signature none --hash-alg sha256 --output ")
-		sb.WriteString(destPath.String())
-		sb.WriteRune(' ')
-		sb.WriteString(srcPath.String())
-		sb.WriteRune('\n')
+		builder.Command().
+			BuiltTool("fsverity_metadata_generator").
+			FlagWithInput("--fsverity-path ", fsverityPath).
+			FlagWithArg("--signature ", "none").
+			FlagWithArg("--hash-alg ", "sha256").
+			FlagWithOutput("--output ", destPath).
+			Text(srcPath.String())
 		f.appendToEntry(ctx, destPath)
+		*fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{
+			SourcePath:      destPath,
+			FullInstallPath: android.PathForModuleInPartitionInstall(ctx, f.PartitionType(), spec.RelPathInPackage()+".fsv_meta"),
+		})
+	}
+
+	fsVerityBaseDir := rootDir.String()
+	if f.PartitionType() == "system_ext" {
+		// Use the equivalent of $PRODUCT_OUT as the base dir.
+		// This ensures that the paths in build_manifest.pb contain on-device paths
+		// e.g. system_ext/framework/javalib.jar
+		// and not framework/javalib.jar.
+		//
+		// Although base-dir is outside the rootdir provided for packaging, this action
+		// is hermetic since it uses `manifestGeneratorListPath` to filter the files to be written to build_manifest.pb
+		fsVerityBaseDir = filepath.Dir(rootDir.String())
 	}
 
 	// STEP 2: generate signed BuildManifest.apk
 	// STEP 2-1: generate build_manifest.pb
+	manifestGeneratorListPath := android.PathForModuleOut(ctx, "fsverity_manifest.list")
+	f.writeManifestGeneratorListFile(ctx, manifestGeneratorListPath, matchedSpecs, rebasedDir)
 	assetsPath := android.PathForModuleOut(ctx, "fsverity_manifest/assets")
 	manifestPbPath := assetsPath.Join(ctx, "build_manifest.pb")
-	manifestGeneratorPath := ctx.Config().HostToolPath(ctx, "fsverity_manifest_generator")
-	cmd.Implicit(manifestGeneratorPath)
-	sb.WriteString("rm -rf ")
-	sb.WriteString(assetsPath.String())
-	sb.WriteString(" && mkdir -p ")
-	sb.WriteString(assetsPath.String())
-	sb.WriteRune('\n')
-	sb.WriteString(manifestGeneratorPath.String())
-	sb.WriteString(" --fsverity-path ")
-	sb.WriteString(fsverityPath.String())
-	sb.WriteString(" --base-dir ")
-	sb.WriteString(rootDir.String())
-	sb.WriteString(" --output ")
-	sb.WriteString(manifestPbPath.String())
-	sb.WriteRune(' ')
-	f.appendToEntry(ctx, manifestPbPath)
+	builder.Command().Text("rm -rf " + assetsPath.String())
+	builder.Command().Text("mkdir -p " + assetsPath.String())
+	builder.Command().
+		BuiltTool("fsverity_manifest_generator").
+		FlagWithInput("--fsverity-path ", fsverityPath).
+		FlagWithArg("--base-dir ", fsVerityBaseDir).
+		FlagWithArg("--output ", manifestPbPath.String()).
+		FlagWithInput("@", manifestGeneratorListPath)
 
-	manifestGeneratorListPath := android.PathForModuleOut(ctx, "fsverity_manifest.list")
-	f.writeManifestGeneratorListFile(ctx, manifestGeneratorListPath.OutputPath, matchedSpecs, rebasedDir)
-	sb.WriteRune('@')
-	sb.WriteString(manifestGeneratorListPath.String())
-	sb.WriteRune('\n')
-	cmd.Implicit(manifestGeneratorListPath)
-	f.appendToEntry(ctx, manifestGeneratorListPath.OutputPath)
+	f.appendToEntry(ctx, manifestPbPath)
+	f.appendToEntry(ctx, manifestGeneratorListPath)
 
 	// STEP 2-2: generate BuildManifest.apk (unsigned)
-	aapt2Path := ctx.Config().HostToolPath(ctx, "aapt2")
-	apkPath := rebasedDir.Join(ctx, "etc", "security", "fsverity", "BuildManifest.apk")
-	idsigPath := rebasedDir.Join(ctx, "etc", "security", "fsverity", "BuildManifest.apk.idsig")
-	manifestTemplatePath := android.PathForSource(ctx, "system/security/fsverity/AndroidManifest.xml")
-	libs := android.PathsForModuleSrc(ctx, f.properties.Fsverity.Libs)
-	cmd.Implicit(aapt2Path)
-	cmd.Implicit(manifestTemplatePath)
-	cmd.Implicits(libs)
-	cmd.ImplicitOutput(apkPath)
-
-	sb.WriteString(aapt2Path.String())
-	sb.WriteString(" link -o ")
-	sb.WriteString(apkPath.String())
-	sb.WriteString(" -A ")
-	sb.WriteString(assetsPath.String())
-	for _, lib := range libs {
-		sb.WriteString(" -I ")
-		sb.WriteString(lib.String())
+	apkNameSuffix := ""
+	if f.PartitionType() == "system_ext" {
+		//https://source.corp.google.com/h/googleplex-android/platform/build/+/e392d2b486c2d4187b20a72b1c67cc737ecbcca5:core/Makefile;l=3410;drc=ea8f34bc1d6e63656b4ec32f2391e9d54b3ebb6b;bpv=1;bpt=0
+		apkNameSuffix = "SystemExt"
 	}
+	apkPath := rebasedDir.Join(ctx, "etc", "security", "fsverity", fmt.Sprintf("BuildManifest%s.apk", apkNameSuffix))
+	idsigPath := rebasedDir.Join(ctx, "etc", "security", "fsverity", fmt.Sprintf("BuildManifest%s.apk.idsig", apkNameSuffix))
+	manifestTemplatePath := android.PathForSource(ctx, "system/security/fsverity/AndroidManifest.xml")
+	libs := android.PathsForModuleSrc(ctx, f.properties.Fsverity.Libs.GetOrDefault(ctx, nil))
+
 	minSdkVersion := ctx.Config().PlatformSdkCodename()
 	if minSdkVersion == "REL" {
 		minSdkVersion = ctx.Config().PlatformSdkVersion().String()
 	}
-	sb.WriteString(" --min-sdk-version ")
-	sb.WriteString(minSdkVersion)
-	sb.WriteString(" --version-code ")
-	sb.WriteString(ctx.Config().PlatformSdkVersion().String())
-	sb.WriteString(" --version-name ")
-	sb.WriteString(ctx.Config().AppsDefaultVersionName())
-	sb.WriteString(" --manifest ")
-	sb.WriteString(manifestTemplatePath.String())
-	sb.WriteString(" --rename-manifest-package com.android.security.fsverity_metadata.")
-	sb.WriteString(f.partitionName())
-	sb.WriteRune('\n')
+
+	unsignedApkCommand := builder.Command().
+		Textf("mkdir -p %s && ", filepath.Dir(apkPath.String())).
+		BuiltTool("aapt2").
+		Text("link").
+		FlagWithOutput("-o ", apkPath).
+		FlagWithArg("-A ", assetsPath.String())
+	for _, lib := range libs {
+		unsignedApkCommand.FlagWithInput("-I ", lib)
+	}
+	unsignedApkCommand.
+		FlagWithArg("--min-sdk-version ", minSdkVersion).
+		FlagWithArg("--version-code ", ctx.Config().PlatformSdkVersion().String()).
+		FlagWithArg("--version-name ", ctx.Config().AppsDefaultVersionName()).
+		FlagWithInput("--manifest ", manifestTemplatePath).
+		Text(" --rename-manifest-package com.android.security.fsverity_metadata." + f.partitionName())
+	*fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{
+		SourcePath:      apkPath,
+		FullInstallPath: android.PathForModuleInPartitionInstall(ctx, f.PartitionType(), fmt.Sprintf("etc/security/fsverity/BuildManifest%s.apk", apkNameSuffix)),
+	})
 
 	f.appendToEntry(ctx, apkPath)
 
 	// STEP 2-3: sign BuildManifest.apk
-	apksignerPath := ctx.Config().HostToolPath(ctx, "apksigner")
 	pemPath, keyPath := ctx.Config().DefaultAppCertificate(ctx)
-	cmd.Implicit(apksignerPath)
-	cmd.Implicit(pemPath)
-	cmd.Implicit(keyPath)
-	cmd.ImplicitOutput(idsigPath)
-	sb.WriteString(apksignerPath.String())
-	sb.WriteString(" sign --in ")
-	sb.WriteString(apkPath.String())
-	sb.WriteString(" --cert ")
-	sb.WriteString(pemPath.String())
-	sb.WriteString(" --key ")
-	sb.WriteString(keyPath.String())
-	sb.WriteRune('\n')
+	builder.Command().
+		BuiltTool("apksigner").
+		Text("sign").
+		FlagWithArg("--in ", apkPath.String()).
+		FlagWithInput("--cert ", pemPath).
+		FlagWithInput("--key ", keyPath).
+		ImplicitOutput(idsigPath)
+	*fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{
+		SourcePath:      idsigPath,
+		FullInstallPath: android.PathForModuleInPartitionInstall(ctx, f.PartitionType(), fmt.Sprintf("etc/security/fsverity/BuildManifest%s.apk.idsig", apkNameSuffix)),
+	})
 
 	f.appendToEntry(ctx, idsigPath)
-
-	android.WriteExecutableFileRuleVerbatim(ctx, fsverityBuilderPath, sb.String())
 }
diff --git a/filesystem/logical_partition.go b/filesystem/logical_partition.go
index 988a57b..d0888a9 100644
--- a/filesystem/logical_partition.go
+++ b/filesystem/logical_partition.go
@@ -32,7 +32,7 @@
 
 	properties logicalPartitionProperties
 
-	output     android.OutputPath
+	output     android.Path
 	installDir android.InstallPath
 }
 
@@ -95,11 +95,14 @@
 	builder := android.NewRuleBuilder(pctx, ctx)
 
 	// Sparse the filesystem images and calculate their sizes
-	sparseImages := make(map[string]android.OutputPath)
-	sparseImageSizes := make(map[string]android.OutputPath)
+	sparseImages := make(map[string]android.Path)
+	sparseImageSizes := make(map[string]android.Path)
 
 	sparsePartitions := func(partitions []partitionProperties) {
 		for _, part := range partitions {
+			if part.Filesystem == nil {
+				continue
+			}
 			sparseImg, sizeTxt := sparseFilesystem(ctx, part, builder)
 			pName := proptools.String(part.Name)
 			sparseImages[pName] = sparseImg
@@ -185,31 +188,29 @@
 		addPartitionsToGroup(group.Partitions, gName)
 	}
 
-	l.output = android.PathForModuleOut(ctx, l.installFileName()).OutputPath
-	cmd.FlagWithOutput("--output=", l.output)
+	output := android.PathForModuleOut(ctx, l.installFileName())
+	cmd.FlagWithOutput("--output=", output)
 
 	builder.Build("build_logical_partition", fmt.Sprintf("Creating %s", l.BaseModuleName()))
 
 	l.installDir = android.PathForModuleInstall(ctx, "etc")
-	ctx.InstallFile(l.installDir, l.installFileName(), l.output)
+	ctx.InstallFile(l.installDir, l.installFileName(), output)
 
-	ctx.SetOutputFiles([]android.Path{l.output}, "")
+	ctx.SetOutputFiles([]android.Path{output}, "")
+	l.output = output
 }
 
 // Add a rule that converts the filesystem for the given partition to the given rule builder. The
 // path to the sparse file and the text file having the size of the partition are returned.
-func sparseFilesystem(ctx android.ModuleContext, p partitionProperties, builder *android.RuleBuilder) (sparseImg android.OutputPath, sizeTxt android.OutputPath) {
-	if p.Filesystem == nil {
-		return
-	}
-	img := android.PathForModuleSrc(ctx, proptools.String(p.Filesystem))
+func sparseFilesystem(ctx android.ModuleContext, p partitionProperties, builder *android.RuleBuilder) (android.Path, android.Path) {
+	img := android.PathForModuleSrc(ctx, *p.Filesystem)
 	name := proptools.String(p.Name)
-	sparseImg = android.PathForModuleOut(ctx, name+".img").OutputPath
 
+	sparseImg := android.PathForModuleOut(ctx, name+".img")
 	builder.Temporary(sparseImg)
 	builder.Command().BuiltTool("img2simg").Input(img).Output(sparseImg)
 
-	sizeTxt = android.PathForModuleOut(ctx, name+"-size.txt").OutputPath
+	sizeTxt := android.PathForModuleOut(ctx, name+"-size.txt")
 	builder.Temporary(sizeTxt)
 	builder.Command().BuiltTool("sparse_img").Flag("--get_partition_size").Input(sparseImg).
 		Text("| ").Text("tr").FlagWithArg("-d ", "'\n'").
diff --git a/filesystem/raw_binary.go b/filesystem/raw_binary.go
index ad36c29..707fba0 100644
--- a/filesystem/raw_binary.go
+++ b/filesystem/raw_binary.go
@@ -42,7 +42,7 @@
 
 	properties rawBinaryProperties
 
-	output     android.OutputPath
+	output     android.Path
 	installDir android.InstallPath
 }
 
@@ -71,7 +71,7 @@
 
 func (r *rawBinary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	inputFile := android.PathForModuleSrc(ctx, proptools.String(r.properties.Src))
-	outputFile := android.PathForModuleOut(ctx, r.installFileName()).OutputPath
+	outputFile := android.PathForModuleOut(ctx, r.installFileName())
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        toRawBinary,
@@ -83,11 +83,11 @@
 		},
 	})
 
-	r.output = outputFile
 	r.installDir = android.PathForModuleInstall(ctx, "etc")
-	ctx.InstallFile(r.installDir, r.installFileName(), r.output)
+	ctx.InstallFile(r.installDir, r.installFileName(), outputFile)
 
-	ctx.SetOutputFiles([]android.Path{r.output}, "")
+	ctx.SetOutputFiles([]android.Path{outputFile}, "")
+	r.output = outputFile
 }
 
 var _ android.AndroidMkEntriesProvider = (*rawBinary)(nil)
diff --git a/filesystem/super_image.go b/filesystem/super_image.go
new file mode 100644
index 0000000..58c938a
--- /dev/null
+++ b/filesystem/super_image.go
@@ -0,0 +1,348 @@
+// 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 filesystem
+
+import (
+	"fmt"
+	"path/filepath"
+	"regexp"
+	"slices"
+	"strconv"
+	"strings"
+
+	"android/soong/android"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+func init() {
+	android.RegisterModuleType("super_image", SuperImageFactory)
+}
+
+type superImage struct {
+	android.ModuleBase
+
+	properties     SuperImageProperties
+	partitionProps SuperImagePartitionNameProperties
+
+	installDir android.InstallPath
+}
+
+type SuperImageProperties struct {
+	// the size of the super partition
+	Size *int64
+	// the block device where metadata for dynamic partitions is stored
+	Metadata_device *string
+	// the super partition block device list
+	Block_devices []string
+	// whether A/B updater is used
+	Ab_update *bool
+	// whether dynamic partitions is enabled on devices that were launched without this support
+	Retrofit *bool
+	// whether the output is a sparse image
+	Sparse *bool
+	// information about how partitions within the super partition are grouped together
+	Partition_groups []PartitionGroupsInfo
+	// Name of the system_other partition filesystem module. This module will be installed to
+	// the "b" slot of the system partition in a/b partition builds.
+	System_other_partition *string
+	// whether dynamic partitions is used
+	Use_dynamic_partitions *bool
+	Virtual_ab             struct {
+		// whether virtual A/B seamless update is enabled
+		Enable *bool
+		// whether retrofitting virtual A/B seamless update is enabled
+		Retrofit *bool
+		// If set, device uses virtual A/B Compression
+		Compression *bool
+		// This value controls the compression algorithm used for VABC.
+		// Valid options are defined in system/core/fs_mgr/libsnapshot/cow_writer.cpp
+		// e.g. "none", "gz", "brotli"
+		Compression_method *string
+		// Specifies maximum bytes to be compressed at once during ota. Options: 4096, 8192, 16384, 32768, 65536, 131072, 262144.
+		Compression_factor *int64
+		// Specifies COW version to be used by update_engine and libsnapshot. If this value is not
+		// specified we default to COW version 2 in update_engine for backwards compatibility
+		Cow_version *int64
+	}
+}
+
+type PartitionGroupsInfo struct {
+	Name          string
+	GroupSize     string
+	PartitionList []string
+}
+
+type SuperImagePartitionNameProperties struct {
+	// Name of the System partition filesystem module
+	System_partition *string
+	// Name of the System_ext partition filesystem module
+	System_ext_partition *string
+	// Name of the System_dlkm partition filesystem module
+	System_dlkm_partition *string
+	// Name of the System_other partition filesystem module
+	System_other_partition *string
+	// Name of the Product partition filesystem module
+	Product_partition *string
+	// Name of the Vendor partition filesystem module
+	Vendor_partition *string
+	// Name of the Vendor_dlkm partition filesystem module
+	Vendor_dlkm_partition *string
+	// Name of the Odm partition filesystem module
+	Odm_partition *string
+	// Name of the Odm_dlkm partition filesystem module
+	Odm_dlkm_partition *string
+}
+
+type SuperImageInfo struct {
+	// The built super.img file, which contains the sub-partitions
+	SuperImage android.Path
+
+	// Mapping from the sub-partition type to its re-exported FileSystemInfo providers from the
+	// sub-partitions.
+	SubImageInfo map[string]FilesystemInfo
+}
+
+var SuperImageProvider = blueprint.NewProvider[SuperImageInfo]()
+
+func SuperImageFactory() android.Module {
+	module := &superImage{}
+	module.AddProperties(&module.properties, &module.partitionProps)
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	return module
+}
+
+type superImageDepTagType struct {
+	blueprint.BaseDependencyTag
+}
+
+var subImageDepTag superImageDepTagType
+
+type systemOtherDepTagType struct {
+	blueprint.BaseDependencyTag
+}
+
+var systemOtherDepTag systemOtherDepTagType
+
+func (s *superImage) DepsMutator(ctx android.BottomUpMutatorContext) {
+	addDependencyIfDefined := func(dep *string) {
+		if dep != nil {
+			ctx.AddDependency(ctx.Module(), subImageDepTag, proptools.String(dep))
+		}
+	}
+
+	addDependencyIfDefined(s.partitionProps.System_partition)
+	addDependencyIfDefined(s.partitionProps.System_ext_partition)
+	addDependencyIfDefined(s.partitionProps.System_dlkm_partition)
+	addDependencyIfDefined(s.partitionProps.System_other_partition)
+	addDependencyIfDefined(s.partitionProps.Product_partition)
+	addDependencyIfDefined(s.partitionProps.Vendor_partition)
+	addDependencyIfDefined(s.partitionProps.Vendor_dlkm_partition)
+	addDependencyIfDefined(s.partitionProps.Odm_partition)
+	addDependencyIfDefined(s.partitionProps.Odm_dlkm_partition)
+	if s.properties.System_other_partition != nil {
+		ctx.AddDependency(ctx.Module(), systemOtherDepTag, *s.properties.System_other_partition)
+	}
+}
+
+func (s *superImage) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	miscInfo, deps, subImageInfos := s.buildMiscInfo(ctx)
+	builder := android.NewRuleBuilder(pctx, ctx)
+	output := android.PathForModuleOut(ctx, s.installFileName())
+	lpMake := ctx.Config().HostToolPath(ctx, "lpmake")
+	lpMakeDir := filepath.Dir(lpMake.String())
+	deps = append(deps, lpMake)
+	builder.Command().Textf("PATH=%s:\\$PATH", lpMakeDir).
+		BuiltTool("build_super_image").
+		Text("-v").
+		Input(miscInfo).
+		Implicits(deps).
+		Output(output)
+	builder.Build("build_super_image", fmt.Sprintf("Creating super image %s", s.BaseModuleName()))
+	android.SetProvider(ctx, SuperImageProvider, SuperImageInfo{
+		SuperImage:   output,
+		SubImageInfo: subImageInfos,
+	})
+	ctx.SetOutputFiles([]android.Path{output}, "")
+	ctx.CheckbuildFile(output)
+}
+
+func (s *superImage) installFileName() string {
+	return "super.img"
+}
+
+func (s *superImage) buildMiscInfo(ctx android.ModuleContext) (android.Path, android.Paths, map[string]FilesystemInfo) {
+	var miscInfoString strings.Builder
+	addStr := func(name string, value string) {
+		miscInfoString.WriteString(name)
+		miscInfoString.WriteRune('=')
+		miscInfoString.WriteString(value)
+		miscInfoString.WriteRune('\n')
+	}
+
+	addStr("build_super_partition", "true")
+	addStr("use_dynamic_partitions", strconv.FormatBool(proptools.Bool(s.properties.Use_dynamic_partitions)))
+	if proptools.Bool(s.properties.Retrofit) {
+		addStr("dynamic_partition_retrofit", "true")
+	}
+	addStr("lpmake", "lpmake")
+	addStr("super_metadata_device", proptools.String(s.properties.Metadata_device))
+	if len(s.properties.Block_devices) > 0 {
+		addStr("super_block_devices", strings.Join(s.properties.Block_devices, " "))
+	}
+	addStr("super_partition_size", strconv.Itoa(proptools.Int(s.properties.Size)))
+	// TODO: In make, there's more complicated logic than just this surrounding super_*_device_size
+	addStr("super_super_device_size", strconv.Itoa(proptools.Int(s.properties.Size)))
+	var groups, partitionList []string
+	for _, groupInfo := range s.properties.Partition_groups {
+		groups = append(groups, groupInfo.Name)
+		partitionList = append(partitionList, groupInfo.PartitionList...)
+		addStr("super_"+groupInfo.Name+"_group_size", groupInfo.GroupSize)
+		addStr("super_"+groupInfo.Name+"_partition_list", strings.Join(groupInfo.PartitionList, " "))
+	}
+	initialPartitionListLen := len(partitionList)
+	partitionList = android.SortedUniqueStrings(partitionList)
+	if len(partitionList) != initialPartitionListLen {
+		ctx.ModuleErrorf("Duplicate partitions found in the partition_groups property")
+	}
+	addStr("super_partition_groups", strings.Join(groups, " "))
+	addStr("dynamic_partition_list", strings.Join(partitionList, " "))
+
+	addStr("ab_update", strconv.FormatBool(proptools.Bool(s.properties.Ab_update)))
+
+	if proptools.Bool(s.properties.Virtual_ab.Enable) {
+		addStr("virtual_ab", "true")
+		if proptools.Bool(s.properties.Virtual_ab.Retrofit) {
+			addStr("virtual_ab_retrofit", "true")
+		}
+		addStr("virtual_ab_compression", strconv.FormatBool(proptools.Bool(s.properties.Virtual_ab.Compression)))
+		if s.properties.Virtual_ab.Compression_method != nil {
+			matched, _ := regexp.MatchString("^[a-zA-Z0-9_-]+$", *s.properties.Virtual_ab.Compression_method)
+			if !matched {
+				ctx.PropertyErrorf("virtual_ab.compression_method", "compression_method cannot have special characters")
+			}
+			addStr("virtual_ab_compression_method", *s.properties.Virtual_ab.Compression_method)
+		}
+		if s.properties.Virtual_ab.Compression_factor != nil {
+			addStr("virtual_ab_compression_factor", strconv.FormatInt(*s.properties.Virtual_ab.Compression_factor, 10))
+		}
+		if s.properties.Virtual_ab.Cow_version != nil {
+			addStr("virtual_ab_cow_version", strconv.FormatInt(*s.properties.Virtual_ab.Cow_version, 10))
+		}
+
+	} else {
+		if s.properties.Virtual_ab.Retrofit != nil {
+			ctx.PropertyErrorf("virtual_ab.retrofit", "This property cannot be set when virtual_ab is disabled")
+		}
+		if s.properties.Virtual_ab.Compression != nil {
+			ctx.PropertyErrorf("virtual_ab.compression", "This property cannot be set when virtual_ab is disabled")
+		}
+		if s.properties.Virtual_ab.Compression_method != nil {
+			ctx.PropertyErrorf("virtual_ab.compression_method", "This property cannot be set when virtual_ab is disabled")
+		}
+		if s.properties.Virtual_ab.Compression_factor != nil {
+			ctx.PropertyErrorf("virtual_ab.compression_factor", "This property cannot be set when virtual_ab is disabled")
+		}
+	}
+
+	subImageInfo := make(map[string]FilesystemInfo)
+	var deps android.Paths
+
+	missingPartitionErrorMessage := ""
+	handleSubPartition := func(partitionType string, name *string) {
+		if proptools.String(name) == "" {
+			missingPartitionErrorMessage += fmt.Sprintf("%s image listed in partition groups, but its module was not specified. ", partitionType)
+			return
+		}
+		mod := ctx.GetDirectDepWithTag(*name, subImageDepTag)
+		if mod == nil {
+			ctx.ModuleErrorf("Could not get dep %q", *name)
+			return
+		}
+		info, ok := android.OtherModuleProvider(ctx, mod, FilesystemProvider)
+		if !ok {
+			ctx.ModuleErrorf("Expected dep %q to provide FilesystemInfo", *name)
+			return
+		}
+		addStr(partitionType+"_image", info.Output.String())
+		deps = append(deps, info.Output)
+		if _, ok := subImageInfo[partitionType]; ok {
+			ctx.ModuleErrorf("Already set subimageInfo for %q", partitionType)
+		}
+		subImageInfo[partitionType] = info
+	}
+
+	// Build partitionToImagePath, because system partition may need system_other
+	// partition image path
+	for _, p := range partitionList {
+		switch p {
+		case "system":
+			handleSubPartition("system", s.partitionProps.System_partition)
+		case "system_dlkm":
+			handleSubPartition("system_dlkm", s.partitionProps.System_dlkm_partition)
+		case "system_ext":
+			handleSubPartition("system_ext", s.partitionProps.System_ext_partition)
+		case "product":
+			handleSubPartition("product", s.partitionProps.Product_partition)
+		case "vendor":
+			handleSubPartition("vendor", s.partitionProps.Vendor_partition)
+		case "vendor_dlkm":
+			handleSubPartition("vendor_dlkm", s.partitionProps.Vendor_dlkm_partition)
+		case "odm":
+			handleSubPartition("odm", s.partitionProps.Odm_partition)
+		case "odm_dlkm":
+			handleSubPartition("odm_dlkm", s.partitionProps.Odm_dlkm_partition)
+		default:
+			ctx.ModuleErrorf("partition %q is not a super image supported partition", p)
+		}
+	}
+
+	if s.properties.System_other_partition != nil {
+		if !slices.Contains(partitionList, "system") {
+			ctx.PropertyErrorf("system_other_partition", "Must have a system partition to use a system_other partition")
+		}
+		systemOther := ctx.GetDirectDepProxyWithTag(*s.properties.System_other_partition, systemOtherDepTag)
+		systemOtherFiles := android.OutputFilesForModule(ctx, systemOther, "")
+		if len(systemOtherFiles) != 1 {
+			ctx.PropertyErrorf("system_other_partition", "Expected 1 output file from module %q", *&s.properties.System_other_partition)
+		} else {
+			handleSubPartition("system_other", s.partitionProps.System_other_partition)
+		}
+	}
+
+	// Delay the error message until execution time because on aosp-main-future-without-vendor,
+	// BUILDING_VENDOR_IMAGE is false so we don't get the vendor image, but it's still listed in
+	// BOARD_GOOGLE_DYNAMIC_PARTITIONS_PARTITION_LIST.
+	missingPartitionErrorMessageFile := android.PathForModuleOut(ctx, "missing_partition_error.txt")
+	if missingPartitionErrorMessage != "" {
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   android.ErrorRule,
+			Output: missingPartitionErrorMessageFile,
+			Args: map[string]string{
+				"error": missingPartitionErrorMessage,
+			},
+		})
+	} else {
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   android.Touch,
+			Output: missingPartitionErrorMessageFile,
+		})
+	}
+
+	miscInfo := android.PathForModuleOut(ctx, "misc_info.txt")
+	android.WriteFileRule(ctx, miscInfo, miscInfoString.String(), missingPartitionErrorMessageFile)
+	return miscInfo, deps, subImageInfo
+}
diff --git a/filesystem/system_image.go b/filesystem/system_image.go
index 57239ae..cc9093f 100644
--- a/filesystem/system_image.go
+++ b/filesystem/system_image.go
@@ -16,28 +16,26 @@
 
 import (
 	"android/soong/android"
+	"android/soong/cc"
 	"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 +44,78 @@
 	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,
+	fullInstallPaths *[]FullInstallPathInfo,
+) {
+	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()]
+	output := rebasedDir.Join(ctx, "etc", "linker.config.pb")
+	if s.filesystem.properties.Linker_config.Linker_config_srcs != nil {
+		provideModules, requireModules := s.getLibsForLinkerConfig(ctx)
+		intermediateOutput := android.PathForModuleOut(ctx, "linker.config.pb")
+		linkerconfig.BuildLinkerConfig(ctx, android.PathsForModuleSrc(ctx, s.filesystem.properties.Linker_config.Linker_config_srcs), provideModules, requireModules, intermediateOutput)
+		builder.Command().Text("cp").Input(intermediateOutput).Output(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
-	})
+		*fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{
+			FullInstallPath: android.PathForModuleInPartitionInstall(ctx, s.PartitionType(), "etc", "linker.config.pb"),
+			SourcePath:      intermediateOutput,
+		})
+	} else {
+		// TODO: This branch is the logic that make uses for the linker config file, which is
+		// different than linkerconfig.BuildLinkerConfig used above. Keeping both branches for now
+		// because microdroid uses the other method and is in theory happy with it. But we should
+		// consider deduping them.
+		stubLibraries := cc.StubLibrariesFile(ctx)
+		llndkMovedToApexLibraries := cc.MovedToApexLlndkLibrariesFile(ctx)
+		outputStep1 := android.PathForModuleOut(ctx, "linker.config.pb.step1")
+		builder.Command().
+			BuiltTool("conv_linker_config").
+			Text("proto --force").
+			FlagWithInput("-s ", android.PathForSource(ctx, "system/core/rootdir/etc/linker.config.json")).
+			FlagWithOutput("-o ", outputStep1)
+		builder.Temporary(outputStep1)
+		builder.Command().
+			BuiltTool("conv_linker_config").
+			Text("systemprovide").
+			FlagWithInput("--source ", outputStep1).
+			FlagWithArg("--output ", output.String()).
+			Textf(`--value "$(cat %s)"`, stubLibraries).
+			Implicit(stubLibraries).
+			FlagWithArg("--system ", rebasedDir.String())
+		builder.Command().
+			BuiltTool("conv_linker_config").
+			Text("append").
+			FlagWithArg("--source ", output.String()).
+			FlagWithOutput("--output ", output).
+			FlagWithArg("--key ", "requireLibs").
+			Textf(`--value "$(cat %s)"`, llndkMovedToApexLibraries).
+			Implicit(llndkMovedToApexLibraries)
+		// TODO: Make also supports adding an extra append command with PRODUCT_EXTRA_STUB_LIBRARIES,
+		// but that variable appears to have no usages.
 
-	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
+		*fullInstallPaths = append(*fullInstallPaths, FullInstallPathInfo{
+			FullInstallPath: android.PathForModuleInPartitionInstall(ctx, s.PartitionType(), "etc", "linker.config.pb"),
+			SourcePath:      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 {
-	return s.filesystem.filterInstallablePackagingSpec(ps) &&
-		(ps.Partition() == "system" || ps.Partition() == "root")
+func (s *systemImage) FilterPackagingSpec(ps android.PackagingSpec) bool {
+	return !ps.SkipInstall() &&
+		(ps.Partition() == "system" || ps.Partition() == "root" ||
+			strings.HasPrefix(ps.Partition(), "system/"))
+}
+
+func (s *systemImage) ShouldUseVintfFragmentModuleOnly() bool {
+	return true
 }
diff --git a/filesystem/system_other.go b/filesystem/system_other.go
new file mode 100644
index 0000000..1c00dd3
--- /dev/null
+++ b/filesystem/system_other.go
@@ -0,0 +1,174 @@
+// 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 filesystem
+
+import (
+	"android/soong/android"
+	"path/filepath"
+	"strings"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+type SystemOtherImageProperties struct {
+	// The system_other image always requires a reference to the system image. The system_other
+	// partition gets built into the system partition's "b" slot in a/b partition builds. Thus, it
+	// copies most of its configuration from the system image, such as filesystem type, avb signing
+	// info, etc. Including it here does not automatically mean that it will pick up the system
+	// image's dexpropt files, it must also be listed in Preinstall_dexpreopt_files_from for that.
+	System_image *string
+
+	// This system_other partition will include all the dexpreopt files from the apps on these
+	// partitions.
+	Preinstall_dexpreopt_files_from []string
+}
+
+type systemOtherImage struct {
+	android.ModuleBase
+	android.DefaultableModuleBase
+	properties SystemOtherImageProperties
+}
+
+// The system_other image is the default contents of the "b" slot of the system image.
+// It contains the dexpreopt files of all the apps on the device, for a faster first boot.
+// Afterwards, at runtime, it will be used as a regular b slot for OTA updates, and the initial
+// dexpreopt files will be deleted.
+func SystemOtherImageFactory() android.Module {
+	module := &systemOtherImage{}
+	module.AddProperties(&module.properties)
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+type systemImageDeptag struct {
+	blueprint.BaseDependencyTag
+}
+
+var systemImageDependencyTag = systemImageDeptag{}
+
+type dexpreoptDeptag struct {
+	blueprint.BaseDependencyTag
+}
+
+var dexpreoptDependencyTag = dexpreoptDeptag{}
+
+func (m *systemOtherImage) DepsMutator(ctx android.BottomUpMutatorContext) {
+	if proptools.String(m.properties.System_image) == "" {
+		ctx.ModuleErrorf("system_image property must be set")
+		return
+	}
+	ctx.AddDependency(ctx.Module(), systemImageDependencyTag, *m.properties.System_image)
+	ctx.AddDependency(ctx.Module(), dexpreoptDependencyTag, m.properties.Preinstall_dexpreopt_files_from...)
+}
+
+func (m *systemOtherImage) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	systemImage := ctx.GetDirectDepProxyWithTag(*m.properties.System_image, systemImageDependencyTag)
+	systemInfo, ok := android.OtherModuleProvider(ctx, systemImage, FilesystemProvider)
+	if !ok {
+		ctx.PropertyErrorf("system_image", "Expected system_image module to provide FilesystemProvider")
+		return
+	}
+
+	output := android.PathForModuleOut(ctx, "system_other.img")
+	stagingDir := android.PathForModuleOut(ctx, "staging_dir")
+	stagingDirTimestamp := android.PathForModuleOut(ctx, "staging_dir.timestamp")
+
+	builder := android.NewRuleBuilder(pctx, ctx)
+	builder.Command().Textf("rm -rf %s && mkdir -p %s", stagingDir, stagingDir)
+
+	specs := make(map[string]android.PackagingSpec)
+	for _, otherPartition := range m.properties.Preinstall_dexpreopt_files_from {
+		dexModule := ctx.GetDirectDepProxyWithTag(otherPartition, dexpreoptDependencyTag)
+		fsInfo, ok := android.OtherModuleProvider(ctx, dexModule, FilesystemProvider)
+		if !ok {
+			ctx.PropertyErrorf("preinstall_dexpreopt_files_from", "Expected module %q to provide FilesystemProvider", otherPartition)
+			return
+		}
+		// Merge all the packaging specs into 1 map
+		for k := range fsInfo.SpecsForSystemOther {
+			if _, ok := specs[k]; ok {
+				ctx.ModuleErrorf("Packaging spec %s given by two different partitions", k)
+				continue
+			}
+			specs[k] = fsInfo.SpecsForSystemOther[k]
+		}
+	}
+
+	// TOOD: CopySpecsToDir only exists on PackagingBase, but doesn't use any fields from it. Clean this up.
+	(&android.PackagingBase{}).CopySpecsToDir(ctx, builder, specs, stagingDir)
+
+	if len(m.properties.Preinstall_dexpreopt_files_from) > 0 {
+		builder.Command().Textf("touch %s", filepath.Join(stagingDir.String(), "system-other-odex-marker"))
+	}
+	builder.Command().Textf("touch").Output(stagingDirTimestamp)
+	builder.Build("assemble_filesystem_staging_dir", "Assemble filesystem staging dir")
+
+	// Most of the time, if build_image were to call a host tool, it accepts the path to the
+	// host tool in a field in the prop file. However, it doesn't have that option for fec, which
+	// it expects to just be on the PATH. Add fec to the PATH.
+	fec := ctx.Config().HostToolPath(ctx, "fec")
+	pathToolDirs := []string{filepath.Dir(fec.String())}
+
+	builder = android.NewRuleBuilder(pctx, ctx)
+	builder.Command().
+		Textf("PATH=%s:$PATH", strings.Join(pathToolDirs, ":")).
+		BuiltTool("build_image").
+		Text(stagingDir.String()). // input directory
+		Input(systemInfo.BuildImagePropFile).
+		Implicits(systemInfo.BuildImagePropFileDeps).
+		Implicit(fec).
+		Implicit(stagingDirTimestamp).
+		Output(output).
+		Text(stagingDir.String())
+
+	builder.Build("build_system_other", "build system other")
+
+	// Create a hermetic system_other.img with pinned timestamps
+	builder = android.NewRuleBuilder(pctx, ctx)
+	outputHermetic := android.PathForModuleOut(ctx, "for_target_files", "system_other.img")
+	outputHermeticPropFile := m.propFileForHermeticImg(ctx, builder, systemInfo.BuildImagePropFile)
+	builder.Command().
+		Textf("PATH=%s:$PATH", strings.Join(pathToolDirs, ":")).
+		BuiltTool("build_image").
+		Text(stagingDir.String()). // input directory
+		Input(outputHermeticPropFile).
+		Implicits(systemInfo.BuildImagePropFileDeps).
+		Implicit(fec).
+		Implicit(stagingDirTimestamp).
+		Output(outputHermetic).
+		Text(stagingDir.String())
+
+	builder.Build("build_system_other_hermetic", "build system other")
+
+	fsInfo := FilesystemInfo{
+		Output:         output,
+		OutputHermetic: outputHermetic,
+		RootDir:        stagingDir,
+	}
+
+	android.SetProvider(ctx, FilesystemProvider, fsInfo)
+
+	ctx.SetOutputFiles(android.Paths{output}, "")
+	ctx.CheckbuildFile(output)
+}
+
+func (f *systemOtherImage) propFileForHermeticImg(ctx android.ModuleContext, builder *android.RuleBuilder, inputPropFile android.Path) android.Path {
+	propFilePinnedTimestamp := android.PathForModuleOut(ctx, "for_target_files", "prop")
+	builder.Command().Textf("cat").Input(inputPropFile).Flag(">").Output(propFilePinnedTimestamp).
+		Textf(" && echo use_fixed_timestamp=true >> %s", propFilePinnedTimestamp)
+	return propFilePinnedTimestamp
+}
diff --git a/filesystem/vbmeta.go b/filesystem/vbmeta.go
index 1d64796..e5809d3 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
+	output     android.Path
 	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,11 +93,11 @@
 	Value *string
 }
 
-type chainedPartitionProperties struct {
+type ChainedPartitionProperties struct {
 	// Name of the chained partition
 	Name *string
 
-	// Rollback index location of the chained partition. Must be 0, 1, 2, etc. Default is the
+	// Rollback index location of the chained partition. Must be 1, 2, 3, etc. Default is the
 	// index of this partition in the list + 1.
 	Rollback_index_location *int64
 
@@ -94,23 +111,45 @@
 	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
+
+	// The output of the vbmeta module
+	Output 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.MultilibFirst)
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	return module
 }
 
 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.AddVariationDependencies(ctx.Config().AndroidFirstDeviceTarget.Variations(), vbmetaPartitionDep, v.properties.Partitions.GetOrDefault(ctx, nil)...)
+	ctx.AddVariationDependencies(ctx.Config().AndroidFirstDeviceTarget.Variations(), vbmetaChainedPartitionDep, v.properties.Chained_partitions...)
 }
 
 func (v *vbmeta) installFileName() string {
@@ -125,10 +164,6 @@
 const vbmetaMaxSize = 64 * 1024
 
 func (v *vbmeta) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	extractedPublicKeys := v.extractPublicKeys(ctx)
-
-	v.output = android.PathForModuleOut(ctx, v.installFileName()).OutputPath
-
 	builder := android.NewRuleBuilder(pctx, ctx)
 	cmd := builder.Command().BuiltTool("avbtool").Text("make_vbmeta_image")
 
@@ -138,13 +173,14 @@
 	algorithm := proptools.StringDefault(v.properties.Algorithm, "SHA256_RSA4096")
 	cmd.FlagWithArg("--algorithm ", algorithm)
 
+	cmd.FlagWithArg("--padding_size ", "4096")
+
 	cmd.FlagWithArg("--rollback_index ", v.rollbackIndexCommand(ctx))
 	ril := proptools.IntDefault(v.properties.Rollback_index_location, 0)
 	if ril < 0 {
 		ctx.PropertyErrorf("rollback_index_location", "must be 0, 1, 2, ...")
 		return
 	}
-	cmd.FlagWithArg("--rollback_index_location ", strconv.Itoa(ril))
 
 	for _, avb_prop := range v.properties.Avb_properties {
 		key := proptools.String(avb_prop.Key)
@@ -176,97 +212,115 @@
 		cmd.FlagWithInput("--include_descriptors_from_image ", signedImage)
 	}
 
-	for i, cp := range v.properties.Chained_partitions {
-		name := proptools.String(cp.Name)
-		if 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 := proptools.IntDefault(cp.Rollback_index_location, i+1)
-		if ril < 0 {
-			ctx.PropertyErrorf("chained_partitions", "must be 0, 1, 2, ...")
+		ril := info.RollbackIndexLocation
+		if ril < 1 {
+			ctx.PropertyErrorf("chained_partitions", "rollback index location must be 1, 2, 3, ...")
+			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_partition_metadata", "name 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]
+		ril := proptools.IntDefault(cpm.Rollback_index_location, 0)
+		if ril < 1 {
+			ctx.PropertyErrorf("chained_partition_metadata", "rollback index location must be 1, 2, 3, ...")
+			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
+		}
+
 		cmd.FlagWithArg("--chain_partition ", fmt.Sprintf("%s:%d:%s", name, ril, publicKey.String()))
 		cmd.Implicit(publicKey)
 	}
 
-	cmd.FlagWithOutput("--output ", v.output)
+	output := android.PathForModuleOut(ctx, v.installFileName())
+	cmd.FlagWithOutput("--output ", output)
 
 	// libavb expects to be able to read the maximum vbmeta size, so we must provide a partition
 	// which matches this or the read will fail.
 	builder.Command().Text("truncate").
 		FlagWithArg("-s ", strconv.Itoa(vbmetaMaxSize)).
-		Output(v.output)
+		Output(output)
 
 	builder.Build("vbmeta", fmt.Sprintf("vbmeta %s", ctx.ModuleName()))
 
 	v.installDir = android.PathForModuleInstall(ctx, "etc")
-	ctx.InstallFile(v.installDir, v.installFileName(), v.output)
+	ctx.InstallFile(v.installDir, v.installFileName(), output)
 
-	ctx.SetOutputFiles([]android.Path{v.output}, "")
-	android.SetProvider(ctx, android.AndroidMkInfoProvider, v.prepareAndroidMKProviderInfo())
+	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,
+		Output:                output,
+	})
+
+	ctx.SetOutputFiles([]android.Path{output}, "")
+	v.output = 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)
+var _ android.AndroidMkProviderInfoProducer = (*vbmeta)(nil)
 
-	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
-}
-
-func (v *vbmeta) prepareAndroidMKProviderInfo() *android.AndroidMkProviderInfo {
+func (v *vbmeta) PrepareAndroidMKProviderInfo(config android.Config) *android.AndroidMkProviderInfo {
 	providerData := android.AndroidMkProviderInfo{
 		PrimaryInfo: android.AndroidMkInfo{
 			Class:      "ETC",
diff --git a/fsgen/Android.bp b/fsgen/Android.bp
index 9fa9557..1b828c5 100644
--- a/fsgen/Android.bp
+++ b/fsgen/Android.bp
@@ -10,14 +10,23 @@
         "soong",
         "soong-android",
         "soong-filesystem",
+        "soong-kernel",
     ],
     srcs: [
+        "boot_imgs.go",
+        "config.go",
         "filesystem_creator.go",
+        "fsgen_mutators.go",
+        "prebuilt_etc_modules_gen.go",
+        "super_img.go",
+        "util.go",
+        "vbmeta_partitions.go",
     ],
     testSrcs: [
         "filesystem_creator_test.go",
     ],
     pluginFor: ["soong_build"],
+    visibility: ["//visibility:public"],
 }
 
 soong_filesystem_creator {
diff --git a/fsgen/boot_imgs.go b/fsgen/boot_imgs.go
new file mode 100644
index 0000000..58ebcc4
--- /dev/null
+++ b/fsgen/boot_imgs.go
@@ -0,0 +1,339 @@
+package fsgen
+
+import (
+	"android/soong/android"
+	"android/soong/filesystem"
+	"fmt"
+	"path/filepath"
+	"strconv"
+	"strings"
+
+	"github.com/google/blueprint/proptools"
+)
+
+func createBootImage(ctx android.LoadHookContext, dtbImg dtbImg) bool {
+	partitionVariables := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+
+	if partitionVariables.TargetKernelPath == "" {
+		// There are potentially code paths that don't set TARGET_KERNEL_PATH
+		return false
+	}
+
+	kernelDir := filepath.Dir(partitionVariables.TargetKernelPath)
+	kernelBase := filepath.Base(partitionVariables.TargetKernelPath)
+	kernelFilegroupName := generatedModuleName(ctx.Config(), "kernel")
+
+	ctx.CreateModuleInDirectory(
+		android.FileGroupFactory,
+		kernelDir,
+		&struct {
+			Name       *string
+			Srcs       []string
+			Visibility []string
+		}{
+			Name:       proptools.StringPtr(kernelFilegroupName),
+			Srcs:       []string{kernelBase},
+			Visibility: []string{"//visibility:public"},
+		},
+	)
+
+	var partitionSize *int64
+	if partitionVariables.BoardBootimagePartitionSize != "" {
+		// Base of zero will allow base 10 or base 16 if starting with 0x
+		parsed, err := strconv.ParseInt(partitionVariables.BoardBootimagePartitionSize, 0, 64)
+		if err != nil {
+			panic(fmt.Sprintf("BOARD_BOOTIMAGE_PARTITION_SIZE must be an int, got %s", partitionVariables.BoardBootimagePartitionSize))
+		}
+		partitionSize = &parsed
+	}
+
+	var securityPatch *string
+	if partitionVariables.BootSecurityPatch != "" {
+		securityPatch = &partitionVariables.BootSecurityPatch
+	}
+
+	avbInfo := getAvbInfo(ctx.Config(), "boot")
+
+	bootImageName := generatedModuleNameForPartition(ctx.Config(), "boot")
+
+	var dtbPrebuilt *string
+	if dtbImg.include && dtbImg.imgType == "boot" {
+		dtbPrebuilt = proptools.StringPtr(":" + dtbImg.name)
+	}
+
+	var cmdline []string
+	if !buildingVendorBootImage(partitionVariables) {
+		cmdline = partitionVariables.InternalKernelCmdline
+	}
+
+	ctx.CreateModule(
+		filesystem.BootimgFactory,
+		&filesystem.BootimgProperties{
+			Kernel_prebuilt:             proptools.StringPtr(":" + kernelFilegroupName),
+			Header_version:              proptools.StringPtr(partitionVariables.BoardBootHeaderVersion),
+			Partition_size:              partitionSize,
+			Use_avb:                     avbInfo.avbEnable,
+			Avb_mode:                    avbInfo.avbMode,
+			Avb_private_key:             avbInfo.avbkeyFilegroup,
+			Avb_rollback_index:          avbInfo.avbRollbackIndex,
+			Avb_rollback_index_location: avbInfo.avbRollbackIndexLocation,
+			Avb_algorithm:               avbInfo.avbAlgorithm,
+			Security_patch:              securityPatch,
+			Dtb_prebuilt:                dtbPrebuilt,
+			Cmdline:                     cmdline,
+			Stem:                        proptools.StringPtr("boot.img"),
+		},
+		&struct {
+			Name       *string
+			Visibility []string
+		}{
+			Name:       proptools.StringPtr(bootImageName),
+			Visibility: []string{"//visibility:public"},
+		},
+	)
+	return true
+}
+
+func createVendorBootImage(ctx android.LoadHookContext, dtbImg dtbImg) bool {
+	partitionVariables := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+
+	bootImageName := generatedModuleNameForPartition(ctx.Config(), "vendor_boot")
+
+	avbInfo := getAvbInfo(ctx.Config(), "vendor_boot")
+
+	var dtbPrebuilt *string
+	if dtbImg.include && dtbImg.imgType == "vendor_boot" {
+		dtbPrebuilt = proptools.StringPtr(":" + dtbImg.name)
+	}
+
+	cmdline := partitionVariables.InternalKernelCmdline
+
+	var vendorBootConfigImg *string
+	if name, ok := createVendorBootConfigImg(ctx); ok {
+		vendorBootConfigImg = proptools.StringPtr(":" + name)
+	}
+
+	var partitionSize *int64
+	if partitionVariables.BoardVendorBootimagePartitionSize != "" {
+		// Base of zero will allow base 10 or base 16 if starting with 0x
+		parsed, err := strconv.ParseInt(partitionVariables.BoardVendorBootimagePartitionSize, 0, 64)
+		if err != nil {
+			ctx.ModuleErrorf("BOARD_VENDOR_BOOTIMAGE_PARTITION_SIZE must be an int, got %s", partitionVariables.BoardVendorBootimagePartitionSize)
+		}
+		partitionSize = &parsed
+	}
+
+	ctx.CreateModule(
+		filesystem.BootimgFactory,
+		&filesystem.BootimgProperties{
+			Boot_image_type:             proptools.StringPtr("vendor_boot"),
+			Ramdisk_module:              proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "vendor_ramdisk")),
+			Header_version:              proptools.StringPtr(partitionVariables.BoardBootHeaderVersion),
+			Partition_size:              partitionSize,
+			Use_avb:                     avbInfo.avbEnable,
+			Avb_mode:                    avbInfo.avbMode,
+			Avb_private_key:             avbInfo.avbkeyFilegroup,
+			Avb_rollback_index:          avbInfo.avbRollbackIndex,
+			Avb_rollback_index_location: avbInfo.avbRollbackIndexLocation,
+			Dtb_prebuilt:                dtbPrebuilt,
+			Cmdline:                     cmdline,
+			Bootconfig:                  vendorBootConfigImg,
+			Stem:                        proptools.StringPtr("vendor_boot.img"),
+		},
+		&struct {
+			Name       *string
+			Visibility []string
+		}{
+			Name:       proptools.StringPtr(bootImageName),
+			Visibility: []string{"//visibility:public"},
+		},
+	)
+	return true
+}
+
+func createInitBootImage(ctx android.LoadHookContext) bool {
+	partitionVariables := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+
+	bootImageName := generatedModuleNameForPartition(ctx.Config(), "init_boot")
+
+	var securityPatch *string
+	if partitionVariables.InitBootSecurityPatch != "" {
+		securityPatch = &partitionVariables.InitBootSecurityPatch
+	} else if partitionVariables.BootSecurityPatch != "" {
+		securityPatch = &partitionVariables.BootSecurityPatch
+	}
+
+	var partitionSize *int64
+	if partitionVariables.BoardInitBootimagePartitionSize != "" {
+		// Base of zero will allow base 10 or base 16 if starting with 0x
+		parsed, err := strconv.ParseInt(partitionVariables.BoardInitBootimagePartitionSize, 0, 64)
+		if err != nil {
+			panic(fmt.Sprintf("BOARD_INIT_BOOT_IMAGE_PARTITION_SIZE must be an int, got %s", partitionVariables.BoardInitBootimagePartitionSize))
+		}
+		partitionSize = &parsed
+	}
+
+	avbInfo := getAvbInfo(ctx.Config(), "init_boot")
+
+	ctx.CreateModule(
+		filesystem.BootimgFactory,
+		&filesystem.BootimgProperties{
+			Boot_image_type:             proptools.StringPtr("init_boot"),
+			Ramdisk_module:              proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "ramdisk")),
+			Header_version:              proptools.StringPtr(partitionVariables.BoardBootHeaderVersion),
+			Security_patch:              securityPatch,
+			Partition_size:              partitionSize,
+			Use_avb:                     avbInfo.avbEnable,
+			Avb_mode:                    avbInfo.avbMode,
+			Avb_private_key:             avbInfo.avbkeyFilegroup,
+			Avb_rollback_index:          avbInfo.avbRollbackIndex,
+			Avb_rollback_index_location: avbInfo.avbRollbackIndexLocation,
+			Avb_algorithm:               avbInfo.avbAlgorithm,
+			Stem:                        proptools.StringPtr("init_boot.img"),
+		},
+		&struct {
+			Name       *string
+			Visibility []string
+		}{
+			Name:       proptools.StringPtr(bootImageName),
+			Visibility: []string{"//visibility:public"},
+		},
+	)
+	return true
+}
+
+// Returns the equivalent of the BUILDING_BOOT_IMAGE variable in make. Derived from this logic:
+// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/board_config.mk;l=458;drc=5b55f926830963c02ab1d2d91e46442f04ba3af0
+func buildingBootImage(partitionVars android.PartitionVariables) bool {
+	if partitionVars.BoardUsesRecoveryAsBoot {
+		return false
+	}
+
+	if partitionVars.ProductBuildBootImage {
+		return true
+	}
+
+	if len(partitionVars.BoardPrebuiltBootimage) > 0 {
+		return false
+	}
+
+	if len(partitionVars.BoardBootimagePartitionSize) > 0 {
+		return true
+	}
+
+	// TODO: return true if BOARD_KERNEL_BINARIES is set and has a *_BOOTIMAGE_PARTITION_SIZE
+	// variable. However, I don't think BOARD_KERNEL_BINARIES is ever set in practice.
+
+	return false
+}
+
+// Returns the equivalent of the BUILDING_VENDOR_BOOT_IMAGE variable in make. Derived from this logic:
+// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/board_config.mk;l=518;drc=5b55f926830963c02ab1d2d91e46442f04ba3af0
+func buildingVendorBootImage(partitionVars android.PartitionVariables) bool {
+	if v, exists := boardBootHeaderVersion(partitionVars); exists && v >= 3 {
+		x := partitionVars.ProductBuildVendorBootImage
+		if x == "" || x == "true" {
+			return true
+		}
+	}
+
+	return false
+}
+
+// Derived from: https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/board_config.mk;l=480;drc=5b55f926830963c02ab1d2d91e46442f04ba3af0
+func buildingInitBootImage(partitionVars android.PartitionVariables) bool {
+	if !partitionVars.ProductBuildInitBootImage {
+		if partitionVars.BoardUsesRecoveryAsBoot || len(partitionVars.BoardPrebuiltInitBootimage) > 0 {
+			return false
+		} else if len(partitionVars.BoardInitBootimagePartitionSize) > 0 {
+			return true
+		}
+	} else {
+		if partitionVars.BoardUsesRecoveryAsBoot {
+			panic("PRODUCT_BUILD_INIT_BOOT_IMAGE is true, but so is BOARD_USES_RECOVERY_AS_BOOT. Use only one option.")
+		}
+		return true
+	}
+	return false
+}
+
+func boardBootHeaderVersion(partitionVars android.PartitionVariables) (int, bool) {
+	if len(partitionVars.BoardBootHeaderVersion) == 0 {
+		return 0, false
+	}
+	v, err := strconv.ParseInt(partitionVars.BoardBootHeaderVersion, 10, 32)
+	if err != nil {
+		panic(fmt.Sprintf("BOARD_BOOT_HEADER_VERSION must be an int, got: %q", partitionVars.BoardBootHeaderVersion))
+	}
+	return int(v), true
+}
+
+type dtbImg struct {
+	// whether to include the dtb image in boot image
+	include bool
+
+	// name of the generated dtb image filegroup name
+	name string
+
+	// type of the boot image that the dtb image argument should be specified
+	imgType string
+}
+
+func createDtbImgFilegroup(ctx android.LoadHookContext) dtbImg {
+	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+	if !partitionVars.BoardIncludeDtbInBootimg {
+		return dtbImg{include: false}
+	}
+	for _, copyFilePair := range partitionVars.ProductCopyFiles {
+		srcDestList := strings.Split(copyFilePair, ":")
+		if len(srcDestList) < 2 {
+			ctx.ModuleErrorf("PRODUCT_COPY_FILES must follow the format \"src:dest\", got: %s", copyFilePair)
+		}
+		if srcDestList[1] == "dtb.img" {
+			moduleName := generatedModuleName(ctx.Config(), "dtb_img_filegroup")
+			ctx.CreateModuleInDirectory(
+				android.FileGroupFactory,
+				filepath.Dir(srcDestList[0]),
+				&struct {
+					Name *string
+					Srcs []string
+				}{
+					Name: proptools.StringPtr(moduleName),
+					Srcs: []string{filepath.Base(srcDestList[1])},
+				},
+			)
+			imgType := "vendor_boot"
+			if !buildingVendorBootImage(partitionVars) {
+				imgType = "boot"
+			}
+			return dtbImg{include: true, name: moduleName, imgType: imgType}
+		}
+	}
+	return dtbImg{include: false}
+}
+
+func createVendorBootConfigImg(ctx android.LoadHookContext) (string, bool) {
+	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+	bootconfig := partitionVars.InternalBootconfig
+	bootconfigFile := partitionVars.InternalBootconfigFile
+	if len(bootconfig) == 0 && len(bootconfigFile) == 0 {
+		return "", false
+	}
+
+	vendorBootconfigImgModuleName := generatedModuleName(ctx.Config(), "vendor_bootconfig_image")
+
+	ctx.CreateModule(
+		filesystem.BootconfigModuleFactory,
+		&struct {
+			Name             *string
+			Boot_config      []string
+			Boot_config_file *string
+		}{
+			Name:             proptools.StringPtr(vendorBootconfigImgModuleName),
+			Boot_config:      bootconfig,
+			Boot_config_file: proptools.StringPtr(bootconfigFile),
+		},
+	)
+
+	return vendorBootconfigImgModuleName, true
+}
diff --git a/fsgen/config.go b/fsgen/config.go
new file mode 100644
index 0000000..a217600
--- /dev/null
+++ b/fsgen/config.go
@@ -0,0 +1,141 @@
+// 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/filesystem"
+
+	"github.com/google/blueprint/proptools"
+)
+
+var (
+	// Most of the symlinks and directories listed here originate from create_root_structure.mk,
+	// but the handwritten generic system image also recreates them:
+	// https://cs.android.com/android/platform/superproject/main/+/main:build/make/target/product/generic/Android.bp;l=33;drc=db08311f1b6ef6cb0a4fbcc6263b89849360ce04
+	// TODO(b/377734331): only generate the symlinks if the relevant partitions exist
+	commonSymlinksFromRoot = []filesystem.SymlinkDefinition{
+		{
+			Target: proptools.StringPtr("/system/bin/init"),
+			Name:   proptools.StringPtr("init"),
+		},
+		{
+			Target: proptools.StringPtr("/system/etc"),
+			Name:   proptools.StringPtr("etc"),
+		},
+		{
+			Target: proptools.StringPtr("/system/bin"),
+			Name:   proptools.StringPtr("bin"),
+		},
+		{
+			Target: proptools.StringPtr("/data/user_de/0/com.android.shell/files/bugreports"),
+			Name:   proptools.StringPtr("bugreports"),
+		},
+		{
+			Target: proptools.StringPtr("/sys/kernel/debug"),
+			Name:   proptools.StringPtr("d"),
+		},
+		{
+			Target: proptools.StringPtr("/product/etc/security/adb_keys"),
+			Name:   proptools.StringPtr("adb_keys"),
+		},
+		{
+			Target: proptools.StringPtr("/vendor/odm/app"),
+			Name:   proptools.StringPtr("odm/app"),
+		},
+		{
+			Target: proptools.StringPtr("/vendor/odm/bin"),
+			Name:   proptools.StringPtr("odm/bin"),
+		},
+		{
+			Target: proptools.StringPtr("/vendor/odm/etc"),
+			Name:   proptools.StringPtr("odm/etc"),
+		},
+		{
+			Target: proptools.StringPtr("/vendor/odm/firmware"),
+			Name:   proptools.StringPtr("odm/firmware"),
+		},
+		{
+			Target: proptools.StringPtr("/vendor/odm/framework"),
+			Name:   proptools.StringPtr("odm/framework"),
+		},
+		{
+			Target: proptools.StringPtr("/vendor/odm/lib"),
+			Name:   proptools.StringPtr("odm/lib"),
+		},
+		{
+			Target: proptools.StringPtr("/vendor/odm/lib64"),
+			Name:   proptools.StringPtr("odm/lib64"),
+		},
+		{
+			Target: proptools.StringPtr("/vendor/odm/overlay"),
+			Name:   proptools.StringPtr("odm/overlay"),
+		},
+		{
+			Target: proptools.StringPtr("/vendor/odm/priv-app"),
+			Name:   proptools.StringPtr("odm/priv-app"),
+		},
+		{
+			Target: proptools.StringPtr("/vendor/odm/usr"),
+			Name:   proptools.StringPtr("odm/usr"),
+		},
+		// For Treble Generic System Image (GSI), system-as-root GSI needs to work on
+		// both devices with and without /odm_dlkm partition. Those symlinks are for
+		// devices without /odm_dlkm partition. For devices with /odm_dlkm
+		// partition, mount odm_dlkm.img under /odm_dlkm will hide those symlinks.
+		// Note that /odm_dlkm/lib is omitted because odm DLKMs should be accessed
+		// via /odm/lib/modules directly. All of this also applies to the vendor_dlkm symlink
+		{
+			Target: proptools.StringPtr("/odm/odm_dlkm/etc"),
+			Name:   proptools.StringPtr("odm_dlkm/etc"),
+		},
+		{
+			Target: proptools.StringPtr("/vendor/vendor_dlkm/etc"),
+			Name:   proptools.StringPtr("vendor_dlkm/etc"),
+		},
+	}
+
+	// Common directories between partitions that may be listed as `Dirs` property in the
+	// filesystem module.
+	commonPartitionDirs = []string{
+		// From generic_rootdirs in build/make/target/product/generic/Android.bp
+		"apex",
+		"bootstrap-apex",
+		"config",
+		"data",
+		"data_mirror",
+		"debug_ramdisk",
+		"dev",
+		"linkerconfig",
+		"metadata",
+		"mnt",
+		"odm",
+		"odm_dlkm",
+		"oem",
+		"postinstall",
+		"proc",
+		"second_stage_resources",
+		"storage",
+		"sys",
+		"system",
+		"system_dlkm",
+		"tmp",
+		"vendor",
+		"vendor_dlkm",
+
+		// from android_rootdirs in build/make/target/product/generic/Android.bp
+		"system_ext",
+		"product",
+	}
+)
diff --git a/fsgen/filesystem_creator.go b/fsgen/filesystem_creator.go
index a9f4256..c2721d2 100644
--- a/fsgen/filesystem_creator.go
+++ b/fsgen/filesystem_creator.go
@@ -15,13 +15,19 @@
 package fsgen
 
 import (
-	"android/soong/android"
-	"android/soong/filesystem"
 	"crypto/sha256"
 	"fmt"
+	"path/filepath"
+	"slices"
 	"strconv"
+	"strings"
+
+	"android/soong/android"
+	"android/soong/filesystem"
+	"android/soong/kernel"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/parser"
 	"github.com/google/blueprint/proptools"
 )
 
@@ -33,11 +39,97 @@
 
 func registerBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("soong_filesystem_creator", filesystemCreatorFactory)
+	ctx.PreDepsMutators(RegisterCollectFileSystemDepsMutators)
+}
+
+type generatedPartitionData struct {
+	partitionType string
+	moduleName    string
+	// supported is true if the module was created successfully, false if there was some problem
+	// and the module couldn't be created.
+	supported   bool
+	handwritten bool
+}
+
+type allGeneratedPartitionData []generatedPartitionData
+
+func (d allGeneratedPartitionData) moduleNames() []string {
+	var result []string
+	for _, data := range d {
+		if data.supported {
+			result = append(result, data.moduleName)
+		}
+	}
+	return result
+}
+
+func (d allGeneratedPartitionData) types() []string {
+	var result []string
+	for _, data := range d {
+		if data.supported {
+			result = append(result, data.partitionType)
+		}
+	}
+	return result
+}
+
+func (d allGeneratedPartitionData) unsupportedTypes() []string {
+	var result []string
+	for _, data := range d {
+		if !data.supported {
+			result = append(result, data.partitionType)
+		}
+	}
+	return result
+}
+
+func (d allGeneratedPartitionData) names() []string {
+	var result []string
+	for _, data := range d {
+		if data.supported {
+			result = append(result, data.moduleName)
+		}
+	}
+	return result
+}
+
+func (d allGeneratedPartitionData) nameForType(ty string) string {
+	for _, data := range d {
+		if data.supported && data.partitionType == ty {
+			return data.moduleName
+		}
+	}
+	return ""
+}
+
+func (d allGeneratedPartitionData) typeForName(name string) string {
+	for _, data := range d {
+		if data.supported && data.moduleName == name {
+			return data.partitionType
+		}
+	}
+	return ""
+}
+
+func (d allGeneratedPartitionData) isHandwritten(name string) bool {
+	for _, data := range d {
+		if data.supported && data.moduleName == name {
+			return data.handwritten
+		}
+	}
+	return false
 }
 
 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"`
+
+	Boot_image        string `blueprint:"mutated" android:"path_device_first"`
+	Vendor_boot_image string `blueprint:"mutated" android:"path_device_first"`
+	Init_boot_image   string `blueprint:"mutated" android:"path_device_first"`
+	Super_image       string `blueprint:"mutated" android:"path_device_first"`
 }
 
 type filesystemCreator struct {
@@ -49,128 +141,971 @@
 func filesystemCreatorFactory() android.Module {
 	module := &filesystemCreator{}
 
-	android.InitAndroidModule(module)
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	module.AddProperties(&module.properties)
 	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
+		generatedPrebuiltEtcModuleNames := createPrebuiltEtcModules(ctx)
+		avbpubkeyGenerated := createAvbpubkeyModule(ctx)
+		createFsGenState(ctx, generatedPrebuiltEtcModuleNames, avbpubkeyGenerated)
+		module.createAvbKeyFilegroups(ctx)
+		module.createMiscFilegroups(ctx)
 		module.createInternalModules(ctx)
 	})
 
 	return module
 }
 
-func (f *filesystemCreator) createInternalModules(ctx android.LoadHookContext) {
-	for _, partitionType := range []string{"system"} {
-		if f.createPartition(ctx, partitionType) {
-			f.properties.Generated_partition_types = append(f.properties.Generated_partition_types, partitionType)
-		} else {
-			f.properties.Unsupported_partition_types = append(f.properties.Unsupported_partition_types, partitionType)
-		}
+func generatedPartitions(ctx android.EarlyModuleContext) allGeneratedPartitionData {
+	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+
+	var result allGeneratedPartitionData
+	addGenerated := func(ty string) {
+		result = append(result, generatedPartitionData{
+			partitionType: ty,
+			moduleName:    generatedModuleNameForPartition(ctx.Config(), ty),
+			supported:     true,
+		})
 	}
+
+	if ctx.Config().UseSoongSystemImage() {
+		if ctx.Config().SoongDefinedSystemImage() == "" {
+			panic("PRODUCT_SOONG_DEFINED_SYSTEM_IMAGE must be set if USE_SOONG_DEFINED_SYSTEM_IMAGE is true")
+		}
+		result = append(result, generatedPartitionData{
+			partitionType: "system",
+			moduleName:    ctx.Config().SoongDefinedSystemImage(),
+			supported:     true,
+			handwritten:   true,
+		})
+	} else {
+		addGenerated("system")
+	}
+	if ctx.DeviceConfig().SystemExtPath() == "system_ext" {
+		addGenerated("system_ext")
+	}
+	if ctx.DeviceConfig().BuildingVendorImage() && ctx.DeviceConfig().VendorPath() == "vendor" {
+		addGenerated("vendor")
+	}
+	if ctx.DeviceConfig().BuildingProductImage() && ctx.DeviceConfig().ProductPath() == "product" {
+		addGenerated("product")
+	}
+	if ctx.DeviceConfig().BuildingOdmImage() && ctx.DeviceConfig().OdmPath() == "odm" {
+		addGenerated("odm")
+	}
+	if ctx.DeviceConfig().BuildingUserdataImage() && ctx.DeviceConfig().UserdataPath() == "data" {
+		addGenerated("userdata")
+	}
+	if partitionVars.BuildingSystemDlkmImage {
+		addGenerated("system_dlkm")
+	}
+	if partitionVars.BuildingVendorDlkmImage {
+		addGenerated("vendor_dlkm")
+	}
+	if partitionVars.BuildingOdmDlkmImage {
+		addGenerated("odm_dlkm")
+	}
+	if partitionVars.BuildingRamdiskImage {
+		addGenerated("ramdisk")
+	}
+	if buildingVendorBootImage(partitionVars) {
+		addGenerated("vendor_ramdisk")
+	}
+	if ctx.DeviceConfig().BuildingRecoveryImage() && ctx.DeviceConfig().RecoveryPath() == "recovery" {
+		addGenerated("recovery")
+	}
+	return result
 }
 
-func (f *filesystemCreator) generatedModuleNameForPartition(cfg android.Config, partitionType string) string {
+func (f *filesystemCreator) createInternalModules(ctx android.LoadHookContext) {
+	partitions := generatedPartitions(ctx)
+	for i := range partitions {
+		f.createPartition(ctx, partitions, &partitions[i])
+	}
+	// Create android_info.prop
+	f.createAndroidInfo(ctx)
+
+	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+	dtbImg := createDtbImgFilegroup(ctx)
+
+	if buildingBootImage(partitionVars) {
+		if createBootImage(ctx, dtbImg) {
+			f.properties.Boot_image = ":" + generatedModuleNameForPartition(ctx.Config(), "boot")
+		} else {
+			f.properties.Unsupported_partition_types = append(f.properties.Unsupported_partition_types, "boot")
+		}
+	}
+	if buildingVendorBootImage(partitionVars) {
+		if createVendorBootImage(ctx, dtbImg) {
+			f.properties.Vendor_boot_image = ":" + generatedModuleNameForPartition(ctx.Config(), "vendor_boot")
+		} else {
+			f.properties.Unsupported_partition_types = append(f.properties.Unsupported_partition_types, "vendor_boot")
+		}
+	}
+	if buildingInitBootImage(partitionVars) {
+		if createInitBootImage(ctx) {
+			f.properties.Init_boot_image = ":" + generatedModuleNameForPartition(ctx.Config(), "init_boot")
+		} else {
+			f.properties.Unsupported_partition_types = append(f.properties.Unsupported_partition_types, "init_boot")
+		}
+	}
+
+	var systemOtherImageName string
+	if buildingSystemOtherImage(partitionVars) {
+		systemModule := partitions.nameForType("system")
+		systemOtherImageName = generatedModuleNameForPartition(ctx.Config(), "system_other")
+		ctx.CreateModule(
+			filesystem.SystemOtherImageFactory,
+			&filesystem.SystemOtherImageProperties{
+				System_image:                    &systemModule,
+				Preinstall_dexpreopt_files_from: partitions.moduleNames(),
+			},
+			&struct {
+				Name *string
+			}{
+				Name: proptools.StringPtr(systemOtherImageName),
+			},
+		)
+	}
+
+	for _, x := range f.createVbmetaPartitions(ctx, partitions) {
+		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)
+	}
+
+	var superImageSubpartitions []string
+	if buildingSuperImage(partitionVars) {
+		superImageSubpartitions = createSuperImage(ctx, partitions, partitionVars, systemOtherImageName)
+		f.properties.Super_image = ":" + generatedModuleNameForPartition(ctx.Config(), "super")
+	}
+
+	ctx.Config().Get(fsGenStateOnceKey).(*FsGenState).soongGeneratedPartitions = partitions
+	f.createDeviceModule(ctx, partitions, f.properties.Vbmeta_module_names, superImageSubpartitions)
+}
+
+func generatedModuleName(cfg android.Config, suffix string) string {
 	prefix := "soong"
 	if cfg.HasDeviceProduct() {
 		prefix = cfg.DeviceProduct()
 	}
-	return fmt.Sprintf("%s_generated_%s_image", prefix, partitionType)
+	return fmt.Sprintf("%s_generated_%s", prefix, suffix)
 }
 
-// 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 {
-	baseProps := &struct {
-		Name *string
-	}{
-		Name: proptools.StringPtr(f.generatedModuleNameForPartition(ctx.Config(), partitionType)),
+func generatedModuleNameForPartition(cfg android.Config, partitionType string) string {
+	return generatedModuleName(cfg, fmt.Sprintf("%s_image", partitionType))
+}
+
+func buildingSystemOtherImage(partitionVars android.PartitionVariables) bool {
+	// TODO: Recreate this logic from make instead of just depending on the final result variable:
+	// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/board_config.mk;l=429;drc=15a0df840e7093f65518003ab80cf24a3d9e8e6a
+	return partitionVars.BuildingSystemOtherImage
+}
+
+func (f *filesystemCreator) createBootloaderFilegroup(ctx android.LoadHookContext) (string, bool) {
+	bootloaderPath := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.PrebuiltBootloader
+	if len(bootloaderPath) == 0 {
+		return "", false
 	}
 
+	bootloaderFilegroupName := generatedModuleName(ctx.Config(), "bootloader")
+	filegroupProps := &struct {
+		Name       *string
+		Srcs       []string
+		Visibility []string
+	}{
+		Name:       proptools.StringPtr(bootloaderFilegroupName),
+		Srcs:       []string{bootloaderPath},
+		Visibility: []string{"//visibility:public"},
+	}
+	ctx.CreateModuleInDirectory(android.FileGroupFactory, ".", filegroupProps)
+	return bootloaderFilegroupName, true
+}
+
+func (f *filesystemCreator) createReleaseToolsFilegroup(ctx android.LoadHookContext) (string, bool) {
+	releaseToolsDir := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.ReleaseToolsExtensionDir
+	if releaseToolsDir == "" {
+		return "", false
+	}
+
+	releaseToolsFilegroupName := generatedModuleName(ctx.Config(), "releasetools")
+	filegroupProps := &struct {
+		Name       *string
+		Srcs       []string
+		Visibility []string
+	}{
+		Name:       proptools.StringPtr(releaseToolsFilegroupName),
+		Srcs:       []string{"releasetools.py"},
+		Visibility: []string{"//visibility:public"},
+	}
+	ctx.CreateModuleInDirectory(android.FileGroupFactory, releaseToolsDir, filegroupProps)
+	return releaseToolsFilegroupName, true
+}
+
+func (f *filesystemCreator) createDeviceModule(
+	ctx android.LoadHookContext,
+	partitions allGeneratedPartitionData,
+	vbmetaPartitions []string,
+	superImageSubPartitions []string,
+) {
+	baseProps := &struct {
+		Name         *string
+		Android_info *string
+	}{
+		Name:         proptools.StringPtr(generatedModuleName(ctx.Config(), "device")),
+		Android_info: proptools.StringPtr(":" + generatedModuleName(ctx.Config(), "android_info.prop{.txt}")),
+	}
+
+	// Currently, only the system and system_ext partition module is created.
+	partitionProps := &filesystem.PartitionNameProperties{}
+	if f.properties.Super_image != "" {
+		partitionProps.Super_partition_name = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "super"))
+	}
+	if modName := partitions.nameForType("system"); modName != "" && !android.InList("system", superImageSubPartitions) {
+		partitionProps.System_partition_name = proptools.StringPtr(modName)
+	}
+	if modName := partitions.nameForType("system_ext"); modName != "" && !android.InList("system_ext", superImageSubPartitions) {
+		partitionProps.System_ext_partition_name = proptools.StringPtr(modName)
+	}
+	if modName := partitions.nameForType("vendor"); modName != "" && !android.InList("vendor", superImageSubPartitions) {
+		partitionProps.Vendor_partition_name = proptools.StringPtr(modName)
+	}
+	if modName := partitions.nameForType("product"); modName != "" && !android.InList("product", superImageSubPartitions) {
+		partitionProps.Product_partition_name = proptools.StringPtr(modName)
+	}
+	if modName := partitions.nameForType("odm"); modName != "" && !android.InList("odm", superImageSubPartitions) {
+		partitionProps.Odm_partition_name = proptools.StringPtr(modName)
+	}
+	if modName := partitions.nameForType("userdata"); modName != "" {
+		partitionProps.Userdata_partition_name = proptools.StringPtr(modName)
+	}
+	if modName := partitions.nameForType("recovery"); modName != "" {
+		partitionProps.Recovery_partition_name = proptools.StringPtr(modName)
+	}
+	if modName := partitions.nameForType("system_dlkm"); modName != "" && !android.InList("system_dlkm", superImageSubPartitions) {
+		partitionProps.System_dlkm_partition_name = proptools.StringPtr(modName)
+	}
+	if modName := partitions.nameForType("vendor_dlkm"); modName != "" && !android.InList("vendor_dlkm", superImageSubPartitions) {
+		partitionProps.Vendor_dlkm_partition_name = proptools.StringPtr(modName)
+	}
+	if modName := partitions.nameForType("odm_dlkm"); modName != "" && !android.InList("odm_dlkm", superImageSubPartitions) {
+		partitionProps.Odm_dlkm_partition_name = proptools.StringPtr(modName)
+	}
+	if f.properties.Boot_image != "" {
+		partitionProps.Boot_partition_name = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "boot"))
+	}
+	if f.properties.Vendor_boot_image != "" {
+		partitionProps.Vendor_boot_partition_name = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "vendor_boot"))
+	}
+	if f.properties.Init_boot_image != "" {
+		partitionProps.Init_boot_partition_name = proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "init_boot"))
+	}
+	partitionProps.Vbmeta_partitions = vbmetaPartitions
+
+	deviceProps := &filesystem.DeviceProperties{
+		Main_device:               proptools.BoolPtr(true),
+		Ab_ota_updater:            proptools.BoolPtr(ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.AbOtaUpdater),
+		Ab_ota_partitions:         ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.AbOtaPartitions,
+		Ab_ota_postinstall_config: ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.AbOtaPostInstallConfig,
+		Ramdisk_node_list:         proptools.StringPtr(":ramdisk_node_list"),
+	}
+
+	if bootloader, ok := f.createBootloaderFilegroup(ctx); ok {
+		deviceProps.Bootloader = proptools.StringPtr(":" + bootloader)
+	}
+	if releaseTools, ok := f.createReleaseToolsFilegroup(ctx); ok {
+		deviceProps.Releasetools_extension = proptools.StringPtr(":" + releaseTools)
+	}
+
+	ctx.CreateModule(filesystem.AndroidDeviceFactory, baseProps, partitionProps, deviceProps)
+}
+
+func partitionSpecificFsProps(ctx android.EarlyModuleContext, partitions allGeneratedPartitionData, fsProps *filesystem.FilesystemProperties, partitionType string) {
+	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+	switch partitionType {
+	case "system":
+		fsProps.Build_logtags = proptools.BoolPtr(true)
+		// https://source.corp.google.com/h/googleplex-android/platform/build//639d79f5012a6542ab1f733b0697db45761ab0f3:core/packaging/flags.mk;l=21;drc=5ba8a8b77507f93aa48cc61c5ba3f31a4d0cbf37;bpv=1;bpt=0
+		fsProps.Gen_aconfig_flags_pb = proptools.BoolPtr(true)
+		// Identical to that of the aosp_shared_system_image
+		if partitionVars.ProductFsverityGenerateMetadata {
+			fsProps.Fsverity.Inputs = proptools.NewSimpleConfigurable([]string{
+				"etc/boot-image.prof",
+				"etc/dirty-image-objects",
+				"etc/preloaded-classes",
+				"etc/classpaths/*.pb",
+				"framework/*",
+				"framework/*/*",     // framework/{arch}
+				"framework/oat/*/*", // framework/oat/{arch}
+			})
+			fsProps.Fsverity.Libs = proptools.NewSimpleConfigurable([]string{":framework-res{.export-package.apk}"})
+		}
+		fsProps.Symlinks = commonSymlinksFromRoot
+		fsProps.Symlinks = append(fsProps.Symlinks,
+			[]filesystem.SymlinkDefinition{
+				{
+					Target: proptools.StringPtr("/data/cache"),
+					Name:   proptools.StringPtr("cache"),
+				},
+				{
+					Target: proptools.StringPtr("/storage/self/primary"),
+					Name:   proptools.StringPtr("sdcard"),
+				},
+				{
+					Target: proptools.StringPtr("/system_dlkm/lib/modules"),
+					Name:   proptools.StringPtr("system/lib/modules"),
+				},
+				{
+					Target: proptools.StringPtr("/product"),
+					Name:   proptools.StringPtr("system/product"),
+				},
+				{
+					Target: proptools.StringPtr("/system_ext"),
+					Name:   proptools.StringPtr("system/system_ext"),
+				},
+				{
+					Target: proptools.StringPtr("/vendor"),
+					Name:   proptools.StringPtr("system/vendor"),
+				},
+			}...,
+		)
+		fsProps.Base_dir = proptools.StringPtr("system")
+		fsProps.Dirs = proptools.NewSimpleConfigurable(commonPartitionDirs)
+		fsProps.Security_patch = proptools.StringPtr(ctx.Config().PlatformSecurityPatch())
+
+		if systemExtName := partitions.nameForType("system_ext"); systemExtName != "" {
+			fsProps.Import_aconfig_flags_from = []string{systemExtName}
+		}
+		fsProps.Stem = proptools.StringPtr("system.img")
+	case "system_ext":
+		if partitionVars.ProductFsverityGenerateMetadata {
+			fsProps.Fsverity.Inputs = proptools.NewSimpleConfigurable([]string{
+				"framework/*",
+				"framework/*/*",     // framework/{arch}
+				"framework/oat/*/*", // framework/oat/{arch}
+			})
+			fsProps.Fsverity.Libs = proptools.NewSimpleConfigurable([]string{":framework-res{.export-package.apk}"})
+		}
+		fsProps.Security_patch = proptools.StringPtr(ctx.Config().PlatformSecurityPatch())
+		fsProps.Stem = proptools.StringPtr("system_ext.img")
+	case "product":
+		fsProps.Gen_aconfig_flags_pb = proptools.BoolPtr(true)
+		fsProps.Android_filesystem_deps.System = proptools.StringPtr(partitions.nameForType("system"))
+		if systemExtName := partitions.nameForType("system_ext"); systemExtName != "" {
+			fsProps.Android_filesystem_deps.System_ext = proptools.StringPtr(systemExtName)
+		}
+		fsProps.Security_patch = proptools.StringPtr(ctx.Config().PlatformSecurityPatch())
+		fsProps.Stem = proptools.StringPtr("product.img")
+	case "vendor":
+		fsProps.Gen_aconfig_flags_pb = proptools.BoolPtr(true)
+		fsProps.Symlinks = []filesystem.SymlinkDefinition{
+			filesystem.SymlinkDefinition{
+				Target: proptools.StringPtr("/odm"),
+				Name:   proptools.StringPtr("odm"),
+			},
+			filesystem.SymlinkDefinition{
+				Target: proptools.StringPtr("/vendor_dlkm/lib/modules"),
+				Name:   proptools.StringPtr("lib/modules"),
+			},
+		}
+		fsProps.Android_filesystem_deps.System = proptools.StringPtr(partitions.nameForType("system"))
+		if systemExtName := partitions.nameForType("system_ext"); systemExtName != "" {
+			fsProps.Android_filesystem_deps.System_ext = proptools.StringPtr(systemExtName)
+		}
+		fsProps.Security_patch = proptools.StringPtr(partitionVars.VendorSecurityPatch)
+		fsProps.Stem = proptools.StringPtr("vendor.img")
+	case "odm":
+		fsProps.Symlinks = []filesystem.SymlinkDefinition{
+			filesystem.SymlinkDefinition{
+				Target: proptools.StringPtr("/odm_dlkm/lib/modules"),
+				Name:   proptools.StringPtr("lib/modules"),
+			},
+		}
+		fsProps.Security_patch = proptools.StringPtr(partitionVars.OdmSecurityPatch)
+		fsProps.Stem = proptools.StringPtr("odm.img")
+	case "userdata":
+		fsProps.Stem = proptools.StringPtr("userdata.img")
+		if vars, ok := partitionVars.PartitionQualifiedVariables["userdata"]; ok {
+			parsed, err := strconv.ParseInt(vars.BoardPartitionSize, 10, 64)
+			if err != nil {
+				panic(fmt.Sprintf("Partition size must be an int, got %s", vars.BoardPartitionSize))
+			}
+			fsProps.Partition_size = &parsed
+			// Disable avb for userdata partition
+			fsProps.Use_avb = nil
+		}
+		// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=2265;drc=7f50a123045520f2c5e18e9eb4e83f92244a1459
+		if s, err := strconv.ParseBool(partitionVars.ProductFsCasefold); err == nil {
+			fsProps.Support_casefolding = proptools.BoolPtr(s)
+		} else if len(partitionVars.ProductFsCasefold) > 0 {
+			ctx.ModuleErrorf("Unrecognized PRODUCT_FS_CASEFOLD value %s", partitionVars.ProductFsCasefold)
+		}
+		if s, err := strconv.ParseBool(partitionVars.ProductQuotaProjid); err == nil {
+			fsProps.Support_project_quota = proptools.BoolPtr(s)
+		} else if len(partitionVars.ProductQuotaProjid) > 0 {
+			ctx.ModuleErrorf("Unrecognized PRODUCT_QUOTA_PROJID value %s", partitionVars.ProductQuotaProjid)
+		}
+		if s, err := strconv.ParseBool(partitionVars.ProductFsCompression); err == nil {
+			fsProps.Enable_compression = proptools.BoolPtr(s)
+		} else if len(partitionVars.ProductFsCompression) > 0 {
+			ctx.ModuleErrorf("Unrecognized PRODUCT_FS_COMPRESSION value %s", partitionVars.ProductFsCompression)
+		}
+
+	case "ramdisk":
+		// Following the logic in https://cs.android.com/android/platform/superproject/main/+/c3c5063df32748a8806ce5da5dd0db158eab9ad9:build/make/core/Makefile;l=1307
+		fsProps.Dirs = android.NewSimpleConfigurable([]string{
+			"debug_ramdisk",
+			"dev",
+			"metadata",
+			"mnt",
+			"proc",
+			"second_stage_resources",
+			"sys",
+		})
+		if partitionVars.BoardUsesGenericKernelImage {
+			fsProps.Dirs.AppendSimpleValue([]string{
+				"first_stage_ramdisk/debug_ramdisk",
+				"first_stage_ramdisk/dev",
+				"first_stage_ramdisk/metadata",
+				"first_stage_ramdisk/mnt",
+				"first_stage_ramdisk/proc",
+				"first_stage_ramdisk/second_stage_resources",
+				"first_stage_ramdisk/sys",
+			})
+		}
+		fsProps.Stem = proptools.StringPtr("ramdisk.img")
+	case "recovery":
+		dirs := append(commonPartitionDirs, []string{
+			"sdcard",
+		}...)
+
+		dirsWithRoot := make([]string, len(dirs))
+		for i, dir := range dirs {
+			dirsWithRoot[i] = filepath.Join("root", dir)
+		}
+
+		fsProps.Dirs = proptools.NewSimpleConfigurable(dirsWithRoot)
+		fsProps.Symlinks = symlinksWithNamePrefix(append(commonSymlinksFromRoot, filesystem.SymlinkDefinition{
+			Target: proptools.StringPtr("prop.default"),
+			Name:   proptools.StringPtr("default.prop"),
+		}), "root")
+		fsProps.Stem = proptools.StringPtr("recovery.img")
+	case "system_dlkm":
+		fsProps.Security_patch = proptools.StringPtr(partitionVars.SystemDlkmSecurityPatch)
+		fsProps.Stem = proptools.StringPtr("system_dlkm.img")
+	case "vendor_dlkm":
+		fsProps.Security_patch = proptools.StringPtr(partitionVars.VendorDlkmSecurityPatch)
+		fsProps.Stem = proptools.StringPtr("vendor_dlkm.img")
+	case "odm_dlkm":
+		fsProps.Security_patch = proptools.StringPtr(partitionVars.OdmDlkmSecurityPatch)
+		fsProps.Stem = proptools.StringPtr("odm_dlkm.img")
+	case "vendor_ramdisk":
+		if recoveryName := partitions.nameForType("recovery"); recoveryName != "" {
+			fsProps.Include_files_of = []string{recoveryName}
+		}
+		fsProps.Stem = proptools.StringPtr("vendor_ramdisk.img")
+	}
+}
+
+var (
+	dlkmPartitions = []string{
+		"system_dlkm",
+		"vendor_dlkm",
+		"odm_dlkm",
+	}
+)
+
+// Creates a soong module to build the given partition.
+func (f *filesystemCreator) createPartition(ctx android.LoadHookContext, partitions allGeneratedPartitionData, partition *generatedPartitionData) {
+	// Nextgen team's handwritten soong system image, don't need to create anything ourselves
+	if partition.partitionType == "system" && ctx.Config().UseSoongSystemImage() {
+		return
+	}
+
+	baseProps := generateBaseProps(proptools.StringPtr(partition.moduleName))
+
+	fsProps, supported := generateFsProps(ctx, partitions, partition.partitionType)
+	if !supported {
+		partition.supported = false
+		return
+	}
+
+	partitionType := partition.partitionType
+	if partitionType == "vendor" || partitionType == "product" || partitionType == "system" {
+		fsProps.Linker_config.Gen_linker_config = proptools.BoolPtr(true)
+		if partitionType != "system" {
+			fsProps.Linker_config.Linker_config_srcs = f.createLinkerConfigSourceFilegroups(ctx, partitionType)
+		}
+	}
+
+	if android.InList(partitionType, append(dlkmPartitions, "vendor_ramdisk")) {
+		f.createPrebuiltKernelModules(ctx, partitionType)
+	}
+
+	var module android.Module
+	if partitionType == "system" {
+		module = ctx.CreateModule(filesystem.SystemImageFactory, baseProps, fsProps)
+	} else {
+		// Explicitly set the partition.
+		fsProps.Partition_type = proptools.StringPtr(partitionType)
+		module = ctx.CreateModule(filesystem.FilesystemFactory, baseProps, fsProps)
+	}
+	module.HideFromMake()
+	if partitionType == "vendor" {
+		f.createVendorBuildProp(ctx)
+	}
+}
+
+// Creates filegroups for the files specified in BOARD_(partition_)AVB_KEY_PATH
+func (f *filesystemCreator) createAvbKeyFilegroups(ctx android.LoadHookContext) {
+	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+	var files []string
+
+	if len(partitionVars.BoardAvbKeyPath) > 0 {
+		files = append(files, partitionVars.BoardAvbKeyPath)
+	}
+	for _, partition := range android.SortedKeys(partitionVars.PartitionQualifiedVariables) {
+		specificPartitionVars := partitionVars.PartitionQualifiedVariables[partition]
+		if len(specificPartitionVars.BoardAvbKeyPath) > 0 {
+			files = append(files, specificPartitionVars.BoardAvbKeyPath)
+		}
+	}
+
+	fsGenState := ctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
+	for _, file := range files {
+		if _, ok := fsGenState.avbKeyFilegroups[file]; ok {
+			continue
+		}
+		if file == "external/avb/test/data/testkey_rsa4096.pem" {
+			// There already exists a checked-in filegroup for this commonly-used key, just use that
+			fsGenState.avbKeyFilegroups[file] = "avb_testkey_rsa4096"
+			continue
+		}
+		dir := filepath.Dir(file)
+		base := filepath.Base(file)
+		name := fmt.Sprintf("avb_key_%x", strings.ReplaceAll(file, "/", "_"))
+		ctx.CreateModuleInDirectory(
+			android.FileGroupFactory,
+			dir,
+			&struct {
+				Name       *string
+				Srcs       []string
+				Visibility []string
+			}{
+				Name:       proptools.StringPtr(name),
+				Srcs:       []string{base},
+				Visibility: []string{"//visibility:public"},
+			},
+		)
+		fsGenState.avbKeyFilegroups[file] = name
+	}
+}
+
+// Creates filegroups for miscellaneous other files
+func (f *filesystemCreator) createMiscFilegroups(ctx android.LoadHookContext) {
+	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+
+	if partitionVars.BoardErofsCompressorHints != "" {
+		dir := filepath.Dir(partitionVars.BoardErofsCompressorHints)
+		base := filepath.Base(partitionVars.BoardErofsCompressorHints)
+		ctx.CreateModuleInDirectory(
+			android.FileGroupFactory,
+			dir,
+			&struct {
+				Name       *string
+				Srcs       []string
+				Visibility []string
+			}{
+				Name:       proptools.StringPtr("soong_generated_board_erofs_compress_hints_filegroup"),
+				Srcs:       []string{base},
+				Visibility: []string{"//visibility:public"},
+			},
+		)
+	}
+}
+
+// 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
+		Vendor_ramdisk       *bool
+		Load_by_default      *bool
+		Blocklist_file       *string
+		Options_file         *string
+		Strip_debug_symbols  *bool
+	}{
+		Name:                proptools.StringPtr(name),
+		Strip_debug_symbols: proptools.BoolPtr(false),
+	}
+	switch partitionType {
+	case "system_dlkm":
+		props.Srcs = android.ExistentPathsForSources(ctx, ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.SystemKernelModules).Strings()
+		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 = android.ExistentPathsForSources(ctx, ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.VendorKernelModules).Strings()
+		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 = android.ExistentPathsForSources(ctx, ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.OdmKernelModules).Strings()
+		props.Odm_dlkm_specific = proptools.BoolPtr(true)
+		if blocklistFile := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.OdmKernelBlocklistFile; blocklistFile != "" {
+			props.Blocklist_file = proptools.StringPtr(blocklistFile)
+		}
+	case "vendor_ramdisk":
+		props.Srcs = android.ExistentPathsForSources(ctx, ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.VendorRamdiskKernelModules).Strings()
+		props.Vendor_ramdisk = proptools.BoolPtr(true)
+		if blocklistFile := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.VendorRamdiskKernelBlocklistFile; blocklistFile != "" {
+			props.Blocklist_file = proptools.StringPtr(blocklistFile)
+		}
+		if optionsFile := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.VendorRamdiskKernelOptionsFile; optionsFile != "" {
+			props.Options_file = proptools.StringPtr(optionsFile)
+		}
+
+	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 an android_info module. This will be used to create /vendor/build.prop
+func (f *filesystemCreator) createAndroidInfo(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
+		Stem                  *string
+	}{
+		Name:             proptools.StringPtr(generatedModuleName(ctx.Config(), "android_info.prop")),
+		Board_info_files: partitionVars.BoardInfoFiles,
+		Stem:             proptools.StringPtr("android-info.txt"),
+	}
+	if len(androidInfoProps.Board_info_files) == 0 {
+		androidInfoProps.Bootloader_board_name = proptools.StringPtr(partitionVars.BootLoaderBoardName)
+	}
+	androidInfoProp := ctx.CreateModuleInDirectory(
+		android.AndroidInfoFactory,
+		".",
+		androidInfoProps,
+	)
+	androidInfoProp.HideFromMake()
+}
+
+func (f *filesystemCreator) createVendorBuildProp(ctx android.LoadHookContext) {
+	vendorBuildProps := &struct {
+		Name           *string
+		Vendor         *bool
+		Stem           *string
+		Product_config *string
+		Android_info   *string
+		Licenses       []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(":" + generatedModuleName(ctx.Config(), "android_info.prop")),
+		Licenses:       []string{"Android-Apache-2.0"},
+	}
+	vendorBuildProp := ctx.CreateModule(
+		android.BuildPropFactory,
+		vendorBuildProps,
+	)
+	vendorBuildProp.HideFromMake()
+}
+
+func createRecoveryBuildProp(ctx android.LoadHookContext) string {
+	moduleName := generatedModuleName(ctx.Config(), "recovery-prop.default")
+
+	var vendorBuildProp *string
+	if ctx.DeviceConfig().BuildingVendorImage() && ctx.DeviceConfig().VendorPath() == "vendor" {
+		vendorBuildProp = proptools.StringPtr(":" + generatedModuleName(ctx.Config(), "vendor-build.prop"))
+	}
+
+	recoveryBuildProps := &struct {
+		Name                  *string
+		System_build_prop     *string
+		Vendor_build_prop     *string
+		Odm_build_prop        *string
+		Product_build_prop    *string
+		System_ext_build_prop *string
+
+		Recovery        *bool
+		No_full_install *bool
+		Visibility      []string
+	}{
+		Name:                  proptools.StringPtr(moduleName),
+		System_build_prop:     proptools.StringPtr(":system-build.prop"),
+		Vendor_build_prop:     vendorBuildProp,
+		Odm_build_prop:        proptools.StringPtr(":odm-build.prop"),
+		Product_build_prop:    proptools.StringPtr(":product-build.prop"),
+		System_ext_build_prop: proptools.StringPtr(":system_ext-build.prop"),
+
+		Recovery:        proptools.BoolPtr(true),
+		No_full_install: proptools.BoolPtr(true),
+		Visibility:      []string{"//visibility:public"},
+	}
+
+	ctx.CreateModule(android.RecoveryBuildPropModuleFactory, recoveryBuildProps)
+
+	return moduleName
+}
+
+// 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__"},
+	}
+}
+
+func generateFsProps(ctx android.EarlyModuleContext, partitions allGeneratedPartitionData, partitionType string) (*filesystem.FilesystemProperties, bool) {
 	fsProps := &filesystem.FilesystemProperties{}
 
+	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+	var avbInfo avbInfo
+	var fsType string
+	if strings.Contains(partitionType, "ramdisk") {
+		fsType = "compressed_cpio"
+	} else {
+		specificPartitionVars := partitionVars.PartitionQualifiedVariables[partitionType]
+		fsType = specificPartitionVars.BoardFileSystemType
+		avbInfo = getAvbInfo(ctx.Config(), partitionType)
+		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
+		return nil, false
+	}
+
+	if *fsProps.Type == "erofs" {
+		if partitionVars.BoardErofsCompressor != "" {
+			fsProps.Erofs.Compressor = proptools.StringPtr(partitionVars.BoardErofsCompressor)
+		}
+		if partitionVars.BoardErofsCompressorHints != "" {
+			fsProps.Erofs.Compress_hints = proptools.StringPtr(":soong_generated_board_erofs_compress_hints_filegroup")
+		}
+	}
+
 	// Don't build this module on checkbuilds, the soong-built partitions are still in-progress
 	// and sometimes don't build.
 	fsProps.Unchecked_module = proptools.BoolPtr(true)
 
-	partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
-	specificPartitionVars := partitionVars.PartitionQualifiedVariables[partitionType]
-
 	// BOARD_AVB_ENABLE
-	fsProps.Use_avb = proptools.BoolPtr(partitionVars.BoardAvbEnable)
+	fsProps.Use_avb = avbInfo.avbEnable
 	// BOARD_AVB_KEY_PATH
-	fsProps.Avb_private_key = proptools.StringPtr(specificPartitionVars.BoardAvbKeyPath)
+	fsProps.Avb_private_key = avbInfo.avbkeyFilegroup
 	// BOARD_AVB_ALGORITHM
-	fsProps.Avb_algorithm = proptools.StringPtr(specificPartitionVars.BoardAvbAlgorithm)
+	fsProps.Avb_algorithm = avbInfo.avbAlgorithm
 	// BOARD_AVB_SYSTEM_ROLLBACK_INDEX
-	if rollbackIndex, err := strconv.ParseInt(specificPartitionVars.BoardAvbRollbackIndex, 10, 64); err == nil {
-		fsProps.Rollback_index = proptools.Int64Ptr(rollbackIndex)
-	}
+	fsProps.Rollback_index = avbInfo.avbRollbackIndex
+	// BOARD_AVB_SYSTEM_ROLLBACK_INDEX_LOCATION
+	fsProps.Rollback_index_location = avbInfo.avbRollbackIndexLocation
+	fsProps.Avb_hash_algorithm = avbInfo.avbHashAlgorithm
 
 	fsProps.Partition_name = proptools.StringPtr(partitionType)
-	// BOARD_SYSTEMIMAGE_FILE_SYSTEM_TYPE
-	fsProps.Type = proptools.StringPtr(specificPartitionVars.BoardFileSystemType)
-	if *fsProps.Type != "ext4" {
-		// Currently the android_filesystem module type only supports ext4:
-		// https://cs.android.com/android/platform/superproject/main/+/main:build/soong/filesystem/filesystem.go;l=416;drc=98047cfd07944b297a12d173453bc984806760d2
-		return false
+
+	switch partitionType {
+	// The partitions that support file_contexts came from here:
+	// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=2270;drc=ad7cfb56010cb22c3aa0e70cf71c804352553526
+	case "system", "userdata", "cache", "vendor", "product", "system_ext", "odm", "vendor_dlkm", "odm_dlkm", "system_dlkm", "oem":
+		fsProps.Precompiled_file_contexts = proptools.StringPtr(":file_contexts_bin_gen")
 	}
 
-	fsProps.Base_dir = proptools.StringPtr(partitionType)
+	fsProps.Is_auto_generated = proptools.BoolPtr(true)
+	if partitionType != "system" {
+		mountPoint := proptools.StringPtr(partitionType)
+		// https://cs.android.com/android/platform/superproject/main/+/main:build/make/tools/releasetools/build_image.py;l=1012;drc=3f576a753594bad3fc838ccb8b1b72f7efac1d50
+		if partitionType == "userdata" {
+			mountPoint = proptools.StringPtr("data")
+		}
+		fsProps.Mount_point = mountPoint
 
-	fsProps.Gen_aconfig_flags_pb = proptools.BoolPtr(true)
-
-	// Identical to that of the generic_system_image
-	fsProps.Fsverity.Inputs = []string{
-		"etc/boot-image.prof",
-		"etc/dirty-image-objects",
-		"etc/preloaded-classes",
-		"etc/classpaths/*.pb",
-		"framework/*",
-		"framework/*/*",     // framework/{arch}
-		"framework/oat/*/*", // framework/oat/{arch}
 	}
 
-	// system_image properties that are not set:
-	// - filesystemProperties.Avb_hash_algorithm
-	// - filesystemProperties.File_contexts
-	// - filesystemProperties.Dirs
-	// - filesystemProperties.Symlinks
-	// - filesystemProperties.Fake_timestamp
-	// - filesystemProperties.Uuid
-	// - filesystemProperties.Mount_point
-	// - filesystemProperties.Include_make_built_files
-	// - filesystemProperties.Build_logtags
-	// - filesystemProperties.Fsverity.Libs
-	// - systemImageProperties.Linker_config_src
-	var module android.Module
-	if partitionType == "system" {
-		module = ctx.CreateModule(filesystem.SystemImageFactory, baseProps, fsProps)
-	} else {
-		module = ctx.CreateModule(filesystem.FilesystemFactory, baseProps, fsProps)
-	}
-	module.HideFromMake()
-	return true
+	partitionSpecificFsProps(ctx, partitions, fsProps, partitionType)
+
+	return fsProps, true
 }
 
-func (f *filesystemCreator) createDiffTest(ctx android.ModuleContext, partitionType string) android.Path {
-	partitionModuleName := f.generatedModuleNameForPartition(ctx.Config(), partitionType)
-	systemImage := ctx.GetDirectDepWithTag(partitionModuleName, generatedFilesystemDepTag)
-	filesystemInfo, ok := android.OtherModuleProvider(ctx, systemImage, filesystem.FilesystemProvider)
+type avbInfo struct {
+	avbEnable                *bool
+	avbKeyPath               *string
+	avbkeyFilegroup          *string
+	avbAlgorithm             *string
+	avbRollbackIndex         *int64
+	avbRollbackIndexLocation *int64
+	avbMode                  *string
+	avbHashAlgorithm         *string
+}
+
+func getAvbInfo(config android.Config, partitionType string) avbInfo {
+	partitionVars := config.ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+	specificPartitionVars := partitionVars.PartitionQualifiedVariables[partitionType]
+	var result avbInfo
+	boardAvbEnable := partitionVars.BoardAvbEnable
+	if boardAvbEnable {
+		result.avbEnable = proptools.BoolPtr(true)
+		// There are "global" and "specific" copies of a lot of these variables. Sometimes they
+		// choose the specific and then fall back to the global one if it's not set, other times
+		// the global one actually only applies to the vbmeta partition.
+		if partitionType == "vbmeta" {
+			if partitionVars.BoardAvbKeyPath != "" {
+				result.avbKeyPath = proptools.StringPtr(partitionVars.BoardAvbKeyPath)
+			}
+			if partitionVars.BoardAvbRollbackIndex != "" {
+				parsed, err := strconv.ParseInt(partitionVars.BoardAvbRollbackIndex, 10, 64)
+				if err != nil {
+					panic(fmt.Sprintf("Rollback index must be an int, got %s", partitionVars.BoardAvbRollbackIndex))
+				}
+				result.avbRollbackIndex = &parsed
+			}
+		}
+		if specificPartitionVars.BoardAvbKeyPath != "" {
+			result.avbKeyPath = proptools.StringPtr(specificPartitionVars.BoardAvbKeyPath)
+		}
+		if specificPartitionVars.BoardAvbAlgorithm != "" {
+			result.avbAlgorithm = proptools.StringPtr(specificPartitionVars.BoardAvbAlgorithm)
+		} else if partitionVars.BoardAvbAlgorithm != "" {
+			result.avbAlgorithm = proptools.StringPtr(partitionVars.BoardAvbAlgorithm)
+		}
+		if specificPartitionVars.BoardAvbRollbackIndex != "" {
+			parsed, err := strconv.ParseInt(specificPartitionVars.BoardAvbRollbackIndex, 10, 64)
+			if err != nil {
+				panic(fmt.Sprintf("Rollback index must be an int, got %s", specificPartitionVars.BoardAvbRollbackIndex))
+			}
+			result.avbRollbackIndex = &parsed
+		}
+		if specificPartitionVars.BoardAvbRollbackIndexLocation != "" {
+			parsed, err := strconv.ParseInt(specificPartitionVars.BoardAvbRollbackIndexLocation, 10, 64)
+			if err != nil {
+				panic(fmt.Sprintf("Rollback index location must be an int, got %s", specificPartitionVars.BoardAvbRollbackIndexLocation))
+			}
+			result.avbRollbackIndexLocation = &parsed
+		}
+
+		// Make allows you to pass arbitrary arguments to avbtool via this variable, but in practice
+		// it's only used for --hash_algorithm. The soong module has a dedicated property for the
+		// hashtree algorithm, and doesn't allow custom arguments, so just extract the hashtree
+		// algorithm out of the arbitrary arguments.
+		addHashtreeFooterArgs := strings.Split(specificPartitionVars.BoardAvbAddHashtreeFooterArgs, " ")
+		if i := slices.Index(addHashtreeFooterArgs, "--hash_algorithm"); i >= 0 {
+			result.avbHashAlgorithm = &addHashtreeFooterArgs[i+1]
+		}
+
+		result.avbMode = proptools.StringPtr("make_legacy")
+	}
+	if result.avbKeyPath != nil {
+		fsGenState := config.Get(fsGenStateOnceKey).(*FsGenState)
+		filegroup := fsGenState.avbKeyFilegroups[*result.avbKeyPath]
+		result.avbkeyFilegroup = proptools.StringPtr(":" + filegroup)
+	}
+	return result
+}
+
+func (f *filesystemCreator) createFileListDiffTest(ctx android.ModuleContext, partitionType string, partitionModuleName string) android.Path {
+	partitionImage := ctx.GetDirectDepWithTag(partitionModuleName, generatedFilesystemDepTag)
+	filesystemInfo, ok := android.OtherModuleProvider(ctx, partitionImage, filesystem.FilesystemProvider)
 	if !ok {
 		ctx.ModuleErrorf("Expected module %s to provide FileysystemInfo", partitionModuleName)
+		return nil
 	}
 	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
@@ -188,15 +1123,45 @@
 	return file
 }
 
-type systemImageDepTagType struct {
+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))
+	createDiffTest(ctx, diffTestResultFile, soongVbMetaFile, makeVbmetaFile)
+	return diffTestResultFile
+}
+
+func createDiffTest(ctx android.ModuleContext, diffTestResultFile android.WritablePath, file1 android.Path, file2 android.Path) {
+	builder := android.NewRuleBuilder(pctx, ctx)
+	builder.Command().Text("diff").
+		Input(file1).
+		Input(file2)
+	builder.Command().Text("touch").Output(diffTestResultFile)
+	builder.Build("diff test "+diffTestResultFile.String(), "diff test")
+}
+
+type imageDepTagType struct {
 	blueprint.BaseDependencyTag
 }
 
-var generatedFilesystemDepTag systemImageDepTagType
+var generatedFilesystemDepTag imageDepTagType
+var generatedVbmetaPartitionDepTag imageDepTagType
 
 func (f *filesystemCreator) DepsMutator(ctx android.BottomUpMutatorContext) {
-	for _, partitionType := range f.properties.Generated_partition_types {
-		ctx.AddDependency(ctx.Module(), generatedFilesystemDepTag, f.generatedModuleNameForPartition(ctx.Config(), partitionType))
+	for _, name := range ctx.Config().Get(fsGenStateOnceKey).(*FsGenState).soongGeneratedPartitions.names() {
+		ctx.AddDependency(ctx.Module(), generatedFilesystemDepTag, name)
+	}
+	for _, vbmetaModule := range f.properties.Vbmeta_module_names {
+		ctx.AddDependency(ctx.Module(), generatedVbmetaPartitionDepTag, vbmetaModule)
 	}
 }
 
@@ -206,12 +1171,107 @@
 	}
 	f.HideFromMake()
 
-	var diffTestFiles []android.Path
-	for _, partitionType := range f.properties.Generated_partition_types {
-		diffTestFiles = append(diffTestFiles, f.createDiffTest(ctx, partitionType))
+	partitions := ctx.Config().Get(fsGenStateOnceKey).(*FsGenState).soongGeneratedPartitions
+
+	var content strings.Builder
+	generatedBp := android.PathForModuleOut(ctx, "soong_generated_product_config.bp")
+	for _, partition := range partitions.types() {
+		content.WriteString(generateBpContent(ctx, partition))
+		content.WriteString("\n")
 	}
-	for _, partitionType := range f.properties.Unsupported_partition_types {
-		diffTestFiles = append(diffTestFiles, createFailingCommand(ctx, fmt.Sprintf("Couldn't build %s partition", partitionType)))
+	android.WriteFileRule(ctx, generatedBp, content.String())
+
+	ctx.Phony("product_config_to_bp", generatedBp)
+
+	if !ctx.Config().KatiEnabled() {
+		// Cannot diff since the kati packaging rules will not be created.
+		return
+	}
+	var diffTestFiles []android.Path
+	for _, partitionType := range partitions.types() {
+		diffTestFile := f.createFileListDiffTest(ctx, partitionType, partitions.nameForType(partitionType))
+		diffTestFiles = append(diffTestFiles, diffTestFile)
+		ctx.Phony(fmt.Sprintf("soong_generated_%s_filesystem_test", partitionType), diffTestFile)
+	}
+	for _, partitionType := range slices.Concat(partitions.unsupportedTypes(), f.properties.Unsupported_partition_types) {
+		diffTestFile := createFailingCommand(ctx, fmt.Sprintf("Couldn't build %s partition", partitionType))
+		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)
+	}
+	if f.properties.Boot_image != "" {
+		diffTestFile := android.PathForModuleOut(ctx, "boot_diff_test.txt")
+		soongBootImg := android.PathForModuleSrc(ctx, f.properties.Boot_image)
+		makeBootImage := android.PathForArbitraryOutput(ctx, fmt.Sprintf("target/product/%s/boot.img", ctx.Config().DeviceName()))
+		createDiffTest(ctx, diffTestFile, soongBootImg, makeBootImage)
+		diffTestFiles = append(diffTestFiles, diffTestFile)
+		ctx.Phony("soong_generated_boot_filesystem_test", diffTestFile)
+	}
+	if f.properties.Vendor_boot_image != "" {
+		diffTestFile := android.PathForModuleOut(ctx, "vendor_boot_diff_test.txt")
+		soongBootImg := android.PathForModuleSrc(ctx, f.properties.Vendor_boot_image)
+		makeBootImage := android.PathForArbitraryOutput(ctx, fmt.Sprintf("target/product/%s/vendor_boot.img", ctx.Config().DeviceName()))
+		createDiffTest(ctx, diffTestFile, soongBootImg, makeBootImage)
+		diffTestFiles = append(diffTestFiles, diffTestFile)
+		ctx.Phony("soong_generated_vendor_boot_filesystem_test", diffTestFile)
+	}
+	if f.properties.Init_boot_image != "" {
+		diffTestFile := android.PathForModuleOut(ctx, "init_boot_diff_test.txt")
+		soongBootImg := android.PathForModuleSrc(ctx, f.properties.Init_boot_image)
+		makeBootImage := android.PathForArbitraryOutput(ctx, fmt.Sprintf("target/product/%s/init_boot.img", ctx.Config().DeviceName()))
+		createDiffTest(ctx, diffTestFile, soongBootImg, makeBootImage)
+		diffTestFiles = append(diffTestFiles, diffTestFile)
+		ctx.Phony("soong_generated_init_boot_filesystem_test", diffTestFile)
+	}
+	if f.properties.Super_image != "" {
+		diffTestFile := android.PathForModuleOut(ctx, "super_diff_test.txt")
+		soongSuperImg := android.PathForModuleSrc(ctx, f.properties.Super_image)
+		makeSuperImage := android.PathForArbitraryOutput(ctx, fmt.Sprintf("target/product/%s/super.img", ctx.Config().DeviceName()))
+		createDiffTest(ctx, diffTestFile, soongSuperImg, makeSuperImage)
+		diffTestFiles = append(diffTestFiles, diffTestFile)
+		ctx.Phony("soong_generated_super_filesystem_test", diffTestFile)
 	}
 	ctx.Phony("soong_generated_filesystem_tests", diffTestFiles...)
 }
+
+func generateBpContent(ctx android.EarlyModuleContext, partitionType string) string {
+	fsGenState := ctx.Config().Get(fsGenStateOnceKey).(*FsGenState)
+	fsProps, fsTypeSupported := generateFsProps(ctx, fsGenState.soongGeneratedPartitions, partitionType)
+	if !fsTypeSupported {
+		return ""
+	}
+
+	baseProps := generateBaseProps(proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), partitionType)))
+	deps := fsGenState.fsDeps[partitionType]
+	highPriorityDeps := fsGenState.generatedPrebuiltEtcModuleNames
+	depProps := generateDepStruct(*deps, highPriorityDeps)
+
+	result, err := proptools.RepackProperties([]interface{}{baseProps, fsProps, depProps})
+	if err != nil {
+		ctx.ModuleErrorf("%s", err.Error())
+		return ""
+	}
+
+	moduleType := "android_filesystem"
+	if partitionType == "system" {
+		moduleType = "android_system_image"
+	}
+
+	file := &parser.File{
+		Defs: []parser.Definition{
+			&parser.Module{
+				Type: moduleType,
+				Map:  *result,
+			},
+		},
+	}
+	bytes, err := parser.Print(file)
+	if err != nil {
+		ctx.ModuleErrorf(err.Error())
+	}
+	return strings.TrimSpace(string(bytes))
+}
diff --git a/fsgen/filesystem_creator_test.go b/fsgen/filesystem_creator_test.go
index 554b66b..418e48b 100644
--- a/fsgen/filesystem_creator_test.go
+++ b/fsgen/filesystem_creator_test.go
@@ -15,19 +15,34 @@
 package fsgen
 
 import (
-	"android/soong/android"
-	"android/soong/filesystem"
+	"strings"
 	"testing"
 
+	"android/soong/android"
+	"android/soong/etc"
+	"android/soong/filesystem"
+	"android/soong/java"
+
 	"github.com/google/blueprint/proptools"
 )
 
 var prepareForTestWithFsgenBuildComponents = android.FixtureRegisterWithContext(registerBuildComponents)
 
+var prepareMockRamdiksNodeList = android.FixtureMergeMockFs(android.MockFS{
+	"ramdisk_node_list/ramdisk_node_list": nil,
+	"ramdisk_node_list/Android.bp": []byte(`
+		filegroup {
+			name: "ramdisk_node_list",
+			srcs: ["ramdisk_node_list"],
+		}
+	`),
+})
+
 func TestFileSystemCreatorSystemImageProps(t *testing.T) {
 	result := android.GroupFixturePreparers(
 		android.PrepareForIntegrationTestWithAndroid,
 		android.PrepareForTestWithAndroidBuildComponents,
+		android.PrepareForTestWithAllowMissingDependencies,
 		filesystem.PrepareForTestWithFilesystemBuildComponents,
 		prepareForTestWithFsgenBuildComponents,
 		android.FixtureModifyConfig(func(config android.Config) {
@@ -42,8 +57,15 @@
 					},
 				}
 		}),
+		prepareMockRamdiksNodeList,
 		android.FixtureMergeMockFs(android.MockFS{
 			"external/avb/test/data/testkey_rsa4096.pem": nil,
+			"external/avb/test/Android.bp": []byte(`
+			filegroup {
+				name: "avb_testkey_rsa4096",
+				srcs: ["data/testkey_rsa4096.pem"],
+			}
+			`),
 			"build/soong/fsgen/Android.bp": []byte(`
 			soong_filesystem_creator {
 				name: "foo",
@@ -52,7 +74,7 @@
 		}),
 	).RunTest(t)
 
-	fooSystem := result.ModuleForTests("test_product_generated_system_image", "android_common").Module().(interface {
+	fooSystem := result.ModuleForTests(t, "test_product_generated_system_image", "android_common").Module().(interface {
 		FsProps() filesystem.FilesystemProperties
 	})
 	android.AssertBoolEquals(
@@ -63,8 +85,8 @@
 	)
 	android.AssertStringEquals(
 		t,
-		"Property expected to match the product variable 'BOARD_AVB_KEY_PATH'",
-		"external/avb/test/data/testkey_rsa4096.pem",
+		"Property the avb_private_key property to be set to the existing filegroup",
+		":avb_testkey_rsa4096",
 		proptools.String(fooSystem.FsProps().Avb_private_key),
 	)
 	android.AssertStringEquals(
@@ -86,3 +108,293 @@
 		proptools.String(fooSystem.FsProps().Type),
 	)
 }
+
+func TestFileSystemCreatorSetPartitionDeps(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		android.PrepareForIntegrationTestWithAndroid,
+		android.PrepareForTestWithAndroidBuildComponents,
+		android.PrepareForTestWithAllowMissingDependencies,
+		filesystem.PrepareForTestWithFilesystemBuildComponents,
+		prepareForTestWithFsgenBuildComponents,
+		java.PrepareForTestWithJavaBuildComponents,
+		java.PrepareForTestWithJavaDefaultModules,
+		android.FixtureModifyConfig(func(config android.Config) {
+			config.TestProductVariables.PartitionVarsForSoongMigrationOnlyDoNotUse.ProductPackages = []string{"bar", "baz"}
+			config.TestProductVariables.PartitionVarsForSoongMigrationOnlyDoNotUse.PartitionQualifiedVariables =
+				map[string]android.PartitionQualifiedVariablesType{
+					"system": {
+						BoardFileSystemType: "ext4",
+					},
+				}
+		}),
+		prepareMockRamdiksNodeList,
+		android.FixtureMergeMockFs(android.MockFS{
+			"external/avb/test/data/testkey_rsa4096.pem": nil,
+			"build/soong/fsgen/Android.bp": []byte(`
+			soong_filesystem_creator {
+				name: "foo",
+			}
+			`),
+		}),
+	).RunTestWithBp(t, `
+	java_library {
+		name: "bar",
+		srcs: ["A.java"],
+	}
+	java_library {
+		name: "baz",
+		srcs: ["A.java"],
+		product_specific: true,
+	}
+	`)
+
+	android.AssertBoolEquals(
+		t,
+		"Generated system image expected to depend on system partition installed \"bar\"",
+		true,
+		java.CheckModuleHasDependency(t, result.TestContext, "test_product_generated_system_image", "android_common", "bar"),
+	)
+	android.AssertBoolEquals(
+		t,
+		"Generated system image expected to not depend on product partition installed \"baz\"",
+		false,
+		java.CheckModuleHasDependency(t, result.TestContext, "test_product_generated_system_image", "android_common", "baz"),
+	)
+}
+
+func TestFileSystemCreatorDepsWithNamespace(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		android.PrepareForIntegrationTestWithAndroid,
+		android.PrepareForTestWithAndroidBuildComponents,
+		android.PrepareForTestWithAllowMissingDependencies,
+		android.PrepareForTestWithNamespace,
+		android.PrepareForTestWithArchMutator,
+		filesystem.PrepareForTestWithFilesystemBuildComponents,
+		prepareForTestWithFsgenBuildComponents,
+		java.PrepareForTestWithJavaBuildComponents,
+		java.PrepareForTestWithJavaDefaultModules,
+		android.FixtureModifyConfig(func(config android.Config) {
+			config.TestProductVariables.PartitionVarsForSoongMigrationOnlyDoNotUse.ProductPackages = []string{"bar"}
+			config.TestProductVariables.NamespacesToExport = []string{"a/b"}
+			config.TestProductVariables.PartitionVarsForSoongMigrationOnlyDoNotUse.PartitionQualifiedVariables =
+				map[string]android.PartitionQualifiedVariablesType{
+					"system": {
+						BoardFileSystemType: "ext4",
+					},
+				}
+		}),
+		android.PrepareForNativeBridgeEnabled,
+		prepareMockRamdiksNodeList,
+		android.FixtureMergeMockFs(android.MockFS{
+			"external/avb/test/data/testkey_rsa4096.pem": nil,
+			"build/soong/fsgen/Android.bp": []byte(`
+			soong_filesystem_creator {
+				name: "foo",
+			}
+			`),
+			"a/b/Android.bp": []byte(`
+			soong_namespace{
+			}
+			java_library {
+				name: "bar",
+				srcs: ["A.java"],
+				compile_multilib: "64",
+			}
+			`),
+			"c/d/Android.bp": []byte(`
+			soong_namespace{
+			}
+			java_library {
+				name: "bar",
+				srcs: ["A.java"],
+			}
+			`),
+		}),
+	).RunTest(t)
+
+	var packagingProps android.PackagingProperties
+	for _, prop := range result.ModuleForTests(t, "test_product_generated_system_image", "android_common").Module().GetProperties() {
+		if packagingPropStruct, ok := prop.(*android.PackagingProperties); ok {
+			packagingProps = *packagingPropStruct
+		}
+	}
+	moduleDeps := packagingProps.Multilib.Lib64.Deps
+
+	eval := result.ModuleForTests(t, "test_product_generated_system_image", "android_common").Module().ConfigurableEvaluator(android.PanickingConfigAndErrorContext(result.TestContext))
+	android.AssertStringListContains(
+		t,
+		"Generated system image expected to depend on \"bar\" defined in \"a/b\" namespace",
+		moduleDeps.GetOrDefault(eval, nil),
+		"//a/b:bar",
+	)
+	android.AssertStringListDoesNotContain(
+		t,
+		"Generated system image expected to not depend on \"bar\" defined in \"c/d\" namespace",
+		moduleDeps.GetOrDefault(eval, nil),
+		"//c/d:bar",
+	)
+}
+
+func TestRemoveOverriddenModulesFromDeps(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		android.PrepareForIntegrationTestWithAndroid,
+		android.PrepareForTestWithAndroidBuildComponents,
+		android.PrepareForTestWithAllowMissingDependencies,
+		prepareForTestWithFsgenBuildComponents,
+		java.PrepareForTestWithJavaBuildComponents,
+		prepareMockRamdiksNodeList,
+		android.FixtureMergeMockFs(android.MockFS{
+			"external/avb/test/data/testkey_rsa4096.pem": nil,
+			"build/soong/fsgen/Android.bp": []byte(`
+			soong_filesystem_creator {
+				name: "foo",
+			}
+			`),
+		}),
+		android.FixtureModifyConfig(func(config android.Config) {
+			config.TestProductVariables.PartitionVarsForSoongMigrationOnlyDoNotUse.ProductPackages = []string{"libfoo", "libbar"}
+		}),
+	).RunTestWithBp(t, `
+java_library {
+	name: "libfoo",
+}
+java_library {
+	name: "libbar",
+	required: ["libbaz"],
+}
+java_library {
+	name: "libbaz",
+	overrides: ["libfoo"], // overrides libfoo
+}
+	`)
+	resolvedSystemDeps := result.TestContext.Config().Get(fsGenStateOnceKey).(*FsGenState).fsDeps["system"]
+	_, 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",
+					},
+				}
+		}),
+		prepareMockRamdiksNodeList,
+		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(t, "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(t, "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.AssertStringEquals(
+		t,
+		"prebuilt_* module not generated for non existing source file",
+		"",
+		strings.Join(result.ModuleVariantsForTests("system-some_non_existing-etc-0"), ","),
+	)
+
+	// check that duplicate src file can exist in PRODUCT_COPY_FILES and generates separate modules
+	generatedModule0 := result.ModuleForTests(t, "product-device_sample_etc-etc-0", "android_arm64_armv8-a").Module()
+	generatedModule1 := result.ModuleForTests(t, "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..9b25e77
--- /dev/null
+++ b/fsgen/fsgen_mutators.go
@@ -0,0 +1,393 @@
+// 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
+	// Information about the main soong-generated partitions
+	soongGeneratedPartitions allGeneratedPartitionData
+	// Mutex to protect the fsDeps
+	fsDepsMutex sync.Mutex
+	// Map of _all_ soong module names to their corresponding installation properties
+	moduleToInstallationProps map[string]installationProperties
+	// List of prebuilt_* modules that are autogenerated.
+	generatedPrebuiltEtcModuleNames []string
+	// Mapping from a path to an avb key to the name of a filegroup module that contains it
+	avbKeyFilegroups map[string]string
+}
+
+type installationProperties struct {
+	Required  []string
+	Overrides []string
+}
+
+func defaultDepCandidateProps(config android.Config) *depCandidateProps {
+	return &depCandidateProps{
+		Namespace: ".",
+		Arch:      []android.ArchType{config.BuildArch},
+	}
+}
+
+func createFsGenState(ctx android.LoadHookContext, generatedPrebuiltEtcModuleNames []string, avbpubkeyGenerated bool) *FsGenState {
+	return ctx.Config().Once(fsGenStateOnceKey, func() interface{} {
+		partitionVars := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
+		candidates := android.FirstUniqueStrings(android.Concat(partitionVars.ProductPackages, partitionVars.ProductPackagesDebug))
+		candidates = android.Concat(candidates, generatedPrebuiltEtcModuleNames)
+
+		fsGenState := FsGenState{
+			depCandidates: candidates,
+			fsDeps: map[string]*multilibDeps{
+				// These additional deps are added according to the cuttlefish system image bp.
+				"system": {
+					// keep-sorted start
+					"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()),
+					// keep-sorted end
+				},
+				"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":        {},
+				"vendor_ramdisk": {},
+				"recovery": {
+					"sepolicy.recovery":                     defaultDepCandidateProps(ctx.Config()),
+					"plat_file_contexts.recovery":           defaultDepCandidateProps(ctx.Config()),
+					"plat_service_contexts.recovery":        defaultDepCandidateProps(ctx.Config()),
+					"plat_property_contexts.recovery":       defaultDepCandidateProps(ctx.Config()),
+					"system_ext_file_contexts.recovery":     defaultDepCandidateProps(ctx.Config()),
+					"system_ext_service_contexts.recovery":  defaultDepCandidateProps(ctx.Config()),
+					"system_ext_property_contexts.recovery": defaultDepCandidateProps(ctx.Config()),
+					"vendor_file_contexts.recovery":         defaultDepCandidateProps(ctx.Config()),
+					"vendor_service_contexts.recovery":      defaultDepCandidateProps(ctx.Config()),
+					"vendor_property_contexts.recovery":     defaultDepCandidateProps(ctx.Config()),
+					"odm_file_contexts.recovery":            defaultDepCandidateProps(ctx.Config()),
+					"odm_property_contexts.recovery":        defaultDepCandidateProps(ctx.Config()),
+					"product_file_contexts.recovery":        defaultDepCandidateProps(ctx.Config()),
+					"product_service_contexts.recovery":     defaultDepCandidateProps(ctx.Config()),
+					"product_property_contexts.recovery":    defaultDepCandidateProps(ctx.Config()),
+				},
+			},
+			fsDepsMutex:                     sync.Mutex{},
+			moduleToInstallationProps:       map[string]installationProperties{},
+			generatedPrebuiltEtcModuleNames: generatedPrebuiltEtcModuleNames,
+			avbKeyFilegroups:                map[string]string{},
+		}
+
+		if avbpubkeyGenerated {
+			(*fsGenState.fsDeps["product"])["system_other_avbpubkey"] = defaultDepCandidateProps(ctx.Config())
+		}
+
+		// Add common resources `prebuilt_res` module as dep of recovery partition
+		(*fsGenState.fsDeps["recovery"])[fmt.Sprintf("recovery-resources-common-%s", getDpi(ctx))] = defaultDepCandidateProps(ctx.Config())
+		(*fsGenState.fsDeps["recovery"])[getRecoveryFontModuleName(ctx)] = defaultDepCandidateProps(ctx.Config())
+		(*fsGenState.fsDeps["recovery"])[createRecoveryBuildProp(ctx)] = defaultDepCandidateProps(ctx.Config())
+
+		return &fsGenState
+	}).(*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
+	m := mctx.Module()
+	if partition := fsGenState.soongGeneratedPartitions.typeForName(m.Name()); partition != "" {
+		if fsGenState.soongGeneratedPartitions.isHandwritten(m.Name()) {
+			// Handwritten image, don't modify it
+			return
+		}
+		depsStruct := generateDepStruct(*fsDeps[partition], fsGenState.generatedPrebuiltEtcModuleNames)
+		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, highPriorityDeps []string) *packagingPropsStruct {
+	depsStruct := packagingPropsStruct{}
+	for depName, depProps := range deps {
+		bitness := getBitness(depProps.Arch)
+		fullyQualifiedDepName := fullyQualifiedModuleName(depName, depProps.Namespace)
+		if android.InList(depName, highPriorityDeps) {
+			depsStruct.High_priority_deps = append(depsStruct.High_priority_deps, fullyQualifiedDepName)
+		} else if android.InList("32", bitness) && android.InList("64", bitness) {
+			// If both 32 and 64 bit variants are enabled for this module
+			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)
+	depsStruct.High_priority_deps = android.SortedUniqueStrings(depsStruct.High_priority_deps)
+
+	return &depsStruct
+}
diff --git a/fsgen/prebuilt_etc_modules_gen.go b/fsgen/prebuilt_etc_modules_gen.go
new file mode 100644
index 0000000..e028b1d
--- /dev/null
+++ b/fsgen/prebuilt_etc_modules_gen.go
@@ -0,0 +1,412 @@
+// 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
+	recovery   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{},
+		recovery:   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
+			case "recovery":
+				srcMap = srcGroup.recovery
+			}
+			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: "recovery", installPath: "recovery/root"},
+		{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
+	Recovery            *bool
+	Ramdisk             *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
+}
+
+// Split install_in_root to a separate struct as it is part of rootProperties instead of
+// properties
+type prebuiltInstallInRootProperties struct {
+	Install_in_root *bool
+}
+
+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,
+		"first_stage_ramdisk": etc.PrebuiltFirstStageRamdiskFactory,
+		"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,
+		"sbin":                etc.PrebuiltSbinFactory,
+		"system":              etc.PrebuiltSystemFactory,
+		"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":              etc.PrebuiltVendorFactory,
+		"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(ctx android.LoadHookContext, moduleName, partition, destDir 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)
+	case "recovery":
+		// To match the logic in modulePartition() in android/paths.go
+		if ctx.DeviceConfig().BoardUsesRecoveryAsBoot() && strings.HasPrefix(destDir, "first_stage_ramdisk") {
+			moduleProps.Ramdisk = proptools.BoolPtr(true)
+		} else {
+			moduleProps.Recovery = 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(ctx, moduleName, partition, destDir)
+		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)
+		}
+
+		// Recovery partition-installed modules are installed to `recovery/root/system` by
+		// default (See modulePartition() in android/paths.go). If the destination file
+		// directory is not `recovery/root/system/...`, it should set install_in_root to true
+		// to prevent being installed in `recovery/root/system`.
+		if partition == "recovery" && !strings.HasPrefix(destDir, "system") {
+			propsList = append(propsList, &prebuiltInstallInRootProperties{
+				Install_in_root: proptools.BoolPtr(true),
+			})
+		}
+
+		// 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)...)
+		ret = append(ret, createPrebuiltEtcModulesForPartition(ctx, "recovery", srcDir, groupedSource.recovery)...)
+	}
+
+	return ret
+}
+
+func createAvbpubkeyModule(ctx android.LoadHookContext) bool {
+	avbKeyPath := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.BoardAvbKeyPath
+	if avbKeyPath == "" {
+		return false
+	}
+	ctx.CreateModuleInDirectory(
+		etc.AvbpubkeyModuleFactory,
+		".",
+		&struct {
+			Name             *string
+			Product_specific *bool
+			Private_key      *string
+			No_full_install  *bool
+			Visibility       []string
+		}{
+			Name:             proptools.StringPtr("system_other_avbpubkey"),
+			Product_specific: proptools.BoolPtr(true),
+			Private_key:      proptools.StringPtr(avbKeyPath),
+			No_full_install:  proptools.BoolPtr(true),
+			Visibility:       []string{"//visibility:public"},
+		},
+	)
+	return true
+}
diff --git a/fsgen/super_img.go b/fsgen/super_img.go
new file mode 100644
index 0000000..569f780
--- /dev/null
+++ b/fsgen/super_img.go
@@ -0,0 +1,132 @@
+// 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 (
+	"strconv"
+
+	"android/soong/android"
+	"android/soong/filesystem"
+
+	"github.com/google/blueprint/proptools"
+)
+
+func buildingSuperImage(partitionVars android.PartitionVariables) bool {
+	return partitionVars.ProductBuildSuperPartition
+}
+
+func createSuperImage(
+	ctx android.LoadHookContext,
+	partitions allGeneratedPartitionData,
+	partitionVars android.PartitionVariables,
+	systemOtherImageName string,
+) []string {
+	baseProps := &struct {
+		Name *string
+	}{
+		Name: proptools.StringPtr(generatedModuleNameForPartition(ctx.Config(), "super")),
+	}
+
+	superImageProps := &filesystem.SuperImageProperties{
+		Metadata_device:        proptools.StringPtr(partitionVars.BoardSuperPartitionMetadataDevice),
+		Block_devices:          partitionVars.BoardSuperPartitionBlockDevices,
+		Ab_update:              proptools.BoolPtr(partitionVars.AbOtaUpdater),
+		Retrofit:               proptools.BoolPtr(partitionVars.ProductRetrofitDynamicPartitions),
+		Use_dynamic_partitions: proptools.BoolPtr(partitionVars.ProductUseDynamicPartitions),
+	}
+	if partitionVars.ProductVirtualAbOta {
+		superImageProps.Virtual_ab.Enable = proptools.BoolPtr(true)
+		superImageProps.Virtual_ab.Retrofit = proptools.BoolPtr(partitionVars.ProductVirtualAbOtaRetrofit)
+		superImageProps.Virtual_ab.Compression = proptools.BoolPtr(partitionVars.ProductVirtualAbCompression)
+		if partitionVars.ProductVirtualAbCompressionMethod != "" {
+			superImageProps.Virtual_ab.Compression_method = proptools.StringPtr(partitionVars.ProductVirtualAbCompressionMethod)
+		}
+		if partitionVars.ProductVirtualAbCompressionFactor != "" {
+			factor, err := strconv.ParseInt(partitionVars.ProductVirtualAbCompressionFactor, 10, 32)
+			if err != nil {
+				ctx.ModuleErrorf("Compression factor must be an int, got %q", partitionVars.ProductVirtualAbCompressionFactor)
+			}
+			superImageProps.Virtual_ab.Compression_factor = proptools.Int64Ptr(factor)
+		}
+		if partitionVars.ProductVirtualAbCowVersion != "" {
+			version, err := strconv.ParseInt(partitionVars.ProductVirtualAbCowVersion, 10, 32)
+			if err != nil {
+				ctx.ModuleErrorf("COW version must be an int, got %q", partitionVars.ProductVirtualAbCowVersion)
+			}
+			superImageProps.Virtual_ab.Cow_version = proptools.Int64Ptr(version)
+		}
+	}
+	size, _ := strconv.ParseInt(partitionVars.BoardSuperPartitionSize, 10, 64)
+	superImageProps.Size = proptools.Int64Ptr(size)
+	sparse := !partitionVars.TargetUserimagesSparseExtDisabled && !partitionVars.TargetUserimagesSparseF2fsDisabled
+	superImageProps.Sparse = proptools.BoolPtr(sparse)
+
+	var partitionGroupsInfo []filesystem.PartitionGroupsInfo
+	for _, groupName := range android.SortedKeys(partitionVars.BoardSuperPartitionGroups) {
+		info := filesystem.PartitionGroupsInfo{
+			Name:          groupName,
+			GroupSize:     partitionVars.BoardSuperPartitionGroups[groupName].GroupSize,
+			PartitionList: partitionVars.BoardSuperPartitionGroups[groupName].PartitionList,
+		}
+		partitionGroupsInfo = append(partitionGroupsInfo, info)
+	}
+	superImageProps.Partition_groups = partitionGroupsInfo
+
+	if systemOtherImageName != "" {
+		superImageProps.System_other_partition = proptools.StringPtr(systemOtherImageName)
+	}
+
+	var superImageSubpartitions []string
+	partitionNameProps := &filesystem.SuperImagePartitionNameProperties{}
+	if modName := partitions.nameForType("system"); modName != "" {
+		partitionNameProps.System_partition = proptools.StringPtr(modName)
+		superImageSubpartitions = append(superImageSubpartitions, "system")
+	}
+	if modName := partitions.nameForType("system_ext"); modName != "" {
+		partitionNameProps.System_ext_partition = proptools.StringPtr(modName)
+		superImageSubpartitions = append(superImageSubpartitions, "system_ext")
+	}
+	if modName := partitions.nameForType("system_dlkm"); modName != "" {
+		partitionNameProps.System_dlkm_partition = proptools.StringPtr(modName)
+		superImageSubpartitions = append(superImageSubpartitions, "system_dlkm")
+	}
+	if modName := partitions.nameForType("system_other"); modName != "" {
+		partitionNameProps.System_other_partition = proptools.StringPtr(modName)
+		superImageSubpartitions = append(superImageSubpartitions, "system_other")
+	}
+	if modName := partitions.nameForType("product"); modName != "" {
+		partitionNameProps.Product_partition = proptools.StringPtr(modName)
+		superImageSubpartitions = append(superImageSubpartitions, "product")
+	}
+	if modName := partitions.nameForType("vendor"); modName != "" {
+		partitionNameProps.Vendor_partition = proptools.StringPtr(modName)
+		superImageSubpartitions = append(superImageSubpartitions, "vendor")
+	}
+	if modName := partitions.nameForType("vendor_dlkm"); modName != "" {
+		partitionNameProps.Vendor_dlkm_partition = proptools.StringPtr(modName)
+		superImageSubpartitions = append(superImageSubpartitions, "vendor_dlkm")
+	}
+	if modName := partitions.nameForType("odm"); modName != "" {
+		partitionNameProps.Odm_partition = proptools.StringPtr(modName)
+		superImageSubpartitions = append(superImageSubpartitions, "odm")
+	}
+	if modName := partitions.nameForType("odm_dlkm"); modName != "" {
+		partitionNameProps.Odm_dlkm_partition = proptools.StringPtr(modName)
+		superImageSubpartitions = append(superImageSubpartitions, "odm_dlkm")
+	}
+
+	ctx.CreateModule(filesystem.SuperImageFactory, baseProps, superImageProps, partitionNameProps)
+	return superImageSubpartitions
+}
diff --git a/fsgen/util.go b/fsgen/util.go
new file mode 100644
index 0000000..008f9fe
--- /dev/null
+++ b/fsgen/util.go
@@ -0,0 +1,79 @@
+// 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"
+	"fmt"
+	"strconv"
+	"strings"
+)
+
+// Returns the appropriate dpi for recovery common resources selection. Replicates the logic in
+// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=2536;drc=a6af369e71ded123734523ea640b97b70a557cb9
+func getDpi(ctx android.LoadHookContext) string {
+	recoveryDensity := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.TargetScreenDensity
+	if len(recoveryDensity) == 0 {
+		aaptPreferredConfig := ctx.Config().ProductAAPTPreferredConfig()
+		if len(aaptPreferredConfig) > 0 {
+			recoveryDensity = aaptPreferredConfig
+		} else {
+			recoveryDensity = "mdpi"
+		}
+	}
+	if !android.InList(recoveryDensity, []string{"xxxhdpi", "xxhdpi", "xhdpi", "hdpi", "mdpi"}) {
+		recoveryDensity = strings.TrimSuffix(recoveryDensity, "dpi")
+		dpiInt, err := strconv.ParseInt(recoveryDensity, 10, 64)
+		if err != nil {
+			panic(fmt.Sprintf("Error in parsing recoveryDensity: %s", err.Error()))
+		}
+		if dpiInt >= 560 {
+			recoveryDensity = "xxxhdpi"
+		} else if dpiInt >= 400 {
+			recoveryDensity = "xxhdpi"
+		} else if dpiInt >= 280 {
+			recoveryDensity = "xhdpi"
+		} else if dpiInt >= 200 {
+			recoveryDensity = "hdpi"
+		} else {
+			recoveryDensity = "mdpi"
+		}
+	}
+
+	if p := android.ExistentPathForSource(ctx, fmt.Sprintf("bootable/recovery/res-%s", recoveryDensity)); !p.Valid() {
+		recoveryDensity = "xhdpi"
+	}
+
+	return recoveryDensity
+}
+
+// Returns the name of the appropriate prebuilt module for installing font.png file.
+// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=2536;drc=a6af369e71ded123734523ea640b97b70a557cb9
+func getRecoveryFontModuleName(ctx android.LoadHookContext) string {
+	if android.InList(getDpi(ctx), []string{"xxxhdpi", "xxhdpi", "xhdpi"}) {
+		return "recovery-fonts-18"
+	}
+	return "recovery-fonts-12"
+}
+
+// Returns a new list of symlinks with prefix added to the dest directory for all symlinks
+func symlinksWithNamePrefix(symlinks []filesystem.SymlinkDefinition, prefix string) []filesystem.SymlinkDefinition {
+	ret := make([]filesystem.SymlinkDefinition, len(symlinks))
+	for i, symlink := range symlinks {
+		ret[i] = symlink.CopyWithNamePrefix(prefix)
+	}
+	return ret
+}
diff --git a/fsgen/vbmeta_partitions.go b/fsgen/vbmeta_partitions.go
new file mode 100644
index 0000000..11f4bd0
--- /dev/null
+++ b/fsgen/vbmeta_partitions.go
@@ -0,0 +1,271 @@
+// 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"
+	"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
+}
+
+// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=4849;drc=62e20f0d218f60bae563b4ee742d88cca1fc1901
+var avbPartitions = []string{
+	"boot",
+	"init_boot",
+	"vendor_boot",
+	"vendor_kernel_boot",
+	"system",
+	"vendor",
+	"product",
+	"system_ext",
+	"odm",
+	"vendor_dlkm",
+	"odm_dlkm",
+	"system_dlkm",
+	"dtbo",
+	"pvmfw",
+	"recovery",
+	"vbmeta_system",
+	"vbmeta_vendor",
+}
+
+// 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 (f *filesystemCreator) createVbmetaPartitions(ctx android.LoadHookContext, partitions allGeneratedPartitionData) []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
+
+	// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=4593;drc=62e20f0d218f60bae563b4ee742d88cca1fc1901
+	var internalAvbPartitionsInChainedVbmetaImages []string
+	var chainedPartitionTypes []string
+	for _, chainedName := range android.SortedKeys(partitionVars.ChainedVbmetaPartitions) {
+		props := partitionVars.ChainedVbmetaPartitions[chainedName]
+		chainedName = "vbmeta_" + chainedName
+		if len(props.Partitions) == 0 {
+			continue
+		}
+		internalAvbPartitionsInChainedVbmetaImages = append(internalAvbPartitionsInChainedVbmetaImages, props.Partitions...)
+		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 {
+			if modName := partitions.nameForType(partition); modName != "" {
+				partitionModules = append(partitionModules, modName)
+			}
+		}
+
+		name := generatedModuleNameForPartition(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()
+
+		result = append(result, vbmetaModuleInfo{
+			moduleName:    name,
+			partitionName: chainedName,
+		})
+
+		chainedPartitionTypes = append(chainedPartitionTypes, chainedName)
+	}
+
+	vbmetaModuleName := generatedModuleNameForPartition(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
+	}
+
+	// --chain_partition argument is only set for partitions that set
+	// `BOARD_AVB_<partition name>_KEY_PATH` value and is not "recovery"
+	// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=4823;drc=62e20f0d218f60bae563b4ee742d88cca1fc1901
+	includeAsChainedPartitionInVbmeta := func(partition string) bool {
+		val, ok := partitionVars.PartitionQualifiedVariables[partition]
+		return ok && len(val.BoardAvbKeyPath) > 0 && partition != "recovery"
+	}
+
+	// --include_descriptors_from_image is passed if both conditions are met:
+	// - `BOARD_AVB_<partition name>_KEY_PATH` value is not set
+	// - not included in INTERNAL_AVB_PARTITIONS_IN_CHAINED_VBMETA_IMAGES
+	// for partitions that set INSTALLED_<partition name>IMAGE_TARGET
+	// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=4827;drc=62e20f0d218f60bae563b4ee742d88cca1fc1901
+	includeAsIncludedPartitionInVbmeta := func(partition string) bool {
+		if android.InList(partition, internalAvbPartitionsInChainedVbmetaImages) {
+			// Already handled by chained vbmeta partitions
+			return false
+		}
+		partitionQualifiedVars := partitionVars.PartitionQualifiedVariables[partition]
+
+		// The return logic in the switch cases below are identical to
+		// ifdef INSTALLED_<partition name>IMAGE_TARGET
+		switch partition {
+		case "boot":
+			return partitionQualifiedVars.BuildingImage || partitionQualifiedVars.PrebuiltImage || partitionVars.BoardUsesRecoveryAsBoot
+		case "vendor_kernel_boot", "dtbo":
+			return partitionQualifiedVars.PrebuiltImage
+		case "system":
+			return partitionQualifiedVars.BuildingImage
+		case "init_boot", "vendor_boot", "vendor", "product", "system_ext", "odm", "vendor_dlkm", "odm_dlkm", "system_dlkm":
+			return partitionQualifiedVars.BuildingImage || partitionQualifiedVars.PrebuiltImage
+		// TODO: Import BOARD_USES_PVMFWIMAGE
+		// ifeq ($(BOARD_USES_PVMFWIMAGE),true)
+		// case "pvmfw":
+		case "recovery":
+			// ifdef INSTALLED_RECOVERYIMAGE_TARGET
+			return !ctx.DeviceConfig().BoardUsesRecoveryAsBoot() && !ctx.DeviceConfig().BoardMoveRecoveryResourcesToVendorBoot()
+		// Technically these partitions are determined based on len(BOARD_AVB_VBMETA_SYSTEM) and
+		// len(BOARD_AVB_VBMETA_VENDOR) but if these are non empty these partitions are
+		// already included in the chained partitions.
+		case "vbmeta_system", "vbmeta_vendor":
+			return false
+		default:
+			return false
+		}
+	}
+
+	var chainedPartitionModules []string
+	var includePartitionModules []string
+	allGeneratedPartitionTypes := append(partitions.types(),
+		chainedPartitionTypes...,
+	)
+	if len(f.properties.Boot_image) > 0 {
+		allGeneratedPartitionTypes = append(allGeneratedPartitionTypes, "boot")
+	}
+	if len(f.properties.Init_boot_image) > 0 {
+		allGeneratedPartitionTypes = append(allGeneratedPartitionTypes, "init_boot")
+	}
+	if len(f.properties.Vendor_boot_image) > 0 {
+		allGeneratedPartitionTypes = append(allGeneratedPartitionTypes, "vendor_boot")
+	}
+
+	// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=4919;drc=62e20f0d218f60bae563b4ee742d88cca1fc1901
+	for _, partitionType := range android.SortedUniqueStrings(append(avbPartitions, chainedPartitionTypes...)) {
+		if !android.InList(partitionType, allGeneratedPartitionTypes) {
+			// Skip if the partition is not auto generated
+			continue
+		}
+		name := partitions.nameForType(partitionType)
+		if name == "" {
+			name = generatedModuleNameForPartition(ctx.Config(), partitionType)
+		}
+		if includeAsChainedPartitionInVbmeta(partitionType) {
+			chainedPartitionModules = append(chainedPartitionModules, name)
+		} else if includeAsIncludedPartitionInVbmeta(partitionType) {
+			includePartitionModules = append(includePartitionModules, name)
+		}
+	}
+
+	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: chainedPartitionModules,
+			Partitions:         proptools.NewSimpleConfigurable(includePartitionModules),
+			Partition_name:     proptools.StringPtr("vbmeta"),
+		}, &struct {
+			Name *string
+		}{
+			Name: &vbmetaModuleName,
+		},
+	).HideFromMake()
+
+	result = append(result, vbmetaModuleInfo{
+		moduleName:    vbmetaModuleName,
+		partitionName: "vbmeta",
+	})
+	return result
+}
diff --git a/fuzz/fuzz_common.go b/fuzz/fuzz_common.go
index a059837..3fd79a7 100644
--- a/fuzz/fuzz_common.go
+++ b/fuzz/fuzz_common.go
@@ -44,6 +44,32 @@
 	UnknownFramework Framework = "unknownframework"
 )
 
+func (f Framework) Variant() string {
+	switch f {
+	case AFL:
+		return "afl"
+	case LibFuzzer:
+		return "libfuzzer"
+	case Jazzer:
+		return "jazzer"
+	default:
+		panic(fmt.Errorf("unknown fuzzer %q when getting variant", f))
+	}
+}
+
+func FrameworkFromVariant(v string) Framework {
+	switch v {
+	case "afl":
+		return AFL
+	case "libfuzzer":
+		return LibFuzzer
+	case "jazzer":
+		return Jazzer
+	default:
+		panic(fmt.Errorf("unknown variant %q when getting fuzzer", v))
+	}
+}
+
 var BoolDefault = proptools.BoolDefault
 
 type FuzzModule struct {
@@ -385,9 +411,22 @@
 	// Optional list of seed files to be installed to the fuzz target's output
 	// directory.
 	Corpus []string `android:"path"`
+
+	// Same as corpus, but adds dependencies on module references using the device's os variant
+	// and the common arch variant.
+	Device_common_corpus []string `android:"path_device_common"`
+
 	// Optional list of data files to be installed to the fuzz target's output
 	// directory. Directory structure relative to the module is preserved.
 	Data []string `android:"path"`
+	// Same as data, but adds dependencies on modules using the device's os variant, and common
+	// architecture's variant. Can be useful to add device-built apps to the data of a host
+	// test.
+	Device_common_data []string `android:"path_device_common"`
+	// Same as data, but adds dependencies on modules using the device's os variant, and the
+	// device's first architecture's variant. Can be useful to add device-built apps to the data
+	// of a host test.
+	Device_first_data []string `android:"path_device_first"`
 	// Optional dictionary to be installed to the fuzz target's output directory.
 	Dictionary *string `android:"path"`
 	// Define the fuzzing frameworks this fuzz target can be built for. If
diff --git a/genrule/genrule.go b/genrule/genrule.go
index e5222a4..92f038f 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -63,7 +63,7 @@
 	ctx.RegisterModuleType("genrule", GenRuleFactory)
 
 	ctx.FinalDepsMutators(func(ctx android.RegisterMutatorsContext) {
-		ctx.BottomUp("genrule_tool_deps", toolDepsMutator).Parallel()
+		ctx.BottomUp("genrule_tool_deps", toolDepsMutator)
 	})
 }
 
@@ -88,9 +88,7 @@
 }
 
 type SourceFileGenerator interface {
-	GeneratedSourceFiles() android.Paths
-	GeneratedHeaderDirs() android.Paths
-	GeneratedDeps() android.Paths
+	android.SourceFileGenerator
 }
 
 // Alias for android.HostToolProvider
@@ -112,6 +110,12 @@
 	return target.IsReplacedByPrebuilt()
 }
 
+func (t hostToolDependencyTag) AllowDisabledModuleDependencyProxy(
+	ctx android.OtherModuleProviderContext, target android.ModuleProxy) bool {
+	return android.OtherModuleProviderOrDefault(
+		ctx, target, android.CommonModuleInfoKey).ReplacedByPrebuilt
+}
+
 var _ android.AllowDisabledModuleDependency = (*hostToolDependencyTag)(nil)
 
 type generatorProperties struct {
@@ -139,8 +143,19 @@
 	Export_include_dirs []string
 
 	// list of input files
-	Srcs         proptools.Configurable[[]string] `android:"path,arch_variant"`
-	ResolvedSrcs []string                         `blueprint:"mutated"`
+	Srcs proptools.Configurable[[]string] `android:"path,arch_variant"`
+
+	// Same as srcs, but will add dependencies on modules via a device os variation and the device's
+	// first supported arch's variation. Can be used to add a dependency from a host genrule to
+	// a device module.
+	Device_first_srcs proptools.Configurable[[]string] `android:"path_device_first"`
+
+	// Same as srcs, but will add dependencies on modules via a device os variation and the common
+	// arch variation. Can be used to add a dependency from a host genrule to a device module.
+	Device_common_srcs proptools.Configurable[[]string] `android:"path_device_common"`
+
+	// Same as srcs, but will add dependencies on modules via a common_os os variation.
+	Common_os_srcs proptools.Configurable[[]string] `android:"path_common_os"`
 
 	// input files to exclude
 	Exclude_srcs []string `android:"path,arch_variant"`
@@ -213,7 +228,9 @@
 	shards int
 
 	// For nsjail tasks
-	useNsjail bool
+	useNsjail  bool
+	dirSrcs    android.DirectoryPaths
+	keepGendir bool
 }
 
 func (g *Module) GeneratedSourceFiles() android.Paths {
@@ -264,6 +281,7 @@
 			"hardware/google/camera/common/hal/aidl_service:aidl_camera_build_version",
 			"tools/tradefederation/core:tradefed_zip",
 			"vendor/google/services/LyricCameraHAL/src/apex:com.google.pixel.camera.hal.manifest",
+			"vendor/google_tradefederation/core:gen_google_tradefed_zip",
 			// go/keep-sorted end
 		}
 		allowlistMap := make(map[string]bool, len(allowlist))
@@ -283,7 +301,15 @@
 // approach zero; there should be no genrule action registration done directly
 // by Soong logic in the mixed-build case.
 func (g *Module) generateCommonBuildActions(ctx android.ModuleContext) {
-	g.subName = ctx.ModuleSubDir()
+	// Add the variant as a suffix to the make modules to create, so that the make modules
+	// don't conflict because make doesn't know about variants. However, this causes issues with
+	// tracking required dependencies as the required property in soong is passed straight to make
+	// without accounting for these suffixes. To make it a little easier to work with, don't use
+	// a suffix for android_common variants so that java_genrules look like regular 1-variant
+	// genrules to make.
+	if ctx.ModuleSubDir() != "android_common" {
+		g.subName = ctx.ModuleSubDir()
+	}
 
 	if len(g.properties.Export_include_dirs) > 0 {
 		for _, dir := range g.properties.Export_include_dirs {
@@ -316,21 +342,18 @@
 	var packagedTools []android.PackagingSpec
 	if len(g.properties.Tools) > 0 {
 		seenTools := make(map[string]bool)
-
-		ctx.VisitDirectDepsAllowDisabled(func(module android.Module) {
-			switch tag := ctx.OtherModuleDependencyTag(module).(type) {
+		ctx.VisitDirectDepsProxyAllowDisabled(func(proxy android.ModuleProxy) {
+			switch tag := ctx.OtherModuleDependencyTag(proxy).(type) {
 			case hostToolDependencyTag:
-				tool := ctx.OtherModuleName(module)
 				// Necessary to retrieve any prebuilt replacement for the tool, since
 				// toolDepsMutator runs too late for the prebuilt mutators to have
 				// replaced the dependency.
-				module = android.PrebuiltGetPreferred(ctx, module)
-
-				switch t := module.(type) {
-				case android.HostToolProvider:
+				module := android.PrebuiltGetPreferred(ctx, proxy)
+				tool := ctx.OtherModuleName(module)
+				if h, ok := android.OtherModuleProvider(ctx, module, android.HostToolProviderInfoProvider); ok {
 					// A HostToolProvider provides the path to a tool, which will be copied
 					// into the sandbox.
-					if !t.(android.Module).Enabled(ctx) {
+					if !android.OtherModuleProviderOrDefault(ctx, module, android.CommonModuleInfoKey).Enabled {
 						if ctx.Config().AllowMissingDependencies() {
 							ctx.AddMissingDependencies([]string{tool})
 						} else {
@@ -338,13 +361,13 @@
 						}
 						return
 					}
-					path := t.HostToolPath()
+					path := h.HostToolPath
 					if !path.Valid() {
 						ctx.ModuleErrorf("host tool %q missing output file", tool)
 						return
 					}
 					if specs := android.OtherModuleProviderOrDefault(
-						ctx, t, android.InstallFilesProvider).TransitivePackagingSpecs.ToList(); specs != nil {
+						ctx, module, android.InstallFilesProvider).TransitivePackagingSpecs.ToList(); specs != nil {
 						// If the HostToolProvider has PackgingSpecs, which are definitions of the
 						// required relative locations of the tool and its dependencies, use those
 						// instead.  They will be copied to those relative locations in the sbox
@@ -366,7 +389,7 @@
 						tools = append(tools, path.Path())
 						addLocationLabel(tag.label, toolLocation{android.Paths{path.Path()}})
 					}
-				default:
+				} else {
 					ctx.ModuleErrorf("%q is not a host tool provider", tool)
 					return
 				}
@@ -426,9 +449,11 @@
 		}
 		return srcFiles
 	}
-	g.properties.ResolvedSrcs = g.properties.Srcs.GetOrDefault(ctx, nil)
-	srcFiles := addLabelsForInputs("srcs", g.properties.ResolvedSrcs, g.properties.Exclude_srcs)
-	android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: srcFiles.Strings()})
+	srcs := g.properties.Srcs.GetOrDefault(ctx, nil)
+	srcFiles := addLabelsForInputs("srcs", srcs, g.properties.Exclude_srcs)
+	srcFiles = append(srcFiles, addLabelsForInputs("device_first_srcs", g.properties.Device_first_srcs.GetOrDefault(ctx, nil), nil)...)
+	srcFiles = append(srcFiles, addLabelsForInputs("device_common_srcs", g.properties.Device_common_srcs.GetOrDefault(ctx, nil), nil)...)
+	srcFiles = append(srcFiles, addLabelsForInputs("common_os_srcs", g.properties.Common_os_srcs.GetOrDefault(ctx, nil), nil)...)
 
 	var copyFrom android.Paths
 	var outputFiles android.WritablePaths
@@ -461,6 +486,9 @@
 		name := "generator"
 		if task.useNsjail {
 			rule = android.NewRuleBuilder(pctx, ctx).Nsjail(task.genDir, android.PathForModuleOut(ctx, "nsjail_build_sandbox"))
+			if task.keepGendir {
+				rule.NsjailKeepGendir()
+			}
 		} else {
 			manifestName := "genrule.sbox.textproto"
 			if task.shards > 0 {
@@ -577,10 +605,13 @@
 		}
 
 		if task.useNsjail {
-			for _, input := range task.in {
-				// can fail if input is a file.
+			for _, input := range task.dirSrcs {
+				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 {
+					ctx.PropertyErrorf("dir_srcs", "can't glob %q", input.String())
 				}
 			}
 		}
@@ -643,6 +674,12 @@
 	}
 
 	g.setOutputFiles(ctx)
+
+	if ctx.Os() == android.Windows {
+		// Make doesn't support windows:
+		// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/module_arch_supported.mk;l=66;drc=f264690860bb6ee7762784d6b7201aae057ba6f2
+		g.HideFromMake()
+	}
 }
 
 func (g *Module) setOutputFiles(ctx android.ModuleContext) {
@@ -659,7 +696,7 @@
 // Collect information for opening IDE project files in java/jdeps.go.
 func (g *Module) IDEInfo(ctx android.BaseModuleContext, dpInfo *android.IdeInfo) {
 	dpInfo.Srcs = append(dpInfo.Srcs, g.Srcs().Strings()...)
-	for _, src := range g.properties.ResolvedSrcs {
+	for _, src := range g.properties.Srcs.GetOrDefault(ctx, nil) {
 		if strings.HasPrefix(src, ":") {
 			src = strings.Trim(src, ":")
 			dpInfo.Deps = append(dpInfo.Deps, src)
@@ -712,16 +749,22 @@
 
 type noopImageInterface struct{}
 
-func (x noopImageInterface) ImageMutatorBegin(android.BaseModuleContext)                 {}
-func (x noopImageInterface) VendorVariantNeeded(android.BaseModuleContext) bool          { return false }
-func (x noopImageInterface) ProductVariantNeeded(android.BaseModuleContext) bool         { return false }
-func (x noopImageInterface) CoreVariantNeeded(android.BaseModuleContext) bool            { return false }
-func (x noopImageInterface) RamdiskVariantNeeded(android.BaseModuleContext) bool         { return false }
-func (x noopImageInterface) VendorRamdiskVariantNeeded(android.BaseModuleContext) bool   { return false }
-func (x noopImageInterface) DebugRamdiskVariantNeeded(android.BaseModuleContext) bool    { return false }
-func (x noopImageInterface) RecoveryVariantNeeded(android.BaseModuleContext) bool        { return false }
-func (x noopImageInterface) ExtraImageVariations(ctx android.BaseModuleContext) []string { return nil }
-func (x noopImageInterface) SetImageVariation(ctx android.BaseModuleContext, variation string) {
+func (x noopImageInterface) ImageMutatorBegin(android.ImageInterfaceContext)         {}
+func (x noopImageInterface) VendorVariantNeeded(android.ImageInterfaceContext) bool  { return false }
+func (x noopImageInterface) ProductVariantNeeded(android.ImageInterfaceContext) bool { return false }
+func (x noopImageInterface) CoreVariantNeeded(android.ImageInterfaceContext) bool    { return false }
+func (x noopImageInterface) RamdiskVariantNeeded(android.ImageInterfaceContext) bool { return false }
+func (x noopImageInterface) VendorRamdiskVariantNeeded(android.ImageInterfaceContext) bool {
+	return false
+}
+func (x noopImageInterface) DebugRamdiskVariantNeeded(android.ImageInterfaceContext) bool {
+	return false
+}
+func (x noopImageInterface) RecoveryVariantNeeded(android.ImageInterfaceContext) bool { return false }
+func (x noopImageInterface) ExtraImageVariations(ctx android.ImageInterfaceContext) []string {
+	return nil
+}
+func (x noopImageInterface) SetImageVariation(ctx android.ImageInterfaceContext, variation string) {
 }
 
 func NewGenSrcs() *Module {
@@ -850,16 +893,30 @@
 	taskGenerator := func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask {
 		useNsjail := Bool(properties.Use_nsjail)
 
+		dirSrcs := android.DirectoryPathsForModuleSrc(ctx, properties.Dir_srcs)
+		if len(dirSrcs) > 0 && !useNsjail {
+			ctx.PropertyErrorf("dir_srcs", "can't use dir_srcs if use_nsjail is false")
+			return nil
+		}
+
+		keepGendir := Bool(properties.Keep_gendir)
+		if keepGendir && !useNsjail {
+			ctx.PropertyErrorf("keep_gendir", "can't use keep_gendir if use_nsjail is false")
+			return nil
+		}
+
 		outs := make(android.WritablePaths, len(properties.Out))
 		for i, out := range properties.Out {
 			outs[i] = android.PathForModuleGen(ctx, out)
 		}
 		return []generateTask{{
-			in:        srcFiles,
-			out:       outs,
-			genDir:    android.PathForModuleGen(ctx),
-			cmd:       rawCommand,
-			useNsjail: useNsjail,
+			in:         srcFiles,
+			out:        outs,
+			genDir:     android.PathForModuleGen(ctx),
+			cmd:        rawCommand,
+			useNsjail:  useNsjail,
+			dirSrcs:    dirSrcs,
+			keepGendir: keepGendir,
 		}}
 	}
 
@@ -876,6 +933,14 @@
 type genRuleProperties struct {
 	Use_nsjail *bool
 
+	// List of input directories. Can be set only when use_nsjail is true. Currently, usage of
+	// dir_srcs is limited only to Trusty build.
+	Dir_srcs []string `android:"path"`
+
+	// If set to true, $(genDir) is not truncated. Useful when this genrule can be incrementally
+	// built. Can be set only when use_nsjail is true.
+	Keep_gendir *bool
+
 	// names of the output files that will be generated
 	Out []string `android:"arch_variant"`
 }
diff --git a/genrule/genrule_test.go b/genrule/genrule_test.go
index 9278f15..dcec297 100644
--- a/genrule/genrule_test.go
+++ b/genrule/genrule_test.go
@@ -515,7 +515,7 @@
 
 	for _, test := range testcases {
 		t.Run(test.name, func(t *testing.T) {
-			gen := result.ModuleForTests(test.name, "")
+			gen := result.ModuleForTests(t, test.name, "")
 			manifest := android.RuleBuilderSboxProtoForTests(t, result.TestContext, gen.Output("genrule.sbox.textproto"))
 			hash := manifest.Commands[0].GetInputHash()
 
@@ -643,7 +643,7 @@
 				ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(expectedErrors)).
 				RunTestWithBp(t, testGenruleBp()+bp)
 
-			mod := result.ModuleForTests("gen", "")
+			mod := result.ModuleForTests(t, "gen", "")
 			if expectedErrors != nil {
 				return
 			}
@@ -693,9 +693,6 @@
 
 	expectedCmd := "cp in1 __SBOX_SANDBOX_DIR__/out/out"
 	android.AssertStringEquals(t, "cmd", expectedCmd, gen.rawCommands[0])
-
-	expectedSrcs := []string{"in1"}
-	android.AssertDeepEquals(t, "srcs", expectedSrcs, gen.properties.ResolvedSrcs)
 }
 
 func TestGenruleAllowMissingDependencies(t *testing.T) {
@@ -722,7 +719,7 @@
 				ctx.SetAllowMissingDependencies(true)
 			})).RunTestWithBp(t, bp)
 
-	gen := result.ModuleForTests("gen", "").Output("out")
+	gen := result.ModuleForTests(t, "gen", "").Output("out")
 	if gen.Rule != android.ErrorRule {
 		t.Errorf("Expected missing dependency error rule for gen, got %q", gen.Rule.String())
 	}
@@ -753,15 +750,15 @@
 	android.AssertPathsRelativeToTopEquals(t,
 		"genrule.tag with output",
 		[]string{"out/soong/.intermediates/gen/gen/foo"},
-		result.ModuleForTests("gen_foo", "").Module().(*useSource).srcs)
+		result.ModuleForTests(t, "gen_foo", "").Module().(*useSource).srcs)
 	android.AssertPathsRelativeToTopEquals(t,
 		"genrule.tag with output in subdir",
 		[]string{"out/soong/.intermediates/gen/gen/sub/bar"},
-		result.ModuleForTests("gen_bar", "").Module().(*useSource).srcs)
+		result.ModuleForTests(t, "gen_bar", "").Module().(*useSource).srcs)
 	android.AssertPathsRelativeToTopEquals(t,
 		"genrule.tag with all",
 		[]string{"out/soong/.intermediates/gen/gen/foo", "out/soong/.intermediates/gen/gen/sub/bar"},
-		result.ModuleForTests("gen_all", "").Module().(*useSource).srcs)
+		result.ModuleForTests(t, "gen_all", "").Module().(*useSource).srcs)
 }
 
 func TestGenruleInterface(t *testing.T) {
diff --git a/go.mod b/go.mod
index aa43066..57b59c0 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module android/soong
 
-go 1.22
+go 1.23
 
 require (
 	github.com/google/blueprint v0.0.0
diff --git a/go.work b/go.work
index 46a135b..e538915 100644
--- a/go.work
+++ b/go.work
@@ -1,4 +1,4 @@
-go 1.22
+go 1.23
 
 use (
 	.
diff --git a/golang/golang.go b/golang/golang.go
index 6ee924f..9e0744a 100644
--- a/golang/golang.go
+++ b/golang/golang.go
@@ -47,7 +47,7 @@
 func goPackageModuleFactory() android.Module {
 	module := &GoPackage{}
 	module.AddProperties(module.Properties()...)
-	android.InitAndroidArchModule(module, android.HostSupportedNoCross, android.MultilibFirst)
+	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
 	return module
 }
 
@@ -74,7 +74,7 @@
 func goBinaryModuleFactory() android.Module {
 	module := &GoBinary{}
 	module.AddProperties(module.Properties()...)
-	android.InitAndroidArchModule(module, android.HostSupportedNoCross, android.MultilibFirst)
+	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
 	return module
 }
 
@@ -97,17 +97,16 @@
 	outputFile := android.PathForArbitraryOutput(ctx, android.Rel(ctx, ctx.Config().OutDir(), g.IntermediateFile())).WithoutRel()
 	g.outputFile = outputFile
 
-	// Don't create install rules for modules used by bootstrap, the install command line will differ from
-	// what was used during bootstrap, which will cause ninja to rebuild the module on the next run,
-	// triggering reanalysis.
-	if !usedByBootstrap(ctx.ModuleName()) {
-		installPath := ctx.InstallFile(android.PathForModuleInstall(ctx, "bin"), ctx.ModuleName(), outputFile)
+	installPath := ctx.InstallFile(android.PathForModuleInstall(ctx, "bin"), ctx.ModuleName(), outputFile)
 
-		// Modules in an unexported namespace have no install rule, only add modules in the exported namespaces
-		// to the blueprint_tools phony rules.
-		if !ctx.Config().KatiEnabled() || g.ExportedToMake() {
-			ctx.Phony("blueprint_tools", installPath)
-		}
+	// Modules in an unexported namespace have no install rule, only add modules in the exported namespaces
+	// to the blueprint_tools phony rules.
+	if g.ExportedToMake() && !usedByBootstrap(ctx.ModuleName()) {
+		// Don't add the installed file of bootstrap tools to the deps of `blueprint_tools`.
+		// The install command line will differ from what was used during bootstrap,
+		// which will cause ninja to rebuild the module on the next run,
+		// triggering reanalysis.
+		ctx.Phony("blueprint_tools", installPath)
 	}
 
 	ctx.SetOutputFiles(android.Paths{outputFile}, "")
diff --git a/golang/golang_test.go b/golang/golang_test.go
index b512144..89a8761 100644
--- a/golang/golang_test.go
+++ b/golang/golang_test.go
@@ -16,9 +16,10 @@
 
 import (
 	"android/soong/android"
-	"github.com/google/blueprint/bootstrap"
-	"path/filepath"
+	"regexp"
 	"testing"
+
+	"github.com/google/blueprint/bootstrap"
 )
 
 func TestGolang(t *testing.T) {
@@ -39,13 +40,19 @@
 		android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
 			RegisterGoModuleTypes(ctx)
 			ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
-				ctx.BottomUpBlueprint("bootstrap_deps", bootstrap.BootstrapDeps)
+				ctx.BottomUpBlueprint("bootstrap_deps", bootstrap.BootstrapDeps).UsesReverseDependencies()
 			})
 		}),
 	).RunTestWithBp(t, bp)
 
-	bin := result.ModuleForTests("gobin", result.Config.BuildOSTarget.String())
+	bin := result.ModuleForTests(t, "gobin", result.Config.BuildOSTarget.String())
 
-	expected := filepath.Join("out/soong/host", result.Config.PrebuiltOS(), "bin/go/gobin/obj/gobin")
-	android.AssertPathsRelativeToTopEquals(t, "output files", []string{expected}, bin.OutputFiles(result.TestContext, t, ""))
+	expected := "^out/host/" + result.Config.PrebuiltOS() + "/bin/go/gobin/?[^/]*/obj/gobin$"
+	actual := android.PathsRelativeToTop(bin.OutputFiles(result.TestContext, t, ""))
+	if len(actual) != 1 {
+		t.Fatalf("Expected 1 output file, got %v", actual)
+	}
+	if match, err := regexp.Match(expected, []byte(actual[0])); err != nil || !match {
+		t.Fatalf("Expected output file to match %q, but got %q", expected, actual[0])
+	}
 }
diff --git a/java/Android.bp b/java/Android.bp
index 1101d7a..911af83 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -7,6 +7,7 @@
     pkgPath: "android/soong/java",
     deps: [
         "blueprint",
+        "blueprint-depset",
         "blueprint-pathtools",
         "soong",
         "soong-aconfig",
@@ -15,7 +16,6 @@
         "soong-dexpreopt",
         "soong-genrule",
         "soong-java-config",
-        "soong-testing",
         "soong-provenance",
         "soong-python",
         "soong-remoteexec",
@@ -51,6 +51,7 @@
         "gen.go",
         "generated_java_library.go",
         "genrule.go",
+        "genrule_combiner.go",
         "hiddenapi.go",
         "hiddenapi_modular.go",
         "hiddenapi_monolithic.go",
@@ -86,7 +87,6 @@
         "app_import_test.go",
         "app_set_test.go",
         "app_test.go",
-        "code_metadata_test.go",
         "container_test.go",
         "bootclasspath_fragment_test.go",
         "device_host_converter_test.go",
@@ -96,6 +96,7 @@
         "droiddoc_test.go",
         "droidstubs_test.go",
         "fuzz_test.go",
+        "genrule_combiner_test.go",
         "genrule_test.go",
         "generated_java_library_test.go",
         "hiddenapi_singleton_test.go",
@@ -117,7 +118,6 @@
         "sdk_version_test.go",
         "system_modules_test.go",
         "systemserver_classpath_fragment_test.go",
-        "test_spec_test.go",
     ],
     pluginFor: ["soong_build"],
     visibility: ["//visibility:public"],
diff --git a/java/aapt2.go b/java/aapt2.go
index 61cf373..bae4d1e 100644
--- a/java/aapt2.go
+++ b/java/aapt2.go
@@ -15,7 +15,9 @@
 package java
 
 import (
+	"fmt"
 	"path/filepath"
+	"regexp"
 	"sort"
 	"strconv"
 	"strings"
@@ -31,19 +33,35 @@
 	return strings.HasPrefix(lastDir, "values")
 }
 
+func isFlagsPath(subDir string) bool {
+	re := regexp.MustCompile(`flag\(!?([a-zA-Z_-]+\.)*[a-zA-Z0-9_-]+\)`)
+	lastDir := filepath.Base(subDir)
+	return re.MatchString(lastDir)
+}
+
 // Convert input resource file path to output file path.
 // values-[config]/<file>.xml -> values-[config]_<file>.arsc.flat;
+// flag(fully.qualified.flag_name)/values-[config]/<file>.xml -> /values-[config]_<file>.(fully.qualified.flag_name).arsc.flat;
 // For other resource file, just replace the last "/" with "_" and add .flat extension.
 func pathToAapt2Path(ctx android.ModuleContext, res android.Path) android.WritablePath {
 
-	name := res.Base()
+	extension := filepath.Ext(res.Base())
+	name := strings.TrimSuffix(res.Base(), extension)
 	if isPathValueResource(res) {
-		name = strings.TrimSuffix(name, ".xml") + ".arsc"
+		extension = ".arsc"
 	}
 	subDir := filepath.Dir(res.String())
 	subDir, lastDir := filepath.Split(subDir)
-	name = lastDir + "_" + name + ".flat"
-	return android.PathForModuleOut(ctx, "aapt2", subDir, name)
+	if isFlagsPath(subDir) {
+		var flag string
+		subDir, flag = filepath.Split(filepath.Dir(subDir))
+		flag = strings.TrimPrefix(flag, "flag")
+		name = fmt.Sprintf("%s_%s.%s%s.flat", lastDir, name, flag, extension)
+	} else {
+		name = fmt.Sprintf("%s_%s%s.flat", lastDir, name, extension)
+	}
+	out := android.PathForModuleOut(ctx, "aapt2", subDir, name)
+	return out
 }
 
 // pathsToAapt2Paths Calls pathToAapt2Path on each entry of the given Paths, i.e. []Path.
diff --git a/java/aar.go b/java/aar.go
index 41cc24a..95387a3 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -25,14 +25,15 @@
 	"android/soong/dexpreopt"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/depset"
 	"github.com/google/blueprint/proptools"
 )
 
 type AndroidLibraryDependency interface {
 	ExportPackage() android.Path
-	ResourcesNodeDepSet() *android.DepSet[*resourcesNode]
-	RRODirsDepSet() *android.DepSet[rroDir]
-	ManifestsDepSet() *android.DepSet[android.Path]
+	ResourcesNodeDepSet() depset.DepSet[*resourcesNode]
+	RRODirsDepSet() depset.DepSet[rroDir]
+	ManifestsDepSet() depset.DepSet[android.Path]
 	SetRROEnforcedForDependent(enforce bool)
 	IsRROEnforced(ctx android.BaseModuleContext) bool
 }
@@ -76,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"`
@@ -136,9 +137,9 @@
 
 	aaptProperties aaptProperties
 
-	resourcesNodesDepSet *android.DepSet[*resourcesNode]
-	rroDirsDepSet        *android.DepSet[rroDir]
-	manifestsDepSet      *android.DepSet[android.Path]
+	resourcesNodesDepSet depset.DepSet[*resourcesNode]
+	rroDirsDepSet        depset.DepSet[rroDir]
+	manifestsDepSet      depset.DepSet[android.Path]
 
 	manifestValues struct {
 		applicationId string
@@ -232,15 +233,15 @@
 func (a *aapt) ExportPackage() android.Path {
 	return a.exportPackage
 }
-func (a *aapt) ResourcesNodeDepSet() *android.DepSet[*resourcesNode] {
+func (a *aapt) ResourcesNodeDepSet() depset.DepSet[*resourcesNode] {
 	return a.resourcesNodesDepSet
 }
 
-func (a *aapt) RRODirsDepSet() *android.DepSet[rroDir] {
+func (a *aapt) RRODirsDepSet() depset.DepSet[rroDir] {
 	return a.rroDirsDepSet
 }
 
-func (a *aapt) ManifestsDepSet() *android.DepSet[android.Path] {
+func (a *aapt) ManifestsDepSet() depset.DepSet[android.Path] {
 	return a.manifestsDepSet
 }
 
@@ -256,7 +257,7 @@
 }
 
 func (a *aapt) aapt2Flags(ctx android.ModuleContext, sdkContext android.SdkContext,
-	manifestPath android.Path) (compileFlags, linkFlags []string, linkDeps android.Paths,
+	manifestPath android.Path, doNotIncludeAssetDirImplicitly bool) (compileFlags, linkFlags []string, linkDeps android.Paths,
 	resDirs, overlayDirs []globbedResourceDir, rroDirs []rroDir, resZips android.Paths) {
 
 	hasVersionCode := android.PrefixInList(a.aaptProperties.Aaptflags, "--version-code")
@@ -273,8 +274,13 @@
 		Paths:       a.aaptProperties.Assets,
 		IncludeDirs: false,
 	})
-	assetDirs := android.PathsWithOptionalDefaultForModuleSrc(ctx, a.aaptProperties.Asset_dirs, "assets")
-	resourceDirs := android.PathsWithOptionalDefaultForModuleSrc(ctx, a.aaptProperties.Resource_dirs, "res")
+	var assetDirs android.Paths
+	if doNotIncludeAssetDirImplicitly {
+		assetDirs = android.PathsForModuleSrc(ctx, a.aaptProperties.Asset_dirs)
+	} else {
+		assetDirs = android.PathsWithOptionalDefaultForModuleSrc(ctx, a.aaptProperties.Asset_dirs, "assets")
+	}
+	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
@@ -385,8 +391,9 @@
 		versionName = proptools.NinjaEscape(versionName)
 		linkFlags = append(linkFlags, "--version-name ", versionName)
 	}
-
-	linkFlags, compileFlags = android.FilterList(linkFlags, []string{"--legacy"})
+	// Split the flags by prefix, as --png-compression-level has the "=value" suffix.
+	linkFlags, compileFlags = android.FilterListByPrefix(linkFlags,
+		[]string{"--legacy", "--png-compression-level"})
 
 	// Always set --pseudo-localize, it will be stripped out later for release
 	// builds that don't want it.
@@ -416,6 +423,25 @@
 	extraLinkFlags                 []string
 	aconfigTextFiles               android.Paths
 	usesLibrary                    *usesLibrary
+	// If rroDirs is provided, it will be used to generate package-res.apk
+	rroDirs *android.Paths
+	// If manifestForAapt is not nil, it will be used for aapt instead of the default source manifest.
+	manifestForAapt android.Path
+}
+
+func filterRRO(rroDirsDepSet depset.DepSet[rroDir], filter overlayType) android.Paths {
+	var paths android.Paths
+	seen := make(map[android.Path]bool)
+	for _, d := range rroDirsDepSet.ToList() {
+		if d.overlayType == filter {
+			if seen[d.path] {
+				continue
+			}
+			seen[d.path] = true
+			paths = append(paths, d.path)
+		}
+	}
+	return paths
 }
 
 func (a *aapt) buildActions(ctx android.ModuleContext, opts aaptBuildActionOptions) {
@@ -427,10 +453,15 @@
 	opts.classLoaderContexts = opts.classLoaderContexts.ExcludeLibs(opts.excludedLibs)
 
 	// App manifest file
-	manifestFile := proptools.StringDefault(a.aaptProperties.Manifest, "AndroidManifest.xml")
-	manifestSrcPath := android.PathForModuleSrc(ctx, manifestFile)
+	var manifestFilePath android.Path
+	if opts.manifestForAapt != nil {
+		manifestFilePath = opts.manifestForAapt
+	} else {
+		manifestFile := proptools.StringDefault(a.aaptProperties.Manifest, "AndroidManifest.xml")
+		manifestFilePath = android.PathForModuleSrc(ctx, manifestFile)
+	}
 
-	manifestPath := ManifestFixer(ctx, manifestSrcPath, ManifestFixerParams{
+	manifestPath := ManifestFixer(ctx, manifestFilePath, ManifestFixerParams{
 		SdkContext:                     opts.sdkContext,
 		ClassLoaderContexts:            opts.classLoaderContexts,
 		IsLibrary:                      a.isLibrary,
@@ -469,7 +500,12 @@
 		a.mergedManifestFile = manifestPath
 	}
 
-	compileFlags, linkFlags, linkDeps, resDirs, overlayDirs, rroDirs, resZips := a.aapt2Flags(ctx, opts.sdkContext, manifestPath)
+	// do not include assets in autogenerated RRO.
+	compileFlags, linkFlags, linkDeps, resDirs, overlayDirs, rroDirs, resZips := a.aapt2Flags(ctx, opts.sdkContext, manifestPath, opts.rroDirs != nil)
+
+	a.rroDirsDepSet = depset.NewBuilder[rroDir](depset.TOPOLOGICAL).
+		Direct(rroDirs...).
+		Transitive(staticRRODirsDepSet).Build()
 
 	linkFlags = append(linkFlags, libFlags...)
 	linkDeps = append(linkDeps, sharedExportPackages...)
@@ -559,9 +595,16 @@
 		}
 	}
 
-	for _, dir := range overlayDirs {
-		compiledOverlay = append(compiledOverlay, aapt2Compile(ctx, dir.dir, dir.files,
-			compileFlags, a.filterProduct(), opts.aconfigTextFiles).Paths()...)
+	var compiledRro, compiledRroOverlay android.Paths
+	if opts.rroDirs != nil {
+		compiledRro, compiledRroOverlay = a.compileResInDir(ctx, *opts.rroDirs, compileFlags, opts.aconfigTextFiles)
+	} else {
+		// RRO enforcement is done based on module name. Compile the overlayDirs only if rroDirs is nil.
+		// This ensures that the autogenerated RROs do not compile the overlay dirs twice.
+		for _, dir := range overlayDirs {
+			compiledOverlay = append(compiledOverlay, aapt2Compile(ctx, dir.dir, dir.files,
+				compileFlags, a.filterProduct(), opts.aconfigTextFiles).Paths()...)
+		}
 	}
 
 	var splitPackages android.WritablePaths
@@ -590,10 +633,20 @@
 	if !a.isLibrary {
 		transitiveAssets = android.ReverseSliceInPlace(staticDeps.assets())
 	}
-	aapt2Link(ctx, packageRes, srcJar, proguardOptionsFile, rTxt,
-		linkFlags, linkDeps, compiledRes, compiledOverlay, transitiveAssets, splitPackages,
-		opts.aconfigTextFiles)
-	ctx.CheckbuildFile(packageRes)
+	if opts.rroDirs == nil { // link resources and overlay
+		aapt2Link(ctx, packageRes, srcJar, proguardOptionsFile, rTxt,
+			linkFlags, linkDeps, compiledRes, compiledOverlay, transitiveAssets, splitPackages,
+			opts.aconfigTextFiles)
+		ctx.CheckbuildFile(packageRes)
+	} else { // link autogenerated rro
+		if len(compiledRro) == 0 {
+			return
+		}
+		aapt2Link(ctx, packageRes, srcJar, proguardOptionsFile, rTxt,
+			linkFlags, linkDeps, compiledRro, compiledRroOverlay, nil, nil,
+			opts.aconfigTextFiles)
+		ctx.CheckbuildFile(packageRes)
+	}
 
 	// Extract assets from the resource package output so that they can be used later in aapt2link
 	// for modules that depend on this one.
@@ -639,7 +692,7 @@
 	a.extraAaptPackagesFile = extraPackages
 	a.rTxt = rTxt
 	a.splits = splits
-	a.resourcesNodesDepSet = android.NewDepSetBuilder[*resourcesNode](android.TOPOLOGICAL).
+	a.resourcesNodesDepSet = depset.NewBuilder[*resourcesNode](depset.TOPOLOGICAL).
 		Direct(&resourcesNode{
 			resPackage:          a.exportPackage,
 			manifest:            a.manifestPath,
@@ -651,15 +704,46 @@
 			usedResourceProcessor: a.useResourceProcessorBusyBox(ctx),
 		}).
 		Transitive(staticResourcesNodesDepSet).Build()
-	a.rroDirsDepSet = android.NewDepSetBuilder[rroDir](android.TOPOLOGICAL).
-		Direct(rroDirs...).
-		Transitive(staticRRODirsDepSet).Build()
-	a.manifestsDepSet = android.NewDepSetBuilder[android.Path](android.TOPOLOGICAL).
+	a.manifestsDepSet = depset.NewBuilder[android.Path](depset.TOPOLOGICAL).
 		Direct(a.manifestPath).
 		DirectSlice(additionalManifests).
 		Transitive(staticManifestsDepSet).Build()
 }
 
+// comileResInDir finds the resource files in dirs by globbing and then compiles them using aapt2
+// returns the file paths of compiled resources
+// dirs[0] is used as compileRes
+// dirs[1:] is used as compileOverlay
+func (a *aapt) compileResInDir(ctx android.ModuleContext, dirs android.Paths, compileFlags []string, aconfig android.Paths) (android.Paths, android.Paths) {
+	filesInDir := func(dir android.Path) android.Paths {
+		files, err := ctx.GlobWithDeps(filepath.Join(dir.String(), "**/*"), androidResourceIgnoreFilenames)
+		if err != nil {
+			ctx.ModuleErrorf("failed to glob overlay resource dir %q: %s", dir, err.Error())
+			return nil
+		}
+		var filePaths android.Paths
+		for _, file := range files {
+			if strings.HasSuffix(file, "/") {
+				continue // ignore directories
+			}
+			filePaths = append(filePaths, android.PathForSource(ctx, file))
+		}
+		return filePaths
+	}
+
+	var compiledRes, compiledOverlay android.Paths
+	if len(dirs) == 0 {
+		return nil, nil
+	}
+	compiledRes = append(compiledRes, aapt2Compile(ctx, dirs[0], filesInDir(dirs[0]), compileFlags, a.filterProduct(), aconfig).Paths()...)
+	if len(dirs) > 0 {
+		for _, dir := range dirs[1:] {
+			compiledOverlay = append(compiledOverlay, aapt2Compile(ctx, dir, filesInDir(dir), compileFlags, a.filterProduct(), aconfig).Paths()...)
+		}
+	}
+	return compiledRes, compiledOverlay
+}
+
 var resourceProcessorBusyBox = pctx.AndroidStaticRule("resourceProcessorBusyBox",
 	blueprint.RuleParams{
 		Command: "${config.JavaCmd} -cp ${config.ResourceProcessorBusyBox} " +
@@ -773,8 +857,8 @@
 // aaptLibs collects libraries from dependencies and sdk_version and converts them into paths
 func aaptLibs(ctx android.ModuleContext, sdkContext android.SdkContext,
 	classLoaderContexts dexpreopt.ClassLoaderContextMap, usesLibrary *usesLibrary) (
-	staticResourcesNodes, sharedResourcesNodes *android.DepSet[*resourcesNode], staticRRODirs *android.DepSet[rroDir],
-	staticManifests *android.DepSet[android.Path], sharedLibs android.Paths, flags []string) {
+	staticResourcesNodes, sharedResourcesNodes depset.DepSet[*resourcesNode], staticRRODirs depset.DepSet[rroDir],
+	staticManifests depset.DepSet[android.Path], sharedLibs android.Paths, flags []string) {
 
 	if classLoaderContexts == nil {
 		// Not all callers need to compute class loader context, those who don't just pass nil.
@@ -787,26 +871,28 @@
 		sharedLibs = append(sharedLibs, sdkDep.jars...)
 	}
 
-	var staticResourcesNodeDepSets []*android.DepSet[*resourcesNode]
-	var sharedResourcesNodeDepSets []*android.DepSet[*resourcesNode]
-	rroDirsDepSetBuilder := android.NewDepSetBuilder[rroDir](android.TOPOLOGICAL)
-	manifestsDepSetBuilder := android.NewDepSetBuilder[android.Path](android.TOPOLOGICAL)
+	var staticResourcesNodeDepSets []depset.DepSet[*resourcesNode]
+	var sharedResourcesNodeDepSets []depset.DepSet[*resourcesNode]
+	rroDirsDepSetBuilder := depset.NewBuilder[rroDir](depset.TOPOLOGICAL)
+	manifestsDepSetBuilder := depset.NewBuilder[android.Path](depset.TOPOLOGICAL)
 
-	ctx.VisitDirectDeps(func(module android.Module) {
+	ctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 		depTag := ctx.OtherModuleDependencyTag(module)
 
 		var exportPackage android.Path
-		aarDep, _ := module.(AndroidLibraryDependency)
-		if aarDep != nil {
-			exportPackage = aarDep.ExportPackage()
+		var aarDep *AndroidLibraryDependencyInfo
+		javaInfo, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider)
+		if ok && javaInfo.AndroidLibraryDependencyInfo != nil {
+			aarDep = javaInfo.AndroidLibraryDependencyInfo
+			exportPackage = aarDep.ExportPackage
 		}
 
 		switch depTag {
 		case instrumentationForTag:
 			// Nothing, instrumentationForTag is treated as libTag for javac but not for aapt2.
-		case sdkLibTag, libTag:
+		case sdkLibTag, libTag, rroDepTag:
 			if exportPackage != nil {
-				sharedResourcesNodeDepSets = append(sharedResourcesNodeDepSets, aarDep.ResourcesNodeDepSet())
+				sharedResourcesNodeDepSets = append(sharedResourcesNodeDepSets, aarDep.ResourcesNodeDepSet)
 				sharedLibs = append(sharedLibs, exportPackage)
 			}
 		case frameworkResTag:
@@ -815,9 +901,9 @@
 			}
 		case staticLibTag:
 			if exportPackage != nil {
-				staticResourcesNodeDepSets = append(staticResourcesNodeDepSets, aarDep.ResourcesNodeDepSet())
-				rroDirsDepSetBuilder.Transitive(aarDep.RRODirsDepSet())
-				manifestsDepSetBuilder.Transitive(aarDep.ManifestsDepSet())
+				staticResourcesNodeDepSets = append(staticResourcesNodeDepSets, aarDep.ResourcesNodeDepSet)
+				rroDirsDepSetBuilder.Transitive(aarDep.RRODirsDepSet)
+				manifestsDepSetBuilder.Transitive(aarDep.ManifestsDepSet)
 			}
 		}
 
@@ -834,9 +920,9 @@
 	// dependencies) the highest priority dependency is listed first, but for resources the highest priority
 	// dependency has to be listed last.  This is also inconsistent with the way manifests from the same
 	// transitive dependencies are merged.
-	staticResourcesNodes = android.NewDepSet(android.TOPOLOGICAL, nil,
+	staticResourcesNodes = depset.New(depset.TOPOLOGICAL, nil,
 		android.ReverseSliceInPlace(staticResourcesNodeDepSets))
-	sharedResourcesNodes = android.NewDepSet(android.TOPOLOGICAL, nil,
+	sharedResourcesNodes = depset.New(depset.TOPOLOGICAL, nil,
 		android.ReverseSliceInPlace(sharedResourcesNodeDepSets))
 
 	staticRRODirs = rroDirsDepSetBuilder.Build()
@@ -853,6 +939,12 @@
 	return staticResourcesNodes, sharedResourcesNodes, staticRRODirs, staticManifests, sharedLibs, flags
 }
 
+type AndroidLibraryInfo struct {
+	// Empty for now
+}
+
+var AndroidLibraryInfoProvider = blueprint.NewProvider[AndroidLibraryInfo]()
+
 type AndroidLibrary struct {
 	Library
 	aapt
@@ -939,7 +1031,7 @@
 		extraSrcJars = android.Paths{a.aapt.aaptSrcJar}
 	}
 
-	a.Module.compile(ctx, extraSrcJars, extraClasspathJars, extraCombinedJars, nil)
+	javaInfo := a.Module.compile(ctx, extraSrcJars, extraClasspathJars, extraCombinedJars, nil)
 
 	a.aarFile = android.PathForModuleOut(ctx, ctx.ModuleName()+".aar")
 	var res android.Paths
@@ -948,7 +1040,7 @@
 	}
 
 	prebuiltJniPackages := android.Paths{}
-	ctx.VisitDirectDeps(func(module android.Module) {
+	ctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 		if info, ok := android.OtherModuleProvider(ctx, module, JniPackageProvider); ok {
 			prebuiltJniPackages = append(prebuiltJniPackages, info.JniPackages...)
 		}
@@ -963,7 +1055,16 @@
 		AconfigTextFiles: aconfigTextFilePaths,
 	})
 
+	android.SetProvider(ctx, AndroidLibraryInfoProvider, AndroidLibraryInfo{})
+
+	if javaInfo != nil {
+		setExtraJavaInfo(ctx, a, javaInfo)
+		android.SetProvider(ctx, JavaInfoProvider, javaInfo)
+	}
+
 	a.setOutputFiles(ctx)
+
+	buildComplianceMetadata(ctx)
 }
 
 func (a *AndroidLibrary) setOutputFiles(ctx android.ModuleContext) {
@@ -1064,8 +1165,8 @@
 	rTxt                               android.Path
 	rJar                               android.Path
 
-	resourcesNodesDepSet *android.DepSet[*resourcesNode]
-	manifestsDepSet      *android.DepSet[android.Path]
+	resourcesNodesDepSet depset.DepSet[*resourcesNode]
+	manifestsDepSet      depset.DepSet[android.Path]
 
 	hideApexVariantFromMake bool
 
@@ -1111,15 +1212,15 @@
 func (a *AARImport) ExportPackage() android.Path {
 	return a.exportPackage
 }
-func (a *AARImport) ResourcesNodeDepSet() *android.DepSet[*resourcesNode] {
+func (a *AARImport) ResourcesNodeDepSet() depset.DepSet[*resourcesNode] {
 	return a.resourcesNodesDepSet
 }
 
-func (a *AARImport) RRODirsDepSet() *android.DepSet[rroDir] {
-	return android.NewDepSet[rroDir](android.TOPOLOGICAL, nil, nil)
+func (a *AARImport) RRODirsDepSet() depset.DepSet[rroDir] {
+	return depset.New[rroDir](depset.TOPOLOGICAL, nil, nil)
 }
 
-func (a *AARImport) ManifestsDepSet() *android.DepSet[android.Path] {
+func (a *AARImport) ManifestsDepSet() depset.DepSet[android.Path] {
 	return a.manifestsDepSet
 }
 
@@ -1233,13 +1334,13 @@
 	proguardFlags := extractedAARDir.Join(ctx, "proguard.txt")
 	transitiveProguardFlags, transitiveUnconditionalExportedFlags := collectDepProguardSpecInfo(ctx)
 	android.SetProvider(ctx, ProguardSpecInfoProvider, ProguardSpecInfo{
-		ProguardFlagsFiles: android.NewDepSet[android.Path](
-			android.POSTORDER,
+		ProguardFlagsFiles: depset.New[android.Path](
+			depset.POSTORDER,
 			android.Paths{proguardFlags},
 			transitiveProguardFlags,
 		),
-		UnconditionallyExportedProguardFlags: android.NewDepSet[android.Path](
-			android.POSTORDER,
+		UnconditionallyExportedProguardFlags: depset.New[android.Path](
+			depset.POSTORDER,
 			nil,
 			transitiveUnconditionalExportedFlags,
 		),
@@ -1320,7 +1421,7 @@
 	aapt2ExtractExtraPackages(ctx, extraAaptPackagesFile, a.rJar)
 	a.extraAaptPackagesFile = extraAaptPackagesFile
 
-	resourcesNodesDepSetBuilder := android.NewDepSetBuilder[*resourcesNode](android.TOPOLOGICAL)
+	resourcesNodesDepSetBuilder := depset.NewBuilder[*resourcesNode](depset.TOPOLOGICAL)
 	resourcesNodesDepSetBuilder.Direct(&resourcesNode{
 		resPackage: a.exportPackage,
 		manifest:   a.manifest,
@@ -1333,7 +1434,7 @@
 	resourcesNodesDepSetBuilder.Transitive(staticResourcesNodesDepSet)
 	a.resourcesNodesDepSet = resourcesNodesDepSetBuilder.Build()
 
-	manifestDepSetBuilder := android.NewDepSetBuilder[android.Path](android.TOPOLOGICAL).Direct(a.manifest)
+	manifestDepSetBuilder := depset.NewBuilder[android.Path](depset.TOPOLOGICAL).Direct(a.manifest)
 	manifestDepSetBuilder.Transitive(staticManifestsDepSet)
 	a.manifestsDepSet = manifestDepSetBuilder.Build()
 
@@ -1352,11 +1453,11 @@
 	var staticJars android.Paths
 	var staticHeaderJars android.Paths
 	var staticResourceJars android.Paths
-	var transitiveStaticLibsHeaderJars []*android.DepSet[android.Path]
-	var transitiveStaticLibsImplementationJars []*android.DepSet[android.Path]
-	var transitiveStaticLibsResourceJars []*android.DepSet[android.Path]
+	var transitiveStaticLibsHeaderJars []depset.DepSet[android.Path]
+	var transitiveStaticLibsImplementationJars []depset.DepSet[android.Path]
+	var transitiveStaticLibsResourceJars []depset.DepSet[android.Path]
 
-	ctx.VisitDirectDeps(func(module android.Module) {
+	ctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 		if dep, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider); ok {
 			tag := ctx.OtherModuleDependencyTag(module)
 			switch tag {
@@ -1364,24 +1465,18 @@
 				staticJars = append(staticJars, dep.ImplementationJars...)
 				staticHeaderJars = append(staticHeaderJars, dep.HeaderJars...)
 				staticResourceJars = append(staticResourceJars, dep.ResourceJars...)
-				if dep.TransitiveStaticLibsHeaderJars != nil {
-					transitiveStaticLibsHeaderJars = append(transitiveStaticLibsHeaderJars, dep.TransitiveStaticLibsHeaderJars)
-				}
-				if dep.TransitiveStaticLibsImplementationJars != nil {
-					transitiveStaticLibsImplementationJars = append(transitiveStaticLibsImplementationJars, dep.TransitiveStaticLibsImplementationJars)
-				}
-				if dep.TransitiveStaticLibsResourceJars != nil {
-					transitiveStaticLibsResourceJars = append(transitiveStaticLibsResourceJars, dep.TransitiveStaticLibsResourceJars)
-				}
+				transitiveStaticLibsHeaderJars = append(transitiveStaticLibsHeaderJars, dep.TransitiveStaticLibsHeaderJars)
+				transitiveStaticLibsImplementationJars = append(transitiveStaticLibsImplementationJars, dep.TransitiveStaticLibsImplementationJars)
+				transitiveStaticLibsResourceJars = append(transitiveStaticLibsResourceJars, dep.TransitiveStaticLibsResourceJars)
 			}
 		}
 		addCLCFromDep(ctx, module, a.classLoaderContexts)
 		addMissingOptionalUsesLibsFromDep(ctx, module, &a.usesLibrary)
 	})
 
-	completeStaticLibsHeaderJars := android.NewDepSet(android.PREORDER, android.Paths{classpathFile}, transitiveStaticLibsHeaderJars)
-	completeStaticLibsImplementationJars := android.NewDepSet(android.PREORDER, android.Paths{classpathFile}, transitiveStaticLibsImplementationJars)
-	completeStaticLibsResourceJars := android.NewDepSet(android.PREORDER, nil, transitiveStaticLibsResourceJars)
+	completeStaticLibsHeaderJars := depset.New(depset.PREORDER, android.Paths{classpathFile}, transitiveStaticLibsHeaderJars)
+	completeStaticLibsImplementationJars := depset.New(depset.PREORDER, android.Paths{classpathFile}, transitiveStaticLibsImplementationJars)
+	completeStaticLibsResourceJars := depset.New(depset.PREORDER, nil, transitiveStaticLibsResourceJars)
 
 	var implementationJarFile android.Path
 	var combineJars android.Paths
@@ -1457,7 +1552,7 @@
 		ctx.CheckbuildFile(a.implementationJarFile)
 	}
 
-	android.SetProvider(ctx, JavaInfoProvider, &JavaInfo{
+	javaInfo := &JavaInfo{
 		HeaderJars:                             android.PathsIfNonNil(a.headerJarFile),
 		LocalHeaderJars:                        android.PathsIfNonNil(classpathFile),
 		TransitiveStaticLibsHeaderJars:         completeStaticLibsHeaderJars,
@@ -1470,7 +1565,9 @@
 		ImplementationJars:                     android.PathsIfNonNil(a.implementationJarFile),
 		StubsLinkType:                          Implementation,
 		// TransitiveAconfigFiles: // TODO(b/289117800): LOCAL_ACONFIG_FILES for prebuilts
-	})
+	}
+	setExtraJavaInfo(ctx, a, javaInfo)
+	android.SetProvider(ctx, JavaInfoProvider, javaInfo)
 
 	if proptools.Bool(a.properties.Extract_jni) {
 		for _, t := range ctx.MultiTargets() {
@@ -1499,6 +1596,8 @@
 
 	ctx.SetOutputFiles([]android.Path{a.implementationAndResourcesJarFile}, "")
 	ctx.SetOutputFiles([]android.Path{a.aarPath}, ".aar")
+
+	buildComplianceMetadata(ctx)
 }
 
 func (a *AARImport) HeaderJars() android.Paths {
@@ -1526,8 +1625,16 @@
 var _ android.ApexModule = (*AARImport)(nil)
 
 // Implements android.ApexModule
-func (a *AARImport) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
-	return a.depIsInSameApex(ctx, dep)
+func (m *AARImport) GetDepInSameApexChecker() android.DepInSameApexChecker {
+	return AARImportDepInSameApexChecker{}
+}
+
+type AARImportDepInSameApexChecker struct {
+	android.BaseDepInSameApexChecker
+}
+
+func (m AARImportDepInSameApexChecker) OutgoingDepIsInSameApex(tag blueprint.DependencyTag) bool {
+	return depIsInSameApex(tag)
 }
 
 // Implements android.ApexModule
@@ -1563,5 +1670,5 @@
 }
 
 func (a *AARImport) IDEInfo(ctx android.BaseModuleContext, dpInfo *android.IdeInfo) {
-	dpInfo.Jars = append(dpInfo.Jars, a.headerJarFile.String(), a.rJar.String())
+	dpInfo.Jars = append(dpInfo.Jars, a.implementationJarFile.String(), a.rJar.String())
 }
diff --git a/java/aar_test.go b/java/aar_test.go
index aa4f0af..088ad6c 100644
--- a/java/aar_test.go
+++ b/java/aar_test.go
@@ -21,6 +21,7 @@
 )
 
 func TestAarImportProducesJniPackages(t *testing.T) {
+	t.Parallel()
 	ctx := android.GroupFixturePreparers(
 		PrepareForTestWithJavaDefaultModules,
 	).RunTestWithBp(t, `
@@ -50,8 +51,9 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
 			appMod := ctx.Module(tc.name, "android_common")
-			appTestMod := ctx.ModuleForTests(tc.name, "android_common")
+			appTestMod := ctx.ModuleForTests(t, tc.name, "android_common")
 
 			info, ok := android.OtherModuleProvider(ctx, appMod, JniPackageProvider)
 			if !ok {
@@ -84,6 +86,7 @@
 }
 
 func TestLibraryFlagsPackages(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
 	).RunTestWithBp(t, `
@@ -114,7 +117,7 @@
 		}
 	`)
 
-	foo := result.ModuleForTests("foo", "android_common")
+	foo := result.ModuleForTests(t, "foo", "android_common")
 
 	// android_library module depends on aconfig_declarations listed in flags_packages
 	android.AssertBoolEquals(t, "foo expected to depend on bar", true,
@@ -133,6 +136,7 @@
 }
 
 func TestAndroidLibraryOutputFilesRel(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		PrepareForTestWithJavaDefaultModules,
 	).RunTestWithBp(t, `
@@ -155,9 +159,9 @@
 		}
 	`)
 
-	foo := result.ModuleForTests("foo", "android_common")
-	bar := result.ModuleForTests("bar", "android_common")
-	baz := result.ModuleForTests("baz", "android_common")
+	foo := result.ModuleForTests(t, "foo", "android_common")
+	bar := result.ModuleForTests(t, "bar", "android_common")
+	baz := result.ModuleForTests(t, "baz", "android_common")
 
 	fooOutputPaths := foo.OutputFiles(result.TestContext, t, "")
 	barOutputPaths := bar.OutputFiles(result.TestContext, t, "")
diff --git a/java/android_manifest_test.go b/java/android_manifest_test.go
index 7c91884..ce227b9 100644
--- a/java/android_manifest_test.go
+++ b/java/android_manifest_test.go
@@ -21,6 +21,7 @@
 )
 
 func TestManifestMerger(t *testing.T) {
+	t.Parallel()
 	bp := `
 		android_app {
 			name: "app",
@@ -80,7 +81,7 @@
 
 	result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(t, bp)
 
-	manifestMergerRule := result.ModuleForTests("app", "android_common").Rule("manifestMerger")
+	manifestMergerRule := result.ModuleForTests(t, "app", "android_common").Rule("manifestMerger")
 	android.AssertPathRelativeToTopEquals(t, "main manifest",
 		"out/soong/.intermediates/app/android_common/manifest_fixer/AndroidManifest.xml",
 		manifestMergerRule.Input)
@@ -100,6 +101,7 @@
 }
 
 func TestManifestValuesApplicationIdSetsPackageName(t *testing.T) {
+	t.Parallel()
 	bp := `
 		android_test {
 			name: "test",
@@ -127,7 +129,7 @@
 
 	result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(t, bp)
 
-	manifestMergerRule := result.ModuleForTests("test", "android_common").Rule("manifestMerger")
+	manifestMergerRule := result.ModuleForTests(t, "test", "android_common").Rule("manifestMerger")
 	android.AssertStringMatches(t,
 		"manifest merger args",
 		manifestMergerRule.Args["args"],
diff --git a/java/androidmk.go b/java/androidmk.go
index 2dff6cd..c9de7e6 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -23,13 +23,12 @@
 	"github.com/google/blueprint/proptools"
 )
 
-func (library *Library) AndroidMkEntriesHostDex() android.AndroidMkEntries {
-	hostDexNeeded := Bool(library.deviceProperties.Hostdex) && !library.Host()
-	if library.hideApexVariantFromMake {
-		hostDexNeeded = false
-	}
+func (library *Library) hostDexNeeded() bool {
+	return Bool(library.deviceProperties.Hostdex) && !library.Host() && !library.hideApexVariantFromMake
+}
 
-	if hostDexNeeded {
+func (library *Library) AndroidMkEntriesHostDex() android.AndroidMkEntries {
+	if library.hostDexNeeded() {
 		var output android.Path
 		if library.dexJarFile.IsSet() {
 			output = library.dexJarFile.Path()
@@ -71,11 +70,7 @@
 	if library.hideApexVariantFromMake {
 		// For a java library built for an APEX, we don't need a Make module for itself. Otherwise, it
 		// will conflict with the platform variant because they have the same module name in the
-		// makefile. However, we need to add its dexpreopt outputs as sub-modules, if it is preopted.
-		dexpreoptEntries := library.dexpreopter.AndroidMkEntriesForApex()
-		if len(dexpreoptEntries) > 0 {
-			entriesList = append(entriesList, dexpreoptEntries...)
-		}
+		// makefile.
 		entriesList = append(entriesList, android.AndroidMkEntries{Disabled: true})
 	} else if !library.ApexModuleBase.AvailableFor(android.AvailableToPlatform) {
 		// Platform variant.  If not available for the platform, we don't need Make module.
@@ -125,6 +120,14 @@
 					}
 				},
 			},
+			ExtraFooters: []android.AndroidMkExtraFootersFunc{
+				func(w io.Writer, name, prefix, moduleDir string) {
+					if library.apiXmlFile != nil {
+						fmt.Fprintf(w, "$(call declare-1p-target,%s,)\n", library.apiXmlFile.String())
+						fmt.Fprintf(w, "$(eval $(call copy-one-file,%s,$(TARGET_OUT_COMMON_INTERMEDIATES)/%s))\n", library.apiXmlFile.String(), library.apiXmlFile.Base())
+					}
+				},
+			},
 		})
 	}
 
@@ -281,50 +284,24 @@
 		return nil
 	}
 
-	if !binary.isWrapperVariant {
-		return []android.AndroidMkEntries{android.AndroidMkEntries{
-			Class:      "JAVA_LIBRARIES",
-			OutputFile: android.OptionalPathForPath(binary.outputFile),
-			Include:    "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
-			ExtraEntries: []android.AndroidMkExtraEntriesFunc{
-				func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-					entries.SetPath("LOCAL_SOONG_HEADER_JAR", binary.headerJarFile)
-					entries.SetPath("LOCAL_SOONG_CLASSES_JAR", binary.implementationAndResourcesJar)
-					if binary.dexJarFile.IsSet() {
-						entries.SetPath("LOCAL_SOONG_DEX_JAR", binary.dexJarFile.Path())
-					}
-					if len(binary.dexpreopter.builtInstalled) > 0 {
-						entries.SetString("LOCAL_SOONG_BUILT_INSTALLED", binary.dexpreopter.builtInstalled)
-					}
-				},
+	return []android.AndroidMkEntries{{
+		Class:      "JAVA_LIBRARIES",
+		OutputFile: android.OptionalPathForPath(binary.outputFile),
+		Include:    "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+				entries.SetPath("LOCAL_SOONG_HEADER_JAR", binary.headerJarFile)
+				entries.SetPath("LOCAL_SOONG_CLASSES_JAR", binary.implementationAndResourcesJar)
+				if binary.dexJarFile.IsSet() {
+					entries.SetPath("LOCAL_SOONG_DEX_JAR", binary.dexJarFile.Path())
+				}
+				if len(binary.dexpreopter.builtInstalled) > 0 {
+					entries.SetString("LOCAL_SOONG_BUILT_INSTALLED", binary.dexpreopter.builtInstalled)
+				}
+				entries.AddStrings("LOCAL_REQUIRED_MODULES", binary.androidMkNamesOfJniLibs...)
 			},
-			ExtraFooters: []android.AndroidMkExtraFootersFunc{
-				func(w io.Writer, name, prefix, moduleDir string) {
-					fmt.Fprintln(w, "jar_installed_module := $(LOCAL_INSTALLED_MODULE)")
-				},
-			},
-		}}
-	} else {
-		outputFile := binary.wrapperFile
-
-		return []android.AndroidMkEntries{android.AndroidMkEntries{
-			Class:      "EXECUTABLES",
-			OutputFile: android.OptionalPathForPath(outputFile),
-			ExtraEntries: []android.AndroidMkExtraEntriesFunc{
-				func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-					entries.SetBool("LOCAL_STRIP_MODULE", false)
-					entries.AddStrings("LOCAL_REQUIRED_MODULES", binary.androidMkNamesOfJniLibs...)
-				},
-			},
-			ExtraFooters: []android.AndroidMkExtraFootersFunc{
-				func(w io.Writer, name, prefix, moduleDir string) {
-					// Ensure that the wrapper script timestamp is always updated when the jar is updated
-					fmt.Fprintln(w, "$(LOCAL_INSTALLED_MODULE): $(jar_installed_module)")
-					fmt.Fprintln(w, "jar_installed_module :=")
-				},
-			},
-		}}
-	}
+		},
+	}}
 }
 
 func (app *AndroidApp) AndroidMkEntries() []android.AndroidMkEntries {
@@ -333,15 +310,11 @@
 			Disabled: true,
 		}}
 	}
-	var required []string
-	if proptools.Bool(app.appProperties.Generate_product_characteristics_rro) {
-		required = []string{app.productCharacteristicsRROPackageName()}
-	}
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
 		Class:      "APPS",
 		OutputFile: android.OptionalPathForPath(app.outputFile),
 		Include:    "$(BUILD_SYSTEM)/soong_app_prebuilt.mk",
-		Required:   required,
+		Required:   app.requiredModuleNames,
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				// App module names can be overridden.
@@ -376,31 +349,6 @@
 					entries.SetBoolIfTrue("LOCAL_NO_STANDARD_LIBRARIES", true)
 				}
 
-				filterRRO := func(filter overlayType) android.Paths {
-					var paths android.Paths
-					seen := make(map[android.Path]bool)
-					for _, d := range app.rroDirsDepSet.ToList() {
-						if d.overlayType == filter {
-							if seen[d.path] {
-								continue
-							}
-							seen[d.path] = true
-							paths = append(paths, d.path)
-						}
-					}
-					// Reverse the order, Soong stores rroDirs in aapt2 order (low to high priority), but Make
-					// expects it in LOCAL_RESOURCE_DIRS order (high to low priority).
-					return android.ReversePaths(paths)
-				}
-				deviceRRODirs := filterRRO(device)
-				if len(deviceRRODirs) > 0 {
-					entries.AddStrings("LOCAL_SOONG_DEVICE_RRO_DIRS", deviceRRODirs.Strings()...)
-				}
-				productRRODirs := filterRRO(product)
-				if len(productRRODirs) > 0 {
-					entries.AddStrings("LOCAL_SOONG_PRODUCT_RRO_DIRS", productRRODirs.Strings()...)
-				}
-
 				entries.SetBoolIfTrue("LOCAL_EXPORT_PACKAGE_RESOURCES", Bool(app.appProperties.Export_package_resources))
 
 				entries.SetPath("LOCAL_FULL_MANIFEST_FILE", app.manifestPath)
@@ -451,6 +399,24 @@
 	}
 }
 
+func (a *AutogenRuntimeResourceOverlay) AndroidMkEntries() []android.AndroidMkEntries {
+	if a.IsHideFromMake() || a.outputFile == nil {
+		return []android.AndroidMkEntries{android.AndroidMkEntries{
+			Disabled: true,
+		}}
+	}
+	return []android.AndroidMkEntries{android.AndroidMkEntries{
+		Class:      "APPS",
+		OutputFile: android.OptionalPathForPath(a.outputFile),
+		Include:    "$(BUILD_SYSTEM)/soong_app_prebuilt.mk",
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+				entries.SetString("LOCAL_CERTIFICATE", a.certificate.AndroidMkString())
+			},
+		},
+	}}
+}
+
 func (a *AndroidApp) getOverriddenPackages() []string {
 	var overridden []string
 	if len(a.overridableAppProperties.Overrides) > 0 {
@@ -592,73 +558,11 @@
 		},
 		ExtraFooters: []android.AndroidMkExtraFootersFunc{
 			func(w io.Writer, name, prefix, moduleDir string) {
-				if dstubs.apiFile != nil {
-					fmt.Fprintf(w, ".PHONY: %s %s.txt\n", dstubs.Name(), dstubs.Name())
-					fmt.Fprintf(w, "%s %s.txt: %s\n", dstubs.Name(), dstubs.Name(), dstubs.apiFile)
-				}
-				if dstubs.removedApiFile != nil {
-					fmt.Fprintf(w, ".PHONY: %s %s.txt\n", dstubs.Name(), dstubs.Name())
-					fmt.Fprintf(w, "%s %s.txt: %s\n", dstubs.Name(), dstubs.Name(), dstubs.removedApiFile)
-				}
-				if dstubs.checkCurrentApiTimestamp != nil {
-					fmt.Fprintln(w, ".PHONY:", dstubs.Name()+"-check-current-api")
-					fmt.Fprintln(w, dstubs.Name()+"-check-current-api:",
-						dstubs.checkCurrentApiTimestamp.String())
-
-					fmt.Fprintln(w, ".PHONY: checkapi")
-					fmt.Fprintln(w, "checkapi:",
-						dstubs.checkCurrentApiTimestamp.String())
-
-					fmt.Fprintln(w, ".PHONY: droidcore")
-					fmt.Fprintln(w, "droidcore: checkapi")
-				}
-				if dstubs.updateCurrentApiTimestamp != nil {
-					fmt.Fprintln(w, ".PHONY:", dstubs.Name()+"-update-current-api")
-					fmt.Fprintln(w, dstubs.Name()+"-update-current-api:",
-						dstubs.updateCurrentApiTimestamp.String())
-
-					fmt.Fprintln(w, ".PHONY: update-api")
-					fmt.Fprintln(w, "update-api:",
-						dstubs.updateCurrentApiTimestamp.String())
-				}
-				if dstubs.checkLastReleasedApiTimestamp != nil {
-					fmt.Fprintln(w, ".PHONY:", dstubs.Name()+"-check-last-released-api")
-					fmt.Fprintln(w, dstubs.Name()+"-check-last-released-api:",
-						dstubs.checkLastReleasedApiTimestamp.String())
-
-					fmt.Fprintln(w, ".PHONY: checkapi")
-					fmt.Fprintln(w, "checkapi:",
-						dstubs.checkLastReleasedApiTimestamp.String())
-
-					fmt.Fprintln(w, ".PHONY: droidcore")
-					fmt.Fprintln(w, "droidcore: checkapi")
-				}
 				if dstubs.apiLintTimestamp != nil {
-					fmt.Fprintln(w, ".PHONY:", dstubs.Name()+"-api-lint")
-					fmt.Fprintln(w, dstubs.Name()+"-api-lint:",
-						dstubs.apiLintTimestamp.String())
-
-					fmt.Fprintln(w, ".PHONY: checkapi")
-					fmt.Fprintln(w, "checkapi:",
-						dstubs.Name()+"-api-lint")
-
-					fmt.Fprintln(w, ".PHONY: droidcore")
-					fmt.Fprintln(w, "droidcore: checkapi")
-
 					if dstubs.apiLintReport != nil {
-						fmt.Fprintf(w, "$(call dist-for-goals,%s,%s:%s)\n", dstubs.Name()+"-api-lint",
-							dstubs.apiLintReport.String(), "apilint/"+dstubs.Name()+"-lint-report.txt")
 						fmt.Fprintf(w, "$(call declare-0p-target,%s)\n", dstubs.apiLintReport.String())
 					}
 				}
-				if dstubs.checkNullabilityWarningsTimestamp != nil {
-					fmt.Fprintln(w, ".PHONY:", dstubs.Name()+"-check-nullability-warnings")
-					fmt.Fprintln(w, dstubs.Name()+"-check-nullability-warnings:",
-						dstubs.checkNullabilityWarningsTimestamp.String())
-
-					fmt.Fprintln(w, ".PHONY:", "droidcore")
-					fmt.Fprintln(w, "droidcore: ", dstubs.Name()+"-check-nullability-warnings")
-				}
 			},
 		},
 	}}
diff --git a/java/androidmk_test.go b/java/androidmk_test.go
index 1d98b18..b4b13b1 100644
--- a/java/androidmk_test.go
+++ b/java/androidmk_test.go
@@ -23,6 +23,7 @@
 )
 
 func TestRequired(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_library {
 			name: "foo",
@@ -31,7 +32,7 @@
 		}
 	`)
 
-	mod := ctx.ModuleForTests("foo", "android_common").Module()
+	mod := ctx.ModuleForTests(t, "foo", "android_common").Module()
 	entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
 
 	expected := []string{"libfoo"}
@@ -42,6 +43,7 @@
 }
 
 func TestHostdex(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_library {
 			name: "foo",
@@ -50,7 +52,7 @@
 		}
 	`)
 
-	mod := ctx.ModuleForTests("foo", "android_common").Module()
+	mod := ctx.ModuleForTests(t, "foo", "android_common").Module()
 	entriesList := android.AndroidMkEntriesForTest(t, ctx, mod)
 	if len(entriesList) != 2 {
 		t.Errorf("two entries are expected, but got %d", len(entriesList))
@@ -72,6 +74,7 @@
 }
 
 func TestHostdexRequired(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_library {
 			name: "foo",
@@ -81,7 +84,7 @@
 		}
 	`)
 
-	mod := ctx.ModuleForTests("foo", "android_common").Module()
+	mod := ctx.ModuleForTests(t, "foo", "android_common").Module()
 	entriesList := android.AndroidMkEntriesForTest(t, ctx, mod)
 	if len(entriesList) != 2 {
 		t.Errorf("two entries are expected, but got %d", len(entriesList))
@@ -103,6 +106,7 @@
 }
 
 func TestHostdexSpecificRequired(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_library {
 			name: "foo",
@@ -116,7 +120,7 @@
 		}
 	`)
 
-	mod := ctx.ModuleForTests("foo", "android_common").Module()
+	mod := ctx.ModuleForTests(t, "foo", "android_common").Module()
 	entriesList := android.AndroidMkEntriesForTest(t, ctx, mod)
 	if len(entriesList) != 2 {
 		t.Errorf("two entries are expected, but got %d", len(entriesList))
@@ -136,6 +140,7 @@
 }
 
 func TestJavaSdkLibrary_RequireXmlPermissionFile(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -153,7 +158,7 @@
 		`)
 
 	// Verify the existence of internal modules
-	result.ModuleForTests("foo-shared_library.xml", "android_common")
+	result.ModuleForTests(t, "foo-shared_library.xml", "android_common")
 
 	testCases := []struct {
 		moduleName string
@@ -163,7 +168,7 @@
 		{"foo-no_shared_library", []string{"foo-no_shared_library.impl"}},
 	}
 	for _, tc := range testCases {
-		mod := result.ModuleForTests(tc.moduleName, "android_common").Module()
+		mod := result.ModuleForTests(t, tc.moduleName, "android_common").Module()
 		entries := android.AndroidMkEntriesForTest(t, result.TestContext, mod)[0]
 		actual := entries.EntryMap["LOCAL_REQUIRED_MODULES"]
 		if !reflect.DeepEqual(tc.expected, actual) {
@@ -173,6 +178,7 @@
 }
 
 func TestImportSoongDexJar(t *testing.T) {
+	t.Parallel()
 	result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(t, `
 		java_import {
 			name: "my-java-import",
@@ -191,6 +197,7 @@
 }
 
 func TestAndroidTestHelperApp_LocalDisableTestConfig(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		android_test_helper_app {
 			name: "foo",
@@ -198,7 +205,7 @@
 		}
 	`)
 
-	mod := ctx.ModuleForTests("foo", "android_common").Module()
+	mod := ctx.ModuleForTests(t, "foo", "android_common").Module()
 	entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
 
 	expected := []string{"true"}
@@ -209,6 +216,7 @@
 }
 
 func TestGetOverriddenPackages(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(
 		t, `
 		android_app {
@@ -246,7 +254,7 @@
 	}
 
 	for _, expected := range expectedVariants {
-		mod := ctx.ModuleForTests(expected.name, expected.variantName).Module()
+		mod := ctx.ModuleForTests(t, expected.name, expected.variantName).Module()
 		entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
 		actual := entries.EntryMap["LOCAL_OVERRIDES_PACKAGES"]
 
@@ -255,6 +263,7 @@
 }
 
 func TestJniAsRequiredDeps(t *testing.T) {
+	t.Parallel()
 	ctx := android.GroupFixturePreparers(
 		PrepareForTestWithJavaDefaultModules,
 		cc.PrepareForTestWithCcDefaultModules,
@@ -295,7 +304,7 @@
 	}
 
 	for _, tc := range testcases {
-		mod := ctx.ModuleForTests(tc.name, "android_common").Module()
+		mod := ctx.ModuleForTests(t, tc.name, "android_common").Module()
 		entries := android.AndroidMkEntriesForTest(t, ctx.TestContext, mod)[0]
 		required := entries.EntryMap["LOCAL_REQUIRED_MODULES"]
 		android.AssertDeepEquals(t, "unexpected required deps", tc.expected, required)
diff --git a/java/app.go b/java/app.go
index dd99675..a634f9c 100644
--- a/java/app.go
+++ b/java/app.go
@@ -22,15 +22,13 @@
 	"path/filepath"
 	"strings"
 
-	"android/soong/testing"
-
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/depset"
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
 	"android/soong/cc"
 	"android/soong/dexpreopt"
-	"android/soong/genrule"
 	"android/soong/tradefed"
 )
 
@@ -69,6 +67,11 @@
 
 	// TestHelperApp is true if the module is a android_test_helper_app
 	TestHelperApp bool
+
+	// EmbeddedJNILibs is the list of paths to JNI libraries that were embedded in the APK.
+	EmbeddedJNILibs android.Paths
+
+	MergedManifestFile android.Path
 }
 
 var AppInfoProvider = blueprint.NewProvider[*AppInfo]()
@@ -163,7 +166,7 @@
 type overridableAppProperties struct {
 	// The name of a certificate in the default certificate directory, blank to use the default product certificate,
 	// or an android_app_certificate module name in the form ":module".
-	Certificate *string
+	Certificate proptools.Configurable[string] `android:"replace_instead_of_append"`
 
 	// Name of the signing certificate lineage file or filegroup module.
 	Lineage *string `android:"path"`
@@ -222,13 +225,15 @@
 	javaApiUsedByOutputFile android.ModuleOutPath
 
 	privAppAllowlist android.OptionalPath
+
+	requiredModuleNames []string
 }
 
 func (a *AndroidApp) IsInstallable() bool {
 	return Bool(a.properties.Installable)
 }
 
-func (a *AndroidApp) ResourcesNodeDepSet() *android.DepSet[*resourcesNode] {
+func (a *AndroidApp) ResourcesNodeDepSet() depset.DepSet[*resourcesNode] {
 	return a.aapt.resourcesNodesDepSet
 }
 
@@ -400,6 +405,14 @@
 		Updatable:     Bool(a.appProperties.Updatable),
 		TestHelperApp: true,
 	})
+
+	moduleInfoJSON := ctx.ModuleInfoJSON()
+	moduleInfoJSON.Tags = append(moduleInfoJSON.Tags, "tests")
+	if len(a.appTestHelperAppProperties.Test_suites) > 0 {
+		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, a.appTestHelperAppProperties.Test_suites...)
+	} else {
+		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "null-suite")
+	}
 }
 
 func (a *AndroidApp) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -407,10 +420,38 @@
 	a.checkEmbedJnis(ctx)
 	a.generateAndroidBuildActions(ctx)
 	a.generateJavaUsedByApex(ctx)
+
+	var embeddedJniLibs []android.Path
+
+	if a.embeddedJniLibs {
+		for _, jni := range a.jniLibs {
+			embeddedJniLibs = append(embeddedJniLibs, jni.path)
+		}
+	}
 	android.SetProvider(ctx, AppInfoProvider, &AppInfo{
-		Updatable:     Bool(a.appProperties.Updatable),
-		TestHelperApp: false,
+		Updatable:          Bool(a.appProperties.Updatable),
+		TestHelperApp:      false,
+		EmbeddedJNILibs:    embeddedJniLibs,
+		MergedManifestFile: a.mergedManifest,
 	})
+
+	a.requiredModuleNames = a.getRequiredModuleNames(ctx)
+}
+
+func (a *AndroidApp) getRequiredModuleNames(ctx android.ModuleContext) []string {
+	var required []string
+	if proptools.Bool(a.appProperties.Generate_product_characteristics_rro) {
+		required = []string{a.productCharacteristicsRROPackageName()}
+	}
+	// Install the vendor overlay variant if this app is installed.
+	if len(filterRRO(a.rroDirsDepSet, device)) > 0 {
+		required = append(required, AutogeneratedRroModuleName(ctx, ctx.Module().Name(), "vendor"))
+	}
+	// Install the product overlay variant if this app is installed.
+	if len(filterRRO(a.rroDirsDepSet, product)) > 0 {
+		required = append(required, AutogeneratedRroModuleName(ctx, ctx.Module().Name(), "product"))
+	}
+	return required
 }
 
 func (a *AndroidApp) checkAppSdkVersions(ctx android.ModuleContext) {
@@ -457,18 +498,28 @@
 // This check is enforced for "updatable" APKs (including APK-in-APEX).
 func (a *AndroidApp) checkJniLibsSdkVersion(ctx android.ModuleContext, minSdkVersion android.ApiLevel) {
 	// It's enough to check direct JNI deps' sdk_version because all transitive deps from JNI deps are checked in cc.checkLinkType()
-	ctx.VisitDirectDeps(func(m android.Module) {
+	ctx.VisitDirectDepsProxy(func(m android.ModuleProxy) {
 		if !IsJniDepTag(ctx.OtherModuleDependencyTag(m)) {
 			return
 		}
-		dep, _ := m.(*cc.Module)
+		if _, ok := android.OtherModuleProvider(ctx, m, cc.CcInfoProvider); !ok {
+			panic(fmt.Errorf("jni dependency is not a cc module: %v", m))
+		}
+		commonInfo, ok := android.OtherModuleProvider(ctx, m, android.CommonModuleInfoKey)
+		if !ok {
+			panic(fmt.Errorf("jni dependency doesn't have CommonModuleInfo provider: %v", m))
+		}
 		// The domain of cc.sdk_version is "current" and <number>
 		// We can rely on android.SdkSpec to convert it to <number> so that "current" is
 		// handled properly regardless of sdk finalization.
-		jniSdkVersion, err := android.SdkSpecFrom(ctx, dep.MinSdkVersion()).EffectiveVersion(ctx)
+		ver := ""
+		if !commonInfo.MinSdkVersion.IsPlatform {
+			ver = commonInfo.MinSdkVersion.ApiLevel.String()
+		}
+		jniSdkVersion, err := android.SdkSpecFrom(ctx, ver).EffectiveVersion(ctx)
 		if err != nil || minSdkVersion.LessThan(jniSdkVersion) {
-			ctx.OtherModuleErrorf(dep, "min_sdk_version(%v) is higher than min_sdk_version(%v) of the containing android_app(%v)",
-				dep.MinSdkVersion(), minSdkVersion, ctx.ModuleName())
+			ctx.OtherModuleErrorf(m, "min_sdk_version(%v) is higher than min_sdk_version(%v) of the containing android_app(%v)",
+				ver, minSdkVersion, ctx.ModuleName())
 			return
 		}
 
@@ -531,7 +582,7 @@
 }
 
 func getAconfigFilePaths(ctx android.ModuleContext) (aconfigTextFilePaths android.Paths) {
-	ctx.VisitDirectDeps(func(dep android.Module) {
+	ctx.VisitDirectDepsProxy(func(dep android.ModuleProxy) {
 		tag := ctx.OtherModuleDependencyTag(dep)
 		switch tag {
 		case staticLibTag:
@@ -637,7 +688,7 @@
 
 func (a *AndroidApp) proguardBuildActions(ctx android.ModuleContext) {
 	var staticLibProguardFlagFiles android.Paths
-	ctx.VisitDirectDeps(func(m android.Module) {
+	ctx.VisitDirectDepsProxy(func(m android.ModuleProxy) {
 		depProguardInfo, _ := android.OtherModuleProvider(ctx, m, ProguardSpecInfoProvider)
 		staticLibProguardFlagFiles = append(staticLibProguardFlagFiles, depProguardInfo.UnconditionallyExportedProguardFlags.ToList()...)
 		if ctx.OtherModuleDependencyTag(m) == staticLibTag {
@@ -671,7 +722,7 @@
 	return android.PathForModuleInstall(ctx, installDir, a.installApkName+".apk")
 }
 
-func (a *AndroidApp) dexBuildActions(ctx android.ModuleContext) (android.Path, android.Path) {
+func (a *AndroidApp) dexBuildActions(ctx android.ModuleContext) (android.Path, android.Path, *JavaInfo) {
 	a.dexpreopter.installPath = a.installPath(ctx)
 	a.dexpreopter.isApp = true
 	if a.dexProperties.Uncompress_dex == nil {
@@ -679,13 +730,14 @@
 		a.dexProperties.Uncompress_dex = proptools.BoolPtr(a.shouldUncompressDex(ctx))
 	}
 	a.dexpreopter.uncompressedDex = *a.dexProperties.Uncompress_dex
-	a.dexpreopter.enforceUsesLibs = a.usesLibrary.enforceUsesLibraries()
+	a.dexpreopter.enforceUsesLibs = a.usesLibrary.enforceUsesLibraries(ctx)
 	a.dexpreopter.classLoaderContexts = a.classLoaderContexts
 	a.dexpreopter.manifestFile = a.mergedManifestFile
 	a.dexpreopter.preventInstall = a.appProperties.PreventInstall
 
 	var packageResources = a.exportPackage
 
+	javaInfo := &JavaInfo{}
 	if ctx.ModuleName() != "framework-res" {
 		if a.dexProperties.resourceShrinkingEnabled(ctx) {
 			protoFile := android.PathForModuleOut(ctx, packageResources.Base()+".proto.apk")
@@ -709,7 +761,7 @@
 			extraSrcJars = android.Paths{a.aapt.aaptSrcJar}
 		}
 
-		a.Module.compile(ctx, extraSrcJars, extraClasspathJars, extraCombinedJars, nil)
+		javaInfo = a.Module.compile(ctx, extraSrcJars, extraClasspathJars, extraCombinedJars, nil)
 		if a.dexProperties.resourceShrinkingEnabled(ctx) {
 			binaryResources := android.PathForModuleOut(ctx, packageResources.Base()+".binary.out.apk")
 			aapt2Convert(ctx, binaryResources, a.dexer.resourcesOutput.Path(), "binary")
@@ -717,7 +769,7 @@
 		}
 	}
 
-	return a.dexJarFile.PathOrNil(), packageResources
+	return a.dexJarFile.PathOrNil(), packageResources, javaInfo
 }
 
 func (a *AndroidApp) jniBuildActions(jniLibs []jniLib, prebuiltJniPackages android.Paths, ctx android.ModuleContext) android.WritablePath {
@@ -838,7 +890,7 @@
 
 	packageName := packageNameProp.Get()
 	fileName := "privapp_allowlist_" + packageName + ".xml"
-	outPath := android.PathForModuleOut(ctx, fileName).OutputPath
+	outPath := android.PathForModuleOut(ctx, fileName)
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   modifyAllowlist,
 		Input:  android.PathForModuleSrc(ctx, *a.appProperties.Privapp_allowlist),
@@ -847,7 +899,7 @@
 			"packageName": packageName,
 		},
 	})
-	return &outPath
+	return outPath
 }
 
 func (a *AndroidApp) generateAndroidBuildActions(ctx android.ModuleContext) {
@@ -908,10 +960,10 @@
 	// Process all building blocks, from AAPT to certificates.
 	a.aaptBuildActions(ctx)
 	// The decision to enforce <uses-library> checks is made before adding implicit SDK libraries.
-	a.usesLibrary.freezeEnforceUsesLibraries()
+	a.usesLibrary.freezeEnforceUsesLibraries(ctx)
 
 	// Check that the <uses-library> list is coherent with the manifest.
-	if a.usesLibrary.enforceUsesLibraries() {
+	if a.usesLibrary.enforceUsesLibraries(ctx) {
 		manifestCheckFile := a.usesLibrary.verifyUsesLibrariesManifest(
 			ctx, a.mergedManifestFile, &a.classLoaderContexts)
 		apkDeps = append(apkDeps, manifestCheckFile)
@@ -924,7 +976,7 @@
 	a.linter.resources = a.aapt.resourceFiles
 	a.linter.buildModuleReportZip = ctx.Config().UnbundledBuildApps()
 
-	dexJarFile, packageResources := a.dexBuildActions(ctx)
+	dexJarFile, packageResources, javaInfo := a.dexBuildActions(ctx)
 
 	// No need to check the SDK version of the JNI deps unless we embed them
 	checkNativeSdkVersion := a.shouldEmbedJnis(ctx) && !Bool(a.appProperties.Jni_uses_platform_apis)
@@ -1040,7 +1092,23 @@
 		},
 	)
 
+	if javaInfo != nil {
+		javaInfo.OutputFile = a.outputFile
+		setExtraJavaInfo(ctx, a, javaInfo)
+		android.SetProvider(ctx, JavaInfoProvider, javaInfo)
+	}
+
+	moduleInfoJSON := ctx.ModuleInfoJSON()
+	moduleInfoJSON.Class = []string{"APPS"}
+	if !a.embeddedJniLibs {
+		for _, jniLib := range a.jniLibs {
+			moduleInfoJSON.ExtraRequired = append(moduleInfoJSON.ExtraRequired, jniLib.name)
+		}
+	}
+
 	a.setOutputFiles(ctx)
+
+	buildComplianceMetadata(ctx)
 }
 
 func (a *AndroidApp) setOutputFiles(ctx android.ModuleContext) {
@@ -1072,46 +1140,77 @@
 			app.SdkVersion(ctx).Kind != android.SdkCorePlatform && !app.RequiresStableAPIs(ctx)
 	}
 	jniLib, prebuiltJniPackages := collectJniDeps(ctx, shouldCollectRecursiveNativeDeps,
-		checkNativeSdkVersion, func(dep cc.LinkableInterface) bool {
-			return !dep.IsNdk(ctx.Config()) && !dep.IsStubs()
+		checkNativeSdkVersion, func(parent, child android.ModuleProxy) bool {
+			apkInApex := ctx.Module().(android.ApexModule).NotInPlatform()
+			childLinkable, _ := android.OtherModuleProvider(ctx, child, cc.LinkableInfoProvider)
+			parentIsLinkable := false
+			if ctx.EqualModules(ctx.Module(), parent) {
+				parentLinkable, _ := ctx.Module().(cc.LinkableInterface)
+				parentIsLinkable = parentLinkable != nil
+			} else {
+				_, parentIsLinkable = android.OtherModuleProvider(ctx, parent, cc.LinkableInfoProvider)
+			}
+			useStubsOfDep := childLinkable.IsStubs
+			if apkInApex && parentIsLinkable {
+				// APK-in-APEX
+				// If the parent is a linkable interface, use stubs if the dependency edge crosses an apex boundary.
+				useStubsOfDep = useStubsOfDep || (childLinkable.HasStubsVariants && cc.ShouldUseStubForApex(ctx, parent, child))
+			}
+			return !childLinkable.IsNdk && !useStubsOfDep
 		})
 
 	var certificates []Certificate
 
-	ctx.VisitDirectDeps(func(module android.Module) {
+	var directImplementationDeps android.Paths
+	var transitiveImplementationDeps []depset.DepSet[android.Path]
+	ctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 		otherName := ctx.OtherModuleName(module)
 		tag := ctx.OtherModuleDependencyTag(module)
 
 		if tag == certificateTag {
-			if dep, ok := module.(*AndroidAppCertificate); ok {
+			if dep, ok := android.OtherModuleProvider(ctx, module, AndroidAppCertificateInfoProvider); ok {
 				certificates = append(certificates, dep.Certificate)
 			} else {
 				ctx.ModuleErrorf("certificate dependency %q must be an android_app_certificate module", otherName)
 			}
 		}
+
+		if IsJniDepTag(tag) {
+			directImplementationDeps = append(directImplementationDeps, android.OutputFileForModule(ctx, module, ""))
+			if info, ok := android.OtherModuleProvider(ctx, module, cc.ImplementationDepInfoProvider); ok {
+				transitiveImplementationDeps = append(transitiveImplementationDeps, info.ImplementationDeps)
+			}
+		}
 	})
+	android.SetProvider(ctx, cc.ImplementationDepInfoProvider, &cc.ImplementationDepInfo{
+		ImplementationDeps: depset.New(depset.PREORDER, directImplementationDeps, transitiveImplementationDeps),
+	})
+
 	return jniLib, prebuiltJniPackages, certificates
 }
 
 func collectJniDeps(ctx android.ModuleContext,
 	shouldCollectRecursiveNativeDeps bool,
 	checkNativeSdkVersion bool,
-	filter func(cc.LinkableInterface) bool) ([]jniLib, android.Paths) {
+	filter func(parent, child android.ModuleProxy) bool) ([]jniLib, android.Paths) {
 	var jniLibs []jniLib
 	var prebuiltJniPackages android.Paths
 	seenModulePaths := make(map[string]bool)
 
-	ctx.WalkDeps(func(module android.Module, parent android.Module) bool {
+	ctx.WalkDepsProxy(func(module, parent android.ModuleProxy) bool {
+		if !android.OtherModuleProviderOrDefault(ctx, module, android.CommonModuleInfoKey).Enabled {
+			return false
+		}
 		otherName := ctx.OtherModuleName(module)
 		tag := ctx.OtherModuleDependencyTag(module)
 
 		if IsJniDepTag(tag) || cc.IsSharedDepTag(tag) {
-			if dep, ok := module.(cc.LinkableInterface); ok {
-				if filter != nil && !filter(dep) {
+			if dep, ok := android.OtherModuleProvider(ctx, module, cc.LinkableInfoProvider); ok {
+				if filter != nil && !filter(parent, module) {
 					return false
 				}
 
-				lib := dep.OutputFile()
+				lib := dep.OutputFile
 				if lib.Valid() {
 					path := lib.Path()
 					if seenModulePaths[path.String()] {
@@ -1119,7 +1218,8 @@
 					}
 					seenModulePaths[path.String()] = true
 
-					if checkNativeSdkVersion && dep.SdkVersion() == "" {
+					commonInfo := android.OtherModuleProviderOrDefault(ctx, module, android.CommonModuleInfoKey)
+					if checkNativeSdkVersion && commonInfo.SdkVersion == "" {
 						ctx.PropertyErrorf("jni_libs", "JNI dependency %q uses platform APIs, but this module does not",
 							otherName)
 					}
@@ -1127,11 +1227,11 @@
 					jniLibs = append(jniLibs, jniLib{
 						name:           ctx.OtherModuleName(module),
 						path:           path,
-						target:         module.Target(),
-						coverageFile:   dep.CoverageOutputFile(),
-						unstrippedFile: dep.UnstrippedOutputFile(),
-						partition:      dep.Partition(),
-						installPaths:   android.OtherModuleProviderOrDefault(ctx, dep, android.InstallFilesProvider).InstallFiles,
+						target:         commonInfo.Target,
+						coverageFile:   dep.CoverageOutputFile,
+						unstrippedFile: dep.UnstrippedOutputFile,
+						partition:      dep.Partition,
+						installPaths:   android.OtherModuleProviderOrDefault(ctx, module, android.InstallFilesProvider).InstallFiles,
 					})
 				} else if ctx.Config().AllowMissingDependencies() {
 					ctx.AddMissingDependencies([]string{otherName})
@@ -1157,7 +1257,10 @@
 
 func (a *AndroidApp) WalkPayloadDeps(ctx android.BaseModuleContext, do android.PayloadDepsCallback) {
 	ctx.WalkDeps(func(child, parent android.Module) bool {
-		isExternal := !a.DepIsInSameApex(ctx, child)
+		// TODO(ccross): Should this use android.DepIsInSameApex?  Right now it is applying the android app
+		// heuristics to every transitive dependency, when it should probably be using the heuristics of the
+		// immediate parent.
+		isExternal := !a.GetDepInSameApexChecker().OutgoingDepIsInSameApex(ctx.OtherModuleDependencyTag(child))
 		if am, ok := child.(android.ApexModule); ok {
 			if !do(ctx, parent, am, isExternal) {
 				return false
@@ -1173,7 +1276,7 @@
 	}
 
 	depsInfo := android.DepNameToDepInfoMap{}
-	a.WalkPayloadDeps(ctx, func(ctx android.BaseModuleContext, from blueprint.Module, to android.ApexModule, externalDep bool) bool {
+	a.WalkPayloadDeps(ctx, func(ctx android.BaseModuleContext, from android.Module, to android.ApexModule, externalDep bool) bool {
 		depName := to.Name()
 
 		// Skip dependencies that are only available to APEXes; they are developed with updatability
@@ -1231,14 +1334,22 @@
 	if overridden {
 		return ":" + certificate
 	}
-	return String(a.overridableAppProperties.Certificate)
+	return a.overridableAppProperties.Certificate.GetOrDefault(ctx, "")
 }
 
-func (a *AndroidApp) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
-	if IsJniDepTag(ctx.OtherModuleDependencyTag(dep)) {
+func (m *AndroidApp) GetDepInSameApexChecker() android.DepInSameApexChecker {
+	return AppDepInSameApexChecker{}
+}
+
+type AppDepInSameApexChecker struct {
+	android.BaseDepInSameApexChecker
+}
+
+func (m AppDepInSameApexChecker) OutgoingDepIsInSameApex(tag blueprint.DependencyTag) bool {
+	if IsJniDepTag(tag) {
 		return true
 	}
-	return a.Library.DepIsInSameApex(ctx, dep)
+	return depIsInSameApex(tag)
 }
 
 func (a *AndroidApp) Privileged() bool {
@@ -1333,20 +1444,22 @@
 			Srcs:  []string{":" + a.Name() + "{.apk}"},
 			Cmd:   proptools.StringPtr("$(location characteristics_rro_generator) $$($(location aapt2) dump packagename $(in)) $(out)"),
 		}
-		ctx.CreateModule(genrule.GenRuleFactory, &rroManifestProperties)
+		ctx.CreateModule(GenRuleFactory, &rroManifestProperties)
 
 		rroProperties := struct {
 			Name           *string
 			Filter_product *string
 			Aaptflags      []string
 			Manifest       *string
-			Resource_dirs  []string
+			Resource_dirs  proptools.Configurable[[]string]
+			Flags_packages []string
 		}{
 			Name:           proptools.StringPtr(rroPackageName),
 			Filter_product: proptools.StringPtr(characteristics),
 			Aaptflags:      []string{"--auto-add-overlay"},
 			Manifest:       proptools.StringPtr(":" + rroManifestName),
 			Resource_dirs:  a.aaptProperties.Resource_dirs,
+			Flags_packages: a.aaptProperties.Flags_packages,
 		}
 		if !Bool(a.aaptProperties.Aapt_include_all_resources) {
 			for _, aaptConfig := range ctx.Config().ProductAAPTConfig() {
@@ -1354,11 +1467,82 @@
 			}
 		}
 		ctx.CreateModule(RuntimeResourceOverlayFactory, &rroProperties)
+
+	})
+
+	module.SetDefaultableHook(func(ctx android.DefaultableHookContext) {
+		createInternalRuntimeOverlays(ctx, module.ModuleBase)
 	})
 
 	return module
 }
 
+func AutogeneratedRroModuleName(ctx android.EarlyModuleContext, moduleName, partition string) string {
+	return fmt.Sprintf("%s__%s__auto_generated_rro_%s", moduleName, ctx.Config().DeviceProduct(), partition)
+}
+
+type createModuleContext interface {
+	android.EarlyModuleContext
+	CreateModule(android.ModuleFactory, ...interface{}) android.Module
+}
+
+func createInternalRuntimeOverlays(ctx createModuleContext, a android.ModuleBase) {
+	if !ctx.Config().HasDeviceProduct() {
+		return
+	}
+	// vendor
+	vendorOverlayProps := struct {
+		Name                *string
+		Base                *string
+		Vendor              *bool
+		Product_specific    *bool
+		System_ext_specific *bool
+		Manifest            *string
+		Sdk_version         *string
+		Compile_multilib    *string
+		Enabled             proptools.Configurable[bool]
+	}{
+		Name:                proptools.StringPtr(AutogeneratedRroModuleName(ctx, a.Name(), "vendor")),
+		Base:                proptools.StringPtr(a.Name()),
+		Vendor:              proptools.BoolPtr(true),
+		Product_specific:    proptools.BoolPtr(false),
+		System_ext_specific: proptools.BoolPtr(false),
+		Manifest:            proptools.StringPtr(":" + a.Name() + "{.manifest.xml}"),
+		Sdk_version:         proptools.StringPtr("current"),
+		Compile_multilib:    proptools.StringPtr("first"),
+		Enabled:             a.EnabledProperty().Clone(),
+	}
+	ctx.CreateModule(AutogenRuntimeResourceOverlayFactory, &vendorOverlayProps)
+
+	// product
+	productOverlayProps := struct {
+		Name                *string
+		Base                *string
+		Vendor              *bool
+		Proprietary         *bool
+		Soc_specific        *bool
+		Product_specific    *bool
+		System_ext_specific *bool
+		Manifest            *string
+		Sdk_version         *string
+		Compile_multilib    *string
+		Enabled             proptools.Configurable[bool]
+	}{
+		Name:                proptools.StringPtr(AutogeneratedRroModuleName(ctx, a.Name(), "product")),
+		Base:                proptools.StringPtr(a.Name()),
+		Vendor:              proptools.BoolPtr(false),
+		Proprietary:         proptools.BoolPtr(false),
+		Soc_specific:        proptools.BoolPtr(false),
+		Product_specific:    proptools.BoolPtr(true),
+		System_ext_specific: proptools.BoolPtr(false),
+		Manifest:            proptools.StringPtr(":" + a.Name() + "{.manifest.xml}"),
+		Sdk_version:         proptools.StringPtr("current"),
+		Compile_multilib:    proptools.StringPtr("first"),
+		Enabled:             a.EnabledProperty().Clone(),
+	}
+	ctx.CreateModule(AutogenRuntimeResourceOverlayFactory, &productOverlayProps)
+}
+
 // A dictionary of values to be overridden in the manifest.
 type Manifest_values struct {
 	// Overrides the value of package_name in the manifest
@@ -1429,6 +1613,9 @@
 	}
 	a.generateAndroidBuildActions(ctx)
 
+	for _, c := range a.testProperties.Test_options.Tradefed_options {
+		configs = append(configs, c)
+	}
 	for _, module := range a.testProperties.Test_mainline_modules {
 		configs = append(configs, tradefed.Option{Name: "config-descriptor:metadata", Key: "mainline-param", Value: module})
 	}
@@ -1439,9 +1626,25 @@
 	a.testConfig = a.FixTestConfig(ctx, testConfig)
 	a.extraTestConfigs = android.PathsForModuleSrc(ctx, a.testProperties.Test_options.Extra_test_configs)
 	a.data = android.PathsForModuleSrc(ctx, a.testProperties.Data)
-	android.SetProvider(ctx, testing.TestModuleProviderKey, testing.TestModuleProviderData{})
+	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)...)
+
+	// Install test deps
+	if !ctx.Config().KatiEnabled() {
+		pathInTestCases := android.PathForModuleInstall(ctx, ctx.Module().Name())
+		if a.testConfig != nil {
+			ctx.InstallFile(pathInTestCases, ctx.Module().Name()+".config", a.testConfig)
+		}
+		testDeps := append(a.data, a.extraTestConfigs...)
+		for _, data := range android.SortedUniquePaths(testDeps) {
+			dataPath := android.DataPath{SrcPath: data}
+			ctx.InstallTestData(pathInTestCases, []android.DataPath{dataPath})
+		}
+	}
+
 	android.SetProvider(ctx, tradefed.BaseTestProviderKey, tradefed.BaseTestProviderData{
-		InstalledFiles:          a.data,
+		TestcaseRelDataFiles:    testcaseRel(a.data),
 		OutputFile:              a.OutputFile(),
 		TestConfig:              a.testConfig,
 		HostRequiredModuleNames: a.HostRequiredModuleNames(),
@@ -1449,12 +1652,38 @@
 		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,
 		TopLevelTarget: true,
 	})
 
+	moduleInfoJSON := ctx.ModuleInfoJSON()
+	moduleInfoJSON.Tags = append(moduleInfoJSON.Tags, "tests")
+	if a.testConfig != nil {
+		moduleInfoJSON.TestConfig = append(moduleInfoJSON.TestConfig, a.testConfig.String())
+	}
+	moduleInfoJSON.TestConfig = append(moduleInfoJSON.TestConfig, a.extraTestConfigs.Strings()...)
+	if len(a.testProperties.Test_suites) > 0 {
+		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, a.testProperties.Test_suites...)
+	} else {
+		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "null-suite")
+	}
+
+	if _, ok := testConfig.(android.WritablePath); ok {
+		moduleInfoJSON.AutoTestConfig = []string{"true"}
+	}
+	moduleInfoJSON.TestMainlineModules = append(moduleInfoJSON.TestMainlineModules, a.testProperties.Test_mainline_modules...)
+}
+
+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 {
@@ -1535,7 +1764,7 @@
 	module.appProperties.Use_embedded_native_libs = proptools.BoolPtr(true)
 	module.appProperties.AlwaysPackageNativeLibs = true
 	module.Module.dexpreopter.isTest = true
-	module.Module.linter.properties.Lint.Test = proptools.BoolPtr(true)
+	module.Module.linter.properties.Lint.Test_module_type = proptools.BoolPtr(true)
 
 	module.addHostAndDeviceProperties()
 	module.AddProperties(
@@ -1586,12 +1815,14 @@
 
 	// TODO(b/192032291): Disable by default after auditing downstream usage.
 	module.Module.dexProperties.Optimize.EnabledByDefault = true
+	module.Module.dexProperties.Optimize.Ignore_library_extends_program = proptools.BoolPtr(true)
+	module.Module.dexProperties.Optimize.Proguard_compatibility = proptools.BoolPtr(false)
 
 	module.Module.properties.Installable = proptools.BoolPtr(true)
 	module.appProperties.Use_embedded_native_libs = proptools.BoolPtr(true)
 	module.appProperties.AlwaysPackageNativeLibs = true
 	module.Module.dexpreopter.isTest = true
-	module.Module.linter.properties.Lint.Test = proptools.BoolPtr(true)
+	module.Module.linter.properties.Lint.Test_module_type = proptools.BoolPtr(true)
 
 	module.addHostAndDeviceProperties()
 	module.AddProperties(
@@ -1618,6 +1849,12 @@
 	Certificate *string
 }
 
+type AndroidAppCertificateInfo struct {
+	Certificate Certificate
+}
+
+var AndroidAppCertificateInfoProvider = blueprint.NewProvider[AndroidAppCertificateInfo]()
+
 // android_app_certificate modules can be referenced by the certificates property of android_app modules to select
 // the signing key.
 func AndroidAppCertificateFactory() android.Module {
@@ -1633,6 +1870,10 @@
 		Pem: android.PathForModuleSrc(ctx, cert+".x509.pem"),
 		Key: android.PathForModuleSrc(ctx, cert+".pk8"),
 	}
+
+	android.SetProvider(ctx, AndroidAppCertificateInfoProvider, AndroidAppCertificateInfo{
+		Certificate: c.Certificate,
+	})
 }
 
 type OverrideAndroidApp struct {
@@ -1656,6 +1897,10 @@
 
 	android.InitAndroidMultiTargetsArchModule(m, android.DeviceSupported, android.MultilibCommon)
 	android.InitOverrideModule(m)
+	android.AddLoadHookWithPriority(m, func(ctx android.LoadHookContext) {
+		createInternalRuntimeOverlays(ctx, m.ModuleBase)
+	}, 1) // Run after soong config load hoook
+
 	return m
 }
 
@@ -1687,11 +1932,11 @@
 
 type UsesLibraryProperties struct {
 	// A list of shared library modules that will be listed in uses-library tags in the AndroidManifest.xml file.
-	Uses_libs []string
+	Uses_libs proptools.Configurable[[]string]
 
 	// A list of shared library modules that will be listed in uses-library tags in the AndroidManifest.xml file with
 	// required=false.
-	Optional_uses_libs []string
+	Optional_uses_libs proptools.Configurable[[]string]
 
 	// If true, the list of uses_libs and optional_uses_libs modules must match the AndroidManifest.xml file.  Defaults
 	// to true if either uses_libs or optional_uses_libs is set.  Will unconditionally default to true in the future.
@@ -1739,7 +1984,7 @@
 
 func (u *usesLibrary) deps(ctx android.BottomUpMutatorContext, addCompatDeps bool) {
 	if !ctx.Config().UnbundledBuild() || ctx.Config().UnbundledBuildImage() {
-		ctx.AddVariationDependencies(nil, usesLibReqTag, u.usesLibraryProperties.Uses_libs...)
+		ctx.AddVariationDependencies(nil, usesLibReqTag, u.usesLibraryProperties.Uses_libs.GetOrDefault(ctx, nil)...)
 		presentOptionalUsesLibs := u.presentOptionalUsesLibs(ctx)
 		ctx.AddVariationDependencies(nil, usesLibOptTag, presentOptionalUsesLibs...)
 		// Only add these extra dependencies if the module is an app that depends on framework
@@ -1752,17 +1997,17 @@
 			ctx.AddVariationDependencies(nil, usesLibCompat28OptTag, dexpreopt.OptionalCompatUsesLibs28...)
 			ctx.AddVariationDependencies(nil, usesLibCompat30OptTag, dexpreopt.OptionalCompatUsesLibs30...)
 		}
-		_, diff, _ := android.ListSetDifference(u.usesLibraryProperties.Optional_uses_libs, presentOptionalUsesLibs)
+		_, diff, _ := android.ListSetDifference(u.usesLibraryProperties.Optional_uses_libs.GetOrDefault(ctx, nil), presentOptionalUsesLibs)
 		u.usesLibraryProperties.Missing_optional_uses_libs = diff
 	} else {
-		ctx.AddVariationDependencies(nil, r8LibraryJarTag, u.usesLibraryProperties.Uses_libs...)
+		ctx.AddVariationDependencies(nil, r8LibraryJarTag, u.usesLibraryProperties.Uses_libs.GetOrDefault(ctx, nil)...)
 		ctx.AddVariationDependencies(nil, r8LibraryJarTag, u.presentOptionalUsesLibs(ctx)...)
 	}
 }
 
 // presentOptionalUsesLibs returns optional_uses_libs after filtering out libraries that don't exist in the source tree.
 func (u *usesLibrary) presentOptionalUsesLibs(ctx android.BaseModuleContext) []string {
-	optionalUsesLibs := android.FilterListPred(u.usesLibraryProperties.Optional_uses_libs, func(s string) bool {
+	optionalUsesLibs := android.FilterListPred(u.usesLibraryProperties.Optional_uses_libs.GetOrDefault(ctx, nil), func(s string) bool {
 		exists := ctx.OtherModuleExists(s)
 		if !exists && !android.InList(ctx.ModuleName(), ctx.Config().BuildWarningBadOptionalUsesLibsAllowlist()) {
 			fmt.Printf("Warning: Module '%s' depends on non-existing optional_uses_libs '%s'\n", ctx.ModuleName(), s)
@@ -1783,7 +2028,7 @@
 		return clcMap
 	}
 
-	ctx.VisitDirectDeps(func(m android.Module) {
+	ctx.VisitDirectDepsProxy(func(m android.ModuleProxy) {
 		tag, isUsesLibTag := ctx.OtherModuleDependencyTag(m).(usesLibraryDependencyTag)
 		if !isUsesLibTag {
 			return
@@ -1791,31 +2036,35 @@
 
 		dep := android.RemoveOptionalPrebuiltPrefix(ctx.OtherModuleName(m))
 
+		javaInfo, ok := android.OtherModuleProvider(ctx, m, JavaInfoProvider)
+		if !ok {
+			return
+		}
 		// Skip stub libraries. A dependency on the implementation library has been added earlier,
 		// so it will be added to CLC, but the stub shouldn't be. Stub libraries can be distingushed
 		// from implementation libraries by their name, which is different as it has a suffix.
-		if comp, ok := m.(SdkLibraryComponentDependency); ok {
-			if impl := comp.OptionalSdkLibraryImplementation(); impl != nil && *impl != dep {
+		if comp := javaInfo.SdkLibraryComponentDependencyInfo; comp != nil {
+			if impl := comp.OptionalSdkLibraryImplementation; impl != nil && *impl != dep {
 				return
 			}
 		}
 
-		if lib, ok := m.(UsesLibraryDependency); ok {
+		if lib := javaInfo.UsesLibraryDependencyInfo; lib != nil {
 			if _, ok := android.OtherModuleProvider(ctx, m, SdkLibraryInfoProvider); ok {
 				// Skip java_sdk_library dependencies that provide stubs, but not an implementation.
 				// This will be restricted to optional_uses_libs
-				if tag == usesLibOptTag && lib.DexJarBuildPath(ctx).PathOrNil() == nil {
+				if tag == usesLibOptTag && lib.DexJarBuildPath.PathOrNil() == nil {
 					u.shouldDisableDexpreopt = true
 					return
 				}
 			}
 			libName := dep
-			if ulib, ok := m.(ProvidesUsesLib); ok && ulib.ProvidesUsesLib() != nil {
-				libName = *ulib.ProvidesUsesLib()
+			if ulib := javaInfo.ProvidesUsesLibInfo; ulib != nil && ulib.ProvidesUsesLib != nil {
+				libName = *ulib.ProvidesUsesLib
 			}
 			clcMap.AddContext(ctx, tag.sdkVersion, libName, tag.optional,
-				lib.DexJarBuildPath(ctx).PathOrNil(), lib.DexJarInstallPath(),
-				lib.ClassLoaderContexts())
+				lib.DexJarBuildPath.PathOrNil(), lib.DexJarInstallPath,
+				lib.ClassLoaderContexts)
 		} else if ctx.Config().AllowMissingDependencies() {
 			ctx.AddMissingDependencies([]string{dep})
 		} else {
@@ -1828,15 +2077,15 @@
 // enforceUsesLibraries returns true of <uses-library> tags should be checked against uses_libs and optional_uses_libs
 // properties.  Defaults to true if either of uses_libs or optional_uses_libs is specified.  Will default to true
 // unconditionally in the future.
-func (u *usesLibrary) enforceUsesLibraries() bool {
-	defaultEnforceUsesLibs := len(u.usesLibraryProperties.Uses_libs) > 0 ||
-		len(u.usesLibraryProperties.Optional_uses_libs) > 0
+func (u *usesLibrary) enforceUsesLibraries(ctx android.ModuleContext) bool {
+	defaultEnforceUsesLibs := len(u.usesLibraryProperties.Uses_libs.GetOrDefault(ctx, nil)) > 0 ||
+		len(u.usesLibraryProperties.Optional_uses_libs.GetOrDefault(ctx, nil)) > 0
 	return BoolDefault(u.usesLibraryProperties.Enforce_uses_libs, u.enforce || defaultEnforceUsesLibs)
 }
 
 // Freeze the value of `enforce_uses_libs` based on the current values of `uses_libs` and `optional_uses_libs`.
-func (u *usesLibrary) freezeEnforceUsesLibraries() {
-	enforce := u.enforceUsesLibraries()
+func (u *usesLibrary) freezeEnforceUsesLibraries(ctx android.ModuleContext) {
+	enforce := u.enforceUsesLibraries(ctx)
 	u.usesLibraryProperties.Enforce_uses_libs = &enforce
 }
 
diff --git a/java/app_import.go b/java/app_import.go
index a54cf2f..a122510 100644
--- a/java/app_import.go
+++ b/java/app_import.go
@@ -43,6 +43,12 @@
 		Description: "Uncompress embedded JNI libs",
 	})
 
+	stripEmbeddedJniLibsUnusedArchRule = pctx.AndroidStaticRule("strip-embedded-jni-libs-from-unused-arch", blueprint.RuleParams{
+		Command:     `${config.Zip2ZipCmd} -i $in -o $out -x 'lib/**/*.so' $extraArgs`,
+		CommandDeps: []string{"${config.Zip2ZipCmd}"},
+		Description: "Remove all JNI libs from unused architectures",
+	}, "extraArgs")
+
 	uncompressDexRule = pctx.AndroidStaticRule("uncompress-dex", blueprint.RuleParams{
 		Command: `if (zipinfo $in '*.dex' 2>/dev/null | grep -v ' stor ' >/dev/null) ; then ` +
 			`${config.Zip2ZipCmd} -i $in -o $out -0 'classes*.dex'` +
@@ -56,11 +62,26 @@
 		CommandDeps: []string{"build/soong/scripts/check_prebuilt_presigned_apk.py", "${config.Aapt2Cmd}", "${config.ZipAlign}"},
 		Description: "Check presigned apk",
 	}, "extraArgs")
+
+	extractApkRule = pctx.AndroidStaticRule("extract-apk", blueprint.RuleParams{
+		Command:     "unzip -p $in $extract_apk > $out",
+		Description: "Extract specific sub apk",
+	}, "extract_apk")
+
+	gzipRule = pctx.AndroidStaticRule("gzip",
+		blueprint.RuleParams{
+			Command:     "prebuilts/build-tools/path/linux-x86/gzip -9 -c $in > $out",
+			CommandDeps: []string{"prebuilts/build-tools/path/linux-x86/gzip"},
+			Description: "gzip $out",
+		})
 )
 
 func RegisterAppImportBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("android_app_import", AndroidAppImportFactory)
 	ctx.RegisterModuleType("android_test_import", AndroidTestImportFactory)
+	ctx.PreArchMutators(func(ctx android.RegisterMutatorsContext) {
+		ctx.BottomUp("disable_prebuilts_without_apk", disablePrebuiltsWithoutApkMutator)
+	})
 }
 
 type AndroidAppImport struct {
@@ -85,16 +106,16 @@
 
 	hideApexVariantFromMake bool
 
-	provenanceMetaDataFile android.OutputPath
+	provenanceMetaDataFile android.Path
 }
 
 type AndroidAppImportProperties struct {
 	// A prebuilt apk to import
-	Apk *string `android:"path"`
+	Apk proptools.Configurable[string] `android:"path,replace_instead_of_append"`
 
 	// The name of a certificate in the default certificate directory or an android_app_certificate
 	// module name in the form ":module". Should be empty if presigned or default_dev_cert is set.
-	Certificate *string
+	Certificate proptools.Configurable[string] `android:"replace_instead_of_append"`
 
 	// Names of extra android_app_certificate modules to sign the apk with in the form ":module".
 	Additional_certificates []string
@@ -147,10 +168,19 @@
 	// the prebuilt is Name() without "prebuilt_" prefix
 	Source_module_name *string
 
+	// Whether stripping all libraries from unused architectures.
+	Strip_unused_jni_arch *bool
+
 	// Path to the .prebuilt_info file of the prebuilt app.
 	// In case of mainline modules, the .prebuilt_info file contains the build_id that was used
 	// to generate the prebuilt.
 	Prebuilt_info *string `android:"path"`
+
+	// Path of extracted apk which is extracted from prebuilt apk. Use this extracted to import.
+	Extract_apk *string
+
+	// Compress the output APK using gzip. Defaults to false.
+	Compress_apk proptools.Configurable[bool] `android:"arch_variant,replace_instead_of_append"`
 }
 
 func (a *AndroidAppImport) IsInstallable() bool {
@@ -193,13 +223,6 @@
 			}
 		}
 	}
-
-	if String(a.properties.Apk) == "" {
-		// Disable this module since the apk property is still empty after processing all matching
-		// variants. This likely means there is no matching variant, and the default variant doesn't
-		// have an apk property value either.
-		a.Disable()
-	}
 }
 
 func MergePropertiesFromVariant(ctx android.EarlyModuleContext,
@@ -219,8 +242,32 @@
 	}
 }
 
+// disablePrebuiltsWithoutApkMutator is a pre-arch mutator that disables AndroidAppImport or
+// AndroidTestImport modules that don't have an apk set. We need this separate mutator instead
+// of doing it in processVariants because processVariants is a defaultable hook, and configurable
+// properties can only be evaluated after the defaults (and eventually, base configurabtion)
+// mutators.
+func disablePrebuiltsWithoutApkMutator(ctx android.BottomUpMutatorContext) {
+	switch a := ctx.Module().(type) {
+	case *AndroidAppImport:
+		if a.properties.Apk.GetOrDefault(ctx, "") == "" {
+			// Disable this module since the apk property is still empty after processing all
+			// matching variants. This likely means there is no matching variant, and the default
+			// variant doesn't have an apk property value either.
+			a.Disable()
+		}
+	case *AndroidTestImport:
+		if a.properties.Apk.GetOrDefault(ctx, "") == "" {
+			// Disable this module since the apk property is still empty after processing all
+			// matching variants. This likely means there is no matching variant, and the default
+			// variant doesn't have an apk property value either.
+			a.Disable()
+		}
+	}
+}
+
 func (a *AndroidAppImport) DepsMutator(ctx android.BottomUpMutatorContext) {
-	cert := android.SrcIsModule(String(a.properties.Certificate))
+	cert := android.SrcIsModule(a.properties.Certificate.GetOrDefault(ctx, ""))
 	if cert != "" {
 		ctx.AddDependency(ctx.Module(), certificateTag, cert)
 	}
@@ -239,7 +286,7 @@
 }
 
 func (a *AndroidAppImport) uncompressEmbeddedJniLibs(
-	ctx android.ModuleContext, inputPath android.Path, outputPath android.OutputPath) {
+	ctx android.ModuleContext, inputPath android.Path, outputPath android.WritablePath) {
 	// Test apps don't need their JNI libraries stored uncompressed. As a matter of fact, messing
 	// with them may invalidate pre-existing signature data.
 	if ctx.InstallInTestcases() && (Bool(a.properties.Presigned) || Bool(a.properties.Preprocessed)) {
@@ -258,6 +305,19 @@
 	})
 }
 
+func (a *AndroidAppImport) extractSubApk(
+	ctx android.ModuleContext, inputPath android.Path, outputPath android.WritablePath) {
+	extractApkPath := *a.properties.Extract_apk
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   extractApkRule,
+		Input:  inputPath,
+		Output: outputPath,
+		Args: map[string]string{
+			"extract_apk": extractApkPath,
+		},
+	})
+}
+
 // Returns whether this module should have the dex file stored uncompressed in the APK.
 func (a *AndroidAppImport) shouldUncompressDex(ctx android.ModuleContext) bool {
 	if ctx.Config().UnbundledBuild() || proptools.Bool(a.properties.Preprocessed) {
@@ -272,6 +332,26 @@
 	return shouldUncompressDex(ctx, android.RemoveOptionalPrebuiltPrefix(ctx.ModuleName()), &a.dexpreopter)
 }
 
+func (a *AndroidAppImport) stripEmbeddedJniLibsUnusedArch(
+	ctx android.ModuleContext, inputPath android.Path, outputPath android.WritablePath) {
+	var wantedJniLibSlice []string
+	for _, target := range ctx.MultiTargets() {
+		supported_abis := target.Arch.Abi
+		for _, arch := range supported_abis {
+			wantedJniLibSlice = append(wantedJniLibSlice, " -X lib/"+arch+"/*.so")
+		}
+	}
+	wantedJniLibString := strings.Join(wantedJniLibSlice, " ")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   stripEmbeddedJniLibsUnusedArchRule,
+		Input:  inputPath,
+		Output: outputPath,
+		Args: map[string]string{
+			"extraArgs": wantedJniLibString,
+		},
+	})
+}
+
 func (a *AndroidAppImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	a.generateAndroidBuildActions(ctx)
 }
@@ -303,7 +383,7 @@
 	}
 
 	numCertPropsSet := 0
-	if String(a.properties.Certificate) != "" {
+	if a.properties.Certificate.GetOrDefault(ctx, "") != "" {
 		numCertPropsSet++
 	}
 	if Bool(a.properties.Presigned) {
@@ -316,16 +396,27 @@
 		ctx.ModuleErrorf("One and only one of certficate, presigned (implied by preprocessed), and default_dev_cert properties must be set")
 	}
 
-	// TODO: LOCAL_EXTRACT_APK/LOCAL_EXTRACT_DPI_APK
 	// TODO: LOCAL_PACKAGE_SPLITS
 
 	srcApk := a.prebuilt.SingleSourcePath(ctx)
+	if a.properties.Extract_apk != nil {
+		extract_apk := android.PathForModuleOut(ctx, "extract-apk", ctx.ModuleName()+".apk")
+		a.extractSubApk(ctx, srcApk, extract_apk)
+		srcApk = extract_apk
+	}
 
 	// TODO: Install or embed JNI libraries
 
 	// Uncompress JNI libraries in the apk
 	jnisUncompressed := android.PathForModuleOut(ctx, "jnis-uncompressed", ctx.ModuleName()+".apk")
-	a.uncompressEmbeddedJniLibs(ctx, srcApk, jnisUncompressed.OutputPath)
+	a.uncompressEmbeddedJniLibs(ctx, srcApk, jnisUncompressed)
+
+	// Strip all embedded JNI libs and include only required ones accordingly to the module's compile_multilib
+	if Bool(a.properties.Strip_unused_jni_arch) {
+		jnisStripped := android.PathForModuleOut(ctx, "jnis-stripped", ctx.ModuleName()+".apk")
+		a.stripEmbeddedJniLibsUnusedArch(ctx, jnisUncompressed, jnisStripped)
+		jnisUncompressed = jnisStripped
+	}
 
 	var pathFragments []string
 	relInstallPath := String(a.properties.Relative_install_path)
@@ -344,13 +435,15 @@
 	a.dexpreopter.isPresignedPrebuilt = Bool(a.properties.Presigned)
 	a.dexpreopter.uncompressedDex = a.shouldUncompressDex(ctx)
 
-	a.dexpreopter.enforceUsesLibs = a.usesLibrary.enforceUsesLibraries()
+	a.dexpreopter.enforceUsesLibs = a.usesLibrary.enforceUsesLibraries(ctx)
 	a.dexpreopter.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
-	if a.usesLibrary.shouldDisableDexpreopt {
+
+	// Disable Dexpreopt if Compress_apk is true. It follows the build/make/core/app_prebuilt_internal.mk
+	if a.usesLibrary.shouldDisableDexpreopt || a.properties.Compress_apk.GetOrDefault(ctx, false) {
 		a.dexpreopter.disableDexpreopt()
 	}
 
-	if a.usesLibrary.enforceUsesLibraries() {
+	if a.usesLibrary.enforceUsesLibraries(ctx) {
 		a.usesLibrary.verifyUsesLibrariesAPK(ctx, srcApk, &a.dexpreopter.classLoaderContexts)
 	}
 
@@ -365,7 +458,13 @@
 		jnisUncompressed = dexUncompressed
 	}
 
-	apkFilename := proptools.StringDefault(a.properties.Filename, a.BaseModuleName()+".apk")
+	defaultApkFilename := a.BaseModuleName()
+	if a.properties.Compress_apk.GetOrDefault(ctx, false) {
+		defaultApkFilename += ".apk.gz"
+	} else {
+		defaultApkFilename += ".apk"
+	}
+	apkFilename := proptools.StringDefault(a.properties.Filename, defaultApkFilename)
 
 	// TODO: Handle EXTERNAL
 
@@ -386,7 +485,7 @@
 		// If the certificate property is empty at this point, default_dev_cert must be set to true.
 		// Which makes processMainCert's behavior for the empty cert string WAI.
 		_, _, certificates := collectAppDeps(ctx, a, false, false)
-		a.certificate, certificates = processMainCert(a.ModuleBase, String(a.properties.Certificate), certificates, ctx)
+		a.certificate, certificates = processMainCert(a.ModuleBase, a.properties.Certificate.GetOrDefault(ctx, ""), certificates, ctx)
 		signed := android.PathForModuleOut(ctx, "signed", apkFilename)
 		var lineageFile android.Path
 		if lineage := String(a.properties.Lineage); lineage != "" {
@@ -405,11 +504,20 @@
 		a.certificate = PresignedCertificate
 	}
 
-	// TODO: Optionally compress the output apk.
+	if a.properties.Compress_apk.GetOrDefault(ctx, false) {
+		outputFile := android.PathForModuleOut(ctx, "compressed_apk", apkFilename)
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        gzipRule,
+			Input:       a.outputFile,
+			Output:      outputFile,
+			Description: "Compressing " + a.outputFile.Base(),
+		})
+		a.outputFile = outputFile
+	}
 
 	if apexInfo.IsForPlatform() {
 		a.installPath = ctx.InstallFile(installDir, apkFilename, a.outputFile)
-		artifactPath := android.PathForModuleSrc(ctx, *a.properties.Apk)
+		artifactPath := android.PathForModuleSrc(ctx, a.properties.Apk.GetOrDefault(ctx, ""))
 		a.provenanceMetaDataFile = provenance.GenerateArtifactProvenanceMetaData(ctx, artifactPath, a.installPath)
 	}
 
@@ -423,6 +531,8 @@
 
 	ctx.SetOutputFiles([]android.Path{a.outputFile}, "")
 
+	buildComplianceMetadata(ctx)
+
 	// TODO: androidmk converter jni libs
 }
 
@@ -473,7 +583,7 @@
 	return a.certificate
 }
 
-func (a *AndroidAppImport) ProvenanceMetaDataFile() android.OutputPath {
+func (a *AndroidAppImport) ProvenanceMetaDataFile() android.Path {
 	return a.provenanceMetaDataFile
 }
 
@@ -518,7 +628,15 @@
 	return Bool(a.properties.Privileged)
 }
 
-func (a *AndroidAppImport) DepIsInSameApex(_ android.BaseModuleContext, _ android.Module) bool {
+func (m *AndroidAppImport) GetDepInSameApexChecker() android.DepInSameApexChecker {
+	return AppImportDepInSameApexChecker{}
+}
+
+type AppImportDepInSameApexChecker struct {
+	android.BaseDepInSameApexChecker
+}
+
+func (m AppImportDepInSameApexChecker) OutgoingDepIsInSameApex(tag blueprint.DependencyTag) bool {
 	// android_app_import might have extra dependencies via uses_libs property.
 	// Don't track the dependency as we don't automatically add those libraries
 	// to the classpath. It should be explicitly added to java_libs property of APEX
@@ -633,7 +751,7 @@
 	android.InitApexModule(module)
 	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	android.InitDefaultableModule(module)
-	android.InitSingleSourcePrebuiltModule(module, &module.properties, "Apk")
+	android.InitConfigurablePrebuiltModuleString(module, &module.properties.Apk, "Apk")
 
 	module.usesLibrary.enforce = true
 
@@ -662,9 +780,27 @@
 func (a *AndroidTestImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	a.generateAndroidBuildActions(ctx)
 
+	a.updateModuleInfoJSON(ctx)
+
 	a.data = android.PathsForModuleSrc(ctx, a.testProperties.Data)
 }
 
+func (a *AndroidTestImport) updateModuleInfoJSON(ctx android.ModuleContext) {
+	moduleInfoJSON := ctx.ModuleInfoJSON()
+	moduleInfoJSON.Class = []string{"APPS"}
+	moduleInfoJSON.CompatibilitySuites = []string{"null-suite"}
+	if len(a.testProperties.Test_suites) > 0 {
+		moduleInfoJSON.CompatibilitySuites = a.testProperties.Test_suites
+	}
+	moduleInfoJSON.SystemSharedLibs = []string{"none"}
+	moduleInfoJSON.Tags = []string{"tests"}
+	moduleInfoJSON.RegisterNameOverride = a.BaseModuleName()
+	testConfig := android.ExistentPathForSource(ctx, ctx.ModuleDir(), "AndroidTest.xml")
+	if testConfig.Valid() {
+		moduleInfoJSON.TestConfig = []string{testConfig.String()}
+	}
+}
+
 func (a *AndroidTestImport) InstallInTestcases() bool {
 	return true
 }
@@ -686,7 +822,7 @@
 	android.InitApexModule(module)
 	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	android.InitDefaultableModule(module)
-	android.InitSingleSourcePrebuiltModule(module, &module.properties, "Apk")
+	android.InitConfigurablePrebuiltModuleString(module, &module.properties.Apk, "Apk")
 
 	return module
 }
diff --git a/java/app_import_test.go b/java/app_import_test.go
index 54a5e75..2600767 100644
--- a/java/app_import_test.go
+++ b/java/app_import_test.go
@@ -26,6 +26,7 @@
 )
 
 func TestAndroidAppImport(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		android_app_import {
 			name: "foo",
@@ -37,7 +38,47 @@
 		}
 		`)
 
-	variant := ctx.ModuleForTests("foo", "android_common")
+	variant := ctx.ModuleForTests(t, "foo", "android_common")
+
+	// Check dexpreopt outputs.
+	if variant.MaybeOutput("dexpreopt/foo/oat/arm64/package.vdex").Rule == nil ||
+		variant.MaybeOutput("dexpreopt/foo/oat/arm64/package.odex").Rule == nil {
+		t.Errorf("can't find dexpreopt outputs")
+	}
+
+	// Check cert signing flag.
+	signedApk := variant.Output("signed/foo.apk")
+	signingFlag := signedApk.Args["certificates"]
+	expected := "build/make/target/product/security/platform.x509.pem build/make/target/product/security/platform.pk8"
+	if expected != signingFlag {
+		t.Errorf("Incorrect signing flags, expected: %q, got: %q", expected, signingFlag)
+	}
+	rule := variant.Rule("genProvenanceMetaData")
+	android.AssertStringEquals(t, "Invalid input", "prebuilts/apk/app.apk", rule.Inputs[0].String())
+	android.AssertStringEquals(t, "Invalid output", "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto", rule.Output.String())
+	android.AssertStringEquals(t, "Invalid args", "foo", rule.Args["module_name"])
+	android.AssertStringEquals(t, "Invalid args", "/system/app/foo/foo.apk", rule.Args["install_path"])
+}
+
+func TestAndroidAppImportWithDefaults(t *testing.T) {
+	t.Parallel()
+	ctx, _ := testJava(t, `
+		android_app_import {
+			name: "foo",
+			defaults: ["foo_defaults"],
+		}
+
+		java_defaults {
+			name: "foo_defaults",
+			apk: "prebuilts/apk/app.apk",
+			certificate: "platform",
+			dex_preopt: {
+				enabled: true,
+			},
+		}
+		`)
+
+	variant := ctx.ModuleForTests(t, "foo", "android_common")
 
 	// Check dexpreopt outputs.
 	if variant.MaybeOutput("dexpreopt/foo/oat/arm64/package.vdex").Rule == nil ||
@@ -60,6 +101,7 @@
 }
 
 func TestAndroidAppImport_NoDexPreopt(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		android_app_import {
 			name: "foo",
@@ -71,7 +113,7 @@
 		}
 		`)
 
-	variant := ctx.ModuleForTests("foo", "android_common")
+	variant := ctx.ModuleForTests(t, "foo", "android_common")
 
 	// Check dexpreopt outputs. They shouldn't exist.
 	if variant.MaybeOutput("dexpreopt/foo/oat/arm64/package.vdex").Rule != nil ||
@@ -87,6 +129,7 @@
 }
 
 func TestAndroidAppImport_Presigned(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		android_app_import {
 			name: "foo",
@@ -98,7 +141,7 @@
 		}
 		`)
 
-	variant := ctx.ModuleForTests("foo", "android_common")
+	variant := ctx.ModuleForTests(t, "foo", "android_common")
 
 	// Check dexpreopt outputs.
 	if variant.MaybeOutput("dexpreopt/foo/oat/arm64/package.vdex").Rule == nil ||
@@ -121,6 +164,7 @@
 }
 
 func TestAndroidAppImport_SigningLineage(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 	  android_app_import {
 			name: "foo",
@@ -137,7 +181,7 @@
 		}
 	`)
 
-	variant := ctx.ModuleForTests("foo", "android_common")
+	variant := ctx.ModuleForTests(t, "foo", "android_common")
 
 	signedApk := variant.Output("signed/foo.apk")
 	// Check certificates
@@ -164,6 +208,7 @@
 }
 
 func TestAndroidAppImport_SigningLineageFilegroup(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 	  android_app_import {
 			name: "foo",
@@ -178,7 +223,7 @@
 		}
 	`)
 
-	variant := ctx.ModuleForTests("foo", "android_common")
+	variant := ctx.ModuleForTests(t, "foo", "android_common")
 
 	signedApk := variant.Output("signed/foo.apk")
 	// Check cert signing lineage flag.
@@ -196,6 +241,7 @@
 }
 
 func TestAndroidAppImport_DefaultDevCert(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		android_app_import {
 			name: "foo",
@@ -207,7 +253,7 @@
 		}
 		`)
 
-	variant := ctx.ModuleForTests("foo", "android_common")
+	variant := ctx.ModuleForTests(t, "foo", "android_common")
 
 	// Check dexpreopt outputs.
 	if variant.MaybeOutput("dexpreopt/foo/oat/arm64/package.vdex").Rule == nil ||
@@ -231,6 +277,7 @@
 }
 
 func TestAndroidAppImport_DpiVariants(t *testing.T) {
+	t.Parallel()
 	bp := `
 		android_app_import {
 			name: "foo",
@@ -302,7 +349,7 @@
 			}),
 		).RunTestWithBp(t, bp)
 
-		variant := result.ModuleForTests("foo", "android_common")
+		variant := result.ModuleForTests(t, "foo", "android_common")
 		input := variant.Output("jnis-uncompressed/foo.apk").Input.String()
 		if strings.HasSuffix(input, test.expected) {
 			t.Errorf("wrong src apk, expected: %q got: %q", test.expected, input)
@@ -317,6 +364,7 @@
 }
 
 func TestAndroidAppImport_Filename(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		android_app_import {
 			name: "foo",
@@ -325,10 +373,25 @@
 		}
 
 		android_app_import {
+			name: "foo_compressed",
+			apk: "prebuilts/apk/app.apk",
+			presigned: true,
+			compress_apk: true,
+		}
+
+		android_app_import {
 			name: "bar",
 			apk: "prebuilts/apk/app.apk",
 			presigned: true,
-			filename: "bar_sample.apk"
+			filename: "bar_sample.apk",
+		}
+
+		android_app_import {
+			name: "compressed_bar",
+			apk: "prebuilts/apk/app.apk",
+			presigned: true,
+			filename: "bar_sample.apk",
+			compress_apk: true,
 		}
 		`)
 
@@ -347,16 +410,30 @@
 			expectedMetaDataPath: "out/soong/.intermediates/provenance_metadata/foo/provenance_metadata.textproto",
 		},
 		{
+			name:                 "foo_compressed",
+			expected:             "foo_compressed.apk.gz",
+			onDevice:             "/system/app/foo_compressed/foo_compressed.apk.gz",
+			expectedArtifactPath: "prebuilts/apk/app.apk",
+			expectedMetaDataPath: "out/soong/.intermediates/provenance_metadata/foo_compressed/provenance_metadata.textproto",
+		},
+		{
 			name:                 "bar",
 			expected:             "bar_sample.apk",
 			onDevice:             "/system/app/bar/bar_sample.apk",
 			expectedArtifactPath: "prebuilts/apk/app.apk",
 			expectedMetaDataPath: "out/soong/.intermediates/provenance_metadata/bar/provenance_metadata.textproto",
 		},
+		{
+			name:                 "compressed_bar",
+			expected:             "bar_sample.apk",
+			onDevice:             "/system/app/compressed_bar/bar_sample.apk",
+			expectedArtifactPath: "prebuilts/apk/app.apk",
+			expectedMetaDataPath: "out/soong/.intermediates/provenance_metadata/compressed_bar/provenance_metadata.textproto",
+		},
 	}
 
 	for _, test := range testCases {
-		variant := ctx.ModuleForTests(test.name, "android_common")
+		variant := ctx.ModuleForTests(t, test.name, "android_common")
 		if variant.MaybeOutput(test.expected).Rule == nil {
 			t.Errorf("can't find output named %q - all outputs: %v", test.expected, variant.AllOutputs())
 		}
@@ -380,6 +457,7 @@
 }
 
 func TestAndroidAppImport_ArchVariants(t *testing.T) {
+	t.Parallel()
 	// The test config's target arch is ARM64.
 	testCases := []struct {
 		name         string
@@ -505,9 +583,10 @@
 
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
 			ctx, _ := testJava(t, test.bp)
 
-			variant := ctx.ModuleForTests("foo", "android_common")
+			variant := ctx.ModuleForTests(t, "foo", "android_common")
 			if test.expected == "" {
 				if variant.Module().Enabled(android.PanickingConfigAndErrorContext(ctx)) {
 					t.Error("module should have been disabled, but wasn't")
@@ -530,6 +609,7 @@
 }
 
 func TestAndroidAppImport_SoongConfigVariables(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		name         string
 		bp           string
@@ -572,6 +652,7 @@
 
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
 			ctx := android.GroupFixturePreparers(
 				prepareForJavaTest,
 				android.PrepareForTestWithSoongConfigModuleBuildComponents,
@@ -584,7 +665,7 @@
 				}),
 			).RunTestWithBp(t, test.bp).TestContext
 
-			variant := ctx.ModuleForTests("foo", "android_common")
+			variant := ctx.ModuleForTests(t, "foo", "android_common")
 			if test.expected == "" {
 				if variant.Module().Enabled(android.PanickingConfigAndErrorContext(ctx)) {
 					t.Error("module should have been disabled, but wasn't")
@@ -607,6 +688,7 @@
 }
 
 func TestAndroidAppImport_overridesDisabledAndroidApp(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		android_app {
 			name: "foo",
@@ -622,7 +704,7 @@
 		}
 		`)
 
-	variant := ctx.ModuleForTests("prebuilt_foo", "android_common")
+	variant := ctx.ModuleForTests(t, "prebuilt_foo", "android_common")
 	a := variant.Module().(*AndroidAppImport)
 	// The prebuilt module should still be enabled and active even if the source-based counterpart
 	// is disabled.
@@ -635,6 +717,7 @@
 }
 
 func TestAndroidAppImport_relativeInstallPath(t *testing.T) {
+	t.Parallel()
 	bp := `
 		android_app_import {
 			name: "no_relative_install_path",
@@ -664,28 +747,49 @@
 	}{
 		{
 			name:                "no_relative_install_path",
-			expectedInstallPath: "out/soong/target/product/test_device/system/app/no_relative_install_path/no_relative_install_path.apk",
+			expectedInstallPath: "out/target/product/test_device/system/app/no_relative_install_path/no_relative_install_path.apk",
 			errorMessage:        "Install path is not correct when relative_install_path is missing",
 		},
 		{
 			name:                "relative_install_path",
-			expectedInstallPath: "out/soong/target/product/test_device/system/app/my/path/relative_install_path/relative_install_path.apk",
+			expectedInstallPath: "out/target/product/test_device/system/app/my/path/relative_install_path/relative_install_path.apk",
 			errorMessage:        "Install path is not correct for app when relative_install_path is present",
 		},
 		{
 			name:                "privileged_relative_install_path",
-			expectedInstallPath: "out/soong/target/product/test_device/system/priv-app/my/path/privileged_relative_install_path/privileged_relative_install_path.apk",
+			expectedInstallPath: "out/target/product/test_device/system/priv-app/my/path/privileged_relative_install_path/privileged_relative_install_path.apk",
 			errorMessage:        "Install path is not correct for privileged app when relative_install_path is present",
 		},
 	}
 	for _, testCase := range testCases {
-		ctx, _ := testJava(t, bp)
-		mod := ctx.ModuleForTests(testCase.name, "android_common").Module().(*AndroidAppImport)
-		android.AssertPathRelativeToTopEquals(t, testCase.errorMessage, testCase.expectedInstallPath, mod.installPath)
+		t.Run(testCase.name, func(t *testing.T) {
+			t.Parallel()
+			ctx, _ := testJava(t, bp)
+			mod := ctx.ModuleForTests(t, testCase.name, "android_common").Module().(*AndroidAppImport)
+			android.AssertPathRelativeToTopEquals(t, testCase.errorMessage, testCase.expectedInstallPath, mod.installPath)
+		})
 	}
 }
 
+func TestAndroidAppImport_ExtractApk(t *testing.T) {
+	t.Parallel()
+	ctx, _ := testJava(t, `
+		android_app_import {
+			name: "foo",
+			apk: "prebuilts/apk/app.apk",
+			certificate: "platform",
+			extract_apk: "extract_path/sub_app.apk"
+		}
+		`)
+
+	variant := ctx.ModuleForTests(t, "foo", "android_common")
+	extractRuleArgs := variant.Output("extract-apk/foo.apk").BuildParams.Args
+	if extractRuleArgs["extract_apk"] != "extract_path/sub_app.apk" {
+		t.Errorf("Unexpected extract apk args: %s", extractRuleArgs["extract_apk"])
+	}
+}
 func TestAndroidTestImport(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		android_test_import {
 			name: "foo",
@@ -697,7 +801,7 @@
 		}
 		`)
 
-	test := ctx.ModuleForTests("foo", "android_common").Module().(*AndroidTestImport)
+	test := ctx.ModuleForTests(t, "foo", "android_common").Module().(*AndroidTestImport)
 
 	// Check android mks.
 	entries := android.AndroidMkEntriesForTest(t, ctx, test)[0]
@@ -714,6 +818,7 @@
 }
 
 func TestAndroidTestImport_NoJinUncompressForPresigned(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		android_test_import {
 			name: "foo",
@@ -734,16 +839,16 @@
 		}
 		`)
 
-	variant := ctx.ModuleForTests("foo", "android_common")
+	variant := ctx.ModuleForTests(t, "foo", "android_common")
 	jniRule := variant.Output("jnis-uncompressed/foo.apk").BuildParams.Rule.String()
 	if jniRule == android.Cp.String() {
-		t.Errorf("Unexpected JNI uncompress rule command: " + jniRule)
+		t.Errorf("Unexpected JNI uncompress rule command: %s", jniRule)
 	}
 
-	variant = ctx.ModuleForTests("foo_presigned", "android_common")
+	variant = ctx.ModuleForTests(t, "foo_presigned", "android_common")
 	jniRule = variant.Output("jnis-uncompressed/foo_presigned.apk").BuildParams.Rule.String()
 	if jniRule != android.Cp.String() {
-		t.Errorf("Unexpected JNI uncompress rule: " + jniRule)
+		t.Errorf("Unexpected JNI uncompress rule: %s", jniRule)
 	}
 	if variant.MaybeOutput("zip-aligned/foo_presigned.apk").Rule == nil {
 		t.Errorf("Presigned test apk should be aligned")
@@ -751,6 +856,7 @@
 }
 
 func TestAndroidTestImport_Preprocessed(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		android_test_import {
 			name: "foo",
@@ -761,10 +867,10 @@
 		`)
 
 	apkName := "foo.apk"
-	variant := ctx.ModuleForTests("foo", "android_common")
+	variant := ctx.ModuleForTests(t, "foo", "android_common")
 	jniRule := variant.Output("jnis-uncompressed/" + apkName).BuildParams.Rule.String()
 	if jniRule != android.Cp.String() {
-		t.Errorf("Unexpected JNI uncompress rule: " + jniRule)
+		t.Errorf("Unexpected JNI uncompress rule: %s", jniRule)
 	}
 
 	// Make sure signing and aligning were skipped.
@@ -777,9 +883,11 @@
 }
 
 func TestAndroidAppImport_Preprocessed(t *testing.T) {
+	t.Parallel()
 	for _, dontUncompressPrivAppDexs := range []bool{false, true} {
 		name := fmt.Sprintf("dontUncompressPrivAppDexs:%t", dontUncompressPrivAppDexs)
 		t.Run(name, func(t *testing.T) {
+			t.Parallel()
 			result := android.GroupFixturePreparers(
 				PrepareForTestWithJavaDefaultModules,
 				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
@@ -804,10 +912,10 @@
 
 			// non-privileged app
 			apkName := "foo.apk"
-			variant := result.ModuleForTests("foo", "android_common")
+			variant := result.ModuleForTests(t, "foo", "android_common")
 			outputBuildParams := variant.Output(apkName).BuildParams
 			if outputBuildParams.Rule.String() != android.Cp.String() {
-				t.Errorf("Unexpected prebuilt android_app_import rule: " + outputBuildParams.Rule.String())
+				t.Errorf("Unexpected prebuilt android_app_import rule: %s", outputBuildParams.Rule.String())
 			}
 
 			// Make sure compression and aligning were validated.
@@ -817,7 +925,7 @@
 
 			validationBuildParams := variant.Output("validated-prebuilt/check.stamp").BuildParams
 			if validationBuildParams.Rule.String() != checkPresignedApkRule.String() {
-				t.Errorf("Unexpected validation rule: " + validationBuildParams.Rule.String())
+				t.Errorf("Unexpected validation rule: %s", validationBuildParams.Rule.String())
 			}
 
 			expectedScriptArgs := "--preprocessed"
@@ -826,10 +934,10 @@
 
 			// privileged app
 			apkName = "bar.apk"
-			variant = result.ModuleForTests("bar", "android_common")
+			variant = result.ModuleForTests(t, "bar", "android_common")
 			outputBuildParams = variant.Output(apkName).BuildParams
 			if outputBuildParams.Rule.String() != android.Cp.String() {
-				t.Errorf("Unexpected prebuilt android_app_import rule: " + outputBuildParams.Rule.String())
+				t.Errorf("Unexpected prebuilt android_app_import rule: %s", outputBuildParams.Rule.String())
 			}
 
 			// Make sure compression and aligning were validated.
@@ -839,7 +947,7 @@
 
 			validationBuildParams = variant.Output("validated-prebuilt/check.stamp").BuildParams
 			if validationBuildParams.Rule.String() != checkPresignedApkRule.String() {
-				t.Errorf("Unexpected validation rule: " + validationBuildParams.Rule.String())
+				t.Errorf("Unexpected validation rule: %s", validationBuildParams.Rule.String())
 			}
 
 			expectedScriptArgs = "--privileged"
@@ -854,6 +962,7 @@
 }
 
 func TestAndroidTestImport_UncompressDex(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		name string
 		bp   string
@@ -894,7 +1003,7 @@
 			}),
 		).RunTestWithBp(t, bp)
 
-		foo := result.ModuleForTests("foo", "android_common")
+		foo := result.ModuleForTests(t, "foo", "android_common")
 		actual := foo.MaybeRule("uncompress-dex").Rule != nil
 
 		expect := !unbundled
@@ -917,6 +1026,7 @@
 				name := fmt.Sprintf("%s,unbundled:%t,dontUncompressPrivAppDexs:%t",
 					tt.name, unbundled, dontUncompressPrivAppDexs)
 				t.Run(name, func(t *testing.T) {
+					t.Parallel()
 					test(t, tt.bp, unbundled, dontUncompressPrivAppDexs)
 				})
 			}
@@ -925,6 +1035,7 @@
 }
 
 func TestAppImportMissingCertificateAllowMissingDependencies(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		PrepareForTestWithJavaDefaultModules,
 		android.PrepareForTestWithAllowMissingDependencies,
@@ -936,7 +1047,7 @@
 			certificate: ":missing_certificate",
 		}`)
 
-	foo := result.ModuleForTests("foo", "android_common")
+	foo := result.ModuleForTests(t, "foo", "android_common")
 	fooApk := foo.Output("signed/foo.apk")
 	if fooApk.Rule != android.ErrorRule {
 		t.Fatalf("expected ErrorRule for foo.apk, got %s", fooApk.Rule.String())
diff --git a/java/app_set_test.go b/java/app_set_test.go
index c02b359..9b4c44b 100644
--- a/java/app_set_test.go
+++ b/java/app_set_test.go
@@ -24,13 +24,14 @@
 )
 
 func TestAndroidAppSet(t *testing.T) {
+	t.Parallel()
 	result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(t, `
 		android_app_set {
 			name: "foo",
 			set: "prebuilts/apks/app.apks",
 			prerelease: true,
 		}`)
-	module := result.ModuleForTests("foo", "android_common")
+	module := result.ModuleForTests(t, "foo", "android_common")
 	const packedSplitApks = "foo.zip"
 	params := module.Output(packedSplitApks)
 	if params.Rule == nil {
@@ -65,6 +66,7 @@
 }
 
 func TestAndroidAppSet_Variants(t *testing.T) {
+	t.Parallel()
 	bp := `
 		android_app_set {
 			name: "foo",
@@ -113,24 +115,24 @@
 	}
 
 	for _, test := range testCases {
-		ctx := android.GroupFixturePreparers(
-			PrepareForTestWithJavaDefaultModules,
-			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
-				variables.AAPTPrebuiltDPI = test.aaptPrebuiltDPI
-				variables.Platform_sdk_version = &test.sdkVersion
-			}),
-			android.FixtureModifyConfig(func(config android.Config) {
-				config.Targets[android.Android] = test.targets
-			}),
-		).RunTestWithBp(t, bp)
+		t.Run(test.name, func(t *testing.T) {
+			ctx := android.GroupFixturePreparers(
+				PrepareForTestWithJavaDefaultModules,
+				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+					variables.AAPTPrebuiltDPI = test.aaptPrebuiltDPI
+					variables.Platform_sdk_version = &test.sdkVersion
+				}),
+				android.FixtureModifyConfig(func(config android.Config) {
+					config.Targets[android.Android] = test.targets
+				}),
+			).RunTestWithBp(t, bp)
 
-		module := ctx.ModuleForTests("foo", "android_common")
-		const packedSplitApks = "foo.zip"
-		params := module.Output(packedSplitApks)
-		for k, v := range test.expected {
-			t.Run(test.name, func(t *testing.T) {
+			module := ctx.ModuleForTests(t, "foo", "android_common")
+			const packedSplitApks = "foo.zip"
+			params := module.Output(packedSplitApks)
+			for k, v := range test.expected {
 				android.AssertStringEquals(t, fmt.Sprintf("arg value for `%s`", k), v, params.Args[k])
-			})
-		}
+			}
+		})
 	}
 }
diff --git a/java/app_test.go b/java/app_test.go
index dd672a0..4f23f61 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -18,6 +18,7 @@
 	"fmt"
 	"path/filepath"
 	"reflect"
+	"regexp"
 	"sort"
 	"strings"
 	"testing"
@@ -41,6 +42,7 @@
 }
 
 func TestApp(t *testing.T) {
+	t.Parallel()
 	resourceFiles := []string{
 		"res/layout/layout.xml",
 		"res/values/strings.xml",
@@ -55,6 +57,7 @@
 
 	for _, moduleType := range []string{"android_app", "android_library"} {
 		t.Run(moduleType, func(t *testing.T) {
+			t.Parallel()
 			result := android.GroupFixturePreparers(
 				prepareForJavaTest,
 				android.FixtureModifyMockFS(func(fs android.MockFS) {
@@ -69,14 +72,14 @@
 				}
 			`)
 
-			foo := result.ModuleForTests("foo", "android_common")
+			foo := result.ModuleForTests(t, "foo", "android_common")
 
 			var expectedLinkImplicits []string
 
 			manifestFixer := foo.Output("manifest_fixer/AndroidManifest.xml")
 			expectedLinkImplicits = append(expectedLinkImplicits, manifestFixer.Output.String())
 
-			frameworkRes := result.ModuleForTests("framework-res", "android_common")
+			frameworkRes := result.ModuleForTests(t, "framework-res", "android_common")
 			expectedLinkImplicits = append(expectedLinkImplicits,
 				frameworkRes.Output("package-res.apk").Output.String())
 
@@ -93,13 +96,14 @@
 			expectedLinkImplicits = append(expectedLinkImplicits, list.Output.String())
 
 			// Check that the link rule uses
-			res := result.ModuleForTests("foo", "android_common").Output("package-res.apk")
+			res := result.ModuleForTests(t, "foo", "android_common").Output("package-res.apk")
 			android.AssertDeepEquals(t, "aapt2 link implicits", expectedLinkImplicits, res.Implicits.Strings())
 		})
 	}
 }
 
 func TestAppSplits(t *testing.T) {
+	t.Parallel()
 	ctx := testApp(t, `
 				android_app {
 					name: "foo",
@@ -108,7 +112,7 @@
 					sdk_version: "current"
 				}`)
 
-	foo := ctx.ModuleForTests("foo", "android_common")
+	foo := ctx.ModuleForTests(t, "foo", "android_common")
 
 	expectedOutputs := []string{
 		"out/soong/.intermediates/foo/android_common/foo.apk",
@@ -124,6 +128,7 @@
 }
 
 func TestPlatformAPIs(t *testing.T) {
+	t.Parallel()
 	testJava(t, `
 		android_app {
 			name: "foo",
@@ -158,6 +163,7 @@
 }
 
 func TestAndroidAppLinkType(t *testing.T) {
+	t.Parallel()
 	testJava(t, `
 		android_app {
 			name: "foo",
@@ -247,6 +253,7 @@
 }
 
 func TestUpdatableApps(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		name          string
 		bp            string
@@ -358,6 +365,7 @@
 
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
 			errorHandler := android.FixtureExpectsNoErrors
 			if test.expectedError != "" {
 				errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(test.expectedError)
@@ -372,6 +380,7 @@
 }
 
 func TestUpdatableApps_TransitiveDepsShouldSetMinSdkVersion(t *testing.T) {
+	t.Parallel()
 	testJavaError(t, `module "bar".*: should support min_sdk_version\(29\)`, cc.GatherRequiredDepsForTest(android.Android)+`
 		android_app {
 			name: "foo",
@@ -390,6 +399,7 @@
 }
 
 func TestUpdatableApps_JniLibsShouldShouldSupportMinSdkVersion(t *testing.T) {
+	t.Parallel()
 	testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
 		android_app {
 			name: "foo",
@@ -410,6 +420,7 @@
 }
 
 func TestUpdatableApps_JniLibShouldBeBuiltAgainstMinSdkVersion(t *testing.T) {
+	t.Parallel()
 	bp := cc.GatherRequiredDepsForTest(android.Android) + `
 		android_app {
 			name: "foo",
@@ -437,11 +448,11 @@
 
 	ctx, _ := testJavaWithFS(t, bp, fs)
 
-	inputs := ctx.ModuleForTests("libjni", "android_arm64_armv8-a_sdk_shared").Description("link").Implicits
+	inputs := ctx.ModuleForTests(t, "libjni", "android_arm64_armv8-a_sdk_shared").Description("link").Implicits
 	var crtbeginFound, crtendFound bool
-	expectedCrtBegin := ctx.ModuleForTests("crtbegin_so",
+	expectedCrtBegin := ctx.ModuleForTests(t, "crtbegin_so",
 		"android_arm64_armv8-a_sdk_29").Rule("noAddrSig").Output
-	expectedCrtEnd := ctx.ModuleForTests("crtend_so",
+	expectedCrtEnd := ctx.ModuleForTests(t, "crtend_so",
 		"android_arm64_armv8-a_sdk_29").Rule("noAddrSig").Output
 	implicits := []string{}
 	for _, input := range inputs {
@@ -465,6 +476,7 @@
 }
 
 func TestUpdatableApps_ErrorIfJniLibDoesntSupportMinSdkVersion(t *testing.T) {
+	t.Parallel()
 	bp := cc.GatherRequiredDepsForTest(android.Android) + `
 		android_app {
 			name: "foo",
@@ -486,6 +498,7 @@
 }
 
 func TestUpdatableApps_ErrorIfDepMinSdkVersionIsHigher(t *testing.T) {
+	t.Parallel()
 	bp := cc.GatherRequiredDepsForTest(android.Android) + `
 		android_app {
 			name: "foo",
@@ -517,6 +530,7 @@
 }
 
 func TestUpdatableApps_ApplyDefaultUpdatableModuleVersion(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		PrepareForTestWithJavaDefaultModules,
 	).RunTestWithBp(t, `
@@ -528,7 +542,7 @@
 			updatable: true,
 		}
 	`)
-	foo := result.ModuleForTests("com.android.foo", "android_common").Rule("manifestFixer")
+	foo := result.ModuleForTests(t, "com.android.foo", "android_common").Rule("manifestFixer")
 	android.AssertStringDoesContain(t,
 		"com.android.foo: expected manifest fixer to set override-placeholder-version to RELEASE_DEFAULT_UPDATABLE_MODULE_VERSION",
 		foo.BuildParams.Args["args"],
@@ -537,6 +551,7 @@
 }
 
 func TestUpdatableApps_ApplyOverrideApexManifestDefaultVersion(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		PrepareForTestWithJavaDefaultModules,
 		android.FixtureMergeEnv(map[string]string{
@@ -551,7 +566,7 @@
 			updatable: true,
 		}
 	`)
-	foo := result.ModuleForTests("com.android.foo", "android_common").Rule("manifestFixer")
+	foo := result.ModuleForTests(t, "com.android.foo", "android_common").Rule("manifestFixer")
 	android.AssertStringDoesContain(t,
 		"com.android.foo: expected manifest fixer to set override-placeholder-version to 1234",
 		foo.BuildParams.Args["args"],
@@ -560,6 +575,7 @@
 }
 
 func TestResourceDirs(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		name      string
 		prop      string
@@ -596,12 +612,13 @@
 
 	for _, testCase := range testCases {
 		t.Run(testCase.name, func(t *testing.T) {
+			t.Parallel()
 			result := android.GroupFixturePreparers(
 				PrepareForTestWithJavaDefaultModules,
 				fs.AddToFixture(),
 			).RunTestWithBp(t, fmt.Sprintf(bp, testCase.prop))
 
-			module := result.ModuleForTests("foo", "android_common")
+			module := result.ModuleForTests(t, "foo", "android_common")
 			resourceList := module.MaybeOutput("aapt2/res.list")
 
 			var resources []string
@@ -617,6 +634,7 @@
 }
 
 func TestLibraryAssets(t *testing.T) {
+	t.Parallel()
 	bp := `
 			android_app {
 				name: "foo",
@@ -711,7 +729,8 @@
 
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
-			m := ctx.ModuleForTests(test.name, "android_common")
+			t.Parallel()
+			m := ctx.ModuleForTests(t, test.name, "android_common")
 
 			// Check asset flag in aapt2 link flags
 			var aapt2link android.TestingBuildParams
@@ -746,6 +765,7 @@
 }
 
 func TestAppJavaResources(t *testing.T) {
+	t.Parallel()
 	bp := `
 			android_app {
 				name: "foo",
@@ -763,7 +783,7 @@
 
 	ctx := testApp(t, bp)
 
-	foo := ctx.ModuleForTests("foo", "android_common")
+	foo := ctx.ModuleForTests(t, "foo", "android_common")
 	fooResources := foo.Output("res/foo.jar")
 	fooDexJar := foo.Output("dex-withres/foo.jar")
 	fooDexJarAligned := foo.Output("dex-withres-aligned/foo.jar")
@@ -781,7 +801,7 @@
 		t.Errorf("expected aligned dex jar %q in foo apk inputs %q", w, g)
 	}
 
-	bar := ctx.ModuleForTests("bar", "android_common")
+	bar := ctx.ModuleForTests(t, "bar", "android_common")
 	barResources := bar.Output("res/bar.jar")
 	barApk := bar.Rule("combineApk")
 
@@ -791,6 +811,7 @@
 }
 
 func TestAndroidResourceProcessor(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		name                            string
 		appUsesRP                       bool
@@ -1223,6 +1244,7 @@
 
 	for _, testCase := range testCases {
 		t.Run(testCase.name, func(t *testing.T) {
+			t.Parallel()
 			bp := fmt.Sprintf(`
 				android_app {
 					name: "app",
@@ -1330,7 +1352,7 @@
 			}
 
 			getAaptInfo := func(moduleName string) (aaptInfo aaptInfo) {
-				mod := result.ModuleForTests(moduleName, "android_common")
+				mod := result.ModuleForTests(t, moduleName, "android_common")
 				resourceListRule := mod.MaybeOutput("aapt2/res.list")
 				overlayListRule := mod.MaybeOutput("aapt2/overlay.list")
 				aaptRule := mod.Rule("aapt2Link")
@@ -1419,6 +1441,7 @@
 }
 
 func TestAndroidResourceOverlays(t *testing.T) {
+	t.Parallel()
 	type moduleAndVariant struct {
 		module  string
 		variant string
@@ -1615,6 +1638,7 @@
 
 	for _, testCase := range testCases {
 		t.Run(testCase.name, func(t *testing.T) {
+			t.Parallel()
 			result := android.GroupFixturePreparers(
 				PrepareForTestWithJavaDefaultModules,
 				fs.AddToFixture(),
@@ -1646,7 +1670,7 @@
 			}
 
 			getResources := func(moduleName, variantName string) (resourceFiles, overlayFiles, rroDirs []string) {
-				module := result.ModuleForTests(moduleName, variantName)
+				module := result.ModuleForTests(t, moduleName, variantName)
 				resourceList := module.MaybeOutput("aapt2/res.list")
 				if resourceList.Rule != nil {
 					resourceFiles = resourceListToFiles(module, android.PathsRelativeToTop(resourceList.Inputs))
@@ -1705,7 +1729,7 @@
 }
 
 func checkSdkVersion(t *testing.T, result *android.TestResult, expectedSdkVersion string) {
-	foo := result.ModuleForTests("foo", "android_common")
+	foo := result.ModuleForTests(t, "foo", "android_common")
 	link := foo.Output("package-res.apk")
 	linkFlags := strings.Split(link.Args["flags"], " ")
 	min := android.IndexList("--min-sdk-version", linkFlags)
@@ -1724,6 +1748,7 @@
 }
 
 func TestAppSdkVersion(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		name                  string
 		sdkVersion            string
@@ -1791,6 +1816,7 @@
 	for _, moduleType := range []string{"android_app", "android_library"} {
 		for _, test := range testCases {
 			t.Run(moduleType+" "+test.name, func(t *testing.T) {
+				t.Parallel()
 				platformApiProp := ""
 				if test.platformApis {
 					platformApiProp = "platform_apis: true,"
@@ -1827,6 +1853,7 @@
 }
 
 func TestVendorAppSdkVersion(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		name                                  string
 		sdkVersion                            string
@@ -1869,6 +1896,7 @@
 		for _, sdkKind := range []string{"", "system_"} {
 			for _, test := range testCases {
 				t.Run(moduleType+" "+test.name, func(t *testing.T) {
+					t.Parallel()
 					bp := fmt.Sprintf(`%s {
 						name: "foo",
 						srcs: ["a.java"],
@@ -1900,6 +1928,7 @@
 }
 
 func TestJNIABI(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
 		cc_library {
 			name: "libjni",
@@ -1956,7 +1985,8 @@
 
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
-			app := ctx.ModuleForTests(test.name, "android_common")
+			t.Parallel()
+			app := ctx.ModuleForTests(t, test.name, "android_common")
 			jniLibZip := app.Output("jnilibs.zip")
 			var abis []string
 			args := strings.Fields(jniLibZip.Args["jarArgs"])
@@ -1974,6 +2004,7 @@
 }
 
 func TestAppSdkVersionByPartition(t *testing.T) {
+	t.Parallel()
 	testJavaError(t, "sdk_version must have a value when the module is located at vendor or product", `
 		android_app {
 			name: "foo",
@@ -2018,6 +2049,7 @@
 }
 
 func TestJNIPackaging(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
 		cc_library {
 			name: "libjni",
@@ -2089,7 +2121,8 @@
 
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
-			app := ctx.ModuleForTests(test.name, "android_common")
+			t.Parallel()
+			app := ctx.ModuleForTests(t, test.name, "android_common")
 			jniLibZip := app.MaybeOutput("jnilibs.zip")
 			if g, w := (jniLibZip.Rule != nil), test.packaged; g != w {
 				t.Errorf("expected jni packaged %v, got %v", w, g)
@@ -2109,6 +2142,7 @@
 }
 
 func TestJNISDK(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
 		cc_library {
 			name: "libjni",
@@ -2170,16 +2204,17 @@
 		{name: "app_vendor", vendorJNI: true},
 	}
 
-	platformJNI := ctx.ModuleForTests("libjni", "android_arm64_armv8-a_shared").
+	platformJNI := ctx.ModuleForTests(t, "libjni", "android_arm64_armv8-a_shared").
 		Output("libjni.so").Output.String()
-	sdkJNI := ctx.ModuleForTests("libjni", "android_arm64_armv8-a_sdk_shared").
+	sdkJNI := ctx.ModuleForTests(t, "libjni", "android_arm64_armv8-a_sdk_shared").
 		Output("libjni.so").Output.String()
-	vendorJNI := ctx.ModuleForTests("libvendorjni", "android_vendor_arm64_armv8-a_shared").
+	vendorJNI := ctx.ModuleForTests(t, "libvendorjni", "android_vendor_arm64_armv8-a_shared").
 		Output("libvendorjni.so").Output.String()
 
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
-			app := ctx.ModuleForTests(test.name, "android_common")
+			t.Parallel()
+			app := ctx.ModuleForTests(t, test.name, "android_common")
 
 			jniLibZip := app.MaybeOutput("jnilibs.zip")
 			if len(jniLibZip.Implicits) != 1 {
@@ -2204,6 +2239,7 @@
 	}
 
 	t.Run("jni_uses_platform_apis_error", func(t *testing.T) {
+		t.Parallel()
 		testJavaError(t, `jni_uses_platform_apis: can only be set for modules that set sdk_version`, `
 			android_test {
 				name: "app_platform",
@@ -2214,6 +2250,7 @@
 	})
 
 	t.Run("jni_uses_sdk_apis_error", func(t *testing.T) {
+		t.Parallel()
 		testJavaError(t, `jni_uses_sdk_apis: can only be set for modules that do not set sdk_version`, `
 			android_test {
 				name: "app_sdk",
@@ -2226,6 +2263,7 @@
 }
 
 func TestCertificates(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		name                     string
 		bp                       string
@@ -2363,6 +2401,7 @@
 
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
 			result := android.GroupFixturePreparers(
 				PrepareForTestWithJavaDefaultModules,
 				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
@@ -2378,7 +2417,7 @@
 				}),
 			).RunTestWithBp(t, test.bp)
 
-			foo := result.ModuleForTests("foo", "android_common")
+			foo := result.ModuleForTests(t, "foo", "android_common")
 
 			certificate := foo.Module().(*AndroidApp).certificate
 			android.AssertPathRelativeToTopEquals(t, "certificates key", test.expectedCertificate+".pk8", certificate.Key)
@@ -2400,6 +2439,7 @@
 }
 
 func TestRequestV4SigningFlag(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		name     string
 		bp       string
@@ -2444,11 +2484,12 @@
 
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
 			result := android.GroupFixturePreparers(
 				PrepareForTestWithJavaDefaultModules,
 			).RunTestWithBp(t, test.bp)
 
-			foo := result.ModuleForTests("foo", "android_common")
+			foo := result.ModuleForTests(t, "foo", "android_common")
 
 			signapk := foo.Output("foo.apk")
 			signFlags := signapk.Args["flags"]
@@ -2458,6 +2499,7 @@
 }
 
 func TestPackageNameOverride(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		name                string
 		bp                  string
@@ -2476,7 +2518,7 @@
 			packageNameOverride: "",
 			expected: []string{
 				"out/soong/.intermediates/foo/android_common/foo.apk",
-				"out/soong/target/product/test_device/system/app/foo/foo.apk",
+				"out/target/product/test_device/system/app/foo/foo.apk",
 			},
 		},
 		{
@@ -2492,7 +2534,7 @@
 			expected: []string{
 				// The package apk should be still be the original name for test dependencies.
 				"out/soong/.intermediates/foo/android_common/bar.apk",
-				"out/soong/target/product/test_device/system/app/bar/bar.apk",
+				"out/target/product/test_device/system/app/bar/bar.apk",
 			},
 		},
 		{
@@ -2508,13 +2550,14 @@
 			packageNameOverride: "",
 			expected: []string{
 				"out/soong/.intermediates/foo/android_common/bar.apk",
-				"out/soong/target/product/test_device/system/app/bar/bar.apk",
+				"out/target/product/test_device/system/app/bar/bar.apk",
 			},
 		},
 	}
 
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
 			result := android.GroupFixturePreparers(
 				PrepareForTestWithJavaDefaultModules,
 				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
@@ -2524,7 +2567,7 @@
 				}),
 			).RunTestWithBp(t, test.bp)
 
-			foo := result.ModuleForTests("foo", "android_common")
+			foo := result.ModuleForTests(t, "foo", "android_common")
 
 			outSoongDir := result.Config.SoongOutDir()
 
@@ -2543,6 +2586,7 @@
 }
 
 func TestInstrumentationTargetOverridden(t *testing.T) {
+	t.Parallel()
 	bp := `
 		android_app {
 			name: "foo",
@@ -2564,7 +2608,7 @@
 		}),
 	).RunTestWithBp(t, bp)
 
-	bar := result.ModuleForTests("bar", "android_common")
+	bar := result.ModuleForTests(t, "bar", "android_common")
 	res := bar.Output("package-res.apk")
 	aapt2Flags := res.Args["flags"]
 	e := "--rename-instrumentation-target-package org.dandroid.bp"
@@ -2574,6 +2618,7 @@
 }
 
 func TestOverrideAndroidApp(t *testing.T) {
+	t.Parallel()
 	result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(
 		t, `
 		android_app {
@@ -2651,7 +2696,7 @@
 			name:             "foo",
 			moduleName:       "foo",
 			variantName:      "android_common",
-			apkPath:          "out/soong/target/product/test_device/system/app/foo/foo.apk",
+			apkPath:          "out/target/product/test_device/system/app/foo/foo.apk",
 			certFlag:         "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
 			certSigningFlags: "",
 			overrides:        []string{"qux"},
@@ -2663,7 +2708,7 @@
 			name:             "foo",
 			moduleName:       "bar",
 			variantName:      "android_common_bar",
-			apkPath:          "out/soong/target/product/test_device/system/app/bar/bar.apk",
+			apkPath:          "out/target/product/test_device/system/app/bar/bar.apk",
 			certFlag:         "cert/new_cert.x509.pem cert/new_cert.pk8",
 			certSigningFlags: "--lineage lineage.bin --rotation-min-sdk-version 32",
 			overrides:        []string{"qux", "foo"},
@@ -2675,7 +2720,7 @@
 			name:             "foo",
 			moduleName:       "baz",
 			variantName:      "android_common_baz",
-			apkPath:          "out/soong/target/product/test_device/system/app/baz/baz.apk",
+			apkPath:          "out/target/product/test_device/system/app/baz/baz.apk",
 			certFlag:         "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
 			certSigningFlags: "",
 			overrides:        []string{"qux", "foo"},
@@ -2687,7 +2732,7 @@
 			name:             "foo",
 			moduleName:       "baz_no_rename_resources",
 			variantName:      "android_common_baz_no_rename_resources",
-			apkPath:          "out/soong/target/product/test_device/system/app/baz_no_rename_resources/baz_no_rename_resources.apk",
+			apkPath:          "out/target/product/test_device/system/app/baz_no_rename_resources/baz_no_rename_resources.apk",
 			certFlag:         "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
 			certSigningFlags: "",
 			overrides:        []string{"qux", "foo"},
@@ -2699,7 +2744,7 @@
 			name:             "foo_no_rename_resources",
 			moduleName:       "baz_base_no_rename_resources",
 			variantName:      "android_common_baz_base_no_rename_resources",
-			apkPath:          "out/soong/target/product/test_device/system/app/baz_base_no_rename_resources/baz_base_no_rename_resources.apk",
+			apkPath:          "out/target/product/test_device/system/app/baz_base_no_rename_resources/baz_base_no_rename_resources.apk",
 			certFlag:         "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
 			certSigningFlags: "",
 			overrides:        []string{"qux", "foo_no_rename_resources"},
@@ -2711,7 +2756,7 @@
 			name:             "foo_no_rename_resources",
 			moduleName:       "baz_override_base_rename_resources",
 			variantName:      "android_common_baz_override_base_rename_resources",
-			apkPath:          "out/soong/target/product/test_device/system/app/baz_override_base_rename_resources/baz_override_base_rename_resources.apk",
+			apkPath:          "out/target/product/test_device/system/app/baz_override_base_rename_resources/baz_override_base_rename_resources.apk",
 			certFlag:         "build/make/target/product/security/expiredkey.x509.pem build/make/target/product/security/expiredkey.pk8",
 			certSigningFlags: "",
 			overrides:        []string{"qux", "foo_no_rename_resources"},
@@ -2721,7 +2766,7 @@
 		},
 	}
 	for _, expected := range expectedVariants {
-		variant := result.ModuleForTests(expected.name, expected.variantName)
+		variant := result.ModuleForTests(t, expected.name, expected.variantName)
 
 		// Check the final apk name
 		variant.Output(expected.apkPath)
@@ -2756,6 +2801,7 @@
 }
 
 func TestOverrideAndroidAppOverrides(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(
 		t, `
 		android_app {
@@ -2805,7 +2851,7 @@
 		},
 	}
 	for _, expected := range expectedVariants {
-		variant := ctx.ModuleForTests(expected.name, expected.variantName)
+		variant := ctx.ModuleForTests(t, expected.name, expected.variantName)
 
 		// Check if the overrides field values are correctly aggregated.
 		mod := variant.Module().(*AndroidApp)
@@ -2814,6 +2860,7 @@
 }
 
 func TestOverrideAndroidAppWithPrebuilt(t *testing.T) {
+	t.Parallel()
 	result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(
 		t, `
 		android_app {
@@ -2836,19 +2883,20 @@
 		`)
 
 	// An app that has an override that also has a prebuilt should not be hidden.
-	foo := result.ModuleForTests("foo", "android_common")
+	foo := result.ModuleForTests(t, "foo", "android_common")
 	if foo.Module().IsHideFromMake() {
 		t.Errorf("expected foo to have HideFromMake false")
 	}
 
 	// An override that also has a prebuilt should be hidden.
-	barOverride := result.ModuleForTests("foo", "android_common_bar")
+	barOverride := result.ModuleForTests(t, "foo", "android_common_bar")
 	if !barOverride.Module().IsHideFromMake() {
 		t.Errorf("expected bar override variant of foo to have HideFromMake true")
 	}
 }
 
 func TestOverrideAndroidAppStem(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		android_app {
 			name: "foo",
@@ -2888,41 +2936,42 @@
 		{
 			moduleName:  "foo",
 			variantName: "android_common",
-			apkPath:     "out/soong/target/product/test_device/system/app/foo/foo.apk",
+			apkPath:     "out/target/product/test_device/system/app/foo/foo.apk",
 		},
 		{
 			moduleName:  "foo",
 			variantName: "android_common_bar",
-			apkPath:     "out/soong/target/product/test_device/system/app/bar/bar.apk",
+			apkPath:     "out/target/product/test_device/system/app/bar/bar.apk",
 		},
 		{
 			moduleName:  "foo",
 			variantName: "android_common_baz",
-			apkPath:     "out/soong/target/product/test_device/system/app/baz_stem/baz_stem.apk",
+			apkPath:     "out/target/product/test_device/system/app/baz_stem/baz_stem.apk",
 		},
 		{
 			moduleName:  "foo2",
 			variantName: "android_common",
-			apkPath:     "out/soong/target/product/test_device/system/app/foo2_stem/foo2_stem.apk",
+			apkPath:     "out/target/product/test_device/system/app/foo2_stem/foo2_stem.apk",
 		},
 		{
 			moduleName:  "foo2",
 			variantName: "android_common_bar2",
 			// Note that this may cause the duplicate output error.
-			apkPath: "out/soong/target/product/test_device/system/app/foo2_stem/foo2_stem.apk",
+			apkPath: "out/target/product/test_device/system/app/foo2_stem/foo2_stem.apk",
 		},
 		{
 			moduleName:  "foo2",
 			variantName: "android_common_baz2",
-			apkPath:     "out/soong/target/product/test_device/system/app/baz2_stem/baz2_stem.apk",
+			apkPath:     "out/target/product/test_device/system/app/baz2_stem/baz2_stem.apk",
 		},
 	} {
-		variant := ctx.ModuleForTests(expected.moduleName, expected.variantName)
+		variant := ctx.ModuleForTests(t, expected.moduleName, expected.variantName)
 		variant.Output(expected.apkPath)
 	}
 }
 
 func TestOverrideAndroidAppDependency(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		android_app {
 			name: "foo",
@@ -2950,14 +2999,14 @@
 		`)
 
 	// Verify baz, which depends on the overridden module foo, has the correct classpath javac arg.
-	javac := ctx.ModuleForTests("baz", "android_common").Rule("javac")
+	javac := ctx.ModuleForTests(t, "baz", "android_common").Rule("javac")
 	fooTurbine := "out/soong/.intermediates/foo/android_common/turbine-combined/foo.jar"
 	if !strings.Contains(javac.Args["classpath"], fooTurbine) {
 		t.Errorf("baz classpath %v does not contain %q", javac.Args["classpath"], fooTurbine)
 	}
 
 	// Verify qux, which depends on the overriding module bar, has the correct classpath javac arg.
-	javac = ctx.ModuleForTests("qux", "android_common").Rule("javac")
+	javac = ctx.ModuleForTests(t, "qux", "android_common").Rule("javac")
 	barTurbine := "out/soong/.intermediates/foo/android_common_bar/turbine-combined/foo.jar"
 	if !strings.Contains(javac.Args["classpath"], barTurbine) {
 		t.Errorf("qux classpath %v does not contain %q", javac.Args["classpath"], barTurbine)
@@ -3021,10 +3070,10 @@
 		},
 	}
 	for _, expected := range expectedVariants {
-		variant := ctx.ModuleForTests("foo_test", expected.variantName)
+		variant := ctx.ModuleForTests(t, "foo_test", expected.variantName)
 
 		// Check the final apk name
-		variant.Output("out/soong" + expected.apkPath)
+		variant.Output("out" + expected.apkPath)
 
 		// Check if the overrides field values are correctly aggregated.
 		mod := variant.Module().(*AndroidTest)
@@ -3112,7 +3161,7 @@
 	}
 
 	for _, test := range testCases {
-		variant := ctx.ModuleForTests(test.moduleName, test.variantName)
+		variant := ctx.ModuleForTests(t, test.moduleName, test.variantName)
 		params := variant.MaybeOutput("test_config_fixer/AndroidTest.xml")
 
 		if len(test.expectedFlags) > 0 {
@@ -3158,6 +3207,7 @@
 }
 
 func TestStl(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, cc.GatherRequiredDepsForTest(android.Android)+`
 		cc_library {
 			name: "libjni",
@@ -3200,7 +3250,8 @@
 
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
-			app := ctx.ModuleForTests(test.name, "android_common")
+			t.Parallel()
+			app := ctx.ModuleForTests(t, test.name, "android_common")
 			jniLibZip := app.Output("jnilibs.zip")
 			var jnis []string
 			args := strings.Fields(jniLibZip.Args["jarArgs"])
@@ -3377,8 +3428,8 @@
 		}),
 	).RunTestWithBp(t, bp)
 
-	app := result.ModuleForTests("app", "android_common")
-	prebuilt := result.ModuleForTests("prebuilt", "android_common")
+	app := result.ModuleForTests(t, "app", "android_common")
+	prebuilt := result.ModuleForTests(t, "prebuilt", "android_common")
 
 	// Test that implicit dependencies on java_sdk_library instances are passed to the manifest.
 	// These also include explicit `uses_libs`/`optional_uses_libs` entries, as they may be
@@ -3430,6 +3481,7 @@
 }
 
 func TestDexpreoptBcp(t *testing.T) {
+	t.Parallel()
 	bp := `
 		java_sdk_library {
 			name: "foo",
@@ -3472,6 +3524,7 @@
 
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
 			result := android.GroupFixturePreparers(
 				prepareForJavaTest,
 				PrepareForTestWithJavaSdkLibraryFiles,
@@ -3481,7 +3534,7 @@
 				dexpreopt.FixtureSetPreoptWithUpdatableBcp(test.with),
 			).RunTestWithBp(t, bp)
 
-			app := result.ModuleForTests("app", "android_common")
+			app := result.ModuleForTests(t, "app", "android_common")
 			cmd := app.Rule("dexpreopt").RuleParams.Command
 			bcp := " -Xbootclasspath-locations:" + test.expect + " " // space at the end matters
 			android.AssertStringDoesContain(t, "dexpreopt app bcp", cmd, bcp)
@@ -3490,6 +3543,7 @@
 }
 
 func TestCodelessApp(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		name   string
 		bp     string
@@ -3554,9 +3608,10 @@
 
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
 			ctx := testApp(t, test.bp)
 
-			foo := ctx.ModuleForTests("foo", "android_common")
+			foo := ctx.ModuleForTests(t, "foo", "android_common")
 			manifestFixerArgs := foo.Output("manifest_fixer/AndroidManifest.xml").Args["args"]
 			if strings.Contains(manifestFixerArgs, "--has-no-code") != test.noCode {
 				t.Errorf("unexpected manifest_fixer args: %q", manifestFixerArgs)
@@ -3566,6 +3621,7 @@
 }
 
 func TestUncompressDex(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		name string
 		bp   string
@@ -3653,7 +3709,7 @@
 			}),
 		).RunTestWithBp(t, bp)
 
-		foo := result.ModuleForTests("foo", "android_common")
+		foo := result.ModuleForTests(t, "foo", "android_common")
 		dex := foo.Rule("r8")
 		uncompressedInDexJar := strings.Contains(dex.Args["zipFlags"], "-L 0")
 		aligned := foo.MaybeRule("zipalign").Rule != nil
@@ -3665,10 +3721,13 @@
 
 	for _, tt := range testCases {
 		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
 			t.Run("platform", func(t *testing.T) {
+				t.Parallel()
 				test(t, tt.bp, tt.uncompressedPlatform, false)
 			})
 			t.Run("unbundled", func(t *testing.T) {
+				t.Parallel()
 				test(t, tt.bp, tt.uncompressedUnbundled, true)
 			})
 		})
@@ -3690,6 +3749,7 @@
 }
 
 func TestExportedProguardFlagFiles(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		android_app {
 			name: "foo",
@@ -3735,7 +3795,7 @@
 
 	`)
 
-	m := ctx.ModuleForTests("foo", "android_common")
+	m := ctx.ModuleForTests(t, "foo", "android_common")
 	r8 := m.Rule("java.r8")
 	implicits := r8.Implicits.RelativeToTop().Strings()
 	android.AssertStringListContains(t, "r8 implicits", implicits, "lib1proguard.cfg")
@@ -3751,6 +3811,7 @@
 }
 
 func TestTargetSdkVersionManifestFixer(t *testing.T) {
+	t.Parallel()
 	platform_sdk_codename := "Tiramisu"
 	platform_sdk_version := 33
 	testCases := []struct {
@@ -3803,43 +3864,47 @@
 		},
 	}
 	for _, testCase := range testCases {
-		targetSdkVersionTemplate := ""
-		if testCase.targetSdkVersionInBp != "" {
-			targetSdkVersionTemplate = fmt.Sprintf(`target_sdk_version: "%s",`, testCase.targetSdkVersionInBp)
-		}
-		bp := fmt.Sprintf(`
+		t.Run(testCase.name, func(t *testing.T) {
+			t.Parallel()
+			targetSdkVersionTemplate := ""
+			if testCase.targetSdkVersionInBp != "" {
+				targetSdkVersionTemplate = fmt.Sprintf(`target_sdk_version: "%s",`, testCase.targetSdkVersionInBp)
+			}
+			bp := fmt.Sprintf(`
 			android_app {
 				name: "foo",
 				sdk_version: "current",
 				%s
 			}
 			`, targetSdkVersionTemplate)
-		fixture := android.GroupFixturePreparers(
-			prepareForJavaTest,
-			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
-				if testCase.platformSdkFinal {
-					variables.Platform_sdk_final = proptools.BoolPtr(true)
-				}
-				// explicitly set platform_sdk_codename to make the test deterministic
-				variables.Platform_sdk_codename = &platform_sdk_codename
-				variables.Platform_sdk_version = &platform_sdk_version
-				variables.Platform_version_active_codenames = []string{platform_sdk_codename}
-				// create a non-empty list if unbundledBuild==true
-				if testCase.unbundledBuild {
-					variables.Unbundled_build_apps = []string{"apex_a", "apex_b"}
-				}
-			}),
-		)
+			fixture := android.GroupFixturePreparers(
+				prepareForJavaTest,
+				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+					if testCase.platformSdkFinal {
+						variables.Platform_sdk_final = proptools.BoolPtr(true)
+					}
+					// explicitly set platform_sdk_codename to make the test deterministic
+					variables.Platform_sdk_codename = &platform_sdk_codename
+					variables.Platform_sdk_version = &platform_sdk_version
+					variables.Platform_version_active_codenames = []string{platform_sdk_codename}
+					// create a non-empty list if unbundledBuild==true
+					if testCase.unbundledBuild {
+						variables.Unbundled_build_apps = []string{"apex_a", "apex_b"}
+					}
+				}),
+			)
 
-		result := fixture.RunTestWithBp(t, bp)
-		foo := result.ModuleForTests("foo", "android_common")
+			result := fixture.RunTestWithBp(t, bp)
+			foo := result.ModuleForTests(t, "foo", "android_common")
 
-		manifestFixerArgs := foo.Output("manifest_fixer/AndroidManifest.xml").Args["args"]
-		android.AssertStringDoesContain(t, testCase.name, manifestFixerArgs, "--targetSdkVersion  "+testCase.targetSdkVersionExpected)
+			manifestFixerArgs := foo.Output("manifest_fixer/AndroidManifest.xml").Args["args"]
+			android.AssertStringDoesContain(t, testCase.name, manifestFixerArgs, "--targetSdkVersion  "+testCase.targetSdkVersionExpected)
+		})
 	}
 }
 
 func TestDefaultAppTargetSdkVersionForUpdatableModules(t *testing.T) {
+	t.Parallel()
 	platform_sdk_codename := "Tiramisu"
 	platform_sdk_version := 33
 	testCases := []struct {
@@ -3895,11 +3960,13 @@
 		},
 	}
 	for _, testCase := range testCases {
-		targetSdkVersionTemplate := ""
-		if testCase.targetSdkVersionInBp != nil {
-			targetSdkVersionTemplate = fmt.Sprintf(`target_sdk_version: "%s",`, *testCase.targetSdkVersionInBp)
-		}
-		bp := fmt.Sprintf(`
+		t.Run(testCase.name, func(t *testing.T) {
+			t.Parallel()
+			targetSdkVersionTemplate := ""
+			if testCase.targetSdkVersionInBp != nil {
+				targetSdkVersionTemplate = fmt.Sprintf(`target_sdk_version: "%s",`, *testCase.targetSdkVersionInBp)
+			}
+			bp := fmt.Sprintf(`
 			android_app {
 				name: "foo",
 				sdk_version: "current",
@@ -3910,30 +3977,32 @@
 			}
 			`, targetSdkVersionTemplate, testCase.updatable, testCase.updatable) // enforce default target sdk version if app is updatable
 
-		fixture := android.GroupFixturePreparers(
-			PrepareForTestWithJavaDefaultModules,
-			android.PrepareForTestWithAllowMissingDependencies,
-			android.PrepareForTestWithAndroidMk,
-			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
-				// explicitly set following platform variables to make the test deterministic
-				variables.Platform_sdk_final = &testCase.platform_sdk_final
-				variables.Platform_sdk_version = &platform_sdk_version
-				variables.Platform_sdk_codename = &platform_sdk_codename
-				variables.Platform_version_active_codenames = []string{platform_sdk_codename}
-				variables.Unbundled_build = proptools.BoolPtr(true)
-				variables.Unbundled_build_apps = []string{"sampleModule"}
-			}),
-		)
+			fixture := android.GroupFixturePreparers(
+				PrepareForTestWithJavaDefaultModules,
+				android.PrepareForTestWithAllowMissingDependencies,
+				android.PrepareForTestWithAndroidMk,
+				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+					// explicitly set following platform variables to make the test deterministic
+					variables.Platform_sdk_final = &testCase.platform_sdk_final
+					variables.Platform_sdk_version = &platform_sdk_version
+					variables.Platform_sdk_codename = &platform_sdk_codename
+					variables.Platform_version_active_codenames = []string{platform_sdk_codename}
+					variables.Unbundled_build = proptools.BoolPtr(true)
+					variables.Unbundled_build_apps = []string{"sampleModule"}
+				}),
+			)
 
-		result := fixture.RunTestWithBp(t, bp)
-		foo := result.ModuleForTests("foo", "android_common")
+			result := fixture.RunTestWithBp(t, bp)
+			foo := result.ModuleForTests(t, "foo", "android_common")
 
-		manifestFixerArgs := foo.Output("manifest_fixer/AndroidManifest.xml").Args["args"]
-		android.AssertStringDoesContain(t, testCase.name, manifestFixerArgs, "--targetSdkVersion  "+*testCase.targetSdkVersionExpected)
+			manifestFixerArgs := foo.Output("manifest_fixer/AndroidManifest.xml").Args["args"]
+			android.AssertStringDoesContain(t, testCase.name, manifestFixerArgs, "--targetSdkVersion  "+*testCase.targetSdkVersionExpected)
+		})
 	}
 }
 
 func TestEnforceDefaultAppTargetSdkVersionFlag(t *testing.T) {
+	t.Parallel()
 	platform_sdk_codename := "Tiramisu"
 	platform_sdk_version := 33
 	testCases := []struct {
@@ -3978,8 +4047,10 @@
 		},
 	}
 	for _, testCase := range testCases {
-		errExpected := testCase.expectedError != ""
-		bp := fmt.Sprintf(`
+		t.Run(testCase.name, func(t *testing.T) {
+			t.Parallel()
+			errExpected := testCase.expectedError != ""
+			bp := fmt.Sprintf(`
 			android_app {
 				name: "foo",
 				enforce_default_target_sdk_version: %t,
@@ -3990,35 +4061,37 @@
 			}
 			`, testCase.enforceDefaultTargetSdkVersion, testCase.targetSdkVersionInBp, testCase.updatable)
 
-		fixture := android.GroupFixturePreparers(
-			PrepareForTestWithJavaDefaultModules,
-			android.PrepareForTestWithAllowMissingDependencies,
-			android.PrepareForTestWithAndroidMk,
-			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
-				// explicitly set following platform variables to make the test deterministic
-				variables.Platform_sdk_final = &testCase.platform_sdk_final
-				variables.Platform_sdk_version = &platform_sdk_version
-				variables.Platform_sdk_codename = &platform_sdk_codename
-				variables.Unbundled_build = proptools.BoolPtr(true)
-				variables.Unbundled_build_apps = []string{"sampleModule"}
-			}),
-		)
+			fixture := android.GroupFixturePreparers(
+				PrepareForTestWithJavaDefaultModules,
+				android.PrepareForTestWithAllowMissingDependencies,
+				android.PrepareForTestWithAndroidMk,
+				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+					// explicitly set following platform variables to make the test deterministic
+					variables.Platform_sdk_final = &testCase.platform_sdk_final
+					variables.Platform_sdk_version = &platform_sdk_version
+					variables.Platform_sdk_codename = &platform_sdk_codename
+					variables.Unbundled_build = proptools.BoolPtr(true)
+					variables.Unbundled_build_apps = []string{"sampleModule"}
+				}),
+			)
 
-		errorHandler := android.FixtureExpectsNoErrors
-		if errExpected {
-			errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(testCase.expectedError)
-		}
-		result := fixture.ExtendWithErrorHandler(errorHandler).RunTestWithBp(t, bp)
+			errorHandler := android.FixtureExpectsNoErrors
+			if errExpected {
+				errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(testCase.expectedError)
+			}
+			result := fixture.ExtendWithErrorHandler(errorHandler).RunTestWithBp(t, bp)
 
-		if !errExpected {
-			foo := result.ModuleForTests("foo", "android_common")
-			manifestFixerArgs := foo.Output("manifest_fixer/AndroidManifest.xml").Args["args"]
-			android.AssertStringDoesContain(t, testCase.name, manifestFixerArgs, "--targetSdkVersion  "+testCase.targetSdkVersionExpected)
-		}
+			if !errExpected {
+				foo := result.ModuleForTests(t, "foo", "android_common")
+				manifestFixerArgs := foo.Output("manifest_fixer/AndroidManifest.xml").Args["args"]
+				android.AssertStringDoesContain(t, testCase.name, manifestFixerArgs, "--targetSdkVersion  "+testCase.targetSdkVersionExpected)
+			}
+		})
 	}
 }
 
 func TestEnforceDefaultAppTargetSdkVersionFlagForTests(t *testing.T) {
+	t.Parallel()
 	platform_sdk_codename := "Tiramisu"
 	platform_sdk_version := 33
 	testCases := []struct {
@@ -4051,8 +4124,10 @@
 		},
 	}
 	for _, testCase := range testCases {
-		errExpected := testCase.expectedError != ""
-		bp := fmt.Sprintf(`
+		t.Run(testCase.name, func(t *testing.T) {
+			t.Parallel()
+			errExpected := testCase.expectedError != ""
+			bp := fmt.Sprintf(`
 			android_test {
 				name: "foo",
 				enforce_default_target_sdk_version: %t,
@@ -4061,35 +4136,37 @@
 			}
 		`, testCase.enforceDefaultTargetSdkVersion, testCase.targetSdkVersionInBp)
 
-		fixture := android.GroupFixturePreparers(
-			PrepareForTestWithJavaDefaultModules,
-			android.PrepareForTestWithAllowMissingDependencies,
-			android.PrepareForTestWithAndroidMk,
-			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
-				// explicitly set following platform variables to make the test deterministic
-				variables.Platform_sdk_final = &testCase.platform_sdk_final
-				variables.Platform_sdk_version = &platform_sdk_version
-				variables.Platform_sdk_codename = &platform_sdk_codename
-				variables.Unbundled_build = proptools.BoolPtr(true)
-				variables.Unbundled_build_apps = []string{"sampleModule"}
-			}),
-		)
+			fixture := android.GroupFixturePreparers(
+				PrepareForTestWithJavaDefaultModules,
+				android.PrepareForTestWithAllowMissingDependencies,
+				android.PrepareForTestWithAndroidMk,
+				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+					// explicitly set following platform variables to make the test deterministic
+					variables.Platform_sdk_final = &testCase.platform_sdk_final
+					variables.Platform_sdk_version = &platform_sdk_version
+					variables.Platform_sdk_codename = &platform_sdk_codename
+					variables.Unbundled_build = proptools.BoolPtr(true)
+					variables.Unbundled_build_apps = []string{"sampleModule"}
+				}),
+			)
 
-		errorHandler := android.FixtureExpectsNoErrors
-		if errExpected {
-			errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(testCase.expectedError)
-		}
-		result := fixture.ExtendWithErrorHandler(errorHandler).RunTestWithBp(t, bp)
+			errorHandler := android.FixtureExpectsNoErrors
+			if errExpected {
+				errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(testCase.expectedError)
+			}
+			result := fixture.ExtendWithErrorHandler(errorHandler).RunTestWithBp(t, bp)
 
-		if !errExpected {
-			foo := result.ModuleForTests("foo", "android_common")
-			manifestFixerArgs := foo.Output("manifest_fixer/AndroidManifest.xml").Args["args"]
-			android.AssertStringDoesContain(t, testCase.name, manifestFixerArgs, "--targetSdkVersion  "+testCase.targetSdkVersionExpected)
-		}
+			if !errExpected {
+				foo := result.ModuleForTests(t, "foo", "android_common")
+				manifestFixerArgs := foo.Output("manifest_fixer/AndroidManifest.xml").Args["args"]
+				android.AssertStringDoesContain(t, testCase.name, manifestFixerArgs, "--targetSdkVersion  "+testCase.targetSdkVersionExpected)
+			}
+		})
 	}
 }
 
 func TestAppMissingCertificateAllowMissingDependencies(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		PrepareForTestWithJavaDefaultModules,
 		android.PrepareForTestWithAllowMissingDependencies,
@@ -4110,7 +4187,7 @@
 			sdk_version: "current",
 		}`)
 
-	foo := result.ModuleForTests("foo", "android_common")
+	foo := result.ModuleForTests(t, "foo", "android_common")
 	fooApk := foo.Output("foo.apk")
 	if fooApk.Rule != android.ErrorRule {
 		t.Fatalf("expected ErrorRule for foo.apk, got %s", fooApk.Rule.String())
@@ -4119,6 +4196,7 @@
 }
 
 func TestAppIncludesJniPackages(t *testing.T) {
+	t.Parallel()
 	ctx := android.GroupFixturePreparers(
 		PrepareForTestWithJavaDefaultModules,
 	).RunTestWithBp(t, `
@@ -4181,7 +4259,8 @@
 
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
-			app := ctx.ModuleForTests(tc.name, "android_common")
+			t.Parallel()
+			app := ctx.ModuleForTests(t, tc.name, "android_common")
 
 			outputFile := "jnilibs.zip"
 			jniOutputLibZip := app.MaybeOutput(outputFile)
@@ -4205,6 +4284,7 @@
 }
 
 func TestTargetSdkVersionMtsTests(t *testing.T) {
+	t.Parallel()
 	platformSdkCodename := "Tiramisu"
 	android_test := "android_test"
 	android_test_helper_app := "android_test_helper_app"
@@ -4260,14 +4340,18 @@
 		}),
 	)
 	for _, testCase := range testCases {
-		result := fixture.RunTestWithBp(t, fmt.Sprintf(bpTemplate, testCase.moduleType, testCase.targetSdkVersionInBp, testCase.testSuites))
-		mytest := result.ModuleForTests("mytest", "android_common")
-		manifestFixerArgs := mytest.Output("manifest_fixer/AndroidManifest.xml").Args["args"]
-		android.AssertStringDoesContain(t, testCase.desc, manifestFixerArgs, "--targetSdkVersion  "+testCase.targetSdkVersionExpected)
+		t.Run(testCase.desc, func(t *testing.T) {
+			t.Parallel()
+			result := fixture.RunTestWithBp(t, fmt.Sprintf(bpTemplate, testCase.moduleType, testCase.targetSdkVersionInBp, testCase.testSuites))
+			mytest := result.ModuleForTests(t, "mytest", "android_common")
+			manifestFixerArgs := mytest.Output("manifest_fixer/AndroidManifest.xml").Args["args"]
+			android.AssertStringDoesContain(t, testCase.desc, manifestFixerArgs, "--targetSdkVersion  "+testCase.targetSdkVersionExpected)
+		})
 	}
 }
 
 func TestPrivappAllowlist(t *testing.T) {
+	t.Parallel()
 	testJavaError(t, "privileged must be set in order to use privapp_allowlist", `
 		android_app {
 			name: "foo",
@@ -4293,8 +4377,8 @@
 		}
 		`,
 	)
-	app := result.ModuleForTests("foo", "android_common")
-	overrideApp := result.ModuleForTests("foo", "android_common_bar")
+	app := result.ModuleForTests(t, "foo", "android_common")
+	overrideApp := result.ModuleForTests(t, "foo", "android_common_bar")
 
 	// verify that privapp allowlist is created for override apps
 	overrideApp.Output("out/soong/.intermediates/foo/android_common_bar/privapp_allowlist_com.google.android.foo.xml")
@@ -4305,11 +4389,12 @@
 	}
 
 	// verify that permissions are copied to device
-	app.Output("out/soong/target/product/test_device/system/etc/permissions/foo.xml")
-	overrideApp.Output("out/soong/target/product/test_device/system/etc/permissions/bar.xml")
+	app.Output("out/target/product/test_device/system/etc/permissions/foo.xml")
+	overrideApp.Output("out/target/product/test_device/system/etc/permissions/bar.xml")
 }
 
 func TestPrivappAllowlistAndroidMk(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		PrepareForTestWithJavaDefaultModules,
 		android.PrepareForTestWithAndroidMk,
@@ -4330,8 +4415,8 @@
 		}
 		`,
 	)
-	baseApp := result.ModuleForTests("foo", "android_common")
-	overrideApp := result.ModuleForTests("foo", "android_common_bar")
+	baseApp := result.ModuleForTests(t, "foo", "android_common")
+	overrideApp := result.ModuleForTests(t, "foo", "android_common_bar")
 
 	baseAndroidApp := baseApp.Module().(*AndroidApp)
 	baseEntries := android.AndroidMkEntriesForTest(t, result.TestContext, baseAndroidApp)[0]
@@ -4389,6 +4474,7 @@
 }
 
 func TestAppFlagsPackages(t *testing.T) {
+	t.Parallel()
 	ctx := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		android.FixtureMergeMockFs(
@@ -4426,7 +4512,7 @@
 		}
 	`)
 
-	foo := ctx.ModuleForTests("foo", "android_common")
+	foo := ctx.ModuleForTests(t, "foo", "android_common")
 
 	// android_app module depends on aconfig_declarations listed in flags_packages
 	android.AssertBoolEquals(t, "foo expected to depend on bar", true,
@@ -4453,6 +4539,7 @@
 }
 
 func TestAppFlagsPackagesPropagation(t *testing.T) {
+	t.Parallel()
 	ctx := testApp(t, `
 		aconfig_declarations {
 			name: "foo",
@@ -4510,7 +4597,7 @@
 		}
 	`)
 
-	bazApp := ctx.ModuleForTests("baz_app", "android_common")
+	bazApp := ctx.ModuleForTests(t, "baz_app", "android_common")
 
 	// android_app module depends on aconfig_declarations listed in flags_packages
 	// and that of static libs, but not libs
@@ -4530,6 +4617,7 @@
 
 // Test that dexpreopt is disabled if an optional_uses_libs exists, but does not provide an implementation.
 func TestNoDexpreoptOptionalUsesLibDoesNotHaveImpl(t *testing.T) {
+	t.Parallel()
 	bp := `
 		java_sdk_library_import {
 			name: "sdklib_noimpl",
@@ -4547,7 +4635,7 @@
 		}
 	`
 	result := prepareForJavaTest.RunTestWithBp(t, bp)
-	dexpreopt := result.ModuleForTests("app", "android_common").MaybeRule("dexpreopt").Rule
+	dexpreopt := result.ModuleForTests(t, "app", "android_common").MaybeRule("dexpreopt").Rule
 	android.AssertBoolEquals(t, "dexpreopt should be disabled if optional_uses_libs does not have an implementation", true, dexpreopt == nil)
 }
 
@@ -4589,7 +4677,77 @@
 	assertTestOnlyAndTopLevel(t, ctx, expectedTestOnly, expectedTopLevel)
 }
 
+func TestTestConfigTemplate(t *testing.T) {
+	t.Parallel()
+	ctx := android.GroupFixturePreparers(
+		prepareForJavaTest,
+	).RunTestWithBp(t, `
+		android_test {
+			name: "android-test",
+			test_config_template: "AndroidTestTemplate.xml",
+			test_options: {
+				tradefed_options: [
+					{
+						name: "name1",
+						key: "key1",
+						value: "value1",
+					},
+					{
+						name: "name2",
+						key: "key2",
+						value: "value2",
+					},
+				],
+				test_runner_options: [
+					{
+						name: "name3",
+						key: "key3",
+						value: "value3",
+					},
+					{
+						name: "name4",
+						key: "key4",
+						value: "value4",
+					},
+				],
+			},
+		}
+	`)
+	type option struct {
+		name  string
+		key   string
+		value string
+	}
+	re := regexp.MustCompile(`<option name="(.*)" key="(.*)" value="(.*)" />`)
+	parse_options := func(optionsString string) []option {
+		lines := strings.Split(optionsString, `\n`)
+		var ret []option
+		for _, l := range lines {
+			sm := re.FindStringSubmatch(l)
+			if sm == nil {
+				continue
+			}
+			ret = append(ret, option{sm[1], sm[2], sm[3]})
+		}
+		return ret
+	}
+	rule := ctx.ModuleForTests(t, "android-test", "android_common").Rule("autogenInstrumentationTest")
+	android.AssertSameArray(t, "extraConfigs mismatch",
+		[]option{
+			{"name1", "key1", "value1"},
+			{"name2", "key2", "value2"},
+		},
+		parse_options(rule.Args["extraConfigs"]))
+	android.AssertSameArray(t, "extraTestRunnerConfigs mismatch",
+		[]option{
+			{"name3", "key3", "value3"},
+			{"name4", "key4", "value4"},
+		},
+		parse_options(rule.Args["extraTestRunnerConfigs"]))
+}
+
 func TestAppStem(t *testing.T) {
+	t.Parallel()
 	ctx := testApp(t, `
 				android_app {
 					name: "foo",
@@ -4598,7 +4756,7 @@
 					sdk_version: "current",
 				}`)
 
-	foo := ctx.ModuleForTests("foo", "android_common")
+	foo := ctx.ModuleForTests(t, "foo", "android_common")
 
 	outputs := fmt.Sprint(foo.AllOutputs())
 	if !strings.Contains(outputs, "foo-new.apk") {
@@ -4607,6 +4765,7 @@
 }
 
 func TestAppMinSdkVersionOverride(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		PrepareForTestWithJavaDefaultModules,
 	).RunTestWithBp(t, `
@@ -4623,8 +4782,8 @@
 			min_sdk_version: "33",
 		}
 	`)
-	foo := result.ModuleForTests("com.android.foo", "android_common").Rule("manifestFixer")
-	fooOverride := result.ModuleForTests("com.android.foo", "android_common_com.android.go.foo").Rule("manifestFixer")
+	foo := result.ModuleForTests(t, "com.android.foo", "android_common").Rule("manifestFixer")
+	fooOverride := result.ModuleForTests(t, "com.android.foo", "android_common_com.android.go.foo").Rule("manifestFixer")
 
 	android.AssertStringDoesContain(t,
 		"com.android.foo: expected manifest fixer to set minSdkVersion to T",
@@ -4640,6 +4799,7 @@
 }
 
 func TestNotApplyDefaultUpdatableModuleVersion(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		PrepareForTestWithJavaDefaultModules,
 	).RunTestWithBp(t, `
@@ -4650,7 +4810,7 @@
 			min_sdk_version: "31",
 		}
 	`)
-	foo := result.ModuleForTests("com.android.foo", "android_common").Rule("manifestFixer")
+	foo := result.ModuleForTests(t, "com.android.foo", "android_common").Rule("manifestFixer")
 	android.AssertStringDoesNotContain(t,
 		"com.android.foo: expected manifest fixer to not set override-placeholder-version",
 		foo.BuildParams.Args["args"],
@@ -4659,6 +4819,7 @@
 }
 
 func TestNotApplyOverrideApexManifestDefaultVersion(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		PrepareForTestWithJavaDefaultModules,
 		android.FixtureMergeEnv(map[string]string{
@@ -4672,10 +4833,209 @@
 			min_sdk_version: "31",
 		}
 	`)
-	foo := result.ModuleForTests("com.android.foo", "android_common").Rule("manifestFixer")
+	foo := result.ModuleForTests(t, "com.android.foo", "android_common").Rule("manifestFixer")
 	android.AssertStringDoesNotContain(t,
 		"com.android.foo: expected manifest fixer to not set override-placeholder-version",
 		foo.BuildParams.Args["args"],
 		"--override-placeholder-version",
 	)
 }
+
+func TestResourcesWithFlagDirectories(t *testing.T) {
+	t.Parallel()
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		android.FixtureMergeMockFs(android.MockFS{
+			"res/flag(test.package.flag1)/values/bools.xml":                          nil,
+			"res/flag(!test.package.flag2)/values/bools.xml":                         nil,
+			"res/flag(test.package.flag1)/values-config/strings_google_services.xml": nil,
+			"res/flags(test.package.flag1)/values/strings.xml":                       nil,
+		}),
+	).RunTestWithBp(t, `
+		android_library {
+			name: "foo",
+			srcs: ["a.java"],
+			use_resource_processor: true,
+			resource_dirs: [
+				"res",
+			],
+		}
+	`)
+	fooModule := result.ModuleForTests(t, "foo", "android_common")
+	compileOutputPaths := fooModule.Rule("aapt2Compile").Outputs.Strings()
+
+	android.AssertStringListContains(
+		t,
+		"Expected to generate flag path",
+		compileOutputPaths,
+		"out/soong/.intermediates/foo/android_common/aapt2/res/values_bools.(test.package.flag1).arsc.flat",
+	)
+	android.AssertStringListContains(
+		t,
+		"Expected to generate flag path with ! prefix in name",
+		compileOutputPaths,
+		"out/soong/.intermediates/foo/android_common/aapt2/res/values_bools.(!test.package.flag2).arsc.flat",
+	)
+	android.AssertStringListContains(
+		t,
+		"Expected to generate flag path with configs",
+		compileOutputPaths,
+		"out/soong/.intermediates/foo/android_common/aapt2/res/values-config_strings_google_services.(test.package.flag1).arsc.flat",
+	)
+	android.AssertStringListDoesNotContain(
+		t,
+		"Expected to not generate flag path with non-flag(flag_name) pattern",
+		compileOutputPaths,
+		"out/soong/.intermediates/foo/android_common/aapt2/res/values_strings.(test.package.flag1).arsc.flat",
+	)
+}
+
+func TestAutogeneratedStaticRro(t *testing.T) {
+	t.Parallel()
+	bp := `
+android_app {
+	name: "foo",
+	srcs: ["foo.java"],
+	platform_apis: true,
+}
+override_android_app {
+	name: "override_foo",
+	base: "foo",
+}
+`
+	testCases := []struct {
+		desc               string
+		preparer           android.FixturePreparer
+		overlayApkExpected bool
+	}{
+		{
+			desc:               "No DEVICE_PACKAGE_OVERLAYS, no overlay .apk file",
+			overlayApkExpected: false,
+		},
+		{
+			desc:               "DEVICE_PACKAGE_OVERLAYS exists, but the directory is empty",
+			overlayApkExpected: false,
+			preparer: android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+				variables.DeviceResourceOverlays = []string{"device/company/test_product"}
+			}),
+		},
+		{
+			desc:               "DEVICE_PACKAGE_OVERLAYS exists, directory is non-empty, but does not contain a matching resource dir",
+			overlayApkExpected: false,
+			preparer: android.GroupFixturePreparers(
+				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+					variables.DeviceResourceOverlays = []string{"device/company/test_product"}
+				}),
+				android.MockFS{
+					"res/foo.xml": nil,
+					"device/company/test_product/different_res/foo.xml": nil, // different dir
+				}.AddToFixture(),
+			),
+		},
+		{
+			desc:               "DEVICE_PACKAGE_OVERLAYS and the directory contain a matching resource dir",
+			overlayApkExpected: true,
+			preparer: android.GroupFixturePreparers(
+				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+					variables.DeviceResourceOverlays = []string{"device/company/test_product"}
+				}),
+				android.MockFS{
+					"res/foo.xml": nil,
+					"device/company/test_product/res/foo.xml": nil,
+				}.AddToFixture(),
+			),
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			t.Parallel()
+			result := android.GroupFixturePreparers(
+				PrepareForTestWithJavaDefaultModules,
+				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+					variables.EnforceRROTargets = []string{"*"}
+				}),
+				android.OptionalFixturePreparer(tc.preparer),
+			).RunTestWithBp(t, bp)
+			vendorOverlayApk := result.ModuleForTests(t, "foo__test_product__auto_generated_rro_vendor", "android_arm64_armv8-a").MaybeOutput("foo__test_product__auto_generated_rro_vendor.apk")
+			android.AssertBoolEquals(t, tc.desc, tc.overlayApkExpected, vendorOverlayApk.Rule != nil)
+			overrideVendorOverlayApk := result.ModuleForTests(t, "override_foo__test_product__auto_generated_rro_vendor", "android_arm64_armv8-a").MaybeOutput("override_foo__test_product__auto_generated_rro_vendor.apk")
+			android.AssertBoolEquals(t, tc.desc, tc.overlayApkExpected, overrideVendorOverlayApk.Rule != nil)
+		})
+	}
+}
+
+func TestNoAutogeneratedStaticRroForDisabledOverrideApps(t *testing.T) {
+	t.Parallel()
+	bp := `
+soong_config_module_type {
+	name: "my_custom_override_android_app",
+	module_type: "override_android_app",
+	config_namespace: "my_namespace",
+	value_variables: ["my_app_enabled"],
+	properties: ["enabled"],
+}
+soong_config_bool_variable {
+	name: "my_app_enabled",
+}
+android_app {
+	name: "foo",
+	srcs: ["foo.java"],
+	platform_apis: true,
+}
+my_custom_override_android_app {
+	name: "override_foo",
+	base: "foo",
+	soong_config_variables: {
+		my_app_enabled: {
+			enabled: true,
+			conditions_default: {
+				enabled: false
+			},
+		},
+	}
+}
+`
+	testCases := []struct {
+		desc               string
+		preparer           android.FixturePreparer
+		overlayApkExpected bool
+	}{
+		{
+			desc:               "my_app_enabled is empty",
+			overlayApkExpected: false,
+		},
+		{
+			desc:               "my_app_enabled is true",
+			overlayApkExpected: true,
+			preparer: android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+				variables.VendorVars = map[string]map[string]string{
+					"my_namespace": {
+						"my_app_enabled": "true",
+					},
+				}
+			}),
+		},
+	}
+	for _, tc := range testCases {
+		t.Run(tc.desc, func(t *testing.T) {
+			t.Parallel()
+			result := android.GroupFixturePreparers(
+				PrepareForTestWithJavaDefaultModules,
+				android.PrepareForTestWithSoongConfigModuleBuildComponents,
+				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+					variables.EnforceRROTargets = []string{"*"}
+				}),
+				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+					variables.DeviceResourceOverlays = []string{"device/company/test_product"}
+				}),
+				android.MockFS{
+					"res/foo.xml": nil,
+					"device/company/test_product/res/foo.xml": nil,
+				}.AddToFixture(),
+				android.OptionalFixturePreparer(tc.preparer),
+			).RunTestWithBp(t, bp)
+			overrideVendorOverlayApk := result.ModuleForTests(t, "override_foo__test_product__auto_generated_rro_vendor", "android_arm64_armv8-a").Module().(*AutogenRuntimeResourceOverlay)
+			android.AssertBoolEquals(t, tc.desc, tc.overlayApkExpected, overrideVendorOverlayApk.exportPackage != nil)
+		})
+	}
+}
diff --git a/java/base.go b/java/base.go
index 7a95735..7305548 100644
--- a/java/base.go
+++ b/java/base.go
@@ -24,6 +24,7 @@
 	"strings"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/depset"
 	"github.com/google/blueprint/pathtools"
 	"github.com/google/blueprint/proptools"
 
@@ -59,6 +60,9 @@
 	// This is most useful in the arch/multilib variants to remove non-common files
 	Exclude_srcs []string `android:"path,arch_variant"`
 
+	// list of Kotlin source files that should excluded from the list of common_srcs.
+	Exclude_common_srcs []string `android:"path,arch_variant"`
+
 	// list of directories containing Java resources
 	Java_resource_dirs []string `android:"arch_variant"`
 
@@ -66,17 +70,30 @@
 	Exclude_java_resource_dirs []string `android:"arch_variant"`
 
 	// list of files to use as Java resources
-	Java_resources []string `android:"path,arch_variant"`
+	Java_resources proptools.Configurable[[]string] `android:"path,arch_variant"`
 
 	// list of files that should be excluded from java_resources and java_resource_dirs
 	Exclude_java_resources []string `android:"path,arch_variant"`
 
+	// Same as java_resources, but modules added here will use the device variant. Can be useful
+	// for making a host test that tests the contents of a device built app.
+	Device_common_java_resources proptools.Configurable[[]string] `android:"path_device_common"`
+
+	// Same as java_resources, but modules added here will use the device's os variant and the
+	// device's first architecture variant. Can be useful for making a host test that tests the
+	// contents of a native device built app.
+	Device_first_java_resources proptools.Configurable[[]string] `android:"path_device_first"`
+
 	// list of module-specific flags that will be used for javac compiles
 	Javacflags []string `android:"arch_variant"`
 
 	// list of module-specific flags that will be used for kotlinc compiles
 	Kotlincflags []string `android:"arch_variant"`
 
+	// Kotlin language version to target. Currently only 1.9 and 2 are supported.
+	// See kotlinc's `-language-version` flag.
+	Kotlin_lang_version *string
+
 	// list of java libraries that will be in the classpath
 	Libs []string `android:"arch_variant"`
 
@@ -96,6 +113,10 @@
 	// if not blank, used as prefix to generate repackage rule
 	Jarjar_prefix *string
 
+	// Number of shards for jarjar. It needs to be an integer represented as a string.
+	// TODO(b/383559945) change it to int, once Configurable supports the type.
+	Jarjar_shards proptools.Configurable[string]
+
 	// If not blank, set the java version passed to javac as -source and -target
 	Java_version *string
 
@@ -113,6 +134,9 @@
 	// List of modules to use as annotation processors
 	Plugins []string
 
+	// List of modules to use as kotlin plugin
+	Kotlin_plugins []string
+
 	// List of modules to export to libraries that directly depend on this library as annotation
 	// processors.  Note that if the plugins set generates_api: true this will disable the turbine
 	// optimization on modules that depend on this module, which will reduce parallelism and cause
@@ -349,13 +373,13 @@
 	e.initSdkLibraryComponent(module)
 }
 
-// Module/Import's DepIsInSameApex(...) delegates to this method.
+// Module/Import's OutgoingDepIsInSameApex(...) delegates to this method.
 //
-// This cannot implement DepIsInSameApex(...) directly as that leads to ambiguity with
+// This cannot implement OutgoingDepIsInSameApex(...) directly as that leads to ambiguity with
 // the one provided by ApexModuleBase.
-func (e *embeddableInModuleAndImport) depIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
+func depIsInSameApex(tag blueprint.DependencyTag) bool {
 	// dependencies other than the static linkage are all considered crossing APEX boundary
-	if staticLibTag == ctx.OtherModuleDependencyTag(dep) {
+	if tag == staticLibTag {
 		return true
 	}
 	return false
@@ -471,7 +495,7 @@
 	srcJarDeps android.Paths
 
 	// the source files of this module and all its static dependencies
-	transitiveSrcFiles *android.DepSet[android.Path]
+	transitiveSrcFiles depset.DepSet[android.Path]
 
 	// jar file containing implementation classes and resources including static library
 	// dependencies
@@ -599,21 +623,24 @@
 	return proptools.Bool(j.properties.Is_stubs_module)
 }
 
-func (j *Module) CheckStableSdkVersion(ctx android.BaseModuleContext) error {
-	sdkVersion := j.SdkVersion(ctx)
-	if sdkVersion.Stable() {
-		return nil
-	}
-	if sdkVersion.Kind == android.SdkCorePlatform {
-		if useLegacyCorePlatformApi(ctx, j.BaseModuleName()) {
-			return fmt.Errorf("non stable SDK %v - uses legacy core platform", sdkVersion)
-		} else {
-			// Treat stable core platform as stable.
+func CheckStableSdkVersion(ctx android.BaseModuleContext, module android.ModuleProxy) error {
+	if info, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider); ok {
+		if info.SdkVersion.Stable() {
 			return nil
 		}
-	} else {
-		return fmt.Errorf("non stable SDK %v", sdkVersion)
+		if info.SdkVersion.Kind == android.SdkCorePlatform {
+			if useLegacyCorePlatformApi(ctx, android.OtherModuleProviderOrDefault(ctx, module, android.CommonModuleInfoKey).BaseModuleName) {
+				return fmt.Errorf("non stable SDK %v - uses legacy core platform", info.SdkVersion)
+			} else {
+				// Treat stable core platform as stable.
+				return nil
+			}
+		} else {
+			return fmt.Errorf("non stable SDK %v", info.SdkVersion)
+		}
 	}
+
+	return nil
 }
 
 // checkSdkVersions enforces restrictions around SDK dependencies.
@@ -629,14 +656,17 @@
 
 	// Make sure this module doesn't statically link to modules with lower-ranked SDK link type.
 	// See rank() for details.
-	ctx.VisitDirectDeps(func(module android.Module) {
+	ctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 		tag := ctx.OtherModuleDependencyTag(module)
-		switch module.(type) {
-		// TODO(satayev): cover other types as well, e.g. imports
-		case *Library, *AndroidLibrary:
+		_, isJavaLibrary := android.OtherModuleProvider(ctx, module, JavaLibraryInfoProvider)
+		_, isAndroidLibrary := android.OtherModuleProvider(ctx, module, AndroidLibraryInfoProvider)
+		_, isJavaAconfigLibrary := android.OtherModuleProvider(ctx, module, android.CodegenInfoProvider)
+		// Exclude java_aconfig_library modules to maintain consistency with existing behavior.
+		if (isJavaLibrary && !isJavaAconfigLibrary) || isAndroidLibrary {
+			// TODO(satayev): cover other types as well, e.g. imports
 			switch tag {
 			case bootClasspathTag, sdkLibTag, libTag, staticLibTag, java9LibTag:
-				j.checkSdkLinkType(ctx, module.(moduleWithSdkDep), tag.(dependencyTag))
+				j.checkSdkLinkType(ctx, module)
 			}
 		}
 	})
@@ -701,10 +731,10 @@
 
 // helper method for java modules to set OutputFilesProvider
 func setOutputFiles(ctx android.ModuleContext, m Module) {
-	ctx.SetOutputFiles(append(android.Paths{m.outputFile}, m.extraOutputFiles...), "")
-	ctx.SetOutputFiles(android.Paths{m.outputFile}, android.DefaultDistTag)
-	ctx.SetOutputFiles(android.Paths{m.implementationAndResourcesJar}, ".jar")
-	ctx.SetOutputFiles(android.Paths{m.headerJarFile}, ".hjar")
+	ctx.SetOutputFiles(append(android.PathsIfNonNil(m.outputFile), m.extraOutputFiles...), "")
+	ctx.SetOutputFiles(android.PathsIfNonNil(m.outputFile), android.DefaultDistTag)
+	ctx.SetOutputFiles(android.PathsIfNonNil(m.implementationAndResourcesJar), ".jar")
+	ctx.SetOutputFiles(android.PathsIfNonNil(m.headerJarFile), ".hjar")
 	if m.dexer.proguardDictionary.Valid() {
 		ctx.SetOutputFiles(android.Paths{m.dexer.proguardDictionary.Path()}, ".proguard_map")
 	}
@@ -753,7 +783,8 @@
 	apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
 	isJacocoAgent := ctx.ModuleName() == "jacocoagent"
 
-	if j.DirectlyInAnyApex() && !isJacocoAgent && !apexInfo.IsForPlatform() {
+	compileDex := Bool(j.dexProperties.Compile_dex) || Bool(j.properties.Installable)
+	if compileDex && !isJacocoAgent && !apexInfo.IsForPlatform() {
 		if !inList(ctx.ModuleName(), config.InstrumentFrameworkModules) {
 			return true
 		} else if ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_FRAMEWORK") {
@@ -818,13 +849,18 @@
 }
 
 func (j *Module) AvailableFor(what string) bool {
-	if what == android.AvailableToPlatform && Bool(j.deviceProperties.Hostdex) {
+	return android.CheckAvailableForApex(what, j.ApexAvailableFor())
+}
+
+func (j *Module) ApexAvailableFor() []string {
+	list := j.ApexModuleBase.ApexAvailable()
+	if Bool(j.deviceProperties.Hostdex) {
 		// Exception: for hostdex: true libraries, the platform variant is created
 		// even if it's not marked as available to platform. In that case, the platform
 		// variant is used only for the hostdex and not installed to the device.
-		return true
+		list = append(list, android.AvailableToPlatform)
 	}
-	return j.ApexModuleBase.AvailableFor(what)
+	return android.FirstUniqueStrings(list)
 }
 
 func (j *Module) staticLibs(ctx android.BaseModuleContext) []string {
@@ -862,7 +898,7 @@
 					// explicitly listed in the optional_uses_libs property.
 					tag := usesLibReqTag
 					if android.InList(*lib, dexpreopt.OptionalCompatUsesLibs) ||
-						android.InList(*lib, j.usesLibrary.usesLibraryProperties.Optional_uses_libs) {
+						android.InList(*lib, j.usesLibrary.usesLibraryProperties.Optional_uses_libs.GetOrDefault(ctx, nil)) {
 						tag = usesLibOptTag
 					}
 					ctx.AddVariationDependencies(nil, tag, *lib)
@@ -872,6 +908,7 @@
 	}
 
 	ctx.AddFarVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), pluginTag, j.properties.Plugins...)
+	ctx.AddFarVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), kotlinPluginTag, j.properties.Kotlin_plugins...)
 	ctx.AddFarVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), errorpronePluginTag, j.properties.Errorprone.Extra_check_modules...)
 	ctx.AddFarVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), exportedPluginTag, j.properties.Exported_plugins...)
 
@@ -904,7 +941,7 @@
 
 	if j.useCompose(ctx) {
 		ctx.AddVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(), kotlinPluginTag,
-			"androidx.compose.compiler_compiler-hosted")
+			"kotlin-compose-compiler-plugin")
 	}
 }
 
@@ -1123,7 +1160,7 @@
 	j.properties.Generated_srcjars = append(j.properties.Generated_srcjars, path)
 }
 
-func (j *Module) compile(ctx android.ModuleContext, extraSrcJars, extraClasspathJars, extraCombinedJars, extraDepCombinedJars android.Paths) {
+func (j *Module) compile(ctx android.ModuleContext, extraSrcJars, extraClasspathJars, extraCombinedJars, extraDepCombinedJars android.Paths) *JavaInfo {
 	// Auto-propagating jarjar rules
 	jarjarProviderData := j.collectJarJarRules(ctx)
 	if jarjarProviderData != nil {
@@ -1164,7 +1201,7 @@
 		flags = protoFlags(ctx, &j.properties, &j.protoProperties, flags)
 	}
 
-	kotlinCommonSrcFiles := android.PathsForModuleSrcExcludes(ctx, j.properties.Common_srcs, nil)
+	kotlinCommonSrcFiles := android.PathsForModuleSrcExcludes(ctx, j.properties.Common_srcs, j.properties.Exclude_common_srcs)
 	if len(kotlinCommonSrcFiles.FilterOutByExt(".kt")) > 0 {
 		ctx.PropertyErrorf("common_srcs", "common_srcs must be .kt files")
 	}
@@ -1211,7 +1248,6 @@
 	uniqueSrcFiles = append(uniqueSrcFiles, uniqueJavaFiles...)
 	uniqueSrcFiles = append(uniqueSrcFiles, uniqueKtFiles...)
 	j.uniqueSrcFiles = uniqueSrcFiles
-	android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: uniqueSrcFiles.Strings()})
 
 	// We don't currently run annotation processors in turbine, which means we can't use turbine
 	// generated header jars when an annotation processor that generates API is enabled.  One
@@ -1247,7 +1283,7 @@
 		localHeaderJars, combinedHeaderJarFile := j.compileJavaHeader(ctx, uniqueJavaFiles, srcJars, deps, flags, jarName,
 			extraCombinedJars)
 
-		combinedHeaderJarFile, jarjared := j.jarjarIfNecessary(ctx, combinedHeaderJarFile, jarName, "turbine")
+		combinedHeaderJarFile, jarjared := j.jarjarIfNecessary(ctx, combinedHeaderJarFile, jarName, "turbine", false)
 		if jarjared {
 			localHeaderJars = android.Paths{combinedHeaderJarFile}
 			transitiveStaticLibsHeaderJars = nil
@@ -1258,7 +1294,7 @@
 			transitiveStaticLibsHeaderJars = nil
 		}
 		if ctx.Failed() {
-			return
+			return nil
 		}
 		j.headerJarFile = combinedHeaderJarFile
 
@@ -1273,10 +1309,11 @@
 			ctx.CheckbuildFile(j.headerJarFile)
 		}
 
-		android.SetProvider(ctx, JavaInfoProvider, &JavaInfo{
+		j.outputFile = j.headerJarFile
+		return &JavaInfo{
 			HeaderJars:                          android.PathsIfNonNil(j.headerJarFile),
 			LocalHeaderJars:                     localHeaderJars,
-			TransitiveStaticLibsHeaderJars:      android.NewDepSet(android.PREORDER, localHeaderJars, transitiveStaticLibsHeaderJars),
+			TransitiveStaticLibsHeaderJars:      depset.New(depset.PREORDER, localHeaderJars, transitiveStaticLibsHeaderJars),
 			TransitiveLibsHeaderJarsForR8:       j.transitiveLibsHeaderJarsForR8,
 			TransitiveStaticLibsHeaderJarsForR8: j.transitiveStaticLibsHeaderJarsForR8,
 			AidlIncludeDirs:                     j.exportAidlIncludeDirs,
@@ -1285,10 +1322,8 @@
 			ExportedPluginDisableTurbine:        j.exportedDisableTurbine,
 			StubsLinkType:                       j.stubsLinkType,
 			AconfigIntermediateCacheOutputPaths: deps.aconfigProtoFiles,
-		})
-
-		j.outputFile = j.headerJarFile
-		return
+			SdkVersion:                          j.SdkVersion(ctx),
+		}
 	}
 
 	if srcFiles.HasExt(".kt") {
@@ -1301,6 +1336,26 @@
 		kotlincFlags := j.properties.Kotlincflags
 		CheckKotlincFlags(ctx, kotlincFlags)
 
+		// Available kotlin versions can be found at
+		// https://github.com/JetBrains/kotlin/blob/master/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt#L560
+		// in the `LanguageVersion` class.
+		// For now, avoid targeting language versions directly, as we'd like to kee our source
+		// code version aligned as much as possible. Ideally, after defaulting to "2", we
+		// can remove the "1.9" option entirely, or at least make it emit a warning.
+		kotlin_default_lang_version := "1.9"
+		if build_flag_lang_version, ok := ctx.Config().GetBuildFlag("RELEASE_KOTLIN_LANG_VERSION"); ok {
+			kotlin_default_lang_version = build_flag_lang_version
+		}
+		kotlin_lang_version := proptools.StringDefault(j.properties.Kotlin_lang_version, kotlin_default_lang_version)
+		switch kotlin_lang_version {
+		case "1.9":
+			kotlincFlags = append(kotlincFlags, "-language-version 1.9")
+		case "2":
+			kotlincFlags = append(kotlincFlags, "-Xsuppress-version-warnings", "-Xconsistent-data-class-copy-visibility")
+		default:
+			ctx.PropertyErrorf("kotlin_lang_version", "Must be one of `1.9` or `2`")
+		}
+
 		// Workaround for KT-46512
 		kotlincFlags = append(kotlincFlags, "-Xsam-conversions=class")
 
@@ -1345,7 +1400,7 @@
 		kotlinHeaderJar := android.PathForModuleOut(ctx, "kotlin_headers", jarName)
 		j.kotlinCompile(ctx, kotlinJar, kotlinHeaderJar, uniqueSrcFiles, kotlinCommonSrcFiles, srcJars, flags)
 		if ctx.Failed() {
-			return
+			return nil
 		}
 
 		kotlinJarPath, _ := j.repackageFlagsIfNecessary(ctx, kotlinJar, jarName, "kotlinc")
@@ -1382,7 +1437,7 @@
 		shardingHeaderJars = localHeaderJars
 
 		var jarjared bool
-		j.headerJarFile, jarjared = j.jarjarIfNecessary(ctx, combinedHeaderJarFile, jarName, "turbine")
+		j.headerJarFile, jarjared = j.jarjarIfNecessary(ctx, combinedHeaderJarFile, jarName, "turbine", false)
 		if jarjared {
 			// jarjar modifies transitive static dependencies, use the combined header jar and drop the transitive
 			// static libs header jars.
@@ -1415,20 +1470,27 @@
 			// build.
 			flags = enableErrorproneFlags(flags)
 		} else if hasErrorproneableFiles && ctx.Config().RunErrorProne() && j.properties.Errorprone.Enabled == nil {
-			// Otherwise, if the RUN_ERROR_PRONE environment variable is set, create
-			// a new jar file just for compiling with the errorprone compiler to.
-			// This is because we don't want to cause the java files to get completely
-			// rebuilt every time the state of the RUN_ERROR_PRONE variable changes.
-			// We also don't want to run this if errorprone is enabled by default for
-			// this module, or else we could have duplicated errorprone messages.
-			errorproneFlags := enableErrorproneFlags(flags)
-			errorprone := android.PathForModuleOut(ctx, "errorprone", jarName)
-			errorproneAnnoSrcJar := android.PathForModuleOut(ctx, "errorprone", "anno.srcjar")
+			if ctx.Config().RunErrorProneInline() {
+				// On CI, we're not going to toggle back/forth between errorprone and non-errorprone
+				// builds, so it's faster if we don't compile the module twice and instead always
+				// compile the module with errorprone.
+				flags = enableErrorproneFlags(flags)
+			} else {
+				// Otherwise, if the RUN_ERROR_PRONE environment variable is set, create
+				// a new jar file just for compiling with the errorprone compiler to.
+				// This is because we don't want to cause the java files to get completely
+				// rebuilt every time the state of the RUN_ERROR_PRONE variable changes.
+				// We also don't want to run this if errorprone is enabled by default for
+				// this module, or else we could have duplicated errorprone messages.
+				errorproneFlags := enableErrorproneFlags(flags)
+				errorprone := android.PathForModuleOut(ctx, "errorprone", jarName)
+				errorproneAnnoSrcJar := android.PathForModuleOut(ctx, "errorprone", "anno.srcjar")
 
-			transformJavaToClasses(ctx, errorprone, -1, uniqueJavaFiles, srcJars, errorproneAnnoSrcJar, errorproneFlags, nil,
-				"errorprone", "errorprone")
+				transformJavaToClasses(ctx, errorprone, -1, uniqueJavaFiles, srcJars, errorproneAnnoSrcJar, errorproneFlags, nil,
+					"errorprone", "errorprone")
 
-			extraJarDeps = append(extraJarDeps, errorprone)
+				extraJarDeps = append(extraJarDeps, errorprone)
+			}
 		}
 
 		if enableSharding {
@@ -1465,7 +1527,7 @@
 			localImplementationJars = append(localImplementationJars, classes)
 		}
 		if ctx.Failed() {
-			return
+			return nil
 		}
 	}
 
@@ -1481,7 +1543,11 @@
 
 	dirArgs, dirDeps := ResourceDirsToJarArgs(ctx, j.properties.Java_resource_dirs,
 		j.properties.Exclude_java_resource_dirs, j.properties.Exclude_java_resources)
-	fileArgs, fileDeps := ResourceFilesToJarArgs(ctx, j.properties.Java_resources, j.properties.Exclude_java_resources)
+	fileArgs, fileDeps := ResourceFilesToJarArgs(ctx, j.properties.Java_resources.GetOrDefault(ctx, nil), j.properties.Exclude_java_resources)
+	fileArgs2, fileDeps2 := ResourceFilesToJarArgs(ctx, j.properties.Device_common_java_resources.GetOrDefault(ctx, nil), nil)
+	fileArgs3, fileDeps3 := ResourceFilesToJarArgs(ctx, j.properties.Device_first_java_resources.GetOrDefault(ctx, nil), nil)
+	fileArgs = slices.Concat(fileArgs, fileArgs2, fileArgs3)
+	fileDeps = slices.Concat(fileDeps, fileDeps2, fileDeps3)
 	extraArgs, extraDeps := resourcePathsToJarArgs(j.extraResources), j.extraResources
 
 	var resArgs []string
@@ -1501,7 +1567,7 @@
 		resourceJar := android.PathForModuleOut(ctx, "res", jarName)
 		TransformResourcesToJar(ctx, resourceJar, resArgs, resDeps)
 		if ctx.Failed() {
-			return
+			return nil
 		}
 		localResourceJars = append(localResourceJars, resourceJar)
 	}
@@ -1535,7 +1601,7 @@
 		localResourceJars = append(localResourceJars, servicesJar)
 	}
 
-	completeStaticLibsResourceJars := android.NewDepSet(android.PREORDER, localResourceJars, deps.transitiveStaticLibsResourceJars)
+	completeStaticLibsResourceJars := depset.New(depset.PREORDER, localResourceJars, deps.transitiveStaticLibsResourceJars)
 
 	var combinedResourceJar android.Path
 	var resourceJars android.Paths
@@ -1562,7 +1628,7 @@
 	// classes.jar. If there is only one input jar this step will be skipped.
 	var outputFile android.Path
 
-	completeStaticLibsImplementationJars := android.NewDepSet(android.PREORDER, localImplementationJars, deps.transitiveStaticLibsImplementationJars)
+	completeStaticLibsImplementationJars := depset.New(depset.PREORDER, localImplementationJars, deps.transitiveStaticLibsImplementationJars)
 
 	var jars android.Paths
 	if ctx.Config().UseTransitiveJarsInClasspath() {
@@ -1592,7 +1658,7 @@
 				Input:  jars[0],
 				Output: copiedJar,
 			})
-			completeStaticLibsImplementationJars = android.NewDepSet(android.PREORDER, android.Paths{copiedJar}, nil)
+			completeStaticLibsImplementationJars = depset.New(depset.PREORDER, android.Paths{copiedJar}, nil)
 			outputFile = copiedJar
 		} else {
 			outputFile = jars[0]
@@ -1605,25 +1671,25 @@
 	}
 
 	// jarjar implementation jar if necessary
-	jarjarFile, jarjarred := j.jarjarIfNecessary(ctx, outputFile, jarName, "")
+	jarjarFile, jarjarred := j.jarjarIfNecessary(ctx, outputFile, jarName, "", true)
 	if jarjarred {
 		localImplementationJars = android.Paths{jarjarFile}
-		completeStaticLibsImplementationJars = android.NewDepSet(android.PREORDER, localImplementationJars, nil)
+		completeStaticLibsImplementationJars = depset.New(depset.PREORDER, localImplementationJars, nil)
 	}
 	outputFile = jarjarFile
 
 	// jarjar resource jar if necessary
 	if combinedResourceJar != nil {
-		resourceJarJarFile, jarjarred := j.jarjarIfNecessary(ctx, combinedResourceJar, jarName, "resource")
+		resourceJarJarFile, jarjarred := j.jarjarIfNecessary(ctx, combinedResourceJar, jarName, "resource", false)
 		combinedResourceJar = resourceJarJarFile
 		if jarjarred {
 			localResourceJars = android.Paths{resourceJarJarFile}
-			completeStaticLibsResourceJars = android.NewDepSet(android.PREORDER, localResourceJars, nil)
+			completeStaticLibsResourceJars = depset.New(depset.PREORDER, localResourceJars, nil)
 		}
 	}
 
 	if ctx.Failed() {
-		return
+		return nil
 	}
 
 	if j.ravenizer.enabled {
@@ -1636,14 +1702,14 @@
 		TransformRavenizer(ctx, ravenizerOutput, ravenizerInput, ravenizerArgs)
 		outputFile = ravenizerOutput
 		localImplementationJars = android.Paths{ravenizerOutput}
-		completeStaticLibsImplementationJars = android.NewDepSet(android.PREORDER, localImplementationJars, nil)
+		completeStaticLibsImplementationJars = depset.New(depset.PREORDER, localImplementationJars, nil)
 		if combinedResourceJar != nil {
 			ravenizerInput = combinedResourceJar
 			ravenizerOutput = android.PathForModuleOut(ctx, "ravenizer", "resources", jarName)
 			TransformRavenizer(ctx, ravenizerOutput, ravenizerInput, ravenizerArgs)
 			combinedResourceJar = ravenizerOutput
 			localResourceJars = android.Paths{ravenizerOutput}
-			completeStaticLibsResourceJars = android.NewDepSet(android.PREORDER, localResourceJars, nil)
+			completeStaticLibsResourceJars = depset.New(depset.PREORDER, localResourceJars, nil)
 		}
 	}
 
@@ -1658,7 +1724,7 @@
 		})
 		outputFile = apiMapperFile
 		localImplementationJars = android.Paths{apiMapperFile}
-		completeStaticLibsImplementationJars = android.NewDepSet(android.PREORDER, localImplementationJars, nil)
+		completeStaticLibsImplementationJars = depset.New(depset.PREORDER, localImplementationJars, nil)
 	}
 
 	// Check package restrictions if necessary.
@@ -1681,13 +1747,13 @@
 		})
 		outputFile = packageCheckOutputFile
 		localImplementationJars = android.Paths{packageCheckOutputFile}
-		completeStaticLibsImplementationJars = android.NewDepSet(android.PREORDER, localImplementationJars, nil)
+		completeStaticLibsImplementationJars = depset.New(depset.PREORDER, localImplementationJars, nil)
 
 		// Check packages and create a timestamp file when complete.
 		CheckJarPackages(ctx, pkgckFile, outputFile, j.properties.Permitted_packages)
 
 		if ctx.Failed() {
-			return
+			return nil
 		}
 	}
 
@@ -1712,14 +1778,19 @@
 	// enforce syntax check to jacoco filters for any build (http://b/183622051)
 	specs := j.jacocoModuleToZipCommand(ctx)
 	if ctx.Failed() {
-		return
+		return nil
 	}
 
 	completeStaticLibsImplementationJarsToCombine := completeStaticLibsImplementationJars
 
-	if j.shouldInstrument(ctx) {
+	apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
+
+	// Enable dex compilation for the APEX variants, unless it is disabled explicitly
+	compileDex := Bool(j.dexProperties.Compile_dex) || Bool(j.properties.Installable)
+
+	if j.shouldInstrument(ctx) && (!ctx.Device() || compileDex) {
 		instrumentedOutputFile := j.instrument(ctx, flags, outputFile, jarName, specs)
-		completeStaticLibsImplementationJarsToCombine = android.NewDepSet(android.PREORDER, android.Paths{instrumentedOutputFile}, nil)
+		completeStaticLibsImplementationJarsToCombine = depset.New(depset.PREORDER, android.Paths{instrumentedOutputFile}, nil)
 		outputFile = instrumentedOutputFile
 	}
 
@@ -1746,19 +1817,7 @@
 
 	j.implementationAndResourcesJar = outputFile
 
-	// Enable dex compilation for the APEX variants, unless it is disabled explicitly
-	compileDex := j.dexProperties.Compile_dex
-	apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
-	if j.DirectlyInAnyApex() && !apexInfo.IsForPlatform() {
-		if compileDex == nil {
-			compileDex = proptools.BoolPtr(true)
-		}
-		if j.deviceProperties.Hostdex == nil {
-			j.deviceProperties.Hostdex = proptools.BoolPtr(true)
-		}
-	}
-
-	if ctx.Device() && (Bool(j.properties.Installable) || Bool(compileDex)) {
+	if ctx.Device() && compileDex {
 		if j.hasCode(ctx) {
 			if j.shouldInstrumentStatic(ctx) {
 				j.dexer.extraProguardFlagsFiles = append(j.dexer.extraProguardFlagsFiles,
@@ -1787,7 +1846,7 @@
 			}
 			dexOutputFile, dexArtProfileOutput := j.dexer.compileDex(ctx, params)
 			if ctx.Failed() {
-				return
+				return nil
 			}
 
 			// If r8/d8 provides a profile that matches the optimized dex, use that for dexpreopt.
@@ -1846,7 +1905,7 @@
 		}
 
 		if ctx.Failed() {
-			return
+			return nil
 		}
 	}
 
@@ -1893,12 +1952,15 @@
 		ctx.CheckbuildFile(j.headerJarFile)
 	}
 
-	android.SetProvider(ctx, JavaInfoProvider, &JavaInfo{
+	// Save the output file with no relative path so that it doesn't end up in a subdirectory when used as a resource
+	j.outputFile = outputFile.WithoutRel()
+
+	return &JavaInfo{
 		HeaderJars:           android.PathsIfNonNil(j.headerJarFile),
 		RepackagedHeaderJars: android.PathsIfNonNil(repackagedHeaderJarFile),
 
 		LocalHeaderJars:                        localHeaderJars,
-		TransitiveStaticLibsHeaderJars:         android.NewDepSet(android.PREORDER, localHeaderJars, transitiveStaticLibsHeaderJars),
+		TransitiveStaticLibsHeaderJars:         depset.New(depset.PREORDER, localHeaderJars, transitiveStaticLibsHeaderJars),
 		TransitiveStaticLibsImplementationJars: completeStaticLibsImplementationJars,
 		TransitiveStaticLibsResourceJars:       completeStaticLibsResourceJars,
 
@@ -1917,27 +1979,24 @@
 		JacocoReportClassesFile:             j.jacocoReportClassesFile,
 		StubsLinkType:                       j.stubsLinkType,
 		AconfigIntermediateCacheOutputPaths: j.aconfigCacheFiles,
-	})
-
-	// Save the output file with no relative path so that it doesn't end up in a subdirectory when used as a resource
-	j.outputFile = outputFile.WithoutRel()
+		SdkVersion:                          j.SdkVersion(ctx),
+		OutputFile:                          j.outputFile,
+	}
 }
 
 func (j *Module) useCompose(ctx android.BaseModuleContext) bool {
 	return android.InList("androidx.compose.runtime_runtime", j.staticLibs(ctx))
 }
 
-func collectDepProguardSpecInfo(ctx android.ModuleContext) (transitiveProguardFlags, transitiveUnconditionalExportedFlags []*android.DepSet[android.Path]) {
-	ctx.VisitDirectDeps(func(m android.Module) {
+func collectDepProguardSpecInfo(ctx android.ModuleContext) (transitiveProguardFlags, transitiveUnconditionalExportedFlags []depset.DepSet[android.Path]) {
+	ctx.VisitDirectDepsProxy(func(m android.ModuleProxy) {
 		depProguardInfo, _ := android.OtherModuleProvider(ctx, m, ProguardSpecInfoProvider)
 		depTag := ctx.OtherModuleDependencyTag(m)
 
-		if depProguardInfo.UnconditionallyExportedProguardFlags != nil {
-			transitiveUnconditionalExportedFlags = append(transitiveUnconditionalExportedFlags, depProguardInfo.UnconditionallyExportedProguardFlags)
-			transitiveProguardFlags = append(transitiveProguardFlags, depProguardInfo.UnconditionallyExportedProguardFlags)
-		}
+		transitiveUnconditionalExportedFlags = append(transitiveUnconditionalExportedFlags, depProguardInfo.UnconditionallyExportedProguardFlags)
+		transitiveProguardFlags = append(transitiveProguardFlags, depProguardInfo.UnconditionallyExportedProguardFlags)
 
-		if depTag == staticLibTag && depProguardInfo.ProguardFlagsFiles != nil {
+		if depTag == staticLibTag {
 			transitiveProguardFlags = append(transitiveProguardFlags, depProguardInfo.ProguardFlagsFiles)
 		}
 	})
@@ -1959,13 +2018,13 @@
 
 	return ProguardSpecInfo{
 		Export_proguard_flags_files: exportUnconditionally,
-		ProguardFlagsFiles: android.NewDepSet[android.Path](
-			android.POSTORDER,
+		ProguardFlagsFiles: depset.New[android.Path](
+			depset.POSTORDER,
 			proguardFlagsForThisModule,
 			transitiveProguardFlags,
 		),
-		UnconditionallyExportedProguardFlags: android.NewDepSet[android.Path](
-			android.POSTORDER,
+		UnconditionallyExportedProguardFlags: depset.New[android.Path](
+			depset.POSTORDER,
 			directUnconditionalExportedFlags,
 			transitiveUnconditionalExportedFlags,
 		),
@@ -2026,7 +2085,9 @@
 		} else if strings.HasPrefix(flag, "-Xintellij-plugin-root") {
 			ctx.PropertyErrorf("kotlincflags",
 				"Bad flag: `%s`, only use internal compiler for consistency.", flag)
-		} else if inList(flag, config.KotlincIllegalFlags) {
+		} else if slices.ContainsFunc(config.KotlincIllegalFlags, func(f string) bool {
+			return strings.HasPrefix(flag, f)
+		}) {
 			ctx.PropertyErrorf("kotlincflags", "Flag `%s` already used by build system", flag)
 		} else if flag == "-include-runtime" {
 			ctx.PropertyErrorf("kotlincflags", "Bad flag: `%s`, do not include runtime.", flag)
@@ -2057,7 +2118,7 @@
 	// one input jar this step will be skipped.
 	var jars android.Paths
 	if ctx.Config().UseTransitiveJarsInClasspath() {
-		depSet := android.NewDepSet(android.PREORDER, localHeaderJars, deps.transitiveStaticLibsHeaderJars)
+		depSet := depset.New(depset.PREORDER, localHeaderJars, deps.transitiveStaticLibsHeaderJars)
 		jars = depSet.ToList()
 	} else {
 		jars = append(slices.Clone(localHeaderJars), deps.staticHeaderJars...)
@@ -2087,9 +2148,9 @@
 
 type providesTransitiveHeaderJarsForR8 struct {
 	// set of header jars for all transitive libs deps
-	transitiveLibsHeaderJarsForR8 *android.DepSet[android.Path]
+	transitiveLibsHeaderJarsForR8 depset.DepSet[android.Path]
 	// set of header jars for all transitive static libs deps
-	transitiveStaticLibsHeaderJarsForR8 *android.DepSet[android.Path]
+	transitiveStaticLibsHeaderJarsForR8 depset.DepSet[android.Path]
 }
 
 // collectTransitiveHeaderJarsForR8 visits direct dependencies and collects all transitive libs and static_libs
@@ -2099,9 +2160,9 @@
 func (j *providesTransitiveHeaderJarsForR8) collectTransitiveHeaderJarsForR8(ctx android.ModuleContext) {
 	directLibs := android.Paths{}
 	directStaticLibs := android.Paths{}
-	transitiveLibs := []*android.DepSet[android.Path]{}
-	transitiveStaticLibs := []*android.DepSet[android.Path]{}
-	ctx.VisitDirectDeps(func(module android.Module) {
+	transitiveLibs := []depset.DepSet[android.Path]{}
+	transitiveStaticLibs := []depset.DepSet[android.Path]{}
+	ctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 		// don't add deps of the prebuilt version of the same library
 		if ctx.ModuleName() == android.RemoveOptionalPrebuiltPrefix(module.Name()) {
 			return
@@ -2119,17 +2180,12 @@
 				return
 			}
 
-			if dep.TransitiveLibsHeaderJarsForR8 != nil {
-				transitiveLibs = append(transitiveLibs, dep.TransitiveLibsHeaderJarsForR8)
-			}
-			if dep.TransitiveStaticLibsHeaderJarsForR8 != nil {
-				transitiveStaticLibs = append(transitiveStaticLibs, dep.TransitiveStaticLibsHeaderJarsForR8)
-			}
-
+			transitiveLibs = append(transitiveLibs, dep.TransitiveLibsHeaderJarsForR8)
+			transitiveStaticLibs = append(transitiveStaticLibs, dep.TransitiveStaticLibsHeaderJarsForR8)
 		}
 	})
-	j.transitiveLibsHeaderJarsForR8 = android.NewDepSet(android.POSTORDER, directLibs, transitiveLibs)
-	j.transitiveStaticLibsHeaderJarsForR8 = android.NewDepSet(android.POSTORDER, directStaticLibs, transitiveStaticLibs)
+	j.transitiveLibsHeaderJarsForR8 = depset.New(depset.POSTORDER, directLibs, transitiveLibs)
+	j.transitiveStaticLibsHeaderJarsForR8 = depset.New(depset.POSTORDER, directStaticLibs, transitiveStaticLibs)
 }
 
 func (j *Module) HeaderJars() android.Paths {
@@ -2172,16 +2228,16 @@
 
 // Collect information for opening IDE project files in java/jdeps.go.
 func (j *Module) IDEInfo(ctx android.BaseModuleContext, dpInfo *android.IdeInfo) {
-	// jarjar rules will repackage the sources. To prevent misleading results, IdeInfo should contain the
-	// repackaged jar instead of the input sources.
 	if j.expandJarjarRules != nil {
 		dpInfo.Jarjar_rules = append(dpInfo.Jarjar_rules, j.expandJarjarRules.String())
-		dpInfo.Jars = append(dpInfo.Jars, j.headerJarFile.String())
-	} else {
-		dpInfo.Srcs = append(dpInfo.Srcs, j.expandIDEInfoCompiledSrcs...)
-		dpInfo.SrcJars = append(dpInfo.SrcJars, j.compiledSrcJars.Strings()...)
-		dpInfo.SrcJars = append(dpInfo.SrcJars, j.annoSrcJars.Strings()...)
 	}
+	if j.headerJarFile != nil {
+		// Add the header jar so that the rdeps can be resolved to the repackaged classes.
+		dpInfo.Jars = append(dpInfo.Jars, j.headerJarFile.String())
+	}
+	dpInfo.Srcs = append(dpInfo.Srcs, j.expandIDEInfoCompiledSrcs...)
+	dpInfo.SrcJars = append(dpInfo.SrcJars, j.compiledSrcJars.Strings()...)
+	dpInfo.SrcJars = append(dpInfo.SrcJars, j.annoSrcJars.Strings()...)
 	dpInfo.Deps = append(dpInfo.Deps, j.CompilerDeps()...)
 	dpInfo.Aidl_include_dirs = append(dpInfo.Aidl_include_dirs, j.deviceProperties.Aidl.Include_dirs...)
 	dpInfo.Static_libs = append(dpInfo.Static_libs, j.staticLibs(ctx)...)
@@ -2194,12 +2250,20 @@
 
 func (j *Module) hasCode(ctx android.ModuleContext) bool {
 	srcFiles := android.PathsForModuleSrcExcludes(ctx, j.properties.Srcs, j.properties.Exclude_srcs)
-	return len(srcFiles) > 0 || len(ctx.GetDirectDepsWithTag(staticLibTag)) > 0
+	return len(srcFiles) > 0 || len(ctx.GetDirectDepsProxyWithTag(staticLibTag)) > 0
 }
 
 // Implements android.ApexModule
-func (j *Module) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
-	return j.depIsInSameApex(ctx, dep)
+func (m *Module) GetDepInSameApexChecker() android.DepInSameApexChecker {
+	return JavaDepInSameApexChecker{}
+}
+
+type JavaDepInSameApexChecker struct {
+	android.BaseDepInSameApexChecker
+}
+
+func (m JavaDepInSameApexChecker) OutgoingDepIsInSameApex(tag blueprint.DependencyTag) bool {
+	return depIsInSameApex(tag)
 }
 
 // Implements android.ApexModule
@@ -2231,19 +2295,17 @@
 }
 
 func (j *Module) collectTransitiveSrcFiles(ctx android.ModuleContext, mine android.Paths) {
-	var fromDeps []*android.DepSet[android.Path]
-	ctx.VisitDirectDeps(func(module android.Module) {
+	var fromDeps []depset.DepSet[android.Path]
+	ctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 		tag := ctx.OtherModuleDependencyTag(module)
 		if tag == staticLibTag {
 			if depInfo, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider); ok {
-				if depInfo.TransitiveSrcFiles != nil {
-					fromDeps = append(fromDeps, depInfo.TransitiveSrcFiles)
-				}
+				fromDeps = append(fromDeps, depInfo.TransitiveSrcFiles)
 			}
 		}
 	})
 
-	j.transitiveSrcFiles = android.NewDepSet(android.POSTORDER, mine, fromDeps)
+	j.transitiveSrcFiles = depset.New(depset.POSTORDER, mine, fromDeps)
 }
 
 func (j *Module) IsInstallable() bool {
@@ -2321,7 +2383,10 @@
 		"stable.core.platform.api.stubs",
 		"stub-annotations", "private-stub-annotations-jar",
 		"core-lambda-stubs",
-		"core-generated-annotation-stubs":
+		"core-generated-annotation-stubs",
+		// jacocoagent only uses core APIs, but has to specify a non-core sdk_version so it can use
+		// a prebuilt SDK to avoid circular dependencies when it statically included in the bootclasspath.
+		"jacocoagent":
 		return javaCore, true
 	case android.SdkPublic.DefaultJavaLibraryName():
 		return javaSdk, true
@@ -2350,7 +2415,7 @@
 // checkSdkLinkType make sures the given dependency doesn't have a lower SDK link type rank than
 // this module's. See the comment on rank() for details and an example.
 func (j *Module) checkSdkLinkType(
-	ctx android.ModuleContext, dep moduleWithSdkDep, tag dependencyTag) {
+	ctx android.ModuleContext, dep android.ModuleProxy) {
 	if ctx.Host() {
 		return
 	}
@@ -2359,7 +2424,12 @@
 	if stubs {
 		return
 	}
-	depLinkType, _ := dep.getSdkLinkType(ctx, ctx.OtherModuleName(dep))
+	info, ok := android.OtherModuleProvider(ctx, dep, JavaInfoProvider)
+	if !ok || info.ModuleWithSdkDepInfo == nil {
+		panic(fmt.Errorf("dependency doesn't have ModuleWithSdkDepInfo: %v", dep))
+	}
+
+	depLinkType := info.ModuleWithSdkDepInfo.SdkLinkType
 
 	if myLinkType.rank() < depLinkType.rank() {
 		ctx.ModuleErrorf("compiles against %v, but dependency %q is compiling against %v. "+
@@ -2377,14 +2447,14 @@
 
 	j.collectTransitiveHeaderJarsForR8(ctx)
 
-	var transitiveBootClasspathHeaderJars []*android.DepSet[android.Path]
-	var transitiveClasspathHeaderJars []*android.DepSet[android.Path]
-	var transitiveJava9ClasspathHeaderJars []*android.DepSet[android.Path]
-	var transitiveStaticJarsHeaderLibs []*android.DepSet[android.Path]
-	var transitiveStaticJarsImplementationLibs []*android.DepSet[android.Path]
-	var transitiveStaticJarsResourceLibs []*android.DepSet[android.Path]
+	var transitiveBootClasspathHeaderJars []depset.DepSet[android.Path]
+	var transitiveClasspathHeaderJars []depset.DepSet[android.Path]
+	var transitiveJava9ClasspathHeaderJars []depset.DepSet[android.Path]
+	var transitiveStaticJarsHeaderLibs []depset.DepSet[android.Path]
+	var transitiveStaticJarsImplementationLibs []depset.DepSet[android.Path]
+	var transitiveStaticJarsResourceLibs []depset.DepSet[android.Path]
 
-	ctx.VisitDirectDeps(func(module android.Module) {
+	ctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 		otherName := ctx.OtherModuleName(module)
 		tag := ctx.OtherModuleDependencyTag(module)
 
@@ -2416,11 +2486,9 @@
 			switch tag {
 			case bootClasspathTag:
 				deps.bootClasspath = append(deps.bootClasspath, dep.HeaderJars...)
-				if dep.TransitiveStaticLibsHeaderJars != nil {
-					transitiveBootClasspathHeaderJars = append(transitiveBootClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
-				}
+				transitiveBootClasspathHeaderJars = append(transitiveBootClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
 			case sdkLibTag, libTag, instrumentationForTag:
-				if _, ok := module.(*Plugin); ok {
+				if _, ok := android.OtherModuleProvider(ctx, module, JavaPluginInfoProvider); ok {
 					ctx.ModuleErrorf("a java_plugin (%s) cannot be used as a libs dependency", otherName)
 				}
 				deps.classpath = append(deps.classpath, dep.HeaderJars...)
@@ -2433,16 +2501,12 @@
 				addPlugins(&deps, dep.ExportedPlugins, dep.ExportedPluginClasses...)
 				deps.disableTurbine = deps.disableTurbine || dep.ExportedPluginDisableTurbine
 
-				if dep.TransitiveStaticLibsHeaderJars != nil {
-					transitiveClasspathHeaderJars = append(transitiveClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
-				}
+				transitiveClasspathHeaderJars = append(transitiveClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
 			case java9LibTag:
 				deps.java9Classpath = append(deps.java9Classpath, dep.HeaderJars...)
-				if dep.TransitiveStaticLibsHeaderJars != nil {
-					transitiveJava9ClasspathHeaderJars = append(transitiveJava9ClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
-				}
+				transitiveJava9ClasspathHeaderJars = append(transitiveJava9ClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
 			case staticLibTag:
-				if _, ok := module.(*Plugin); ok {
+				if _, ok := android.OtherModuleProvider(ctx, module, JavaPluginInfoProvider); ok {
 					ctx.ModuleErrorf("a java_plugin (%s) cannot be used as a static_libs dependency", otherName)
 				}
 				deps.classpath = append(deps.classpath, dep.HeaderJars...)
@@ -2457,51 +2521,49 @@
 				deps.disableTurbine = deps.disableTurbine || dep.ExportedPluginDisableTurbine
 				deps.aconfigProtoFiles = append(deps.aconfigProtoFiles, dep.AconfigIntermediateCacheOutputPaths...)
 
-				if dep.TransitiveStaticLibsHeaderJars != nil {
-					transitiveClasspathHeaderJars = append(transitiveClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
-					transitiveStaticJarsHeaderLibs = append(transitiveStaticJarsHeaderLibs, dep.TransitiveStaticLibsHeaderJars)
-				}
-				if dep.TransitiveStaticLibsImplementationJars != nil {
-					transitiveStaticJarsImplementationLibs = append(transitiveStaticJarsImplementationLibs, dep.TransitiveStaticLibsImplementationJars)
-				}
-				if dep.TransitiveStaticLibsResourceJars != nil {
-					transitiveStaticJarsResourceLibs = append(transitiveStaticJarsResourceLibs, dep.TransitiveStaticLibsResourceJars)
-				}
+				transitiveClasspathHeaderJars = append(transitiveClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
+				transitiveStaticJarsHeaderLibs = append(transitiveStaticJarsHeaderLibs, dep.TransitiveStaticLibsHeaderJars)
+				transitiveStaticJarsImplementationLibs = append(transitiveStaticJarsImplementationLibs, dep.TransitiveStaticLibsImplementationJars)
+				transitiveStaticJarsResourceLibs = append(transitiveStaticJarsResourceLibs, dep.TransitiveStaticLibsResourceJars)
 			case pluginTag:
-				if plugin, ok := module.(*Plugin); ok {
-					if plugin.pluginProperties.Processor_class != nil {
-						addPlugins(&deps, dep.ImplementationAndResourcesJars, *plugin.pluginProperties.Processor_class)
+				if plugin, ok := android.OtherModuleProvider(ctx, module, JavaPluginInfoProvider); ok {
+					if plugin.ProcessorClass != nil {
+						addPlugins(&deps, dep.ImplementationAndResourcesJars, *plugin.ProcessorClass)
 					} else {
 						addPlugins(&deps, dep.ImplementationAndResourcesJars)
 					}
 					// Turbine doesn't run annotation processors, so any module that uses an
 					// annotation processor that generates API is incompatible with the turbine
 					// optimization.
-					deps.disableTurbine = deps.disableTurbine || Bool(plugin.pluginProperties.Generates_api)
+					deps.disableTurbine = deps.disableTurbine || plugin.GeneratesApi
 				} else {
 					ctx.PropertyErrorf("plugins", "%q is not a java_plugin module", otherName)
 				}
 			case errorpronePluginTag:
-				if _, ok := module.(*Plugin); ok {
+				if _, ok := android.OtherModuleProvider(ctx, module, JavaPluginInfoProvider); ok {
 					deps.errorProneProcessorPath = append(deps.errorProneProcessorPath, dep.ImplementationAndResourcesJars...)
 				} else {
 					ctx.PropertyErrorf("plugins", "%q is not a java_plugin module", otherName)
 				}
 			case exportedPluginTag:
-				if plugin, ok := module.(*Plugin); ok {
+				if plugin, ok := android.OtherModuleProvider(ctx, module, JavaPluginInfoProvider); ok {
 					j.exportedPluginJars = append(j.exportedPluginJars, dep.ImplementationAndResourcesJars...)
-					if plugin.pluginProperties.Processor_class != nil {
-						j.exportedPluginClasses = append(j.exportedPluginClasses, *plugin.pluginProperties.Processor_class)
+					if plugin.ProcessorClass != nil {
+						j.exportedPluginClasses = append(j.exportedPluginClasses, *plugin.ProcessorClass)
 					}
 					// Turbine doesn't run annotation processors, so any module that uses an
 					// annotation processor that generates API is incompatible with the turbine
 					// optimization.
-					j.exportedDisableTurbine = Bool(plugin.pluginProperties.Generates_api)
+					j.exportedDisableTurbine = plugin.GeneratesApi
 				} else {
 					ctx.PropertyErrorf("exported_plugins", "%q is not a java_plugin module", otherName)
 				}
 			case kotlinPluginTag:
-				deps.kotlinPlugins = append(deps.kotlinPlugins, dep.ImplementationAndResourcesJars...)
+				if _, ok := android.OtherModuleProvider(ctx, module, KotlinPluginInfoProvider); ok {
+					deps.kotlinPlugins = append(deps.kotlinPlugins, dep.ImplementationAndResourcesJars...)
+				} else {
+					ctx.PropertyErrorf("kotlin_plugins", "%q is not a kotlin_plugin module", otherName)
+				}
 			case syspropPublicStubDepTag:
 				// This is a sysprop implementation library, forward the JavaInfoProvider from
 				// the corresponding sysprop public stub library as SyspropPublicStubInfoProvider.
@@ -2509,21 +2571,21 @@
 					JavaInfo: dep,
 				})
 			}
-		} else if dep, ok := module.(android.SourceFileProducer); ok {
+		} else if dep, ok := android.OtherModuleProvider(ctx, module, android.SourceFilesInfoProvider); ok {
 			switch tag {
 			case sdkLibTag, libTag:
-				checkProducesJars(ctx, dep)
-				deps.classpath = append(deps.classpath, dep.Srcs()...)
-				deps.dexClasspath = append(deps.classpath, dep.Srcs()...)
+				checkProducesJars(ctx, dep, module)
+				deps.classpath = append(deps.classpath, dep.Srcs...)
+				deps.dexClasspath = append(deps.classpath, dep.Srcs...)
 				transitiveClasspathHeaderJars = append(transitiveClasspathHeaderJars,
-					android.NewDepSet(android.PREORDER, dep.Srcs(), nil))
+					depset.New(depset.PREORDER, dep.Srcs, nil))
 			case staticLibTag:
-				checkProducesJars(ctx, dep)
-				deps.classpath = append(deps.classpath, dep.Srcs()...)
-				deps.staticJars = append(deps.staticJars, dep.Srcs()...)
-				deps.staticHeaderJars = append(deps.staticHeaderJars, dep.Srcs()...)
+				checkProducesJars(ctx, dep, module)
+				deps.classpath = append(deps.classpath, dep.Srcs...)
+				deps.staticJars = append(deps.staticJars, dep.Srcs...)
+				deps.staticHeaderJars = append(deps.staticHeaderJars, dep.Srcs...)
 
-				depHeaderJars := android.NewDepSet(android.PREORDER, dep.Srcs(), nil)
+				depHeaderJars := depset.New(depset.PREORDER, dep.Srcs, nil)
 				transitiveClasspathHeaderJars = append(transitiveClasspathHeaderJars, depHeaderJars)
 				transitiveStaticJarsHeaderLibs = append(transitiveStaticJarsHeaderLibs, depHeaderJars)
 				transitiveStaticJarsImplementationLibs = append(transitiveStaticJarsImplementationLibs, depHeaderJars)
@@ -2540,10 +2602,8 @@
 				// then add its libs to the bootclasspath.
 				if sm, ok := android.OtherModuleProvider(ctx, module, SystemModulesProvider); ok {
 					deps.bootClasspath = append(deps.bootClasspath, sm.HeaderJars...)
-					if sm.TransitiveStaticLibsHeaderJars != nil {
-						transitiveBootClasspathHeaderJars = append(transitiveBootClasspathHeaderJars,
-							sm.TransitiveStaticLibsHeaderJars)
-					}
+					transitiveBootClasspathHeaderJars = append(transitiveBootClasspathHeaderJars,
+						sm.TransitiveStaticLibsHeaderJars)
 				} else {
 					ctx.PropertyErrorf("boot classpath dependency %q does not provide SystemModulesProvider",
 						ctx.OtherModuleName(module))
@@ -2579,11 +2639,11 @@
 	deps.transitiveStaticLibsResourceJars = transitiveStaticJarsResourceLibs
 
 	if ctx.Config().UseTransitiveJarsInClasspath() {
-		depSet := android.NewDepSet(android.PREORDER, nil, transitiveClasspathHeaderJars)
+		depSet := depset.New(depset.PREORDER, nil, transitiveClasspathHeaderJars)
 		deps.classpath = depSet.ToList()
-		depSet = android.NewDepSet(android.PREORDER, nil, transitiveBootClasspathHeaderJars)
+		depSet = depset.New(depset.PREORDER, nil, transitiveBootClasspathHeaderJars)
 		deps.bootClasspath = depSet.ToList()
-		depSet = android.NewDepSet(android.PREORDER, nil, transitiveJava9ClasspathHeaderJars)
+		depSet = depset.New(depset.PREORDER, nil, transitiveJava9ClasspathHeaderJars)
 		deps.java9Classpath = depSet.ToList()
 	}
 
@@ -2626,18 +2686,11 @@
 	RenameUseExclude
 )
 
-type RenameUseElement struct {
-	DepName   string
-	RenameUse DependencyUse
-	Why       string // token for determining where in the logic the decision was made.
-}
-
 type JarJarProviderData struct {
 	// Mapping of class names: original --> renamed.  If the value is "", the class will be
 	// renamed by the next rdep that has the jarjar_prefix attribute (or this module if it has
 	// attribute). Rdeps of that module will inherit the renaming.
-	Rename    map[string]string
-	RenameUse []RenameUseElement
+	Rename map[string]string
 }
 
 func (this JarJarProviderData) GetDebugString() string {
@@ -2708,7 +2761,7 @@
 	module := ctx.Module()
 	moduleName := module.Name()
 
-	ctx.VisitDirectDeps(func(m android.Module) {
+	ctx.VisitDirectDepsProxy(func(m android.ModuleProxy) {
 		tag := ctx.OtherModuleDependencyTag(m)
 		// This logic mirrors that in (*Module).collectDeps above.  There are several places
 		// where we explicitly return RenameUseExclude, even though it is the default, to
@@ -2716,93 +2769,89 @@
 		//
 		// Note well: there are probably cases that are getting to the unconditional return
 		// and are therefore wrong.
-		shouldIncludeRenames := func() (DependencyUse, string) {
+		shouldIncludeRenames := func() DependencyUse {
 			if moduleName == m.Name() {
-				return RenameUseInclude, "name" // If we have the same module name, include the renames.
+				return RenameUseInclude // If we have the same module name, include the renames.
 			}
 			if sc, ok := module.(android.SdkContext); ok {
 				if ctx.Device() {
 					sdkDep := decodeSdkDep(ctx, sc)
 					if !sdkDep.invalidVersion && sdkDep.useFiles {
-						return RenameUseExclude, "useFiles"
+						return RenameUseExclude
 					}
 				}
 			}
 			if IsJniDepTag(tag) || tag == certificateTag || tag == proguardRaiseTag {
-				return RenameUseExclude, "tags"
+				return RenameUseExclude
 			}
 			if _, ok := android.OtherModuleProvider(ctx, m, SdkLibraryInfoProvider); ok {
 				switch tag {
 				case sdkLibTag, libTag:
-					return RenameUseExclude, "sdklibdep" // matches collectDeps()
+					return RenameUseExclude // matches collectDeps()
 				}
-				return RenameUseInvalid, "sdklibdep" // dep is not used in collectDeps()
+				return RenameUseInvalid // dep is not used in collectDeps()
 			} else if ji, ok := android.OtherModuleProvider(ctx, m, JavaInfoProvider); ok {
 				switch ji.StubsLinkType {
 				case Stubs:
-					return RenameUseExclude, "info"
+					return RenameUseExclude
 				case Implementation:
-					return RenameUseInclude, "info"
+					return RenameUseInclude
 				default:
 					//fmt.Printf("collectDirectDepsProviders: %v -> %v StubsLinkType unknown\n", module, m)
 					// Fall through to the heuristic logic.
 				}
-				switch reflect.TypeOf(m).String() {
-				case "*java.GeneratedJavaLibraryModule":
+				if _, ok := android.OtherModuleProvider(ctx, m, android.CodegenInfoProvider); ok {
 					// Probably a java_aconfig_library module.
-					// TODO: make this check better.
-					return RenameUseInclude, "reflect"
+					return RenameUseInclude
 				}
 				switch tag {
 				case bootClasspathTag:
-					return RenameUseExclude, "tagswitch"
+					return RenameUseExclude
 				case sdkLibTag, libTag, instrumentationForTag:
-					return RenameUseInclude, "tagswitch"
+					return RenameUseInclude
 				case java9LibTag:
-					return RenameUseExclude, "tagswitch"
+					return RenameUseExclude
 				case staticLibTag:
-					return RenameUseInclude, "tagswitch"
+					return RenameUseInclude
 				case pluginTag:
-					return RenameUseInclude, "tagswitch"
+					return RenameUseInclude
 				case errorpronePluginTag:
-					return RenameUseInclude, "tagswitch"
+					return RenameUseInclude
 				case exportedPluginTag:
-					return RenameUseInclude, "tagswitch"
+					return RenameUseInclude
 				case kotlinPluginTag:
-					return RenameUseInclude, "tagswitch"
+					return RenameUseInclude
 				default:
-					return RenameUseExclude, "tagswitch"
+					return RenameUseExclude
 				}
-			} else if _, ok := m.(android.SourceFileProducer); ok {
+			} else if _, ok := android.OtherModuleProvider(ctx, m, android.SourceFilesInfoProvider); ok {
 				switch tag {
 				case sdkLibTag, libTag, staticLibTag:
-					return RenameUseInclude, "srcfile"
+					return RenameUseInclude
 				default:
-					return RenameUseExclude, "srcfile"
+					return RenameUseExclude
 				}
 			} else if _, ok := android.OtherModuleProvider(ctx, m, android.CodegenInfoProvider); ok {
-				return RenameUseInclude, "aconfig_declarations_group"
+				return RenameUseInclude
 			} else {
 				switch tag {
 				case bootClasspathTag:
-					return RenameUseExclude, "else"
+					return RenameUseExclude
 				case systemModulesTag:
-					return RenameUseInclude, "else"
+					return RenameUseInclude
 				}
 			}
 			// If we got here, choose the safer option, which may lead to a build failure, rather
 			// than runtime failures on the device.
-			return RenameUseExclude, "end"
+			return RenameUseExclude
 		}
 
 		if result == nil {
 			result = &JarJarProviderData{
-				Rename:    make(map[string]string),
-				RenameUse: make([]RenameUseElement, 0),
+				Rename: make(map[string]string),
 			}
 		}
-		how, why := shouldIncludeRenames()
-		result.RenameUse = append(result.RenameUse, RenameUseElement{DepName: m.Name(), RenameUse: how, Why: why})
+		how := shouldIncludeRenames()
 		if how != RenameUseInclude {
 			// Nothing to merge.
 			return
@@ -2907,14 +2956,18 @@
 // Get the jarjar rule text for a given provider for the fully resolved rules. Classes that map
 // to "" won't be in this list because they shouldn't be renamed yet.
 func getJarJarRuleText(provider *JarJarProviderData) string {
-	result := ""
+	result := strings.Builder{}
 	for _, orig := range android.SortedKeys(provider.Rename) {
 		renamed := provider.Rename[orig]
 		if renamed != "" {
-			result += "rule " + orig + " " + renamed + "\n"
+			result.WriteString("rule ")
+			result.WriteString(orig)
+			result.WriteString(" ")
+			result.WriteString(renamed)
+			result.WriteString("\n")
 		}
 	}
-	return result
+	return result.String()
 }
 
 // Repackage the flags if the jarjar rule txt for the flags is generated
@@ -2927,12 +2980,23 @@
 	return repackagedJarjarFile, true
 }
 
-func (j *Module) jarjarIfNecessary(ctx android.ModuleContext, infile android.Path, jarName, info string) (android.Path, bool) {
+func (j *Module) jarjarIfNecessary(ctx android.ModuleContext, infile android.Path, jarName, info string, useShards bool) (android.Path, bool) {
 	if j.expandJarjarRules == nil {
 		return infile, false
 	}
 	jarjarFile := android.PathForModuleOut(ctx, "jarjar", info, jarName)
-	TransformJarJar(ctx, jarjarFile, infile, j.expandJarjarRules)
+
+	totalShards := 1
+	if useShards {
+		totalShardsStr := j.properties.Jarjar_shards.GetOrDefault(ctx, "1")
+		ts, err := strconv.Atoi(totalShardsStr)
+		if err != nil {
+			ctx.PropertyErrorf("jarjar_shards", "jarjar_shards must be an integer represented as a string")
+			return infile, false
+		}
+		totalShards = ts
+	}
+	TransformJarJarWithShards(ctx, jarjarFile, infile, j.expandJarjarRules, totalShards)
 	return jarjarFile, true
 
 }
diff --git a/java/bootclasspath.go b/java/bootclasspath.go
index 029f6f6..98fb417 100644
--- a/java/bootclasspath.go
+++ b/java/bootclasspath.go
@@ -15,6 +15,8 @@
 package java
 
 import (
+	"fmt"
+
 	"android/soong/android"
 
 	"github.com/google/blueprint"
@@ -23,36 +25,9 @@
 
 // Contains code that is common to both platform_bootclasspath and bootclasspath_fragment.
 
-func init() {
-	registerBootclasspathBuildComponents(android.InitRegistrationContext)
-}
-
-func registerBootclasspathBuildComponents(ctx android.RegistrationContext) {
-	ctx.FinalDepsMutators(func(ctx android.RegisterMutatorsContext) {
-		ctx.BottomUp("bootclasspath_deps", bootclasspathDepsMutator).Parallel()
-	})
-}
-
-// BootclasspathDepsMutator is the interface that a module must implement if it wants to add
-// dependencies onto APEX specific variants of bootclasspath fragments or bootclasspath contents.
-type BootclasspathDepsMutator interface {
-	// BootclasspathDepsMutator implementations should add dependencies using
-	// addDependencyOntoApexModulePair and addDependencyOntoApexVariants.
-	BootclasspathDepsMutator(ctx android.BottomUpMutatorContext)
-}
-
-// bootclasspathDepsMutator is called during the final deps phase after all APEX variants have
-// been created so can add dependencies onto specific APEX variants of modules.
-func bootclasspathDepsMutator(ctx android.BottomUpMutatorContext) {
-	m := ctx.Module()
-	if p, ok := m.(BootclasspathDepsMutator); ok {
-		p.BootclasspathDepsMutator(ctx)
-	}
-}
-
 // addDependencyOntoApexVariants adds dependencies onto the appropriate apex specific variants of
 // the module as specified in the ApexVariantReference list.
-func addDependencyOntoApexVariants(ctx android.BottomUpMutatorContext, propertyName string, refs []ApexVariantReference, tag blueprint.DependencyTag) {
+func addDependencyOntoApexVariants(ctx android.BottomUpMutatorContext, propertyName string, refs []ApexVariantReference, tagType bootclasspathDependencyTagType) {
 	for i, ref := range refs {
 		apex := proptools.StringDefault(ref.Apex, "platform")
 
@@ -62,7 +37,7 @@
 		}
 		name := proptools.String(ref.Module)
 
-		addDependencyOntoApexModulePair(ctx, apex, name, tag)
+		addDependencyOntoApexModulePair(ctx, apex, name, tagType)
 	}
 }
 
@@ -75,68 +50,154 @@
 // module when both source and prebuilt modules are available.
 //
 // Use gatherApexModulePairDepsWithTag to retrieve the dependencies.
-func addDependencyOntoApexModulePair(ctx android.BottomUpMutatorContext, apex string, name string, tag blueprint.DependencyTag) {
-	var variations []blueprint.Variation
-	if !android.IsConfiguredJarForPlatform(apex) {
-		// Pick the correct apex variant.
-		variations = []blueprint.Variation{
-			{Mutator: "apex", Variation: apex},
-		}
+func addDependencyOntoApexModulePair(ctx android.BottomUpMutatorContext, apex string, name string, tagType bootclasspathDependencyTagType) {
+	tag := bootclasspathDependencyTag{
+		typ: tagType,
 	}
-
 	target := ctx.Module().Target()
-	variations = append(variations, target.Variations()...)
-
-	addedDep := false
-	if ctx.OtherModuleDependencyVariantExists(variations, name) {
-		ctx.AddFarVariationDependencies(variations, tag, name)
-		addedDep = true
+	if android.IsConfiguredJarForPlatform(apex) {
+		// Platform variant, add a direct dependency.
+		ctx.AddFarVariationDependencies(target.Variations(), tag, name)
+	} else {
+		// A module in an apex.  Dependencies can't be added directly onto an apex variation, as that would
+		// require constructing a full ApexInfo configuration, which can't be predicted here.  Add a dependency
+		// on the apex instead, and annotate the dependency tag with the desired module in the apex.
+		tag.moduleInApex = name
+		ctx.AddFarVariationDependencies(target.Variations(), tag, apex)
 	}
 
-	// Add a dependency on the prebuilt module if it exists.
-	prebuiltName := android.PrebuiltNameFromSource(name)
-	if ctx.OtherModuleDependencyVariantExists(variations, prebuiltName) {
-		ctx.AddVariationDependencies(variations, tag, prebuiltName)
-		addedDep = true
-	}
-
-	// If no appropriate variant existing for this, so no dependency could be added, then it is an
-	// error, unless missing dependencies are allowed. The simplest way to handle that is to add a
-	// dependency that will not be satisfied and the default behavior will handle it.
-	if !addedDep {
-		// Add dependency on the unprefixed (i.e. source or renamed prebuilt) module which we know does
-		// not exist. The resulting error message will contain useful information about the available
-		// variants.
-		reportMissingVariationDependency(ctx, variations, name)
-
-		// Add dependency on the missing prefixed prebuilt variant too if a module with that name exists
-		// so that information about its available variants will be reported too.
-		if ctx.OtherModuleExists(prebuiltName) {
-			reportMissingVariationDependency(ctx, variations, prebuiltName)
-		}
-	}
 }
 
-// reportMissingVariationDependency intentionally adds a dependency on a missing variation in order
-// to generate an appropriate error message with information about the available variations.
-func reportMissingVariationDependency(ctx android.BottomUpMutatorContext, variations []blueprint.Variation, name string) {
-	ctx.AddFarVariationDependencies(variations, nil, name)
+// gatherFragments collects fragments that are direct dependencies of this module, as well as
+// any fragments in apexes via the dependency on the apex.  It returns a list of the fragment
+// modules and map from apex name to the fragment in that apex.
+func gatherFragments(ctx android.BaseModuleContext) ([]android.Module, map[string]android.Module) {
+	var fragments []android.Module
+
+	type fragmentInApex struct {
+		module string
+		apex   string
+	}
+
+	var fragmentsInApexes []fragmentInApex
+
+	// Find any direct dependencies, as well as a list of the modules in apexes.
+	ctx.VisitDirectDeps(func(module android.Module) {
+		t := ctx.OtherModuleDependencyTag(module)
+		if bcpTag, ok := t.(bootclasspathDependencyTag); ok && bcpTag.typ == fragment {
+			if bcpTag.moduleInApex != "" {
+				fragmentsInApexes = append(fragmentsInApexes, fragmentInApex{bcpTag.moduleInApex, ctx.OtherModuleName(module)})
+			} else {
+				fragments = append(fragments, module)
+			}
+		}
+	})
+
+	fragmentsMap := make(map[string]android.Module)
+	for _, fragmentInApex := range fragmentsInApexes {
+		var found android.Module
+		// Find a desired module in an apex.
+		ctx.WalkDeps(func(child, parent android.Module) bool {
+			t := ctx.OtherModuleDependencyTag(child)
+			if parent == ctx.Module() {
+				if bcpTag, ok := t.(bootclasspathDependencyTag); ok && bcpTag.typ == fragment && ctx.OtherModuleName(child) == fragmentInApex.apex {
+					// This is the dependency from this module to the apex, recurse into it.
+					return true
+				}
+			} else if android.IsDontReplaceSourceWithPrebuiltTag(t) {
+				return false
+			} else if t == android.PrebuiltDepTag {
+				return false
+			} else if IsBootclasspathFragmentContentDepTag(t) {
+				return false
+			} else if android.RemoveOptionalPrebuiltPrefix(ctx.OtherModuleName(child)) == fragmentInApex.module {
+				// This is the desired module inside the apex.
+				if found != nil && child != found {
+					panic(fmt.Errorf("found two conflicting modules %q in apex %q: %s and %s",
+						fragmentInApex.module, fragmentInApex.apex, found, child))
+				}
+				found = child
+			}
+			return false
+		})
+		if found != nil {
+			if existing, exists := fragmentsMap[fragmentInApex.apex]; exists {
+				ctx.ModuleErrorf("apex %s has multiple fragments, %s and %s", fragmentInApex.apex, fragmentInApex.module, existing)
+			} else {
+				fragmentsMap[fragmentInApex.apex] = found
+				fragments = append(fragments, found)
+			}
+		} else if !ctx.Config().AllowMissingDependencies() {
+			ctx.ModuleErrorf("failed to find fragment %q in apex %q\n",
+				fragmentInApex.module, fragmentInApex.apex)
+		}
+	}
+	return fragments, fragmentsMap
 }
 
 // gatherApexModulePairDepsWithTag returns the list of dependencies with the supplied tag that was
 // added by addDependencyOntoApexModulePair.
-func gatherApexModulePairDepsWithTag(ctx android.BaseModuleContext, tag blueprint.DependencyTag) []android.Module {
+func gatherApexModulePairDepsWithTag(ctx android.BaseModuleContext, tagType bootclasspathDependencyTagType) ([]android.Module, map[android.Module]string) {
 	var modules []android.Module
-	isActiveModulePred := func(module android.Module) bool {
-		return isActiveModule(ctx, module)
+	modulesToApex := make(map[android.Module]string)
+
+	type moduleInApex struct {
+		module string
+		apex   string
 	}
-	ctx.VisitDirectDepsIf(isActiveModulePred, func(module android.Module) {
+
+	var modulesInApexes []moduleInApex
+
+	ctx.VisitDirectDeps(func(module android.Module) {
 		t := ctx.OtherModuleDependencyTag(module)
-		if t == tag {
-			modules = append(modules, module)
+		if bcpTag, ok := t.(bootclasspathDependencyTag); ok && bcpTag.typ == tagType {
+			if bcpTag.moduleInApex != "" {
+				modulesInApexes = append(modulesInApexes, moduleInApex{bcpTag.moduleInApex, ctx.OtherModuleName(module)})
+			} else {
+				modules = append(modules, module)
+			}
 		}
 	})
-	return modules
+
+	for _, moduleInApex := range modulesInApexes {
+		var found android.Module
+		ctx.WalkDeps(func(child, parent android.Module) bool {
+			t := ctx.OtherModuleDependencyTag(child)
+			if parent == ctx.Module() {
+				if bcpTag, ok := t.(bootclasspathDependencyTag); ok && bcpTag.typ == tagType && ctx.OtherModuleName(child) == moduleInApex.apex {
+					// recurse into the apex
+					return true
+				}
+			} else if tagType != fragment && android.IsFragmentInApexTag(t) {
+				return true
+			} else if android.IsDontReplaceSourceWithPrebuiltTag(t) {
+				return false
+			} else if t == android.PrebuiltDepTag {
+				return false
+			} else if IsBootclasspathFragmentContentDepTag(t) {
+				return false
+			} else if android.RemoveOptionalPrebuiltPrefix(ctx.OtherModuleName(child)) == moduleInApex.module {
+				if found != nil && child != found {
+					panic(fmt.Errorf("found two conflicting modules %q in apex %q: %s and %s",
+						moduleInApex.module, moduleInApex.apex, found, child))
+				}
+				found = child
+			}
+			return false
+		})
+		if found != nil {
+			modules = append(modules, found)
+			if existing, exists := modulesToApex[found]; exists && existing != moduleInApex.apex {
+				ctx.ModuleErrorf("module %s is in two apexes, %s and %s", moduleInApex.module, existing, moduleInApex.apex)
+			} else {
+				modulesToApex[found] = moduleInApex.apex
+			}
+		} else if !ctx.Config().AllowMissingDependencies() {
+			ctx.ModuleErrorf("failed to find module %q in apex %q\n",
+				moduleInApex.module, moduleInApex.apex)
+		}
+	}
+	return modules, modulesToApex
 }
 
 // ApexVariantReference specifies a particular apex variant of a module.
@@ -165,7 +226,7 @@
 // addDependenciesOntoFragments adds dependencies to the fragments specified in this properties
 // structure.
 func (p *BootclasspathFragmentsDepsProperties) addDependenciesOntoFragments(ctx android.BottomUpMutatorContext) {
-	addDependencyOntoApexVariants(ctx, "fragments", p.Fragments, bootclasspathFragmentDepTag)
+	addDependencyOntoApexVariants(ctx, "fragments", p.Fragments, fragment)
 }
 
 // bootclasspathDependencyTag defines dependencies from/to bootclasspath_fragment,
@@ -174,23 +235,38 @@
 type bootclasspathDependencyTag struct {
 	blueprint.BaseDependencyTag
 
-	name string
+	typ bootclasspathDependencyTagType
+
+	// moduleInApex is set to the name of the desired module when this dependency points
+	// to the apex that the modules is contained in.
+	moduleInApex string
 }
 
+type bootclasspathDependencyTagType int
+
+const (
+	// The tag used for dependencies onto bootclasspath_fragments.
+	fragment bootclasspathDependencyTagType = iota
+	// The tag used for dependencies onto platform_bootclasspath.
+	platform
+	dexpreoptBootJar
+	artBootJar
+	platformBootJar
+	apexBootJar
+)
+
 func (t bootclasspathDependencyTag) ExcludeFromVisibilityEnforcement() {
 }
 
+func (t bootclasspathDependencyTag) LicenseAnnotations() []android.LicenseAnnotation {
+	return []android.LicenseAnnotation{android.LicenseAnnotationSharedDependency}
+}
+
 // Dependencies that use the bootclasspathDependencyTag instances are only added after all the
 // visibility checking has been done so this has no functional effect. However, it does make it
 // clear that visibility is not being enforced on these tags.
 var _ android.ExcludeFromVisibilityEnforcementTag = bootclasspathDependencyTag{}
 
-// The tag used for dependencies onto bootclasspath_fragments.
-var bootclasspathFragmentDepTag = bootclasspathDependencyTag{name: "fragment"}
-
-// The tag used for dependencies onto platform_bootclasspath.
-var platformBootclasspathDepTag = bootclasspathDependencyTag{name: "platform"}
-
 // BootclasspathNestedAPIProperties defines properties related to the API provided by parts of the
 // bootclasspath that are nested within the main BootclasspathAPIProperties.
 type BootclasspathNestedAPIProperties struct {
diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go
index 4fcd40b..8383a5a 100644
--- a/java/bootclasspath_fragment.go
+++ b/java/bootclasspath_fragment.go
@@ -23,7 +23,6 @@
 
 	"android/soong/android"
 	"android/soong/dexpreopt"
-	"android/soong/testing"
 
 	"github.com/google/blueprint/proptools"
 
@@ -84,20 +83,28 @@
 	return true
 }
 
-// Contents of bootclasspath fragments in an apex are considered to be directly in the apex, as if
-// they were listed in java_libs.
-func (b bootclasspathFragmentContentDependencyTag) CopyDirectlyInAnyApex() {}
-
 // Contents of bootclasspath fragments require files from prebuilt apex files.
 func (b bootclasspathFragmentContentDependencyTag) RequiresFilesFromPrebuiltApex() {}
 
 // The tag used for the dependency between the bootclasspath_fragment module and its contents.
 var bootclasspathFragmentContentDepTag = bootclasspathFragmentContentDependencyTag{}
 
+type moduleInFragmentDependencyTag struct {
+	blueprint.DependencyTag
+}
+
+func (m moduleInFragmentDependencyTag) ExcludeFromVisibilityEnforcement() {
+}
+
+// moduleInFragmentDepTag is added alongside bootclasspathFragmentContentDependencyTag,
+// but doesn't set ReplaceSourceWithPrebuilt.  It is used to find modules in the fragment
+// by traversing from the apex to the fragment to the module, which prevents having to
+// construct a dependency on the apex variant of the fragment directly.
+var moduleInFragmentDepTag = moduleInFragmentDependencyTag{}
+
 var _ android.ExcludeFromVisibilityEnforcementTag = bootclasspathFragmentContentDepTag
 var _ android.ReplaceSourceWithPrebuilt = bootclasspathFragmentContentDepTag
 var _ android.SdkMemberDependencyTag = bootclasspathFragmentContentDepTag
-var _ android.CopyDirectlyInAnyApexTag = bootclasspathFragmentContentDepTag
 var _ android.RequiresFilesFromPrebuiltApexTag = bootclasspathFragmentContentDepTag
 
 func IsBootclasspathFragmentContentDepTag(tag blueprint.DependencyTag) bool {
@@ -112,7 +119,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
@@ -296,8 +303,12 @@
 	return m
 }
 
-func (m *BootclasspathFragmentModule) bootclasspathFragmentPropertyCheck(ctx android.EarlyModuleContext) {
-	contents := m.properties.Contents
+func (m *BootclasspathFragmentModule) UniqueApexVariations() bool {
+	return true
+}
+
+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
@@ -399,11 +410,17 @@
 	return i.profileInstallPathInApex
 }
 
-func (b *BootclasspathFragmentModule) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
-	tag := ctx.OtherModuleDependencyTag(dep)
+func (m *BootclasspathFragmentModule) GetDepInSameApexChecker() android.DepInSameApexChecker {
+	return BootclasspathFragmentDepInSameApexChecker{}
+}
 
+type BootclasspathFragmentDepInSameApexChecker struct {
+	android.BaseDepInSameApexChecker
+}
+
+func (b BootclasspathFragmentDepInSameApexChecker) OutgoingDepIsInSameApex(tag blueprint.DependencyTag) bool {
 	// If the module is a default module, do not check the tag
-	if _, ok := dep.(*Defaults); ok {
+	if tag == android.DefaultsDepTag {
 		return true
 	}
 	if IsBootclasspathFragmentContentDepTag(tag) {
@@ -414,13 +431,27 @@
 		// Cross-cutting metadata dependencies are metadata.
 		return false
 	}
+	if tag == moduleInFragmentDepTag {
+		return true
+	}
 	// Dependency to the bootclasspath fragment of another apex
 	// e.g. concsrypt-bootclasspath-fragment --> art-bootclasspath-fragment
-	if tag == bootclasspathFragmentDepTag {
+	if bcpTag, ok := tag.(bootclasspathDependencyTag); ok && bcpTag.typ == fragment {
 		return false
-
 	}
-	panic(fmt.Errorf("boot_image module %q should not have a dependency on %q via tag %s", b, dep, android.PrettyPrintTag(tag)))
+	if tag == moduleInFragmentDepTag {
+		return false
+	}
+	if tag == dexpreopt.Dex2oatDepTag {
+		return false
+	}
+	if tag == android.PrebuiltDepTag {
+		return false
+	}
+	if _, ok := tag.(hiddenAPIStubsDependencyTag); ok {
+		return false
+	}
+	panic(fmt.Errorf("boot_image module should not have a dependency tag %s", android.PrettyPrintTag(tag)))
 }
 
 func (b *BootclasspathFragmentModule) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion android.ApiLevel) error {
@@ -435,7 +466,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.
 		//
@@ -462,24 +493,24 @@
 		}
 	}
 
-	if !dexpreopt.IsDex2oatNeeded(ctx) {
-		return
+	if dexpreopt.IsDex2oatNeeded(ctx) {
+		// Add a dependency onto the dex2oat tool which is needed for creating the boot image. The
+		// path is retrieved from the dependency by GetGlobalSoongConfig(ctx).
+		dexpreopt.RegisterToolDeps(ctx)
 	}
 
-	// Add a dependency onto the dex2oat tool which is needed for creating the boot image. The
-	// path is retrieved from the dependency by GetGlobalSoongConfig(ctx).
-	dexpreopt.RegisterToolDeps(ctx)
-
 	// Add a dependency to `all_apex_contributions` to determine if prebuilts are active.
 	// If prebuilts are active, `contents` validation on the source bootclasspath fragment should be disabled.
 	if _, isPrebuiltModule := ctx.Module().(*PrebuiltBootclasspathFragmentModule); !isPrebuiltModule {
 		ctx.AddDependency(b, android.AcDepTag, "all_apex_contributions")
 	}
-}
 
-func (b *BootclasspathFragmentModule) BootclasspathDepsMutator(ctx android.BottomUpMutatorContext) {
 	// Add dependencies on all the fragments.
 	b.properties.BootclasspathFragmentsDepsProperties.addDependenciesOntoFragments(ctx)
+
+	for _, name := range b.properties.Contents.GetOrDefault(ctx, nil) {
+		ctx.AddDependency(ctx.Module(), moduleInFragmentDepTag, name)
+	}
 }
 
 func (b *BootclasspathFragmentModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -502,7 +533,7 @@
 		}
 	})
 
-	fragments := gatherApexModulePairDepsWithTag(ctx, bootclasspathFragmentDepTag)
+	fragments, _ := gatherFragments(ctx)
 
 	// Perform hidden API processing.
 	hiddenAPIOutput := b.generateHiddenAPIBuildActions(ctx, contents, fragments)
@@ -521,10 +552,9 @@
 	// 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()
 	}
-	android.SetProvider(ctx, testing.TestModuleProviderKey, testing.TestModuleProviderData{})
 }
 
 // getProfileProviderApex returns the name of the apex that provides a boot image profile, or an
@@ -536,19 +566,18 @@
 	}
 
 	// Bootclasspath fragment modules that are for the platform do not produce boot related files.
-	apexInfos, _ := android.ModuleProvider(ctx, android.AllApexInfoProvider)
-	if apexInfos == nil {
+	apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
+	if apexInfo.IsForPlatform() {
 		return ""
 	}
 
-	for _, apexInfo := range apexInfos.ApexInfos {
-		for _, apex := range apexInfo.InApexVariants {
-			if isProfileProviderApex(ctx, apex) {
-				return apex
+	for _, config := range genBootImageConfigs(ctx) {
+		if config.profileProviderModule == b.BaseModuleName() {
+			if len(config.profileImports) > 0 {
+				return config.profileImports[0]
 			}
 		}
 	}
-
 	return ""
 }
 
@@ -590,7 +619,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
@@ -602,7 +631,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.
@@ -612,7 +641,7 @@
 			if android.IsModulePrebuilt(ctx.Module()) {
 				// prebuilt bcpf. the validation of this will be done at the top-level apex
 				providerClasspathFragmentValidationInfoProvider(ctx, unknown)
-			} else if !disableSourceApexVariant(ctx) {
+			} else if !disableSourceApexVariant(ctx) && android.IsModulePreferred(ctx.Module()) {
 				// source bcpf, and prebuilt apex are not selected.
 				ctx.ModuleErrorf("%s in contents must also be declared in PRODUCT_APEX_BOOT_JARS", unknown)
 			}
@@ -849,7 +878,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 {
@@ -925,7 +954,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()
@@ -1148,6 +1177,13 @@
 	android.InitPrebuiltModule(m, &[]string{"placeholder"})
 	android.InitApexModule(m)
 	android.InitAndroidArchModule(m, android.HostAndDeviceSupported, android.MultilibCommon)
+	android.InitDefaultableModule(m)
+
+	m.SetDefaultableHook(func(mctx android.DefaultableHookContext) {
+		if mctx.Config().AlwaysUsePrebuiltSdks() {
+			m.prebuilt.ForcePrefer()
+		}
+	})
 
 	return m
 }
diff --git a/java/bootclasspath_fragment_test.go b/java/bootclasspath_fragment_test.go
index 60f1a50..87b853c 100644
--- a/java/bootclasspath_fragment_test.go
+++ b/java/bootclasspath_fragment_test.go
@@ -31,6 +31,7 @@
 )
 
 func TestBootclasspathFragment_UnknownImageName(t *testing.T) {
+	t.Parallel()
 	prepareForTestWithBootclasspathFragment.
 		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
 			`\Qimage_name: unknown image name "unknown", expected "art"\E`)).
@@ -50,6 +51,7 @@
 }
 
 func TestPrebuiltBootclasspathFragment_UnknownImageName(t *testing.T) {
+	t.Parallel()
 	prepareForTestWithBootclasspathFragment.
 		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
 			`\Qimage_name: unknown image name "unknown", expected "art"\E`)).
@@ -68,6 +70,7 @@
 }
 
 func TestBootclasspathFragmentInconsistentArtConfiguration_Platform(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		prepareForTestWithBootclasspathFragment,
 		dexpreopt.FixtureSetArtBootJars("platform:foo", "apex:bar"),
@@ -99,6 +102,7 @@
 }
 
 func TestBootclasspathFragmentInconsistentArtConfiguration_ApexMixture(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		prepareForTestWithBootclasspathFragment,
 		dexpreopt.FixtureSetArtBootJars("apex1:foo", "apex2:bar"),
@@ -131,6 +135,7 @@
 }
 
 func TestBootclasspathFragment_Coverage(t *testing.T) {
+	t.Parallel()
 	prepareWithBp := android.FixtureWithRootAndroidBp(`
 		bootclasspath_fragment {
 			name: "myfragment",
@@ -191,7 +196,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(
@@ -203,11 +209,13 @@
 	)
 
 	t.Run("without coverage", func(t *testing.T) {
+		t.Parallel()
 		result := preparer.RunTest(t)
 		checkContents(t, result, "mybootlib")
 	})
 
 	t.Run("with coverage", func(t *testing.T) {
+		t.Parallel()
 		result := android.GroupFixturePreparers(
 			prepareForTestWithFrameworkJacocoInstrumentation,
 			preparer,
@@ -363,7 +371,7 @@
 		}
 	`)
 
-	fragment := result.ModuleForTests("myfragment", "android_common")
+	fragment := result.ModuleForTests(t, "myfragment", "android_common")
 	dependencyStubDexFlag := "--dependency-stub-dex=out/soong/.intermediates/default/java/android-non-updatable.stubs.test_module_lib/android_common/dex/android-non-updatable.stubs.test_module_lib.jar"
 	stubFlagsCommand := fragment.Output("modular-hiddenapi/stub-flags.csv").RuleParams.Command
 	android.AssertStringDoesContain(t,
@@ -471,7 +479,7 @@
 
 	// Make sure that the signature-patterns.csv is passed all the appropriate package properties
 	// from the bootclasspath_fragment and its contents.
-	fragment := result.ModuleForTests("mybootclasspathfragment", "android_common")
+	fragment := result.ModuleForTests(t, "mybootclasspathfragment", "android_common")
 	rule := fragment.Output("modular-hiddenapi/signature-patterns.csv")
 	expectedCommand := strings.Join([]string{
 		"--split-package newlibrary",
diff --git a/java/builder.go b/java/builder.go
index e5d5109..ade9784 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -46,6 +46,7 @@
 				`mkdir -p "$outDir" "$annoDir" "$srcJarDir" && ` +
 				`${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` +
 				`(if [ -s $srcJarDir/list ] || [ -s $out.rsp ] ; then ` +
+				`${config.FindInputDeltaCmd} --template '' --target "$out" --inputs_file "$out.rsp" && ` +
 				`${config.SoongJavacWrapper} $javaTemplate${config.JavacCmd} ` +
 				`${config.JavacHeapFlags} ${config.JavacVmFlags} ${config.CommonJdkFlags} ` +
 				`$processorpath $processor $javacFlags $bootClasspath $classpath ` +
@@ -55,8 +56,10 @@
 				`$zipTemplate${config.SoongZipCmd} -jar -o $out.tmp -C $outDir -D $outDir && ` +
 				`if ! cmp -s "$out.tmp" "$out"; then mv "$out.tmp" "$out"; fi && ` +
 				`if ! cmp -s "$annoSrcJar.tmp" "$annoSrcJar"; then mv "$annoSrcJar.tmp" "$annoSrcJar"; fi && ` +
+				`if [ -f "$out.pc_state.new" ]; then mv "$out.pc_state.new" "$out.pc_state"; fi && ` +
 				`rm -rf "$srcJarDir" "$outDir"`,
 			CommandDeps: []string{
+				"${config.FindInputDeltaCmd}",
 				"${config.JavacCmd}",
 				"${config.SoongZipCmd}",
 				"${config.ZipSyncCmd}",
@@ -165,7 +168,7 @@
 				"${config.JavaCmd}",
 			},
 			Rspfile:        "$out.rsp",
-			RspfileContent: "$in",
+			RspfileContent: "$in_newline",
 			Restat:         true,
 		},
 		&remoteexec.REParams{Labels: map[string]string{"type": "tool", "name": "turbine"},
@@ -223,6 +226,12 @@
 		},
 		"jarArgs")
 
+	extractR8Rules = pctx.AndroidStaticRule("extractR8Rules",
+		blueprint.RuleParams{
+			Command:     `${config.ExtractR8RulesCmd} --rules-output $out --include-origin-comments $in`,
+			CommandDeps: []string{"${config.ExtractR8RulesCmd}"},
+		})
+
 	jarjar = pctx.AndroidStaticRule("jarjar",
 		blueprint.RuleParams{
 			Command: "" +
@@ -235,12 +244,12 @@
 				// for newly repackaged classes. Dropping @UnsupportedAppUsage on repackaged classes
 				// avoids adding new hiddenapis after jarjar'ing.
 				" -DremoveAndroidCompatAnnotations=true" +
-				" -jar ${config.JarjarCmd} process $rulesFile $in $out && " +
+				" -jar ${config.JarjarCmd} process $rulesFile $in $out $total_shards $shard_index && " +
 				// Turn a missing output file into a ninja error
 				`[ -e ${out} ] || (echo "Missing output file"; exit 1)`,
 			CommandDeps: []string{"${config.JavaCmd}", "${config.JarjarCmd}", "$rulesFile"},
 		},
-		"rulesFile")
+		"rulesFile", "total_shards", "shard_index")
 
 	packageCheck = pctx.AndroidStaticRule("packageCheck",
 		blueprint.RuleParams{
@@ -301,7 +310,7 @@
 
 	gatherReleasedFlaggedApisRule = pctx.AndroidStaticRule("gatherReleasedFlaggedApisRule",
 		blueprint.RuleParams{
-			Command: `${aconfig} dump-cache --dedup --format='{fully_qualified_name}={state:bool}' ` +
+			Command: `${aconfig} dump-cache --dedup --format='{fully_qualified_name}' ` +
 				`--out ${out} ` +
 				`${flags_path} ` +
 				`${filter_args} `,
@@ -314,6 +323,13 @@
 			Command:     `${keep-flagged-apis} ${in} > ${out}`,
 			CommandDeps: []string{"${keep-flagged-apis}"},
 		})
+
+	generateApiXMLRule = pctx.AndroidStaticRule("generateApiXMLRule",
+		blueprint.RuleParams{
+			Command:     `${config.JavaCmd} ${config.JavaVmFlags} -Xmx4g -jar ${config.MetalavaJar} jar-to-jdiff ${in} ${out}`,
+			CommandDeps: []string{"${config.JavaCmd}", "${config.MetalavaJar}"},
+			Description: "Converting API file to XML",
+		})
 )
 
 func init() {
@@ -456,9 +472,10 @@
 	const srcJarArgsLimit = 32 * 1024
 	if len(srcJarArgs) > srcJarArgsLimit {
 		srcJarRspFile := android.PathForModuleOut(ctx, "turbine", "srcjars.rsp")
-		android.WriteFileRule(ctx, srcJarRspFile, srcJarArgs)
+		android.WriteFileRule(ctx, srcJarRspFile, strings.Join(srcJars.Strings(), "\n"))
 		srcJarArgs = "@" + srcJarRspFile.String()
 		implicits = append(implicits, srcJarRspFile)
+		rspFiles = append(rspFiles, srcJarRspFile)
 		rbeInputs = append(rbeInputs, srcJarRspFile)
 	} else {
 		rbeInputs = append(rbeInputs, srcJars...)
@@ -488,7 +505,7 @@
 	const classpathLimit = 32 * 1024
 	if len(classpathFlags) > classpathLimit {
 		classpathRspFile := android.PathForModuleOut(ctx, dir, "classpath.rsp")
-		android.WriteFileRule(ctx, classpathRspFile, classpathFlags)
+		android.WriteFileRule(ctx, classpathRspFile, strings.Join(classpath.Strings(), "\n"))
 		classpathFlags = "@" + classpathRspFile.String()
 		implicits = append(implicits, classpathRspFile)
 		rspFiles = append(rspFiles, classpathRspFile)
@@ -736,6 +753,16 @@
 	})
 }
 
+func TransformJarToR8Rules(ctx android.ModuleContext, outputFile android.WritablePath,
+	jar android.Path) {
+
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   extractR8Rules,
+		Output: outputFile,
+		Input:  jar,
+	})
+}
+
 func convertImplementationJarToHeaderJar(ctx android.ModuleContext, implementationJarFile android.Path,
 	headerJarFile android.WritablePath) {
 	ctx.Build(pctx, android.BuildParams{
@@ -747,16 +774,58 @@
 
 func TransformJarJar(ctx android.ModuleContext, outputFile android.WritablePath,
 	classesJar android.Path, rulesFile android.Path) {
+	TransformJarJarWithShards(ctx, outputFile, classesJar, rulesFile, 1)
+}
+
+func TransformJarJarWithShards(ctx android.ModuleContext, outputFile android.WritablePath,
+	classesJar android.Path, rulesFile android.Path, totalShards int) {
+
+	// If the total number of shards is 1, just run jarjar as-is, with `total_shards` = 1
+	// and `shard_index` == 0, which effectively disables sharding
+	if totalShards == 1 {
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        jarjar,
+			Description: "jarjar",
+			Output:      outputFile,
+			Input:       classesJar,
+			Implicit:    rulesFile,
+			Args: map[string]string{
+				"rulesFile":    rulesFile.String(),
+				"total_shards": "1",
+				"shard_index":  "0",
+			},
+		})
+		return
+	}
+
+	// Otherwise, run multiple jarjar instances and use merge_zips to combine the output.
+	tempJars := make([]android.Path, 0)
+	totalStr := strconv.Itoa(totalShards)
+	for i := 0; i < totalShards; i++ {
+		iStr := strconv.Itoa(i)
+		tempOut := outputFile.ReplaceExtension(ctx, "-"+iStr+".jar")
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        jarjar,
+			Description: "jarjar (" + iStr + "/" + totalStr + ")",
+			Output:      tempOut,
+			Input:       classesJar,
+			Implicit:    rulesFile,
+			Args: map[string]string{
+				"rulesFile":    rulesFile.String(),
+				"total_shards": totalStr,
+				"shard_index":  iStr,
+			},
+		})
+		tempJars = append(tempJars, tempOut)
+	}
+
 	ctx.Build(pctx, android.BuildParams{
-		Rule:        jarjar,
-		Description: "jarjar",
+		Rule:        combineJar,
+		Description: "merge jarjar shards",
 		Output:      outputFile,
-		Input:       classesJar,
-		Implicit:    rulesFile,
-		Args: map[string]string{
-			"rulesFile": rulesFile.String(),
-		},
+		Inputs:      tempJars,
 	})
+
 }
 
 func CheckJarPackages(ctx android.ModuleContext, outputFile android.WritablePath,
diff --git a/java/classpath_element.go b/java/classpath_element.go
index abbcae7..4af2770 100644
--- a/java/classpath_element.go
+++ b/java/classpath_element.go
@@ -108,33 +108,18 @@
 //
 // e.g. Given the following input:
 //
-//	libraries: com.android.art:core-oj, com.android.art:core-libart, framework, ext
-//	fragments: com.android.art:art-bootclasspath-fragment
+//		libraries: core-oj, core-libart, framework, ext
+//		fragments: art-bootclasspath-fragment
+//	    libraryToApex: core-oj: com.android.art, core-libart: com.android.art
+//	    apexNameToFragment: com.android.art: art-bootclasspath-fragment
 //
 // Then this will return:
 //
 //	ClasspathFragmentElement(art-bootclasspath-fragment, [core-oj, core-libart]),
 //	ClasspathLibraryElement(framework),
 //	ClasspathLibraryElement(ext),
-func CreateClasspathElements(ctx ClasspathElementContext, libraries []android.Module, fragments []android.Module) ClasspathElements {
-	// Create a map from apex name to the fragment module. This makes it easy to find the fragment
-	// associated with a particular apex.
-	apexToFragment := map[string]android.Module{}
-	for _, fragment := range fragments {
-		apexInfo, ok := android.OtherModuleProvider(ctx, fragment, android.ApexInfoProvider)
-		if !ok {
-			ctx.ModuleErrorf("fragment %s is not part of an apex", fragment)
-			continue
-		}
-
-		for _, apex := range apexInfo.InApexVariants {
-			if existing, ok := apexToFragment[apex]; ok {
-				ctx.ModuleErrorf("apex %s has multiple fragments, %s and %s", apex, fragment, existing)
-				continue
-			}
-			apexToFragment[apex] = fragment
-		}
-	}
+func CreateClasspathElements(ctx ClasspathElementContext, libraries []android.Module, fragments []android.Module,
+	libraryToApex map[android.Module]string, apexNameToFragment map[string]android.Module) ClasspathElements {
 
 	fragmentToElement := map[android.Module]*ClasspathFragmentElement{}
 	elements := []ClasspathElement{}
@@ -144,31 +129,28 @@
 	// Iterate over the libraries to construct the ClasspathElements list.
 	for _, library := range libraries {
 		var element ClasspathElement
-		if apexInfo, ok := android.OtherModuleProvider(ctx, library, android.ApexInfoProvider); ok {
-
+		if libraryApex, ok := libraryToApex[library]; ok {
 			var fragment android.Module
 
 			// Make sure that the library is in only one fragment of the classpath.
-			for _, apex := range apexInfo.InApexVariants {
-				if f, ok := apexToFragment[apex]; ok {
-					if fragment == nil {
-						// This is the first fragment so just save it away.
-						fragment = f
-					} else if f != fragment {
-						// This apex variant of the library is in a different fragment.
-						ctx.ModuleErrorf("library %s is in two separate fragments, %s and %s", library, fragment, f)
-						// Skip over this library entirely as otherwise the resulting classpath elements would
-						// be invalid.
-						continue skipLibrary
-					}
-				} else {
-					// There is no fragment associated with the library's apex.
+			if f, ok := apexNameToFragment[libraryApex]; ok {
+				if fragment == nil {
+					// This is the first fragment so just save it away.
+					fragment = f
+				} else if f != fragment {
+					// This apex variant of the library is in a different fragment.
+					ctx.ModuleErrorf("library %s is in two separate fragments, %s and %s", library, fragment, f)
+					// Skip over this library entirely as otherwise the resulting classpath elements would
+					// be invalid.
+					continue skipLibrary
 				}
+			} else {
+				// There is no fragment associated with the library's apex.
 			}
 
 			if fragment == nil {
 				ctx.ModuleErrorf("library %s is from apexes %s which have no corresponding fragment in %s",
-					library, apexInfo.InApexVariants, fragments)
+					library, []string{libraryApex}, fragments)
 				// Skip over this library entirely as otherwise the resulting classpath elements would
 				// be invalid.
 				continue skipLibrary
diff --git a/java/classpath_fragment.go b/java/classpath_fragment.go
index 18a5dae..88a8fb8 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,11 @@
 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)
+		if dep == nil && ctx.Config().AllowMissingDependencies() {
+			// Ignore apex boot jars from dexpreopt if it does not exist, and missing deps are allowed.
+			continue
+		}
 		set[ModuleStemForDeapexing(dep)] = struct{}{}
 		if m, ok := dep.(ModuleWithStem); ok {
 			set[m.Stem()] = struct{}{}
diff --git a/java/code_metadata_test.go b/java/code_metadata_test.go
deleted file mode 100644
index 9dc9a22..0000000
--- a/java/code_metadata_test.go
+++ /dev/null
@@ -1,115 +0,0 @@
-package java
-
-import (
-	"strings"
-	"testing"
-
-	"android/soong/android"
-	soongTesting "android/soong/testing"
-	"android/soong/testing/code_metadata_internal_proto"
-
-	"google.golang.org/protobuf/proto"
-)
-
-func TestCodeMetadata(t *testing.T) {
-	bp := `code_metadata {
-		name: "module-name",
-		teamId: "12345",
-		code: [
-			"foo",
-		]
-	}
-
-	java_sdk_library {
-		name: "foo",
-		srcs: ["a.java"],
-	}`
-	result := runCodeMetadataTest(t, android.FixtureExpectsNoErrors, bp)
-
-	module := result.ModuleForTests("module-name", "")
-
-	// Check that the provider has the right contents
-	data, _ := android.OtherModuleProvider(result, module.Module(), soongTesting.CodeMetadataProviderKey)
-	if !strings.HasSuffix(
-		data.IntermediatePath.String(), "/intermediateCodeMetadata.pb",
-	) {
-		t.Errorf(
-			"Missing intermediates path in provider: %s",
-			data.IntermediatePath.String(),
-		)
-	}
-
-	metadata := android.ContentFromFileRuleForTests(t, result.TestContext,
-		module.Output(data.IntermediatePath.String()))
-
-	metadataList := make([]*code_metadata_internal_proto.CodeMetadataInternal_TargetOwnership, 0, 2)
-	teamId := "12345"
-	bpFilePath := "Android.bp"
-	targetName := "foo"
-	srcFile := []string{"a.java"}
-	expectedMetadataProto := code_metadata_internal_proto.CodeMetadataInternal_TargetOwnership{
-		TrendyTeamId: &teamId,
-		TargetName:   &targetName,
-		Path:         &bpFilePath,
-		SourceFiles:  srcFile,
-	}
-	metadataList = append(metadataList, &expectedMetadataProto)
-
-	CodeMetadataMetadata := code_metadata_internal_proto.CodeMetadataInternal{TargetOwnershipList: metadataList}
-	protoData, _ := proto.Marshal(&CodeMetadataMetadata)
-	expectedMetadata := string(protoData)
-
-	if metadata != expectedMetadata {
-		t.Errorf(
-			"Retrieved metadata: %s is not equal to expectedMetadata: %s", metadata,
-			expectedMetadata,
-		)
-	}
-
-	// Tests for all_test_spec singleton.
-	singleton := result.SingletonForTests("all_code_metadata")
-	rule := singleton.Rule("all_code_metadata_rule")
-	prebuiltOs := result.Config.PrebuiltOS()
-	expectedCmd := "out/soong/host/" + prebuiltOs + "/bin/metadata -rule code_metadata -inputFile out/soong/all_code_metadata_paths.rsp -outputFile out/soong/ownership/all_code_metadata.pb"
-	expectedOutputFile := "out/soong/ownership/all_code_metadata.pb"
-	expectedInputFile := "out/soong/.intermediates/module-name/intermediateCodeMetadata.pb"
-	if !strings.Contains(
-		strings.TrimSpace(rule.Output.String()),
-		expectedOutputFile,
-	) {
-		t.Errorf(
-			"Retrieved singletonOutputFile: %s is not equal to expectedSingletonOutputFile: %s",
-			rule.Output.String(), expectedOutputFile,
-		)
-	}
-
-	if !strings.Contains(
-		strings.TrimSpace(rule.Inputs[0].String()),
-		expectedInputFile,
-	) {
-		t.Errorf(
-			"Retrieved singletonInputFile: %s is not equal to expectedSingletonInputFile: %s",
-			rule.Inputs[0].String(), expectedInputFile,
-		)
-	}
-
-	if !strings.Contains(
-		strings.TrimSpace(rule.RuleParams.Command),
-		expectedCmd,
-	) {
-		t.Errorf(
-			"Retrieved cmd: %s doesn't contain expectedCmd: %s",
-			rule.RuleParams.Command, expectedCmd,
-		)
-	}
-}
-func runCodeMetadataTest(
-	t *testing.T, errorHandler android.FixtureErrorHandler, bp string,
-) *android.TestResult {
-	return android.GroupFixturePreparers(
-		soongTesting.PrepareForTestWithTestingBuildComponents, prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles, FixtureWithLastReleaseApis("foo"),
-	).
-		ExtendWithErrorHandler(errorHandler).
-		RunTestWithBp(t, bp)
-}
diff --git a/java/config/config.go b/java/config/config.go
index 87703d8..71025de 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -159,6 +159,7 @@
 	pctx.SourcePathVariable("ResourceProcessorBusyBox", "prebuilts/bazel/common/android_tools/android_tools/all_android_tools_deploy.jar")
 
 	pctx.HostBinToolVariable("GenKotlinBuildFileCmd", "gen-kotlin-build-file")
+	pctx.HostBinToolVariable("FindInputDeltaCmd", "find_input_delta")
 
 	pctx.SourcePathVariable("JarArgsCmd", "build/soong/scripts/jar-args.sh")
 	pctx.SourcePathVariable("PackageCheckCmd", "build/soong/scripts/package-check.sh")
@@ -170,6 +171,7 @@
 	pctx.HostBinToolVariable("ApiCheckCmd", "apicheck")
 	pctx.HostBinToolVariable("D8Cmd", "d8")
 	pctx.HostBinToolVariable("R8Cmd", "r8")
+	pctx.HostBinToolVariable("ExtractR8RulesCmd", "extract-r8-rules")
 	pctx.HostBinToolVariable("ResourceShrinkerCmd", "resourceshrinker")
 	pctx.HostBinToolVariable("HiddenAPICmd", "hiddenapi")
 	pctx.HostBinToolVariable("ExtractApksCmd", "extract_apks")
diff --git a/java/config/kotlin.go b/java/config/kotlin.go
index 302d021..ffb025d 100644
--- a/java/config/kotlin.go
+++ b/java/config/kotlin.go
@@ -21,6 +21,7 @@
 	KotlincIllegalFlags = []string{
 		"-no-jdk",
 		"-no-stdlib",
+		"-language-version",
 	}
 )
 
@@ -56,5 +57,5 @@
 	// platform that are not fully compatible with the kotlinc used in g3 kythe indexers.
 	// e.g. uninitialized variables are a warning in 1.*, but an error in 2.*
 	// https://github.com/JetBrains/kotlin/blob/master/compiler/fir/checkers/gen/org/jetbrains/kotlin/fir/analysis/diagnostics/FirErrors.kt#L748
-	pctx.StaticVariable("KotlincKytheGlobalFlags", strings.Join([]string{"-language-version 1.9"}, " "))
+	pctx.StaticVariable("KotlincKytheGlobalFlags", strings.Join([]string{}, " "))
 }
diff --git a/java/container_test.go b/java/container_test.go
index 25cfa4c..35a3020 100644
--- a/java/container_test.go
+++ b/java/container_test.go
@@ -26,6 +26,7 @@
 }
 
 func TestJavaContainersModuleProperties(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
 	).RunTestWithBp(t, `
@@ -154,7 +155,7 @@
 	}
 
 	for _, c := range testcases {
-		m := result.ModuleForTests(c.moduleName, "android_common")
+		m := result.ModuleForTests(t, c.moduleName, "android_common")
 		containers, _ := android.OtherModuleProvider(result.TestContext.OtherModuleProviderAdaptor(), m.Module(), android.ContainersInfoProvider)
 		belongingContainers := containers.BelongingContainers()
 		checkContainerMatch(t, c.moduleName, "system", c.isSystemContainer, android.InList(android.SystemContainer, belongingContainers))
diff --git a/java/core-libraries/Android.bp b/java/core-libraries/Android.bp
index 1cca7ad..8c808e4 100644
--- a/java/core-libraries/Android.bp
+++ b/java/core-libraries/Android.bp
@@ -41,10 +41,50 @@
     is_stubs_module: true,
 }
 
+soong_config_module_type {
+    name: "core_current_stubs_soong_config_defaults",
+    module_type: "java_defaults",
+    config_namespace: "ANDROID",
+    bool_variables: [
+        "release_hidden_api_exportable_stubs",
+    ],
+    properties: [
+        "dist.targets",
+        "dist.dest",
+    ],
+}
+
+core_current_stubs_soong_config_defaults {
+    name: "core_current_stubs_everything_soong_config_defaults",
+    soong_config_variables: {
+        release_hidden_api_exportable_stubs: {
+            conditions_default: {
+                dist: {
+                    targets: dist_targets,
+                    dest: "core.current.stubs.jar",
+                },
+            },
+        },
+    },
+}
+
+core_current_stubs_soong_config_defaults {
+    name: "core_current_stubs_exportable_soong_config_defaults",
+    soong_config_variables: {
+        release_hidden_api_exportable_stubs: {
+            dist: {
+                targets: dist_targets,
+                dest: "core.current.stubs.jar",
+            },
+        },
+    },
+}
+
 java_library {
     name: "core.current.stubs",
     defaults: [
         "core.current.stubs.defaults",
+        "core_current_stubs_everything_soong_config_defaults",
     ],
     static_libs: [
         "art.module.public.api.stubs",
@@ -76,16 +116,13 @@
     name: "core.current.stubs.exportable",
     defaults: [
         "core.current.stubs.defaults",
+        "core_current_stubs_exportable_soong_config_defaults",
     ],
     static_libs: [
         "art.module.public.api.stubs.exportable",
         "conscrypt.module.public.api.stubs.exportable",
         "i18n.module.public.api.stubs.exportable",
     ],
-    dist: {
-        targets: dist_targets,
-        dest: "core.current.stubs.jar",
-    },
 }
 
 // Distributed with the SDK for turning into system modules to compile apps
@@ -95,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",
@@ -118,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/core-libraries/jarjar-strip-annotations-rules.txt b/java/core-libraries/jarjar-strip-annotations-rules.txt
index a1c261b..c74eaca 100644
--- a/java/core-libraries/jarjar-strip-annotations-rules.txt
+++ b/java/core-libraries/jarjar-strip-annotations-rules.txt
@@ -2,3 +2,4 @@
 strip-annotation android.annotation.Nullable
 strip-annotation androidx.annotation.RecentlyNonNull
 strip-annotation androidx.annotation.RecentlyNullable
+strip-annotation android.annotation.FlaggedApi
diff --git a/java/device_host_converter.go b/java/device_host_converter.go
index 3f4e3cd..04def3e 100644
--- a/java/device_host_converter.go
+++ b/java/device_host_converter.go
@@ -20,6 +20,8 @@
 
 	"android/soong/android"
 	"android/soong/dexpreopt"
+
+	"github.com/google/blueprint/depset"
 )
 
 type DeviceHostConverter struct {
@@ -96,9 +98,9 @@
 		ctx.PropertyErrorf("libs", "at least one dependency is required")
 	}
 
-	var transitiveHeaderJars []*android.DepSet[android.Path]
-	var transitiveImplementationJars []*android.DepSet[android.Path]
-	var transitiveResourceJars []*android.DepSet[android.Path]
+	var transitiveHeaderJars []depset.DepSet[android.Path]
+	var transitiveImplementationJars []depset.DepSet[android.Path]
+	var transitiveResourceJars []depset.DepSet[android.Path]
 
 	ctx.VisitDirectDepsWithTag(deviceHostConverterDepTag, func(m android.Module) {
 		if dep, ok := android.OtherModuleProvider(ctx, m, JavaInfoProvider); ok {
@@ -110,15 +112,9 @@
 			d.srcJarArgs = append(d.srcJarArgs, dep.SrcJarArgs...)
 			d.srcJarDeps = append(d.srcJarDeps, dep.SrcJarDeps...)
 
-			if dep.TransitiveStaticLibsHeaderJars != nil {
-				transitiveHeaderJars = append(transitiveHeaderJars, dep.TransitiveStaticLibsHeaderJars)
-			}
-			if dep.TransitiveStaticLibsImplementationJars != nil {
-				transitiveImplementationJars = append(transitiveImplementationJars, dep.TransitiveStaticLibsImplementationJars)
-			}
-			if dep.TransitiveStaticLibsResourceJars != nil {
-				transitiveResourceJars = append(transitiveResourceJars, dep.TransitiveStaticLibsResourceJars)
-			}
+			transitiveHeaderJars = append(transitiveHeaderJars, dep.TransitiveStaticLibsHeaderJars)
+			transitiveImplementationJars = append(transitiveImplementationJars, dep.TransitiveStaticLibsImplementationJars)
+			transitiveResourceJars = append(transitiveResourceJars, dep.TransitiveStaticLibsResourceJars)
 		} else {
 			ctx.PropertyErrorf("libs", "module %q cannot be used as a dependency", ctx.OtherModuleName(m))
 		}
@@ -144,12 +140,12 @@
 		d.combinedHeaderJar = d.headerJars[0]
 	}
 
-	android.SetProvider(ctx, JavaInfoProvider, &JavaInfo{
+	javaInfo := &JavaInfo{
 		HeaderJars:                             d.headerJars,
 		LocalHeaderJars:                        d.headerJars,
-		TransitiveStaticLibsHeaderJars:         android.NewDepSet(android.PREORDER, nil, transitiveHeaderJars),
-		TransitiveStaticLibsImplementationJars: android.NewDepSet(android.PREORDER, nil, transitiveImplementationJars),
-		TransitiveStaticLibsResourceJars:       android.NewDepSet(android.PREORDER, nil, transitiveResourceJars),
+		TransitiveStaticLibsHeaderJars:         depset.New(depset.PREORDER, nil, transitiveHeaderJars),
+		TransitiveStaticLibsImplementationJars: depset.New(depset.PREORDER, nil, transitiveImplementationJars),
+		TransitiveStaticLibsResourceJars:       depset.New(depset.PREORDER, nil, transitiveResourceJars),
 		ImplementationAndResourcesJars:         d.implementationAndResourceJars,
 		ImplementationJars:                     d.implementationJars,
 		ResourceJars:                           d.resourceJars,
@@ -158,7 +154,9 @@
 		StubsLinkType:                          Implementation,
 		// TODO: Not sure if aconfig flags that have been moved between device and host variants
 		// make sense.
-	})
+	}
+	setExtraJavaInfo(ctx, d, javaInfo)
+	android.SetProvider(ctx, JavaInfoProvider, javaInfo)
 
 }
 
diff --git a/java/device_host_converter_test.go b/java/device_host_converter_test.go
index 6ccc5c1..197bb9f 100644
--- a/java/device_host_converter_test.go
+++ b/java/device_host_converter_test.go
@@ -22,6 +22,7 @@
 )
 
 func TestDeviceForHost(t *testing.T) {
+	t.Parallel()
 	bp := `
 		java_library {
 			name: "device_module",
@@ -52,15 +53,15 @@
 
 	ctx, config := testJava(t, bp)
 
-	deviceModule := ctx.ModuleForTests("device_module", "android_common")
+	deviceModule := ctx.ModuleForTests(t, "device_module", "android_common")
 	deviceTurbineCombined := deviceModule.Output("turbine-combined/device_module.jar")
 	deviceJavac := deviceModule.Output("javac/device_module.jar")
 	deviceRes := deviceModule.Output("res/device_module.jar")
 
-	deviceImportModule := ctx.ModuleForTests("device_import_module", "android_common")
+	deviceImportModule := ctx.ModuleForTests(t, "device_import_module", "android_common")
 	deviceImportCombined := deviceImportModule.Output("combined/device_import_module.jar")
 
-	hostModule := ctx.ModuleForTests("host_module", config.BuildOSCommonTarget.String())
+	hostModule := ctx.ModuleForTests(t, "host_module", config.BuildOSCommonTarget.String())
 	hostJavac := hostModule.Output("javac/host_module.jar")
 	hostRes := hostModule.Output("res/host_module.jar")
 	combined := hostModule.Output("combined/host_module.jar")
@@ -102,6 +103,7 @@
 }
 
 func TestHostForDevice(t *testing.T) {
+	t.Parallel()
 	bp := `
 		java_library_host {
 			name: "host_module",
@@ -133,15 +135,15 @@
 
 	ctx, config := testJava(t, bp)
 
-	hostModule := ctx.ModuleForTests("host_module", config.BuildOSCommonTarget.String())
+	hostModule := ctx.ModuleForTests(t, "host_module", config.BuildOSCommonTarget.String())
 	hostJavac := hostModule.Output("javac/host_module.jar")
 	hostJavacHeader := hostModule.Output("javac-header/host_module.jar")
 	hostRes := hostModule.Output("res/host_module.jar")
 
-	hostImportModule := ctx.ModuleForTests("host_import_module", config.BuildOSCommonTarget.String())
+	hostImportModule := ctx.ModuleForTests(t, "host_import_module", config.BuildOSCommonTarget.String())
 	hostImportCombined := hostImportModule.Output("combined/host_import_module.jar")
 
-	deviceModule := ctx.ModuleForTests("device_module", "android_common")
+	deviceModule := ctx.ModuleForTests(t, "device_module", "android_common")
 	deviceJavac := deviceModule.Output("javac/device_module.jar")
 	deviceRes := deviceModule.Output("res/device_module.jar")
 	combined := deviceModule.Output("combined/device_module.jar")
diff --git a/java/dex.go b/java/dex.go
index a3f699b..64465a2 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -42,11 +42,25 @@
 		// True if the module containing this has it set by default.
 		EnabledByDefault bool `blueprint:"mutated"`
 
+		// Whether to allow that library classes inherit from program classes.
+		// Defaults to false.
+		Ignore_library_extends_program *bool
+
 		// Whether to continue building even if warnings are emitted.  Defaults to true.
 		Ignore_warnings *bool
 
+		// Whether runtime invisible annotations should be kept by R8. Defaults to false.
+		// This is equivalent to:
+		//   -keepattributes RuntimeInvisibleAnnotations,
+		//                   RuntimeInvisibleParameterAnnotations,
+		//                   RuntimeInvisibleTypeAnnotations
+		// This is only applicable when RELEASE_R8_ONLY_RUNTIME_VISIBLE_ANNOTATIONS is
+		// enabled and will be used to migrate away from keeping runtime invisible
+		// annotations (b/387958004).
+		Keep_runtime_invisible_annotations *bool
+
 		// If true, runs R8 in Proguard compatibility mode, otherwise runs R8 in full mode.
-		// Defaults to false for apps, true for libraries and tests.
+		// Defaults to false for apps and tests, true for libraries.
 		Proguard_compatibility *bool
 
 		// If true, optimize for size by removing unused code.  Defaults to true for apps,
@@ -220,21 +234,29 @@
 		deps = append(deps, f)
 	}
 
-	var requestReleaseMode bool
+	var requestReleaseMode, requestDebugMode bool
 	requestReleaseMode, flags = android.RemoveFromList("--release", flags)
+	requestDebugMode, flags = android.RemoveFromList("--debug", flags)
 
 	if ctx.Config().Getenv("NO_OPTIMIZE_DX") != "" || ctx.Config().Getenv("GENERATE_DEX_DEBUG") != "" {
-		flags = append(flags, "--debug")
+		requestDebugMode = true
 		requestReleaseMode = false
 	}
 
 	// Don't strip out debug information for eng builds, unless the target
 	// explicitly provided the `--release` build flag. This allows certain
 	// test targets to remain optimized as part of eng test_suites builds.
-	if requestReleaseMode {
+	if requestDebugMode {
+		flags = append(flags, "--debug")
+	} else if requestReleaseMode {
 		flags = append(flags, "--release")
 	} else if ctx.Config().Eng() {
 		flags = append(flags, "--debug")
+	} else if !d.effectiveOptimizeEnabled() && d.dexProperties.Optimize.EnabledByDefault {
+		// D8 uses --debug by default, whereas R8 uses --release by default.
+		// For targets that default to R8 usage (e.g., apps), but override this default, we still
+		// want D8 to run in release mode, preserving semantics as much as possible between the two.
+		flags = append(flags, "--release")
 	}
 
 	// Supplying the platform build flag disables various features like API modeling and desugaring.
@@ -252,14 +274,11 @@
 	if err != nil {
 		ctx.PropertyErrorf("min_sdk_version", "%s", err)
 	}
-	if !Bool(d.dexProperties.No_dex_container) && effectiveVersion.FinalOrFutureInt() >= 36 {
+	if !Bool(d.dexProperties.No_dex_container) && effectiveVersion.FinalOrFutureInt() >= 36 && ctx.Config().UseDexV41() {
 		// W is 36, but we have not bumped the SDK version yet, so check for both.
 		if ctx.Config().PlatformSdkVersion().FinalInt() >= 36 ||
-			ctx.Config().PlatformSdkCodename() == "Wear" {
-			// TODO(b/329465418): Skip this module since it causes issue with app DRM
-			if ctx.ModuleName() != "framework-minus-apex" {
-				flags = append([]string{"-JDcom.android.tools.r8.dexContainerExperiment"}, flags...)
-			}
+			ctx.Config().PlatformSdkCodename() == "Baklava" {
+			flags = append([]string{"-JDcom.android.tools.r8.dexContainerExperiment"}, flags...)
 		}
 	}
 
@@ -295,7 +314,7 @@
 	return d8Flags, d8Deps, artProfileOutput
 }
 
-func (d *dexer) r8Flags(ctx android.ModuleContext, dexParams *compileDexParams) (r8Flags []string, r8Deps android.Paths, artProfileOutput *android.OutputPath) {
+func (d *dexer) r8Flags(ctx android.ModuleContext, dexParams *compileDexParams, debugMode bool) (r8Flags []string, r8Deps android.Paths, artProfileOutput *android.OutputPath) {
 	flags := dexParams.flags
 	opt := d.dexProperties.Optimize
 
@@ -309,7 +328,7 @@
 	// TODO(b/360905238): Remove SdkSystemServer exception after resolving missing class references.
 	if !dexParams.sdkVersion.Stable() || dexParams.sdkVersion.Kind == android.SdkSystemServer {
 		var proguardRaiseDeps classpath
-		ctx.VisitDirectDepsWithTag(proguardRaiseTag, func(m android.Module) {
+		ctx.VisitDirectDepsProxyWithTag(proguardRaiseTag, func(m android.ModuleProxy) {
 			if dep, ok := android.OtherModuleProvider(ctx, m, JavaInfoProvider); ok {
 				proguardRaiseDeps = append(proguardRaiseDeps, dep.RepackagedHeaderJars...)
 			}
@@ -324,20 +343,16 @@
 	r8Deps = append(r8Deps, flags.dexClasspath...)
 
 	transitiveStaticLibsLookupMap := map[android.Path]bool{}
-	if d.transitiveStaticLibsHeaderJarsForR8 != nil {
-		for _, jar := range d.transitiveStaticLibsHeaderJarsForR8.ToList() {
-			transitiveStaticLibsLookupMap[jar] = true
-		}
+	for _, jar := range d.transitiveStaticLibsHeaderJarsForR8.ToList() {
+		transitiveStaticLibsLookupMap[jar] = true
 	}
 	transitiveHeaderJars := android.Paths{}
-	if d.transitiveLibsHeaderJarsForR8 != nil {
-		for _, jar := range d.transitiveLibsHeaderJarsForR8.ToList() {
-			if _, ok := transitiveStaticLibsLookupMap[jar]; ok {
-				// don't include a lib if it is already packaged in the current JAR as a static lib
-				continue
-			}
-			transitiveHeaderJars = append(transitiveHeaderJars, jar)
+	for _, jar := range d.transitiveLibsHeaderJarsForR8.ToList() {
+		if _, ok := transitiveStaticLibsLookupMap[jar]; ok {
+			// don't include a lib if it is already packaged in the current JAR as a static lib
+			continue
 		}
+		transitiveHeaderJars = append(transitiveHeaderJars, jar)
 	}
 	transitiveClasspath := classpath(transitiveHeaderJars)
 	r8Flags = append(r8Flags, transitiveClasspath.FormJavaClassPath("-libraryjars"))
@@ -363,11 +378,21 @@
 
 	r8Flags = append(r8Flags, opt.Proguard_flags...)
 
-	if BoolDefault(opt.Proguard_compatibility, true) {
+	if BoolDefault(opt.Ignore_library_extends_program, false) {
+		r8Flags = append(r8Flags, "--ignore-library-extends-program")
+	}
+
+	if BoolDefault(opt.Keep_runtime_invisible_annotations, false) {
+		r8Flags = append(r8Flags, "--keep-runtime-invisible-annotations")
+	}
+
+	if BoolDefault(opt.Proguard_compatibility, !ctx.Config().UseR8FullModeByDefault()) {
 		r8Flags = append(r8Flags, "--force-proguard-compatibility")
 	}
 
-	if Bool(opt.Optimize) || Bool(opt.Obfuscate) {
+	// Avoid unnecessary stack frame noise by only injecting source map ids for non-debug
+	// optimized or obfuscated targets.
+	if (Bool(opt.Optimize) || Bool(opt.Obfuscate)) && !debugMode {
 		// TODO(b/213833843): Allow configuration of the prefix via a build variable.
 		var sourceFilePrefix = "go/retraceme "
 		var sourceFileTemplate = "\"" + sourceFilePrefix + "%MAP_ID\""
@@ -404,6 +429,10 @@
 		r8Flags = append(r8Flags, "--resource-output", d.resourcesOutput.Path().String())
 		if d.dexProperties.optimizedResourceShrinkingEnabled(ctx) {
 			r8Flags = append(r8Flags, "--optimized-resource-shrinking")
+			if Bool(d.dexProperties.Optimize.Optimized_shrink_resources) {
+				// Explicitly opted into optimized shrinking, no need for keeping R$id entries
+				r8Flags = append(r8Flags, "--force-optimized-resource-shrinking")
+			}
 		}
 	}
 
@@ -413,6 +442,10 @@
 		artProfileOutput = profileOutput
 	}
 
+	if ctx.Config().UseR8StoreStoreFenceConstructorInlining() {
+		r8Flags = append(r8Flags, "--store-store-fence-constructor-inlining")
+	}
+
 	return r8Flags, r8Deps, artProfileOutput
 }
 
@@ -428,17 +461,18 @@
 // Adds --art-profile to r8/d8 command.
 // r8/d8 will output a generated profile file to match the optimized dex code.
 func (d *dexer) addArtProfile(ctx android.ModuleContext, dexParams *compileDexParams) (flags []string, deps android.Paths, artProfileOutputPath *android.OutputPath) {
-	if dexParams.artProfileInput != nil {
-		artProfileInputPath := android.PathForModuleSrc(ctx, *dexParams.artProfileInput)
-		artProfileOutputPathValue := android.PathForModuleOut(ctx, "profile.prof.txt").OutputPath
-		artProfileOutputPath = &artProfileOutputPathValue
-		flags = []string{
-			"--art-profile",
-			artProfileInputPath.String(),
-			artProfileOutputPath.String(),
-		}
-		deps = append(deps, artProfileInputPath)
+	if dexParams.artProfileInput == nil {
+		return nil, nil, nil
 	}
+	artProfileInputPath := android.PathForModuleSrc(ctx, *dexParams.artProfileInput)
+	artProfileOutputPathValue := android.PathForModuleOut(ctx, "profile.prof.txt").OutputPath
+	artProfileOutputPath = &artProfileOutputPathValue
+	flags = []string{
+		"--art-profile",
+		artProfileInputPath.String(),
+		artProfileOutputPath.String(),
+	}
+	deps = append(deps, artProfileInputPath)
 	return flags, deps, artProfileOutputPath
 
 }
@@ -482,7 +516,8 @@
 			proguardUsageZip,
 			proguardConfiguration,
 		}
-		r8Flags, r8Deps, r8ArtProfileOutputPath := d.r8Flags(ctx, dexParams)
+		debugMode := android.InList("--debug", commonFlags)
+		r8Flags, r8Deps, r8ArtProfileOutputPath := d.r8Flags(ctx, dexParams, debugMode)
 		rule := r8
 		args := map[string]string{
 			"r8Flags":        strings.Join(append(commonFlags, r8Flags...), " "),
diff --git a/java/dex_test.go b/java/dex_test.go
index 8bc28e6..2126e42 100644
--- a/java/dex_test.go
+++ b/java/dex_test.go
@@ -16,6 +16,7 @@
 
 import (
 	"fmt"
+	"strconv"
 	"testing"
 
 	"android/soong/android"
@@ -24,6 +25,7 @@
 )
 
 func TestR8(t *testing.T) {
+	t.Parallel()
 	result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(t, `
 		android_app {
 			name: "app",
@@ -58,11 +60,11 @@
 		}
 	`)
 
-	app := result.ModuleForTests("app", "android_common")
-	stableApp := result.ModuleForTests("stable_app", "android_common")
-	corePlatformApp := result.ModuleForTests("core_platform_app", "android_common")
-	lib := result.ModuleForTests("lib", "android_common")
-	staticLib := result.ModuleForTests("static_lib", "android_common")
+	app := result.ModuleForTests(t, "app", "android_common")
+	stableApp := result.ModuleForTests(t, "stable_app", "android_common")
+	corePlatformApp := result.ModuleForTests(t, "core_platform_app", "android_common")
+	lib := result.ModuleForTests(t, "lib", "android_common")
+	staticLib := result.ModuleForTests(t, "static_lib", "android_common")
 
 	appJavac := app.Rule("javac")
 	appR8 := app.Rule("r8")
@@ -91,6 +93,7 @@
 }
 
 func TestR8TransitiveDeps(t *testing.T) {
+	t.Parallel()
 	bp := `
 		override_android_app {
 			name: "override_app",
@@ -192,6 +195,7 @@
 
 	for _, tc := range testcases {
 		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
 			fixturePreparer := PrepareForTestWithJavaDefaultModules
 			if tc.unbundled {
 				fixturePreparer = android.GroupFixturePreparers(
@@ -206,14 +210,14 @@
 			result := fixturePreparer.RunTestWithBp(t, bp)
 
 			getHeaderJar := func(name string) android.Path {
-				mod := result.ModuleForTests(name, "android_common")
+				mod := result.ModuleForTests(t, name, "android_common")
 				return mod.Output("turbine-combined/" + name + ".jar").Output
 			}
 
-			appR8 := result.ModuleForTests("app", "android_common").Rule("r8")
-			overrideAppR8 := result.ModuleForTests("app", "android_common_override_app").Rule("r8")
+			appR8 := result.ModuleForTests(t, "app", "android_common").Rule("r8")
+			overrideAppR8 := result.ModuleForTests(t, "app", "android_common_override_app").Rule("r8")
 			appHeader := getHeaderJar("app")
-			overrideAppHeader := result.ModuleForTests("app", "android_common_override_app").Output("turbine-combined/app.jar").Output
+			overrideAppHeader := result.ModuleForTests(t, "app", "android_common_override_app").Output("turbine-combined/app.jar").Output
 			libHeader := getHeaderJar("lib")
 			transitiveLibHeader := getHeaderJar("transitive_lib")
 			transitiveLib2Header := getHeaderJar("transitive_lib_2")
@@ -222,7 +226,7 @@
 			repeatedDepHeader := getHeaderJar("repeated_dep")
 			usesLibHeader := getHeaderJar("uses_lib")
 			optionalUsesLibHeader := getHeaderJar("optional_uses_lib")
-			prebuiltLibHeader := result.ModuleForTests("prebuilt_lib", "android_common").Output("combined/lib.jar").Output
+			prebuiltLibHeader := result.ModuleForTests(t, "prebuilt_lib", "android_common").Output("combined/lib.jar").Output
 
 			for _, rule := range []android.TestingBuildParams{appR8, overrideAppR8} {
 				android.AssertStringDoesNotContain(t, "expected no app header jar in app r8 classpath",
@@ -259,6 +263,7 @@
 }
 
 func TestR8Flags(t *testing.T) {
+	t.Parallel()
 	result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(t, `
 		android_app {
 			name: "app",
@@ -273,7 +278,7 @@
 		}
 	`)
 
-	app := result.ModuleForTests("app", "android_common")
+	app := result.ModuleForTests(t, "app", "android_common")
 	appR8 := app.Rule("r8")
 	android.AssertStringDoesContain(t, "expected -dontshrink in app r8 flags",
 		appR8.Args["r8Flags"], "-dontshrink")
@@ -288,6 +293,7 @@
 }
 
 func TestD8(t *testing.T) {
+	t.Parallel()
 	result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(t, `
 		java_library {
 			name: "foo",
@@ -306,14 +312,25 @@
 			name: "static_lib",
 			srcs: ["foo.java"],
 		}
+
+		android_app {
+			name: "app",
+			srcs: ["foo.java"],
+			platform_apis: true,
+			optimize: {
+				enabled: false,
+			},
+		}
 	`)
 
-	foo := result.ModuleForTests("foo", "android_common")
-	lib := result.ModuleForTests("lib", "android_common")
-	staticLib := result.ModuleForTests("static_lib", "android_common")
+	foo := result.ModuleForTests(t, "foo", "android_common")
+	lib := result.ModuleForTests(t, "lib", "android_common")
+	app := result.ModuleForTests(t, "app", "android_common")
+	staticLib := result.ModuleForTests(t, "static_lib", "android_common")
 
 	fooJavac := foo.Rule("javac")
 	fooD8 := foo.Rule("d8")
+	appD8 := app.Rule("d8")
 	libHeader := lib.Output("turbine-combined/lib.jar").Output
 	staticLibHeader := staticLib.Output("turbine-combined/static_lib.jar").Output
 
@@ -326,9 +343,20 @@
 		fooD8.Args["d8Flags"], libHeader.String())
 	android.AssertStringDoesNotContain(t, "expected no  static_lib header jar in foo javac classpath",
 		fooD8.Args["d8Flags"], staticLibHeader.String())
+
+	// A --release flag is added only for targets that opt out of default R8 behavior (e.g., apps).
+	// For library targets that don't use R8 by default, no --debug or --release flag should be
+	// added, instead relying on default D8 behavior (--debug).
+	android.AssertStringDoesContain(t, "expected --release in app d8 flags",
+		appD8.Args["d8Flags"], "--release")
+	android.AssertStringDoesNotContain(t, "expected no --release flag in lib d8 flags",
+		fooD8.Args["d8Flags"], "--release")
+	android.AssertStringDoesNotContain(t, "expected no --debug flag in lib d8 flags",
+		fooD8.Args["d8Flags"], "--debug")
 }
 
 func TestProguardFlagsInheritanceStatic(t *testing.T) {
+	t.Parallel()
 	result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(t, `
 		android_app {
 			name: "app",
@@ -370,7 +398,7 @@
 		}
 	`)
 
-	app := result.ModuleForTests("app", "android_common")
+	app := result.ModuleForTests(t, "app", "android_common")
 	appR8 := app.Rule("r8")
 	android.AssertStringDoesContain(t, "expected primary_lib's proguard flags from direct dep",
 		appR8.Args["r8Flags"], "primary.flags")
@@ -383,6 +411,7 @@
 }
 
 func TestProguardFlagsInheritance(t *testing.T) {
+	t.Parallel()
 	directDepFlagsFileName := "direct_dep.flags"
 	transitiveDepFlagsFileName := "transitive_dep.flags"
 
@@ -601,6 +630,7 @@
 	for _, topLevelModuleDef := range topLevelModules {
 		for _, tc := range testcases {
 			t.Run(topLevelModuleDef.name+"-"+tc.name, func(t *testing.T) {
+				t.Parallel()
 				result := android.GroupFixturePreparers(
 					PrepareForTestWithJavaDefaultModules,
 					android.FixtureMergeMockFs(android.MockFS{
@@ -617,7 +647,7 @@
 							tc.transitiveDepExportsFlagsFiles,
 						),
 				)
-				appR8 := result.ModuleForTests("app", "android_common").Rule("r8")
+				appR8 := result.ModuleForTests(t, "app", "android_common").Rule("r8")
 
 				shouldHaveDepFlags := android.InList(directDepFlagsFileName, tc.expectedFlagsFiles)
 				if shouldHaveDepFlags {
@@ -642,6 +672,7 @@
 }
 
 func TestProguardFlagsInheritanceAppImport(t *testing.T) {
+	t.Parallel()
 	bp := `
 		android_app {
 			name: "app",
@@ -658,12 +689,13 @@
 		PrepareForTestWithJavaDefaultModules,
 	).RunTestWithBp(t, bp)
 
-	appR8 := result.ModuleForTests("app", "android_common").Rule("r8")
+	appR8 := result.ModuleForTests(t, "app", "android_common").Rule("r8")
 	android.AssertStringDoesContain(t, "expected aarimports's proguard flags",
 		appR8.Args["r8Flags"], "proguard.txt")
 }
 
 func TestR8FlagsArtProfile(t *testing.T) {
+	t.Parallel()
 	result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(t, `
 		android_app {
 			name: "app",
@@ -677,7 +709,7 @@
 		}
 	`)
 
-	app := result.ModuleForTests("app", "android_common")
+	app := result.ModuleForTests(t, "app", "android_common")
 	appR8 := app.Rule("r8")
 	android.AssertStringDoesContain(t, "expected --art-profile in app r8 flags",
 		appR8.Args["r8Flags"], "--art-profile")
@@ -696,6 +728,7 @@
 //
 // The rewritten profile should be used since the dex signatures in the checked-in profile will not match the optimized binary.
 func TestEnableProfileRewritingIsRequiredForOptimizedApps(t *testing.T) {
+	t.Parallel()
 	testJavaError(t,
 		"Enable_profile_rewriting must be true when profile_guided dexpreopt and R8 optimization/obfuscation is turned on",
 		`
@@ -715,11 +748,15 @@
 }
 
 func TestDebugReleaseFlags(t *testing.T) {
+	t.Parallel()
 	bp := `
 		android_app {
 			name: "app",
 			srcs: ["foo.java"],
 			platform_apis: true,
+			optimize: {
+				enabled: %s,
+			},
 			dxflags: ["%s"]
 		}
 	`
@@ -728,6 +765,7 @@
 		name          string
 		envVar        string
 		isEng         bool
+		useD8         bool
 		dxFlags       string
 		expectedFlags string
 	}{
@@ -767,10 +805,24 @@
 			// Eng mode does *not* override explicit dxflags.
 			expectedFlags: "--release",
 		},
+		{
+			name:  "app_d8",
+			useD8: true,
+			// D8 usage w/ apps should explicitly enable --release mode.
+			expectedFlags: "--release",
+		},
+		{
+			name:    "app_d8_debug",
+			useD8:   true,
+			dxFlags: "--debug",
+			// D8 usage w/ apps respects overriding dxFlags.
+			expectedFlags: "--debug",
+		},
 	}
 
 	for _, tc := range testcases {
 		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
 			fixturePreparer := PrepareForTestWithJavaDefaultModules
 			fixturePreparer = android.GroupFixturePreparers(
 				fixturePreparer,
@@ -788,11 +840,16 @@
 					}),
 				)
 			}
-			result := fixturePreparer.RunTestWithBp(t, fmt.Sprintf(bp, tc.dxFlags))
+			result := fixturePreparer.RunTestWithBp(t, fmt.Sprintf(bp, strconv.FormatBool(!tc.useD8), tc.dxFlags))
 
-			appR8 := result.ModuleForTests("app", "android_common").Rule("r8")
-			android.AssertStringDoesContain(t, "expected flag in R8 flags",
-				appR8.Args["r8Flags"], tc.expectedFlags)
+			dexRuleKey := "r8"
+			if tc.useD8 {
+				dexRuleKey = "d8"
+			}
+			dexFlagsKey := dexRuleKey + "Flags"
+			appDex := result.ModuleForTests(t, "app", "android_common").Rule(dexRuleKey)
+			android.AssertStringDoesContain(t, "expected flag in dex flags",
+				appDex.Args[dexFlagsKey], tc.expectedFlags)
 
 			var unexpectedFlags string
 			if tc.expectedFlags == "--debug" {
@@ -801,8 +858,8 @@
 				unexpectedFlags = "--debug"
 			}
 			if unexpectedFlags != "" {
-				android.AssertStringDoesNotContain(t, "unexpected flag in R8 flags",
-					appR8.Args["r8Flags"], unexpectedFlags)
+				android.AssertStringDoesNotContain(t, "unexpected flag in dex flags",
+					appDex.Args[dexFlagsKey], unexpectedFlags)
 			}
 		})
 	}
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index 637da36..5755dec 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -36,61 +36,24 @@
 	// If the java module is to be installed into an APEX, this list contains information about the
 	// dexpreopt outputs to be installed on devices. Note that these dexpreopt outputs are installed
 	// outside of the APEX.
-	DexpreoptBuiltInstalledForApex() []dexpreopterInstall
+	ApexSystemServerDexpreoptInstalls() []DexpreopterInstall
 
-	// The Make entries to install the dexpreopt outputs. Derived from
-	// `DexpreoptBuiltInstalledForApex`.
-	AndroidMkEntriesForApex() []android.AndroidMkEntries
+	// ApexSystemServerDexJars returns the list of dex jars if this is an apex system server jar.
+	ApexSystemServerDexJars() android.Paths
 
 	// See `dexpreopter.outputProfilePathOnHost`.
 	OutputProfilePathOnHost() android.Path
 }
 
-type dexpreopterInstall struct {
-	// A unique name to distinguish an output from others for the same java library module. Usually in
-	// the form of `<arch>-<encoded-path>.odex/vdex/art`.
-	name string
-
-	// The name of the input java module.
-	moduleName string
-
+type DexpreopterInstall struct {
 	// The path to the dexpreopt output on host.
-	outputPathOnHost android.Path
+	OutputPathOnHost android.Path
 
 	// The directory on the device for the output to install to.
-	installDirOnDevice android.InstallPath
+	InstallDirOnDevice android.InstallPath
 
 	// The basename (the last segment of the path) for the output to install as.
-	installFileOnDevice string
-}
-
-// The full module name of the output in the makefile.
-func (install *dexpreopterInstall) FullModuleName() string {
-	return install.moduleName + install.SubModuleName()
-}
-
-// The sub-module name of the output in the makefile (the name excluding the java module name).
-func (install *dexpreopterInstall) SubModuleName() string {
-	return "-dexpreopt-" + install.name
-}
-
-// Returns Make entries for installing the file.
-//
-// This function uses a value receiver rather than a pointer receiver to ensure that the object is
-// safe to use in `android.AndroidMkExtraEntriesFunc`.
-func (install dexpreopterInstall) ToMakeEntries() android.AndroidMkEntries {
-	return android.AndroidMkEntries{
-		Class:      "ETC",
-		OutputFile: android.OptionalPathForPath(install.outputPathOnHost),
-		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
-			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE", install.FullModuleName())
-				entries.SetString("LOCAL_MODULE_PATH", install.installDirOnDevice.String())
-				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", install.installFileOnDevice)
-				entries.SetString("LOCAL_NOT_AVAILABLE_FOR_PLATFORM", "false")
-			},
-		},
-	}
+	InstallFileOnDevice string
 }
 
 type Dexpreopter struct {
@@ -120,8 +83,9 @@
 	classLoaderContexts dexpreopt.ClassLoaderContextMap
 
 	// See the `dexpreopt` function for details.
-	builtInstalled        string
-	builtInstalledForApex []dexpreopterInstall
+	builtInstalled                    string
+	apexSystemServerDexpreoptInstalls []DexpreopterInstall
+	apexSystemServerDexJars           android.Paths
 
 	// The config is used for two purposes:
 	// - Passing dexpreopt information about libraries from Soong to Make. This is needed when
@@ -204,27 +168,23 @@
 	}
 	apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
 	psi := android.PrebuiltSelectionInfoMap{}
-	ctx.VisitDirectDeps(func(am android.Module) {
+	ctx.VisitDirectDepsProxy(func(am android.ModuleProxy) {
 		if prebuiltSelectionInfo, ok := android.OtherModuleProvider(ctx, am, android.PrebuiltSelectionInfoProvider); ok {
 			psi = prebuiltSelectionInfo
 		}
 	})
 
 	// Find the apex variant for this module
-	apexVariantsWithoutTestApexes := []string{}
+	apexVariants := []string{}
 	if apexInfo.BaseApexName != "" {
-		// This is a transitive dependency of an override_apex
-		apexVariantsWithoutTestApexes = append(apexVariantsWithoutTestApexes, apexInfo.BaseApexName)
-	} else {
-		_, variants, _ := android.ListSetDifference(apexInfo.InApexVariants, apexInfo.TestApexes)
-		apexVariantsWithoutTestApexes = append(apexVariantsWithoutTestApexes, variants...)
+		apexVariants = append(apexVariants, apexInfo.BaseApexName)
 	}
 	if apexInfo.ApexAvailableName != "" {
-		apexVariantsWithoutTestApexes = append(apexVariantsWithoutTestApexes, apexInfo.ApexAvailableName)
+		apexVariants = append(apexVariants, apexInfo.ApexAvailableName)
 	}
 	disableSource := false
 	// find the selected apexes
-	for _, apexVariant := range apexVariantsWithoutTestApexes {
+	for _, apexVariant := range apexVariants {
 		if len(psi.GetSelectedModulesForApiDomain(apexVariant)) > 0 {
 			// If the apex_contribution for this api domain is non-empty, disable the source variant
 			disableSource = true
@@ -279,20 +239,6 @@
 		if !isApexSystemServerJar {
 			return true
 		}
-		ai, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
-		allApexInfos := []android.ApexInfo{}
-		if allApexInfosProvider, ok := android.ModuleProvider(ctx, android.AllApexInfoProvider); ok {
-			allApexInfos = allApexInfosProvider.ApexInfos
-		}
-		if len(allApexInfos) > 0 && !ai.MinSdkVersion.EqualTo(allApexInfos[0].MinSdkVersion) {
-			// Apex system server jars are dexpreopted and installed on to the system image.
-			// Since we can have BigAndroid and Go variants of system server jar providing apexes,
-			// and these two variants can have different min_sdk_versions, hide one of the apex variants
-			// from make to prevent collisions.
-			//
-			// Unlike cc, min_sdk_version does not have an effect on the build actions of java libraries.
-			ctx.Module().MakeUninstallable()
-		}
 	} else {
 		// Don't preopt the platform variant of an APEX system server jar to avoid conflicts.
 		if isApexSystemServerJar {
@@ -347,7 +293,11 @@
 	d.installPath = android.PathForModuleInPartitionInstall(ctx, "", strings.TrimPrefix(dexpreopt.GetSystemServerDexLocation(ctx, dc, libraryName), "/"))
 	// generate the rules for creating the .odex and .vdex files for this system server jar
 	dexJarFile := di.PrebuiltExportPath(ApexRootRelativePathToJavaLib(libraryName))
-
+	if dexJarFile == nil {
+		ctx.ModuleErrorf(
+			`Could not find library %s in prebuilt apex %s.
+Please make sure that the value of PRODUCT_APEX_(SYSTEM_SERVER|STANDALONE_SYSTEM_SERVER)_JARS is correct`, libraryName, ctx.ModuleName())
+	}
 	d.inputProfilePathOnHost = nil // reset: TODO(spandandas): Make dexpreopter stateless
 	if android.InList(libraryName, di.GetDexpreoptProfileGuidedExportedModuleNames()) {
 		// Set the profile path to guide optimization
@@ -539,12 +489,8 @@
 		Output(appProductPackages)
 	productPackagesRule.Restat().Build("product_packages."+dexJarStem, "dexpreopt product_packages")
 
-	// Prebuilts are active, do not copy the dexpreopt'd source javalib to out/soong/system_server_dexjars
-	// The javalib from the deapexed prebuilt will be copied to this location.
-	// TODO (b/331665856): Implement a principled solution for this.
-	copyApexSystemServerJarDex := !disableSourceApexVariant(ctx) && !ctx.Module().IsHideFromMake()
 	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(
-		ctx, globalSoong, global, dexpreoptConfig, appProductPackages, copyApexSystemServerJarDex)
+		ctx, globalSoong, global, dexpreoptConfig, appProductPackages)
 	if err != nil {
 		ctx.ModuleErrorf("error generating dexpreopt rule: %s", err.Error())
 		return
@@ -562,7 +508,7 @@
 	// TODO(b/346662300): Let dexpreopter generate the installPath for dexpreopt files instead of
 	// using the dex location to generate the installPath.
 	if isApexSystemServerJar {
-		dexpreoptPartition = "system"
+		dexpreoptPartition = dexpreoptConfig.ApexPartition
 	}
 	for _, install := range dexpreoptRule.Installs() {
 		// Remove the "/" prefix because the path should be relative to $ANDROID_PRODUCT_OUT.
@@ -577,7 +523,6 @@
 			partition = ""
 		}
 		installBase := filepath.Base(install.To)
-		arch := filepath.Base(installDir)
 		installPath := android.PathForModuleInPartitionInstall(ctx, partition, installDir)
 		isProfile := strings.HasSuffix(installBase, ".prof")
 
@@ -593,16 +538,13 @@
 				// libraries, only those in the system server classpath are handled here.
 				// Preopting of boot classpath jars in the ART APEX are handled in
 				// java/dexpreopt_bootjars.go, and other APEX jars are not preopted.
-				// The installs will be handled by Make as sub-modules of the java library.
-				di := dexpreopterInstall{
-					name:                arch + "-" + installBase,
-					moduleName:          libName,
-					outputPathOnHost:    install.From,
-					installDirOnDevice:  installPath,
-					installFileOnDevice: installBase,
+				// The installs will be handled the apex module that includes this library.
+				di := DexpreopterInstall{
+					OutputPathOnHost:    install.From,
+					InstallDirOnDevice:  installPath,
+					InstallFileOnDevice: installBase,
 				}
-				ctx.InstallFile(di.installDirOnDevice, di.installFileOnDevice, di.outputPathOnHost)
-				d.builtInstalledForApex = append(d.builtInstalledForApex, di)
+				d.apexSystemServerDexpreoptInstalls = append(d.apexSystemServerDexpreoptInstalls, di)
 
 			}
 		} else if !d.preventInstall {
@@ -611,6 +553,15 @@
 		}
 	}
 
+	if isApexSystemServerJar {
+		// Store the dex jar location for system server jars in apexes, the apex will copy the file into
+		// a known location for dex2oat.
+		d.apexSystemServerDexJars = append(d.apexSystemServerDexJars, dexJarFile)
+	} else if isSystemServerJar && !d.preventInstall {
+		// Copy the dex jar into a known location for dex2oat for non-apex system server jars.
+		android.CopyFileRule(ctx, dexJarFile, android.PathForOutput(ctx, dexpreopt.SystemServerDexjarsDir, dexJarFile.Base()))
+	}
+
 	if !isApexSystemServerJar {
 		d.builtInstalled = dexpreoptRule.Installs().String()
 	}
@@ -645,16 +596,12 @@
 	}
 }
 
-func (d *dexpreopter) DexpreoptBuiltInstalledForApex() []dexpreopterInstall {
-	return d.builtInstalledForApex
+func (d *dexpreopter) ApexSystemServerDexpreoptInstalls() []DexpreopterInstall {
+	return d.apexSystemServerDexpreoptInstalls
 }
 
-func (d *dexpreopter) AndroidMkEntriesForApex() []android.AndroidMkEntries {
-	var entries []android.AndroidMkEntries
-	for _, install := range d.builtInstalledForApex {
-		entries = append(entries, install.ToMakeEntries())
-	}
-	return entries
+func (d *dexpreopter) ApexSystemServerDexJars() android.Paths {
+	return d.apexSystemServerDexJars
 }
 
 func (d *dexpreopter) OutputProfilePathOnHost() android.Path {
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 5c69ff1..27027f0 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -15,6 +15,7 @@
 package java
 
 import (
+	"fmt"
 	"path/filepath"
 	"strings"
 
@@ -225,7 +226,6 @@
 }
 
 var (
-	dexpreoptBootJarDepTag          = bootclasspathDependencyTag{name: "dexpreopt-boot-jar"}
 	dexBootJarsFragmentsKey         = android.NewOnceKey("dexBootJarsFragments")
 	apexContributionsMetadataDepTag = dependencyTag{name: "all_apex_contributions"}
 )
@@ -293,6 +293,9 @@
 	// Profiles imported from APEXes, in addition to the profile at the default path. Each entry must
 	// be the name of an APEX module.
 	profileImports []string
+
+	// The name of the module that provides boot image profiles, if any.
+	profileProviderModule string
 }
 
 // Target-dependent description of a boot image.
@@ -464,9 +467,6 @@
 func RegisterDexpreoptBootJarsComponents(ctx android.RegistrationContext) {
 	ctx.RegisterParallelSingletonModuleType("dex_bootjars", dexpreoptBootJarsFactory)
 	ctx.RegisterModuleType("art_boot_images", artBootImagesFactory)
-	ctx.FinalDepsMutators(func(ctx android.RegisterMutatorsContext) {
-		ctx.BottomUp("dex_bootjars_deps", DexpreoptBootJarsMutator).Parallel()
-	})
 }
 
 func SkipDexpreoptBootJars(ctx android.PathContext) bool {
@@ -502,12 +502,6 @@
 func (dbj *dexpreoptBootJars) DepsMutator(ctx android.BottomUpMutatorContext) {
 	// Create a dependency on all_apex_contributions to determine the selected mainline module
 	ctx.AddDependency(ctx.Module(), apexContributionsMetadataDepTag, "all_apex_contributions")
-}
-
-func DexpreoptBootJarsMutator(ctx android.BottomUpMutatorContext) {
-	if _, ok := ctx.Module().(*dexpreoptBootJars); !ok {
-		return
-	}
 
 	if dexpreopt.IsDex2oatNeeded(ctx) {
 		// Add a dependency onto the dex2oat tool which is needed for creating the boot image. The
@@ -521,7 +515,7 @@
 			continue
 		}
 		// For accessing the boot jars.
-		addDependenciesOntoBootImageModules(ctx, config.modules, dexpreoptBootJarDepTag)
+		addDependenciesOntoBootImageModules(ctx, config.modules, dexpreoptBootJar)
 		// Create a dependency on the apex selected using RELEASE_APEX_CONTRIBUTIONS_*
 		// TODO: b/308174306 - Remove the direct depedendency edge to the java_library (source/prebuilt) once all mainline modules
 		// have been flagged using RELEASE_APEX_CONTRIBUTIONS_*
@@ -534,11 +528,11 @@
 
 	if ctx.OtherModuleExists("platform-bootclasspath") {
 		// For accessing all bootclasspath fragments.
-		addDependencyOntoApexModulePair(ctx, "platform", "platform-bootclasspath", platformBootclasspathDepTag)
+		addDependencyOntoApexModulePair(ctx, "platform", "platform-bootclasspath", platform)
 	} else if ctx.OtherModuleExists("art-bootclasspath-fragment") {
 		// For accessing the ART bootclasspath fragment on a thin manifest (e.g., master-art) where
 		// platform-bootclasspath doesn't exist.
-		addDependencyOntoApexModulePair(ctx, "com.android.art", "art-bootclasspath-fragment", bootclasspathFragmentDepTag)
+		addDependencyOntoApexModulePair(ctx, "com.android.art", "art-bootclasspath-fragment", fragment)
 	}
 }
 
@@ -556,14 +550,11 @@
 			// We need to add a dep on only the apex listed in `contents` of the selected apex_contributions module
 			// This is not available in a structured format in `apex_contributions`, so this hack adds a dep on all `contents`
 			// (some modules like art.module.public.api do not have an apex variation since it is a pure stub module that does not get installed)
-			apexVariationOfSelected := append(ctx.Target().Variations(), blueprint.Variation{Mutator: "apex", Variation: apex})
-			if ctx.OtherModuleDependencyVariantExists(apexVariationOfSelected, selected) {
-				ctx.AddFarVariationDependencies(apexVariationOfSelected, dexpreoptBootJarDepTag, selected)
-			} else if ctx.OtherModuleDependencyVariantExists(apexVariationOfSelected, android.RemoveOptionalPrebuiltPrefix(selected)) {
-				// The prebuilt might have been renamed by prebuilt_rename mutator if the source module does not exist.
-				// Remove the prebuilt_ prefix.
-				ctx.AddFarVariationDependencies(apexVariationOfSelected, dexpreoptBootJarDepTag, android.RemoveOptionalPrebuiltPrefix(selected))
+			tag := bootclasspathDependencyTag{
+				typ: dexpreoptBootJar,
 			}
+
+			ctx.AddFarVariationDependencies(ctx.Target().Variations(), tag, android.RemoveOptionalPrebuiltPrefix(selected))
 		}
 	}
 }
@@ -571,23 +562,55 @@
 func gatherBootclasspathFragments(ctx android.ModuleContext) map[string]android.Module {
 	return ctx.Config().Once(dexBootJarsFragmentsKey, func() interface{} {
 		fragments := make(map[string]android.Module)
+
+		type moduleInApexPair struct {
+			module string
+			apex   string
+		}
+
+		var modulesInApexes []moduleInApexPair
+
+		// Find the list of modules in apexes.
 		ctx.WalkDeps(func(child, parent android.Module) bool {
 			if !isActiveModule(ctx, child) {
 				return false
 			}
 			tag := ctx.OtherModuleDependencyTag(child)
-			if tag == platformBootclasspathDepTag {
-				return true
-			}
-			if tag == bootclasspathFragmentDepTag {
-				apexInfo, _ := android.OtherModuleProvider(ctx, child, android.ApexInfoProvider)
-				for _, apex := range apexInfo.InApexVariants {
-					fragments[apex] = child
+			if bcpTag, ok := tag.(bootclasspathDependencyTag); ok {
+				if bcpTag.typ == platform {
+					return true
 				}
-				return false
+				if bcpTag.typ == fragment {
+					if bcpTag.moduleInApex == "" {
+						panic(fmt.Errorf("expected fragment to be in apex"))
+					}
+					modulesInApexes = append(modulesInApexes, moduleInApexPair{bcpTag.moduleInApex, ctx.OtherModuleName(child)})
+					return true
+				}
 			}
 			return false
 		})
+
+		for _, moduleInApex := range modulesInApexes {
+			// Find a desired module in an apex.
+			ctx.WalkDeps(func(child, parent android.Module) bool {
+				t := ctx.OtherModuleDependencyTag(child)
+				if bcpTag, ok := t.(bootclasspathDependencyTag); ok {
+					if bcpTag.typ == platform {
+						return true
+					}
+					if bcpTag.typ == fragment && ctx.OtherModuleName(child) == moduleInApex.apex {
+						// This is the dependency from this module to the apex, recurse into it.
+						return true
+					}
+				} else if android.RemoveOptionalPrebuiltPrefix(ctx.OtherModuleName(child)) == moduleInApex.module {
+					// This is the desired module inside the apex.
+					fragments[android.RemoveOptionalPrebuiltPrefix(moduleInApex.apex)] = child
+				}
+				return false
+			})
+		}
+
 		return fragments
 	}).(map[string]android.Module)
 }
@@ -711,7 +734,8 @@
 	modules := make([]apexJarModulePair, 0, imageConfig.modules.Len())
 	for i := 0; i < imageConfig.modules.Len(); i++ {
 		found := false
-		for _, module := range gatherApexModulePairDepsWithTag(ctx, dexpreoptBootJarDepTag) {
+		dexpreoptBootJarModules, _ := gatherApexModulePairDepsWithTag(ctx, dexpreoptBootJar)
+		for _, module := range dexpreoptBootJarModules {
 			name := android.RemoveOptionalPrebuiltPrefix(module.Name())
 			if name == imageConfig.modules.Jar(i) {
 				modules = append(modules, apexJarModulePair{
@@ -794,11 +818,16 @@
 				"APEX '%[2]s' doesn't exist or is not added as a dependency of dex_bootjars",
 				pair.jarModule.Name(),
 				pair.apex)
+			return nil
 		}
 		bootclasspathFragmentInfo, _ := android.OtherModuleProvider(ctx, fragment, BootclasspathFragmentApexContentInfoProvider)
 		jar, err := bootclasspathFragmentInfo.DexBootJarPathForContentModule(pair.jarModule)
 		if err != nil {
-			ctx.ModuleErrorf("%s", err)
+			if ctx.Config().AllowMissingDependencies() {
+				ctx.AddMissingDependencies([]string{pair.jarModule.String()})
+			} else {
+				ctx.ModuleErrorf("%s", err)
+			}
 		}
 		return jar
 	}
@@ -957,9 +986,16 @@
 
 func getApexNameToApexExportsInfoMap(ctx android.ModuleContext) apexNameToApexExportsInfoMap {
 	apexNameToApexExportsInfoMap := apexNameToApexExportsInfoMap{}
-	ctx.VisitDirectDepsWithTag(dexpreoptBootJarDepTag, func(am android.Module) {
-		if info, exists := android.OtherModuleProvider(ctx, am, android.ApexExportsInfoProvider); exists {
-			apexNameToApexExportsInfoMap[info.ApexName] = info
+
+	ctx.VisitDirectDeps(func(am android.Module) {
+		tag := ctx.OtherModuleDependencyTag(am)
+		if bcpTag, ok := tag.(bootclasspathDependencyTag); ok && bcpTag.typ == dexpreoptBootJar {
+			if bcpTag.moduleInApex == "" {
+				info, exists := android.OtherModuleProvider(ctx, am, android.ApexExportsInfoProvider)
+				if exists {
+					apexNameToApexExportsInfoMap[info.ApexName] = info
+				}
+			}
 		}
 	})
 	return apexNameToApexExportsInfoMap
@@ -1442,24 +1478,33 @@
 
 func (dbj *artBootImages) DepsMutator(ctx android.BottomUpMutatorContext) {
 	// Create a dependency on `dex_bootjars` to access the intermediate locations of host art boot image.
-	ctx.AddDependency(ctx.Module(), dexpreoptBootJarDepTag, "dex_bootjars")
+	tag := bootclasspathDependencyTag{
+		typ: dexpreoptBootJar,
+	}
+	ctx.AddVariationDependencies(ctx.Config().AndroidCommonTarget.Variations(), tag, "dex_bootjars")
 }
 
 func (d *artBootImages) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	ctx.VisitDirectDepsWithTag(dexpreoptBootJarDepTag, func(m android.Module) {
-		hostInstallsInfo, ok := android.OtherModuleProvider(ctx, m, artBootImageHostInfoProvider)
-		if !ok {
-			ctx.ModuleErrorf("Could not find information about the host variant of ART boot image")
-		}
-		installs := d.installFile(ctx, hostInstallsInfo.installs)
-		if len(installs) > 0 {
-			d.outputFile = android.OptionalPathForPath(installs[0])
-			// Create a phony target that can ART run-tests can depend on.
-			ctx.Phony(d.Name(), installs...)
-		} else {
-			// this might be true e.g. when building with `WITH_DEXPREOPT=false`
-			// create an empty file so that the `art_boot_images` is known to the packaging system.
-			d.outputFile = android.OptionalPathForPath(android.PathForModuleOut(ctx, "undefined_art_boot_images"))
+	ctx.VisitDirectDeps(func(m android.Module) {
+		tag := ctx.OtherModuleDependencyTag(m)
+		if bcpTag, ok := tag.(bootclasspathDependencyTag); ok && bcpTag.typ == dexpreoptBootJar {
+			if bcpTag.moduleInApex != "" {
+				panic("unhandled moduleInApex")
+			}
+			hostInstallsInfo, ok := android.OtherModuleProvider(ctx, m, artBootImageHostInfoProvider)
+			if !ok {
+				ctx.ModuleErrorf("Could not find information about the host variant of ART boot image")
+			}
+			installs := d.installFile(ctx, hostInstallsInfo.installs)
+			if len(installs) > 0 {
+				d.outputFile = android.OptionalPathForPath(installs[0])
+				// Create a phony target that can ART run-tests can depend on.
+				ctx.Phony(d.Name(), installs...)
+			} else {
+				// this might be true e.g. when building with `WITH_DEXPREOPT=false`
+				// create an empty file so that the `art_boot_images` is known to the packaging system.
+				d.outputFile = android.OptionalPathForPath(android.PathForModuleOut(ctx, "undefined_art_boot_images"))
+			}
 		}
 	})
 }
diff --git a/java/dexpreopt_config.go b/java/dexpreopt_config.go
index dc0973c..fb5c325 100644
--- a/java/dexpreopt_config.go
+++ b/java/dexpreopt_config.go
@@ -67,15 +67,16 @@
 
 		// ART boot image for testing only. Do not rely on it to make any build-time decision.
 		artCfg := bootImageConfig{
-			name:                 artBootImageName,
-			enabledIfExists:      "art-bootclasspath-fragment",
-			stem:                 bootImageStem,
-			installDir:           "apex/art_boot_images/javalib",
-			modules:              global.TestOnlyArtBootImageJars,
-			preloadedClassesFile: "art/build/boot/preloaded-classes",
-			compilerFilter:       "speed-profile",
-			singleImage:          false,
-			profileImports:       profileImports,
+			name:                  artBootImageName,
+			enabledIfExists:       "art-bootclasspath-fragment",
+			stem:                  bootImageStem,
+			installDir:            "apex/art_boot_images/javalib",
+			modules:               global.TestOnlyArtBootImageJars,
+			preloadedClassesFile:  "art/build/boot/preloaded-classes",
+			compilerFilter:        "speed-profile",
+			singleImage:           false,
+			profileImports:        profileImports,
+			profileProviderModule: "art-bootclasspath-fragment",
 		}
 
 		// Framework config for the boot image extension.
diff --git a/java/dexpreopt_config_test.go b/java/dexpreopt_config_test.go
index 44d2127..99b1f23 100644
--- a/java/dexpreopt_config_test.go
+++ b/java/dexpreopt_config_test.go
@@ -23,6 +23,7 @@
 )
 
 func TestBootImageConfig(t *testing.T) {
+	t.Parallel()
 	if runtime.GOOS != "linux" {
 		t.Skipf("Skipping as boot image config test is only supported on linux not %s", runtime.GOOS)
 	}
diff --git a/java/dexpreopt_config_testing.go b/java/dexpreopt_config_testing.go
index 33c682b..241941e 100644
--- a/java/dexpreopt_config_testing.go
+++ b/java/dexpreopt_config_testing.go
@@ -1217,6 +1217,7 @@
 	}
 
 	t.Run(imageConfig.name, func(t *testing.T) {
+		t.Parallel()
 		nestedCheckBootImageConfig(t, result, imageConfig, mutated, expected)
 	})
 }
@@ -1236,7 +1237,7 @@
 	android.AssertPathRelativeToTopEquals(t, "zip", expected.zip, imageConfig.zip)
 
 	if !mutated {
-		dexBootJarModule := result.ModuleForTests("dex_bootjars", "android_common")
+		dexBootJarModule := result.ModuleForTests(t, "dex_bootjars", "android_common")
 		profileInstallInfo, _ := android.OtherModuleProvider(result, dexBootJarModule.Module(), profileInstallInfoProvider)
 		assertInstallsEqual(t, "profileInstalls", expected.profileInstalls, profileInstallInfo.profileInstalls)
 		android.AssertStringEquals(t, "profileLicenseMetadataFile", expected.profileLicenseMetadataFile, profileInstallInfo.profileLicenseMetadataFile.RelativeToTop().String())
@@ -1246,6 +1247,7 @@
 	for i, variant := range imageConfig.variants {
 		expectedVariant := expected.variants[i]
 		t.Run(variant.target.Arch.ArchType.String(), func(t *testing.T) {
+			t.Parallel()
 			android.AssertDeepEquals(t, "archType", expectedVariant.archType, variant.target.Arch.ArchType)
 			android.AssertDeepEquals(t, "dexLocations", expectedVariant.dexLocations, variant.dexLocations)
 			android.AssertDeepEquals(t, "dexLocationsDeps", expectedVariant.dexLocationsDeps, variant.dexLocationsDeps)
@@ -1279,7 +1281,7 @@
 // checkDexpreoptMakeVars checks the DEXPREOPT_ prefixed make vars produced by dexpreoptBootJars
 // singleton.
 func checkDexpreoptMakeVars(t *testing.T, result *android.TestResult, expectedLicenseMetadataFile string) {
-	vars := result.MakeVarsForTesting(func(variable android.MakeVarVariable) bool {
+	vars := result.MakeVarsForTesting(t, func(variable android.MakeVarVariable) bool {
 		return strings.HasPrefix(variable.Name(), "DEXPREOPT_")
 	})
 
diff --git a/java/dexpreopt_test.go b/java/dexpreopt_test.go
index 07d0595..f437da0 100644
--- a/java/dexpreopt_test.go
+++ b/java/dexpreopt_test.go
@@ -17,7 +17,6 @@
 import (
 	"fmt"
 	"runtime"
-	"strings"
 	"testing"
 
 	"android/soong/android"
@@ -30,6 +29,7 @@
 }
 
 func TestDexpreoptEnabled(t *testing.T) {
+	t.Parallel()
 	tests := []struct {
 		name        string
 		bp          string
@@ -219,6 +219,7 @@
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
 			preparers := android.GroupFixturePreparers(
 				PrepareForTestWithDexpreopt,
 				PrepareForTestWithFakeApexMutator,
@@ -238,7 +239,7 @@
 				variant += "_apex1000"
 			}
 
-			dexpreopt := ctx.ModuleForTests(moduleName, variant).MaybeRule("dexpreopt")
+			dexpreopt := ctx.ModuleForTests(t, moduleName, variant).MaybeRule("dexpreopt")
 			enabled := dexpreopt.Rule != nil
 
 			if enabled != test.enabled {
@@ -258,6 +259,7 @@
 }
 
 func TestDex2oatToolDeps(t *testing.T) {
+	t.Parallel()
 	if runtime.GOOS != "linux" {
 		// The host binary paths checked below are build OS dependent.
 		t.Skipf("Unsupported build OS %s", runtime.GOOS)
@@ -273,6 +275,7 @@
 		name := fmt.Sprintf("sourceEnabled:%t,prebuiltEnabled:%t,prebuiltPreferred:%t",
 			sourceEnabled, prebuiltEnabled, prebuiltPreferred)
 		t.Run(name, func(t *testing.T) {
+			t.Parallel()
 			result := preparers.RunTestWithBp(t, fmt.Sprintf(`
 					cc_binary {
 						name: "dex2oatd",
@@ -296,7 +299,7 @@
 		})
 	}
 
-	sourceDex2oatPath := "host/linux-x86/bin/dex2oatd"
+	sourceDex2oatPath := "../host/linux-x86/bin/dex2oatd"
 	prebuiltDex2oatPath := ".intermediates/prebuilt_dex2oatd/linux_glibc_x86_64/dex2oatd"
 
 	testDex2oatToolDep(true, false, false, sourceDex2oatPath)
@@ -305,7 +308,7 @@
 	testDex2oatToolDep(false, true, false, prebuiltDex2oatPath)
 }
 
-func TestDexpreoptBuiltInstalledForApex(t *testing.T) {
+func TestApexSystemServerDexpreoptInstalls(t *testing.T) {
 	preparers := android.GroupFixturePreparers(
 		PrepareForTestWithDexpreopt,
 		PrepareForTestWithFakeApexMutator,
@@ -322,28 +325,38 @@
 			sdk_version: "current",
 		}`)
 	ctx := result.TestContext
-	module := ctx.ModuleForTests("service-foo", "android_common_apex1000")
+	module := ctx.ModuleForTests(t, "service-foo", "android_common_apex1000")
 	library := module.Module().(*Library)
 
-	installs := library.dexpreopter.DexpreoptBuiltInstalledForApex()
+	installs := library.dexpreopter.ApexSystemServerDexpreoptInstalls()
+	dexJars := library.dexpreopter.ApexSystemServerDexJars()
 
 	android.AssertIntEquals(t, "install count", 2, len(installs))
+	android.AssertIntEquals(t, "dexjar count", 1, len(dexJars))
 
-	android.AssertStringEquals(t, "installs[0] FullModuleName",
-		"service-foo-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.odex",
-		installs[0].FullModuleName())
+	android.AssertPathRelativeToTopEquals(t, "installs[0] OutputPathOnHost",
+		"out/soong/.intermediates/service-foo/android_common_apex1000/dexpreopt/service-foo/oat/arm64/javalib.odex",
+		installs[0].OutputPathOnHost)
 
-	android.AssertStringEquals(t, "installs[0] SubModuleName",
-		"-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.odex",
-		installs[0].SubModuleName())
+	android.AssertPathRelativeToTopEquals(t, "installs[0] InstallDirOnDevice",
+		"out/target/product/test_device/system/framework/oat/arm64",
+		installs[0].InstallDirOnDevice)
 
-	android.AssertStringEquals(t, "installs[1] FullModuleName",
-		"service-foo-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.vdex",
-		installs[1].FullModuleName())
+	android.AssertStringEquals(t, "installs[0] InstallFileOnDevice",
+		"apex@com.android.apex1@javalib@service-foo.jar@classes.odex",
+		installs[0].InstallFileOnDevice)
 
-	android.AssertStringEquals(t, "installs[1] SubModuleName",
-		"-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.vdex",
-		installs[1].SubModuleName())
+	android.AssertPathRelativeToTopEquals(t, "installs[1] OutputPathOnHost",
+		"out/soong/.intermediates/service-foo/android_common_apex1000/dexpreopt/service-foo/oat/arm64/javalib.vdex",
+		installs[1].OutputPathOnHost)
+
+	android.AssertPathRelativeToTopEquals(t, "installs[1] InstallDirOnDevice",
+		"out/target/product/test_device/system/framework/oat/arm64",
+		installs[1].InstallDirOnDevice)
+
+	android.AssertStringEquals(t, "installs[1] InstallFileOnDevice",
+		"apex@com.android.apex1@javalib@service-foo.jar@classes.vdex",
+		installs[1].InstallFileOnDevice)
 
 	// Not an APEX system server jar.
 	result = preparers.RunTestWithBp(t, `
@@ -354,101 +367,14 @@
 			sdk_version: "current",
 		}`)
 	ctx = result.TestContext
-	module = ctx.ModuleForTests("foo", "android_common")
+	module = ctx.ModuleForTests(t, "foo", "android_common")
 	library = module.Module().(*Library)
 
-	installs = library.dexpreopter.DexpreoptBuiltInstalledForApex()
+	installs = library.dexpreopter.ApexSystemServerDexpreoptInstalls()
+	dexJars = library.dexpreopter.ApexSystemServerDexJars()
 
 	android.AssertIntEquals(t, "install count", 0, len(installs))
-}
-
-func filterDexpreoptEntriesList(entriesList []android.AndroidMkEntries) []android.AndroidMkEntries {
-	var results []android.AndroidMkEntries
-	for _, entries := range entriesList {
-		if strings.Contains(entries.EntryMap["LOCAL_MODULE"][0], "-dexpreopt-") {
-			results = append(results, entries)
-		}
-	}
-	return results
-}
-
-func verifyEntries(t *testing.T, message string, expectedModule string,
-	expectedPrebuiltModuleFile string, expectedModulePath string, expectedInstalledModuleStem string,
-	entries android.AndroidMkEntries) {
-	android.AssertStringEquals(t, message+" LOCAL_MODULE", expectedModule,
-		entries.EntryMap["LOCAL_MODULE"][0])
-
-	android.AssertStringEquals(t, message+" LOCAL_MODULE_CLASS", "ETC",
-		entries.EntryMap["LOCAL_MODULE_CLASS"][0])
-
-	android.AssertStringDoesContain(t, message+" LOCAL_PREBUILT_MODULE_FILE",
-		entries.EntryMap["LOCAL_PREBUILT_MODULE_FILE"][0], expectedPrebuiltModuleFile)
-
-	android.AssertStringDoesContain(t, message+" LOCAL_MODULE_PATH",
-		entries.EntryMap["LOCAL_MODULE_PATH"][0], expectedModulePath)
-
-	android.AssertStringEquals(t, message+" LOCAL_INSTALLED_MODULE_STEM",
-		expectedInstalledModuleStem, entries.EntryMap["LOCAL_INSTALLED_MODULE_STEM"][0])
-
-	android.AssertStringEquals(t, message+" LOCAL_NOT_AVAILABLE_FOR_PLATFORM",
-		"false", entries.EntryMap["LOCAL_NOT_AVAILABLE_FOR_PLATFORM"][0])
-}
-
-func TestAndroidMkEntriesForApex(t *testing.T) {
-	preparers := android.GroupFixturePreparers(
-		PrepareForTestWithDexpreopt,
-		PrepareForTestWithFakeApexMutator,
-		dexpreopt.FixtureSetApexSystemServerJars("com.android.apex1:service-foo"),
-	)
-
-	// An APEX system server jar.
-	result := preparers.RunTestWithBp(t, `
-		java_library {
-			name: "service-foo",
-			installable: true,
-			srcs: ["a.java"],
-			apex_available: ["com.android.apex1"],
-			sdk_version: "current",
-		}`)
-	ctx := result.TestContext
-	module := ctx.ModuleForTests("service-foo", "android_common_apex1000")
-
-	entriesList := android.AndroidMkEntriesForTest(t, ctx, module.Module())
-	entriesList = filterDexpreoptEntriesList(entriesList)
-
-	android.AssertIntEquals(t, "entries count", 2, len(entriesList))
-
-	verifyEntries(t,
-		"entriesList[0]",
-		"service-foo-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.odex",
-		"/dexpreopt/service-foo/oat/arm64/javalib.odex",
-		"/system/framework/oat/arm64",
-		"apex@com.android.apex1@javalib@service-foo.jar@classes.odex",
-		entriesList[0])
-
-	verifyEntries(t,
-		"entriesList[1]",
-		"service-foo-dexpreopt-arm64-apex@com.android.apex1@javalib@service-foo.jar@classes.vdex",
-		"/dexpreopt/service-foo/oat/arm64/javalib.vdex",
-		"/system/framework/oat/arm64",
-		"apex@com.android.apex1@javalib@service-foo.jar@classes.vdex",
-		entriesList[1])
-
-	// Not an APEX system server jar.
-	result = preparers.RunTestWithBp(t, `
-		java_library {
-			name: "foo",
-			installable: true,
-			srcs: ["a.java"],
-			sdk_version: "current",
-		}`)
-	ctx = result.TestContext
-	module = ctx.ModuleForTests("foo", "android_common")
-
-	entriesList = android.AndroidMkEntriesForTest(t, ctx, module.Module())
-	entriesList = filterDexpreoptEntriesList(entriesList)
-
-	android.AssertIntEquals(t, "entries count", 0, len(entriesList))
+	android.AssertIntEquals(t, "dexjar count", 0, len(dexJars))
 }
 
 func TestGenerateProfileEvenIfDexpreoptIsDisabled(t *testing.T) {
@@ -470,7 +396,7 @@
 		}`)
 
 	ctx := result.TestContext
-	dexpreopt := ctx.ModuleForTests("foo", "android_common").MaybeRule("dexpreopt")
+	dexpreopt := ctx.ModuleForTests(t, "foo", "android_common").MaybeRule("dexpreopt")
 
 	expected := []string{"out/soong/.intermediates/foo/android_common/dexpreopt/foo/profile.prof"}
 
diff --git a/java/droiddoc.go b/java/droiddoc.go
index 2980d91..225f201 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -19,6 +19,7 @@
 	"path/filepath"
 	"strings"
 
+	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
@@ -357,7 +358,7 @@
 		deps.aidlPreprocess = sdkDep.aidl
 	}
 
-	ctx.VisitDirectDeps(func(module android.Module) {
+	ctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 		otherName := ctx.OtherModuleName(module)
 		tag := ctx.OtherModuleDependencyTag(module)
 
@@ -381,9 +382,9 @@
 				deps.classpath = append(deps.classpath, dep.HeaderJars...)
 				deps.aidlIncludeDirs = append(deps.aidlIncludeDirs, dep.AidlIncludeDirs...)
 				deps.aconfigProtoFiles = append(deps.aconfigProtoFiles, dep.AconfigIntermediateCacheOutputPaths...)
-			} else if dep, ok := module.(android.SourceFileProducer); ok {
-				checkProducesJars(ctx, dep)
-				deps.classpath = append(deps.classpath, dep.Srcs()...)
+			} else if dep, ok := android.OtherModuleProvider(ctx, module, android.SourceFilesInfoProvider); ok {
+				checkProducesJars(ctx, dep, module)
+				deps.classpath = append(deps.classpath, dep.Srcs...)
 			} else {
 				ctx.ModuleErrorf("depends on non-java module %q", otherName)
 			}
@@ -427,9 +428,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...)
 				}
 			}
@@ -578,7 +579,6 @@
 
 	rule.Build("javadoc", "javadoc")
 
-	ctx.SetOutputFiles(android.Paths{j.stubsSrcJar}, "")
 	ctx.SetOutputFiles(android.Paths{j.docZip}, ".docs.zip")
 }
 
@@ -875,6 +875,13 @@
 	Path *string
 }
 
+type ExportedDroiddocDirInfo struct {
+	Deps android.Paths
+	Dir  android.Path
+}
+
+var ExportedDroiddocDirInfoProvider = blueprint.NewProvider[ExportedDroiddocDirInfo]()
+
 type ExportedDroiddocDir struct {
 	android.ModuleBase
 
@@ -898,6 +905,11 @@
 	path := String(d.properties.Path)
 	d.dir = android.PathForModuleSrc(ctx, path)
 	d.deps = android.PathsForModuleSrc(ctx, []string{filepath.Join(path, "**/*")})
+
+	android.SetProvider(ctx, ExportedDroiddocDirInfoProvider, ExportedDroiddocDirInfo{
+		Dir:  d.dir,
+		Deps: d.deps,
+	})
 }
 
 // Defaults
diff --git a/java/droiddoc_test.go b/java/droiddoc_test.go
index 9e1ebbe..0c977bc 100644
--- a/java/droiddoc_test.go
+++ b/java/droiddoc_test.go
@@ -23,6 +23,7 @@
 )
 
 func TestDroiddoc(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJavaWithFS(t, `
 		droiddoc_exported_dir {
 		    name: "droiddoc-templates-sdk",
@@ -69,13 +70,13 @@
 			"bar-doc/a.java": nil,
 			"bar-doc/b.java": nil,
 		})
-	barStubsOutputs := ctx.ModuleForTests("bar-stubs", "android_common").OutputFiles(ctx, t, "")
+	barStubsOutputs := ctx.ModuleForTests(t, "bar-stubs", "android_common").OutputFiles(ctx, t, "")
 	if len(barStubsOutputs) != 1 {
 		t.Errorf("Expected one output from \"bar-stubs\" got %s", barStubsOutputs)
 	}
 
 	barStubsOutput := barStubsOutputs[0]
-	barDoc := ctx.ModuleForTests("bar-doc", "android_common")
+	barDoc := ctx.ModuleForTests(t, "bar-doc", "android_common")
 	javaDoc := barDoc.Rule("javadoc")
 	if g, w := android.PathsRelativeToTop(javaDoc.Implicits), android.PathRelativeToTop(barStubsOutput); !inList(w, g) {
 		t.Errorf("implicits of bar-doc must contain %q, but was %q.", w, g)
@@ -97,6 +98,7 @@
 }
 
 func TestDroiddocArgsAndFlagsCausesError(t *testing.T) {
+	t.Parallel()
 	testJavaError(t, "flags is set. Cannot set args", `
 		droiddoc_exported_dir {
 		    name: "droiddoc-templates-sdk",
diff --git a/java/droidstubs.go b/java/droidstubs.go
index 6bcdf85..caad688 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -20,6 +20,7 @@
 	"regexp"
 	"strings"
 
+	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
@@ -27,6 +28,28 @@
 	"android/soong/remoteexec"
 )
 
+type StubsInfo struct {
+	ApiVersionsXml android.Path
+	AnnotationsZip android.Path
+	ApiFile        android.Path
+	RemovedApiFile android.Path
+}
+
+type DroidStubsInfo struct {
+	CurrentApiTimestamp android.Path
+	EverythingStubsInfo StubsInfo
+	ExportableStubsInfo StubsInfo
+}
+
+var DroidStubsInfoProvider = blueprint.NewProvider[DroidStubsInfo]()
+
+type StubsSrcInfo struct {
+	EverythingStubsSrcJar android.Path
+	ExportableStubsSrcJar android.Path
+}
+
+var StubsSrcInfoProvider = blueprint.NewProvider[StubsSrcInfo]()
+
 // The values allowed for Droidstubs' Api_levels_sdk_type
 var allowedApiLevelSdkTypes = []string{"public", "system", "module-lib", "system-server"}
 
@@ -498,9 +521,9 @@
 }
 
 func (d *Droidstubs) mergeAnnoDirFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
-	ctx.VisitDirectDepsWithTag(metalavaMergeAnnotationsDirTag, func(m android.Module) {
-		if t, ok := m.(*ExportedDroiddocDir); ok {
-			cmd.FlagWithArg("--merge-qualifier-annotations ", t.dir.String()).Implicits(t.deps)
+	ctx.VisitDirectDepsProxyWithTag(metalavaMergeAnnotationsDirTag, func(m android.ModuleProxy) {
+		if t, ok := android.OtherModuleProvider(ctx, m, ExportedDroiddocDirInfoProvider); ok {
+			cmd.FlagWithArg("--merge-qualifier-annotations ", t.Dir.String()).Implicits(t.Deps)
 		} else {
 			ctx.PropertyErrorf("merge_annotations_dirs",
 				"module %q is not a metalava merge-annotations dir", ctx.OtherModuleName(m))
@@ -509,9 +532,9 @@
 }
 
 func (d *Droidstubs) inclusionAnnotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
-	ctx.VisitDirectDepsWithTag(metalavaMergeInclusionAnnotationsDirTag, func(m android.Module) {
-		if t, ok := m.(*ExportedDroiddocDir); ok {
-			cmd.FlagWithArg("--merge-inclusion-annotations ", t.dir.String()).Implicits(t.deps)
+	ctx.VisitDirectDepsProxyWithTag(metalavaMergeInclusionAnnotationsDirTag, func(m android.ModuleProxy) {
+		if t, ok := android.OtherModuleProvider(ctx, m, ExportedDroiddocDirInfoProvider); ok {
+			cmd.FlagWithArg("--merge-inclusion-annotations ", t.Dir.String()).Implicits(t.Deps)
 		} else {
 			ctx.PropertyErrorf("merge_inclusion_annotations_dirs",
 				"module %q is not a metalava merge-annotations dir", ctx.OtherModuleName(m))
@@ -525,12 +548,12 @@
 		d.apiLevelsGenerationFlags(ctx, cmd, stubsType, apiVersionsXml)
 		apiVersions = apiVersionsXml
 	} else {
-		ctx.VisitDirectDepsWithTag(metalavaAPILevelsModuleTag, func(m android.Module) {
-			if s, ok := m.(*Droidstubs); ok {
+		ctx.VisitDirectDepsProxyWithTag(metalavaAPILevelsModuleTag, func(m android.ModuleProxy) {
+			if s, ok := android.OtherModuleProvider(ctx, m, DroidStubsInfoProvider); ok {
 				if stubsType == Everything {
-					apiVersions = s.everythingArtifacts.apiVersionsXml
+					apiVersions = s.EverythingStubsInfo.ApiVersionsXml
 				} else if stubsType == Exportable {
-					apiVersions = s.exportableArtifacts.apiVersionsXml
+					apiVersions = s.ExportableStubsInfo.ApiVersionsXml
 				} else {
 					ctx.ModuleErrorf("%s stubs type does not generate api-versions.xml file", stubsType.String())
 				}
@@ -603,18 +626,18 @@
 
 	var dirs []string
 	var extensions_dir string
-	ctx.VisitDirectDepsWithTag(metalavaAPILevelsAnnotationsDirTag, func(m android.Module) {
-		if t, ok := m.(*ExportedDroiddocDir); ok {
-			extRegex := regexp.MustCompile(t.dir.String() + extensionsPattern)
+	ctx.VisitDirectDepsProxyWithTag(metalavaAPILevelsAnnotationsDirTag, func(m android.ModuleProxy) {
+		if t, ok := android.OtherModuleProvider(ctx, m, ExportedDroiddocDirInfoProvider); ok {
+			extRegex := regexp.MustCompile(t.Dir.String() + extensionsPattern)
 
 			// Grab the first extensions_dir and we find while scanning ExportedDroiddocDir.deps;
 			// ideally this should be read from prebuiltApis.properties.Extensions_*
-			for _, dep := range t.deps {
+			for _, dep := range t.Deps {
 				// Check to see if it matches an extension first.
 				depBase := dep.Base()
 				if extRegex.MatchString(dep.String()) && d.properties.Extensions_info_file != nil {
 					if extensions_dir == "" {
-						extensions_dir = t.dir.String() + "/extensions"
+						extensions_dir = t.Dir.String() + "/extensions"
 					}
 					cmd.Implicit(dep)
 				} else if depBase == filename {
@@ -622,23 +645,17 @@
 					cmd.Implicit(dep)
 				} else if depBase == AndroidPlusUpdatableJar && d.properties.Extensions_info_file != nil {
 					// The output api-versions.xml has been requested to include information on SDK
-					// extensions. That means it also needs to include
-					// so
-					// The module-lib and system-server directories should use `android-plus-updatable.jar`
-					// instead of `android.jar`. See AndroidPlusUpdatableJar for more information.
-					cmd.Implicit(dep)
-				} else if filename != "android.jar" && depBase == "android.jar" {
-					// Metalava implicitly searches these patterns:
-					//  prebuilts/tools/common/api-versions/android-%/android.jar
-					//  prebuilts/sdk/%/public/android.jar
-					// Add android.jar files from the api_levels_annotations_dirs directories to try
-					// to satisfy these patterns.  If Metalava can't find a match for an API level
-					// between 1 and 28 in at least one pattern it will fail.
+					// extensions, i.e. updatable Apis. That means it also needs to include the history of
+					// those updatable APIs. Usually, they would be included in the `android.jar` file but
+					// unfortunately, the `module-lib` and `system-server` cannot as it would lead to build
+					// cycles. So, the module-lib and system-server directories contain an
+					// `android-plus-updatable.jar` that should be used instead of `android.jar`. See
+					// AndroidPlusUpdatableJar for more information.
 					cmd.Implicit(dep)
 				}
 			}
 
-			dirs = append(dirs, t.dir.String())
+			dirs = append(dirs, t.Dir.String())
 		} else {
 			ctx.PropertyErrorf("api_levels_annotations_dirs",
 				"module %q is not a metalava api-levels-annotations dir", ctx.OtherModuleName(m))
@@ -646,11 +663,11 @@
 	})
 
 	// Generate the list of --android-jar-pattern options. The order matters so the first one which
-	// matches will be the one that is used for a specific api level..
+	// matches will be the one that is used for a specific api level.
 	for _, sdkDir := range sdkDirs {
 		for _, dir := range dirs {
 			addPattern := func(jarFilename string) {
-				cmd.FlagWithArg("--android-jar-pattern ", fmt.Sprintf("%s/%%/%s/%s", dir, sdkDir, jarFilename))
+				cmd.FlagWithArg("--android-jar-pattern ", fmt.Sprintf("%s/{version:major.minor?}/%s/%s", dir, sdkDir, jarFilename))
 			}
 
 			if sdkDir == "module-lib" || sdkDir == "system-server" {
@@ -665,6 +682,10 @@
 
 			addPattern(filename)
 		}
+
+		if extensions_dir != "" {
+			cmd.FlagWithArg("--android-jar-pattern ", fmt.Sprintf("%s/{version:extension}/%s/{module}.jar", extensions_dir, sdkDir))
+		}
 	}
 
 	if d.properties.Extensions_info_file != nil {
@@ -673,7 +694,6 @@
 		}
 		info_file := android.PathForModuleSrc(ctx, *d.properties.Extensions_info_file)
 		cmd.Implicit(info_file)
-		cmd.FlagWithArg("--sdk-extensions-root ", extensions_dir)
 		cmd.FlagWithArg("--sdk-extensions-info ", info_file.String())
 	}
 }
@@ -700,7 +720,8 @@
 }
 
 func metalavaCmd(ctx android.ModuleContext, rule *android.RuleBuilder, srcs android.Paths,
-	srcJarList android.Path, homeDir android.WritablePath, params stubsCommandConfigParams, configFiles android.Paths) *android.RuleBuilderCommand {
+	srcJarList android.Path, homeDir android.WritablePath, params stubsCommandConfigParams,
+	configFiles android.Paths, apiSurface *string) *android.RuleBuilderCommand {
 	rule.Command().Text("rm -rf").Flag(homeDir.String())
 	rule.Command().Text("mkdir -p").Flag(homeDir.String())
 
@@ -746,6 +767,8 @@
 
 	addMetalavaConfigFilesToCmd(cmd, configFiles)
 
+	addOptionalApiSurfaceToCmd(cmd, apiSurface)
+
 	return cmd
 }
 
@@ -764,20 +787,19 @@
 	cmd.FlagForEachInput("--config-file ", configFiles)
 }
 
+// addOptionalApiSurfaceToCmd adds --api-surface option is apiSurface is not `nil`.
+func addOptionalApiSurfaceToCmd(cmd *android.RuleBuilderCommand, apiSurface *string) {
+	if apiSurface != nil {
+		cmd.Flag("--api-surface")
+		cmd.Flag(*apiSurface)
+	}
+}
+
 // Pass flagged apis related flags to metalava. When aconfig_declarations property is not
 // defined for a module, simply revert all flagged apis annotations. If aconfig_declarations
 // property is defined, apply transformations and only revert the flagged apis that are not
 // enabled via release configurations and are not specified in aconfig_declarations
 func generateRevertAnnotationArgs(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, stubsType StubsType, aconfigFlagsPaths android.Paths) {
-
-	if len(aconfigFlagsPaths) == 0 {
-		cmd.Flag("--revert-annotation android.annotation.FlaggedApi")
-		return
-	}
-
-	releasedFlaggedApisFile := android.PathForModuleOut(ctx, fmt.Sprintf("released-flagged-apis-%s.txt", stubsType.String()))
-	revertAnnotationsFile := android.PathForModuleOut(ctx, fmt.Sprintf("revert-annotations-%s.txt", stubsType.String()))
-
 	var filterArgs string
 	switch stubsType {
 	// No flagged apis specific flags need to be passed to metalava when generating
@@ -799,6 +821,15 @@
 		}
 	}
 
+	if len(aconfigFlagsPaths) == 0 {
+		// This argument should not be added for "everything" stubs
+		cmd.Flag("--revert-annotation android.annotation.FlaggedApi")
+		return
+	}
+
+	releasedFlaggedApisFile := android.PathForModuleOut(ctx, fmt.Sprintf("released-flagged-apis-%s.txt", stubsType.String()))
+	revertAnnotationsFile := android.PathForModuleOut(ctx, fmt.Sprintf("revert-annotations-%s.txt", stubsType.String()))
+
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        gatherReleasedFlaggedApisRule,
 		Inputs:      aconfigFlagsPaths,
@@ -838,7 +869,8 @@
 
 	configFiles := android.PathsForModuleSrc(ctx, d.properties.ConfigFiles)
 
-	cmd := metalavaCmd(ctx, rule, d.Javadoc.srcFiles, srcJarList, homeDir, params.stubConfig, configFiles)
+	cmd := metalavaCmd(ctx, rule, d.Javadoc.srcFiles, srcJarList, homeDir, params.stubConfig,
+		configFiles, d.properties.Api_surface)
 	cmd.Implicits(d.Javadoc.implicits)
 
 	d.stubsFlags(ctx, cmd, params.stubsDir, params.stubConfig.stubsType, params.stubConfig.checkApi)
@@ -997,12 +1029,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 +1043,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 +1052,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 $' ... ' .
@@ -1176,6 +1209,34 @@
 	rule.Build(fmt.Sprintf("metalava_%s", params.stubConfig.stubsType.String()), "metalava merged")
 }
 
+func (d *Droidstubs) setPhonyRules(ctx android.ModuleContext) {
+	if d.apiFile != nil {
+		ctx.Phony(d.Name(), d.apiFile)
+		ctx.Phony(fmt.Sprintf("%s.txt", d.Name()), d.apiFile)
+	}
+	if d.removedApiFile != nil {
+		ctx.Phony(d.Name(), d.removedApiFile)
+		ctx.Phony(fmt.Sprintf("%s.txt", d.Name()), d.removedApiFile)
+	}
+	if d.checkCurrentApiTimestamp != nil {
+		ctx.Phony(fmt.Sprintf("%s-check-current-api", d.Name()), d.checkCurrentApiTimestamp)
+		ctx.Phony("checkapi", d.checkCurrentApiTimestamp)
+	}
+	if d.updateCurrentApiTimestamp != nil {
+		ctx.Phony(fmt.Sprintf("%s-update-current-api", d.Name()), d.updateCurrentApiTimestamp)
+		ctx.Phony("update-api", d.updateCurrentApiTimestamp)
+	}
+	if d.checkLastReleasedApiTimestamp != nil {
+		ctx.Phony(fmt.Sprintf("%s-check-last-released-api", d.Name()), d.checkLastReleasedApiTimestamp)
+	}
+	if d.apiLintTimestamp != nil {
+		ctx.Phony(fmt.Sprintf("%s-api-lint", d.Name()), d.apiLintTimestamp)
+	}
+	if d.checkNullabilityWarningsTimestamp != nil {
+		ctx.Phony(fmt.Sprintf("%s-check-nullability-warnings", d.Name()), d.checkNullabilityWarningsTimestamp)
+	}
+}
+
 func (d *Droidstubs) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	deps := d.Javadoc.collectDeps(ctx)
 
@@ -1219,6 +1280,41 @@
 	stubCmdParams.stubsType = Exportable
 	d.exportableStubCmd(ctx, stubCmdParams)
 
+	if String(d.properties.Check_nullability_warnings) != "" {
+		if d.everythingArtifacts.nullabilityWarningsFile == nil {
+			ctx.PropertyErrorf("check_nullability_warnings",
+				"Cannot specify check_nullability_warnings unless validating nullability")
+		}
+
+		checkNullabilityWarningsPath := android.PathForModuleSrc(ctx, String(d.properties.Check_nullability_warnings))
+
+		d.checkNullabilityWarningsTimestamp = android.PathForModuleOut(ctx, Everything.String(), "check_nullability_warnings.timestamp")
+
+		msg := fmt.Sprintf(`\n******************************\n`+
+			`The warnings encountered during nullability annotation validation did\n`+
+			`not match the checked in file of expected warnings. The diffs are shown\n`+
+			`above. You have two options:\n`+
+			`   1. Resolve the differences by editing the nullability annotations.\n`+
+			`   2. Update the file of expected warnings by running:\n`+
+			`         cp %s %s\n`+
+			`       and submitting the updated file as part of your change.`,
+			d.everythingArtifacts.nullabilityWarningsFile, checkNullabilityWarningsPath)
+
+		rule := android.NewRuleBuilder(pctx, ctx)
+
+		rule.Command().
+			Text("(").
+			Text("diff").Input(checkNullabilityWarningsPath).Input(d.everythingArtifacts.nullabilityWarningsFile).
+			Text("&&").
+			Text("touch").Output(d.checkNullabilityWarningsTimestamp).
+			Text(") || (").
+			Text("echo").Flag("-e").Flag(`"` + msg + `"`).
+			Text("; exit 38").
+			Text(")")
+
+		rule.Build("nullabilityWarningsCheck", "nullability warnings check")
+	}
+
 	if apiCheckEnabled(ctx, d.properties.Check_api.Current, "current") {
 
 		if len(d.Javadoc.properties.Out) > 0 {
@@ -1268,13 +1364,25 @@
 			`Note that DISABLE_STUB_VALIDATION=true does not bypass checkapi.\n`+
 			`******************************\n`, ctx.ModuleName())
 
-		rule.Command().
+		cmd := rule.Command().
 			Text("touch").Output(d.checkCurrentApiTimestamp).
 			Text(") || (").
 			Text("echo").Flag("-e").Flag(`"` + msg + `"`).
 			Text("; exit 38").
 			Text(")")
 
+		if d.apiLintTimestamp != nil {
+			cmd.Validation(d.apiLintTimestamp)
+		}
+
+		if d.checkLastReleasedApiTimestamp != nil {
+			cmd.Validation(d.checkLastReleasedApiTimestamp)
+		}
+
+		if d.checkNullabilityWarningsTimestamp != nil {
+			cmd.Validation(d.checkNullabilityWarningsTimestamp)
+		}
+
 		rule.Build("metalavaCurrentApiCheck", "check current API")
 
 		d.updateCurrentApiTimestamp = android.PathForModuleOut(ctx, Everything.String(), "update_current_api.timestamp")
@@ -1304,42 +1412,49 @@
 		rule.Build("metalavaCurrentApiUpdate", "update current API")
 	}
 
-	if String(d.properties.Check_nullability_warnings) != "" {
-		if d.everythingArtifacts.nullabilityWarningsFile == nil {
-			ctx.PropertyErrorf("check_nullability_warnings",
-				"Cannot specify check_nullability_warnings unless validating nullability")
-		}
-
-		checkNullabilityWarnings := android.PathForModuleSrc(ctx, String(d.properties.Check_nullability_warnings))
-
-		d.checkNullabilityWarningsTimestamp = android.PathForModuleOut(ctx, Everything.String(), "check_nullability_warnings.timestamp")
-
-		msg := fmt.Sprintf(`\n******************************\n`+
-			`The warnings encountered during nullability annotation validation did\n`+
-			`not match the checked in file of expected warnings. The diffs are shown\n`+
-			`above. You have two options:\n`+
-			`   1. Resolve the differences by editing the nullability annotations.\n`+
-			`   2. Update the file of expected warnings by running:\n`+
-			`         cp %s %s\n`+
-			`       and submitting the updated file as part of your change.`,
-			d.everythingArtifacts.nullabilityWarningsFile, checkNullabilityWarnings)
-
-		rule := android.NewRuleBuilder(pctx, ctx)
-
-		rule.Command().
-			Text("(").
-			Text("diff").Input(checkNullabilityWarnings).Input(d.everythingArtifacts.nullabilityWarningsFile).
-			Text("&&").
-			Text("touch").Output(d.checkNullabilityWarningsTimestamp).
-			Text(") || (").
-			Text("echo").Flag("-e").Flag(`"` + msg + `"`).
-			Text("; exit 38").
-			Text(")")
-
-		rule.Build("nullabilityWarningsCheck", "nullability warnings check")
+	droidInfo := DroidStubsInfo{
+		CurrentApiTimestamp: d.CurrentApiTimestamp(),
+		EverythingStubsInfo: StubsInfo{},
+		ExportableStubsInfo: StubsInfo{},
 	}
+	setDroidInfo(ctx, d, &droidInfo.EverythingStubsInfo, Everything)
+	setDroidInfo(ctx, d, &droidInfo.ExportableStubsInfo, Exportable)
+	android.SetProvider(ctx, DroidStubsInfoProvider, droidInfo)
+
+	android.SetProvider(ctx, StubsSrcInfoProvider, StubsSrcInfo{
+		EverythingStubsSrcJar: d.stubsSrcJar,
+		ExportableStubsSrcJar: d.exportableStubsSrcJar,
+	})
 
 	d.setOutputFiles(ctx)
+
+	d.setPhonyRules(ctx)
+
+	if d.apiLintTimestamp != nil {
+		if d.apiLintReport != nil {
+			ctx.DistForGoalsWithFilename(
+				[]string{fmt.Sprintf("%s-api-lint", d.Name()), "droidcore"},
+				d.apiLintReport,
+				fmt.Sprintf("apilint/%s-lint-report.txt", d.Name()),
+			)
+		}
+	}
+}
+
+func setDroidInfo(ctx android.ModuleContext, d *Droidstubs, info *StubsInfo, typ StubsType) {
+	if typ == Everything {
+		info.ApiFile = d.apiFile
+		info.RemovedApiFile = d.removedApiFile
+		info.AnnotationsZip = d.everythingArtifacts.annotationsZip
+		info.ApiVersionsXml = d.everythingArtifacts.apiVersionsXml
+	} else if typ == Exportable {
+		info.ApiFile = d.exportableApiFile
+		info.RemovedApiFile = d.exportableRemovedApiFile
+		info.AnnotationsZip = d.exportableArtifacts.annotationsZip
+		info.ApiVersionsXml = d.exportableArtifacts.apiVersionsXml
+	} else {
+		ctx.ModuleErrorf("failed to set ApiVersionsXml, stubs type not supported: %d", typ)
+	}
 }
 
 // This method sets the outputFiles property, which is used to set the
@@ -1373,7 +1488,7 @@
 		for _, stubType := range android.SortedKeys(stubsTypeToPrefix) {
 			tagWithPrefix := stubsTypeToPrefix[stubType] + tag
 			outputFile, err := tagToOutputFileFunc[tag](stubType)
-			if err == nil {
+			if err == nil && outputFile != nil {
 				ctx.SetOutputFiles(android.Paths{outputFile}, tagWithPrefix)
 			}
 		}
@@ -1507,6 +1622,11 @@
 		p.stubsSrcJar = outPath
 	}
 
+	android.SetProvider(ctx, StubsSrcInfoProvider, StubsSrcInfo{
+		EverythingStubsSrcJar: p.stubsSrcJar,
+		ExportableStubsSrcJar: p.stubsSrcJar,
+	})
+
 	ctx.SetOutputFiles(android.Paths{p.stubsSrcJar}, "")
 	// prebuilt droidstubs does not output "exportable" stubs.
 	// Output the "everything" stubs srcjar file if the tag is ".exportable".
diff --git a/java/droidstubs_test.go b/java/droidstubs_test.go
index 1e8362c..dfdf877 100644
--- a/java/droidstubs_test.go
+++ b/java/droidstubs_test.go
@@ -27,6 +27,7 @@
 )
 
 func TestDroidstubs(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJavaWithFS(t, `
 		droiddoc_exported_dir {
 			name: "droiddoc-templates-sdk",
@@ -82,13 +83,13 @@
 		},
 	}
 	for _, c := range testcases {
-		m := ctx.ModuleForTests(c.moduleName, "android_common")
+		m := ctx.ModuleForTests(t, c.moduleName, "android_common")
 		manifest := m.Output("metalava.sbox.textproto")
 		sboxProto := android.RuleBuilderSboxProtoForTests(t, ctx, manifest)
 		cmdline := String(sboxProto.Commands[0].Command)
 		android.AssertStringContainsEquals(t, "api-versions generation flag", cmdline, "--generate-api-levels", c.generate_xml)
 		if c.expectedJarFilename != "" {
-			expected := "--android-jar-pattern ./%/public/" + c.expectedJarFilename
+			expected := "--android-jar-pattern ./{version:major.minor?}/public/" + c.expectedJarFilename
 			if !strings.Contains(cmdline, expected) {
 				t.Errorf("For %q, expected metalava argument %q, but was not found %q", c.moduleName, expected, cmdline)
 			}
@@ -131,7 +132,7 @@
 			"foo-doc/a.java": nil,
 		})
 
-	m := ctx.ModuleForTests("foo-stubs", "android_common")
+	m := ctx.ModuleForTests(t, "foo-stubs", "android_common")
 	manifest := m.Output("metalava.sbox.textproto")
 	cmd := String(android.RuleBuilderSboxProtoForTests(t, ctx, manifest).Commands[0].Command)
 	r := regexp.MustCompile(`--android-jar-pattern [^ ]+/android.jar`)
@@ -139,35 +140,38 @@
 }
 
 func TestPublicDroidstubs(t *testing.T) {
+	t.Parallel()
 	patterns := getAndroidJarPatternsForDroidstubs(t, "public")
 
 	android.AssertArrayString(t, "order of patterns", []string{
-		"--android-jar-pattern somedir/%/public/android.jar",
-		"--android-jar-pattern someotherdir/%/public/android.jar",
+		"--android-jar-pattern somedir/{version:major.minor?}/public/android.jar",
+		"--android-jar-pattern someotherdir/{version:major.minor?}/public/android.jar",
 	}, patterns)
 }
 
 func TestSystemDroidstubs(t *testing.T) {
+	t.Parallel()
 	patterns := getAndroidJarPatternsForDroidstubs(t, "system")
 
 	android.AssertArrayString(t, "order of patterns", []string{
-		"--android-jar-pattern somedir/%/system/android.jar",
-		"--android-jar-pattern someotherdir/%/system/android.jar",
-		"--android-jar-pattern somedir/%/public/android.jar",
-		"--android-jar-pattern someotherdir/%/public/android.jar",
+		"--android-jar-pattern somedir/{version:major.minor?}/system/android.jar",
+		"--android-jar-pattern someotherdir/{version:major.minor?}/system/android.jar",
+		"--android-jar-pattern somedir/{version:major.minor?}/public/android.jar",
+		"--android-jar-pattern someotherdir/{version:major.minor?}/public/android.jar",
 	}, patterns)
 }
 
 func TestModuleLibDroidstubs(t *testing.T) {
+	t.Parallel()
 	patterns := getAndroidJarPatternsForDroidstubs(t, "module-lib")
 
 	android.AssertArrayString(t, "order of patterns", []string{
-		"--android-jar-pattern somedir/%/module-lib/android.jar",
-		"--android-jar-pattern someotherdir/%/module-lib/android.jar",
-		"--android-jar-pattern somedir/%/system/android.jar",
-		"--android-jar-pattern someotherdir/%/system/android.jar",
-		"--android-jar-pattern somedir/%/public/android.jar",
-		"--android-jar-pattern someotherdir/%/public/android.jar",
+		"--android-jar-pattern somedir/{version:major.minor?}/module-lib/android.jar",
+		"--android-jar-pattern someotherdir/{version:major.minor?}/module-lib/android.jar",
+		"--android-jar-pattern somedir/{version:major.minor?}/system/android.jar",
+		"--android-jar-pattern someotherdir/{version:major.minor?}/system/android.jar",
+		"--android-jar-pattern somedir/{version:major.minor?}/public/android.jar",
+		"--android-jar-pattern someotherdir/{version:major.minor?}/public/android.jar",
 	}, patterns)
 }
 
@@ -175,14 +179,14 @@
 	patterns := getAndroidJarPatternsForDroidstubs(t, "system-server")
 
 	android.AssertArrayString(t, "order of patterns", []string{
-		"--android-jar-pattern somedir/%/system-server/android.jar",
-		"--android-jar-pattern someotherdir/%/system-server/android.jar",
-		"--android-jar-pattern somedir/%/module-lib/android.jar",
-		"--android-jar-pattern someotherdir/%/module-lib/android.jar",
-		"--android-jar-pattern somedir/%/system/android.jar",
-		"--android-jar-pattern someotherdir/%/system/android.jar",
-		"--android-jar-pattern somedir/%/public/android.jar",
-		"--android-jar-pattern someotherdir/%/public/android.jar",
+		"--android-jar-pattern somedir/{version:major.minor?}/system-server/android.jar",
+		"--android-jar-pattern someotherdir/{version:major.minor?}/system-server/android.jar",
+		"--android-jar-pattern somedir/{version:major.minor?}/module-lib/android.jar",
+		"--android-jar-pattern someotherdir/{version:major.minor?}/module-lib/android.jar",
+		"--android-jar-pattern somedir/{version:major.minor?}/system/android.jar",
+		"--android-jar-pattern someotherdir/{version:major.minor?}/system/android.jar",
+		"--android-jar-pattern somedir/{version:major.minor?}/public/android.jar",
+		"--android-jar-pattern someotherdir/{version:major.minor?}/public/android.jar",
 	}, patterns)
 }
 
@@ -206,7 +210,7 @@
 			"bar-doc/a.java": nil,
 		})
 
-	m := ctx.ModuleForTests("bar-stubs", "android_common")
+	m := ctx.ModuleForTests(t, "bar-stubs", "android_common")
 	metalava := m.Rule("metalava")
 	if g, w := metalava.Inputs.Strings(), []string{"bar-doc/a.java"}; !reflect.DeepEqual(w, g) {
 		t.Errorf("Expected inputs %q, got %q", w, g)
@@ -267,7 +271,7 @@
 }
 
 func checkSystemModulesUseByDroidstubs(t *testing.T, ctx *android.TestContext, moduleName string, systemJar string) {
-	metalavaRule := ctx.ModuleForTests(moduleName, "android_common").Rule("metalava")
+	metalavaRule := ctx.ModuleForTests(t, moduleName, "android_common").Rule("metalava")
 	var systemJars []string
 	for _, i := range metalavaRule.Implicits {
 		systemJars = append(systemJars, i.Base())
@@ -300,10 +304,10 @@
 			"sdk/extensions/1/public/some-mainline-module-stubs.jar": nil,
 			"sdk/extensions/info.txt":                                nil,
 		})
-	m := ctx.ModuleForTests("baz-stubs", "android_common")
+	m := ctx.ModuleForTests(t, "baz-stubs", "android_common")
 	manifest := m.Output("metalava.sbox.textproto")
 	cmdline := String(android.RuleBuilderSboxProtoForTests(t, ctx, manifest).Commands[0].Command)
-	android.AssertStringDoesContain(t, "sdk-extensions-root present", cmdline, "--sdk-extensions-root sdk/extensions")
+	android.AssertStringDoesContain(t, "android-jar-pattern present", cmdline, "--android-jar-pattern sdk/extensions/{version:extension}/public/{module}.jar")
 	android.AssertStringDoesContain(t, "sdk-extensions-info present", cmdline, "--sdk-extensions-info sdk/extensions/info.txt")
 }
 
@@ -328,7 +332,7 @@
 		},
 	)
 
-	ctx.ModuleForTests("foo.api.contribution", "")
+	ctx.ModuleForTests(t, "foo.api.contribution", "")
 }
 
 func TestGeneratedApiContributionVisibilityTest(t *testing.T) {
@@ -362,7 +366,7 @@
 		},
 	)
 
-	ctx.ModuleForTests("bar", "android_common")
+	ctx.ModuleForTests(t, "bar", "android_common")
 }
 
 func TestAconfigDeclarations(t *testing.T) {
@@ -404,7 +408,7 @@
 	android.AssertBoolEquals(t, "foo expected to depend on bar",
 		CheckModuleHasDependency(t, result.TestContext, "foo", "android_common", "bar"), true)
 
-	m := result.ModuleForTests("foo", "android_common")
+	m := result.ModuleForTests(t, "foo", "android_common")
 	android.AssertStringDoesContain(t, "foo generates revert annotations file",
 		strings.Join(m.AllOutputs(), ""), "revert-annotations-exportable.txt")
 
@@ -454,7 +458,7 @@
 	}
 	`)
 
-	m := result.ModuleForTests("foo", "android_common")
+	m := result.ModuleForTests(t, "foo", "android_common")
 
 	rule := m.Output("released-flagged-apis-exportable.txt")
 	exposeWritableApisFilter := "--filter='state:ENABLED+permission:READ_ONLY' --filter='permission:READ_WRITE'"
diff --git a/java/fuzz.go b/java/fuzz.go
index e5f1f04..5973957 100644
--- a/java/fuzz.go
+++ b/java/fuzz.go
@@ -63,7 +63,7 @@
 
 	module.Module.properties.Installable = proptools.BoolPtr(true)
 	module.Module.dexpreopter.isTest = true
-	module.Module.linter.properties.Lint.Test = proptools.BoolPtr(true)
+	module.Module.linter.properties.Lint.Test_module_type = proptools.BoolPtr(true)
 	module.Module.sourceProperties.Test_only = proptools.BoolPtr(true)
 	module.Module.sourceProperties.Top_level_test_target = true
 
@@ -107,20 +107,7 @@
 }
 
 func (j *JavaFuzzTest) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	if j.fuzzPackagedModule.FuzzProperties.Corpus != nil {
-		j.fuzzPackagedModule.Corpus = android.PathsForModuleSrc(ctx, j.fuzzPackagedModule.FuzzProperties.Corpus)
-	}
-	if j.fuzzPackagedModule.FuzzProperties.Data != nil {
-		j.fuzzPackagedModule.Data = android.PathsForModuleSrc(ctx, j.fuzzPackagedModule.FuzzProperties.Data)
-	}
-	if j.fuzzPackagedModule.FuzzProperties.Dictionary != nil {
-		j.fuzzPackagedModule.Dictionary = android.PathForModuleSrc(ctx, *j.fuzzPackagedModule.FuzzProperties.Dictionary)
-	}
-	if j.fuzzPackagedModule.FuzzProperties.Fuzz_config != nil {
-		configPath := android.PathForModuleOut(ctx, "config").Join(ctx, "config.json")
-		android.WriteFileRule(ctx, configPath, j.fuzzPackagedModule.FuzzProperties.Fuzz_config.String())
-		j.fuzzPackagedModule.Config = configPath
-	}
+	j.fuzzPackagedModule = cc.PackageFuzzModule(ctx, j.fuzzPackagedModule)
 
 	_, sharedDeps := cc.CollectAllSharedDependencies(ctx)
 	for _, dep := range sharedDeps {
diff --git a/java/fuzz_test.go b/java/fuzz_test.go
index f29c913..8cbe873 100644
--- a/java/fuzz_test.go
+++ b/java/fuzz_test.go
@@ -30,6 +30,7 @@
 )
 
 func TestJavaFuzz(t *testing.T) {
+	t.Parallel()
 	result := prepForJavaFuzzTest.RunTestWithBp(t, `
 		java_fuzz {
 			name: "foo",
@@ -63,14 +64,14 @@
 
 	osCommonTarget := result.Config.BuildOSCommonTarget.String()
 
-	javac := result.ModuleForTests("foo", osCommonTarget).Rule("javac")
-	combineJar := result.ModuleForTests("foo", osCommonTarget).Description("for javac")
+	javac := result.ModuleForTests(t, "foo", osCommonTarget).Rule("javac")
+	combineJar := result.ModuleForTests(t, "foo", osCommonTarget).Description("for javac")
 
 	if len(javac.Inputs) != 1 || javac.Inputs[0].String() != "a.java" {
 		t.Errorf(`foo inputs %v != ["a.java"]`, javac.Inputs)
 	}
 
-	baz := result.ModuleForTests("baz", osCommonTarget).Rule("javac").Output.String()
+	baz := result.ModuleForTests(t, "baz", osCommonTarget).Rule("javac").Output.String()
 	barOut := filepath.Join("out", "soong", ".intermediates", "bar", osCommonTarget, "javac-header", "bar.jar")
 	bazOut := filepath.Join("out", "soong", ".intermediates", "baz", osCommonTarget, "javac-header", "baz.jar")
 
@@ -82,7 +83,7 @@
 	}
 
 	ctx := result.TestContext
-	foo := ctx.ModuleForTests("foo", osCommonTarget).Module().(*JavaFuzzTest)
+	foo := ctx.ModuleForTests(t, "foo", osCommonTarget).Module().(*JavaFuzzTest)
 
 	expected := "lib64/libjni.so"
 	if runtime.GOOS == "darwin" {
diff --git a/java/gen.go b/java/gen.go
index 1b4f4c7..aab8418 100644
--- a/java/gen.go
+++ b/java/gen.go
@@ -26,15 +26,14 @@
 )
 
 func init() {
-	pctx.SourcePathVariable("logtagsCmd", "build/make/tools/java-event-log-tags.py")
-	pctx.SourcePathVariable("logtagsLib", "build/make/tools/event_log_tags.py")
+	pctx.HostBinToolVariable("logtagsCmd", "java-event-log-tags")
 }
 
 var (
 	logtags = pctx.AndroidStaticRule("logtags",
 		blueprint.RuleParams{
 			Command:     "$logtagsCmd -o $out $in",
-			CommandDeps: []string{"$logtagsCmd", "$logtagsLib"},
+			CommandDeps: []string{"$logtagsCmd"},
 		})
 )
 
diff --git a/java/generated_java_library_test.go b/java/generated_java_library_test.go
index a5c4be1..e5ee586 100644
--- a/java/generated_java_library_test.go
+++ b/java/generated_java_library_test.go
@@ -52,6 +52,7 @@
 }
 
 func TestGenLib(t *testing.T) {
+	t.Parallel()
 	bp := `
 				test_java_gen_lib {
 					name: "javagenlibtest",
@@ -60,6 +61,6 @@
 			`
 	result := testGenLib(t, android.FixtureExpectsNoErrors, bp)
 
-	javagenlibtest := result.ModuleForTests("javagenlibtest", "android_common").Module().(*GeneratedJavaLibraryModule)
+	javagenlibtest := result.ModuleForTests(t, "javagenlibtest", "android_common").Module().(*GeneratedJavaLibraryModule)
 	android.AssertPathsEndWith(t, "Generated_srcjars", []string{"/blah.srcjar"}, javagenlibtest.Library.properties.Generated_srcjars)
 }
diff --git a/java/genrule_combiner.go b/java/genrule_combiner.go
new file mode 100644
index 0000000..357dc2c
--- /dev/null
+++ b/java/genrule_combiner.go
@@ -0,0 +1,252 @@
+// Copyright 2019 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 java
+
+import (
+	"fmt"
+	"io"
+
+	"android/soong/android"
+	"android/soong/dexpreopt"
+
+	"github.com/google/blueprint/depset"
+	"github.com/google/blueprint/proptools"
+)
+
+type GenruleCombiner struct {
+	android.ModuleBase
+	android.DefaultableModuleBase
+
+	genruleCombinerProperties GenruleCombinerProperties
+
+	headerJars                    android.Paths
+	implementationJars            android.Paths
+	implementationAndResourceJars android.Paths
+	resourceJars                  android.Paths
+	aconfigProtoFiles             android.Paths
+
+	srcJarArgs []string
+	srcJarDeps android.Paths
+
+	headerDirs android.Paths
+
+	combinedHeaderJar         android.Path
+	combinedImplementationJar android.Path
+}
+
+type GenruleCombinerProperties struct {
+	// List of modules whose implementation (and resources) jars will be visible to modules
+	// that depend on this module.
+	Static_libs proptools.Configurable[[]string] `android:"arch_variant"`
+
+	// List of modules whose header jars will be visible to modules that depend on this module.
+	Headers proptools.Configurable[[]string] `android:"arch_variant"`
+}
+
+// java_genrule_combiner provides the implementation and resource jars from `static_libs`, with
+// the header jars from `headers`.
+//
+// This is useful when a java_genrule is used to change the implementation of a java library
+// without requiring a change in the header jars.
+func GenruleCombinerFactory() android.Module {
+	module := &GenruleCombiner{}
+
+	module.AddProperties(&module.genruleCombinerProperties)
+	InitJavaModule(module, android.HostAndDeviceSupported)
+	return module
+}
+
+var genruleCombinerHeaderDepTag = dependencyTag{name: "genrule_combiner_header"}
+
+func (j *GenruleCombiner) DepsMutator(ctx android.BottomUpMutatorContext) {
+	ctx.AddVariationDependencies(nil, staticLibTag,
+		j.genruleCombinerProperties.Static_libs.GetOrDefault(ctx, nil)...)
+	ctx.AddVariationDependencies(nil, genruleCombinerHeaderDepTag,
+		j.genruleCombinerProperties.Headers.GetOrDefault(ctx, nil)...)
+}
+
+func (j *GenruleCombiner) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	if len(j.genruleCombinerProperties.Static_libs.GetOrDefault(ctx, nil)) < 1 {
+		ctx.PropertyErrorf("static_libs", "at least one dependency is required")
+	}
+
+	if len(j.genruleCombinerProperties.Headers.GetOrDefault(ctx, nil)) < 1 {
+		ctx.PropertyErrorf("headers", "at least one dependency is required")
+	}
+
+	var transitiveHeaderJars []depset.DepSet[android.Path]
+	var transitiveImplementationJars []depset.DepSet[android.Path]
+	var transitiveResourceJars []depset.DepSet[android.Path]
+	var sdkVersion android.SdkSpec
+	var stubsLinkType StubsLinkType
+	moduleWithSdkDepInfo := &ModuleWithSdkDepInfo{}
+
+	// Collect the headers first, so that aconfig flag values for the libraries will override
+	// values from the headers (if they are different).
+	ctx.VisitDirectDepsWithTag(genruleCombinerHeaderDepTag, func(m android.Module) {
+		if dep, ok := android.OtherModuleProvider(ctx, m, JavaInfoProvider); ok {
+			j.headerJars = append(j.headerJars, dep.HeaderJars...)
+
+			j.srcJarArgs = append(j.srcJarArgs, dep.SrcJarArgs...)
+			j.srcJarDeps = append(j.srcJarDeps, dep.SrcJarDeps...)
+			j.aconfigProtoFiles = append(j.aconfigProtoFiles, dep.AconfigIntermediateCacheOutputPaths...)
+			sdkVersion = dep.SdkVersion
+			stubsLinkType = dep.StubsLinkType
+			*moduleWithSdkDepInfo = *dep.ModuleWithSdkDepInfo
+
+			transitiveHeaderJars = append(transitiveHeaderJars, dep.TransitiveStaticLibsHeaderJars)
+		} else if dep, ok := android.OtherModuleProvider(ctx, m, android.CodegenInfoProvider); ok {
+			j.aconfigProtoFiles = append(j.aconfigProtoFiles, dep.IntermediateCacheOutputPaths...)
+		} else {
+			ctx.PropertyErrorf("headers", "module %q cannot be used as a dependency", ctx.OtherModuleName(m))
+		}
+	})
+	ctx.VisitDirectDepsWithTag(staticLibTag, func(m android.Module) {
+		if dep, ok := android.OtherModuleProvider(ctx, m, JavaInfoProvider); ok {
+			j.implementationJars = append(j.implementationJars, dep.ImplementationJars...)
+			j.implementationAndResourceJars = append(j.implementationAndResourceJars, dep.ImplementationAndResourcesJars...)
+			j.resourceJars = append(j.resourceJars, dep.ResourceJars...)
+
+			transitiveImplementationJars = append(transitiveImplementationJars, dep.TransitiveStaticLibsImplementationJars)
+			transitiveResourceJars = append(transitiveResourceJars, dep.TransitiveStaticLibsResourceJars)
+			j.aconfigProtoFiles = append(j.aconfigProtoFiles, dep.AconfigIntermediateCacheOutputPaths...)
+		} else if dep, ok := android.OtherModuleProvider(ctx, m, android.OutputFilesProvider); ok {
+			// This is provided by `java_genrule` modules.
+			j.implementationJars = append(j.implementationJars, dep.DefaultOutputFiles...)
+			j.implementationAndResourceJars = append(j.implementationAndResourceJars, dep.DefaultOutputFiles...)
+			stubsLinkType = Implementation
+		} else {
+			ctx.PropertyErrorf("static_libs", "module %q cannot be used as a dependency", ctx.OtherModuleName(m))
+		}
+	})
+
+	jarName := ctx.ModuleName() + ".jar"
+
+	if len(j.implementationAndResourceJars) > 1 {
+		outputFile := android.PathForModuleOut(ctx, "combined", jarName)
+		TransformJarsToJar(ctx, outputFile, "combine", j.implementationAndResourceJars,
+			android.OptionalPath{}, false, nil, nil)
+		j.combinedImplementationJar = outputFile
+	} else if len(j.implementationAndResourceJars) == 1 {
+		j.combinedImplementationJar = j.implementationAndResourceJars[0]
+	}
+
+	if len(j.headerJars) > 1 {
+		outputFile := android.PathForModuleOut(ctx, "turbine-combined", jarName)
+		TransformJarsToJar(ctx, outputFile, "turbine combine", j.headerJars,
+			android.OptionalPath{}, false, nil, []string{"META-INF/TRANSITIVE"})
+		j.combinedHeaderJar = outputFile
+		j.headerDirs = append(j.headerDirs, android.PathForModuleOut(ctx, "turbine-combined"))
+	} else if len(j.headerJars) == 1 {
+		j.combinedHeaderJar = j.headerJars[0]
+	}
+
+	javaInfo := &JavaInfo{
+		HeaderJars:                             android.Paths{j.combinedHeaderJar},
+		LocalHeaderJars:                        android.Paths{j.combinedHeaderJar},
+		TransitiveStaticLibsHeaderJars:         depset.New(depset.PREORDER, android.Paths{j.combinedHeaderJar}, transitiveHeaderJars),
+		TransitiveStaticLibsImplementationJars: depset.New(depset.PREORDER, android.Paths{j.combinedImplementationJar}, transitiveImplementationJars),
+		TransitiveStaticLibsResourceJars:       depset.New(depset.PREORDER, nil, transitiveResourceJars),
+		GeneratedSrcjars:                       android.Paths{j.combinedImplementationJar},
+		ImplementationAndResourcesJars:         android.Paths{j.combinedImplementationJar},
+		ImplementationJars:                     android.Paths{j.combinedImplementationJar},
+		ModuleWithSdkDepInfo:                   moduleWithSdkDepInfo,
+		ResourceJars:                           j.resourceJars,
+		OutputFile:                             j.combinedImplementationJar,
+		SdkVersion:                             sdkVersion,
+		SrcJarArgs:                             j.srcJarArgs,
+		SrcJarDeps:                             j.srcJarDeps,
+		StubsLinkType:                          stubsLinkType,
+		AconfigIntermediateCacheOutputPaths:    j.aconfigProtoFiles,
+	}
+	setExtraJavaInfo(ctx, j, javaInfo)
+	ctx.SetOutputFiles(android.Paths{javaInfo.OutputFile}, "")
+	ctx.SetOutputFiles(android.Paths{javaInfo.OutputFile}, android.DefaultDistTag)
+	ctx.SetOutputFiles(javaInfo.ImplementationAndResourcesJars, ".jar")
+	ctx.SetOutputFiles(javaInfo.HeaderJars, ".hjar")
+	android.SetProvider(ctx, JavaInfoProvider, javaInfo)
+
+}
+
+func (j *GenruleCombiner) GeneratedSourceFiles() android.Paths {
+	return append(android.Paths{}, j.combinedImplementationJar)
+}
+
+func (j *GenruleCombiner) GeneratedHeaderDirs() android.Paths {
+	return append(android.Paths{}, j.headerDirs...)
+}
+
+func (j *GenruleCombiner) GeneratedDeps() android.Paths {
+	return append(android.Paths{}, j.combinedImplementationJar)
+}
+
+func (j *GenruleCombiner) Srcs() android.Paths {
+	return append(android.Paths{}, j.implementationAndResourceJars...)
+}
+
+func (j *GenruleCombiner) HeaderJars() android.Paths {
+	return j.headerJars
+}
+
+func (j *GenruleCombiner) ImplementationAndResourcesJars() android.Paths {
+	return j.implementationAndResourceJars
+}
+
+func (j *GenruleCombiner) DexJarBuildPath(ctx android.ModuleErrorfContext) android.Path {
+	return nil
+}
+
+func (j *GenruleCombiner) DexJarInstallPath() android.Path {
+	return nil
+}
+
+func (j *GenruleCombiner) AidlIncludeDirs() android.Paths {
+	return nil
+}
+
+func (j *GenruleCombiner) ClassLoaderContexts() dexpreopt.ClassLoaderContextMap {
+	return nil
+}
+
+func (j *GenruleCombiner) JacocoReportClassesFile() android.Path {
+	return nil
+}
+
+func (j *GenruleCombiner) AndroidMk() android.AndroidMkData {
+	return android.AndroidMkData{
+		Class:      "JAVA_LIBRARIES",
+		OutputFile: android.OptionalPathForPath(j.combinedImplementationJar),
+		// Make does not support Windows Java modules
+		Disabled: j.Os() == android.Windows,
+		Include:  "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
+		Extra: []android.AndroidMkExtraFunc{
+			func(w io.Writer, outputFile android.Path) {
+				fmt.Fprintln(w, "LOCAL_UNINSTALLABLE_MODULE := true")
+				fmt.Fprintln(w, "LOCAL_SOONG_HEADER_JAR :=", j.combinedHeaderJar.String())
+				fmt.Fprintln(w, "LOCAL_SOONG_CLASSES_JAR :=", j.combinedImplementationJar.String())
+			},
+		},
+	}
+}
+
+// implement the following interface for IDE completion.
+var _ android.IDEInfo = (*GenruleCombiner)(nil)
+
+func (j *GenruleCombiner) IDEInfo(ctx android.BaseModuleContext, ideInfo *android.IdeInfo) {
+	ideInfo.Deps = append(ideInfo.Deps, j.genruleCombinerProperties.Static_libs.GetOrDefault(ctx, nil)...)
+	ideInfo.Libs = append(ideInfo.Libs, j.genruleCombinerProperties.Static_libs.GetOrDefault(ctx, nil)...)
+	ideInfo.Deps = append(ideInfo.Deps, j.genruleCombinerProperties.Headers.GetOrDefault(ctx, nil)...)
+	ideInfo.Libs = append(ideInfo.Libs, j.genruleCombinerProperties.Headers.GetOrDefault(ctx, nil)...)
+}
diff --git a/java/genrule_combiner_test.go b/java/genrule_combiner_test.go
new file mode 100644
index 0000000..7d024cf
--- /dev/null
+++ b/java/genrule_combiner_test.go
@@ -0,0 +1,237 @@
+// 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 java
+
+import (
+	"reflect"
+	"testing"
+
+	"android/soong/android"
+)
+
+func TestJarGenruleCombinerSingle(t *testing.T) {
+	t.Parallel()
+	t.Helper()
+	ctx := prepareForJavaTest.RunTestWithBp(t, `
+		java_library {
+			name: "foo",
+			srcs: ["a.java"],
+		}
+
+		java_genrule {
+			name: "gen",
+			tool_files: ["b.java"],
+			cmd: "$(location b.java) $(in) $(out)",
+			out: ["gen.jar"],
+			srcs: [":foo"],
+		}
+
+		java_genrule_combiner {
+			name: "jarcomb",
+			static_libs: ["gen"],
+			headers: ["foo"],
+		}
+
+		java_library {
+			name: "bar",
+			static_libs: ["jarcomb"],
+			srcs: ["c.java"],
+		}
+
+		java_library {
+			name: "baz",
+			libs: ["jarcomb"],
+			srcs: ["c.java"],
+		}
+	`).TestContext
+
+	fooMod := ctx.ModuleForTests(t, "foo", "android_common")
+	fooCombined := fooMod.Output("turbine-combined/foo.jar")
+	fooOutputFiles, _ := android.OtherModuleProvider(ctx.OtherModuleProviderAdaptor(), fooMod.Module(), android.OutputFilesProvider)
+	fooHeaderJars := fooOutputFiles.TaggedOutputFiles[".hjar"]
+
+	genMod := ctx.ModuleForTests(t, "gen", "android_common")
+	gen := genMod.Output("gen.jar")
+
+	jarcombMod := ctx.ModuleForTests(t, "jarcomb", "android_common")
+	jarcombInfo, _ := android.OtherModuleProvider(ctx.OtherModuleProviderAdaptor(), jarcombMod.Module(), JavaInfoProvider)
+	jarcombOutputFiles, _ := android.OtherModuleProvider(ctx.OtherModuleProviderAdaptor(), jarcombMod.Module(), android.OutputFilesProvider)
+
+	// Confirm that jarcomb simply forwards the jarcomb implementation and the foo headers.
+	if len(jarcombOutputFiles.DefaultOutputFiles) != 1 ||
+		android.PathRelativeToTop(jarcombOutputFiles.DefaultOutputFiles[0]) != android.PathRelativeToTop(gen.Output) {
+		t.Errorf("jarcomb Implementation %v is not [%q]",
+			android.PathsRelativeToTop(jarcombOutputFiles.DefaultOutputFiles), android.PathRelativeToTop(gen.Output))
+	}
+	jarcombHeaderJars := jarcombOutputFiles.TaggedOutputFiles[".hjar"]
+	if !reflect.DeepEqual(jarcombHeaderJars, fooHeaderJars) {
+		t.Errorf("jarcomb Header jar %v is not %q",
+			jarcombHeaderJars, fooHeaderJars)
+	}
+
+	// Confirm that JavaInfoProvider agrees.
+	if len(jarcombInfo.ImplementationJars) != 1 ||
+		android.PathRelativeToTop(jarcombInfo.ImplementationJars[0]) != android.PathRelativeToTop(gen.Output) {
+		t.Errorf("jarcomb ImplementationJars %v is not [%q]",
+			android.PathsRelativeToTop(jarcombInfo.ImplementationJars), android.PathRelativeToTop(gen.Output))
+	}
+	if len(jarcombInfo.HeaderJars) != 1 ||
+		android.PathRelativeToTop(jarcombInfo.HeaderJars[0]) != android.PathRelativeToTop(fooCombined.Output) {
+		t.Errorf("jarcomb HeaderJars %v is not [%q]",
+			android.PathsRelativeToTop(jarcombInfo.HeaderJars), android.PathRelativeToTop(fooCombined.Output))
+	}
+
+	barMod := ctx.ModuleForTests(t, "bar", "android_common")
+	bar := barMod.Output("javac/bar.jar")
+	barCombined := barMod.Output("combined/bar.jar")
+
+	// Confirm that bar uses the Implementation from gen and headerJars from foo.
+	if len(barCombined.Inputs) != 2 ||
+		barCombined.Inputs[0].String() != bar.Output.String() ||
+		barCombined.Inputs[1].String() != gen.Output.String() {
+		t.Errorf("bar combined jar inputs %v is not [%q, %q]",
+			barCombined.Inputs.Strings(), bar.Output.String(), gen.Output.String())
+	}
+
+	bazMod := ctx.ModuleForTests(t, "baz", "android_common")
+	baz := bazMod.Output("javac/baz.jar")
+
+	string_in_list := func(s string, l []string) bool {
+		for _, v := range l {
+			if s == v {
+				return true
+			}
+		}
+		return false
+	}
+
+	// Confirm that baz uses the headerJars from foo.
+	bazImplicitsRel := android.PathsRelativeToTop(baz.Implicits)
+	for _, v := range android.PathsRelativeToTop(fooHeaderJars) {
+		if !string_in_list(v, bazImplicitsRel) {
+			t.Errorf("baz Implicits %v does not contain %q", bazImplicitsRel, v)
+		}
+	}
+}
+
+func TestJarGenruleCombinerMulti(t *testing.T) {
+	t.Parallel()
+	t.Helper()
+	ctx := prepareForJavaTest.RunTestWithBp(t, `
+		java_library {
+			name: "foo1",
+			srcs: ["foo1_a.java"],
+		}
+
+		java_library {
+			name: "foo2",
+			srcs: ["foo2_a.java"],
+		}
+
+		java_genrule {
+			name: "gen1",
+			tool_files: ["b.java"],
+			cmd: "$(location b.java) $(in) $(out)",
+			out: ["gen1.jar"],
+			srcs: [":foo1"],
+		}
+
+		java_genrule {
+			name: "gen2",
+			tool_files: ["b.java"],
+			cmd: "$(location b.java) $(in) $(out)",
+			out: ["gen2.jar"],
+			srcs: [":foo2"],
+		}
+
+		// Combine multiple java_genrule modules.
+		java_genrule_combiner {
+			name: "jarcomb",
+			static_libs: ["gen1", "gen2"],
+			headers: ["foo1", "foo2"],
+		}
+
+		java_library {
+			name: "bar",
+			static_libs: ["jarcomb"],
+			srcs: ["c.java"],
+		}
+
+		java_library {
+			name: "baz",
+			libs: ["jarcomb"],
+			srcs: ["c.java"],
+		}
+	`).TestContext
+
+	gen1Mod := ctx.ModuleForTests(t, "gen1", "android_common")
+	gen1 := gen1Mod.Output("gen1.jar")
+	gen2Mod := ctx.ModuleForTests(t, "gen2", "android_common")
+	gen2 := gen2Mod.Output("gen2.jar")
+
+	jarcombMod := ctx.ModuleForTests(t, "jarcomb", "android_common")
+	jarcomb := jarcombMod.Output("combined/jarcomb.jar")
+	jarcombTurbine := jarcombMod.Output("turbine-combined/jarcomb.jar")
+	_ = jarcombTurbine
+	jarcombInfo, _ := android.OtherModuleProvider(ctx.OtherModuleProviderAdaptor(), jarcombMod.Module(), JavaInfoProvider)
+	_ = jarcombInfo
+	jarcombOutputFiles, _ := android.OtherModuleProvider(ctx.OtherModuleProviderAdaptor(), jarcombMod.Module(), android.OutputFilesProvider)
+	jarcombHeaderJars := jarcombOutputFiles.TaggedOutputFiles[".hjar"]
+
+	if len(jarcomb.Inputs) != 2 ||
+		jarcomb.Inputs[0].String() != gen1.Output.String() ||
+		jarcomb.Inputs[1].String() != gen2.Output.String() {
+		t.Errorf("jarcomb inputs %v are not [%q, %q]",
+			jarcomb.Inputs.Strings(), gen1.Output.String(), gen2.Output.String())
+	}
+
+	if len(jarcombHeaderJars) != 1 ||
+		android.PathRelativeToTop(jarcombHeaderJars[0]) != android.PathRelativeToTop(jarcombTurbine.Output) {
+		t.Errorf("jarcomb Header jars %v is not [%q]",
+			android.PathsRelativeToTop(jarcombHeaderJars), android.PathRelativeToTop(jarcombTurbine.Output))
+	}
+
+	barMod := ctx.ModuleForTests(t, "bar", "android_common")
+	bar := barMod.Output("javac/bar.jar")
+	barCombined := barMod.Output("combined/bar.jar")
+
+	// Confirm that bar uses the Implementation and Headers from jarcomb.
+	if len(barCombined.Inputs) != 2 ||
+		barCombined.Inputs[0].String() != bar.Output.String() ||
+		barCombined.Inputs[1].String() != jarcomb.Output.String() {
+		t.Errorf("bar combined jar inputs %v is not [%q, %q]",
+			barCombined.Inputs.Strings(), bar.Output.String(), jarcomb.Output.String())
+	}
+
+	bazMod := ctx.ModuleForTests(t, "baz", "android_common")
+	baz := bazMod.Output("javac/baz.jar")
+
+	string_in_list := func(s string, l []string) bool {
+		for _, v := range l {
+			if s == v {
+				return true
+			}
+		}
+		return false
+	}
+
+	// Confirm that baz uses the headerJars from foo.
+	bazImplicitsRel := android.PathsRelativeToTop(baz.Implicits)
+	for _, v := range android.PathsRelativeToTop(jarcombHeaderJars) {
+		if !string_in_list(v, bazImplicitsRel) {
+			t.Errorf("baz Implicits %v does not contain %q", bazImplicitsRel, v)
+		}
+	}
+}
diff --git a/java/genrule_test.go b/java/genrule_test.go
index 1c294b2..c112e45 100644
--- a/java/genrule_test.go
+++ b/java/genrule_test.go
@@ -31,6 +31,7 @@
 }
 
 func TestGenruleCmd(t *testing.T) {
+	t.Parallel()
 	fs := map[string][]byte{
 		"tool": nil,
 		"foo":  nil,
@@ -56,7 +57,7 @@
 		t.Fatal(errs)
 	}
 
-	gen := ctx.ModuleForTests("gen", "android_common").Output("out")
+	gen := ctx.ModuleForTests(t, "gen", "android_common").Output("out")
 	expected := []string{"foo"}
 	if !reflect.DeepEqual(expected, gen.Implicits.Strings()[:len(expected)]) {
 		t.Errorf(`want arm inputs %v, got %v`, expected, gen.Implicits.Strings())
@@ -64,6 +65,7 @@
 }
 
 func TestJarGenrules(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_library {
 			name: "foo",
@@ -91,11 +93,11 @@
 		}
 	`)
 
-	foo := ctx.ModuleForTests("foo", "android_common").Output("javac/foo.jar")
-	jargen := ctx.ModuleForTests("jargen", "android_common").Output("jargen.jar")
-	bar := ctx.ModuleForTests("bar", "android_common").Output("javac/bar.jar")
-	baz := ctx.ModuleForTests("baz", "android_common").Output("javac/baz.jar")
-	barCombined := ctx.ModuleForTests("bar", "android_common").Output("combined/bar.jar")
+	foo := ctx.ModuleForTests(t, "foo", "android_common").Output("javac/foo.jar")
+	jargen := ctx.ModuleForTests(t, "jargen", "android_common").Output("jargen.jar")
+	bar := ctx.ModuleForTests(t, "bar", "android_common").Output("javac/bar.jar")
+	baz := ctx.ModuleForTests(t, "baz", "android_common").Output("javac/baz.jar")
+	barCombined := ctx.ModuleForTests(t, "bar", "android_common").Output("combined/bar.jar")
 
 	if g, w := jargen.Implicits.Strings(), foo.Output.String(); !android.InList(w, g) {
 		t.Errorf("expected jargen inputs [%q], got %q", w, g)
diff --git a/java/hiddenapi.go b/java/hiddenapi.go
index b1a9deb..c9a1f2b 100644
--- a/java/hiddenapi.go
+++ b/java/hiddenapi.go
@@ -97,7 +97,7 @@
 	// Save the classes jars even if this is not active as they may be used by modular hidden API
 	// processing.
 	classesJars := android.Paths{classesJar}
-	ctx.VisitDirectDepsWithTag(hiddenApiAnnotationsTag, func(dep android.Module) {
+	ctx.VisitDirectDepsProxyWithTag(hiddenApiAnnotationsTag, func(dep android.ModuleProxy) {
 		if javaInfo, ok := android.OtherModuleProvider(ctx, dep, JavaInfoProvider); ok {
 			classesJars = append(classesJars, javaInfo.ImplementationJars...)
 		}
diff --git a/java/hiddenapi_singleton.go b/java/hiddenapi_singleton.go
index 7d21b7a..2c86942 100644
--- a/java/hiddenapi_singleton.go
+++ b/java/hiddenapi_singleton.go
@@ -171,11 +171,11 @@
 	// Now match the apex part of the boot image configuration.
 	requiredApex := configuredBootJars.Apex(index)
 	if android.IsConfiguredJarForPlatform(requiredApex) {
-		if len(apexInfo.InApexVariants) != 0 {
+		if apexInfo.ApexVariationName != "" {
 			// A platform variant is required but this is for an apex so ignore it.
 			return false
 		}
-	} else if !apexInfo.InApexVariant(requiredApex) {
+	} else if apexInfo.BaseApexName != requiredApex {
 		// An apex variant for a specific apex is required but this is the wrong apex.
 		return false
 	}
diff --git a/java/hiddenapi_singleton_test.go b/java/hiddenapi_singleton_test.go
index afe8b4c..147f326 100644
--- a/java/hiddenapi_singleton_test.go
+++ b/java/hiddenapi_singleton_test.go
@@ -44,6 +44,7 @@
 )
 
 func TestHiddenAPISingleton(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		hiddenApiFixtureFactory,
 		FixtureConfigureBootJars("platform:foo"),
@@ -56,13 +57,14 @@
 		}
 	`)
 
-	hiddenAPI := result.ModuleForTests("platform-bootclasspath", "android_common")
+	hiddenAPI := result.ModuleForTests(t, "platform-bootclasspath", "android_common")
 	hiddenapiRule := hiddenAPI.Rule("platform-bootclasspath-monolithic-hiddenapi-stub-flags")
 	want := "--boot-dex=out/soong/.intermediates/foo/android_common/aligned/foo.jar"
 	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, want)
 }
 
 func TestHiddenAPISingletonWithSourceAndPrebuiltPreferredButNoDex(t *testing.T) {
+	t.Parallel()
 	expectedErrorMessage := "module prebuilt_foo{os:android,arch:common} does not provide a dex jar"
 
 	android.GroupFixturePreparers(
@@ -86,6 +88,7 @@
 }
 
 func TestHiddenAPISingletonWithPrebuilt(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		hiddenApiFixtureFactory,
 		FixtureConfigureBootJars("platform:foo"),
@@ -98,13 +101,14 @@
 	}
 	`)
 
-	hiddenAPI := result.ModuleForTests("platform-bootclasspath", "android_common")
+	hiddenAPI := result.ModuleForTests(t, "platform-bootclasspath", "android_common")
 	hiddenapiRule := hiddenAPI.Rule("platform-bootclasspath-monolithic-hiddenapi-stub-flags")
 	want := "--boot-dex=out/soong/.intermediates/foo/android_common/aligned/foo.jar"
 	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, want)
 }
 
 func TestHiddenAPISingletonWithPrebuiltUseSource(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		hiddenApiFixtureFactory,
 		FixtureConfigureBootJars("platform:foo"),
@@ -124,7 +128,7 @@
 		}
 	`)
 
-	hiddenAPI := result.ModuleForTests("platform-bootclasspath", "android_common")
+	hiddenAPI := result.ModuleForTests(t, "platform-bootclasspath", "android_common")
 	hiddenapiRule := hiddenAPI.Rule("platform-bootclasspath-monolithic-hiddenapi-stub-flags")
 	fromSourceJarArg := "--boot-dex=out/soong/.intermediates/foo/android_common/aligned/foo.jar"
 	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, fromSourceJarArg)
@@ -134,6 +138,7 @@
 }
 
 func TestHiddenAPISingletonWithPrebuiltOverrideSource(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		hiddenApiFixtureFactory,
 		FixtureConfigureBootJars("platform:foo"),
@@ -153,7 +158,7 @@
 		}
 	`)
 
-	hiddenAPI := result.ModuleForTests("platform-bootclasspath", "android_common")
+	hiddenAPI := result.ModuleForTests(t, "platform-bootclasspath", "android_common")
 	hiddenapiRule := hiddenAPI.Rule("platform-bootclasspath-monolithic-hiddenapi-stub-flags")
 	prebuiltJarArg := "--boot-dex=out/soong/.intermediates/prebuilt_foo/android_common/dex/foo.jar"
 	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, prebuiltJarArg)
@@ -163,6 +168,7 @@
 }
 
 func TestHiddenAPISingletonSdks(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		name             string
 		unbundledBuild   bool
@@ -213,7 +219,7 @@
 		}
 		`)
 
-			hiddenAPI := result.ModuleForTests("platform-bootclasspath", "android_common")
+			hiddenAPI := result.ModuleForTests(t, "platform-bootclasspath", "android_common")
 			hiddenapiRule := hiddenAPI.Rule("platform-bootclasspath-monolithic-hiddenapi-stub-flags")
 			wantPublicStubs := "--public-stub-classpath=" + generateSdkDexPath(tc.publicStub, tc.unbundledBuild)
 			android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, wantPublicStubs)
@@ -246,6 +252,7 @@
 }
 
 func TestHiddenAPISingletonWithPrebuiltCsvFile(t *testing.T) {
+	t.Parallel()
 
 	// The idea behind this test is to ensure that when the build is
 	// confugured with a PrebuiltHiddenApiDir that the rules for the
@@ -272,9 +279,9 @@
 	expectedCpOutput := "out/soong/hiddenapi/hiddenapi-flags.csv"
 	expectedFlagsCsv := "out/soong/hiddenapi/hiddenapi-flags.csv"
 
-	foo := result.ModuleForTests("foo", "android_common")
+	foo := result.ModuleForTests(t, "foo", "android_common")
 
-	hiddenAPI := result.SingletonForTests("hiddenapi")
+	hiddenAPI := result.SingletonForTests(t, "hiddenapi")
 	cpRule := hiddenAPI.Rule("Cp")
 	actualCpInput := cpRule.BuildParams.Input
 	actualCpOutput := cpRule.BuildParams.Output
@@ -289,6 +296,7 @@
 }
 
 func TestHiddenAPIEncoding_JavaSdkLibrary(t *testing.T) {
+	t.Parallel()
 
 	result := android.GroupFixturePreparers(
 		hiddenApiFixtureFactory,
@@ -310,7 +318,7 @@
 	`)
 
 	checkDexEncoded := func(t *testing.T, name, unencodedDexJar, encodedDexJar string) {
-		moduleForTests := result.ModuleForTests(name+".impl", "android_common")
+		moduleForTests := result.ModuleForTests(t, name+".impl", "android_common")
 
 		encodeDexRule := moduleForTests.Rule("hiddenAPIEncodeDex")
 		actualUnencodedDexJar := encodeDexRule.Input
@@ -324,10 +332,7 @@
 		android.AssertPathRelativeToTopEquals(t, "encode embedded java_library", encodedDexJar, exportedDexJar)
 	}
 
-	// The java_library embedded with the java_sdk_library must be dex encoded.
-	t.Run("foo", func(t *testing.T) {
-		expectedUnencodedDexJar := "out/soong/.intermediates/foo.impl/android_common/aligned/foo.jar"
-		expectedEncodedDexJar := "out/soong/.intermediates/foo.impl/android_common/hiddenapi/foo.jar"
-		checkDexEncoded(t, "foo", expectedUnencodedDexJar, expectedEncodedDexJar)
-	})
+	expectedUnencodedDexJar := "out/soong/.intermediates/foo.impl/android_common/aligned/foo.jar"
+	expectedEncodedDexJar := "out/soong/.intermediates/foo.impl/android_common/hiddenapi/foo.jar"
+	checkDexEncoded(t, "foo", expectedUnencodedDexJar, expectedEncodedDexJar)
 }
diff --git a/java/jacoco_test.go b/java/jacoco_test.go
index 1882908..58a091e 100644
--- a/java/jacoco_test.go
+++ b/java/jacoco_test.go
@@ -17,6 +17,7 @@
 import "testing"
 
 func TestJacocoFilterToSpecs(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		name, in, out string
 	}{
@@ -54,6 +55,7 @@
 
 	for _, testCase := range testCases {
 		t.Run(testCase.name, func(t *testing.T) {
+			t.Parallel()
 			got, err := jacocoFilterToSpec(testCase.in)
 			if err != nil {
 				t.Error(err)
@@ -66,6 +68,7 @@
 }
 
 func TestJacocoFiltersToZipCommand(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		name               string
 		includes, excludes []string
@@ -96,6 +99,7 @@
 
 	for _, testCase := range testCases {
 		t.Run(testCase.name, func(t *testing.T) {
+			t.Parallel()
 			got := jacocoFiltersToZipCommand(testCase.includes, testCase.excludes)
 			if got != testCase.out {
 				t.Errorf("expected %q got %q", testCase.out, got)
diff --git a/java/jarjar_test.go b/java/jarjar_test.go
index 82bfa2b..b689761 100644
--- a/java/jarjar_test.go
+++ b/java/jarjar_test.go
@@ -22,7 +22,7 @@
 )
 
 func AssertJarJarRename(t *testing.T, result *android.TestResult, libName, original, expectedRename string) {
-	module := result.ModuleForTests(libName, "android_common")
+	module := result.ModuleForTests(t, libName, "android_common")
 
 	provider, found := android.OtherModuleProvider(result.OtherModuleProviderAdaptor(), module.Module(), JarJarProvider)
 	android.AssertBoolEquals(t, fmt.Sprintf("found provider (%s)", libName), true, found)
diff --git a/java/java.go b/java/java.go
index 018850f..33941e9 100644
--- a/java/java.go
+++ b/java/java.go
@@ -26,9 +26,9 @@
 	"strings"
 
 	"android/soong/remoteexec"
-	"android/soong/testing"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/depset"
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
@@ -64,15 +64,16 @@
 	ctx.RegisterModuleType("java_api_library", ApiLibraryFactory)
 	ctx.RegisterModuleType("java_api_contribution", ApiContributionFactory)
 	ctx.RegisterModuleType("java_api_contribution_import", ApiContributionImportFactory)
+	ctx.RegisterModuleType("java_genrule_combiner", GenruleCombinerFactory)
 
 	// This mutator registers dependencies on dex2oat for modules that should be
 	// dexpreopted. This is done late when the final variants have been
 	// established, to not get the dependencies split into the wrong variants and
 	// to support the checks in dexpreoptDisabled().
 	ctx.FinalDepsMutators(func(ctx android.RegisterMutatorsContext) {
-		ctx.BottomUp("dexpreopt_tool_deps", dexpreoptToolDepsMutator).Parallel()
+		ctx.BottomUp("dexpreopt_tool_deps", dexpreoptToolDepsMutator)
 		// needs access to ApexInfoProvider which is available after variant creation
-		ctx.BottomUp("jacoco_deps", jacocoDepsMutator).Parallel()
+		ctx.BottomUp("jacoco_deps", jacocoDepsMutator)
 	})
 
 	ctx.RegisterParallelSingletonType("kythe_java_extract", kytheExtractJavaFactory)
@@ -226,9 +227,9 @@
 
 	// Rule for generating device binary default wrapper
 	deviceBinaryWrapper = pctx.StaticRule("deviceBinaryWrapper", blueprint.RuleParams{
-		Command: `echo -e '#!/system/bin/sh\n` +
+		Command: `printf '#!/system/bin/sh\n` +
 			`export CLASSPATH=/system/framework/$jar_name\n` +
-			`exec app_process /$partition/bin $main_class "$$@"'> ${out}`,
+			`exec app_process /$partition/bin $main_class "$$@"\n'> ${out}`,
 		Description: "Generating device binary wrapper ${jar_name}",
 	}, "jar_name", "partition", "main_class")
 )
@@ -242,14 +243,45 @@
 	// TransitiveDepsProguardSpecFiles is a depset of paths to proguard flags files that are exported from
 	// all transitive deps. This list includes all proguard flags files from transitive static dependencies,
 	// and all proguard flags files from transitive libs dependencies which set `export_proguard_spec: true`.
-	ProguardFlagsFiles *android.DepSet[android.Path]
+	ProguardFlagsFiles depset.DepSet[android.Path]
 
 	// implementation detail to store transitive proguard flags files from exporting shared deps
-	UnconditionallyExportedProguardFlags *android.DepSet[android.Path]
+	UnconditionallyExportedProguardFlags depset.DepSet[android.Path]
 }
 
 var ProguardSpecInfoProvider = blueprint.NewProvider[ProguardSpecInfo]()
 
+type AndroidLibraryDependencyInfo struct {
+	ExportPackage       android.Path
+	ResourcesNodeDepSet depset.DepSet[*resourcesNode]
+	RRODirsDepSet       depset.DepSet[rroDir]
+	ManifestsDepSet     depset.DepSet[android.Path]
+}
+
+type UsesLibraryDependencyInfo struct {
+	DexJarBuildPath     OptionalDexJarPath
+	DexJarInstallPath   android.Path
+	ClassLoaderContexts dexpreopt.ClassLoaderContextMap
+}
+
+type SdkLibraryComponentDependencyInfo struct {
+	// The name of the implementation library for the optional SDK library or nil, if there isn't one.
+	OptionalSdkLibraryImplementation *string
+}
+
+type ProvidesUsesLibInfo struct {
+	ProvidesUsesLib *string
+}
+
+type ModuleWithUsesLibraryInfo struct {
+	UsesLibrary *usesLibrary
+}
+
+type ModuleWithSdkDepInfo struct {
+	SdkLinkType sdkLinkType
+	Stubs       bool
+}
+
 // JavaInfo contains information about a java module for use by modules that depend on it.
 type JavaInfo struct {
 	// HeaderJars is a list of jars that can be passed as the javac classpath in order to link
@@ -260,19 +292,19 @@
 	RepackagedHeaderJars android.Paths
 
 	// set of header jars for all transitive libs deps
-	TransitiveLibsHeaderJarsForR8 *android.DepSet[android.Path]
+	TransitiveLibsHeaderJarsForR8 depset.DepSet[android.Path]
 
 	// set of header jars for all transitive static libs deps
-	TransitiveStaticLibsHeaderJarsForR8 *android.DepSet[android.Path]
+	TransitiveStaticLibsHeaderJarsForR8 depset.DepSet[android.Path]
 
 	// depset of header jars for this module and all transitive static dependencies
-	TransitiveStaticLibsHeaderJars *android.DepSet[android.Path]
+	TransitiveStaticLibsHeaderJars depset.DepSet[android.Path]
 
 	// depset of implementation jars for this module and all transitive static dependencies
-	TransitiveStaticLibsImplementationJars *android.DepSet[android.Path]
+	TransitiveStaticLibsImplementationJars depset.DepSet[android.Path]
 
 	// depset of resource jars for this module and all transitive static dependencies
-	TransitiveStaticLibsResourceJars *android.DepSet[android.Path]
+	TransitiveStaticLibsResourceJars depset.DepSet[android.Path]
 
 	// ImplementationAndResourceJars is a list of jars that contain the implementations of classes
 	// in the module as well as any resources included in the module.
@@ -300,7 +332,7 @@
 	SrcJarDeps android.Paths
 
 	// The source files of this module and all its transitive static dependencies.
-	TransitiveSrcFiles *android.DepSet[android.Path]
+	TransitiveSrcFiles depset.DepSet[android.Path]
 
 	// ExportedPlugins is a list of paths that should be used as annotation processors for any
 	// module that depends on this module.
@@ -326,10 +358,94 @@
 	// AconfigIntermediateCacheOutputPaths is a path to the cache files collected from the
 	// java_aconfig_library modules that are statically linked to this module.
 	AconfigIntermediateCacheOutputPaths android.Paths
+
+	SdkVersion android.SdkSpec
+
+	// output file of the module, which may be a classes jar or a dex jar
+	OutputFile android.Path
+
+	ExtraOutputFiles android.Paths
+
+	AndroidLibraryDependencyInfo *AndroidLibraryDependencyInfo
+
+	UsesLibraryDependencyInfo *UsesLibraryDependencyInfo
+
+	SdkLibraryComponentDependencyInfo *SdkLibraryComponentDependencyInfo
+
+	ProvidesUsesLibInfo *ProvidesUsesLibInfo
+
+	MissingOptionalUsesLibs []string
+
+	ModuleWithSdkDepInfo *ModuleWithSdkDepInfo
+
+	// output file containing classes.dex and resources
+	DexJarFile OptionalDexJarPath
+
+	// installed file for binary dependency
+	InstallFile android.Path
+
+	// The path to the dex jar that is in the boot class path. If this is unset then the associated
+	// module is not a boot jar, but could be one of the <x>-hiddenapi modules that provide additional
+	// annotations for the <x> boot dex jar but which do not actually provide a boot dex jar
+	// themselves.
+	//
+	// This must be the path to the unencoded dex jar as the encoded dex jar indirectly depends on
+	// this file so using the encoded dex jar here would result in a cycle in the ninja rules.
+	BootDexJarPath OptionalDexJarPath
+
+	// The compressed state of the dex file being encoded. This is used to ensure that the encoded
+	// dex file has the same state.
+	UncompressDexState *bool
+
+	// True if the module containing this structure contributes to the hiddenapi information or has
+	// that information encoded within it.
+	Active bool
+
+	BuiltInstalled string
+
+	// ApexSystemServerDexpreoptInstalls stores the list of dexpreopt artifacts if this is a system server
+	// jar in an apex.
+	ApexSystemServerDexpreoptInstalls []DexpreopterInstall
+
+	// ApexSystemServerDexJars stores the list of dex jars if this is a system server jar in an apex.
+	ApexSystemServerDexJars android.Paths
+
+	// The config is used for two purposes:
+	// - Passing dexpreopt information about libraries from Soong to Make. This is needed when
+	//   a <uses-library> is defined in Android.bp, but used in Android.mk (see dex_preopt_config_merger.py).
+	//   Note that dexpreopt.config might be needed even if dexpreopt is disabled for the library itself.
+	// - Dexpreopt post-processing (using dexpreopt artifacts from a prebuilt system image to incrementally
+	//   dexpreopt another partition).
+	ConfigPath android.WritablePath
+
+	// The path to the profile on host that dexpreopter generates. This is used as the input for
+	// dex2oat.
+	OutputProfilePathOnHost android.Path
+
+	LogtagsSrcs android.Paths
+
+	ProguardDictionary android.OptionalPath
+
+	ProguardUsageZip android.OptionalPath
+
+	LinterReports android.Paths
+
+	// installed file for hostdex copy
+	HostdexInstallFile android.InstallPath
+
+	// Additional srcJars tacked in by GeneratedJavaLibraryModule
+	GeneratedSrcjars []android.Path
+
+	// True if profile-guided optimization is actually enabled.
+	ProfileGuided bool
 }
 
 var JavaInfoProvider = blueprint.NewProvider[*JavaInfo]()
 
+type JavaLibraryInfo struct{}
+
+var JavaLibraryInfoProvider = blueprint.NewProvider[JavaLibraryInfo]()
+
 // SyspropPublicStubInfo contains info about the sysprop public stub library that corresponds to
 // the sysprop implementation library.
 type SyspropPublicStubInfo struct {
@@ -450,7 +566,6 @@
 	javaApiContributionTag  = dependencyTag{name: "java-api-contribution"}
 	aconfigDeclarationTag   = dependencyTag{name: "aconfig-declaration"}
 	jniInstallTag           = dependencyTag{name: "jni install", runtimeLinked: true, installable: true}
-	binaryInstallTag        = dependencyTag{name: "binary install", runtimeLinked: true, installable: true}
 	usesLibReqTag           = makeUsesLibraryDependencyTag(dexpreopt.AnySdkVersion, false)
 	usesLibOptTag           = makeUsesLibraryDependencyTag(dexpreopt.AnySdkVersion, true)
 	usesLibCompat28OptTag   = makeUsesLibraryDependencyTag(28, true)
@@ -481,7 +596,7 @@
 )
 
 func IsLibDepTag(depTag blueprint.DependencyTag) bool {
-	return depTag == libTag || depTag == sdkLibTag
+	return depTag == libTag
 }
 
 func IsStaticLibDepTag(depTag blueprint.DependencyTag) bool {
@@ -587,16 +702,16 @@
 
 	disableTurbine bool
 
-	transitiveStaticLibsHeaderJars         []*android.DepSet[android.Path]
-	transitiveStaticLibsImplementationJars []*android.DepSet[android.Path]
-	transitiveStaticLibsResourceJars       []*android.DepSet[android.Path]
+	transitiveStaticLibsHeaderJars         []depset.DepSet[android.Path]
+	transitiveStaticLibsImplementationJars []depset.DepSet[android.Path]
+	transitiveStaticLibsResourceJars       []depset.DepSet[android.Path]
 }
 
-func checkProducesJars(ctx android.ModuleContext, dep android.SourceFileProducer) {
-	for _, f := range dep.Srcs() {
+func checkProducesJars(ctx android.ModuleContext, dep android.SourceFilesInfo, module android.ModuleProxy) {
+	for _, f := range dep.Srcs {
 		if f.Ext() != ".jar" {
 			ctx.ModuleErrorf("genrule %q must generate files ending with .jar to be used as a libs or static_libs dependency",
-				ctx.OtherModuleName(dep.(blueprint.Module)))
+				ctx.OtherModuleName(module))
 		}
 	}
 }
@@ -607,10 +722,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
@@ -716,6 +829,8 @@
 	combinedExportedProguardFlagsFile android.Path
 
 	InstallMixin func(ctx android.ModuleContext, installPath android.Path) (extraInstallDeps android.InstallPaths)
+
+	apiXmlFile android.WritablePath
 }
 
 var _ android.ApexModule = (*Library)(nil)
@@ -945,6 +1060,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)
 
@@ -1002,25 +1118,120 @@
 			j.dexpreopter.disableDexpreopt()
 		}
 	}
-	j.compile(ctx, nil, nil, nil, nil)
+	javaInfo := j.compile(ctx, nil, nil, nil, nil)
 
-	// If this module is an impl library created from java_sdk_library,
-	// install the files under the java_sdk_library module outdir instead of this module outdir.
-	if j.SdkLibraryName() != nil && strings.HasSuffix(j.Name(), ".impl") {
-		j.setInstallRules(ctx, proptools.String(j.SdkLibraryName()))
-	} else {
-		j.setInstallRules(ctx, ctx.ModuleName())
-	}
+	j.setInstallRules(ctx)
 
 	android.SetProvider(ctx, android.TestOnlyProviderKey, android.TestModuleInformation{
 		TestOnly:       Bool(j.sourceProperties.Test_only),
 		TopLevelTarget: j.sourceProperties.Top_level_test_target,
 	})
 
+	android.SetProvider(ctx, JavaLibraryInfoProvider, JavaLibraryInfo{})
+
+	if javaInfo != nil {
+		setExtraJavaInfo(ctx, j, javaInfo)
+		javaInfo.ExtraOutputFiles = j.extraOutputFiles
+		javaInfo.DexJarFile = j.dexJarFile
+		javaInfo.InstallFile = j.installFile
+		javaInfo.BootDexJarPath = j.bootDexJarPath
+		javaInfo.UncompressDexState = j.uncompressDexState
+		javaInfo.Active = j.active
+		javaInfo.ApexSystemServerDexpreoptInstalls = j.apexSystemServerDexpreoptInstalls
+		javaInfo.ApexSystemServerDexJars = j.apexSystemServerDexJars
+		javaInfo.BuiltInstalled = j.builtInstalled
+		javaInfo.ConfigPath = j.configPath
+		javaInfo.OutputProfilePathOnHost = j.outputProfilePathOnHost
+		javaInfo.LogtagsSrcs = j.logtagsSrcs
+		javaInfo.ProguardDictionary = j.proguardDictionary
+		javaInfo.ProguardUsageZip = j.proguardUsageZip
+		javaInfo.LinterReports = j.reports
+		javaInfo.HostdexInstallFile = j.hostdexInstallFile
+		javaInfo.GeneratedSrcjars = j.properties.Generated_srcjars
+		javaInfo.ProfileGuided = j.dexpreopter.dexpreoptProperties.Dex_preopt_result.Profile_guided
+
+		android.SetProvider(ctx, JavaInfoProvider, javaInfo)
+	}
+
 	setOutputFiles(ctx, j.Module)
+
+	j.javaLibraryModuleInfoJSON(ctx)
+
+	buildComplianceMetadata(ctx)
+
+	j.createApiXmlFile(ctx)
 }
 
-func (j *Library) setInstallRules(ctx android.ModuleContext, installModuleName string) {
+func (j *Library) javaLibraryModuleInfoJSON(ctx android.ModuleContext) *android.ModuleInfoJSON {
+	moduleInfoJSON := ctx.ModuleInfoJSON()
+	moduleInfoJSON.Class = []string{"JAVA_LIBRARIES"}
+	if j.implementationAndResourcesJar != nil {
+		moduleInfoJSON.ClassesJar = []string{j.implementationAndResourcesJar.String()}
+	}
+	moduleInfoJSON.SystemSharedLibs = []string{"none"}
+
+	if j.hostDexNeeded() {
+		hostDexModuleInfoJSON := ctx.ExtraModuleInfoJSON()
+		hostDexModuleInfoJSON.SubName = "-hostdex"
+		hostDexModuleInfoJSON.Class = []string{"JAVA_LIBRARIES"}
+		if j.implementationAndResourcesJar != nil {
+			hostDexModuleInfoJSON.ClassesJar = []string{j.implementationAndResourcesJar.String()}
+		}
+		hostDexModuleInfoJSON.SystemSharedLibs = []string{"none"}
+		hostDexModuleInfoJSON.SupportedVariantsOverride = []string{"HOST"}
+	}
+
+	if j.hideApexVariantFromMake {
+		moduleInfoJSON.Disabled = true
+	}
+	return moduleInfoJSON
+}
+
+func buildComplianceMetadata(ctx android.ModuleContext) {
+	// Dump metadata that can not be done in android/compliance-metadata.go
+	complianceMetadataInfo := ctx.ComplianceMetadataInfo()
+	builtFiles := ctx.GetOutputFiles().DefaultOutputFiles.Strings()
+	for _, paths := range ctx.GetOutputFiles().TaggedOutputFiles {
+		builtFiles = append(builtFiles, paths.Strings()...)
+	}
+	complianceMetadataInfo.SetListValue(android.ComplianceMetadataProp.BUILT_FILES, android.SortedUniqueStrings(builtFiles))
+
+	// Static deps
+	staticDepNames := make([]string, 0)
+	staticDepFiles := android.Paths{}
+	ctx.VisitDirectDepsWithTag(staticLibTag, func(module android.Module) {
+		if dep, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider); ok {
+			staticDepNames = append(staticDepNames, module.Name())
+			staticDepFiles = append(staticDepFiles, dep.ImplementationJars...)
+			staticDepFiles = append(staticDepFiles, dep.HeaderJars...)
+			staticDepFiles = append(staticDepFiles, dep.ResourceJars...)
+		}
+	})
+	complianceMetadataInfo.SetListValue(android.ComplianceMetadataProp.STATIC_DEPS, android.SortedUniqueStrings(staticDepNames))
+	complianceMetadataInfo.SetListValue(android.ComplianceMetadataProp.STATIC_DEP_FILES, android.SortedUniqueStrings(staticDepFiles.Strings()))
+}
+
+func (j *Library) getJarInstallDir(ctx android.ModuleContext) android.InstallPath {
+	var installDir android.InstallPath
+	if ctx.InstallInTestcases() {
+		var archDir string
+		if !ctx.Host() {
+			archDir = ctx.DeviceConfig().DeviceArch()
+		}
+		installModuleName := ctx.ModuleName()
+		// If this module is an impl library created from java_sdk_library,
+		// install the files under the java_sdk_library module outdir instead of this module outdir.
+		if j.SdkLibraryName() != nil && strings.HasSuffix(j.Name(), ".impl") {
+			installModuleName = proptools.String(j.SdkLibraryName())
+		}
+		installDir = android.PathForModuleInstall(ctx, installModuleName, archDir)
+	} else {
+		installDir = android.PathForModuleInstall(ctx, "framework")
+	}
+	return installDir
+}
+
+func (j *Library) setInstallRules(ctx android.ModuleContext) {
 	apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
 
 	if (Bool(j.properties.Installable) || ctx.Host()) && apexInfo.IsForPlatform() {
@@ -1034,17 +1245,7 @@
 				android.PathForHostDexInstall(ctx, "framework"),
 				j.Stem()+"-hostdex.jar", j.outputFile)
 		}
-		var installDir android.InstallPath
-		if ctx.InstallInTestcases() {
-			var archDir string
-			if !ctx.Host() {
-				archDir = ctx.DeviceConfig().DeviceArch()
-			}
-			installDir = android.PathForModuleInstall(ctx, installModuleName, archDir)
-		} else {
-			installDir = android.PathForModuleInstall(ctx, "framework")
-		}
-		j.installFile = ctx.InstallFileWithoutCheckbuild(installDir, j.Stem()+".jar", j.outputFile, extraInstallDeps...)
+		j.installFile = ctx.InstallFileWithoutCheckbuild(j.getJarInstallDir(ctx), j.Stem()+".jar", j.outputFile, extraInstallDeps...)
 	}
 }
 
@@ -1063,6 +1264,28 @@
 	}
 }
 
+var apiXMLGeneratingApiSurfaces = []android.SdkKind{
+	android.SdkPublic,
+	android.SdkSystem,
+	android.SdkModule,
+	android.SdkSystemServer,
+	android.SdkTest,
+}
+
+func (j *Library) createApiXmlFile(ctx android.ModuleContext) {
+	if kind, ok := android.JavaLibraryNameToSdkKind(ctx.ModuleName()); ok && android.InList(kind, apiXMLGeneratingApiSurfaces) {
+		scopePrefix := AllApiScopes.matchingScopeFromSdkKind(kind).apiFilePrefix
+		j.apiXmlFile = android.PathForModuleOut(ctx, fmt.Sprintf("%sapi.xml", scopePrefix))
+		ctx.Build(pctx, android.BuildParams{
+			Rule: generateApiXMLRule,
+			// LOCAL_SOONG_CLASSES_JAR
+			Input:  j.implementationAndResourcesJar,
+			Output: j.apiXmlFile,
+		})
+		ctx.DistForGoal("dist_files", j.apiXmlFile)
+	}
+}
+
 const (
 	aidlIncludeDir   = "aidl"
 	javaDir          = "java"
@@ -1288,6 +1511,22 @@
 	// the test
 	Data []string `android:"path"`
 
+	// Same as data, but will add dependencies on modules using the device's os variation and
+	// the common arch variation. Useful for a host test that wants to embed a module built for
+	// device.
+	Device_common_data []string `android:"path_device_common"`
+
+	// same as data, but adds dependencies using the device's os variation and the device's first
+	// architecture's variation. Can be used to add a module built for device to the data of a
+	// host test.
+	Device_first_data []string `android:"path_device_first"`
+
+	// 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
+	// host test.
+	Device_first_prefer32_data []string `android:"path_device_first_prefer32"`
+
 	// Flag to indicate whether or not to create test config automatically. If AndroidTest.xml
 	// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
 	// explicitly.
@@ -1538,23 +1777,28 @@
 	}
 
 	j.Test.generateAndroidBuildActionsWithConfig(ctx, configs)
-	android.SetProvider(ctx, testing.TestModuleProviderKey, testing.TestModuleProviderData{})
 	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",
 	})
+
+	moduleInfoJSON := ctx.ModuleInfoJSON()
+	if proptools.Bool(j.testProperties.Test_options.Unit_test) {
+		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "host-unit-tests")
+	}
 }
 
 func (j *Test) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	checkMinSdkVersionMts(ctx, j.MinSdkVersion(ctx))
 	j.generateAndroidBuildActionsWithConfig(ctx, nil)
-	android.SetProvider(ctx, testing.TestModuleProviderKey, testing.TestModuleProviderData{})
 }
 
 func (j *Test) generateAndroidBuildActionsWithConfig(ctx android.ModuleContext, configs []tradefed.Config) {
@@ -1578,18 +1822,23 @@
 	})
 
 	j.data = android.PathsForModuleSrc(ctx, j.testProperties.Data)
+	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.extraTestConfigs = android.PathsForModuleSrc(ctx, j.testProperties.Test_options.Extra_test_configs)
 
-	ctx.VisitDirectDepsWithTag(dataNativeBinsTag, func(dep android.Module) {
+	ctx.VisitDirectDepsProxyWithTag(dataNativeBinsTag, func(dep android.ModuleProxy) {
 		j.data = append(j.data, android.OutputFileForModule(ctx, dep, ""))
 	})
 
-	ctx.VisitDirectDepsWithTag(dataDeviceBinsTag, func(dep android.Module) {
+	ctx.VisitDirectDepsProxyWithTag(dataDeviceBinsTag, func(dep android.ModuleProxy) {
 		j.data = append(j.data, android.OutputFileForModule(ctx, dep, ""))
 	})
 
-	ctx.VisitDirectDepsWithTag(jniLibTag, func(dep android.Module) {
+	var directImplementationDeps android.Paths
+	var transitiveImplementationDeps []depset.DepSet[android.Path]
+	ctx.VisitDirectDepsProxyWithTag(jniLibTag, func(dep android.ModuleProxy) {
 		sharedLibInfo, _ := android.OtherModuleProvider(ctx, dep, cc.SharedLibraryInfoProvider)
 		if sharedLibInfo.SharedLibrary != nil {
 			// Copy to an intermediate output directory to append "lib[64]" to the path,
@@ -1607,16 +1856,85 @@
 				Output: relocatedLib,
 			})
 			j.data = append(j.data, relocatedLib)
+
+			directImplementationDeps = append(directImplementationDeps, android.OutputFileForModule(ctx, dep, ""))
+			if info, ok := android.OtherModuleProvider(ctx, dep, cc.ImplementationDepInfoProvider); ok {
+				transitiveImplementationDeps = append(transitiveImplementationDeps, info.ImplementationDeps)
+			}
 		} else {
 			ctx.PropertyErrorf("jni_libs", "%q of type %q is not supported", dep.Name(), ctx.OtherModuleType(dep))
 		}
 	})
 
+	android.SetProvider(ctx, cc.ImplementationDepInfoProvider, &cc.ImplementationDepInfo{
+		ImplementationDeps: depset.New(depset.PREORDER, directImplementationDeps, transitiveImplementationDeps),
+	})
+
 	j.Library.GenerateAndroidBuildActions(ctx)
+
+	moduleInfoJSON := ctx.ModuleInfoJSON()
+	// LOCAL_MODULE_TAGS
+	moduleInfoJSON.Tags = append(moduleInfoJSON.Tags, "tests")
+	var allTestConfigs android.Paths
+	if j.testConfig != nil {
+		allTestConfigs = append(allTestConfigs, j.testConfig)
+	}
+	allTestConfigs = append(allTestConfigs, j.extraTestConfigs...)
+	if len(allTestConfigs) > 0 {
+		moduleInfoJSON.TestConfig = allTestConfigs.Strings()
+	} else {
+		optionalConfig := android.ExistentPathForSource(ctx, ctx.ModuleDir(), "AndroidTest.xml")
+		if optionalConfig.Valid() {
+			moduleInfoJSON.TestConfig = append(moduleInfoJSON.TestConfig, optionalConfig.String())
+		}
+	}
+	if len(j.testProperties.Test_suites) > 0 {
+		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, j.testProperties.Test_suites...)
+	} else {
+		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "null-suite")
+	}
+	if _, ok := j.testConfig.(android.WritablePath); ok {
+		moduleInfoJSON.AutoTestConfig = []string{"true"}
+	}
+	if proptools.Bool(j.testProperties.Test_options.Unit_test) {
+		moduleInfoJSON.IsUnitTest = "true"
+		if ctx.Host() {
+			moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "host-unit-tests")
+		}
+	}
+	moduleInfoJSON.TestMainlineModules = append(moduleInfoJSON.TestMainlineModules, j.testProperties.Test_mainline_modules...)
+
+	// Install test deps
+	if !ctx.Config().KatiEnabled() {
+		pathInTestCases := android.PathForModuleInstall(ctx, "testcases", ctx.ModuleName())
+		if j.testConfig != nil {
+			ctx.InstallFile(pathInTestCases, ctx.ModuleName()+".config", j.testConfig)
+		}
+		testDeps := append(j.data, j.extraTestConfigs...)
+		for _, data := range android.SortedUniquePaths(testDeps) {
+			dataPath := android.DataPath{SrcPath: data}
+			ctx.InstallTestData(pathInTestCases, []android.DataPath{dataPath})
+		}
+		if j.outputFile != nil {
+			ctx.InstallFile(pathInTestCases, ctx.ModuleName()+".jar", j.outputFile)
+		}
+	}
 }
 
 func (j *TestHelperLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	j.Library.GenerateAndroidBuildActions(ctx)
+
+	moduleInfoJSON := ctx.ModuleInfoJSON()
+	moduleInfoJSON.Tags = append(moduleInfoJSON.Tags, "tests")
+	if len(j.testHelperLibraryProperties.Test_suites) > 0 {
+		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, j.testHelperLibraryProperties.Test_suites...)
+	} else {
+		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "null-suite")
+	}
+	optionalConfig := android.ExistentPathForSource(ctx, ctx.ModuleDir(), "AndroidTest.xml")
+	if optionalConfig.Valid() {
+		moduleInfoJSON.TestConfig = append(moduleInfoJSON.TestConfig, optionalConfig.String())
+	}
 }
 
 func (j *JavaTestImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -1706,7 +2024,7 @@
 
 	module.Module.properties.Installable = proptools.BoolPtr(true)
 	module.Module.dexpreopter.isTest = true
-	module.Module.linter.properties.Lint.Test = proptools.BoolPtr(true)
+	module.Module.linter.properties.Lint.Test_module_type = proptools.BoolPtr(true)
 	module.Module.sourceProperties.Test_only = proptools.BoolPtr(true)
 	module.Module.sourceProperties.Top_level_test_target = true
 
@@ -1723,7 +2041,7 @@
 
 	module.Module.properties.Installable = proptools.BoolPtr(true)
 	module.Module.dexpreopter.isTest = true
-	module.Module.linter.properties.Lint.Test = proptools.BoolPtr(true)
+	module.Module.linter.properties.Lint.Test_module_type = proptools.BoolPtr(true)
 	module.Module.sourceProperties.Test_only = proptools.BoolPtr(true)
 
 	InitJavaModule(module, android.HostAndDeviceSupported)
@@ -1804,8 +2122,6 @@
 
 	binaryProperties binaryProperties
 
-	isWrapperVariant bool
-
 	wrapperFile android.Path
 	binaryFile  android.InstallPath
 
@@ -1819,97 +2135,94 @@
 func (j *Binary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	j.stem = proptools.StringDefault(j.overridableProperties.Stem, ctx.ModuleName())
 
-	if ctx.Arch().ArchType == android.Common {
-		// Compile the jar
-		if j.binaryProperties.Main_class != nil {
-			if j.properties.Manifest != nil {
-				ctx.PropertyErrorf("main_class", "main_class cannot be used when manifest is set")
-			}
-			manifestFile := android.PathForModuleOut(ctx, "manifest.txt")
-			GenerateMainClassManifest(ctx, manifestFile, String(j.binaryProperties.Main_class))
-			j.overrideManifest = android.OptionalPathForPath(manifestFile)
-		}
-
-		j.Library.GenerateAndroidBuildActions(ctx)
+	// Handle the binary wrapper. This comes before compiling the jar so that the wrapper
+	// is the first PackagingSpec
+	if j.binaryProperties.Wrapper != nil {
+		j.wrapperFile = android.PathForModuleSrc(ctx, *j.binaryProperties.Wrapper)
 	} else {
-		// Handle the binary wrapper
-		j.isWrapperVariant = true
-
-		if j.binaryProperties.Wrapper != nil {
-			j.wrapperFile = android.PathForModuleSrc(ctx, *j.binaryProperties.Wrapper)
-		} else {
-			if ctx.Windows() {
-				ctx.PropertyErrorf("wrapper", "wrapper is required for Windows")
-			}
-
-			if ctx.Device() {
-				// device binary should have a main_class property if it does not
-				// have a specific wrapper, so that a default wrapper can
-				// be generated for it.
-				if j.binaryProperties.Main_class == nil {
-					ctx.PropertyErrorf("main_class", "main_class property "+
-						"is required for device binary if no default wrapper is assigned")
-				} else {
-					wrapper := android.PathForModuleOut(ctx, ctx.ModuleName()+".sh")
-					jarName := j.Stem() + ".jar"
-					partition := j.PartitionTag(ctx.DeviceConfig())
-					ctx.Build(pctx, android.BuildParams{
-						Rule:   deviceBinaryWrapper,
-						Output: wrapper,
-						Args: map[string]string{
-							"jar_name":   jarName,
-							"partition":  partition,
-							"main_class": String(j.binaryProperties.Main_class),
-						},
-					})
-					j.wrapperFile = wrapper
-				}
-			} else {
-				j.wrapperFile = android.PathForSource(ctx, "build/soong/scripts/jar-wrapper.sh")
-			}
-		}
-
-		ext := ""
 		if ctx.Windows() {
-			ext = ".bat"
+			ctx.PropertyErrorf("wrapper", "wrapper is required for Windows")
 		}
 
-		// The host installation rules make the installed wrapper depend on all the dependencies
-		// of the wrapper variant, which will include the common variant's jar file and any JNI
-		// libraries.  This is verified by TestBinary.
-		j.binaryFile = ctx.InstallExecutable(android.PathForModuleInstall(ctx, "bin"),
-			ctx.ModuleName()+ext, j.wrapperFile)
-
-		setOutputFiles(ctx, j.Library.Module)
-
-		// Set the jniLibs of this binary.
-		// These will be added to `LOCAL_REQUIRED_MODULES`, and the kati packaging system will
-		// install these alongside the java binary.
-		ctx.VisitDirectDepsWithTag(jniInstallTag, func(jni android.Module) {
-			// Use the BaseModuleName of the dependency (without any prebuilt_ prefix)
-			bmn, _ := jni.(interface{ BaseModuleName() string })
-			j.androidMkNamesOfJniLibs = append(j.androidMkNamesOfJniLibs, bmn.BaseModuleName()+":"+jni.Target().Arch.ArchType.Bitness())
-		})
-		// Check that native libraries are not listed in `required`. Prompt users to use `jni_libs` instead.
-		ctx.VisitDirectDepsWithTag(android.RequiredDepTag, func(dep android.Module) {
-			if _, hasSharedLibraryInfo := android.OtherModuleProvider(ctx, dep, cc.SharedLibraryInfoProvider); hasSharedLibraryInfo {
-				ctx.ModuleErrorf("cc_library %s is no longer supported in `required` of java_binary modules. Please use jni_libs instead.", dep.Name())
+		if ctx.Device() {
+			// device binary should have a main_class property if it does not
+			// have a specific wrapper, so that a default wrapper can
+			// be generated for it.
+			if j.binaryProperties.Main_class == nil {
+				ctx.PropertyErrorf("main_class", "main_class property "+
+					"is required for device binary if no default wrapper is assigned")
+			} else {
+				wrapper := android.PathForModuleOut(ctx, ctx.ModuleName()+".sh")
+				jarName := j.Stem() + ".jar"
+				partition := j.PartitionTag(ctx.DeviceConfig())
+				ctx.Build(pctx, android.BuildParams{
+					Rule:   deviceBinaryWrapper,
+					Output: wrapper,
+					Args: map[string]string{
+						"jar_name":   jarName,
+						"partition":  partition,
+						"main_class": String(j.binaryProperties.Main_class),
+					},
+				})
+				j.wrapperFile = wrapper
 			}
-		})
+		} else {
+			j.wrapperFile = android.PathForSource(ctx, "build/soong/scripts/jar-wrapper.sh")
+		}
 	}
+
+	ext := ""
+	if ctx.Windows() {
+		ext = ".bat"
+	}
+
+	// The host installation rules make the installed wrapper depend on all the dependencies
+	// of the wrapper variant, which will include the common variant's jar file and any JNI
+	// libraries.  This is verified by TestBinary. Also make it depend on the jar file so that
+	// the binary file timestamp will update when the jar file timestamp does. The jar file is
+	// built later on, in j.Library.GenerateAndroidBuildActions, so we have to create an identical
+	// installpath representing it here.
+	j.binaryFile = ctx.InstallExecutable(android.PathForModuleInstall(ctx, "bin"),
+		ctx.ModuleName()+ext, j.wrapperFile, j.getJarInstallDir(ctx).Join(ctx, j.Stem()+".jar"))
+
+	// Set the jniLibs of this binary.
+	// These will be added to `LOCAL_REQUIRED_MODULES`, and the kati packaging system will
+	// install these alongside the java binary.
+	ctx.VisitDirectDepsProxyWithTag(jniInstallTag, func(jni android.ModuleProxy) {
+		// Use the BaseModuleName of the dependency (without any prebuilt_ prefix)
+		commonInfo, _ := android.OtherModuleProvider(ctx, jni, android.CommonModuleInfoKey)
+		j.androidMkNamesOfJniLibs = append(j.androidMkNamesOfJniLibs, commonInfo.BaseModuleName+":"+commonInfo.Target.Arch.ArchType.Bitness())
+	})
+	// Check that native libraries are not listed in `required`. Prompt users to use `jni_libs` instead.
+	ctx.VisitDirectDepsProxyWithTag(android.RequiredDepTag, func(dep android.ModuleProxy) {
+		if _, hasSharedLibraryInfo := android.OtherModuleProvider(ctx, dep, cc.SharedLibraryInfoProvider); hasSharedLibraryInfo {
+			ctx.ModuleErrorf("cc_library %s is no longer supported in `required` of java_binary modules. Please use jni_libs instead.", dep.Name())
+		}
+	})
+
+	// Compile the jar
+	if j.binaryProperties.Main_class != nil {
+		if j.properties.Manifest != nil {
+			ctx.PropertyErrorf("main_class", "main_class cannot be used when manifest is set")
+		}
+		manifestFile := android.PathForModuleOut(ctx, "manifest.txt")
+		GenerateMainClassManifest(ctx, manifestFile, String(j.binaryProperties.Main_class))
+		j.overrideManifest = android.OptionalPathForPath(manifestFile)
+	}
+
+	j.Library.GenerateAndroidBuildActions(ctx)
 }
 
 func (j *Binary) DepsMutator(ctx android.BottomUpMutatorContext) {
-	if ctx.Arch().ArchType == android.Common {
-		j.deps(ctx)
-	}
+	j.deps(ctx)
 	// These dependencies ensure the installation rules will install the jar file when the
 	// wrapper is installed, and the jni libraries when the wrapper is installed.
-	if ctx.Arch().ArchType != android.Common {
-		ctx.AddVariationDependencies(nil, jniInstallTag, j.binaryProperties.Jni_libs...)
-		ctx.AddVariationDependencies(
-			[]blueprint.Variation{{Mutator: "arch", Variation: android.CommonArch.String()}},
-			binaryInstallTag, ctx.ModuleName())
+	if ctx.Os().Class == android.Host {
+		ctx.AddVariationDependencies(ctx.Config().BuildOSTarget.Variations(), jniInstallTag, j.binaryProperties.Jni_libs...)
+	} else if ctx.Os().Class == android.Device {
+		ctx.AddVariationDependencies(ctx.Config().AndroidFirstDeviceTarget.Variations(), jniInstallTag, j.binaryProperties.Jni_libs...)
+	} else {
+		ctx.ModuleErrorf("Unknown os class")
 	}
 }
 
@@ -1929,7 +2242,7 @@
 
 	module.Module.properties.Installable = proptools.BoolPtr(true)
 
-	android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibCommonFirst)
+	android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon)
 	android.InitDefaultableModule(module)
 
 	return module
@@ -1947,7 +2260,7 @@
 
 	module.Module.properties.Installable = proptools.BoolPtr(true)
 
-	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibCommonFirst)
+	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibCommon)
 	android.InitDefaultableModule(module)
 	return module
 }
@@ -2100,7 +2413,7 @@
 
 func metalavaStubCmd(ctx android.ModuleContext, rule *android.RuleBuilder,
 	srcs android.Paths, homeDir android.WritablePath,
-	classpath android.Paths, configFiles android.Paths) *android.RuleBuilderCommand {
+	classpath android.Paths, configFiles android.Paths, apiSurface *string) *android.RuleBuilderCommand {
 	rule.Command().Text("rm -rf").Flag(homeDir.String())
 	rule.Command().Text("mkdir -p").Flag(homeDir.String())
 
@@ -2141,6 +2454,8 @@
 
 	addMetalavaConfigFilesToCmd(cmd, configFiles)
 
+	addOptionalApiSurfaceToCmd(cmd, apiSurface)
+
 	if len(classpath) == 0 {
 		// The main purpose of the `--api-class-resolution api` option is to force metalava to ignore
 		// classes on the classpath when an API file contains missing classes. However, as this command
@@ -2224,6 +2539,17 @@
 var scopeOrderMap = AllApiScopes.MapToIndex(
 	func(s *apiScope) string { return s.name })
 
+// Add some extra entries into scopeOrderMap for some special api surface names needed by libcore,
+// external/conscrypt and external/icu and java/core-libraries.
+func init() {
+	count := len(scopeOrderMap)
+	scopeOrderMap["core"] = count + 1
+	scopeOrderMap["core-platform"] = count + 2
+	scopeOrderMap["intra-core"] = count + 3
+	scopeOrderMap["core-platform-plus-public"] = count + 4
+	scopeOrderMap["core-platform-legacy"] = count + 5
+}
+
 func (al *ApiLibrary) sortApiFilesByApiScope(ctx android.ModuleContext, srcFilesInfo []JavaApiImportInfo) []JavaApiImportInfo {
 	for _, srcFileInfo := range srcFilesInfo {
 		if srcFileInfo.ApiSurface == "" {
@@ -2272,7 +2598,7 @@
 	var bootclassPaths android.Paths
 	var staticLibs android.Paths
 	var systemModulesPaths android.Paths
-	ctx.VisitDirectDeps(func(dep android.Module) {
+	ctx.VisitDirectDepsProxy(func(dep android.ModuleProxy) {
 		tag := ctx.OtherModuleDependencyTag(dep)
 		switch tag {
 		case javaApiContributionTag:
@@ -2284,22 +2610,25 @@
 		case libTag:
 			if provider, ok := android.OtherModuleProvider(ctx, dep, JavaInfoProvider); ok {
 				classPaths = append(classPaths, provider.HeaderJars...)
+				al.aconfigProtoFiles = append(al.aconfigProtoFiles, provider.AconfigIntermediateCacheOutputPaths...)
 			}
 		case bootClasspathTag:
 			if provider, ok := android.OtherModuleProvider(ctx, dep, JavaInfoProvider); ok {
 				bootclassPaths = append(bootclassPaths, provider.HeaderJars...)
+				al.aconfigProtoFiles = append(al.aconfigProtoFiles, provider.AconfigIntermediateCacheOutputPaths...)
 			}
 		case staticLibTag:
 			if provider, ok := android.OtherModuleProvider(ctx, dep, JavaInfoProvider); ok {
 				staticLibs = append(staticLibs, provider.HeaderJars...)
+				al.aconfigProtoFiles = append(al.aconfigProtoFiles, provider.AconfigIntermediateCacheOutputPaths...)
 			}
 		case systemModulesTag:
 			if sm, ok := android.OtherModuleProvider(ctx, dep, SystemModulesProvider); ok {
 				systemModulesPaths = append(systemModulesPaths, sm.HeaderJars...)
 			}
 		case metalavaCurrentApiTimestampTag:
-			if currentApiTimestampProvider, ok := dep.(currentApiTimestampProvider); ok {
-				al.validationPaths = append(al.validationPaths, currentApiTimestampProvider.CurrentApiTimestamp())
+			if currentApiTimestampProvider, ok := android.OtherModuleProvider(ctx, dep, DroidStubsInfoProvider); ok {
+				al.validationPaths = append(al.validationPaths, currentApiTimestampProvider.CurrentApiTimestamp)
 			}
 		case aconfigDeclarationTag:
 			if provider, ok := android.OtherModuleProvider(ctx, dep, android.AconfigDeclarationsProviderKey); ok {
@@ -2331,7 +2660,7 @@
 	combinedPaths := append(([]android.Path)(nil), systemModulesPaths...)
 	combinedPaths = append(combinedPaths, classPaths...)
 	combinedPaths = append(combinedPaths, bootclassPaths...)
-	cmd := metalavaStubCmd(ctx, rule, srcFiles, homeDir, combinedPaths, configFiles)
+	cmd := metalavaStubCmd(ctx, rule, srcFiles, homeDir, combinedPaths, configFiles, al.properties.Api_surface)
 
 	al.stubsFlags(ctx, cmd, stubsDir)
 
@@ -2395,17 +2724,19 @@
 
 	ctx.Phony(ctx.ModuleName(), al.stubsJar)
 
-	android.SetProvider(ctx, JavaInfoProvider, &JavaInfo{
+	javaInfo := &JavaInfo{
 		HeaderJars:                             android.PathsIfNonNil(al.stubsJar),
 		LocalHeaderJars:                        android.PathsIfNonNil(al.stubsJar),
-		TransitiveStaticLibsHeaderJars:         android.NewDepSet(android.PREORDER, android.PathsIfNonNil(al.stubsJar), nil),
-		TransitiveStaticLibsImplementationJars: android.NewDepSet(android.PREORDER, android.PathsIfNonNil(al.stubsJar), nil),
+		TransitiveStaticLibsHeaderJars:         depset.New(depset.PREORDER, android.PathsIfNonNil(al.stubsJar), nil),
+		TransitiveStaticLibsImplementationJars: depset.New(depset.PREORDER, android.PathsIfNonNil(al.stubsJar), nil),
 		ImplementationAndResourcesJars:         android.PathsIfNonNil(al.stubsJar),
 		ImplementationJars:                     android.PathsIfNonNil(al.stubsJar),
 		AidlIncludeDirs:                        android.Paths{},
 		StubsLinkType:                          Stubs,
 		// No aconfig libraries on api libraries
-	})
+	}
+	setExtraJavaInfo(ctx, al, javaInfo)
+	android.SetProvider(ctx, JavaInfoProvider, javaInfo)
 }
 
 func (al *ApiLibrary) DexJarBuildPath(ctx android.ModuleErrorfContext) OptionalDexJarPath {
@@ -2456,7 +2787,7 @@
 	ret := []string{}
 	ret = append(ret, al.properties.Libs.GetOrDefault(ctx, nil)...)
 	ret = append(ret, al.properties.Static_libs.GetOrDefault(ctx, nil)...)
-	if al.properties.System_modules != nil {
+	if proptools.StringDefault(al.properties.System_modules, "none") != "none" {
 		ret = append(ret, proptools.String(al.properties.System_modules))
 	}
 	// Other non java_library dependencies like java_api_contribution are ignored for now.
@@ -2663,46 +2994,36 @@
 
 	var flags javaBuilderFlags
 
-	var transitiveClasspathHeaderJars []*android.DepSet[android.Path]
-	var transitiveBootClasspathHeaderJars []*android.DepSet[android.Path]
-	var transitiveStaticLibsHeaderJars []*android.DepSet[android.Path]
-	var transitiveStaticLibsImplementationJars []*android.DepSet[android.Path]
-	var transitiveStaticLibsResourceJars []*android.DepSet[android.Path]
+	var transitiveClasspathHeaderJars []depset.DepSet[android.Path]
+	var transitiveBootClasspathHeaderJars []depset.DepSet[android.Path]
+	var transitiveStaticLibsHeaderJars []depset.DepSet[android.Path]
+	var transitiveStaticLibsImplementationJars []depset.DepSet[android.Path]
+	var transitiveStaticLibsResourceJars []depset.DepSet[android.Path]
 
 	j.collectTransitiveHeaderJarsForR8(ctx)
 	var staticJars android.Paths
 	var staticResourceJars android.Paths
 	var staticHeaderJars android.Paths
-	ctx.VisitDirectDeps(func(module android.Module) {
+	ctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
 		tag := ctx.OtherModuleDependencyTag(module)
 		if dep, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider); ok {
 			switch tag {
 			case libTag, sdkLibTag:
 				flags.classpath = append(flags.classpath, dep.HeaderJars...)
 				flags.dexClasspath = append(flags.dexClasspath, dep.HeaderJars...)
-				if dep.TransitiveStaticLibsHeaderJars != nil {
-					transitiveClasspathHeaderJars = append(transitiveClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
-				}
+				transitiveClasspathHeaderJars = append(transitiveClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
 			case staticLibTag:
 				flags.classpath = append(flags.classpath, dep.HeaderJars...)
 				staticJars = append(staticJars, dep.ImplementationJars...)
 				staticResourceJars = append(staticResourceJars, dep.ResourceJars...)
 				staticHeaderJars = append(staticHeaderJars, dep.HeaderJars...)
-				if dep.TransitiveStaticLibsHeaderJars != nil {
-					transitiveClasspathHeaderJars = append(transitiveClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
-					transitiveStaticLibsHeaderJars = append(transitiveStaticLibsHeaderJars, dep.TransitiveStaticLibsHeaderJars)
-				}
-				if dep.TransitiveStaticLibsImplementationJars != nil {
-					transitiveStaticLibsImplementationJars = append(transitiveStaticLibsImplementationJars, dep.TransitiveStaticLibsImplementationJars)
-				}
-				if dep.TransitiveStaticLibsResourceJars != nil {
-					transitiveStaticLibsResourceJars = append(transitiveStaticLibsResourceJars, dep.TransitiveStaticLibsResourceJars)
-				}
+				transitiveClasspathHeaderJars = append(transitiveClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
+				transitiveStaticLibsHeaderJars = append(transitiveStaticLibsHeaderJars, dep.TransitiveStaticLibsHeaderJars)
+				transitiveStaticLibsImplementationJars = append(transitiveStaticLibsImplementationJars, dep.TransitiveStaticLibsImplementationJars)
+				transitiveStaticLibsResourceJars = append(transitiveStaticLibsResourceJars, dep.TransitiveStaticLibsResourceJars)
 			case bootClasspathTag:
 				flags.bootClasspath = append(flags.bootClasspath, dep.HeaderJars...)
-				if dep.TransitiveStaticLibsHeaderJars != nil {
-					transitiveBootClasspathHeaderJars = append(transitiveBootClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
-				}
+				transitiveBootClasspathHeaderJars = append(transitiveBootClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
 			}
 		} else if _, ok := android.OtherModuleProvider(ctx, module, SdkLibraryInfoProvider); ok {
 			switch tag {
@@ -2727,9 +3048,9 @@
 		false, j.properties.Exclude_files, j.properties.Exclude_dirs)
 	localStrippedJars := android.Paths{localCombinedHeaderJar}
 
-	completeStaticLibsHeaderJars := android.NewDepSet(android.PREORDER, localStrippedJars, transitiveStaticLibsHeaderJars)
-	completeStaticLibsImplementationJars := android.NewDepSet(android.PREORDER, localStrippedJars, transitiveStaticLibsImplementationJars)
-	completeStaticLibsResourceJars := android.NewDepSet(android.PREORDER, nil, transitiveStaticLibsResourceJars)
+	completeStaticLibsHeaderJars := depset.New(depset.PREORDER, localStrippedJars, transitiveStaticLibsHeaderJars)
+	completeStaticLibsImplementationJars := depset.New(depset.PREORDER, localStrippedJars, transitiveStaticLibsImplementationJars)
+	completeStaticLibsResourceJars := depset.New(depset.PREORDER, nil, transitiveStaticLibsResourceJars)
 
 	// Always pass the input jars to TransformJarsToJar, even if there is only a single jar, we need the output
 	// file of the module to be named jarName.
@@ -2790,8 +3111,8 @@
 
 		// Enabling jetifier requires modifying classes from transitive dependencies, disable transitive
 		// classpath and use the combined header jar instead.
-		completeStaticLibsHeaderJars = android.NewDepSet(android.PREORDER, android.Paths{headerJar}, nil)
-		completeStaticLibsImplementationJars = android.NewDepSet(android.PREORDER, android.Paths{outputFile}, nil)
+		completeStaticLibsHeaderJars = depset.New(depset.PREORDER, android.Paths{headerJar}, nil)
+		completeStaticLibsImplementationJars = depset.New(depset.PREORDER, android.Paths{outputFile}, nil)
 	}
 
 	implementationJarFile := outputFile
@@ -2805,6 +3126,23 @@
 		outputFile = combinedJar
 	}
 
+	proguardFlags := android.PathForModuleOut(ctx, "proguard_flags")
+	TransformJarToR8Rules(ctx, proguardFlags, outputFile)
+
+	transitiveProguardFlags, transitiveUnconditionalExportedFlags := collectDepProguardSpecInfo(ctx)
+	android.SetProvider(ctx, ProguardSpecInfoProvider, ProguardSpecInfo{
+		ProguardFlagsFiles: depset.New[android.Path](
+			depset.POSTORDER,
+			android.Paths{proguardFlags},
+			transitiveProguardFlags,
+		),
+		UnconditionallyExportedProguardFlags: depset.New[android.Path](
+			depset.POSTORDER,
+			nil,
+			transitiveUnconditionalExportedFlags,
+		),
+	})
+
 	// Save the output file with no relative path so that it doesn't end up in a subdirectory when used as a resource.
 	// Also strip the relative path from the header output file so that the reuseImplementationJarAsHeaderJar check
 	// in a module that depends on this module considers them equal.
@@ -2873,7 +3211,7 @@
 		}
 	}
 
-	android.SetProvider(ctx, JavaInfoProvider, &JavaInfo{
+	javaInfo := &JavaInfo{
 		HeaderJars:                             android.PathsIfNonNil(j.combinedHeaderFile),
 		LocalHeaderJars:                        android.PathsIfNonNil(j.combinedHeaderFile),
 		TransitiveLibsHeaderJarsForR8:          j.transitiveLibsHeaderJarsForR8,
@@ -2887,10 +3225,14 @@
 		AidlIncludeDirs:                        j.exportAidlIncludeDirs,
 		StubsLinkType:                          j.stubsLinkType,
 		// TODO(b/289117800): LOCAL_ACONFIG_FILES for prebuilts
-	})
+	}
+	setExtraJavaInfo(ctx, j, javaInfo)
+	android.SetProvider(ctx, JavaInfoProvider, javaInfo)
 
 	ctx.SetOutputFiles(android.Paths{j.combinedImplementationFile}, "")
 	ctx.SetOutputFiles(android.Paths{j.combinedImplementationFile}, ".jar")
+
+	buildComplianceMetadata(ctx)
 }
 
 func (j *Import) maybeInstall(ctx android.ModuleContext, jarName string, outputFile android.Path) {
@@ -2937,8 +3279,16 @@
 var _ android.ApexModule = (*Import)(nil)
 
 // Implements android.ApexModule
-func (j *Import) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
-	return j.depIsInSameApex(ctx, dep)
+func (m *Import) GetDepInSameApexChecker() android.DepInSameApexChecker {
+	return JavaImportDepInSameApexChecker{}
+}
+
+type JavaImportDepInSameApexChecker struct {
+	android.BaseDepInSameApexChecker
+}
+
+func (m JavaImportDepInSameApexChecker) OutgoingDepIsInSameApex(tag blueprint.DependencyTag) bool {
+	return depIsInSameApex(tag)
 }
 
 // Implements android.ApexModule
@@ -2998,7 +3348,7 @@
 // Collect information for opening IDE project files in java/jdeps.go.
 
 func (j *Import) IDEInfo(ctx android.BaseModuleContext, dpInfo *android.IdeInfo) {
-	dpInfo.Jars = append(dpInfo.Jars, j.combinedHeaderFile.String())
+	dpInfo.Jars = append(dpInfo.Jars, j.combinedImplementationFile.String())
 }
 
 func (j *Import) IDECustomizedModuleName() string {
@@ -3197,7 +3547,6 @@
 type Defaults struct {
 	android.ModuleBase
 	android.DefaultsModuleBase
-	android.ApexModuleBase
 }
 
 // java_defaults provides a set of properties that can be inherited by other java or android modules.
@@ -3260,6 +3609,9 @@
 		&JavaApiLibraryProperties{},
 		&bootclasspathFragmentProperties{},
 		&SourceOnlyBootclasspathProperties{},
+		&ravenwoodTestProperties{},
+		&AndroidAppImportProperties{},
+		&UsesLibraryProperties{},
 	)
 
 	android.InitDefaultsModule(module)
@@ -3297,11 +3649,11 @@
 var inList = android.InList[string]
 
 // Add class loader context (CLC) of a given dependency to the current CLC.
-func addCLCFromDep(ctx android.ModuleContext, depModule android.Module,
+func addCLCFromDep(ctx android.ModuleContext, depModule android.ModuleProxy,
 	clcMap dexpreopt.ClassLoaderContextMap) {
 
-	dep, ok := depModule.(UsesLibraryDependency)
-	if !ok {
+	dep, ok := android.OtherModuleProvider(ctx, depModule, JavaInfoProvider)
+	if !ok || dep.UsesLibraryDependencyInfo == nil {
 		return
 	}
 
@@ -3311,14 +3663,14 @@
 	if lib, ok := android.OtherModuleProvider(ctx, depModule, SdkLibraryInfoProvider); ok && lib.SharedLibrary {
 		// A shared SDK library. This should be added as a top-level CLC element.
 		sdkLib = &depName
-	} else if lib, ok := depModule.(SdkLibraryComponentDependency); ok && lib.OptionalSdkLibraryImplementation() != nil {
-		if depModule.Name() == proptools.String(lib.OptionalSdkLibraryImplementation())+".impl" {
-			sdkLib = lib.OptionalSdkLibraryImplementation()
+	} else if lib := dep.SdkLibraryComponentDependencyInfo; lib != nil && lib.OptionalSdkLibraryImplementation != nil {
+		if depModule.Name() == proptools.String(lib.OptionalSdkLibraryImplementation)+".impl" {
+			sdkLib = lib.OptionalSdkLibraryImplementation
 		}
-	} else if ulib, ok := depModule.(ProvidesUsesLib); ok {
+	} else if ulib := dep.ProvidesUsesLibInfo; ulib != nil {
 		// A non-SDK library disguised as an SDK library by the means of `provides_uses_lib`
 		// property. This should be handled in the same way as a shared SDK library.
-		sdkLib = ulib.ProvidesUsesLib()
+		sdkLib = ulib.ProvidesUsesLib
 	}
 
 	depTag := ctx.OtherModuleDependencyTag(depModule)
@@ -3345,26 +3697,27 @@
 	if sdkLib != nil {
 		optional := false
 		if module, ok := ctx.Module().(ModuleWithUsesLibrary); ok {
-			if android.InList(*sdkLib, module.UsesLibrary().usesLibraryProperties.Optional_uses_libs) {
+			if android.InList(*sdkLib, module.UsesLibrary().usesLibraryProperties.Optional_uses_libs.GetOrDefault(ctx, nil)) {
 				optional = true
 			}
 		}
 		clcMap.AddContext(ctx, dexpreopt.AnySdkVersion, *sdkLib, optional,
-			dep.DexJarBuildPath(ctx).PathOrNil(), dep.DexJarInstallPath(), dep.ClassLoaderContexts())
+			dep.UsesLibraryDependencyInfo.DexJarBuildPath.PathOrNil(),
+			dep.UsesLibraryDependencyInfo.DexJarInstallPath, dep.UsesLibraryDependencyInfo.ClassLoaderContexts)
 	} else {
-		clcMap.AddContextMap(dep.ClassLoaderContexts(), depName)
+		clcMap.AddContextMap(dep.UsesLibraryDependencyInfo.ClassLoaderContexts, depName)
 	}
 }
 
-func addMissingOptionalUsesLibsFromDep(ctx android.ModuleContext, depModule android.Module,
+func addMissingOptionalUsesLibsFromDep(ctx android.ModuleContext, depModule android.ModuleProxy,
 	usesLibrary *usesLibrary) {
 
-	dep, ok := depModule.(ModuleWithUsesLibrary)
+	dep, ok := android.OtherModuleProvider(ctx, depModule, JavaInfoProvider)
 	if !ok {
 		return
 	}
 
-	for _, lib := range dep.UsesLibrary().usesLibraryProperties.Missing_optional_uses_libs {
+	for _, lib := range dep.MissingOptionalUsesLibs {
 		if !android.InList(lib, usesLibrary.usesLibraryProperties.Missing_optional_uses_libs) {
 			usesLibrary.usesLibraryProperties.Missing_optional_uses_libs =
 				append(usesLibrary.usesLibraryProperties.Missing_optional_uses_libs, lib)
@@ -3420,3 +3773,46 @@
 func (ap *JavaApiContributionImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	ap.JavaApiContribution.GenerateAndroidBuildActions(ctx)
 }
+
+func setExtraJavaInfo(ctx android.ModuleContext, module android.Module, javaInfo *JavaInfo) {
+	if alDep, ok := module.(AndroidLibraryDependency); ok {
+		javaInfo.AndroidLibraryDependencyInfo = &AndroidLibraryDependencyInfo{
+			ExportPackage:       alDep.ExportPackage(),
+			ResourcesNodeDepSet: alDep.ResourcesNodeDepSet(),
+			RRODirsDepSet:       alDep.RRODirsDepSet(),
+			ManifestsDepSet:     alDep.ManifestsDepSet(),
+		}
+	}
+
+	if ulDep, ok := module.(UsesLibraryDependency); ok {
+		javaInfo.UsesLibraryDependencyInfo = &UsesLibraryDependencyInfo{
+			DexJarBuildPath:     ulDep.DexJarBuildPath(ctx),
+			DexJarInstallPath:   ulDep.DexJarInstallPath(),
+			ClassLoaderContexts: ulDep.ClassLoaderContexts(),
+		}
+	}
+
+	if slcDep, ok := module.(SdkLibraryComponentDependency); ok {
+		javaInfo.SdkLibraryComponentDependencyInfo = &SdkLibraryComponentDependencyInfo{
+			OptionalSdkLibraryImplementation: slcDep.OptionalSdkLibraryImplementation(),
+		}
+	}
+
+	if pul, ok := module.(ProvidesUsesLib); ok {
+		javaInfo.ProvidesUsesLibInfo = &ProvidesUsesLibInfo{
+			ProvidesUsesLib: pul.ProvidesUsesLib(),
+		}
+	}
+
+	if mwul, ok := module.(ModuleWithUsesLibrary); ok {
+		javaInfo.MissingOptionalUsesLibs = mwul.UsesLibrary().usesLibraryProperties.Missing_optional_uses_libs
+	}
+
+	if mwsd, ok := module.(moduleWithSdkDep); ok {
+		linkType, stubs := mwsd.getSdkLinkType(ctx, ctx.ModuleName())
+		javaInfo.ModuleWithSdkDepInfo = &ModuleWithSdkDepInfo{
+			SdkLinkType: linkType,
+			Stubs:       stubs,
+		}
+	}
+}
diff --git a/java/java_resources.go b/java/java_resources.go
index b0dc5a1..c525233 100644
--- a/java/java_resources.go
+++ b/java/java_resources.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 	"path/filepath"
+	"slices"
 	"strings"
 
 	"github.com/google/blueprint/pathtools"
@@ -99,10 +100,7 @@
 // that should not be treated as resources (including *.java).
 func ResourceFilesToJarArgs(ctx android.ModuleContext,
 	res, exclude []string) (args []string, deps android.Paths) {
-
-	exclude = append([]string(nil), exclude...)
-	exclude = append(exclude, resourceExcludes...)
-	return resourceFilesToJarArgs(ctx, res, exclude)
+	return resourceFilesToJarArgs(ctx, res, slices.Concat(exclude, resourceExcludes))
 }
 
 func resourceFilesToJarArgs(ctx android.ModuleContext,
diff --git a/java/java_test.go b/java/java_test.go
index 24dabdb1..f097762 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -117,6 +117,7 @@
 // Test that the PrepareForTestWithJavaDefaultModules provides all the files that it uses by
 // running it in a fixture that requires all source files to exist.
 func TestPrepareForTestWithJavaDefaultModules(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		PrepareForTestWithJavaDefaultModules,
 		android.PrepareForTestDisallowNonExistentPaths,
@@ -124,6 +125,7 @@
 }
 
 func TestJavaLinkType(t *testing.T) {
+	t.Parallel()
 	testJava(t, `
 		java_library {
 			name: "foo",
@@ -212,6 +214,7 @@
 }
 
 func TestSimple(t *testing.T) {
+	t.Parallel()
 	bp := `
 		java_library {
 			name: "foo",
@@ -341,11 +344,12 @@
 
 	for _, tt := range testCases {
 		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
 			result := android.GroupFixturePreparers(
 				PrepareForTestWithJavaDefaultModules,
 				tt.preparer,
 			).RunTestWithBp(t, bp)
-			foo := result.ModuleForTests("foo", "android_common")
+			foo := result.ModuleForTests(t, "foo", "android_common")
 
 			fooJavac := foo.Rule("javac")
 			android.AssertPathsRelativeToTopEquals(t, "foo javac inputs", tt.fooJavacInputs, fooJavac.Inputs)
@@ -360,7 +364,7 @@
 			fooCombinedHeaderJar := foo.Output("turbine-combined/foo.jar")
 			android.AssertPathsRelativeToTopEquals(t, "foo header combined inputs", tt.fooHeaderCombinedInputs, fooCombinedHeaderJar.Inputs)
 
-			bar := result.ModuleForTests("bar", "android_common")
+			bar := result.ModuleForTests(t, "bar", "android_common")
 			barJavac := bar.Rule("javac")
 			android.AssertPathsRelativeToTopEquals(t, "bar javac inputs", tt.barJavacInputs, barJavac.Inputs)
 
@@ -378,6 +382,7 @@
 }
 
 func TestExportedPlugins(t *testing.T) {
+	t.Parallel()
 	type Result struct {
 		library        string
 		processors     string
@@ -456,6 +461,7 @@
 
 	for _, test := range tests {
 		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
 			ctx, _ := testJava(t, `
 				java_plugin {
 					name: "plugin",
@@ -469,11 +475,11 @@
 			`+test.extra)
 
 			for _, want := range test.results {
-				javac := ctx.ModuleForTests(want.library, "android_common").Rule("javac")
+				javac := ctx.ModuleForTests(t, want.library, "android_common").Rule("javac")
 				if javac.Args["processor"] != want.processors {
 					t.Errorf("For library %v, expected %v, found %v", want.library, want.processors, javac.Args["processor"])
 				}
-				turbine := ctx.ModuleForTests(want.library, "android_common").MaybeRule("turbine")
+				turbine := ctx.ModuleForTests(t, want.library, "android_common").MaybeRule("turbine")
 				disableTurbine := turbine.BuildParams.Rule == nil
 				if disableTurbine != want.disableTurbine {
 					t.Errorf("For library %v, expected disableTurbine %v, found %v", want.library, want.disableTurbine, disableTurbine)
@@ -484,6 +490,7 @@
 }
 
 func TestSdkVersionByPartition(t *testing.T) {
+	t.Parallel()
 	testJavaError(t, "sdk_version must have a value when the module is located at vendor or product", `
 		java_library {
 			name: "foo",
@@ -525,6 +532,7 @@
 }
 
 func TestArchSpecific(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_library {
 			name: "foo",
@@ -537,13 +545,14 @@
 		}
 	`)
 
-	javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
+	javac := ctx.ModuleForTests(t, "foo", "android_common").Rule("javac")
 	if len(javac.Inputs) != 2 || javac.Inputs[0].String() != "a.java" || javac.Inputs[1].String() != "b.java" {
 		t.Errorf(`foo inputs %v != ["a.java", "b.java"]`, javac.Inputs)
 	}
 }
 
 func TestBinary(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_library_host {
 			name: "foo",
@@ -567,12 +576,11 @@
 
 	buildOS := ctx.Config().BuildOS.String()
 
-	bar := ctx.ModuleForTests("bar", buildOS+"_common")
+	bar := ctx.ModuleForTests(t, "bar", buildOS+"_common")
 	barJar := bar.Output("bar.jar").Output.String()
-	barWrapper := ctx.ModuleForTests("bar", buildOS+"_x86_64")
-	barWrapperDeps := barWrapper.Output("bar").Implicits.Strings()
+	barWrapperDeps := bar.Output("bar").Implicits.Strings()
 
-	libjni := ctx.ModuleForTests("libjni", buildOS+"_x86_64_shared")
+	libjni := ctx.ModuleForTests(t, "libjni", buildOS+"_x86_64_shared")
 	libjniSO := libjni.Rule("Cp").Output.String()
 
 	// Test that the install binary wrapper depends on the installed jar file
@@ -587,6 +595,7 @@
 }
 
 func TestTest(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_test_host {
 			name: "foo",
@@ -604,7 +613,7 @@
 
 	buildOS := ctx.Config().BuildOS.String()
 
-	foo := ctx.ModuleForTests("foo", buildOS+"_common").Module().(*TestHost)
+	foo := ctx.ModuleForTests(t, "foo", buildOS+"_common").Module().(*TestHost)
 
 	expected := "lib64/libjni.so"
 	if runtime.GOOS == "darwin" {
@@ -619,6 +628,7 @@
 }
 
 func TestHostBinaryNoJavaDebugInfoOverride(t *testing.T) {
+	t.Parallel()
 	bp := `
 		java_library {
 			name: "target_library",
@@ -639,7 +649,7 @@
 	).RunTestWithBp(t, bp)
 
 	// first, check that the -g flag is added to target modules
-	targetLibrary := result.ModuleForTests("target_library", "android_common")
+	targetLibrary := result.ModuleForTests(t, "target_library", "android_common")
 	targetJavaFlags := targetLibrary.Module().VariablesForTests()["javacFlags"]
 	if !strings.Contains(targetJavaFlags, "-g:source,lines") {
 		t.Errorf("target library javac flags %v should contain "+
@@ -648,7 +658,7 @@
 
 	// check that -g is not overridden for host modules
 	buildOS := result.Config.BuildOS.String()
-	hostBinary := result.ModuleForTests("host_binary", buildOS+"_common")
+	hostBinary := result.ModuleForTests(t, "host_binary", buildOS+"_common")
 	hostJavaFlags := hostBinary.Module().VariablesForTests()["javacFlags"]
 	if strings.Contains(hostJavaFlags, "-g:source,lines") {
 		t.Errorf("java_binary_host javac flags %v should not have "+
@@ -666,6 +676,7 @@
 var _ android.ModuleErrorfContext = (*moduleErrorfTestCtx)(nil)
 
 func TestPrebuilts(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_library {
 			name: "foo",
@@ -711,14 +722,14 @@
 		}
 		`)
 
-	fooModule := ctx.ModuleForTests("foo", "android_common")
+	fooModule := ctx.ModuleForTests(t, "foo", "android_common")
 	javac := fooModule.Rule("javac")
-	combineJar := ctx.ModuleForTests("foo", "android_common").Description("for javac")
-	barModule := ctx.ModuleForTests("bar", "android_common")
+	combineJar := ctx.ModuleForTests(t, "foo", "android_common").Description("for javac")
+	barModule := ctx.ModuleForTests(t, "bar", "android_common")
 	barJar := barModule.Output("combined/bar.jar").Output
-	bazModule := ctx.ModuleForTests("baz", "android_common")
+	bazModule := ctx.ModuleForTests(t, "baz", "android_common")
 	bazJar := bazModule.Output("combined/baz.jar").Output
-	sdklibStubsJar := ctx.ModuleForTests("sdklib.stubs", "android_common").
+	sdklibStubsJar := ctx.ModuleForTests(t, "sdklib.stubs", "android_common").
 		Output("combined/sdklib.stubs.jar").Output
 
 	fooLibrary := fooModule.Module().(*Library)
@@ -751,7 +762,7 @@
 	expectedDexJar := "out/soong/.intermediates/baz/android_common/dex/baz.jar"
 	android.AssertPathRelativeToTopEquals(t, "baz dex jar build path", expectedDexJar, bazDexJar)
 
-	ctx.ModuleForTests("qux", "android_common").Rule("Cp")
+	ctx.ModuleForTests(t, "qux", "android_common").Rule("Cp")
 
 	entries := android.AndroidMkEntriesForTest(t, ctx, fooModule.Module())[0]
 	android.AssertStringEquals(t, "unexpected LOCAL_SOONG_MODULE_TYPE", "java_library", entries.EntryMap["LOCAL_SOONG_MODULE_TYPE"][0])
@@ -766,6 +777,7 @@
 }
 
 func TestPrebuiltStubsSources(t *testing.T) {
+	t.Parallel()
 	test := func(t *testing.T, sourcesPath string, expectedInputs []string) {
 		ctx, _ := testJavaWithFS(t, fmt.Sprintf(`
 prebuilt_stubs_sources {
@@ -776,17 +788,19 @@
 			"stubs/sources/pkg/B.java": nil,
 		})
 
-		zipSrc := ctx.ModuleForTests("stubs-source", "android_common").Rule("zip_src")
+		zipSrc := ctx.ModuleForTests(t, "stubs-source", "android_common").Rule("zip_src")
 		if expected, actual := expectedInputs, zipSrc.Inputs.Strings(); !reflect.DeepEqual(expected, actual) {
 			t.Errorf("mismatch of inputs to soong_zip: expected %q, actual %q", expected, actual)
 		}
 	}
 
 	t.Run("empty/missing directory", func(t *testing.T) {
+		t.Parallel()
 		test(t, "empty-directory", nil)
 	})
 
 	t.Run("non-empty set of sources", func(t *testing.T) {
+		t.Parallel()
 		test(t, "stubs/sources", []string{
 			"stubs/sources/pkg/A.java",
 			"stubs/sources/pkg/B.java",
@@ -795,6 +809,7 @@
 }
 
 func TestDefaults(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_defaults {
 			name: "defaults",
@@ -836,8 +851,8 @@
 		}
 		`)
 
-	javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
-	combineJar := ctx.ModuleForTests("foo", "android_common").Description("for javac")
+	javac := ctx.ModuleForTests(t, "foo", "android_common").Rule("javac")
+	combineJar := ctx.ModuleForTests(t, "foo", "android_common").Description("for javac")
 
 	if len(javac.Inputs) != 1 || javac.Inputs[0].String() != "a.java" {
 		t.Errorf(`foo inputs %v != ["a.java"]`, javac.Inputs)
@@ -848,28 +863,29 @@
 		t.Errorf("foo classpath %v does not contain %q", javac.Args["classpath"], barTurbine)
 	}
 
-	baz := ctx.ModuleForTests("baz", "android_common").Rule("javac").Output.String()
+	baz := ctx.ModuleForTests(t, "baz", "android_common").Rule("javac").Output.String()
 	if len(combineJar.Inputs) != 2 || combineJar.Inputs[1].String() != baz {
 		t.Errorf("foo combineJar inputs %v does not contain %q", combineJar.Inputs, baz)
 	}
 
-	atestOptimize := ctx.ModuleForTests("atestOptimize", "android_common").MaybeRule("r8")
+	atestOptimize := ctx.ModuleForTests(t, "atestOptimize", "android_common").MaybeRule("r8")
 	if atestOptimize.Output == nil {
 		t.Errorf("atestOptimize should optimize APK")
 	}
 
-	atestNoOptimize := ctx.ModuleForTests("atestNoOptimize", "android_common").MaybeRule("d8")
+	atestNoOptimize := ctx.ModuleForTests(t, "atestNoOptimize", "android_common").MaybeRule("d8")
 	if atestNoOptimize.Output == nil {
 		t.Errorf("atestNoOptimize should not optimize APK")
 	}
 
-	atestDefault := ctx.ModuleForTests("atestDefault", "android_common").MaybeRule("d8")
+	atestDefault := ctx.ModuleForTests(t, "atestDefault", "android_common").MaybeRule("d8")
 	if atestDefault.Output == nil {
 		t.Errorf("atestDefault should not optimize APK")
 	}
 }
 
 func TestResources(t *testing.T) {
+	t.Parallel()
 	var table = []struct {
 		name  string
 		prop  string
@@ -941,6 +957,7 @@
 
 	for _, test := range table {
 		t.Run(test.name, func(t *testing.T) {
+			t.Parallel()
 			ctx, _ := testJavaWithFS(t, `
 				java_library {
 					name: "foo",
@@ -959,8 +976,8 @@
 				},
 			)
 
-			foo := ctx.ModuleForTests("foo", "android_common").Output("withres/foo.jar")
-			fooRes := ctx.ModuleForTests("foo", "android_common").Output("res/foo.jar")
+			foo := ctx.ModuleForTests(t, "foo", "android_common").Output("withres/foo.jar")
+			fooRes := ctx.ModuleForTests(t, "foo", "android_common").Output("res/foo.jar")
 
 			if !inList(fooRes.Output.String(), foo.Inputs.Strings()) {
 				t.Errorf("foo combined jars %v does not contain %q",
@@ -976,6 +993,7 @@
 }
 
 func TestIncludeSrcs(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJavaWithFS(t, `
 		java_library {
 			name: "foo",
@@ -1004,8 +1022,8 @@
 	})
 
 	// Test a library with include_srcs: true
-	foo := ctx.ModuleForTests("foo", "android_common").Output("withres/foo.jar")
-	fooSrcJar := ctx.ModuleForTests("foo", "android_common").Output("foo.srcjar")
+	foo := ctx.ModuleForTests(t, "foo", "android_common").Output("withres/foo.jar")
+	fooSrcJar := ctx.ModuleForTests(t, "foo", "android_common").Output("foo.srcjar")
 
 	if g, w := fooSrcJar.Output.String(), foo.Inputs.Strings(); !inList(g, w) {
 		t.Errorf("foo combined jars %v does not contain %q", w, g)
@@ -1016,10 +1034,10 @@
 	}
 
 	// Test a library with include_srcs: true and resources
-	bar := ctx.ModuleForTests("bar", "android_common").Output("withres/bar.jar")
-	barResCombined := ctx.ModuleForTests("bar", "android_common").Output("res-combined/bar.jar")
-	barRes := ctx.ModuleForTests("bar", "android_common").Output("res/bar.jar")
-	barSrcJar := ctx.ModuleForTests("bar", "android_common").Output("bar.srcjar")
+	bar := ctx.ModuleForTests(t, "bar", "android_common").Output("withres/bar.jar")
+	barResCombined := ctx.ModuleForTests(t, "bar", "android_common").Output("res-combined/bar.jar")
+	barRes := ctx.ModuleForTests(t, "bar", "android_common").Output("res/bar.jar")
+	barSrcJar := ctx.ModuleForTests(t, "bar", "android_common").Output("bar.srcjar")
 
 	if g, w := barSrcJar.Output.String(), barResCombined.Inputs.Strings(); !inList(g, w) {
 		t.Errorf("bar combined resource jars %v does not contain %q", w, g)
@@ -1043,6 +1061,7 @@
 }
 
 func TestGeneratedSources(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJavaWithFS(t, `
 		java_library {
 			name: "foo",
@@ -1063,8 +1082,8 @@
 		"b.java": nil,
 	})
 
-	javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
-	genrule := ctx.ModuleForTests("gen", "").Rule("generator")
+	javac := ctx.ModuleForTests(t, "foo", "android_common").Rule("javac")
+	genrule := ctx.ModuleForTests(t, "gen", "").Rule("generator")
 
 	if filepath.Base(genrule.Output.String()) != "gen.java" {
 		t.Fatalf(`gen output file %v is not ".../gen.java"`, genrule.Output.String())
@@ -1079,6 +1098,7 @@
 }
 
 func TestTurbine(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest, FixtureWithPrebuiltApis(map[string][]string{"14": {"foo"}})).
 		RunTestWithBp(t, `
@@ -1103,11 +1123,11 @@
 		}
 		`)
 
-	fooTurbine := result.ModuleForTests("foo", "android_common").Rule("turbine")
-	barTurbine := result.ModuleForTests("bar", "android_common").Rule("turbine")
-	barJavac := result.ModuleForTests("bar", "android_common").Rule("javac")
-	barTurbineCombined := result.ModuleForTests("bar", "android_common").Description("for turbine")
-	bazJavac := result.ModuleForTests("baz", "android_common").Rule("javac")
+	fooTurbine := result.ModuleForTests(t, "foo", "android_common").Rule("turbine")
+	barTurbine := result.ModuleForTests(t, "bar", "android_common").Rule("turbine")
+	barJavac := result.ModuleForTests(t, "bar", "android_common").Rule("javac")
+	barTurbineCombined := result.ModuleForTests(t, "bar", "android_common").Description("for turbine")
+	bazJavac := result.ModuleForTests(t, "baz", "android_common").Rule("javac")
 
 	android.AssertPathsRelativeToTopEquals(t, "foo inputs", []string{"a.java"}, fooTurbine.Inputs)
 
@@ -1120,6 +1140,7 @@
 }
 
 func TestSharding(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_library {
 			name: "bar",
@@ -1130,7 +1151,7 @@
 
 	barHeaderJar := filepath.Join("out", "soong", ".intermediates", "bar", "android_common", "turbine", "bar.jar")
 	for i := 0; i < 3; i++ {
-		barJavac := ctx.ModuleForTests("bar", "android_common").Description("javac" + strconv.Itoa(i))
+		barJavac := ctx.ModuleForTests(t, "bar", "android_common").Description("javac" + strconv.Itoa(i))
 		if !strings.HasPrefix(barJavac.Args["classpath"], "-classpath "+barHeaderJar+":") {
 			t.Errorf("bar javac classpath %v does start with %q", barJavac.Args["classpath"], barHeaderJar)
 		}
@@ -1138,6 +1159,7 @@
 }
 
 func TestExcludeFileGroupInSrcs(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_library {
 			name: "foo",
@@ -1156,7 +1178,7 @@
 		}
 	`)
 
-	javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
+	javac := ctx.ModuleForTests(t, "foo", "android_common").Rule("javac")
 
 	if len(javac.Inputs) != 1 || javac.Inputs[0].String() != "java-fg/c.java" {
 		t.Errorf(`foo inputs %v != ["java-fg/c.java"]`, javac.Inputs)
@@ -1164,6 +1186,7 @@
 }
 
 func TestJavaLibraryOutputFiles(t *testing.T) {
+	t.Parallel()
 	testJavaWithFS(t, "", map[string][]byte{
 		"libcore/Android.bp": []byte(`
 				java_library {
@@ -1174,13 +1197,14 @@
 
 				filegroup {
 					name: "core-jar",
-					srcs: [":core{.jar}"],
+					device_common_srcs: [":core{.jar}"],
 				}
 		`),
 	})
 }
 
 func TestJavaImportOutputFiles(t *testing.T) {
+	t.Parallel()
 	testJavaWithFS(t, "", map[string][]byte{
 		"libcore/Android.bp": []byte(`
 				java_import {
@@ -1190,13 +1214,14 @@
 
 				filegroup {
 					name: "core-jar",
-					srcs: [":core{.jar}"],
+					device_common_srcs: [":core{.jar}"],
 				}
 		`),
 	})
 }
 
 func TestJavaImport(t *testing.T) {
+	t.Parallel()
 	bp := `
 		java_library {
 			name: "source_library",
@@ -1224,7 +1249,7 @@
 		PrepareForTestWithJavaDefaultModules,
 	).RunTestWithBp(t, bp)
 
-	source := ctx.ModuleForTests("source_library", "android_common")
+	source := ctx.ModuleForTests(t, "source_library", "android_common")
 	sourceJar := source.Output("javac/source_library.jar")
 	sourceHeaderJar := source.Output("turbine-combined/source_library.jar")
 	sourceJavaInfo, _ := android.OtherModuleProvider(ctx, source.Module(), JavaInfoProvider)
@@ -1235,7 +1260,7 @@
 	android.AssertPathsRelativeToTopEquals(t, "source library header jar",
 		[]string{sourceHeaderJar.Output.String()}, sourceJavaInfo.HeaderJars)
 
-	importWithNoDeps := ctx.ModuleForTests("import_with_no_deps", "android_common")
+	importWithNoDeps := ctx.ModuleForTests(t, "import_with_no_deps", "android_common")
 	importWithNoDepsJar := importWithNoDeps.Output("combined/import_with_no_deps.jar")
 	importWithNoDepsJavaInfo, _ := android.OtherModuleProvider(ctx, importWithNoDeps.Module(), JavaInfoProvider)
 
@@ -1247,7 +1272,7 @@
 	android.AssertPathsRelativeToTopEquals(t, "import with no deps combined inputs",
 		[]string{"no_deps.jar"}, importWithNoDepsJar.Inputs)
 
-	importWithSourceDeps := ctx.ModuleForTests("import_with_source_deps", "android_common")
+	importWithSourceDeps := ctx.ModuleForTests(t, "import_with_source_deps", "android_common")
 	importWithSourceDepsJar := importWithSourceDeps.Output("combined/import_with_source_deps.jar")
 	importWithSourceDepsHeaderJar := importWithSourceDeps.Output("turbine-combined/import_with_source_deps.jar")
 	importWithSourceDepsJavaInfo, _ := android.OtherModuleProvider(ctx, importWithSourceDeps.Module(), JavaInfoProvider)
@@ -1262,7 +1287,7 @@
 	android.AssertPathsRelativeToTopEquals(t, "import with source deps combined header jar inputs",
 		[]string{"source_deps.jar", sourceHeaderJar.Output.String()}, importWithSourceDepsHeaderJar.Inputs)
 
-	importWithImportDeps := ctx.ModuleForTests("import_with_import_deps", "android_common")
+	importWithImportDeps := ctx.ModuleForTests(t, "import_with_import_deps", "android_common")
 	importWithImportDepsJar := importWithImportDeps.Output("combined/import_with_import_deps.jar")
 	importWithImportDepsJavaInfo, _ := android.OtherModuleProvider(ctx, importWithImportDeps.Module(), JavaInfoProvider)
 
@@ -1324,6 +1349,7 @@
 }
 
 func TestCompilerFlags(t *testing.T) {
+	t.Parallel()
 	for _, testCase := range compilerFlagsTestCases {
 		ctx := &mockContext{result: true}
 		CheckKotlincFlags(ctx, []string{testCase.in})
@@ -1338,7 +1364,7 @@
 
 // TODO(jungjw): Consider making this more robust by ignoring path order.
 func checkPatchModuleFlag(t *testing.T, ctx *android.TestContext, moduleName string, expected string) {
-	variables := ctx.ModuleForTests(moduleName, "android_common").VariablesForTestsRelativeToTop()
+	variables := ctx.ModuleForTests(t, moduleName, "android_common").VariablesForTestsRelativeToTop()
 	flags := strings.Split(variables["javacFlags"], " ")
 	got := ""
 	for _, flag := range flags {
@@ -1354,7 +1380,9 @@
 }
 
 func TestPatchModule(t *testing.T) {
+	t.Parallel()
 	t.Run("Java language level 8", func(t *testing.T) {
+		t.Parallel()
 		// Test with legacy javac -source 1.8 -target 1.8
 		bp := `
 			java_library {
@@ -1387,6 +1415,7 @@
 	})
 
 	t.Run("Java language level 9", func(t *testing.T) {
+		t.Parallel()
 		// Test with default javac -source 9 -target 9
 		bp := `
 			java_library {
@@ -1427,6 +1456,7 @@
 }
 
 func TestJavaLibraryWithSystemModules(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_library {
 		    name: "lib-with-source-system-modules",
@@ -1475,7 +1505,7 @@
 }
 
 func checkBootClasspathForLibWithSystemModule(t *testing.T, ctx *android.TestContext, moduleName string, expectedSuffix string) {
-	javacRule := ctx.ModuleForTests(moduleName, "android_common").Rule("javac")
+	javacRule := ctx.ModuleForTests(t, moduleName, "android_common").Rule("javac")
 	bootClasspath := javacRule.Args["bootClasspath"]
 	if strings.HasPrefix(bootClasspath, "--system ") && strings.HasSuffix(bootClasspath, expectedSuffix) {
 		t.Errorf("bootclasspath of %q must start with --system and end with %q, but was %#v.", moduleName, expectedSuffix, bootClasspath)
@@ -1483,6 +1513,7 @@
 }
 
 func TestAidlExportIncludeDirsFromImports(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_library {
 			name: "foo",
@@ -1499,7 +1530,7 @@
 		}
 	`)
 
-	aidlCommand := ctx.ModuleForTests("foo", "android_common").Rule("aidl").RuleParams.Command
+	aidlCommand := ctx.ModuleForTests(t, "foo", "android_common").Rule("aidl").RuleParams.Command
 	expectedAidlFlag := "-Iaidl/bar"
 	if !strings.Contains(aidlCommand, expectedAidlFlag) {
 		t.Errorf("aidl command %q does not contain %q", aidlCommand, expectedAidlFlag)
@@ -1507,6 +1538,7 @@
 }
 
 func TestAidlFlagsArePassedToTheAidlCompiler(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_library {
 			name: "foo",
@@ -1515,7 +1547,7 @@
 		}
 	`)
 
-	aidlCommand := ctx.ModuleForTests("foo", "android_common").Rule("aidl").RuleParams.Command
+	aidlCommand := ctx.ModuleForTests(t, "foo", "android_common").Rule("aidl").RuleParams.Command
 	expectedAidlFlag := "-Werror"
 	if !strings.Contains(aidlCommand, expectedAidlFlag) {
 		t.Errorf("aidl command %q does not contain %q", aidlCommand, expectedAidlFlag)
@@ -1523,6 +1555,7 @@
 }
 
 func TestAidlFlagsWithMinSdkVersion(t *testing.T) {
+	t.Parallel()
 	fixture := android.GroupFixturePreparers(
 		prepareForJavaTest, FixtureWithPrebuiltApis(map[string][]string{"14": {"foo"}}))
 
@@ -1536,6 +1569,7 @@
 		{"system_current", `sdk_version: "system_current"`, "current"},
 	} {
 		t.Run(tc.name, func(t *testing.T) {
+			t.Parallel()
 			ctx := fixture.RunTestWithBp(t, `
 				java_library {
 					name: "foo",
@@ -1543,7 +1577,7 @@
 					`+tc.sdkVersion+`
 				}
 			`)
-			aidlCommand := ctx.ModuleForTests("foo", "android_common").Rule("aidl").RuleParams.Command
+			aidlCommand := ctx.ModuleForTests(t, "foo", "android_common").Rule("aidl").RuleParams.Command
 			expectedAidlFlag := "--min_sdk_version=" + tc.expected
 			if !strings.Contains(aidlCommand, expectedAidlFlag) {
 				t.Errorf("aidl command %q does not contain %q", aidlCommand, expectedAidlFlag)
@@ -1553,6 +1587,7 @@
 }
 
 func TestAidlFlagsMinSdkVersionDroidstubs(t *testing.T) {
+	t.Parallel()
 	bpTemplate := `
 	droidstubs {
 		name: "foo-stubs",
@@ -1579,13 +1614,14 @@
 	}
 	for _, tc := range testCases {
 		ctx := prepareForJavaTest.RunTestWithBp(t, fmt.Sprintf(bpTemplate, tc.sdkVersionBp))
-		aidlCmd := ctx.ModuleForTests("foo-stubs", "android_common").Rule("aidl").RuleParams.Command
+		aidlCmd := ctx.ModuleForTests(t, "foo-stubs", "android_common").Rule("aidl").RuleParams.Command
 		expected := "--min_sdk_version=" + tc.minSdkVersionExpected
 		android.AssertStringDoesContain(t, "aidl command conatins incorrect min_sdk_version for testCse: "+tc.desc, aidlCmd, expected)
 	}
 }
 
 func TestAidlEnforcePermissions(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_library {
 			name: "foo",
@@ -1594,7 +1630,7 @@
 		}
 	`)
 
-	aidlCommand := ctx.ModuleForTests("foo", "android_common").Rule("aidl").RuleParams.Command
+	aidlCommand := ctx.ModuleForTests(t, "foo", "android_common").Rule("aidl").RuleParams.Command
 	expectedAidlFlag := "-Wmissing-permission-annotation -Werror"
 	if !strings.Contains(aidlCommand, expectedAidlFlag) {
 		t.Errorf("aidl command %q does not contain %q", aidlCommand, expectedAidlFlag)
@@ -1602,6 +1638,7 @@
 }
 
 func TestAidlEnforcePermissionsException(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_library {
 			name: "foo",
@@ -1610,7 +1647,7 @@
 		}
 	`)
 
-	aidlCommand := ctx.ModuleForTests("foo", "android_common").Rule("aidl").RuleParams.Command
+	aidlCommand := ctx.ModuleForTests(t, "foo", "android_common").Rule("aidl").RuleParams.Command
 	expectedAidlFlag := "$$FLAGS -Wmissing-permission-annotation -Werror aidl/foo/IFoo.aidl"
 	if !strings.Contains(aidlCommand, expectedAidlFlag) {
 		t.Errorf("aidl command %q does not contain %q", aidlCommand, expectedAidlFlag)
@@ -1622,6 +1659,7 @@
 }
 
 func TestDataNativeBinaries(t *testing.T) {
+	t.Parallel()
 	ctx := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		android.PrepareForTestWithAllowMissingDependencies).RunTestWithBp(t, `
@@ -1639,7 +1677,7 @@
 
 	buildOS := ctx.Config().BuildOS.String()
 
-	test := ctx.ModuleForTests("foo", buildOS+"_common").Module().(*TestHost)
+	test := ctx.ModuleForTests(t, "foo", buildOS+"_common").Module().(*TestHost)
 	entries := android.AndroidMkEntriesForTest(t, ctx, test)[0]
 	expected := []string{"out/soong/.intermediates/bin/" + buildOS + "_x86_64/bin:bin"}
 	actual := entries.EntryMap["LOCAL_COMPATIBILITY_SUPPORT_FILES"]
@@ -1647,6 +1685,7 @@
 }
 
 func TestDefaultInstallable(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_test_host {
 			name: "foo"
@@ -1654,12 +1693,13 @@
 	`)
 
 	buildOS := ctx.Config().BuildOS.String()
-	module := ctx.ModuleForTests("foo", buildOS+"_common").Module().(*TestHost)
+	module := ctx.ModuleForTests(t, "foo", buildOS+"_common").Module().(*TestHost)
 	assertDeepEquals(t, "Default installable value should be true.", proptools.BoolPtr(true),
 		module.properties.Installable)
 }
 
 func TestErrorproneEnabled(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_library {
 			name: "foo",
@@ -1670,7 +1710,7 @@
 		}
 	`)
 
-	javac := ctx.ModuleForTests("foo", "android_common").Description("javac")
+	javac := ctx.ModuleForTests(t, "foo", "android_common").Description("javac")
 
 	// Test that the errorprone plugins are passed to javac
 	expectedSubstring := "-Xplugin:ErrorProne"
@@ -1681,13 +1721,14 @@
 	// Modules with errorprone { enabled: true } will include errorprone checks
 	// in the main javac build rule. Only when RUN_ERROR_PRONE is true will
 	// the explicit errorprone build rule be created.
-	errorprone := ctx.ModuleForTests("foo", "android_common").MaybeDescription("errorprone")
+	errorprone := ctx.ModuleForTests(t, "foo", "android_common").MaybeDescription("errorprone")
 	if errorprone.RuleParams.Description != "" {
 		t.Errorf("expected errorprone build rule to not exist, but it did")
 	}
 }
 
 func TestErrorproneDisabled(t *testing.T) {
+	t.Parallel()
 	bp := `
 		java_library {
 			name: "foo",
@@ -1704,7 +1745,7 @@
 		}),
 	).RunTestWithBp(t, bp)
 
-	javac := ctx.ModuleForTests("foo", "android_common").Description("javac")
+	javac := ctx.ModuleForTests(t, "foo", "android_common").Description("javac")
 
 	// Test that the errorprone plugins are not passed to javac, like they would
 	// be if enabled was true.
@@ -1715,13 +1756,14 @@
 
 	// Check that no errorprone build rule is created, like there would be
 	// if enabled was unset and RUN_ERROR_PRONE was true.
-	errorprone := ctx.ModuleForTests("foo", "android_common").MaybeDescription("errorprone")
+	errorprone := ctx.ModuleForTests(t, "foo", "android_common").MaybeDescription("errorprone")
 	if errorprone.RuleParams.Description != "" {
 		t.Errorf("expected errorprone build rule to not exist, but it did")
 	}
 }
 
 func TestErrorproneEnabledOnlyByEnvironmentVariable(t *testing.T) {
+	t.Parallel()
 	bp := `
 		java_library {
 			name: "foo",
@@ -1735,8 +1777,8 @@
 		}),
 	).RunTestWithBp(t, bp)
 
-	javac := ctx.ModuleForTests("foo", "android_common").Description("javac")
-	errorprone := ctx.ModuleForTests("foo", "android_common").Description("errorprone")
+	javac := ctx.ModuleForTests(t, "foo", "android_common").Description("javac")
+	errorprone := ctx.ModuleForTests(t, "foo", "android_common").Description("errorprone")
 
 	// Check that the errorprone plugins are not passed to javac, because they
 	// will instead be passed to the separate errorprone compilation
@@ -1752,6 +1794,7 @@
 }
 
 func TestDataDeviceBinsBuildsDeviceBinary(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		dataDeviceBinType  string
 		depCompileMultilib string
@@ -1888,6 +1931,7 @@
 
 		testName := fmt.Sprintf(`data_device_bins_%s with compile_multilib:"%s"`, tc.dataDeviceBinType, tc.depCompileMultilib)
 		t.Run(testName, func(t *testing.T) {
+			t.Parallel()
 			ctx := android.GroupFixturePreparers(PrepareForIntegrationTestWithJava).
 				ExtendWithErrorHandler(errorHandler).
 				RunTestWithBp(t, bp)
@@ -1896,7 +1940,7 @@
 			}
 
 			buildOS := ctx.Config.BuildOS.String()
-			fooVariant := ctx.ModuleForTests("foo", buildOS+"_common")
+			fooVariant := ctx.ModuleForTests(t, "foo", buildOS+"_common")
 			fooMod := fooVariant.Module().(*TestHost)
 			entries := android.AndroidMkEntriesForTest(t, ctx.TestContext, fooMod)[0]
 
@@ -1908,7 +1952,7 @@
 
 			expectedData := []string{}
 			for _, variant := range tc.variants {
-				barVariant := ctx.ModuleForTests("bar", variant)
+				barVariant := ctx.ModuleForTests(t, "bar", variant)
 				relocated := barVariant.Output("bar")
 				expectedInput := fmt.Sprintf("out/soong/.intermediates/bar/%s/unstripped/bar", variant)
 				android.AssertPathRelativeToTopEquals(t, "relocation input", expectedInput, relocated.Input)
@@ -1917,12 +1961,13 @@
 			}
 
 			actualData := entries.EntryMap["LOCAL_COMPATIBILITY_SUPPORT_FILES"]
-			android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_TEST_DATA", ctx.Config, expectedData, actualData)
+			android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_TEST_DATA", ctx.Config, android.SortedUniqueStrings(expectedData), android.SortedUniqueStrings(actualData))
 		})
 	}
 }
 
 func TestDeviceBinaryWrapperGeneration(t *testing.T) {
+	t.Parallel()
 	// Scenario 1: java_binary has main_class property in its bp
 	ctx, _ := testJava(t, `
 		java_binary {
@@ -1931,7 +1976,7 @@
 			main_class: "foo.bar.jb",
 		}
 	`)
-	wrapperPath := fmt.Sprint(ctx.ModuleForTests("foo", "android_arm64_armv8-a").AllOutputs())
+	wrapperPath := fmt.Sprint(ctx.ModuleForTests(t, "foo", "android_common").AllOutputs())
 	if !strings.Contains(wrapperPath, "foo.sh") {
 		t.Errorf("wrapper file foo.sh is not generated")
 	}
@@ -1946,6 +1991,7 @@
 }
 
 func TestJavaApiContributionEmptyApiFile(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		prepareForJavaTest,
 		android.FixtureMergeEnv(
@@ -1969,6 +2015,7 @@
 }
 
 func TestJavaApiLibraryAndProviderLink(t *testing.T) {
+	t.Parallel()
 	provider_bp_a := `
 	java_api_contribution {
 		name: "foo1",
@@ -2025,7 +2072,7 @@
 		},
 	}
 	for _, c := range testcases {
-		m := ctx.ModuleForTests(c.moduleName, "android_common")
+		m := ctx.ModuleForTests(t, c.moduleName, "android_common")
 		manifest := m.Output("metalava.sbox.textproto")
 		sboxProto := android.RuleBuilderSboxProtoForTests(t, ctx.TestContext, manifest)
 		manifestCommand := sboxProto.Commands[0].GetCommand()
@@ -2035,6 +2082,7 @@
 }
 
 func TestJavaApiLibraryAndDefaultsLink(t *testing.T) {
+	t.Parallel()
 	provider_bp_a := `
 	java_api_contribution {
 		name: "foo1",
@@ -2133,7 +2181,7 @@
 		},
 	}
 	for _, c := range testcases {
-		m := ctx.ModuleForTests(c.moduleName, "android_common")
+		m := ctx.ModuleForTests(t, c.moduleName, "android_common")
 		manifest := m.Output("metalava.sbox.textproto")
 		sboxProto := android.RuleBuilderSboxProtoForTests(t, ctx.TestContext, manifest)
 		manifestCommand := sboxProto.Commands[0].GetCommand()
@@ -2143,6 +2191,7 @@
 }
 
 func TestJavaApiLibraryJarGeneration(t *testing.T) {
+	t.Parallel()
 	provider_bp_a := `
 	java_api_contribution {
 		name: "foo1",
@@ -2200,7 +2249,7 @@
 		},
 	}
 	for _, c := range testcases {
-		m := ctx.ModuleForTests(c.moduleName, "android_common")
+		m := ctx.ModuleForTests(t, c.moduleName, "android_common")
 		outputs := fmt.Sprint(m.AllOutputs())
 		if !strings.Contains(outputs, c.outputJarName) {
 			t.Errorf("Module output does not contain expected jar %s", c.outputJarName)
@@ -2209,6 +2258,7 @@
 }
 
 func TestJavaApiLibraryLibsLink(t *testing.T) {
+	t.Parallel()
 	provider_bp_a := `
 	java_api_contribution {
 		name: "foo1",
@@ -2285,7 +2335,7 @@
 		},
 	}
 	for _, c := range testcases {
-		m := ctx.ModuleForTests(c.moduleName, "android_common")
+		m := ctx.ModuleForTests(t, c.moduleName, "android_common")
 		javacRules := m.Rule("javac")
 		classPathArgs := javacRules.Args["classpath"]
 		for _, jarName := range c.classPathJarNames {
@@ -2297,6 +2347,7 @@
 }
 
 func TestJavaApiLibraryStaticLibsLink(t *testing.T) {
+	t.Parallel()
 	provider_bp_a := `
 	java_api_contribution {
 		name: "foo1",
@@ -2373,7 +2424,7 @@
 		},
 	}
 	for _, c := range testcases {
-		m := ctx.ModuleForTests(c.moduleName, "android_common")
+		m := ctx.ModuleForTests(t, c.moduleName, "android_common")
 		mergeZipsCommand := m.Rule("merge_zips").RuleParams.Command
 		for _, jarName := range c.staticLibJarNames {
 			if !strings.Contains(mergeZipsCommand, jarName) {
@@ -2384,6 +2435,7 @@
 }
 
 func TestTransitiveSrcFiles(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_library {
 			name: "a",
@@ -2400,13 +2452,14 @@
 			static_libs: ["b"],
 		}
 	`)
-	c := ctx.ModuleForTests("c", "android_common").Module()
+	c := ctx.ModuleForTests(t, "c", "android_common").Module()
 	javaInfo, _ := android.OtherModuleProvider(ctx, c, JavaInfoProvider)
 	transitiveSrcFiles := android.Paths(javaInfo.TransitiveSrcFiles.ToList())
 	android.AssertArrayString(t, "unexpected jar deps", []string{"b.java", "c.java"}, transitiveSrcFiles.Strings())
 }
 
 func TestTradefedOptions(t *testing.T) {
+	t.Parallel()
 	result := PrepareForTestWithJavaBuildComponents.RunTestWithBp(t, `
 java_test_host {
 	name: "foo",
@@ -2422,7 +2475,7 @@
 `)
 
 	buildOS := result.Config.BuildOS.String()
-	args := result.ModuleForTests("foo", buildOS+"_common").
+	args := result.ModuleForTests(t, "foo", buildOS+"_common").
 		Output("out/soong/.intermediates/foo/" + buildOS + "_common/foo.config").Args
 	expected := proptools.NinjaAndShellEscape("<option name=\"exclude-path\" value=\"org/apache\" />")
 	if args["extraConfigs"] != expected {
@@ -2431,6 +2484,7 @@
 }
 
 func TestTestRunnerOptions(t *testing.T) {
+	t.Parallel()
 	result := PrepareForTestWithJavaBuildComponents.RunTestWithBp(t, `
 java_test_host {
 	name: "foo",
@@ -2446,7 +2500,7 @@
 `)
 
 	buildOS := result.Config.BuildOS.String()
-	args := result.ModuleForTests("foo", buildOS+"_common").
+	args := result.ModuleForTests(t, "foo", buildOS+"_common").
 		Output("out/soong/.intermediates/foo/" + buildOS + "_common/foo.config").Args
 	expected := proptools.NinjaAndShellEscape("<option name=\"test-timeout\" value=\"10m\" />\\n        ")
 	if args["extraTestRunnerConfigs"] != expected {
@@ -2455,6 +2509,7 @@
 }
 
 func TestJavaLibraryWithResourcesStem(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJavaWithFS(t, `
     java_library {
         name: "foo",
@@ -2466,7 +2521,7 @@
 			"test-jar/test/resource.txt": nil,
 		})
 
-	m := ctx.ModuleForTests("foo", "android_common")
+	m := ctx.ModuleForTests(t, "foo", "android_common")
 	outputs := fmt.Sprint(m.AllOutputs())
 	if !strings.Contains(outputs, "test.jar") {
 		t.Errorf("Module output does not contain expected jar %s", "test.jar")
@@ -2474,6 +2529,7 @@
 }
 
 func TestHeadersOnly(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_library {
 			name: "foo",
@@ -2482,16 +2538,17 @@
 		}
 	`)
 
-	turbine := ctx.ModuleForTests("foo", "android_common").Rule("turbine")
+	turbine := ctx.ModuleForTests(t, "foo", "android_common").Rule("turbine")
 	if len(turbine.Inputs) != 1 || turbine.Inputs[0].String() != "a.java" {
 		t.Errorf(`foo inputs %v != ["a.java"]`, turbine.Inputs)
 	}
 
-	javac := ctx.ModuleForTests("foo", "android_common").MaybeRule("javac")
+	javac := ctx.ModuleForTests(t, "foo", "android_common").MaybeRule("javac")
 	android.AssertDeepEquals(t, "javac rule", nil, javac.Rule)
 }
 
 func TestJavaApiContributionImport(t *testing.T) {
+	t.Parallel()
 	ctx := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		android.FixtureMergeEnv(
@@ -2511,7 +2568,7 @@
 			api_surface: "public",
 		}
 	`)
-	m := ctx.ModuleForTests("foo", "android_common")
+	m := ctx.ModuleForTests(t, "foo", "android_common")
 	manifest := m.Output("metalava.sbox.textproto")
 	sboxProto := android.RuleBuilderSboxProtoForTests(t, ctx.TestContext, manifest)
 	manifestCommand := sboxProto.Commands[0].GetCommand()
@@ -2520,6 +2577,7 @@
 }
 
 func TestJavaApiLibraryApiFilesSorting(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_api_library {
 			name: "foo",
@@ -2533,7 +2591,7 @@
 			stubs_type: "everything",
 		}
 	`)
-	m := ctx.ModuleForTests("foo", "android_common")
+	m := ctx.ModuleForTests(t, "foo", "android_common")
 	manifest := m.Output("metalava.sbox.textproto")
 	sboxProto := android.RuleBuilderSboxProtoForTests(t, ctx, manifest)
 	manifestCommand := sboxProto.Commands[0].GetCommand()
@@ -2548,6 +2606,7 @@
 }
 
 func TestSdkLibraryProvidesSystemModulesToApiLibrary(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -2577,6 +2636,7 @@
 }
 
 func TestApiLibraryDroidstubsDependency(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -2606,9 +2666,9 @@
 	`)
 
 	currentApiTimestampPath := "api-stubs-docs-non-updatable/android_common/everything/check_current_api.timestamp"
-	foo := result.ModuleForTests("foo", "android_common").Module().(*ApiLibrary)
+	foo := result.ModuleForTests(t, "foo", "android_common").Module().(*ApiLibrary)
 	fooValidationPathsString := strings.Join(foo.validationPaths.Strings(), " ")
-	bar := result.ModuleForTests("bar", "android_common").Module().(*ApiLibrary)
+	bar := result.ModuleForTests(t, "bar", "android_common").Module().(*ApiLibrary)
 	barValidationPathsString := strings.Join(bar.validationPaths.Strings(), " ")
 	android.AssertStringDoesContain(t,
 		"Module expected to have validation",
@@ -2623,6 +2683,7 @@
 }
 
 func TestDisableFromTextStubForCoverageBuild(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -2652,6 +2713,7 @@
 }
 
 func TestMultiplePrebuilts(t *testing.T) {
+	t.Parallel()
 	bp := `
 		// an rdep
 		java_library {
@@ -2734,8 +2796,8 @@
 		).RunTestWithBp(t, fmt.Sprintf(bp, tc.selectedDependencyName))
 
 		// check that rdep gets the correct variation of dep
-		foo := ctx.ModuleForTests("foo", "android_common")
-		expectedDependency := ctx.ModuleForTests(tc.expectedDependencyName, "android_common")
+		foo := ctx.ModuleForTests(t, "foo", "android_common")
+		expectedDependency := ctx.ModuleForTests(t, tc.expectedDependencyName, "android_common")
 		android.AssertBoolEquals(t, fmt.Sprintf("expected dependency from %s to %s\n", foo.Module().Name(), tc.expectedDependencyName), true, hasDep(ctx, foo.Module(), expectedDependency.Module()))
 
 		// check that output file of dep is always bar.jar
@@ -2750,6 +2812,7 @@
 }
 
 func TestMultiplePlatformCompatConfigPrebuilts(t *testing.T) {
+	t.Parallel()
 	bp := `
 		// multiple variations of platform_compat_config
 		// source
@@ -2803,13 +2866,14 @@
 			android.PrepareForTestWithBuildFlag("RELEASE_APEX_CONTRIBUTIONS_ADSERVICES", "myapex_contributions"),
 		).RunTestWithBp(t, fmt.Sprintf(bp, tc.selectedDependencyName))
 
-		mergedGlobalConfig := ctx.SingletonForTests("platform_compat_config_singleton").Output("compat_config/merged_compat_config.xml")
+		mergedGlobalConfig := ctx.SingletonForTests(t, "platform_compat_config_singleton").Output("compat_config/merged_compat_config.xml")
 		android.AssertIntEquals(t, "The merged compat config file should only have a single dependency", 1, len(mergedGlobalConfig.Implicits))
 		android.AssertStringEquals(t, "The merged compat config file is missing the appropriate platform compat config", mergedGlobalConfig.Implicits[0].String(), tc.expectedPlatformCompatConfigXml)
 	}
 }
 
 func TestApiLibraryAconfigDeclarations(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
@@ -2851,7 +2915,7 @@
 	android.AssertBoolEquals(t, "foo expected to depend on bar",
 		CheckModuleHasDependency(t, result.TestContext, "foo", "android_common", "bar"), true)
 
-	m := result.ModuleForTests("foo", "android_common")
+	m := result.ModuleForTests(t, "foo", "android_common")
 	android.AssertStringDoesContain(t, "foo generates revert annotations file",
 		strings.Join(m.AllOutputs(), ""), "revert-annotations-exportable.txt")
 
@@ -2920,6 +2984,7 @@
 
 // Don't allow setting test-only on things that are always tests or never tests.
 func TestInvalidTestOnlyTargets(t *testing.T) {
+	t.Parallel()
 	testCases := []string{
 		` java_test {  name: "java-test", test_only: true, srcs: ["foo.java"],  } `,
 		` java_test_host {  name: "java-test-host", test_only: true, srcs: ["foo.java"],  } `,
@@ -2955,6 +3020,7 @@
 }
 
 func TestJavaLibHostWithStem(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_library_host {
 			name: "foo",
@@ -2964,7 +3030,7 @@
 	`)
 
 	buildOS := ctx.Config().BuildOS.String()
-	foo := ctx.ModuleForTests("foo", buildOS+"_common")
+	foo := ctx.ModuleForTests(t, "foo", buildOS+"_common")
 
 	outputs := fmt.Sprint(foo.AllOutputs())
 	if !strings.Contains(outputs, "foo-new.jar") {
@@ -2973,6 +3039,7 @@
 }
 
 func TestJavaLibWithStem(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_library {
 			name: "foo",
@@ -2981,7 +3048,7 @@
 		}
 	`)
 
-	foo := ctx.ModuleForTests("foo", "android_common")
+	foo := ctx.ModuleForTests(t, "foo", "android_common")
 
 	outputs := fmt.Sprint(foo.AllOutputs())
 	if !strings.Contains(outputs, "foo-new.jar") {
@@ -2990,6 +3057,7 @@
 }
 
 func TestJavaLibraryOutputFilesRel(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		PrepareForTestWithJavaDefaultModules,
 	).RunTestWithBp(t, `
@@ -3011,9 +3079,9 @@
 		}
 	`)
 
-	foo := result.ModuleForTests("foo", "android_common")
-	bar := result.ModuleForTests("bar", "android_common")
-	baz := result.ModuleForTests("baz", "android_common")
+	foo := result.ModuleForTests(t, "foo", "android_common")
+	bar := result.ModuleForTests(t, "bar", "android_common")
+	baz := result.ModuleForTests(t, "baz", "android_common")
 
 	fooOutputPaths := foo.OutputFiles(result.TestContext, t, "")
 	barOutputPaths := bar.OutputFiles(result.TestContext, t, "")
@@ -3035,6 +3103,7 @@
 }
 
 func TestCoverage(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		PrepareForTestWithJavaDefaultModules,
 		prepareForTestWithFrameworkJacocoInstrumentation,
@@ -3051,11 +3120,12 @@
 		java_library {
 			name: "android.car",
 			srcs: ["android.car.java"],
+			installable: true,
 		}
 	`)
 
-	foo := result.ModuleForTests("foo", "android_common")
-	androidCar := result.ModuleForTests("android.car", "android_common")
+	foo := result.ModuleForTests(t, "foo", "android_common")
+	androidCar := result.ModuleForTests(t, "android.car", "android_common")
 
 	fooJacoco := foo.Rule("jacoco")
 	fooCombine := foo.Description("for javac")
@@ -3104,6 +3174,7 @@
 
 // Test that a dependency edge is created to the matching variant of a native library listed in `jni_libs` of java_binary
 func TestNativeRequiredDepOfJavaBinary(t *testing.T) {
+	t.Parallel()
 	findDepsOfModule := func(ctx *android.TestContext, module android.Module, depName string) []blueprint.Module {
 		var ret []blueprint.Module
 		ctx.VisitDirectDeps(module, func(dep blueprint.Module) {
@@ -3125,13 +3196,6 @@
 }
 `
 	res, _ := testJava(t, bp)
-	// The first variant installs the native library via the common variant, so check the deps of both variants.
-	nativeVariantDepsWithDups := findDepsOfModule(res, res.ModuleForTests("myjavabin", "android_arm64_armv8-a").Module(), "mynativelib")
-	nativeVariantDepsWithDups = append(nativeVariantDepsWithDups, findDepsOfModule(res, res.ModuleForTests("myjavabin", "android_common").Module(), "mynativelib")...)
-
-	nativeVariantDepsUnique := map[blueprint.Module]bool{}
-	for _, dep := range nativeVariantDepsWithDups {
-		nativeVariantDepsUnique[dep] = true
-	}
-	android.AssertIntEquals(t, "Create a dep on the first variant", 1, len(nativeVariantDepsUnique))
+	deps := findDepsOfModule(res, res.ModuleForTests(t, "myjavabin", "android_common").Module(), "mynativelib")
+	android.AssertIntEquals(t, "Create a dep on the first variant", 1, len(deps))
 }
diff --git a/java/jdeps.go b/java/jdeps.go
index c2ce503..927c169 100644
--- a/java/jdeps.go
+++ b/java/jdeps.go
@@ -37,8 +37,6 @@
 	outputPath android.Path
 }
 
-var _ android.SingletonMakeVarsProvider = (*jdepsGeneratorSingleton)(nil)
-
 const (
 	jdepsJsonFileName = "module_bp_java_deps.json"
 )
@@ -101,13 +99,6 @@
 		Rule:   android.Touch,
 		Output: jfpath,
 	})
-}
-
-func (j *jdepsGeneratorSingleton) MakeVars(ctx android.MakeVarsContext) {
-	if j.outputPath == nil {
-		return
-	}
-
 	ctx.DistForGoal("general-tests", j.outputPath)
 }
 
diff --git a/java/jdeps_test.go b/java/jdeps_test.go
index d282f19..00f3839 100644
--- a/java/jdeps_test.go
+++ b/java/jdeps_test.go
@@ -22,6 +22,7 @@
 )
 
 func TestCollectJavaLibraryPropertiesAddLibsDeps(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t,
 		`
 		java_library {name: "Foo"}
@@ -31,7 +32,7 @@
 			libs: ["Foo", "Bar"],
 		}
 	`)
-	module := ctx.ModuleForTests("javalib", "android_common").Module().(*Library)
+	module := ctx.ModuleForTests(t, "javalib", "android_common").Module().(*Library)
 	dpInfo, _ := android.OtherModuleProvider(ctx, module, android.IdeInfoProviderKey)
 
 	for _, expected := range []string{"Foo", "Bar"} {
@@ -42,6 +43,7 @@
 }
 
 func TestCollectJavaLibraryPropertiesAddStaticLibsDeps(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t,
 		`
 		java_library {name: "Foo"}
@@ -51,7 +53,7 @@
 			static_libs: ["Foo", "Bar"],
 		}
 	`)
-	module := ctx.ModuleForTests("javalib", "android_common").Module().(*Library)
+	module := ctx.ModuleForTests(t, "javalib", "android_common").Module().(*Library)
 	dpInfo, _ := android.OtherModuleProvider(ctx, module, android.IdeInfoProviderKey)
 
 	for _, expected := range []string{"Foo", "Bar"} {
@@ -62,6 +64,7 @@
 }
 
 func TestCollectJavaLibraryPropertiesAddScrs(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t,
 		`
 		java_library {
@@ -69,7 +72,7 @@
 			srcs: ["Foo.java", "Bar.java"],
 		}
 	`)
-	module := ctx.ModuleForTests("javalib", "android_common").Module().(*Library)
+	module := ctx.ModuleForTests(t, "javalib", "android_common").Module().(*Library)
 	dpInfo, _ := android.OtherModuleProvider(ctx, module, android.IdeInfoProviderKey)
 
 	expected := []string{"Foo.java", "Bar.java"}
@@ -79,6 +82,7 @@
 }
 
 func TestCollectJavaLibraryPropertiesAddAidlIncludeDirs(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t,
 		`
 		java_library {
@@ -88,7 +92,7 @@
 			},
 		}
 	`)
-	module := ctx.ModuleForTests("javalib", "android_common").Module().(*Library)
+	module := ctx.ModuleForTests(t, "javalib", "android_common").Module().(*Library)
 	dpInfo, _ := android.OtherModuleProvider(ctx, module, android.IdeInfoProviderKey)
 
 	expected := []string{"Foo", "Bar"}
@@ -98,6 +102,7 @@
 }
 
 func TestCollectJavaLibraryWithJarJarRules(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t,
 		`
 		java_library {
@@ -106,10 +111,10 @@
 			jarjar_rules: "jarjar_rules.txt",
 		}
 	`)
-	module := ctx.ModuleForTests("javalib", "android_common").Module().(*Library)
+	module := ctx.ModuleForTests(t, "javalib", "android_common").Module().(*Library)
 	dpInfo, _ := android.OtherModuleProvider(ctx, module, android.IdeInfoProviderKey)
 
-	android.AssertBoolEquals(t, "IdeInfo.Srcs of repackaged library should be empty", true, len(dpInfo.Srcs) == 0)
+	android.AssertStringEquals(t, "IdeInfo.Srcs of repackaged library should not be empty", "foo.java", dpInfo.Srcs[0])
 	android.AssertStringEquals(t, "IdeInfo.Jar_rules of repackaged library should not be empty", "jarjar_rules.txt", dpInfo.Jarjar_rules[0])
 	if !android.SubstringInList(dpInfo.Jars, "soong/.intermediates/javalib/android_common/jarjar/turbine/javalib.jar") {
 		t.Errorf("IdeInfo.Jars of repackaged library should contain the output of jarjar-ing. All outputs: %v\n", dpInfo.Jars)
@@ -117,6 +122,7 @@
 }
 
 func TestCollectJavaLibraryLinkingAgainstVersionedSdk(t *testing.T) {
+	t.Parallel()
 	ctx := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		FixtureWithPrebuiltApis(map[string][]string{
@@ -129,8 +135,47 @@
 			sdk_version: "29",
 		}
 	`)
-	module := ctx.ModuleForTests("javalib", "android_common").Module().(*Library)
+	module := ctx.ModuleForTests(t, "javalib", "android_common").Module().(*Library)
 	dpInfo, _ := android.OtherModuleProvider(ctx, module, android.IdeInfoProviderKey)
 
 	android.AssertStringListContains(t, "IdeInfo.Deps should contain versioned sdk module", dpInfo.Deps, "sdk_public_29_android")
 }
+
+func TestDoNotAddNoneSystemModulesToDeps(t *testing.T) {
+	ctx := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		android.FixtureMergeEnv(
+			map[string]string{
+				"DISABLE_STUB_VALIDATION": "true",
+			},
+		),
+	).RunTestWithBp(t,
+		`
+		java_library {
+			name: "javalib",
+			srcs: ["foo.java"],
+			sdk_version: "none",
+			system_modules: "none",
+		}
+
+		java_api_library {
+			name: "javalib.stubs",
+			stubs_type: "everything",
+			api_contributions: ["javalib-current.txt"],
+			api_surface: "public",
+			system_modules: "none",
+		}
+		java_api_contribution {
+			name: "javalib-current.txt",
+			api_file: "javalib-current.txt",
+			api_surface: "public",
+		}
+	`)
+	javalib := ctx.ModuleForTests(t, "javalib", "android_common").Module().(*Library)
+	dpInfo, _ := android.OtherModuleProvider(ctx, javalib, android.IdeInfoProviderKey)
+	android.AssertStringListDoesNotContain(t, "IdeInfo.Deps should contain not contain `none`", dpInfo.Deps, "none")
+
+	javalib_stubs := ctx.ModuleForTests(t, "javalib.stubs", "android_common").Module().(*ApiLibrary)
+	dpInfo, _ = android.OtherModuleProvider(ctx, javalib_stubs, android.IdeInfoProviderKey)
+	android.AssertStringListDoesNotContain(t, "IdeInfo.Deps should contain not contain `none`", dpInfo.Deps, "none")
+}
diff --git a/java/kotlin.go b/java/kotlin.go
index f42d163..308bb03 100644
--- a/java/kotlin.go
+++ b/java/kotlin.go
@@ -181,6 +181,7 @@
 		Command: `rm -rf "$srcJarDir" "$kotlinBuildFile" "$kaptDir" && ` +
 			`mkdir -p "$srcJarDir" "$kaptDir/sources" "$kaptDir/classes" && ` +
 			`${config.ZipSyncCmd} -d $srcJarDir -l $srcJarDir/list -f "*.java" $srcJars && ` +
+			`${config.FindInputDeltaCmd} --template '' --target "$out" --inputs_file "$out.rsp" && ` +
 			`${config.GenKotlinBuildFileCmd} --classpath "$classpath" --name "$name"` +
 			` --srcs "$out.rsp" --srcs "$srcJarDir/list"` +
 			` $commonSrcFilesArg --out "$kotlinBuildFile" && ` +
@@ -197,8 +198,10 @@
 			`$kaptProcessor ` +
 			`-Xbuild-file=$kotlinBuildFile && ` +
 			`${config.SoongZipCmd} -jar -write_if_changed -o $out -C $kaptDir/stubs -D $kaptDir/stubs && ` +
+			`if [ -f "$out.pc_state.new" ]; then mv "$out.pc_state.new" "$out.pc_state"; fi && ` +
 			`rm -rf "$srcJarDir"`,
 		CommandDeps: []string{
+			"${config.FindInputDeltaCmd}",
 			"${config.KotlincCmd}",
 			"${config.KotlinCompilerJar}",
 			"${config.KotlinKaptJar}",
diff --git a/java/kotlin_test.go b/java/kotlin_test.go
index f6e7fca..c7b1ece 100644
--- a/java/kotlin_test.go
+++ b/java/kotlin_test.go
@@ -24,6 +24,7 @@
 )
 
 func TestKotlin(t *testing.T) {
+	t.Parallel()
 	bp := `
 		java_library {
 			name: "foo",
@@ -234,11 +235,12 @@
 
 	for _, tt := range testCases {
 		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
 			result := android.GroupFixturePreparers(
 				PrepareForTestWithJavaDefaultModules,
 				tt.preparer,
 			).RunTestWithBp(t, bp)
-			foo := result.ModuleForTests("foo", "android_common")
+			foo := result.ModuleForTests(t, "foo", "android_common")
 			fooKotlinc := foo.Rule("kotlinc")
 			android.AssertPathsRelativeToTopEquals(t, "foo kotlinc inputs", tt.fooKotlincInputs, fooKotlinc.Inputs)
 
@@ -258,7 +260,7 @@
 			fooCombinedHeaderJar := foo.Output("turbine-combined/foo.jar")
 			android.AssertPathsRelativeToTopEquals(t, "foo header combined inputs", tt.fooHeaderCombinedInputs, fooCombinedHeaderJar.Inputs)
 
-			bar := result.ModuleForTests("bar", "android_common")
+			bar := result.ModuleForTests(t, "bar", "android_common")
 			barKotlinc := bar.Rule("kotlinc")
 			android.AssertPathsRelativeToTopEquals(t, "bar kotlinc inputs", tt.barKotlincInputs, barKotlinc.Inputs)
 
@@ -275,6 +277,7 @@
 }
 
 func TestKapt(t *testing.T) {
+	t.Parallel()
 	bp := `
 		java_library {
 			name: "foo",
@@ -303,18 +306,19 @@
 		}
 	`
 	t.Run("", func(t *testing.T) {
+		t.Parallel()
 		ctx, _ := testJava(t, bp)
 
 		buildOS := ctx.Config().BuildOS.String()
 
-		foo := ctx.ModuleForTests("foo", "android_common")
+		foo := ctx.ModuleForTests(t, "foo", "android_common")
 		kaptStubs := foo.Rule("kapt")
 		turbineApt := foo.Description("turbine apt")
 		kotlinc := foo.Rule("kotlinc")
 		javac := foo.Rule("javac")
 
-		bar := ctx.ModuleForTests("bar", buildOS+"_common").Rule("javac").Output.String()
-		baz := ctx.ModuleForTests("baz", buildOS+"_common").Rule("javac").Output.String()
+		bar := ctx.ModuleForTests(t, "bar", buildOS+"_common").Rule("javac").Output.String()
+		baz := ctx.ModuleForTests(t, "baz", buildOS+"_common").Rule("javac").Output.String()
 
 		// Test that the kotlin and java sources are passed to kapt and kotlinc
 		if len(kaptStubs.Inputs) != 2 || kaptStubs.Inputs[0].String() != "a.java" || kaptStubs.Inputs[1].String() != "b.kt" {
@@ -384,6 +388,7 @@
 	})
 
 	t.Run("errorprone", func(t *testing.T) {
+		t.Parallel()
 		env := map[string]string{
 			"RUN_ERROR_PRONE": "true",
 		}
@@ -395,13 +400,13 @@
 
 		buildOS := result.Config.BuildOS.String()
 
-		kapt := result.ModuleForTests("foo", "android_common").Rule("kapt")
-		javac := result.ModuleForTests("foo", "android_common").Description("javac")
-		errorprone := result.ModuleForTests("foo", "android_common").Description("errorprone")
+		kapt := result.ModuleForTests(t, "foo", "android_common").Rule("kapt")
+		javac := result.ModuleForTests(t, "foo", "android_common").Description("javac")
+		errorprone := result.ModuleForTests(t, "foo", "android_common").Description("errorprone")
 
-		bar := result.ModuleForTests("bar", buildOS+"_common").Description("javac").Output.String()
-		baz := result.ModuleForTests("baz", buildOS+"_common").Description("javac").Output.String()
-		myCheck := result.ModuleForTests("my_check", buildOS+"_common").Description("javac").Output.String()
+		bar := result.ModuleForTests(t, "bar", buildOS+"_common").Description("javac").Output.String()
+		baz := result.ModuleForTests(t, "baz", buildOS+"_common").Description("javac").Output.String()
+		myCheck := result.ModuleForTests(t, "my_check", buildOS+"_common").Description("javac").Output.String()
 
 		// Test that the errorprone plugins are not passed to kapt
 		expectedProcessorPath := "-P plugin:org.jetbrains.kotlin.kapt3:apclasspath=" + bar +
@@ -434,6 +439,7 @@
 }
 
 func TestKaptEncodeFlags(t *testing.T) {
+	t.Parallel()
 	// Compares the kaptEncodeFlags against the results of the example implementation at
 	// https://kotlinlang.org/docs/reference/kapt.html#apjavac-options-encoding
 	tests := []struct {
@@ -484,6 +490,7 @@
 
 	for i, test := range tests {
 		t.Run(strconv.Itoa(i), func(t *testing.T) {
+			t.Parallel()
 			got := kaptEncodeFlags(test.in)
 			if got != test.out {
 				t.Errorf("\nwant %q\n got %q", test.out, got)
@@ -493,6 +500,7 @@
 }
 
 func TestKotlinCompose(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		PrepareForTestWithJavaDefaultModules,
 	).RunTestWithBp(t, `
@@ -500,8 +508,8 @@
 			name: "androidx.compose.runtime_runtime",
 		}
 
-		java_library_host {
-			name: "androidx.compose.compiler_compiler-hosted",
+		kotlin_plugin {
+			name: "kotlin-compose-compiler-plugin",
 		}
 
 		java_library {
@@ -523,9 +531,9 @@
 
 	buildOS := result.Config.BuildOS.String()
 
-	composeCompiler := result.ModuleForTests("androidx.compose.compiler_compiler-hosted", buildOS+"_common").Rule("combineJar").Output
-	withCompose := result.ModuleForTests("withcompose", "android_common")
-	noCompose := result.ModuleForTests("nocompose", "android_common")
+	composeCompiler := result.ModuleForTests(t, "kotlin-compose-compiler-plugin", buildOS+"_common").Rule("combineJar").Output
+	withCompose := result.ModuleForTests(t, "withcompose", "android_common")
+	noCompose := result.ModuleForTests(t, "nocompose", "android_common")
 
 	android.AssertStringListContains(t, "missing compose compiler dependency",
 		withCompose.Rule("kotlinc").Implicits.Strings(), composeCompiler.String())
@@ -542,3 +550,51 @@
 	android.AssertStringDoesNotContain(t, "unexpected compose compiler plugin",
 		noCompose.VariablesForTestsRelativeToTop()["kotlincFlags"], "-Xplugin="+composeCompiler.String())
 }
+
+func TestKotlinPlugin(t *testing.T) {
+	t.Parallel()
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+	).RunTestWithBp(t, `
+		kotlin_plugin {
+			name: "kotlin_plugin",
+		}
+
+		java_library {
+			name: "with_kotlin_plugin",
+			srcs: ["a.kt"],
+			plugins: ["plugin"],
+			kotlin_plugins: ["kotlin_plugin"],
+		}
+
+		java_library {
+			name: "no_kotlin_plugin",
+			srcs: ["a.kt"],
+		}
+
+		java_plugin {
+			name: "plugin",
+		}
+	`)
+
+	buildOS := result.Config.BuildOS.String()
+
+	kotlinPlugin := result.ModuleForTests(t, "kotlin_plugin", buildOS+"_common").Rule("combineJar").Output
+	withKotlinPlugin := result.ModuleForTests(t, "with_kotlin_plugin", "android_common")
+	noKotlinPlugin := result.ModuleForTests(t, "no_kotlin_plugin", "android_common")
+
+	android.AssertStringListContains(t, "missing plugin compiler dependency",
+		withKotlinPlugin.Rule("kotlinc").Implicits.Strings(), kotlinPlugin.String())
+
+	android.AssertStringDoesContain(t, "missing kotlin plugin",
+		withKotlinPlugin.VariablesForTestsRelativeToTop()["kotlincFlags"], "-Xplugin="+kotlinPlugin.String())
+
+	android.AssertStringListContains(t, "missing kapt kotlin plugin dependency",
+		withKotlinPlugin.Rule("kapt").Implicits.Strings(), kotlinPlugin.String())
+
+	android.AssertStringListDoesNotContain(t, "unexpected kotlin plugin dependency",
+		noKotlinPlugin.Rule("kotlinc").Implicits.Strings(), kotlinPlugin.String())
+
+	android.AssertStringDoesNotContain(t, "unexpected kotlin plugin",
+		noKotlinPlugin.VariablesForTestsRelativeToTop()["kotlincFlags"], "-Xplugin="+kotlinPlugin.String())
+}
diff --git a/java/lint.go b/java/lint.go
index 2cbefc3..66f7f85 100644
--- a/java/lint.go
+++ b/java/lint.go
@@ -20,6 +20,7 @@
 	"strings"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/depset"
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
@@ -68,6 +69,11 @@
 		// If soong gets support for testonly, this flag should be replaced with that.
 		Test *bool
 
+		// Same as the regular Test property, but set by internal soong code based on if the module
+		// type is a test module type. This will act as the default value for the test property,
+		// but can be overridden by the user.
+		Test_module_type *bool `blueprint:"mutated"`
+
 		// Whether to ignore the exit code of Android lint. This is the --exit_code
 		// option. Defaults to false.
 		Suppress_exit_code *bool
@@ -101,19 +107,19 @@
 }
 
 type LintDepSets struct {
-	HTML, Text, XML, Baseline *android.DepSet[android.Path]
+	HTML, Text, XML, Baseline depset.DepSet[android.Path]
 }
 
 type LintDepSetsBuilder struct {
-	HTML, Text, XML, Baseline *android.DepSetBuilder[android.Path]
+	HTML, Text, XML, Baseline *depset.Builder[android.Path]
 }
 
 func NewLintDepSetBuilder() LintDepSetsBuilder {
 	return LintDepSetsBuilder{
-		HTML:     android.NewDepSetBuilder[android.Path](android.POSTORDER),
-		Text:     android.NewDepSetBuilder[android.Path](android.POSTORDER),
-		XML:      android.NewDepSetBuilder[android.Path](android.POSTORDER),
-		Baseline: android.NewDepSetBuilder[android.Path](android.POSTORDER),
+		HTML:     depset.NewBuilder[android.Path](depset.POSTORDER),
+		Text:     depset.NewBuilder[android.Path](depset.POSTORDER),
+		XML:      depset.NewBuilder[android.Path](depset.POSTORDER),
+		Baseline: depset.NewBuilder[android.Path](depset.POSTORDER),
 	}
 }
 
@@ -128,18 +134,10 @@
 }
 
 func (l LintDepSetsBuilder) Transitive(info *LintInfo) LintDepSetsBuilder {
-	if info.TransitiveHTML != nil {
-		l.HTML.Transitive(info.TransitiveHTML)
-	}
-	if info.TransitiveText != nil {
-		l.Text.Transitive(info.TransitiveText)
-	}
-	if info.TransitiveXML != nil {
-		l.XML.Transitive(info.TransitiveXML)
-	}
-	if info.TransitiveBaseline != nil {
-		l.Baseline.Transitive(info.TransitiveBaseline)
-	}
+	l.HTML.Transitive(info.TransitiveHTML)
+	l.Text.Transitive(info.TransitiveText)
+	l.XML.Transitive(info.TransitiveXML)
+	l.Baseline.Transitive(info.TransitiveBaseline)
 	return l
 }
 
@@ -204,10 +202,10 @@
 	XML               android.Path
 	ReferenceBaseline android.Path
 
-	TransitiveHTML     *android.DepSet[android.Path]
-	TransitiveText     *android.DepSet[android.Path]
-	TransitiveXML      *android.DepSet[android.Path]
-	TransitiveBaseline *android.DepSet[android.Path]
+	TransitiveHTML     depset.DepSet[android.Path]
+	TransitiveText     depset.DepSet[android.Path]
+	TransitiveXML      depset.DepSet[android.Path]
+	TransitiveBaseline depset.DepSet[android.Path]
 }
 
 func (l *linter) enabled() bool {
@@ -264,7 +262,12 @@
 	if l.library {
 		cmd.Flag("--library")
 	}
-	if proptools.BoolDefault(l.properties.Lint.Test, false) {
+
+	test := proptools.BoolDefault(l.properties.Lint.Test_module_type, false)
+	if l.properties.Lint.Test != nil {
+		test = *l.properties.Lint.Test
+	}
+	if test {
 		cmd.Flag("--test")
 	}
 	if l.manifest != nil {
@@ -395,7 +398,7 @@
 		}
 	}
 
-	extraLintCheckModules := ctx.GetDirectDepsWithTag(extraLintCheckTag)
+	extraLintCheckModules := ctx.GetDirectDepsProxyWithTag(extraLintCheckTag)
 	for _, extraLintCheckModule := range extraLintCheckModules {
 		if dep, ok := android.OtherModuleProvider(ctx, extraLintCheckModule, JavaInfoProvider); ok {
 			l.extraLintCheckJars = append(l.extraLintCheckJars, dep.ImplementationAndResourcesJars...)
@@ -420,7 +423,7 @@
 
 	depSetsBuilder := NewLintDepSetBuilder().Direct(html, text, xml, baseline)
 
-	ctx.VisitDirectDepsWithTag(staticLibTag, func(dep android.Module) {
+	ctx.VisitDirectDepsProxyWithTag(staticLibTag, func(dep android.ModuleProxy) {
 		if info, ok := android.OtherModuleProvider(ctx, dep, LintProvider); ok {
 			depSetsBuilder.Transitive(info)
 		}
@@ -477,7 +480,7 @@
 
 	cmd := rule.Command()
 
-	cmd.Flag(`JAVA_OPTS="-Xmx3072m --add-opens java.base/java.util=ALL-UNNAMED"`).
+	cmd.Flag(`JAVA_OPTS="-Xmx4096m --add-opens java.base/java.util=ALL-UNNAMED"`).
 		FlagWithArg("ANDROID_SDK_HOME=", lintPaths.homeDir.String()).
 		FlagWithInput("SDK_ANNOTATIONS=", annotationsZipPath).
 		FlagWithInput("LINT_OPTS=-DLINT_API_DATABASE=", apiVersionsXMLPath)
@@ -701,16 +704,12 @@
 	zip(l.referenceBaselineZip, func(l *LintInfo) android.Path { return l.ReferenceBaseline })
 
 	ctx.Phony("lint-check", l.htmlZip, l.textZip, l.xmlZip, l.referenceBaselineZip)
-}
 
-func (l *lintSingleton) MakeVars(ctx android.MakeVarsContext) {
 	if !ctx.Config().UnbundledBuild() {
 		ctx.DistForGoal("lint-check", l.htmlZip, l.textZip, l.xmlZip, l.referenceBaselineZip)
 	}
 }
 
-var _ android.SingletonMakeVarsProvider = (*lintSingleton)(nil)
-
 func init() {
 	android.RegisterParallelSingletonType("lint",
 		func() android.Singleton { return &lintSingleton{} })
diff --git a/java/lint_test.go b/java/lint_test.go
index afe3914..236fa63 100644
--- a/java/lint_test.go
+++ b/java/lint_test.go
@@ -22,6 +22,7 @@
 )
 
 func TestJavaLintDoesntUseBaselineImplicitly(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJavaWithFS(t, `
 		java_library {
 			name: "foo",
@@ -37,7 +38,7 @@
 		"lint-baseline.xml": nil,
 	})
 
-	foo := ctx.ModuleForTests("foo", "android_common")
+	foo := ctx.ModuleForTests(t, "foo", "android_common")
 
 	sboxProto := android.RuleBuilderSboxProtoForTests(t, ctx, foo.Output("lint.sbox.textproto"))
 	if strings.Contains(*sboxProto.Commands[0].Command, "--baseline lint-baseline.xml") {
@@ -46,6 +47,7 @@
 }
 
 func TestJavaLintRequiresCustomLintFileToExist(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		PrepareForTestWithJavaDefaultModules,
 		android.PrepareForTestDisallowNonExistentPaths,
@@ -65,6 +67,7 @@
 }
 
 func TestJavaLintUsesCorrectBpConfig(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJavaWithFS(t, `
 		java_library {
 			name: "foo",
@@ -84,7 +87,7 @@
 		"mybaseline.xml": nil,
 	})
 
-	foo := ctx.ModuleForTests("foo", "android_common")
+	foo := ctx.ModuleForTests(t, "foo", "android_common")
 
 	sboxProto := android.RuleBuilderSboxProtoForTests(t, ctx, foo.Output("lint.sbox.textproto"))
 	if !strings.Contains(*sboxProto.Commands[0].Command, "--baseline mybaseline.xml") {
@@ -101,6 +104,7 @@
 }
 
 func TestJavaLintBypassUpdatableChecks(t *testing.T) {
+	t.Parallel()
 	testCases := []struct {
 		name  string
 		bp    string
@@ -144,6 +148,7 @@
 
 	for _, testCase := range testCases {
 		t.Run(testCase.name, func(t *testing.T) {
+			t.Parallel()
 			errorHandler := android.FixtureExpectsAtLeastOneErrorMatchingPattern(testCase.error)
 			android.GroupFixturePreparers(PrepareForTestWithJavaDefaultModules).
 				ExtendWithErrorHandler(errorHandler).
@@ -153,6 +158,7 @@
 }
 
 func TestJavaLintStrictUpdatabilityLinting(t *testing.T) {
+	t.Parallel()
 	bp := `
 		java_library {
 			name: "foo",
@@ -187,7 +193,7 @@
 	result := android.GroupFixturePreparers(PrepareForTestWithJavaDefaultModules, fs.AddToFixture()).
 		RunTestWithBp(t, bp)
 
-	foo := result.ModuleForTests("foo", "android_common")
+	foo := result.ModuleForTests(t, "foo", "android_common")
 	strictUpdatabilityCheck := foo.Output("lint_strict_updatability_check.stamp")
 	if !strings.Contains(strictUpdatabilityCheck.RuleParams.Command,
 		"--disallowed_issues NewApi") {
@@ -250,7 +256,7 @@
 		})).
 			RunTestWithBp(t, thisBp)
 
-		foo := result.ModuleForTests("foo", "android_common")
+		foo := result.ModuleForTests(t, "foo", "android_common")
 		sboxProto := android.RuleBuilderSboxProtoForTests(t, result.TestContext, foo.Output("lint.sbox.textproto"))
 		if !strings.Contains(*sboxProto.Commands[0].Command, "/"+testCase.expected_file) {
 			t.Error("did not use full api database for case", testCase)
@@ -276,3 +282,50 @@
 		ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern("Don't use --disable, --enable, or --check in the flags field, instead use the dedicated disabled_checks, warning_checks, error_checks, or fatal_checks fields")).
 		RunTestWithBp(t, bp)
 }
+
+// b/358643466
+func TestNotTestViaDefault(t *testing.T) {
+	bp := `
+		java_defaults {
+			name: "mydefaults",
+			lint: {
+				test: false,
+			},
+		}
+		android_test {
+			name: "foo",
+			srcs: [
+				"a.java",
+			],
+			min_sdk_version: "29",
+			sdk_version: "current",
+			defaults: ["mydefaults"],
+		}
+		android_test {
+			name: "foo2",
+			srcs: [
+				"a.java",
+			],
+			min_sdk_version: "29",
+			sdk_version: "current",
+		}
+	`
+	result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(t, bp)
+	ctx := result.TestContext
+
+	foo := ctx.ModuleForTests(t, "foo", "android_common")
+	sboxProto := android.RuleBuilderSboxProtoForTests(t, ctx, foo.Output("lint.sbox.textproto"))
+	command := *sboxProto.Commands[0].Command
+
+	if strings.Contains(command, "--test") {
+		t.Fatalf("Expected command to not contain --test")
+	}
+
+	foo2 := ctx.ModuleForTests(t, "foo2", "android_common")
+	sboxProto2 := android.RuleBuilderSboxProtoForTests(t, ctx, foo2.Output("lint.sbox.textproto"))
+	command2 := *sboxProto2.Commands[0].Command
+
+	if !strings.Contains(command2, "--test") {
+		t.Fatalf("Expected command to contain --test")
+	}
+}
diff --git a/java/metalava/main-config.xml b/java/metalava/main-config.xml
index c61196f..c2881ac 100644
--- a/java/metalava/main-config.xml
+++ b/java/metalava/main-config.xml
@@ -16,4 +16,20 @@
 
 <config xmlns="http://www.google.com/tools/metalava/config"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-    xsi:schemaLocation="http://www.google.com/tools/metalava/config ../../../../tools/metalava/metalava/src/main/resources/schemas/config.xsd"/>
+    xsi:schemaLocation="http://www.google.com/tools/metalava/config ../../../../tools/metalava/metalava/src/main/resources/schemas/config.xsd">
+  <api-surfaces>
+    <!-- These are hard coded into java_sdk_library. -->
+    <api-surface name="public"/>
+    <api-surface name="system" extends="public"/>
+    <api-surface name="module-lib" extends="system"/>
+    <api-surface name="test" extends="system"/>
+    <api-surface name="system-server" extends="public"/>
+    <!-- This is used in java/core-libraries/Android.bp. -->
+    <api-surface name="core"/>
+    <!-- These are used in libcore, external/conscrypt and external/icu. -->
+    <api-surface name="core-platform" extends="public"/>
+    <api-surface name="core-platform-legacy" extends="public"/>
+    <api-surface name="core-platform-plus-public"/>
+    <api-surface name="intra-core" extends="public"/>
+  </api-surfaces>
+</config>
diff --git a/java/platform_bootclasspath.go b/java/platform_bootclasspath.go
index 5bb7754..155afc6 100644
--- a/java/platform_bootclasspath.go
+++ b/java/platform_bootclasspath.go
@@ -15,6 +15,11 @@
 package java
 
 import (
+	"maps"
+	"slices"
+
+	"github.com/google/blueprint"
+
 	"android/soong/android"
 	"android/soong/dexpreopt"
 )
@@ -24,20 +29,23 @@
 }
 
 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
 // jars.
-var (
-	platformBootclasspathArtBootJarDepTag  = bootclasspathDependencyTag{name: "art-boot-jar"}
-	platformBootclasspathBootJarDepTag     = bootclasspathDependencyTag{name: "platform-boot-jar"}
-	platformBootclasspathApexBootJarDepTag = bootclasspathDependencyTag{name: "apex-boot-jar"}
-	platformBootclasspathImplLibDepTag     = dependencyTag{name: "impl-lib-tag"}
-)
+type platformBootclasspathImplLibDepTagType struct {
+	blueprint.BaseDependencyTag
+}
+
+func (p platformBootclasspathImplLibDepTagType) ExcludeFromVisibilityEnforcement() {}
+
+var platformBootclasspathImplLibDepTag platformBootclasspathImplLibDepTagType
+
+var _ android.ExcludeFromVisibilityEnforcementTag = platformBootclasspathImplLibDepTag
 
 type platformBootclasspathModule struct {
-	android.SingletonModuleBase
+	android.ModuleBase
 	ClasspathFragmentBase
 
 	properties platformBootclasspathProperties
@@ -48,6 +56,12 @@
 	// The apex:module pairs obtained from the fragments.
 	fragments []android.Module
 
+	// The map of apex to the fragments they contain.
+	apexNameToFragment map[string]android.Module
+
+	// The map of library modules in the bootclasspath to the fragments that contain them.
+	libraryToApex map[android.Module]string
+
 	// Path to the monolithic hiddenapi-flags.csv file.
 	hiddenAPIFlagsCSV android.OutputPath
 
@@ -64,7 +78,7 @@
 	HiddenAPIFlagFileProperties
 }
 
-func platformBootclasspathFactory() android.SingletonModule {
+func platformBootclasspathFactory() android.Module {
 	m := &platformBootclasspathModule{}
 	m.AddProperties(&m.properties)
 	initClasspathFragment(m, BOOTCLASSPATH)
@@ -89,26 +103,12 @@
 
 	b.hiddenAPIDepsMutator(ctx)
 
-	if !dexpreopt.IsDex2oatNeeded(ctx) {
-		return
+	if dexpreopt.IsDex2oatNeeded(ctx) {
+		// Add a dependency onto the dex2oat tool which is needed for creating the boot image. The
+		// path is retrieved from the dependency by GetGlobalSoongConfig(ctx).
+		dexpreopt.RegisterToolDeps(ctx)
 	}
 
-	// Add a dependency onto the dex2oat tool which is needed for creating the boot image. The
-	// path is retrieved from the dependency by GetGlobalSoongConfig(ctx).
-	dexpreopt.RegisterToolDeps(ctx)
-}
-
-func (b *platformBootclasspathModule) hiddenAPIDepsMutator(ctx android.BottomUpMutatorContext) {
-	if ctx.Config().DisableHiddenApiChecks() {
-		return
-	}
-
-	// Add dependencies onto the stub lib modules.
-	apiLevelToStubLibModules := hiddenAPIComputeMonolithicStubLibModules(ctx.Config())
-	hiddenAPIAddStubLibDependencies(ctx, apiLevelToStubLibModules)
-}
-
-func (b *platformBootclasspathModule) BootclasspathDepsMutator(ctx android.BottomUpMutatorContext) {
 	// Add dependencies on all the ART jars.
 	global := dexpreopt.GetGlobalConfig(ctx)
 	addDependenciesOntoSelectedBootImageApexes(ctx, "com.android.art")
@@ -116,13 +116,13 @@
 	var bootImageModuleNames []string
 
 	// TODO: b/308174306 - Remove the mechanism of depending on the java_sdk_library(_import) directly
-	addDependenciesOntoBootImageModules(ctx, global.ArtApexJars, platformBootclasspathArtBootJarDepTag)
+	addDependenciesOntoBootImageModules(ctx, global.ArtApexJars, artBootJar)
 	bootImageModuleNames = append(bootImageModuleNames, global.ArtApexJars.CopyOfJars()...)
 
 	// Add dependencies on all the non-updatable jars, which are on the platform or in non-updatable
 	// APEXes.
 	platformJars := b.platformJars(ctx)
-	addDependenciesOntoBootImageModules(ctx, platformJars, platformBootclasspathBootJarDepTag)
+	addDependenciesOntoBootImageModules(ctx, platformJars, platformBootJar)
 	bootImageModuleNames = append(bootImageModuleNames, platformJars.CopyOfJars()...)
 
 	// Add dependencies on all the updatable jars, except the ART jars.
@@ -133,7 +133,7 @@
 	}
 	addDependenciesOntoSelectedBootImageApexes(ctx, android.FirstUniqueStrings(apexes)...)
 	// TODO: b/308174306 - Remove the mechanism of depending on the java_sdk_library(_import) directly
-	addDependenciesOntoBootImageModules(ctx, apexJars, platformBootclasspathApexBootJarDepTag)
+	addDependenciesOntoBootImageModules(ctx, apexJars, apexBootJar)
 	bootImageModuleNames = append(bootImageModuleNames, apexJars.CopyOfJars()...)
 
 	// Add dependencies on all the fragments.
@@ -147,62 +147,58 @@
 	}
 }
 
-func addDependenciesOntoBootImageModules(ctx android.BottomUpMutatorContext, modules android.ConfiguredJarList, tag bootclasspathDependencyTag) {
+func (b *platformBootclasspathModule) hiddenAPIDepsMutator(ctx android.BottomUpMutatorContext) {
+	if ctx.Config().DisableHiddenApiChecks() {
+		return
+	}
+
+	// Add dependencies onto the stub lib modules.
+	apiLevelToStubLibModules := hiddenAPIComputeMonolithicStubLibModules(ctx.Config())
+	hiddenAPIAddStubLibDependencies(ctx, apiLevelToStubLibModules)
+}
+
+func addDependenciesOntoBootImageModules(ctx android.BottomUpMutatorContext, modules android.ConfiguredJarList, tagType bootclasspathDependencyTagType) {
 	for i := 0; i < modules.Len(); i++ {
 		apex := modules.Apex(i)
 		name := modules.Jar(i)
 
-		addDependencyOntoApexModulePair(ctx, apex, name, tag)
+		addDependencyOntoApexModulePair(ctx, apex, name, tagType)
 	}
 }
 
-// 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)
-	platformModules := gatherApexModulePairDepsWithTag(ctx, platformBootclasspathBootJarDepTag)
-	apexModules := gatherApexModulePairDepsWithTag(ctx, platformBootclasspathApexBootJarDepTag)
+	artModules, artModulesToApex := gatherApexModulePairDepsWithTag(ctx, artBootJar)
+	platformModules, platformModulesToApex := gatherApexModulePairDepsWithTag(ctx, platformBootJar)
+	apexModules, apexModulesToApex := gatherApexModulePairDepsWithTag(ctx, apexBootJar)
 
 	// Concatenate them all, in order as they would appear on the bootclasspath.
-	var allModules []android.Module
-	allModules = append(allModules, artModules...)
-	allModules = append(allModules, platformModules...)
-	allModules = append(allModules, apexModules...)
+	allModules := slices.Concat(artModules, platformModules, apexModules)
 	b.configuredModules = allModules
+	b.libraryToApex = maps.Clone(artModulesToApex)
+	maps.Copy(b.libraryToApex, platformModulesToApex)
+	maps.Copy(b.libraryToApex, apexModulesToApex)
 
 	// Do not add implLibModule to allModules as the impl lib is only used to collect the
 	// transitive source files
 	var implLibModule []android.Module
-	ctx.VisitDirectDepsWithTag(implLibraryTag, func(m android.Module) {
+	ctx.VisitDirectDepsWithTag(platformBootclasspathImplLibDepTag, func(m android.Module) {
 		implLibModule = append(implLibModule, m)
 	})
 
 	var transitiveSrcFiles android.Paths
 	for _, module := range append(allModules, implLibModule...) {
 		if depInfo, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider); ok {
-			if depInfo.TransitiveSrcFiles != nil {
-				transitiveSrcFiles = append(transitiveSrcFiles, depInfo.TransitiveSrcFiles.ToList()...)
-			}
+			transitiveSrcFiles = append(transitiveSrcFiles, depInfo.TransitiveSrcFiles.ToList()...)
 		}
 	}
 	jarArgs := resourcePathsToJarArgs(transitiveSrcFiles)
 	jarArgs = append(jarArgs, "-srcjar") // Move srcfiles to the right package
-	srcjar := android.PathForModuleOut(ctx, ctx.ModuleName()+"-transitive.srcjar").OutputPath
+	srcjar := android.PathForModuleOut(ctx, ctx.ModuleName()+"-transitive.srcjar")
 	TransformResourcesToJar(ctx, srcjar, jarArgs, transitiveSrcFiles)
 
 	// Gather all the fragments dependencies.
-	b.fragments = gatherApexModulePairDepsWithTag(ctx, bootclasspathFragmentDepTag)
+	b.fragments, b.apexNameToFragment = gatherFragments(ctx)
 
 	// Check the configuration of the boot modules.
 	// ART modules are checked by the art-bootclasspath-fragment.
@@ -211,7 +207,7 @@
 
 	b.generateClasspathProtoBuildActions(ctx)
 
-	bootDexJarByModule := b.generateHiddenAPIBuildActions(ctx, b.configuredModules, b.fragments)
+	bootDexJarByModule := b.generateHiddenAPIBuildActions(ctx, b.configuredModules, b.fragments, b.libraryToApex, b.apexNameToFragment)
 	buildRuleForBootJarsPackageCheck(ctx, bootDexJarByModule)
 
 	ctx.SetOutputFiles(android.Paths{b.hiddenAPIFlagsCSV}, "hiddenapi-flags.csv")
@@ -262,7 +258,7 @@
 		fromUpdatableApex := apexInfo.Updatable
 		if fromUpdatableApex {
 			// error: this jar is part of an updatable apex
-			ctx.ModuleErrorf("module %q from updatable apexes %q is not allowed in the platform bootclasspath", ctx.OtherModuleName(m), apexInfo.InApexVariants)
+			ctx.ModuleErrorf("module %q from updatable apex %q is not allowed in the platform bootclasspath", ctx.OtherModuleName(m), apexInfo.BaseApexName)
 		} else {
 			// ok: this jar is part of the platform or a non-updatable apex
 		}
@@ -286,7 +282,11 @@
 				//  modules is complete.
 				if !ctx.Config().AlwaysUsePrebuiltSdks() {
 					// error: this jar is part of the platform
-					ctx.ModuleErrorf("module %q from platform is not allowed in the apex boot jars list", name)
+					if ctx.Config().AllowMissingDependencies() {
+						ctx.AddMissingDependencies([]string{"module_" + name + "_from_platform_is_not_allowed_in_the_apex_boot_jars_list"})
+					} else {
+						ctx.ModuleErrorf("module %q from platform is not allowed in the apex boot jars list", name)
+					}
 				}
 			} else {
 				// TODO(b/177892522): Treat this as an error.
@@ -298,7 +298,8 @@
 }
 
 // generateHiddenAPIBuildActions generates all the hidden API related build rules.
-func (b *platformBootclasspathModule) generateHiddenAPIBuildActions(ctx android.ModuleContext, modules []android.Module, fragments []android.Module) bootDexJarByModule {
+func (b *platformBootclasspathModule) generateHiddenAPIBuildActions(ctx android.ModuleContext, modules []android.Module,
+	fragments []android.Module, libraryToApex map[android.Module]string, apexNameToFragment map[string]android.Module) bootDexJarByModule {
 	createEmptyHiddenApiFiles := func() {
 		paths := android.OutputPaths{b.hiddenAPIFlagsCSV, b.hiddenAPIIndexCSV, b.hiddenAPIMetadataCSV}
 		for _, path := range paths {
@@ -325,7 +326,7 @@
 	}
 
 	// Construct a list of ClasspathElement objects from the modules and fragments.
-	classpathElements := CreateClasspathElements(ctx, modules, fragments)
+	classpathElements := CreateClasspathElements(ctx, modules, fragments, libraryToApex, apexNameToFragment)
 
 	monolithicInfo := b.createAndProvideMonolithicHiddenAPIInfo(ctx, classpathElements)
 
@@ -430,13 +431,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/platform_bootclasspath_test.go b/java/platform_bootclasspath_test.go
index 0d2acae..727e306 100644
--- a/java/platform_bootclasspath_test.go
+++ b/java/platform_bootclasspath_test.go
@@ -27,21 +27,27 @@
 )
 
 func TestPlatformBootclasspath(t *testing.T) {
+	t.Parallel()
 	preparer := android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
 		FixtureConfigureBootJars("platform:foo", "system_ext:bar"),
+		android.FixtureMergeMockFs(android.MockFS{
+			"api/current.txt": nil,
+			"api/removed.txt": nil,
+		}),
 		android.FixtureWithRootAndroidBp(`
 			platform_bootclasspath {
 				name: "platform-bootclasspath",
 			}
 
-			java_library {
+			java_sdk_library {
 				name: "bar",
 				srcs: ["a.java"],
 				system_modules: "none",
 				sdk_version: "none",
 				compile_dex: true,
 				system_ext_specific: true,
+				unsafe_ignore_missing_latest_api: true,
 			}
 		`),
 	)
@@ -76,6 +82,7 @@
 	`)
 
 	t.Run("missing", func(t *testing.T) {
+		t.Parallel()
 		preparer.
 			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`"platform-bootclasspath" depends on undefined module "foo"`)).
 			RunTest(t)
@@ -86,11 +93,12 @@
 
 	checkSrcJarInputs := func(t *testing.T, result *android.TestResult, name string, expected []string) {
 		t.Helper()
-		srcjar := result.ModuleForTests(name, "android_common").Output(name + "-transitive.srcjar")
+		srcjar := result.ModuleForTests(t, name, "android_common").Output(name + "-transitive.srcjar")
 		android.AssertStringDoesContain(t, "srcjar arg", srcjar.Args["jarArgs"], "-srcjar")
 		android.AssertArrayString(t, "srcjar inputs", expected, srcjar.Implicits.Strings())
 	}
 	t.Run("source", func(t *testing.T) {
+		t.Parallel()
 		result := android.GroupFixturePreparers(
 			preparer,
 			addSourceBootclassPathModule,
@@ -107,6 +115,7 @@
 	})
 
 	t.Run("prebuilt", func(t *testing.T) {
+		t.Parallel()
 		result := android.GroupFixturePreparers(
 			preparer,
 			addPrebuiltBootclassPathModule,
@@ -123,6 +132,7 @@
 	})
 
 	t.Run("source+prebuilt - source preferred", func(t *testing.T) {
+		t.Parallel()
 		result := android.GroupFixturePreparers(
 			preparer,
 			addSourceBootclassPathModule,
@@ -140,6 +150,7 @@
 	})
 
 	t.Run("source+prebuilt - prebuilt preferred", func(t *testing.T) {
+		t.Parallel()
 		result := android.GroupFixturePreparers(
 			preparer,
 			addSourceBootclassPathModule,
@@ -157,6 +168,7 @@
 	})
 
 	t.Run("dex import", func(t *testing.T) {
+		t.Parallel()
 		result := android.GroupFixturePreparers(
 			preparer,
 			android.FixtureAddTextFile("deximport/Android.bp", `
@@ -179,6 +191,7 @@
 }
 
 func TestPlatformBootclasspathVariant(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
 		android.FixtureWithRootAndroidBp(`
@@ -193,6 +206,7 @@
 }
 
 func TestPlatformBootclasspath_ClasspathFragmentPaths(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
 		android.FixtureWithRootAndroidBp(`
@@ -204,10 +218,11 @@
 
 	p := result.Module("platform-bootclasspath", "android_common").(*platformBootclasspathModule)
 	android.AssertStringEquals(t, "output filepath", "bootclasspath.pb", p.ClasspathFragmentBase.outputFilepath.Base())
-	android.AssertPathRelativeToTopEquals(t, "install filepath", "out/soong/target/product/test_device/system/etc/classpaths", p.ClasspathFragmentBase.installDirPath)
+	android.AssertPathRelativeToTopEquals(t, "install filepath", "out/target/product/test_device/system/etc/classpaths", p.ClasspathFragmentBase.installDirPath)
 }
 
 func TestPlatformBootclasspathModule_AndroidMkEntries(t *testing.T) {
+	t.Parallel()
 	preparer := android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
 		android.FixtureWithRootAndroidBp(`
@@ -218,6 +233,7 @@
 	)
 
 	t.Run("AndroidMkEntries", func(t *testing.T) {
+		t.Parallel()
 		result := preparer.RunTest(t)
 
 		p := result.Module("platform-bootclasspath", "android_common").(*platformBootclasspathModule)
@@ -227,6 +243,7 @@
 	})
 
 	t.Run("hiddenapi-flags-entry", func(t *testing.T) {
+		t.Parallel()
 		result := preparer.RunTest(t)
 
 		p := result.Module("platform-bootclasspath", "android_common").(*platformBootclasspathModule)
@@ -238,6 +255,7 @@
 	})
 
 	t.Run("classpath-fragment-entry", func(t *testing.T) {
+		t.Parallel()
 		result := preparer.RunTest(t)
 
 		want := map[string][]string{
@@ -262,6 +280,7 @@
 }
 
 func TestPlatformBootclasspath_Dist(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
 		FixtureConfigureBootJars("platform:foo", "platform:bar"),
@@ -298,13 +317,14 @@
 	platformBootclasspath := result.Module("platform-bootclasspath", "android_common").(*platformBootclasspathModule)
 	entries := android.AndroidMkEntriesForTest(t, result.TestContext, platformBootclasspath)
 	goals := entries[0].GetDistForGoals(platformBootclasspath)
-	android.AssertStringEquals(t, "platform dist goals phony", ".PHONY: droidcore\n", goals[0])
+	android.AssertStringEquals(t, "platform dist goals phony", ".PHONY: droidcore", goals[0])
 	android.AssertStringDoesContain(t, "platform dist goals meta check", goals[1], "$(if $(strip $(ALL_TARGETS.")
 	android.AssertStringDoesContain(t, "platform dist goals meta assign", goals[1], "),,$(eval ALL_TARGETS.")
-	android.AssertStringEquals(t, "platform dist goals call", "$(call dist-for-goals,droidcore,out/soong/hiddenapi/hiddenapi-flags.csv:hiddenapi-flags.csv)\n", android.StringRelativeToTop(result.Config, goals[2]))
+	android.AssertStringEquals(t, "platform dist goals call", "$(call dist-for-goals,droidcore,out/soong/hiddenapi/hiddenapi-flags.csv:hiddenapi-flags.csv)", android.StringRelativeToTop(result.Config, goals[2]))
 }
 
 func TestPlatformBootclasspath_HiddenAPIMonolithicFiles(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		hiddenApiFixtureFactory,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -347,7 +367,7 @@
 
 	// Make sure that the foo-hiddenapi-annotations.jar is included in the inputs to the rules that
 	// creates the index.csv file.
-	platformBootclasspath := result.ModuleForTests("myplatform-bootclasspath", "android_common")
+	platformBootclasspath := result.ModuleForTests(t, "myplatform-bootclasspath", "android_common")
 
 	var rule android.TestingBuildParams
 
diff --git a/java/platform_compat_config.go b/java/platform_compat_config.go
index 5b145c6..d4d2fb5 100644
--- a/java/platform_compat_config.go
+++ b/java/platform_compat_config.go
@@ -51,6 +51,10 @@
 
 type platformCompatConfigProperties struct {
 	Src *string `android:"path"`
+
+	// If true, we include it in the "merged" XML (merged_compat_config.xml).
+	// Default is true.
+	Include_in_merged_xml *bool
 }
 
 type platformCompatConfig struct {
@@ -60,6 +64,7 @@
 	installDirPath android.InstallPath
 	configFile     android.OutputPath
 	metadataFile   android.OutputPath
+	doMerge        bool
 
 	installConfigFile android.InstallPath
 }
@@ -68,6 +73,10 @@
 	return p.metadataFile
 }
 
+func (p *platformCompatConfig) includeInMergedXml() bool {
+	return p.doMerge
+}
+
 func (p *platformCompatConfig) CompatConfig() android.OutputPath {
 	return p.configFile
 }
@@ -78,6 +87,9 @@
 
 type platformCompatConfigMetadataProvider interface {
 	compatConfigMetadata() android.Path
+
+	// Whether to include it in the "merged" XML (merged_compat_config.xml) or not.
+	includeInMergedXml() bool
 }
 
 type PlatformCompatConfigIntf interface {
@@ -98,6 +110,7 @@
 	metadataFileName := p.Name() + "_meta.xml"
 	p.configFile = android.PathForModuleOut(ctx, configFileName).OutputPath
 	p.metadataFile = android.PathForModuleOut(ctx, metadataFileName).OutputPath
+	p.doMerge = proptools.BoolDefault(p.properties.Include_in_merged_xml, true)
 	path := android.PathForModuleSrc(ctx, String(p.properties.Src))
 
 	rule.Command().
@@ -201,6 +214,10 @@
 	return module.metadataFile
 }
 
+func (module *prebuiltCompatConfigModule) includeInMergedXml() bool {
+	return true // Always include in merged.xml
+}
+
 func (module *prebuiltCompatConfigModule) BaseModuleName() string {
 	return proptools.StringDefault(module.properties.Source_module_name, module.ModuleBase.Name())
 }
@@ -237,6 +254,9 @@
 			if !android.IsModulePreferred(module) {
 				return
 			}
+			if !c.includeInMergedXml() {
+				return
+			}
 			metadata := c.compatConfigMetadata()
 			compatConfigMetadata = append(compatConfigMetadata, metadata)
 		}
@@ -258,12 +278,7 @@
 	rule.Build("merged-compat-config", "Merge compat config")
 
 	p.metadata = outputPath
-}
-
-func (p *platformCompatConfigSingleton) MakeVars(ctx android.MakeVarsContext) {
-	if p.metadata != nil {
-		ctx.DistForGoal("droidcore", p.metadata)
-	}
+	ctx.DistForGoal("droidcore", p.metadata)
 }
 
 func platformCompatConfigSingletonFactory() android.Singleton {
diff --git a/java/platform_compat_config_test.go b/java/platform_compat_config_test.go
index 80d991c..72f81e0 100644
--- a/java/platform_compat_config_test.go
+++ b/java/platform_compat_config_test.go
@@ -21,11 +21,13 @@
 )
 
 func TestPlatformCompatConfig(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		PrepareForTestWithPlatformCompatConfig,
 		android.FixtureWithRootAndroidBp(`
 			platform_compat_config {
 				name: "myconfig2",
+				include_in_merged_xml: false,
 			}
 			platform_compat_config {
 				name: "myconfig1",
@@ -38,7 +40,6 @@
 
 	CheckMergedCompatConfigInputs(t, result, "myconfig",
 		"out/soong/.intermediates/myconfig1/myconfig1_meta.xml",
-		"out/soong/.intermediates/myconfig2/myconfig2_meta.xml",
 		"out/soong/.intermediates/myconfig3/myconfig3_meta.xml",
 	)
 }
diff --git a/java/plugin.go b/java/plugin.go
index 9c4774a..3534c7b 100644
--- a/java/plugin.go
+++ b/java/plugin.go
@@ -16,14 +16,28 @@
 
 import (
 	"android/soong/android"
+	"github.com/google/blueprint"
 )
 
+type JavaPluginInfo struct {
+	ProcessorClass *string
+	GeneratesApi   bool
+}
+
+var JavaPluginInfoProvider = blueprint.NewProvider[JavaPluginInfo]()
+
+type KotlinPluginInfo struct {
+}
+
+var KotlinPluginInfoProvider = blueprint.NewProvider[KotlinPluginInfo]()
+
 func init() {
 	registerJavaPluginBuildComponents(android.InitRegistrationContext)
 }
 
 func registerJavaPluginBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("java_plugin", PluginFactory)
+	ctx.RegisterModuleType("kotlin_plugin", KotlinPluginFactory)
 }
 
 func PluginFactory() android.Module {
@@ -37,6 +51,16 @@
 	return module
 }
 
+func KotlinPluginFactory() android.Module {
+	module := &KotlinPlugin{}
+
+	module.addHostProperties()
+
+	InitJavaModule(module, android.HostSupported)
+
+	return module
+}
+
 // Plugin describes a java_plugin module, a host java library that will be used by javac as an annotation processor.
 type Plugin struct {
 	Library
@@ -53,3 +77,23 @@
 	// parallelism and cause more recompilation for modules that depend on modules that use this plugin.
 	Generates_api *bool
 }
+
+func (p *Plugin) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	p.Library.GenerateAndroidBuildActions(ctx)
+
+	android.SetProvider(ctx, JavaPluginInfoProvider, JavaPluginInfo{
+		ProcessorClass: p.pluginProperties.Processor_class,
+		GeneratesApi:   Bool(p.pluginProperties.Generates_api),
+	})
+}
+
+// Plugin describes a kotlin_plugin module, a host java/kotlin library that will be used by kotlinc as a compiler plugin.
+type KotlinPlugin struct {
+	Library
+}
+
+func (p *KotlinPlugin) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	p.Library.GenerateAndroidBuildActions(ctx)
+
+	android.SetProvider(ctx, KotlinPluginInfoProvider, KotlinPluginInfo{})
+}
diff --git a/java/plugin_test.go b/java/plugin_test.go
index dc29b1c..007b74a 100644
--- a/java/plugin_test.go
+++ b/java/plugin_test.go
@@ -19,6 +19,7 @@
 )
 
 func TestNoPlugin(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_library {
 			name: "foo",
@@ -26,8 +27,8 @@
 		}
 	`)
 
-	javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
-	turbine := ctx.ModuleForTests("foo", "android_common").MaybeRule("turbine")
+	javac := ctx.ModuleForTests(t, "foo", "android_common").Rule("javac")
+	turbine := ctx.ModuleForTests(t, "foo", "android_common").MaybeRule("turbine")
 
 	if turbine.Rule == nil {
 		t.Errorf("expected turbine to be enabled")
@@ -43,6 +44,7 @@
 }
 
 func TestPlugin(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_library {
 			name: "foo",
@@ -59,14 +61,14 @@
 
 	buildOS := ctx.Config().BuildOS.String()
 
-	javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
-	turbine := ctx.ModuleForTests("foo", "android_common").MaybeRule("turbine")
+	javac := ctx.ModuleForTests(t, "foo", "android_common").Rule("javac")
+	turbine := ctx.ModuleForTests(t, "foo", "android_common").MaybeRule("turbine")
 
 	if turbine.Rule == nil {
 		t.Errorf("expected turbine to be enabled")
 	}
 
-	bar := ctx.ModuleForTests("bar", buildOS+"_common").Rule("javac").Output.String()
+	bar := ctx.ModuleForTests(t, "bar", buildOS+"_common").Rule("javac").Output.String()
 
 	if !inList(bar, javac.Implicits.Strings()) {
 		t.Errorf("foo implicits %v does not contain %q", javac.Implicits.Strings(), bar)
@@ -82,6 +84,7 @@
 }
 
 func TestPluginGeneratesApi(t *testing.T) {
+	t.Parallel()
 	ctx, _ := testJava(t, `
 		java_library {
 			name: "foo",
@@ -99,14 +102,14 @@
 
 	buildOS := ctx.Config().BuildOS.String()
 
-	javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
-	turbine := ctx.ModuleForTests("foo", "android_common").MaybeRule("turbine")
+	javac := ctx.ModuleForTests(t, "foo", "android_common").Rule("javac")
+	turbine := ctx.ModuleForTests(t, "foo", "android_common").MaybeRule("turbine")
 
 	if turbine.Rule != nil {
 		t.Errorf("expected turbine to be disabled")
 	}
 
-	bar := ctx.ModuleForTests("bar", buildOS+"_common").Rule("javac").Output.String()
+	bar := ctx.ModuleForTests(t, "bar", buildOS+"_common").Rule("javac").Output.String()
 
 	if !inList(bar, javac.Implicits.Strings()) {
 		t.Errorf("foo implicits %v does not contain %q", javac.Implicits.Strings(), bar)
diff --git a/java/prebuilt_apis_test.go b/java/prebuilt_apis_test.go
index b6fb2c6..1f095e4 100644
--- a/java/prebuilt_apis_test.go
+++ b/java/prebuilt_apis_test.go
@@ -29,6 +29,7 @@
 }
 
 func TestPrebuiltApis_SystemModulesCreation(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		FixtureWithPrebuiltApis(map[string][]string{
@@ -61,6 +62,7 @@
 }
 
 func TestPrebuiltApis_WithExtensions(t *testing.T) {
+	t.Parallel()
 	runTestWithBaseExtensionLevel := func(v int) (foo_input, bar_input, baz_input string) {
 		result := android.GroupFixturePreparers(
 			prepareForJavaTest,
@@ -76,9 +78,9 @@
 				"2": {"foo", "bar"},
 			}),
 		).RunTest(t)
-		foo_input = result.ModuleForTests("foo.api.public.latest", "").Rule("generator").Implicits[0].String()
-		bar_input = result.ModuleForTests("bar.api.public.latest", "").Rule("generator").Implicits[0].String()
-		baz_input = result.ModuleForTests("baz.api.public.latest", "").Rule("generator").Implicits[0].String()
+		foo_input = result.ModuleForTests(t, "foo.api.public.latest", "").Rule("generator").Implicits[0].String()
+		bar_input = result.ModuleForTests(t, "bar.api.public.latest", "").Rule("generator").Implicits[0].String()
+		baz_input = result.ModuleForTests(t, "baz.api.public.latest", "").Rule("generator").Implicits[0].String()
 		return
 	}
 	// Extension 2 is the latest for both foo and bar, finalized after the base extension version.
@@ -101,6 +103,7 @@
 }
 
 func TestPrebuiltApis_WithIncrementalApi(t *testing.T) {
+	t.Parallel()
 	runTestWithIncrementalApi := func() (foo_input, bar_input, baz_input string) {
 		result := android.GroupFixturePreparers(
 			prepareForJavaTest,
@@ -111,9 +114,9 @@
 				"current": {"foo", "bar"},
 			}),
 		).RunTest(t)
-		foo_input = result.ModuleForTests("foo.api.public.latest", "").Rule("generator").Implicits[0].String()
-		bar_input = result.ModuleForTests("bar.api.public.latest", "").Rule("generator").Implicits[0].String()
-		baz_input = result.ModuleForTests("baz.api.public.latest", "").Rule("generator").Implicits[0].String()
+		foo_input = result.ModuleForTests(t, "foo.api.public.latest", "").Rule("generator").Implicits[0].String()
+		bar_input = result.ModuleForTests(t, "bar.api.public.latest", "").Rule("generator").Implicits[0].String()
+		baz_input = result.ModuleForTests(t, "baz.api.public.latest", "").Rule("generator").Implicits[0].String()
 		return
 	}
 	// 33.1 is the latest for baz, 33.2 is the latest for both foo & bar
diff --git a/java/proto_test.go b/java/proto_test.go
index d1cb714..3fbe3e6 100644
--- a/java/proto_test.go
+++ b/java/proto_test.go
@@ -28,6 +28,7 @@
 `
 
 func TestProtoStream(t *testing.T) {
+	t.Parallel()
 	bp := `
 		java_library {
 			name: "java-stream-protos",
@@ -45,7 +46,7 @@
 		PrepareForIntegrationTestWithJava,
 	).RunTestWithBp(t, protoModules+bp)
 
-	proto0 := ctx.ModuleForTests("java-stream-protos", "android_common").Output("proto/proto0.srcjar")
+	proto0 := ctx.ModuleForTests(t, "java-stream-protos", "android_common").Output("proto/proto0.srcjar")
 
 	if cmd := proto0.RuleParams.Command; !strings.Contains(cmd, "--javastream_out=") {
 		t.Errorf("expected '--javastream_out' in %q", cmd)
diff --git a/java/ravenwood.go b/java/ravenwood.go
index 4c9fdc2..3b6c80b 100644
--- a/java/ravenwood.go
+++ b/java/ravenwood.go
@@ -14,6 +14,8 @@
 package java
 
 import (
+	"strconv"
+
 	"android/soong/android"
 	"android/soong/tradefed"
 
@@ -36,6 +38,14 @@
 var ravenwoodTestResourceApkTag = dependencyTag{name: "ravenwoodtestresapk"}
 var ravenwoodTestInstResourceApkTag = dependencyTag{name: "ravenwoodtest-inst-res-apk"}
 
+var genManifestProperties = pctx.AndroidStaticRule("genManifestProperties",
+	blueprint.RuleParams{
+		Command: "echo targetSdkVersionInt=$targetSdkVersionInt > $out && " +
+			"echo targetSdkVersionRaw=$targetSdkVersionRaw >> $out && " +
+			"echo packageName=$packageName >> $out && " +
+			"echo instPackageName=$instPackageName >> $out",
+	}, "targetSdkVersionInt", "targetSdkVersionRaw", "packageName", "instPackageName")
+
 const ravenwoodUtilsName = "ravenwood-utils"
 const ravenwoodRuntimeName = "ravenwood-runtime"
 
@@ -68,6 +78,17 @@
 	// the ravenwood test can access it. This APK will be loaded as resources of the test
 	// instrumentation app itself.
 	Inst_resource_apk *string
+
+	// Specify the package name of the test target apk.
+	// This will be set to the target Context's package name.
+	// (i.e. Instrumentation.getTargetContext().getPackageName())
+	// If this is omitted, Package_name will be used.
+	Package_name *string
+
+	// Specify the package name of this test module.
+	// This will be set to the test Context's package name.
+	//(i.e. Instrumentation.getContext().getPackageName())
+	Inst_package_name *string
 }
 
 type ravenwoodTest struct {
@@ -89,7 +110,7 @@
 	module.AddProperties(&module.testProperties, &module.ravenwoodTestProperties)
 
 	module.Module.dexpreopter.isTest = true
-	module.Module.linter.properties.Lint.Test = proptools.BoolPtr(true)
+	module.Module.linter.properties.Lint.Test_module_type = proptools.BoolPtr(true)
 
 	module.testProperties.Test_suites = []string{
 		"general-tests",
@@ -164,26 +185,24 @@
 	// All JNI libraries included in the runtime
 	var runtimeJniModuleNames map[string]bool
 
-	if utils := ctx.GetDirectDepsWithTag(ravenwoodUtilsTag)[0]; utils != nil {
-		for _, installFile := range android.OtherModuleProviderOrDefault(
-			ctx, utils, android.InstallFilesProvider).InstallFiles {
-			installDeps = append(installDeps, installFile)
-		}
-		jniDeps, ok := android.OtherModuleProvider(ctx, utils, ravenwoodLibgroupJniDepProvider)
-		if ok {
-			runtimeJniModuleNames = jniDeps.names
-		}
+	utils := ctx.GetDirectDepsProxyWithTag(ravenwoodUtilsTag)[0]
+	for _, installFile := range android.OtherModuleProviderOrDefault(
+		ctx, utils, android.InstallFilesProvider).InstallFiles {
+		installDeps = append(installDeps, installFile)
+	}
+	jniDeps, ok := android.OtherModuleProvider(ctx, utils, ravenwoodLibgroupJniDepProvider)
+	if ok {
+		runtimeJniModuleNames = jniDeps.names
 	}
 
-	if runtime := ctx.GetDirectDepsWithTag(ravenwoodRuntimeTag)[0]; runtime != nil {
-		for _, installFile := range android.OtherModuleProviderOrDefault(
-			ctx, runtime, android.InstallFilesProvider).InstallFiles {
-			installDeps = append(installDeps, installFile)
-		}
-		jniDeps, ok := android.OtherModuleProvider(ctx, runtime, ravenwoodLibgroupJniDepProvider)
-		if ok {
-			runtimeJniModuleNames = jniDeps.names
-		}
+	runtime := ctx.GetDirectDepsProxyWithTag(ravenwoodRuntimeTag)[0]
+	for _, installFile := range android.OtherModuleProviderOrDefault(
+		ctx, runtime, android.InstallFilesProvider).InstallFiles {
+		installDeps = append(installDeps, installFile)
+	}
+	jniDeps, ok = android.OtherModuleProvider(ctx, runtime, ravenwoodLibgroupJniDepProvider)
+	if ok {
+		runtimeJniModuleNames = jniDeps.names
 	}
 
 	// Also remember what JNI libs are in the runtime.
@@ -207,7 +226,7 @@
 	resApkInstallPath := installPath.Join(ctx, "ravenwood-res-apks")
 
 	copyResApk := func(tag blueprint.DependencyTag, toFileName string) {
-		if resApk := ctx.GetDirectDepsWithTag(tag); len(resApk) > 0 {
+		if resApk := ctx.GetDirectDepsProxyWithTag(tag); len(resApk) > 0 {
 			installFile := android.OutputFileForModule(ctx, resApk[0], "")
 			installResApk := ctx.InstallFile(resApkInstallPath, toFileName, installFile)
 			installDeps = append(installDeps, installResApk)
@@ -216,8 +235,38 @@
 	copyResApk(ravenwoodTestResourceApkTag, "ravenwood-res.apk")
 	copyResApk(ravenwoodTestInstResourceApkTag, "ravenwood-inst-res.apk")
 
+	// Generate manifest properties
+	propertiesOutputPath := android.PathForModuleGen(ctx, "ravenwood.properties")
+
+	targetSdkVersion := proptools.StringDefault(r.deviceProperties.Target_sdk_version, "")
+	targetSdkVersionInt := r.TargetSdkVersion(ctx).FinalOrFutureInt() // FinalOrFutureInt may be 10000.
+	packageName := proptools.StringDefault(r.ravenwoodTestProperties.Package_name, "")
+	instPackageName := proptools.StringDefault(r.ravenwoodTestProperties.Inst_package_name, "")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        genManifestProperties,
+		Description: "genManifestProperties",
+		Output:      propertiesOutputPath,
+		Args: map[string]string{
+			"targetSdkVersionInt": strconv.Itoa(targetSdkVersionInt),
+			"targetSdkVersionRaw": targetSdkVersion,
+			"packageName":         packageName,
+			"instPackageName":     instPackageName,
+		},
+	})
+	installProps := ctx.InstallFile(installPath, "ravenwood.properties", propertiesOutputPath)
+	installDeps = append(installDeps, installProps)
+
 	// Install our JAR with all dependencies
 	ctx.InstallFile(installPath, ctx.ModuleName()+".jar", r.outputFile, installDeps...)
+
+	moduleInfoJSON := ctx.ModuleInfoJSON()
+	if _, ok := r.testConfig.(android.WritablePath); ok {
+		moduleInfoJSON.AutoTestConfig = []string{"true"}
+	}
+	if r.testConfig != nil {
+		moduleInfoJSON.TestConfig = append(moduleInfoJSON.TestConfig, r.testConfig.String())
+	}
+	moduleInfoJSON.CompatibilitySuites = []string{"general-tests", "ravenwood-tests"}
 }
 
 func (r *ravenwoodTest) AndroidMkEntries() []android.AndroidMkEntries {
@@ -303,7 +352,7 @@
 	// Install our runtime into expected location for packaging
 	installPath := android.PathForModuleInstall(ctx, r.BaseModuleName())
 	for _, lib := range r.ravenwoodLibgroupProperties.Libs {
-		libModule := ctx.GetDirectDepWithTag(lib, ravenwoodLibContentTag)
+		libModule := ctx.GetDirectDepProxyWithTag(lib, ravenwoodLibContentTag)
 		if libModule == nil {
 			if ctx.Config().AllowMissingDependencies() {
 				ctx.AddMissingDependencies([]string{lib})
diff --git a/java/ravenwood_test.go b/java/ravenwood_test.go
index 753a118..24a02bb 100644
--- a/java/ravenwood_test.go
+++ b/java/ravenwood_test.go
@@ -100,9 +100,10 @@
 	`),
 )
 
-var installPathPrefix = "out/soong/host/linux-x86/testcases"
+var installPathPrefix = "out/host/linux-x86/testcases"
 
 func TestRavenwoodRuntime(t *testing.T) {
+	t.Parallel()
 	if runtime.GOOS != "linux" {
 		t.Skip("requires linux")
 	}
@@ -120,7 +121,7 @@
 	CheckModuleHasDependency(t, ctx.TestContext, "ravenwood-utils", "android_common", "framework-rules.ravenwood")
 
 	// Verify that we've emitted artifacts in expected location
-	runtime := ctx.ModuleForTests("ravenwood-runtime", "android_common")
+	runtime := ctx.ModuleForTests(t, "ravenwood-runtime", "android_common")
 	runtime.Output(installPathPrefix + "/ravenwood-runtime/framework-minus-apex.ravenwood.jar")
 	runtime.Output(installPathPrefix + "/ravenwood-runtime/framework-services.ravenwood.jar")
 	runtime.Output(installPathPrefix + "/ravenwood-runtime/lib64/ravenwood-runtime-jni1.so")
@@ -128,11 +129,12 @@
 	runtime.Output(installPathPrefix + "/ravenwood-runtime/lib64/ravenwood-runtime-jni3.so")
 	runtime.Output(installPathPrefix + "/ravenwood-runtime/ravenwood-data/app1.apk")
 	runtime.Output(installPathPrefix + "/ravenwood-runtime/fonts/Font.ttf")
-	utils := ctx.ModuleForTests("ravenwood-utils", "android_common")
+	utils := ctx.ModuleForTests(t, "ravenwood-utils", "android_common")
 	utils.Output(installPathPrefix + "/ravenwood-utils/framework-rules.ravenwood.jar")
 }
 
 func TestRavenwoodTest(t *testing.T) {
+	t.Parallel()
 	if runtime.GOOS != "linux" {
 		t.Skip("requires linux")
 	}
@@ -162,17 +164,27 @@
 			srcs: ["jni.cpp"],
 			stem: "libpink",
 		}
+		java_defaults {
+			name: "ravenwood-test-defaults",
+			jni_libs: ["jni-lib2"],
+		}
 		android_ravenwood_test {
 			name: "ravenwood-test",
 			srcs: ["Test.java"],
+			defaults: ["ravenwood-test-defaults"],
 			jni_libs: [
 				"jni-lib1",
-				"jni-lib2",
 				"ravenwood-runtime-jni2",
 			],
 			resource_apk: "app2",
 			inst_resource_apk: "app3",
 			sdk_version: "test_current",
+			target_sdk_version: "34",
+			package_name: "a.b.c",
+			inst_package_name: "x.y.z",
+		}
+		android_ravenwood_test {
+			name: "ravenwood-test-empty",
 		}
 	`)
 
@@ -181,7 +193,7 @@
 	CheckModuleHasDependency(t, ctx.TestContext, "ravenwood-test", "android_common", "ravenwood-utils")
 	CheckModuleHasDependency(t, ctx.TestContext, "ravenwood-test", "android_common", "jni-lib")
 
-	module := ctx.ModuleForTests("ravenwood-test", "android_common")
+	module := ctx.ModuleForTests(t, "ravenwood-test", "android_common")
 	classpath := module.Rule("javac").Args["classpath"]
 
 	// Verify that we're linking against test_current
@@ -195,12 +207,16 @@
 	// Verify that we've emitted test artifacts in expected location
 	outputJar := module.Output(installPathPrefix + "/ravenwood-test/ravenwood-test.jar")
 	module.Output(installPathPrefix + "/ravenwood-test/ravenwood-test.config")
+	module.Output(installPathPrefix + "/ravenwood-test/ravenwood.properties")
 	module.Output(installPathPrefix + "/ravenwood-test/lib64/jni-lib1.so")
 	module.Output(installPathPrefix + "/ravenwood-test/lib64/libblue.so")
 	module.Output(installPathPrefix + "/ravenwood-test/lib64/libpink.so")
 	module.Output(installPathPrefix + "/ravenwood-test/ravenwood-res-apks/ravenwood-res.apk")
 	module.Output(installPathPrefix + "/ravenwood-test/ravenwood-res-apks/ravenwood-inst-res.apk")
 
+	module = ctx.ModuleForTests(t, "ravenwood-test-empty", "android_common")
+	module.Output(installPathPrefix + "/ravenwood-test-empty/ravenwood.properties")
+
 	// ravenwood-runtime*.so are included in the runtime, so it shouldn't be emitted.
 	for _, o := range module.AllOutputs() {
 		android.AssertStringDoesNotContain(t, "runtime libs shouldn't be included", o, "/ravenwood-test/lib64/ravenwood-runtime")
diff --git a/java/robolectric.go b/java/robolectric.go
index 30c7203..43e17f9 100644
--- a/java/robolectric.go
+++ b/java/robolectric.go
@@ -19,9 +19,9 @@
 
 	"android/soong/android"
 	"android/soong/java/config"
-	"android/soong/testing"
 	"android/soong/tradefed"
 
+	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
 
@@ -29,6 +29,23 @@
 	RegisterRobolectricBuildComponents(android.InitRegistrationContext)
 }
 
+type RobolectricRuntimesInfo struct {
+	Runtimes []android.InstallPath
+}
+
+var RobolectricRuntimesInfoProvider = blueprint.NewProvider[RobolectricRuntimesInfo]()
+
+type roboRuntimeOnlyDependencyTag struct {
+	blueprint.BaseDependencyTag
+}
+
+var roboRuntimeOnlyDepTag roboRuntimeOnlyDependencyTag
+
+// Mark this tag so dependencies that use it are excluded from visibility enforcement.
+func (t roboRuntimeOnlyDependencyTag) ExcludeFromVisibilityEnforcement() {}
+
+var _ android.ExcludeFromVisibilityEnforcementTag = roboRuntimeOnlyDepTag
+
 func RegisterRobolectricBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("android_robolectric_test", RobolectricTestFactory)
 	ctx.RegisterModuleType("android_robolectric_runtimes", robolectricRuntimesFactory)
@@ -42,12 +59,12 @@
 }
 
 const robolectricCurrentLib = "Robolectric_all-target"
+const clearcutJunitLib = "ClearcutJunitListenerAar"
 const robolectricPrebuiltLibPattern = "platform-robolectric-%s-prebuilt"
 
 var (
 	roboCoverageLibsTag = dependencyTag{name: "roboCoverageLibs"}
 	roboRuntimesTag     = dependencyTag{name: "roboRuntimes"}
-	roboRuntimeOnlyTag  = dependencyTag{name: "roboRuntimeOnlyTag"}
 )
 
 type robolectricProperties struct {
@@ -65,10 +82,6 @@
 		Shards *int64
 	}
 
-	// The version number of a robolectric prebuilt to use from prebuilts/misc/common/robolectric
-	// instead of the one built from source in external/robolectric-shadows.
-	Robolectric_prebuilt_version *string
-
 	// Use /external/robolectric rather than /external/robolectric-shadows as the version of robolectric
 	// to use.  /external/robolectric closely tracks github's master, and will fully replace /external/robolectric-shadows
 	Upstream *bool
@@ -107,21 +120,12 @@
 		ctx.PropertyErrorf("instrumentation_for", "missing required instrumented module")
 	}
 
-	if v := String(r.robolectricProperties.Robolectric_prebuilt_version); v != "" {
-		ctx.AddVariationDependencies(nil, staticLibTag, fmt.Sprintf(robolectricPrebuiltLibPattern, v))
-	} else if !proptools.BoolDefault(r.robolectricProperties.Strict_mode, true) {
-		if proptools.Bool(r.robolectricProperties.Upstream) {
-			ctx.AddVariationDependencies(nil, staticLibTag, robolectricCurrentLib+"_upstream")
-		} else {
-			ctx.AddVariationDependencies(nil, staticLibTag, robolectricCurrentLib)
-		}
-	}
+	ctx.AddVariationDependencies(nil, roboRuntimeOnlyDepTag, clearcutJunitLib)
 
 	if proptools.BoolDefault(r.robolectricProperties.Strict_mode, true) {
-		ctx.AddVariationDependencies(nil, roboRuntimeOnlyTag, robolectricCurrentLib+"_upstream")
+		ctx.AddVariationDependencies(nil, roboRuntimeOnlyDepTag, robolectricCurrentLib)
 	} else {
-		// opting out from strict mode, robolectric_non_strict_mode_permission lib should be added
-		ctx.AddVariationDependencies(nil, staticLibTag, "robolectric_non_strict_mode_permission")
+		ctx.AddVariationDependencies(nil, staticLibTag, robolectricCurrentLib)
 	}
 
 	ctx.AddVariationDependencies(nil, staticLibTag, robolectricDefaultLibs...)
@@ -140,36 +144,53 @@
 	r.forceOSType = ctx.Config().BuildOS
 	r.forceArchType = ctx.Config().BuildArch
 
+	var extraTestRunnerOptions []tradefed.Option
+	extraTestRunnerOptions = append(extraTestRunnerOptions, tradefed.Option{Name: "java-flags", Value: "-Drobolectric=true"})
+	if proptools.BoolDefault(r.robolectricProperties.Strict_mode, true) {
+		extraTestRunnerOptions = append(extraTestRunnerOptions, tradefed.Option{Name: "java-flags", Value: "-Drobolectric.strict.mode=true"})
+	}
+
+	var extraOptions []tradefed.Option
+	var javaHome = ctx.Config().Getenv("ANDROID_JAVA_HOME")
+	extraOptions = append(extraOptions, tradefed.Option{Name: "java-folder", Value: javaHome})
+
 	r.testConfig = tradefed.AutoGenTestConfig(ctx, tradefed.AutoGenTestConfigOptions{
-		TestConfigProp:         r.testProperties.Test_config,
-		TestConfigTemplateProp: r.testProperties.Test_config_template,
-		TestSuites:             r.testProperties.Test_suites,
-		AutoGenConfig:          r.testProperties.Auto_gen_config,
-		DeviceTemplate:         "${RobolectricTestConfigTemplate}",
-		HostTemplate:           "${RobolectricTestConfigTemplate}",
+		TestConfigProp:          r.testProperties.Test_config,
+		TestConfigTemplateProp:  r.testProperties.Test_config_template,
+		TestSuites:              r.testProperties.Test_suites,
+		OptionsForAutogenerated: extraOptions,
+		TestRunnerOptions:       extraTestRunnerOptions,
+		AutoGenConfig:           r.testProperties.Auto_gen_config,
+		DeviceTemplate:          "${RobolectricTestConfigTemplate}",
+		HostTemplate:            "${RobolectricTestConfigTemplate}",
 	})
 	r.data = android.PathsForModuleSrc(ctx, r.testProperties.Data)
+	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)...)
 
 	var ok bool
-	var instrumentedApp *AndroidApp
+	var instrumentedApp *JavaInfo
+	var appInfo *AppInfo
 
 	// TODO: this inserts paths to built files into the test, it should really be inserting the contents.
-	instrumented := ctx.GetDirectDepsWithTag(instrumentationForTag)
+	instrumented := ctx.GetDirectDepsProxyWithTag(instrumentationForTag)
 
 	if len(instrumented) == 1 {
-		instrumentedApp, ok = instrumented[0].(*AndroidApp)
+		appInfo, ok = android.OtherModuleProvider(ctx, instrumented[0], AppInfoProvider)
 		if !ok {
 			ctx.PropertyErrorf("instrumentation_for", "dependency must be an android_app")
 		}
+		instrumentedApp = android.OtherModuleProviderOrDefault(ctx, instrumented[0], JavaInfoProvider)
 	} else if !ctx.Config().AllowMissingDependencies() {
 		panic(fmt.Errorf("expected exactly 1 instrumented dependency, got %d", len(instrumented)))
 	}
 
 	var resourceApk android.Path
 	var manifest android.Path
-	if instrumentedApp != nil {
-		manifest = instrumentedApp.mergedManifestFile
-		resourceApk = instrumentedApp.outputFile
+	if appInfo != nil {
+		manifest = appInfo.MergedManifestFile
+		resourceApk = instrumentedApp.OutputFile
 	}
 
 	roboTestConfigJar := android.PathForModuleOut(ctx, "robolectric_samedir", "samedir_config.jar")
@@ -177,7 +198,7 @@
 
 	extraCombinedJars := android.Paths{roboTestConfigJar}
 
-	handleLibDeps := func(dep android.Module) {
+	handleLibDeps := func(dep android.ModuleProxy) {
 		if !android.InList(ctx.OtherModuleName(dep), config.FrameworkLibraries) {
 			if m, ok := android.OtherModuleProvider(ctx, dep, JavaInfoProvider); ok {
 				extraCombinedJars = append(extraCombinedJars, m.ImplementationAndResourcesJars...)
@@ -185,29 +206,34 @@
 		}
 	}
 
-	for _, dep := range ctx.GetDirectDepsWithTag(libTag) {
+	for _, dep := range ctx.GetDirectDepsProxyWithTag(libTag) {
 		handleLibDeps(dep)
 	}
-	for _, dep := range ctx.GetDirectDepsWithTag(sdkLibTag) {
+	for _, dep := range ctx.GetDirectDepsProxyWithTag(sdkLibTag) {
 		handleLibDeps(dep)
 	}
 	// handle the runtimeOnly tag for strict_mode
-	for _, dep := range ctx.GetDirectDepsWithTag(roboRuntimeOnlyTag) {
+	for _, dep := range ctx.GetDirectDepsProxyWithTag(roboRuntimeOnlyDepTag) {
 		handleLibDeps(dep)
 	}
 
-	if instrumentedApp != nil {
-		extraCombinedJars = append(extraCombinedJars, instrumentedApp.implementationAndResourcesJar)
+	if appInfo != nil {
+		extraCombinedJars = append(extraCombinedJars, instrumentedApp.ImplementationAndResourcesJars...)
 	}
 
 	r.stem = proptools.StringDefault(r.overridableProperties.Stem, ctx.ModuleName())
 	r.classLoaderContexts = r.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
 	r.dexpreopter.disableDexpreopt()
-	r.compile(ctx, nil, nil, nil, extraCombinedJars)
+	javaInfo := r.compile(ctx, nil, nil, nil, extraCombinedJars)
 
 	installPath := android.PathForModuleInstall(ctx, r.BaseModuleName())
 	var installDeps android.InstallPaths
 
+	for _, data := range r.data {
+		installedData := ctx.InstallFile(installPath, data.Rel(), data)
+		installDeps = append(installDeps, installedData)
+	}
+
 	if manifest != nil {
 		r.data = append(r.data, manifest)
 		installedManifest := ctx.InstallFile(installPath, ctx.ModuleName()+"-AndroidManifest.xml", manifest)
@@ -220,19 +246,14 @@
 		installDeps = append(installDeps, installedResourceApk)
 	}
 
-	runtimes := ctx.GetDirectDepWithTag("robolectric-android-all-prebuilts", roboRuntimesTag)
-	for _, runtime := range runtimes.(*robolectricRuntimes).runtimes {
+	runtimes := ctx.GetDirectDepProxyWithTag("robolectric-android-all-prebuilts", roboRuntimesTag)
+	for _, runtime := range android.OtherModuleProviderOrDefault(ctx, runtimes, RobolectricRuntimesInfoProvider).Runtimes {
 		installDeps = append(installDeps, runtime)
 	}
 
 	installedConfig := ctx.InstallFile(installPath, ctx.ModuleName()+".config", r.testConfig)
 	installDeps = append(installDeps, installedConfig)
 
-	for _, data := range android.PathsForModuleSrc(ctx, r.testProperties.Data) {
-		installedData := ctx.InstallFile(installPath, data.Rel(), data)
-		installDeps = append(installDeps, installedData)
-	}
-
 	soInstallPath := installPath.Join(ctx, getLibPath(r.forceArchType))
 	for _, jniLib := range collectTransitiveJniDeps(ctx) {
 		installJni := ctx.InstallFile(soInstallPath, jniLib.path.Base(), jniLib.path)
@@ -240,7 +261,24 @@
 	}
 
 	r.installFile = ctx.InstallFile(installPath, ctx.ModuleName()+".jar", r.outputFile, installDeps...)
-	android.SetProvider(ctx, testing.TestModuleProviderKey, testing.TestModuleProviderData{})
+
+	if javaInfo != nil {
+		setExtraJavaInfo(ctx, r, javaInfo)
+		android.SetProvider(ctx, JavaInfoProvider, javaInfo)
+	}
+
+	moduleInfoJSON := r.javaLibraryModuleInfoJSON(ctx)
+	if _, ok := r.testConfig.(android.WritablePath); ok {
+		moduleInfoJSON.AutoTestConfig = []string{"true"}
+	}
+	if r.testConfig != nil {
+		moduleInfoJSON.TestConfig = append(moduleInfoJSON.TestConfig, r.testConfig.String())
+	}
+	if len(r.testProperties.Test_suites) > 0 {
+		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, r.testProperties.Test_suites...)
+	} else {
+		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "null-suite")
+	}
 }
 
 func generateSameDirRoboTestConfigJar(ctx android.ModuleContext, outputFile android.ModuleOutPath) {
@@ -291,7 +329,7 @@
 		&module.testProperties)
 
 	module.Module.dexpreopter.isTest = true
-	module.Module.linter.properties.Lint.Test = proptools.BoolPtr(true)
+	module.Module.linter.properties.Lint.Test_module_type = proptools.BoolPtr(true)
 
 	module.testProperties.Test_suites = []string{"robolectric-tests"}
 
@@ -356,7 +394,7 @@
 	}
 
 	if !ctx.Config().AlwaysUsePrebuiltSdks() && r.props.Lib != nil {
-		runtimeFromSourceModule := ctx.GetDirectDepWithTag(String(r.props.Lib), libTag)
+		runtimeFromSourceModule := ctx.GetDirectDepProxyWithTag(String(r.props.Lib), libTag)
 		if runtimeFromSourceModule == nil {
 			if ctx.Config().AllowMissingDependencies() {
 				ctx.AddMissingDependencies([]string{String(r.props.Lib)})
@@ -374,6 +412,10 @@
 		installedRuntime := ctx.InstallFile(androidAllDir, runtimeName, runtimeFromSourceJar)
 		r.runtimes = append(r.runtimes, installedRuntime)
 	}
+
+	android.SetProvider(ctx, RobolectricRuntimesInfoProvider, RobolectricRuntimesInfo{
+		Runtimes: r.runtimes,
+	})
 }
 
 func (r *robolectricRuntimes) InstallInTestcases() bool { return true }
diff --git a/java/robolectric_test.go b/java/robolectric_test.go
index 4775bac..4bf224b 100644
--- a/java/robolectric_test.go
+++ b/java/robolectric_test.go
@@ -32,6 +32,11 @@
 	}
 
 	java_library {
+		name: "Robolectric_all-target",
+		srcs: ["Robo.java"]
+	}
+
+	java_library {
 		name: "mockito-robolectric-prebuilt",
 		srcs: ["Mockito.java"]
 	}
@@ -44,6 +49,12 @@
 	java_library {
 		name: "junitxml",
 		srcs: ["JUnitXml.java"]
+
+	}
+
+	java_library {
+		name: "ClearcutJunitListenerAar",
+		srcs: ["Runtime.java"]
 	}
 
 	java_library_host {
@@ -60,6 +71,7 @@
 )
 
 func TestRobolectricJniTest(t *testing.T) {
+	t.Parallel()
 	if runtime.GOOS != "linux" {
 		t.Skip("requires linux")
 	}
@@ -93,6 +105,6 @@
 	CheckModuleHasDependency(t, ctx.TestContext, "robo-test", "android_common", "jni-lib1")
 
 	// Check that the .so files make it into the output.
-	module := ctx.ModuleForTests("robo-test", "android_common")
+	module := ctx.ModuleForTests(t, "robo-test", "android_common")
 	module.Output(installPathPrefix + "/robo-test/lib64/jni-lib1.so")
 }
diff --git a/java/rro.go b/java/rro.go
index 8bb9be2..d9f4ff7 100644
--- a/java/rro.go
+++ b/java/rro.go
@@ -20,6 +20,7 @@
 import (
 	"android/soong/android"
 
+	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
 
@@ -29,6 +30,7 @@
 
 func RegisterRuntimeResourceOverlayBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("runtime_resource_overlay", RuntimeResourceOverlayFactory)
+	ctx.RegisterModuleType("autogen_runtime_resource_overlay", AutogenRuntimeResourceOverlayFactory)
 	ctx.RegisterModuleType("override_runtime_resource_overlay", OverrideRuntimeResourceOverlayModuleFactory)
 }
 
@@ -50,7 +52,7 @@
 type RuntimeResourceOverlayProperties struct {
 	// the name of a certificate in the default certificate directory or an android_app_certificate
 	// module name in the form ":module".
-	Certificate *string
+	Certificate proptools.Configurable[string] `android:"replace_instead_of_append"`
 
 	// Name of the signing certificate lineage file.
 	Lineage *string
@@ -119,7 +121,7 @@
 		r.aapt.deps(ctx, sdkDep)
 	}
 
-	cert := android.SrcIsModule(String(r.properties.Certificate))
+	cert := android.SrcIsModule(r.properties.Certificate.GetOrDefault(ctx, ""))
 	if cert != "" {
 		ctx.AddDependency(ctx.Module(), certificateTag, cert)
 	}
@@ -137,6 +139,25 @@
 	r.aapt.hasNoCode = true
 	// Do not remove resources without default values nor dedupe resource configurations with the same value
 	aaptLinkFlags := []string{"--no-resource-deduping", "--no-resource-removal"}
+
+	// Add TARGET_AAPT_CHARACTERISTICS values to AAPT link flags if they exist and --product flags were not provided.
+	hasProduct := android.PrefixInList(r.aaptProperties.Aaptflags, "--product")
+	if !hasProduct && len(ctx.Config().ProductAAPTCharacteristics()) > 0 {
+		aaptLinkFlags = append(aaptLinkFlags, "--product", ctx.Config().ProductAAPTCharacteristics())
+	}
+
+	if !Bool(r.aaptProperties.Aapt_include_all_resources) {
+		// Product AAPT config
+		for _, aaptConfig := range ctx.Config().ProductAAPTConfig() {
+			aaptLinkFlags = append(aaptLinkFlags, "-c", aaptConfig)
+		}
+
+		// Product AAPT preferred config
+		if len(ctx.Config().ProductAAPTPreferredConfig()) > 0 {
+			aaptLinkFlags = append(aaptLinkFlags, "--preferred-density", ctx.Config().ProductAAPTPreferredConfig())
+		}
+	}
+
 	// Allow the override of "package name" and "overlay target package name"
 	manifestPackageName, overridden := ctx.DeviceConfig().OverrideManifestPackageNameFor(ctx.ModuleName())
 	if overridden || r.overridableProperties.Package_name != nil {
@@ -166,7 +187,7 @@
 
 	// Sign the built package
 	_, _, certificates := collectAppDeps(ctx, r, false, false)
-	r.certificate, certificates = processMainCert(r.ModuleBase, String(r.properties.Certificate), certificates, ctx)
+	r.certificate, certificates = processMainCert(r.ModuleBase, r.properties.Certificate.GetOrDefault(ctx, ""), certificates, ctx)
 	signed := android.PathForModuleOut(ctx, "signed", r.Name()+".apk")
 	var lineageFile android.Path
 	if lineage := String(r.properties.Lineage); lineage != "" {
@@ -185,6 +206,8 @@
 	android.SetProvider(ctx, FlagsPackagesProvider, FlagsPackages{
 		AconfigTextFiles: aconfigTextFilePaths,
 	})
+
+	buildComplianceMetadata(ctx)
 }
 
 func (r *RuntimeResourceOverlay) SdkVersion(ctx android.EarlyModuleContext) android.SdkSpec {
@@ -269,3 +292,147 @@
 	android.InitOverrideModule(m)
 	return m
 }
+
+var (
+	generateOverlayManifestFile = pctx.AndroidStaticRule("generate_overlay_manifest",
+		blueprint.RuleParams{
+			Command: "build/make/tools/generate-enforce-rro-android-manifest.py " +
+				"--package-info $in " +
+				"--partition ${partition} " +
+				"--priority ${priority} -o $out",
+			CommandDeps: []string{"build/make/tools/generate-enforce-rro-android-manifest.py"},
+		}, "partition", "priority",
+	)
+)
+
+type AutogenRuntimeResourceOverlay struct {
+	android.ModuleBase
+	aapt
+
+	properties AutogenRuntimeResourceOverlayProperties
+
+	certificate Certificate
+	outputFile  android.Path
+}
+
+type AutogenRuntimeResourceOverlayProperties struct {
+	Base        *string
+	Sdk_version *string
+	Manifest    *string `android:"path"`
+}
+
+func AutogenRuntimeResourceOverlayFactory() android.Module {
+	m := &AutogenRuntimeResourceOverlay{}
+	m.AddProperties(&m.properties)
+	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
+
+	return m
+}
+
+type rroDependencyTag struct {
+	blueprint.DependencyTag
+}
+
+// Autogenerated RROs should always depend on the source android_app that created it.
+func (tag rroDependencyTag) ReplaceSourceWithPrebuilt() bool {
+	return false
+}
+
+var rroDepTag = rroDependencyTag{}
+
+func (a *AutogenRuntimeResourceOverlay) DepsMutator(ctx android.BottomUpMutatorContext) {
+	sdkDep := decodeSdkDep(ctx, android.SdkContext(a))
+	if sdkDep.hasFrameworkLibs() {
+		a.aapt.deps(ctx, sdkDep)
+	}
+	ctx.AddDependency(ctx.Module(), rroDepTag, proptools.String(a.properties.Base))
+}
+
+func (a *AutogenRuntimeResourceOverlay) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	if !a.Enabled(ctx) {
+		return
+	}
+	var rroDirs android.Paths
+	// Get rro dirs of the base app
+	ctx.VisitDirectDepsWithTag(rroDepTag, func(m android.Module) {
+		aarDep, _ := m.(AndroidLibraryDependency)
+		if ctx.InstallInProduct() {
+			rroDirs = filterRRO(aarDep.RRODirsDepSet(), product)
+		} else {
+			rroDirs = filterRRO(aarDep.RRODirsDepSet(), device)
+		}
+	})
+
+	if len(rroDirs) == 0 {
+		return
+	}
+
+	// Generate a manifest file
+	genManifest := android.PathForModuleGen(ctx, "AndroidManifest.xml")
+	partition := "vendor"
+	priority := "0"
+	if ctx.InstallInProduct() {
+		partition = "product"
+		priority = "1"
+	}
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   generateOverlayManifestFile,
+		Input:  android.PathForModuleSrc(ctx, proptools.String(a.properties.Manifest)),
+		Output: genManifest,
+		Args: map[string]string{
+			"partition": partition,
+			"priority":  priority,
+		},
+	})
+
+	// Compile and link resources into package-res.apk
+	a.aapt.hasNoCode = true
+	aaptLinkFlags := []string{"--auto-add-overlay", "--keep-raw-values", "--no-resource-deduping", "--no-resource-removal"}
+
+	a.aapt.buildActions(ctx,
+		aaptBuildActionOptions{
+			sdkContext:      a,
+			extraLinkFlags:  aaptLinkFlags,
+			rroDirs:         &rroDirs,
+			manifestForAapt: genManifest,
+		},
+	)
+
+	if a.exportPackage == nil {
+		return
+	}
+	// Sign the built package
+	var certificates []Certificate
+	a.certificate, certificates = processMainCert(a.ModuleBase, "", nil, ctx)
+	signed := android.PathForModuleOut(ctx, "signed", a.Name()+".apk")
+	SignAppPackage(ctx, signed, a.exportPackage, certificates, nil, nil, "")
+	a.outputFile = signed
+
+	// Install the signed apk
+	installDir := android.PathForModuleInstall(ctx, "overlay")
+	ctx.InstallFile(installDir, signed.Base(), signed)
+}
+
+func (a *AutogenRuntimeResourceOverlay) SdkVersion(ctx android.EarlyModuleContext) android.SdkSpec {
+	return android.SdkSpecFrom(ctx, String(a.properties.Sdk_version))
+}
+
+func (a *AutogenRuntimeResourceOverlay) SystemModules() string {
+	return ""
+}
+
+func (a *AutogenRuntimeResourceOverlay) MinSdkVersion(ctx android.EarlyModuleContext) android.ApiLevel {
+	return a.SdkVersion(ctx).ApiLevel
+}
+
+func (r *AutogenRuntimeResourceOverlay) ReplaceMaxSdkVersionPlaceholder(ctx android.EarlyModuleContext) android.ApiLevel {
+	return android.SdkSpecPrivate.ApiLevel
+}
+
+func (a *AutogenRuntimeResourceOverlay) TargetSdkVersion(ctx android.EarlyModuleContext) android.ApiLevel {
+	return a.SdkVersion(ctx).ApiLevel
+}
+
+func (a *AutogenRuntimeResourceOverlay) InstallInProduct() bool {
+	return a.ProductSpecific()
+}
diff --git a/java/rro_test.go b/java/rro_test.go
index 4d79130..0ccc8e7 100644
--- a/java/rro_test.go
+++ b/java/rro_test.go
@@ -24,6 +24,7 @@
 )
 
 func TestRuntimeResourceOverlay(t *testing.T) {
+	t.Parallel()
 	fs := android.MockFS{
 		"baz/res/res/values/strings.xml": nil,
 		"bar/res/res/values/strings.xml": nil,
@@ -66,7 +67,7 @@
 		fs.AddToFixture(),
 	).RunTestWithBp(t, bp)
 
-	m := result.ModuleForTests("foo", "android_common")
+	m := result.ModuleForTests(t, "foo", "android_common")
 
 	// Check AAPT2 link flags.
 	aapt2Flags := m.Output("package-res.apk").Args["flags"]
@@ -115,7 +116,7 @@
 	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_MODULE_PATH", result.Config, expectedPath, path)
 
 	// A themed module has a different device location
-	m = result.ModuleForTests("foo_themed", "android_common")
+	m = result.ModuleForTests(t, "foo_themed", "android_common")
 	androidMkEntries = android.AndroidMkEntriesForTest(t, result.TestContext, m.Module())[0]
 	path = androidMkEntries.EntryMap["LOCAL_MODULE_PATH"]
 	expectedPath = []string{shared.JoinPath("out/target/product/test_device/product/overlay/faza")}
@@ -129,6 +130,7 @@
 }
 
 func TestRuntimeResourceOverlay_JavaDefaults(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		PrepareForTestWithJavaDefaultModules,
 		android.FixtureModifyConfig(android.SetKatiEnabledForTests),
@@ -153,7 +155,7 @@
 	//
 	// RRO module with defaults
 	//
-	m := result.ModuleForTests("foo_with_defaults", "android_common")
+	m := result.ModuleForTests(t, "foo_with_defaults", "android_common")
 
 	// Check AAPT2 link flags.
 	aapt2Flags := strings.Split(m.Output("package-res.apk").Args["flags"], " ")
@@ -171,7 +173,7 @@
 	//
 	// RRO module without defaults
 	//
-	m = result.ModuleForTests("foo_barebones", "android_common")
+	m = result.ModuleForTests(t, "foo_barebones", "android_common")
 
 	// Check AAPT2 link flags.
 	aapt2Flags = strings.Split(m.Output("package-res.apk").Args["flags"], " ")
@@ -216,7 +218,7 @@
 	}{
 		{
 			variantName:       "android_common",
-			apkPath:           "out/soong/target/product/test_device/product/overlay/foo_overlay.apk",
+			apkPath:           "out/target/product/test_device/product/overlay/foo_overlay.apk",
 			overrides:         nil,
 			targetVariant:     "android_common",
 			packageFlag:       "",
@@ -224,7 +226,7 @@
 		},
 		{
 			variantName:       "android_common_bar_overlay",
-			apkPath:           "out/soong/target/product/test_device/product/overlay/bar_overlay.apk",
+			apkPath:           "out/target/product/test_device/product/overlay/bar_overlay.apk",
 			overrides:         []string{"foo_overlay"},
 			targetVariant:     "android_common_bar",
 			packageFlag:       "com.android.bar.overlay",
@@ -233,7 +235,7 @@
 		},
 	}
 	for _, expected := range expectedVariants {
-		variant := ctx.ModuleForTests("foo_overlay", expected.variantName)
+		variant := ctx.ModuleForTests(t, "foo_overlay", expected.variantName)
 
 		// Check the final apk name
 		variant.Output(expected.apkPath)
@@ -255,103 +257,6 @@
 	}
 }
 
-func TestEnforceRRO_propagatesToDependencies(t *testing.T) {
-	testCases := []struct {
-		name              string
-		enforceRROTargets []string
-		rroDirs           map[string][]string
-	}{
-		{
-			name:              "no RRO",
-			enforceRROTargets: nil,
-			rroDirs: map[string][]string{
-				"foo": nil,
-				"bar": nil,
-			},
-		},
-		{
-			name:              "enforce RRO on all",
-			enforceRROTargets: []string{"*"},
-			rroDirs: map[string][]string{
-				"foo": {"product/vendor/blah/overlay/lib2/res"},
-				"bar": {"product/vendor/blah/overlay/lib2/res"},
-			},
-		},
-		{
-			name:              "enforce RRO on foo",
-			enforceRROTargets: []string{"foo"},
-			rroDirs: map[string][]string{
-				"foo": {"product/vendor/blah/overlay/lib2/res"},
-				"bar": nil,
-			},
-		},
-	}
-
-	productResourceOverlays := []string{
-		"product/vendor/blah/overlay",
-	}
-
-	fs := android.MockFS{
-		"lib2/res/values/strings.xml":                             nil,
-		"product/vendor/blah/overlay/lib2/res/values/strings.xml": nil,
-	}
-
-	bp := `
-			android_app {
-				name: "foo",
-				sdk_version: "current",
-				resource_dirs: [],
-				static_libs: ["lib"],
-			}
-
-			android_app {
-				name: "bar",
-				sdk_version: "current",
-				resource_dirs: [],
-				static_libs: ["lib"],
-			}
-
-			android_library {
-				name: "lib",
-				sdk_version: "current",
-				resource_dirs: [],
-				static_libs: ["lib2"],
-			}
-
-			android_library {
-				name: "lib2",
-				sdk_version: "current",
-				resource_dirs: ["lib2/res"],
-			}
-		`
-
-	for _, testCase := range testCases {
-		t.Run(testCase.name, func(t *testing.T) {
-			result := android.GroupFixturePreparers(
-				PrepareForTestWithJavaDefaultModules,
-				fs.AddToFixture(),
-				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
-					variables.ProductResourceOverlays = productResourceOverlays
-					if testCase.enforceRROTargets != nil {
-						variables.EnforceRROTargets = testCase.enforceRROTargets
-					}
-				}),
-			).RunTestWithBp(t, bp)
-
-			modules := []string{"foo", "bar"}
-			for _, moduleName := range modules {
-				module := result.ModuleForTests(moduleName, "android_common")
-				mkEntries := android.AndroidMkEntriesForTest(t, result.TestContext, module.Module())[0]
-				actualRRODirs := mkEntries.EntryMap["LOCAL_SOONG_PRODUCT_RRO_DIRS"]
-				if !reflect.DeepEqual(actualRRODirs, testCase.rroDirs[moduleName]) {
-					t.Errorf("exected %s LOCAL_SOONG_PRODUCT_RRO_DIRS entry: %v\ngot:%q",
-						moduleName, testCase.rroDirs[moduleName], actualRRODirs)
-				}
-			}
-		})
-	}
-}
-
 func TestRuntimeResourceOverlayPartition(t *testing.T) {
 	bp := `
 		runtime_resource_overlay {
@@ -380,28 +285,28 @@
 	}{
 		{
 			name:         "device_specific",
-			expectedPath: "out/soong/target/product/test_device/odm/overlay",
+			expectedPath: "out/target/product/test_device/odm/overlay",
 		},
 		{
 			name:         "soc_specific",
-			expectedPath: "out/soong/target/product/test_device/vendor/overlay",
+			expectedPath: "out/target/product/test_device/vendor/overlay",
 		},
 		{
 			name:         "system_ext_specific",
-			expectedPath: "out/soong/target/product/test_device/system_ext/overlay",
+			expectedPath: "out/target/product/test_device/system_ext/overlay",
 		},
 		{
 			name:         "product_specific",
-			expectedPath: "out/soong/target/product/test_device/product/overlay",
+			expectedPath: "out/target/product/test_device/product/overlay",
 		},
 		{
 			name:         "default",
-			expectedPath: "out/soong/target/product/test_device/product/overlay",
+			expectedPath: "out/target/product/test_device/product/overlay",
 		},
 	}
 	for _, testCase := range testCases {
 		ctx, _ := testJava(t, bp)
-		mod := ctx.ModuleForTests(testCase.name, "android_common").Module().(*RuntimeResourceOverlay)
+		mod := ctx.ModuleForTests(t, testCase.name, "android_common").Module().(*RuntimeResourceOverlay)
 		android.AssertPathRelativeToTopEquals(t, "Install dir is not correct for "+testCase.name, testCase.expectedPath, mod.installDir)
 	}
 }
@@ -436,7 +341,7 @@
 		}
 	`)
 
-	foo := result.ModuleForTests("foo", "android_common")
+	foo := result.ModuleForTests(t, "foo", "android_common")
 
 	// runtime_resource_overlay module depends on aconfig_declarations listed in flags_packages
 	android.AssertBoolEquals(t, "foo expected to depend on bar", true,
diff --git a/java/sdk.go b/java/sdk.go
index 4537f19..4960ab5 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
@@ -392,4 +390,8 @@
 
 	ctx.Strict("FRAMEWORK_AIDL", sdkFrameworkAidlPath(ctx).String())
 	ctx.Strict("API_FINGERPRINT", android.ApiFingerprintPath(ctx).String())
+
+	if ctx.Config().BuildOS == android.Linux {
+		ctx.DistForGoals([]string{"sdk", "droidcore"}, android.ApiFingerprintPath(ctx))
+	}
 }
diff --git a/java/sdk_library.go b/java/sdk_library.go
index dfbde0e..fafb448 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -316,6 +316,15 @@
 	return name
 }
 
+func (scopes apiScopes) matchingScopeFromSdkKind(kind android.SdkKind) *apiScope {
+	for _, scope := range scopes {
+		if scope.kind == kind {
+			return scope
+		}
+	}
+	return nil
+}
+
 var (
 	scopeByName    = make(map[string]*apiScope)
 	allScopeNames  []string
@@ -480,6 +489,9 @@
 
 	// Extra libs used when compiling stubs for this scope.
 	Libs []string
+
+	// Name to override the api_surface that is passed down to droidstubs.
+	Api_surface *string
 }
 
 type sdkLibraryProperties struct {
@@ -690,9 +702,9 @@
 		paths.stubsHeaderPath = lib.HeaderJars
 		paths.stubsImplPath = lib.ImplementationJars
 
-		libDep := dep.(UsesLibraryDependency)
-		paths.stubsDexJarPath = libDep.DexJarBuildPath(ctx)
-		paths.exportableStubsDexJarPath = libDep.DexJarBuildPath(ctx)
+		libDep := android.OtherModuleProviderOrDefault(ctx, dep, JavaInfoProvider).UsesLibraryDependencyInfo
+		paths.stubsDexJarPath = libDep.DexJarBuildPath
+		paths.exportableStubsDexJarPath = libDep.DexJarBuildPath
 		return nil
 	} else {
 		return fmt.Errorf("expected module that has JavaInfoProvider, e.g. java_library")
@@ -706,8 +718,8 @@
 			paths.stubsImplPath = lib.ImplementationJars
 		}
 
-		libDep := dep.(UsesLibraryDependency)
-		paths.stubsDexJarPath = libDep.DexJarBuildPath(ctx)
+		libDep := android.OtherModuleProviderOrDefault(ctx, dep, JavaInfoProvider).UsesLibraryDependencyInfo
+		paths.stubsDexJarPath = libDep.DexJarBuildPath
 		return nil
 	} else {
 		return fmt.Errorf("expected module that has JavaInfoProvider, e.g. java_library")
@@ -720,58 +732,67 @@
 			paths.stubsImplPath = lib.ImplementationJars
 		}
 
-		libDep := dep.(UsesLibraryDependency)
-		paths.exportableStubsDexJarPath = libDep.DexJarBuildPath(ctx)
+		libDep := android.OtherModuleProviderOrDefault(ctx, dep, JavaInfoProvider).UsesLibraryDependencyInfo
+		paths.exportableStubsDexJarPath = libDep.DexJarBuildPath
 		return nil
 	} else {
 		return fmt.Errorf("expected module that has JavaInfoProvider, e.g. java_library")
 	}
 }
 
-func (paths *scopePaths) treatDepAsApiStubsProvider(dep android.Module, action func(provider ApiStubsProvider) error) error {
-	if apiStubsProvider, ok := dep.(ApiStubsProvider); ok {
-		err := action(apiStubsProvider)
+func (paths *scopePaths) treatDepAsApiStubsProvider(ctx android.ModuleContext, dep android.Module,
+	action func(*DroidStubsInfo, *StubsSrcInfo) error) error {
+	apiStubsProvider, ok := android.OtherModuleProvider(ctx, dep, DroidStubsInfoProvider)
+	if !ok {
+		return fmt.Errorf("expected module that provides DroidStubsInfo, e.g. droidstubs")
+	}
+
+	apiStubsSrcProvider, ok := android.OtherModuleProvider(ctx, dep, StubsSrcInfoProvider)
+	if !ok {
+		return fmt.Errorf("expected module that provides StubsSrcInfo, e.g. droidstubs")
+	}
+	return action(&apiStubsProvider, &apiStubsSrcProvider)
+}
+
+func (paths *scopePaths) treatDepAsApiStubsSrcProvider(
+	ctx android.ModuleContext, dep android.Module, action func(provider *StubsSrcInfo) error) error {
+	if apiStubsProvider, ok := android.OtherModuleProvider(ctx, dep, StubsSrcInfoProvider); ok {
+		err := action(&apiStubsProvider)
 		if err != nil {
 			return err
 		}
 		return nil
 	} else {
-		return fmt.Errorf("expected module that implements ExportableApiStubsSrcProvider, e.g. droidstubs")
+		return fmt.Errorf("expected module that provides DroidStubsInfo, e.g. droidstubs")
 	}
 }
 
-func (paths *scopePaths) treatDepAsApiStubsSrcProvider(dep android.Module, action func(provider ApiStubsSrcProvider) error) error {
-	if apiStubsProvider, ok := dep.(ApiStubsSrcProvider); ok {
-		err := action(apiStubsProvider)
-		if err != nil {
-			return err
-		}
-		return nil
-	} else {
-		return fmt.Errorf("expected module that implements ApiStubsSrcProvider, e.g. droidstubs")
+func (paths *scopePaths) extractApiInfoFromApiStubsProvider(provider *DroidStubsInfo, stubsType StubsType) error {
+	var currentApiFilePathErr, removedApiFilePathErr error
+	info, err := getStubsInfoForType(provider, stubsType)
+	if err != nil {
+		return err
 	}
-}
-
-func (paths *scopePaths) extractApiInfoFromApiStubsProvider(provider ApiStubsProvider, stubsType StubsType) error {
-	var annotationsZip, currentApiFilePath, removedApiFilePath android.Path
-	annotationsZip, annotationsZipErr := provider.AnnotationsZip(stubsType)
-	currentApiFilePath, currentApiFilePathErr := provider.ApiFilePath(stubsType)
-	removedApiFilePath, removedApiFilePathErr := provider.RemovedApiFilePath(stubsType)
-
-	combinedError := errors.Join(annotationsZipErr, currentApiFilePathErr, removedApiFilePathErr)
+	if info.ApiFile == nil {
+		currentApiFilePathErr = fmt.Errorf("expected module that provides ApiFile")
+	}
+	if info.RemovedApiFile == nil {
+		removedApiFilePathErr = fmt.Errorf("expected module that provides RemovedApiFile")
+	}
+	combinedError := errors.Join(currentApiFilePathErr, removedApiFilePathErr)
 
 	if combinedError == nil {
-		paths.annotationsZip = android.OptionalPathForPath(annotationsZip)
-		paths.currentApiFilePath = android.OptionalPathForPath(currentApiFilePath)
-		paths.removedApiFilePath = android.OptionalPathForPath(removedApiFilePath)
+		paths.annotationsZip = android.OptionalPathForPath(info.AnnotationsZip)
+		paths.currentApiFilePath = android.OptionalPathForPath(info.ApiFile)
+		paths.removedApiFilePath = android.OptionalPathForPath(info.RemovedApiFile)
 	}
 	return combinedError
 }
 
-func (paths *scopePaths) extractStubsSourceInfoFromApiStubsProviders(provider ApiStubsSrcProvider, stubsType StubsType) error {
-	stubsSrcJar, err := provider.StubsSrcJar(stubsType)
+func (paths *scopePaths) extractStubsSourceInfoFromApiStubsProviders(provider *StubsSrcInfo, stubsType StubsType) error {
+	path, err := getStubsSrcInfoForType(provider, stubsType)
 	if err == nil {
-		paths.stubsSrcJar = android.OptionalPathForPath(stubsSrcJar)
+		paths.stubsSrcJar = android.OptionalPathForPath(path)
 	}
 	return err
 }
@@ -781,7 +802,7 @@
 	if ctx.Config().ReleaseHiddenApiExportableStubs() {
 		stubsType = Exportable
 	}
-	return paths.treatDepAsApiStubsSrcProvider(dep, func(provider ApiStubsSrcProvider) error {
+	return paths.treatDepAsApiStubsSrcProvider(ctx, dep, func(provider *StubsSrcInfo) error {
 		return paths.extractStubsSourceInfoFromApiStubsProviders(provider, stubsType)
 	})
 }
@@ -791,17 +812,17 @@
 	if ctx.Config().ReleaseHiddenApiExportableStubs() {
 		stubsType = Exportable
 	}
-	return paths.treatDepAsApiStubsProvider(dep, func(provider ApiStubsProvider) error {
-		extractApiInfoErr := paths.extractApiInfoFromApiStubsProvider(provider, stubsType)
-		extractStubsSourceInfoErr := paths.extractStubsSourceInfoFromApiStubsProviders(provider, stubsType)
+	return paths.treatDepAsApiStubsProvider(ctx, dep, func(apiStubsProvider *DroidStubsInfo, apiStubsSrcProvider *StubsSrcInfo) error {
+		extractApiInfoErr := paths.extractApiInfoFromApiStubsProvider(apiStubsProvider, stubsType)
+		extractStubsSourceInfoErr := paths.extractStubsSourceInfoFromApiStubsProviders(apiStubsSrcProvider, stubsType)
 		return errors.Join(extractApiInfoErr, extractStubsSourceInfoErr)
 	})
 }
 
-func extractOutputPaths(dep android.Module) (android.Paths, error) {
+func extractOutputPaths(ctx android.ModuleContext, dep android.Module) (android.Paths, error) {
 	var paths android.Paths
-	if sourceFileProducer, ok := dep.(android.SourceFileProducer); ok {
-		paths = sourceFileProducer.Srcs()
+	if sourceFileProducer, ok := android.OtherModuleProvider(ctx, dep, android.SourceFilesInfoProvider); ok {
+		paths = sourceFileProducer.Srcs
 		return paths, nil
 	} else {
 		return nil, fmt.Errorf("module %q does not produce source files", dep)
@@ -809,17 +830,47 @@
 }
 
 func (paths *scopePaths) extractLatestApiPath(ctx android.ModuleContext, dep android.Module) error {
-	outputPaths, err := extractOutputPaths(dep)
+	outputPaths, err := extractOutputPaths(ctx, dep)
 	paths.latestApiPaths = outputPaths
 	return err
 }
 
 func (paths *scopePaths) extractLatestRemovedApiPath(ctx android.ModuleContext, dep android.Module) error {
-	outputPaths, err := extractOutputPaths(dep)
+	outputPaths, err := extractOutputPaths(ctx, dep)
 	paths.latestRemovedApiPaths = outputPaths
 	return err
 }
 
+func getStubsInfoForType(info *DroidStubsInfo, stubsType StubsType) (ret *StubsInfo, err error) {
+	switch stubsType {
+	case Everything:
+		ret, err = &info.EverythingStubsInfo, nil
+	case Exportable:
+		ret, err = &info.ExportableStubsInfo, nil
+	default:
+		ret, err = nil, fmt.Errorf("stubs info not supported for the stub type %s", stubsType.String())
+	}
+	if ret == nil && err == nil {
+		err = fmt.Errorf("stubs info is null for the stub type %s", stubsType.String())
+	}
+	return ret, err
+}
+
+func getStubsSrcInfoForType(info *StubsSrcInfo, stubsType StubsType) (ret android.Path, err error) {
+	switch stubsType {
+	case Everything:
+		ret, err = info.EverythingStubsSrcJar, nil
+	case Exportable:
+		ret, err = info.ExportableStubsSrcJar, nil
+	default:
+		ret, err = nil, fmt.Errorf("stubs src info not supported for the stub type %s", stubsType.String())
+	}
+	if ret == nil && err == nil {
+		err = fmt.Errorf("stubs src info is null for the stub type %s", stubsType.String())
+	}
+	return ret, err
+}
+
 type commonToSdkLibraryAndImportProperties struct {
 	// Specifies whether this module can be used as an Android shared library; defaults
 	// to true.
@@ -908,9 +959,9 @@
 	// This is non-empty only when api_only is false.
 	implLibraryHeaderJars android.Paths
 
-	// The reference to the implementation library created by the source module.
-	// Is nil if the source module does not exist.
-	implLibraryModule *Library
+	// The reference to the JavaInfo provided by implementation library created by
+	// the source module. Is nil if the source module does not exist.
+	implLibraryInfo *JavaInfo
 }
 
 func (c *commonToSdkLibraryAndImport) initCommon(module commonSdkLibraryAndImportModule) {
@@ -964,6 +1015,10 @@
 		removedApiFilePaths[kind] = removedApiFilePath
 	}
 
+	javaInfo := &JavaInfo{}
+	setExtraJavaInfo(ctx, ctx.Module(), javaInfo)
+	android.SetProvider(ctx, JavaInfoProvider, javaInfo)
+
 	return SdkLibraryInfo{
 		EverythingStubDexJarPaths: everythingStubPaths,
 		ExportableStubDexJarPaths: exportableStubPaths,
@@ -1200,7 +1255,8 @@
 
 	commonToSdkLibraryAndImport
 
-	builtInstalledForApex []dexpreopterInstall
+	apexSystemServerDexpreoptInstalls []DexpreopterInstall
+	apexSystemServerDexJars           android.Paths
 }
 
 func (module *SdkLibrary) generateTestAndSystemScopesByDefault() bool {
@@ -1211,16 +1267,16 @@
 
 // To satisfy the UsesLibraryDependency interface
 func (module *SdkLibrary) DexJarBuildPath(ctx android.ModuleErrorfContext) OptionalDexJarPath {
-	if module.implLibraryModule != nil {
-		return module.implLibraryModule.DexJarBuildPath(ctx)
+	if module.implLibraryInfo != nil {
+		return module.implLibraryInfo.DexJarFile
 	}
 	return makeUnsetDexJarPath()
 }
 
 // To satisfy the UsesLibraryDependency interface
 func (module *SdkLibrary) DexJarInstallPath() android.Path {
-	if module.implLibraryModule != nil {
-		return module.implLibraryModule.DexJarInstallPath()
+	if module.implLibraryInfo != nil {
+		return module.implLibraryInfo.InstallFile
 	}
 	return nil
 }
@@ -1282,7 +1338,7 @@
 func CheckMinSdkVersion(ctx android.ModuleContext, module *Library) {
 	android.CheckMinSdkVersion(ctx, module.MinSdkVersion(ctx), func(c android.BaseModuleContext, do android.PayloadDepsCallback) {
 		ctx.WalkDeps(func(child android.Module, parent android.Module) bool {
-			isExternal := !module.depIsInSameApex(ctx, child)
+			isExternal := !android.IsDepInSameApex(ctx, module, child)
 			if am, ok := child.(android.ApexModule); ok {
 				if !do(ctx, parent, am, isExternal) {
 					return false
@@ -1314,12 +1370,6 @@
 
 var _ android.InstallNeededDependencyTag = sdkLibraryComponentTag{}
 
-// To satisfy the CopyDirectlyInAnyApexTag interface. Implementation library of the sdk library
-// in an apex is considered to be directly in the apex, as if it was listed in java_libs.
-func (t sdkLibraryComponentTag) CopyDirectlyInAnyApex() {}
-
-var _ android.CopyDirectlyInAnyApexTag = implLibraryTag
-
 func (t sdkLibraryComponentTag) InstallDepNeeded() bool {
 	return t.name == "xml-permissions-file" || t.name == "impl-library"
 }
@@ -1403,6 +1453,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())
@@ -1412,11 +1463,11 @@
 	// Collate the components exported by this module. All scope specific modules are exported but
 	// the impl and xml component modules are not.
 	exportedComponents := map[string]struct{}{}
-
+	var implLib android.ModuleProxy
 	// Record the paths to the header jars of the library (stubs and impl).
 	// When this java_sdk_library is depended upon from others via "libs" property,
 	// the recorded paths will be returned depending on the link type of the caller.
-	ctx.VisitDirectDeps(func(to android.Module) {
+	ctx.VisitDirectDepsProxy(func(to android.ModuleProxy) {
 		tag := ctx.OtherModuleDependencyTag(to)
 
 		// Extract information from any of the scope specific dependencies.
@@ -1436,7 +1487,8 @@
 		if tag == implLibraryTag {
 			if dep, ok := android.OtherModuleProvider(ctx, to, JavaInfoProvider); ok {
 				module.implLibraryHeaderJars = append(module.implLibraryHeaderJars, dep.HeaderJars...)
-				module.implLibraryModule = to.(*Library)
+				module.implLibraryInfo = dep
+				implLib = to
 			}
 		}
 	})
@@ -1447,44 +1499,44 @@
 		module.hideApexVariantFromMake = true
 	}
 
-	if module.implLibraryModule != nil {
+	if module.implLibraryInfo != nil {
 		if ctx.Device() {
-			module.classesJarPaths = android.Paths{module.implLibraryModule.implementationJarFile}
-			module.bootDexJarPath = module.implLibraryModule.bootDexJarPath
-			module.uncompressDexState = module.implLibraryModule.uncompressDexState
-			module.active = module.implLibraryModule.active
+			module.classesJarPaths = module.implLibraryInfo.ImplementationJars
+			module.bootDexJarPath = module.implLibraryInfo.BootDexJarPath
+			module.uncompressDexState = module.implLibraryInfo.UncompressDexState
+			module.active = module.implLibraryInfo.Active
 		}
 
-		module.outputFile = module.implLibraryModule.outputFile
-		module.dexJarFile = makeDexJarPathFromPath(module.implLibraryModule.dexJarFile.Path())
-		module.headerJarFile = module.implLibraryModule.headerJarFile
-		module.implementationAndResourcesJar = module.implLibraryModule.implementationAndResourcesJar
-		module.builtInstalledForApex = module.implLibraryModule.builtInstalledForApex
-		module.dexpreopter.configPath = module.implLibraryModule.dexpreopter.configPath
-		module.dexpreopter.outputProfilePathOnHost = module.implLibraryModule.dexpreopter.outputProfilePathOnHost
+		module.outputFile = module.implLibraryInfo.OutputFile
+		module.dexJarFile = makeDexJarPathFromPath(module.implLibraryInfo.DexJarFile.Path())
+		module.headerJarFile = module.implLibraryInfo.HeaderJars[0]
+		module.implementationAndResourcesJar = module.implLibraryInfo.ImplementationAndResourcesJars[0]
+		module.apexSystemServerDexpreoptInstalls = module.implLibraryInfo.ApexSystemServerDexpreoptInstalls
+		module.apexSystemServerDexJars = module.implLibraryInfo.ApexSystemServerDexJars
+		module.dexpreopter.configPath = module.implLibraryInfo.ConfigPath
+		module.dexpreopter.outputProfilePathOnHost = module.implLibraryInfo.OutputProfilePathOnHost
 
 		// Properties required for Library.AndroidMkEntries
-		module.logtagsSrcs = module.implLibraryModule.logtagsSrcs
-		module.dexpreopter.builtInstalled = module.implLibraryModule.dexpreopter.builtInstalled
-		module.jacocoReportClassesFile = module.implLibraryModule.jacocoReportClassesFile
-		module.dexer.proguardDictionary = module.implLibraryModule.dexer.proguardDictionary
-		module.dexer.proguardUsageZip = module.implLibraryModule.dexer.proguardUsageZip
-		module.linter.reports = module.implLibraryModule.linter.reports
+		module.logtagsSrcs = module.implLibraryInfo.LogtagsSrcs
+		module.dexpreopter.builtInstalled = module.implLibraryInfo.BuiltInstalled
+		module.jacocoReportClassesFile = module.implLibraryInfo.JacocoReportClassesFile
+		module.dexer.proguardDictionary = module.implLibraryInfo.ProguardDictionary
+		module.dexer.proguardUsageZip = module.implLibraryInfo.ProguardUsageZip
+		module.linter.reports = module.implLibraryInfo.LinterReports
 
-		if lintInfo, ok := android.OtherModuleProvider(ctx, module.implLibraryModule, LintProvider); ok {
+		if lintInfo, ok := android.OtherModuleProvider(ctx, implLib, LintProvider); ok {
 			android.SetProvider(ctx, LintProvider, lintInfo)
 		}
 
 		if !module.Host() {
-			module.hostdexInstallFile = module.implLibraryModule.hostdexInstallFile
+			module.hostdexInstallFile = module.implLibraryInfo.HostdexInstallFile
 		}
 
-		if installFilesInfo, ok := android.OtherModuleProvider(ctx, module.implLibraryModule, android.InstallFilesProvider); ok {
+		if installFilesInfo, ok := android.OtherModuleProvider(ctx, implLib, android.InstallFilesProvider); ok {
 			if installFilesInfo.CheckbuildTarget != nil {
 				ctx.CheckbuildFile(installFilesInfo.CheckbuildTarget)
 			}
 		}
-		android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: module.implLibraryModule.uniqueSrcFiles.Strings()})
 	}
 
 	// Make the set of components exported by this module available for use elsewhere.
@@ -1523,17 +1575,32 @@
 		}
 	}
 
-	if module.requiresRuntimeImplementationLibrary() && module.implLibraryModule != nil {
+	if module.requiresRuntimeImplementationLibrary() && module.implLibraryInfo != nil {
 		generatingLibs = append(generatingLibs, module.implLibraryModuleName())
-		setOutputFiles(ctx, module.implLibraryModule.Module)
+		setOutputFilesFromJavaInfo(ctx, module.implLibraryInfo)
 	}
 
 	sdkLibInfo.GeneratingLibs = generatingLibs
 	android.SetProvider(ctx, SdkLibraryInfoProvider, sdkLibInfo)
 }
 
-func (module *SdkLibrary) BuiltInstalledForApex() []dexpreopterInstall {
-	return module.builtInstalledForApex
+func setOutputFilesFromJavaInfo(ctx android.ModuleContext, info *JavaInfo) {
+	ctx.SetOutputFiles(append(android.PathsIfNonNil(info.OutputFile), info.ExtraOutputFiles...), "")
+	ctx.SetOutputFiles(android.PathsIfNonNil(info.OutputFile), android.DefaultDistTag)
+	ctx.SetOutputFiles(info.ImplementationAndResourcesJars, ".jar")
+	ctx.SetOutputFiles(info.HeaderJars, ".hjar")
+	if info.ProguardDictionary.Valid() {
+		ctx.SetOutputFiles(android.Paths{info.ProguardDictionary.Path()}, ".proguard_map")
+	}
+	ctx.SetOutputFiles(info.GeneratedSrcjars, ".generated_srcjars")
+}
+
+func (module *SdkLibrary) ApexSystemServerDexpreoptInstalls() []DexpreopterInstall {
+	return module.apexSystemServerDexpreoptInstalls
+}
+
+func (module *SdkLibrary) ApexSystemServerDexJars() android.Paths {
+	return module.apexSystemServerDexJars
 }
 
 func (module *SdkLibrary) AndroidMkEntries() []android.AndroidMkEntries {
@@ -1641,15 +1708,22 @@
 }
 
 // Implements android.ApexModule
-func (module *SdkLibrary) DepIsInSameApex(mctx android.BaseModuleContext, dep android.Module) bool {
-	depTag := mctx.OtherModuleDependencyTag(dep)
-	if depTag == xmlPermissionsFileTag {
+func (m *SdkLibrary) GetDepInSameApexChecker() android.DepInSameApexChecker {
+	return SdkLibraryDepInSameApexChecker{}
+}
+
+type SdkLibraryDepInSameApexChecker struct {
+	android.BaseDepInSameApexChecker
+}
+
+func (m SdkLibraryDepInSameApexChecker) OutgoingDepIsInSameApex(tag blueprint.DependencyTag) bool {
+	if tag == xmlPermissionsFileTag {
 		return true
 	}
-	if dep.Name() == module.implLibraryModuleName() {
+	if tag == implLibraryTag {
 		return true
 	}
-	return module.Library.DepIsInSameApex(mctx, dep)
+	return depIsInSameApex(tag)
 }
 
 // Implements android.ApexModule
@@ -1906,10 +1980,6 @@
 
 	commonToSdkLibraryAndImport
 
-	// The reference to the xml permissions module created by the source module.
-	// Is nil if the source module does not exist.
-	xmlPermissionsFileModule *sdkLibraryXml
-
 	// Build path to the dex implementation jar obtained from the prebuilt_apex, if any.
 	dexJarFile    OptionalDexJarPath
 	dexJarFileErr error
@@ -2064,9 +2134,16 @@
 var _ android.ApexModule = (*SdkLibraryImport)(nil)
 
 // Implements android.ApexModule
-func (module *SdkLibraryImport) DepIsInSameApex(mctx android.BaseModuleContext, dep android.Module) bool {
-	depTag := mctx.OtherModuleDependencyTag(dep)
-	if depTag == xmlPermissionsFileTag {
+func (m *SdkLibraryImport) GetDepInSameApexChecker() android.DepInSameApexChecker {
+	return SdkLibraryImportDepIsInSameApexChecker{}
+}
+
+type SdkLibraryImportDepIsInSameApexChecker struct {
+	android.BaseDepInSameApexChecker
+}
+
+func (m SdkLibraryImportDepIsInSameApexChecker) OutgoingDepIsInSameApex(tag blueprint.DependencyTag) bool {
+	if tag == xmlPermissionsFileTag {
 		return true
 	}
 
@@ -2099,7 +2176,7 @@
 	module.installFile = android.PathForModuleInstall(ctx, "framework", module.Stem()+".jar")
 
 	// Record the paths to the prebuilt stubs library and stubs source.
-	ctx.VisitDirectDeps(func(to android.Module) {
+	ctx.VisitDirectDepsProxy(func(to android.ModuleProxy) {
 		tag := ctx.OtherModuleDependencyTag(to)
 
 		// Extract information from any of the scope specific dependencies.
@@ -2111,17 +2188,11 @@
 			// is determined by the nature of the dependency which is determined by the tag.
 			scopeTag.extractDepInfo(ctx, to, scopePaths)
 		} else if tag == implLibraryTag {
-			if implLibrary, ok := to.(*Library); ok {
-				module.implLibraryModule = implLibrary
+			if implInfo, ok := android.OtherModuleProvider(ctx, to, JavaInfoProvider); ok {
+				module.implLibraryInfo = implInfo
 			} else {
 				ctx.ModuleErrorf("implementation library must be of type *java.Library but was %T", to)
 			}
-		} else if tag == xmlPermissionsFileTag {
-			if xmlPermissionsFileModule, ok := to.(*sdkLibraryXml); ok {
-				module.xmlPermissionsFileModule = xmlPermissionsFileModule
-			} else {
-				ctx.ModuleErrorf("xml permissions file module must be of type *sdkLibraryXml but was %T", to)
-			}
 		}
 	})
 	sdkLibInfo := module.generateCommonBuildActions(ctx)
@@ -2161,9 +2232,9 @@
 	}
 
 	module.setOutputFiles(ctx)
-	if module.implLibraryModule != nil {
+	if module.implLibraryInfo != nil {
 		generatingLibs = append(generatingLibs, module.implLibraryModuleName())
-		setOutputFiles(ctx, module.implLibraryModule.Module)
+		setOutputFilesFromJavaInfo(ctx, module.implLibraryInfo)
 	}
 
 	sdkLibInfo.GeneratingLibs = generatingLibs
@@ -2182,10 +2253,10 @@
 	if module.dexJarFile.IsSet() {
 		return module.dexJarFile
 	}
-	if module.implLibraryModule == nil {
+	if module.implLibraryInfo == nil {
 		return makeUnsetDexJarPath()
 	} else {
-		return module.implLibraryModule.DexJarBuildPath(ctx)
+		return module.implLibraryInfo.DexJarFile
 	}
 }
 
@@ -2201,10 +2272,10 @@
 
 // to satisfy apex.javaDependency interface
 func (module *SdkLibraryImport) JacocoReportClassesFile() android.Path {
-	if module.implLibraryModule == nil {
+	if module.implLibraryInfo == nil {
 		return nil
 	} else {
-		return module.implLibraryModule.JacocoReportClassesFile()
+		return module.implLibraryInfo.JacocoReportClassesFile
 	}
 }
 
@@ -2217,19 +2288,19 @@
 
 // to satisfy java.ApexDependency interface
 func (module *SdkLibraryImport) HeaderJars() android.Paths {
-	if module.implLibraryModule == nil {
+	if module.implLibraryInfo == nil {
 		return nil
 	} else {
-		return module.implLibraryModule.HeaderJars()
+		return module.implLibraryInfo.HeaderJars
 	}
 }
 
 // to satisfy java.ApexDependency interface
 func (module *SdkLibraryImport) ImplementationAndResourcesJars() android.Paths {
-	if module.implLibraryModule == nil {
+	if module.implLibraryInfo == nil {
 		return nil
 	} else {
-		return module.implLibraryModule.ImplementationAndResourcesJars()
+		return module.implLibraryInfo.ImplementationAndResourcesJars
 	}
 }
 
@@ -2394,8 +2465,7 @@
 	s.Min_device_sdk = sdk.commonSdkLibraryProperties.Min_device_sdk
 	s.Max_device_sdk = sdk.commonSdkLibraryProperties.Max_device_sdk
 
-	implLibrary := sdk.implLibraryModule
-	if implLibrary != nil && implLibrary.dexpreopter.dexpreoptProperties.Dex_preopt_result.Profile_guided {
+	if sdk.implLibraryInfo != nil && sdk.implLibraryInfo.ProfileGuided {
 		s.DexPreoptProfileGuided = proptools.BoolPtr(true)
 	}
 }
diff --git a/java/sdk_library_internal.go b/java/sdk_library_internal.go
index ca088cf..db9cd24 100644
--- a/java/sdk_library_internal.go
+++ b/java/sdk_library_internal.go
@@ -15,12 +15,13 @@
 package java
 
 import (
-	"android/soong/android"
-	"android/soong/etc"
 	"fmt"
 	"path"
 	"strings"
 
+	"android/soong/android"
+	"android/soong/etc"
+
 	"github.com/google/blueprint/proptools"
 )
 
@@ -173,6 +174,20 @@
 	mctx.CreateModule(LibraryFactory, properties...)
 }
 
+// getApiSurfaceForScope returns the api surface name to use for the apiScope. If one is specified
+// in the corresponding ApiScopeProperties.Api_surface property that is used, otherwise the name of
+// the apiScope is used.
+func (module *SdkLibrary) getApiSurfaceForScope(apiScope *apiScope) *string {
+	scopeProperties := module.scopeToProperties[apiScope]
+
+	apiSurface := scopeProperties.Api_surface
+	if apiSurface == nil {
+		apiSurface = &apiScope.name
+	}
+
+	return apiSurface
+}
+
 // Creates the [Droidstubs] module with ".stubs.source.<[apiScope.name]>" that creates stubs
 // source files from the given full source files and also updates and checks the API
 // specification files (i.e. "*-current.txt", "*-removed.txt" files).
@@ -226,7 +241,7 @@
 	props.Srcs = append(props.Srcs, module.properties.Srcs...)
 	props.Srcs = append(props.Srcs, module.sdkLibraryProperties.Api_srcs...)
 	props.Sdk_version = module.deviceProperties.Sdk_version
-	props.Api_surface = &apiScope.name
+	props.Api_surface = module.getApiSurfaceForScope(apiScope)
 	props.System_modules = module.deviceProperties.System_modules
 	props.Installable = proptools.BoolPtr(false)
 	// A droiddoc module has only one Libs property and doesn't distinguish between
@@ -566,7 +581,7 @@
 		Min_device_sdk            *string
 		Max_device_sdk            *string
 		Sdk_library_min_api_level *string
-		Uses_libs_dependencies    []string
+		Uses_libs_dependencies    proptools.Configurable[[]string]
 	}{
 		Name:                      proptools.StringPtr(module.xmlPermissionsModuleName()),
 		Enabled:                   module.EnabledProperty(),
@@ -577,7 +592,7 @@
 		Min_device_sdk:            module.commonSdkLibraryProperties.Min_device_sdk,
 		Max_device_sdk:            module.commonSdkLibraryProperties.Max_device_sdk,
 		Sdk_library_min_api_level: &moduleMinApiLevelStr,
-		Uses_libs_dependencies:    module.usesLibraryProperties.Uses_libs,
+		Uses_libs_dependencies:    module.usesLibraryProperties.Uses_libs.Clone(),
 	}
 
 	mctx.CreateModule(sdkLibraryXmlFactory, &props)
@@ -742,7 +757,7 @@
 	// Uses-libs dependencies that the shared library requires to work correctly.
 	//
 	// This will add dependency="foo:bar" to the <library> section.
-	Uses_libs_dependencies []string
+	Uses_libs_dependencies proptools.Configurable[[]string]
 }
 
 // java_sdk_library_xml builds the permission xml file for a java_sdk_library.
@@ -778,7 +793,11 @@
 
 // from android.ApexModule
 func (module *sdkLibraryXml) AvailableFor(what string) bool {
-	return true
+	return android.CheckAvailableForApex(what, module.ApexAvailableFor())
+}
+
+func (module *sdkLibraryXml) ApexAvailableFor() []string {
+	return []string{android.AvailableToPlatform, android.AvailableToAnyApex}
 }
 
 func (module *sdkLibraryXml) DepsMutator(ctx android.BottomUpMutatorContext) {
@@ -864,7 +883,7 @@
 	implicitUntilAttr := formattedOptionalSdkLevelAttribute(ctx, "on-bootclasspath-before", module.properties.On_bootclasspath_before)
 	minSdkAttr := formattedOptionalSdkLevelAttribute(ctx, "min-device-sdk", module.properties.Min_device_sdk)
 	maxSdkAttr := formattedOptionalSdkLevelAttribute(ctx, "max-device-sdk", module.properties.Max_device_sdk)
-	dependenciesAttr := formattedDependenciesAttribute(module.properties.Uses_libs_dependencies)
+	dependenciesAttr := formattedDependenciesAttribute(module.properties.Uses_libs_dependencies.GetOrDefault(ctx, nil))
 	// <library> is understood in all android versions whereas <apex-library> is only understood from API T (and ignored before that).
 	// similarly, min_device_sdk is only understood from T. So if a library is using that, we need to use the apex-library to make sure this library is not loaded before T
 	var libraryTag string
diff --git a/java/sdk_library_test.go b/java/sdk_library_test.go
index 6031d72..2cb827d 100644
--- a/java/sdk_library_test.go
+++ b/java/sdk_library_test.go
@@ -25,6 +25,7 @@
 )
 
 func TestJavaSdkLibrary(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -114,19 +115,19 @@
 	`)
 
 	// check the existence of the internal modules
-	foo := result.ModuleForTests("foo", "android_common")
-	result.ModuleForTests(apiScopePublic.stubsLibraryModuleName("foo"), "android_common")
-	result.ModuleForTests(apiScopeSystem.stubsLibraryModuleName("foo"), "android_common")
-	result.ModuleForTests(apiScopeTest.stubsLibraryModuleName("foo"), "android_common")
-	result.ModuleForTests(apiScopePublic.stubsSourceModuleName("foo"), "android_common")
-	result.ModuleForTests(apiScopeSystem.stubsSourceModuleName("foo"), "android_common")
-	result.ModuleForTests(apiScopeTest.stubsSourceModuleName("foo"), "android_common")
-	result.ModuleForTests(apiScopePublic.stubsSourceModuleName("foo")+".api.contribution", "")
-	result.ModuleForTests(apiScopePublic.apiLibraryModuleName("foo"), "android_common")
-	result.ModuleForTests("foo"+sdkXmlFileSuffix, "android_common")
-	result.ModuleForTests("foo.api.public.28", "")
-	result.ModuleForTests("foo.api.system.28", "")
-	result.ModuleForTests("foo.api.test.28", "")
+	foo := result.ModuleForTests(t, "foo", "android_common")
+	result.ModuleForTests(t, apiScopePublic.stubsLibraryModuleName("foo"), "android_common")
+	result.ModuleForTests(t, apiScopeSystem.stubsLibraryModuleName("foo"), "android_common")
+	result.ModuleForTests(t, apiScopeTest.stubsLibraryModuleName("foo"), "android_common")
+	result.ModuleForTests(t, apiScopePublic.stubsSourceModuleName("foo"), "android_common")
+	result.ModuleForTests(t, apiScopeSystem.stubsSourceModuleName("foo"), "android_common")
+	result.ModuleForTests(t, apiScopeTest.stubsSourceModuleName("foo"), "android_common")
+	result.ModuleForTests(t, apiScopePublic.stubsSourceModuleName("foo")+".api.contribution", "")
+	result.ModuleForTests(t, apiScopePublic.apiLibraryModuleName("foo"), "android_common")
+	result.ModuleForTests(t, "foo"+sdkXmlFileSuffix, "android_common")
+	result.ModuleForTests(t, "foo.api.public.28", "")
+	result.ModuleForTests(t, "foo.api.system.28", "")
+	result.ModuleForTests(t, "foo.api.test.28", "")
 
 	exportedComponentsInfo, _ := android.OtherModuleProvider(result, foo.Module(), android.ExportedComponentsInfoProvider)
 	expectedFooExportedComponents := []string{
@@ -146,7 +147,7 @@
 	}
 	android.AssertArrayString(t, "foo exported components", expectedFooExportedComponents, exportedComponentsInfo.Components)
 
-	bazJavac := result.ModuleForTests("baz", "android_common").Rule("javac")
+	bazJavac := result.ModuleForTests(t, "baz", "android_common").Rule("javac")
 	// tests if baz is actually linked to the stubs lib
 	android.AssertStringDoesContain(t, "baz javac classpath", bazJavac.Args["classpath"], "foo.stubs.system.jar")
 	// ... and not to the impl lib
@@ -154,20 +155,20 @@
 	// test if baz is not linked to the system variant of foo
 	android.AssertStringDoesNotContain(t, "baz javac classpath", bazJavac.Args["classpath"], "foo.stubs.jar")
 
-	bazTestJavac := result.ModuleForTests("baz-test", "android_common").Rule("javac")
+	bazTestJavac := result.ModuleForTests(t, "baz-test", "android_common").Rule("javac")
 	// tests if baz-test is actually linked to the test stubs lib
 	android.AssertStringDoesContain(t, "baz-test javac classpath", bazTestJavac.Args["classpath"], "foo.stubs.test.jar")
 
-	baz29Javac := result.ModuleForTests("baz-29", "android_common").Rule("javac")
+	baz29Javac := result.ModuleForTests(t, "baz-29", "android_common").Rule("javac")
 	// tests if baz-29 is actually linked to the system 29 stubs lib
 	android.AssertStringDoesContain(t, "baz-29 javac classpath", baz29Javac.Args["classpath"], "prebuilts/sdk/sdk_system_29_foo/android_common/combined/sdk_system_29_foo.jar")
 
-	bazModule30Javac := result.ModuleForTests("baz-module-30", "android_common").Rule("javac")
+	bazModule30Javac := result.ModuleForTests(t, "baz-module-30", "android_common").Rule("javac")
 	// tests if "baz-module-30" is actually linked to the module 30 stubs lib
 	android.AssertStringDoesContain(t, "baz-module-30 javac classpath", bazModule30Javac.Args["classpath"], "prebuilts/sdk/sdk_module-lib_30_foo/android_common/combined/sdk_module-lib_30_foo.jar")
 
 	// test if baz has exported SDK lib names foo and bar to qux
-	qux := result.ModuleForTests("qux", "android_common")
+	qux := result.ModuleForTests(t, "qux", "android_common")
 	if quxLib, ok := qux.Module().(*Library); ok {
 		requiredSdkLibs, optionalSdkLibs := quxLib.ClassLoaderContexts().UsesLibs()
 		android.AssertDeepEquals(t, "qux exports (required)", []string{"fred", "quuz", "foo", "bar"}, requiredSdkLibs)
@@ -175,18 +176,19 @@
 	}
 
 	// test if quuz have created the api_contribution module
-	result.ModuleForTests(apiScopePublic.stubsSourceModuleName("quuz")+".api.contribution", "")
+	result.ModuleForTests(t, apiScopePublic.stubsSourceModuleName("quuz")+".api.contribution", "")
 
-	fooImplDexJar := result.ModuleForTests("foo.impl", "android_common").Rule("d8")
+	fooImplDexJar := result.ModuleForTests(t, "foo.impl", "android_common").Rule("d8")
 	// tests if kotlinc generated files are NOT excluded from output of foo.impl.
 	android.AssertStringDoesNotContain(t, "foo.impl dex", fooImplDexJar.BuildParams.Args["mergeZipsFlags"], "-stripFile META-INF/*.kotlin_module")
 
-	barImplDexJar := result.ModuleForTests("bar.impl", "android_common").Rule("d8")
+	barImplDexJar := result.ModuleForTests(t, "bar.impl", "android_common").Rule("d8")
 	// tests if kotlinc generated files are excluded from output of bar.impl.
 	android.AssertStringDoesContain(t, "bar.impl dex", barImplDexJar.BuildParams.Args["mergeZipsFlags"], "-stripFile META-INF/*.kotlin_module")
 }
 
 func TestJavaSdkLibrary_UpdatableLibrary(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -218,7 +220,7 @@
 `)
 
 	// test that updatability attributes are passed on correctly
-	fooUpdatable := result.ModuleForTests("fooUpdatable.xml", "android_common").Output("fooUpdatable.xml")
+	fooUpdatable := result.ModuleForTests(t, "fooUpdatable.xml", "android_common").Output("fooUpdatable.xml")
 	fooUpdatableContents := android.ContentFromFileRuleForTests(t, result.TestContext, fooUpdatable)
 	android.AssertStringDoesContain(t, "fooUpdatable.xml contents", fooUpdatableContents, `on-bootclasspath-since="U"`)
 	android.AssertStringDoesContain(t, "fooUpdatable.xml contents", fooUpdatableContents, `on-bootclasspath-before="V"`)
@@ -227,7 +229,7 @@
 
 	// double check that updatability attributes are not written if they don't exist in the bp file
 	// the permissions file for the foo library defined above
-	fooPermissions := result.ModuleForTests("foo.xml", "android_common").Output("foo.xml")
+	fooPermissions := result.ModuleForTests(t, "foo.xml", "android_common").Output("foo.xml")
 	fooPermissionsContents := android.ContentFromFileRuleForTests(t, result.TestContext, fooPermissions)
 	android.AssertStringDoesNotContain(t, "foo.xml contents", fooPermissionsContents, `on-bootclasspath-since`)
 	android.AssertStringDoesNotContain(t, "foo.xml contents", fooPermissionsContents, `on-bootclasspath-before`)
@@ -236,6 +238,7 @@
 }
 
 func TestJavaSdkLibrary_UpdatableLibrary_Validation_ValidVersion(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -263,6 +266,7 @@
 }
 
 func TestJavaSdkLibrary_UpdatableLibrary_Validation_AtLeastTAttributes(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -292,6 +296,7 @@
 }
 
 func TestJavaSdkLibrary_UpdatableLibrary_Validation_MinAndMaxDeviceSdk(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -319,6 +324,7 @@
 }
 
 func TestJavaSdkLibrary_UpdatableLibrary_Validation_MinAndMaxDeviceSdkAndModuleMinSdk(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -347,6 +353,7 @@
 }
 
 func TestJavaSdkLibrary_UpdatableLibrary_usesNewTag(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -363,13 +370,14 @@
 		}
 `)
 	// test that updatability attributes are passed on correctly
-	fooUpdatable := result.ModuleForTests("foo.xml", "android_common").Output("foo.xml")
+	fooUpdatable := result.ModuleForTests(t, "foo.xml", "android_common").Output("foo.xml")
 	fooUpdatableContents := android.ContentFromFileRuleForTests(t, result.TestContext, fooUpdatable)
 	android.AssertStringDoesContain(t, "foo.xml contents", fooUpdatableContents, `<apex-library`)
 	android.AssertStringDoesNotContain(t, "foo.xml contents", fooUpdatableContents, `<library`)
 }
 
 func TestJavaSdkLibrary_StubOrImplOnlyLibs(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -409,11 +417,11 @@
 		{lib: "stub-only-static-lib", in_stub_combined: true},
 	}
 	verify := func(sdklib, dep string, cp, combined bool) {
-		sdklibCp := result.ModuleForTests(sdklib, "android_common").Rule("javac").Args["classpath"]
+		sdklibCp := result.ModuleForTests(t, sdklib, "android_common").Rule("javac").Args["classpath"]
 		expected := cp || combined // Every combined jar is also on the classpath.
 		android.AssertStringContainsEquals(t, "bad classpath for "+sdklib, sdklibCp, "/"+dep+".jar", expected)
 
-		combineJarInputs := result.ModuleForTests(sdklib, "android_common").Rule("combineJar").Inputs.Strings()
+		combineJarInputs := result.ModuleForTests(t, sdklib, "android_common").Rule("combineJar").Inputs.Strings()
 		depPath := filepath.Join("out", "soong", ".intermediates", dep, "android_common", "turbine-combined", dep+".jar")
 		android.AssertStringListContainsEquals(t, "bad combined inputs for "+sdklib, combineJarInputs, depPath, combined)
 	}
@@ -426,6 +434,7 @@
 }
 
 func TestJavaSdkLibrary_DoNotAccessImplWhenItIsNotBuilt(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -448,13 +457,14 @@
 		`)
 
 	// The bar library should depend on the stubs jar.
-	barLibrary := result.ModuleForTests("bar", "android_common").Rule("javac")
+	barLibrary := result.ModuleForTests(t, "bar", "android_common").Rule("javac")
 	if expected, actual := `^-classpath .*:out/soong/[^:]*/turbine-combined/foo\.stubs\.jar$`, barLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
 		t.Errorf("expected %q, found %#q", expected, actual)
 	}
 }
 
 func TestJavaSdkLibrary_AccessOutputFiles(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -478,6 +488,7 @@
 }
 
 func TestJavaSdkLibrary_AccessOutputFiles_NoAnnotations(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -503,6 +514,7 @@
 }
 
 func TestJavaSdkLibrary_AccessOutputFiles_MissingScope(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -527,6 +539,7 @@
 }
 
 func TestJavaSdkLibrary_Deps(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -557,6 +570,7 @@
 }
 
 func TestJavaSdkLibraryImport_AccessOutputFiles(t *testing.T) {
+	t.Parallel()
 	prepareForJavaTest.RunTestWithBp(t, `
 		java_sdk_library_import {
 			name: "foo",
@@ -582,6 +596,7 @@
 }
 
 func TestJavaSdkLibraryImport_AccessOutputFiles_Invalid(t *testing.T) {
+	t.Parallel()
 	bp := `
 		java_sdk_library_import {
 			name: "foo",
@@ -592,6 +607,7 @@
 		`
 
 	t.Run("stubs.source", func(t *testing.T) {
+		t.Parallel()
 		prepareForJavaTest.
 			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "foo" is not a SourceFileProducer or having valid output file for tag ".public.stubs.source"`)).
 			RunTestWithBp(t, bp+`
@@ -607,6 +623,7 @@
 	})
 
 	t.Run("api.txt", func(t *testing.T) {
+		t.Parallel()
 		prepareForJavaTest.
 			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "foo" is not a SourceFileProducer or having valid output file for tag ".public.api.txt"`)).
 			RunTestWithBp(t, bp+`
@@ -621,6 +638,7 @@
 	})
 
 	t.Run("removed-api.txt", func(t *testing.T) {
+		t.Parallel()
 		prepareForJavaTest.
 			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "foo" is not a SourceFileProducer or having valid output file for tag ".public.removed-api.txt"`)).
 			RunTestWithBp(t, bp+`
@@ -636,6 +654,7 @@
 }
 
 func TestJavaSdkLibrary_InvalidScopes(t *testing.T) {
+	t.Parallel()
 	prepareForJavaTest.
 		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "foo": enabled api scope "system" depends on disabled scope "public"`)).
 		RunTestWithBp(t, `
@@ -656,6 +675,7 @@
 }
 
 func TestJavaSdkLibrary_SdkVersion_ForScope(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -674,6 +694,7 @@
 }
 
 func TestJavaSdkLibrary_ModuleLib(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -694,6 +715,7 @@
 }
 
 func TestJavaSdkLibrary_SystemServer(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -714,6 +736,7 @@
 }
 
 func TestJavaSdkLibrary_SystemServer_AccessToStubScopeLibs(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -774,7 +797,7 @@
 	// The bar library should depend on the highest (where system server is highest and public is
 	// lowest) API scopes provided by each of the foo-* modules. The highest API scope provided by the
 	// foo-<x> module is <x>.
-	barLibrary := result.ModuleForTests("bar", "android_common").Rule("javac")
+	barLibrary := result.ModuleForTests(t, "bar", "android_common").Rule("javac")
 	stubLibraries := []string{
 		stubsPath("foo-public", apiScopePublic),
 		stubsPath("foo-system", apiScopeSystem),
@@ -788,6 +811,7 @@
 }
 
 func TestJavaSdkLibraryImport(t *testing.T) {
+	t.Parallel()
 	result := prepareForJavaTest.RunTestWithBp(t, `
 		java_library {
 			name: "foo",
@@ -826,10 +850,10 @@
 		`)
 
 	for _, scope := range []string{"", ".system", ".test"} {
-		fooModule := result.ModuleForTests("foo"+scope, "android_common")
+		fooModule := result.ModuleForTests(t, "foo"+scope, "android_common")
 		javac := fooModule.Rule("javac")
 
-		sdklibStubsJar := result.ModuleForTests("sdklib.stubs"+scope, "android_common").Output("combined/sdklib.stubs" + scope + ".jar").Output
+		sdklibStubsJar := result.ModuleForTests(t, "sdklib.stubs"+scope, "android_common").Output("combined/sdklib.stubs" + scope + ".jar").Output
 		android.AssertStringDoesContain(t, "foo classpath", javac.Args["classpath"], sdklibStubsJar.String())
 	}
 
@@ -844,6 +868,7 @@
 }
 
 func TestJavaSdkLibraryImport_WithSource(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -968,15 +993,13 @@
 	CheckModuleDependencies(t, result.TestContext, "combined", "android_common", []string{
 		// Each use of :sdklib{...} adds a dependency onto prebuilt_sdklib.
 		`prebuilt_sdklib`,
-		`prebuilt_sdklib`,
-		`prebuilt_sdklib`,
 		`prebuilt_sdklib.stubs`,
 		`prebuilt_sdklib.stubs.source`,
 	})
 
 	// Make sure that dependencies on sdklib that resolve to one of the child libraries use the
 	// prebuilt library.
-	public := result.ModuleForTests("public", "android_common")
+	public := result.ModuleForTests(t, "public", "android_common")
 	rule := public.Output("javac/public.jar")
 	inputs := rule.Implicits.Strings()
 	expected := "out/soong/.intermediates/prebuilt_sdklib.stubs/android_common/combined/sdklib.stubs.jar"
@@ -986,7 +1009,9 @@
 }
 
 func TestJavaSdkLibraryImport_Preferred(t *testing.T) {
+	t.Parallel()
 	t.Run("prefer", func(t *testing.T) {
+		t.Parallel()
 		testJavaSdkLibraryImport_Preferred(t, "prefer: true,", android.NullFixturePreparer)
 	})
 }
@@ -994,6 +1019,7 @@
 // If a module is listed in `mainline_module_contributions, it should be used
 // It will supersede any other source vs prebuilt selection mechanism like `prefer` attribute
 func TestSdkLibraryImport_MetadataModuleSupersedesPreferred(t *testing.T) {
+	t.Parallel()
 	bp := `
 		apex_contributions {
 			name: "my_mainline_module_contributions",
@@ -1093,7 +1119,7 @@
 	).RunTestWithBp(t, bp)
 
 	// Make sure that rdeps get the correct source vs prebuilt based on mainline_module_contributions
-	public := result.ModuleForTests("public", "android_common")
+	public := result.ModuleForTests(t, "public", "android_common")
 	rule := public.Output("javac/public.jar")
 	inputs := rule.Implicits.Strings()
 	expectedInputs := []string{
@@ -1113,6 +1139,7 @@
 }
 
 func TestJavaSdkLibraryDist(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		PrepareForTestWithJavaBuildComponents,
 		PrepareForTestWithJavaDefaultModules,
@@ -1179,7 +1206,8 @@
 
 	for _, tt := range testCases {
 		t.Run(tt.module, func(t *testing.T) {
-			m := result.ModuleForTests(apiScopePublic.exportableStubsLibraryModuleName(tt.module), "android_common").Module().(*Library)
+			t.Parallel()
+			m := result.ModuleForTests(t, apiScopePublic.exportableStubsLibraryModuleName(tt.module), "android_common").Module().(*Library)
 			dists := m.Dists()
 			if len(dists) != 1 {
 				t.Fatalf("expected exactly 1 dist entry, got %d", len(dists))
@@ -1195,6 +1223,7 @@
 }
 
 func TestSdkLibrary_CheckMinSdkVersion(t *testing.T) {
+	t.Parallel()
 	preparer := android.GroupFixturePreparers(
 		PrepareForTestWithJavaBuildComponents,
 		PrepareForTestWithJavaDefaultModules,
@@ -1279,6 +1308,7 @@
 }
 
 func TestJavaSdkLibrary_StubOnlyLibs_PassedToDroidstubs(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -1300,12 +1330,13 @@
 		`)
 
 	// The foo.stubs.source should depend on bar-lib
-	fooStubsSources := result.ModuleForTests("foo.stubs.source", "android_common").Module().(*Droidstubs)
+	fooStubsSources := result.ModuleForTests(t, "foo.stubs.source", "android_common").Module().(*Droidstubs)
 	eval := fooStubsSources.ConfigurableEvaluator(android.PanickingConfigAndErrorContext(result.TestContext))
 	android.AssertStringListContains(t, "foo stubs should depend on bar-lib", fooStubsSources.Javadoc.properties.Libs.GetOrDefault(eval, nil), "bar-lib")
 }
 
 func TestJavaSdkLibrary_Scope_Libs_PassedToDroidstubs(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -1327,12 +1358,13 @@
 		`)
 
 	// The foo.stubs.source should depend on bar-lib
-	fooStubsSources := result.ModuleForTests("foo.stubs.source", "android_common").Module().(*Droidstubs)
+	fooStubsSources := result.ModuleForTests(t, "foo.stubs.source", "android_common").Module().(*Droidstubs)
 	eval := fooStubsSources.ConfigurableEvaluator(android.PanickingConfigAndErrorContext(result.TestContext))
 	android.AssertStringListContains(t, "foo stubs should depend on bar-lib", fooStubsSources.Javadoc.properties.Libs.GetOrDefault(eval, nil), "bar-lib")
 }
 
 func TestJavaSdkLibrary_ApiLibrary(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -1377,12 +1409,13 @@
 	}
 
 	for _, c := range testCases {
-		m := result.ModuleForTests(c.scope.apiLibraryModuleName("foo"), "android_common").Module().(*ApiLibrary)
+		m := result.ModuleForTests(t, c.scope.apiLibraryModuleName("foo"), "android_common").Module().(*ApiLibrary)
 		android.AssertArrayString(t, "Module expected to contain api contributions", c.apiContributions, m.properties.Api_contributions)
 	}
 }
 
 func TestStaticDepStubLibrariesVisibility(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -1412,6 +1445,7 @@
 }
 
 func TestSdkLibraryDependency(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -1438,12 +1472,13 @@
 		}
 `)
 
-	barPermissions := result.ModuleForTests("bar.xml", "android_common").Output("bar.xml")
+	barPermissions := result.ModuleForTests(t, "bar.xml", "android_common").Output("bar.xml")
 	barContents := android.ContentFromFileRuleForTests(t, result.TestContext, barPermissions)
 	android.AssertStringDoesContain(t, "bar.xml java_sdk_xml command", barContents, `dependency="foo"`)
 }
 
 func TestSdkLibraryExportableStubsLibrary(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -1480,8 +1515,8 @@
 	exportableSourceStubsLibraryModuleName := apiScopePublic.exportableSourceStubsLibraryModuleName("foo")
 
 	// Check modules generation
-	result.ModuleForTests(exportableStubsLibraryModuleName, "android_common")
-	result.ModuleForTests(exportableSourceStubsLibraryModuleName, "android_common")
+	result.ModuleForTests(t, exportableStubsLibraryModuleName, "android_common")
+	result.ModuleForTests(t, exportableSourceStubsLibraryModuleName, "android_common")
 
 	// Check static lib dependency
 	android.AssertBoolEquals(t, "exportable top level stubs library module depends on the"+
@@ -1494,6 +1529,7 @@
 // For java libraries depending on java_sdk_library(_import) via libs, assert that
 // rdep gets stubs of source if source is listed in apex_contributions and prebuilt has prefer (legacy mechanism)
 func TestStubResolutionOfJavaSdkLibraryInLibs(t *testing.T) {
+	t.Parallel()
 	bp := `
 		apex_contributions {
 			name: "my_mainline_module_contributions",
@@ -1539,7 +1575,7 @@
 
 	result := fixture.RunTestWithBp(t, bp)
 	// Make sure that rdeps get the correct source vs prebuilt based on mainline_module_contributions
-	public := result.ModuleForTests("mymodule", "android_common")
+	public := result.ModuleForTests(t, "mymodule", "android_common")
 	rule := public.Output("javac/mymodule.jar")
 	inputs := rule.Implicits.Strings()
 	android.AssertStringListContains(t, "Could not find the expected stub on classpath", inputs, "out/soong/.intermediates/sdklib.stubs/android_common/turbine-combined/sdklib.stubs.jar")
@@ -1547,6 +1583,7 @@
 
 // test that rdep gets resolved to the correct version of a java_sdk_library (source or a specific prebuilt)
 func TestMultipleSdkLibraryPrebuilts(t *testing.T) {
+	t.Parallel()
 	bp := `
 		apex_contributions {
 			name: "my_mainline_module_contributions",
@@ -1624,7 +1661,7 @@
 		result := fixture.RunTestWithBp(t, fmt.Sprintf(bp, tc.selectedDependencyName))
 
 		// Make sure that rdeps get the correct source vs prebuilt based on mainline_module_contributions
-		public := result.ModuleForTests("mymodule", "android_common")
+		public := result.ModuleForTests(t, "mymodule", "android_common")
 		rule := public.Output("javac/mymodule.jar")
 		inputs := rule.Implicits.Strings()
 		android.AssertStringListContains(t, "Could not find the expected stub on classpath", inputs, tc.expectedStubPath)
@@ -1632,6 +1669,7 @@
 }
 
 func TestStubLinkType(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -1668,6 +1706,7 @@
 }
 
 func TestSdkLibDirectDependency(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
@@ -1732,6 +1771,7 @@
 }
 
 func TestSdkLibDirectDependencyWithPrebuiltSdk(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		prepareForJavaTest,
 		PrepareForTestWithJavaSdkLibraryFiles,
diff --git a/java/sdk_test.go b/java/sdk_test.go
index 9bfe6a2..49983ad 100644
--- a/java/sdk_test.go
+++ b/java/sdk_test.go
@@ -53,6 +53,7 @@
 }
 
 func TestClasspath(t *testing.T) {
+	t.Parallel()
 	const frameworkAidl = "-I" + defaultJavaDir + "/framework/aidl"
 	var classpathTestcases = []classpathTestCase{
 		{
@@ -388,17 +389,18 @@
 		},
 	}
 
-	t.Parallel()
 	t.Run("basic", func(t *testing.T) {
 		t.Parallel()
 		testClasspathTestCases(t, classpathTestcases, false, false)
 	})
 
 	t.Run("Always_use_prebuilt_sdks=true", func(t *testing.T) {
+		t.Parallel()
 		testClasspathTestCases(t, classpathTestcases, true, false)
 	})
 
 	t.Run("UseTransitiveJarsInClasspath", func(t *testing.T) {
+		t.Parallel()
 		testClasspathTestCases(t, classpathTestcases, false, true)
 	})
 }
@@ -499,7 +501,7 @@
 			}
 
 			checkClasspath := func(t *testing.T, result *android.TestResult, isJava8 bool) {
-				foo := result.ModuleForTests("foo", variant(result))
+				foo := result.ModuleForTests(t, "foo", variant(result))
 				javac := foo.Rule("javac")
 				var deps []string
 
@@ -571,12 +573,13 @@
 
 			// Test with legacy javac -source 1.8 -target 1.8
 			t.Run("Java language level 8", func(t *testing.T) {
+				t.Parallel()
 				result := fixtureFactory.RunTestWithBp(t, bpJava8)
 
 				checkClasspath(t, result, true /* isJava8 */)
 
 				if testcase.host != android.Host {
-					aidl := result.ModuleForTests("foo", variant(result)).Rule("aidl")
+					aidl := result.ModuleForTests(t, "foo", variant(result)).Rule("aidl")
 
 					android.AssertStringDoesContain(t, "aidl command", aidl.RuleParams.Command, testcase.aidl+" -I.")
 				}
@@ -584,12 +587,13 @@
 
 			// Test with default javac -source 9 -target 9
 			t.Run("Java language level 9", func(t *testing.T) {
+				t.Parallel()
 				result := fixtureFactory.RunTestWithBp(t, bp)
 
 				checkClasspath(t, result, false /* isJava8 */)
 
 				if testcase.host != android.Host {
-					aidl := result.ModuleForTests("foo", variant(result)).Rule("aidl")
+					aidl := result.ModuleForTests(t, "foo", variant(result)).Rule("aidl")
 
 					android.AssertStringDoesContain(t, "aidl command", aidl.RuleParams.Command, testcase.aidl+" -I.")
 				}
@@ -602,6 +606,7 @@
 
 			// Test again with PLATFORM_VERSION_CODENAME=REL, javac -source 8 -target 8
 			t.Run("REL + Java language level 8", func(t *testing.T) {
+				t.Parallel()
 				result := android.GroupFixturePreparers(
 					fixtureFactory, prepareWithPlatformVersionRel).RunTestWithBp(t, bpJava8)
 
@@ -610,6 +615,7 @@
 
 			// Test again with PLATFORM_VERSION_CODENAME=REL, javac -source 9 -target 9
 			t.Run("REL + Java language level 9", func(t *testing.T) {
+				t.Parallel()
 				result := android.GroupFixturePreparers(
 					fixtureFactory, prepareWithPlatformVersionRel).RunTestWithBp(t, bp)
 
diff --git a/java/sdk_version_test.go b/java/sdk_version_test.go
index 88351d2..03d55f7 100644
--- a/java/sdk_version_test.go
+++ b/java/sdk_version_test.go
@@ -25,6 +25,7 @@
 }
 
 func TestSystemSdkFromVendor(t *testing.T) {
+	t.Parallel()
 	fixtures := android.GroupFixturePreparers(
 		PrepareForTestWithJavaDefaultModules,
 		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
@@ -57,7 +58,7 @@
 			vendor: true,
 			sdk_version: "system_current",
 		}`)
-	fooModule := result.ModuleForTests("foo", "android_common")
+	fooModule := result.ModuleForTests(t, "foo", "android_common")
 	fooClasspath := fooModule.Rule("javac").Args["classpath"]
 
 	android.AssertStringDoesContain(t, "foo classpath", fooClasspath, "prebuilts/sdk/34/system/android.jar")
diff --git a/java/system_modules.go b/java/system_modules.go
index d9430b2..e955aec 100644
--- a/java/system_modules.go
+++ b/java/system_modules.go
@@ -19,6 +19,7 @@
 	"strings"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/depset"
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
@@ -129,7 +130,7 @@
 	OutputDirDeps android.Paths
 
 	// depset of header jars for this module and all transitive static dependencies
-	TransitiveStaticLibsHeaderJars *android.DepSet[android.Path]
+	TransitiveStaticLibsHeaderJars depset.DepSet[android.Path]
 }
 
 var SystemModulesProvider = blueprint.NewProvider[*SystemModulesProviderInfo]()
@@ -152,13 +153,11 @@
 func (system *SystemModules) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	var jars android.Paths
 
-	var transitiveStaticLibsHeaderJars []*android.DepSet[android.Path]
+	var transitiveStaticLibsHeaderJars []depset.DepSet[android.Path]
 	ctx.VisitDirectDepsWithTag(systemModulesLibsTag, func(module android.Module) {
 		if dep, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider); ok {
 			jars = append(jars, dep.HeaderJars...)
-			if dep.TransitiveStaticLibsHeaderJars != nil {
-				transitiveStaticLibsHeaderJars = append(transitiveStaticLibsHeaderJars, dep.TransitiveStaticLibsHeaderJars)
-			}
+			transitiveStaticLibsHeaderJars = append(transitiveStaticLibsHeaderJars, dep.TransitiveStaticLibsHeaderJars)
 		}
 	})
 
@@ -168,7 +167,7 @@
 		HeaderJars:                     jars,
 		OutputDir:                      system.outputDir,
 		OutputDirDeps:                  system.outputDeps,
-		TransitiveStaticLibsHeaderJars: android.NewDepSet(android.PREORDER, nil, transitiveStaticLibsHeaderJars),
+		TransitiveStaticLibsHeaderJars: depset.New(depset.PREORDER, nil, transitiveStaticLibsHeaderJars),
 	})
 }
 
diff --git a/java/system_modules_test.go b/java/system_modules_test.go
index b05b0e4..99301bc 100644
--- a/java/system_modules_test.go
+++ b/java/system_modules_test.go
@@ -51,10 +51,11 @@
 `)
 
 func TestJavaSystemModules(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(prepareForJavaTest, addSourceSystemModules).RunTest(t)
 
 	// check the existence of the source module
-	sourceSystemModules := result.ModuleForTests("system-modules", "android_common")
+	sourceSystemModules := result.ModuleForTests(t, "system-modules", "android_common")
 	sourceInputs := sourceSystemModules.Rule("jarsTosystemModules").Inputs
 
 	// The expected paths are the header jars from the source input modules.
@@ -78,10 +79,11 @@
 `)
 
 func TestJavaSystemModulesImport(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(prepareForJavaTest, addPrebuiltSystemModules).RunTest(t)
 
 	// check the existence of the renamed prebuilt module
-	prebuiltSystemModules := result.ModuleForTests("system-modules", "android_common")
+	prebuiltSystemModules := result.ModuleForTests(t, "system-modules", "android_common")
 	prebuiltInputs := prebuiltSystemModules.Rule("jarsTosystemModules").Inputs
 
 	// The expected paths are the header jars from the renamed prebuilt input modules.
@@ -90,6 +92,7 @@
 }
 
 func TestJavaSystemModulesMixSourceAndPrebuilt(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForJavaTest,
 		addSourceSystemModules,
@@ -97,7 +100,7 @@
 	).RunTest(t)
 
 	// check the existence of the source module
-	sourceSystemModules := result.ModuleForTests("system-modules", "android_common")
+	sourceSystemModules := result.ModuleForTests(t, "system-modules", "android_common")
 	sourceInputs := sourceSystemModules.Rule("jarsTosystemModules").Inputs
 
 	// The expected paths are the header jars from the source input modules.
@@ -105,7 +108,7 @@
 	android.AssertArrayString(t, "source system modules inputs", expectedSourcePaths, sourceInputs.RelativeToTop().Strings())
 
 	// check the existence of the renamed prebuilt module
-	prebuiltSystemModules := result.ModuleForTests("prebuilt_system-modules", "android_common")
+	prebuiltSystemModules := result.ModuleForTests(t, "prebuilt_system-modules", "android_common")
 	prebuiltInputs := prebuiltSystemModules.Rule("jarsTosystemModules").Inputs
 
 	// The expected paths are the header jars from the renamed prebuilt input modules.
@@ -114,6 +117,7 @@
 }
 
 func TestMultipleSystemModulesPrebuilts(t *testing.T) {
+	t.Parallel()
 	bp := `
 		// an rdep
 		java_library {
diff --git a/java/systemserver_classpath_fragment.go b/java/systemserver_classpath_fragment.go
index aad1060..6f746b4 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() {
@@ -57,6 +58,10 @@
 	return m
 }
 
+func (m *platformSystemServerClasspathModule) UniqueApexVariations() bool {
+	return true
+}
+
 func (p *platformSystemServerClasspathModule) AndroidMkEntries() (entries []android.AndroidMkEntries) {
 	return p.classpathFragmentBase().androidMkEntries()
 }
@@ -98,12 +103,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 {
@@ -115,8 +120,12 @@
 	return m
 }
 
+func (m *SystemServerClasspathModule) UniqueApexVariations() bool {
+	return true
+}
+
 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 +161,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 +193,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.
@@ -217,16 +226,11 @@
 	return true
 }
 
-// Contents of system server fragments in an apex are considered to be directly in the apex, as if
-// they were listed in java_libs.
-func (systemServerClasspathFragmentContentDependencyTag) CopyDirectlyInAnyApex() {}
-
 // Contents of system server fragments require files from prebuilt apex files.
 func (systemServerClasspathFragmentContentDependencyTag) RequiresFilesFromPrebuiltApex() {}
 
 var _ android.ReplaceSourceWithPrebuilt = systemServerClasspathFragmentContentDepTag
 var _ android.SdkMemberDependencyTag = systemServerClasspathFragmentContentDepTag
-var _ android.CopyDirectlyInAnyApexTag = systemServerClasspathFragmentContentDepTag
 var _ android.RequiresFilesFromPrebuiltApexTag = systemServerClasspathFragmentContentDepTag
 
 // The tag used for the dependency between the systemserverclasspath_fragment module and its contents.
@@ -245,8 +249,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 +264,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 +306,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/java/systemserver_classpath_fragment_test.go b/java/systemserver_classpath_fragment_test.go
index ba328e7..704f5a4 100644
--- a/java/systemserver_classpath_fragment_test.go
+++ b/java/systemserver_classpath_fragment_test.go
@@ -25,6 +25,7 @@
 )
 
 func TestPlatformSystemServerClasspathVariant(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForTestWithSystemServerClasspath,
 		android.FixtureWithRootAndroidBp(`
@@ -39,6 +40,7 @@
 }
 
 func TestPlatformSystemServerClasspath_ClasspathFragmentPaths(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForTestWithSystemServerClasspath,
 		android.FixtureWithRootAndroidBp(`
@@ -50,10 +52,11 @@
 
 	p := result.Module("platform-systemserverclasspath", "android_common").(*platformSystemServerClasspathModule)
 	android.AssertStringEquals(t, "output filepath", "systemserverclasspath.pb", p.ClasspathFragmentBase.outputFilepath.Base())
-	android.AssertPathRelativeToTopEquals(t, "install filepath", "out/soong/target/product/test_device/system/etc/classpaths", p.ClasspathFragmentBase.installDirPath)
+	android.AssertPathRelativeToTopEquals(t, "install filepath", "out/target/product/test_device/system/etc/classpaths", p.ClasspathFragmentBase.installDirPath)
 }
 
 func TestPlatformSystemServerClasspathModule_AndroidMkEntries(t *testing.T) {
+	t.Parallel()
 	preparer := android.GroupFixturePreparers(
 		prepareForTestWithSystemServerClasspath,
 		android.FixtureWithRootAndroidBp(`
@@ -97,6 +100,7 @@
 }
 
 func TestSystemServerClasspathFragmentWithoutContents(t *testing.T) {
+	t.Parallel()
 	prepareForTestWithSystemServerClasspath.
 		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
 			`\QEither contents or standalone_contents needs to be non-empty\E`)).
diff --git a/java/test_spec_test.go b/java/test_spec_test.go
deleted file mode 100644
index f0a5fdb..0000000
--- a/java/test_spec_test.go
+++ /dev/null
@@ -1,122 +0,0 @@
-package java
-
-import (
-	"strings"
-	"testing"
-
-	"android/soong/android"
-	soongTesting "android/soong/testing"
-	"android/soong/testing/test_spec_proto"
-	"google.golang.org/protobuf/proto"
-)
-
-func TestTestSpec(t *testing.T) {
-	bp := `test_spec {
-		name: "module-name",
-		teamId: "12345",
-		tests: [
-			"java-test-module-name-one",
-			"java-test-module-name-two"
-		]
-	}
-
-	java_test {
-		name: "java-test-module-name-one",
-	}
-
-	java_test {
-		name: "java-test-module-name-two",
-	}`
-	result := runTestSpecTest(t, android.FixtureExpectsNoErrors, bp)
-
-	module := result.ModuleForTests("module-name", "")
-
-	// Check that the provider has the right contents
-	data, _ := android.OtherModuleProvider(result, module.Module(), soongTesting.TestSpecProviderKey)
-	if !strings.HasSuffix(
-		data.IntermediatePath.String(), "/intermediateTestSpecMetadata.pb",
-	) {
-		t.Errorf(
-			"Missing intermediates path in provider: %s",
-			data.IntermediatePath.String(),
-		)
-	}
-
-	metadata := android.ContentFromFileRuleForTests(t, result.TestContext,
-		module.Output(data.IntermediatePath.String()))
-
-	metadataList := make([]*test_spec_proto.TestSpec_OwnershipMetadata, 0, 2)
-	teamId := "12345"
-	bpFilePath := "Android.bp"
-	targetNames := []string{
-		"java-test-module-name-one", "java-test-module-name-two",
-	}
-
-	for _, test := range targetNames {
-		targetName := test
-		metadata := test_spec_proto.TestSpec_OwnershipMetadata{
-			TrendyTeamId: &teamId,
-			TargetName:   &targetName,
-			Path:         &bpFilePath,
-		}
-		metadataList = append(metadataList, &metadata)
-	}
-	testSpecMetadata := test_spec_proto.TestSpec{OwnershipMetadataList: metadataList}
-	protoData, _ := proto.Marshal(&testSpecMetadata)
-	expectedMetadata := string(protoData)
-
-	if metadata != expectedMetadata {
-		t.Errorf(
-			"Retrieved metadata: %s doesn't contain expectedMetadata: %s", metadata,
-			expectedMetadata,
-		)
-	}
-
-	// Tests for all_test_spec singleton.
-	singleton := result.SingletonForTests("all_test_specs")
-	rule := singleton.Rule("all_test_specs_rule")
-	prebuiltOs := result.Config.PrebuiltOS()
-	expectedCmd := "out/soong/host/" + prebuiltOs + "/bin/metadata -rule test_spec -inputFile out/soong/all_test_spec_paths.rsp -outputFile out/soong/ownership/all_test_specs.pb"
-	expectedOutputFile := "out/soong/ownership/all_test_specs.pb"
-	expectedInputFile := "out/soong/.intermediates/module-name/intermediateTestSpecMetadata.pb"
-	if !strings.Contains(
-		strings.TrimSpace(rule.Output.String()),
-		expectedOutputFile,
-	) {
-		t.Errorf(
-			"Retrieved singletonOutputFile: %s is not equal to expectedSingletonOutputFile: %s",
-			rule.Output.String(), expectedOutputFile,
-		)
-	}
-
-	if !strings.Contains(
-		strings.TrimSpace(rule.Inputs[0].String()),
-		expectedInputFile,
-	) {
-		t.Errorf(
-			"Retrieved singletonInputFile: %s is not equal to expectedSingletonInputFile: %s",
-			rule.Inputs[0].String(), expectedInputFile,
-		)
-	}
-
-	if !strings.Contains(
-		strings.TrimSpace(rule.RuleParams.Command),
-		expectedCmd,
-	) {
-		t.Errorf(
-			"Retrieved cmd: %s is not equal to expectedCmd: %s",
-			rule.RuleParams.Command, expectedCmd,
-		)
-	}
-}
-
-func runTestSpecTest(
-	t *testing.T, errorHandler android.FixtureErrorHandler, bp string,
-) *android.TestResult {
-	return android.GroupFixturePreparers(
-		soongTesting.PrepareForTestWithTestingBuildComponents,
-		PrepareForIntegrationTestWithJava,
-	).
-		ExtendWithErrorHandler(errorHandler).
-		RunTestWithBp(t, bp)
-}
diff --git a/java/testing.go b/java/testing.go
index 988514d..35319ae 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -18,7 +18,6 @@
 	"fmt"
 	"reflect"
 	"regexp"
-	"sort"
 	"strings"
 	"testing"
 
@@ -190,6 +189,7 @@
 				"//apex_available:anyapex",
 				"//apex_available:platform",
 			],
+			compile_dex: true,
 		}
 	`)),
 )
@@ -377,7 +377,6 @@
 	RegisterAppBuildComponents(ctx)
 	RegisterAppImportBuildComponents(ctx)
 	RegisterAppSetBuildComponents(ctx)
-	registerBootclasspathBuildComponents(ctx)
 	registerBootclasspathFragmentBuildComponents(ctx)
 	RegisterDexpreoptBootJarsComponents(ctx)
 	RegisterDocsBuildComponents(ctx)
@@ -427,7 +426,7 @@
 		"stub-annotations",
 
 		"aconfig-annotations-lib",
-		"aconfig_storage_reader_java",
+		"aconfig_storage_stub",
 		"unsupportedappusage",
 	}
 
@@ -606,19 +605,18 @@
 
 func getModuleDependencies(t *testing.T, ctx *android.TestContext, name, variant string) []string {
 	t.Helper()
-	module := ctx.ModuleForTests(name, variant).Module()
+	module := ctx.ModuleForTests(t, name, variant).Module()
 	deps := []string{}
 	ctx.VisitDirectDeps(module, func(m blueprint.Module) {
 		deps = append(deps, m.Name())
 	})
-	sort.Strings(deps)
-
-	return deps
+	return android.SortedUniqueStrings(deps)
 }
 
 // CheckModuleDependencies checks if the expected dependencies of the module are
 // identical to the actual dependencies.
 func CheckModuleDependencies(t *testing.T, ctx *android.TestContext, name, variant string, expected []string) {
+	t.Helper()
 	deps := getModuleDependencies(t, ctx, name, variant)
 
 	if actual := deps; !reflect.DeepEqual(expected, actual) {
@@ -638,7 +636,7 @@
 
 // CheckModuleHasDependency returns true if the module depends on the expected dependency.
 func CheckModuleHasDependencyWithTag(t *testing.T, ctx *android.TestContext, name, variant string, desiredTag blueprint.DependencyTag, expected string) bool {
-	module := ctx.ModuleForTests(name, variant).Module()
+	module := ctx.ModuleForTests(t, name, variant).Module()
 	found := false
 	ctx.VisitDirectDepsWithTags(module, func(m blueprint.Module, tag blueprint.DependencyTag) {
 		if tag == desiredTag && m.Name() == expected {
@@ -653,7 +651,7 @@
 func CheckPlatformBootclasspathModules(t *testing.T, result *android.TestResult, name string, expected []string) {
 	t.Helper()
 	platformBootclasspath := result.Module(name, "android_common").(*platformBootclasspathModule)
-	pairs := ApexNamePairsFromModules(result.TestContext, platformBootclasspath.configuredModules)
+	pairs := apexNamePairsFromModules(result.TestContext, platformBootclasspath.configuredModules, platformBootclasspath.libraryToApex)
 	android.AssertDeepEquals(t, fmt.Sprintf("%s modules", "platform-bootclasspath"), expected, pairs)
 }
 
@@ -668,23 +666,54 @@
 	android.AssertPathRelativeToTopEquals(t, "install filepath", installDir, info.ClasspathFragmentProtoInstallDir)
 }
 
-// ApexNamePairsFromModules returns the apex:module pair for the supplied modules.
-func ApexNamePairsFromModules(ctx *android.TestContext, modules []android.Module) []string {
+// CheckPlatformBootclasspathDependencies checks the dependencies of the selected module against the expected list.
+//
+// The expected list must be a list of strings of the form "<apex>:<module>", where <apex> is the
+// name of the apex, or platform is it is not part of an apex and <module> is the module name.
+func CheckPlatformBootclasspathDependencies(t *testing.T, ctx *android.TestContext, name, variant string, expected []string) {
+	t.Helper()
+	platformBootclasspath := ctx.ModuleForTests(t, name, variant).Module().(*platformBootclasspathModule)
+	modules := []android.Module{}
+	ctx.VisitDirectDeps(platformBootclasspath, func(m blueprint.Module) {
+		modules = append(modules, m.(android.Module))
+	})
+
+	pairs := apexNamePairsFromModules(ctx, modules, platformBootclasspath.libraryToApex)
+	android.AssertDeepEquals(t, "module dependencies", expected, pairs)
+}
+
+// apexNamePairsFromModules returns the apex:module pair for the supplied modules.
+func apexNamePairsFromModules(ctx *android.TestContext, modules []android.Module, modulesToApex map[android.Module]string) []string {
 	pairs := []string{}
 	for _, module := range modules {
-		pairs = append(pairs, apexNamePairFromModule(ctx, module))
+		pairs = append(pairs, apexNamePairFromModule(ctx, module, modulesToApex))
 	}
 	return pairs
 }
 
-func apexNamePairFromModule(ctx *android.TestContext, module android.Module) string {
+// ApexFragmentPairsFromModules returns the apex:fragment pair for the supplied fragments.
+func ApexFragmentPairsFromModules(ctx *android.TestContext, fragments []android.Module, apexNameToFragment map[string]android.Module) []string {
+	pairs := []string{}
+	for _, fragment := range fragments {
+		found := false
+		for apex, apexFragment := range apexNameToFragment {
+			if apexFragment == fragment {
+				pairs = append(pairs, apex+":"+ctx.ModuleName(fragment))
+				found = true
+			}
+		}
+		if !found {
+			pairs = append(pairs, "platform:"+ctx.ModuleName(fragment))
+		}
+	}
+	return pairs
+}
+
+func apexNamePairFromModule(ctx *android.TestContext, module android.Module, modulesToApex map[android.Module]string) string {
 	name := module.Name()
-	var apex string
-	apexInfo, _ := android.OtherModuleProvider(ctx, module, android.ApexInfoProvider)
-	if apexInfo.IsForPlatform() {
+	apex := modulesToApex[module]
+	if apex == "" {
 		apex = "platform"
-	} else {
-		apex = apexInfo.InApexVariants[0]
 	}
 
 	return fmt.Sprintf("%s:%s", apex, name)
@@ -695,7 +724,7 @@
 func CheckPlatformBootclasspathFragments(t *testing.T, result *android.TestResult, name string, expected []string) {
 	t.Helper()
 	platformBootclasspath := result.Module(name, "android_common").(*platformBootclasspathModule)
-	pairs := ApexNamePairsFromModules(result.TestContext, platformBootclasspath.fragments)
+	pairs := ApexFragmentPairsFromModules(result.TestContext, platformBootclasspath.fragments, platformBootclasspath.apexNameToFragment)
 	android.AssertDeepEquals(t, fmt.Sprintf("%s fragments", "platform-bootclasspath"), expected, pairs)
 }
 
@@ -718,7 +747,7 @@
 
 // Check that the merged file create by platform_compat_config_singleton has the correct inputs.
 func CheckMergedCompatConfigInputs(t *testing.T, result *android.TestResult, message string, expectedPaths ...string) {
-	sourceGlobalCompatConfig := result.SingletonForTests("platform_compat_config_singleton")
+	sourceGlobalCompatConfig := result.SingletonForTests(t, "platform_compat_config_singleton")
 	allOutputs := sourceGlobalCompatConfig.AllOutputs()
 	android.AssertIntEquals(t, message+": output len", 1, len(allOutputs))
 	output := sourceGlobalCompatConfig.Output(allOutputs[0])
diff --git a/kernel/prebuilt_kernel_modules.go b/kernel/prebuilt_kernel_modules.go
index e200ee2..1225da0 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,18 +47,36 @@
 	// 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"`
+
+	// Path to the kernel module options file
+	Options_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
 
 	// Whether this module is directly installable to one of the partitions. Default is true
 	Installable *bool
+
+	// Whether debug symbols should be stripped from the *.ko files.
+	// Defaults to true.
+	Strip_debug_symbols *bool
 }
 
 // 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,23 +99,66 @@
 	if !pkm.installable() {
 		pkm.SkipInstall()
 	}
-	modules := android.PathsForModuleSrc(ctx, pkm.properties.Srcs)
 
-	depmodOut := runDepmod(ctx, modules)
-	strippedModules := stripDebugSymbols(ctx, modules)
+	modules := android.PathsForModuleSrc(ctx, pkm.properties.Srcs)
+	systemModules := android.PathsForModuleSrc(ctx, pkm.properties.System_deps)
+
+	depmodOut := pkm.runDepmod(ctx, modules, systemModules)
+	if proptools.BoolDefault(pkm.properties.Strip_debug_symbols, true) {
+		modules = stripDebugSymbols(ctx, modules)
+	}
 
 	installDir := android.PathForModuleInstall(ctx, "lib", "modules")
+	// Kernel module is installed to vendor_ramdisk/lib/modules regardless of product
+	// configuration. This matches the behavior in make and prevents the files from being
+	// installed in `vendor_ramdisk/first_stage_ramdisk`.
+	if pkm.InstallInVendorRamdisk() {
+		installDir = android.PathForModuleInPartitionInstall(ctx, "vendor_ramdisk", "lib", "modules")
+	}
+
 	if pkm.KernelVersion() != "" {
 		installDir = installDir.Join(ctx, pkm.KernelVersion())
 	}
 
-	for _, m := range strippedModules {
+	for _, m := range modules {
 		ctx.InstallFile(installDir, filepath.Base(m.String()), m)
 	}
 	ctx.InstallFile(installDir, "modules.load", depmodOut.modulesLoad)
 	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)
+	pkm.installOptionsFile(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)
+}
+
+func (pkm *prebuiltKernelModules) installOptionsFile(ctx android.ModuleContext, installDir android.InstallPath) {
+	if pkm.properties.Options_file == nil {
+		return
+	}
+	optionsOut := android.PathForModuleOut(ctx, "modules.options")
+
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   processOptionsFile,
+		Input:  android.PathForModuleSrc(ctx, proptools.String(pkm.properties.Options_file)),
+		Output: optionsOut,
+	})
+	ctx.InstallFile(installDir, "modules.options", optionsOut)
 }
 
 var (
@@ -110,9 +171,9 @@
 		}, "stripCmd")
 )
 
-func stripDebugSymbols(ctx android.ModuleContext, modules android.Paths) android.OutputPaths {
+func stripDebugSymbols(ctx android.ModuleContext, modules android.Paths) android.Paths {
 	dir := android.PathForModuleOut(ctx, "stripped").OutputPath
-	var outputs android.OutputPaths
+	var outputs android.Paths
 
 	for _, m := range modules {
 		stripped := dir.Join(ctx, filepath.Base(m.String()))
@@ -137,35 +198,108 @@
 	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 }'`,
+		},
+	)
+	// Remove empty lines. Raise an exception if line is _not_ formatted as `options $name.ko`
+	processOptionsFile = pctx.AndroidStaticRule("process_options_file",
+		blueprint.RuleParams{
+			Command: `rm -rf $out && awk <$in > $out` +
+				` '/^#/ { print; next }` +
+				` NF == 0 { next }` +
+				` NF < 2 || $$1 != "options"` +
+				` { print "Invalid options 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 if ctx.InstallInVendorRamdisk() {
+		return modulesDir.Join(ctx, "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 +310,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 and vendor ramdisk
+	if ctx.InstallInSystemDlkm() || ctx.InstallInVendorDlkm() || ctx.InstallInOdmDlkm() || ctx.InstallInVendorRamdisk() {
+		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/kernel/prebuilt_kernel_modules_test.go b/kernel/prebuilt_kernel_modules_test.go
index 7b81869..0fc2720 100644
--- a/kernel/prebuilt_kernel_modules_test.go
+++ b/kernel/prebuilt_kernel_modules_test.go
@@ -50,7 +50,7 @@
 
 	var actual []string
 	for _, ps := range android.OtherModuleProviderOrDefault(
-		ctx, ctx.ModuleForTests("foo", "android_arm64_armv8-a").Module(), android.InstallFilesProvider).PackagingSpecs {
+		ctx, ctx.ModuleForTests(t, "foo", "android_arm64_armv8-a").Module(), android.InstallFilesProvider).PackagingSpecs {
 		actual = append(actual, ps.RelPathInPackage())
 	}
 	actual = android.SortedUniqueStrings(actual)
diff --git a/licenses/Android.bp b/licenses/Android.bp
index e4e5da7..f420110 100644
--- a/licenses/Android.bp
+++ b/licenses/Android.bp
@@ -32,6 +32,17 @@
 }
 
 license_kind {
+    name: "BSD-Binary-Only",
+    conditions: [
+        "notice",
+        "by_exception_only",
+        "proprietary",
+    ],
+}
+
+// Deprecated. All users of the following license should be changed to
+// BSD-Binary-Only and it should be removed.
+license_kind {
     name: "BSD-Like-Binary-Only",
     conditions: [
         "notice",
diff --git a/linkerconfig/linkerconfig.go b/linkerconfig/linkerconfig.go
index 05b99fd..4f1ef9d 100644
--- a/linkerconfig/linkerconfig.go
+++ b/linkerconfig/linkerconfig.go
@@ -76,9 +76,7 @@
 	input := android.PathForModuleSrc(ctx, android.String(l.properties.Src))
 	output := android.PathForModuleOut(ctx, "linker.config.pb").OutputPath
 
-	builder := android.NewRuleBuilder(pctx, ctx)
-	BuildLinkerConfig(ctx, builder, input, nil, nil, output)
-	builder.Build("conv_linker_config", "Generate linker config protobuf "+output.String())
+	BuildLinkerConfig(ctx, android.Paths{input}, nil, nil, output)
 
 	l.outputFilePath = output
 	l.installDirPath = android.PathForModuleInstall(ctx, "etc")
@@ -90,24 +88,32 @@
 	ctx.SetOutputFiles(android.Paths{l.outputFilePath}, "")
 }
 
-func BuildLinkerConfig(ctx android.ModuleContext, builder *android.RuleBuilder,
-	input android.Path, provideModules []android.Module, requireModules []android.Module, output android.OutputPath) {
-
+func BuildLinkerConfig(
+	ctx android.ModuleContext,
+	inputs android.Paths,
+	provideModules []android.ModuleProxy,
+	requireModules []android.ModuleProxy,
+	output android.WritablePath,
+) {
 	// First, convert the input json to protobuf format
+	builder := android.NewRuleBuilder(pctx, ctx)
 	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
 	for _, m := range provideModules {
-		if c, ok := m.(*cc.Module); ok && (cc.IsStubTarget(c) || c.HasLlndkStubs()) {
+		ccInfo, ok := android.OtherModuleProvider(ctx, m, cc.CcInfoProvider)
+		if ok && (cc.IsStubTarget(android.OtherModuleProviderOrDefault(ctx, m, cc.LinkableInfoProvider)) || ccInfo.HasLlndkStubs) {
 			for _, ps := range android.OtherModuleProviderOrDefault(
-				ctx, c, android.InstallFilesProvider).PackagingSpecs {
+				ctx, m, android.InstallFilesProvider).PackagingSpecs {
 				provideLibs = append(provideLibs, ps.FileName())
 			}
 		}
@@ -117,8 +123,15 @@
 
 	var requireLibs []string
 	for _, m := range requireModules {
-		if c, ok := m.(*cc.Module); ok && c.HasStubsVariants() && !c.Host() {
-			requireLibs = append(requireLibs, c.ImplementationModuleName(ctx)+".so")
+		if _, ok := android.OtherModuleProvider(ctx, m, cc.CcInfoProvider); ok {
+			if android.OtherModuleProviderOrDefault(ctx, m, cc.LinkableInfoProvider).HasStubsVariants &&
+				!android.OtherModuleProviderOrDefault(ctx, m, android.CommonModuleInfoKey).Host {
+				name := ctx.OtherModuleName(m)
+				if ccInfo, ok := android.OtherModuleProvider(ctx, m, cc.CcInfoProvider); ok && ccInfo.LinkerInfo != nil && ccInfo.LinkerInfo.ImplementationModuleName != nil {
+					name = *ccInfo.LinkerInfo.ImplementationModuleName
+				}
+				requireLibs = append(requireLibs, name+".so")
+			}
 		}
 	}
 
@@ -155,6 +168,7 @@
 
 	builder.Temporary(interimOutput)
 	builder.DeleteTemporaryFiles()
+	builder.Build("conv_linker_config_"+output.String(), "Generate linker config protobuf "+output.String())
 }
 
 // linker_config generates protobuf file from json file. This protobuf file will be used from
diff --git a/linkerconfig/linkerconfig_test.go b/linkerconfig/linkerconfig_test.go
index 939e4bb..9e08b19 100644
--- a/linkerconfig/linkerconfig_test.go
+++ b/linkerconfig/linkerconfig_test.go
@@ -46,7 +46,7 @@
 		"LOCAL_INSTALLED_MODULE_STEM": {"linker.config.pb"},
 	}
 
-	p := result.ModuleForTests("linker-config-base", "android_arm64_armv8-a").Module().(*linkerConfig)
+	p := result.ModuleForTests(t, "linker-config-base", "android_arm64_armv8-a").Module().(*linkerConfig)
 
 	if p.outputFilePath.Base() != "linker.config.pb" {
 		t.Errorf("expected linker.config.pb, got %q", p.outputFilePath.Base())
@@ -79,7 +79,7 @@
 
 	expected := []string{"true"}
 
-	p := result.ModuleForTests("linker-config-base", "android_arm64_armv8-a").Module().(*linkerConfig)
+	p := result.ModuleForTests(t, "linker-config-base", "android_arm64_armv8-a").Module().(*linkerConfig)
 	entries := android.AndroidMkEntriesForTest(t, result.TestContext, p)[0]
 	if value, ok := entries.EntryMap["LOCAL_UNINSTALLABLE_MODULE"]; ok {
 		if !reflect.DeepEqual(value, expected) {
diff --git a/phony/phony.go b/phony/phony.go
index 807b95b..4f61c45 100644
--- a/phony/phony.go
+++ b/phony/phony.go
@@ -38,9 +38,11 @@
 
 type phony struct {
 	android.ModuleBase
+
 	requiredModuleNames       []string
 	hostRequiredModuleNames   []string
 	targetRequiredModuleNames []string
+	outputDeps                android.Paths
 }
 
 func PhonyFactory() android.Module {
@@ -54,6 +56,14 @@
 	p.requiredModuleNames = ctx.RequiredModuleNames(ctx)
 	p.hostRequiredModuleNames = ctx.HostRequiredModuleNames()
 	p.targetRequiredModuleNames = ctx.TargetRequiredModuleNames()
+
+	ctx.VisitDirectDepsWithTag(android.RequiredDepTag, func(dep android.Module) {
+		if o, ok := android.OtherModuleProvider(ctx, dep, android.OutputFilesProvider); ok {
+			p.outputDeps = append(p.outputDeps, o.DefaultOutputFiles...)
+		}
+	})
+
+	ctx.Phony(p.Name(), p.outputDeps...)
 }
 
 func (p *phony) AndroidMk() android.AndroidMkData {
@@ -77,6 +87,10 @@
 				fmt.Fprintln(w, "LOCAL_TARGET_REQUIRED_MODULES :=",
 					strings.Join(p.targetRequiredModuleNames, " "))
 			}
+			if len(p.outputDeps) > 0 {
+				fmt.Fprintln(w, "LOCAL_ADDITIONAL_DEPENDENCIES :=",
+					strings.Join(p.outputDeps.Strings(), " "))
+			}
 			// AconfigUpdateAndroidMkData may have added elements to Extra.  Process them here.
 			for _, extra := range data.Extra {
 				extra(w, nil)
diff --git a/provenance/provenance_singleton.go b/provenance/provenance_singleton.go
index 679632c..c1bc1c7 100644
--- a/provenance/provenance_singleton.go
+++ b/provenance/provenance_singleton.go
@@ -46,7 +46,7 @@
 )
 
 type ProvenanceMetadata interface {
-	ProvenanceMetaDataFile() android.OutputPath
+	ProvenanceMetaDataFile() android.Path
 }
 
 func init() {
@@ -74,7 +74,7 @@
 			return false
 		}
 		if p, ok := module.(ProvenanceMetadata); ok {
-			return p.ProvenanceMetaDataFile().String() != ""
+			return p.ProvenanceMetaDataFile() != nil
 		}
 		return false
 	}
@@ -99,9 +99,10 @@
 	})
 
 	context.Phony("droidcore", android.PathForPhony(context, "provenance_metadata"))
+	context.DistForGoal("droidcore", p.mergedMetaDataFile)
 }
 
-func GenerateArtifactProvenanceMetaData(ctx android.ModuleContext, artifactPath android.Path, installedFile android.InstallPath) android.OutputPath {
+func GenerateArtifactProvenanceMetaData(ctx android.ModuleContext, artifactPath android.Path, installedFile android.InstallPath) android.Path {
 	onDevicePathOfInstalledFile := android.InstallPathToOnDevicePath(ctx, installedFile)
 	artifactMetaDataFile := android.PathForIntermediates(ctx, "provenance_metadata", ctx.ModuleDir(), ctx.ModuleName(), "provenance_metadata.textproto")
 	ctx.Build(pctx, android.BuildParams{
@@ -116,9 +117,3 @@
 
 	return artifactMetaDataFile
 }
-
-func (p *provenanceInfoSingleton) MakeVars(ctx android.MakeVarsContext) {
-	ctx.DistForGoal("droidcore", p.mergedMetaDataFile)
-}
-
-var _ android.SingletonMakeVarsProvider = (*provenanceInfoSingleton)(nil)
diff --git a/provenance/provenance_singleton_test.go b/provenance/provenance_singleton_test.go
index 0f1eae2..05f3474 100644
--- a/provenance/provenance_singleton_test.go
+++ b/provenance/provenance_singleton_test.go
@@ -28,9 +28,9 @@
 		PrepareForTestWithProvenanceSingleton,
 		android.PrepareForTestWithAndroidMk).RunTestWithBp(t, "")
 
-	outputs := result.SingletonForTests("provenance_metadata_singleton").AllOutputs()
+	outputs := result.SingletonForTests(t, "provenance_metadata_singleton").AllOutputs()
 	for _, output := range outputs {
-		testingBuildParam := result.SingletonForTests("provenance_metadata_singleton").Output(output)
+		testingBuildParam := result.SingletonForTests(t, "provenance_metadata_singleton").Output(output)
 		switch {
 		case strings.Contains(output, "soong/provenance_metadata.textproto"):
 			android.AssertStringEquals(t, "Invalid build rule", "android/soong/provenance.mergeProvenanceMetaData", testingBuildParam.Rule.String())
diff --git a/python/Android.bp b/python/Android.bp
index 14e83c1..3b54455 100644
--- a/python/Android.bp
+++ b/python/Android.bp
@@ -10,7 +10,6 @@
         "soong-android",
         "soong-tradefed",
         "soong-cc",
-        "soong-testing",
     ],
     srcs: [
         "binary.go",
diff --git a/python/binary.go b/python/binary.go
index 5f60761..4d6e118 100644
--- a/python/binary.go
+++ b/python/binary.go
@@ -22,8 +22,15 @@
 	"strings"
 
 	"android/soong/android"
+	"android/soong/cc"
+
+	"github.com/google/blueprint"
 )
 
+type PythonBinaryInfo struct{}
+
+var PythonBinaryInfoProvider = blueprint.NewProvider[PythonBinaryInfo]()
+
 func init() {
 	registerPythonBinaryComponents(android.InitRegistrationContext)
 }
@@ -103,6 +110,9 @@
 	p.buildBinary(ctx)
 	p.installedDest = ctx.InstallFile(installDir(ctx, "bin", "", ""),
 		p.installSource.Base(), p.installSource)
+
+	android.SetProvider(ctx, PythonBinaryInfoProvider, PythonBinaryInfo{})
+
 	ctx.SetOutputFiles(android.Paths{p.installSource}, "")
 }
 
@@ -116,13 +126,13 @@
 
 	var launcherPath android.OptionalPath
 	if embeddedLauncher {
-		ctx.VisitDirectDepsWithTag(launcherTag, func(m android.Module) {
-			if provider, ok := m.(IntermPathProvider); ok {
+		ctx.VisitDirectDepsProxyWithTag(launcherTag, func(m android.ModuleProxy) {
+			if provider, ok := android.OtherModuleProvider(ctx, m, cc.LinkableInfoProvider); ok {
 				if launcherPath.Valid() {
 					panic(fmt.Errorf("launcher path was found before: %q",
 						launcherPath))
 				}
-				launcherPath = provider.IntermPathForModuleOut()
+				launcherPath = provider.OutputFile
 			}
 		})
 	}
@@ -134,13 +144,12 @@
 	}
 	srcsZips = append(srcsZips, depsSrcsZips...)
 	p.installSource = registerBuildActionForParFile(ctx, embeddedLauncher, launcherPath,
-		p.getHostInterpreterName(ctx, p.properties.Actual_version),
-		main, p.getStem(ctx), srcsZips)
+		"python3", main, p.getStem(ctx), srcsZips)
 
 	var sharedLibs []string
 	// if embedded launcher is enabled, we need to collect the shared library dependencies of the
 	// launcher
-	for _, dep := range ctx.GetDirectDepsWithTag(launcherSharedLibTag) {
+	for _, dep := range ctx.GetDirectDepsProxyWithTag(launcherSharedLibTag) {
 		sharedLibs = append(sharedLibs, ctx.OtherModuleName(dep))
 	}
 	p.androidMkSharedLibs = sharedLibs
@@ -196,23 +205,6 @@
 	return BoolDefault(b.binaryProperties.Autorun, true)
 }
 
-// get host interpreter name.
-func (p *PythonBinaryModule) getHostInterpreterName(ctx android.ModuleContext,
-	actualVersion string) string {
-	var interp string
-	switch actualVersion {
-	case pyVersion2:
-		interp = "python2.7"
-	case pyVersion3:
-		interp = "python3"
-	default:
-		panic(fmt.Errorf("unknown Python actualVersion: %q for module: %q.",
-			actualVersion, ctx.ModuleName()))
-	}
-
-	return interp
-}
-
 // find main program path within runfiles tree.
 func (p *PythonBinaryModule) getPyMainFile(ctx android.ModuleContext,
 	srcsPathMappings []pathMapping) string {
diff --git a/python/defaults.go b/python/defaults.go
index 3dc5bc4..b5ee2bc 100644
--- a/python/defaults.go
+++ b/python/defaults.go
@@ -18,10 +18,6 @@
 	"android/soong/android"
 )
 
-func init() {
-	android.RegisterModuleType("python_defaults", DefaultsFactory)
-}
-
 type Defaults struct {
 	android.ModuleBase
 	android.DefaultsModuleBase
diff --git a/python/library.go b/python/library.go
index 7cdb80b..c197028 100644
--- a/python/library.go
+++ b/python/library.go
@@ -27,6 +27,7 @@
 func registerPythonLibraryComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("python_library_host", PythonLibraryHostFactory)
 	ctx.RegisterModuleType("python_library", PythonLibraryFactory)
+	ctx.RegisterModuleType("python_defaults", DefaultsFactory)
 }
 
 func PythonLibraryHostFactory() android.Module {
diff --git a/python/python.go b/python/python.go
index 01ac86c..f8f4165 100644
--- a/python/python.go
+++ b/python/python.go
@@ -22,24 +22,23 @@
 	"regexp"
 	"strings"
 
+	"android/soong/cc"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
 )
 
-func init() {
-	registerPythonMutators(android.InitRegistrationContext)
+type PythonLibraryInfo struct {
+	SrcsPathMappings   []pathMapping
+	DataPathMappings   []pathMapping
+	SrcsZip            android.Path
+	PrecompiledSrcsZip android.Path
+	PkgPath            string
 }
 
-func registerPythonMutators(ctx android.RegistrationContext) {
-	ctx.PreDepsMutators(RegisterPythonPreDepsMutators)
-}
-
-// Exported to support other packages using Python modules in tests.
-func RegisterPythonPreDepsMutators(ctx android.RegisterMutatorsContext) {
-	ctx.Transition("python_version", &versionSplitTransitionMutator{})
-}
+var PythonLibraryInfoProvider = blueprint.NewProvider[PythonLibraryInfo]()
 
 // the version-specific properties that apply to python modules.
 type VersionProperties struct {
@@ -90,6 +89,16 @@
 	// the test. the file extension can be arbitrary except for (.py).
 	Data []string `android:"path,arch_variant"`
 
+	// Same as data, but will add dependencies on modules using the device's os variation and
+	// the common arch variation. Useful for a host test that wants to embed a module built for
+	// device.
+	Device_common_data []string `android:"path_device_common"`
+
+	// Same as data, but will add dependencies on modules via a device os variation and the
+	// device's first supported arch's variation. Useful for a host test that wants to embed a
+	// module built for device.
+	Device_first_data []string `android:"path_device_first"`
+
 	// list of java modules that provide data that should be installed alongside the test.
 	Java_data []string
 
@@ -106,18 +115,14 @@
 		Py3 VersionProperties `android:"arch_variant"`
 	} `android:"arch_variant"`
 
-	// the actual version each module uses after variations created.
-	// this property name is hidden from users' perspectives, and soong will populate it during
-	// runtime.
-	Actual_version string `blueprint:"mutated"`
-
-	// whether the module is required to be built with actual_version.
-	// this is set by the python version mutator based on version-specific properties
+	// This enabled property is to accept the collapsed enabled property from the VersionProperties.
+	// It is unused now, as all builds should be python3.
 	Enabled *bool `blueprint:"mutated"`
 
-	// whether the binary is required to be built with embedded launcher for this actual_version.
-	// this is set by the python version mutator based on version-specific properties
-	Embedded_launcher *bool `blueprint:"mutated"`
+	// whether the binary is required to be built with an embedded python interpreter, defaults to
+	// true. This allows taking the resulting binary outside of the build and running it on machines
+	// that don't have python installed or may have an older version of python.
+	Embedded_launcher *bool
 }
 
 // Used to store files of current module after expanding dependencies
@@ -163,16 +168,6 @@
 	}
 }
 
-// interface implemented by Python modules to provide source and data mappings and zip to python
-// modules that depend on it
-type pythonDependency interface {
-	getSrcsPathMappings() []pathMapping
-	getDataPathMappings() []pathMapping
-	getSrcsZip() android.Path
-	getPrecompiledSrcsZip() android.Path
-	getPkgPath() string
-}
-
 // getSrcsPathMappings gets this module's path mapping of src source path : runfiles destination
 func (p *PythonLibraryModule) getSrcsPathMappings() []pathMapping {
 	return p.srcsPathMappings
@@ -202,8 +197,6 @@
 	return &p.properties
 }
 
-var _ pythonDependency = (*PythonLibraryModule)(nil)
-
 func (p *PythonLibraryModule) init() android.Module {
 	p.AddProperties(&p.properties, &p.protoProperties, &p.sourceProperties)
 	android.InitAndroidArchModule(p, p.hod, p.multilib)
@@ -243,8 +236,6 @@
 	pathComponentRegexp      = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_-]*$`)
 	pyExt                    = ".py"
 	protoExt                 = ".proto"
-	pyVersion2               = "PY2"
-	pyVersion3               = "PY3"
 	internalPath             = "internal"
 )
 
@@ -252,71 +243,6 @@
 	getBaseProperties() *BaseProperties
 }
 
-type versionSplitTransitionMutator struct{}
-
-func (versionSplitTransitionMutator) Split(ctx android.BaseModuleContext) []string {
-	if base, ok := ctx.Module().(basePropertiesProvider); ok {
-		props := base.getBaseProperties()
-		var variants []string
-		// PY3 is first so that we alias the PY3 variant rather than PY2 if both
-		// are available
-		if proptools.BoolDefault(props.Version.Py3.Enabled, true) {
-			variants = append(variants, pyVersion3)
-		}
-		if proptools.BoolDefault(props.Version.Py2.Enabled, false) {
-			if ctx.ModuleName() != "py2-cmd" &&
-				ctx.ModuleName() != "py2-stdlib" {
-				ctx.PropertyErrorf("version.py2.enabled", "Python 2 is no longer supported, please convert to python 3.")
-			}
-			variants = append(variants, pyVersion2)
-		}
-		return variants
-	}
-	return []string{""}
-}
-
-func (versionSplitTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
-	return ""
-}
-
-func (versionSplitTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
-	if incomingVariation != "" {
-		return incomingVariation
-	}
-	if base, ok := ctx.Module().(basePropertiesProvider); ok {
-		props := base.getBaseProperties()
-		if proptools.BoolDefault(props.Version.Py3.Enabled, true) {
-			return pyVersion3
-		} else {
-			return pyVersion2
-		}
-	}
-
-	return ""
-}
-
-func (versionSplitTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
-	if variation == "" {
-		return
-	}
-	if base, ok := ctx.Module().(basePropertiesProvider); ok {
-		props := base.getBaseProperties()
-		props.Actual_version = variation
-
-		var versionProps *VersionProperties
-		if variation == pyVersion3 {
-			versionProps = &props.Version.Py3
-		} else if variation == pyVersion2 {
-			versionProps = &props.Version.Py2
-		}
-
-		err := proptools.AppendMatchingProperties([]interface{}{props}, versionProps, nil)
-		if err != nil {
-			panic(err)
-		}
-	}
-}
-
 func anyHasExt(paths []string, ext string) bool {
 	for _, p := range paths {
 		if filepath.Ext(p) == ext {
@@ -336,19 +262,26 @@
 //   - if required, specifies launcher and adds launcher dependencies,
 //   - applies python version mutations to Python dependencies
 func (p *PythonLibraryModule) DepsMutator(ctx android.BottomUpMutatorContext) {
-	android.ProtoDeps(ctx, &p.protoProperties)
+	// Flatten the version.py3 props down into the main property struct. Leftover from when
+	// there was both python2 and 3 in the build, and properties could be different between them.
+	if base, ok := ctx.Module().(basePropertiesProvider); ok {
+		props := base.getBaseProperties()
 
-	versionVariation := []blueprint.Variation{
-		{"python_version", p.properties.Actual_version},
+		err := proptools.AppendMatchingProperties([]interface{}{props}, &props.Version.Py3, nil)
+		if err != nil {
+			panic(err)
+		}
 	}
 
+	android.ProtoDeps(ctx, &p.protoProperties)
+
 	// If sources contain a proto file, add dependency on libprotobuf-python
 	if p.anySrcHasExt(ctx, protoExt) && p.Name() != "libprotobuf-python" {
-		ctx.AddVariationDependencies(versionVariation, pythonLibTag, "libprotobuf-python")
+		ctx.AddDependency(ctx.Module(), pythonLibTag, "libprotobuf-python")
 	}
 
 	// Add python library dependencies for this python version variation
-	ctx.AddVariationDependencies(versionVariation, pythonLibTag, android.LastUniqueStrings(p.properties.Libs)...)
+	ctx.AddDependency(ctx.Module(), pythonLibTag, android.LastUniqueStrings(p.properties.Libs)...)
 
 	// Emulate the data property for java_data but with the arch variation overridden to "common"
 	// so that it can point to java modules.
@@ -385,55 +318,38 @@
 		launcherSharedLibDeps = append(launcherSharedLibDeps, "libc_musl")
 	}
 
-	switch p.properties.Actual_version {
-	case pyVersion2:
-		stdLib = "py2-stdlib"
-
-		launcherModule = "py2-launcher"
-		if autorun {
-			launcherModule = "py2-launcher-autorun"
-		}
-
-		launcherSharedLibDeps = append(launcherSharedLibDeps, "libc++")
-	case pyVersion3:
-		var prebuiltStdLib bool
-		if targetForDeps.Os.Bionic() {
-			prebuiltStdLib = false
-		} else if ctx.Config().VendorConfig("cpython3").Bool("force_build_host") {
-			prebuiltStdLib = false
-		} else {
-			prebuiltStdLib = true
-		}
-
-		if prebuiltStdLib {
-			stdLib = "py3-stdlib-prebuilt"
-		} else {
-			stdLib = "py3-stdlib"
-		}
-
-		launcherModule = "py3-launcher"
-		if autorun {
-			launcherModule = "py3-launcher-autorun"
-		}
-		if ctx.Config().HostStaticBinaries() && targetForDeps.Os == android.LinuxMusl {
-			launcherModule += "-static"
-		}
-		if ctx.Device() {
-			launcherSharedLibDeps = append(launcherSharedLibDeps, "liblog")
-		}
-	default:
-		panic(fmt.Errorf("unknown Python Actual_version: %q for module: %q.",
-			p.properties.Actual_version, ctx.ModuleName()))
+	var prebuiltStdLib bool
+	if targetForDeps.Os.Bionic() {
+		prebuiltStdLib = false
+	} else if ctx.Config().VendorConfig("cpython3").Bool("force_build_host") {
+		prebuiltStdLib = false
+	} else {
+		prebuiltStdLib = true
 	}
+
+	if prebuiltStdLib {
+		stdLib = "py3-stdlib-prebuilt"
+	} else {
+		stdLib = "py3-stdlib"
+	}
+
+	launcherModule = "py3-launcher"
+	if autorun {
+		launcherModule = "py3-launcher-autorun"
+	}
+	if ctx.Config().HostStaticBinaries() && targetForDeps.Os == android.LinuxMusl {
+		launcherModule += "-static"
+	}
+	if ctx.Device() {
+		launcherSharedLibDeps = append(launcherSharedLibDeps, "liblog")
+	}
+
 	targetVariations := targetForDeps.Variations()
 	if ctx.ModuleName() != stdLib {
-		stdLibVariations := make([]blueprint.Variation, 0, len(targetVariations)+1)
-		stdLibVariations = append(stdLibVariations, blueprint.Variation{Mutator: "python_version", Variation: p.properties.Actual_version})
-		stdLibVariations = append(stdLibVariations, targetVariations...)
 		// Using AddFarVariationDependencies for all of these because they can be for a different
 		// platform, like if the python module itself was being compiled for device, we may want
 		// the python interpreter built for host so that we can precompile python sources.
-		ctx.AddFarVariationDependencies(stdLibVariations, stdLibTag, stdLib)
+		ctx.AddFarVariationDependencies(targetVariations, stdLibTag, stdLib)
 	}
 	ctx.AddFarVariationDependencies(targetVariations, launcherTag, launcherModule)
 	ctx.AddFarVariationDependencies(targetVariations, launcherSharedLibTag, launcherSharedLibDeps...)
@@ -441,8 +357,10 @@
 
 // GenerateAndroidBuildActions performs build actions common to all Python modules
 func (p *PythonLibraryModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	if proptools.BoolDefault(p.properties.Version.Py2.Enabled, false) {
+		ctx.PropertyErrorf("version.py2.enabled", "Python 2 is no longer supported, please convert to python 3.")
+	}
 	expandedSrcs := android.PathsForModuleSrcExcludes(ctx, p.properties.Srcs, p.properties.Exclude_srcs)
-	android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: expandedSrcs.Strings()})
 	// Keep before any early returns.
 	android.SetProvider(ctx, android.TestOnlyProviderKey, android.TestModuleInformation{
 		TestOnly:       Bool(p.sourceProperties.Test_only),
@@ -451,9 +369,11 @@
 
 	// expand data files from "data" property.
 	expandedData := android.PathsForModuleSrc(ctx, p.properties.Data)
+	expandedData = append(expandedData, android.PathsForModuleSrc(ctx, p.properties.Device_common_data)...)
+	expandedData = append(expandedData, android.PathsForModuleSrc(ctx, p.properties.Device_first_data)...)
 
 	// Emulate the data property for java_data dependencies.
-	for _, javaData := range ctx.GetDirectDepsWithTag(javaDataTag) {
+	for _, javaData := range ctx.GetDirectDepsProxyWithTag(javaDataTag) {
 		expandedData = append(expandedData, android.OutputFilesForModule(ctx, javaData, "")...)
 	}
 
@@ -480,7 +400,16 @@
 
 	// generate the zipfile of all source and data files
 	p.srcsZip = p.createSrcsZip(ctx, pkgPath)
-	p.precompiledSrcsZip = p.precompileSrcs(ctx)
+	// TODO(b/388344853): precompilation temporarily disabled for python3.13 upgrade
+	p.precompiledSrcsZip = p.srcsZip //p.precompileSrcs(ctx)
+
+	android.SetProvider(ctx, PythonLibraryInfoProvider, PythonLibraryInfo{
+		SrcsPathMappings:   p.getSrcsPathMappings(),
+		DataPathMappings:   p.getDataPathMappings(),
+		SrcsZip:            p.getSrcsZip(),
+		PkgPath:            p.getPkgPath(),
+		PrecompiledSrcsZip: p.getPrecompiledSrcsZip(),
+	})
 }
 
 func isValidPythonPath(path string) error {
@@ -646,16 +575,16 @@
 		stdLib = p.srcsZip
 		stdLibPkg = p.getPkgPath()
 	} else {
-		ctx.VisitDirectDepsWithTag(hostStdLibTag, func(module android.Module) {
-			if dep, ok := module.(pythonDependency); ok {
-				stdLib = dep.getPrecompiledSrcsZip()
-				stdLibPkg = dep.getPkgPath()
+		ctx.VisitDirectDepsProxyWithTag(hostStdLibTag, func(module android.ModuleProxy) {
+			if dep, ok := android.OtherModuleProvider(ctx, module, PythonLibraryInfoProvider); ok {
+				stdLib = dep.PrecompiledSrcsZip
+				stdLibPkg = dep.PkgPath
 			}
 		})
 	}
-	ctx.VisitDirectDepsWithTag(hostLauncherTag, func(module android.Module) {
-		if dep, ok := module.(IntermPathProvider); ok {
-			optionalLauncher := dep.IntermPathForModuleOut()
+	ctx.VisitDirectDepsProxyWithTag(hostLauncherTag, func(module android.ModuleProxy) {
+		if dep, ok := android.OtherModuleProvider(ctx, module, cc.LinkableInfoProvider); ok {
+			optionalLauncher := dep.OutputFile
 			if optionalLauncher.Valid() {
 				launcher = optionalLauncher.Path()
 			}
@@ -663,9 +592,9 @@
 	})
 	var launcherSharedLibs android.Paths
 	var ldLibraryPath []string
-	ctx.VisitDirectDepsWithTag(hostlauncherSharedLibTag, func(module android.Module) {
-		if dep, ok := module.(IntermPathProvider); ok {
-			optionalPath := dep.IntermPathForModuleOut()
+	ctx.VisitDirectDepsProxyWithTag(hostlauncherSharedLibTag, func(module android.ModuleProxy) {
+		if dep, ok := android.OtherModuleProvider(ctx, module, cc.LinkableInfoProvider); ok {
+			optionalPath := dep.OutputFile
 			if optionalPath.Valid() {
 				launcherSharedLibs = append(launcherSharedLibs, optionalPath.Path())
 				ldLibraryPath = append(ldLibraryPath, filepath.Dir(optionalPath.Path().String()))
@@ -696,16 +625,6 @@
 	return out
 }
 
-// isPythonLibModule returns whether the given module is a Python library PythonLibraryModule or not
-func isPythonLibModule(module blueprint.Module) bool {
-	if _, ok := module.(*PythonLibraryModule); ok {
-		if _, ok := module.(*PythonBinaryModule); !ok {
-			return true
-		}
-	}
-	return false
-}
-
 // collectPathsFromTransitiveDeps checks for source/data files for duplicate paths
 // for module and its transitive dependencies and collects list of data/source file
 // zips for transitive dependencies.
@@ -726,7 +645,7 @@
 	var result android.Paths
 
 	// visit all its dependencies in depth first.
-	ctx.WalkDeps(func(child, parent android.Module) bool {
+	ctx.WalkDepsProxy(func(child, _ android.ModuleProxy) bool {
 		// we only collect dependencies tagged as python library deps
 		if ctx.OtherModuleDependencyTag(child) != pythonLibTag {
 			return false
@@ -736,27 +655,29 @@
 		}
 		seen[child] = true
 		// Python modules only can depend on Python libraries.
-		if !isPythonLibModule(child) {
+		dep, isLibrary := android.OtherModuleProvider(ctx, child, PythonLibraryInfoProvider)
+		_, isBinary := android.OtherModuleProvider(ctx, child, PythonBinaryInfoProvider)
+		if !isLibrary || isBinary {
 			ctx.PropertyErrorf("libs",
 				"the dependency %q of module %q is not Python library!",
 				ctx.OtherModuleName(child), ctx.ModuleName())
 		}
 		// collect source and data paths, checking that there are no duplicate output file conflicts
-		if dep, ok := child.(pythonDependency); ok {
-			srcs := dep.getSrcsPathMappings()
+		if isLibrary {
+			srcs := dep.SrcsPathMappings
 			for _, path := range srcs {
 				checkForDuplicateOutputPath(ctx, destToPySrcs,
 					path.dest, path.src.String(), ctx.ModuleName(), ctx.OtherModuleName(child))
 			}
-			data := dep.getDataPathMappings()
+			data := dep.DataPathMappings
 			for _, path := range data {
 				checkForDuplicateOutputPath(ctx, destToPyData,
 					path.dest, path.src.String(), ctx.ModuleName(), ctx.OtherModuleName(child))
 			}
 			if precompiled {
-				result = append(result, dep.getPrecompiledSrcsZip())
+				result = append(result, dep.PrecompiledSrcsZip)
 			} else {
-				result = append(result, dep.getSrcsZip())
+				result = append(result, dep.SrcsZip)
 			}
 		}
 		return true
diff --git a/python/python_test.go b/python/python_test.go
index 6a6bd1d..5f971cd 100644
--- a/python/python_test.go
+++ b/python/python_test.go
@@ -36,10 +36,8 @@
 }
 
 var (
-	buildNamePrefix = "soong_python_test"
-	// We allow maching almost anything before the actual variant so that the os/arch variant
-	// is matched.
-	moduleVariantErrTemplate = `%s: module %q variant "[a-zA-Z0-9_]*%s": `
+	buildNamePrefix          = "soong_python_test"
+	moduleVariantErrTemplate = `%s: module %q variant "[a-zA-Z0-9_]*": `
 	pkgPathErrTemplate       = moduleVariantErrTemplate +
 		"pkg_path: %q must be a relative path contained in par file."
 	badIdentifierErrTemplate = moduleVariantErrTemplate +
@@ -48,9 +46,8 @@
 		"found two files to be placed at the same location within zip %q." +
 		" First file: in module %s at path %q." +
 		" Second file: in module %s at path %q."
-	noSrcFileErr      = moduleVariantErrTemplate + "doesn't have any source files!"
-	badSrcFileExtErr  = moduleVariantErrTemplate + "srcs: found non (.py|.proto) file: %q!"
-	badDataFileExtErr = moduleVariantErrTemplate + "data: found (.py) file: %q!"
+	badSrcFileExtErr  = moduleVariantErrTemplate + `srcs: found non \(.py\|.proto\) file: %q!`
+	badDataFileExtErr = moduleVariantErrTemplate + `data: found \(.py\) file: %q!`
 	bpFile            = "Android.bp"
 
 	data = []struct {
@@ -61,20 +58,6 @@
 		expectedBinaries []pyModule
 	}{
 		{
-			desc: "module without any src files",
-			mockFiles: map[string][]byte{
-				filepath.Join("dir", bpFile): []byte(
-					`python_library_host {
-						name: "lib1",
-					}`,
-				),
-			},
-			errors: []string{
-				fmt.Sprintf(noSrcFileErr,
-					"dir/Android.bp:1:1", "lib1", "PY3"),
-			},
-		},
-		{
 			desc: "module with bad src file ext",
 			mockFiles: map[string][]byte{
 				filepath.Join("dir", bpFile): []byte(
@@ -89,7 +72,7 @@
 			},
 			errors: []string{
 				fmt.Sprintf(badSrcFileExtErr,
-					"dir/Android.bp:3:11", "lib1", "PY3", "dir/file1.exe"),
+					"dir/Android.bp:3:11", "lib1", "dir/file1.exe"),
 			},
 		},
 		{
@@ -111,7 +94,7 @@
 			},
 			errors: []string{
 				fmt.Sprintf(badDataFileExtErr,
-					"dir/Android.bp:6:11", "lib1", "PY3", "dir/file2.py"),
+					"dir/Android.bp:6:11", "lib1", "dir/file2.py"),
 			},
 		},
 		{
@@ -146,9 +129,9 @@
 			},
 			errors: []string{
 				fmt.Sprintf(pkgPathErrTemplate,
-					"dir/Android.bp:11:15", "lib2", "PY3", "a/c/../../../"),
+					"dir/Android.bp:11:15", "lib2", "a/c/../../../"),
 				fmt.Sprintf(pkgPathErrTemplate,
-					"dir/Android.bp:19:15", "lib3", "PY3", "/a/c/../../"),
+					"dir/Android.bp:19:15", "lib3", "/a/c/../../"),
 			},
 		},
 		{
@@ -171,11 +154,11 @@
 			},
 			errors: []string{
 				fmt.Sprintf(badIdentifierErrTemplate, "dir/Android.bp:4:11",
-					"lib1", "PY3", "a/b/c/-e/f/file1.py", "-e"),
+					"lib1", "a/b/c/-e/f/file1.py", "-e"),
 				fmt.Sprintf(badIdentifierErrTemplate, "dir/Android.bp:4:11",
-					"lib1", "PY3", "a/b/c/.file1.py", ".file1"),
+					"lib1", "a/b/c/.file1.py", ".file1"),
 				fmt.Sprintf(badIdentifierErrTemplate, "dir/Android.bp:4:11",
-					"lib1", "PY3", "a/b/c/123/file1.py", "123"),
+					"lib1", "a/b/c/123/file1.py", "123"),
 			},
 		},
 		{
@@ -219,115 +202,15 @@
 			},
 			errors: []string{
 				fmt.Sprintf(dupRunfileErrTemplate, "dir/Android.bp:20:6",
-					"bin", "PY3", "a/b/c/file1.py", "bin", "dir/file1.py",
+					"bin", "a/b/c/file1.py", "bin", "dir/file1.py",
 					"lib1", "dir/c/file1.py"),
 			},
 		},
-		{
-			desc: "module for testing dependencies",
-			mockFiles: map[string][]byte{
-				filepath.Join("dir", bpFile): []byte(
-					`python_defaults {
-						name: "default_lib",
-						srcs: [
-							"default.py",
-						],
-						version: {
-							py2: {
-								enabled: true,
-								srcs: [
-									"default_py2.py",
-								],
-							},
-							py3: {
-								enabled: false,
-								srcs: [
-									"default_py3.py",
-								],
-							},
-						},
-					}
-
-					python_library_host {
-						name: "lib5",
-						pkg_path: "a/b/",
-						srcs: [
-							"file1.py",
-						],
-						version: {
-							py2: {
-								enabled: true,
-							},
-							py3: {
-								enabled: true,
-							},
-						},
-					}
-
-					python_library_host {
-						name: "lib6",
-						pkg_path: "c/d/",
-						srcs: [
-							"file2.py",
-						],
-						libs: [
-							"lib5",
-						],
-					}
-
-					python_binary_host {
-						name: "bin",
-						defaults: ["default_lib"],
-						pkg_path: "e/",
-						srcs: [
-							"bin.py",
-						],
-						libs: [
-							"lib5",
-						],
-						version: {
-							py3: {
-								enabled: true,
-								srcs: [
-									"file4.py",
-								],
-								libs: [
-									"lib6",
-								],
-							},
-						},
-					}`,
-				),
-				filepath.Join("dir", "default.py"):     nil,
-				filepath.Join("dir", "default_py2.py"): nil,
-				filepath.Join("dir", "default_py3.py"): nil,
-				filepath.Join("dir", "file1.py"):       nil,
-				filepath.Join("dir", "file2.py"):       nil,
-				filepath.Join("dir", "bin.py"):         nil,
-				filepath.Join("dir", "file4.py"):       nil,
-			},
-			expectedBinaries: []pyModule{
-				{
-					name:          "bin",
-					actualVersion: "PY3",
-					pyRunfiles: []string{
-						"e/default.py",
-						"e/bin.py",
-						"e/default_py3.py",
-						"e/file4.py",
-					},
-					srcsZip: "out/soong/.intermediates/dir/bin/PY3/bin.py.srcszip",
-				},
-			},
-		},
 	}
 )
 
 func TestPythonModule(t *testing.T) {
 	for _, d := range data {
-		if d.desc != "module with duplicate runfile path" {
-			continue
-		}
 		d.mockFiles[filepath.Join("common", bpFile)] = []byte(`
 python_library {
   name: "py3-stdlib",
@@ -416,10 +299,6 @@
 	for i, bp := range testCases {
 		ctx := android.GroupFixturePreparers(
 			PrepareForTestWithPythonBuildComponents,
-			android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
-
-				ctx.RegisterModuleType("python_defaults", DefaultsFactory)
-			}),
 			android.PrepareForTestWithAllowMissingDependencies).
 			ExtendWithErrorHandler(android.FixtureIgnoreErrors).
 			RunTestWithBp(t, bp)
@@ -434,7 +313,7 @@
 }
 
 func expectModule(t *testing.T, ctx *android.TestContext, name, variant, expectedSrcsZip string, expectedPyRunfiles []string) {
-	module := ctx.ModuleForTests(name, variant)
+	module := ctx.ModuleForTests(t, name, variant)
 
 	base, baseOk := module.Module().(*PythonLibraryModule)
 	if !baseOk {
diff --git a/python/test.go b/python/test.go
index 85decf9..5e70fc1 100644
--- a/python/test.go
+++ b/python/test.go
@@ -17,8 +17,6 @@
 import (
 	"fmt"
 
-	"android/soong/testing"
-
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
@@ -65,6 +63,16 @@
 	// the test
 	Data []string `android:"path,arch_variant"`
 
+	// Same as data, but will add dependencies on modules using the device's os variation and
+	// the common arch variation. Useful for a host test that wants to embed a module built for
+	// device.
+	Device_common_data []string `android:"path_device_common"`
+
+	// Same as data, but will add dependencies on modules via a device os variation and the
+	// device's first supported arch's variation. Useful for a host test that wants to embed a
+	// module built for device.
+	Device_first_data []string `android:"path_device_first"`
+
 	// list of java modules that provide data that should be installed alongside the test.
 	Java_data []string
 
@@ -183,15 +191,21 @@
 	for _, dataSrcPath := range android.PathsForModuleSrc(ctx, p.testProperties.Data) {
 		p.data = append(p.data, android.DataPath{SrcPath: dataSrcPath})
 	}
+	for _, dataSrcPath := range android.PathsForModuleSrc(ctx, p.testProperties.Device_common_data) {
+		p.data = append(p.data, android.DataPath{SrcPath: dataSrcPath})
+	}
+	for _, dataSrcPath := range android.PathsForModuleSrc(ctx, p.testProperties.Device_first_data) {
+		p.data = append(p.data, android.DataPath{SrcPath: dataSrcPath})
+	}
 
 	if p.isTestHost() && len(p.testProperties.Data_device_bins_both) > 0 {
-		ctx.VisitDirectDepsWithTag(dataDeviceBinsTag, func(dep android.Module) {
+		ctx.VisitDirectDepsProxyWithTag(dataDeviceBinsTag, func(dep android.ModuleProxy) {
 			p.data = append(p.data, android.DataPath{SrcPath: android.OutputFileForModule(ctx, dep, "")})
 		})
 	}
 
 	// Emulate the data property for java_data dependencies.
-	for _, javaData := range ctx.GetDirectDepsWithTag(javaDataTag) {
+	for _, javaData := range ctx.GetDirectDepsProxyWithTag(javaDataTag) {
 		for _, javaDataSrcPath := range android.OutputFilesForModule(ctx, javaData, "") {
 			p.data = append(p.data, android.DataPath{SrcPath: javaDataSrcPath})
 		}
@@ -201,7 +215,45 @@
 	installedData := ctx.InstallTestData(installDir, p.data)
 	p.installedDest = ctx.InstallFile(installDir, p.installSource.Base(), p.installSource, installedData...)
 
-	android.SetProvider(ctx, testing.TestModuleProviderKey, testing.TestModuleProviderData{})
+	// TODO: Remove the special case for kati
+	if !ctx.Config().KatiEnabled() {
+		// Install the test config in testcases/ directory for atest.
+		// Install configs in the root of $PRODUCT_OUT/testcases/$module
+		testCases := android.PathForModuleInPartitionInstall(ctx, "testcases", ctx.ModuleName())
+		if ctx.PrimaryArch() {
+			if p.testConfig != nil {
+				ctx.InstallFile(testCases, ctx.ModuleName()+".config", p.testConfig)
+			}
+		}
+		// Install tests and data in arch specific subdir $PRODUCT_OUT/testcases/$module/$arch
+		testCases = testCases.Join(ctx, ctx.Target().Arch.ArchType.String())
+		installedData := ctx.InstallTestData(testCases, p.data)
+		ctx.InstallFile(testCases, p.installSource.Base(), p.installSource, installedData...)
+	}
+
+	moduleInfoJSON := ctx.ModuleInfoJSON()
+	moduleInfoJSON.Class = []string{"NATIVE_TESTS"}
+	if len(p.binaryProperties.Test_suites) > 0 {
+		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, p.binaryProperties.Test_suites...)
+	} else {
+		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "null-suite")
+	}
+	if p.testConfig != nil {
+		moduleInfoJSON.TestConfig = append(moduleInfoJSON.TestConfig, p.testConfig.String())
+	}
+	if _, ok := p.testConfig.(android.WritablePath); ok {
+		moduleInfoJSON.AutoTestConfig = []string{"true"}
+	}
+	moduleInfoJSON.TestOptionsTags = append(moduleInfoJSON.TestOptionsTags, p.testProperties.Test_options.Tags...)
+	moduleInfoJSON.Dependencies = append(moduleInfoJSON.Dependencies, p.androidMkSharedLibs...)
+	moduleInfoJSON.SharedLibs = append(moduleInfoJSON.Dependencies, p.androidMkSharedLibs...)
+	moduleInfoJSON.SystemSharedLibs = []string{"none"}
+	if proptools.Bool(p.testProperties.Test_options.Unit_test) {
+		moduleInfoJSON.IsUnitTest = "true"
+		if p.isTestHost() {
+			moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "host-unit-tests")
+		}
+	}
 }
 
 func (p *PythonTestModule) AndroidMkEntries() []android.AndroidMkEntries {
diff --git a/python/testing.go b/python/testing.go
index ce1a5ab..fe53ee5 100644
--- a/python/testing.go
+++ b/python/testing.go
@@ -20,5 +20,4 @@
 	android.FixtureRegisterWithContext(registerPythonBinaryComponents),
 	android.FixtureRegisterWithContext(registerPythonLibraryComponents),
 	android.FixtureRegisterWithContext(registerPythonTestComponents),
-	android.FixtureRegisterWithContext(registerPythonMutators),
 )
diff --git a/python/tests/runtest.sh b/python/tests/runtest.sh
index c44ec58..9167561 100755
--- a/python/tests/runtest.sh
+++ b/python/tests/runtest.sh
@@ -25,15 +25,9 @@
 
 if [[ ( ! -f $ANDROID_HOST_OUT/nativetest64/par_test/par_test ) ||
       ( ! -f $ANDROID_HOST_OUT/bin/py3-cmd )]]; then
-  echo "Run 'm par_test py2-cmd py3-cmd' first"
+  echo "Run 'm par_test py3-cmd' first"
   exit 1
 fi
-if [ $(uname -s) = Linux ]; then
-  if [[ ! -f $ANDROID_HOST_OUT/bin/py2-cmd ]]; then
-    echo "Run 'm par_test py2-cmd py3-cmd' first"
-    exit 1
-  fi
-fi
 
 export LD_LIBRARY_PATH=$ANDROID_HOST_OUT/lib64
 
@@ -47,16 +41,8 @@
 
 cd $(dirname ${BASH_SOURCE[0]})
 
-if [ $(uname -s) = Linux ]; then
-  PYTHONPATH=/extra $ANDROID_HOST_OUT/bin/py2-cmd py-cmd_test.py
-fi
 PYTHONPATH=/extra $ANDROID_HOST_OUT/bin/py3-cmd py-cmd_test.py
 
-if [ $(uname -s) = Linux ]; then
-  ARGTEST=true PYTHONPATH=/extra $ANDROID_HOST_OUT/bin/py2-cmd py-cmd_test.py arg1 arg2
-  ARGTEST2=true PYTHONPATH=/extra $ANDROID_HOST_OUT/bin/py2-cmd py-cmd_test.py --arg1 arg2
-fi
-
 ARGTEST=true PYTHONPATH=/extra $ANDROID_HOST_OUT/bin/py3-cmd py-cmd_test.py arg1 arg2
 ARGTEST2=true PYTHONPATH=/extra $ANDROID_HOST_OUT/bin/py3-cmd py-cmd_test.py --arg1 arg2
 
diff --git a/rust/Android.bp b/rust/Android.bp
index 781f325..54ba9d4 100644
--- a/rust/Android.bp
+++ b/rust/Android.bp
@@ -12,7 +12,6 @@
         "soong-bloaty",
         "soong-cc",
         "soong-rust-config",
-        "soong-testing",
     ],
     srcs: [
         "afdo.go",
diff --git a/rust/afdo.go b/rust/afdo.go
index 6bd4bae..1bec709 100644
--- a/rust/afdo.go
+++ b/rust/afdo.go
@@ -66,7 +66,7 @@
 		return flags, deps
 	}
 
-	ctx.VisitDirectDepsWithTag(cc.FdoProfileTag, func(m android.Module) {
+	ctx.VisitDirectDepsProxyWithTag(cc.FdoProfileTag, func(m android.ModuleProxy) {
 		if info, ok := android.OtherModuleProvider(ctx, m, cc.FdoProfileProvider); ok {
 			path := info.Path
 			profileUseFlag := fmt.Sprintf(afdoFlagFormat, path.String())
diff --git a/rust/afdo_test.go b/rust/afdo_test.go
index 0cdf704..69aa97e 100644
--- a/rust/afdo_test.go
+++ b/rust/afdo_test.go
@@ -50,7 +50,7 @@
 		rustMockedFiles.AddToFixture(),
 	).RunTestWithBp(t, bp)
 
-	foo := result.ModuleForTests("foo", "android_arm64_armv8-a").Rule("rustc")
+	foo := result.ModuleForTests(t, "foo", "android_arm64_armv8-a").Rule("rustc")
 
 	expectedCFlag := fmt.Sprintf(afdoFlagFormat, "afdo_profiles_package/foo.afdo")
 
@@ -96,8 +96,8 @@
 		rustMockedFiles.AddToFixture(),
 	).RunTestWithBp(t, bp)
 
-	fooArm := result.ModuleForTests("foo", "android_arm_armv7-a-neon").Rule("rustc")
-	fooArm64 := result.ModuleForTests("foo", "android_arm64_armv8-a").Rule("rustc")
+	fooArm := result.ModuleForTests(t, "foo", "android_arm_armv7-a-neon").Rule("rustc")
+	fooArm64 := result.ModuleForTests(t, "foo", "android_arm64_armv8-a").Rule("rustc")
 
 	expectedCFlagArm := fmt.Sprintf(afdoFlagFormat, "afdo_profiles_package/foo_arm.afdo")
 	expectedCFlagArm64 := fmt.Sprintf(afdoFlagFormat, "afdo_profiles_package/foo_arm64.afdo")
diff --git a/rust/androidmk.go b/rust/androidmk.go
index 8de6b60..9894684 100644
--- a/rust/androidmk.go
+++ b/rust/androidmk.go
@@ -92,9 +92,6 @@
 func (binary *binaryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
 	ctx.SubAndroidMk(ret, binary.baseCompiler)
 
-	if binary.distFile.Valid() {
-		ret.DistFiles = android.MakeDefaultDistFiles(binary.distFile.Path())
-	}
 	ret.Class = "EXECUTABLES"
 }
 
@@ -143,9 +140,6 @@
 	} else if library.shared() {
 		ret.Class = "SHARED_LIBRARIES"
 	}
-	if library.distFile.Valid() {
-		ret.DistFiles = android.MakeDefaultDistFiles(library.distFile.Path())
-	}
 	ret.ExtraEntries = append(ret.ExtraEntries,
 		func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 			if library.tocFile.Valid() {
@@ -158,10 +152,6 @@
 	ctx.SubAndroidMk(ret, procMacro.baseCompiler)
 
 	ret.Class = "PROC_MACRO_LIBRARIES"
-	if procMacro.distFile.Valid() {
-		ret.DistFiles = android.MakeDefaultDistFiles(procMacro.distFile.Path())
-	}
-
 }
 
 func (sourceProvider *BaseSourceProvider) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
diff --git a/rust/benchmark.go b/rust/benchmark.go
index 8c3e515..daba964 100644
--- a/rust/benchmark.go
+++ b/rust/benchmark.go
@@ -89,7 +89,7 @@
 	return rlibAutoDep
 }
 
-func (benchmark *benchmarkDecorator) stdLinkage(ctx *depsContext) RustLinkage {
+func (benchmark *benchmarkDecorator) stdLinkage(device bool) RustLinkage {
 	return RlibLinkage
 }
 
@@ -130,3 +130,20 @@
 
 	benchmark.binaryDecorator.install(ctx)
 }
+
+func (benchmark *benchmarkDecorator) moduleInfoJSON(ctx ModuleContext, moduleInfoJSON *android.ModuleInfoJSON) {
+	benchmark.binaryDecorator.moduleInfoJSON(ctx, moduleInfoJSON)
+	moduleInfoJSON.Class = []string{"NATIVE_TESTS"}
+	if benchmark.testConfig != nil {
+		if _, ok := benchmark.testConfig.(android.WritablePath); ok {
+			moduleInfoJSON.AutoTestConfig = []string{"true"}
+		}
+		moduleInfoJSON.TestConfig = append(moduleInfoJSON.TestConfig, benchmark.testConfig.String())
+	}
+
+	if len(benchmark.Properties.Test_suites) > 0 {
+		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, benchmark.Properties.Test_suites...)
+	} else {
+		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "null-suite")
+	}
+}
diff --git a/rust/benchmark_test.go b/rust/benchmark_test.go
index 734dda7..c239a09 100644
--- a/rust/benchmark_test.go
+++ b/rust/benchmark_test.go
@@ -28,7 +28,7 @@
 			srcs: ["foo.rs"],
 		}`)
 
-	testingModule := ctx.ModuleForTests("my_bench", "linux_glibc_x86_64")
+	testingModule := ctx.ModuleForTests(t, "my_bench", "linux_glibc_x86_64")
 	expectedOut := "my_bench/linux_glibc_x86_64/my_bench"
 	outPath := testingModule.Output("my_bench").Output.String()
 	if !strings.Contains(outPath, expectedOut) {
@@ -43,7 +43,7 @@
 			srcs: ["foo.rs"],
 		}`)
 
-	testingModule := ctx.ModuleForTests("my_bench", "android_arm64_armv8-a").Module().(*Module)
+	testingModule := ctx.ModuleForTests(t, "my_bench", "android_arm64_armv8-a").Module().(*Module)
 
 	if !android.InList("libcriterion.rlib-std", testingModule.Properties.AndroidMkRlibs) {
 		t.Errorf("rlib-std variant for libcriterion not detected as a rustlib-defined rlib dependency for device rust_benchmark module")
diff --git a/rust/binary.go b/rust/binary.go
index cba29a0..5a03d91 100644
--- a/rust/binary.go
+++ b/rust/binary.go
@@ -139,7 +139,10 @@
 
 	flags.RustFlags = append(flags.RustFlags, deps.depFlags...)
 	flags.LinkFlags = append(flags.LinkFlags, deps.depLinkFlags...)
-	flags.LinkFlags = append(flags.LinkFlags, deps.linkObjects...)
+	flags.LinkFlags = append(flags.LinkFlags, deps.rustLibObjects...)
+	flags.LinkFlags = append(flags.LinkFlags, deps.sharedLibObjects...)
+	flags.LinkFlags = append(flags.LinkFlags, deps.staticLibObjects...)
+	flags.LinkFlags = append(flags.LinkFlags, deps.wholeStaticLibObjects...)
 
 	if binary.stripper.NeedsStrip(ctx) {
 		strippedOutputFile := outputFile
@@ -165,11 +168,11 @@
 	}
 }
 
-func (binary *binaryDecorator) stdLinkage(ctx *depsContext) RustLinkage {
+func (binary *binaryDecorator) stdLinkage(device bool) RustLinkage {
 	if binary.preferRlib() {
 		return RlibLinkage
 	}
-	return binary.baseCompiler.stdLinkage(ctx)
+	return binary.baseCompiler.stdLinkage(device)
 }
 
 func (binary *binaryDecorator) binary() bool {
@@ -183,3 +186,8 @@
 func (binary *binaryDecorator) testBinary() bool {
 	return false
 }
+
+func (binary *binaryDecorator) moduleInfoJSON(ctx ModuleContext, moduleInfoJSON *android.ModuleInfoJSON) {
+	binary.baseCompiler.moduleInfoJSON(ctx, moduleInfoJSON)
+	moduleInfoJSON.Class = []string{"EXECUTABLES"}
+}
diff --git a/rust/binary_test.go b/rust/binary_test.go
index ef93037..33710f9 100644
--- a/rust/binary_test.go
+++ b/rust/binary_test.go
@@ -36,7 +36,7 @@
 			host_supported: true,
 		}
 	`)
-	fizzBuzz := ctx.ModuleForTests("fizz-buzz", "linux_glibc_x86_64").Module().(*Module)
+	fizzBuzz := ctx.ModuleForTests(t, "fizz-buzz", "linux_glibc_x86_64").Module().(*Module)
 	if !android.InList("libfoo.rlib-std", fizzBuzz.Properties.AndroidMkRlibs) {
 		t.Errorf("rustlibs dependency libfoo should be an rlib dep for host binaries")
 	}
@@ -65,8 +65,8 @@
 			host_supported: true,
 		}`)
 
-	fizzBuzzHost := ctx.ModuleForTests("fizz-buzz", "linux_glibc_x86_64").Module().(*Module)
-	fizzBuzzDevice := ctx.ModuleForTests("fizz-buzz", "android_arm64_armv8-a").Module().(*Module)
+	fizzBuzzHost := ctx.ModuleForTests(t, "fizz-buzz", "linux_glibc_x86_64").Module().(*Module)
+	fizzBuzzDevice := ctx.ModuleForTests(t, "fizz-buzz", "android_arm64_armv8-a").Module().(*Module)
 
 	if !android.InList("libfoo.rlib-std", fizzBuzzHost.Properties.AndroidMkRlibs) {
 		t.Errorf("rustlibs dependency libfoo should be an rlib dep for host modules")
@@ -76,7 +76,7 @@
 		t.Errorf("rustlibs dependency libfoo should be an dylib dep for device modules")
 	}
 
-	rlibLinkDevice := ctx.ModuleForTests("rlib_linked", "android_arm64_armv8-a").Module().(*Module)
+	rlibLinkDevice := ctx.ModuleForTests(t, "rlib_linked", "android_arm64_armv8-a").Module().(*Module)
 
 	if !android.InList("libfoo.rlib-std", rlibLinkDevice.Properties.AndroidMkRlibs) {
 		t.Errorf("rustlibs dependency libfoo should be an rlib dep for device modules when prefer_rlib is set")
@@ -100,7 +100,7 @@
 			host_supported: true,
 		}`)
 
-	mod := ctx.ModuleForTests("rlib_linked", "android_arm64_armv8-a").Module().(*Module)
+	mod := ctx.ModuleForTests(t, "rlib_linked", "android_arm64_armv8-a").Module().(*Module)
 
 	if !android.InList("libfoo.rlib-std", mod.Properties.AndroidMkRlibs) {
 		t.Errorf("rustlibs dependency libfoo should be an rlib dep when prefer_rlib is defined")
@@ -119,7 +119,7 @@
 			srcs: ["foo.rs"],
 		}`)
 
-	path := ctx.ModuleForTests("fizz-buzz", "linux_glibc_x86_64").Module().(*Module).HostToolPath()
+	path := ctx.ModuleForTests(t, "fizz-buzz", "linux_glibc_x86_64").Module().(*Module).HostToolPath()
 	if g, w := path.String(), "/host/linux-x86/bin/fizz-buzz"; !strings.Contains(g, w) {
 		t.Errorf("wrong host tool path, expected %q got %q", w, g)
 	}
@@ -133,7 +133,7 @@
 			srcs: ["foo.rs"],
 		}`)
 
-	fizzBuzz := ctx.ModuleForTests("fizz-buzz", "linux_glibc_x86_64").Rule("rustc")
+	fizzBuzz := ctx.ModuleForTests(t, "fizz-buzz", "linux_glibc_x86_64").Rule("rustc")
 
 	flags := fizzBuzz.Args["rustcFlags"]
 	if strings.Contains(flags, "--test") {
@@ -150,7 +150,7 @@
 			bootstrap: true,
 		}`)
 
-	foo := ctx.ModuleForTests("foo", "android_arm64_armv8-a").Rule("rustc")
+	foo := ctx.ModuleForTests(t, "foo", "android_arm64_armv8-a").Rule("rustc")
 
 	flag := "-Wl,-dynamic-linker,/system/bin/bootstrap/linker64"
 	if !strings.Contains(foo.Args["linkFlags"], flag) {
@@ -166,8 +166,8 @@
 			static_executable: true,
 		}`)
 
-	fizzOut := ctx.ModuleForTests("fizz", "android_arm64_armv8-a").Rule("rustc")
-	fizzMod := ctx.ModuleForTests("fizz", "android_arm64_armv8-a").Module().(*Module)
+	fizzOut := ctx.ModuleForTests(t, "fizz", "android_arm64_armv8-a").Rule("rustc")
+	fizzMod := ctx.ModuleForTests(t, "fizz", "android_arm64_armv8-a").Module().(*Module)
 
 	flags := fizzOut.Args["rustcFlags"]
 	linkFlags := fizzOut.Args["linkFlags"]
@@ -200,7 +200,7 @@
 			name: "libfoo",
 		}`)
 
-	fizzBuzz := ctx.ModuleForTests("fizz-buzz", "android_arm64_armv8-a").Rule("rustc")
+	fizzBuzz := ctx.ModuleForTests(t, "fizz-buzz", "android_arm64_armv8-a").Rule("rustc")
 	linkFlags := fizzBuzz.Args["linkFlags"]
 	if !strings.Contains(linkFlags, "/libfoo.so") {
 		t.Errorf("missing shared dependency 'libfoo.so' in linkFlags: %#v", linkFlags)
@@ -223,7 +223,7 @@
 		}
 	`)
 
-	foo := ctx.ModuleForTests("foo", "android_arm64_armv8-a")
+	foo := ctx.ModuleForTests(t, "foo", "android_arm64_armv8-a")
 	foo.Output("unstripped/foo")
 	foo.Output("foo")
 
@@ -233,7 +233,7 @@
 		t.Errorf("installed binary not based on stripped version: %v", cp.Input)
 	}
 
-	fizzBar := ctx.ModuleForTests("bar", "android_arm64_armv8-a").MaybeOutput("unstripped/bar")
+	fizzBar := ctx.ModuleForTests(t, "bar", "android_arm64_armv8-a").MaybeOutput("unstripped/bar")
 	if fizzBar.Rule != nil {
 		t.Errorf("unstripped binary exists, so stripped binary has incorrectly been generated")
 	}
diff --git a/rust/bindgen.go b/rust/bindgen.go
index abb5181..2f84168 100644
--- a/rust/bindgen.go
+++ b/rust/bindgen.go
@@ -186,7 +186,7 @@
 	// Default clang flags
 	cflags = append(cflags, "${cc_config.CommonGlobalCflags}")
 	if ctx.Device() {
-		cflags = append(cflags, "${cc_config.DeviceGlobalCflags}")
+		cflags = append(cflags, "${cc_config.DeviceGlobalCflags}", "-nostdlibinc")
 	}
 
 	// Toolchain clang flags
@@ -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))
 	}
@@ -300,11 +300,17 @@
 	// it cannot recognize. Turn off unknown warning flags warning.
 	cflags = append(cflags, "-Wno-unknown-warning-option")
 
+	// Suppress warnings while testing a new compiler.
+	if ctx.Config().IsEnvTrue("LLVM_NEXT") {
+		cflags = append(cflags, "-Wno-everything")
+	}
+
 	outputFile := android.PathForModuleOut(ctx, b.BaseSourceProvider.getStem(ctx)+".rs")
 
 	var cmd, cmdDesc string
 	if b.Properties.Custom_bindgen != "" {
-		cmd = ctx.GetDirectDepWithTag(b.Properties.Custom_bindgen, customBindgenDepTag).(android.HostToolProvider).HostToolPath().String()
+		m := ctx.GetDirectDepProxyWithTag(b.Properties.Custom_bindgen, customBindgenDepTag)
+		cmd = android.OtherModuleProviderOrDefault(ctx, m, android.HostToolProviderInfoProvider).HostToolPath.String()
 		cmdDesc = b.Properties.Custom_bindgen
 	} else {
 		cmd = "$bindgenCmd"
@@ -392,7 +398,7 @@
 		//
 		// This is necessary to avoid a circular dependency between the source variant and the
 		// dependent cc module.
-		deps.StaticLibs = append(deps.StaticLibs, String(b.Properties.Static_inline_library))
+		deps.WholeStaticLibs = append(deps.WholeStaticLibs, String(b.Properties.Static_inline_library))
 	}
 
 	deps.SharedLibs = append(deps.SharedLibs, b.ClangProperties.Shared_libs.GetOrDefault(ctx, nil)...)
diff --git a/rust/bindgen_test.go b/rust/bindgen_test.go
index 2b7362f..267fb1c 100644
--- a/rust/bindgen_test.go
+++ b/rust/bindgen_test.go
@@ -67,10 +67,10 @@
 			cflags: ["--default-flag"],
 		}
 	`)
-	libbindgen := ctx.ModuleForTests("libbindgen", "android_arm64_armv8-a_source").Output("bindings.rs")
-	libbindgenStatic := ctx.ModuleForTests("libbindgen_staticlib", "android_arm64_armv8-a_source").Output("bindings.rs")
-	libbindgenHeader := ctx.ModuleForTests("libbindgen_headerlib", "android_arm64_armv8-a_source").Output("bindings.rs")
-	libbindgenHeaderModule := ctx.ModuleForTests("libbindgen_headerlib", "android_arm64_armv8-a_source").Module().(*Module)
+	libbindgen := ctx.ModuleForTests(t, "libbindgen", "android_arm64_armv8-a_source").Output("bindings.rs")
+	libbindgenStatic := ctx.ModuleForTests(t, "libbindgen_staticlib", "android_arm64_armv8-a_source").Output("bindings.rs")
+	libbindgenHeader := ctx.ModuleForTests(t, "libbindgen_headerlib", "android_arm64_armv8-a_source").Output("bindings.rs")
+	libbindgenHeaderModule := ctx.ModuleForTests(t, "libbindgen_headerlib", "android_arm64_armv8-a_source").Module().(*Module)
 	// Ensure that the flags are present and escaped
 	if !strings.Contains(libbindgen.Args["flags"], "'--bindgen-flag.*'") {
 		t.Errorf("missing bindgen flags in rust_bindgen rule: flags %#v", libbindgen.Args["flags"])
@@ -113,7 +113,7 @@
 		}
 	`)
 
-	libbindgen := ctx.ModuleForTests("libbindgen", "android_arm64_armv8-a_source").Output("bindings.rs")
+	libbindgen := ctx.ModuleForTests(t, "libbindgen", "android_arm64_armv8-a_source").Output("bindings.rs")
 
 	// The rule description should contain the custom binary name rather than bindgen, so checking the description
 	// should be sufficient.
@@ -155,8 +155,8 @@
 		}
 	`)
 
-	libbindgen_cstd := ctx.ModuleForTests("libbindgen_cstd", "android_arm64_armv8-a_source").Output("bindings.rs")
-	libbindgen_cppstd := ctx.ModuleForTests("libbindgen_cppstd", "android_arm64_armv8-a_source").Output("bindings.rs")
+	libbindgen_cstd := ctx.ModuleForTests(t, "libbindgen_cstd", "android_arm64_armv8-a_source").Output("bindings.rs")
+	libbindgen_cppstd := ctx.ModuleForTests(t, "libbindgen_cppstd", "android_arm64_armv8-a_source").Output("bindings.rs")
 
 	if !strings.Contains(libbindgen_cstd.Args["cflags"], "-std=foo") {
 		t.Errorf("c_std value not passed in to rust_bindgen as a clang flag")
@@ -216,7 +216,7 @@
 			],
 		}
 	`)
-	libbindgen := ctx.ModuleForTests("libbindgen", "android_arm64_armv8-a_source").Output("bindings.rs")
+	libbindgen := ctx.ModuleForTests(t, "libbindgen", "android_arm64_armv8-a_source").Output("bindings.rs")
 
 	if !strings.Contains(libbindgen.Args["flagfiles"], "/dev/null") {
 		t.Errorf("missing /dev/null in rust_bindgen rule: flags %#v", libbindgen.Args["flagfiles"])
@@ -246,7 +246,7 @@
 			include_dirs: ["src/"],
 		}
 	`)
-	libbindgen := ctx.ModuleForTests("libbindgen", "android_arm64_armv8-a_source").Output("bindings.rs")
+	libbindgen := ctx.ModuleForTests(t, "libbindgen", "android_arm64_armv8-a_source").Output("bindings.rs")
 	// Make sure the flag to support `static inline` functions is present
 	if !strings.Contains(libbindgen.Args["flags"], "--wrap-static-fns") {
 		t.Errorf("missing flag to handle static inlining in rust_bindgen rule: flags %#v", libbindgen.Args["flags"])
diff --git a/rust/builder.go b/rust/builder.go
index f469f56..1b6a6c1 100644
--- a/rust/builder.go
+++ b/rust/builder.go
@@ -30,11 +30,11 @@
 	rustc = pctx.AndroidStaticRule("rustc",
 		blueprint.RuleParams{
 			Command: "$envVars $rustcCmd " +
-				"-C linker=${config.RustLinker} " +
-				"-C link-args=\"${crtBegin} ${earlyLinkFlags} ${linkFlags} ${crtEnd}\" " +
+				"-C linker=${RustcLinkerCmd} " +
+				"-C link-args=\"--android-clang-bin=${config.ClangCmd} ${crtBegin} ${earlyLinkFlags} ${linkFlags} ${crtEnd}\" " +
 				"--emit link -o $out --emit dep-info=$out.d.raw $in ${libFlags} $rustcFlags" +
 				" && grep ^$out: $out.d.raw > $out.d",
-			CommandDeps: []string{"$rustcCmd"},
+			CommandDeps: []string{"$rustcCmd", "${RustcLinkerCmd}", "${config.ClangCmd}"},
 			// Rustc deps-info writes out make compatible dep files: https://github.com/rust-lang/rust/issues/7633
 			// Rustc emits unneeded dependency lines for the .d and input .rs files.
 			// Those extra lines cause ninja warning:
@@ -102,10 +102,10 @@
 				`KYTHE_CANONICALIZE_VNAME_PATHS=prefer-relative ` +
 				`$rustExtractor $envVars ` +
 				`$rustcCmd ` +
-				`-C linker=${config.RustLinker} ` +
-				`-C link-args="${crtBegin} ${linkFlags} ${crtEnd}" ` +
+				`-C linker=${RustcLinkerCmd} ` +
+				`-C link-args="--android-clang-bin=${config.ClangCmd} ${crtBegin} ${linkFlags} ${crtEnd}" ` +
 				`$in ${libFlags} $rustcFlags`,
-			CommandDeps:    []string{"$rustExtractor", "$kytheVnames"},
+			CommandDeps:    []string{"$rustExtractor", "$kytheVnames", "${RustcLinkerCmd}", "${config.ClangCmd}"},
 			Rspfile:        "${out}.rsp",
 			RspfileContent: "$in",
 		},
@@ -119,6 +119,7 @@
 
 func init() {
 	pctx.HostBinToolVariable("SoongZipCmd", "soong_zip")
+	pctx.HostBinToolVariable("RustcLinkerCmd", "rustc_linker")
 	cc.TransformRlibstoStaticlib = TransformRlibstoStaticlib
 }
 
@@ -170,6 +171,9 @@
 	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, getTransformProperties(ctx, "rlib"))
 }
 
+// TransformRlibstoStaticlib is assumed to be callable 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 {
 
@@ -181,7 +185,7 @@
 		rustPathDeps.linkDirs = append(rustPathDeps.linkDirs, rlibDep.LinkDirs...)
 	}
 
-	ccModule := ctx.(cc.ModuleContext).Module().(*cc.Module)
+	mod := ctx.Module().(cc.LinkableInterface)
 	toolchain := config.FindToolchain(ctx.Os(), ctx.Arch())
 	t := transformProperties{
 		// Crate name can be a predefined value as this is a staticlib and
@@ -191,10 +195,10 @@
 		crateName:       "generated_rust_staticlib",
 		is64Bit:         toolchain.Is64Bit(),
 		targetTriple:    toolchain.RustTriple(),
-		bootstrap:       ccModule.Bootstrap(),
-		inRecovery:      ccModule.InRecovery(),
-		inRamdisk:       ccModule.InRamdisk(),
-		inVendorRamdisk: ccModule.InVendorRamdisk(),
+		bootstrap:       mod.Bootstrap(),
+		inRecovery:      mod.InRecovery(),
+		inRamdisk:       mod.InRamdisk(),
+		inVendorRamdisk: mod.InVendorRamdisk(),
 
 		// crateType indicates what type of crate to build
 		crateType: "staticlib",
@@ -260,7 +264,7 @@
 		libFlags = append(libFlags, "--extern "+lib.CrateName+"="+lib.Path.String())
 	}
 	for _, lib := range deps.DyLibs {
-		libFlags = append(libFlags, "--extern "+lib.CrateName+"="+lib.Path.String())
+		libFlags = append(libFlags, "--extern force:"+lib.CrateName+"="+lib.Path.String())
 	}
 	for _, proc_macro := range deps.ProcMacros {
 		libFlags = append(libFlags, "--extern "+proc_macro.CrateName+"="+proc_macro.Path.String())
@@ -398,6 +402,11 @@
 		linkFlags = append(linkFlags, dynamicLinker)
 	}
 
+	if generatedLib := cc.GenerateRustStaticlib(ctx, deps.ccRlibDeps); generatedLib != nil {
+		deps.StaticLibs = append(deps.StaticLibs, generatedLib)
+		linkFlags = append(linkFlags, generatedLib.String())
+	}
+
 	libFlags := makeLibFlags(deps)
 
 	// Collect dependencies
@@ -408,6 +417,7 @@
 	implicits = append(implicits, deps.SharedLibDeps...)
 	implicits = append(implicits, deps.srcProviderFiles...)
 	implicits = append(implicits, deps.AfdoProfiles...)
+	implicits = append(implicits, deps.LinkerDeps...)
 
 	implicits = append(implicits, deps.CrtBegin...)
 	implicits = append(implicits, deps.CrtEnd...)
diff --git a/rust/builder_test.go b/rust/builder_test.go
index ae5ccde..7d6b56a 100644
--- a/rust/builder_test.go
+++ b/rust/builder_test.go
@@ -85,7 +85,7 @@
 				"out/soong/.intermediates/libfizz_buzz/android_arm64_armv8-a_dylib/libfizz_buzz.dylib.so",
 				"out/soong/.intermediates/libfizz_buzz/android_arm64_armv8-a_dylib/libfizz_buzz.dylib.so.clippy",
 				"out/soong/.intermediates/libfizz_buzz/android_arm64_armv8-a_dylib/unstripped/libfizz_buzz.dylib.so",
-				"out/soong/target/product/test_device/system/lib64/libfizz_buzz.dylib.so",
+				"out/target/product/test_device/system/lib64/libfizz_buzz.dylib.so",
 				"out/soong/.intermediates/libfizz_buzz/android_arm64_armv8-a_dylib/meta_lic",
 			},
 		},
@@ -118,7 +118,7 @@
 				"out/soong/.intermediates/fizz_buzz/android_arm64_armv8-a/fizz_buzz",
 				"out/soong/.intermediates/fizz_buzz/android_arm64_armv8-a/fizz_buzz.clippy",
 				"out/soong/.intermediates/fizz_buzz/android_arm64_armv8-a/unstripped/fizz_buzz",
-				"out/soong/target/product/test_device/system/bin/fizz_buzz",
+				"out/target/product/test_device/system/bin/fizz_buzz",
 				"out/soong/.intermediates/fizz_buzz/android_arm64_armv8-a/meta_lic",
 			},
 		},
@@ -154,13 +154,13 @@
 				"out/soong/.intermediates/librust_ffi/android_arm64_armv8-a_shared/unstripped/librust_ffi.so.toc",
 				"out/soong/.intermediates/librust_ffi/android_arm64_armv8-a_shared/meta_lic",
 				"out/soong/.intermediates/librust_ffi/android_arm64_armv8-a_shared/rustdoc.timestamp",
-				"out/soong/target/product/test_device/system/lib64/librust_ffi.so",
+				"out/target/product/test_device/system/lib64/librust_ffi.so",
 			},
 		},
 	}
 	for _, tc := range testcases {
 		t.Run(tc.testName, func(t *testing.T) {
-			modOutputs := ctx.ModuleForTests(tc.moduleName, tc.variant).AllOutputs()
+			modOutputs := ctx.ModuleForTests(t, tc.moduleName, tc.variant).AllOutputs()
 			sort.Strings(tc.expectedFiles)
 			sort.Strings(modOutputs)
 			android.AssertStringPathsRelativeToTopEquals(
diff --git a/rust/clippy_test.go b/rust/clippy_test.go
index bd3bfb1..3563348 100644
--- a/rust/clippy_test.go
+++ b/rust/clippy_test.go
@@ -62,13 +62,13 @@
 				android.FixtureAddTextFile(tc.modulePath+"Android.bp", bp),
 			).RunTest(t)
 
-			r := result.ModuleForTests("libfoo", "android_arm64_armv8-a_dylib").MaybeRule("clippy")
+			r := result.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_dylib").MaybeRule("clippy")
 			android.AssertStringEquals(t, "libfoo flags", tc.fooFlags, r.Args["clippyFlags"])
 
-			r = result.ModuleForTests("libbar", "android_arm64_armv8-a_dylib").MaybeRule("clippy")
+			r = result.ModuleForTests(t, "libbar", "android_arm64_armv8-a_dylib").MaybeRule("clippy")
 			android.AssertStringEquals(t, "libbar flags", "${config.ClippyDefaultLints}", r.Args["clippyFlags"])
 
-			r = result.ModuleForTests("libfoobar", "android_arm64_armv8-a_dylib").MaybeRule("clippy")
+			r = result.ModuleForTests(t, "libfoobar", "android_arm64_armv8-a_dylib").MaybeRule("clippy")
 			if r.Rule != nil {
 				t.Errorf("libfoobar is setup to use clippy when explicitly disabled: clippyFlags=%q", r.Args["clippyFlags"])
 			}
diff --git a/rust/compiler.go b/rust/compiler.go
index fd86917..c3bc937 100644
--- a/rust/compiler.go
+++ b/rust/compiler.go
@@ -30,9 +30,8 @@
 type RustLinkage int
 
 const (
-	DefaultLinkage RustLinkage = iota
+	DylibLinkage RustLinkage = iota
 	RlibLinkage
-	DylibLinkage
 )
 
 type compiler interface {
@@ -40,6 +39,7 @@
 	compilerFlags(ctx ModuleContext, flags Flags) Flags
 	cfgFlags(ctx ModuleContext, flags Flags) Flags
 	featureFlags(ctx ModuleContext, module *Module, flags Flags) Flags
+	baseCompilerProps() BaseCompilerProperties
 	compilerProps() []interface{}
 	compile(ctx ModuleContext, flags Flags, deps PathDeps) buildOutput
 	compilerDeps(ctx DepsContext, deps Deps) Deps
@@ -69,7 +69,7 @@
 	Disabled() bool
 	SetDisabled()
 
-	stdLinkage(ctx *depsContext) RustLinkage
+	stdLinkage(device bool) RustLinkage
 	noStdlibs() bool
 
 	unstrippedOutputFilePath() android.Path
@@ -78,6 +78,8 @@
 	checkedCrateRootPath() (android.Path, error)
 
 	Aliases() map[string]string
+
+	moduleInfoJSON(ctx ModuleContext, moduleInfoJSON *android.ModuleInfoJSON)
 }
 
 func (compiler *baseCompiler) edition() string {
@@ -150,21 +152,21 @@
 	Aliases []string
 
 	// list of rust rlib crate dependencies
-	Rlibs []string `android:"arch_variant"`
+	Rlibs proptools.Configurable[[]string] `android:"arch_variant"`
 
 	// list of rust automatic crate dependencies.
 	// Rustlibs linkage is rlib for host targets and dylib for device targets.
 	Rustlibs proptools.Configurable[[]string] `android:"arch_variant"`
 
 	// list of rust proc_macro crate dependencies
-	Proc_macros []string `android:"arch_variant"`
+	Proc_macros proptools.Configurable[[]string] `android:"arch_variant"`
 
 	// list of C shared library dependencies
-	Shared_libs []string `android:"arch_variant"`
+	Shared_libs proptools.Configurable[[]string] `android:"arch_variant"`
 
 	// list of C static library dependencies. These dependencies do not normally propagate to dependents
 	// and may need to be redeclared. See whole_static_libs for bundling static dependencies into a library.
-	Static_libs []string `android:"arch_variant"`
+	Static_libs proptools.Configurable[[]string] `android:"arch_variant"`
 
 	// Similar to static_libs, but will bundle the static library dependency into a library. This is helpful
 	// to avoid having to redeclare the dependency for dependents of this library, but in some cases may also
@@ -179,13 +181,13 @@
 	//
 	// For rust_library rlib variants, these libraries will be bundled into the resulting rlib library. This will
 	// include all of the static libraries symbols in any dylibs or binaries which use this rlib as well.
-	Whole_static_libs []string `android:"arch_variant"`
+	Whole_static_libs proptools.Configurable[[]string] `android:"arch_variant"`
 
 	// list of Rust system library dependencies.
 	//
 	// This is usually only needed when `no_stdlibs` is true, in which case it can be used to depend on system crates
 	// like `core` and `alloc`.
-	Stdlibs []string `android:"arch_variant"`
+	Stdlibs proptools.Configurable[[]string] `android:"arch_variant"`
 
 	// crate name, required for modules which produce Rust libraries: rust_library, rust_ffi and SourceProvider
 	// modules which create library variants (rust_bindgen). This must be the expected extern crate name used in
@@ -255,8 +257,6 @@
 	location installLocation
 	sanitize *sanitize
 
-	distFile android.OptionalPath
-
 	installDeps android.InstallPaths
 
 	// unstripped output file.
@@ -316,17 +316,42 @@
 	return aliases
 }
 
-func (compiler *baseCompiler) stdLinkage(ctx *depsContext) RustLinkage {
+func (compiler *baseCompiler) stdLinkage(device bool) RustLinkage {
 	// For devices, we always link stdlibs in as dylibs by default.
 	if compiler.preferRlib() {
 		return RlibLinkage
-	} else if ctx.Device() {
+	} else if device {
 		return DylibLinkage
 	} else {
 		return RlibLinkage
 	}
 }
 
+func (compiler *baseCompiler) moduleInfoJSON(ctx ModuleContext, moduleInfoJSON *android.ModuleInfoJSON) {
+	moduleInfoJSON.Class = []string{"ETC"}
+
+	mod := ctx.Module().(*Module)
+
+	moduleInfoJSON.SharedLibs = mod.transitiveAndroidMkSharedLibs.ToList()
+	moduleInfoJSON.Dependencies = append(moduleInfoJSON.Dependencies, mod.transitiveAndroidMkSharedLibs.ToList()...)
+	moduleInfoJSON.Dependencies = append(moduleInfoJSON.Dependencies, mod.Properties.AndroidMkDylibs...)
+	moduleInfoJSON.Dependencies = append(moduleInfoJSON.Dependencies, mod.Properties.AndroidMkHeaderLibs...)
+	moduleInfoJSON.Dependencies = append(moduleInfoJSON.Dependencies, mod.Properties.AndroidMkProcMacroLibs...)
+	moduleInfoJSON.Dependencies = append(moduleInfoJSON.Dependencies, mod.Properties.AndroidMkRlibs...)
+	moduleInfoJSON.Dependencies = append(moduleInfoJSON.Dependencies, mod.Properties.AndroidMkStaticLibs...)
+	moduleInfoJSON.SystemSharedLibs = []string{"none"}
+	moduleInfoJSON.StaticLibs = mod.Properties.AndroidMkStaticLibs
+
+	if mod.sourceProvider != nil {
+		moduleInfoJSON.SubName += mod.sourceProvider.getSubName()
+	}
+	moduleInfoJSON.SubName += mod.AndroidMkSuffix()
+
+	if mod.Properties.IsSdkVariant {
+		moduleInfoJSON.Uninstallable = true
+	}
+}
+
 var _ compiler = (*baseCompiler)(nil)
 
 func (compiler *baseCompiler) inData() bool {
@@ -337,6 +362,10 @@
 	return []interface{}{&compiler.Properties}
 }
 
+func (compiler *baseCompiler) baseCompilerProps() BaseCompilerProperties {
+	return compiler.Properties
+}
+
 func cfgsToFlags(cfgs []string) []string {
 	flags := make([]string, 0, len(cfgs))
 	for _, cfg := range cfgs {
@@ -496,13 +525,13 @@
 }
 
 func (compiler *baseCompiler) compilerDeps(ctx DepsContext, deps Deps) Deps {
-	deps.Rlibs = append(deps.Rlibs, compiler.Properties.Rlibs...)
+	deps.Rlibs = append(deps.Rlibs, compiler.Properties.Rlibs.GetOrDefault(ctx, nil)...)
 	deps.Rustlibs = append(deps.Rustlibs, compiler.Properties.Rustlibs.GetOrDefault(ctx, nil)...)
-	deps.ProcMacros = append(deps.ProcMacros, compiler.Properties.Proc_macros...)
-	deps.StaticLibs = append(deps.StaticLibs, compiler.Properties.Static_libs...)
-	deps.WholeStaticLibs = append(deps.WholeStaticLibs, compiler.Properties.Whole_static_libs...)
-	deps.SharedLibs = append(deps.SharedLibs, compiler.Properties.Shared_libs...)
-	deps.Stdlibs = append(deps.Stdlibs, compiler.Properties.Stdlibs...)
+	deps.ProcMacros = append(deps.ProcMacros, compiler.Properties.Proc_macros.GetOrDefault(ctx, nil)...)
+	deps.StaticLibs = append(deps.StaticLibs, compiler.Properties.Static_libs.GetOrDefault(ctx, nil)...)
+	deps.WholeStaticLibs = append(deps.WholeStaticLibs, compiler.Properties.Whole_static_libs.GetOrDefault(ctx, nil)...)
+	deps.SharedLibs = append(deps.SharedLibs, compiler.Properties.Shared_libs.GetOrDefault(ctx, nil)...)
+	deps.Stdlibs = append(deps.Stdlibs, compiler.Properties.Stdlibs.GetOrDefault(ctx, nil)...)
 
 	if !Bool(compiler.Properties.No_stdlibs) {
 		for _, stdlib := range config.Stdlibs {
diff --git a/rust/compiler_test.go b/rust/compiler_test.go
index 4caa12b..8805d15 100644
--- a/rust/compiler_test.go
+++ b/rust/compiler_test.go
@@ -34,7 +34,7 @@
 			],
 		}`)
 
-	libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib").Rule("rustc")
+	libfooDylib := ctx.ModuleForTests(t, "libfoo", "linux_glibc_x86_64_dylib").Rule("rustc")
 
 	if !strings.Contains(libfooDylib.Args["rustcFlags"], "cfg 'feature=\"fizz\"'") ||
 		!strings.Contains(libfooDylib.Args["rustcFlags"], "cfg 'feature=\"buzz\"'") {
@@ -55,7 +55,7 @@
 			],
 		}`)
 
-	libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib").Rule("rustc")
+	libfooDylib := ctx.ModuleForTests(t, "libfoo", "linux_glibc_x86_64_dylib").Rule("rustc")
 
 	if !strings.Contains(libfooDylib.Args["rustcFlags"], "cfg 'std'") ||
 		!strings.Contains(libfooDylib.Args["rustcFlags"], "cfg 'cfg1=\"one\"'") {
@@ -81,8 +81,8 @@
 		}
 		`)
 
-	libfoo := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib").Rule("rustc")
-	libfooLto := ctx.ModuleForTests("libfoo_lto", "linux_glibc_x86_64_dylib").Rule("rustc")
+	libfoo := ctx.ModuleForTests(t, "libfoo", "linux_glibc_x86_64_dylib").Rule("rustc")
+	libfooLto := ctx.ModuleForTests(t, "libfoo_lto", "linux_glibc_x86_64_dylib").Rule("rustc")
 
 	if strings.Contains(libfoo.Args["rustcFlags"], "-C lto=thin") {
 		t.Fatalf("libfoo expected to disable lto -- rustcFlags: %#v", libfoo.Args["rustcFlags"])
@@ -174,7 +174,7 @@
 			cargo_pkg_version: "1.0.0"
 		}`)
 
-	fizz := ctx.ModuleForTests("fizz", "android_arm64_armv8-a").Rule("rustc")
+	fizz := ctx.ModuleForTests(t, "fizz", "android_arm64_armv8-a").Rule("rustc")
 
 	if !strings.Contains(fizz.Args["envVars"], "CARGO_BIN_NAME=fizz") {
 		t.Fatalf("expected 'CARGO_BIN_NAME=fizz' in envVars, actual envVars: %#v", fizz.Args["envVars"])
@@ -199,11 +199,11 @@
 			srcs: ["foo.rs"],
 		}`)
 
-	install_path_lib64 := ctx.ModuleForTests("libfoo",
+	install_path_lib64 := ctx.ModuleForTests(t, "libfoo",
 		"android_arm64_armv8-a_dylib").Module().(*Module).compiler.(*libraryDecorator).path.String()
-	install_path_lib32 := ctx.ModuleForTests("libfoo",
+	install_path_lib32 := ctx.ModuleForTests(t, "libfoo",
 		"android_arm_armv7-a-neon_dylib").Module().(*Module).compiler.(*libraryDecorator).path.String()
-	install_path_bin := ctx.ModuleForTests("fizzbuzz",
+	install_path_bin := ctx.ModuleForTests(t, "fizzbuzz",
 		"android_arm64_armv8-a").Module().(*Module).compiler.(*binaryDecorator).path.String()
 
 	if !strings.HasSuffix(install_path_lib64, "system/lib64/libfoo.dylib.so") {
@@ -259,13 +259,13 @@
 				android.FixtureAddTextFile(tc.modulePath+"Android.bp", bp),
 			).RunTest(t)
 
-			r := result.ModuleForTests("libfoo", "android_arm64_armv8-a_dylib").MaybeRule("rustc")
+			r := result.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_dylib").MaybeRule("rustc")
 			android.AssertStringDoesContain(t, "libfoo flags", r.Args["rustcFlags"], tc.fooFlags)
 
-			r = result.ModuleForTests("libbar", "android_arm64_armv8-a_dylib").MaybeRule("rustc")
+			r = result.ModuleForTests(t, "libbar", "android_arm64_armv8-a_dylib").MaybeRule("rustc")
 			android.AssertStringDoesContain(t, "libbar flags", r.Args["rustcFlags"], "${config.RustDefaultLints}")
 
-			r = result.ModuleForTests("libfoobar", "android_arm64_armv8-a_dylib").MaybeRule("rustc")
+			r = result.ModuleForTests(t, "libfoobar", "android_arm64_armv8-a_dylib").MaybeRule("rustc")
 			android.AssertStringDoesContain(t, "libfoobar flags", r.Args["rustcFlags"], "${config.RustAllowAllLints}")
 		})
 	}
@@ -283,9 +283,9 @@
 			srcs: ["foo.rs"],
 			crate_name: "foo",
 		}`)
-	fizz := ctx.ModuleForTests("fizz", "android_arm64_armv8-a").Module().(*Module)
-	fooRlib := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_rlib_dylib-std").Module().(*Module)
-	fooDylib := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_dylib").Module().(*Module)
+	fizz := ctx.ModuleForTests(t, "fizz", "android_arm64_armv8-a").Module().(*Module)
+	fooRlib := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_rlib_dylib-std").Module().(*Module)
+	fooDylib := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_dylib").Module().(*Module)
 
 	if !android.InList("libstd", fizz.Properties.AndroidMkDylibs) {
 		t.Errorf("libstd is not linked dynamically for device binaries")
diff --git a/rust/config/global.go b/rust/config/global.go
index 68a74c2..907316f 100644
--- a/rust/config/global.go
+++ b/rust/config/global.go
@@ -15,6 +15,7 @@
 package config
 
 import (
+	"fmt"
 	"strings"
 
 	"android/soong/android"
@@ -24,7 +25,7 @@
 var (
 	pctx = android.NewPackageContext("android/soong/rust/config")
 
-	RustDefaultVersion = "1.81.0"
+	RustDefaultVersion = "1.83.0"
 	RustDefaultBase    = "prebuilts/rust/"
 	DefaultEdition     = "2021"
 	Stdlibs            = []string{
@@ -41,6 +42,8 @@
 	}
 
 	GlobalRustFlags = []string{
+		// Allow `--extern force:foo` for dylib support
+		"-Z unstable-options",
 		"-Z stack-protector=strong",
 		"-Z remap-cwd-prefix=.",
 		"-C debuginfo=2",
@@ -93,6 +96,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)
@@ -110,7 +123,7 @@
 	pctx.StaticVariable("RustBin", "${RustPath}/bin")
 
 	pctx.ImportAs("cc_config", "android/soong/cc/config")
-	pctx.StaticVariable("RustLinker", "${cc_config.ClangBin}/clang++")
+	pctx.StaticVariable("ClangCmd", "${cc_config.ClangBin}/clang++")
 
 	pctx.StaticVariable("DeviceGlobalLinkFlags", strings.Join(deviceGlobalLinkFlags, " "))
 
diff --git a/rust/config/lints.go b/rust/config/lints.go
index 735aa16..715e8aa 100644
--- a/rust/config/lints.go
+++ b/rust/config/lints.go
@@ -55,6 +55,7 @@
 	defaultClippyLints = []string{
 		// Let people hack in peace. ;)
 		"-A clippy::disallowed_names",
+		"-A clippy::empty_line_after_doc_comments",
 		"-A clippy::type-complexity",
 		"-A clippy::unnecessary_fallible_conversions",
 		"-A clippy::unnecessary-wraps",
diff --git a/rust/coverage.go b/rust/coverage.go
index 381fcf1..798b21d 100644
--- a/rust/coverage.go
+++ b/rust/coverage.go
@@ -15,12 +15,13 @@
 package rust
 
 import (
+	"android/soong/android"
+
 	"github.com/google/blueprint"
 
 	"android/soong/cc"
 )
 
-var CovLibraryName = "libprofile-clang-extras"
 var ProfilerBuiltins = "libprofiler_builtins.rust_sysroot"
 
 // Add '%c' to default specifier after we resolve http://b/210012154
@@ -37,12 +38,20 @@
 	return []interface{}{&cov.Properties}
 }
 
+func getClangProfileLibraryName(ctx ModuleContextIntf) string {
+	if ctx.RustModule().UseSdk() {
+		return "libprofile-clang-extras_ndk"
+	} else {
+		return "libprofile-clang-extras"
+	}
+}
+
 func (cov *coverage) deps(ctx DepsContext, deps Deps) Deps {
 	if cov.Properties.NeedCoverageVariant {
 		if ctx.Device() {
 			ctx.AddVariationDependencies([]blueprint.Variation{
 				{Mutator: "link", Variation: "static"},
-			}, cc.CoverageDepTag, CovLibraryName)
+			}, cc.CoverageDepTag, getClangProfileLibraryName(ctx))
 		}
 
 		// no_std modules are missing libprofiler_builtins which provides coverage, so we need to add it as a dependency.
@@ -65,16 +74,18 @@
 		flags.RustFlags = append(flags.RustFlags,
 			"-C instrument-coverage", "-g")
 		if ctx.Device() {
-			coverage := ctx.GetDirectDepWithTag(CovLibraryName, cc.CoverageDepTag).(cc.LinkableInterface)
+			m := ctx.GetDirectDepProxyWithTag(getClangProfileLibraryName(ctx), cc.CoverageDepTag)
+			coverage := android.OtherModuleProviderOrDefault(ctx, m, cc.LinkableInfoProvider)
 			flags.LinkFlags = append(flags.LinkFlags,
-				profileInstrFlag, "-g", coverage.OutputFile().Path().String(), "-Wl,--wrap,open")
-			deps.StaticLibs = append(deps.StaticLibs, coverage.OutputFile().Path())
+				profileInstrFlag, "-g", coverage.OutputFile.Path().String(), "-Wl,--wrap,open")
+			deps.StaticLibs = append(deps.StaticLibs, coverage.OutputFile.Path())
 		}
 
 		// no_std modules are missing libprofiler_builtins which provides coverage, so we need to add it as a dependency.
 		if rustModule, ok := ctx.Module().(*Module); ok && rustModule.compiler.noStdlibs() {
-			profiler_builtins := ctx.GetDirectDepWithTag(ProfilerBuiltins, rlibDepTag).(*Module)
-			deps.RLibs = append(deps.RLibs, RustLibrary{Path: profiler_builtins.OutputFile().Path(), CrateName: profiler_builtins.CrateName()})
+			m := ctx.GetDirectDepProxyWithTag(ProfilerBuiltins, rlibDepTag)
+			profiler_builtins := android.OtherModuleProviderOrDefault(ctx, m, cc.LinkableInfoProvider)
+			deps.RLibs = append(deps.RLibs, RustLibrary{Path: profiler_builtins.OutputFile.Path(), CrateName: profiler_builtins.CrateName})
 		}
 
 		if cc.EnableContinuousCoverage(ctx) {
diff --git a/rust/coverage_test.go b/rust/coverage_test.go
index 0f599d7..f9198f1 100644
--- a/rust/coverage_test.go
+++ b/rust/coverage_test.go
@@ -51,10 +51,10 @@
 	}
 
 	// Just test the dylib variants unless the library coverage logic changes to distinguish between the types.
-	libfooCov := ctx.ModuleForTests("libfoo_cov", "android_arm64_armv8-a_dylib_cov").Rule("rustc")
-	libbarNoCov := ctx.ModuleForTests("libbar_nocov", "android_arm64_armv8-a_dylib").Rule("rustc")
-	fizzCov := ctx.ModuleForTests("fizz_cov", "android_arm64_armv8-a_cov").Rule("rustc")
-	buzzNoCov := ctx.ModuleForTests("buzzNoCov", "android_arm64_armv8-a").Rule("rustc")
+	libfooCov := ctx.ModuleForTests(t, "libfoo_cov", "android_arm64_armv8-a_dylib_cov").Rule("rustc")
+	libbarNoCov := ctx.ModuleForTests(t, "libbar_nocov", "android_arm64_armv8-a_dylib").Rule("rustc")
+	fizzCov := ctx.ModuleForTests(t, "fizz_cov", "android_arm64_armv8-a_cov").Rule("rustc")
+	buzzNoCov := ctx.ModuleForTests(t, "buzzNoCov", "android_arm64_armv8-a").Rule("rustc")
 
 	rustcCoverageFlags := []string{"-C instrument-coverage", " -g "}
 	for _, flag := range rustcCoverageFlags {
@@ -103,7 +103,7 @@
 			srcs: ["foo.rs"],
 		}`)
 
-	fizz := ctx.ModuleForTests("fizz", "android_arm64_armv8-a_cov").Rule("rustc")
+	fizz := ctx.ModuleForTests(t, "fizz", "android_arm64_armv8-a_cov").Rule("rustc")
 	if !strings.Contains(fizz.Args["linkFlags"], "libprofile-clang-extras.a") {
 		t.Fatalf("missing expected coverage 'libprofile-clang-extras' dependency in linkFlags: %#v", fizz.Args["linkFlags"])
 	}
diff --git a/rust/fuzz.go b/rust/fuzz.go
index 1770d2e..9e8efd7 100644
--- a/rust/fuzz.go
+++ b/rust/fuzz.go
@@ -121,7 +121,7 @@
 	return out
 }
 
-func (fuzzer *fuzzDecorator) stdLinkage(ctx *depsContext) RustLinkage {
+func (fuzzer *fuzzDecorator) stdLinkage(device bool) RustLinkage {
 	return RlibLinkage
 }
 
@@ -130,7 +130,7 @@
 }
 
 func (fuzz *fuzzDecorator) install(ctx ModuleContext) {
-	fuzz.fuzzPackagedModule = cc.PackageFuzzModule(ctx, fuzz.fuzzPackagedModule, pctx)
+	fuzz.fuzzPackagedModule = cc.PackageFuzzModule(ctx, fuzz.fuzzPackagedModule)
 
 	installBase := "fuzz"
 
diff --git a/rust/fuzz_test.go b/rust/fuzz_test.go
index 6cb8b93..bdcfbbb 100644
--- a/rust/fuzz_test.go
+++ b/rust/fuzz_test.go
@@ -41,7 +41,7 @@
 	`)
 
 	// Check that appropriate dependencies are added and that the rustlib linkage is correct.
-	fuzz_libtest_mod := ctx.ModuleForTests("fuzz_libtest", "android_arm64_armv8-a_fuzzer").Module().(*Module)
+	fuzz_libtest_mod := ctx.ModuleForTests(t, "fuzz_libtest", "android_arm64_armv8-a_fuzzer").Module().(*Module)
 	if !android.InList("liblibfuzzer_sys.rlib-std", fuzz_libtest_mod.Properties.AndroidMkRlibs) {
 		t.Errorf("liblibfuzzer_sys rlib library dependency missing for rust_fuzz module. %#v", fuzz_libtest_mod.Properties.AndroidMkRlibs)
 	}
@@ -50,21 +50,21 @@
 	}
 
 	// Check that compiler flags are set appropriately .
-	fuzz_libtest := ctx.ModuleForTests("fuzz_libtest", "android_arm64_armv8-a_fuzzer").Rule("rustc")
+	fuzz_libtest := ctx.ModuleForTests(t, "fuzz_libtest", "android_arm64_armv8-a_fuzzer").Rule("rustc")
 	if !strings.Contains(fuzz_libtest.Args["rustcFlags"], "-C passes='sancov-module'") ||
 		!strings.Contains(fuzz_libtest.Args["rustcFlags"], "--cfg fuzzing") {
 		t.Errorf("rust_fuzz module does not contain the expected flags (sancov-module, cfg fuzzing).")
 	}
 
 	// Check that host modules support fuzzing.
-	host_fuzzer := ctx.ModuleForTests("fuzz_libtest", "android_arm64_armv8-a_fuzzer").Rule("rustc")
+	host_fuzzer := ctx.ModuleForTests(t, "fuzz_libtest", "android_arm64_armv8-a_fuzzer").Rule("rustc")
 	if !strings.Contains(host_fuzzer.Args["rustcFlags"], "-C passes='sancov-module'") ||
 		!strings.Contains(host_fuzzer.Args["rustcFlags"], "--cfg fuzzing") {
 		t.Errorf("rust_fuzz_host module does not contain the expected flags (sancov-module, cfg fuzzing).")
 	}
 
 	// Check that dependencies have 'fuzzer' variants produced for them as well.
-	libtest_fuzzer := ctx.ModuleForTests("libtest_fuzzing", "android_arm64_armv8-a_rlib_rlib-std_fuzzer").Output("libtest_fuzzing.rlib")
+	libtest_fuzzer := ctx.ModuleForTests(t, "libtest_fuzzing", "android_arm64_armv8-a_rlib_rlib-std_fuzzer").Output("libtest_fuzzing.rlib")
 	if !strings.Contains(libtest_fuzzer.Args["rustcFlags"], "-C passes='sancov-module'") ||
 		!strings.Contains(libtest_fuzzer.Args["rustcFlags"], "--cfg fuzzing") {
 		t.Errorf("rust_fuzz dependent library does not contain the expected flags (sancov-module, cfg fuzzing).")
@@ -93,7 +93,7 @@
 			}
 	`)
 
-	fuzz_libtest := ctx.ModuleForTests("fuzz_libtest", "android_arm64_armv8-a_fuzzer").Module().(*Module)
+	fuzz_libtest := ctx.ModuleForTests(t, "fuzz_libtest", "android_arm64_armv8-a_fuzzer").Module().(*Module)
 
 	if !strings.Contains(fuzz_libtest.FuzzSharedLibraries().String(), ":libcc_direct_dep.so") {
 		t.Errorf("rust_fuzz does not contain the expected bundled direct shared libs ('libcc_direct_dep'): %#v", fuzz_libtest.FuzzSharedLibraries().String())
@@ -134,9 +134,9 @@
 			}
 	`)
 
-	fuzz_shared_libtest := ctx.ModuleForTests("fuzz_shared_libtest", "android_arm64_armv8-a_fuzzer").Module().(cc.LinkableInterface)
-	fuzz_static_libtest := ctx.ModuleForTests("fuzz_static_libtest", "android_arm64_armv8-a_fuzzer").Module().(cc.LinkableInterface)
-	fuzz_staticffi_libtest := ctx.ModuleForTests("fuzz_staticffi_libtest", "android_arm64_armv8-a_fuzzer").Module().(cc.LinkableInterface)
+	fuzz_shared_libtest := ctx.ModuleForTests(t, "fuzz_shared_libtest", "android_arm64_armv8-a_fuzzer").Module().(cc.LinkableInterface)
+	fuzz_static_libtest := ctx.ModuleForTests(t, "fuzz_static_libtest", "android_arm64_armv8-a_fuzzer").Module().(cc.LinkableInterface)
+	fuzz_staticffi_libtest := ctx.ModuleForTests(t, "fuzz_staticffi_libtest", "android_arm64_armv8-a_fuzzer").Module().(cc.LinkableInterface)
 
 	if !strings.Contains(fuzz_shared_libtest.FuzzSharedLibraries().String(), ":libcc_transitive_dep.so") {
 		t.Errorf("cc_fuzz does not contain the expected bundled transitive shared libs from rust_ffi_shared ('libcc_transitive_dep'): %#v", fuzz_shared_libtest.FuzzSharedLibraries().String())
@@ -145,6 +145,6 @@
 		t.Errorf("cc_fuzz does not contain the expected bundled transitive shared libs from rust_ffi_static ('libcc_transitive_dep'): %#v", fuzz_static_libtest.FuzzSharedLibraries().String())
 	}
 	if !strings.Contains(fuzz_staticffi_libtest.FuzzSharedLibraries().String(), ":libcc_transitive_dep.so") {
-		t.Errorf("cc_fuzz does not contain the expected bundled transitive shared libs from rust_ffi_rlib ('libcc_transitive_dep'): %#v", fuzz_staticffi_libtest.FuzzSharedLibraries().String())
+		t.Errorf("cc_fuzz does not contain the expected bundled transitive shared libs from rust_ffi_static ('libcc_transitive_dep'): %#v", fuzz_staticffi_libtest.FuzzSharedLibraries().String())
 	}
 }
diff --git a/rust/image.go b/rust/image.go
index 26929b1..51b8289 100644
--- a/rust/image.go
+++ b/rust/image.go
@@ -85,7 +85,7 @@
 	mod.Properties.VendorVariantNeeded = b
 }
 
-func (mod *Module) SnapshotVersion(mctx android.BaseModuleContext) string {
+func (mod *Module) SnapshotVersion(mctx android.ImageInterfaceContext) string {
 	if snapshot, ok := mod.compiler.(cc.SnapshotInterface); ok {
 		return snapshot.Version()
 	} else {
@@ -94,35 +94,35 @@
 	}
 }
 
-func (mod *Module) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
+func (mod *Module) VendorVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return mod.Properties.VendorVariantNeeded
 }
 
-func (mod *Module) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
+func (mod *Module) ProductVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return mod.Properties.ProductVariantNeeded
 }
 
-func (mod *Module) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (mod *Module) VendorRamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return mod.Properties.VendorRamdiskVariantNeeded
 }
 
-func (mod *Module) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
+func (mod *Module) CoreVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return mod.Properties.CoreVariantNeeded
 }
 
-func (mod *Module) RamdiskVariantNeeded(android.BaseModuleContext) bool {
+func (mod *Module) RamdiskVariantNeeded(android.ImageInterfaceContext) bool {
 	return mod.Properties.RamdiskVariantNeeded
 }
 
-func (mod *Module) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (mod *Module) DebugRamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return false
 }
 
-func (mod *Module) RecoveryVariantNeeded(android.BaseModuleContext) bool {
+func (mod *Module) RecoveryVariantNeeded(android.ImageInterfaceContext) bool {
 	return mod.Properties.RecoveryVariantNeeded
 }
 
-func (mod *Module) ExtraImageVariations(android.BaseModuleContext) []string {
+func (mod *Module) ExtraImageVariations(android.ImageInterfaceContext) []string {
 	return mod.Properties.ExtraVariants
 }
 
@@ -213,7 +213,7 @@
 	return mod.InVendor() || mod.InProduct()
 }
 
-func (mod *Module) SetImageVariation(ctx android.BaseModuleContext, variant string) {
+func (mod *Module) SetImageVariation(ctx android.ImageInterfaceContext, variant string) {
 	if variant == android.VendorRamdiskVariation {
 		mod.MakeAsPlatform()
 	} else if variant == android.RecoveryVariation {
@@ -231,7 +231,7 @@
 	}
 }
 
-func (mod *Module) ImageMutatorBegin(mctx android.BaseModuleContext) {
+func (mod *Module) ImageMutatorBegin(mctx android.ImageInterfaceContext) {
 	if Bool(mod.VendorProperties.Double_loadable) {
 		mctx.PropertyErrorf("double_loadable",
 			"Rust modules do not yet support double loading")
diff --git a/rust/image_test.go b/rust/image_test.go
index d84eb10..e5ecd79 100644
--- a/rust/image_test.go
+++ b/rust/image_test.go
@@ -22,14 +22,13 @@
 	"android/soong/cc"
 )
 
-// Test that cc modules can depend on vendor_available rust_ffi_rlib/rust_ffi_static libraries.
+// Test that cc modules can depend on vendor_available rust_ffi_static libraries.
 func TestVendorLinkage(t *testing.T) {
 	ctx := testRust(t, `
 			cc_binary {
 				name: "fizz_vendor_available",
 				static_libs: [
 					"libfoo_vendor",
-					"libfoo_vendor_static"
 				],
 				vendor_available: true,
 			}
@@ -38,24 +37,18 @@
 				static_libs: ["libfoo_vendor"],
 				soc_specific: true,
 			}
-			rust_ffi_rlib {
-				name: "libfoo_vendor",
-				crate_name: "foo",
-				srcs: ["foo.rs"],
-				vendor_available: true,
-			}
 			rust_ffi_static {
-				name: "libfoo_vendor_static",
+				name: "libfoo_vendor",
 				crate_name: "foo",
 				srcs: ["foo.rs"],
 				vendor_available: true,
 			}
 		`)
 
-	vendorBinary := ctx.ModuleForTests("fizz_vendor_available", "android_vendor_arm64_armv8-a").Module().(*cc.Module)
+	vendorBinary := ctx.ModuleForTests(t, "fizz_vendor_available", "android_vendor_arm64_armv8-a").Module().(*cc.Module)
 
-	if android.InList("libfoo_vendor_static.vendor", vendorBinary.Properties.AndroidMkStaticLibs) {
-		t.Errorf("vendorBinary should not have a staticlib dependency on libfoo_vendor_static.vendor: %#v", vendorBinary.Properties.AndroidMkStaticLibs)
+	if android.InList("libfoo_vendor.vendor", vendorBinary.Properties.AndroidMkStaticLibs) {
+		t.Errorf("vendorBinary should not have a staticlib dependency on libfoo_vendor.vendor: %#v", vendorBinary.Properties.AndroidMkStaticLibs)
 	}
 }
 
@@ -71,7 +64,7 @@
 			}
 		`)
 
-	vendor := ctx.ModuleForTests("libfoo", "android_vendor_arm64_armv8-a_shared").Rule("rustc")
+	vendor := ctx.ModuleForTests(t, "libfoo", "android_vendor_arm64_armv8-a_shared").Rule("rustc")
 
 	if !strings.Contains(vendor.Args["rustcFlags"], "--cfg 'android_vndk'") {
 		t.Errorf("missing \"--cfg 'android_vndk'\" for libfoo vendor variant, rustcFlags: %#v", vendor.Args["rustcFlags"])
@@ -83,7 +76,7 @@
 		t.Errorf("unexpected \"--cfg 'android_product'\" for libfoo vendor variant, rustcFlags: %#v", vendor.Args["rustcFlags"])
 	}
 
-	product := ctx.ModuleForTests("libfoo", "android_product_arm64_armv8-a_shared").Rule("rustc")
+	product := ctx.ModuleForTests(t, "libfoo", "android_product_arm64_armv8-a_shared").Rule("rustc")
 	if !strings.Contains(product.Args["rustcFlags"], "--cfg 'android_vndk'") {
 		t.Errorf("missing \"--cfg 'android_vndk'\" for libfoo product variant, rustcFlags: %#v", product.Args["rustcFlags"])
 	}
@@ -94,7 +87,7 @@
 		t.Errorf("missing \"--cfg 'android_product'\" for libfoo product variant, rustcFlags: %#v", product.Args["rustcFlags"])
 	}
 
-	system := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Rule("rustc")
+	system := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_shared").Rule("rustc")
 	if strings.Contains(system.Args["rustcFlags"], "--cfg 'android_vndk'") {
 		t.Errorf("unexpected \"--cfg 'android_vndk'\" for libfoo system variant, rustcFlags: %#v", system.Args["rustcFlags"])
 	}
@@ -107,36 +100,29 @@
 
 }
 
-// Test that cc modules can link against vendor_ramdisk_available rust_ffi_rlib and rust_ffi_static libraries.
+// Test that cc modules can link against vendor_ramdisk_available rust_ffi_static libraries.
 func TestVendorRamdiskLinkage(t *testing.T) {
 	ctx := testRust(t, `
 			cc_library_shared {
 				name: "libcc_vendor_ramdisk",
 				static_libs: [
 					"libfoo_vendor_ramdisk",
-					"libfoo_static_vendor_ramdisk"
 				],
 				system_shared_libs: [],
 				vendor_ramdisk_available: true,
 			}
-			rust_ffi_rlib {
-				name: "libfoo_vendor_ramdisk",
-				crate_name: "foo",
-				srcs: ["foo.rs"],
-				vendor_ramdisk_available: true,
-			}
 			rust_ffi_static {
-				name: "libfoo_static_vendor_ramdisk",
+				name: "libfoo_vendor_ramdisk",
 				crate_name: "foo",
 				srcs: ["foo.rs"],
 				vendor_ramdisk_available: true,
 			}
 		`)
 
-	vendorRamdiskLibrary := ctx.ModuleForTests("libcc_vendor_ramdisk", "android_vendor_ramdisk_arm64_armv8-a_shared").Module().(*cc.Module)
+	vendorRamdiskLibrary := ctx.ModuleForTests(t, "libcc_vendor_ramdisk", "android_vendor_ramdisk_arm64_armv8-a_shared").Module().(*cc.Module)
 
-	if android.InList("libfoo_static_vendor_ramdisk.vendor_ramdisk", vendorRamdiskLibrary.Properties.AndroidMkStaticLibs) {
-		t.Errorf("libcc_vendor_ramdisk should not have a dependency on the libfoo_static_vendor_ramdisk static library")
+	if android.InList("libfoo_vendor_ramdisk.vendor_ramdisk", vendorRamdiskLibrary.Properties.AndroidMkStaticLibs) {
+		t.Errorf("libcc_vendor_ramdisk should not have a dependency on the libfoo_vendor_ramdisk static library")
 	}
 }
 
@@ -158,7 +144,7 @@
 }
 
 func checkInstallPartition(t *testing.T, ctx *android.TestContext, name, variant, expected string) {
-	mod := ctx.ModuleForTests(name, variant).Module().(*Module)
+	mod := ctx.ModuleForTests(t, name, variant).Module().(*Module)
 	partitionDefined := false
 	checkPartition := func(specific bool, partition string) {
 		if specific {
diff --git a/rust/library.go b/rust/library.go
index 7db8f36..7f5861f 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -21,9 +21,11 @@
 	"strings"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/depset"
 
 	"android/soong/android"
 	"android/soong/cc"
+	cc_config "android/soong/cc/config"
 )
 
 var (
@@ -39,15 +41,10 @@
 	android.RegisterModuleType("rust_library_host_rlib", RustLibraryRlibHostFactory)
 	android.RegisterModuleType("rust_ffi", RustFFIFactory)
 	android.RegisterModuleType("rust_ffi_shared", RustFFISharedFactory)
-	android.RegisterModuleType("rust_ffi_rlib", RustFFIRlibFactory)
 	android.RegisterModuleType("rust_ffi_host", RustFFIHostFactory)
 	android.RegisterModuleType("rust_ffi_host_shared", RustFFISharedHostFactory)
-	android.RegisterModuleType("rust_ffi_host_rlib", RustFFIRlibHostFactory)
-
-	// TODO: Remove when all instances of rust_ffi_static have been switched to rust_ffi_rlib
-	// Alias rust_ffi_static to the rust_ffi_rlib factory
-	android.RegisterModuleType("rust_ffi_static", RustFFIRlibFactory)
-	android.RegisterModuleType("rust_ffi_host_static", RustFFIRlibHostFactory)
+	android.RegisterModuleType("rust_ffi_static", RustLibraryRlibFactory)
+	android.RegisterModuleType("rust_ffi_host_static", RustLibraryRlibHostFactory)
 }
 
 type VariantLibraryProperties struct {
@@ -68,12 +65,28 @@
 	// path to include directories to export to cc_* modules, only relevant for static/shared variants.
 	Export_include_dirs []string `android:"path,arch_variant"`
 
+	// Version script to pass to the linker. By default this will replace the
+	// implicit rustc emitted version script to mirror expected behavior in CC.
+	// This is only relevant for rust_ffi_shared modules which are exposing a
+	// versioned C API.
+	Version_script *string `android:"path,arch_variant"`
+
+	// A version_script formatted text file with additional symbols to export
+	// for rust shared or dylibs which the rustc compiler does not automatically
+	// export, e.g. additional symbols from whole_static_libs. Unlike
+	// Version_script, this is not meant to imply a stable API.
+	Extra_exported_symbols *string `android:"path,arch_variant"`
+
 	// Whether this library is part of the Rust toolchain sysroot.
 	Sysroot *bool
 
-	// Exclude this rust_ffi target from being included in APEXes.
-	// TODO(b/362509506): remove this once stubs are properly supported by rust_ffi targets.
+	// Deprecated - exclude this rust_ffi target from being included in APEXes.
+	// TODO(b/362509506): remove this once all apex_exclude uses are switched to stubs.
 	Apex_exclude *bool
+
+	// Generate stubs to make this library accessible to APEXes.
+	// Can only be set for modules producing shared libraries.
+	Stubs cc.StubsProperties `android:"arch_variant"`
 }
 
 type LibraryMutatedProperties struct {
@@ -101,6 +114,15 @@
 
 	// Whether this library variant should be link libstd via rlibs
 	VariantIsStaticStd bool `blueprint:"mutated"`
+
+	// This variant is a stubs lib
+	BuildStubs bool `blueprint:"mutated"`
+	// This variant is the latest version
+	IsLatestVersion bool `blueprint:"mutated"`
+	// Version of the stubs lib
+	StubsVersion string `blueprint:"mutated"`
+	// List of all stubs versions associated with an implementation lib
+	AllStubsVersions []string `blueprint:"mutated"`
 }
 
 type libraryDecorator struct {
@@ -113,13 +135,30 @@
 	includeDirs       android.Paths
 	sourceProvider    SourceProvider
 
-	isFFI bool
-
 	// table-of-contents file for cdylib crates to optimize out relinking when possible
 	tocFile android.OptionalPath
+
+	// Path to the file containing the APIs exported by this library
+	stubsSymbolFilePath    android.Path
+	apiListCoverageXmlPath android.ModuleOutPath
+	versionScriptPath      android.OptionalPath
+}
+
+func (library *libraryDecorator) stubs() bool {
+	return library.MutatedProperties.BuildStubs
+}
+
+func (library *libraryDecorator) setAPIListCoverageXMLPath(xml android.ModuleOutPath) {
+	library.apiListCoverageXmlPath = xml
+}
+
+func (library *libraryDecorator) libraryProperties() LibraryCompilerProperties {
+	return library.Properties
 }
 
 type libraryInterface interface {
+	cc.VersionedInterface
+
 	rlib() bool
 	dylib() bool
 	static() bool
@@ -156,10 +195,16 @@
 
 	toc() android.OptionalPath
 
-	isFFILibrary() bool
+	IsStubsImplementationRequired() bool
+	setAPIListCoverageXMLPath(out android.ModuleOutPath)
+
+	libraryProperties() LibraryCompilerProperties
 }
 
 func (library *libraryDecorator) nativeCoverage() bool {
+	if library.BuildStubs() {
+		return false
+	}
 	return true
 }
 
@@ -261,18 +306,96 @@
 	}
 }
 
-func (library *libraryDecorator) stdLinkage(ctx *depsContext) RustLinkage {
-	if library.static() || library.MutatedProperties.VariantIsStaticStd || (library.rlib() && library.isFFILibrary()) {
+func (library *libraryDecorator) stdLinkage(device bool) RustLinkage {
+	if library.static() || library.MutatedProperties.VariantIsStaticStd {
 		return RlibLinkage
 	} else if library.baseCompiler.preferRlib() {
 		return RlibLinkage
 	}
-	return DefaultLinkage
+	return DylibLinkage
 }
 
 var _ compiler = (*libraryDecorator)(nil)
 var _ libraryInterface = (*libraryDecorator)(nil)
+var _ cc.VersionedInterface = (*libraryDecorator)(nil)
 var _ exportedFlagsProducer = (*libraryDecorator)(nil)
+var _ cc.VersionedInterface = (*libraryDecorator)(nil)
+
+func (library *libraryDecorator) HasLLNDKStubs() bool {
+	// Rust LLNDK is currently unsupported
+	return false
+}
+
+func (library *libraryDecorator) HasVendorPublicLibrary() bool {
+	// Rust does not support vendor_public_library yet.
+	return false
+}
+
+func (library *libraryDecorator) HasLLNDKHeaders() bool {
+	// Rust LLNDK is currently unsupported
+	return false
+}
+
+func (library *libraryDecorator) HasStubsVariants() bool {
+	// Just having stubs.symbol_file is enough to create a stub variant. In that case
+	// the stub for the future API level is created.
+	return library.Properties.Stubs.Symbol_file != nil ||
+		len(library.Properties.Stubs.Versions) > 0
+}
+
+func (library *libraryDecorator) IsStubsImplementationRequired() bool {
+	return BoolDefault(library.Properties.Stubs.Implementation_installable, true)
+}
+
+func (library *libraryDecorator) GetAPIListCoverageXMLPath() android.ModuleOutPath {
+	return library.apiListCoverageXmlPath
+}
+
+func (library *libraryDecorator) AllStubsVersions() []string {
+	return library.MutatedProperties.AllStubsVersions
+}
+
+func (library *libraryDecorator) SetAllStubsVersions(versions []string) {
+	library.MutatedProperties.AllStubsVersions = versions
+}
+
+func (library *libraryDecorator) SetStubsVersion(version string) {
+	library.MutatedProperties.StubsVersion = version
+}
+
+func (library *libraryDecorator) SetBuildStubs(isLatest bool) {
+	library.MutatedProperties.BuildStubs = true
+	library.MutatedProperties.IsLatestVersion = isLatest
+}
+
+func (library *libraryDecorator) BuildStubs() bool {
+	return library.MutatedProperties.BuildStubs
+}
+
+func (library *libraryDecorator) ImplementationModuleName(name string) string {
+	return name
+}
+
+func (library *libraryDecorator) IsLLNDKMovedToApex() bool {
+	// Rust does not support LLNDK.
+	return false
+}
+
+func (library *libraryDecorator) StubsVersion() string {
+	return library.MutatedProperties.StubsVersion
+}
+
+// stubsVersions implements cc.VersionedInterface.
+func (library *libraryDecorator) StubsVersions(ctx android.BaseModuleContext) []string {
+	if !library.HasStubsVariants() {
+		return nil
+	}
+
+	// Future API level is implicitly added if there isn't
+	versions := cc.AddCurrentVersionIfNotPresent(library.Properties.Stubs.Versions)
+	cc.NormalizeVersions(ctx, versions)
+	return versions
+}
 
 // rust_library produces all Rust variants (rust_library_dylib and
 // rust_library_rlib).
@@ -282,8 +405,7 @@
 	return module.Init()
 }
 
-// rust_ffi produces all FFI variants (rust_ffi_shared, rust_ffi_static, and
-// rust_ffi_rlib).
+// rust_ffi produces all FFI variants (rust_ffi_shared, rust_ffi_static).
 func RustFFIFactory() android.Module {
 	module, library := NewRustLibrary(android.HostAndDeviceSupported)
 	library.BuildOnlyFFI()
@@ -297,7 +419,7 @@
 	return module.Init()
 }
 
-// rust_library_rlib produces an rlib (Rust crate type "rlib").
+// rust_library_rlib and rust_ffi_static produces an rlib (Rust crate type "rlib").
 func RustLibraryRlibFactory() android.Module {
 	module, library := NewRustLibrary(android.HostAndDeviceSupported)
 	library.BuildOnlyRlib()
@@ -321,7 +443,7 @@
 }
 
 // rust_ffi_host produces all FFI variants for the host
-// (rust_ffi_rlib_host, rust_ffi_static_host, and rust_ffi_shared_host).
+// (rust_ffi_static_host and rust_ffi_shared_host).
 func RustFFIHostFactory() android.Module {
 	module, library := NewRustLibrary(android.HostSupported)
 	library.BuildOnlyFFI()
@@ -336,8 +458,8 @@
 	return module.Init()
 }
 
-// rust_library_rlib_host produces an rlib for the host (Rust crate
-// type "rlib").
+// rust_library_rlib_host and rust_ffi_static_host produces an rlib for the host
+// (Rust crate type "rlib").
 func RustLibraryRlibHostFactory() android.Module {
 	module, library := NewRustLibrary(android.HostSupported)
 	library.BuildOnlyRlib()
@@ -352,23 +474,16 @@
 	return module.Init()
 }
 
-// rust_ffi_rlib_host produces an rlib for the host (Rust crate
-// type "rlib").
-func RustFFIRlibHostFactory() android.Module {
-	module, library := NewRustLibrary(android.HostSupported)
-	library.BuildOnlyRlib()
+func CheckRustLibraryProperties(mctx android.DefaultableHookContext) {
+	lib := mctx.Module().(*Module).compiler.(libraryInterface)
+	if !lib.buildShared() {
+		if lib.libraryProperties().Stubs.Symbol_file != nil ||
+			lib.libraryProperties().Stubs.Implementation_installable != nil ||
+			len(lib.libraryProperties().Stubs.Versions) > 0 {
 
-	library.isFFI = true
-	return module.Init()
-}
-
-// rust_ffi_rlib produces an rlib (Rust crate type "rlib").
-func RustFFIRlibFactory() android.Module {
-	module, library := NewRustLibrary(android.HostAndDeviceSupported)
-	library.BuildOnlyRlib()
-
-	library.isFFI = true
-	return module.Init()
+			mctx.PropertyErrorf("stubs", "stubs properties can only be set for rust_ffi or rust_ffi_shared modules")
+		}
+	}
 }
 
 func (library *libraryDecorator) BuildOnlyFFI() {
@@ -377,8 +492,6 @@
 	library.MutatedProperties.BuildRlib = true
 	library.MutatedProperties.BuildShared = true
 	library.MutatedProperties.BuildStatic = false
-
-	library.isFFI = true
 }
 
 func (library *libraryDecorator) BuildOnlyRust() {
@@ -407,8 +520,6 @@
 	library.MutatedProperties.BuildDylib = false
 	library.MutatedProperties.BuildShared = false
 	library.MutatedProperties.BuildStatic = true
-
-	library.isFFI = true
 }
 
 func (library *libraryDecorator) BuildOnlyShared() {
@@ -416,12 +527,6 @@
 	library.MutatedProperties.BuildDylib = false
 	library.MutatedProperties.BuildStatic = false
 	library.MutatedProperties.BuildShared = true
-
-	library.isFFI = true
-}
-
-func (library *libraryDecorator) isFFILibrary() bool {
-	return library.isFFI
 }
 
 func NewRustLibrary(hod android.HostOrDeviceSupported) (*Module, *libraryDecorator) {
@@ -440,6 +545,7 @@
 
 	module.compiler = library
 
+	module.SetDefaultableHook(CheckRustLibraryProperties)
 	return module, library
 }
 
@@ -483,13 +589,6 @@
 
 	cfgs := library.baseCompiler.Properties.Cfgs.GetOrDefault(ctx, nil)
 
-	if library.dylib() {
-		// We need to add a dependency on std in order to link crates as dylibs.
-		// The hack to add this dependency is guarded by the following cfg so
-		// that we don't force a dependency when it isn't needed.
-		cfgs = append(cfgs, "android_dylib")
-	}
-
 	cfgFlags := cfgsToFlags(cfgs)
 
 	flags.RustFlags = append(flags.RustFlags, cfgFlags...)
@@ -510,7 +609,9 @@
 
 	flags = CommonLibraryCompilerFlags(ctx, flags)
 
-	if library.isFFI {
+	if library.rlib() || library.shared() {
+		// rlibs collect include dirs as well since they are used to
+		// produce staticlibs in the final C linkages
 		library.includeDirs = append(library.includeDirs, android.PathsForModuleSrc(ctx, library.Properties.Include_dirs)...)
 		library.includeDirs = append(library.includeDirs, android.PathsForModuleSrc(ctx, library.Properties.Export_include_dirs)...)
 	}
@@ -573,9 +674,36 @@
 
 	flags.RustFlags = append(flags.RustFlags, deps.depFlags...)
 	flags.LinkFlags = append(flags.LinkFlags, deps.depLinkFlags...)
-	flags.LinkFlags = append(flags.LinkFlags, deps.linkObjects...)
+	flags.LinkFlags = append(flags.LinkFlags, deps.rustLibObjects...)
+	flags.LinkFlags = append(flags.LinkFlags, deps.sharedLibObjects...)
+	flags.LinkFlags = append(flags.LinkFlags, deps.staticLibObjects...)
+	flags.LinkFlags = append(flags.LinkFlags, deps.wholeStaticLibObjects...)
+
+	if String(library.Properties.Version_script) != "" {
+		if String(library.Properties.Extra_exported_symbols) != "" {
+			ctx.ModuleErrorf("version_script and extra_exported_symbols cannot both be set.")
+		}
+
+		if library.shared() {
+			// "-Wl,--android-version-script" signals to the rustcLinker script
+			// that the default version script should be removed.
+			flags.LinkFlags = append(flags.LinkFlags, "-Wl,--android-version-script="+android.PathForModuleSrc(ctx, String(library.Properties.Version_script)).String())
+			deps.LinkerDeps = append(deps.LinkerDeps, android.PathForModuleSrc(ctx, String(library.Properties.Version_script)))
+		} else if !library.static() && !library.rlib() {
+			// We include rlibs here because rust_ffi produces rlib variants
+			ctx.PropertyErrorf("version_script", "can only be set for rust_ffi modules")
+		}
+	}
+
+	if String(library.Properties.Extra_exported_symbols) != "" {
+		// Passing a second version script (rustc calculates and emits a
+		// default version script) will concatenate the first version script.
+		flags.LinkFlags = append(flags.LinkFlags, "-Wl,--version-script="+android.PathForModuleSrc(ctx, String(library.Properties.Extra_exported_symbols)).String())
+		deps.LinkerDeps = append(deps.LinkerDeps, android.PathForModuleSrc(ctx, String(library.Properties.Extra_exported_symbols)))
+	}
 
 	if library.dylib() {
+
 		// We need prefer-dynamic for now to avoid linking in the static stdlib. See:
 		// https://github.com/rust-lang/rust/issues/19680
 		// https://github.com/rust-lang/rust/issues/34909
@@ -583,7 +711,11 @@
 	}
 
 	// Call the appropriate builder for this library type
-	if library.rlib() {
+	if library.stubs() {
+		ccFlags := library.getApiStubsCcFlags(ctx)
+		stubObjs := library.compileModuleLibApiStubs(ctx, ccFlags)
+		cc.BuildRustStubs(ctx, outputFile, stubObjs, ccFlags)
+	} else if library.rlib() {
 		ret.kytheFile = TransformSrctoRlib(ctx, crateRootPath, deps, flags, outputFile).kytheFile
 	} else if library.dylib() {
 		ret.kytheFile = TransformSrctoDylib(ctx, crateRootPath, deps, flags, outputFile).kytheFile
@@ -593,19 +725,30 @@
 		ret.kytheFile = TransformSrctoShared(ctx, crateRootPath, deps, flags, outputFile).kytheFile
 	}
 
+	// rlibs and dylibs propagate their shared, whole static, and rustlib dependencies
 	if library.rlib() || library.dylib() {
 		library.flagExporter.exportLinkDirs(deps.linkDirs...)
-		library.flagExporter.exportLinkObjects(deps.linkObjects...)
+		library.flagExporter.exportRustLibs(deps.rustLibObjects...)
+		library.flagExporter.exportSharedLibs(deps.sharedLibObjects...)
+		library.flagExporter.exportWholeStaticLibs(deps.wholeStaticLibObjects...)
 	}
 
+	// rlibs also propagate their staticlibs dependencies
+	if library.rlib() {
+		library.flagExporter.exportStaticLibs(deps.staticLibObjects...)
+	}
 	// Since we have FFI rlibs, we need to collect their includes as well
-	if library.static() || library.shared() || library.rlib() {
-		android.SetProvider(ctx, cc.FlagExporterInfoProvider, cc.FlagExporterInfo{
+	if library.static() || library.shared() || library.rlib() || library.stubs() {
+		ccExporter := cc.FlagExporterInfo{
 			IncludeDirs: android.FirstUniquePaths(library.includeDirs),
-		})
+		}
+		if library.rlib() {
+			ccExporter.RustRlibDeps = append(ccExporter.RustRlibDeps, deps.reexportedCcRlibDeps...)
+		}
+		android.SetProvider(ctx, cc.FlagExporterInfoProvider, ccExporter)
 	}
 
-	if library.shared() {
+	if library.shared() || library.stubs() {
 		// Optimize out relinking against shared libraries whose interface hasn't changed by
 		// depending on a table of contents file instead of the library itself.
 		tocFile := outputFile.ReplaceExtension(ctx, flags.Toolchain.SharedLibSuffix()[1:]+".toc")
@@ -616,19 +759,22 @@
 			TableOfContents: android.OptionalPathForPath(tocFile),
 			SharedLibrary:   outputFile,
 			Target:          ctx.Target(),
+			IsStubs:         library.BuildStubs(),
 		})
 	}
 
 	if library.static() {
-		depSet := android.NewDepSetBuilder[android.Path](android.TOPOLOGICAL).Direct(outputFile).Build()
+		depSet := depset.NewBuilder[android.Path](depset.TOPOLOGICAL).Direct(outputFile).Build()
 		android.SetProvider(ctx, cc.StaticLibraryInfoProvider, cc.StaticLibraryInfo{
 			StaticLibrary: outputFile,
 
 			TransitiveStaticLibrariesForOrdering: depSet,
 		})
 	}
+	cc.AddStubDependencyProviders(ctx)
 
-	library.flagExporter.setProvider(ctx)
+	// Set our flagexporter provider to export relevant Rust flags
+	library.flagExporter.setRustProvider(ctx)
 
 	return ret
 }
@@ -646,6 +792,53 @@
 	}
 }
 
+func (library *libraryDecorator) getApiStubsCcFlags(ctx ModuleContext) cc.Flags {
+	ccFlags := cc.Flags{}
+	toolchain := cc_config.FindToolchain(ctx.Os(), ctx.Arch())
+
+	platformSdkVersion := ""
+	if ctx.Device() {
+		platformSdkVersion = ctx.Config().PlatformSdkVersion().String()
+	}
+	minSdkVersion := cc.MinSdkVersion(ctx.RustModule(), cc.CtxIsForPlatform(ctx), ctx.Device(), platformSdkVersion)
+
+	// Collect common CC compilation flags
+	ccFlags = cc.CommonLinkerFlags(ctx, ccFlags, true, toolchain, false)
+	ccFlags = cc.CommonLibraryLinkerFlags(ctx, ccFlags, toolchain, library.getStem(ctx))
+	ccFlags = cc.AddStubLibraryCompilerFlags(ccFlags)
+	ccFlags = cc.AddTargetFlags(ctx, ccFlags, toolchain, minSdkVersion, false)
+
+	return ccFlags
+}
+
+func (library *libraryDecorator) compileModuleLibApiStubs(ctx ModuleContext, ccFlags cc.Flags) cc.Objects {
+	mod := ctx.RustModule()
+
+	symbolFile := String(library.Properties.Stubs.Symbol_file)
+	library.stubsSymbolFilePath = android.PathForModuleSrc(ctx, symbolFile)
+
+	apiParams := cc.ApiStubsParams{
+		NotInPlatform:  mod.NotInPlatform(),
+		IsNdk:          mod.IsNdk(ctx.Config()),
+		BaseModuleName: mod.BaseModuleName(),
+		ModuleName:     ctx.ModuleName(),
+	}
+	flag := cc.GetApiStubsFlags(apiParams)
+
+	nativeAbiResult := cc.ParseNativeAbiDefinition(ctx, symbolFile,
+		android.ApiLevelOrPanic(ctx, library.MutatedProperties.StubsVersion), flag)
+	objs := cc.CompileStubLibrary(ctx, ccFlags, nativeAbiResult.StubSrc, mod.getSharedFlags())
+
+	library.versionScriptPath = android.OptionalPathForPath(nativeAbiResult.VersionScript)
+
+	// Parse symbol file to get API list for coverage
+	if library.StubsVersion() == "current" && ctx.PrimaryArch() && !mod.InRecovery() && !mod.InProduct() && !mod.InVendor() {
+		library.apiListCoverageXmlPath = cc.ParseSymbolFileForAPICoverage(ctx, symbolFile)
+	}
+
+	return objs
+}
+
 func (library *libraryDecorator) rustdoc(ctx ModuleContext, flags Flags,
 	deps PathDeps) android.OptionalPath {
 	// rustdoc has builtin support for documenting config specific information
@@ -683,6 +876,20 @@
 	library.MutatedProperties.VariantIsDisabled = true
 }
 
+func (library *libraryDecorator) moduleInfoJSON(ctx ModuleContext, moduleInfoJSON *android.ModuleInfoJSON) {
+	library.baseCompiler.moduleInfoJSON(ctx, moduleInfoJSON)
+
+	if library.rlib() {
+		moduleInfoJSON.Class = []string{"RLIB_LIBRARIES"}
+	} else if library.dylib() {
+		moduleInfoJSON.Class = []string{"DYLIB_LIBRARIES"}
+	} else if library.static() {
+		moduleInfoJSON.Class = []string{"STATIC_LIBRARIES"}
+	} else if library.shared() {
+		moduleInfoJSON.Class = []string{"SHARED_LIBRARIES"}
+	}
+}
+
 var validCrateName = regexp.MustCompile("[^a-zA-Z0-9_]+")
 
 func validateLibraryStem(ctx BaseModuleContext, filename string, crate_name string) {
@@ -741,6 +948,9 @@
 }
 
 func (libraryTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+	if ctx.DepTag() == android.PrebuiltDepTag {
+		return sourceVariation
+	}
 	return ""
 }
 
@@ -808,6 +1018,12 @@
 			},
 			sourceDepTag, ctx.ModuleName())
 	}
+
+	if prebuilt, ok := m.compiler.(*prebuiltLibraryDecorator); ok {
+		if Bool(prebuilt.Properties.Force_use_prebuilt) && len(prebuilt.prebuiltSrcs()) > 0 {
+			m.Prebuilt().SetUsePrebuilt(true)
+		}
+	}
 }
 
 type libstdTransitionMutator struct{}
@@ -817,11 +1033,7 @@
 		// Only create a variant if a library is actually being built.
 		if library, ok := m.compiler.(libraryInterface); ok {
 			if library.rlib() && !library.sysroot() {
-				if library.isFFILibrary() {
-					return []string{"rlib-std"}
-				} else {
-					return []string{"rlib-std", "dylib-std"}
-				}
+				return []string{"rlib-std", "dylib-std"}
 			}
 		}
 	}
@@ -829,6 +1041,9 @@
 }
 
 func (libstdTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+	if ctx.DepTag() == android.PrebuiltDepTag {
+		return sourceVariation
+	}
 	return ""
 }
 
diff --git a/rust/library_test.go b/rust/library_test.go
index 35a420c..6db9525 100644
--- a/rust/library_test.go
+++ b/rust/library_test.go
@@ -42,10 +42,10 @@
 		}`)
 
 	// Test all variants are being built.
-	libfooRlib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_rlib_rlib-std").Rule("rustc")
-	libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib").Rule("rustc")
-	libfooFFIRlib := ctx.ModuleForTests("libfoo.ffi", "linux_glibc_x86_64_rlib_rlib-std").Rule("rustc")
-	libfooShared := ctx.ModuleForTests("libfoo.ffi", "linux_glibc_x86_64_shared").Rule("rustc")
+	libfooRlib := ctx.ModuleForTests(t, "libfoo", "linux_glibc_x86_64_rlib_rlib-std").Rule("rustc")
+	libfooDylib := ctx.ModuleForTests(t, "libfoo", "linux_glibc_x86_64_dylib").Rule("rustc")
+	libfooFFIRlib := ctx.ModuleForTests(t, "libfoo.ffi", "linux_glibc_x86_64_rlib_rlib-std").Rule("rustc")
+	libfooShared := ctx.ModuleForTests(t, "libfoo.ffi", "linux_glibc_x86_64_shared").Rule("rustc")
 
 	rlibCrateType := "rlib"
 	dylibCrateType := "dylib"
@@ -82,29 +82,13 @@
 			crate_name: "foo",
 		}`)
 
-	libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib").Rule("rustc")
+	libfooDylib := ctx.ModuleForTests(t, "libfoo", "linux_glibc_x86_64_dylib").Rule("rustc")
 
 	if !strings.Contains(libfooDylib.Args["rustcFlags"], "prefer-dynamic") {
 		t.Errorf("missing prefer-dynamic flag for libfoo dylib, rustcFlags: %#v", libfooDylib.Args["rustcFlags"])
 	}
 }
 
-// Check that we are passing the android_dylib config flag
-func TestAndroidDylib(t *testing.T) {
-	ctx := testRust(t, `
-		rust_library_host_dylib {
-			name: "libfoo",
-			srcs: ["foo.rs"],
-			crate_name: "foo",
-		}`)
-
-	libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib").Rule("rustc")
-
-	if !strings.Contains(libfooDylib.Args["rustcFlags"], "--cfg 'android_dylib'") {
-		t.Errorf("missing android_dylib cfg flag for libfoo dylib, rustcFlags: %#v", libfooDylib.Args["rustcFlags"])
-	}
-}
-
 func TestValidateLibraryStem(t *testing.T) {
 	testRustError(t, "crate_name must be defined.", `
 			rust_library_host {
@@ -150,7 +134,7 @@
 			crate_name: "foo",
 		}`)
 
-	libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared")
+	libfoo := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_shared")
 
 	libfooOutput := libfoo.Rule("rustc")
 	if !strings.Contains(libfooOutput.Args["linkFlags"], "-Wl,-soname=libfoo.so") {
@@ -176,7 +160,7 @@
 			shared_libs: ["libfoo"],
 		}`)
 
-	fizzbuzz := ctx.ModuleForTests("fizzbuzz", "android_arm64_armv8-a").Rule("ld")
+	fizzbuzz := ctx.ModuleForTests(t, "fizzbuzz", "android_arm64_armv8-a").Rule("ld")
 
 	if !android.SuffixInList(fizzbuzz.Implicits.Strings(), "libfoo.so.toc") {
 		t.Errorf("missing expected libfoo.so.toc implicit dependency, instead found: %#v",
@@ -192,7 +176,7 @@
 			crate_name: "foo",
 		}`)
 
-	libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_rlib_rlib-std")
+	libfoo := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_rlib_rlib-std")
 
 	if !android.InList("libstd", libfoo.Module().(*Module).Properties.AndroidMkRlibs) {
 		t.Errorf("Static libstd rlib expected to be a dependency of Rust rlib libraries. Rlib deps are: %#v",
@@ -202,9 +186,9 @@
 
 func TestNativeDependencyOfRlib(t *testing.T) {
 	ctx := testRust(t, `
-		rust_ffi_rlib {
-			name: "libffi_rlib",
-			crate_name: "ffi_rlib",
+		rust_ffi_static {
+			name: "libffi_static",
+			crate_name: "ffi_static",
 			rlibs: ["librust_rlib"],
 			srcs: ["foo.rs"],
 		}
@@ -225,9 +209,9 @@
 		}
 		`)
 
-	rustRlibRlibStd := ctx.ModuleForTests("librust_rlib", "android_arm64_armv8-a_rlib_rlib-std")
-	rustRlibDylibStd := ctx.ModuleForTests("librust_rlib", "android_arm64_armv8-a_rlib_dylib-std")
-	ffiRlib := ctx.ModuleForTests("libffi_rlib", "android_arm64_armv8-a_rlib_rlib-std")
+	rustRlibRlibStd := ctx.ModuleForTests(t, "librust_rlib", "android_arm64_armv8-a_rlib_rlib-std")
+	rustRlibDylibStd := ctx.ModuleForTests(t, "librust_rlib", "android_arm64_armv8-a_rlib_dylib-std")
+	ffiRlib := ctx.ModuleForTests(t, "libffi_static", "android_arm64_armv8-a_rlib_rlib-std")
 
 	modules := []android.TestingModule{
 		rustRlibRlibStd,
@@ -301,10 +285,10 @@
 			],
 		}`)
 
-	libfooRlib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_rlib_rlib-std")
-	libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib")
-	libfooFFIRlib := ctx.ModuleForTests("libfoo.ffi", "linux_glibc_x86_64_rlib_rlib-std")
-	libfooShared := ctx.ModuleForTests("libfoo.ffi", "linux_glibc_x86_64_shared")
+	libfooRlib := ctx.ModuleForTests(t, "libfoo", "linux_glibc_x86_64_rlib_rlib-std")
+	libfooDylib := ctx.ModuleForTests(t, "libfoo", "linux_glibc_x86_64_dylib")
+	libfooFFIRlib := ctx.ModuleForTests(t, "libfoo.ffi", "linux_glibc_x86_64_rlib_rlib-std")
+	libfooShared := ctx.ModuleForTests(t, "libfoo.ffi", "linux_glibc_x86_64_shared")
 
 	for _, static := range []android.TestingModule{libfooRlib, libfooFFIRlib} {
 		if !android.InList("libbar.rlib-std", static.Module().(*Module).Properties.AndroidMkRlibs) {
@@ -346,7 +330,7 @@
 		}
 	`)
 
-	foo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_dylib")
+	foo := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_dylib")
 	foo.Output("libfoo.dylib.so")
 	foo.Output("unstripped/libfoo.dylib.so")
 	// Check that the `cp` rule is using the stripped version as input.
@@ -355,7 +339,7 @@
 		t.Errorf("installed library not based on stripped version: %v", cp.Input)
 	}
 
-	fizzBar := ctx.ModuleForTests("libbar", "android_arm64_armv8-a_dylib").MaybeOutput("unstripped/libbar.dylib.so")
+	fizzBar := ctx.ModuleForTests(t, "libbar", "android_arm64_armv8-a_dylib").MaybeOutput("unstripped/libbar.dylib.so")
 	if fizzBar.Rule != nil {
 		t.Errorf("unstripped library exists, so stripped library has incorrectly been generated")
 	}
@@ -388,15 +372,15 @@
 			prefer_rlib: true,
 		}`)
 
-	libfooDylib := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_dylib").Module().(*Module)
-	libfooRlibStatic := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_rlib_rlib-std").Module().(*Module)
-	libfooRlibDynamic := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_rlib_dylib-std").Module().(*Module)
+	libfooDylib := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_dylib").Module().(*Module)
+	libfooRlibStatic := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_rlib_rlib-std").Module().(*Module)
+	libfooRlibDynamic := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_rlib_dylib-std").Module().(*Module)
 
-	libbarShared := ctx.ModuleForTests("libbar", "android_arm64_armv8-a_shared").Module().(*Module)
-	libbarFFIRlib := ctx.ModuleForTests("libbar", "android_arm64_armv8-a_rlib_rlib-std").Module().(*Module)
+	libbarShared := ctx.ModuleForTests(t, "libbar", "android_arm64_armv8-a_shared").Module().(*Module)
+	libbarFFIRlib := ctx.ModuleForTests(t, "libbar", "android_arm64_armv8-a_rlib_rlib-std").Module().(*Module)
 
 	// prefer_rlib works the same for both rust_library and rust_ffi, so a single check is sufficient here.
-	libbarRlibStd := ctx.ModuleForTests("libbar.prefer_rlib", "android_arm64_armv8-a_shared").Module().(*Module)
+	libbarRlibStd := ctx.ModuleForTests(t, "libbar.prefer_rlib", "android_arm64_armv8-a_shared").Module().(*Module)
 
 	if !android.InList("libstd", libfooRlibStatic.Properties.AndroidMkRlibs) {
 		t.Errorf("rlib-std variant for device rust_library_rlib does not link libstd as an rlib")
@@ -412,10 +396,10 @@
 		t.Errorf("Device rust_ffi_shared does not link libstd as an dylib")
 	}
 	if !android.InList("libstd", libbarFFIRlib.Properties.AndroidMkRlibs) {
-		t.Errorf("Device rust_ffi_rlib does not link libstd as an rlib")
+		t.Errorf("Device rust_ffi_static does not link libstd as an rlib")
 	}
 	if !android.InList("libfoo.rlib-std", libbarFFIRlib.Properties.AndroidMkRlibs) {
-		t.Errorf("Device rust_ffi_rlib does not link dependent rustlib rlib-std variant")
+		t.Errorf("Device rust_ffi_static does not link dependent rustlib rlib-std variant")
 	}
 	if !android.InList("libstd", libbarRlibStd.Properties.AndroidMkRlibs) {
 		t.Errorf("rust_ffi with prefer_rlib does not link libstd as an rlib")
@@ -438,6 +422,349 @@
 			shared_libs: ["libbar"],
 			host_supported: true,
 		}`)
-	libfooStatic := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_static").Rule("cc")
+	libfooStatic := ctx.ModuleForTests(t, "libfoo", "linux_glibc_x86_64_static").Rule("cc")
 	android.AssertStringDoesContain(t, "cFlags for lib module", libfooStatic.Args["cFlags"], " -Irust_includes ")
 }
+
+func TestRustVersionScript(t *testing.T) {
+	ctx := testRust(t, `
+	rust_library {
+		name: "librs",
+		srcs: ["bar.rs"],
+		crate_name: "rs",
+		extra_exported_symbols: "librs.map.txt",
+	}
+	rust_ffi {
+		name: "libffi",
+		srcs: ["foo.rs"],
+		crate_name: "ffi",
+		version_script: "libffi.map.txt",
+	}
+	`)
+
+	//linkFlags
+	librs := ctx.ModuleForTests(t, "librs", "android_arm64_armv8-a_dylib").Rule("rustc")
+	libffi := ctx.ModuleForTests(t, "libffi", "android_arm64_armv8-a_shared").Rule("rustc")
+
+	if !strings.Contains(librs.Args["linkFlags"], "-Wl,--version-script=librs.map.txt") {
+		t.Errorf("missing expected -Wl,--version-script= linker flag for libextended shared lib, linkFlags: %#v",
+			librs.Args["linkFlags"])
+	}
+	if strings.Contains(librs.Args["linkFlags"], "-Wl,--android-version-script=librs.map.txt") {
+		t.Errorf("unexpected -Wl,--android-version-script= linker flag for libextended shared lib, linkFlags: %#v",
+			librs.Args["linkFlags"])
+	}
+
+	if !strings.Contains(libffi.Args["linkFlags"], "-Wl,--android-version-script=libffi.map.txt") {
+		t.Errorf("missing -Wl,--android-version-script= linker flag for libreplaced shared lib, linkFlags: %#v",
+			libffi.Args["linkFlags"])
+	}
+	if strings.Contains(libffi.Args["linkFlags"], "-Wl,--version-script=libffi.map.txt") {
+		t.Errorf("unexpected -Wl,--version-script= linker flag for libextended shared lib, linkFlags: %#v",
+			libffi.Args["linkFlags"])
+	}
+}
+
+func TestRustVersionScriptPropertyErrors(t *testing.T) {
+	testRustError(t, "version_script: can only be set for rust_ffi modules", `
+		rust_library {
+			name: "librs",
+			srcs: ["bar.rs"],
+			crate_name: "rs",
+			version_script: "libbar.map.txt",
+		}`)
+	testRustError(t, "version_script and extra_exported_symbols", `
+		rust_ffi {
+			name: "librs",
+			srcs: ["bar.rs"],
+			crate_name: "rs",
+			version_script: "libbar.map.txt",
+			extra_exported_symbols: "libbar.map.txt",
+		}`)
+}
+
+func TestStubsVersions(t *testing.T) {
+	t.Parallel()
+	bp := `
+		rust_ffi {
+			name: "libfoo",
+			crate_name: "foo",
+			srcs: ["foo.rs"],
+			stubs: {
+				versions: ["29", "R", "current"],
+			},
+		}
+	`
+	ctx := android.GroupFixturePreparers(
+		prepareForRustTest,
+		android.PrepareForTestWithVisibility,
+		rustMockedFiles.AddToFixture(),
+		android.FixtureModifyConfigAndContext(func(config android.Config, ctx *android.TestContext) {
+			config.TestProductVariables.Platform_version_active_codenames = []string{"R"}
+		})).RunTestWithBp(t, bp)
+
+	variants := ctx.ModuleVariantsForTests("libfoo")
+	for _, expectedVer := range []string{"29", "R", "current"} {
+		expectedVariant := "android_arm_armv7-a-neon_shared_" + expectedVer
+		if !android.InList(expectedVariant, variants) {
+			t.Errorf("missing expected variant: %q", expectedVariant)
+		}
+	}
+}
+
+func TestStubsVersions_NotSorted(t *testing.T) {
+	t.Parallel()
+	bp := `
+	rust_ffi_shared {
+		name: "libfoo",
+		crate_name: "foo",
+		srcs: ["foo.rs"],
+		stubs: {
+				versions: ["29", "current", "R"],
+			},
+		}
+	`
+	fixture := android.GroupFixturePreparers(
+		prepareForRustTest,
+		android.PrepareForTestWithVisibility,
+		rustMockedFiles.AddToFixture(),
+
+		android.FixtureModifyConfigAndContext(func(config android.Config, ctx *android.TestContext) {
+			config.TestProductVariables.Platform_version_active_codenames = []string{"R"}
+		}))
+
+	fixture.ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(`"libfoo" .*: versions: not sorted`)).RunTestWithBp(t, bp)
+}
+
+func TestStubsVersions_ParseError(t *testing.T) {
+	t.Parallel()
+	bp := `
+	rust_ffi_shared {
+		name: "libfoo",
+		crate_name: "foo",
+		srcs: ["foo.rs"],
+			stubs: {
+				versions: ["29", "current", "X"],
+			},
+		}
+	`
+	fixture := android.GroupFixturePreparers(
+		prepareForRustTest,
+		android.PrepareForTestWithVisibility,
+		rustMockedFiles.AddToFixture(),
+
+		android.FixtureModifyConfigAndContext(func(config android.Config, ctx *android.TestContext) {
+			config.TestProductVariables.Platform_version_active_codenames = []string{"R"}
+		}))
+
+	fixture.ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(`"libfoo" .*: versions: "X" could not be parsed as an integer and is not a recognized codename`)).RunTestWithBp(t, bp)
+}
+
+func TestVersionedStubs(t *testing.T) {
+	t.Parallel()
+	bp := `
+	rust_ffi_shared {
+		name: "libFoo",
+		crate_name: "Foo",
+		srcs: ["foo.rs"],
+			stubs: {
+				symbol_file: "foo.map.txt",
+				versions: ["1", "2", "3"],
+			},
+		}
+
+	cc_library_shared {
+		name: "libBar",
+		srcs: ["bar.c"],
+		shared_libs: ["libFoo#1"],
+	}
+
+	rust_library {
+		name: "libbar_rs",
+		crate_name: "bar_rs",
+		srcs: ["bar.rs"],
+		shared_libs: ["libFoo#1"],
+	}
+	rust_ffi {
+		name: "libbar_ffi_rs",
+		crate_name: "bar_ffi_rs",
+		srcs: ["bar.rs"],
+		shared_libs: ["libFoo#1"],
+	}
+	`
+
+	ctx := android.GroupFixturePreparers(
+		prepareForRustTest,
+		android.PrepareForTestWithVisibility,
+		rustMockedFiles.AddToFixture()).RunTestWithBp(t, bp)
+
+	variants := ctx.ModuleVariantsForTests("libFoo")
+	expectedVariants := []string{
+		"android_arm64_armv8-a_shared",
+		"android_arm64_armv8-a_shared_1",
+		"android_arm64_armv8-a_shared_2",
+		"android_arm64_armv8-a_shared_3",
+		"android_arm64_armv8-a_shared_current",
+		"android_arm_armv7-a-neon_shared",
+		"android_arm_armv7-a-neon_shared_1",
+		"android_arm_armv7-a-neon_shared_2",
+		"android_arm_armv7-a-neon_shared_3",
+		"android_arm_armv7-a-neon_shared_current",
+	}
+	variantsMismatch := false
+	if len(variants) != len(expectedVariants) {
+		variantsMismatch = true
+	} else {
+		for _, v := range expectedVariants {
+			if !android.InList(v, variants) {
+				variantsMismatch = false
+			}
+		}
+	}
+	if variantsMismatch {
+		t.Errorf("variants of libFoo expected:\n")
+		for _, v := range expectedVariants {
+			t.Errorf("%q\n", v)
+		}
+		t.Errorf(", but got:\n")
+		for _, v := range variants {
+			t.Errorf("%q\n", v)
+		}
+	}
+
+	libBarLinkRule := ctx.ModuleForTests(t, "libBar", "android_arm64_armv8-a_shared").Rule("ld")
+	libBarFlags := libBarLinkRule.Args["libFlags"]
+
+	libBarRsRustcRule := ctx.ModuleForTests(t, "libbar_rs", "android_arm64_armv8-a_dylib").Rule("rustc")
+	libBarRsFlags := libBarRsRustcRule.Args["linkFlags"]
+
+	libBarFfiRsRustcRule := ctx.ModuleForTests(t, "libbar_ffi_rs", "android_arm64_armv8-a_shared").Rule("rustc")
+	libBarFfiRsFlags := libBarFfiRsRustcRule.Args["linkFlags"]
+
+	libFoo1StubPath := "libFoo/android_arm64_armv8-a_shared_1/unstripped/libFoo.so"
+	if !strings.Contains(libBarFlags, libFoo1StubPath) {
+		t.Errorf("%q is not found in %q", libFoo1StubPath, libBarFlags)
+	}
+	if !strings.Contains(libBarRsFlags, libFoo1StubPath) {
+		t.Errorf("%q is not found in %q", libFoo1StubPath, libBarRsFlags)
+	}
+	if !strings.Contains(libBarFfiRsFlags, libFoo1StubPath) {
+		t.Errorf("%q is not found in %q", libFoo1StubPath, libBarFfiRsFlags)
+	}
+}
+
+func TestCheckConflictingExplicitVersions(t *testing.T) {
+	t.Parallel()
+	bp := `
+	cc_library_shared {
+		name: "libbar",
+		srcs: ["bar.c"],
+		shared_libs: ["libfoo", "libfoo#impl"],
+	}
+
+	rust_ffi_shared {
+		name: "libfoo",
+		crate_name: "foo",
+		srcs: ["foo.rs"],
+		stubs: {
+			versions: ["29", "current"],
+		},
+	}
+	`
+	fixture := android.GroupFixturePreparers(
+		prepareForRustTest,
+		android.PrepareForTestWithVisibility,
+		rustMockedFiles.AddToFixture())
+
+	fixture.ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(`duplicate shared libraries with different explicit versions`)).RunTestWithBp(t, bp)
+}
+
+func TestAddnoOverride64GlobalCflags(t *testing.T) {
+	t.Parallel()
+	bp := `
+		cc_library_shared {
+			name: "libclient",
+			srcs: ["foo.c"],
+			shared_libs: ["libfoo#1"],
+		}
+
+		rust_ffi_shared {
+			name: "libfoo",
+			crate_name: "foo",
+			srcs: ["foo.c"],
+			shared_libs: ["libbar"],
+			stubs: {
+				symbol_file: "foo.map.txt",
+				versions: ["1", "2", "3"],
+			},
+		}
+
+		cc_library_shared {
+			name: "libbar",
+			export_include_dirs: ["include/libbar"],
+			srcs: ["foo.c"],
+		}`
+	ctx := android.GroupFixturePreparers(
+		prepareForRustTest,
+		android.PrepareForTestWithVisibility,
+		rustMockedFiles.AddToFixture()).RunTestWithBp(t, bp)
+
+	cFlags := ctx.ModuleForTests(t, "libclient", "android_arm64_armv8-a_shared").Rule("cc").Args["cFlags"]
+
+	if !strings.Contains(cFlags, "${config.NoOverride64GlobalCflags}") {
+		t.Errorf("expected %q in cflags, got %q", "${config.NoOverride64GlobalCflags}", cFlags)
+	}
+}
+
+// Make sure the stubs properties can only be used in modules producing shared libs
+func TestRustStubsFFIOnly(t *testing.T) {
+	testRustError(t, "stubs properties", `
+		rust_library {
+			name: "libfoo",
+			crate_name: "foo",
+			srcs: ["foo.c"],
+			shared_libs: ["libbar"],
+			stubs: {
+				symbol_file: "foo.map.txt",
+			},
+		}
+	`)
+
+	testRustError(t, "stubs properties", `
+		rust_library {
+			name: "libfoo",
+			crate_name: "foo",
+			srcs: ["foo.c"],
+			shared_libs: ["libbar"],
+			stubs: {
+				versions: ["1"],
+			},
+		}
+	`)
+
+	testRustError(t, "stubs properties", `
+		rust_ffi_static {
+			name: "libfoo",
+			crate_name: "foo",
+			srcs: ["foo.c"],
+			shared_libs: ["libbar"],
+			stubs: {
+				symbol_file: "foo.map.txt",
+			},
+		}
+	`)
+	testRustError(t, "stubs properties", `
+		rust_ffi_static {
+			name: "libfoo",
+			crate_name: "foo",
+			srcs: ["foo.c"],
+			shared_libs: ["libbar"],
+			stubs: {
+				versions: ["1"],
+			},
+		}
+	`)
+}
+
+// TODO: When rust_ffi libraries support export_*_lib_headers,
+// add a test similar to cc.TestStubsLibReexportsHeaders
diff --git a/rust/prebuilt.go b/rust/prebuilt.go
index e35e510..7c92dda 100644
--- a/rust/prebuilt.go
+++ b/rust/prebuilt.go
@@ -30,6 +30,8 @@
 	Srcs []string `android:"path,arch_variant"`
 	// directories containing associated rlib dependencies
 	Link_dirs []string `android:"path,arch_variant"`
+
+	Force_use_prebuilt *bool `android:"arch_variant"`
 }
 
 type prebuiltLibraryDecorator struct {
@@ -158,7 +160,7 @@
 
 func (prebuilt *prebuiltLibraryDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) buildOutput {
 	prebuilt.flagExporter.exportLinkDirs(android.PathsForModuleSrc(ctx, prebuilt.Properties.Link_dirs).Strings()...)
-	prebuilt.flagExporter.setProvider(ctx)
+	prebuilt.flagExporter.setRustProvider(ctx)
 	srcPath := prebuiltPath(ctx, prebuilt)
 	prebuilt.baseCompiler.unstrippedOutputFile = srcPath
 	return buildOutput{outputFile: srcPath}
@@ -211,7 +213,7 @@
 
 func (prebuilt *prebuiltProcMacroDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) buildOutput {
 	prebuilt.flagExporter.exportLinkDirs(android.PathsForModuleSrc(ctx, prebuilt.Properties.Link_dirs).Strings()...)
-	prebuilt.flagExporter.setProvider(ctx)
+	prebuilt.flagExporter.setRustProvider(ctx)
 	srcPath := prebuiltPath(ctx, prebuilt)
 	prebuilt.baseCompiler.unstrippedOutputFile = srcPath
 	return buildOutput{outputFile: srcPath}
diff --git a/rust/proc_macro.go b/rust/proc_macro.go
index 1ff6637..837e1a6 100644
--- a/rust/proc_macro.go
+++ b/rust/proc_macro.go
@@ -67,6 +67,7 @@
 func (procMacro *procMacroDecorator) compilerFlags(ctx ModuleContext, flags Flags) Flags {
 	flags = procMacro.baseCompiler.compilerFlags(ctx, flags)
 	flags.RustFlags = append(flags.RustFlags, "--extern proc_macro")
+	flags.RustFlags = append(flags.RustFlags, "-C metadata="+ctx.ModuleName())
 	return flags
 }
 
@@ -99,3 +100,9 @@
 	// Proc_macros are never installed
 	return false
 }
+
+func (library *procMacroDecorator) moduleInfoJSON(ctx ModuleContext, moduleInfoJSON *android.ModuleInfoJSON) {
+	library.baseCompiler.moduleInfoJSON(ctx, moduleInfoJSON)
+
+	moduleInfoJSON.Class = []string{"PROC_MACRO_LIBRARIES"}
+}
diff --git a/rust/proc_macro_test.go b/rust/proc_macro_test.go
index cc81938..8a95eb4 100644
--- a/rust/proc_macro_test.go
+++ b/rust/proc_macro_test.go
@@ -28,7 +28,7 @@
 	  }
 	`)
 
-	libprocmacro := ctx.ModuleForTests("libprocmacro", "linux_glibc_x86_64").Rule("rustc")
+	libprocmacro := ctx.ModuleForTests(t, "libprocmacro", "linux_glibc_x86_64").Rule("rustc")
 
 	if !strings.Contains(libprocmacro.Args["rustcFlags"], "--extern proc_macro") {
 		t.Errorf("--extern proc_macro flag not being passed to rustc for proc macro %#v", libprocmacro.Args["rustcFlags"])
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/protobuf_test.go b/rust/protobuf_test.go
index cae071b..531e034 100644
--- a/rust/protobuf_test.go
+++ b/rust/protobuf_test.go
@@ -41,13 +41,13 @@
 		}
 	`)
 	// Check that libprotobuf is added as a dependency.
-	librust_proto := ctx.ModuleForTests("librust_proto", "android_arm64_armv8-a_dylib").Module().(*Module)
+	librust_proto := ctx.ModuleForTests(t, "librust_proto", "android_arm64_armv8-a_dylib").Module().(*Module)
 	if !android.InList("libprotobuf", librust_proto.Properties.AndroidMkDylibs) {
 		t.Errorf("libprotobuf dependency missing for rust_protobuf (dependency missing from AndroidMkDylibs)")
 	}
 
 	// Make sure the correct plugin is being used.
-	librust_proto_out := ctx.ModuleForTests("librust_proto", "android_arm64_armv8-a_source").Output("buf.rs")
+	librust_proto_out := ctx.ModuleForTests(t, "librust_proto", "android_arm64_armv8-a_source").Output("buf.rs")
 	cmd := librust_proto_out.RuleParams.Command
 	if w := "protoc-gen-rust"; !strings.Contains(cmd, w) {
 		t.Errorf("expected %q in %q", w, cmd)
@@ -62,7 +62,7 @@
 	}
 
 	// Check proto.rs, the second protobuf, is listed as an output
-	librust_proto_outputs := ctx.ModuleForTests("librust_proto", "android_arm64_armv8-a_source").AllOutputs()
+	librust_proto_outputs := ctx.ModuleForTests(t, "librust_proto", "android_arm64_armv8-a_source").AllOutputs()
 	if android.InList("proto.rs", librust_proto_outputs) {
 		t.Errorf("rust_protobuf is not producing multiple outputs; expected 'proto.rs' in list, got: %#v ",
 			librust_proto_outputs)
@@ -92,7 +92,7 @@
 		}
 	`)
 	// Check that librust_exported_proto is added as additional crate to generate source.
-	librust_proto := ctx.ModuleForTests("librust_proto", "android_arm64_armv8-a_source").Module().(*Module).sourceProvider.(*protobufDecorator)
+	librust_proto := ctx.ModuleForTests(t, "librust_proto", "android_arm64_armv8-a_source").Module().(*Module).sourceProvider.(*protobufDecorator)
 	if !android.InList("rust_exported_proto", librust_proto.additionalCrates) {
 		t.Errorf("librust_proto should have librust_exported_proto included as an additional crate for generated source, instead got: %#v", librust_proto.additionalCrates)
 	}
@@ -111,7 +111,7 @@
 	}
 
 	// Check librust_proto args includes -Iproto
-	librust_proto_rule := ctx.ModuleForTests("librust_proto", "android_arm64_armv8-a_source").Output("proto.rs")
+	librust_proto_rule := ctx.ModuleForTests(t, "librust_proto", "android_arm64_armv8-a_source").Output("proto.rs")
 	cmd := librust_proto_rule.RuleParams.Command
 	if w := "-Iproto"; !strings.Contains(cmd, w) {
 		t.Errorf("expected %q in %q", w, cmd)
@@ -131,7 +131,7 @@
 	`)
 
 	// Check that libprotobuf is added as a dependency.
-	librust_grpcio_module := ctx.ModuleForTests("librust_grpcio", "android_arm64_armv8-a_dylib").Module().(*Module)
+	librust_grpcio_module := ctx.ModuleForTests(t, "librust_grpcio", "android_arm64_armv8-a_dylib").Module().(*Module)
 
 	// Check that libgrpcio is added as a dependency.
 	if !android.InList("libgrpcio", librust_grpcio_module.Properties.AndroidMkDylibs) {
@@ -144,7 +144,7 @@
 	}
 
 	// Make sure the correct plugin is being used.
-	librust_grpcio_out := ctx.ModuleForTests("librust_grpcio", "android_arm64_armv8-a_source").Output("foo_grpc.rs")
+	librust_grpcio_out := ctx.ModuleForTests(t, "librust_grpcio", "android_arm64_armv8-a_source").Output("foo_grpc.rs")
 	cmd := librust_grpcio_out.RuleParams.Command
 	if w := "protoc-gen-grpc"; !strings.Contains(cmd, w) {
 		t.Errorf("expected %q in %q", w, cmd)
@@ -156,7 +156,7 @@
 	}
 
 	// Check proto.rs, the second protobuf, is listed as an output
-	librust_grpcio_outputs := ctx.ModuleForTests("librust_grpcio", "android_arm64_armv8-a_source").AllOutputs()
+	librust_grpcio_outputs := ctx.ModuleForTests(t, "librust_grpcio", "android_arm64_armv8-a_source").AllOutputs()
 	if android.InList("proto_grpc.rs", librust_grpcio_outputs) {
 		t.Errorf("rust_protobuf is not producing multiple outputs; expected 'proto_grpc.rs' in list, got: %#v ",
 			librust_grpcio_outputs)
diff --git a/rust/rust.go b/rust/rust.go
index a044a99..4fd8002 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -20,9 +20,9 @@
 	"strings"
 
 	"android/soong/bloaty"
-	"android/soong/testing"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/depset"
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
@@ -34,6 +34,36 @@
 
 var pctx = android.NewPackageContext("android/soong/rust")
 
+type LibraryInfo struct {
+	Rlib  bool
+	Dylib bool
+}
+
+type CompilerInfo struct {
+	StdLinkageForDevice    RustLinkage
+	StdLinkageForNonDevice RustLinkage
+	NoStdlibs              bool
+	LibraryInfo            *LibraryInfo
+}
+
+type ProtobufDecoratorInfo struct{}
+
+type SourceProviderInfo struct {
+	Srcs                  android.Paths
+	ProtobufDecoratorInfo *ProtobufDecoratorInfo
+}
+
+type RustInfo struct {
+	AndroidMkSuffix               string
+	RustSubName                   string
+	TransitiveAndroidMkSharedLibs depset.DepSet[string]
+	CompilerInfo                  *CompilerInfo
+	SnapshotInfo                  *cc.SnapshotInfo
+	SourceProviderInfo            *SourceProviderInfo
+}
+
+var RustInfoProvider = blueprint.NewProvider[*RustInfo]()
+
 func init() {
 	android.RegisterModuleType("rust_defaults", defaultsFactory)
 	android.PreDepsMutators(registerPreDepsMutators)
@@ -47,11 +77,11 @@
 func registerPreDepsMutators(ctx android.RegisterMutatorsContext) {
 	ctx.Transition("rust_libraries", &libraryTransitionMutator{})
 	ctx.Transition("rust_stdlinkage", &libstdTransitionMutator{})
-	ctx.BottomUp("rust_begin", BeginMutator).Parallel()
+	ctx.BottomUp("rust_begin", BeginMutator)
 }
 
 func registerPostDepsMutators(ctx android.RegisterMutatorsContext) {
-	ctx.BottomUp("rust_sanitizers", rustSanitizerRuntimeMutator).Parallel()
+	ctx.BottomUp("rust_sanitizers", rustSanitizerRuntimeMutator)
 }
 
 type Flags struct {
@@ -133,9 +163,36 @@
 	// Make this module available when building for recovery
 	Recovery_available *bool
 
-	// Minimum sdk version that the artifact should support when it runs as part of mainline modules(APEX).
+	// The API level that this module is built against. The APIs of this API level will be
+	// visible at build time, but use of any APIs newer than min_sdk_version will render the
+	// module unloadable on older devices.  In the future it will be possible to weakly-link new
+	// APIs, making the behavior match Java: such modules will load on older devices, but
+	// calling new APIs on devices that do not support them will result in a crash.
+	//
+	// This property has the same behavior as sdk_version does for Java modules. For those
+	// familiar with Android Gradle, the property behaves similarly to how compileSdkVersion
+	// does for Java code.
+	//
+	// In addition, setting this property causes two variants to be built, one for the platform
+	// and one for apps.
+	Sdk_version *string
+
+	// Minimum OS API level supported by this C or C++ module. This property becomes the value
+	// of the __ANDROID_API__ macro. When the C or C++ module is included in an APEX or an APK,
+	// this property is also used to ensure that the min_sdk_version of the containing module is
+	// not older (i.e. less) than this module's min_sdk_version. When not set, this property
+	// defaults to the value of sdk_version.  When this is set to "apex_inherit", this tracks
+	// min_sdk_version of the containing APEX. When the module
+	// is not built for an APEX, "apex_inherit" defaults to sdk_version.
 	Min_sdk_version *string
 
+	// Variant is an SDK variant created by sdkMutator
+	IsSdkVariant bool `blueprint:"mutated"`
+
+	// Set by factories of module types that can only be referenced from variants compiled against
+	// the SDK.
+	AlwaysSdk bool `blueprint:"mutated"`
+
 	HideFromMake   bool `blueprint:"mutated"`
 	PreventInstall bool `blueprint:"mutated"`
 
@@ -179,7 +236,10 @@
 	// For apex variants, this is set as apex.min_sdk_version
 	apexSdkVersion android.ApiLevel
 
-	transitiveAndroidMkSharedLibs *android.DepSet[string]
+	transitiveAndroidMkSharedLibs depset.DepSet[string]
+
+	// Shared flags among stubs build rules of this module
+	sharedFlags cc.SharedFlags
 }
 
 func (mod *Module) Header() bool {
@@ -345,7 +405,8 @@
 }
 
 func (mod *Module) IsVendorPublicLibrary() bool {
-	return mod.VendorProperties.IsVendorPublicLibrary
+	// Rust modules do not currently support vendor_public_library
+	return false
 }
 
 func (mod *Module) SdkAndPlatformVariantVisibleToMake() bool {
@@ -354,10 +415,12 @@
 }
 
 func (c *Module) IsVndkPrivate() bool {
+	// Rust modules do not currently support VNDK variants
 	return false
 }
 
 func (c *Module) IsLlndk() bool {
+	// Rust modules do not currently support LLNDK variants
 	return false
 }
 
@@ -366,35 +429,34 @@
 }
 
 func (m *Module) NeedsLlndkVariants() bool {
+	// Rust modules do not currently support LLNDK variants
 	return false
 }
 
 func (m *Module) NeedsVendorPublicLibraryVariants() bool {
+	// Rust modules do not currently support vendor_public_library
 	return false
 }
 
 func (mod *Module) HasLlndkStubs() bool {
+	// Rust modules do not currently support LLNDK stubs
 	return false
 }
 
-func (mod *Module) StubsVersion() string {
-	panic(fmt.Errorf("StubsVersion called on non-versioned module: %q", mod.BaseModuleName()))
-}
-
 func (mod *Module) SdkVersion() string {
-	return ""
+	return String(mod.Properties.Sdk_version)
 }
 
 func (mod *Module) AlwaysSdk() bool {
-	return false
+	return mod.Properties.AlwaysSdk
 }
 
 func (mod *Module) IsSdkVariant() bool {
-	return false
+	return mod.Properties.IsSdkVariant
 }
 
 func (mod *Module) SplitPerApiLevel() bool {
-	return false
+	return cc.CanUseSdk(mod) && mod.IsCrt()
 }
 
 func (mod *Module) XrefRustFiles() android.Paths {
@@ -427,15 +489,23 @@
 	StaticLibs    android.Paths
 	ProcMacros    RustLibraries
 	AfdoProfiles  android.Paths
+	LinkerDeps    android.Paths
 
 	// depFlags and depLinkFlags are rustc and linker (clang) flags.
 	depFlags     []string
 	depLinkFlags []string
 
+	// track cc static-libs that have Rlib dependencies
+	reexportedCcRlibDeps []cc.RustRlibDep
+	ccRlibDeps           []cc.RustRlibDep
+
 	// linkDirs are link paths passed via -L to rustc. linkObjects are objects passed directly to the linker
 	// Both of these are exported and propagate to dependencies.
-	linkDirs    []string
-	linkObjects []string
+	linkDirs              []string
+	rustLibObjects        []string
+	staticLibObjects      []string
+	wholeStaticLibObjects []string
+	sharedLibObjects      []string
 
 	// exportedLinkDirs are exported linkDirs for direct rlib dependencies to
 	// cc_library_static dependants of rlibs.
@@ -454,6 +524,9 @@
 	// Paths to generated source files
 	SrcDeps          android.Paths
 	srcProviderFiles android.Paths
+
+	directImplementationDeps     android.Paths
+	transitiveImplementationDeps []depset.DepSet[android.Path]
 }
 
 type RustLibraries []RustLibrary
@@ -465,7 +538,10 @@
 
 type exportedFlagsProducer interface {
 	exportLinkDirs(...string)
-	exportLinkObjects(...string)
+	exportRustLibs(...string)
+	exportStaticLibs(...string)
+	exportWholeStaticLibs(...string)
+	exportSharedLibs(...string)
 }
 
 type xref interface {
@@ -473,23 +549,41 @@
 }
 
 type flagExporter struct {
-	linkDirs    []string
-	ccLinkDirs  []string
-	linkObjects []string
+	linkDirs              []string
+	ccLinkDirs            []string
+	rustLibPaths          []string
+	staticLibObjects      []string
+	sharedLibObjects      []string
+	wholeStaticLibObjects []string
 }
 
 func (flagExporter *flagExporter) exportLinkDirs(dirs ...string) {
 	flagExporter.linkDirs = android.FirstUniqueStrings(append(flagExporter.linkDirs, dirs...))
 }
 
-func (flagExporter *flagExporter) exportLinkObjects(flags ...string) {
-	flagExporter.linkObjects = android.FirstUniqueStrings(append(flagExporter.linkObjects, flags...))
+func (flagExporter *flagExporter) exportRustLibs(flags ...string) {
+	flagExporter.rustLibPaths = android.FirstUniqueStrings(append(flagExporter.rustLibPaths, flags...))
 }
 
-func (flagExporter *flagExporter) setProvider(ctx ModuleContext) {
-	android.SetProvider(ctx, FlagExporterInfoProvider, FlagExporterInfo{
-		LinkDirs:    flagExporter.linkDirs,
-		LinkObjects: flagExporter.linkObjects,
+func (flagExporter *flagExporter) exportStaticLibs(flags ...string) {
+	flagExporter.staticLibObjects = android.FirstUniqueStrings(append(flagExporter.staticLibObjects, flags...))
+}
+
+func (flagExporter *flagExporter) exportSharedLibs(flags ...string) {
+	flagExporter.sharedLibObjects = android.FirstUniqueStrings(append(flagExporter.sharedLibObjects, flags...))
+}
+
+func (flagExporter *flagExporter) exportWholeStaticLibs(flags ...string) {
+	flagExporter.wholeStaticLibObjects = android.FirstUniqueStrings(append(flagExporter.wholeStaticLibObjects, flags...))
+}
+
+func (flagExporter *flagExporter) setRustProvider(ctx ModuleContext) {
+	android.SetProvider(ctx, RustFlagExporterInfoProvider, RustFlagExporterInfo{
+		LinkDirs:              flagExporter.linkDirs,
+		RustLibObjects:        flagExporter.rustLibPaths,
+		StaticLibObjects:      flagExporter.staticLibObjects,
+		WholeStaticLibObjects: flagExporter.wholeStaticLibObjects,
+		SharedLibPaths:        flagExporter.sharedLibObjects,
 	})
 }
 
@@ -499,13 +593,16 @@
 	return &flagExporter{}
 }
 
-type FlagExporterInfo struct {
-	Flags       []string
-	LinkDirs    []string // TODO: this should be android.Paths
-	LinkObjects []string // TODO: this should be android.Paths
+type RustFlagExporterInfo struct {
+	Flags                 []string
+	LinkDirs              []string
+	RustLibObjects        []string
+	StaticLibObjects      []string
+	WholeStaticLibObjects []string
+	SharedLibPaths        []string
 }
 
-var FlagExporterInfoProvider = blueprint.NewProvider[FlagExporterInfo]()
+var RustFlagExporterInfoProvider = blueprint.NewProvider[RustFlagExporterInfo]()
 
 func (mod *Module) isCoverageVariant() bool {
 	return mod.coverage.Properties.IsCoverageVariant
@@ -528,6 +625,9 @@
 func (mod *Module) PreventInstall() bool {
 	return mod.Properties.PreventInstall
 }
+func (c *Module) ForceDisableSanitizers() {
+	c.sanitize.Properties.ForceDisable = true
+}
 
 func (mod *Module) MarkAsCoverageVariant(coverage bool) {
 	mod.coverage.Properties.IsCoverageVariant = coverage
@@ -591,7 +691,7 @@
 	if mod.compiler != nil {
 		// use build{Static,Shared}() instead of {static,shared}() here because this might be called before
 		// VariantIs{Static,Shared} is set.
-		if lib, ok := mod.compiler.(libraryInterface); ok && (lib.buildShared() || lib.buildStatic()) {
+		if lib, ok := mod.compiler.(libraryInterface); ok && (lib.buildShared() || lib.buildStatic() || lib.buildRlib()) {
 			return true
 		}
 	}
@@ -677,15 +777,6 @@
 	panic(fmt.Errorf("BuildRlibVariant called on non-library module: %q", mod.BaseModuleName()))
 }
 
-func (mod *Module) IsRustFFI() bool {
-	if mod.compiler != nil {
-		if library, ok := mod.compiler.(libraryInterface); ok {
-			return library.isFFILibrary()
-		}
-	}
-	return false
-}
-
 func (mod *Module) BuildSharedVariant() bool {
 	if mod.compiler != nil {
 		if library, ok := mod.compiler.(libraryInterface); ok {
@@ -720,6 +811,50 @@
 }
 
 func (mod *Module) IsStubs() bool {
+	if lib, ok := mod.compiler.(libraryInterface); ok {
+		return lib.BuildStubs()
+	}
+	return false
+}
+
+func (mod *Module) HasStubsVariants() bool {
+	if lib, ok := mod.compiler.(libraryInterface); ok {
+		return lib.HasStubsVariants()
+	}
+	return false
+}
+
+func (mod *Module) ApexSdkVersion() android.ApiLevel {
+	return mod.apexSdkVersion
+}
+
+func (mod *Module) RustApexExclude() bool {
+	return mod.ApexExclude()
+}
+
+func (mod *Module) getSharedFlags() *cc.SharedFlags {
+	shared := &mod.sharedFlags
+	if shared.FlagsMap == nil {
+		shared.NumSharedFlags = 0
+		shared.FlagsMap = make(map[string]string)
+	}
+	return shared
+}
+
+func (mod *Module) ImplementationModuleNameForMake(ctx android.BaseModuleContext) string {
+	name := mod.BaseModuleName()
+	if versioned, ok := mod.compiler.(cc.VersionedInterface); ok {
+		name = versioned.ImplementationModuleName(name)
+	}
+	return name
+}
+
+func (mod *Module) Multilib() string {
+	return mod.Arch().ArchType.Multilib
+}
+
+func (mod *Module) IsCrt() bool {
+	// Rust does not currently provide any crt modules.
 	return false
 }
 
@@ -743,6 +878,7 @@
 }
 
 var _ cc.LinkableInterface = (*Module)(nil)
+var _ cc.VersionedLinkableInterface = (*Module)(nil)
 
 func (mod *Module) Init() android.Module {
 	mod.AddProperties(&mod.Properties)
@@ -853,6 +989,25 @@
 	return mod.compiler != nil && mod.compiler.nativeCoverage()
 }
 
+func (mod *Module) SetStl(s string) {
+	// STL is a CC concept; do nothing for Rust
+}
+
+func (mod *Module) SetSdkVersion(s string) {
+	mod.Properties.Sdk_version = StringPtr(s)
+}
+
+func (mod *Module) SetMinSdkVersion(s string) {
+	mod.Properties.Min_sdk_version = StringPtr(s)
+}
+
+func (mod *Module) VersionedInterface() cc.VersionedInterface {
+	if _, ok := mod.compiler.(cc.VersionedInterface); ok {
+		return mod.compiler.(cc.VersionedInterface)
+	}
+	return nil
+}
+
 func (mod *Module) EverInstallable() bool {
 	return mod.compiler != nil &&
 		// Check to see whether the module is actually ever installable.
@@ -942,12 +1097,11 @@
 			mod.sourceProvider.GenerateSource(ctx, deps)
 			mod.sourceProvider.setSubName(ctx.ModuleSubDir())
 		} else {
-			sourceMod := actx.GetDirectDepWithTag(mod.Name(), sourceDepTag)
-			sourceLib := sourceMod.(*Module).compiler.(*libraryDecorator)
-			mod.sourceProvider.setOutputFiles(sourceLib.sourceProvider.Srcs())
+			sourceMod := actx.GetDirectDepProxyWithTag(mod.Name(), sourceDepTag)
+			sourceLib := android.OtherModuleProviderOrDefault(ctx, sourceMod, RustInfoProvider).SourceProviderInfo
+			mod.sourceProvider.setOutputFiles(sourceLib.Srcs)
 		}
 		ctx.CheckbuildFile(mod.sourceProvider.Srcs()...)
-		android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: mod.sourceProvider.Srcs().Strings()})
 	}
 
 	if mod.compiler != nil && !mod.compiler.Disabled() {
@@ -974,6 +1128,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()
 		}
@@ -990,15 +1145,81 @@
 
 		}
 
+		android.SetProvider(ctx, cc.ImplementationDepInfoProvider, &cc.ImplementationDepInfo{
+			ImplementationDeps: depset.New(depset.PREORDER, deps.directImplementationDeps, deps.transitiveImplementationDeps),
+		})
+
 		ctx.Phony("rust", ctx.RustModule().OutputFile().Path())
 	}
-	if mod.testModule {
-		android.SetProvider(ctx, testing.TestModuleProviderKey, testing.TestModuleProviderData{})
+
+	linkableInfo := cc.CreateCommonLinkableInfo(ctx, mod)
+	linkableInfo.Static = mod.Static()
+	linkableInfo.Shared = mod.Shared()
+	linkableInfo.CrateName = mod.CrateName()
+	linkableInfo.ExportedCrateLinkDirs = mod.ExportedCrateLinkDirs()
+	if lib, ok := mod.compiler.(cc.VersionedInterface); ok {
+		linkableInfo.StubsVersion = lib.StubsVersion()
 	}
 
+	android.SetProvider(ctx, cc.LinkableInfoProvider, linkableInfo)
+
+	rustInfo := &RustInfo{
+		AndroidMkSuffix:               mod.AndroidMkSuffix(),
+		RustSubName:                   mod.Properties.RustSubName,
+		TransitiveAndroidMkSharedLibs: mod.transitiveAndroidMkSharedLibs,
+	}
+	if mod.compiler != nil {
+		rustInfo.CompilerInfo = &CompilerInfo{
+			NoStdlibs:              mod.compiler.noStdlibs(),
+			StdLinkageForDevice:    mod.compiler.stdLinkage(true),
+			StdLinkageForNonDevice: mod.compiler.stdLinkage(false),
+		}
+		if lib, ok := mod.compiler.(libraryInterface); ok {
+			rustInfo.CompilerInfo.LibraryInfo = &LibraryInfo{
+				Dylib: lib.dylib(),
+				Rlib:  lib.rlib(),
+			}
+		}
+		if lib, ok := mod.compiler.(cc.SnapshotInterface); ok {
+			rustInfo.SnapshotInfo = &cc.SnapshotInfo{
+				SnapshotAndroidMkSuffix: lib.SnapshotAndroidMkSuffix(),
+			}
+		}
+	}
+	if mod.sourceProvider != nil {
+		rustInfo.SourceProviderInfo = &SourceProviderInfo{
+			Srcs: mod.sourceProvider.Srcs(),
+		}
+		if _, ok := mod.sourceProvider.(*protobufDecorator); ok {
+			rustInfo.SourceProviderInfo.ProtobufDecoratorInfo = &ProtobufDecoratorInfo{}
+		}
+	}
+	android.SetProvider(ctx, RustInfoProvider, rustInfo)
+
+	ccInfo := &cc.CcInfo{
+		IsPrebuilt: mod.IsPrebuilt(),
+	}
+
+	// Define the linker info if compiler != nil because Rust currently
+	// does compilation and linking in one step. If this changes in the future,
+	// move this as appropriate.
+	baseCompilerProps := mod.compiler.baseCompilerProps()
+	ccInfo.LinkerInfo = &cc.LinkerInfo{
+		WholeStaticLibs: baseCompilerProps.Whole_static_libs.GetOrDefault(ctx, nil),
+		StaticLibs:      baseCompilerProps.Static_libs.GetOrDefault(ctx, nil),
+		SharedLibs:      baseCompilerProps.Shared_libs.GetOrDefault(ctx, nil),
+	}
+
+	android.SetProvider(ctx, cc.CcInfoProvider, ccInfo)
+
 	mod.setOutputFiles(ctx)
 
 	buildComplianceMetadataInfo(ctx, mod, deps)
+
+	moduleInfoJSON := ctx.ModuleInfoJSON()
+	if mod.compiler != nil {
+		mod.compiler.moduleInfoJSON(ctx, moduleInfoJSON)
+	}
 }
 
 func (mod *Module) setOutputFiles(ctx ModuleContext) {
@@ -1021,12 +1242,12 @@
 	metadataInfo.SetStringValue(android.ComplianceMetadataProp.BUILT_FILES, mod.outputFile.String())
 
 	// Static libs
-	staticDeps := ctx.GetDirectDepsWithTag(rlibDepTag)
+	staticDeps := ctx.GetDirectDepsProxyWithTag(rlibDepTag)
 	staticDepNames := make([]string, 0, len(staticDeps))
 	for _, dep := range staticDeps {
 		staticDepNames = append(staticDepNames, dep.Name())
 	}
-	ccStaticDeps := ctx.GetDirectDepsWithTag(cc.StaticDepTag(false))
+	ccStaticDeps := ctx.GetDirectDepsProxyWithTag(cc.StaticDepTag(false))
 	for _, dep := range ccStaticDeps {
 		staticDepNames = append(staticDepNames, dep.Name())
 	}
@@ -1044,7 +1265,7 @@
 	metadataInfo.SetListValue(android.ComplianceMetadataProp.STATIC_DEP_FILES, android.FirstUniqueStrings(staticDepPaths))
 
 	// C Whole static libs
-	ccWholeStaticDeps := ctx.GetDirectDepsWithTag(cc.StaticDepTag(true))
+	ccWholeStaticDeps := ctx.GetDirectDepsProxyWithTag(cc.StaticDepTag(true))
 	wholeStaticDepNames := make([]string, 0, len(ccWholeStaticDeps))
 	for _, dep := range ccStaticDeps {
 		wholeStaticDepNames = append(wholeStaticDepNames, dep.Name())
@@ -1157,6 +1378,21 @@
 	if mod.sanitize != nil {
 		mod.sanitize.begin(ctx)
 	}
+
+	if mod.UseSdk() && mod.IsSdkVariant() {
+		sdkVersion := ""
+		if ctx.Device() {
+			sdkVersion = mod.SdkVersion()
+		}
+		version, err := cc.NativeApiLevelFromUser(ctx, sdkVersion)
+		if err != nil {
+			ctx.PropertyErrorf("sdk_version", err.Error())
+			mod.Properties.Sdk_version = nil
+		} else {
+			mod.Properties.Sdk_version = StringPtr(version.String())
+		}
+	}
+
 }
 
 func (mod *Module) Prebuilt() *android.Prebuilt {
@@ -1171,21 +1407,21 @@
 	return nil
 }
 
-func rustMakeLibName(ctx android.ModuleContext, c cc.LinkableInterface, dep cc.LinkableInterface, depName string) string {
-	if rustDep, ok := dep.(*Module); ok {
+func rustMakeLibName(rustInfo *RustInfo, linkableInfo *cc.LinkableInfo, commonInfo *android.CommonModuleInfo, depName string) string {
+	if rustInfo != nil {
 		// Use base module name for snapshots when exporting to Makefile.
-		if snapshotPrebuilt, ok := rustDep.compiler.(cc.SnapshotInterface); ok {
-			baseName := rustDep.BaseModuleName()
-			return baseName + snapshotPrebuilt.SnapshotAndroidMkSuffix() + rustDep.AndroidMkSuffix()
+		if rustInfo.SnapshotInfo != nil {
+			baseName := linkableInfo.BaseModuleName
+			return baseName + rustInfo.SnapshotInfo.SnapshotAndroidMkSuffix + rustInfo.AndroidMkSuffix
 		}
 	}
-	return cc.MakeLibName(ctx, c, dep, depName)
+	return cc.MakeLibName(nil, linkableInfo, commonInfo, depName)
 }
 
-func collectIncludedProtos(mod *Module, dep *Module) {
+func collectIncludedProtos(mod *Module, rustInfo *RustInfo, linkableInfo *cc.LinkableInfo) {
 	if protoMod, ok := mod.sourceProvider.(*protobufDecorator); ok {
-		if _, ok := dep.sourceProvider.(*protobufDecorator); ok {
-			protoMod.additionalCrates = append(protoMod.additionalCrates, dep.CrateName())
+		if rustInfo.SourceProviderInfo.ProtobufDecoratorInfo != nil {
+			protoMod.additionalCrates = append(protoMod.additionalCrates, linkableInfo.CrateName)
 		}
 	}
 }
@@ -1193,13 +1429,13 @@
 func (mod *Module) depsToPaths(ctx android.ModuleContext) PathDeps {
 	var depPaths PathDeps
 
-	directRlibDeps := []*Module{}
-	directDylibDeps := []*Module{}
-	directProcMacroDeps := []*Module{}
+	directRlibDeps := []*cc.LinkableInfo{}
+	directDylibDeps := []*cc.LinkableInfo{}
+	directProcMacroDeps := []*cc.LinkableInfo{}
 	directSharedLibDeps := []cc.SharedLibraryInfo{}
-	directStaticLibDeps := [](cc.LinkableInterface){}
-	directSrcProvidersDeps := []*Module{}
-	directSrcDeps := [](android.SourceFileProducer){}
+	directStaticLibDeps := [](*cc.LinkableInfo){}
+	directSrcProvidersDeps := []*android.ModuleProxy{}
+	directSrcDeps := []android.SourceFilesInfo{}
 
 	// For the dependency from platform to apex, use the latest stubs
 	mod.apexSdkVersion = android.FutureApiLevel
@@ -1217,12 +1453,14 @@
 
 	skipModuleList := map[string]bool{}
 
-	var transitiveAndroidMkSharedLibs []*android.DepSet[string]
+	var transitiveAndroidMkSharedLibs []depset.DepSet[string]
 	var directAndroidMkSharedLibs []string
 
-	ctx.VisitDirectDeps(func(dep android.Module) {
+	ctx.VisitDirectDepsProxy(func(dep android.ModuleProxy) {
 		depName := ctx.OtherModuleName(dep)
 		depTag := ctx.OtherModuleDependencyTag(dep)
+		modStdLinkage := mod.compiler.stdLinkage(ctx.Device())
+
 		if _, exists := skipModuleList[depName]; exists {
 			return
 		}
@@ -1231,45 +1469,88 @@
 			return
 		}
 
-		if rustDep, ok := dep.(*Module); ok && !rustDep.Static() && !rustDep.Shared() {
+		rustInfo, hasRustInfo := android.OtherModuleProvider(ctx, dep, RustInfoProvider)
+		ccInfo, _ := android.OtherModuleProvider(ctx, dep, cc.CcInfoProvider)
+		linkableInfo, hasLinkableInfo := android.OtherModuleProvider(ctx, dep, cc.LinkableInfoProvider)
+		commonInfo := android.OtherModuleProviderOrDefault(ctx, dep, android.CommonModuleInfoKey)
+		if hasRustInfo && !linkableInfo.Static && !linkableInfo.Shared {
 			//Handle Rust Modules
-			makeLibName := rustMakeLibName(ctx, mod, rustDep, depName+rustDep.Properties.RustSubName)
+			makeLibName := rustMakeLibName(rustInfo, linkableInfo, &commonInfo, depName+rustInfo.RustSubName)
 
 			switch {
 			case depTag == dylibDepTag:
-				dylib, ok := rustDep.compiler.(libraryInterface)
-				if !ok || !dylib.dylib() {
+				dylib := rustInfo.CompilerInfo.LibraryInfo
+				if dylib == nil || !dylib.Dylib {
 					ctx.ModuleErrorf("mod %q not an dylib library", depName)
 					return
 				}
-				directDylibDeps = append(directDylibDeps, rustDep)
+				directDylibDeps = append(directDylibDeps, linkableInfo)
 				mod.Properties.AndroidMkDylibs = append(mod.Properties.AndroidMkDylibs, makeLibName)
 				mod.Properties.SnapshotDylibs = append(mod.Properties.SnapshotDylibs, cc.BaseLibName(depName))
 
+				depPaths.directImplementationDeps = append(depPaths.directImplementationDeps, android.OutputFileForModule(ctx, dep, ""))
+				if info, ok := android.OtherModuleProvider(ctx, dep, cc.ImplementationDepInfoProvider); ok {
+					depPaths.transitiveImplementationDeps = append(depPaths.transitiveImplementationDeps, info.ImplementationDeps)
+				}
+
+				if !rustInfo.CompilerInfo.NoStdlibs {
+					rustDepStdLinkage := rustInfo.CompilerInfo.StdLinkageForNonDevice
+					if ctx.Device() {
+						rustDepStdLinkage = rustInfo.CompilerInfo.StdLinkageForDevice
+					}
+					if rustDepStdLinkage != modStdLinkage {
+						ctx.ModuleErrorf("Rust dependency %q has the wrong StdLinkage; expected %#v, got %#v", depName, modStdLinkage, rustDepStdLinkage)
+						return
+					}
+				}
+
 			case depTag == rlibDepTag:
-				rlib, ok := rustDep.compiler.(libraryInterface)
-				if !ok || !rlib.rlib() {
+				rlib := rustInfo.CompilerInfo.LibraryInfo
+				if rlib == nil || !rlib.Rlib {
 					ctx.ModuleErrorf("mod %q not an rlib library", makeLibName)
 					return
 				}
-				directRlibDeps = append(directRlibDeps, rustDep)
+				directRlibDeps = append(directRlibDeps, linkableInfo)
 				mod.Properties.AndroidMkRlibs = append(mod.Properties.AndroidMkRlibs, makeLibName)
 				mod.Properties.SnapshotRlibs = append(mod.Properties.SnapshotRlibs, cc.BaseLibName(depName))
 
 				// rust_ffi rlibs may export include dirs, so collect those here.
 				exportedInfo, _ := android.OtherModuleProvider(ctx, dep, cc.FlagExporterInfoProvider)
 				depPaths.depIncludePaths = append(depPaths.depIncludePaths, exportedInfo.IncludeDirs...)
-				depPaths.exportedLinkDirs = append(depPaths.exportedLinkDirs, linkPathFromFilePath(rustDep.OutputFile().Path()))
+				depPaths.exportedLinkDirs = append(depPaths.exportedLinkDirs, linkPathFromFilePath(linkableInfo.OutputFile.Path()))
+
+				// rlibs are not installed, so don't add the output file to directImplementationDeps
+				if info, ok := android.OtherModuleProvider(ctx, dep, cc.ImplementationDepInfoProvider); ok {
+					depPaths.transitiveImplementationDeps = append(depPaths.transitiveImplementationDeps, info.ImplementationDeps)
+				}
+
+				if !rustInfo.CompilerInfo.NoStdlibs {
+					rustDepStdLinkage := rustInfo.CompilerInfo.StdLinkageForNonDevice
+					if ctx.Device() {
+						rustDepStdLinkage = rustInfo.CompilerInfo.StdLinkageForDevice
+					}
+					if rustDepStdLinkage != modStdLinkage {
+						ctx.ModuleErrorf("Rust dependency %q has the wrong StdLinkage; expected %#v, got %#v", depName, modStdLinkage, rustDepStdLinkage)
+						return
+					}
+				}
+
+				if !mod.Rlib() {
+					depPaths.ccRlibDeps = append(depPaths.ccRlibDeps, exportedInfo.RustRlibDeps...)
+				} else {
+					// rlibs need to reexport these
+					depPaths.reexportedCcRlibDeps = append(depPaths.reexportedCcRlibDeps, exportedInfo.RustRlibDeps...)
+				}
 
 			case depTag == procMacroDepTag:
-				directProcMacroDeps = append(directProcMacroDeps, rustDep)
+				directProcMacroDeps = append(directProcMacroDeps, linkableInfo)
 				mod.Properties.AndroidMkProcMacroLibs = append(mod.Properties.AndroidMkProcMacroLibs, makeLibName)
 				// proc_macro link dirs need to be exported, so collect those here.
-				depPaths.exportedLinkDirs = append(depPaths.exportedLinkDirs, linkPathFromFilePath(rustDep.OutputFile().Path()))
+				depPaths.exportedLinkDirs = append(depPaths.exportedLinkDirs, linkPathFromFilePath(linkableInfo.OutputFile.Path()))
 
 			case depTag == sourceDepTag:
 				if _, ok := mod.sourceProvider.(*protobufDecorator); ok {
-					collectIncludedProtos(mod, rustDep)
+					collectIncludedProtos(mod, rustInfo, linkableInfo)
 				}
 			case cc.IsStaticDepTag(depTag):
 				// Rust FFI rlibs should not be declared in a Rust modules
@@ -1282,7 +1563,7 @@
 
 			}
 
-			transitiveAndroidMkSharedLibs = append(transitiveAndroidMkSharedLibs, rustDep.transitiveAndroidMkSharedLibs)
+			transitiveAndroidMkSharedLibs = append(transitiveAndroidMkSharedLibs, rustInfo.TransitiveAndroidMkSharedLibs)
 
 			if android.IsSourceDepTagWithOutputTag(depTag, "") {
 				// Since these deps are added in path_properties.go via AddDependencies, we need to ensure the correct
@@ -1294,26 +1575,30 @@
 					helper = "device module defined?"
 				}
 
-				if dep.Target().Os != ctx.Os() {
+				if commonInfo.Target.Os != ctx.Os() {
 					ctx.ModuleErrorf("OS mismatch on dependency %q (%s)", dep.Name(), helper)
 					return
-				} else if dep.Target().Arch.ArchType != ctx.Arch().ArchType {
+				} else if commonInfo.Target.Arch.ArchType != ctx.Arch().ArchType {
 					ctx.ModuleErrorf("Arch mismatch on dependency %q (%s)", dep.Name(), helper)
 					return
 				}
-				directSrcProvidersDeps = append(directSrcProvidersDeps, rustDep)
+				directSrcProvidersDeps = append(directSrcProvidersDeps, &dep)
 			}
 
-			exportedInfo, _ := android.OtherModuleProvider(ctx, dep, FlagExporterInfoProvider)
-			//Append the dependencies exportedDirs, except for proc-macros which target a different arch/OS
+			exportedInfo, _ := android.OtherModuleProvider(ctx, dep, RustFlagExporterInfoProvider)
+
+			//Append the dependencies exported objects, except for proc-macros which target a different arch/OS
 			if depTag != procMacroDepTag {
 				depPaths.depFlags = append(depPaths.depFlags, exportedInfo.Flags...)
-				depPaths.linkObjects = append(depPaths.linkObjects, exportedInfo.LinkObjects...)
+				depPaths.rustLibObjects = append(depPaths.rustLibObjects, exportedInfo.RustLibObjects...)
+				depPaths.sharedLibObjects = append(depPaths.sharedLibObjects, exportedInfo.SharedLibPaths...)
+				depPaths.staticLibObjects = append(depPaths.staticLibObjects, exportedInfo.StaticLibObjects...)
+				depPaths.wholeStaticLibObjects = append(depPaths.wholeStaticLibObjects, exportedInfo.WholeStaticLibObjects...)
 				depPaths.linkDirs = append(depPaths.linkDirs, exportedInfo.LinkDirs...)
 			}
 
 			if depTag == dylibDepTag || depTag == rlibDepTag || depTag == procMacroDepTag {
-				linkFile := rustDep.UnstrippedOutputFile()
+				linkFile := linkableInfo.UnstrippedOutputFile
 				linkDir := linkPathFromFilePath(linkFile)
 				if lib, ok := mod.compiler.(exportedFlagsProducer); ok {
 					lib.exportLinkDirs(linkDir)
@@ -1322,27 +1607,27 @@
 
 			if depTag == sourceDepTag {
 				if _, ok := mod.sourceProvider.(*protobufDecorator); ok && mod.Source() {
-					if _, ok := rustDep.sourceProvider.(*protobufDecorator); ok {
+					if rustInfo.SourceProviderInfo.ProtobufDecoratorInfo != nil {
 						exportedInfo, _ := android.OtherModuleProvider(ctx, dep, cc.FlagExporterInfoProvider)
 						depPaths.depIncludePaths = append(depPaths.depIncludePaths, exportedInfo.IncludeDirs...)
 					}
 				}
 			}
-		} else if ccDep, ok := dep.(cc.LinkableInterface); ok {
+		} else if hasLinkableInfo {
 			//Handle C dependencies
-			makeLibName := cc.MakeLibName(ctx, mod, ccDep, depName)
-			if _, ok := ccDep.(*Module); !ok {
-				if ccDep.Module().Target().Os != ctx.Os() {
+			makeLibName := cc.MakeLibName(ccInfo, linkableInfo, &commonInfo, depName)
+			if !hasRustInfo {
+				if commonInfo.Target.Os != ctx.Os() {
 					ctx.ModuleErrorf("OS mismatch between %q and %q", ctx.ModuleName(), depName)
 					return
 				}
-				if ccDep.Module().Target().Arch.ArchType != ctx.Arch().ArchType {
+				if commonInfo.Target.Arch.ArchType != ctx.Arch().ArchType {
 					ctx.ModuleErrorf("Arch mismatch between %q and %q", ctx.ModuleName(), depName)
 					return
 				}
 			}
-			linkObject := ccDep.OutputFile()
-			if !linkObject.Valid() {
+			ccLibPath := linkableInfo.OutputFile
+			if !ccLibPath.Valid() {
 				if !ctx.Config().AllowMissingDependencies() {
 					ctx.ModuleErrorf("Invalid output file when adding dep %q to %q", depName, ctx.ModuleName())
 				} else {
@@ -1351,7 +1636,7 @@
 				return
 			}
 
-			linkPath := linkPathFromFilePath(linkObject.Path())
+			linkPath := linkPathFromFilePath(ccLibPath.Path())
 
 			exportDep := false
 			switch {
@@ -1360,20 +1645,25 @@
 					// rustc will bundle static libraries when they're passed with "-lstatic=<lib>". This will fail
 					// if the library is not prefixed by "lib".
 					if mod.Binary() {
-						// Binaries may sometimes need to link whole static libraries that don't start with 'lib'.
 						// Since binaries don't need to 'rebundle' these like libraries and only use these for the
 						// final linkage, pass the args directly to the linker to handle these cases.
-						depPaths.depLinkFlags = append(depPaths.depLinkFlags, []string{"-Wl,--whole-archive", linkObject.Path().String(), "-Wl,--no-whole-archive"}...)
-					} else if libName, ok := libNameFromFilePath(linkObject.Path()); ok {
-						depPaths.depFlags = append(depPaths.depFlags, "-lstatic="+libName)
+						depPaths.depLinkFlags = append(depPaths.depLinkFlags, []string{"-Wl,--whole-archive", ccLibPath.Path().String(), "-Wl,--no-whole-archive"}...)
+					} else if libName, ok := libNameFromFilePath(ccLibPath.Path()); ok {
+						depPaths.depFlags = append(depPaths.depFlags, "-lstatic:+whole-archive="+libName)
+						depPaths.depLinkFlags = append(depPaths.depLinkFlags, ccLibPath.Path().String())
 					} else {
 						ctx.ModuleErrorf("'%q' cannot be listed as a whole_static_library in Rust modules unless the output is prefixed by 'lib'", depName, ctx.ModuleName())
 					}
 				}
 
-				// Add this to linkObjects to pass the library directly to the linker as well. This propagates
-				// to dependencies to avoid having to redeclare static libraries for dependents of the dylib variant.
-				depPaths.linkObjects = append(depPaths.linkObjects, linkObject.String())
+				if cc.IsWholeStaticLib(depTag) {
+					// Add whole staticlibs to wholeStaticLibObjects to propagate to Rust all dependents.
+					depPaths.wholeStaticLibObjects = append(depPaths.wholeStaticLibObjects, ccLibPath.String())
+				} else {
+					// Otherwise add to staticLibObjects, which only propagate through rlibs to their dependents.
+					depPaths.staticLibObjects = append(depPaths.staticLibObjects, ccLibPath.String())
+				}
+
 				depPaths.linkDirs = append(depPaths.linkDirs, linkPath)
 
 				exportedInfo, _ := android.OtherModuleProvider(ctx, dep, cc.FlagExporterInfoProvider)
@@ -1381,7 +1671,15 @@
 				depPaths.depSystemIncludePaths = append(depPaths.depSystemIncludePaths, exportedInfo.SystemIncludeDirs...)
 				depPaths.depClangFlags = append(depPaths.depClangFlags, exportedInfo.Flags...)
 				depPaths.depGeneratedHeaders = append(depPaths.depGeneratedHeaders, exportedInfo.GeneratedHeaders...)
-				directStaticLibDeps = append(directStaticLibDeps, ccDep)
+
+				if !mod.Rlib() {
+					// rlibs don't need to build the generated static library, so they don't need to track these.
+					depPaths.ccRlibDeps = append(depPaths.ccRlibDeps, exportedInfo.RustRlibDeps...)
+				} else {
+					depPaths.reexportedCcRlibDeps = append(depPaths.reexportedCcRlibDeps, exportedInfo.RustRlibDeps...)
+				}
+
+				directStaticLibDeps = append(directStaticLibDeps, linkableInfo)
 
 				// Record baseLibName for snapshots.
 				mod.Properties.SnapshotStaticLibs = append(mod.Properties.SnapshotStaticLibs, cc.BaseLibName(depName))
@@ -1393,10 +1691,20 @@
 				// dependency crosses the APEX boundaries).
 				sharedLibraryInfo, exportedInfo := cc.ChooseStubOrImpl(ctx, dep)
 
+				if !sharedLibraryInfo.IsStubs {
+					// TODO(b/362509506): remove this additional check once all apex_exclude uses are switched to stubs.
+					if !linkableInfo.RustApexExclude {
+						depPaths.directImplementationDeps = append(depPaths.directImplementationDeps, android.OutputFileForModule(ctx, dep, ""))
+						if info, ok := android.OtherModuleProvider(ctx, dep, cc.ImplementationDepInfoProvider); ok {
+							depPaths.transitiveImplementationDeps = append(depPaths.transitiveImplementationDeps, info.ImplementationDeps)
+						}
+					}
+				}
+
 				// Re-get linkObject as ChooseStubOrImpl actually tells us which
 				// object (either from stub or non-stub) to use.
-				linkObject = android.OptionalPathForPath(sharedLibraryInfo.SharedLibrary)
-				if !linkObject.Valid() {
+				ccLibPath = android.OptionalPathForPath(sharedLibraryInfo.SharedLibrary)
+				if !ccLibPath.Valid() {
 					if !ctx.Config().AllowMissingDependencies() {
 						ctx.ModuleErrorf("Invalid output file when adding dep %q to %q", depName, ctx.ModuleName())
 					} else {
@@ -1404,10 +1712,10 @@
 					}
 					return
 				}
-				linkPath = linkPathFromFilePath(linkObject.Path())
+				linkPath = linkPathFromFilePath(ccLibPath.Path())
 
 				depPaths.linkDirs = append(depPaths.linkDirs, linkPath)
-				depPaths.linkObjects = append(depPaths.linkObjects, linkObject.String())
+				depPaths.sharedLibObjects = append(depPaths.sharedLibObjects, ccLibPath.String())
 				depPaths.depIncludePaths = append(depPaths.depIncludePaths, exportedInfo.IncludeDirs...)
 				depPaths.depSystemIncludePaths = append(depPaths.depSystemIncludePaths, exportedInfo.SystemIncludeDirs...)
 				depPaths.depClangFlags = append(depPaths.depClangFlags, exportedInfo.Flags...)
@@ -1426,15 +1734,15 @@
 				depPaths.depGeneratedHeaders = append(depPaths.depGeneratedHeaders, exportedInfo.GeneratedHeaders...)
 				mod.Properties.AndroidMkHeaderLibs = append(mod.Properties.AndroidMkHeaderLibs, makeLibName)
 			case depTag == cc.CrtBeginDepTag:
-				depPaths.CrtBegin = append(depPaths.CrtBegin, linkObject.Path())
+				depPaths.CrtBegin = append(depPaths.CrtBegin, ccLibPath.Path())
 			case depTag == cc.CrtEndDepTag:
-				depPaths.CrtEnd = append(depPaths.CrtEnd, linkObject.Path())
+				depPaths.CrtEnd = append(depPaths.CrtEnd, ccLibPath.Path())
 			}
 
-			// Make sure these dependencies are propagated
+			// Make sure shared dependencies are propagated
 			if lib, ok := mod.compiler.(exportedFlagsProducer); ok && exportDep {
 				lib.exportLinkDirs(linkPath)
-				lib.exportLinkObjects(linkObject.String())
+				lib.exportSharedLibs(ccLibPath.String())
 			}
 		} else {
 			switch {
@@ -1445,7 +1753,7 @@
 			}
 		}
 
-		if srcDep, ok := dep.(android.SourceFileProducer); ok {
+		if srcDep, ok := android.OtherModuleProvider(ctx, dep, android.SourceFilesInfoProvider); ok {
 			if android.IsSourceDepTagWithOutputTag(depTag, "") {
 				// These are usually genrules which don't have per-target variants.
 				directSrcDeps = append(directSrcDeps, srcDep)
@@ -1453,37 +1761,37 @@
 		}
 	})
 
-	mod.transitiveAndroidMkSharedLibs = android.NewDepSet[string](android.PREORDER, directAndroidMkSharedLibs, transitiveAndroidMkSharedLibs)
+	mod.transitiveAndroidMkSharedLibs = depset.New[string](depset.PREORDER, directAndroidMkSharedLibs, transitiveAndroidMkSharedLibs)
 
 	var rlibDepFiles RustLibraries
 	aliases := mod.compiler.Aliases()
 	for _, dep := range directRlibDeps {
-		crateName := dep.CrateName()
+		crateName := dep.CrateName
 		if alias, aliased := aliases[crateName]; aliased {
 			crateName = alias
 		}
-		rlibDepFiles = append(rlibDepFiles, RustLibrary{Path: dep.UnstrippedOutputFile(), CrateName: crateName})
+		rlibDepFiles = append(rlibDepFiles, RustLibrary{Path: dep.UnstrippedOutputFile, CrateName: crateName})
 	}
 	var dylibDepFiles RustLibraries
 	for _, dep := range directDylibDeps {
-		crateName := dep.CrateName()
+		crateName := dep.CrateName
 		if alias, aliased := aliases[crateName]; aliased {
 			crateName = alias
 		}
-		dylibDepFiles = append(dylibDepFiles, RustLibrary{Path: dep.UnstrippedOutputFile(), CrateName: crateName})
+		dylibDepFiles = append(dylibDepFiles, RustLibrary{Path: dep.UnstrippedOutputFile, CrateName: crateName})
 	}
 	var procMacroDepFiles RustLibraries
 	for _, dep := range directProcMacroDeps {
-		crateName := dep.CrateName()
+		crateName := dep.CrateName
 		if alias, aliased := aliases[crateName]; aliased {
 			crateName = alias
 		}
-		procMacroDepFiles = append(procMacroDepFiles, RustLibrary{Path: dep.UnstrippedOutputFile(), CrateName: crateName})
+		procMacroDepFiles = append(procMacroDepFiles, RustLibrary{Path: dep.UnstrippedOutputFile, CrateName: crateName})
 	}
 
 	var staticLibDepFiles android.Paths
 	for _, dep := range directStaticLibDeps {
-		staticLibDepFiles = append(staticLibDepFiles, dep.OutputFile().Path())
+		staticLibDepFiles = append(staticLibDepFiles, dep.OutputFile.Path())
 	}
 
 	var sharedLibFiles android.Paths
@@ -1499,11 +1807,11 @@
 
 	var srcProviderDepFiles android.Paths
 	for _, dep := range directSrcProvidersDeps {
-		srcs := android.OutputFilesForModule(ctx, dep, "")
+		srcs := android.OutputFilesForModule(ctx, *dep, "")
 		srcProviderDepFiles = append(srcProviderDepFiles, srcs...)
 	}
 	for _, dep := range directSrcDeps {
-		srcs := dep.Srcs()
+		srcs := dep.Srcs
 		srcProviderDepFiles = append(srcProviderDepFiles, srcs...)
 	}
 
@@ -1517,11 +1825,17 @@
 
 	// Dedup exported flags from dependencies
 	depPaths.linkDirs = android.FirstUniqueStrings(depPaths.linkDirs)
-	depPaths.linkObjects = android.FirstUniqueStrings(depPaths.linkObjects)
+	depPaths.rustLibObjects = android.FirstUniqueStrings(depPaths.rustLibObjects)
+	depPaths.staticLibObjects = android.FirstUniqueStrings(depPaths.staticLibObjects)
+	depPaths.wholeStaticLibObjects = android.FirstUniqueStrings(depPaths.wholeStaticLibObjects)
+	depPaths.sharedLibObjects = android.FirstUniqueStrings(depPaths.sharedLibObjects)
 	depPaths.depFlags = android.FirstUniqueStrings(depPaths.depFlags)
 	depPaths.depClangFlags = android.FirstUniqueStrings(depPaths.depClangFlags)
 	depPaths.depIncludePaths = android.FirstUniquePaths(depPaths.depIncludePaths)
 	depPaths.depSystemIncludePaths = android.FirstUniquePaths(depPaths.depSystemIncludePaths)
+	depPaths.depLinkFlags = android.FirstUniqueStrings(depPaths.depLinkFlags)
+	depPaths.reexportedCcRlibDeps = android.FirstUniqueFunc(depPaths.reexportedCcRlibDeps, cc.EqRustRlibDeps)
+	depPaths.ccRlibDeps = android.FirstUniqueFunc(depPaths.ccRlibDeps, cc.EqRustRlibDeps)
 
 	return depPaths
 }
@@ -1572,7 +1886,7 @@
 	}
 
 	stdLinkage := "dylib-std"
-	if mod.compiler.stdLinkage(ctx) == RlibLinkage {
+	if mod.compiler.stdLinkage(ctx.Device()) == RlibLinkage {
 		stdLinkage = "rlib-std"
 	}
 
@@ -1639,7 +1953,7 @@
 
 	// stdlibs
 	if deps.Stdlibs != nil {
-		if mod.compiler.stdLinkage(ctx) == RlibLinkage {
+		if mod.compiler.stdLinkage(ctx.Device()) == RlibLinkage {
 			for _, lib := range deps.Stdlibs {
 				actx.AddVariationDependencies(append(commonDepVariations, []blueprint.Variation{{Mutator: "rust_libraries", Variation: "rlib"}}...),
 					rlibDepTag, lib)
@@ -1757,7 +2071,7 @@
 var _ android.ApexModule = (*Module)(nil)
 
 // If a module is marked for exclusion from apexes, don't provide apex variants.
-// TODO(b/362509506): remove this once stubs are properly supported by rust_ffi targets.
+// TODO(b/362509506): remove this once all apex_exclude usages are removed.
 func (m *Module) CanHaveApexVariants() bool {
 	if m.ApexExclude() {
 		return false
@@ -1794,48 +2108,93 @@
 }
 
 // Implements android.ApexModule
-func (mod *Module) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
-	depTag := ctx.OtherModuleDependencyTag(dep)
+func (mod *Module) AlwaysRequiresPlatformApexVariant() bool {
+	// stub libraries and native bridge libraries are always available to platform
+	// TODO(b/362509506): remove the ApexExclude() check once all apex_exclude uses are switched to stubs.
+	return mod.IsStubs() || mod.Target().NativeBridge == android.NativeBridgeEnabled || mod.ApexExclude()
+}
 
-	if ccm, ok := dep.(*cc.Module); ok {
-		if ccm.HasStubsVariants() {
-			if cc.IsSharedDepTag(depTag) {
-				// dynamic dep to a stubs lib crosses APEX boundary
-				return false
-			}
-			if cc.IsRuntimeDepTag(depTag) {
-				// runtime dep to a stubs lib also crosses APEX boundary
-				return false
-			}
+// Implements android.ApexModule
+type RustDepInSameApexChecker struct {
+	Static           bool
+	HasStubsVariants bool
+	ApexExclude      bool
+	Host             bool
+}
 
-			if cc.IsHeaderDepTag(depTag) {
-				return false
-			}
-		}
-		if mod.Static() && cc.IsSharedDepTag(depTag) {
-			// shared_lib dependency from a static lib is considered as crossing
-			// the APEX boundary because the dependency doesn't actually is
-			// linked; the dependency is used only during the compilation phase.
-			return false
-		}
+func (mod *Module) GetDepInSameApexChecker() android.DepInSameApexChecker {
+	return RustDepInSameApexChecker{
+		Static:           mod.Static(),
+		HasStubsVariants: mod.HasStubsVariants(),
+		ApexExclude:      mod.ApexExclude(),
+		Host:             mod.Host(),
 	}
+}
 
+func (r RustDepInSameApexChecker) OutgoingDepIsInSameApex(depTag blueprint.DependencyTag) bool {
 	if depTag == procMacroDepTag || depTag == customBindgenDepTag {
 		return false
 	}
 
-	if rustDep, ok := dep.(*Module); ok && rustDep.ApexExclude() {
+	if r.Static && cc.IsSharedDepTag(depTag) {
+		// shared_lib dependency from a static lib is considered as crossing
+		// the APEX boundary because the dependency doesn't actually is
+		// linked; the dependency is used only during the compilation phase.
+		return false
+	}
+
+	if depTag == cc.StubImplDepTag {
+		// We don't track from an implementation library to its stubs.
+		return false
+	}
+
+	if cc.ExcludeInApexDepTag(depTag) {
+		return false
+	}
+
+	// TODO(b/362509506): remove once all apex_exclude uses are switched to stubs.
+	if r.ApexExclude {
 		return false
 	}
 
 	return true
 }
 
+func (r RustDepInSameApexChecker) IncomingDepIsInSameApex(depTag blueprint.DependencyTag) bool {
+	if r.Host {
+		return false
+	}
+	// TODO(b/362509506): remove once all apex_exclude uses are switched to stubs.
+	if r.ApexExclude {
+		return false
+	}
+
+	if r.HasStubsVariants {
+		if cc.IsSharedDepTag(depTag) && !cc.IsExplicitImplSharedDepTag(depTag) {
+			// dynamic dep to a stubs lib crosses APEX boundary
+			return false
+		}
+		if cc.IsRuntimeDepTag(depTag) {
+			// runtime dep to a stubs lib also crosses APEX boundary
+			return false
+		}
+		if cc.IsHeaderDepTag(depTag) {
+			return false
+		}
+	}
+	return true
+}
+
 // Overrides ApexModule.IsInstallabeToApex()
 func (mod *Module) IsInstallableToApex() bool {
+	// TODO(b/362509506): remove once all apex_exclude uses are switched to stubs.
+	if mod.ApexExclude() {
+		return false
+	}
+
 	if mod.compiler != nil {
-		if lib, ok := mod.compiler.(libraryInterface); ok && (lib.shared() || lib.dylib()) {
-			return true
+		if lib, ok := mod.compiler.(libraryInterface); ok {
+			return (lib.shared() || lib.dylib()) && !lib.BuildStubs()
 		}
 		if _, ok := mod.compiler.(*binaryDecorator); ok {
 			return true
diff --git a/rust/rust_test.go b/rust/rust_test.go
index eeedf3f..fbb9947 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -184,8 +184,8 @@
 			srcs: ["foo.rs"],
 		}
 	`)
-	module := ctx.ModuleForTests("fizz-buzz", "linux_glibc_x86_64").Module().(*Module)
-	rustc := ctx.ModuleForTests("librlib", "linux_glibc_x86_64_rlib_rlib-std").Rule("rustc")
+	module := ctx.ModuleForTests(t, "fizz-buzz", "linux_glibc_x86_64").Module().(*Module)
+	rustc := ctx.ModuleForTests(t, "librlib", "linux_glibc_x86_64_rlib_rlib-std").Rule("rustc")
 
 	// Since dependencies are added to AndroidMk* properties, we can check these to see if they've been picked up.
 	if !android.InList("librlib.rlib-std", module.Properties.AndroidMkRlibs) {
@@ -204,7 +204,7 @@
 		t.Errorf("Static library dependency not detected (dependency missing from AndroidMkStaticLibs)")
 	}
 
-	if !strings.Contains(rustc.Args["rustcFlags"], "-lstatic=wholestatic") {
+	if !strings.Contains(rustc.Args["rustcFlags"], "-lstatic:+whole-archive=wholestatic") {
 		t.Errorf("-lstatic flag not being passed to rustc for static library %#v", rustc.Args["rustcFlags"])
 	}
 
@@ -274,7 +274,7 @@
 		}
 	`)
 
-	libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_rlib_dylib-std").Rule("rustc")
+	libfoo := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_rlib_dylib-std").Rule("rustc")
 	if !android.SuffixInList(libfoo.Implicits.Strings(), "/out/bindings.rs") {
 		t.Errorf("rust_bindgen generated source not included as implicit input for libfoo; Implicits %#v", libfoo.Implicits.Strings())
 	}
@@ -282,7 +282,7 @@
 		t.Errorf("genrule generated source not included as implicit input for libfoo; Implicits %#v", libfoo.Implicits.Strings())
 	}
 
-	fizzBuzz := ctx.ModuleForTests("fizz-buzz-dep", "android_arm64_armv8-a").Rule("rustc")
+	fizzBuzz := ctx.ModuleForTests(t, "fizz-buzz-dep", "android_arm64_armv8-a").Rule("rustc")
 	if !android.SuffixInList(fizzBuzz.Implicits.Strings(), "/out/bindings.rs") {
 		t.Errorf("rust_bindgen generated source not included as implicit input for fizz-buzz-dep; Implicits %#v", libfoo.Implicits.Strings())
 	}
@@ -290,7 +290,7 @@
 		t.Errorf("genrule generated source not included as implicit input for fizz-buzz-dep; Implicits %#v", libfoo.Implicits.Strings())
 	}
 
-	libprocmacro := ctx.ModuleForTests("libprocmacro", "linux_glibc_x86_64").Rule("rustc")
+	libprocmacro := ctx.ModuleForTests(t, "libprocmacro", "linux_glibc_x86_64").Rule("rustc")
 	if !android.SuffixInList(libprocmacro.Implicits.Strings(), "/out/bindings.rs") {
 		t.Errorf("rust_bindgen generated source not included as implicit input for libprocmacro; Implicits %#v", libfoo.Implicits.Strings())
 	}
@@ -299,15 +299,15 @@
 	}
 
 	// Check that our bindings are picked up as crate dependencies as well
-	libfooMod := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_dylib").Module().(*Module)
+	libfooMod := ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_dylib").Module().(*Module)
 	if !android.InList("libbindings", libfooMod.Properties.AndroidMkRlibs) {
 		t.Errorf("bindgen dependency not detected as a rlib dependency (dependency missing from AndroidMkRlibs)")
 	}
-	fizzBuzzMod := ctx.ModuleForTests("fizz-buzz-dep", "android_arm64_armv8-a").Module().(*Module)
+	fizzBuzzMod := ctx.ModuleForTests(t, "fizz-buzz-dep", "android_arm64_armv8-a").Module().(*Module)
 	if !android.InList("libbindings", fizzBuzzMod.Properties.AndroidMkRlibs) {
 		t.Errorf("bindgen dependency not detected as a rlib dependency (dependency missing from AndroidMkRlibs)")
 	}
-	libprocmacroMod := ctx.ModuleForTests("libprocmacro", "linux_glibc_x86_64").Module().(*Module)
+	libprocmacroMod := ctx.ModuleForTests(t, "libprocmacro", "linux_glibc_x86_64").Module().(*Module)
 	if !android.InList("libbindings.rlib-std", libprocmacroMod.Properties.AndroidMkRlibs) {
 		t.Errorf("bindgen dependency not detected as a rlib dependency (dependency missing from AndroidMkRlibs)")
 	}
@@ -354,7 +354,7 @@
 			srcs: ["foo.rs"],
 		}
 	`)
-	rustc := ctx.ModuleForTests("libpm", "linux_glibc_x86_64").Rule("rustc")
+	rustc := ctx.ModuleForTests(t, "libpm", "linux_glibc_x86_64").Rule("rustc")
 
 	if !strings.Contains(rustc.Args["libFlags"], "libbar/linux_glibc_x86_64") {
 		t.Errorf("Proc_macro is not using host variant of dependent modules.")
@@ -369,7 +369,7 @@
 			srcs: ["foo.rs"],
 			no_stdlibs: true,
 		}`)
-	module := ctx.ModuleForTests("fizz-buzz", "android_arm64_armv8-a").Module().(*Module)
+	module := ctx.ModuleForTests(t, "fizz-buzz", "android_arm64_armv8-a").Module().(*Module)
 
 	if android.InList("libstd", module.Properties.AndroidMkDylibs) {
 		t.Errorf("no_stdlibs did not suppress dependency on libstd")
@@ -385,8 +385,8 @@
 			crate_name: "foo",
 		}`)
 
-	_ = ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_rlib_dylib-std")
-	_ = ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_rlib_dylib-std")
+	_ = ctx.ModuleForTests(t, "libfoo", "android_arm64_armv8-a_rlib_dylib-std")
+	_ = ctx.ModuleForTests(t, "libfoo", "android_arm_armv7-a-neon_rlib_dylib-std")
 }
 
 // Test that library size measurements are generated.
@@ -398,7 +398,7 @@
 			crate_name: "waldo",
 		}`)
 
-	m := ctx.SingletonForTests("file_metrics")
+	m := ctx.SingletonForTests(t, "file_metrics")
 	m.Output("unstripped/libwaldo.dylib.so.bloaty.csv")
 	m.Output("libwaldo.dylib.so.bloaty.csv")
 }
@@ -423,38 +423,52 @@
 			aliases: ["bar:bar_renamed"],
 		}`)
 
-	fooRustc := ctx.ModuleForTests("foo", "android_arm64_armv8-a").Rule("rustc")
-	if !strings.Contains(fooRustc.Args["libFlags"], "--extern bar_renamed=out/soong/.intermediates/libbar/android_arm64_armv8-a_dylib/unstripped/libbar.dylib.so") {
-		t.Errorf("--extern bar_renamed=out/soong/.intermediates/libbar/android_arm64_armv8-a_dylib/unstripped/libbar.dylib.so flag not being passed to rustc for rust_binary with aliases. libFlags: %#v", fooRustc.Args["libFlags"])
+	fooRustc := ctx.ModuleForTests(t, "foo", "android_arm64_armv8-a").Rule("rustc")
+	if !strings.Contains(fooRustc.Args["libFlags"], "--extern force:bar_renamed=out/soong/.intermediates/libbar/android_arm64_armv8-a_dylib/unstripped/libbar.dylib.so") {
+		t.Errorf("--extern force:bar_renamed=out/soong/.intermediates/libbar/android_arm64_armv8-a_dylib/unstripped/libbar.dylib.so flag not being passed to rustc for rust_binary with aliases. libFlags: %#v", fooRustc.Args["libFlags"])
 	}
-	if !strings.Contains(fooRustc.Args["libFlags"], "--extern baz=out/soong/.intermediates/libbaz/android_arm64_armv8-a_dylib/unstripped/libbaz.dylib.so") {
-		t.Errorf("--extern baz=out/soong/.intermediates/libbaz/android_arm64_armv8-a_dylib/unstripped/libbaz.dylib.so flag not being passed to rustc for rust_binary with aliases. libFlags: %#v", fooRustc.Args["libFlags"])
+	if !strings.Contains(fooRustc.Args["libFlags"], "--extern force:baz=out/soong/.intermediates/libbaz/android_arm64_armv8-a_dylib/unstripped/libbaz.dylib.so") {
+		t.Errorf("--extern force:baz=out/soong/.intermediates/libbaz/android_arm64_armv8-a_dylib/unstripped/libbaz.dylib.so flag not being passed to rustc for rust_binary with aliases. libFlags: %#v", fooRustc.Args["libFlags"])
 	}
 }
 
-func TestRustRlibs(t *testing.T) {
+func TestRustFFIRlibs(t *testing.T) {
 	ctx := testRust(t, `
-		rust_ffi_rlib {
+		rust_ffi_static {
 			name: "libbar",
 			crate_name: "bar",
 			srcs: ["src/lib.rs"],
 			export_include_dirs: ["bar_includes"]
 		}
 
-		rust_ffi_rlib {
+		rust_ffi_static {
 			name: "libfoo",
 			crate_name: "foo",
 			srcs: ["src/lib.rs"],
 			export_include_dirs: ["foo_includes"]
 		}
 
-		rust_ffi_rlib {
+		rust_ffi_static {
+			name: "libfoo_from_rlib",
+			crate_name: "foo_from_rlib",
+			srcs: ["src/lib.rs"],
+			export_include_dirs: ["foo_includes"]
+		}
+
+		rust_ffi_static {
 			name: "libbuzz",
 			crate_name: "buzz",
 			srcs: ["src/lib.rs"],
 			export_include_dirs: ["buzz_includes"]
 		}
 
+		rust_ffi_static {
+			name: "libbuzz_from_rlib",
+			crate_name: "buzz_from_rlib",
+			srcs: ["src/lib.rs"],
+			export_include_dirs: ["buzz_includes"]
+		}
+
 		cc_library_shared {
 			name: "libcc_shared",
 			srcs:["foo.c"],
@@ -468,20 +482,45 @@
 			whole_static_libs: ["libfoo"],
 		}
 
+		cc_library_static {
+			name: "libcc_static_from_rlib",
+			srcs:["foo.c"],
+			static_libs: ["libbuzz_from_rlib"],
+			whole_static_libs: ["libfoo_from_rlib"],
+		}
+
 		cc_binary {
 			name: "ccBin",
 			srcs:["foo.c"],
 			static_libs: ["libcc_static", "libbar"],
 		}
+
+		rust_library {
+			name: "librs",
+			srcs:["src/foo.rs"],
+			crate_name: "rs",
+			static_libs: ["libcc_static_from_rlib"],
+		}
+
+		rust_binary {
+			name: "rsBin",
+			srcs:["src/foo.rs"],
+			crate_name: "rsBin",
+			rlibs: ["librs", "libbar"],
+			static_libs: ["libcc_static"],
+		}
 		`)
 
-	libbar := ctx.ModuleForTests("libbar", "android_arm64_armv8-a_rlib_rlib-std").Rule("rustc")
-	libcc_shared_rustc := ctx.ModuleForTests("libcc_shared", "android_arm64_armv8-a_shared").Rule("rustc")
-	libcc_shared_ld := ctx.ModuleForTests("libcc_shared", "android_arm64_armv8-a_shared").Rule("ld")
-	libcc_shared_cc := ctx.ModuleForTests("libcc_shared", "android_arm64_armv8-a_shared").Rule("cc")
-	ccbin_rustc := ctx.ModuleForTests("ccBin", "android_arm64_armv8-a").Rule("rustc")
-	ccbin_ld := ctx.ModuleForTests("ccBin", "android_arm64_armv8-a").Rule("ld")
-	ccbin_cc := ctx.ModuleForTests("ccBin", "android_arm64_armv8-a").Rule("cc")
+	libbar := ctx.ModuleForTests(t, "libbar", "android_arm64_armv8-a_rlib_rlib-std").Rule("rustc")
+	libcc_shared_rustc := ctx.ModuleForTests(t, "libcc_shared", "android_arm64_armv8-a_shared").Rule("rustc")
+	libcc_shared_ld := ctx.ModuleForTests(t, "libcc_shared", "android_arm64_armv8-a_shared").Rule("ld")
+	libcc_shared_cc := ctx.ModuleForTests(t, "libcc_shared", "android_arm64_armv8-a_shared").Rule("cc")
+	ccbin_rustc := ctx.ModuleForTests(t, "ccBin", "android_arm64_armv8-a").Rule("rustc")
+	ccbin_ld := ctx.ModuleForTests(t, "ccBin", "android_arm64_armv8-a").Rule("ld")
+	ccbin_cc := ctx.ModuleForTests(t, "ccBin", "android_arm64_armv8-a").Rule("cc")
+	rustbin_genlib := ctx.ModuleForTests(t, "rsBin", "android_arm64_armv8-a").Output("generated_rust_staticlib/librustlibs.a")
+	rustbin := ctx.ModuleForTests(t, "rsBin", "android_arm64_armv8-a").Output("unstripped/rsBin")
+	librs_rlib := ctx.ModuleForTests(t, "librs", "android_arm64_armv8-a_rlib_dylib-std").MaybeOutput("generated_rust_staticlib/librustlibs.a")
 
 	if !strings.Contains(libbar.Args["rustcFlags"], "crate-type=rlib") {
 		t.Errorf("missing crate-type for static variant, expecting %#v, rustcFlags: %#v", "rlib", libbar.Args["rustcFlags"])
@@ -494,7 +533,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,9 +550,9 @@
 	}
 
 	// 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"])
+			"generated_rust_staticlib/librustlibs.a", ccbin_ld.Args["libFlags"])
 	}
 
 	// Make sure the static lib includes are in the ld command
@@ -534,11 +573,43 @@
 		t.Errorf("Missing direct dependency libbar when writing generated Rust staticlib: %#v", ccbin_rustc.Args["libFlags"])
 	}
 
+	// Make sure the static lib is included in the rustc command
+	if !strings.Contains(rustbin.Args["linkFlags"], "generated_rust_staticlib/librustlibs.a") {
+		t.Errorf("missing generated static library in linker step libFlags in Rust module, expecting %#v, libFlags: %#v",
+			"generated_rust_staticlib/librustlibs.a", rustbin.Args["libFlags"])
+	}
+
+	// Make sure that direct dependencies and indirect whole static dependencies are
+	// propagating correctly for the rlib -> cc_library_static -> rust_* generated library example.
+	if !strings.Contains(rustbin_genlib.Args["libFlags"], "--extern foo=") {
+		t.Errorf("Missing indirect whole_static_lib dependency libfoo from cc static_lib when writing generated Rust staticlib: %#v", rustbin_genlib.Args["libFlags"])
+	}
+	if strings.Contains(rustbin_genlib.Args["libFlags"], "--extern buzz=") {
+		t.Errorf("Indirect rlib dependency libbuzz from cc static_lib found when writing generated Rust staticlib: %#v", rustbin_genlib.Args["libFlags"])
+	}
+	if strings.Contains(rustbin_genlib.Args["libFlags"], "--extern bar=") {
+		t.Errorf("Direct rlib dependency libbar getting included in the generated Rust staticlib: %#v", rustbin_genlib.Args["libFlags"])
+	}
+	if !strings.Contains(rustbin_genlib.Args["libFlags"], "--extern foo_from_rlib=") {
+		t.Errorf("Missing indirect whole_static_lib dependency libfoo_from_rlib from cc static_lib when writing generated Rust staticlib: %#v", rustbin_genlib.Args["libFlags"])
+	}
+	if strings.Contains(rustbin_genlib.Args["libFlags"], "--extern buzz_from_rlib=") {
+		// While static-libs propagate for rust modules, this is not the
+		// expected behavior for cc modules. Thus, libbuzz_from_rlib would
+		// be expected to have to be re-declared as a direct rlib dependency.
+		t.Errorf("Indirect rlib dependency libbuzz_from_rlib from cc static_lib found when writing generated Rust staticlib: %#v", rustbin_genlib.Args["libFlags"])
+	}
+
 	// Test indirect includes propagation
 	if !strings.Contains(ccbin_cc.Args["cFlags"], "-Ifoo_includes") {
 		t.Errorf("missing rlibs includes, expecting %#v, cFlags: %#v",
 			"-Ifoo_includes", ccbin_cc.Args)
 	}
+
+	// Make sure we're not generating superfluous mto staticlibs.
+	if librs_rlib.Rule != nil {
+		t.Error("rlibs should not be generating mto staticlibs", "rlib", libbar.Args["rustcFlags"])
+	}
 }
 
 func assertString(t *testing.T, got, expected string) {
@@ -547,3 +618,204 @@
 		t.Errorf("expected %q got %q", expected, got)
 	}
 }
+
+func TestStdLinkMismatch(t *testing.T) {
+	// Test that we catch cases where the std linkage mismatches. This leads to
+	// a confusing rustc error where a crate is declared missing despite being
+	// passed in as a rustlib dependency / via the --extern flag. Thus, we want
+	// to make sure we detect it in Soong.
+
+	// libfoo depends on libbar as an rlib, but does not link libstd as an rlib.
+	// libbar only links libstd as an rlib (prefer_rlib).
+	testRustError(t, "wrong StdLinkage", `
+		rust_library {
+			name: "libfoo",
+			crate_name: "foo",
+			srcs: [
+				"foo.rs",
+			],
+			rlibs: ["libbar"],
+		}
+		rust_library {
+			name: "libbar",
+			crate_name: "bar",
+			srcs: [
+				"bar.rs",
+			],
+			prefer_rlib: true,
+		}
+	`)
+}
+
+func TestRustLinkPropagation(t *testing.T) {
+	// Test static and whole static propagation behavior
+	//
+	//  Whole static libs propagate through rlibs and through dylibs to
+	//  dependencies further down. rustc does not re-export whole-archived
+	//  static libs for dylibs, so this simulates re-exporting those symbols.
+	//
+	//  Static libs only propagate through rlibs to some final dylib. We propagate
+	//  normal static libs because we allow rustlib dependencies to represent
+	//  either rlibs or dylibs. Not propagating static libs through rlibs would
+	//  mean we'd need to always redeclare static libs throughout a dependency tree
+	//  We don't propagate past dylibs because they represent a final link.
+
+	ctx := testRust(t, `
+	rust_library_rlib {
+		name: "librlib1",
+		crate_name: "rlib1",
+		srcs: ["src/lib.rs"],
+		static_libs: ["libcc_static_rlib1"],
+		whole_static_libs: ["libcc_whole_static_rlib1"],
+	}
+
+	rust_library_dylib {
+		name: "libdylib1",
+		crate_name: "dylib1",
+		static_libs: ["libcc_static_dylib1"],
+		srcs: ["src/lib.rs"],
+		whole_static_libs: ["libcc_whole_static_dylib1"],
+	}
+
+	rust_library_rlib {
+		name: "librlib2",
+		crate_name: "rlib2",
+		srcs: ["src/lib.rs"],
+		rlibs: ["librlib1"],
+		static_libs: ["libcc_static_rlib2"],
+		whole_static_libs: ["libcc_whole_static_rlib2"],
+	}
+
+	rust_library_dylib {
+		name: "libdylib2",
+		crate_name: "dylib2",
+		srcs: ["src/lib.rs"],
+		rlibs: ["librlib1"],
+		rustlibs: ["libdylib1"],
+		static_libs: ["libcc_static_dylib2"],
+		whole_static_libs: ["libcc_whole_static_dylib2"],
+	}
+
+	cc_library_static {
+		name: "libcc_static_rlib1",
+		srcs:["foo.c"],
+	}
+
+	cc_library_static {
+		name: "libcc_static_rlib2",
+		srcs:["foo.c"],
+	}
+
+	cc_library_static {
+		name: "libcc_static_dylib1",
+		srcs:["foo.c"],
+	}
+
+	cc_library_static {
+		name: "libcc_static_dylib2",
+		srcs:["foo.c"],
+	}
+
+	cc_library_static {
+		name: "libcc_whole_static_rlib1",
+		srcs:["foo.c"],
+	}
+
+	cc_library_static {
+		name: "libcc_whole_static_rlib2",
+		srcs:["foo.c"],
+	}
+
+	cc_library_static {
+		name: "libcc_whole_static_dylib1",
+		srcs:["foo.c"],
+	}
+
+	cc_library_static {
+		name: "libcc_whole_static_dylib2",
+		srcs:["foo.c"],
+	}
+
+	rust_library_rlib {
+		name: "librlib3",
+		crate_name: "rlib3",
+		srcs: ["src/lib.rs"],
+		rlibs: ["librlib2"],
+	}
+
+	rust_library_dylib {
+		name: "libdylib3",
+		crate_name: "dylib3",
+		srcs: ["src/lib.rs"],
+		rlibs: ["librlib2"],
+		rustlibs: ["libdylib2"],
+	}
+	`)
+
+	librlib3 := ctx.ModuleForTests(t, "librlib3", "android_arm64_armv8-a_rlib_dylib-std").Rule("rustc")
+	libdylib3 := ctx.ModuleForTests(t, "libdylib3", "android_arm64_armv8-a_dylib").Rule("rustc")
+
+	// Test static lib propagation from:
+	// rlib -> rlib
+	if !strings.Contains(librlib3.Args["linkFlags"], "libcc_static_rlib2.a") {
+		t.Errorf("direct dependency static lib not propagating from rlib to rlib; linkFlags %#v",
+			librlib3.Args["linkFlags"])
+	}
+	// rlib -> rlib -> rlib
+	if !strings.Contains(librlib3.Args["linkFlags"], "libcc_static_rlib1.a") {
+		t.Errorf("indirect dependency static lib not propagating from rlib to rlib: linkFlags %#v",
+			librlib3.Args["linkFlags"])
+	}
+	// rlib -> rlib -> dylib
+	if !strings.Contains(libdylib3.Args["linkFlags"], "libcc_static_rlib1.a") {
+		t.Errorf("indirect dependency static lib not propagating from rlib to dylib: linkFlags %#v",
+			libdylib3.Args["linkFlags"])
+	}
+	// rlib -> dylib
+	if !strings.Contains(libdylib3.Args["linkFlags"], "libcc_static_rlib2.a") {
+		t.Errorf("direct dependency static lib not propagating from rlib to dylib: linkFlags: %#v",
+			libdylib3.Args["linkFlags"])
+	}
+	// dylib -> dylib (negative case, should not propagate)
+	if strings.Contains(libdylib3.Args["linkFlags"], "libcc_static_dylib2.a") {
+		t.Errorf("direct dependency static lib propagating from dylib to dylib: linkFlags: %#v",
+			libdylib3.Args["linkFlags"])
+	}
+	// dylib -> dylib -> dylib (negative case, should not propagate)
+	if strings.Contains(libdylib3.Args["linkFlags"], "libcc_static_dylib1.a") {
+		t.Errorf("indirect dependency static lib propagating from dylib to dylib: linkFlags: %#v",
+			libdylib3.Args["linkFlags"])
+	}
+
+	// Test whole static lib propagation from:
+	// rlib -> rlib
+	if !strings.Contains(librlib3.Args["linkFlags"], "libcc_whole_static_rlib2.a") {
+		t.Errorf("direct dependency whole static lib not propagating from rlib to rlib: linkFlags %#v",
+			librlib3.Args["linkFlags"])
+	}
+	// rlib -> rlib -> rlib
+	if !strings.Contains(librlib3.Args["linkFlags"], "libcc_whole_static_rlib1.a") {
+		t.Errorf("indirect dependency whole static lib not propagating from rlib to rlib: linkFlags %#v",
+			librlib3.Args["linkFlags"])
+	}
+	// rlib -> dylib
+	if !strings.Contains(libdylib3.Args["linkFlags"], "libcc_whole_static_rlib2.a") {
+		t.Errorf("direct dependency whole static lib not propagating from rlib to dylib: linkFlags %#v",
+			libdylib3.Args["linkFlags"])
+	}
+	// rlib -> rlib -> dylib
+	if !strings.Contains(libdylib3.Args["linkFlags"], "libcc_whole_static_rlib1.a") {
+		t.Errorf("indirect dependency whole static lib not propagating from rlib to dylib: linkFlags %#v",
+			libdylib3.Args["linkFlags"])
+	}
+	// dylib -> dylib
+	if !strings.Contains(libdylib3.Args["linkFlags"], "libcc_whole_static_dylib2.a") {
+		t.Errorf("direct dependency whole static lib not propagating from dylib to dylib: linkFlags %#v",
+			libdylib3.Args["linkFlags"])
+	}
+	// dylib -> dylib -> dylib
+	if !strings.Contains(libdylib3.Args["linkFlags"], "libcc_whole_static_dylib1.a") {
+		t.Errorf("indirect dependency whole static lib not propagating from dylib to dylib: linkFlags %#v",
+			libdylib3.Args["linkFlags"])
+	}
+}
diff --git a/rust/sanitize.go b/rust/sanitize.go
index c086880..50f55ce 100644
--- a/rust/sanitize.go
+++ b/rust/sanitize.go
@@ -53,6 +53,9 @@
 
 	// Used when we need to place libraries in their own directory, such as ASAN.
 	InSanitizerDir bool `blueprint:"mutated"`
+
+	// ForceDisable is set by the version mutator to disable sanitization of stubs variants
+	ForceDisable bool `blueprint:"mutated"`
 }
 
 var fuzzerFlags = []string{
@@ -94,14 +97,6 @@
 	"-C llvm-args=--hwasan-with-ifunc",
 }
 
-func boolPtr(v bool) *bool {
-	if v {
-		return &v
-	} else {
-		return nil
-	}
-}
-
 func init() {
 }
 func (sanitize *sanitize) props() []interface{} {
@@ -111,6 +106,15 @@
 func (sanitize *sanitize) begin(ctx BaseModuleContext) {
 	s := &sanitize.Properties.Sanitize
 
+	if sanitize.Properties.ForceDisable {
+		return
+	}
+
+	// Disable sanitizers for musl x86 modules, rustc does not support any sanitizers.
+	if ctx.Os() == android.LinuxMusl && ctx.Arch().ArchType == android.X86 {
+		s.Never = proptools.BoolPtr(true)
+	}
+
 	// Never always wins.
 	if Bool(s.Never) {
 		return
@@ -212,11 +216,6 @@
 		s.Memtag_heap = nil
 	}
 
-	// Disable sanitizers for musl x86 modules, rustc does not support any sanitizers.
-	if ctx.Os() == android.LinuxMusl && ctx.Arch().ArchType == android.X86 {
-		s.Never = boolPtr(true)
-	}
-
 	// TODO:(b/178369775)
 	// For now sanitizing is only supported on non-windows targets
 	if ctx.Os() != android.Windows && (Bool(s.Hwaddress) || Bool(s.Address) || Bool(s.Memtag_heap) || Bool(s.Fuzzer)) {
@@ -229,6 +228,10 @@
 }
 
 func (sanitize *sanitize) flags(ctx ModuleContext, flags Flags, deps PathDeps) (Flags, PathDeps) {
+	if sanitize.Properties.ForceDisable {
+		return flags, deps
+	}
+
 	if !sanitize.Properties.SanitizerEnabled {
 		return flags, deps
 	}
@@ -261,6 +264,9 @@
 		if !mod.Enabled(mctx) {
 			return
 		}
+		if mod.sanitize.Properties.ForceDisable {
+			return
+		}
 
 		if Bool(mod.sanitize.Properties.Sanitize.Memtag_heap) && mod.Binary() {
 			noteDep := "note_memtag_heap_async"
@@ -318,16 +324,16 @@
 	sanitizerSet := false
 	switch t {
 	case cc.Fuzzer:
-		sanitize.Properties.Sanitize.Fuzzer = boolPtr(b)
+		sanitize.Properties.Sanitize.Fuzzer = proptools.BoolPtr(b)
 		sanitizerSet = true
 	case cc.Asan:
-		sanitize.Properties.Sanitize.Address = boolPtr(b)
+		sanitize.Properties.Sanitize.Address = proptools.BoolPtr(b)
 		sanitizerSet = true
 	case cc.Hwasan:
-		sanitize.Properties.Sanitize.Hwaddress = boolPtr(b)
+		sanitize.Properties.Sanitize.Hwaddress = proptools.BoolPtr(b)
 		sanitizerSet = true
 	case cc.Memtag_heap:
-		sanitize.Properties.Sanitize.Memtag_heap = boolPtr(b)
+		sanitize.Properties.Sanitize.Memtag_heap = proptools.BoolPtr(b)
 		sanitizerSet = true
 	default:
 		panic(fmt.Errorf("setting unsupported sanitizerType %d", t))
@@ -372,7 +378,7 @@
 // distinguish between the cases. It isn't needed though - both cases can be
 // treated identically.
 func (sanitize *sanitize) isSanitizerEnabled(t cc.SanitizerType) bool {
-	if sanitize == nil || !sanitize.Properties.SanitizerEnabled {
+	if sanitize == nil || !sanitize.Properties.SanitizerEnabled || sanitize.Properties.ForceDisable {
 		return false
 	}
 
@@ -461,7 +467,7 @@
 }
 
 func (mod *Module) SanitizeNever() bool {
-	return Bool(mod.sanitize.Properties.Sanitize.Never)
+	return Bool(mod.sanitize.Properties.Sanitize.Never) || mod.sanitize.Properties.ForceDisable
 }
 
 var _ cc.PlatformSanitizeable = (*Module)(nil)
diff --git a/rust/sanitize_test.go b/rust/sanitize_test.go
index d6a14b2..a3fe282 100644
--- a/rust/sanitize_test.go
+++ b/rust/sanitize_test.go
@@ -153,65 +153,65 @@
 	).RunTest(t)
 	ctx := result.TestContext
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_no_override", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_async", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_sync", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_override_default_sync", variant), None)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_no_override", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_async", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_sync", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_override_default_sync", variant), None)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_no_override", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_async", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_disable", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_no_override", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_async", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_disable", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_sync", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_override_default_sync", variant), Async)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_no_override", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_async", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_disable", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_sync", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_override_default_sync", variant), Async)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_no_override", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_no_override", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_async", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_override_default_sync", variant), Sync)
 }
 
 func TestSanitizeMemtagHeapWithSanitizeDevice(t *testing.T) {
@@ -226,67 +226,67 @@
 	).RunTest(t)
 	ctx := result.TestContext
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_no_override", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_async", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_sync", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_override_default_sync", variant), None)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_no_override", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_async", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_sync", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_override_default_sync", variant), None)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_no_override", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_async", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_disable", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_no_override", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_async", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_disable", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_sync", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_override_default_sync", variant), Async)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_no_override", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_async", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_disable", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_sync", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_override_default_sync", variant), Async)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_override_default_sync", variant), Sync)
 
 	// should sanitize: { diag: { memtag: true } } result in Sync instead of None here?
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_override_default_async", variant), Sync)
 	// should sanitize: { diag: { memtag: true } } result in Sync instead of None here?
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_no_override", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_async", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_override_default_sync", variant), Sync)
 }
 
 func TestSanitizeMemtagHeapWithSanitizeDeviceDiag(t *testing.T) {
@@ -302,64 +302,64 @@
 	).RunTest(t)
 	ctx := result.TestContext
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_no_override", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_async", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_binary_override_default_sync", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_binary_override_default_sync", variant), None)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_no_override", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_async", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("no_memtag_test_override_default_sync", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_no_override", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_override_default_async", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "no_memtag_test_override_default_sync", variant), None)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_test_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_no_override", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_async", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_disable", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_binary_override_default_sync", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_binary_override_default_sync", variant), Async)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_no_override", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_async", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_disable", variant), Async)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_async_test_override_default_sync", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_no_override", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_override_default_async", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_override_default_disable", variant), Async)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_async_test_override_default_sync", variant), Async)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("set_memtag_set_sync_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "set_memtag_set_sync_test_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_override_default_async", variant), Sync)
 	// should sanitize: { diag: { memtag: true } } result in Sync instead of None here?
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_memtag_set_sync_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_memtag_set_sync_test_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_disable", variant), None)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_binary_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_override_default_disable", variant), None)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_binary_override_default_sync", variant), Sync)
 
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_no_override", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_async", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_disable", variant), Sync)
-	checkHasMemtagNote(t, ctx.ModuleForTests("unset_test_override_default_sync", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_no_override", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_override_default_async", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_override_default_disable", variant), Sync)
+	checkHasMemtagNote(t, ctx.ModuleForTests(t, "unset_test_override_default_sync", variant), Sync)
 }
diff --git a/rust/source_provider.go b/rust/source_provider.go
index 3236bce..27c62c2 100644
--- a/rust/source_provider.go
+++ b/rust/source_provider.go
@@ -43,6 +43,7 @@
 	SourceProviderProps() []interface{}
 	SourceProviderDeps(ctx DepsContext, deps Deps) Deps
 	setSubName(subName string)
+	getSubName() string
 	setOutputFiles(outputFiles android.Paths)
 }
 
@@ -100,6 +101,10 @@
 	sp.subName = subName
 }
 
+func (sp *BaseSourceProvider) getSubName() string {
+	return sp.subName
+}
+
 func (sp *BaseSourceProvider) setOutputFiles(outputFiles android.Paths) {
 	sp.OutputFiles = outputFiles
 }
diff --git a/rust/test.go b/rust/test.go
index b7ddd06..b658ae2 100644
--- a/rust/test.go
+++ b/rust/test.go
@@ -46,6 +46,9 @@
 	// the test
 	Data []string `android:"path,arch_variant"`
 
+	// Same as data, but will add dependencies on the device's
+	Device_common_data []string `android:"path_device_common"`
+
 	// list of shared library modules that should be installed alongside the test
 	Data_libs []string `android:"arch_variant"`
 
@@ -143,36 +146,38 @@
 	})
 
 	dataSrcPaths := android.PathsForModuleSrc(ctx, test.Properties.Data)
+	dataSrcPaths = append(dataSrcPaths, android.PathsForModuleSrc(ctx, test.Properties.Device_common_data)...)
 
-	ctx.VisitDirectDepsWithTag(dataLibDepTag, func(dep android.Module) {
+	ctx.VisitDirectDepsProxyWithTag(dataLibDepTag, func(dep android.ModuleProxy) {
 		depName := ctx.OtherModuleName(dep)
-		linkableDep, ok := dep.(cc.LinkableInterface)
+		linkableDep, ok := android.OtherModuleProvider(ctx, dep, cc.LinkableInfoProvider)
 		if !ok {
 			ctx.ModuleErrorf("data_lib %q is not a linkable module", depName)
 		}
-		if linkableDep.OutputFile().Valid() {
+		if linkableDep.OutputFile.Valid() {
 			// Copy the output in "lib[64]" so that it's compatible with
 			// the default rpath values.
+			commonInfo := android.OtherModuleProviderOrDefault(ctx, dep, android.CommonModuleInfoKey)
 			libDir := "lib"
-			if linkableDep.Target().Arch.ArchType.Multilib == "lib64" {
+			if commonInfo.Target.Arch.ArchType.Multilib == "lib64" {
 				libDir = "lib64"
 			}
 			test.data = append(test.data,
-				android.DataPath{SrcPath: linkableDep.OutputFile().Path(),
-					RelativeInstallPath: filepath.Join(libDir, linkableDep.RelativeInstallPath())})
+				android.DataPath{SrcPath: linkableDep.OutputFile.Path(),
+					RelativeInstallPath: filepath.Join(libDir, linkableDep.RelativeInstallPath)})
 		}
 	})
 
-	ctx.VisitDirectDepsWithTag(dataBinDepTag, func(dep android.Module) {
+	ctx.VisitDirectDepsProxyWithTag(dataBinDepTag, func(dep android.ModuleProxy) {
 		depName := ctx.OtherModuleName(dep)
-		linkableDep, ok := dep.(cc.LinkableInterface)
+		linkableDep, ok := android.OtherModuleProvider(ctx, dep, cc.LinkableInfoProvider)
 		if !ok {
 			ctx.ModuleErrorf("data_bin %q is not a linkable module", depName)
 		}
-		if linkableDep.OutputFile().Valid() {
+		if linkableDep.OutputFile.Valid() {
 			test.data = append(test.data,
-				android.DataPath{SrcPath: linkableDep.OutputFile().Path(),
-					RelativeInstallPath: linkableDep.RelativeInstallPath()})
+				android.DataPath{SrcPath: linkableDep.OutputFile.Path(),
+					RelativeInstallPath: linkableDep.RelativeInstallPath})
 		}
 	})
 
@@ -190,6 +195,27 @@
 	if ctx.Host() && test.Properties.Test_options.Unit_test == nil {
 		test.Properties.Test_options.Unit_test = proptools.BoolPtr(true)
 	}
+
+	if !ctx.Config().KatiEnabled() { // TODO(spandandas): Remove the special case for kati
+		// Install the test config in testcases/ directory for atest.
+		r, ok := ctx.Module().(*Module)
+		if !ok {
+			ctx.ModuleErrorf("Not a rust test module")
+		}
+		// Install configs in the root of $PRODUCT_OUT/testcases/$module
+		testCases := android.PathForModuleInPartitionInstall(ctx, "testcases", ctx.ModuleName()+r.SubName())
+		if ctx.PrimaryArch() {
+			if test.testConfig != nil {
+				ctx.InstallFile(testCases, ctx.ModuleName()+".config", test.testConfig)
+			}
+		}
+		// Install tests and data in arch specific subdir $PRODUCT_OUT/testcases/$module/$arch
+		testCases = testCases.Join(ctx, ctx.Target().Arch.ArchType.String())
+		ctx.InstallTestData(testCases, test.data)
+		testPath := ctx.RustModule().OutputFile().Path()
+		ctx.InstallFile(testCases, testPath.Base(), testPath)
+	}
+
 	test.binaryDecorator.installTestData(ctx, test.data)
 	test.binaryDecorator.install(ctx)
 }
@@ -198,6 +224,7 @@
 	flags = test.binaryDecorator.compilerFlags(ctx, flags)
 	if test.testHarness() {
 		flags.RustFlags = append(flags.RustFlags, "--test")
+		flags.RustFlags = append(flags.RustFlags, "-A missing-docs")
 	}
 	if ctx.Device() {
 		flags.RustFlags = append(flags.RustFlags, "-Z panic_abort_tests")
@@ -234,7 +261,7 @@
 	return module.Init()
 }
 
-func (test *testDecorator) stdLinkage(ctx *depsContext) RustLinkage {
+func (test *testDecorator) stdLinkage(device bool) RustLinkage {
 	return RlibLinkage
 }
 
@@ -253,6 +280,32 @@
 	return true
 }
 
+func (test *testDecorator) moduleInfoJSON(ctx ModuleContext, moduleInfoJSON *android.ModuleInfoJSON) {
+	test.binaryDecorator.moduleInfoJSON(ctx, moduleInfoJSON)
+	moduleInfoJSON.Class = []string{"NATIVE_TESTS"}
+	if Bool(test.Properties.Test_options.Unit_test) {
+		moduleInfoJSON.IsUnitTest = "true"
+		if ctx.Host() {
+			moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "host-unit-tests")
+		}
+	}
+	moduleInfoJSON.TestOptionsTags = append(moduleInfoJSON.TestOptionsTags, test.Properties.Test_options.Tags...)
+	if test.testConfig != nil {
+		if _, ok := test.testConfig.(android.WritablePath); ok {
+			moduleInfoJSON.AutoTestConfig = []string{"true"}
+		}
+		moduleInfoJSON.TestConfig = append(moduleInfoJSON.TestConfig, test.testConfig.String())
+	}
+
+	moduleInfoJSON.DataDependencies = append(moduleInfoJSON.DataDependencies, test.Properties.Data_bins...)
+
+	if len(test.Properties.Test_suites) > 0 {
+		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, test.Properties.Test_suites...)
+	} else {
+		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "null-suite")
+	}
+}
+
 func rustTestHostMultilib(ctx android.LoadHookContext) {
 	type props struct {
 		Target struct {
diff --git a/rust/test_test.go b/rust/test_test.go
index 1097da2..076a259 100644
--- a/rust/test_test.go
+++ b/rust/test_test.go
@@ -29,7 +29,7 @@
 			data: ["data.txt"],
 		}`)
 
-	testingModule := ctx.ModuleForTests("my_test", "linux_glibc_x86_64")
+	testingModule := ctx.ModuleForTests(t, "my_test", "linux_glibc_x86_64")
 	expectedOut := "my_test/linux_glibc_x86_64/my_test"
 	outPath := testingModule.Output("my_test").Output.String()
 	if !strings.Contains(outPath, expectedOut) {
@@ -62,7 +62,7 @@
 			crate_name: "bar",
 		}`)
 
-	testingModule := ctx.ModuleForTests("my_test", "android_arm64_armv8-a").Module().(*Module)
+	testingModule := ctx.ModuleForTests(t, "my_test", "android_arm64_armv8-a").Module().(*Module)
 
 	if !android.InList("libfoo.rlib-std", testingModule.Properties.AndroidMkRlibs) {
 		t.Errorf("rlib-std variant for libfoo not detected as a rustlib-defined rlib dependency for device rust_test module")
@@ -106,7 +106,7 @@
 
 	ctx := testRust(t, bp)
 
-	testingModule := ctx.ModuleForTests("main_test", "android_arm64_armv8-a")
+	testingModule := ctx.ModuleForTests(t, "main_test", "android_arm64_armv8-a")
 	testBinary := testingModule.Module().(*Module).compiler.(*testDecorator)
 	outputFiles := testingModule.OutputFiles(ctx, t, "")
 	if len(outputFiles) != 1 {
@@ -165,7 +165,7 @@
  `
 
 	ctx := testRust(t, bp)
-	testingModule := ctx.ModuleForTests("main_test", "android_arm64_armv8-a")
+	testingModule := ctx.ModuleForTests(t, "main_test", "android_arm64_armv8-a")
 	module := testingModule.Module()
 	testBinary := module.(*Module).compiler.(*testDecorator)
 	outputFiles := testingModule.OutputFiles(ctx, t, "")
diff --git a/rust/testing.go b/rust/testing.go
index 32cc823..2082b52 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -80,7 +80,6 @@
 			no_libcrt: true,
 			nocrt: true,
 			system_shared_libs: [],
-			apex_available: ["//apex_available:platform", "//apex_available:anyapex"],
 			min_sdk_version: "29",
 			vendor_available: true,
 			host_supported: true,
@@ -88,6 +87,13 @@
 			llndk: {
 				symbol_file: "liblog.map.txt",
 			},
+			stubs: {
+				symbol_file: "liblog.map.txt",
+				versions: [
+					"29",
+					"30",
+				],
+			},
 		}
 		cc_library {
 			name: "libprotobuf-cpp-full",
@@ -188,12 +194,10 @@
 	ctx.RegisterModuleType("rust_fuzz_host", RustFuzzHostFactory)
 	ctx.RegisterModuleType("rust_ffi", RustFFIFactory)
 	ctx.RegisterModuleType("rust_ffi_shared", RustFFISharedFactory)
-	ctx.RegisterModuleType("rust_ffi_rlib", RustFFIRlibFactory)
-	ctx.RegisterModuleType("rust_ffi_static", RustFFIRlibFactory)
+	ctx.RegisterModuleType("rust_ffi_static", RustLibraryRlibFactory)
 	ctx.RegisterModuleType("rust_ffi_host", RustFFIHostFactory)
 	ctx.RegisterModuleType("rust_ffi_host_shared", RustFFISharedHostFactory)
-	ctx.RegisterModuleType("rust_ffi_host_rlib", RustFFIRlibHostFactory)
-	ctx.RegisterModuleType("rust_ffi_host_static", RustFFIRlibHostFactory)
+	ctx.RegisterModuleType("rust_ffi_host_static", RustLibraryRlibHostFactory)
 	ctx.RegisterModuleType("rust_proc_macro", ProcMacroFactory)
 	ctx.RegisterModuleType("rust_protobuf", RustProtobufFactory)
 	ctx.RegisterModuleType("rust_protobuf_host", RustProtobufHostFactory)
diff --git a/scripts/Android.bp b/scripts/Android.bp
index 00b3ca5..94163a5 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -293,14 +293,6 @@
 }
 
 python_binary_host {
-    name: "merge_directories",
-    main: "merge_directories.py",
-    srcs: [
-        "merge_directories.py",
-    ],
-}
-
-python_binary_host {
     name: "merge_json",
     main: "merge_json.py",
     srcs: [
@@ -319,3 +311,11 @@
     main: "extra_install_zips_file_list.py",
     srcs: ["extra_install_zips_file_list.py"],
 }
+
+python_binary_host {
+    name: "rustc_linker",
+    main: "rustc_linker.py",
+    srcs: [
+        "rustc_linker.py",
+    ],
+}
diff --git a/scripts/build-apex-bundle.py b/scripts/build-apex-bundle.py
index dcdd9ef..277e112 100644
--- a/scripts/build-apex-bundle.py
+++ b/scripts/build-apex-bundle.py
@@ -16,8 +16,6 @@
 #
 """A tool to create an APEX bundle out of Soong-built base.zip"""
 
-from __future__ import print_function
-
 import argparse
 import sys
 import tempfile
diff --git a/scripts/build-ndk-prebuilts.sh b/scripts/build-ndk-prebuilts.sh
index ef0f44a..b600443 100755
--- a/scripts/build-ndk-prebuilts.sh
+++ b/scripts/build-ndk-prebuilts.sh
@@ -23,9 +23,18 @@
 # TODO: remove ALLOW_MISSING_DEPENDENCIES=true when all the riscv64
 # dependencies exist (currently blocked by http://b/273792258).
 # TODO: remove BUILD_BROKEN_DISABLE_BAZEL=1 when bazel supports riscv64 (http://b/262192655).
+#
+# LTO is disabled because the NDK compiler is not necessarily in-sync with the
+# compiler used to build the platform sysroot, and the sysroot includes static
+# libraries which would be incompatible with mismatched compilers when built
+# with LTO. Disabling LTO globally for the NDK sysroot is okay because the only
+# compiled code in the sysroot that will end up in apps is those static
+# libraries.
+# https://github.com/android/ndk/issues/1591
 TARGET_RELEASE=trunk_staging \
 ALLOW_MISSING_DEPENDENCIES=true \
 BUILD_BROKEN_DISABLE_BAZEL=1 \
+DISABLE_LTO=true \
     TARGET_PRODUCT=ndk build/soong/soong_ui.bash --make-mode --soong-only ${OUT_DIR}/soong/ndk.timestamp
 
 if [ -n "${DIST_DIR}" ]; then
diff --git a/scripts/check_boot_jars/check_boot_jars.py b/scripts/check_boot_jars/check_boot_jars.py
index b711f9d..174b96e 100755
--- a/scripts/check_boot_jars/check_boot_jars.py
+++ b/scripts/check_boot_jars/check_boot_jars.py
@@ -4,7 +4,6 @@
 Usage: check_boot_jars.py <dexdump_path> <package_allow_list_file> <jar1> \
 <jar2> ...
 """
-from __future__ import print_function
 import logging
 import re
 import subprocess
diff --git a/scripts/construct_context.py b/scripts/construct_context.py
index fc3a89e..882c2db 100755
--- a/scripts/construct_context.py
+++ b/scripts/construct_context.py
@@ -16,8 +16,6 @@
 #
 """A tool for constructing class loader context."""
 
-from __future__ import print_function
-
 import argparse
 import json
 import sys
diff --git a/scripts/diff_build_graphs.sh b/scripts/diff_build_graphs.sh
deleted file mode 100755
index 8d01124..0000000
--- a/scripts/diff_build_graphs.sh
+++ /dev/null
@@ -1,170 +0,0 @@
-#!/bin/bash -eu
-#
-# Copyright 2017 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.
-
-set -e
-
-# This file makes it easy to confirm that a set of changes in source code don't result in any
-# changes to the generated ninja files. This is to reduce the effort required to be confident
-# in the correctness of refactorings
-
-function die() {
-  echo "$@" >&2
-  exit 1
-}
-
-function usage() {
-  violation="$1"
-  die "$violation
-
-  Usage: diff_build_graphs.sh [--products=product1,product2...] <OLD_VERSIONS> <NEW_VERSIONS>
-
-  This file builds and parses the build files (Android.mk, Android.bp, etc) for each requested
-  product and for both sets of versions, and checks whether the ninja files (which implement
-  the build graph) changed between the two versions.
-
-  Example: diff_build_graphs.sh 'build/soong:work^ build/blueprint:work^' 'build/soong:work build/blueprint:work'
-
-  Options:
-    --products=PRODUCTS  comma-separated list of products to check"
-}
-
-PRODUCTS_ARG=""
-OLD_VERSIONS=""
-NEW_VERSIONS=""
-function parse_args() {
-  # parse optional arguments
-  while true; do
-    arg="${1-}"
-    case "$arg" in
-      --products=*) PRODUCTS_ARG="$arg";;
-      *) break;;
-    esac
-    shift
-  done
-  # parse required arguments
-  if [ "$#" != "2" ]; then
-    usage ""
-  fi
-  #argument validation
-  OLD_VERSIONS="$1"
-  NEW_VERSIONS="$2"
-
-}
-parse_args "$@"
-
-
-# find some file paths
-cd "$(dirname $0)"
-SCRIPT_DIR="$PWD"
-cd ../../..
-CHECKOUT_ROOT="$PWD"
-OUT_DIR="${OUT_DIR-}"
-if [ -z "$OUT_DIR" ]; then
-  OUT_DIR=out
-fi
-WORK_DIR="$OUT_DIR/diff"
-OUT_DIR_OLD="$WORK_DIR/out_old"
-OUT_DIR_NEW="$WORK_DIR/out_new"
-OUT_DIR_TEMP="$WORK_DIR/out_temp"
-
-
-function checkout() {
-  versionSpecs="$1"
-  for versionSpec in $versionSpecs; do
-    project="$(echo $versionSpec | sed 's|\([^:]*\):\([^:]*\)|\1|')"
-    ref="$(echo     $versionSpec | sed 's|\([^:]*\):\([^:]*\)|\2|')"
-    echo "checking out ref $ref in project $project"
-    git -C "$project" checkout "$ref"
-  done
-}
-
-function run_build() {
-  echo
-  echo "Starting build"
-  # rebuild multiproduct_kati, in case it was missing before,
-  # or in case it is affected by some of the changes we're testing
-  make blueprint_tools
-  # find multiproduct_kati and have it build the ninja files for each product
-  builder="$(echo $OUT_DIR/host/*/bin/multiproduct_kati)"
-  BUILD_NUMBER=sample "$builder" $PRODUCTS_ARG --keep --out "$OUT_DIR_TEMP" || true
-  echo
-}
-
-function diffProduct() {
-  product="$1"
-
-  zip1="$OUT_DIR_OLD/${product}.zip"
-  unzipped1="$OUT_DIR_OLD/$product"
-
-  zip2="$OUT_DIR_NEW/${product}.zip"
-  unzipped2="$OUT_DIR_NEW/$product"
-
-  unzip -qq "$zip1" -d "$unzipped1"
-  unzip -qq "$zip2" -d "$unzipped2"
-
-  #do a diff of the ninja files
-  diffFile="$WORK_DIR/diff.txt"
-  diff -r "$unzipped1" "$unzipped2" -x build_date.txt -x build_number.txt -x '\.*' -x '*.log' -x build_fingerprint.txt -x build.ninja.d -x '*.zip' > $diffFile || true
-  if [[ -s "$diffFile" ]]; then
-    # outputs are different, so remove the unzipped versions but keep the zipped versions
-    echo "First few differences (total diff linecount=$(wc -l $diffFile)) for product $product:"
-    cat "$diffFile" | head -n 10
-    echo "End of differences for product $product"
-    rm -rf "$unzipped1" "$unzipped2"
-  else
-    # outputs are the same, so remove all of the outputs
-    rm -rf "$zip1" "$unzipped1" "$zip2" "$unzipped2"
-  fi
-}
-
-function do_builds() {
-  #reset work dir
-  rm -rf "$WORK_DIR"
-  mkdir "$WORK_DIR"
-
-  #build new code
-  checkout "$NEW_VERSIONS"
-  run_build
-  mv "$OUT_DIR_TEMP" "$OUT_DIR_NEW"
-
-  #build old code
-  #TODO do we want to cache old results? Maybe by the time we care to cache old results this will
-  #be running on a remote server somewhere and be completely different
-  checkout "$OLD_VERSIONS"
-  run_build
-  mv "$OUT_DIR_TEMP" "$OUT_DIR_OLD"
-
-  #cleanup
-  echo created "$OUT_DIR_OLD" and "$OUT_DIR_NEW"
-}
-
-function main() {
-  do_builds
-  checkout "$NEW_VERSIONS"
-
-  #find all products
-  productsFile="$WORK_DIR/all_products.txt"
-  find $OUT_DIR_OLD $OUT_DIR_NEW -mindepth 1 -maxdepth 1 -name "*.zip" | sed "s|^$OUT_DIR_OLD/||" | sed "s|^$OUT_DIR_NEW/||" | sed "s|\.zip$||" | sort | uniq > "$productsFile"
-  echo Diffing products
-  for product in $(cat $productsFile); do
-    diffProduct "$product"
-  done
-  echo Done diffing products
-  echo "Any differing outputs can be seen at $OUT_DIR_OLD/*.zip and $OUT_DIR_NEW/*.zip"
-  echo "See $WORK_DIR/diff.txt for the full list of differences for the latest product checked"
-}
-
-main
diff --git a/scripts/gen_build_prop.py b/scripts/gen_build_prop.py
index c08a3fd..74befd5 100644
--- a/scripts/gen_build_prop.py
+++ b/scripts/gen_build_prop.py
@@ -108,7 +108,7 @@
 
 def generate_common_build_props(args):
   print("####################################")
-  print("# from generate_common_build_props")
+  print("# from generate-common-build-props")
   print("# These properties identify this partition image.")
   print("####################################")
 
@@ -243,9 +243,15 @@
   print(f"# end build properties")
 
 def write_properties_from_file(file):
+  # Make and Soong use different intermediate files to build vendor/build.prop.
+  # Although the sysprop contents are same, the absolute paths of these
+  # intermediate files are different.
+  # Print the filename for the intermediate files (files in OUT_DIR).
+  # This helps with validating mk->soong migration of android partitions.
+  filename = os.path.basename(file.name) if file.name.startswith(os.environ.get("OUT_DIR")) else file.name
   print()
   print("####################################")
-  print(f"# from {file.name}")
+  print(f"# from {filename}")
   print("####################################")
   print(file.read(), end="")
 
@@ -302,7 +308,15 @@
     props.append(f"ro.sanitize.{sanitize_target}=true")
 
   # Sets the default value of ro.postinstall.fstab.prefix to /system.
-  # Device board config should override the value to /product when needed by:
+  #
+  # Device board configs can override this to /product to use a
+  # product-specific fstab.postinstall file (installed to
+  # /product/etc/fstab.postinstall). If not overridden, the
+  # system/extras/cppreopts/fstab.postinstall file (installed to
+  # /system/etc/fstab.postinstall) will be used.
+  # Note: The default fstab.postinstall is generic and may be slower
+  # because it tries different mount options line by line to ensure
+  # compatibility across various devices.
   #
   #     PRODUCT_PRODUCT_PROPERTIES += ro.postinstall.fstab.prefix=/product
   #
@@ -355,9 +369,6 @@
       props = list(filter(lambda x: not x.startswith("ro.setupwizard.mode="), props))
       props.append("ro.setupwizard.mode=OPTIONAL")
 
-    if not config["SdkBuild"]:
-      # To speedup startup of non-preopted builds, don't verify or compile the boot image.
-      props.append("dalvik.vm.image-dex2oat-filter=extract")
     # b/323566535
     props.append("init.svc_debug.no_fatal.zygote=true")
 
@@ -429,7 +440,9 @@
   # Build system set BOARD_API_LEVEL to show the api level of the vendor API surface.
   # This must not be altered outside of build system.
   if config["VendorApiLevel"]:
-    props.append(f"ro.board.api_level={config['VendorApiLevel']}")
+    props.append(f"ro.board.api_level?={config['VendorApiLevel']}")
+    if config["VendorApiLevelPropOverride"]:
+      props.append(f"ro.board.api_level={config['VendorApiLevelPropOverride']}")
 
   # RELEASE_BOARD_API_LEVEL_FROZEN is true when the vendor API surface is frozen.
   if build_flags["RELEASE_BOARD_API_LEVEL_FROZEN"]:
@@ -448,7 +461,7 @@
   props.append(f"ro.vendor.build.security_patch={config['VendorSecurityPatch']}")
   props.append(f"ro.product.board={config['BootloaderBoardName']}")
   props.append(f"ro.board.platform={config['BoardPlatform']}")
-  props.append(f"ro.hwui.use_vulkan={'true' if config['UsesVulkan'] else 'false'}")
+  props.append(f"ro.hwui.use_vulkan={config['UsesVulkan']}")
 
   if config["ScreenDensity"]:
     props.append(f"ro.sf.lcd_density={config['ScreenDensity']}")
@@ -522,7 +535,6 @@
 
   build_prop(args, gen_build_info=False, gen_common_build_props=True, variables=variables)
 
-'''
 def build_vendor_prop(args):
   config = args.config
 
@@ -539,7 +551,6 @@
     ]
 
   build_prop(args, gen_build_info=False, gen_common_build_props=True, variables=variables)
-'''
 
 def build_product_prop(args):
   config = args.config
@@ -606,8 +617,10 @@
         build_odm_prop(args)
       case "product":
         build_product_prop(args)
-      # case "vendor":  # NOT IMPLEMENTED
-      #  build_vendor_prop(args)
+      case "vendor":
+        build_vendor_prop(args)
+      case "system_dlkm" | "vendor_dlkm" | "odm_dlkm" | "bootimage":
+        build_prop(args, gen_build_info=False, gen_common_build_props=True, variables=[])
       case _:
         sys.exit(f"not supported partition {args.partition}")
 
diff --git a/scripts/hiddenapi/Android.bp b/scripts/hiddenapi/Android.bp
index 43edf44..061af19 100644
--- a/scripts/hiddenapi/Android.bp
+++ b/scripts/hiddenapi/Android.bp
@@ -27,6 +27,12 @@
     libs: [
         "signature_trie",
     ],
+    target: {
+        windows: {
+            // go modules (bpmodify) don't support windows
+            enabled: false,
+        },
+    },
 }
 
 python_test_host {
@@ -44,6 +50,12 @@
     test_options: {
         unit_test: true,
     },
+    target: {
+        windows: {
+            // go modules (bpmodify) don't support windows
+            enabled: false,
+        },
+    },
 }
 
 python_binary_host {
diff --git a/scripts/keep-flagged-apis.sh b/scripts/keep-flagged-apis.sh
index 9c48fdb..48efb7a 100755
--- a/scripts/keep-flagged-apis.sh
+++ b/scripts/keep-flagged-apis.sh
@@ -25,21 +25,12 @@
 # Convert the list of feature flags in the input file to Metalava options
 # of the form `--revert-annotation !android.annotation.FlaggedApi("<flag>")`
 # to prevent the annotated APIs from being hidden, i.e. include the annotated
-# APIs in the SDK snapshots. This also preserves the line comments, they will
-# be ignored by Metalava but might be useful when debugging.
+# APIs in the SDK snapshots.
 while read -r line; do
-  key=$(echo "$line" | cut -d= -f1)
-  value=$(echo "$line" | cut -d= -f2)
-
-  # Skip if value is not true and line does not start with '#'
-  if [[ ( $value != "true" ) && ( $line =~ ^[^#] )]]; then
-    continue
-  fi
-
   # Escape and quote the key for sed
-  escaped_key=$(echo "$key" | sed "s/'/\\\'/g; s/ /\\ /g")
+  escaped_line=$(echo "$line" | sed "s/'/\\\'/g; s/ /\\ /g")
 
-  echo $line | sed "s|^[^#].*$|--revert-annotation '!$FLAGGED(\"$escaped_key\")'|"
+  echo "--revert-annotation '!$FLAGGED(\"$escaped_line\")'"
 done < "$FLAGS"
 
 # Revert all flagged APIs, unless listed above.
diff --git a/scripts/manifest.py b/scripts/manifest.py
index 32603e8..87f4f0c 100755
--- a/scripts/manifest.py
+++ b/scripts/manifest.py
@@ -16,7 +16,6 @@
 #
 """A tool for inserting values from the build system into a manifest or a test config."""
 
-from __future__ import print_function
 from xml.dom import minidom
 
 
diff --git a/scripts/manifest_check.py b/scripts/manifest_check.py
index 1e32d1d..175451e 100755
--- a/scripts/manifest_check.py
+++ b/scripts/manifest_check.py
@@ -16,8 +16,6 @@
 #
 """A tool for checking that a manifest agrees with the build system."""
 
-from __future__ import print_function
-
 import argparse
 import json
 import re
diff --git a/scripts/manifest_fixer.py b/scripts/manifest_fixer.py
index 9847ad5..ad3b313 100755
--- a/scripts/manifest_fixer.py
+++ b/scripts/manifest_fixer.py
@@ -16,8 +16,6 @@
 #
 """A tool for inserting values from the build system into a manifest."""
 
-from __future__ import print_function
-
 import argparse
 import sys
 from xml.dom import minidom
diff --git a/scripts/merge_directories.py b/scripts/merge_directories.py
deleted file mode 100755
index 3f8631b..0000000
--- a/scripts/merge_directories.py
+++ /dev/null
@@ -1,60 +0,0 @@
-#!/usr/bin/env python3
-import argparse
-import os
-import shutil
-import sys
-
-def main():
-    parser = argparse.ArgumentParser(
-        description="Given a list of directories, this script will copy the contents of all of "
-        "them into the first directory, erroring out if any duplicate files are found."
-    )
-    parser.add_argument(
-        "--ignore-duplicates",
-        action="store_true",
-        help="Don't error out on duplicate files, just skip them. The file from the earliest "
-        "directory listed on the command line will be the winner."
-    )
-    parser.add_argument(
-        "--file-list",
-        help="Path to a text file containing paths relative to in_dir. Only these paths will be "
-        "copied out of in_dir."
-    )
-    parser.add_argument("out_dir")
-    parser.add_argument("in_dir")
-    args = parser.parse_args()
-
-    if not os.path.isdir(args.out_dir):
-        sys.exit(f"error: {args.out_dir} must be a directory")
-    if not os.path.isdir(args.in_dir):
-        sys.exit(f"error: {args.in_dir} must be a directory")
-
-    file_list = None
-    if args.file_list:
-        with open(file_list_file, "r") as f:
-            file_list = f.read().strip().splitlines()
-
-    in_dir = args.in_dir
-    for root, dirs, files in os.walk(in_dir):
-        rel_root = os.path.relpath(root, in_dir)
-        dst_root = os.path.join(args.out_dir, rel_root)
-        made_parent_dirs = False
-        for f in files:
-            src = os.path.join(root, f)
-            dst = os.path.join(dst_root, f)
-            p = os.path.normpath(os.path.join(rel_root, f))
-            if file_list is not None and p not in file_list:
-                continue
-            if os.path.lexists(dst):
-                if args.ignore_duplicates:
-                    continue
-                sys.exit(f"error: {p} exists in both {args.out_dir} and {in_dir}")
-
-            if not made_parent_dirs:
-                os.makedirs(dst_root, exist_ok=True)
-                made_parent_dirs = True
-
-            shutil.copy2(src, dst, follow_symlinks=False)
-
-if __name__ == "__main__":
-    main()
diff --git a/scripts/modify_permissions_allowlist.py b/scripts/modify_permissions_allowlist.py
index 38ec7ec..4a0ca8f 100755
--- a/scripts/modify_permissions_allowlist.py
+++ b/scripts/modify_permissions_allowlist.py
@@ -16,8 +16,6 @@
 #
 """A tool for modifying privileged permission allowlists."""
 
-from __future__ import print_function
-
 import argparse
 import sys
 from xml.dom import minidom
diff --git a/scripts/modify_permissions_allowlist_test.py b/scripts/modify_permissions_allowlist_test.py
index ee8b12c..577388f 100755
--- a/scripts/modify_permissions_allowlist_test.py
+++ b/scripts/modify_permissions_allowlist_test.py
@@ -16,8 +16,6 @@
 #
 """Unit tests for modify_permissions_allowlist.py."""
 
-from __future__ import print_function
-
 import unittest
 
 from xml.dom import minidom
diff --git a/scripts/ninja_determinism_test.py b/scripts/ninja_determinism_test.py
new file mode 100755
index 0000000..e207b96
--- /dev/null
+++ b/scripts/ninja_determinism_test.py
@@ -0,0 +1,210 @@
+#!/usr/bin/env python3
+
+import asyncio
+import argparse
+import dataclasses
+import hashlib
+import os
+import re
+import socket
+import subprocess
+import sys
+import zipfile
+
+from typing import List
+
+def get_top() -> str:
+  path = '.'
+  while not os.path.isfile(os.path.join(path, 'build/soong/soong_ui.bash')):
+    if os.path.abspath(path) == '/':
+      sys.exit('Could not find android source tree root.')
+    path = os.path.join(path, '..')
+  return os.path.abspath(path)
+
+
+_PRODUCT_REGEX = re.compile(r'([a-zA-Z_][a-zA-Z0-9_]*)(?:(?:-([a-zA-Z_][a-zA-Z0-9_]*))?-(user|userdebug|eng))?')
+
+
+@dataclasses.dataclass(frozen=True)
+class Product:
+  """Represents a TARGET_PRODUCT and TARGET_BUILD_VARIANT."""
+  product: str
+  release: str
+  variant: str
+
+  def __post_init__(self):
+    if not _PRODUCT_REGEX.match(str(self)):
+      raise ValueError(f'Invalid product name: {self}')
+
+  def __str__(self):
+    return self.product + '-' + self.release + '-' + self.variant
+
+
+async def run_make_nothing(product: Product, out_dir: str) -> bool:
+  """Runs a build and returns if it succeeded or not."""
+  with open(os.path.join(out_dir, 'build.log'), 'wb') as f:
+    result = await asyncio.create_subprocess_exec(
+        'prebuilts/build-tools/linux-x86/bin/nsjail',
+        '-q',
+        '--cwd',
+        os.getcwd(),
+        '-e',
+        '-B',
+        '/',
+        '-B',
+        f'{os.path.abspath(out_dir)}:{os.path.join(os.getcwd(), "out")}',
+        '--time_limit',
+        '0',
+        '--skip_setsid',
+        '--keep_caps',
+        '--disable_clone_newcgroup',
+        '--disable_clone_newnet',
+        '--rlimit_as',
+        'soft',
+        '--rlimit_core',
+        'soft',
+        '--rlimit_cpu',
+        'soft',
+        '--rlimit_fsize',
+        'soft',
+        '--rlimit_nofile',
+        'soft',
+        '--proc_rw',
+        '--hostname',
+        socket.gethostname(),
+        '--',
+        'build/soong/soong_ui.bash',
+        '--make-mode',
+        f'TARGET_PRODUCT={product.product}',
+        f'TARGET_RELEASE={product.release}',
+        f'TARGET_BUILD_VARIANT={product.variant}',
+        '--skip-ninja',
+        'nothing', stdout=f, stderr=subprocess.STDOUT)
+    return await result.wait() == 0
+
+SUBNINJA_OR_INCLUDE_REGEX = re.compile(rb'\n(?:include|subninja) ')
+
+def find_subninjas_and_includes(contents) -> List[str]:
+  results = []
+  def get_path_from_directive(i):
+    j = contents.find(b'\n', i)
+    if j < 0:
+      path_bytes = contents[i:]
+    else:
+      path_bytes = contents[i:j]
+    path_bytes = path_bytes.removesuffix(b'\r')
+    path = path_bytes.decode()
+    if '$' in path:
+      sys.exit('includes/subninjas with variables are unsupported: '+path)
+    return path
+
+  if contents.startswith(b"include "):
+    results.append(get_path_from_directive(len(b"include ")))
+  elif contents.startswith(b"subninja "):
+    results.append(get_path_from_directive(len(b"subninja ")))
+
+  for match in SUBNINJA_OR_INCLUDE_REGEX.finditer(contents):
+    results.append(get_path_from_directive(match.end()))
+
+  return results
+
+
+def transitively_included_ninja_files(out_dir: str, ninja_file: str, seen):
+  with open(ninja_file, 'rb') as f:
+    contents = f.read()
+
+  results = [ninja_file]
+  seen[ninja_file] = True
+  sub_files = find_subninjas_and_includes(contents)
+  for sub_file in sub_files:
+    sub_file = os.path.join(out_dir, sub_file.removeprefix('out/'))
+    if sub_file not in seen:
+      results.extend(transitively_included_ninja_files(out_dir, sub_file, seen))
+
+  return results
+
+
+def hash_ninja_file(out_dir: str, ninja_file: str, hasher):
+  with open(ninja_file, 'rb') as f:
+    contents = f.read()
+
+  sub_files = find_subninjas_and_includes(contents)
+
+  hasher.update(contents)
+
+  for sub_file in sub_files:
+    hash_ninja_file(out_dir, os.path.join(out_dir, sub_file.removeprefix('out/')), hasher)
+
+
+def hash_files(files: List[str]) -> str:
+  hasher = hashlib.md5()
+  for file in files:
+    with open(file, 'rb') as f:
+      hasher.update(f.read())
+  return hasher.hexdigest()
+
+
+def dist_ninja_files(out_dir: str, zip_name: str, ninja_files: List[str]):
+  dist_dir = os.getenv('DIST_DIR', os.path.join(os.getenv('OUT_DIR', 'out'), 'dist'))
+  os.makedirs(dist_dir, exist_ok=True)
+
+  with open(os.path.join(dist_dir, zip_name), 'wb') as f:
+    with zipfile.ZipFile(f, mode='w') as zf:
+      for ninja_file in ninja_files:
+        zf.write(ninja_file, arcname=os.path.basename(out_dir)+'/out/' + os.path.relpath(ninja_file, out_dir))
+
+
+async def main():
+    parser = argparse.ArgumentParser()
+    args = parser.parse_args()
+
+    os.chdir(get_top())
+    subprocess.check_call(['touch', 'build/soong/Android.bp'])
+
+    product = Product(
+      'aosp_cf_x86_64_phone',
+      'trunk_staging',
+      'userdebug',
+    )
+    os.environ['TARGET_PRODUCT'] = 'aosp_cf_x86_64_phone'
+    os.environ['TARGET_RELEASE'] = 'trunk_staging'
+    os.environ['TARGET_BUILD_VARIANT'] = 'userdebug'
+
+    out_dir1 = os.path.join(os.getenv('OUT_DIR', 'out'), 'determinism_test_out1')
+    out_dir2 = os.path.join(os.getenv('OUT_DIR', 'out'), 'determinism_test_out2')
+
+    os.makedirs(out_dir1, exist_ok=True)
+    os.makedirs(out_dir2, exist_ok=True)
+
+    success1, success2 = await asyncio.gather(
+      run_make_nothing(product, out_dir1),
+      run_make_nothing(product, out_dir2))
+
+    if not success1:
+      with open(os.path.join(out_dir1, 'build.log'), 'r') as f:
+        print(f.read(), file=sys.stderr)
+      sys.exit('build failed')
+    if not success2:
+      with open(os.path.join(out_dir2, 'build.log'), 'r') as f:
+        print(f.read(), file=sys.stderr)
+      sys.exit('build failed')
+
+    ninja_files1 = transitively_included_ninja_files(out_dir1, os.path.join(out_dir1, f'combined-{product.product}.ninja'), {})
+    ninja_files2 = transitively_included_ninja_files(out_dir2, os.path.join(out_dir2, f'combined-{product.product}.ninja'), {})
+
+    dist_ninja_files(out_dir1, 'determinism_test_files_1.zip', ninja_files1)
+    dist_ninja_files(out_dir2, 'determinism_test_files_2.zip', ninja_files2)
+
+    hash1 = hash_files(ninja_files1)
+    hash2 = hash_files(ninja_files2)
+
+    if hash1 != hash2:
+        sys.exit("ninja files were not deterministic! See disted determinism_test_files_1/2.zip")
+
+    print("Success, ninja files were deterministic")
+
+
+if __name__ == "__main__":
+    asyncio.run(main())
+
+
diff --git a/scripts/run-soong-tests-with-go-tools.sh b/scripts/run-soong-tests-with-go-tools.sh
index 1fbb1fc..82efaa0 100755
--- a/scripts/run-soong-tests-with-go-tools.sh
+++ b/scripts/run-soong-tests-with-go-tools.sh
@@ -38,6 +38,11 @@
     CLANG_VERSION=$(build/soong/scripts/get_clang_version.py)
     export CC="${TOP}/prebuilts/clang/host/${OS}-x86/${CLANG_VERSION}/bin/clang"
     export CXX="${TOP}/prebuilts/clang/host/${OS}-x86/${CLANG_VERSION}/bin/clang++"
+    glibc_dir="${TOP}/prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.17-4.8"
+    export CGO_CFLAGS="--sysroot ${glibc_dir}/sysroot/"
+    export CGO_CPPFLAGS="--sysroot ${glibc_dir}/sysroot/"
+    export CGO_CXXFLAGS="--sysroot ${glibc_dir}/sysroot/"
+    export CGO_LDFLAGS="--sysroot ${glibc_dir}/sysroot/ -B ${glibc_dir}/lib/gcc/x86_64-linux/4.8.3 -L ${glibc_dir}/lib/gcc/x86_64-linux/4.8.3 -L ${glibc_dir}/x86_64-linux/lib64"
 fi
 
 # androidmk_test.go gets confused if ANDROID_BUILD_TOP is set.
diff --git a/scripts/rustc_linker.py b/scripts/rustc_linker.py
new file mode 100755
index 0000000..3f60708
--- /dev/null
+++ b/scripts/rustc_linker.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python3
+#
+# 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.
+#
+
+"""
+This script is used as a replacement for the Rust linker to allow fine-grained
+control over the what gets emitted to the linker.
+"""
+
+import os
+import shutil
+import subprocess
+import sys
+import argparse
+
+replacementVersionScript = None
+
+argparser = argparse.ArgumentParser()
+argparser.add_argument('--android-clang-bin', required=True)
+args = argparser.parse_known_args()
+clang_args = [args[0].android_clang_bin] + args[1]
+
+for i, arg in enumerate(clang_args):
+   if arg.startswith('-Wl,--android-version-script='):
+      replacementVersionScript = arg.split("=")[1]
+      del clang_args[i]
+      break
+
+if replacementVersionScript:
+   versionScriptFound = False
+   for i, arg in enumerate(clang_args):
+      if arg.startswith('-Wl,--version-script='):
+         clang_args[i] ='-Wl,--version-script=' + replacementVersionScript
+         versionScriptFound = True
+         break
+
+   if not versionScriptFound:
+       # If rustc did not emit a version script, just append the arg
+       clang_args.append('-Wl,--version-script=' + replacementVersionScript)
+try:
+   subprocess.run(clang_args, encoding='utf-8', check=True)
+except subprocess.CalledProcessError as e:
+   sys.exit(-1)
+
diff --git a/scripts/test_config_fixer.py b/scripts/test_config_fixer.py
index 2876bcb..91a83f2 100644
--- a/scripts/test_config_fixer.py
+++ b/scripts/test_config_fixer.py
@@ -16,8 +16,6 @@
 #
 """A tool for modifying values in a test config."""
 
-from __future__ import print_function
-
 import argparse
 import json
 import sys
diff --git a/scripts/text_file_processor.py b/scripts/text_file_processor.py
new file mode 100755
index 0000000..10186ce
--- /dev/null
+++ b/scripts/text_file_processor.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+#
+# 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.
+
+import argparse
+import re
+
+def main():
+    parser = argparse.ArgumentParser(description='This script looks for '
+        '`{CONTENTS_OF:path/to/file}` markers in the input file and replaces them with the actual '
+        'contents of that file, with leading/trailing whitespace stripped. The idea is that this '
+        'script could be extended to support more types of markers in the future.')
+    parser.add_argument('input')
+    parser.add_argument('output')
+    args = parser.parse_args()
+
+    with open(args.input, 'r') as f:
+        contents = f.read()
+
+    i = 0
+    replacedContents = ''
+    for m in re.finditer(r'{CONTENTS_OF:([a-zA-Z0-9 _/.-]+)}', contents):
+        replacedContents += contents[i:m.start()]
+        with open(m.group(1), 'r') as f:
+            replacedContents += f.read().strip()
+        i = m.end()
+    replacedContents += contents[i:]
+
+    with open(args.output, 'w') as f:
+        f.write(replacedContents)
+
+
+if __name__ == '__main__':
+    main()
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/bootclasspath_fragment_sdk_test.go b/sdk/bootclasspath_fragment_sdk_test.go
index 34e11f0..80ced00 100644
--- a/sdk/bootclasspath_fragment_sdk_test.go
+++ b/sdk/bootclasspath_fragment_sdk_test.go
@@ -66,6 +66,7 @@
 				exported_bootclasspath_fragments: [
 					"%s",
 				],
+				prefer: false,
 			}
 		`, apex, apexFile, fragment)),
 		android.FixtureAddFile(filepath.Join(dir, apexFile), nil),
@@ -73,6 +74,7 @@
 }
 
 func TestSnapshotWithBootclasspathFragment_ImageName(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
 		java.PrepareForTestWithDexpreopt,
@@ -225,8 +227,8 @@
 			checkBootJarsPackageCheckRule(t, result,
 				append(
 					[]string{
-						"out/soong/.intermediates/prebuilts/apex/com.android.art/android_common_com.android.art/deapexer/javalib/core1.jar",
-						"out/soong/.intermediates/prebuilts/apex/com.android.art/android_common_com.android.art/deapexer/javalib/core2.jar",
+						"out/soong/.intermediates/prebuilts/apex/com.android.art/android_common_prebuilt_com.android.art/deapexer/javalib/core1.jar",
+						"out/soong/.intermediates/prebuilts/apex/com.android.art/android_common_prebuilt_com.android.art/deapexer/javalib/core2.jar",
 						"out/soong/.intermediates/default/java/framework/android_common/aligned/framework.jar",
 					},
 					java.ApexBootJarDexJarPaths...,
@@ -270,7 +272,7 @@
 // package check rule.
 func checkBootJarsPackageCheckRule(t *testing.T, result *android.TestResult, expectedModules ...string) {
 	t.Helper()
-	platformBcp := result.ModuleForTests("platform-bootclasspath", "android_common")
+	platformBcp := result.ModuleForTests(t, "platform-bootclasspath", "android_common")
 	bootJarsCheckRule := platformBcp.Rule("boot_jars_package_check")
 	command := bootJarsCheckRule.RuleParams.Command
 	expectedCommandArgs := " build/soong/scripts/check_boot_jars/package_allowed_list.txt " + strings.Join(expectedModules, " ") + " &&"
@@ -479,7 +481,7 @@
 		checkAllCopyRules(copyRules),
 		snapshotTestPreparer(checkSnapshotWithoutSource, preparerForSnapshot),
 		snapshotTestChecker(checkSnapshotWithoutSource, func(t *testing.T, result *android.TestResult) {
-			module := result.ModuleForTests("platform-bootclasspath", "android_common")
+			module := result.ModuleForTests(t, "platform-bootclasspath", "android_common")
 			var rule android.TestingBuildParams
 			rule = module.Output("out/soong/hiddenapi/hiddenapi-flags.csv")
 			java.CheckHiddenAPIRuleInputs(t, "monolithic flags", `
@@ -505,7 +507,7 @@
 		}),
 		snapshotTestPreparer(checkSnapshotWithSourcePreferred, preparerForSnapshot),
 		snapshotTestChecker(checkSnapshotWithSourcePreferred, func(t *testing.T, result *android.TestResult) {
-			module := result.ModuleForTests("platform-bootclasspath", "android_common")
+			module := result.ModuleForTests(t, "platform-bootclasspath", "android_common")
 			rule := module.Output("out/soong/hiddenapi/hiddenapi-flags.csv.valid")
 			android.AssertStringDoesContain(t, "verify-overlaps", rule.RuleParams.Command, " out/soong/.intermediates/mybootclasspathfragment/android_common_myapex/modular-hiddenapi/filtered-flags.csv:out/soong/.intermediates/mybootclasspathfragment/android_common_myapex/modular-hiddenapi/signature-patterns.csv ")
 		}),
@@ -514,7 +516,9 @@
 }
 
 func TestSnapshotWithBootClasspathFragment_Contents(t *testing.T) {
+	t.Parallel()
 	t.Run("added-directly", func(t *testing.T) {
+		t.Parallel()
 		testSnapshotWithBootClasspathFragment_Contents(t, `
 			sdk {
 				name: "mysdk",
@@ -566,6 +570,7 @@
 .intermediates/mycoreplatform.stubs.source/android_common/exportable/mycoreplatform.stubs.source_removed.txt -> sdk_library/public/mycoreplatform-removed.txt
 `
 	t.Run("added-via-apex", func(t *testing.T) {
+		t.Parallel()
 		testSnapshotWithBootClasspathFragment_Contents(t, `
 			sdk {
 				name: "mysdk",
@@ -575,6 +580,7 @@
 	})
 
 	t.Run("added-directly-and-indirectly", func(t *testing.T) {
+		t.Parallel()
 		testSnapshotWithBootClasspathFragment_Contents(t, `
 			sdk {
 				name: "mysdk",
@@ -599,6 +605,7 @@
 // TestSnapshotWithBootClasspathFragment_Fragments makes sure that the fragments property of a
 // bootclasspath_fragment is correctly output to the sdk snapshot.
 func TestSnapshotWithBootClasspathFragment_Fragments(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
 		java.PrepareForTestWithJavaDefaultModules,
@@ -734,9 +741,11 @@
 
 // Test that bootclasspath_fragment works with sdk.
 func TestBasicSdkWithBootclasspathFragment(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		prepareForSdkTestWithApex,
 		prepareForSdkTestWithJava,
+		java.PrepareForTestWithJavaDefaultModules,
 		android.FixtureMergeMockFs(android.MockFS{
 			"java/mybootlib.jar":                nil,
 			"hiddenapi/annotation-flags.csv":    nil,
@@ -752,6 +761,13 @@
 			bootclasspath_fragments: ["mybootclasspathfragment"],
 		}
 
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			min_sdk_version: "1",
+			bootclasspath_fragments: ["mybootclasspathfragment"],
+		}
+
 		bootclasspath_fragment {
 			name: "mybootclasspathfragment",
 			image_name: "art",
@@ -794,7 +810,7 @@
 		java_import {
 			name: "mybootlib",
 			visibility: ["//visibility:public"],
-			apex_available: ["com.android.art"],
+			apex_available: ["myapex"],
 			jars: ["java/mybootlib.jar"],
 		}
 	`),
@@ -802,6 +818,7 @@
 }
 
 func TestSnapshotWithBootclasspathFragment_HiddenAPI(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
 		java.PrepareForTestWithJavaDefaultModules,
@@ -1116,7 +1133,7 @@
 		`),
 	).RunTest(t)
 
-	bcpf := result.ModuleForTests("mybootclasspathfragment", "android_common")
+	bcpf := result.ModuleForTests(t, "mybootclasspathfragment", "android_common")
 	rule := bcpf.Output("out/soong/.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi" + suffix + "/stub-flags.csv")
 	android.AssertPathsRelativeToTopEquals(t, "stub flags inputs", android.SortedUniqueStrings(expectedStubFlagsInputs), android.SortedUniquePaths(rule.Implicits))
 
@@ -1127,7 +1144,9 @@
 }
 
 func TestSnapshotWithBootClasspathFragment_MinSdkVersion(t *testing.T) {
+	t.Parallel()
 	t.Run("target S build", func(t *testing.T) {
+		t.Parallel()
 		expectedSnapshot := `
 // This is auto-generated. DO NOT EDIT.
 
@@ -1184,6 +1203,7 @@
 	})
 
 	t.Run("target-Tiramisu-build", func(t *testing.T) {
+		t.Parallel()
 		expectedSnapshot := `
 // This is auto-generated. DO NOT EDIT.
 
@@ -1268,6 +1288,7 @@
 }
 
 func TestSnapshotWithEmptyBootClasspathFragment(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
 		java.PrepareForTestWithJavaDefaultModules,
diff --git a/sdk/bp_test.go b/sdk/bp_test.go
index c620ac2..d3eaafe 100644
--- a/sdk/bp_test.go
+++ b/sdk/bp_test.go
@@ -73,6 +73,7 @@
 }
 
 func TestAddPropertySimple(t *testing.T) {
+	t.Parallel()
 	set := newPropertySet()
 	for name, val := range map[string]interface{}{
 		"x":   "taxi",
@@ -91,14 +92,17 @@
 }
 
 func TestAddPropertySubset(t *testing.T) {
+	t.Parallel()
 	getFixtureMap := map[string]func() interface{}{
 		"property set":    propertySetFixture,
 		"property struct": propertyStructFixture,
 	}
 
 	t.Run("add new subset", func(t *testing.T) {
+		t.Parallel()
 		for name, getFixture := range getFixtureMap {
 			t.Run(name, func(t *testing.T) {
+				t.Parallel()
 				set := propertySetFixture().(*bpPropertySet)
 				set.AddProperty("new", getFixture())
 				checkPropertySetFixture(t, set, true)
@@ -108,8 +112,10 @@
 	})
 
 	t.Run("merge existing subset", func(t *testing.T) {
+		t.Parallel()
 		for name, getFixture := range getFixtureMap {
 			t.Run(name, func(t *testing.T) {
+				t.Parallel()
 				set := newPropertySet()
 				subset := set.AddPropertySet("sub")
 				subset.AddProperty("flag", false)
@@ -123,12 +129,14 @@
 	})
 
 	t.Run("add conflicting subset", func(t *testing.T) {
+		t.Parallel()
 		set := propertySetFixture().(*bpPropertySet)
 		android.AssertPanicMessageContains(t, "adding x again should panic", `Property "x" already exists in property set`,
 			func() { set.AddProperty("x", propertySetFixture()) })
 	})
 
 	t.Run("add non-pointer struct", func(t *testing.T) {
+		t.Parallel()
 		set := propertySetFixture().(*bpPropertySet)
 		str := propertyStructFixture().(*propertyStruct)
 		android.AssertPanicMessageContains(t, "adding a non-pointer struct should panic", "Value is a struct, not a pointer to one:",
@@ -137,6 +145,7 @@
 }
 
 func TestAddPropertySetNew(t *testing.T) {
+	t.Parallel()
 	set := newPropertySet()
 	subset := set.AddPropertySet("sub")
 	subset.AddProperty("new", "d^^b")
@@ -144,6 +153,7 @@
 }
 
 func TestAddPropertySetExisting(t *testing.T) {
+	t.Parallel()
 	set := propertySetFixture().(*bpPropertySet)
 	subset := set.AddPropertySet("sub")
 	subset.AddProperty("new", "d^^b")
@@ -176,6 +186,7 @@
 }
 
 func TestTransformRemoveProperty(t *testing.T) {
+	t.Parallel()
 	set := newPropertySet()
 	set.AddProperty("name", "name")
 	set.AddProperty("fred", "12")
@@ -188,6 +199,7 @@
 }
 
 func TestTransformRemovePropertySet(t *testing.T) {
+	t.Parallel()
 	set := newPropertySet()
 	set.AddProperty("name", "name")
 	set.AddPropertySet("fred")
diff --git a/sdk/build_release.go b/sdk/build_release.go
index 6bb05a3..3656c98 100644
--- a/sdk/build_release.go
+++ b/sdk/build_release.go
@@ -101,6 +101,8 @@
 	buildReleaseS = initBuildRelease("S")
 	buildReleaseT = initBuildRelease("Tiramisu")
 	buildReleaseU = initBuildRelease("UpsideDownCake")
+	buildReleaseV = initBuildRelease("VanillaIceCream")
+	buildReleaseB = initBuildRelease("Baklava")
 
 	// Add the current build release which is always treated as being more recent than any other
 	// build release, including those added in tests.
diff --git a/sdk/build_release_test.go b/sdk/build_release_test.go
index 5bf57b5..07f99ee 100644
--- a/sdk/build_release_test.go
+++ b/sdk/build_release_test.go
@@ -42,7 +42,7 @@
 		android.AssertDeepEquals(t, "release", (*buildRelease)(nil), release)
 		// Uses a wildcard in the error message to allow for additional build releases to be added to
 		// the supported set without breaking this test.
-		android.FailIfNoMatchingErrors(t, `unknown release "A", expected one of \[S,Tiramisu,UpsideDownCake,F1,F2,current\]`, []error{err})
+		android.FailIfNoMatchingErrors(t, `unknown release "A", expected one of \[S,Tiramisu,UpsideDownCake,VanillaIceCream,Baklava,F1,F2,current\]`, []error{err})
 	})
 }
 
@@ -60,7 +60,7 @@
 	t.Run("closed range", func(t *testing.T) {
 		set, err := parseBuildReleaseSet("S-F1")
 		android.AssertDeepEquals(t, "errors", nil, err)
-		android.AssertStringEquals(t, "set", "[S,Tiramisu,UpsideDownCake,F1]", set.String())
+		android.AssertStringEquals(t, "set", "[S,Tiramisu,UpsideDownCake,VanillaIceCream,Baklava,F1]", set.String())
 	})
 	invalidAReleaseMessage := `unknown release "A", expected one of ` + allBuildReleaseSet.String()
 	t.Run("invalid release", func(t *testing.T) {
diff --git a/sdk/cc_sdk_test.go b/sdk/cc_sdk_test.go
index 25839b8..bf4ac13 100644
--- a/sdk/cc_sdk_test.go
+++ b/sdk/cc_sdk_test.go
@@ -58,6 +58,7 @@
 // Contains tests for SDK members provided by the cc package.
 
 func TestSingleDeviceOsAssumption(t *testing.T) {
+	t.Parallel()
 	// Mock a module with DeviceSupported() == true.
 	s := &sdk{}
 	android.InitAndroidArchModule(s, android.DeviceSupported, android.MultilibCommon)
@@ -72,6 +73,7 @@
 }
 
 func TestSdkIsCompileMultilibBoth(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -102,6 +104,7 @@
 }
 
 func TestSdkCompileMultilibOverride(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -161,6 +164,7 @@
 
 // Make sure the sdk can use host specific cc libraries static/shared and both.
 func TestHostSdkWithCc(t *testing.T) {
+	t.Parallel()
 	testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -184,6 +188,7 @@
 
 // Make sure the sdk can use cc libraries static/shared and both.
 func TestSdkWithCc(t *testing.T) {
+	t.Parallel()
 	testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -214,6 +219,7 @@
 }
 
 func TestSnapshotWithObject(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -268,6 +274,7 @@
 }
 
 func TestSnapshotWithCcDuplicateHeaders(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -305,6 +312,7 @@
 }
 
 func TestSnapshotWithCcExportGeneratedHeaders(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -393,6 +401,7 @@
 // handling is tested with the sanitize clauses (but note there's a lot of
 // built-in logic in sanitize.go that can affect those flags).
 func TestSnapshotWithCcSharedLibraryCommonProperties(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -475,6 +484,7 @@
 }
 
 func TestSnapshotWithCcBinary(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		module_exports {
 			name: "mymodule_exports",
@@ -523,6 +533,7 @@
 }
 
 func TestMultipleHostOsTypesSnapshotWithCcBinary(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		module_exports {
 			name: "myexports",
@@ -604,6 +615,7 @@
 }
 
 func TestSnapshotWithSingleHostOsType(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForSdkTest,
 		ccTestFs.AddToFixture(),
@@ -721,6 +733,7 @@
 // Test that we support the necessary flags for the linker binary, which is
 // special in several ways.
 func TestSnapshotWithCcStaticNocrtBinary(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		module_exports {
 			name: "mymodule_exports",
@@ -785,19 +798,26 @@
 }
 
 func TestSnapshotWithCcSharedLibrary(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
 			native_shared_libs: ["mynativelib"],
 		}
 
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			min_sdk_version: "1",
+		}
+
 		cc_library_shared {
 			name: "mynativelib",
 			srcs: [
 				"Test.cpp",
 				"aidl/foo/bar/Test.aidl",
 			],
-			apex_available: ["apex1", "apex2"],
+			apex_available: ["myapex"],
 			export_include_dirs: ["myinclude"],
 			aidl: {
 				export_aidl_headers: true,
@@ -807,6 +827,18 @@
 	`)
 
 	CheckSnapshot(t, result, "mysdk", "",
+		snapshotTestPreparer(checkSnapshotWithoutSource,
+			android.FixtureMergeMockFs(android.MockFS{
+				"myapex/Android.bp": []byte(`
+				apex {
+					name: "myapex",
+					key: "myapex.key",
+					min_sdk_version: "1",
+				}
+				`),
+				"myapex/apex_manifest.json": nil,
+			}),
+		),
 		checkAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
 
@@ -819,10 +851,7 @@
     name: "mynativelib",
     prefer: false,
     visibility: ["//visibility:public"],
-    apex_available: [
-        "apex1",
-        "apex2",
-    ],
+    apex_available: ["myapex"],
     stl: "none",
     compile_multilib: "both",
     export_include_dirs: ["include/myinclude"],
@@ -856,6 +885,7 @@
 }
 
 func TestSnapshotWithCcSharedLibrarySharedLibs(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -1005,6 +1035,7 @@
 }
 
 func TestHostSnapshotWithCcSharedLibrary(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -1085,6 +1116,7 @@
 }
 
 func TestMultipleHostOsTypesSnapshotWithCcSharedLibrary(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -1168,6 +1200,7 @@
 }
 
 func TestSnapshotWithCcStaticLibrary(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		module_exports {
 			name: "myexports",
@@ -1232,6 +1265,7 @@
 }
 
 func TestHostSnapshotWithCcStaticLibrary(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		module_exports {
 			name: "myexports",
@@ -1307,6 +1341,7 @@
 }
 
 func TestSnapshotWithCcLibrary(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		module_exports {
 			name: "myexports",
@@ -1370,12 +1405,11 @@
 .intermediates/mynativelib/android_arm_armv7-a-neon_static/mynativelib.a -> arm/lib/mynativelib.a
 .intermediates/mynativelib/android_arm_armv7-a-neon_shared/mynativelib.so -> arm/lib/mynativelib.so
 `),
-		// TODO(b/183315522): Remove this and fix the issue.
-		snapshotTestErrorHandler(checkSnapshotPreferredWithSource, android.FixtureExpectsAtLeastOneErrorMatchingPattern(`\Qunrecognized property "arch.arm.shared.export_include_dirs"\E`)),
 	)
 }
 
 func TestSnapshotSameLibraryWithNativeLibsAndNativeSharedLib(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		module_exports {
 			host_supported: true,
@@ -1477,6 +1511,7 @@
 }
 
 func TestSnapshotSameLibraryWithAndroidNativeLibsAndHostNativeSharedLib(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		module_exports {
 			host_supported: true,
@@ -1578,6 +1613,7 @@
 }
 
 func TestSnapshotSameLibraryWithNativeStaticLibsAndNativeSharedLib(t *testing.T) {
+	t.Parallel()
 	testSdkError(t, "Incompatible member types", `
 		module_exports {
 			host_supported: true,
@@ -1609,6 +1645,7 @@
 }
 
 func TestHostSnapshotWithMultiLib64(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		module_exports {
 			name: "myexports",
@@ -1682,6 +1719,7 @@
 }
 
 func TestSnapshotWithCcHeadersLibrary(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -1721,6 +1759,7 @@
 }
 
 func TestSnapshotWithCcHeadersLibraryAndNativeBridgeSupport(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		cc.PrepareForTestWithCcDefaultModules,
 		PrepareForTestWithSdkBuildComponents,
@@ -1778,6 +1817,7 @@
 // module that has different output files for a native bridge target requests the native bridge
 // variants are copied into the sdk snapshot that it reports an error.
 func TestSnapshotWithCcHeadersLibrary_DetectsNativeBridgeSpecificProperties(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		cc.PrepareForTestWithCcDefaultModules,
 		PrepareForTestWithSdkBuildComponents,
@@ -1814,6 +1854,7 @@
 }
 
 func TestSnapshotWithCcHeadersLibraryAndImageVariants(t *testing.T) {
+	t.Parallel()
 	testImageVariant := func(t *testing.T, property, trait string) {
 		result := android.GroupFixturePreparers(
 			cc.PrepareForTestWithCcDefaultModules,
@@ -1877,6 +1918,7 @@
 }
 
 func TestHostSnapshotWithCcHeadersLibrary(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -1933,6 +1975,7 @@
 }
 
 func TestDeviceAndHostSnapshotWithCcHeadersLibrary(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -2002,6 +2045,7 @@
 }
 
 func TestSystemSharedLibPropagation(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -2162,6 +2206,7 @@
 }
 
 func TestStubsLibrary(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -2223,6 +2268,7 @@
 }
 
 func TestDeviceAndHostSnapshotWithStubsLibrary(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -2299,6 +2345,7 @@
 }
 
 func TestUniqueHostSoname(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
@@ -2364,6 +2411,7 @@
 }
 
 func TestNoSanitizerMembers(t *testing.T) {
+	t.Parallel()
 	result := testSdkWithCc(t, `
 		sdk {
 			name: "mysdk",
diff --git a/sdk/compat_config_sdk_test.go b/sdk/compat_config_sdk_test.go
index 75b5229..1737b3a 100644
--- a/sdk/compat_config_sdk_test.go
+++ b/sdk/compat_config_sdk_test.go
@@ -76,6 +76,7 @@
 }
 
 func TestSnapshotWithCompatConfig(t *testing.T) {
+	t.Parallel()
 	testSnapshotWithCompatConfig(t, `
 		sdk {
 			name: "mysdk",
@@ -85,6 +86,7 @@
 }
 
 func TestSnapshotWithCompatConfig_Apex(t *testing.T) {
+	t.Parallel()
 	testSnapshotWithCompatConfig(t, `
 		apex {
 			name: "myapex",
diff --git a/sdk/exports_test.go b/sdk/exports_test.go
index 9d0a242..5a7ce84 100644
--- a/sdk/exports_test.go
+++ b/sdk/exports_test.go
@@ -20,6 +20,7 @@
 
 // Ensure that module_exports generates a module_exports_snapshot module.
 func TestModuleExportsSnapshot(t *testing.T) {
+	t.Parallel()
 	packageBp := `
 		module_exports {
 			name: "myexports",
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 6e52a3d..0000000
--- a/sdk/genrule_test.go
+++ /dev/null
@@ -1,52 +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 an sdk_genrule can depend on an sdk, and that a genrule can depend on an sdk_genrule
-	bp := `
-				sdk {
-					name: "my_sdk",
-				}
-				sdk_genrule {
-					name: "my_sdk_genrule",
-					tool_files: ["tool"],
-					cmd: "$(location tool) $(in) $(out)",
-					srcs: [":my_sdk"],
-					out: ["out"],
-				}
-				genrule {
-					name: "my_regular_genrule",
-					srcs: [":my_sdk_genrule"],
-					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/sdk/java_sdk_test.go b/sdk/java_sdk_test.go
index 15e13db..1e545ce 100644
--- a/sdk/java_sdk_test.go
+++ b/sdk/java_sdk_test.go
@@ -51,6 +51,7 @@
 // Contains tests for SDK members provided by the java package.
 
 func TestSdkDependsOnSourceEvenWhenPrebuiltPreferred(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(prepareForSdkTestWithJava).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
@@ -77,6 +78,7 @@
 }
 
 func TestSnapshotWithJavaHeaderLibrary(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
 		android.FixtureAddFile("aidl/foo/bar/Test.aidl", nil),
@@ -126,6 +128,7 @@
 }
 
 func TestHostSnapshotWithJavaHeaderLibrary(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
 		android.FixtureAddFile("aidl/foo/bar/Test.aidl", nil),
@@ -178,6 +181,7 @@
 }
 
 func TestDeviceAndHostSnapshotWithJavaHeaderLibrary(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(prepareForSdkTestWithJava).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
@@ -228,6 +232,7 @@
 }
 
 func TestSnapshotWithJavaImplLibrary(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
 		android.FixtureAddFile("aidl/foo/bar/Test.aidl", nil),
@@ -277,6 +282,7 @@
 }
 
 func TestSnapshotWithJavaBootLibrary(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
 		android.FixtureAddFile("aidl", nil),
@@ -328,6 +334,7 @@
 }
 
 func TestSnapshotWithJavaBootLibrary_UpdatableMedia(t *testing.T) {
+	t.Parallel()
 	runTest := func(t *testing.T, targetBuildRelease, expectedJarPath, expectedCopyRule string) {
 		result := android.GroupFixturePreparers(
 			prepareForSdkTestWithJava,
@@ -385,6 +392,7 @@
 }
 
 func TestSnapshotWithJavaLibrary_MinSdkVersion(t *testing.T) {
+	t.Parallel()
 	runTest := func(t *testing.T, targetBuildRelease, minSdkVersion, expectedMinSdkVersion string) {
 		result := android.GroupFixturePreparers(
 			prepareForSdkTestWithJava,
@@ -457,6 +465,7 @@
 }
 
 func TestSnapshotWithJavaSystemserverLibrary(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
 		android.FixtureAddFile("aidl", nil),
@@ -509,6 +518,7 @@
 }
 
 func TestHostSnapshotWithJavaImplLibrary(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
 		android.FixtureAddFile("aidl/foo/bar/Test.aidl", nil),
@@ -561,6 +571,7 @@
 }
 
 func TestSnapshotWithJavaTest(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(prepareForSdkTestWithJava).RunTestWithBp(t, `
 		module_exports {
 			name: "myexports",
@@ -603,6 +614,7 @@
 }
 
 func TestHostSnapshotWithJavaTest(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(prepareForSdkTestWithJava).RunTestWithBp(t, `
 		module_exports {
 			name: "myexports",
@@ -650,6 +662,7 @@
 }
 
 func TestSnapshotWithJavaSystemModules(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
 		java.PrepareForTestWithJavaDefaultModules,
@@ -853,6 +866,7 @@
 }
 
 func TestHostSnapshotWithJavaSystemModules(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(prepareForSdkTestWithJava).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
@@ -911,6 +925,7 @@
 }
 
 func TestDeviceAndHostSnapshotWithOsSpecificMembers(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(prepareForSdkTestWithJava).RunTestWithBp(t, `
 		module_exports {
 			name: "myexports",
@@ -1004,6 +1019,7 @@
 }
 
 func TestSnapshotWithJavaSdkLibrary(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
@@ -1081,6 +1097,7 @@
 }
 
 func TestSnapshotWithJavaSdkLibrary_DistStem(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
@@ -1136,6 +1153,7 @@
 }
 
 func TestSnapshotWithJavaSdkLibrary_UseSrcJar(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJavaSdkLibrary,
 		android.FixtureMergeEnv(map[string]string{
@@ -1192,6 +1210,7 @@
 }
 
 func TestSnapshotWithJavaSdkLibrary_AnnotationsZip(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
@@ -1246,6 +1265,7 @@
 }
 
 func TestSnapshotWithJavaSdkLibrary_AnnotationsZip_PreT(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJavaSdkLibrary,
 		android.FixtureMergeEnv(map[string]string{
@@ -1303,6 +1323,7 @@
 }
 
 func TestSnapshotWithJavaSdkLibrary_CompileDex(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJavaSdkLibrary,
 		android.PrepareForTestWithBuildFlag("RELEASE_HIDDEN_API_EXPORTABLE_STUBS", "true"),
@@ -1385,6 +1406,7 @@
 }
 
 func TestSnapshotWithJavaSdkLibrary_SdkVersion_None(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
@@ -1435,6 +1457,7 @@
 }
 
 func TestSnapshotWithJavaSdkLibrary_SdkVersion_ForScope(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
@@ -1488,6 +1511,7 @@
 }
 
 func TestSnapshotWithJavaSdkLibrary_ApiScopes(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
@@ -1555,6 +1579,7 @@
 }
 
 func TestSnapshotWithJavaSdkLibrary_ModuleLib(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
@@ -1636,6 +1661,7 @@
 }
 
 func TestSnapshotWithJavaSdkLibrary_SystemServer(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
@@ -1703,6 +1729,7 @@
 }
 
 func TestSnapshotWithJavaSdkLibrary_DoctagFiles(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJavaSdkLibrary,
 		android.FixtureAddFile("docs/known_doctags", nil),
@@ -1724,7 +1751,7 @@
 
 		filegroup {
 			name: "mygroup",
-			srcs: [":myjavalib{.doctags}"],
+			device_common_srcs: [":myjavalib{.doctags}"],
 		}
 	`)
 
diff --git a/sdk/license_sdk_test.go b/sdk/license_sdk_test.go
index 754f019..eb8112b 100644
--- a/sdk/license_sdk_test.go
+++ b/sdk/license_sdk_test.go
@@ -21,6 +21,7 @@
 )
 
 func TestSnapshotWithPackageDefaultLicense(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
 		android.PrepareForTestWithLicenses,
diff --git a/sdk/member_trait_test.go b/sdk/member_trait_test.go
index 673d6fb..9b41e9b 100644
--- a/sdk/member_trait_test.go
+++ b/sdk/member_trait_test.go
@@ -116,6 +116,7 @@
 }
 
 func TestBasicTrait_WithoutTrait(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
 		android.FixtureWithRootAndroidBp(`
@@ -154,6 +155,7 @@
 }
 
 func TestBasicTrait_MultipleTraits(t *testing.T) {
+	t.Parallel()
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
 		android.FixtureWithRootAndroidBp(`
@@ -262,6 +264,7 @@
 }
 
 func TestTraitUnsupportedByMemberType(t *testing.T) {
+	t.Parallel()
 	android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
 		android.FixtureWithRootAndroidBp(`
diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go
index 2532a25..985641e 100644
--- a/sdk/sdk_test.go
+++ b/sdk/sdk_test.go
@@ -40,6 +40,7 @@
 // Ensure that prebuilt modules have the same effective visibility as the source
 // modules.
 func TestSnapshotVisibility(t *testing.T) {
+	t.Parallel()
 	packageBp := `
 		package {
 			default_visibility: ["//other/foo"],
@@ -160,6 +161,7 @@
 }
 
 func TestSdkInstall(t *testing.T) {
+	t.Parallel()
 	sdk := `
 		sdk {
 			name: "mysdk",
@@ -326,6 +328,7 @@
 
 // Ensure that sdk snapshot related environment variables work correctly.
 func TestSnapshot_EnvConfiguration(t *testing.T) {
+	t.Parallel()
 	bp := `
 		sdk {
 			name: "mysdk",
@@ -347,11 +350,12 @@
 	)
 
 	checkZipFile := func(t *testing.T, result *android.TestResult, expected string) {
-		zipRule := result.ModuleForTests("mysdk", "common_os").Rule("SnapshotZipFiles")
+		zipRule := result.ModuleForTests(t, "mysdk", "common_os").Rule("SnapshotZipFiles")
 		android.AssertStringEquals(t, "snapshot zip file", expected, zipRule.Output.String())
 	}
 
 	t.Run("no env variables", func(t *testing.T) {
+		t.Parallel()
 		result := preparer.RunTest(t)
 
 		checkZipFile(t, result, "out/soong/.intermediates/mysdk/common_os/mysdk-current.zip")
@@ -377,6 +381,7 @@
 	})
 
 	t.Run("SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE=S", func(t *testing.T) {
+		t.Parallel()
 		result := android.GroupFixturePreparers(
 			prepareForSdkTestWithJava,
 			java.PrepareForTestWithJavaDefaultModules,
@@ -468,6 +473,7 @@
 	})
 
 	t.Run("test replacing exportable module", func(t *testing.T) {
+		t.Parallel()
 		result := android.GroupFixturePreparers(
 			prepareForSdkTestWithJava,
 			java.PrepareForTestWithJavaDefaultModules,
diff --git a/sdk/systemserverclasspath_fragment_sdk_test.go b/sdk/systemserverclasspath_fragment_sdk_test.go
index fd6c4e7..7ebdcd4 100644
--- a/sdk/systemserverclasspath_fragment_sdk_test.go
+++ b/sdk/systemserverclasspath_fragment_sdk_test.go
@@ -87,10 +87,23 @@
 
 	CheckSnapshot(t, result, "mysdk", "",
 		checkAndroidBpContents(expectedSdkSnapshot),
+		snapshotTestPreparer(checkSnapshotWithoutSource,
+			android.FixtureMergeMockFs(android.MockFS{
+				"myapex/Android.bp": []byte(`
+				apex {
+					name: "myapex",
+					key: "myapex.key",
+					min_sdk_version: "1",
+				}
+				`),
+				"myapex/apex_manifest.json": nil,
+			}),
+		),
 	)
 }
 
 func TestSnapshotWithPartialSystemServerClasspathFragment(t *testing.T) {
+	t.Parallel()
 	commonSdk := `
 		apex {
 			name: "myapex",
@@ -185,6 +198,7 @@
 }
 
 func TestSnapshotWithEmptySystemServerClasspathFragment(t *testing.T) {
+	t.Parallel()
 	commonSdk := `
 		apex {
 			name: "myapex",
@@ -231,6 +245,7 @@
 }
 
 func TestSnapshotWithSystemServerClasspathFragment(t *testing.T) {
+	t.Parallel()
 
 	commonSdk := `
 sdk {
@@ -298,6 +313,7 @@
 `
 
 	t.Run("target-s", func(t *testing.T) {
+		t.Parallel()
 		testSnapshotWithSystemServerClasspathFragment(t, commonSdk, "S", `
 // This is auto-generated. DO NOT EDIT.
 
@@ -319,6 +335,7 @@
 	})
 
 	t.Run("target-t", func(t *testing.T) {
+		t.Parallel()
 		testSnapshotWithSystemServerClasspathFragment(t, commonSdk, "Tiramisu", `
 // This is auto-generated. DO NOT EDIT.
 
@@ -361,6 +378,7 @@
 	})
 
 	t.Run("target-u", func(t *testing.T) {
+		t.Parallel()
 		testSnapshotWithSystemServerClasspathFragment(t, commonSdk, "UpsideDownCake", `
 // This is auto-generated. DO NOT EDIT.
 
@@ -409,10 +427,12 @@
 	})
 
 	t.Run("added-directly", func(t *testing.T) {
+		t.Parallel()
 		testSnapshotWithSystemServerClasspathFragment(t, commonSdk, `latest`, expectedLatestSnapshot)
 	})
 
 	t.Run("added-via-apex", func(t *testing.T) {
+		t.Parallel()
 		testSnapshotWithSystemServerClasspathFragment(t, `
 			sdk {
 				name: "mysdk",
diff --git a/sdk/testing.go b/sdk/testing.go
index f4e2b03..cd7bbf5 100644
--- a/sdk/testing.go
+++ b/sdk/testing.go
@@ -128,12 +128,11 @@
 // generated, etc.
 func getSdkSnapshotBuildInfo(t *testing.T, result *android.TestResult, sdk *sdk) *snapshotBuildInfo {
 	info := &snapshotBuildInfo{
-		t:                          t,
-		r:                          result,
-		androidBpContents:          sdk.GetAndroidBpContentsForTests(),
-		infoContents:               sdk.GetInfoContentsForTests(),
-		snapshotTestCustomizations: map[snapshotTest]*snapshotTestCustomization{},
-		targetBuildRelease:         sdk.builderForTests.targetBuildRelease,
+		t:                  t,
+		r:                  result,
+		androidBpContents:  sdk.GetAndroidBpContentsForTests(),
+		infoContents:       sdk.GetInfoContentsForTests(),
+		targetBuildRelease: sdk.builderForTests.targetBuildRelease,
 	}
 
 	buildParams := sdk.BuildParamsForTests()
@@ -144,7 +143,7 @@
 	seenBuildNumberFile := false
 	for _, bp := range buildParams {
 		switch bp.Rule.String() {
-		case android.Cp.String():
+		case android.Cp.String(), android.CpWithBash.String():
 			output := bp.Output
 			// Get destination relative to the snapshot root
 			dest := output.Rel()
@@ -282,7 +281,7 @@
 
 		// Run the snapshot with the snapshot preparer and the extra preparer, which must come after as
 		// it may need to modify parts of the MockFS populated by the snapshot preparer.
-		result := android.GroupFixturePreparers(snapshotPreparer, extraPreparer, customizedPreparers).
+		result := android.GroupFixturePreparers(snapshotPreparer, customizedPreparers, extraPreparer).
 			ExtendWithErrorHandler(customization.errorHandler).
 			RunTest(t)
 
@@ -293,6 +292,7 @@
 	}
 
 	t.Run("snapshot without source", func(t *testing.T) {
+		t.Parallel()
 		// Remove the source Android.bp file to make sure it works without.
 		removeSourceAndroidBp := android.FixtureModifyMockFS(func(fs android.MockFS) {
 			delete(fs, "Android.bp")
@@ -302,16 +302,23 @@
 	})
 
 	t.Run("snapshot with source preferred", func(t *testing.T) {
+		t.Parallel()
 		runSnapshotTestWithCheckers(t, checkSnapshotWithSourcePreferred, android.NullFixturePreparer)
 	})
 
 	t.Run("snapshot preferred with source", func(t *testing.T) {
+		t.Parallel()
 		// Replace the snapshot/Android.bp file with one where "prefer: false," has been replaced with
 		// "prefer: true,"
 		preferPrebuilts := android.FixtureModifyMockFS(func(fs android.MockFS) {
 			snapshotBpFile := filepath.Join(snapshotSubDir, "Android.bp")
 			unpreferred := string(fs[snapshotBpFile])
 			fs[snapshotBpFile] = []byte(strings.ReplaceAll(unpreferred, "prefer: false,", "prefer: true,"))
+
+			prebuiltApexBpFile := "prebuilts/apex/Android.bp"
+			if prebuiltApexBp, ok := fs[prebuiltApexBpFile]; ok {
+				fs[prebuiltApexBpFile] = []byte(strings.ReplaceAll(string(prebuiltApexBp), "prefer: false,", "prefer: true,"))
+			}
 		})
 
 		runSnapshotTestWithCheckers(t, checkSnapshotPreferredWithSource, preferPrebuilts)
@@ -469,19 +476,40 @@
 	targetBuildRelease *buildRelease
 
 	// The test specific customizations for each snapshot test.
-	snapshotTestCustomizations map[snapshotTest]*snapshotTestCustomization
+	snapshotTestCustomizations snapshotTestCustomizationSet
+}
+
+type snapshotTestCustomizationSet struct {
+	snapshotWithoutSource       *snapshotTestCustomization
+	snapshotWithSourcePreferred *snapshotTestCustomization
+	snapshotPreferredWithSource *snapshotTestCustomization
+}
+
+func (s *snapshotTestCustomizationSet) customization(snapshotTest snapshotTest) **snapshotTestCustomization {
+	var customization **snapshotTestCustomization
+	switch snapshotTest {
+	case checkSnapshotWithoutSource:
+
+		customization = &s.snapshotWithoutSource
+	case checkSnapshotWithSourcePreferred:
+		customization = &s.snapshotWithSourcePreferred
+	case checkSnapshotPreferredWithSource:
+		customization = &s.snapshotPreferredWithSource
+	default:
+		panic(fmt.Errorf("unsupported snapshotTest %v", snapshotTest))
+	}
+	return customization
 }
 
 // snapshotTestCustomization gets the test specific customization for the specified snapshotTest.
 //
 // If no customization was created previously then it creates a default customization.
 func (i *snapshotBuildInfo) snapshotTestCustomization(snapshotTest snapshotTest) *snapshotTestCustomization {
-	customization := i.snapshotTestCustomizations[snapshotTest]
-	if customization == nil {
-		customization = &snapshotTestCustomization{
+	customization := i.snapshotTestCustomizations.customization(snapshotTest)
+	if *customization == nil {
+		*customization = &snapshotTestCustomization{
 			errorHandler: android.FixtureExpectsNoErrors,
 		}
-		i.snapshotTestCustomizations[snapshotTest] = customization
 	}
-	return customization
+	return *customization
 }
diff --git a/sdk/update.go b/sdk/update.go
index 7f4f80a..00352cb 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -1145,7 +1145,7 @@
 
 	// The licenses are the same for all variants.
 	mctx := s.ctx
-	licenseInfo, _ := android.OtherModuleProvider(mctx, variant, android.LicenseInfoProvider)
+	licenseInfo, _ := android.OtherModuleProvider(mctx, variant, android.LicensesInfoProvider)
 	if len(licenseInfo.Licenses) > 0 {
 		m.AddPropertyWithTag("licenses", licenseInfo.Licenses, s.OptionalSdkMemberReferencePropertyTag())
 	}
@@ -1201,7 +1201,7 @@
 // the snapshot.
 func (s *snapshotBuilder) snapshotSdkMemberName(name string, required bool) string {
 	if _, ok := s.allMembersByName[name]; !ok {
-		if required {
+		if required && !s.ctx.Config().AllowMissingDependencies() {
 			s.ctx.ModuleErrorf("Required member reference %s is not a member of the sdk", name)
 		}
 		return name
diff --git a/sh/Android.bp b/sh/Android.bp
index 930fcf5..1deedc7 100644
--- a/sh/Android.bp
+++ b/sh/Android.bp
@@ -11,7 +11,6 @@
         "soong-android",
         "soong-cc",
         "soong-java",
-        "soong-testing",
         "soong-tradefed",
     ],
     srcs: [
diff --git a/sh/sh_binary.go b/sh/sh_binary.go
index 2e48d83..d753d24 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -15,11 +15,10 @@
 package sh
 
 import (
+	"fmt"
 	"path/filepath"
 	"strings"
 
-	"android/soong/testing"
-
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 
@@ -47,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.
@@ -120,6 +120,16 @@
 	// the test.
 	Data []string `android:"path,arch_variant"`
 
+	// same as data, but adds dependencies using the device's os variation and the common
+	// architecture's variation. Can be used to add a module built for device to the data of a
+	// host test.
+	Device_common_data []string `android:"path_device_common"`
+
+	// same as data, but adds dependencies using the device's os variation and the device's first
+	// architecture's variation. Can be used to add a module built for device to the data of a
+	// host test.
+	Device_first_data []string `android:"path_device_first"`
+
 	// Add RootTargetPreparer to auto generated test config. This guarantees the test to run
 	// with root permission.
 	Require_root *bool
@@ -155,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
 
@@ -176,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
 }
@@ -189,7 +204,7 @@
 func (s *ShBinary) DepsMutator(ctx android.BottomUpMutatorContext) {
 }
 
-func (s *ShBinary) OutputFile() android.OutputPath {
+func (s *ShBinary) OutputFile() android.Path {
 	return s.outputFilePath
 }
 
@@ -210,41 +225,41 @@
 
 var _ android.ImageInterface = (*ShBinary)(nil)
 
-func (s *ShBinary) ImageMutatorBegin(ctx android.BaseModuleContext) {}
+func (s *ShBinary) ImageMutatorBegin(ctx android.ImageInterfaceContext) {}
 
-func (s *ShBinary) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
+func (s *ShBinary) VendorVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return s.InstallInVendor()
 }
 
-func (s *ShBinary) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
+func (s *ShBinary) ProductVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return s.InstallInProduct()
 }
 
-func (s *ShBinary) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
+func (s *ShBinary) CoreVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return !s.InstallInRecovery() && !s.InstallInRamdisk() && !s.InstallInVendorRamdisk() && !s.ModuleBase.InstallInVendor()
 }
 
-func (s *ShBinary) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (s *ShBinary) RamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return proptools.Bool(s.properties.Ramdisk_available) || s.InstallInRamdisk()
 }
 
-func (s *ShBinary) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (s *ShBinary) VendorRamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return proptools.Bool(s.properties.Vendor_ramdisk_available) || s.InstallInVendorRamdisk()
 }
 
-func (s *ShBinary) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+func (s *ShBinary) DebugRamdiskVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return false
 }
 
-func (s *ShBinary) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
+func (s *ShBinary) RecoveryVariantNeeded(ctx android.ImageInterfaceContext) bool {
 	return proptools.Bool(s.properties.Recovery_available) || s.InstallInRecovery()
 }
 
-func (s *ShBinary) ExtraImageVariations(ctx android.BaseModuleContext) []string {
+func (s *ShBinary) ExtraImageVariations(ctx android.ImageInterfaceContext) []string {
 	return nil
 }
 
-func (s *ShBinary) SetImageVariation(ctx android.BaseModuleContext, variation string) {
+func (s *ShBinary) SetImageVariation(ctx android.ImageInterfaceContext, variation string) {
 	s.properties.ImageVariation = variation
 }
 
@@ -299,8 +314,6 @@
 
 	s.properties.SubName = s.GetSubname(ctx)
 
-	android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: []string{s.sourceFilePath.String()}})
-
 	ctx.SetOutputFiles(android.Paths{s.outputFilePath}, "")
 }
 
@@ -407,8 +420,10 @@
 	s.ShBinary.generateAndroidBuildActions(ctx)
 
 	expandedData := android.PathsForModuleSrc(ctx, s.testProperties.Data)
+	expandedData = append(expandedData, android.PathsForModuleSrc(ctx, s.testProperties.Device_common_data)...)
+	expandedData = append(expandedData, android.PathsForModuleSrc(ctx, s.testProperties.Device_first_data)...)
 	// Emulate the data property for java_data dependencies.
-	for _, javaData := range ctx.GetDirectDepsWithTag(shTestJavaDataTag) {
+	for _, javaData := range ctx.GetDirectDepsProxyWithTag(shTestJavaDataTag) {
 		expandedData = append(expandedData, android.OutputFilesForModule(ctx, javaData, "")...)
 	}
 	for _, d := range expandedData {
@@ -459,22 +474,26 @@
 		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) {
+	ctx.VisitDirectDepsProxy(func(dep android.ModuleProxy) {
 		depTag := ctx.OtherModuleDependencyTag(dep)
 		switch depTag {
 		case shTestDataBinsTag, shTestDataDeviceBinsTag:
 			path := android.OutputFileForModule(ctx, dep, "")
 			s.addToDataModules(ctx, path.Base(), path)
 		case shTestDataLibsTag, shTestDataDeviceLibsTag:
-			if cc, isCc := dep.(*cc.Module); isCc {
+			if _, isCc := android.OtherModuleProvider(ctx, dep, cc.CcInfoProvider); isCc {
 				// Copy to an intermediate output directory to append "lib[64]" to the path,
 				// so that it's compatible with the default rpath values.
 				var relPath string
-				if cc.Arch().ArchType.Multilib == "lib64" {
-					relPath = filepath.Join("lib64", cc.OutputFile().Path().Base())
+				linkableInfo := android.OtherModuleProviderOrDefault(ctx, dep, cc.LinkableInfoProvider)
+				commonInfo := android.OtherModuleProviderOrDefault(ctx, dep, android.CommonModuleInfoKey)
+
+				if commonInfo.Target.Arch.ArchType.Multilib == "lib64" {
+					relPath = filepath.Join("lib64", linkableInfo.OutputFile.Path().Base())
 				} else {
-					relPath = filepath.Join("lib", cc.OutputFile().Path().Base())
+					relPath = filepath.Join("lib", linkableInfo.OutputFile.Path().Base())
 				}
 				if _, exist := s.dataModules[relPath]; exist {
 					return
@@ -482,7 +501,7 @@
 				relocatedLib := android.PathForModuleOut(ctx, "relocated").Join(ctx, relPath)
 				ctx.Build(pctx, android.BuildParams{
 					Rule:   android.Cp,
-					Input:  cc.OutputFile().Path(),
+					Input:  linkableInfo.OutputFile.Path(),
 					Output: relocatedLib,
 				})
 				s.addToDataModules(ctx, relPath, relocatedLib)
@@ -499,7 +518,26 @@
 	installedData := ctx.InstallTestData(s.installDir, s.data)
 	s.installedFile = ctx.InstallExecutable(s.installDir, s.outputFilePath.Base(), s.outputFilePath, installedData...)
 
-	android.SetProvider(ctx, testing.TestModuleProviderKey, testing.TestModuleProviderData{})
+	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 {
@@ -523,6 +561,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)
 			},
@@ -540,6 +581,7 @@
 	module := &ShBinary{}
 	initShBinaryModule(module)
 	android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
 	return module
 }
 
@@ -549,6 +591,7 @@
 	module := &ShBinary{}
 	initShBinaryModule(module)
 	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
 	return module
 }
 
@@ -559,6 +602,7 @@
 	module.AddProperties(&module.testProperties)
 
 	android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
 	return module
 }
 
@@ -573,6 +617,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..c2e2d2b 100644
--- a/sh/sh_binary_test.go
+++ b/sh/sh_binary_test.go
@@ -58,7 +58,7 @@
 		}
 	`)
 
-	mod := result.ModuleForTests("foo", "android_arm64_armv8-a").Module().(*ShTest)
+	mod := result.ModuleForTests(t, "foo", "android_arm64_armv8-a").Module().(*ShTest)
 
 	entries := android.AndroidMkEntriesForTest(t, result.TestContext, mod)[0]
 
@@ -83,7 +83,7 @@
 		}
 	`)
 
-	mod := result.ModuleForTests("foo", "android_arm64_armv8-a").Module().(*ShTest)
+	mod := result.ModuleForTests(t, "foo", "android_arm64_armv8-a").Module().(*ShTest)
 
 	entries := android.AndroidMkEntriesForTest(t, result.TestContext, mod)[0]
 
@@ -129,7 +129,7 @@
 	buildOS := config.BuildOS.String()
 	arches := []string{"android_arm64_armv8-a", buildOS + "_x86_64"}
 	for _, arch := range arches {
-		variant := ctx.ModuleForTests("foo", arch)
+		variant := ctx.ModuleForTests(t, "foo", arch)
 
 		libExt := ".so"
 		if arch == "darwin_x86_64" {
@@ -167,7 +167,7 @@
 	`)
 
 	buildOS := ctx.Config().BuildOS.String()
-	mod := ctx.ModuleForTests("foo", buildOS+"_x86_64").Module().(*ShTest)
+	mod := ctx.ModuleForTests(t, "foo", buildOS+"_x86_64").Module().(*ShTest)
 	if !mod.Host() {
 		t.Errorf("host bit is not set for a sh_test_host module.")
 	}
@@ -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(t, "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 {
@@ -205,7 +221,7 @@
 
 	buildOS := config.BuildOS.String()
 	variant := buildOS + "_x86_64"
-	foo := ctx.ModuleForTests("foo", variant)
+	foo := ctx.ModuleForTests(t, "foo", variant)
 
 	relocated := foo.Output(filepath.Join("out/soong/.intermediates/foo", variant, "relocated/lib64/libbar.so"))
 	expectedInput := "out/soong/.intermediates/libbar/android_arm64_armv8-a_shared/libbar.so"
@@ -250,7 +266,7 @@
 	`)
 
 	buildOS := config.BuildOS.String()
-	fooModule := ctx.ModuleForTests("foo", buildOS+"_x86_64")
+	fooModule := ctx.ModuleForTests(t, "foo", buildOS+"_x86_64")
 
 	expectedBinAutogenConfig := `<option name="push-file" key="bar" value="/data/local/tests/unrestricted/foo/bar" />`
 	autogen := fooModule.Rule("autogen")
@@ -280,7 +296,7 @@
 		}
 	`)
 	buildOS := ctx.Config().BuildOS.String()
-	mod := ctx.ModuleForTests("foo", buildOS+"_x86_64").Module().(*ShTest)
+	mod := ctx.ModuleForTests(t, "foo", buildOS+"_x86_64").Module().(*ShTest)
 	if !mod.Host() {
 		t.Errorf("host bit is not set for a sh_test_host module.")
 	}
@@ -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(t, "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(t, "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(t, "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(t, "the-binary", "android_arm64_armv8-a").Module().(*ShBinary)
+	android.AssertStringEquals(t, "Filename", expectedFilename, *mod.properties.Filename)
+}
diff --git a/shared/paths.go b/shared/paths.go
index 1ee66d5..64f94ba 100644
--- a/shared/paths.go
+++ b/shared/paths.go
@@ -20,14 +20,6 @@
 	"path/filepath"
 )
 
-// A SharedPaths represents a list of paths that are shared between
-// soong_ui and soong.
-type SharedPaths interface {
-	// BazelMetricsDir returns the path where a set of bazel profile
-	// files are stored for later processed by the metrics pipeline.
-	BazelMetricsDir() string
-}
-
 // Joins the path strings in the argument list, taking absolute paths into
 // account. That is, if one of the strings is an absolute path, the ones before
 // are ignored.
diff --git a/snapshot/snapshot_base.go b/snapshot/snapshot_base.go
index 6bf3c87..510e9cf 100644
--- a/snapshot/snapshot_base.go
+++ b/snapshot/snapshot_base.go
@@ -45,14 +45,3 @@
 	LicenseKinds []string `json:",omitempty"`
 	LicenseTexts []string `json:",omitempty"`
 }
-
-func (prop *SnapshotJsonFlags) InitBaseSnapshotPropsWithName(m android.Module, name string) {
-	prop.ModuleName = name
-
-	prop.LicenseKinds = m.EffectiveLicenseKinds()
-	prop.LicenseTexts = m.EffectiveLicenseFiles().Strings()
-}
-
-func (prop *SnapshotJsonFlags) InitBaseSnapshotProps(m android.Module) {
-	prop.InitBaseSnapshotPropsWithName(m, m.Name())
-}
diff --git a/sysprop/sysprop_library.go b/sysprop/sysprop_library.go
index 84f20c5..25fbc41 100644
--- a/sysprop/sysprop_library.go
+++ b/sysprop/sysprop_library.go
@@ -346,7 +346,6 @@
 			ctx.PropertyErrorf("srcs", "srcs contains non-sysprop file %q", syspropFile.String())
 		}
 	}
-	android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: srcs.Strings()})
 
 	if ctx.Failed() {
 		return
@@ -680,7 +679,7 @@
 		Sysprop_srcs: m.properties.Srcs,
 		Scope:        scope,
 		Check_api:    proptools.StringPtr(ctx.ModuleName()),
-		Installable:  proptools.BoolPtr(false),
+		Installable:  m.properties.Installable,
 		Crate_name:   m.rustCrateName(),
 		Rustlibs: []string{
 			"liblog_rust",
diff --git a/sysprop/sysprop_test.go b/sysprop/sysprop_test.go
index 7d4e69d..06c5e9c 100644
--- a/sysprop/sysprop_test.go
+++ b/sysprop/sysprop_test.go
@@ -261,9 +261,9 @@
 		"android_vendor_arm64_armv8-a_shared",
 		"android_vendor_arm64_armv8-a_static",
 	} {
-		result.ModuleForTests("libsysprop-platform", variant)
-		result.ModuleForTests("libsysprop-vendor", variant)
-		result.ModuleForTests("libsysprop-odm", variant)
+		result.ModuleForTests(t, "libsysprop-platform", variant)
+		result.ModuleForTests(t, "libsysprop-vendor", variant)
+		result.ModuleForTests(t, "libsysprop-odm", variant)
 	}
 
 	// product variant of vendor-owned sysprop_library
@@ -273,7 +273,7 @@
 		"android_product_arm64_armv8-a_shared",
 		"android_product_arm64_armv8-a_static",
 	} {
-		result.ModuleForTests("libsysprop-vendor-on-product", variant)
+		result.ModuleForTests(t, "libsysprop-vendor-on-product", variant)
 	}
 
 	for _, variant := range []string{
@@ -282,15 +282,15 @@
 		"android_arm64_armv8-a_shared",
 		"android_arm64_armv8-a_static",
 	} {
-		library := result.ModuleForTests("libsysprop-platform", variant).Module().(*cc.Module)
+		library := result.ModuleForTests(t, "libsysprop-platform", variant).Module().(*cc.Module)
 		expectedApexAvailableOnLibrary := []string{"//apex_available:platform"}
 		android.AssertDeepEquals(t, "apex available property on libsysprop-platform", expectedApexAvailableOnLibrary, library.ApexProperties.Apex_available)
 	}
 
-	result.ModuleForTests("sysprop-platform", "android_common")
-	result.ModuleForTests("sysprop-platform_public", "android_common")
-	result.ModuleForTests("sysprop-vendor", "android_common")
-	result.ModuleForTests("sysprop-vendor-on-product", "android_common")
+	result.ModuleForTests(t, "sysprop-platform", "android_common")
+	result.ModuleForTests(t, "sysprop-platform_public", "android_common")
+	result.ModuleForTests(t, "sysprop-vendor", "android_common")
+	result.ModuleForTests(t, "sysprop-vendor-on-product", "android_common")
 
 	// Check for exported includes
 	coreVariant := "android_arm64_armv8-a_static"
@@ -305,19 +305,19 @@
 	vendorInternalPath := "libsysprop-vendor/android_vendor_arm64_armv8-a_static/gen/sysprop/include"
 	vendorOnProductPath := "libsysprop-vendor-on-product/android_product_arm64_armv8-a_static/gen/sysprop/public/include"
 
-	platformClient := result.ModuleForTests("cc-client-platform", coreVariant)
+	platformClient := result.ModuleForTests(t, "cc-client-platform", coreVariant)
 	platformFlags := platformClient.Rule("cc").Args["cFlags"]
 
 	// platform should use platform's internal header
 	android.AssertStringDoesContain(t, "flags for platform", platformFlags, platformInternalPath)
 
-	platformStaticClient := result.ModuleForTests("cc-client-platform-static", coreVariant)
+	platformStaticClient := result.ModuleForTests(t, "cc-client-platform-static", coreVariant)
 	platformStaticFlags := platformStaticClient.Rule("cc").Args["cFlags"]
 
 	// platform-static should use platform's internal header
 	android.AssertStringDoesContain(t, "flags for platform-static", platformStaticFlags, platformInternalPath)
 
-	productClient := result.ModuleForTests("cc-client-product", productVariant)
+	productClient := result.ModuleForTests(t, "cc-client-product", productVariant)
 	productFlags := productClient.Rule("cc").Args["cFlags"]
 
 	// Product should use platform's and vendor's public headers
@@ -327,7 +327,7 @@
 			platformOnProductPath, vendorOnProductPath, productFlags)
 	}
 
-	vendorClient := result.ModuleForTests("cc-client-vendor", vendorVariant)
+	vendorClient := result.ModuleForTests(t, "cc-client-vendor", vendorVariant)
 	vendorFlags := vendorClient.Rule("cc").Args["cFlags"]
 
 	// Vendor should use platform's public header and vendor's internal header
@@ -338,8 +338,8 @@
 	}
 
 	// Java modules linking against system API should use public stub
-	javaSystemApiClient := result.ModuleForTests("java-platform", "android_common").Rule("javac")
-	syspropPlatformPublic := result.ModuleForTests("sysprop-platform_public", "android_common").Description("for turbine")
+	javaSystemApiClient := result.ModuleForTests(t, "java-platform", "android_common").Rule("javac")
+	syspropPlatformPublic := result.ModuleForTests(t, "sysprop-platform_public", "android_common").Description("for turbine")
 	if g, w := javaSystemApiClient.Implicits.Strings(), syspropPlatformPublic.Output.String(); !android.InList(w, g) {
 		t.Errorf("system api client should use public stub %q, got %q", w, g)
 	}
@@ -358,15 +358,15 @@
 
 	expected := []string{"//apex_available:platform"}
 
-	ccModule := result.ModuleForTests("libsysprop-platform", "android_arm64_armv8-a_shared").Module().(*cc.Module)
+	ccModule := result.ModuleForTests(t, "libsysprop-platform", "android_arm64_armv8-a_shared").Module().(*cc.Module)
 	propFromCc := ccModule.ApexProperties.Apex_available
 	android.AssertDeepEquals(t, "apex_available forwarding to cc module", expected, propFromCc)
 
-	javaModule := result.ModuleForTests("sysprop-platform", "android_common").Module().(*java.Library)
+	javaModule := result.ModuleForTests(t, "sysprop-platform", "android_common").Module().(*java.Library)
 	propFromJava := javaModule.ApexProperties.Apex_available
 	android.AssertDeepEquals(t, "apex_available forwarding to java module", expected, propFromJava)
 
-	rustModule := result.ModuleForTests("libsysprop_platform_rust", "android_arm64_armv8-a_rlib_rlib-std").Module().(*rust.Module)
+	rustModule := result.ModuleForTests(t, "libsysprop_platform_rust", "android_arm64_armv8-a_rlib_rlib-std").Module().(*rust.Module)
 	propFromRust := rustModule.ApexProperties.Apex_available
 	android.AssertDeepEquals(t, "apex_available forwarding to rust module", expected, propFromRust)
 }
@@ -390,15 +390,15 @@
 		}
 	`)
 
-	ccModule := result.ModuleForTests("libsysprop-platform", "android_arm64_armv8-a_shared").Module().(*cc.Module)
+	ccModule := result.ModuleForTests(t, "libsysprop-platform", "android_arm64_armv8-a_shared").Module().(*cc.Module)
 	propFromCc := proptools.String(ccModule.Properties.Min_sdk_version)
 	android.AssertStringEquals(t, "min_sdk_version forwarding to cc module", "29", propFromCc)
 
-	javaModule := result.ModuleForTests("sysprop-platform", "android_common").Module().(*java.Library)
+	javaModule := result.ModuleForTests(t, "sysprop-platform", "android_common").Module().(*java.Library)
 	propFromJava := javaModule.MinSdkVersionString()
 	android.AssertStringEquals(t, "min_sdk_version forwarding to java module", "30", propFromJava)
 
-	rustModule := result.ModuleForTests("libsysprop_platform_rust", "android_arm64_armv8-a_rlib_rlib-std").Module().(*rust.Module)
+	rustModule := result.ModuleForTests(t, "libsysprop_platform_rust", "android_arm64_armv8-a_rlib_rlib-std").Module().(*rust.Module)
 	propFromRust := proptools.String(rustModule.Properties.Min_sdk_version)
 	android.AssertStringEquals(t, "min_sdk_version forwarding to rust module", "29", propFromRust)
 }
diff --git a/systemfeatures/Android.bp b/systemfeatures/Android.bp
new file mode 100644
index 0000000..a65a6b6
--- /dev/null
+++ b/systemfeatures/Android.bp
@@ -0,0 +1,18 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+    name: "soong-systemfeatures",
+    pkgPath: "android/soong/systemfeatures",
+    deps: [
+        "blueprint",
+        "blueprint-proptools",
+        "soong",
+        "soong-android",
+        "soong-java",
+    ],
+    srcs: ["system_features.go"],
+    testSrcs: ["system_features_test.go"],
+    pluginFor: ["soong_build"],
+}
diff --git a/systemfeatures/OWNERS b/systemfeatures/OWNERS
new file mode 100644
index 0000000..3e44806
--- /dev/null
+++ b/systemfeatures/OWNERS
@@ -0,0 +1,2 @@
+jdduke@google.com
+shayba@google.com
diff --git a/systemfeatures/system_features.go b/systemfeatures/system_features.go
new file mode 100644
index 0000000..b8dacfb
--- /dev/null
+++ b/systemfeatures/system_features.go
@@ -0,0 +1,111 @@
+// 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 systemfeatures
+
+import (
+	"fmt"
+	"sort"
+	"strings"
+
+	"android/soong/android"
+	"android/soong/genrule"
+
+	"github.com/google/blueprint/proptools"
+)
+
+var (
+	pctx = android.NewPackageContext("android/soong/systemfeatures")
+)
+
+func init() {
+	registerSystemFeaturesComponents(android.InitRegistrationContext)
+}
+
+func registerSystemFeaturesComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("java_system_features_srcs", JavaSystemFeaturesSrcsFactory)
+}
+
+type javaSystemFeaturesSrcs struct {
+	android.ModuleBase
+	properties struct {
+		// The fully qualified class name for the generated code, e.g., com.android.Foo
+		Full_class_name string
+		// Whether to generate only a simple metadata class with details about the full API surface.
+		// This is useful for tools that rely on the mapping from feature names to their generated
+		// method names, but don't want the fully generated API class (e.g., for linting).
+
+		Metadata_only *bool
+	}
+	outputFiles android.WritablePaths
+}
+
+var _ genrule.SourceFileGenerator = (*javaSystemFeaturesSrcs)(nil)
+var _ android.SourceFileProducer = (*javaSystemFeaturesSrcs)(nil)
+
+func (m *javaSystemFeaturesSrcs) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	// Create a file name appropriate for the given fully qualified (w/ package) class name.
+	classNameParts := strings.Split(m.properties.Full_class_name, ".")
+	outputDir := android.PathForModuleGen(ctx)
+	outputFileName := classNameParts[len(classNameParts)-1] + ".java"
+	outputFile := android.PathForModuleGen(ctx, outputFileName).OutputPath
+
+	// Collect all RELEASE_SYSTEM_FEATURE_$K:$V build flags into a list of "$K:$V" pairs.
+	var features []string
+	for k, v := range ctx.Config().ProductVariables().BuildFlags {
+		if strings.HasPrefix(k, "RELEASE_SYSTEM_FEATURE_") {
+			shortFeatureName := strings.TrimPrefix(k, "RELEASE_SYSTEM_FEATURE_")
+			features = append(features, fmt.Sprintf("%s:%s", shortFeatureName, v))
+		}
+	}
+	// Ensure sorted outputs for consistency of flag ordering in ninja outputs.
+	sort.Strings(features)
+
+	rule := android.NewRuleBuilder(pctx, ctx)
+	rule.Command().Text("rm -rf").Text(outputDir.String())
+	rule.Command().Text("mkdir -p").Text(outputDir.String())
+	rule.Command().
+		BuiltTool("systemfeatures-gen-tool").
+		Flag(m.properties.Full_class_name).
+		FlagForEachArg("--feature=", features).
+		FlagWithArg("--readonly=", fmt.Sprint(ctx.Config().ReleaseUseSystemFeatureBuildFlags())).
+		FlagWithArg("--metadata-only=", fmt.Sprint(proptools.Bool(m.properties.Metadata_only))).
+		FlagWithOutput(" > ", outputFile)
+	rule.Build(ctx.ModuleName(), "Generating systemfeatures srcs filegroup")
+
+	m.outputFiles = append(m.outputFiles, outputFile)
+}
+
+func (m *javaSystemFeaturesSrcs) Srcs() android.Paths {
+	return m.outputFiles.Paths()
+}
+
+func (m *javaSystemFeaturesSrcs) GeneratedSourceFiles() android.Paths {
+	return m.outputFiles.Paths()
+}
+
+func (m *javaSystemFeaturesSrcs) GeneratedDeps() android.Paths {
+	return m.outputFiles.Paths()
+}
+
+func (m *javaSystemFeaturesSrcs) GeneratedHeaderDirs() android.Paths {
+	return nil
+}
+
+func JavaSystemFeaturesSrcsFactory() android.Module {
+	module := &javaSystemFeaturesSrcs{}
+	module.AddProperties(&module.properties)
+	module.properties.Metadata_only = proptools.BoolPtr(false)
+	android.InitAndroidModule(module)
+	return module
+}
diff --git a/systemfeatures/system_features_test.go b/systemfeatures/system_features_test.go
new file mode 100644
index 0000000..58e6a06
--- /dev/null
+++ b/systemfeatures/system_features_test.go
@@ -0,0 +1,51 @@
+// 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 systemfeatures
+
+import (
+	"android/soong/android"
+
+	"testing"
+)
+
+func TestJavaSystemFeaturesSrcs(t *testing.T) {
+	bp := `
+java_system_features_srcs {
+    name: "system-features-srcs",
+	full_class_name: "com.android.test.RoSystemFeatures",
+}
+`
+
+	res := android.GroupFixturePreparers(
+		android.FixtureRegisterWithContext(registerSystemFeaturesComponents),
+		android.PrepareForTestWithBuildFlag("RELEASE_USE_SYSTEM_FEATURE_BUILD_FLAGS", "true"),
+		android.PrepareForTestWithBuildFlag("RELEASE_SYSTEM_FEATURE_AUTOMOTIVE", "0"),
+		android.PrepareForTestWithBuildFlag("RELEASE_SYSTEM_FEATURE_TELEVISION", "UNAVAILABLE"),
+		android.PrepareForTestWithBuildFlag("RELEASE_SYSTEM_FEATURE_WATCH", ""),
+		android.PrepareForTestWithBuildFlag("RELEASE_NOT_SYSTEM_FEATURE_FOO", "BAR"),
+	).RunTestWithBp(t, bp)
+
+	module := res.ModuleForTests(t, "system-features-srcs", "")
+	cmd := module.Rule("system-features-srcs").RuleParams.Command
+	android.AssertStringDoesContain(t, "Expected fully class name", cmd, " com.android.test.RoSystemFeatures ")
+	android.AssertStringDoesContain(t, "Expected readonly flag", cmd, "--readonly=true")
+	android.AssertStringDoesContain(t, "Expected AUTOMOTIVE feature flag", cmd, "--feature=AUTOMOTIVE:0 ")
+	android.AssertStringDoesContain(t, "Expected TELEVISION feature flag", cmd, "--feature=TELEVISION:UNAVAILABLE ")
+	android.AssertStringDoesContain(t, "Expected WATCH feature flag", cmd, "--feature=WATCH: ")
+	android.AssertStringDoesNotContain(t, "Unexpected FOO arg from non-system feature flag", cmd, "FOO")
+
+	systemFeaturesModule := module.Module().(*javaSystemFeaturesSrcs)
+	expectedOutputPath := "out/soong/.intermediates/system-features-srcs/gen/RoSystemFeatures.java"
+	android.AssertPathsRelativeToTopEquals(t, "Expected output file", []string{expectedOutputPath}, systemFeaturesModule.Srcs())
+}
diff --git a/testing/Android.bp b/testing/Android.bp
deleted file mode 100644
index 43040b0..0000000
--- a/testing/Android.bp
+++ /dev/null
@@ -1,24 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-bootstrap_go_package {
-    name: "soong-testing",
-    pkgPath: "android/soong/testing",
-    deps: [
-        "blueprint",
-        "soong-android",
-        "soong-testing-code_metadata_internal_proto",
-        "soong-testing-test_spec_proto",
-
-    ],
-    srcs: [
-        "all_code_metadata.go",
-        "all_test_specs.go",
-        "code_metadata.go",
-        "test_spec.go",
-        "init.go",
-        "test.go",
-    ],
-    pluginFor: ["soong_build"],
-}
diff --git a/testing/OWNERS b/testing/OWNERS
deleted file mode 100644
index 03bcdf1..0000000
--- a/testing/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-dariofreni@google.com
-joeo@google.com
-ronish@google.com
-caditya@google.com
diff --git a/testing/all_code_metadata.go b/testing/all_code_metadata.go
deleted file mode 100644
index e89b281..0000000
--- a/testing/all_code_metadata.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package testing
-
-import (
-	"android/soong/android"
-)
-
-const fileContainingCodeMetadataFilePaths = "all_code_metadata_paths.rsp"
-const allCodeMetadataFile = "all_code_metadata.pb"
-
-func AllCodeMetadataFactory() android.Singleton {
-	return &allCodeMetadataSingleton{}
-}
-
-type allCodeMetadataSingleton struct {
-	// Path where the collected metadata is stored after successful validation.
-	outputPath android.OutputPath
-}
-
-func (this *allCodeMetadataSingleton) GenerateBuildActions(ctx android.SingletonContext) {
-	var intermediateMetadataPaths android.Paths
-
-	ctx.VisitAllModules(
-		func(module android.Module) {
-			if metadata, ok := android.OtherModuleProvider(ctx, module, CodeMetadataProviderKey); ok {
-				intermediateMetadataPaths = append(intermediateMetadataPaths, metadata.IntermediatePath)
-			}
-		},
-	)
-
-	rspFile := android.PathForOutput(ctx, fileContainingCodeMetadataFilePaths)
-	this.outputPath = android.PathForOutput(ctx, ownershipDirectory, allCodeMetadataFile)
-
-	rule := android.NewRuleBuilder(pctx, ctx)
-	cmd := rule.Command().
-		BuiltTool("metadata").
-		FlagWithArg("-rule ", "code_metadata").
-		FlagWithRspFileInputList("-inputFile ", rspFile, intermediateMetadataPaths)
-	cmd.FlagWithOutput("-outputFile ", this.outputPath)
-	rule.Build("all_code_metadata_rule", "Generate all code metadata")
-
-	ctx.Phony("all_code_metadata", this.outputPath)
-}
-
-func (this *allCodeMetadataSingleton) MakeVars(ctx android.MakeVarsContext) {
-	ctx.DistForGoal("code_metadata", this.outputPath)
-}
diff --git a/testing/all_test_specs.go b/testing/all_test_specs.go
deleted file mode 100644
index 68f24d1..0000000
--- a/testing/all_test_specs.go
+++ /dev/null
@@ -1,44 +0,0 @@
-package testing
-
-import (
-	"android/soong/android"
-)
-
-const ownershipDirectory = "ownership"
-const fileContainingFilePaths = "all_test_spec_paths.rsp"
-const allTestSpecsFile = "all_test_specs.pb"
-
-func AllTestSpecsFactory() android.Singleton {
-	return &allTestSpecsSingleton{}
-}
-
-type allTestSpecsSingleton struct {
-	// Path where the collected metadata is stored after successful validation.
-	outputPath android.OutputPath
-}
-
-func (this *allTestSpecsSingleton) GenerateBuildActions(ctx android.SingletonContext) {
-	var intermediateMetadataPaths android.Paths
-
-	ctx.VisitAllModules(func(module android.Module) {
-		if metadata, ok := android.OtherModuleProvider(ctx, module, TestSpecProviderKey); ok {
-			intermediateMetadataPaths = append(intermediateMetadataPaths, metadata.IntermediatePath)
-		}
-	})
-
-	rspFile := android.PathForOutput(ctx, fileContainingFilePaths)
-	this.outputPath = android.PathForOutput(ctx, ownershipDirectory, allTestSpecsFile)
-
-	rule := android.NewRuleBuilder(pctx, ctx)
-	cmd := rule.Command().
-		BuiltTool("metadata").
-		FlagWithArg("-rule ", "test_spec").
-		FlagWithRspFileInputList("-inputFile ", rspFile, intermediateMetadataPaths)
-	cmd.FlagWithOutput("-outputFile ", this.outputPath)
-	rule.Build("all_test_specs_rule", "Generate all test specifications")
-	ctx.Phony("all_test_specs", this.outputPath)
-}
-
-func (this *allTestSpecsSingleton) MakeVars(ctx android.MakeVarsContext) {
-	ctx.DistForGoal("test_specs", this.outputPath)
-}
diff --git a/testing/code_metadata.go b/testing/code_metadata.go
deleted file mode 100644
index 11ba430..0000000
--- a/testing/code_metadata.go
+++ /dev/null
@@ -1,137 +0,0 @@
-// Copyright 2020 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 testing
-
-import (
-	"path/filepath"
-
-	"android/soong/android"
-	"android/soong/testing/code_metadata_internal_proto"
-	"github.com/google/blueprint"
-
-	"google.golang.org/protobuf/proto"
-)
-
-func CodeMetadataFactory() android.Module {
-	module := &CodeMetadataModule{}
-
-	android.InitAndroidModule(module)
-	android.InitDefaultableModule(module)
-	module.AddProperties(&module.properties)
-
-	return module
-}
-
-type CodeMetadataModule struct {
-	android.ModuleBase
-	android.DefaultableModuleBase
-
-	// Properties for "code_metadata"
-	properties struct {
-		// Specifies the name of the code_config.
-		Name string
-		// Specifies the team ID.
-		TeamId string
-		// Specifies the list of modules that this code_metadata covers.
-		Code []string
-		// An optional field to specify if multiple ownerships for source files is allowed.
-		MultiOwnership bool
-	}
-}
-
-type codeDepTagType struct {
-	blueprint.BaseDependencyTag
-}
-
-var codeDepTag = codeDepTagType{}
-
-func (module *CodeMetadataModule) DepsMutator(ctx android.BottomUpMutatorContext) {
-	// Validate Properties
-	if len(module.properties.TeamId) == 0 {
-		ctx.PropertyErrorf(
-			"TeamId",
-			"Team Id not found in the code_metadata module. Hint: Maybe the teamId property hasn't been properly specified.",
-		)
-	}
-	if !isInt(module.properties.TeamId) {
-		ctx.PropertyErrorf(
-			"TeamId", "Invalid value for Team ID. The Team ID must be an integer.",
-		)
-	}
-	if len(module.properties.Code) == 0 {
-		ctx.PropertyErrorf(
-			"Code",
-			"Targets to be attributed cannot be empty. Hint: Maybe the code property hasn't been properly specified.",
-		)
-	}
-	ctx.AddDependency(ctx.Module(), codeDepTag, module.properties.Code...)
-}
-
-// Provider published by CodeMetadata
-type CodeMetadataProviderData struct {
-	IntermediatePath android.WritablePath
-}
-
-var CodeMetadataProviderKey = blueprint.NewProvider[CodeMetadataProviderData]()
-
-func (module *CodeMetadataModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	metadataList := make(
-		[]*code_metadata_internal_proto.CodeMetadataInternal_TargetOwnership, 0,
-		len(module.properties.Code),
-	)
-	bpFilePath := filepath.Join(ctx.ModuleDir(), ctx.BlueprintsFile())
-
-	for _, m := range ctx.GetDirectDepsWithTag(codeDepTag) {
-		targetName := m.Name()
-		var moduleSrcs []string
-		if srcsFileInfo, ok := android.OtherModuleProvider(ctx, m, blueprint.SrcsFileProviderKey); ok {
-			moduleSrcs = srcsFileInfo.SrcPaths
-		}
-		if module.properties.MultiOwnership {
-			metadata := &code_metadata_internal_proto.CodeMetadataInternal_TargetOwnership{
-				TargetName:     &targetName,
-				TrendyTeamId:   &module.properties.TeamId,
-				Path:           &bpFilePath,
-				MultiOwnership: &module.properties.MultiOwnership,
-				SourceFiles:    moduleSrcs,
-			}
-			metadataList = append(metadataList, metadata)
-		} else {
-			metadata := &code_metadata_internal_proto.CodeMetadataInternal_TargetOwnership{
-				TargetName:   &targetName,
-				TrendyTeamId: &module.properties.TeamId,
-				Path:         &bpFilePath,
-				SourceFiles:  moduleSrcs,
-			}
-			metadataList = append(metadataList, metadata)
-		}
-
-	}
-	codeMetadata := &code_metadata_internal_proto.CodeMetadataInternal{TargetOwnershipList: metadataList}
-	protoData, err := proto.Marshal(codeMetadata)
-	if err != nil {
-		ctx.ModuleErrorf("Error marshaling code metadata: %s", err.Error())
-		return
-	}
-	intermediatePath := android.PathForModuleOut(
-		ctx, "intermediateCodeMetadata.pb",
-	)
-	android.WriteFileRuleVerbatim(ctx, intermediatePath, string(protoData))
-
-	android.SetProvider(ctx,
-		CodeMetadataProviderKey,
-		CodeMetadataProviderData{IntermediatePath: intermediatePath},
-	)
-}
diff --git a/testing/code_metadata_internal_proto/OWNERS b/testing/code_metadata_internal_proto/OWNERS
deleted file mode 100644
index 03bcdf1..0000000
--- a/testing/code_metadata_internal_proto/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-dariofreni@google.com
-joeo@google.com
-ronish@google.com
-caditya@google.com
diff --git a/testing/code_metadata_internal_proto/code_metadata_internal.pb.go b/testing/code_metadata_internal_proto/code_metadata_internal.pb.go
deleted file mode 100644
index ecb8b86..0000000
--- a/testing/code_metadata_internal_proto/code_metadata_internal.pb.go
+++ /dev/null
@@ -1,277 +0,0 @@
-// 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.30.0
-// 	protoc        v3.21.12
-// source: code_metadata_internal.proto
-
-package code_metadata_internal_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 CodeMetadataInternal struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	// List of all code targets and their metadata.
-	TargetOwnershipList []*CodeMetadataInternal_TargetOwnership `protobuf:"bytes,1,rep,name=target_ownership_list,json=targetOwnershipList" json:"target_ownership_list,omitempty"`
-}
-
-func (x *CodeMetadataInternal) Reset() {
-	*x = CodeMetadataInternal{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_code_metadata_internal_proto_msgTypes[0]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *CodeMetadataInternal) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*CodeMetadataInternal) ProtoMessage() {}
-
-func (x *CodeMetadataInternal) ProtoReflect() protoreflect.Message {
-	mi := &file_code_metadata_internal_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 CodeMetadataInternal.ProtoReflect.Descriptor instead.
-func (*CodeMetadataInternal) Descriptor() ([]byte, []int) {
-	return file_code_metadata_internal_proto_rawDescGZIP(), []int{0}
-}
-
-func (x *CodeMetadataInternal) GetTargetOwnershipList() []*CodeMetadataInternal_TargetOwnership {
-	if x != nil {
-		return x.TargetOwnershipList
-	}
-	return nil
-}
-
-type CodeMetadataInternal_TargetOwnership struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	// REQUIRED: Name of the build target
-	TargetName *string `protobuf:"bytes,1,opt,name=target_name,json=targetName" json:"target_name,omitempty"`
-	// REQUIRED: Code location of the target.
-	// To be used to support legacy/backup systems that use OWNERS file and is
-	// also required for our dashboard to support per code location basis UI
-	Path *string `protobuf:"bytes,2,opt,name=path" json:"path,omitempty"`
-	// REQUIRED: Team ID of the team that owns this target.
-	TrendyTeamId *string `protobuf:"bytes,3,opt,name=trendy_team_id,json=trendyTeamId" json:"trendy_team_id,omitempty"`
-	// OPTIONAL: The src files of the target.
-	// To be used to determine ownership of a file for ownership
-	SourceFiles []string `protobuf:"bytes,4,rep,name=source_files,json=sourceFiles" json:"source_files,omitempty"`
-	// OPTIONAL: Specify if multiple ownerships of the source files are allowed.
-	MultiOwnership *bool `protobuf:"varint,5,opt,name=multi_ownership,json=multiOwnership" json:"multi_ownership,omitempty"`
-}
-
-func (x *CodeMetadataInternal_TargetOwnership) Reset() {
-	*x = CodeMetadataInternal_TargetOwnership{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_code_metadata_internal_proto_msgTypes[1]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *CodeMetadataInternal_TargetOwnership) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*CodeMetadataInternal_TargetOwnership) ProtoMessage() {}
-
-func (x *CodeMetadataInternal_TargetOwnership) ProtoReflect() protoreflect.Message {
-	mi := &file_code_metadata_internal_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 CodeMetadataInternal_TargetOwnership.ProtoReflect.Descriptor instead.
-func (*CodeMetadataInternal_TargetOwnership) Descriptor() ([]byte, []int) {
-	return file_code_metadata_internal_proto_rawDescGZIP(), []int{0, 0}
-}
-
-func (x *CodeMetadataInternal_TargetOwnership) GetTargetName() string {
-	if x != nil && x.TargetName != nil {
-		return *x.TargetName
-	}
-	return ""
-}
-
-func (x *CodeMetadataInternal_TargetOwnership) GetPath() string {
-	if x != nil && x.Path != nil {
-		return *x.Path
-	}
-	return ""
-}
-
-func (x *CodeMetadataInternal_TargetOwnership) GetTrendyTeamId() string {
-	if x != nil && x.TrendyTeamId != nil {
-		return *x.TrendyTeamId
-	}
-	return ""
-}
-
-func (x *CodeMetadataInternal_TargetOwnership) GetSourceFiles() []string {
-	if x != nil {
-		return x.SourceFiles
-	}
-	return nil
-}
-
-func (x *CodeMetadataInternal_TargetOwnership) GetMultiOwnership() bool {
-	if x != nil && x.MultiOwnership != nil {
-		return *x.MultiOwnership
-	}
-	return false
-}
-
-var File_code_metadata_internal_proto protoreflect.FileDescriptor
-
-var file_code_metadata_internal_proto_rawDesc = []byte{
-	0x0a, 0x1c, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f,
-	0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1c,
-	0x63, 0x6f, 0x64, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x69, 0x6e,
-	0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc9, 0x02, 0x0a,
-	0x14, 0x43, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x49, 0x6e, 0x74,
-	0x65, 0x72, 0x6e, 0x61, 0x6c, 0x12, 0x76, 0x0a, 0x15, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f,
-	0x6f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x01,
-	0x20, 0x03, 0x28, 0x0b, 0x32, 0x42, 0x2e, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61,
-	0x64, 0x61, 0x74, 0x61, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x72,
-	0x6f, 0x74, 0x6f, 0x2e, 0x43, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
-	0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4f,
-	0x77, 0x6e, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x52, 0x13, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74,
-	0x4f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x4c, 0x69, 0x73, 0x74, 0x1a, 0xb8, 0x01,
-	0x0a, 0x0f, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x68, 0x69,
-	0x70, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4e, 0x61,
-	0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x24, 0x0a, 0x0e, 0x74, 0x72, 0x65, 0x6e, 0x64, 0x79,
-	0x5f, 0x74, 0x65, 0x61, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c,
-	0x74, 0x72, 0x65, 0x6e, 0x64, 0x79, 0x54, 0x65, 0x61, 0x6d, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c,
-	0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x04, 0x20, 0x03,
-	0x28, 0x09, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x12,
-	0x27, 0x0a, 0x0f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x68,
-	0x69, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x4f,
-	0x77, 0x6e, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x42, 0x34, 0x5a, 0x32, 0x61, 0x6e, 0x64, 0x72,
-	0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e,
-	0x67, 0x2f, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f,
-	0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-}
-
-var (
-	file_code_metadata_internal_proto_rawDescOnce sync.Once
-	file_code_metadata_internal_proto_rawDescData = file_code_metadata_internal_proto_rawDesc
-)
-
-func file_code_metadata_internal_proto_rawDescGZIP() []byte {
-	file_code_metadata_internal_proto_rawDescOnce.Do(func() {
-		file_code_metadata_internal_proto_rawDescData = protoimpl.X.CompressGZIP(file_code_metadata_internal_proto_rawDescData)
-	})
-	return file_code_metadata_internal_proto_rawDescData
-}
-
-var file_code_metadata_internal_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
-var file_code_metadata_internal_proto_goTypes = []interface{}{
-	(*CodeMetadataInternal)(nil),                 // 0: code_metadata_internal_proto.CodeMetadataInternal
-	(*CodeMetadataInternal_TargetOwnership)(nil), // 1: code_metadata_internal_proto.CodeMetadataInternal.TargetOwnership
-}
-var file_code_metadata_internal_proto_depIdxs = []int32{
-	1, // 0: code_metadata_internal_proto.CodeMetadataInternal.target_ownership_list:type_name -> code_metadata_internal_proto.CodeMetadataInternal.TargetOwnership
-	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_code_metadata_internal_proto_init() }
-func file_code_metadata_internal_proto_init() {
-	if File_code_metadata_internal_proto != nil {
-		return
-	}
-	if !protoimpl.UnsafeEnabled {
-		file_code_metadata_internal_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*CodeMetadataInternal); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_code_metadata_internal_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*CodeMetadataInternal_TargetOwnership); 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_code_metadata_internal_proto_rawDesc,
-			NumEnums:      0,
-			NumMessages:   2,
-			NumExtensions: 0,
-			NumServices:   0,
-		},
-		GoTypes:           file_code_metadata_internal_proto_goTypes,
-		DependencyIndexes: file_code_metadata_internal_proto_depIdxs,
-		MessageInfos:      file_code_metadata_internal_proto_msgTypes,
-	}.Build()
-	File_code_metadata_internal_proto = out.File
-	file_code_metadata_internal_proto_rawDesc = nil
-	file_code_metadata_internal_proto_goTypes = nil
-	file_code_metadata_internal_proto_depIdxs = nil
-}
diff --git a/testing/code_metadata_internal_proto/code_metadata_internal.proto b/testing/code_metadata_internal_proto/code_metadata_internal.proto
deleted file mode 100644
index 14edc0f..0000000
--- a/testing/code_metadata_internal_proto/code_metadata_internal.proto
+++ /dev/null
@@ -1,40 +0,0 @@
-// 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 code_metadata_internal_proto;
-option go_package = "android/soong/testing/code_metadata_internal_proto";
-
-message CodeMetadataInternal {
-
-  message TargetOwnership {
-    // REQUIRED: Name of the build target
-    optional string target_name = 1;
-
-    // REQUIRED: Code location of the target.
-    // To be used to support legacy/backup systems that use OWNERS file and is
-    // also required for our dashboard to support per code location basis UI
-    optional string path = 2;
-
-    // REQUIRED: Team ID of the team that owns this target.
-    optional string trendy_team_id = 3;
-
-    // OPTIONAL: The src files of the target.
-    // To be used to determine ownership of a file for ownership
-    repeated string source_files = 4;
-
-    // OPTIONAL: Specify if multiple ownerships of the source files are allowed.
-    optional bool multi_ownership = 5;
-  }
-
-  // List of all code targets and their metadata.
-  repeated TargetOwnership target_ownership_list = 1;
-}
diff --git a/testing/code_metadata_internal_proto/regen.sh b/testing/code_metadata_internal_proto/regen.sh
deleted file mode 100644
index f101a02..0000000
--- a/testing/code_metadata_internal_proto/regen.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/bash
-
-aprotoc --go_out=paths=source_relative:. code_metadata_internal.proto
diff --git a/testing/code_metadata_proto/Android.bp b/testing/code_metadata_proto/Android.bp
deleted file mode 100644
index ae41d4a..0000000
--- a/testing/code_metadata_proto/Android.bp
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2022 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-testing-code_metadata_proto",
-    pkgPath: "android/soong/testing/code_metadata_proto",
-    deps: [
-        "golang-protobuf-reflect-protoreflect",
-        "golang-protobuf-runtime-protoimpl",
-    ],
-    srcs: [
-        "code_metadata.pb.go",
-    ],
-    visibility: ["//build/make/tools/metadata"],
-}
-
-python_library_host {
-    name: "code-metadata-proto-py",
-    pkg_path: "code_metadata",
-    srcs: [
-        "code_metadata.proto",
-    ],
-    libs: [
-        "libprotobuf-python",
-    ],
-    proto: {
-        canonical_path_from_root: false,
-    },
-    visibility: ["//tools/asuite/team_build_scripts"],
-}
diff --git a/testing/code_metadata_proto/OWNERS b/testing/code_metadata_proto/OWNERS
deleted file mode 100644
index 03bcdf1..0000000
--- a/testing/code_metadata_proto/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-dariofreni@google.com
-joeo@google.com
-ronish@google.com
-caditya@google.com
diff --git a/testing/code_metadata_proto/code_metadata.pb.go b/testing/code_metadata_proto/code_metadata.pb.go
deleted file mode 100644
index 711bf7a..0000000
--- a/testing/code_metadata_proto/code_metadata.pb.go
+++ /dev/null
@@ -1,263 +0,0 @@
-// 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.30.0
-// 	protoc        v3.21.12
-// source: code_metadata.proto
-
-package code_metadata_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 CodeMetadata struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	// List of all code targets and their metadata.
-	TargetOwnershipList []*CodeMetadata_TargetOwnership `protobuf:"bytes,1,rep,name=target_ownership_list,json=targetOwnershipList" json:"target_ownership_list,omitempty"`
-}
-
-func (x *CodeMetadata) Reset() {
-	*x = CodeMetadata{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_code_metadata_proto_msgTypes[0]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *CodeMetadata) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*CodeMetadata) ProtoMessage() {}
-
-func (x *CodeMetadata) ProtoReflect() protoreflect.Message {
-	mi := &file_code_metadata_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 CodeMetadata.ProtoReflect.Descriptor instead.
-func (*CodeMetadata) Descriptor() ([]byte, []int) {
-	return file_code_metadata_proto_rawDescGZIP(), []int{0}
-}
-
-func (x *CodeMetadata) GetTargetOwnershipList() []*CodeMetadata_TargetOwnership {
-	if x != nil {
-		return x.TargetOwnershipList
-	}
-	return nil
-}
-
-type CodeMetadata_TargetOwnership struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	// REQUIRED: Name of the build target
-	TargetName *string `protobuf:"bytes,1,opt,name=target_name,json=targetName" json:"target_name,omitempty"`
-	// REQUIRED: Code location of the target.
-	// To be used to support legacy/backup systems that use OWNERS file and is
-	// also required for our dashboard to support per code location basis UI
-	Path *string `protobuf:"bytes,2,opt,name=path" json:"path,omitempty"`
-	// REQUIRED: Team ID of the team that owns this target.
-	TrendyTeamId *string `protobuf:"bytes,3,opt,name=trendy_team_id,json=trendyTeamId" json:"trendy_team_id,omitempty"`
-	// OPTIONAL: The src files of the target.
-	// To be used to determine ownership of a file for ownership
-	SourceFiles []string `protobuf:"bytes,4,rep,name=source_files,json=sourceFiles" json:"source_files,omitempty"`
-}
-
-func (x *CodeMetadata_TargetOwnership) Reset() {
-	*x = CodeMetadata_TargetOwnership{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_code_metadata_proto_msgTypes[1]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *CodeMetadata_TargetOwnership) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*CodeMetadata_TargetOwnership) ProtoMessage() {}
-
-func (x *CodeMetadata_TargetOwnership) ProtoReflect() protoreflect.Message {
-	mi := &file_code_metadata_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 CodeMetadata_TargetOwnership.ProtoReflect.Descriptor instead.
-func (*CodeMetadata_TargetOwnership) Descriptor() ([]byte, []int) {
-	return file_code_metadata_proto_rawDescGZIP(), []int{0, 0}
-}
-
-func (x *CodeMetadata_TargetOwnership) GetTargetName() string {
-	if x != nil && x.TargetName != nil {
-		return *x.TargetName
-	}
-	return ""
-}
-
-func (x *CodeMetadata_TargetOwnership) GetPath() string {
-	if x != nil && x.Path != nil {
-		return *x.Path
-	}
-	return ""
-}
-
-func (x *CodeMetadata_TargetOwnership) GetTrendyTeamId() string {
-	if x != nil && x.TrendyTeamId != nil {
-		return *x.TrendyTeamId
-	}
-	return ""
-}
-
-func (x *CodeMetadata_TargetOwnership) GetSourceFiles() []string {
-	if x != nil {
-		return x.SourceFiles
-	}
-	return nil
-}
-
-var File_code_metadata_proto protoreflect.FileDescriptor
-
-var file_code_metadata_proto_rawDesc = []byte{
-	0x0a, 0x13, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x13, 0x63, 0x6f, 0x64, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61,
-	0x64, 0x61, 0x74, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x87, 0x02, 0x0a, 0x0c, 0x43,
-	0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x65, 0x0a, 0x15, 0x74,
-	0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x5f,
-	0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 0x2e, 0x63, 0x6f, 0x64,
-	0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-	0x2e, 0x43, 0x6f, 0x64, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x54, 0x61,
-	0x72, 0x67, 0x65, 0x74, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x52, 0x13, 0x74,
-	0x61, 0x72, 0x67, 0x65, 0x74, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x4c, 0x69,
-	0x73, 0x74, 0x1a, 0x8f, 0x01, 0x0a, 0x0f, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4f, 0x77, 0x6e,
-	0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74,
-	0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x74, 0x61, 0x72,
-	0x67, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x24, 0x0a, 0x0e, 0x74,
-	0x72, 0x65, 0x6e, 0x64, 0x79, 0x5f, 0x74, 0x65, 0x61, 0x6d, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x72, 0x65, 0x6e, 0x64, 0x79, 0x54, 0x65, 0x61, 0x6d, 0x49,
-	0x64, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x66, 0x69, 0x6c, 0x65,
-	0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x46,
-	0x69, 0x6c, 0x65, 0x73, 0x42, 0x2b, 0x5a, 0x29, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f,
-	0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x63, 0x6f,
-	0x64, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x74,
-	0x6f,
-}
-
-var (
-	file_code_metadata_proto_rawDescOnce sync.Once
-	file_code_metadata_proto_rawDescData = file_code_metadata_proto_rawDesc
-)
-
-func file_code_metadata_proto_rawDescGZIP() []byte {
-	file_code_metadata_proto_rawDescOnce.Do(func() {
-		file_code_metadata_proto_rawDescData = protoimpl.X.CompressGZIP(file_code_metadata_proto_rawDescData)
-	})
-	return file_code_metadata_proto_rawDescData
-}
-
-var file_code_metadata_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
-var file_code_metadata_proto_goTypes = []interface{}{
-	(*CodeMetadata)(nil),                 // 0: code_metadata_proto.CodeMetadata
-	(*CodeMetadata_TargetOwnership)(nil), // 1: code_metadata_proto.CodeMetadata.TargetOwnership
-}
-var file_code_metadata_proto_depIdxs = []int32{
-	1, // 0: code_metadata_proto.CodeMetadata.target_ownership_list:type_name -> code_metadata_proto.CodeMetadata.TargetOwnership
-	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_code_metadata_proto_init() }
-func file_code_metadata_proto_init() {
-	if File_code_metadata_proto != nil {
-		return
-	}
-	if !protoimpl.UnsafeEnabled {
-		file_code_metadata_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*CodeMetadata); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_code_metadata_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*CodeMetadata_TargetOwnership); 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_code_metadata_proto_rawDesc,
-			NumEnums:      0,
-			NumMessages:   2,
-			NumExtensions: 0,
-			NumServices:   0,
-		},
-		GoTypes:           file_code_metadata_proto_goTypes,
-		DependencyIndexes: file_code_metadata_proto_depIdxs,
-		MessageInfos:      file_code_metadata_proto_msgTypes,
-	}.Build()
-	File_code_metadata_proto = out.File
-	file_code_metadata_proto_rawDesc = nil
-	file_code_metadata_proto_goTypes = nil
-	file_code_metadata_proto_depIdxs = nil
-}
diff --git a/testing/code_metadata_proto/code_metadata.proto b/testing/code_metadata_proto/code_metadata.proto
deleted file mode 100644
index 2548363..0000000
--- a/testing/code_metadata_proto/code_metadata.proto
+++ /dev/null
@@ -1,37 +0,0 @@
-// 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 code_metadata_proto;
-option go_package = "android/soong/testing/code_metadata_proto";
-
-message CodeMetadata {
-
-  message TargetOwnership {
-    // REQUIRED: Name of the build target
-    optional string target_name = 1;
-
-    // REQUIRED: Code location of the target.
-    // To be used to support legacy/backup systems that use OWNERS file and is
-    // also required for our dashboard to support per code location basis UI
-    optional string path = 2;
-
-    // REQUIRED: Team ID of the team that owns this target.
-    optional string trendy_team_id = 3;
-
-    // OPTIONAL: The src files of the target.
-    // To be used to determine ownership of a file for ownership
-    repeated string source_files = 4;
-  }
-
-  // List of all code targets and their metadata.
-  repeated TargetOwnership target_ownership_list = 1;
-}
diff --git a/testing/code_metadata_proto/regen.sh b/testing/code_metadata_proto/regen.sh
deleted file mode 100644
index ffe06f7..0000000
--- a/testing/code_metadata_proto/regen.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/bash
-
-aprotoc --go_out=paths=source_relative:. code_metadata.proto
diff --git a/testing/init.go b/testing/init.go
deleted file mode 100644
index edcbf59..0000000
--- a/testing/init.go
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright 2022 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 testing
-
-import (
-	"android/soong/android"
-)
-
-var (
-	pctx = android.NewPackageContext("android/soong/testing")
-)
-
-func init() {
-	RegisterBuildComponents(android.InitRegistrationContext)
-	pctx.HostBinToolVariable("metadata", "metadata")
-}
-
-func RegisterBuildComponents(ctx android.RegistrationContext) {
-	ctx.RegisterModuleType("code_metadata", CodeMetadataFactory)
-	ctx.RegisterModuleType("test_spec", TestSpecFactory)
-	ctx.RegisterParallelSingletonType("all_code_metadata", AllCodeMetadataFactory)
-	ctx.RegisterParallelSingletonType("all_test_specs", AllTestSpecsFactory)
-}
diff --git a/testing/test.go b/testing/test.go
deleted file mode 100644
index cd97a8f..0000000
--- a/testing/test.go
+++ /dev/null
@@ -1,21 +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 testing
-
-import (
-	"android/soong/android"
-)
-
-var PrepareForTestWithTestingBuildComponents = android.FixtureRegisterWithContext(RegisterBuildComponents)
diff --git a/testing/test_spec.go b/testing/test_spec.go
deleted file mode 100644
index 4d885c6..0000000
--- a/testing/test_spec.go
+++ /dev/null
@@ -1,127 +0,0 @@
-// Copyright 2020 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 testing
-
-import (
-	"path/filepath"
-	"strconv"
-
-	"android/soong/android"
-	"android/soong/testing/test_spec_proto"
-	"google.golang.org/protobuf/proto"
-
-	"github.com/google/blueprint"
-)
-
-// ErrTestModuleDataNotFound is the error message for missing test module provider data.
-const ErrTestModuleDataNotFound = "The module '%s' does not provide test specification data. Hint: This issue could arise if either the module is not a valid testing module or if it lacks the required 'TestModuleProviderKey' provider.\n"
-
-func TestSpecFactory() android.Module {
-	module := &TestSpecModule{}
-
-	android.InitAndroidModule(module)
-	android.InitDefaultableModule(module)
-	module.AddProperties(&module.properties)
-
-	return module
-}
-
-type TestSpecModule struct {
-	android.ModuleBase
-	android.DefaultableModuleBase
-
-	// Properties for "test_spec"
-	properties struct {
-		// Specifies the name of the test config.
-		Name string
-		// Specifies the team ID.
-		TeamId string
-		// Specifies the list of tests covered under this module.
-		Tests []string
-	}
-}
-
-type testsDepTagType struct {
-	blueprint.BaseDependencyTag
-}
-
-var testsDepTag = testsDepTagType{}
-
-func (module *TestSpecModule) DepsMutator(ctx android.BottomUpMutatorContext) {
-	// Validate Properties
-	if len(module.properties.TeamId) == 0 {
-		ctx.PropertyErrorf("TeamId", "Team Id not found in the test_spec module. Hint: Maybe the TeamId property hasn't been properly specified.")
-	}
-	if !isInt(module.properties.TeamId) {
-		ctx.PropertyErrorf("TeamId", "Invalid value for Team ID. The Team ID must be an integer.")
-	}
-	if len(module.properties.Tests) == 0 {
-		ctx.PropertyErrorf("Tests", "Expected to attribute some test but none found. Hint: Maybe the test property hasn't been properly specified.")
-	}
-	ctx.AddDependency(ctx.Module(), testsDepTag, module.properties.Tests...)
-}
-func isInt(s string) bool {
-	_, err := strconv.Atoi(s)
-	return err == nil
-}
-
-// Provider published by TestSpec
-type TestSpecProviderData struct {
-	IntermediatePath android.WritablePath
-}
-
-var TestSpecProviderKey = blueprint.NewProvider[TestSpecProviderData]()
-
-type TestModuleProviderData struct {
-}
-
-var TestModuleProviderKey = blueprint.NewProvider[TestModuleProviderData]()
-
-func (module *TestSpecModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	for _, m := range ctx.GetDirectDepsWithTag(testsDepTag) {
-		if _, ok := android.OtherModuleProvider(ctx, m, TestModuleProviderKey); !ok {
-			ctx.ModuleErrorf(ErrTestModuleDataNotFound, m.Name())
-		}
-	}
-	bpFilePath := filepath.Join(ctx.ModuleDir(), ctx.BlueprintsFile())
-	metadataList := make(
-		[]*test_spec_proto.TestSpec_OwnershipMetadata, 0,
-		len(module.properties.Tests),
-	)
-	for _, test := range module.properties.Tests {
-		targetName := test
-		metadata := test_spec_proto.TestSpec_OwnershipMetadata{
-			TrendyTeamId: &module.properties.TeamId,
-			TargetName:   &targetName,
-			Path:         &bpFilePath,
-		}
-		metadataList = append(metadataList, &metadata)
-	}
-	intermediatePath := android.PathForModuleOut(
-		ctx, "intermediateTestSpecMetadata.pb",
-	)
-	testSpecMetadata := test_spec_proto.TestSpec{OwnershipMetadataList: metadataList}
-	protoData, err := proto.Marshal(&testSpecMetadata)
-	if err != nil {
-		ctx.ModuleErrorf("Error: %s", err.Error())
-	}
-	android.WriteFileRuleVerbatim(ctx, intermediatePath, string(protoData))
-
-	android.SetProvider(ctx,
-		TestSpecProviderKey, TestSpecProviderData{
-			IntermediatePath: intermediatePath,
-		},
-	)
-}
diff --git a/testing/test_spec_proto/Android.bp b/testing/test_spec_proto/Android.bp
deleted file mode 100644
index 1070d1a..0000000
--- a/testing/test_spec_proto/Android.bp
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright 2022 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-testing-test_spec_proto",
-    pkgPath: "android/soong/testing/test_spec_proto",
-    deps: [
-        "golang-protobuf-reflect-protoreflect",
-        "golang-protobuf-runtime-protoimpl",
-    ],
-    srcs: [
-        "test_spec.pb.go",
-    ],
-    visibility: [
-        "//build/make/tools/metadata",
-        "//build/soong:__subpackages__",
-        "//vendor:__subpackages__",
-    ],
-}
-
-python_library_host {
-    name: "test-spec-proto-py",
-    pkg_path: "test_spec",
-    srcs: [
-        "test_spec.proto",
-    ],
-    libs: [
-        "libprotobuf-python",
-    ],
-    proto: {
-        canonical_path_from_root: false,
-    },
-    visibility: ["//tools/asuite/team_build_scripts"],
-}
diff --git a/testing/test_spec_proto/OWNERS b/testing/test_spec_proto/OWNERS
deleted file mode 100644
index 03bcdf1..0000000
--- a/testing/test_spec_proto/OWNERS
+++ /dev/null
@@ -1,4 +0,0 @@
-dariofreni@google.com
-joeo@google.com
-ronish@google.com
-caditya@google.com
diff --git a/testing/test_spec_proto/regen.sh b/testing/test_spec_proto/regen.sh
deleted file mode 100644
index 2cf8203..0000000
--- a/testing/test_spec_proto/regen.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/bash
-
-aprotoc --go_out=paths=source_relative:. test_spec.proto
diff --git a/testing/test_spec_proto/test_spec.pb.go b/testing/test_spec_proto/test_spec.pb.go
deleted file mode 100644
index 5cce600..0000000
--- a/testing/test_spec_proto/test_spec.pb.go
+++ /dev/null
@@ -1,244 +0,0 @@
-// 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.30.0
-// 	protoc        v3.21.12
-// source: test_spec.proto
-
-package test_spec_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 TestSpec struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	// List of all test targets and their metadata.
-	OwnershipMetadataList []*TestSpec_OwnershipMetadata `protobuf:"bytes,1,rep,name=ownership_metadata_list,json=ownershipMetadataList" json:"ownership_metadata_list,omitempty"`
-}
-
-func (x *TestSpec) Reset() {
-	*x = TestSpec{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_test_spec_proto_msgTypes[0]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *TestSpec) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*TestSpec) ProtoMessage() {}
-
-func (x *TestSpec) ProtoReflect() protoreflect.Message {
-	mi := &file_test_spec_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 TestSpec.ProtoReflect.Descriptor instead.
-func (*TestSpec) Descriptor() ([]byte, []int) {
-	return file_test_spec_proto_rawDescGZIP(), []int{0}
-}
-
-func (x *TestSpec) GetOwnershipMetadataList() []*TestSpec_OwnershipMetadata {
-	if x != nil {
-		return x.OwnershipMetadataList
-	}
-	return nil
-}
-
-type TestSpec_OwnershipMetadata struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	TargetName   *string `protobuf:"bytes,1,opt,name=target_name,json=targetName" json:"target_name,omitempty"`
-	Path         *string `protobuf:"bytes,2,opt,name=path" json:"path,omitempty"`
-	TrendyTeamId *string `protobuf:"bytes,3,opt,name=trendy_team_id,json=trendyTeamId" json:"trendy_team_id,omitempty"`
-}
-
-func (x *TestSpec_OwnershipMetadata) Reset() {
-	*x = TestSpec_OwnershipMetadata{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_test_spec_proto_msgTypes[1]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *TestSpec_OwnershipMetadata) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*TestSpec_OwnershipMetadata) ProtoMessage() {}
-
-func (x *TestSpec_OwnershipMetadata) ProtoReflect() protoreflect.Message {
-	mi := &file_test_spec_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 TestSpec_OwnershipMetadata.ProtoReflect.Descriptor instead.
-func (*TestSpec_OwnershipMetadata) Descriptor() ([]byte, []int) {
-	return file_test_spec_proto_rawDescGZIP(), []int{0, 0}
-}
-
-func (x *TestSpec_OwnershipMetadata) GetTargetName() string {
-	if x != nil && x.TargetName != nil {
-		return *x.TargetName
-	}
-	return ""
-}
-
-func (x *TestSpec_OwnershipMetadata) GetPath() string {
-	if x != nil && x.Path != nil {
-		return *x.Path
-	}
-	return ""
-}
-
-func (x *TestSpec_OwnershipMetadata) GetTrendyTeamId() string {
-	if x != nil && x.TrendyTeamId != nil {
-		return *x.TrendyTeamId
-	}
-	return ""
-}
-
-var File_test_spec_proto protoreflect.FileDescriptor
-
-var file_test_spec_proto_rawDesc = []byte{
-	0x0a, 0x0f, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74,
-	0x6f, 0x12, 0x0f, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x5f, 0x70, 0x72, 0x6f,
-	0x74, 0x6f, 0x22, 0xdf, 0x01, 0x0a, 0x08, 0x54, 0x65, 0x73, 0x74, 0x53, 0x70, 0x65, 0x63, 0x12,
-	0x63, 0x0a, 0x17, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x5f, 0x6d, 0x65, 0x74,
-	0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b,
-	0x32, 0x2b, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x5f, 0x70, 0x72, 0x6f,
-	0x74, 0x6f, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x53, 0x70, 0x65, 0x63, 0x2e, 0x4f, 0x77, 0x6e, 0x65,
-	0x72, 0x73, 0x68, 0x69, 0x70, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x15, 0x6f,
-	0x77, 0x6e, 0x65, 0x72, 0x73, 0x68, 0x69, 0x70, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
-	0x4c, 0x69, 0x73, 0x74, 0x1a, 0x6e, 0x0a, 0x11, 0x4f, 0x77, 0x6e, 0x65, 0x72, 0x73, 0x68, 0x69,
-	0x70, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x61, 0x72,
-	0x67, 0x65, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a,
-	0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61,
-	0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x24,
-	0x0a, 0x0e, 0x74, 0x72, 0x65, 0x6e, 0x64, 0x79, 0x5f, 0x74, 0x65, 0x61, 0x6d, 0x5f, 0x69, 0x64,
-	0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x72, 0x65, 0x6e, 0x64, 0x79, 0x54, 0x65,
-	0x61, 0x6d, 0x49, 0x64, 0x42, 0x27, 0x5a, 0x25, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f,
-	0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2f, 0x74, 0x65,
-	0x73, 0x74, 0x5f, 0x73, 0x70, 0x65, 0x63, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
-}
-
-var (
-	file_test_spec_proto_rawDescOnce sync.Once
-	file_test_spec_proto_rawDescData = file_test_spec_proto_rawDesc
-)
-
-func file_test_spec_proto_rawDescGZIP() []byte {
-	file_test_spec_proto_rawDescOnce.Do(func() {
-		file_test_spec_proto_rawDescData = protoimpl.X.CompressGZIP(file_test_spec_proto_rawDescData)
-	})
-	return file_test_spec_proto_rawDescData
-}
-
-var file_test_spec_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
-var file_test_spec_proto_goTypes = []interface{}{
-	(*TestSpec)(nil),                   // 0: test_spec_proto.TestSpec
-	(*TestSpec_OwnershipMetadata)(nil), // 1: test_spec_proto.TestSpec.OwnershipMetadata
-}
-var file_test_spec_proto_depIdxs = []int32{
-	1, // 0: test_spec_proto.TestSpec.ownership_metadata_list:type_name -> test_spec_proto.TestSpec.OwnershipMetadata
-	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_test_spec_proto_init() }
-func file_test_spec_proto_init() {
-	if File_test_spec_proto != nil {
-		return
-	}
-	if !protoimpl.UnsafeEnabled {
-		file_test_spec_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*TestSpec); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_test_spec_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*TestSpec_OwnershipMetadata); 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_test_spec_proto_rawDesc,
-			NumEnums:      0,
-			NumMessages:   2,
-			NumExtensions: 0,
-			NumServices:   0,
-		},
-		GoTypes:           file_test_spec_proto_goTypes,
-		DependencyIndexes: file_test_spec_proto_depIdxs,
-		MessageInfos:      file_test_spec_proto_msgTypes,
-	}.Build()
-	File_test_spec_proto = out.File
-	file_test_spec_proto_rawDesc = nil
-	file_test_spec_proto_goTypes = nil
-	file_test_spec_proto_depIdxs = nil
-}
diff --git a/testing/test_spec_proto/test_spec.proto b/testing/test_spec_proto/test_spec.proto
deleted file mode 100644
index 86bc789..0000000
--- a/testing/test_spec_proto/test_spec.proto
+++ /dev/null
@@ -1,33 +0,0 @@
-// 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 test_spec_proto;
-option go_package = "android/soong/testing/test_spec_proto";
-
-message TestSpec {
-
-  message OwnershipMetadata {
-    // REQUIRED: Name of the build target
-    optional string target_name = 1;
-
-    // REQUIRED: Code location of the target.
-    // To be used to support legacy/backup systems that use OWNERS file and is
-    // also required for our dashboard to support per code location basis UI
-    optional string path = 2;
-
-    // REQUIRED: Team ID of the team that owns this target.
-    optional string trendy_team_id = 3;
-  }
-
-  // List of all test targets and their metadata.
-  repeated OwnershipMetadata ownership_metadata_list = 1;
-}
diff --git a/tests/b_args_test.sh b/tests/b_args_test.sh
deleted file mode 100755
index 0dfbabf..0000000
--- a/tests/b_args_test.sh
+++ /dev/null
@@ -1,43 +0,0 @@
-#!/bin/bash -eu
-
-# This file tests the creation of bazel commands for b usage
-set -o pipefail
-source "$(dirname "$0")/../../bazel/lib.sh"
-
-BES_UUID="blank"
-OUT_DIR="arbitrary_out"
-b_args=$(formulate_b_args "build --config=nonsense foo:bar")
-
-if [[ $b_args != "build --profile=$OUT_DIR/bazel_metrics-profile --config=bp2build --invocation_id=$BES_UUID --config=metrics_data --config=nonsense foo:bar" ]]; then
-   echo "b args are malformed"
-   echo "Expected : build --profile=$OUT_DIR/bazel_metrics-profile --config=bp2build  --invocation_id=$BES_UUID --config=metrics_data --config=nonsense foo:bar"
-   echo "Actual: $b_args"
-   exit 1
-fi
-
-b_args=$(formulate_b_args "build --config=nonsense --disable_bes --package_path \"my package\" foo:bar")
-
-if [[ $b_args != "build --profile=$OUT_DIR/bazel_metrics-profile --config=bp2build --invocation_id=$BES_UUID --config=nonsense --package_path \"my package\" foo:bar" ]]; then
-   echo "b args are malformed"
-   echo "Expected : build --profile=$OUT_DIR/bazel_metrics-profile --config=bp2build  --invocation_id=$BES_UUID --config=nonsense --package_path \"my package\" foo:bar"
-   echo "Actual: $b_args"
-   exit 1
-fi
-
-# Test with startup option
-b_args=$(formulate_b_args "--batch build --config=nonsense --disable_bes --package_path \"my package\" foo:bar")
-if [[ $b_args != "--batch build --profile=$OUT_DIR/bazel_metrics-profile --config=bp2build --invocation_id=$BES_UUID --config=nonsense --package_path \"my package\" foo:bar" ]]; then
-   echo "b args are malformed"
-   echo "Expected : --batch build --profile=$OUT_DIR/bazel_metrics-profile --config=bp2build  --invocation_id=$BES_UUID --config=nonsense --package_path \"my package\" foo:bar"
-   echo "Actual: $b_args"
-   exit 1
-fi
-
-OUT_DIR="mock_out"
-TEST_PROFILE_OUT=$(get_profile_out_dir)
-if [[ $TEST_PROFILE_OUT != "mock_out" ]]; then
-   echo "Profile Out is malformed."
-   echo "Expected: mock_out"
-   echo "Actual: $TEST_PROFILE_OUT"
-   exit 1
-fi
diff --git a/tests/bootstrap_test.sh b/tests/bootstrap_test.sh
index 715f976..5a660e9 100755
--- a/tests/bootstrap_test.sh
+++ b/tests/bootstrap_test.sh
@@ -577,20 +577,6 @@
 
 }
 
-function test_queryview_null_build() {
-  setup
-
-  run_soong queryview
-  local -r output_mtime1=$(stat -c "%y" out/soong/queryview.marker)
-
-  run_soong queryview
-  local -r output_mtime2=$(stat -c "%y" out/soong/queryview.marker)
-
-  if [[ "$output_mtime1" != "$output_mtime2" ]]; then
-    fail "Queryview marker file changed on null build"
-  fi
-}
-
 # This test verifies that adding a new glob to a blueprint file only
 # causes build."${target_product}".ninja to be regenerated on the *next* build, and *not*
 # the build after. (This is a regression test for a bug where globs
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/tests/sbom_test.sh b/tests/sbom_test.sh
index 0471853..2ef9e37 100755
--- a/tests/sbom_test.sh
+++ b/tests/sbom_test.sh
@@ -83,6 +83,12 @@
   dump_erofs=$out_dir/host/linux-x86/bin/dump.erofs
   lz4=$out_dir/host/linux-x86/bin/lz4
 
+  declare -A diff_excludes
+  diff_excludes[system]="\
+    -I /etc/NOTICE.xml.gz \
+    -I /odm_dlkm/etc \
+    -I /vendor_dlkm/etc"
+
   # Example output of dump.erofs is as below, and the data used in the test start
   # at line 11. Column 1 is inode id, column 2 is inode type and column 3 is name.
   # Each line is captured in variable "entry", awk is used to get type and name.
@@ -155,7 +161,11 @@
     sort -n -o "$files_in_soong_spdx_file" "$files_in_soong_spdx_file"
 
     echo ============ Diffing files in $f and SBOM created by Soong
-    diff_files "$file_list_file" "$files_in_soong_spdx_file" "$partition_name" ""
+    exclude=
+    if [ -v 'diff_excludes[$partition_name]' ]; then
+     exclude=${diff_excludes[$partition_name]}
+    fi
+    diff_files "$file_list_file" "$files_in_soong_spdx_file" "$partition_name" "$exclude"
   done
 
   RAMDISK_IMAGES="$product_out/ramdisk.img"
@@ -230,10 +240,10 @@
     exit 1
   fi
 
-  # PRODUCT and 7 prebuilt packages have "PackageLicenseDeclared: NOASSERTION"
+  # PRODUCT and 6 prebuilt packages have "PackageLicenseDeclared: NOASSERTION"
   # All other packages have declared licenses
   num_of_packages_with_noassertion_license=$(grep 'PackageLicenseDeclared: NOASSERTION' $sbom_file | wc -l)
-  if [ $num_of_packages_with_noassertion_license = 15 ]
+  if [ $num_of_packages_with_noassertion_license = 13 ]
   then
     echo "Number of packages with NOASSERTION license is correct."
   else
diff --git a/tradefed/autogen.go b/tradefed/autogen.go
index e230795..89c69bd 100644
--- a/tradefed/autogen.go
+++ b/tradefed/autogen.go
@@ -160,6 +160,7 @@
 	DeviceTemplate          string
 	HostTemplate            string
 	HostUnitTestTemplate    string
+	StandaloneTest          *bool
 }
 
 func AutoGenTestConfig(ctx android.ModuleContext, options AutoGenTestConfigOptions) android.Path {
@@ -178,6 +179,12 @@
 			autogenTemplate(ctx, name, autogenPath, templatePath.String(), configs, options.TestRunnerOptions, options.OutputFileName, options.TestInstallBase)
 		} else {
 			if ctx.Device() {
+				if Bool(options.StandaloneTest) {
+					options.TestRunnerOptions = append(options.TestRunnerOptions, Option{
+						Name:  "ld-library-path",
+						Value: "{TEST_INSTALL_BASE}/" + name + "/" + ctx.Arch().ArchType.String() + "/standalone-libs",
+					})
+				}
 				autogenTemplate(ctx, name, autogenPath, options.DeviceTemplate, configs, options.TestRunnerOptions, options.OutputFileName, options.TestInstallBase)
 			} else {
 				if Bool(options.UnitTest) {
@@ -190,7 +197,10 @@
 		return autogenPath
 	}
 	if len(options.OptionsForAutogenerated) > 0 {
-		ctx.ModuleErrorf("Extra tradefed configurations were provided for an autogenerated xml file, but the autogenerated xml file was not used.")
+		ctx.ModuleErrorf("You likely need to delete your soong modules local AndroidTest.xml file.  Extra tradefed configurations (%v) were provided for an autogenerated xml file, but the autogenerated xml file was not used.", options.OptionsForAutogenerated)
+	}
+	if len(options.TestRunnerOptions) > 0 {
+		ctx.ModuleErrorf("You likely need to delete your soong modules local AndroidTest.xml file.  Extra test runner options (%v) were provided for an autogenerated xml file, but the autogenerated xml file was not used.", options.TestRunnerOptions)
 	}
 	return path
 }
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 f76a152..302f9a9 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,21 +65,21 @@
 		android.FixtureRegisterWithContext(RegisterTestModuleConfigBuildComponents),
 	).RunTestWithBp(t, bp)
 
-	derived := ctx.ModuleForTests("derived_test", "android_common")
+	derived := ctx.ModuleForTests(t, "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")
 
 	// Ensure some basic rules exist.
-	ctx.ModuleForTests("base", "android_common").Output("package-res.apk")
+	ctx.ModuleForTests(t, "base", "android_common").Output("package-res.apk")
 	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/derived_test/arm64/base.apk",
-			"out/soong/target/product/test_device/testcases/derived_test/HelperApp.apk",
-			"out/soong/target/product/test_device/testcases/derived_test/data/testfile"},
+		[]string{"out/target/product/test_device/testcases/derived_test/arm64/base.apk",
+			"out/target/product/test_device/testcases/derived_test/HelperApp.apk",
+			"out/target/product/test_device/testcases/derived_test/data/testfile"},
 		entries.EntryMap["LOCAL_SOONG_INSTALLED_COMPATIBILITY_SUPPORT_FILES"])
 	android.AssertArrayString(t, "", entries.EntryMap["LOCAL_COMPATIBILITY_SUPPORT_FILES"], []string{})
 
@@ -88,21 +91,95 @@
 	// 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)
 	for i, e := range entries.FooterLinesForTests() {
 		// AssertStringPathsRelativeToTop doesn't replace both instances
-		convertedActual[i] = strings.Replace(e, ctx.Config.SoongOutDir(), "", 2)
+		convertedActual[i] = strings.Replace(e, ctx.Config.OutDir(), "", 2)
 	}
-	android.AssertArrayString(t, fmt.Sprintf("%s", ctx.Config.SoongOutDir()), convertedActual, []string{
+	android.AssertArrayString(t, fmt.Sprintf("%s", ctx.Config.OutDir()), []string{
 		"include $(BUILD_SYSTEM)/soong_app_prebuilt.mk",
 		"/target/product/test_device/testcases/derived_test/arm64/base.apk: /target/product/test_device/testcases/base/arm64/base.apk",
 		"/target/product/test_device/testcases/derived_test/HelperApp.apk: /target/product/test_device/testcases/base/HelperApp.apk",
 		"/target/product/test_device/testcases/derived_test/data/testfile: /target/product/test_device/testcases/base/data/testfile",
 		"",
-	})
+	}, convertedActual)
+}
+
+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(t, "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/target/product/test_device/testcases/conch/arm64/testdata/data1",
+			"out/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.OutDir(), "", 2)
+	}
+	android.AssertArrayString(t, fmt.Sprintf("%s", ctx.Config.OutDir()), []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",
+		"",
+	}, convertedActual)
+
 }
 
 // Make sure we call test-config-fixer with the right args.
@@ -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(t, "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"}]'`)
@@ -123,24 +200,24 @@
 // Ensure we error for a base we don't support.
 func TestModuleConfigWithHostBaseShouldFailWithExplicitMessage(t *testing.T) {
 	badBp := `
-		java_test_host {
-			name: "base",
-                        srcs: ["a.java"],
+        java_test {
+            name: "base",
+            srcs: ["a.java"],
 		}
 
-                test_module_config {
-                        name: "derived_test",
-                        base: "base",
-                        exclude_filters: ["android.test.example.devcodelab.DevCodelabTest#testHelloFail"],
-                        include_annotations: ["android.platform.test.annotations.LargeTest"],
-                        test_suites: ["general-tests"],
-                }`
+        test_module_config {
+            name: "derived_test",
+            base: "base",
+            exclude_filters: ["android.test.example.devcodelab.DevCodelabTest#testHelloFail"],
+            include_annotations: ["android.platform.test.annotations.LargeTest"],
+            test_suites: ["general-tests"],
+        }`
 
 	android.GroupFixturePreparers(
 		java.PrepareForTestWithJavaDefaultModules,
 		android.FixtureRegisterWithContext(RegisterTestModuleConfigBuildComponents),
 	).ExtendWithErrorHandler(
-		android.FixtureExpectsAtLeastOneErrorMatchingPattern("'java_test_host' module used as base, but 'android_test' expected")).
+		android.FixtureExpectsAtLeastOneErrorMatchingPattern("'base' module used as base but it is not a 'android_test' module.")).
 		RunTestWithBp(t, badBp)
 }
 
@@ -211,8 +288,7 @@
 	).ExtendWithErrorHandler(
 		android.FixtureExpectsAtLeastOneErrorMatchingPattern("Test options must be given")).
 		RunTestWithBp(t, badBp)
-
-	ctx.ModuleForTests("derived_test", "android_common")
+	ctx.ModuleForTests(t, "derived_test", variant)
 }
 
 func TestModuleConfigMultipleDerivedTestsWriteDistinctMakeEntries(t *testing.T) {
@@ -250,32 +326,33 @@
 	).RunTestWithBp(t, multiBp)
 
 	{
-		derived := ctx.ModuleForTests("derived_test", "android_common")
+		derived := ctx.ModuleForTests(t, "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,
-			[]string{"out/soong/target/product/test_device/testcases/derived_test/arm64/base.apk",
-				"out/soong/target/product/test_device/testcases/derived_test/HelperApp.apk",
-				"out/soong/target/product/test_device/testcases/derived_test/data/testfile"},
+			[]string{"out/target/product/test_device/testcases/derived_test/arm64/base.apk",
+				"out/target/product/test_device/testcases/derived_test/HelperApp.apk",
+				"out/target/product/test_device/testcases/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], "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(t, "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,
-			[]string{"out/soong/target/product/test_device/testcases/another_derived_test/arm64/base.apk",
-				"out/soong/target/product/test_device/testcases/another_derived_test/HelperApp.apk",
-				"out/soong/target/product/test_device/testcases/another_derived_test/data/testfile"},
+			[]string{"out/target/product/test_device/testcases/another_derived_test/arm64/base.apk",
+				"out/target/product/test_device/testcases/another_derived_test/HelperApp.apk",
+				"out/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"})
 	}
@@ -304,7 +381,7 @@
 	).RunTestWithBp(t, bp)
 
 	variant := ctx.Config.BuildOS.String() + "_common"
-	derived := ctx.ModuleForTests("derived_test", variant)
+	derived := ctx.ModuleForTests(t, "derived_test", variant)
 	mod := derived.Module().(*testModuleConfigHostModule)
 	allEntries := android.AndroidMkEntriesForTest(t, ctx.TestContext, mod)
 	entries := allEntries[0]
diff --git a/tradefed_modules/test_suite.go b/tradefed_modules/test_suite.go
new file mode 100644
index 0000000..8b7babf
--- /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: android.SortedUniqueStrings(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..3e1472c
--- /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(t, "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(t, "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/build/Android.bp b/ui/build/Android.bp
index fcf29c5..a868d6a 100644
--- a/ui/build/Android.bp
+++ b/ui/build/Android.bp
@@ -35,12 +35,13 @@
         "blueprint",
         "blueprint-bootstrap",
         "blueprint-microfactory",
-        "soong-android",
         "soong-elf",
         "soong-finder",
+        "soong-finder-fs",
         "soong-remoteexec",
         "soong-shared",
         "soong-ui-build-paths",
+        "soong-ui-execution-metrics",
         "soong-ui-logger",
         "soong-ui-metrics",
         "soong-ui-status",
@@ -54,6 +55,7 @@
         "config.go",
         "context.go",
         "staging_snapshot.go",
+        "source_inputs.go",
         "dumpvars.go",
         "environment.go",
         "exec.go",
diff --git a/ui/build/androidmk_denylist.go b/ui/build/androidmk_denylist.go
index 2ec8972..640a82d 100644
--- a/ui/build/androidmk_denylist.go
+++ b/ui/build/androidmk_denylist.go
@@ -29,6 +29,9 @@
 	"device/google_car/",
 	"device/sample/",
 	"frameworks/",
+	"hardware/libhardware/",
+	"hardware/libhardware_legacy/",
+	"hardware/ril/",
 	// Do not block other directories in kernel/, see b/319658303.
 	"kernel/configs/",
 	"kernel/prebuilts/",
@@ -37,8 +40,10 @@
 	"libnativehelper/",
 	"packages/",
 	"pdk/",
+	"platform_testing/",
 	"prebuilts/",
 	"sdk/",
+	"system/",
 	"test/",
 	"trusty/",
 	// Add back toolchain/ once defensive Android.mk files are removed
@@ -64,3 +69,44 @@
 		}
 	}
 }
+
+// The Android.mk files in these directories are for NDK build system.
+var external_ndk_androidmks []string = []string{
+	"external/fmtlib/",
+	"external/google-breakpad/",
+	"external/googletest/",
+	"external/libaom/",
+	"external/libusb/",
+	"external/libvpx/",
+	"external/libwebm/",
+	"external/libwebsockets/",
+	"external/vulkan-validation-layers/",
+	"external/walt/",
+	"external/webp/",
+}
+
+var art_androidmks = []string{
+	//"art/",
+}
+
+func ignoreSomeAndroidMks(androidMks []string) (filtered []string) {
+	ignore_androidmks := make([]string, 0, len(external_ndk_androidmks)+len(art_androidmks))
+	ignore_androidmks = append(ignore_androidmks, external_ndk_androidmks...)
+	ignore_androidmks = append(ignore_androidmks, art_androidmks...)
+
+	shouldKeep := func(androidmk string) bool {
+		for _, prefix := range ignore_androidmks {
+			if strings.HasPrefix(androidmk, prefix) {
+				return false
+			}
+		}
+		return true
+	}
+
+	for _, l := range androidMks {
+		if shouldKeep(l) {
+			filtered = append(filtered, l)
+		}
+	}
+	return
+}
diff --git a/ui/build/build.go b/ui/build/build.go
index 28c3284..781ca18 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -33,26 +33,13 @@
 	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "CleanSpec.mk"))
 	ensureEmptyDirectoriesExist(ctx, config.TempDir())
 
-	// Potentially write a marker file for whether kati is enabled. This is used by soong_build to
-	// potentially run the AndroidMk singleton and postinstall commands.
-	// Note that the absence of the  file does not not preclude running Kati for product
-	// configuration purposes.
-	katiEnabledMarker := filepath.Join(config.SoongOutDir(), ".soong.kati_enabled")
-	if config.SkipKatiNinja() {
-		os.Remove(katiEnabledMarker)
-		// Note that we can not remove the file for SkipKati builds yet -- some continuous builds
-		// --skip-make builds rely on kati targets being defined.
-	} else if !config.SkipKati() {
-		ensureEmptyFileExists(ctx, katiEnabledMarker)
-	}
-
 	// The ninja_build file is used by our buildbots to understand that the output
 	// can be parsed as ninja output.
 	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), "ninja_build"))
 	ensureEmptyFileExists(ctx, filepath.Join(config.OutDir(), ".out-dir"))
 
 	if buildDateTimeFile, ok := config.environ.Get("BUILD_DATETIME_FILE"); ok {
-		err := ioutil.WriteFile(buildDateTimeFile, []byte(config.buildDateTime), 0666) // a+rw
+		err := os.WriteFile(buildDateTimeFile, []byte(config.buildDateTime), 0666) // a+rw
 		if err != nil {
 			ctx.Fatalln("Failed to write BUILD_DATETIME to file:", err)
 		}
@@ -87,6 +74,32 @@
 	// without changing the command line every time.  Avoids rebuilds
 	// when using ninja.
 	writeValueIfChanged(ctx, config, config.SoongOutDir(), "build_number.txt", buildNumber)
+
+	hostname, ok := config.environ.Get("BUILD_HOSTNAME")
+	if !ok {
+		var err error
+		hostname, err = os.Hostname()
+		if err != nil {
+			ctx.Println("Failed to read hostname:", err)
+			hostname = "unknown"
+		}
+	}
+	writeValueIfChanged(ctx, config, config.SoongOutDir(), "build_hostname.txt", hostname)
+}
+
+// SetupKatiEnabledMarker creates or delets a file that tells soong_build if we're running with
+// kati.
+func SetupKatiEnabledMarker(ctx Context, config Config) {
+	// Potentially write a marker file for whether kati is enabled. This is used by soong_build to
+	// potentially run the AndroidMk singleton and postinstall commands.
+	// Note that the absence of the file does not preclude running Kati for product
+	// configuration purposes.
+	katiEnabledMarker := filepath.Join(config.SoongOutDir(), ".soong.kati_enabled")
+	if config.SkipKati() || config.SkipKatiNinja() {
+		os.Remove(katiEnabledMarker)
+	} else {
+		ensureEmptyFileExists(ctx, katiEnabledMarker)
+	}
 }
 
 var combinedBuildNinjaTemplate = template.Must(template.New("combined").Parse(`
@@ -96,8 +109,11 @@
 {{end -}}
 pool highmem_pool
  depth = {{.HighmemParallel}}
-{{if and (not .SkipKatiNinja) .HasKatiSuffix}}subninja {{.KatiBuildNinjaFile}}
+{{if and (not .SkipKatiNinja) .HasKatiSuffix}}
+subninja {{.KatiBuildNinjaFile}}
 subninja {{.KatiPackageNinjaFile}}
+{{else}}
+subninja {{.KatiSoongOnlyPackageNinjaFile}}
 {{end -}}
 subninja {{.SoongNinjaFile}}
 `))
@@ -315,10 +331,16 @@
 
 	if what&RunProductConfig != 0 {
 		runMakeProductConfig(ctx, config)
+
+		// Re-evaluate what to run because there are product variables that control how
+		// soong and make are run.
+		what = evaluateWhatToRun(config, ctx.Verboseln)
 	}
 
 	// Everything below here depends on product config.
 
+	SetupKatiEnabledMarker(ctx, config)
+
 	if inList("installclean", config.Arguments()) ||
 		inList("install-clean", config.Arguments()) {
 		logArgsOtherThan("installclean", "install-clean")
@@ -335,25 +357,31 @@
 		return
 	}
 
+	// Still generate the kati suffix in soong-only builds because soong-only still uses kati for
+	// the packaging step. Also, the kati suffix is used for the combined ninja file.
+	genKatiSuffix(ctx, config)
+
 	if what&RunSoong != 0 {
 		runSoong(ctx, config)
 	}
 
 	if what&RunKati != 0 {
-		genKatiSuffix(ctx, config)
 		runKatiCleanSpec(ctx, config)
 		runKatiBuild(ctx, config)
-		runKatiPackage(ctx, config)
+		runKatiPackage(ctx, config, false)
 
-		ioutil.WriteFile(config.LastKatiSuffixFile(), []byte(config.KatiSuffix()), 0666) // a+rw
 	} else if what&RunKatiNinja != 0 {
 		// Load last Kati Suffix if it exists
-		if katiSuffix, err := ioutil.ReadFile(config.LastKatiSuffixFile()); err == nil {
+		if katiSuffix, err := os.ReadFile(config.LastKatiSuffixFile()); err == nil {
 			ctx.Verboseln("Loaded previous kati config:", string(katiSuffix))
 			config.SetKatiSuffix(string(katiSuffix))
 		}
+	} else if what&RunSoong != 0 {
+		runKatiPackage(ctx, config, true)
 	}
 
+	os.WriteFile(config.LastKatiSuffixFile(), []byte(config.KatiSuffix()), 0666) // a+rw
+
 	// Write combined ninja file
 	createCombinedBuildNinjaFile(ctx, config)
 
@@ -373,6 +401,7 @@
 		if what&RunKati != 0 {
 			installCleanIfNecessary(ctx, config)
 		}
+		partialCompileCleanIfNecessary(ctx, config)
 		runNinjaForBuild(ctx, config)
 		updateBuildIdDir(ctx, config)
 	}
@@ -399,6 +428,9 @@
 	if config.Checkbuild() {
 		what |= RunBuildTests
 	}
+	if value, ok := config.environ.Get("RUN_BUILD_TESTS"); ok && value == "true" {
+		what |= RunBuildTests
+	}
 	if !config.SkipConfig() {
 		what |= RunProductConfig
 	} else {
@@ -428,7 +460,7 @@
 	if !config.SoongBuildInvocationNeeded() {
 		// This means that the output of soong_build is not needed and thus it would
 		// run unnecessarily. In addition, if this code wasn't there invocations
-		// with only special-cased target names like "m bp2build" would result in
+		// with only special-cased target names would result in
 		// passing Ninja the empty target list and it would then build the default
 		// targets which is not what the user asked for.
 		what = what &^ RunNinja
@@ -502,4 +534,5 @@
 // Be careful, anything added here slows down EVERY CI build
 func runDistActions(ctx Context, config Config) {
 	runStagingSnapshot(ctx, config)
+	runSourceInputs(ctx, config)
 }
diff --git a/ui/build/cleanbuild.go b/ui/build/cleanbuild.go
index 41cb5ab..723d90f 100644
--- a/ui/build/cleanbuild.go
+++ b/ui/build/cleanbuild.go
@@ -218,6 +218,52 @@
 	writeConfig()
 }
 
+// When SOONG_USE_PARTIAL_COMPILE transitions from on to off, we need to remove
+// all files which were potentially built with partial compile, so that they
+// get rebuilt with that turned off.
+func partialCompileCleanIfNecessary(ctx Context, config Config) {
+	configFile := config.DevicePreviousUsePartialCompile()
+	currentValue, _ := config.Environment().Get("SOONG_USE_PARTIAL_COMPILE")
+
+	ensureDirectoriesExist(ctx, filepath.Dir(configFile))
+
+	writeValue := func() {
+		err := ioutil.WriteFile(configFile, []byte(currentValue), 0666) // a+rw
+		if err != nil {
+			ctx.Fatalln("Failed to write use partial compile config:", err)
+		}
+	}
+
+	previousValueBytes, err := ioutil.ReadFile(configFile)
+	if err != nil {
+		if os.IsNotExist(err) {
+			// Just write the new config file, no old config file to worry about.
+			writeValue()
+			return
+		} else {
+			ctx.Fatalln("Failed to read previous use partial compile config:", err)
+		}
+	}
+
+	previousValue := string(previousValueBytes)
+	switch previousValue {
+	case currentValue:
+		// Same value as before - nothing left to do here.
+		return
+	case "true":
+		// Transitioning from on to off.  Build (phony) target: partialcompileclean.
+		ctx.BeginTrace(metrics.PrimaryNinja, "partialcompileclean")
+		defer ctx.EndTrace()
+
+		ctx.Printf("SOONG_USE_PARTIAL_COMPILE turned off, forcing partialcompileclean\n")
+
+		runNinja(ctx, config, []string{"partialcompileclean"})
+	default:
+		// Transitioning from off to on.  Nothing to do in this case.
+	}
+	writeValue()
+}
+
 // cleanOldFiles takes an input file (with all paths relative to basePath), and removes files from
 // the filesystem if they were removed from the input file since the last execution.
 func cleanOldFiles(ctx Context, basePath, newFile string) {
diff --git a/ui/build/config.go b/ui/build/config.go
index 75edfcd..a4f778d 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -30,6 +30,7 @@
 	"syscall"
 	"time"
 
+	"android/soong/finder/fs"
 	"android/soong/shared"
 	"android/soong/ui/metrics"
 
@@ -62,13 +63,13 @@
 	NINJA_NINJA
 	NINJA_N2
 	NINJA_SISO
+	NINJA_NINJAGO
 )
 
 type Config struct{ *configImpl }
 
 type configImpl struct {
 	// Some targets that are implemented in soong_build
-	// (bp2build, json-module-graph) are not here and have their own bits below.
 	arguments     []string
 	goma          bool
 	environ       *Environment
@@ -77,28 +78,31 @@
 	logsPrefix    string
 
 	// From the arguments
-	parallel                 int
-	keepGoing                int
-	verbose                  bool
-	checkbuild               bool
-	dist                     bool
-	jsonModuleGraph          bool
-	queryview                bool
-	reportMkMetrics          bool // Collect and report mk2bp migration progress metrics.
-	soongDocs                bool
-	skipConfig               bool
-	skipKati                 bool
-	skipKatiNinja            bool
-	skipSoong                bool
-	skipNinja                bool
-	skipSoongTests           bool
-	searchApiDir             bool // Scan the Android.bp files generated in out/api_surfaces
-	skipMetricsUpload        bool
-	buildStartedTime         int64 // For metrics-upload-only - manually specify a build-started time
-	buildFromSourceStub      bool
-	incrementalBuildActions  bool
-	ensureAllowlistIntegrity bool // For CI builds - make sure modules are mixed-built
-	partialCompileFlags      partialCompileFlags
+	parallel        int
+	keepGoing       int
+	verbose         bool
+	checkbuild      bool
+	dist            bool
+	jsonModuleGraph bool
+	reportMkMetrics bool // Collect and report mk2bp migration progress metrics.
+	soongDocs       bool
+	skipConfig      bool
+	// Either the user or product config requested that we skip soong (for the banner). The other
+	// skip flags tell whether *this* soong_ui invocation will skip kati - which will be true
+	// during lunch.
+	soongOnlyRequested        bool
+	skipKati                  bool
+	skipKatiControlledByFlags bool
+	skipKatiNinja             bool
+	skipSoong                 bool
+	skipNinja                 bool
+	skipSoongTests            bool
+	searchApiDir              bool // Scan the Android.bp files generated in out/api_surfaces
+	skipMetricsUpload         bool
+	buildStartedTime          int64 // For metrics-upload-only - manually specify a build-started time
+	buildFromSourceStub       bool
+	incrementalBuildActions   bool
+	ensureAllowlistIntegrity  bool // For CI builds - make sure modules are mixed-built
 
 	// From the product config
 	katiArgs        []string
@@ -109,7 +113,9 @@
 	sandboxConfig   *SandboxConfig
 
 	// Autodetected
-	totalRAM uint64
+	totalRAM      uint64
+	systemCpuInfo *metrics.CpuInfo
+	systemMemInfo *metrics.MemInfo
 
 	brokenDupRules       bool
 	brokenUsesNetwork    bool
@@ -138,16 +144,6 @@
 	ninjaCommand ninjaCommandType
 }
 
-type partialCompileFlags struct {
-	// Is partial compilation enabled at all?
-	enabled bool
-
-	// Whether to use d8 instead of r8
-	use_d8 bool
-
-	// Add others as needed.
-}
-
 type NinjaWeightListSource uint
 
 const (
@@ -250,8 +246,29 @@
 	ret.keepGoing = 1
 
 	ret.totalRAM = detectTotalRAM(ctx)
+	ret.systemCpuInfo, err = metrics.NewCpuInfo(fs.OsFs)
+	if err != nil {
+		ctx.Fatalln("Failed to get cpuinfo:", err)
+	}
+	ret.systemMemInfo, err = metrics.NewMemInfo(fs.OsFs)
+	if err != nil {
+		ctx.Fatalln("Failed to get meminfo:", err)
+	}
 	ret.parseArgs(ctx, args)
 
+	if value, ok := ret.environ.Get("SOONG_ONLY"); ok && !ret.skipKatiControlledByFlags {
+		if value == "true" || value == "1" || value == "y" || value == "yes" {
+			ret.soongOnlyRequested = true
+			ret.skipKatiControlledByFlags = true
+			ret.skipKati = true
+			ret.skipKatiNinja = true
+		} else {
+			ret.skipKatiControlledByFlags = true
+			ret.skipKati = false
+			ret.skipKatiNinja = false
+		}
+	}
+
 	if ret.ninjaWeightListSource == HINT_FROM_SOONG {
 		ret.environ.Set("SOONG_GENERATES_NINJA_HINT", "always")
 	} else if ret.ninjaWeightListSource == DEFAULT {
@@ -304,18 +321,29 @@
 		ret.sandboxConfig.SetSrcDirIsRO(srcDirIsWritable == "false")
 	}
 
-	ret.partialCompileFlags = parsePartialCompileFlags(ctx)
-
 	if os.Getenv("GENERATE_SOONG_DEBUG") == "true" {
 		ret.moduleDebugFile, _ = filepath.Abs(shared.JoinPath(ret.SoongOutDir(), "soong-debug-info.json"))
 	}
 
+	// If SOONG_USE_PARTIAL_COMPILE is set, make it one of "true" or the empty string.
+	// This simplifies the generated Ninja rules, so that they only need to check for the empty string.
+	if value, ok := ret.environ.Get("SOONG_USE_PARTIAL_COMPILE"); ok {
+		if value == "true" || value == "1" || value == "y" || value == "yes" {
+			value = "true"
+		} else {
+			value = ""
+		}
+		ret.environ.Set("SOONG_USE_PARTIAL_COMPILE", value)
+	}
+
 	ret.ninjaCommand = NINJA_NINJA
 	switch os.Getenv("SOONG_NINJA") {
 	case "n2":
 		ret.ninjaCommand = NINJA_N2
 	case "siso":
 		ret.ninjaCommand = NINJA_SISO
+	case "ninjago":
+		ret.ninjaCommand = NINJA_NINJAGO
 	default:
 		if os.Getenv("SOONG_USE_N2") == "true" {
 			ret.ninjaCommand = NINJA_N2
@@ -382,7 +410,9 @@
 		// Use config.ninjaCommand instead.
 		"SOONG_NINJA",
 		"SOONG_USE_N2",
-		"SOONG_PARTIAL_COMPILE",
+
+		// Already incorporated into the config object
+		"SOONG_ONLY",
 	)
 
 	if ret.UseGoma() || ret.ForceUseGoma() {
@@ -501,78 +531,6 @@
 	return c
 }
 
-// Parse SOONG_PARTIAL_COMPILE.
-//
-// The user-facing documentation shows:
-//
-// - empty or not set: "The current default state"
-// - "true" or "on": enable all stable partial compile features.
-// - "false" or "off": disable partial compile completely.
-//
-// What we actually allow is a comma separated list of tokens, whose first
-// character may be "+" (enable) or "-" (disable).  If neither is present, "+"
-// is assumed.  For example, "on,+use_d8" will enable partial compilation, and
-// additionally set the use_d8 flag (regardless of whether it is opt-in or
-// opt-out).
-//
-// 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 parsePartialCompileFlags(ctx Context) partialCompileFlags {
-	defaultFlags := partialCompileFlags{
-		// Set any opt-out flags here.  Opt-in flags are off by default.
-		enabled: false,
-	}
-	value, ok := os.LookupEnv("SOONG_PARTIAL_COMPILE")
-
-	if !ok {
-		return defaultFlags
-	}
-
-	ret := defaultFlags
-	tokens := strings.Split(strings.ToLower(value), ",")
-	makeVal := func(state string, defaultValue bool) bool {
-		switch state {
-		case "":
-			return defaultValue
-		case "-":
-			return false
-		case "+":
-			return true
-		}
-		return false
-	}
-	for _, tok := range tokens {
-		var state string
-		switch tok[0:1] {
-		case "":
-			// Ignore empty tokens.
-			continue
-		case "-", "+":
-			state = tok[0:1]
-			tok = tok[1:]
-		default:
-			// Treat `feature` as `+feature`.
-			state = "+"
-		}
-		switch tok {
-		case "true", "on", "yes":
-			ret = defaultFlags
-			ret.enabled = true
-		case "false", "off", "no":
-			// Set everything to false.
-			ret = partialCompileFlags{}
-		case "enabled":
-			ret.enabled = makeVal(state, defaultFlags.enabled)
-		case "use_d8":
-			ret.use_d8 = makeVal(state, defaultFlags.use_d8)
-		default:
-			ctx.Fatalln("Unknown SOONG_PARTIAL_COMPILE value:", value)
-		}
-	}
-	return ret
-}
-
 // NewBuildActionConfig returns a build configuration based on the build action. The arguments are
 // processed based on the build action and extracts any arguments that belongs to the build action.
 func NewBuildActionConfig(action BuildAction, dir string, ctx Context, args ...string) Config {
@@ -624,9 +582,23 @@
 
 	ctx.Metrics.BuildConfig(buildConfig(config))
 
+	cpuInfo := &smpb.SystemCpuInfo{
+		VendorId:  proto.String(config.systemCpuInfo.VendorId),
+		ModelName: proto.String(config.systemCpuInfo.ModelName),
+		CpuCores:  proto.Int32(config.systemCpuInfo.CpuCores),
+		Flags:     proto.String(config.systemCpuInfo.Flags),
+	}
+	memInfo := &smpb.SystemMemInfo{
+		MemTotal:     proto.Uint64(config.systemMemInfo.MemTotal),
+		MemFree:      proto.Uint64(config.systemMemInfo.MemFree),
+		MemAvailable: proto.Uint64(config.systemMemInfo.MemAvailable),
+	}
+
 	s := &smpb.SystemResourceInfo{
 		TotalPhysicalMemory: proto.Uint64(config.TotalRAM()),
 		AvailableCpus:       proto.Int32(int32(runtime.NumCPU())),
+		CpuInfo:             cpuInfo,
+		MemInfo:             memInfo,
 	}
 	ctx.Metrics.SystemResourceInfo(s)
 }
@@ -647,11 +619,27 @@
 }
 
 func buildConfig(config Config) *smpb.BuildConfig {
+	var soongEnvVars *smpb.SoongEnvVars
+	ensure := func() *smpb.SoongEnvVars {
+		// Create soongEnvVars if it doesn't already exist.
+		if soongEnvVars == nil {
+			soongEnvVars = &smpb.SoongEnvVars{}
+		}
+		return soongEnvVars
+	}
+	if value, ok := config.environ.Get("SOONG_PARTIAL_COMPILE"); ok {
+		ensure().PartialCompile = proto.String(value)
+	}
+	if value, ok := config.environ.Get("SOONG_USE_PARTIAL_COMPILE"); ok {
+		ensure().UsePartialCompile = proto.String(value)
+	}
 	c := &smpb.BuildConfig{
 		ForceUseGoma:          proto.Bool(config.ForceUseGoma()),
 		UseGoma:               proto.Bool(config.UseGoma()),
 		UseRbe:                proto.Bool(config.UseRBE()),
 		NinjaWeightListSource: getNinjaWeightListSourceInMetric(config.NinjaWeightListSource()),
+		SoongEnvVars:          soongEnvVars,
+		SoongOnly:             proto.Bool(config.soongOnlyRequested),
 	}
 	c.Targets = append(c.Targets, config.arguments...)
 
@@ -880,15 +868,21 @@
 			c.emptyNinjaFile = true
 		} else if arg == "--skip-ninja" {
 			c.skipNinja = true
-		} else if arg == "--skip-make" {
-			// TODO(ccross): deprecate this, it has confusing behaviors.  It doesn't run kati,
-			//   but it does run a Kati ninja file if the .kati_enabled marker file was created
-			//   by a previous build.
-			c.skipConfig = true
-			c.skipKati = true
 		} else if arg == "--soong-only" {
+			if c.skipKatiControlledByFlags {
+				ctx.Fatalf("Cannot specify both --soong-only and --no-soong-only")
+			}
+			c.soongOnlyRequested = true
+			c.skipKatiControlledByFlags = true
 			c.skipKati = true
 			c.skipKatiNinja = true
+		} else if arg == "--no-soong-only" {
+			if c.skipKatiControlledByFlags {
+				ctx.Fatalf("Cannot specify both --soong-only and --no-soong-only")
+			}
+			c.skipKatiControlledByFlags = true
+			c.skipKati = false
+			c.skipKatiNinja = false
 		} else if arg == "--config-only" {
 			c.skipKati = true
 			c.skipKatiNinja = true
@@ -983,8 +977,6 @@
 			c.dist = true
 		} else if arg == "json-module-graph" {
 			c.jsonModuleGraph = true
-		} else if arg == "queryview" {
-			c.queryview = true
 		} else if arg == "soong_docs" {
 			c.soongDocs = true
 		} else {
@@ -1079,7 +1071,7 @@
 		return true
 	}
 
-	if !c.JsonModuleGraph() && !c.Queryview() && !c.SoongDocs() {
+	if !c.JsonModuleGraph() && !c.SoongDocs() {
 		// Command line was empty, the default Ninja target is built
 		return true
 	}
@@ -1109,7 +1101,7 @@
 
 func (c *configImpl) NinjaArgs() []string {
 	if c.skipKati {
-		return c.arguments
+		return append(c.arguments, c.ninjaArgs...)
 	}
 	return c.ninjaArgs
 }
@@ -1152,10 +1144,6 @@
 	return shared.JoinPath(c.SoongOutDir(), "docs/soong_build.html")
 }
 
-func (c *configImpl) QueryviewMarkerFile() string {
-	return shared.JoinPath(c.SoongOutDir(), "queryview.marker")
-}
-
 func (c *configImpl) ModuleGraphFile() string {
 	return shared.JoinPath(c.SoongOutDir(), "module-graph.json")
 }
@@ -1193,10 +1181,6 @@
 	return c.jsonModuleGraph
 }
 
-func (c *configImpl) Queryview() bool {
-	return c.queryview
-}
-
 func (c *configImpl) SoongDocs() bool {
 	return c.soongDocs
 }
@@ -1386,6 +1370,10 @@
 }
 
 func (c *configImpl) UseABFS() bool {
+	if c.ninjaCommand == NINJA_NINJAGO {
+		return true
+	}
+
 	if v, ok := c.environ.Get("NO_ABFS"); ok {
 		v = strings.ToLower(strings.TrimSpace(v))
 		if v == "true" || v == "1" {
@@ -1413,7 +1401,7 @@
 
 func (c *configImpl) UseRBE() bool {
 	// These alternate modes of running Soong do not use RBE / reclient.
-	if c.Queryview() || c.JsonModuleGraph() {
+	if c.JsonModuleGraph() {
 		return false
 	}
 
@@ -1639,6 +1627,10 @@
 	return filepath.Join(c.OutDir(), "build"+c.KatiSuffix()+katiPackageSuffix+".ninja")
 }
 
+func (c *configImpl) KatiSoongOnlyPackageNinjaFile() string {
+	return filepath.Join(c.OutDir(), "build"+c.KatiSuffix()+katiSoongOnlyPackageSuffix+".ninja")
+}
+
 func (c *configImpl) SoongVarsFile() string {
 	targetProduct, err := c.TargetProductOrErr()
 	if err != nil {
@@ -1693,8 +1685,12 @@
 	return filepath.Join(c.ProductOut(), "previous_build_config.mk")
 }
 
+func (c *configImpl) DevicePreviousUsePartialCompile() string {
+	return filepath.Join(c.ProductOut(), "previous_use_partial_compile.txt")
+}
+
 func (c *configImpl) KatiPackageMkDir() string {
-	return filepath.Join(c.ProductOut(), "obj", "CONFIG", "kati_packaging")
+	return filepath.Join(c.SoongOutDir(), "kati_packaging"+c.KatiSuffix())
 }
 
 func (c *configImpl) hostOutRoot() string {
@@ -1855,10 +1851,6 @@
 	return c.ensureAllowlistIntegrity
 }
 
-func (c *configImpl) PartialCompileFlags() partialCompileFlags {
-	return c.partialCompileFlags
-}
-
 // Returns a Time object if one was passed via a command-line flag.
 // Otherwise returns the passed default.
 func (c *configImpl) BuildStartedTimeOrDefault(defaultTime time.Time) time.Time {
diff --git a/ui/build/config_test.go b/ui/build/config_test.go
index b42edb0..10de1ad 100644
--- a/ui/build/config_test.go
+++ b/ui/build/config_test.go
@@ -30,8 +30,6 @@
 	smpb "android/soong/ui/metrics/metrics_proto"
 	"android/soong/ui/status"
 
-	"google.golang.org/protobuf/encoding/prototext"
-
 	"google.golang.org/protobuf/proto"
 )
 
@@ -1006,6 +1004,12 @@
 	}
 }
 
+func assertEquals[T ~bool | ~int32](t *testing.T, name string, expected, actual T) {
+	if expected != actual {
+		t.Errorf("Expected %s: %#v\nActual %s: %#v", name, expected, name, actual)
+	}
+}
+
 func TestBuildConfig(t *testing.T) {
 	tests := []struct {
 		name                string
@@ -1063,12 +1067,11 @@
 				arguments: tc.arguments,
 			}
 			config := Config{c}
-			actualBuildConfig := buildConfig(config)
-			if expected := tc.expectedBuildConfig; !proto.Equal(expected, actualBuildConfig) {
-				t.Errorf("Build config mismatch.\n"+
-					"Expected build config: %#v\n"+
-					"Actual build config: %#v", prototext.Format(expected), prototext.Format(actualBuildConfig))
-			}
+			actual := buildConfig(config)
+			assertEquals(t, "ForceUseGoma", *tc.expectedBuildConfig.ForceUseGoma, *actual.ForceUseGoma)
+			assertEquals(t, "UseGoma", *tc.expectedBuildConfig.UseGoma, *actual.UseGoma)
+			assertEquals(t, "UseRbe", *tc.expectedBuildConfig.UseRbe, *actual.UseRbe)
+			assertEquals(t, "NinjaWeightListSource", *tc.expectedBuildConfig.NinjaWeightListSource, *actual.NinjaWeightListSource)
 		})
 	}
 }
diff --git a/ui/build/context.go b/ui/build/context.go
index fd20e26..69e5f96 100644
--- a/ui/build/context.go
+++ b/ui/build/context.go
@@ -18,6 +18,7 @@
 	"context"
 	"io"
 
+	"android/soong/ui/execution_metrics"
 	"android/soong/ui/logger"
 	"android/soong/ui/metrics"
 	soong_metrics_proto "android/soong/ui/metrics/metrics_proto"
@@ -33,7 +34,8 @@
 	context.Context
 	logger.Logger
 
-	Metrics *metrics.Metrics
+	Metrics          *metrics.Metrics
+	ExecutionMetrics *execution_metrics.ExecutionMetrics
 
 	Writer io.Writer
 	Status *status.Status
diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go
index 5df3a95..16a3db8 100644
--- a/ui/build/dumpvars.go
+++ b/ui/build/dumpvars.go
@@ -137,6 +137,12 @@
 		}
 	}
 	if ctx.Metrics != nil {
+		// Also include TARGET_RELEASE in the metrics.  Do this first
+		// so that it gets overwritten if dumpvars ever spits it out.
+		if release, found := os.LookupEnv("TARGET_RELEASE"); found {
+			ctx.Metrics.SetMetadataMetrics(
+				map[string]string{"TARGET_RELEASE": release})
+		}
 		ctx.Metrics.SetMetadataMetrics(ret)
 	}
 
@@ -166,7 +172,7 @@
 	"SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE",
 }
 
-func Banner(make_vars map[string]string) string {
+func Banner(config Config, make_vars map[string]string) string {
 	b := &bytes.Buffer{}
 
 	fmt.Fprintln(b, "============================================")
@@ -175,6 +181,7 @@
 			fmt.Fprintf(b, "%s=%s\n", name, make_vars[name])
 		}
 	}
+	fmt.Fprintf(b, "SOONG_ONLY=%t\n", config.soongOnlyRequested)
 	fmt.Fprint(b, "============================================")
 
 	return b.String()
@@ -240,6 +247,8 @@
 		// `true` will relegate missing outputs to warnings.
 		"BUILD_BROKEN_MISSING_OUTPUTS",
 
+		"PRODUCT_SOONG_ONLY",
+
 		// Not used, but useful to be in the soong.log
 		"TARGET_BUILD_TYPE",
 		"HOST_ARCH",
@@ -273,6 +282,7 @@
 		"BUILD_BROKEN_USES_BUILD_SHARED_LIBRARY",
 		"BUILD_BROKEN_USES_BUILD_STATIC_JAVA_LIBRARY",
 		"BUILD_BROKEN_USES_BUILD_STATIC_LIBRARY",
+		"RELEASE_BUILD_EXECUTION_METRICS",
 	}, exportEnvVars...), BannerVars...)
 
 	makeVars, err := dumpMakeVars(ctx, config, config.Arguments(), allVars, true, "")
@@ -280,13 +290,8 @@
 		ctx.Fatalln("Error dumping make vars:", err)
 	}
 
-	env := config.Environment()
-	// Print the banner like make does
-	if !env.IsEnvTrue("ANDROID_QUIET_BUILD") {
-		fmt.Fprintln(ctx.Writer, Banner(makeVars))
-	}
-
 	// Populate the environment
+	env := config.Environment()
 	for _, name := range exportEnvVars {
 		if makeVars[name] == "" {
 			env.Unset(name)
@@ -307,4 +312,17 @@
 	config.SetBuildBrokenNinjaUsesEnvVars(strings.Fields(makeVars["BUILD_BROKEN_NINJA_USES_ENV_VARS"]))
 	config.SetSourceRootDirs(strings.Fields(makeVars["PRODUCT_SOURCE_ROOT_DIRS"]))
 	config.SetBuildBrokenMissingOutputs(makeVars["BUILD_BROKEN_MISSING_OUTPUTS"] == "true")
+
+	if !config.skipKatiControlledByFlags {
+		if makeVars["PRODUCT_SOONG_ONLY"] == "true" {
+			config.soongOnlyRequested = true
+			config.skipKati = true
+			config.skipKatiNinja = true
+		}
+	}
+
+	// Print the banner like make did
+	if !env.IsEnvTrue("ANDROID_QUIET_BUILD") {
+		fmt.Fprintln(ctx.Writer, Banner(config, makeVars))
+	}
 }
diff --git a/ui/build/finder.go b/ui/build/finder.go
index 573df21..783b488 100644
--- a/ui/build/finder.go
+++ b/ui/build/finder.go
@@ -128,6 +128,7 @@
 
 	// Stop searching a subdirectory recursively after finding an Android.mk.
 	androidMks := f.FindFirstNamedAt(".", "Android.mk")
+	androidMks = ignoreSomeAndroidMks(androidMks)
 	blockAndroidMks(ctx, androidMks)
 	err := dumpListToFile(ctx, config, androidMks, filepath.Join(dumpDir, "Android.mk.list"))
 	if err != nil {
@@ -170,6 +171,7 @@
 
 	// Recursively look for all METADATA files.
 	metadataFiles := f.FindNamedAt(".", "METADATA")
+	metadataFiles = ignoreNonAndroidMetadataFiles(metadataFiles)
 	err = dumpListToFile(ctx, config, metadataFiles, filepath.Join(dumpDir, "METADATA.list"))
 	if err != nil {
 		ctx.Fatalf("Could not find METADATA: %v", err)
@@ -222,3 +224,16 @@
 
 	return nil
 }
+
+func ignoreNonAndroidMetadataFiles(metadataFiles []string) []string {
+	result := make([]string, 0, len(metadataFiles))
+	for _, file := range metadataFiles {
+		// Ignore files like prebuilts/clang/host/linux-x86/clang-r536225/python3/lib/python3.11/site-packages/pip-23.1.2.dist-info/METADATA
+		// these METADATA files are from upstream and are not the METADATA files used in Android codebase.
+		if strings.Contains(file, "prebuilts/clang/host/") && strings.Contains(file, "/site-packages/") {
+			continue
+		}
+		result = append(result, file)
+	}
+	return result
+}
diff --git a/ui/build/kati.go b/ui/build/kati.go
index 5743ff7..6519573 100644
--- a/ui/build/kati.go
+++ b/ui/build/kati.go
@@ -31,6 +31,7 @@
 const katiBuildSuffix = ""
 const katiCleanspecSuffix = "-cleanspec"
 const katiPackageSuffix = "-package"
+const katiSoongOnlyPackageSuffix = "-soong-only-package"
 
 // genKatiSuffix creates a filename suffix for kati-generated files so that we
 // can cache them based on their inputs. Such files include the generated Ninja
@@ -40,8 +41,12 @@
 // Currently that includes the TARGET_PRODUCT and kati-processed command line
 // arguments.
 func genKatiSuffix(ctx Context, config Config) {
+	targetProduct := "unknown"
+	if p, err := config.TargetProductOrErr(); err == nil {
+		targetProduct = p
+	}
 	// Construct the base suffix.
-	katiSuffix := "-" + config.TargetProduct() + config.CoverageSuffix()
+	katiSuffix := "-" + targetProduct + config.CoverageSuffix()
 
 	// Append kati arguments to the suffix.
 	if args := config.KatiArgs(); len(args) > 0 {
@@ -68,13 +73,13 @@
 func writeValueIfChanged(ctx Context, config Config, dir string, filename string, value string) {
 	filePath := filepath.Join(dir, filename)
 	previousValue := ""
-	rawPreviousValue, err := ioutil.ReadFile(filePath)
+	rawPreviousValue, err := os.ReadFile(filePath)
 	if err == nil {
 		previousValue = string(rawPreviousValue)
 	}
 
 	if previousValue != value {
-		if err = ioutil.WriteFile(filePath, []byte(value), 0666); err != nil {
+		if err = os.WriteFile(filePath, []byte(value), 0666); err != nil {
 			ctx.Fatalf("Failed to write: %v", err)
 		}
 	}
@@ -183,18 +188,27 @@
 		username = usernameFromEnv
 	}
 
-	hostname, ok := cmd.Environment.Get("BUILD_HOSTNAME")
+	// SOONG_USE_PARTIAL_COMPILE may be used in makefiles, but both cases must be supported.
+	//
+	// In general, the partial compile features will be implemented in Soong-based rules. We
+	// also allow them to be used in makefiles.  Clear the environment variable when calling
+	// kati so that we avoid reanalysis when the user changes it.  We will pass it to Ninja.
+	// As a result, rules where we want to allow the developer to toggle the feature ("use
+	// the partial compile feature" vs "legacy, aka full compile behavior") need to use this
+	// in the rule, since changing it will not cause reanalysis.
+	//
+	// Shell syntax in the rule might look something like this:
+	//     if [[ -n ${SOONG_USE_PARTIAL_COMPILE} ]]; then
+	//         # partial compile behavior
+	//     else
+	//         # legacy behavior
+	//     fi
+	cmd.Environment.Unset("SOONG_USE_PARTIAL_COMPILE")
+
 	// Unset BUILD_HOSTNAME during kati run to avoid kati rerun, kati will use BUILD_HOSTNAME from a file.
 	cmd.Environment.Unset("BUILD_HOSTNAME")
-	if !ok {
-		hostname, err = os.Hostname()
-		if err != nil {
-			ctx.Println("Failed to read hostname:", err)
-			hostname = "unknown"
-		}
-	}
-	writeValueIfChanged(ctx, config, config.SoongOutDir(), "build_hostname.txt", hostname)
-	_, ok = cmd.Environment.Get("BUILD_NUMBER")
+
+	_, ok := cmd.Environment.Get("BUILD_NUMBER")
 	// Unset BUILD_NUMBER during kati run to avoid kati rerun, kati will use BUILD_NUMBER from a file.
 	cmd.Environment.Unset("BUILD_NUMBER")
 	if ok {
@@ -325,10 +339,19 @@
 
 // Generate the Ninja file containing the packaging command lines for the dist
 // dir.
-func runKatiPackage(ctx Context, config Config) {
+func runKatiPackage(ctx Context, config Config, soongOnly bool) {
 	ctx.BeginTrace(metrics.RunKati, "kati package")
 	defer ctx.EndTrace()
 
+	entryPoint := "build/make/packaging/main.mk"
+	suffix := katiPackageSuffix
+	ninjaFile := config.KatiPackageNinjaFile()
+	if soongOnly {
+		entryPoint = "build/make/packaging/main_soong_only.mk"
+		suffix = katiSoongOnlyPackageSuffix
+		ninjaFile = config.KatiSoongOnlyPackageNinjaFile()
+	}
+
 	args := []string{
 		// Mark the dist dir as writable.
 		"--writable", config.DistDir() + "/",
@@ -337,14 +360,14 @@
 		// Fail when redefining / duplicating a target.
 		"--werror_overriding_commands",
 		// Entry point.
-		"-f", "build/make/packaging/main.mk",
+		"-f", entryPoint,
 		// Directory containing .mk files for packaging purposes, such as
 		// the dist.mk file, containing dist-for-goals data.
 		"KATI_PACKAGE_MK_DIR=" + config.KatiPackageMkDir(),
 	}
 
 	// Run Kati against a restricted set of environment variables.
-	runKati(ctx, config, katiPackageSuffix, args, func(env *Environment) {
+	runKati(ctx, config, suffix, args, func(env *Environment) {
 		env.Allow([]string{
 			// Some generic basics
 			"LANG",
@@ -372,7 +395,7 @@
 	})
 
 	// Compress and dist the packaging Ninja file.
-	distGzipFile(ctx, config, config.KatiPackageNinjaFile())
+	distGzipFile(ctx, config, ninjaFile)
 }
 
 // Run Kati on the cleanspec files to clean the build.
diff --git a/ui/build/ninja.go b/ui/build/ninja.go
index def0783..e2a568f 100644
--- a/ui/build/ninja.go
+++ b/ui/build/ninja.go
@@ -17,6 +17,7 @@
 import (
 	"fmt"
 	"os"
+	"os/exec"
 	"path/filepath"
 	"sort"
 	"strconv"
@@ -35,10 +36,16 @@
 	ninjaWeightListFileName = ".ninja_weight_list"
 )
 
+// Runs ninja with the arguments from the command line, as found in
+// config.NinjaArgs().
+func runNinjaForBuild(ctx Context, config Config) {
+	runNinja(ctx, config, config.NinjaArgs())
+}
+
 // Constructs and runs the Ninja command line with a restricted set of
 // environment variables. It's important to restrict the environment Ninja runs
 // for hermeticity reasons, and to avoid spurious rebuilds.
-func runNinjaForBuild(ctx Context, config Config) {
+func runNinja(ctx Context, config Config, ninjaArgs []string) {
 	ctx.BeginTrace(metrics.PrimaryNinja, "ninja")
 	defer ctx.EndTrace()
 
@@ -75,7 +82,7 @@
 			//"--frontend-file", fifo,
 		}
 	default:
-		// NINJA_NINJA is the default.
+		// NINJA_NINJA or NINJA_NINJAGO.
 		executable = config.NinjaBin()
 		args = []string{
 			"-d", "keepdepfile",
@@ -87,7 +94,7 @@
 			"-w", "missingdepfile=err",
 		}
 	}
-	args = append(args, config.NinjaArgs()...)
+	args = append(args, ninjaArgs...)
 
 	var parallel int
 	if config.UseRemoteBuild() {
@@ -241,6 +248,14 @@
 			"SOONG_USE_N2",
 			"RUST_BACKTRACE",
 			"RUST_LOG",
+
+			// SOONG_USE_PARTIAL_COMPILE only determines which half of the rule we execute.
+			// When it transitions true => false, we build phony target "partialcompileclean",
+			// which removes all files that could have been created while it was true.
+			"SOONG_USE_PARTIAL_COMPILE",
+
+			// Directory for ExecutionMetrics
+			"SOONG_METRICS_AGGREGATION_DIR",
 		}, config.BuildBrokenNinjaUsesEnvVars()...)...)
 	}
 
@@ -253,6 +268,10 @@
 		// Only set RUST_BACKTRACE for n2.
 	}
 
+	// Set up the metrics aggregation directory.
+	ctx.ExecutionMetrics.SetDir(filepath.Join(config.OutDir(), "soong", "metrics_aggregation"))
+	cmd.Environment.Set("SOONG_METRICS_AGGREGATION_DIR", ctx.ExecutionMetrics.MetricsAggregationDir)
+
 	// Print the environment variables that Ninja is operating in.
 	ctx.Verboseln("Ninja environment: ")
 	envVars := cmd.Environment.Environ()
@@ -297,6 +316,8 @@
 		}
 	}()
 
+	ctx.ExecutionMetrics.Start()
+	defer ctx.ExecutionMetrics.Finish(ctx)
 	ctx.Status.Status("Starting ninja...")
 	cmd.RunAndStreamOrFatal()
 }
@@ -336,3 +357,46 @@
 	}
 	c.prevModTime = newModTime
 }
+
+// Constructs and runs the Ninja command line to get the inputs of a goal.
+// For n2 and siso, this will always run ninja, because they don't have the
+// `-t inputs` command.  This command will use the inputs command's -d option,
+// to use the dep file iff ninja was the executor. For other executors, the
+// results will be wrong.
+func runNinjaInputs(ctx Context, config Config, goal string) ([]string, error) {
+	var executable string
+	switch config.ninjaCommand {
+	case NINJA_N2, NINJA_SISO:
+		executable = config.PrebuiltBuildTool("ninja")
+	default:
+		executable = config.NinjaBin()
+	}
+
+	args := []string{
+		"-f",
+		config.CombinedNinjaFile(),
+		"-t",
+		"inputs",
+	}
+	// Add deps file arg for ninja
+	// TODO: Update as inputs command is implemented
+	if config.ninjaCommand == NINJA_NINJA && !config.UseABFS() {
+		args = append(args, "-d")
+	}
+	args = append(args, goal)
+
+	// This is just ninja -t inputs, so we won't bother running it in the sandbox,
+	// so use exec.Command, not soong_ui's command.
+	cmd := exec.Command(executable, args...)
+
+	cmd.Stdin = os.Stdin
+	cmd.Stderr = os.Stderr
+
+	out, err := cmd.Output()
+	if err != nil {
+		fmt.Printf("Error getting goal inputs for %s: %s\n", goal, err)
+		return nil, err
+	}
+
+	return strings.Split(strings.TrimSpace(string(out)), "\n"), nil
+}
diff --git a/ui/build/paths/config.go b/ui/build/paths/config.go
index 6c9a1eb..110ddee 100644
--- a/ui/build/paths/config.go
+++ b/ui/build/paths/config.go
@@ -42,7 +42,7 @@
 }
 
 // This tool is specifically disallowed and calling it will result in an
-// "executable no found" error.
+// "executable not found" error.
 var Forbidden = PathConfig{
 	Symlink: false,
 	Log:     true,
@@ -122,6 +122,10 @@
 	"ld.bfd":     Forbidden,
 	"ld.gold":    Forbidden,
 	"pkg-config": Forbidden,
+	"python":     Forbidden,
+	"python2":    Forbidden,
+	"python2.7":  Forbidden,
+	"python3":    Forbidden,
 
 	// These are toybox tools that only work on Linux.
 	"pgrep": LinuxOnlyPrebuilt,
diff --git a/ui/build/soong.go b/ui/build/soong.go
index f70d9b7..58334a9 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -52,7 +52,6 @@
 
 	soongBuildTag      = "build"
 	jsonModuleGraphTag = "modulegraph"
-	queryviewTag       = "queryview"
 	soongDocsTag       = "soong_docs"
 
 	// bootstrapEpoch is used to determine if an incremental build is incompatible with the current
@@ -198,6 +197,8 @@
 func (pb PrimaryBuilderFactory) primaryBuilderInvocation(config Config) bootstrap.PrimaryBuilderInvocation {
 	commonArgs := make([]string, 0, 0)
 
+	commonArgs = append(commonArgs, "--kati_suffix", config.KatiSuffix())
+
 	if !pb.config.skipSoongTests {
 		commonArgs = append(commonArgs, "-t")
 	}
@@ -288,6 +289,15 @@
 	ctx.BeginTrace(metrics.RunSoong, "blueprint bootstrap")
 	defer ctx.EndTrace()
 
+	st := ctx.Status.StartTool()
+	defer st.Finish()
+	st.SetTotalActions(1)
+	action := &status.Action{
+		Description: "bootstrap blueprint",
+		Outputs:     []string{"bootstrap blueprint"},
+	}
+	st.StartAction(action)
+
 	// Clean up some files for incremental builds across incompatible changes.
 	bootstrapEpochCleanup(ctx, config)
 
@@ -307,8 +317,6 @@
 		mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--incremental-build-actions")
 	}
 
-	queryviewDir := filepath.Join(config.SoongOutDir(), "queryview")
-
 	pbfs := []PrimaryBuilderFactory{
 		{
 			name:         soongBuildTag,
@@ -328,15 +336,6 @@
 			),
 		},
 		{
-			name:        queryviewTag,
-			description: fmt.Sprintf("generating the Soong module graph as a Bazel workspace at %s", queryviewDir),
-			config:      config,
-			output:      config.QueryviewMarkerFile(),
-			specificArgs: append(baseArgs,
-				"--bazel_queryview_dir", queryviewDir,
-			),
-		},
-		{
 			name:        soongDocsTag,
 			description: fmt.Sprintf("generating Soong docs at %s", config.SoongDocsHtml()),
 			config:      config,
@@ -407,8 +406,17 @@
 	// since `bootstrap.ninja` is regenerated unconditionally, we ignore the deps, i.e. little
 	// reason to write a `bootstrap.ninja.d` file
 	_, err := bootstrap.RunBlueprint(blueprintArgs, bootstrap.DoEverything, blueprintCtx, blueprintConfig)
+
+	result := status.ActionResult{
+		Action: action,
+	}
 	if err != nil {
-		ctx.Fatal(err)
+		result.Error = err
+		result.Output = err.Error()
+	}
+	st.FinishAction(result)
+	if err != nil {
+		ctx.Fatalf("bootstrap failed")
 	}
 }
 
@@ -427,13 +435,13 @@
 	}
 }
 
-func updateSymlinks(ctx Context, dir, prevCWD, cwd string) error {
+func updateSymlinks(ctx Context, dir, prevCWD, cwd string, updateSemaphore chan struct{}) error {
 	defer symlinkWg.Done()
 
 	visit := func(path string, d fs.DirEntry, err error) error {
 		if d.IsDir() && path != dir {
 			symlinkWg.Add(1)
-			go updateSymlinks(ctx, path, prevCWD, cwd)
+			go updateSymlinks(ctx, path, prevCWD, cwd, updateSemaphore)
 			return filepath.SkipDir
 		}
 		f, err := d.Info()
@@ -464,12 +472,27 @@
 		return nil
 	}
 
+	<-updateSemaphore
+	defer func() { updateSemaphore <- struct{}{} }()
 	if err := filepath.WalkDir(dir, visit); err != nil {
 		return err
 	}
 	return nil
 }
 
+// b/376466642: If the concurrency of updateSymlinks is unbounded, Go's runtime spawns a
+// theoretically unbounded number of threads to handle blocking syscalls. This causes the runtime to
+// panic due to hitting thread limits in rare cases. Limiting to GOMAXPROCS concurrent symlink
+// updates should make this a non-issue.
+func newUpdateSemaphore() chan struct{} {
+	numPermits := runtime.GOMAXPROCS(0)
+	c := make(chan struct{}, numPermits)
+	for i := 0; i < numPermits; i++ {
+		c <- struct{}{}
+	}
+	return c
+}
+
 func fixOutDirSymlinks(ctx Context, config Config, outDir string) error {
 	cwd, err := os.Getwd()
 	if err != nil {
@@ -480,7 +503,7 @@
 	tf := filepath.Join(outDir, ".top")
 	defer func() {
 		if err := os.WriteFile(tf, []byte(cwd), 0644); err != nil {
-			fmt.Fprintf(os.Stderr, fmt.Sprintf("Unable to log CWD: %v", err))
+			fmt.Fprintf(os.Stderr, "Unable to log CWD: %v", err)
 		}
 	}()
 
@@ -502,7 +525,7 @@
 	}
 
 	symlinkWg.Add(1)
-	if err := updateSymlinks(ctx, outDir, prevCWD, cwd); err != nil {
+	if err := updateSymlinks(ctx, outDir, prevCWD, cwd, newUpdateSemaphore()); err != nil {
 		return err
 	}
 	symlinkWg.Wait()
@@ -572,10 +595,6 @@
 			checkEnvironmentFile(ctx, soongBuildEnv, config.UsedEnvFile(jsonModuleGraphTag))
 		}
 
-		if config.Queryview() {
-			checkEnvironmentFile(ctx, soongBuildEnv, config.UsedEnvFile(queryviewTag))
-		}
-
 		if config.SoongDocs() {
 			checkEnvironmentFile(ctx, soongBuildEnv, config.UsedEnvFile(soongDocsTag))
 		}
@@ -670,10 +689,6 @@
 		targets = append(targets, config.ModuleGraphFile())
 	}
 
-	if config.Queryview() {
-		targets = append(targets, config.QueryviewMarkerFile())
-	}
-
 	if config.SoongDocs() {
 		targets = append(targets, config.SoongDocsHtml())
 	}
@@ -812,8 +827,10 @@
 						changedGlobNameMutex.Lock()
 						defer changedGlobNameMutex.Unlock()
 						changedGlobName = result.Pattern
-						if len(result.Excludes) > 0 {
-							changedGlobName += " (excluding " + strings.Join(result.Excludes, ", ") + ")"
+						if len(result.Excludes) > 2 {
+							changedGlobName += fmt.Sprintf(" (excluding %d other patterns)", len(result.Excludes))
+						} else if len(result.Excludes) > 0 {
+							changedGlobName += " (excluding " + strings.Join(result.Excludes, " and ") + ")"
 						}
 					}
 				}
diff --git a/ui/build/source_inputs.go b/ui/build/source_inputs.go
new file mode 100644
index 0000000..d1cc1a2
--- /dev/null
+++ b/ui/build/source_inputs.go
@@ -0,0 +1,100 @@
+package build
+
+import (
+	"compress/gzip"
+	"fmt"
+	"os"
+	"path/filepath"
+	"sort"
+	"strings"
+
+	"android/soong/shared"
+	"android/soong/ui/metrics"
+)
+
+func sortedStringSetKeys(m map[string]bool) []string {
+	result := make([]string, 0, len(m))
+	for key := range m {
+		result = append(result, key)
+	}
+	sort.Strings(result)
+	return result
+}
+
+func addSlash(str string) string {
+	if len(str) == 0 {
+		return ""
+	}
+	if str[len(str)-1] == '/' {
+		return str
+	}
+	return str + "/"
+}
+
+func hasPrefixStrings(str string, prefixes []string) bool {
+	for _, prefix := range prefixes {
+		if strings.HasPrefix(str, prefix) {
+			return true
+		}
+	}
+	return false
+}
+
+// Output DIST_DIR/source_inputs.txt.gz, which will contain a listing of the files
+// in the source tree (not including in the out directory) that were declared as ninja
+// inputs to the build that was just done.
+func runSourceInputs(ctx Context, config Config) {
+	ctx.BeginTrace(metrics.RunSoong, "runSourceInputs")
+	defer ctx.EndTrace()
+
+	success := false
+	outputFilename := shared.JoinPath(config.RealDistDir(), "source_inputs.txt.gz")
+
+	outputFile, err := os.Create(outputFilename)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "source_files_used: unable to open file for write: %s\n", outputFilename)
+		return
+	}
+	defer func() {
+		outputFile.Close()
+		if !success {
+			os.Remove(outputFilename)
+		}
+	}()
+
+	output := gzip.NewWriter(outputFile)
+	defer output.Close()
+
+	// Skip out dir, both absolute and relative. There are some files
+	// generated during analysis that ninja thinks are inputs not intermediates.
+	absOut, _ := filepath.Abs(config.OutDir())
+	excludes := []string{
+		addSlash(config.OutDir()),
+		addSlash(absOut),
+	}
+
+	goals := config.NinjaArgs()
+
+	result := make(map[string]bool)
+	for _, goal := range goals {
+		inputs, err := runNinjaInputs(ctx, config, goal)
+		if err != nil {
+			fmt.Fprintf(os.Stderr, "source_files_used: %v\n", err)
+			return
+		}
+
+		for _, filename := range inputs {
+			if !hasPrefixStrings(filename, excludes) {
+				result[filename] = true
+			}
+		}
+	}
+
+	for _, filename := range sortedStringSetKeys(result) {
+		output.Write([]byte(filename))
+		output.Write([]byte("\n"))
+	}
+
+	output.Flush()
+	success = true
+}
diff --git a/ui/build/test_build.go b/ui/build/test_build.go
index ba53119..87bec93 100644
--- a/ui/build/test_build.go
+++ b/ui/build/test_build.go
@@ -76,8 +76,10 @@
 	// treated as an source file.
 	dexpreoptConfigFilePath := filepath.Join(outDir, "soong", "dexpreopt.config")
 
-	// out/build_date.txt is considered a "source file"
+	// out/build_(date|hostname|number).txt is considered a "source file"
 	buildDatetimeFilePath := filepath.Join(outDir, "build_date.txt")
+	buildHostnameFilePath := filepath.Join(outDir, "soong", "build_hostname.txt")
+	buildNumberFilePath := filepath.Join(outDir, "soong", "build_number.txt")
 
 	// release-config files are generated from the initial lunch or Kati phase
 	// before running soong and ninja.
@@ -102,6 +104,8 @@
 			line == extraVariablesFilePath ||
 			line == dexpreoptConfigFilePath ||
 			line == buildDatetimeFilePath ||
+			line == buildHostnameFilePath ||
+			line == buildNumberFilePath ||
 			strings.HasPrefix(line, releaseConfigDir) ||
 			buildFingerPrintFilePattern.MatchString(line) {
 			// Leaf node is in one of Soong's bootstrap directories, which do not have
@@ -151,7 +155,7 @@
 
 		ts.FinishAction(status.ActionResult{
 			Action: action,
-			Error:  fmt.Errorf(title),
+			Error:  fmt.Errorf("%s", title),
 			Output: sb.String(),
 		})
 		ctx.Fatal("stopping")
diff --git a/testing/code_metadata_internal_proto/Android.bp b/ui/execution_metrics/Android.bp
similarity index 63%
copy from testing/code_metadata_internal_proto/Android.bp
copy to ui/execution_metrics/Android.bp
index 396e44f..542e550 100644
--- a/testing/code_metadata_internal_proto/Android.bp
+++ b/ui/execution_metrics/Android.bp
@@ -1,4 +1,4 @@
-// Copyright 2022 Google Inc. All rights reserved.
+// 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.
@@ -17,17 +17,19 @@
 }
 
 bootstrap_go_package {
-    name: "soong-testing-code_metadata_internal_proto",
-    pkgPath: "android/soong/testing/code_metadata_internal_proto",
+    name: "soong-ui-execution-metrics",
+    pkgPath: "android/soong/ui/execution_metrics",
     deps: [
-        "golang-protobuf-reflect-protoreflect",
-        "golang-protobuf-runtime-protoimpl",
+        "golang-protobuf-proto",
+        "soong-shared",
+        "soong-ui-logger",
+        "soong-ui-execution_metrics_proto",
+        "soong-ui-metrics_proto",
+        "soong-cmd-find_input_delta-proto",
     ],
     srcs: [
-        "code_metadata_internal.pb.go",
+        "execution_metrics.go",
     ],
-    visibility: [
-        "//build/make/tools/metadata",
-        "//build/soong:__subpackages__",
+    testSrcs: [
     ],
 }
diff --git a/ui/execution_metrics/execution_metrics.go b/ui/execution_metrics/execution_metrics.go
new file mode 100644
index 0000000..db78449
--- /dev/null
+++ b/ui/execution_metrics/execution_metrics.go
@@ -0,0 +1,279 @@
+// 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 execution_metrics represents the metrics system for Android Platform Build Systems.
+package execution_metrics
+
+// This is the main heart of the metrics system for Android Platform Build Systems.
+// The starting of the soong_ui (cmd/soong_ui/main.go), the metrics system is
+// initialized by the invocation of New and is then stored in the context
+// (ui/build/context.go) to be used throughout the system. During the build
+// initialization phase, several functions in this file are invoked to store
+// information such as the environment, build configuration and build metadata.
+// There are several scoped code that has Begin() and defer End() functions
+// that captures the metrics and is them added as a perfInfo into the set
+// of the collected metrics. Finally, when soong_ui has finished the build,
+// the defer Dump function is invoked to store the collected metrics to the
+// raw protobuf file in the $OUT directory and this raw protobuf file will be
+// uploaded to the destination. See ui/build/upload.go for more details. The
+// filename of the raw protobuf file and the list of files to be uploaded is
+// defined in cmd/soong_ui/main.go. See ui/metrics/event.go for the explanation
+// of what an event is and how the metrics system is a stack based system.
+
+import (
+	"context"
+	"io/fs"
+	"maps"
+	"os"
+	"path/filepath"
+	"slices"
+	"sync"
+
+	"android/soong/ui/logger"
+
+	fid_proto "android/soong/cmd/find_input_delta/find_input_delta_proto"
+	"android/soong/ui/metrics"
+	soong_execution_proto "android/soong/ui/metrics/execution_metrics_proto"
+	soong_metrics_proto "android/soong/ui/metrics/metrics_proto"
+	"google.golang.org/protobuf/encoding/protowire"
+	"google.golang.org/protobuf/proto"
+)
+
+type ExecutionMetrics struct {
+	MetricsAggregationDir string
+	ctx                   context.Context
+	logger                logger.Logger
+	waitGroup             sync.WaitGroup
+	fileList              *fileList
+}
+
+type fileList struct {
+	totalChanges uint32
+	changes      fileChanges
+	seenFiles    map[string]bool
+}
+
+type fileChanges struct {
+	additions     changeInfo
+	deletions     changeInfo
+	modifications changeInfo
+}
+
+type fileChangeCounts struct {
+	additions     uint32
+	deletions     uint32
+	modifications uint32
+}
+
+type changeInfo struct {
+	total       uint32
+	list        []string
+	byExtension map[string]uint32
+}
+
+var MAXIMUM_FILES uint32 = 50
+
+// Setup the handler for SoongExecutionMetrics.
+func NewExecutionMetrics(log logger.Logger) *ExecutionMetrics {
+	return &ExecutionMetrics{
+		logger:   log,
+		fileList: &fileList{seenFiles: make(map[string]bool)},
+	}
+}
+
+// Save the path for ExecutionMetrics communications.
+func (c *ExecutionMetrics) SetDir(path string) {
+	c.MetricsAggregationDir = path
+}
+
+// Start collecting SoongExecutionMetrics.
+func (c *ExecutionMetrics) Start() {
+	if c.MetricsAggregationDir == "" {
+		return
+	}
+
+	tmpDir := c.MetricsAggregationDir + ".rm"
+	if _, err := fs.Stat(os.DirFS("."), c.MetricsAggregationDir); err == nil {
+		if err = os.RemoveAll(tmpDir); err != nil {
+			c.logger.Fatalf("Failed to remove %s: %v", tmpDir, err)
+		}
+		if err = os.Rename(c.MetricsAggregationDir, tmpDir); err != nil {
+			c.logger.Fatalf("Failed to rename %s to %s: %v", c.MetricsAggregationDir, tmpDir)
+		}
+	}
+	if err := os.MkdirAll(c.MetricsAggregationDir, 0777); err != nil {
+		c.logger.Fatalf("Failed to create %s: %v", c.MetricsAggregationDir)
+	}
+
+	c.waitGroup.Add(1)
+	go func(d string) {
+		defer c.waitGroup.Done()
+		os.RemoveAll(d)
+	}(tmpDir)
+
+	c.logger.Verbosef("ExecutionMetrics running\n")
+}
+
+type hasTrace interface {
+	BeginTrace(name, desc string)
+	EndTrace()
+}
+
+// Aggregate any execution metrics.
+func (c *ExecutionMetrics) Finish(ctx hasTrace) {
+	ctx.BeginTrace(metrics.RunSoong, "execution_metrics.Finish")
+	defer ctx.EndTrace()
+	if c.MetricsAggregationDir == "" {
+		return
+	}
+	c.waitGroup.Wait()
+
+	// Find and process all of the metrics files.
+	aggFs := os.DirFS(c.MetricsAggregationDir)
+	fs.WalkDir(aggFs, ".", func(path string, d fs.DirEntry, err error) error {
+		if err != nil {
+			c.logger.Fatalf("ExecutionMetrics.Finish: Error walking %s: %v", c.MetricsAggregationDir, err)
+		}
+		if d.IsDir() {
+			return nil
+		}
+		path = filepath.Join(c.MetricsAggregationDir, path)
+		r, err := os.ReadFile(path)
+		if err != nil {
+			c.logger.Fatalf("ExecutionMetrics.Finish: Failed to read %s: %v", path, err)
+		}
+		msg := &soong_execution_proto.SoongExecutionMetrics{}
+		err = proto.Unmarshal(r, msg)
+		if err != nil {
+			c.logger.Verbosef("ExecutionMetrics.Finish: Error unmarshalling SoongExecutionMetrics message: %v\n", err)
+			return nil
+		}
+		switch {
+		case msg.GetFileList() != nil:
+			if err := c.fileList.aggregateFileList(msg.GetFileList()); err != nil {
+				c.logger.Verbosef("ExecutionMetrics.Finish: Error processing SoongExecutionMetrics message: %v\n", err)
+			}
+		// Status update for all others.
+		default:
+			tag, _ := protowire.ConsumeVarint(r)
+			id, _ := protowire.DecodeTag(tag)
+			c.logger.Verbosef("ExecutionMetrics.Finish: Unexpected SoongExecutionMetrics submessage id=%d\n", id)
+		}
+		return nil
+	})
+}
+
+func (fl *fileList) aggregateFileList(msg *fid_proto.FileList) error {
+	fl.updateChangeInfo(msg.GetAdditions(), &fl.changes.additions)
+	fl.updateChangeInfo(msg.GetDeletions(), &fl.changes.deletions)
+	fl.updateChangeInfo(msg.GetChanges(), &fl.changes.modifications)
+	return nil
+}
+
+func (fl *fileList) updateChangeInfo(list []string, info *changeInfo) {
+	for _, filename := range list {
+		if fl.seenFiles[filename] {
+			continue
+		}
+		fl.seenFiles[filename] = true
+		if info.total < MAXIMUM_FILES {
+			info.list = append(info.list, filename)
+		}
+		ext := filepath.Ext(filename)
+		if info.byExtension == nil {
+			info.byExtension = make(map[string]uint32)
+		}
+		info.byExtension[ext] += 1
+		info.total += 1
+		fl.totalChanges += 1
+	}
+}
+
+func (c *ExecutionMetrics) Dump(path string, args []string) error {
+	if c.MetricsAggregationDir == "" {
+		return nil
+	}
+	msg := c.GetMetrics(args)
+
+	if _, err := os.Stat(filepath.Dir(path)); err != nil {
+		if err = os.MkdirAll(filepath.Dir(path), 0775); err != nil {
+			return err
+		}
+	}
+	data, err := proto.Marshal(msg)
+	if err != nil {
+		return err
+	}
+	return os.WriteFile(path, data, 0644)
+}
+
+func (c *ExecutionMetrics) GetMetrics(args []string) *soong_metrics_proto.ExecutionMetrics {
+	return &soong_metrics_proto.ExecutionMetrics{
+		CommandArgs:  args,
+		ChangedFiles: c.getChangedFiles(),
+	}
+}
+
+func (c *ExecutionMetrics) getChangedFiles() *soong_metrics_proto.AggregatedFileList {
+	fl := c.fileList
+	if fl == nil {
+		return nil
+	}
+	var count uint32
+	fileCounts := make(map[string]*soong_metrics_proto.FileCount)
+	ret := &soong_metrics_proto.AggregatedFileList{TotalDelta: proto.Uint32(c.fileList.totalChanges)}
+
+	// MAXIMUM_FILES is the upper bound on total file names reported.
+	if limit := min(MAXIMUM_FILES-min(MAXIMUM_FILES, count), fl.changes.additions.total); limit > 0 {
+		ret.Additions = fl.changes.additions.list[:limit]
+		count += limit
+	}
+	if limit := min(MAXIMUM_FILES-min(MAXIMUM_FILES, count), fl.changes.modifications.total); limit > 0 {
+		ret.Changes = fl.changes.modifications.list[:limit]
+		count += limit
+	}
+	if limit := min(MAXIMUM_FILES-min(MAXIMUM_FILES, count), fl.changes.deletions.total); limit > 0 {
+		ret.Deletions = fl.changes.deletions.list[:limit]
+		count += limit
+	}
+
+	addExt := func(key string) *soong_metrics_proto.FileCount {
+		// Create the fileCounts map entry if needed, and return the address to the caller.
+		if _, ok := fileCounts[key]; !ok {
+			fileCounts[key] = &soong_metrics_proto.FileCount{Extension: proto.String(key)}
+		}
+		return fileCounts[key]
+	}
+	addCount := func(loc **uint32, count uint32) {
+		if *loc == nil {
+			*loc = proto.Uint32(0)
+		}
+		**loc += count
+	}
+	for k, v := range fl.changes.additions.byExtension {
+		addCount(&addExt(k).Additions, v)
+	}
+	for k, v := range fl.changes.modifications.byExtension {
+		addCount(&addExt(k).Modifications, v)
+	}
+	for k, v := range fl.changes.deletions.byExtension {
+		addCount(&addExt(k).Deletions, v)
+	}
+
+	keys := slices.Sorted(maps.Keys(fileCounts))
+	for _, k := range keys {
+		ret.Counts = append(ret.Counts, fileCounts[k])
+	}
+	return ret
+}
diff --git a/ui/execution_metrics/execution_metrics_test.go b/ui/execution_metrics/execution_metrics_test.go
new file mode 100644
index 0000000..28fa973
--- /dev/null
+++ b/ui/execution_metrics/execution_metrics_test.go
@@ -0,0 +1,63 @@
+// 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 execution_metrics represents the metrics system for Android Platform Build Systems.
+package execution_metrics
+
+import (
+	"reflect"
+	"testing"
+
+	fid_proto "android/soong/cmd/find_input_delta/find_input_delta_proto"
+)
+
+func TestUpdateChangeInfo(t *testing.T) {
+	testCases := []struct {
+		Name     string
+		Message  *fid_proto.FileList
+		FileList *fileList
+		Expected *fileList
+	}{
+		{
+			Name: "various",
+			Message: &fid_proto.FileList{
+				Additions: []string{"file1", "file2", "file3", "file2"},
+				Deletions: []string{"file5.go", "file6"},
+			},
+			FileList: &fileList{seenFiles: make(map[string]bool)},
+			Expected: &fileList{
+				seenFiles:    map[string]bool{"file1": true, "file2": true, "file3": true, "file5.go": true, "file6": true},
+				totalChanges: 5,
+				changes: fileChanges{
+					additions: changeInfo{
+						total:       3,
+						list:        []string{"file1", "file2", "file3"},
+						byExtension: map[string]uint32{"": 3},
+					},
+					deletions: changeInfo{
+						total:       2,
+						list:        []string{"file5.go", "file6"},
+						byExtension: map[string]uint32{"": 1, ".go": 1},
+					},
+				},
+			},
+		},
+	}
+	for _, tc := range testCases {
+		tc.FileList.aggregateFileList(tc.Message)
+		if !reflect.DeepEqual(tc.FileList, tc.Expected) {
+			t.Errorf("Expected: %v, Actual: %v", tc.Expected, tc.FileList)
+		}
+	}
+}
diff --git a/ui/metrics/Android.bp b/ui/metrics/Android.bp
index bd1517c..2e19f7a 100644
--- a/ui/metrics/Android.bp
+++ b/ui/metrics/Android.bp
@@ -21,20 +21,34 @@
     pkgPath: "android/soong/ui/metrics",
     deps: [
         "golang-protobuf-proto",
-        "soong-ui-bp2build_metrics_proto",
-        "soong-ui-bazel_metrics_proto",
+        "soong-finder-fs",
         "soong-ui-metrics_upload_proto",
         "soong-ui-metrics_proto",
         "soong-ui-mk_metrics_proto",
         "soong-shared",
+        "soong-ui-execution_metrics_proto",
     ],
     srcs: [
+        "hostinfo.go",
         "metrics.go",
         "event.go",
     ],
     testSrcs: [
         "event_test.go",
     ],
+    linux: {
+        srcs: [
+            "hostinfo_linux.go",
+        ],
+        testSrcs: [
+            "hostinfo_linux_test.go",
+        ],
+    },
+    darwin: {
+        srcs: [
+            "hostinfo_darwin.go",
+        ],
+    },
 }
 
 bootstrap_go_package {
@@ -50,6 +64,19 @@
 }
 
 bootstrap_go_package {
+    name: "soong-ui-execution_metrics_proto",
+    pkgPath: "android/soong/ui/metrics/execution_metrics_proto",
+    deps: [
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+        "soong-cmd-find_input_delta-proto",
+    ],
+    srcs: [
+        "execution_metrics_proto/execution_metrics.pb.go",
+    ],
+}
+
+bootstrap_go_package {
     name: "soong-ui-metrics_upload_proto",
     pkgPath: "android/soong/ui/metrics/upload_proto",
     deps: [
@@ -62,30 +89,6 @@
 }
 
 bootstrap_go_package {
-    name: "soong-ui-bp2build_metrics_proto",
-    pkgPath: "android/soong/ui/metrics/bp2build_metrics_proto",
-    deps: [
-        "golang-protobuf-reflect-protoreflect",
-        "golang-protobuf-runtime-protoimpl",
-    ],
-    srcs: [
-        "bp2build_metrics_proto/bp2build_metrics.pb.go",
-    ],
-}
-
-bootstrap_go_package {
-    name: "soong-ui-bazel_metrics_proto",
-    pkgPath: "android/soong/ui/metrics/bazel_metrics_proto",
-    deps: [
-        "golang-protobuf-reflect-protoreflect",
-        "golang-protobuf-runtime-protoimpl",
-    ],
-    srcs: [
-        "bazel_metrics_proto/bazel_metrics.pb.go",
-    ],
-}
-
-bootstrap_go_package {
     name: "soong-ui-mk_metrics_proto",
     pkgPath: "android/soong/ui/metrics/mk_metrics_proto",
     deps: [
diff --git a/ui/metrics/BUILD.bazel b/ui/metrics/BUILD.bazel
deleted file mode 100644
index ca39c59..0000000
--- a/ui/metrics/BUILD.bazel
+++ /dev/null
@@ -1,31 +0,0 @@
-# Copyright (C) 2023 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.
-
-load("//build/bazel/rules/python:py_proto.bzl", "py_proto_library")
-
-py_proto_library(
-    name = "metrics-py-proto",
-    visibility = ["//build/bazel/scripts:__subpackages__"],
-    deps = [":metrics-proto"],
-)
-
-proto_library(
-    name = "metrics-proto",
-    srcs = [
-        "bazel_metrics_proto/bazel_metrics.proto",
-        "bp2build_metrics_proto/bp2build_metrics.proto",
-        "metrics_proto/metrics.proto",
-    ],
-    strip_import_prefix = "",
-)
diff --git a/ui/metrics/bazel_metrics_proto/bazel_metrics.pb.go b/ui/metrics/bazel_metrics_proto/bazel_metrics.pb.go
deleted file mode 100644
index 8b97b83..0000000
--- a/ui/metrics/bazel_metrics_proto/bazel_metrics.pb.go
+++ /dev/null
@@ -1,290 +0,0 @@
-// Copyright 2022 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// versions:
-// 	protoc-gen-go v1.30.0
-// 	protoc        v3.21.12
-// source: bazel_metrics.proto
-
-package bazel_metrics_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 BazelMetrics struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	PhaseTimings []*PhaseTiming `protobuf:"bytes,1,rep,name=phase_timings,json=phaseTimings,proto3" json:"phase_timings,omitempty"`
-	Total        *int64         `protobuf:"varint,2,opt,name=total,proto3,oneof" json:"total,omitempty"`
-	ExitCode     *int32         `protobuf:"varint,3,opt,name=exit_code,json=exitCode,proto3,oneof" json:"exit_code,omitempty"`
-	BesId        *string        `protobuf:"bytes,4,opt,name=bes_id,json=besId,proto3,oneof" json:"bes_id,omitempty"`
-}
-
-func (x *BazelMetrics) Reset() {
-	*x = BazelMetrics{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_bazel_metrics_proto_msgTypes[0]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *BazelMetrics) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*BazelMetrics) ProtoMessage() {}
-
-func (x *BazelMetrics) ProtoReflect() protoreflect.Message {
-	mi := &file_bazel_metrics_proto_msgTypes[0]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use BazelMetrics.ProtoReflect.Descriptor instead.
-func (*BazelMetrics) Descriptor() ([]byte, []int) {
-	return file_bazel_metrics_proto_rawDescGZIP(), []int{0}
-}
-
-func (x *BazelMetrics) GetPhaseTimings() []*PhaseTiming {
-	if x != nil {
-		return x.PhaseTimings
-	}
-	return nil
-}
-
-func (x *BazelMetrics) GetTotal() int64 {
-	if x != nil && x.Total != nil {
-		return *x.Total
-	}
-	return 0
-}
-
-func (x *BazelMetrics) GetExitCode() int32 {
-	if x != nil && x.ExitCode != nil {
-		return *x.ExitCode
-	}
-	return 0
-}
-
-func (x *BazelMetrics) GetBesId() string {
-	if x != nil && x.BesId != nil {
-		return *x.BesId
-	}
-	return ""
-}
-
-type PhaseTiming struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	// E.g. "execution", "analysis", "launch"
-	PhaseName     *string `protobuf:"bytes,1,opt,name=phase_name,json=phaseName,proto3,oneof" json:"phase_name,omitempty"`
-	DurationNanos *int64  `protobuf:"varint,2,opt,name=duration_nanos,json=durationNanos,proto3,oneof" json:"duration_nanos,omitempty"`
-	// What portion of the build time this phase took, with ten-thousandths precision.
-	// E.g., 1111 = 11.11%, 111 = 1.11%
-	PortionOfBuildTime *int32 `protobuf:"varint,3,opt,name=portion_of_build_time,json=portionOfBuildTime,proto3,oneof" json:"portion_of_build_time,omitempty"`
-}
-
-func (x *PhaseTiming) Reset() {
-	*x = PhaseTiming{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_bazel_metrics_proto_msgTypes[1]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *PhaseTiming) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*PhaseTiming) ProtoMessage() {}
-
-func (x *PhaseTiming) ProtoReflect() protoreflect.Message {
-	mi := &file_bazel_metrics_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 PhaseTiming.ProtoReflect.Descriptor instead.
-func (*PhaseTiming) Descriptor() ([]byte, []int) {
-	return file_bazel_metrics_proto_rawDescGZIP(), []int{1}
-}
-
-func (x *PhaseTiming) GetPhaseName() string {
-	if x != nil && x.PhaseName != nil {
-		return *x.PhaseName
-	}
-	return ""
-}
-
-func (x *PhaseTiming) GetDurationNanos() int64 {
-	if x != nil && x.DurationNanos != nil {
-		return *x.DurationNanos
-	}
-	return 0
-}
-
-func (x *PhaseTiming) GetPortionOfBuildTime() int32 {
-	if x != nil && x.PortionOfBuildTime != nil {
-		return *x.PortionOfBuildTime
-	}
-	return 0
-}
-
-var File_bazel_metrics_proto protoreflect.FileDescriptor
-
-var file_bazel_metrics_proto_rawDesc = []byte{
-	0x0a, 0x13, 0x62, 0x61, 0x7a, 0x65, 0x6c, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x19, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69,
-	0x6c, 0x64, 0x5f, 0x62, 0x61, 0x7a, 0x65, 0x6c, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
-	0x22, 0xd7, 0x01, 0x0a, 0x0c, 0x42, 0x61, 0x7a, 0x65, 0x6c, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63,
-	0x73, 0x12, 0x4b, 0x0a, 0x0d, 0x70, 0x68, 0x61, 0x73, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x69, 0x6e,
-	0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67,
-	0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x62, 0x61, 0x7a, 0x65, 0x6c, 0x5f, 0x6d, 0x65, 0x74,
-	0x72, 0x69, 0x63, 0x73, 0x2e, 0x50, 0x68, 0x61, 0x73, 0x65, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67,
-	0x52, 0x0c, 0x70, 0x68, 0x61, 0x73, 0x65, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x19,
-	0x0a, 0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x48, 0x00, 0x52,
-	0x05, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x88, 0x01, 0x01, 0x12, 0x20, 0x0a, 0x09, 0x65, 0x78, 0x69,
-	0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x48, 0x01, 0x52, 0x08,
-	0x65, 0x78, 0x69, 0x74, 0x43, 0x6f, 0x64, 0x65, 0x88, 0x01, 0x01, 0x12, 0x1a, 0x0a, 0x06, 0x62,
-	0x65, 0x73, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x05, 0x62,
-	0x65, 0x73, 0x49, 0x64, 0x88, 0x01, 0x01, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x74, 0x6f, 0x74, 0x61,
-	0x6c, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x42,
-	0x09, 0x0a, 0x07, 0x5f, 0x62, 0x65, 0x73, 0x5f, 0x69, 0x64, 0x22, 0xd1, 0x01, 0x0a, 0x0b, 0x50,
-	0x68, 0x61, 0x73, 0x65, 0x54, 0x69, 0x6d, 0x69, 0x6e, 0x67, 0x12, 0x22, 0x0a, 0x0a, 0x70, 0x68,
-	0x61, 0x73, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00,
-	0x52, 0x09, 0x70, 0x68, 0x61, 0x73, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x88, 0x01, 0x01, 0x12, 0x2a,
-	0x0a, 0x0e, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x61, 0x6e, 0x6f, 0x73,
-	0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x48, 0x01, 0x52, 0x0d, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69,
-	0x6f, 0x6e, 0x4e, 0x61, 0x6e, 0x6f, 0x73, 0x88, 0x01, 0x01, 0x12, 0x36, 0x0a, 0x15, 0x70, 0x6f,
-	0x72, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6f, 0x66, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x74,
-	0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x48, 0x02, 0x52, 0x12, 0x70, 0x6f, 0x72,
-	0x74, 0x69, 0x6f, 0x6e, 0x4f, 0x66, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x88,
-	0x01, 0x01, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x70, 0x68, 0x61, 0x73, 0x65, 0x5f, 0x6e, 0x61, 0x6d,
-	0x65, 0x42, 0x11, 0x0a, 0x0f, 0x5f, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e,
-	0x61, 0x6e, 0x6f, 0x73, 0x42, 0x18, 0x0a, 0x16, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x69, 0x6f, 0x6e,
-	0x5f, 0x6f, 0x66, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x42, 0x2e,
-	0x5a, 0x2c, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f,
-	0x75, 0x69, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x62, 0x61, 0x7a, 0x65, 0x6c,
-	0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
-}
-
-var (
-	file_bazel_metrics_proto_rawDescOnce sync.Once
-	file_bazel_metrics_proto_rawDescData = file_bazel_metrics_proto_rawDesc
-)
-
-func file_bazel_metrics_proto_rawDescGZIP() []byte {
-	file_bazel_metrics_proto_rawDescOnce.Do(func() {
-		file_bazel_metrics_proto_rawDescData = protoimpl.X.CompressGZIP(file_bazel_metrics_proto_rawDescData)
-	})
-	return file_bazel_metrics_proto_rawDescData
-}
-
-var file_bazel_metrics_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
-var file_bazel_metrics_proto_goTypes = []interface{}{
-	(*BazelMetrics)(nil), // 0: soong_build_bazel_metrics.BazelMetrics
-	(*PhaseTiming)(nil),  // 1: soong_build_bazel_metrics.PhaseTiming
-}
-var file_bazel_metrics_proto_depIdxs = []int32{
-	1, // 0: soong_build_bazel_metrics.BazelMetrics.phase_timings:type_name -> soong_build_bazel_metrics.PhaseTiming
-	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_bazel_metrics_proto_init() }
-func file_bazel_metrics_proto_init() {
-	if File_bazel_metrics_proto != nil {
-		return
-	}
-	if !protoimpl.UnsafeEnabled {
-		file_bazel_metrics_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*BazelMetrics); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_bazel_metrics_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*PhaseTiming); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-	}
-	file_bazel_metrics_proto_msgTypes[0].OneofWrappers = []interface{}{}
-	file_bazel_metrics_proto_msgTypes[1].OneofWrappers = []interface{}{}
-	type x struct{}
-	out := protoimpl.TypeBuilder{
-		File: protoimpl.DescBuilder{
-			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
-			RawDescriptor: file_bazel_metrics_proto_rawDesc,
-			NumEnums:      0,
-			NumMessages:   2,
-			NumExtensions: 0,
-			NumServices:   0,
-		},
-		GoTypes:           file_bazel_metrics_proto_goTypes,
-		DependencyIndexes: file_bazel_metrics_proto_depIdxs,
-		MessageInfos:      file_bazel_metrics_proto_msgTypes,
-	}.Build()
-	File_bazel_metrics_proto = out.File
-	file_bazel_metrics_proto_rawDesc = nil
-	file_bazel_metrics_proto_goTypes = nil
-	file_bazel_metrics_proto_depIdxs = nil
-}
diff --git a/ui/metrics/bazel_metrics_proto/bazel_metrics.proto b/ui/metrics/bazel_metrics_proto/bazel_metrics.proto
deleted file mode 100644
index e45d2bf..0000000
--- a/ui/metrics/bazel_metrics_proto/bazel_metrics.proto
+++ /dev/null
@@ -1,34 +0,0 @@
-// Copyright 2022 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto3";
-
-package soong_build_bazel_metrics;
-option go_package = "android/soong/ui/metrics/bazel_metrics_proto";
-
-message BazelMetrics {
-  repeated PhaseTiming phase_timings = 1;
-  optional int64 total = 2;
-  optional int32 exit_code = 3;
-  optional string bes_id = 4;
-}
-
-message PhaseTiming {
-  // E.g. "execution", "analysis", "launch"
-  optional string phase_name = 1;
-  optional int64 duration_nanos = 2;
-  // What portion of the build time this phase took, with ten-thousandths precision.
-  // E.g., 1111 = 11.11%, 111 = 1.11%
-  optional int32 portion_of_build_time = 3;
-}
diff --git a/ui/metrics/bazel_metrics_proto/regen.sh b/ui/metrics/bazel_metrics_proto/regen.sh
deleted file mode 100755
index 2cf2bf6..0000000
--- a/ui/metrics/bazel_metrics_proto/regen.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/bash -e
-
-# Copyright 2022 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.
-
-# Generates the golang source file of bp2build_metrics.proto protobuf file.
-
-function die() { echo "ERROR: $1" >&2; exit 1; }
-
-readonly error_msg="Maybe you need to run 'lunch aosp_arm-eng && m aprotoc blueprint_tools'?"
-
-if ! hash aprotoc &>/dev/null; then
-  die "could not find aprotoc. ${error_msg}"
-fi
-
-if ! aprotoc --go_out=paths=source_relative:. bazel_metrics.proto; then
-  die "build failed. ${error_msg}"
-fi
diff --git a/ui/metrics/bp2build_metrics_proto/bp2build_metrics.pb.go b/ui/metrics/bp2build_metrics_proto/bp2build_metrics.pb.go
deleted file mode 100644
index b34c2b6..0000000
--- a/ui/metrics/bp2build_metrics_proto/bp2build_metrics.pb.go
+++ /dev/null
@@ -1,590 +0,0 @@
-// Copyright 2021 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-// Code generated by protoc-gen-go. DO NOT EDIT.
-// versions:
-// 	protoc-gen-go v1.30.0
-// 	protoc        v3.21.12
-// source: bp2build_metrics.proto
-
-package bp2build_metrics_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 UnconvertedReasonType int32
-
-const (
-	// Bp2build does not know how to convert this specific module for some reason
-	// not covered by other reason types. The reason detail should explain the
-	// specific issue.
-	UnconvertedReasonType_UNSUPPORTED UnconvertedReasonType = 0
-	// The module was already defined in a BUILD file available in the source tree.
-	UnconvertedReasonType_DEFINED_IN_BUILD_FILE UnconvertedReasonType = 1
-	// The module was explicitly denylisted by name.
-	UnconvertedReasonType_DENYLISTED UnconvertedReasonType = 2
-	// The module's type has no bp2build implementation.
-	UnconvertedReasonType_TYPE_UNSUPPORTED UnconvertedReasonType = 3
-	// The module has a property not yet supported. The detail field should
-	// name the unsupported property name.
-	UnconvertedReasonType_PROPERTY_UNSUPPORTED UnconvertedReasonType = 4
-	// The module has an unconverted dependency. The detail should consist of
-	// the name of the unconverted module.
-	UnconvertedReasonType_UNCONVERTED_DEP UnconvertedReasonType = 5
-	// The module has a source file with the same name as the module itself.
-	UnconvertedReasonType_SRC_NAME_COLLISION UnconvertedReasonType = 6
-)
-
-// Enum value maps for UnconvertedReasonType.
-var (
-	UnconvertedReasonType_name = map[int32]string{
-		0: "UNSUPPORTED",
-		1: "DEFINED_IN_BUILD_FILE",
-		2: "DENYLISTED",
-		3: "TYPE_UNSUPPORTED",
-		4: "PROPERTY_UNSUPPORTED",
-		5: "UNCONVERTED_DEP",
-		6: "SRC_NAME_COLLISION",
-	}
-	UnconvertedReasonType_value = map[string]int32{
-		"UNSUPPORTED":           0,
-		"DEFINED_IN_BUILD_FILE": 1,
-		"DENYLISTED":            2,
-		"TYPE_UNSUPPORTED":      3,
-		"PROPERTY_UNSUPPORTED":  4,
-		"UNCONVERTED_DEP":       5,
-		"SRC_NAME_COLLISION":    6,
-	}
-)
-
-func (x UnconvertedReasonType) Enum() *UnconvertedReasonType {
-	p := new(UnconvertedReasonType)
-	*p = x
-	return p
-}
-
-func (x UnconvertedReasonType) String() string {
-	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
-}
-
-func (UnconvertedReasonType) Descriptor() protoreflect.EnumDescriptor {
-	return file_bp2build_metrics_proto_enumTypes[0].Descriptor()
-}
-
-func (UnconvertedReasonType) Type() protoreflect.EnumType {
-	return &file_bp2build_metrics_proto_enumTypes[0]
-}
-
-func (x UnconvertedReasonType) Number() protoreflect.EnumNumber {
-	return protoreflect.EnumNumber(x)
-}
-
-// Deprecated: Use UnconvertedReasonType.Descriptor instead.
-func (UnconvertedReasonType) EnumDescriptor() ([]byte, []int) {
-	return file_bp2build_metrics_proto_rawDescGZIP(), []int{0}
-}
-
-type Bp2BuildMetrics struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	// Total number of Soong modules converted to generated targets
-	GeneratedModuleCount uint64 `protobuf:"varint,1,opt,name=generatedModuleCount,proto3" json:"generatedModuleCount,omitempty"`
-	// Total number of Soong modules converted to handcrafted targets
-	HandCraftedModuleCount uint64 `protobuf:"varint,2,opt,name=handCraftedModuleCount,proto3" json:"handCraftedModuleCount,omitempty"`
-	// Total number of unconverted Soong modules
-	UnconvertedModuleCount uint64 `protobuf:"varint,3,opt,name=unconvertedModuleCount,proto3" json:"unconvertedModuleCount,omitempty"`
-	// Counts of symlinks in synthetic bazel workspace
-	WorkspaceSymlinkCount uint64 `protobuf:"varint,9,opt,name=workspaceSymlinkCount,proto3" json:"workspaceSymlinkCount,omitempty"`
-	// Counts of mkdir calls during creation of synthetic bazel workspace
-	WorkspaceMkDirCount uint64 `protobuf:"varint,10,opt,name=workspaceMkDirCount,proto3" json:"workspaceMkDirCount,omitempty"`
-	// Counts of generated Bazel targets per Bazel rule class
-	RuleClassCount map[string]uint64 `protobuf:"bytes,4,rep,name=ruleClassCount,proto3" json:"ruleClassCount,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
-	// List of converted modules
-	ConvertedModules []string `protobuf:"bytes,5,rep,name=convertedModules,proto3" json:"convertedModules,omitempty"`
-	// Unconverted modules, mapped to the reason the module was not converted.
-	UnconvertedModules map[string]*UnconvertedReason `protobuf:"bytes,11,rep,name=unconvertedModules,proto3" json:"unconvertedModules,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
-	// Counts of converted modules by module type.
-	ConvertedModuleTypeCount map[string]uint64 `protobuf:"bytes,6,rep,name=convertedModuleTypeCount,proto3" json:"convertedModuleTypeCount,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
-	// Counts of total modules by module type.
-	TotalModuleTypeCount map[string]uint64 `protobuf:"bytes,7,rep,name=totalModuleTypeCount,proto3" json:"totalModuleTypeCount,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"varint,2,opt,name=value,proto3"`
-	// List of traced runtime events of bp2build, useful for tracking bp2build
-	// runtime.
-	Events []*Event `protobuf:"bytes,8,rep,name=events,proto3" json:"events,omitempty"`
-}
-
-func (x *Bp2BuildMetrics) Reset() {
-	*x = Bp2BuildMetrics{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_bp2build_metrics_proto_msgTypes[0]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *Bp2BuildMetrics) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*Bp2BuildMetrics) ProtoMessage() {}
-
-func (x *Bp2BuildMetrics) ProtoReflect() protoreflect.Message {
-	mi := &file_bp2build_metrics_proto_msgTypes[0]
-	if protoimpl.UnsafeEnabled && x != nil {
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		if ms.LoadMessageInfo() == nil {
-			ms.StoreMessageInfo(mi)
-		}
-		return ms
-	}
-	return mi.MessageOf(x)
-}
-
-// Deprecated: Use Bp2BuildMetrics.ProtoReflect.Descriptor instead.
-func (*Bp2BuildMetrics) Descriptor() ([]byte, []int) {
-	return file_bp2build_metrics_proto_rawDescGZIP(), []int{0}
-}
-
-func (x *Bp2BuildMetrics) GetGeneratedModuleCount() uint64 {
-	if x != nil {
-		return x.GeneratedModuleCount
-	}
-	return 0
-}
-
-func (x *Bp2BuildMetrics) GetHandCraftedModuleCount() uint64 {
-	if x != nil {
-		return x.HandCraftedModuleCount
-	}
-	return 0
-}
-
-func (x *Bp2BuildMetrics) GetUnconvertedModuleCount() uint64 {
-	if x != nil {
-		return x.UnconvertedModuleCount
-	}
-	return 0
-}
-
-func (x *Bp2BuildMetrics) GetWorkspaceSymlinkCount() uint64 {
-	if x != nil {
-		return x.WorkspaceSymlinkCount
-	}
-	return 0
-}
-
-func (x *Bp2BuildMetrics) GetWorkspaceMkDirCount() uint64 {
-	if x != nil {
-		return x.WorkspaceMkDirCount
-	}
-	return 0
-}
-
-func (x *Bp2BuildMetrics) GetRuleClassCount() map[string]uint64 {
-	if x != nil {
-		return x.RuleClassCount
-	}
-	return nil
-}
-
-func (x *Bp2BuildMetrics) GetConvertedModules() []string {
-	if x != nil {
-		return x.ConvertedModules
-	}
-	return nil
-}
-
-func (x *Bp2BuildMetrics) GetUnconvertedModules() map[string]*UnconvertedReason {
-	if x != nil {
-		return x.UnconvertedModules
-	}
-	return nil
-}
-
-func (x *Bp2BuildMetrics) GetConvertedModuleTypeCount() map[string]uint64 {
-	if x != nil {
-		return x.ConvertedModuleTypeCount
-	}
-	return nil
-}
-
-func (x *Bp2BuildMetrics) GetTotalModuleTypeCount() map[string]uint64 {
-	if x != nil {
-		return x.TotalModuleTypeCount
-	}
-	return nil
-}
-
-func (x *Bp2BuildMetrics) GetEvents() []*Event {
-	if x != nil {
-		return x.Events
-	}
-	return nil
-}
-
-// Traced runtime event of bp2build.
-type Event struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	// The event name.
-	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
-	// The absolute start time of the event
-	// The number of nanoseconds elapsed since January 1, 1970 UTC.
-	StartTime uint64 `protobuf:"varint,2,opt,name=start_time,json=startTime,proto3" json:"start_time,omitempty"`
-	// The real running time.
-	// The number of nanoseconds elapsed since start_time.
-	RealTime uint64 `protobuf:"varint,3,opt,name=real_time,json=realTime,proto3" json:"real_time,omitempty"`
-}
-
-func (x *Event) Reset() {
-	*x = Event{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_bp2build_metrics_proto_msgTypes[1]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *Event) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*Event) ProtoMessage() {}
-
-func (x *Event) ProtoReflect() protoreflect.Message {
-	mi := &file_bp2build_metrics_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 Event.ProtoReflect.Descriptor instead.
-func (*Event) Descriptor() ([]byte, []int) {
-	return file_bp2build_metrics_proto_rawDescGZIP(), []int{1}
-}
-
-func (x *Event) GetName() string {
-	if x != nil {
-		return x.Name
-	}
-	return ""
-}
-
-func (x *Event) GetStartTime() uint64 {
-	if x != nil {
-		return x.StartTime
-	}
-	return 0
-}
-
-func (x *Event) GetRealTime() uint64 {
-	if x != nil {
-		return x.RealTime
-	}
-	return 0
-}
-
-type UnconvertedReason struct {
-	state         protoimpl.MessageState
-	sizeCache     protoimpl.SizeCache
-	unknownFields protoimpl.UnknownFields
-
-	// The type of reason that the module could not be converted.
-	Type UnconvertedReasonType `protobuf:"varint,1,opt,name=type,proto3,enum=soong_build_bp2build_metrics.UnconvertedReasonType" json:"type,omitempty"`
-	// Descriptive details describing why the module could not be converted.
-	// This detail should be kept very short and should be in the context of
-	// the type. (Otherwise, this would significantly bloat metrics.)
-	Detail string `protobuf:"bytes,2,opt,name=detail,proto3" json:"detail,omitempty"`
-}
-
-func (x *UnconvertedReason) Reset() {
-	*x = UnconvertedReason{}
-	if protoimpl.UnsafeEnabled {
-		mi := &file_bp2build_metrics_proto_msgTypes[2]
-		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
-		ms.StoreMessageInfo(mi)
-	}
-}
-
-func (x *UnconvertedReason) String() string {
-	return protoimpl.X.MessageStringOf(x)
-}
-
-func (*UnconvertedReason) ProtoMessage() {}
-
-func (x *UnconvertedReason) ProtoReflect() protoreflect.Message {
-	mi := &file_bp2build_metrics_proto_msgTypes[2]
-	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 UnconvertedReason.ProtoReflect.Descriptor instead.
-func (*UnconvertedReason) Descriptor() ([]byte, []int) {
-	return file_bp2build_metrics_proto_rawDescGZIP(), []int{2}
-}
-
-func (x *UnconvertedReason) GetType() UnconvertedReasonType {
-	if x != nil {
-		return x.Type
-	}
-	return UnconvertedReasonType_UNSUPPORTED
-}
-
-func (x *UnconvertedReason) GetDetail() string {
-	if x != nil {
-		return x.Detail
-	}
-	return ""
-}
-
-var File_bp2build_metrics_proto protoreflect.FileDescriptor
-
-var file_bp2build_metrics_proto_rawDesc = []byte{
-	0x0a, 0x16, 0x62, 0x70, 0x32, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69,
-	0x63, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x1c, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f,
-	0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x62, 0x70, 0x32, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d,
-	0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x22, 0xc0, 0x09, 0x0a, 0x0f, 0x42, 0x70, 0x32, 0x42, 0x75,
-	0x69, 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x32, 0x0a, 0x14, 0x67, 0x65,
-	0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x75,
-	0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x14, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61,
-	0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x36,
-	0x0a, 0x16, 0x68, 0x61, 0x6e, 0x64, 0x43, 0x72, 0x61, 0x66, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64,
-	0x75, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x16,
-	0x68, 0x61, 0x6e, 0x64, 0x43, 0x72, 0x61, 0x66, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c,
-	0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x36, 0x0a, 0x16, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x76,
-	0x65, 0x72, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74,
-	0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x16, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72,
-	0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x34,
-	0x0a, 0x15, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x79, 0x6d, 0x6c, 0x69,
-	0x6e, 0x6b, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x15, 0x77,
-	0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x53, 0x79, 0x6d, 0x6c, 0x69, 0x6e, 0x6b, 0x43,
-	0x6f, 0x75, 0x6e, 0x74, 0x12, 0x30, 0x0a, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63,
-	0x65, 0x4d, 0x6b, 0x44, 0x69, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28,
-	0x04, 0x52, 0x13, 0x77, 0x6f, 0x72, 0x6b, 0x73, 0x70, 0x61, 0x63, 0x65, 0x4d, 0x6b, 0x44, 0x69,
-	0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x69, 0x0a, 0x0e, 0x72, 0x75, 0x6c, 0x65, 0x43, 0x6c,
-	0x61, 0x73, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x41,
-	0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x62, 0x70, 0x32,
-	0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x42, 0x70,
-	0x32, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x52, 0x75,
-	0x6c, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72,
-	0x79, 0x52, 0x0e, 0x72, 0x75, 0x6c, 0x65, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x43, 0x6f, 0x75, 0x6e,
-	0x74, 0x12, 0x2a, 0x0a, 0x10, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x4d, 0x6f,
-	0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10, 0x63, 0x6f, 0x6e,
-	0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x75, 0x0a,
-	0x12, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75,
-	0x6c, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x45, 0x2e, 0x73, 0x6f, 0x6f, 0x6e,
-	0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x62, 0x70, 0x32, 0x62, 0x75, 0x69, 0x6c, 0x64,
-	0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x42, 0x70, 0x32, 0x42, 0x75, 0x69, 0x6c,
-	0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x76, 0x65,
-	0x72, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79,
-	0x52, 0x12, 0x75, 0x6e, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64,
-	0x75, 0x6c, 0x65, 0x73, 0x12, 0x87, 0x01, 0x0a, 0x18, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74,
-	0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x75, 0x6e,
-	0x74, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x4b, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f,
-	0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x62, 0x70, 0x32, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d,
-	0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x42, 0x70, 0x32, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d,
-	0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64,
-	0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x45,
-	0x6e, 0x74, 0x72, 0x79, 0x52, 0x18, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x4d,
-	0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x7b,
-	0x0a, 0x14, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70,
-	0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x47, 0x2e, 0x73,
-	0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x62, 0x70, 0x32, 0x62, 0x75,
-	0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x42, 0x70, 0x32, 0x42,
-	0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x54, 0x6f, 0x74, 0x61,
-	0x6c, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74,
-	0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x14, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x75,
-	0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x3b, 0x0a, 0x06, 0x65,
-	0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x73, 0x6f,
-	0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x62, 0x70, 0x32, 0x62, 0x75, 0x69,
-	0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74,
-	0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x1a, 0x41, 0x0a, 0x13, 0x52, 0x75, 0x6c, 0x65,
-	0x43, 0x6c, 0x61, 0x73, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12,
-	0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65,
-	0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04,
-	0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x76, 0x0a, 0x17, 0x55,
-	0x6e, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65,
-	0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x45, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
-	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f,
-	0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x62, 0x70, 0x32, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d,
-	0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74,
-	0x65, 0x64, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a,
-	0x02, 0x38, 0x01, 0x1a, 0x4b, 0x0a, 0x1d, 0x43, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64,
-	0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x45,
-	0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18,
-	0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01,
-	0x1a, 0x47, 0x0a, 0x19, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54,
-	0x79, 0x70, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a,
-	0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12,
-	0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05,
-	0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x57, 0x0a, 0x05, 0x45, 0x76, 0x65,
-	0x6e, 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, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f,
-	0x74, 0x69, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72,
-	0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x61, 0x6c, 0x5f, 0x74, 0x69,
-	0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x72, 0x65, 0x61, 0x6c, 0x54, 0x69,
-	0x6d, 0x65, 0x22, 0x74, 0x0a, 0x11, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65,
-	0x64, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x47, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x33, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75,
-	0x69, 0x6c, 0x64, 0x5f, 0x62, 0x70, 0x32, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74,
-	0x72, 0x69, 0x63, 0x73, 0x2e, 0x55, 0x6e, 0x63, 0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64,
-	0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65,
-	0x12, 0x16, 0x0a, 0x06, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x06, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x2a, 0xb0, 0x01, 0x0a, 0x15, 0x55, 0x6e, 0x63,
-	0x6f, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x65, 0x64, 0x52, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x54, 0x79,
-	0x70, 0x65, 0x12, 0x0f, 0x0a, 0x0b, 0x55, 0x4e, 0x53, 0x55, 0x50, 0x50, 0x4f, 0x52, 0x54, 0x45,
-	0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x44, 0x45, 0x46, 0x49, 0x4e, 0x45, 0x44, 0x5f, 0x49,
-	0x4e, 0x5f, 0x42, 0x55, 0x49, 0x4c, 0x44, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x01, 0x12, 0x0e,
-	0x0a, 0x0a, 0x44, 0x45, 0x4e, 0x59, 0x4c, 0x49, 0x53, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x14,
-	0x0a, 0x10, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x55, 0x50, 0x50, 0x4f, 0x52, 0x54,
-	0x45, 0x44, 0x10, 0x03, 0x12, 0x18, 0x0a, 0x14, 0x50, 0x52, 0x4f, 0x50, 0x45, 0x52, 0x54, 0x59,
-	0x5f, 0x55, 0x4e, 0x53, 0x55, 0x50, 0x50, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x10, 0x04, 0x12, 0x13,
-	0x0a, 0x0f, 0x55, 0x4e, 0x43, 0x4f, 0x4e, 0x56, 0x45, 0x52, 0x54, 0x45, 0x44, 0x5f, 0x44, 0x45,
-	0x50, 0x10, 0x05, 0x12, 0x16, 0x0a, 0x12, 0x53, 0x52, 0x43, 0x5f, 0x4e, 0x41, 0x4d, 0x45, 0x5f,
-	0x43, 0x4f, 0x4c, 0x4c, 0x49, 0x53, 0x49, 0x4f, 0x4e, 0x10, 0x06, 0x42, 0x31, 0x5a, 0x2f, 0x61,
-	0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x75, 0x69, 0x2f,
-	0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x62, 0x70, 0x32, 0x62, 0x75, 0x69, 0x6c, 0x64,
-	0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06,
-	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
-}
-
-var (
-	file_bp2build_metrics_proto_rawDescOnce sync.Once
-	file_bp2build_metrics_proto_rawDescData = file_bp2build_metrics_proto_rawDesc
-)
-
-func file_bp2build_metrics_proto_rawDescGZIP() []byte {
-	file_bp2build_metrics_proto_rawDescOnce.Do(func() {
-		file_bp2build_metrics_proto_rawDescData = protoimpl.X.CompressGZIP(file_bp2build_metrics_proto_rawDescData)
-	})
-	return file_bp2build_metrics_proto_rawDescData
-}
-
-var file_bp2build_metrics_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
-var file_bp2build_metrics_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
-var file_bp2build_metrics_proto_goTypes = []interface{}{
-	(UnconvertedReasonType)(0), // 0: soong_build_bp2build_metrics.UnconvertedReasonType
-	(*Bp2BuildMetrics)(nil),    // 1: soong_build_bp2build_metrics.Bp2BuildMetrics
-	(*Event)(nil),              // 2: soong_build_bp2build_metrics.Event
-	(*UnconvertedReason)(nil),  // 3: soong_build_bp2build_metrics.UnconvertedReason
-	nil,                        // 4: soong_build_bp2build_metrics.Bp2BuildMetrics.RuleClassCountEntry
-	nil,                        // 5: soong_build_bp2build_metrics.Bp2BuildMetrics.UnconvertedModulesEntry
-	nil,                        // 6: soong_build_bp2build_metrics.Bp2BuildMetrics.ConvertedModuleTypeCountEntry
-	nil,                        // 7: soong_build_bp2build_metrics.Bp2BuildMetrics.TotalModuleTypeCountEntry
-}
-var file_bp2build_metrics_proto_depIdxs = []int32{
-	4, // 0: soong_build_bp2build_metrics.Bp2BuildMetrics.ruleClassCount:type_name -> soong_build_bp2build_metrics.Bp2BuildMetrics.RuleClassCountEntry
-	5, // 1: soong_build_bp2build_metrics.Bp2BuildMetrics.unconvertedModules:type_name -> soong_build_bp2build_metrics.Bp2BuildMetrics.UnconvertedModulesEntry
-	6, // 2: soong_build_bp2build_metrics.Bp2BuildMetrics.convertedModuleTypeCount:type_name -> soong_build_bp2build_metrics.Bp2BuildMetrics.ConvertedModuleTypeCountEntry
-	7, // 3: soong_build_bp2build_metrics.Bp2BuildMetrics.totalModuleTypeCount:type_name -> soong_build_bp2build_metrics.Bp2BuildMetrics.TotalModuleTypeCountEntry
-	2, // 4: soong_build_bp2build_metrics.Bp2BuildMetrics.events:type_name -> soong_build_bp2build_metrics.Event
-	0, // 5: soong_build_bp2build_metrics.UnconvertedReason.type:type_name -> soong_build_bp2build_metrics.UnconvertedReasonType
-	3, // 6: soong_build_bp2build_metrics.Bp2BuildMetrics.UnconvertedModulesEntry.value:type_name -> soong_build_bp2build_metrics.UnconvertedReason
-	7, // [7:7] is the sub-list for method output_type
-	7, // [7:7] is the sub-list for method input_type
-	7, // [7:7] is the sub-list for extension type_name
-	7, // [7:7] is the sub-list for extension extendee
-	0, // [0:7] is the sub-list for field type_name
-}
-
-func init() { file_bp2build_metrics_proto_init() }
-func file_bp2build_metrics_proto_init() {
-	if File_bp2build_metrics_proto != nil {
-		return
-	}
-	if !protoimpl.UnsafeEnabled {
-		file_bp2build_metrics_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*Bp2BuildMetrics); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_bp2build_metrics_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*Event); i {
-			case 0:
-				return &v.state
-			case 1:
-				return &v.sizeCache
-			case 2:
-				return &v.unknownFields
-			default:
-				return nil
-			}
-		}
-		file_bp2build_metrics_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*UnconvertedReason); 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_bp2build_metrics_proto_rawDesc,
-			NumEnums:      1,
-			NumMessages:   7,
-			NumExtensions: 0,
-			NumServices:   0,
-		},
-		GoTypes:           file_bp2build_metrics_proto_goTypes,
-		DependencyIndexes: file_bp2build_metrics_proto_depIdxs,
-		EnumInfos:         file_bp2build_metrics_proto_enumTypes,
-		MessageInfos:      file_bp2build_metrics_proto_msgTypes,
-	}.Build()
-	File_bp2build_metrics_proto = out.File
-	file_bp2build_metrics_proto_rawDesc = nil
-	file_bp2build_metrics_proto_goTypes = nil
-	file_bp2build_metrics_proto_depIdxs = nil
-}
diff --git a/ui/metrics/bp2build_metrics_proto/bp2build_metrics.proto b/ui/metrics/bp2build_metrics_proto/bp2build_metrics.proto
deleted file mode 100644
index 49cb2b4..0000000
--- a/ui/metrics/bp2build_metrics_proto/bp2build_metrics.proto
+++ /dev/null
@@ -1,105 +0,0 @@
-// Copyright 2021 Google Inc. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-syntax = "proto3";
-
-package soong_build_bp2build_metrics;
-option go_package = "android/soong/ui/metrics/bp2build_metrics_proto";
-
-message Bp2BuildMetrics {
-  // Total number of Soong modules converted to generated targets
-  uint64 generatedModuleCount = 1;
-
-  // Total number of Soong modules converted to handcrafted targets
-  uint64 handCraftedModuleCount = 2;
-
-  // Total number of unconverted Soong modules
-  uint64 unconvertedModuleCount = 3;
-
-  // Counts of symlinks in synthetic bazel workspace
-  uint64 workspaceSymlinkCount= 9;
-
-  // Counts of mkdir calls during creation of synthetic bazel workspace
-  uint64 workspaceMkDirCount= 10;
-
-  // Counts of generated Bazel targets per Bazel rule class
-  map<string, uint64> ruleClassCount = 4;
-
-  // List of converted modules
-  repeated string convertedModules = 5;
-
-  // Unconverted modules, mapped to the reason the module was not converted.
-  map<string, UnconvertedReason> unconvertedModules = 11;
-
-  // Counts of converted modules by module type.
-  map<string, uint64> convertedModuleTypeCount = 6;
-
-  // Counts of total modules by module type.
-  map<string, uint64> totalModuleTypeCount = 7;
-
-  // List of traced runtime events of bp2build, useful for tracking bp2build
-  // runtime.
-  repeated Event events = 8;
-}
-
-// Traced runtime event of bp2build.
-message Event {
-  // The event name.
-  string name = 1;
-
-  // The absolute start time of the event
-  // The number of nanoseconds elapsed since January 1, 1970 UTC.
-  uint64 start_time = 2;
-
-  // The real running time.
-  // The number of nanoseconds elapsed since start_time.
-  uint64 real_time = 3;
-}
-
-message UnconvertedReason {
-  // The type of reason that the module could not be converted.
-  UnconvertedReasonType type = 1;
-
-  // Descriptive details describing why the module could not be converted.
-  // This detail should be kept very short and should be in the context of
-  // the type. (Otherwise, this would significantly bloat metrics.)
-  string detail = 2;
-}
-
-enum UnconvertedReasonType {
-  // Bp2build does not know how to convert this specific module for some reason
-  // not covered by other reason types. The reason detail should explain the
-  // specific issue.
-  UNSUPPORTED = 0;
-
-  // The module was already defined in a BUILD file available in the source tree.
-  DEFINED_IN_BUILD_FILE = 1;
-
-  // The module was explicitly denylisted by name.
-  DENYLISTED = 2;
-
-  // The module's type has no bp2build implementation.
-  TYPE_UNSUPPORTED = 3;
-
-  // The module has a property not yet supported. The detail field should
-  // name the unsupported property name.
-  PROPERTY_UNSUPPORTED = 4;
-
-  // The module has an unconverted dependency. The detail should consist of
-  // the name of the unconverted module.
-  UNCONVERTED_DEP = 5;
-
-  // The module has a source file with the same name as the module itself.
-  SRC_NAME_COLLISION = 6;
-}
\ No newline at end of file
diff --git a/ui/metrics/bp2build_metrics_proto/regen.sh b/ui/metrics/bp2build_metrics_proto/regen.sh
deleted file mode 100755
index bfe4294..0000000
--- a/ui/metrics/bp2build_metrics_proto/regen.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/bash -e
-
-# Copyright 2021 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.
-
-# Generates the golang source file of bp2build_metrics.proto protobuf file.
-
-function die() { echo "ERROR: $1" >&2; exit 1; }
-
-readonly error_msg="Maybe you need to run 'lunch aosp_arm-eng && m aprotoc blueprint_tools'?"
-
-if ! hash aprotoc &>/dev/null; then
-  die "could not find aprotoc. ${error_msg}"
-fi
-
-if ! aprotoc --go_out=paths=source_relative:. bp2build_metrics.proto; then
-  die "build failed. ${error_msg}"
-fi
diff --git a/ui/metrics/bp2build_progress_metrics_proto/BUILD.bazel b/ui/metrics/bp2build_progress_metrics_proto/BUILD.bazel
deleted file mode 100644
index f6c6df8..0000000
--- a/ui/metrics/bp2build_progress_metrics_proto/BUILD.bazel
+++ /dev/null
@@ -1,27 +0,0 @@
-# Copyright (C) 2022 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.
-
-load("//build/bazel/rules/python:py_proto.bzl", "py_proto_library")
-
-proto_library(
-    name = "bp2build_proto",
-    srcs = ["bp2build.proto"],
-    strip_import_prefix = "",
-)
-
-py_proto_library(
-    name = "bp2build_py_proto",
-    visibility = ["//build/bazel/scripts/bp2build_progress:__pkg__"],
-    deps = [":bp2build_proto"],
-)
diff --git a/ui/metrics/bp2build_progress_metrics_proto/bp2build.proto b/ui/metrics/bp2build_progress_metrics_proto/bp2build.proto
deleted file mode 100644
index 5b44002..0000000
--- a/ui/metrics/bp2build_progress_metrics_proto/bp2build.proto
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2022 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 = "proto3";
-
-package bp2build_proto;
-
-
-// Conversion progress report for root_modules .
-message Bp2buildConversionProgress {
-
-  // Soong module identifying information.
-  message Module {
-    // Name of the Soong module.
-    string name = 1;
-
-    // Directory that the Soong module is in.
-    string directory = 2;
-
-    // Module type of this module.
-    string type = 3;
-
-    // All unconverted transitive dependencies.
-    repeated string unconverted_deps = 4;
-
-    // Total number of transitive dependencies.
-    int32 num_deps = 5;
-
-    // Unconverted reasons from heuristics
-    repeated string unconverted_reasons_from_heuristics = 6;
-  }
-
-  // Modules that the transitive dependencies were identified for.
-  repeated string root_modules = 1;
-
-  // Names of all dependencies of the root_modules.
-  int32 num_deps = 2;
-
-  // Module with all its unconverted transitive dependencies.
-  repeated Module unconverted = 3;
-}
diff --git a/ui/metrics/execution_metrics_proto/execution_metrics.pb.go b/ui/metrics/execution_metrics_proto/execution_metrics.pb.go
new file mode 100644
index 0000000..a065dbd
--- /dev/null
+++ b/ui/metrics/execution_metrics_proto/execution_metrics.pb.go
@@ -0,0 +1,240 @@
+// 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.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// 	protoc-gen-go v1.30.0
+// 	protoc        v3.21.12
+// source: execution_metrics.proto
+
+package execution_metrics_proto
+
+import (
+	find_input_delta_proto "android/soong/cmd/find_input_delta/find_input_delta_proto"
+	protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+	protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+	reflect "reflect"
+	sync "sync"
+)
+
+const (
+	// Verify that this generated code is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+	// Verify that runtime/protoimpl is sufficiently up-to-date.
+	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+// These field numbers are also found in the inner message declarations.
+// We verify that the values are the same, and that every enum value is checked
+// in execution_metrics_test.go.
+// Do not change this enum without also updating:
+//   - the submessage's .proto file
+//   - execution_metrics_test.go
+type FieldNumbers int32
+
+const (
+	FieldNumbers_FIELD_NUMBERS_UNSPECIFIED FieldNumbers = 0
+	FieldNumbers_FIELD_NUMBERS_FILE_LIST   FieldNumbers = 1
+)
+
+// Enum value maps for FieldNumbers.
+var (
+	FieldNumbers_name = map[int32]string{
+		0: "FIELD_NUMBERS_UNSPECIFIED",
+		1: "FIELD_NUMBERS_FILE_LIST",
+	}
+	FieldNumbers_value = map[string]int32{
+		"FIELD_NUMBERS_UNSPECIFIED": 0,
+		"FIELD_NUMBERS_FILE_LIST":   1,
+	}
+)
+
+func (x FieldNumbers) Enum() *FieldNumbers {
+	p := new(FieldNumbers)
+	*p = x
+	return p
+}
+
+func (x FieldNumbers) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (FieldNumbers) Descriptor() protoreflect.EnumDescriptor {
+	return file_execution_metrics_proto_enumTypes[0].Descriptor()
+}
+
+func (FieldNumbers) Type() protoreflect.EnumType {
+	return &file_execution_metrics_proto_enumTypes[0]
+}
+
+func (x FieldNumbers) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Do not use.
+func (x *FieldNumbers) UnmarshalJSON(b []byte) error {
+	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
+	if err != nil {
+		return err
+	}
+	*x = FieldNumbers(num)
+	return nil
+}
+
+// Deprecated: Use FieldNumbers.Descriptor instead.
+func (FieldNumbers) EnumDescriptor() ([]byte, []int) {
+	return file_execution_metrics_proto_rawDescGZIP(), []int{0}
+}
+
+type SoongExecutionMetrics struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// cmd/find_input_delta/find_input_delta_proto.FileList
+	FileList *find_input_delta_proto.FileList `protobuf:"bytes,1,opt,name=file_list,json=fileList" json:"file_list,omitempty"`
+}
+
+func (x *SoongExecutionMetrics) Reset() {
+	*x = SoongExecutionMetrics{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_execution_metrics_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SoongExecutionMetrics) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SoongExecutionMetrics) ProtoMessage() {}
+
+func (x *SoongExecutionMetrics) ProtoReflect() protoreflect.Message {
+	mi := &file_execution_metrics_proto_msgTypes[0]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use SoongExecutionMetrics.ProtoReflect.Descriptor instead.
+func (*SoongExecutionMetrics) Descriptor() ([]byte, []int) {
+	return file_execution_metrics_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *SoongExecutionMetrics) GetFileList() *find_input_delta_proto.FileList {
+	if x != nil {
+		return x.FileList
+	}
+	return nil
+}
+
+var File_execution_metrics_proto protoreflect.FileDescriptor
+
+var file_execution_metrics_proto_rawDesc = []byte{
+	0x0a, 0x17, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x65, 0x74, 0x72,
+	0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x13, 0x73, 0x6f, 0x6f, 0x6e, 0x67,
+	0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x1a, 0x3b,
+	0x63, 0x6d, 0x64, 0x2f, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64,
+	0x65, 0x6c, 0x74, 0x61, 0x2f, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f,
+	0x64, 0x65, 0x6c, 0x74, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x66, 0x69, 0x6c, 0x65,
+	0x5f, 0x6c, 0x69, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x5e, 0x0a, 0x15, 0x53,
+	0x6f, 0x6f, 0x6e, 0x67, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74,
+	0x72, 0x69, 0x63, 0x73, 0x12, 0x45, 0x0a, 0x09, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x6c, 0x69, 0x73,
+	0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69,
+	0x64, 0x2e, 0x66, 0x69, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x64, 0x65, 0x6c,
+	0x74, 0x61, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x46, 0x69, 0x6c, 0x65, 0x4c, 0x69, 0x73,
+	0x74, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x2a, 0x4a, 0x0a, 0x0c, 0x46,
+	0x69, 0x65, 0x6c, 0x64, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x73, 0x12, 0x1d, 0x0a, 0x19, 0x46,
+	0x49, 0x45, 0x4c, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x53, 0x5f, 0x55, 0x4e, 0x53,
+	0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x1b, 0x0a, 0x17, 0x46, 0x49,
+	0x45, 0x4c, 0x44, 0x5f, 0x4e, 0x55, 0x4d, 0x42, 0x45, 0x52, 0x53, 0x5f, 0x46, 0x49, 0x4c, 0x45,
+	0x5f, 0x4c, 0x49, 0x53, 0x54, 0x10, 0x01, 0x42, 0x32, 0x5a, 0x30, 0x61, 0x6e, 0x64, 0x72, 0x6f,
+	0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x75, 0x69, 0x2f, 0x6d, 0x65, 0x74, 0x72,
+	0x69, 0x63, 0x73, 0x2f, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6d, 0x65,
+	0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+}
+
+var (
+	file_execution_metrics_proto_rawDescOnce sync.Once
+	file_execution_metrics_proto_rawDescData = file_execution_metrics_proto_rawDesc
+)
+
+func file_execution_metrics_proto_rawDescGZIP() []byte {
+	file_execution_metrics_proto_rawDescOnce.Do(func() {
+		file_execution_metrics_proto_rawDescData = protoimpl.X.CompressGZIP(file_execution_metrics_proto_rawDescData)
+	})
+	return file_execution_metrics_proto_rawDescData
+}
+
+var file_execution_metrics_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_execution_metrics_proto_msgTypes = make([]protoimpl.MessageInfo, 1)
+var file_execution_metrics_proto_goTypes = []interface{}{
+	(FieldNumbers)(0),                       // 0: soong_build_metrics.FieldNumbers
+	(*SoongExecutionMetrics)(nil),           // 1: soong_build_metrics.SoongExecutionMetrics
+	(*find_input_delta_proto.FileList)(nil), // 2: android.find_input_delta_proto.FileList
+}
+var file_execution_metrics_proto_depIdxs = []int32{
+	2, // 0: soong_build_metrics.SoongExecutionMetrics.file_list:type_name -> android.find_input_delta_proto.FileList
+	1, // [1:1] is the sub-list for method output_type
+	1, // [1:1] is the sub-list for method input_type
+	1, // [1:1] is the sub-list for extension type_name
+	1, // [1:1] is the sub-list for extension extendee
+	0, // [0:1] is the sub-list for field type_name
+}
+
+func init() { file_execution_metrics_proto_init() }
+func file_execution_metrics_proto_init() {
+	if File_execution_metrics_proto != nil {
+		return
+	}
+	if !protoimpl.UnsafeEnabled {
+		file_execution_metrics_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*SoongExecutionMetrics); 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_execution_metrics_proto_rawDesc,
+			NumEnums:      1,
+			NumMessages:   1,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_execution_metrics_proto_goTypes,
+		DependencyIndexes: file_execution_metrics_proto_depIdxs,
+		EnumInfos:         file_execution_metrics_proto_enumTypes,
+		MessageInfos:      file_execution_metrics_proto_msgTypes,
+	}.Build()
+	File_execution_metrics_proto = out.File
+	file_execution_metrics_proto_rawDesc = nil
+	file_execution_metrics_proto_goTypes = nil
+	file_execution_metrics_proto_depIdxs = nil
+}
diff --git a/ui/metrics/execution_metrics_proto/execution_metrics.proto b/ui/metrics/execution_metrics_proto/execution_metrics.proto
new file mode 100644
index 0000000..381dcd1
--- /dev/null
+++ b/ui/metrics/execution_metrics_proto/execution_metrics.proto
@@ -0,0 +1,36 @@
+// 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.
+
+syntax = "proto2";
+
+package soong_build_metrics;
+option go_package = "android/soong/ui/metrics/execution_metrics_proto";
+
+import "cmd/find_input_delta/find_input_delta_proto/file_list.proto";
+
+// These field numbers are also found in the inner message declarations.
+// We verify that the values are the same, and that every enum value is checked
+// in execution_metrics_test.go.
+// Do not change this enum without also updating:
+//  - the submessage's .proto file
+//  - execution_metrics_test.go
+enum FieldNumbers {
+  FIELD_NUMBERS_UNSPECIFIED = 0;
+  FIELD_NUMBERS_FILE_LIST = 1;
+}
+
+message SoongExecutionMetrics {
+  // cmd/find_input_delta/find_input_delta_proto.FileList
+  optional android.find_input_delta_proto.FileList file_list = 1;
+}
diff --git a/ui/metrics/execution_metrics_proto/execution_metrics_test.go b/ui/metrics/execution_metrics_proto/execution_metrics_test.go
new file mode 100644
index 0000000..2f71c21
--- /dev/null
+++ b/ui/metrics/execution_metrics_proto/execution_metrics_test.go
@@ -0,0 +1,33 @@
+package execution_metrics_proto
+
+import (
+	"testing"
+
+	find_input_delta_proto "android/soong/cmd/find_input_delta/find_input_delta_proto"
+)
+
+func TestExecutionMetricsMessageNums(t *testing.T) {
+	testCases := []struct {
+		Name         string
+		FieldNumbers map[string]int32
+	}{
+		{
+			Name:         "find_input_delta_proto",
+			FieldNumbers: find_input_delta_proto.FieldNumbers_value,
+		},
+	}
+	verifiedMap := make(map[string]bool)
+	for _, tc := range testCases {
+		for k, v := range tc.FieldNumbers {
+			if FieldNumbers_value[k] != v {
+				t.Errorf("%s: Expected FieldNumbers.%s == %v, found %v", tc.Name, k, FieldNumbers_value[k], v)
+			}
+			verifiedMap[k] = true
+		}
+	}
+	for k, v := range FieldNumbers_value {
+		if !verifiedMap[k] {
+			t.Errorf("No test case verifies FieldNumbers.%s=%v", k, v)
+		}
+	}
+}
diff --git a/ui/metrics/execution_metrics_proto/regen.sh b/ui/metrics/execution_metrics_proto/regen.sh
new file mode 100755
index 0000000..5339a9a
--- /dev/null
+++ b/ui/metrics/execution_metrics_proto/regen.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+# Generates the golang source file of metrics.proto protobuf file.
+
+set -e
+
+function die() { echo "ERROR: $1" >&2; exit 1; }
+
+readonly error_msg="Maybe you need to run 'lunch aosp_arm-eng && m aprotoc blueprint_tools'?"
+
+if ! hash aprotoc &>/dev/null; then
+  die "could not find aprotoc. ${error_msg}"
+fi
+
+if ! aprotoc --go_out=paths=source_relative:. -I .:../../.. execution_metrics.proto; then
+  die "build failed. ${error_msg}"
+fi
diff --git a/ui/metrics/hostinfo.go b/ui/metrics/hostinfo.go
new file mode 100644
index 0000000..f401b2a
--- /dev/null
+++ b/ui/metrics/hostinfo.go
@@ -0,0 +1,111 @@
+// 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 metrics
+
+// The hostinfo* files contain code to extract host information from
+// /proc/cpuinfo and /proc/meminfo relevant to machine performance
+
+import (
+	"strconv"
+	"strings"
+)
+
+// CpuInfo holds information regarding the host's CPU cores.
+type CpuInfo struct {
+	// The vendor id
+	VendorId string
+
+	// The model name
+	ModelName string
+
+	// The number of CPU cores
+	CpuCores int32
+
+	// The CPU flags
+	Flags string
+}
+
+// MemInfo holds information regarding the host's memory.
+// The memory size in each of the field is in bytes.
+type MemInfo struct {
+	// The total memory.
+	MemTotal uint64
+
+	// The amount of free memory.
+	MemFree uint64
+
+	// The amount of available memory.
+	MemAvailable uint64
+}
+
+// fillCpuInfo takes the key and value, converts the value
+// to the proper size unit and is stores it in CpuInfo.
+func (c *CpuInfo) fillInfo(key, value string) {
+	switch key {
+	case "vendor_id":
+		c.VendorId = value
+	case "model name":
+		c.ModelName = value
+	case "cpu cores":
+		v, err := strconv.ParseInt(value, 10, 32)
+		if err == nil {
+			c.CpuCores = int32(v)
+		}
+	case "flags":
+		c.Flags = value
+	default:
+		// Ignore unknown keys
+	}
+}
+
+// fillCpuInfo takes the key and value, converts the value
+// to the proper size unit and is stores it in CpuInfo.
+func (m *MemInfo) fillInfo(key, value string) {
+	v := strToUint64(value)
+	switch key {
+	case "MemTotal":
+		m.MemTotal = v
+	case "MemFree":
+		m.MemFree = v
+	case "MemAvailable":
+		m.MemAvailable = v
+	default:
+		// Ignore unknown keys
+	}
+}
+
+// strToUint64 takes the string and converts to unsigned 64-bit integer.
+// If the string contains a memory unit such as kB and is converted to
+// bytes.
+func strToUint64(v string) uint64 {
+	// v could be "1024 kB" so scan for the empty space and
+	// split between the value and the unit.
+	var separatorIndex int
+	if separatorIndex = strings.IndexAny(v, " "); separatorIndex < 0 {
+		separatorIndex = len(v)
+	}
+	value, err := strconv.ParseUint(v[:separatorIndex], 10, 64)
+	if err != nil {
+		return 0
+	}
+
+	var scale uint64 = 1
+	switch strings.TrimSpace(v[separatorIndex:]) {
+	case "kB", "KB":
+		scale = 1024
+	case "mB", "MB":
+		scale = 1024 * 1024
+	}
+	return value * scale
+}
diff --git a/ui/metrics/hostinfo_darwin.go b/ui/metrics/hostinfo_darwin.go
new file mode 100644
index 0000000..6f35c7d
--- /dev/null
+++ b/ui/metrics/hostinfo_darwin.go
@@ -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 metrics
+
+// This file contain code to extract host information on linux from
+// /proc/cpuinfo and /proc/meminfo relevant to machine performance
+
+import (
+	"android/soong/finder/fs"
+)
+
+func NewCpuInfo(fileSystem fs.FileSystem) (*CpuInfo, error) {
+	return &CpuInfo{}, nil
+}
+
+func NewMemInfo(fileSystem fs.FileSystem) (*MemInfo, error) {
+	return &MemInfo{}, nil
+}
diff --git a/ui/metrics/hostinfo_linux.go b/ui/metrics/hostinfo_linux.go
new file mode 100644
index 0000000..b983dbc
--- /dev/null
+++ b/ui/metrics/hostinfo_linux.go
@@ -0,0 +1,72 @@
+// 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 metrics
+
+// This file contain code to extract host information on linux from
+// /proc/cpuinfo and /proc/meminfo relevant to machine performance
+
+import (
+	"io/ioutil"
+	"strings"
+
+	"android/soong/finder/fs"
+)
+
+type fillable interface {
+	fillInfo(key, value string)
+}
+
+func NewCpuInfo(fileSystem fs.FileSystem) (*CpuInfo, error) {
+	c := &CpuInfo{}
+	if err := parseFile(c, "/proc/cpuinfo", true, fileSystem); err != nil {
+		return &CpuInfo{}, err
+	}
+	return c, nil
+}
+
+func NewMemInfo(fileSystem fs.FileSystem) (*MemInfo, error) {
+	m := &MemInfo{}
+	if err := parseFile(m, "/proc/meminfo", false, fileSystem); err != nil {
+		return &MemInfo{}, err
+	}
+	return m, nil
+}
+
+func parseFile(obj fillable, fileName string, endOnBlank bool, fileSystem fs.FileSystem) error {
+	fd, err := fileSystem.Open(fileName)
+	if err != nil {
+		return err
+	}
+	defer fd.Close()
+
+	data, err := ioutil.ReadAll(fd)
+	if err != nil {
+		return err
+	}
+
+	for _, l := range strings.Split(string(data), "\n") {
+		if !strings.Contains(l, ":") {
+			// Terminate after the first blank line.
+			if endOnBlank && strings.TrimSpace(l) == "" {
+				break
+			}
+			// If the line is not of the form "key: values", just skip it.
+			continue
+		}
+
+		kv := strings.SplitN(l, ":", 2)
+		obj.fillInfo(strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1]))
+	}
+	return nil
+}
diff --git a/ui/metrics/hostinfo_linux_test.go b/ui/metrics/hostinfo_linux_test.go
new file mode 100644
index 0000000..c44e453
--- /dev/null
+++ b/ui/metrics/hostinfo_linux_test.go
@@ -0,0 +1,118 @@
+// 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 metrics
+
+// This file contain code to extract host information on linux from
+// /proc/cpuinfo and /proc/meminfo relevant to machine performance
+
+import (
+	"reflect"
+	"testing"
+
+	"android/soong/finder/fs"
+)
+
+func TestNewCpuInfo(t *testing.T) {
+	fs := fs.NewMockFs(nil)
+
+	if err := fs.MkDirs("/proc"); err != nil {
+		t.Fatalf("failed to create /proc dir: %v", err)
+	}
+	cpuFileName := "/proc/cpuinfo"
+
+	if err := fs.WriteFile(cpuFileName, cpuData, 0644); err != nil {
+		t.Fatalf("failed to write file %s: %v", cpuFileName, err)
+	}
+
+	cpuInfo, err := NewCpuInfo(fs)
+	if err != nil {
+		t.Fatalf("got %v, want nil for error", err)
+	}
+
+	if !reflect.DeepEqual(cpuInfo, expectedCpuInfo) {
+		t.Errorf("got %v, expecting %v for CpuInfo", cpuInfo, expectedCpuInfo)
+	}
+
+}
+
+func TestNewMemInfo(t *testing.T) {
+	fs := fs.NewMockFs(nil)
+
+	if err := fs.MkDirs("/proc"); err != nil {
+		t.Fatalf("failed to create /proc dir: %v", err)
+	}
+	memFileName := "/proc/meminfo"
+
+	if err := fs.WriteFile(memFileName, memData, 0644); err != nil {
+		t.Fatalf("failed to write file %s: %v", memFileName, err)
+	}
+
+	memInfo, err := NewMemInfo(fs)
+	if err != nil {
+		t.Fatalf("got %v, want nil for error", err)
+	}
+
+	if !reflect.DeepEqual(memInfo, expectedMemInfo) {
+		t.Errorf("got %v, expecting %v for MemInfo", memInfo, expectedMemInfo)
+	}
+
+}
+
+var cpuData = []byte(`processor	: 0
+vendor_id	: %%VENDOR%%
+cpu family	: 123
+model		: 456
+model name	: %%CPU MODEL NAME%%
+stepping	: 0
+cpu MHz		: 5555.555
+cache size	: 512 KB
+physical id	: 0
+siblings	: 128
+core id		: 0
+cpu cores	: 64
+apicid		: 0
+initial apicid	: 0
+fpu		: yes
+fpu_exception	: yes
+cpuid level	: 789
+wp		: yes
+flags		: %%cpu flags go here%%
+bugs		: %%bugs go here%%
+
+processor	: 1
+vendor_id	: %%BADVENDOR%%
+cpu family	: 234
+model		: 567
+model name	: %%BAD MODEL NAME%%
+flags		: %%BAD cpu flags go here%%
+`)
+
+var expectedCpuInfo = &CpuInfo{
+	VendorId:  "%%VENDOR%%",
+	ModelName: "%%CPU MODEL NAME%%",
+	CpuCores:  64,
+	Flags:     "%%cpu flags go here%%",
+}
+
+var memData = []byte(`MemTotal:       1000 mB
+MemFree:        10240000
+MemAvailable:   3000 kB
+Buffers:         7177844 kB
+`)
+
+var expectedMemInfo = &MemInfo{
+	MemTotal:     1048576000,
+	MemFree:      10240000,
+	MemAvailable: 3072000,
+}
diff --git a/ui/metrics/metrics.go b/ui/metrics/metrics.go
index 4a275a8..8d29cfc 100644
--- a/ui/metrics/metrics.go
+++ b/ui/metrics/metrics.go
@@ -161,7 +161,7 @@
 }
 
 // SetMetadataMetrics sets information about the build such as the target
-// product, host architecture and out directory.
+// product, host architecture and out directory.  May be called multiple times.
 func (m *Metrics) SetMetadataMetrics(metadata map[string]string) {
 	for k, v := range metadata {
 		switch k {
@@ -171,6 +171,8 @@
 			m.metrics.PlatformVersionCodename = proto.String(v)
 		case "TARGET_PRODUCT":
 			m.metrics.TargetProduct = proto.String(v)
+		case "TARGET_RELEASE":
+			m.metrics.TargetRelease = proto.String(v)
 		case "TARGET_BUILD_VARIANT":
 			switch v {
 			case "user":
diff --git a/ui/metrics/metrics_proto/Android.bp b/ui/metrics/metrics_proto/Android.bp
new file mode 100644
index 0000000..aae5266
--- /dev/null
+++ b/ui/metrics/metrics_proto/Android.bp
@@ -0,0 +1,16 @@
+python_library_host {
+    name: "soong-metrics-proto-py",
+    srcs: [
+        "metrics.proto",
+    ],
+    visibility: [
+        "//build/make/ci",
+    ],
+    libs: [
+        "libprotobuf-python",
+    ],
+    proto: {
+        include_dirs: ["external/protobuf/src"],
+        canonical_path_from_root: false,
+    },
+}
diff --git a/ui/metrics/metrics_proto/metrics.pb.go b/ui/metrics/metrics_proto/metrics.pb.go
index b75f572..1ebe911 100644
--- a/ui/metrics/metrics_proto/metrics.pb.go
+++ b/ui/metrics/metrics_proto/metrics.pb.go
@@ -279,7 +279,7 @@
 
 // Deprecated: Use ModuleTypeInfo_BuildSystem.Descriptor instead.
 func (ModuleTypeInfo_BuildSystem) EnumDescriptor() ([]byte, []int) {
-	return file_metrics_proto_rawDescGZIP(), []int{8, 0}
+	return file_metrics_proto_rawDescGZIP(), []int{11, 0}
 }
 
 type ExpConfigFetcher_ConfigStatus int32
@@ -341,7 +341,7 @@
 
 // Deprecated: Use ExpConfigFetcher_ConfigStatus.Descriptor instead.
 func (ExpConfigFetcher_ConfigStatus) EnumDescriptor() ([]byte, []int) {
-	return file_metrics_proto_rawDescGZIP(), []int{12, 0}
+	return file_metrics_proto_rawDescGZIP(), []int{15, 0}
 }
 
 type MetricsBase struct {
@@ -425,6 +425,10 @@
 	// Note that not all changed environment variables result in analysis retriggering.
 	// If there was no previous build, this list will be empty.
 	ChangedEnvironmentVariable []string `protobuf:"bytes,34,rep,name=changed_environment_variable,json=changedEnvironmentVariable" json:"changed_environment_variable,omitempty"`
+	// Metrics related to optimized builds.
+	OptimizedBuildMetrics *OptimizedBuildMetrics `protobuf:"bytes,35,opt,name=optimized_build_metrics,json=optimizedBuildMetrics" json:"optimized_build_metrics,omitempty"`
+	// The target release information. e.g., trunk_staging.
+	TargetRelease *string `protobuf:"bytes,36,opt,name=target_release,json=targetRelease" json:"target_release,omitempty"`
 }
 
 // Default values for MetricsBase fields.
@@ -706,6 +710,20 @@
 	return nil
 }
 
+func (x *MetricsBase) GetOptimizedBuildMetrics() *OptimizedBuildMetrics {
+	if x != nil {
+		return x.OptimizedBuildMetrics
+	}
+	return nil
+}
+
+func (x *MetricsBase) GetTargetRelease() string {
+	if x != nil && x.TargetRelease != nil {
+		return *x.TargetRelease
+	}
+	return ""
+}
+
 type BuildConfig struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -730,6 +748,11 @@
 	// EXTERNAL_FILE - ninja uses an external custom weight list
 	// HINT_FROM_SOONG - ninja uses a prioritized module list from Soong
 	NinjaWeightListSource *BuildConfig_NinjaWeightListSource `protobuf:"varint,8,opt,name=ninja_weight_list_source,json=ninjaWeightListSource,enum=soong_build_metrics.BuildConfig_NinjaWeightListSource,def=0" json:"ninja_weight_list_source,omitempty"`
+	// Values of some build-affecting environment variables.
+	SoongEnvVars *SoongEnvVars `protobuf:"bytes,9,opt,name=soong_env_vars,json=soongEnvVars" json:"soong_env_vars,omitempty"`
+	// Whether this build uses soong-only (no kati) mode (either via environment variable,
+	// command line flag or product config.
+	SoongOnly *bool `protobuf:"varint,10,opt,name=soong_only,json=soongOnly" json:"soong_only,omitempty"`
 }
 
 // Default values for BuildConfig fields.
@@ -825,6 +848,77 @@
 	return Default_BuildConfig_NinjaWeightListSource
 }
 
+func (x *BuildConfig) GetSoongEnvVars() *SoongEnvVars {
+	if x != nil {
+		return x.SoongEnvVars
+	}
+	return nil
+}
+
+func (x *BuildConfig) GetSoongOnly() bool {
+	if x != nil && x.SoongOnly != nil {
+		return *x.SoongOnly
+	}
+	return false
+}
+
+type SoongEnvVars struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// SOONG_PARTIAL_COMPILE
+	PartialCompile *string `protobuf:"bytes,1,opt,name=partial_compile,json=partialCompile" json:"partial_compile,omitempty"`
+	// SOONG_USE_PARTIAL_COMPILE
+	UsePartialCompile *string `protobuf:"bytes,2,opt,name=use_partial_compile,json=usePartialCompile" json:"use_partial_compile,omitempty"`
+}
+
+func (x *SoongEnvVars) Reset() {
+	*x = SoongEnvVars{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_metrics_proto_msgTypes[2]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SoongEnvVars) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SoongEnvVars) ProtoMessage() {}
+
+func (x *SoongEnvVars) ProtoReflect() protoreflect.Message {
+	mi := &file_metrics_proto_msgTypes[2]
+	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 SoongEnvVars.ProtoReflect.Descriptor instead.
+func (*SoongEnvVars) Descriptor() ([]byte, []int) {
+	return file_metrics_proto_rawDescGZIP(), []int{2}
+}
+
+func (x *SoongEnvVars) GetPartialCompile() string {
+	if x != nil && x.PartialCompile != nil {
+		return *x.PartialCompile
+	}
+	return ""
+}
+
+func (x *SoongEnvVars) GetUsePartialCompile() string {
+	if x != nil && x.UsePartialCompile != nil {
+		return *x.UsePartialCompile
+	}
+	return ""
+}
+
 type SystemResourceInfo struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -834,12 +928,16 @@
 	TotalPhysicalMemory *uint64 `protobuf:"varint,1,opt,name=total_physical_memory,json=totalPhysicalMemory" json:"total_physical_memory,omitempty"`
 	// The total of available cores for building
 	AvailableCpus *int32 `protobuf:"varint,2,opt,name=available_cpus,json=availableCpus" json:"available_cpus,omitempty"`
+	// Information about the machine's CPU(s).
+	CpuInfo *SystemCpuInfo `protobuf:"bytes,3,opt,name=cpu_info,json=cpuInfo" json:"cpu_info,omitempty"`
+	// Information about the machine's memory.
+	MemInfo *SystemMemInfo `protobuf:"bytes,4,opt,name=mem_info,json=memInfo" json:"mem_info,omitempty"`
 }
 
 func (x *SystemResourceInfo) Reset() {
 	*x = SystemResourceInfo{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_metrics_proto_msgTypes[2]
+		mi := &file_metrics_proto_msgTypes[3]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -852,7 +950,7 @@
 func (*SystemResourceInfo) ProtoMessage() {}
 
 func (x *SystemResourceInfo) ProtoReflect() protoreflect.Message {
-	mi := &file_metrics_proto_msgTypes[2]
+	mi := &file_metrics_proto_msgTypes[3]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -865,7 +963,7 @@
 
 // Deprecated: Use SystemResourceInfo.ProtoReflect.Descriptor instead.
 func (*SystemResourceInfo) Descriptor() ([]byte, []int) {
-	return file_metrics_proto_rawDescGZIP(), []int{2}
+	return file_metrics_proto_rawDescGZIP(), []int{3}
 }
 
 func (x *SystemResourceInfo) GetTotalPhysicalMemory() uint64 {
@@ -882,6 +980,161 @@
 	return 0
 }
 
+func (x *SystemResourceInfo) GetCpuInfo() *SystemCpuInfo {
+	if x != nil {
+		return x.CpuInfo
+	}
+	return nil
+}
+
+func (x *SystemResourceInfo) GetMemInfo() *SystemMemInfo {
+	if x != nil {
+		return x.MemInfo
+	}
+	return nil
+}
+
+type SystemCpuInfo struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The vendor id
+	VendorId *string `protobuf:"bytes,1,opt,name=vendor_id,json=vendorId" json:"vendor_id,omitempty"`
+	// The model name
+	ModelName *string `protobuf:"bytes,2,opt,name=model_name,json=modelName" json:"model_name,omitempty"`
+	// The number of CPU cores
+	CpuCores *int32 `protobuf:"varint,3,opt,name=cpu_cores,json=cpuCores" json:"cpu_cores,omitempty"`
+	// The CPU flags
+	Flags *string `protobuf:"bytes,4,opt,name=flags" json:"flags,omitempty"`
+}
+
+func (x *SystemCpuInfo) Reset() {
+	*x = SystemCpuInfo{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_metrics_proto_msgTypes[4]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SystemCpuInfo) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SystemCpuInfo) ProtoMessage() {}
+
+func (x *SystemCpuInfo) ProtoReflect() protoreflect.Message {
+	mi := &file_metrics_proto_msgTypes[4]
+	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 SystemCpuInfo.ProtoReflect.Descriptor instead.
+func (*SystemCpuInfo) Descriptor() ([]byte, []int) {
+	return file_metrics_proto_rawDescGZIP(), []int{4}
+}
+
+func (x *SystemCpuInfo) GetVendorId() string {
+	if x != nil && x.VendorId != nil {
+		return *x.VendorId
+	}
+	return ""
+}
+
+func (x *SystemCpuInfo) GetModelName() string {
+	if x != nil && x.ModelName != nil {
+		return *x.ModelName
+	}
+	return ""
+}
+
+func (x *SystemCpuInfo) GetCpuCores() int32 {
+	if x != nil && x.CpuCores != nil {
+		return *x.CpuCores
+	}
+	return 0
+}
+
+func (x *SystemCpuInfo) GetFlags() string {
+	if x != nil && x.Flags != nil {
+		return *x.Flags
+	}
+	return ""
+}
+
+type SystemMemInfo struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The total system memory
+	MemTotal *uint64 `protobuf:"varint,1,opt,name=mem_total,json=memTotal" json:"mem_total,omitempty"`
+	// The free system memory
+	MemFree *uint64 `protobuf:"varint,2,opt,name=mem_free,json=memFree" json:"mem_free,omitempty"`
+	// The available system memory
+	MemAvailable *uint64 `protobuf:"varint,3,opt,name=mem_available,json=memAvailable" json:"mem_available,omitempty"`
+}
+
+func (x *SystemMemInfo) Reset() {
+	*x = SystemMemInfo{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_metrics_proto_msgTypes[5]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *SystemMemInfo) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*SystemMemInfo) ProtoMessage() {}
+
+func (x *SystemMemInfo) ProtoReflect() protoreflect.Message {
+	mi := &file_metrics_proto_msgTypes[5]
+	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 SystemMemInfo.ProtoReflect.Descriptor instead.
+func (*SystemMemInfo) Descriptor() ([]byte, []int) {
+	return file_metrics_proto_rawDescGZIP(), []int{5}
+}
+
+func (x *SystemMemInfo) GetMemTotal() uint64 {
+	if x != nil && x.MemTotal != nil {
+		return *x.MemTotal
+	}
+	return 0
+}
+
+func (x *SystemMemInfo) GetMemFree() uint64 {
+	if x != nil && x.MemFree != nil {
+		return *x.MemFree
+	}
+	return 0
+}
+
+func (x *SystemMemInfo) GetMemAvailable() uint64 {
+	if x != nil && x.MemAvailable != nil {
+		return *x.MemAvailable
+	}
+	return 0
+}
+
 type PerfInfo struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -913,7 +1166,7 @@
 func (x *PerfInfo) Reset() {
 	*x = PerfInfo{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_metrics_proto_msgTypes[3]
+		mi := &file_metrics_proto_msgTypes[6]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -926,7 +1179,7 @@
 func (*PerfInfo) ProtoMessage() {}
 
 func (x *PerfInfo) ProtoReflect() protoreflect.Message {
-	mi := &file_metrics_proto_msgTypes[3]
+	mi := &file_metrics_proto_msgTypes[6]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -939,7 +1192,7 @@
 
 // Deprecated: Use PerfInfo.ProtoReflect.Descriptor instead.
 func (*PerfInfo) Descriptor() ([]byte, []int) {
-	return file_metrics_proto_rawDescGZIP(), []int{3}
+	return file_metrics_proto_rawDescGZIP(), []int{6}
 }
 
 func (x *PerfInfo) GetDescription() string {
@@ -1013,7 +1266,7 @@
 func (x *PerfCounters) Reset() {
 	*x = PerfCounters{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_metrics_proto_msgTypes[4]
+		mi := &file_metrics_proto_msgTypes[7]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1026,7 +1279,7 @@
 func (*PerfCounters) ProtoMessage() {}
 
 func (x *PerfCounters) ProtoReflect() protoreflect.Message {
-	mi := &file_metrics_proto_msgTypes[4]
+	mi := &file_metrics_proto_msgTypes[7]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1039,7 +1292,7 @@
 
 // Deprecated: Use PerfCounters.ProtoReflect.Descriptor instead.
 func (*PerfCounters) Descriptor() ([]byte, []int) {
-	return file_metrics_proto_rawDescGZIP(), []int{4}
+	return file_metrics_proto_rawDescGZIP(), []int{7}
 }
 
 func (x *PerfCounters) GetTime() uint64 {
@@ -1070,7 +1323,7 @@
 func (x *PerfCounterGroup) Reset() {
 	*x = PerfCounterGroup{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_metrics_proto_msgTypes[5]
+		mi := &file_metrics_proto_msgTypes[8]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1083,7 +1336,7 @@
 func (*PerfCounterGroup) ProtoMessage() {}
 
 func (x *PerfCounterGroup) ProtoReflect() protoreflect.Message {
-	mi := &file_metrics_proto_msgTypes[5]
+	mi := &file_metrics_proto_msgTypes[8]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1096,7 +1349,7 @@
 
 // Deprecated: Use PerfCounterGroup.ProtoReflect.Descriptor instead.
 func (*PerfCounterGroup) Descriptor() ([]byte, []int) {
-	return file_metrics_proto_rawDescGZIP(), []int{5}
+	return file_metrics_proto_rawDescGZIP(), []int{8}
 }
 
 func (x *PerfCounterGroup) GetName() string {
@@ -1127,7 +1380,7 @@
 func (x *PerfCounter) Reset() {
 	*x = PerfCounter{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_metrics_proto_msgTypes[6]
+		mi := &file_metrics_proto_msgTypes[9]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1140,7 +1393,7 @@
 func (*PerfCounter) ProtoMessage() {}
 
 func (x *PerfCounter) ProtoReflect() protoreflect.Message {
-	mi := &file_metrics_proto_msgTypes[6]
+	mi := &file_metrics_proto_msgTypes[9]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1153,7 +1406,7 @@
 
 // Deprecated: Use PerfCounter.ProtoReflect.Descriptor instead.
 func (*PerfCounter) Descriptor() ([]byte, []int) {
-	return file_metrics_proto_rawDescGZIP(), []int{6}
+	return file_metrics_proto_rawDescGZIP(), []int{9}
 }
 
 func (x *PerfCounter) GetName() string {
@@ -1200,7 +1453,7 @@
 func (x *ProcessResourceInfo) Reset() {
 	*x = ProcessResourceInfo{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_metrics_proto_msgTypes[7]
+		mi := &file_metrics_proto_msgTypes[10]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1213,7 +1466,7 @@
 func (*ProcessResourceInfo) ProtoMessage() {}
 
 func (x *ProcessResourceInfo) ProtoReflect() protoreflect.Message {
-	mi := &file_metrics_proto_msgTypes[7]
+	mi := &file_metrics_proto_msgTypes[10]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1226,7 +1479,7 @@
 
 // Deprecated: Use ProcessResourceInfo.ProtoReflect.Descriptor instead.
 func (*ProcessResourceInfo) Descriptor() ([]byte, []int) {
-	return file_metrics_proto_rawDescGZIP(), []int{7}
+	return file_metrics_proto_rawDescGZIP(), []int{10}
 }
 
 func (x *ProcessResourceInfo) GetName() string {
@@ -1320,7 +1573,7 @@
 func (x *ModuleTypeInfo) Reset() {
 	*x = ModuleTypeInfo{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_metrics_proto_msgTypes[8]
+		mi := &file_metrics_proto_msgTypes[11]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1333,7 +1586,7 @@
 func (*ModuleTypeInfo) ProtoMessage() {}
 
 func (x *ModuleTypeInfo) ProtoReflect() protoreflect.Message {
-	mi := &file_metrics_proto_msgTypes[8]
+	mi := &file_metrics_proto_msgTypes[11]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1346,7 +1599,7 @@
 
 // Deprecated: Use ModuleTypeInfo.ProtoReflect.Descriptor instead.
 func (*ModuleTypeInfo) Descriptor() ([]byte, []int) {
-	return file_metrics_proto_rawDescGZIP(), []int{8}
+	return file_metrics_proto_rawDescGZIP(), []int{11}
 }
 
 func (x *ModuleTypeInfo) GetBuildSystem() ModuleTypeInfo_BuildSystem {
@@ -1384,7 +1637,7 @@
 func (x *CriticalUserJourneyMetrics) Reset() {
 	*x = CriticalUserJourneyMetrics{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_metrics_proto_msgTypes[9]
+		mi := &file_metrics_proto_msgTypes[12]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1397,7 +1650,7 @@
 func (*CriticalUserJourneyMetrics) ProtoMessage() {}
 
 func (x *CriticalUserJourneyMetrics) ProtoReflect() protoreflect.Message {
-	mi := &file_metrics_proto_msgTypes[9]
+	mi := &file_metrics_proto_msgTypes[12]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1410,7 +1663,7 @@
 
 // Deprecated: Use CriticalUserJourneyMetrics.ProtoReflect.Descriptor instead.
 func (*CriticalUserJourneyMetrics) Descriptor() ([]byte, []int) {
-	return file_metrics_proto_rawDescGZIP(), []int{9}
+	return file_metrics_proto_rawDescGZIP(), []int{12}
 }
 
 func (x *CriticalUserJourneyMetrics) GetName() string {
@@ -1439,7 +1692,7 @@
 func (x *CriticalUserJourneysMetrics) Reset() {
 	*x = CriticalUserJourneysMetrics{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_metrics_proto_msgTypes[10]
+		mi := &file_metrics_proto_msgTypes[13]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1452,7 +1705,7 @@
 func (*CriticalUserJourneysMetrics) ProtoMessage() {}
 
 func (x *CriticalUserJourneysMetrics) ProtoReflect() protoreflect.Message {
-	mi := &file_metrics_proto_msgTypes[10]
+	mi := &file_metrics_proto_msgTypes[13]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1465,7 +1718,7 @@
 
 // Deprecated: Use CriticalUserJourneysMetrics.ProtoReflect.Descriptor instead.
 func (*CriticalUserJourneysMetrics) Descriptor() ([]byte, []int) {
-	return file_metrics_proto_rawDescGZIP(), []int{10}
+	return file_metrics_proto_rawDescGZIP(), []int{13}
 }
 
 func (x *CriticalUserJourneysMetrics) GetCujs() []*CriticalUserJourneyMetrics {
@@ -1501,7 +1754,7 @@
 func (x *SoongBuildMetrics) Reset() {
 	*x = SoongBuildMetrics{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_metrics_proto_msgTypes[11]
+		mi := &file_metrics_proto_msgTypes[14]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1514,7 +1767,7 @@
 func (*SoongBuildMetrics) ProtoMessage() {}
 
 func (x *SoongBuildMetrics) ProtoReflect() protoreflect.Message {
-	mi := &file_metrics_proto_msgTypes[11]
+	mi := &file_metrics_proto_msgTypes[14]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1527,7 +1780,7 @@
 
 // Deprecated: Use SoongBuildMetrics.ProtoReflect.Descriptor instead.
 func (*SoongBuildMetrics) Descriptor() ([]byte, []int) {
-	return file_metrics_proto_rawDescGZIP(), []int{11}
+	return file_metrics_proto_rawDescGZIP(), []int{14}
 }
 
 func (x *SoongBuildMetrics) GetModules() uint32 {
@@ -1605,7 +1858,7 @@
 func (x *ExpConfigFetcher) Reset() {
 	*x = ExpConfigFetcher{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_metrics_proto_msgTypes[12]
+		mi := &file_metrics_proto_msgTypes[15]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1618,7 +1871,7 @@
 func (*ExpConfigFetcher) ProtoMessage() {}
 
 func (x *ExpConfigFetcher) ProtoReflect() protoreflect.Message {
-	mi := &file_metrics_proto_msgTypes[12]
+	mi := &file_metrics_proto_msgTypes[15]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1631,7 +1884,7 @@
 
 // Deprecated: Use ExpConfigFetcher.ProtoReflect.Descriptor instead.
 func (*ExpConfigFetcher) Descriptor() ([]byte, []int) {
-	return file_metrics_proto_rawDescGZIP(), []int{12}
+	return file_metrics_proto_rawDescGZIP(), []int{15}
 }
 
 func (x *ExpConfigFetcher) GetStatus() ExpConfigFetcher_ConfigStatus {
@@ -1669,7 +1922,7 @@
 func (x *MixedBuildsInfo) Reset() {
 	*x = MixedBuildsInfo{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_metrics_proto_msgTypes[13]
+		mi := &file_metrics_proto_msgTypes[16]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1682,7 +1935,7 @@
 func (*MixedBuildsInfo) ProtoMessage() {}
 
 func (x *MixedBuildsInfo) ProtoReflect() protoreflect.Message {
-	mi := &file_metrics_proto_msgTypes[13]
+	mi := &file_metrics_proto_msgTypes[16]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1695,7 +1948,7 @@
 
 // Deprecated: Use MixedBuildsInfo.ProtoReflect.Descriptor instead.
 func (*MixedBuildsInfo) Descriptor() ([]byte, []int) {
-	return file_metrics_proto_rawDescGZIP(), []int{13}
+	return file_metrics_proto_rawDescGZIP(), []int{16}
 }
 
 func (x *MixedBuildsInfo) GetMixedBuildEnabledModules() []string {
@@ -1732,7 +1985,7 @@
 func (x *CriticalPathInfo) Reset() {
 	*x = CriticalPathInfo{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_metrics_proto_msgTypes[14]
+		mi := &file_metrics_proto_msgTypes[17]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1745,7 +1998,7 @@
 func (*CriticalPathInfo) ProtoMessage() {}
 
 func (x *CriticalPathInfo) ProtoReflect() protoreflect.Message {
-	mi := &file_metrics_proto_msgTypes[14]
+	mi := &file_metrics_proto_msgTypes[17]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1758,7 +2011,7 @@
 
 // Deprecated: Use CriticalPathInfo.ProtoReflect.Descriptor instead.
 func (*CriticalPathInfo) Descriptor() ([]byte, []int) {
-	return file_metrics_proto_rawDescGZIP(), []int{14}
+	return file_metrics_proto_rawDescGZIP(), []int{17}
 }
 
 func (x *CriticalPathInfo) GetElapsedTimeMicros() uint64 {
@@ -1803,7 +2056,7 @@
 func (x *JobInfo) Reset() {
 	*x = JobInfo{}
 	if protoimpl.UnsafeEnabled {
-		mi := &file_metrics_proto_msgTypes[15]
+		mi := &file_metrics_proto_msgTypes[18]
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		ms.StoreMessageInfo(mi)
 	}
@@ -1816,7 +2069,7 @@
 func (*JobInfo) ProtoMessage() {}
 
 func (x *JobInfo) ProtoReflect() protoreflect.Message {
-	mi := &file_metrics_proto_msgTypes[15]
+	mi := &file_metrics_proto_msgTypes[18]
 	if protoimpl.UnsafeEnabled && x != nil {
 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
 		if ms.LoadMessageInfo() == nil {
@@ -1829,7 +2082,7 @@
 
 // Deprecated: Use JobInfo.ProtoReflect.Descriptor instead.
 func (*JobInfo) Descriptor() ([]byte, []int) {
-	return file_metrics_proto_rawDescGZIP(), []int{15}
+	return file_metrics_proto_rawDescGZIP(), []int{18}
 }
 
 func (x *JobInfo) GetElapsedTimeMicros() uint64 {
@@ -1846,12 +2099,449 @@
 	return ""
 }
 
+type OptimizedBuildMetrics struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The total time spent analyzing what/how to optimize everything.
+	AnalysisPerf *PerfInfo `protobuf:"bytes,1,opt,name=analysis_perf,json=analysisPerf" json:"analysis_perf,omitempty"`
+	// The total time spent packaging artifacts.
+	PackagingPerf *PerfInfo `protobuf:"bytes,2,opt,name=packaging_perf,json=packagingPerf" json:"packaging_perf,omitempty"`
+	// Information for a single target (e.g. general-tests).
+	TargetResult []*OptimizedBuildMetrics_TargetOptimizationResult `protobuf:"bytes,3,rep,name=target_result,json=targetResult" json:"target_result,omitempty"`
+}
+
+func (x *OptimizedBuildMetrics) Reset() {
+	*x = OptimizedBuildMetrics{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_metrics_proto_msgTypes[19]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *OptimizedBuildMetrics) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*OptimizedBuildMetrics) ProtoMessage() {}
+
+func (x *OptimizedBuildMetrics) ProtoReflect() protoreflect.Message {
+	mi := &file_metrics_proto_msgTypes[19]
+	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 OptimizedBuildMetrics.ProtoReflect.Descriptor instead.
+func (*OptimizedBuildMetrics) Descriptor() ([]byte, []int) {
+	return file_metrics_proto_rawDescGZIP(), []int{19}
+}
+
+func (x *OptimizedBuildMetrics) GetAnalysisPerf() *PerfInfo {
+	if x != nil {
+		return x.AnalysisPerf
+	}
+	return nil
+}
+
+func (x *OptimizedBuildMetrics) GetPackagingPerf() *PerfInfo {
+	if x != nil {
+		return x.PackagingPerf
+	}
+	return nil
+}
+
+func (x *OptimizedBuildMetrics) GetTargetResult() []*OptimizedBuildMetrics_TargetOptimizationResult {
+	if x != nil {
+		return x.TargetResult
+	}
+	return nil
+}
+
+// This is created by soong_ui from SoongExexcutionMetrics files.
+type ExecutionMetrics struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The arguments provided on the command line.
+	CommandArgs []string `protobuf:"bytes,1,rep,name=command_args,json=commandArgs" json:"command_args,omitempty"`
+	// Changed files detected by the build.
+	ChangedFiles *AggregatedFileList `protobuf:"bytes,2,opt,name=changed_files,json=changedFiles" json:"changed_files,omitempty"`
+}
+
+func (x *ExecutionMetrics) Reset() {
+	*x = ExecutionMetrics{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_metrics_proto_msgTypes[20]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *ExecutionMetrics) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*ExecutionMetrics) ProtoMessage() {}
+
+func (x *ExecutionMetrics) ProtoReflect() protoreflect.Message {
+	mi := &file_metrics_proto_msgTypes[20]
+	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 ExecutionMetrics.ProtoReflect.Descriptor instead.
+func (*ExecutionMetrics) Descriptor() ([]byte, []int) {
+	return file_metrics_proto_rawDescGZIP(), []int{20}
+}
+
+func (x *ExecutionMetrics) GetCommandArgs() []string {
+	if x != nil {
+		return x.CommandArgs
+	}
+	return nil
+}
+
+func (x *ExecutionMetrics) GetChangedFiles() *AggregatedFileList {
+	if x != nil {
+		return x.ChangedFiles
+	}
+	return nil
+}
+
+// This is created by soong_ui from the various
+// android.find_input_delta_proto.FileList metrics provided to it by
+// find_input_delta.
+type AggregatedFileList struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The (possibly truncated list of) added files.
+	Additions []string `protobuf:"bytes,2,rep,name=additions" json:"additions,omitempty"`
+	// The (possibly truncated list of) changed files.
+	Changes []string `protobuf:"bytes,3,rep,name=changes" json:"changes,omitempty"`
+	// The (possibly truncated list of) deleted files.
+	Deletions []string `protobuf:"bytes,4,rep,name=deletions" json:"deletions,omitempty"`
+	// Count of files added/changed/deleted.
+	TotalDelta *uint32 `protobuf:"varint,5,opt,name=total_delta,json=totalDelta" json:"total_delta,omitempty"`
+	// Counts by extension.
+	Counts []*FileCount `protobuf:"bytes,6,rep,name=counts" json:"counts,omitempty"`
+}
+
+func (x *AggregatedFileList) Reset() {
+	*x = AggregatedFileList{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_metrics_proto_msgTypes[21]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *AggregatedFileList) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*AggregatedFileList) ProtoMessage() {}
+
+func (x *AggregatedFileList) ProtoReflect() protoreflect.Message {
+	mi := &file_metrics_proto_msgTypes[21]
+	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 AggregatedFileList.ProtoReflect.Descriptor instead.
+func (*AggregatedFileList) Descriptor() ([]byte, []int) {
+	return file_metrics_proto_rawDescGZIP(), []int{21}
+}
+
+func (x *AggregatedFileList) GetAdditions() []string {
+	if x != nil {
+		return x.Additions
+	}
+	return nil
+}
+
+func (x *AggregatedFileList) GetChanges() []string {
+	if x != nil {
+		return x.Changes
+	}
+	return nil
+}
+
+func (x *AggregatedFileList) GetDeletions() []string {
+	if x != nil {
+		return x.Deletions
+	}
+	return nil
+}
+
+func (x *AggregatedFileList) GetTotalDelta() uint32 {
+	if x != nil && x.TotalDelta != nil {
+		return *x.TotalDelta
+	}
+	return 0
+}
+
+func (x *AggregatedFileList) GetCounts() []*FileCount {
+	if x != nil {
+		return x.Counts
+	}
+	return nil
+}
+
+type FileCount struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The file extension
+	Extension *string `protobuf:"bytes,1,opt,name=extension" json:"extension,omitempty"`
+	// Number of added files with this extension.
+	Additions *uint32 `protobuf:"varint,2,opt,name=additions" json:"additions,omitempty"`
+	// Number of modified files with this extension.
+	Modifications *uint32 `protobuf:"varint,3,opt,name=modifications" json:"modifications,omitempty"`
+	// Number of deleted files with this extension.
+	Deletions *uint32 `protobuf:"varint,4,opt,name=deletions" json:"deletions,omitempty"`
+}
+
+func (x *FileCount) Reset() {
+	*x = FileCount{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_metrics_proto_msgTypes[22]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *FileCount) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*FileCount) ProtoMessage() {}
+
+func (x *FileCount) ProtoReflect() protoreflect.Message {
+	mi := &file_metrics_proto_msgTypes[22]
+	if protoimpl.UnsafeEnabled && x != nil {
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		if ms.LoadMessageInfo() == nil {
+			ms.StoreMessageInfo(mi)
+		}
+		return ms
+	}
+	return mi.MessageOf(x)
+}
+
+// Deprecated: Use FileCount.ProtoReflect.Descriptor instead.
+func (*FileCount) Descriptor() ([]byte, []int) {
+	return file_metrics_proto_rawDescGZIP(), []int{22}
+}
+
+func (x *FileCount) GetExtension() string {
+	if x != nil && x.Extension != nil {
+		return *x.Extension
+	}
+	return ""
+}
+
+func (x *FileCount) GetAdditions() uint32 {
+	if x != nil && x.Additions != nil {
+		return *x.Additions
+	}
+	return 0
+}
+
+func (x *FileCount) GetModifications() uint32 {
+	if x != nil && x.Modifications != nil {
+		return *x.Modifications
+	}
+	return 0
+}
+
+func (x *FileCount) GetDeletions() uint32 {
+	if x != nil && x.Deletions != nil {
+		return *x.Deletions
+	}
+	return 0
+}
+
+type OptimizedBuildMetrics_TargetOptimizationResult struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Target name (e.g. general-tests).
+	Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
+	// Whether or not this target was optimized.
+	Optimized *bool `protobuf:"varint,2,opt,name=optimized" json:"optimized,omitempty"`
+	// Reasoning for why the target wasn't optimized if it wasn't
+	OptimizationRationale *string `protobuf:"bytes,3,opt,name=optimization_rationale,json=optimizationRationale" json:"optimization_rationale,omitempty"`
+	// Time spent packaging this specific target (if it was optimized).
+	PackagingPerf *PerfInfo `protobuf:"bytes,4,opt,name=packaging_perf,json=packagingPerf" json:"packaging_perf,omitempty"`
+	// Information for each different artifact produced by this target (if it
+	// was optimized).
+	OutputArtifact []*OptimizedBuildMetrics_TargetOptimizationResult_OutputArtifact `protobuf:"bytes,5,rep,name=output_artifact,json=outputArtifact" json:"output_artifact,omitempty"`
+}
+
+func (x *OptimizedBuildMetrics_TargetOptimizationResult) Reset() {
+	*x = OptimizedBuildMetrics_TargetOptimizationResult{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_metrics_proto_msgTypes[23]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *OptimizedBuildMetrics_TargetOptimizationResult) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*OptimizedBuildMetrics_TargetOptimizationResult) ProtoMessage() {}
+
+func (x *OptimizedBuildMetrics_TargetOptimizationResult) ProtoReflect() protoreflect.Message {
+	mi := &file_metrics_proto_msgTypes[23]
+	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 OptimizedBuildMetrics_TargetOptimizationResult.ProtoReflect.Descriptor instead.
+func (*OptimizedBuildMetrics_TargetOptimizationResult) Descriptor() ([]byte, []int) {
+	return file_metrics_proto_rawDescGZIP(), []int{19, 0}
+}
+
+func (x *OptimizedBuildMetrics_TargetOptimizationResult) GetName() string {
+	if x != nil && x.Name != nil {
+		return *x.Name
+	}
+	return ""
+}
+
+func (x *OptimizedBuildMetrics_TargetOptimizationResult) GetOptimized() bool {
+	if x != nil && x.Optimized != nil {
+		return *x.Optimized
+	}
+	return false
+}
+
+func (x *OptimizedBuildMetrics_TargetOptimizationResult) GetOptimizationRationale() string {
+	if x != nil && x.OptimizationRationale != nil {
+		return *x.OptimizationRationale
+	}
+	return ""
+}
+
+func (x *OptimizedBuildMetrics_TargetOptimizationResult) GetPackagingPerf() *PerfInfo {
+	if x != nil {
+		return x.PackagingPerf
+	}
+	return nil
+}
+
+func (x *OptimizedBuildMetrics_TargetOptimizationResult) GetOutputArtifact() []*OptimizedBuildMetrics_TargetOptimizationResult_OutputArtifact {
+	if x != nil {
+		return x.OutputArtifact
+	}
+	return nil
+}
+
+type OptimizedBuildMetrics_TargetOptimizationResult_OutputArtifact struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// Artifact file name (e.g. general-tests.zip)
+	Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
+	// Size of the file.
+	Size *int64 `protobuf:"varint,2,opt,name=size" json:"size,omitempty"`
+	// Lists of modules packaged into this artifact.
+	IncludedModules []string `protobuf:"bytes,3,rep,name=included_modules,json=includedModules" json:"included_modules,omitempty"`
+}
+
+func (x *OptimizedBuildMetrics_TargetOptimizationResult_OutputArtifact) Reset() {
+	*x = OptimizedBuildMetrics_TargetOptimizationResult_OutputArtifact{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_metrics_proto_msgTypes[24]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *OptimizedBuildMetrics_TargetOptimizationResult_OutputArtifact) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*OptimizedBuildMetrics_TargetOptimizationResult_OutputArtifact) ProtoMessage() {}
+
+func (x *OptimizedBuildMetrics_TargetOptimizationResult_OutputArtifact) ProtoReflect() protoreflect.Message {
+	mi := &file_metrics_proto_msgTypes[24]
+	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 OptimizedBuildMetrics_TargetOptimizationResult_OutputArtifact.ProtoReflect.Descriptor instead.
+func (*OptimizedBuildMetrics_TargetOptimizationResult_OutputArtifact) Descriptor() ([]byte, []int) {
+	return file_metrics_proto_rawDescGZIP(), []int{19, 0, 0}
+}
+
+func (x *OptimizedBuildMetrics_TargetOptimizationResult_OutputArtifact) GetName() string {
+	if x != nil && x.Name != nil {
+		return *x.Name
+	}
+	return ""
+}
+
+func (x *OptimizedBuildMetrics_TargetOptimizationResult_OutputArtifact) GetSize() int64 {
+	if x != nil && x.Size != nil {
+		return *x.Size
+	}
+	return 0
+}
+
+func (x *OptimizedBuildMetrics_TargetOptimizationResult_OutputArtifact) GetIncludedModules() []string {
+	if x != nil {
+		return x.IncludedModules
+	}
+	return nil
+}
+
 var File_metrics_proto protoreflect.FileDescriptor
 
 var file_metrics_proto_rawDesc = []byte{
 	0x0a, 0x0d, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12,
 	0x13, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74,
-	0x72, 0x69, 0x63, 0x73, 0x22, 0xcc, 0x0f, 0x0a, 0x0b, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
+	0x72, 0x69, 0x63, 0x73, 0x22, 0xd7, 0x10, 0x0a, 0x0b, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
 	0x42, 0x61, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x64, 0x61,
 	0x74, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01,
 	0x28, 0x03, 0x52, 0x12, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x61, 0x74, 0x65, 0x54, 0x69, 0x6d,
@@ -1969,220 +2659,338 @@
 	0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c,
 	0x65, 0x18, 0x22, 0x20, 0x03, 0x28, 0x09, 0x52, 0x1a, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64,
 	0x45, 0x6e, 0x76, 0x69, 0x72, 0x6f, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x61, 0x72, 0x69, 0x61,
-	0x62, 0x6c, 0x65, 0x22, 0x30, 0x0a, 0x0c, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x56, 0x61, 0x72, 0x69,
-	0x61, 0x6e, 0x74, 0x12, 0x08, 0x0a, 0x04, 0x55, 0x53, 0x45, 0x52, 0x10, 0x00, 0x12, 0x0d, 0x0a,
-	0x09, 0x55, 0x53, 0x45, 0x52, 0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03,
-	0x45, 0x4e, 0x47, 0x10, 0x02, 0x22, 0x3c, 0x0a, 0x04, 0x41, 0x72, 0x63, 0x68, 0x12, 0x0b, 0x0a,
-	0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x52,
-	0x4d, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x52, 0x4d, 0x36, 0x34, 0x10, 0x02, 0x12, 0x07,
-	0x0a, 0x03, 0x58, 0x38, 0x36, 0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x58, 0x38, 0x36, 0x5f, 0x36,
-	0x34, 0x10, 0x04, 0x22, 0x8a, 0x04, 0x0a, 0x0b, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, 0x6e,
-	0x66, 0x69, 0x67, 0x12, 0x19, 0x0a, 0x08, 0x75, 0x73, 0x65, 0x5f, 0x67, 0x6f, 0x6d, 0x61, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x75, 0x73, 0x65, 0x47, 0x6f, 0x6d, 0x61, 0x12, 0x17,
-	0x0a, 0x07, 0x75, 0x73, 0x65, 0x5f, 0x72, 0x62, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52,
-	0x06, 0x75, 0x73, 0x65, 0x52, 0x62, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x6f, 0x72, 0x63, 0x65,
-	0x5f, 0x75, 0x73, 0x65, 0x5f, 0x67, 0x6f, 0x6d, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52,
-	0x0c, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x55, 0x73, 0x65, 0x47, 0x6f, 0x6d, 0x61, 0x12, 0x24, 0x0a,
-	0x0e, 0x62, 0x61, 0x7a, 0x65, 0x6c, 0x5f, 0x61, 0x73, 0x5f, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x18,
-	0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x62, 0x61, 0x7a, 0x65, 0x6c, 0x41, 0x73, 0x4e, 0x69,
-	0x6e, 0x6a, 0x61, 0x12, 0x2a, 0x0a, 0x11, 0x62, 0x61, 0x7a, 0x65, 0x6c, 0x5f, 0x6d, 0x69, 0x78,
-	0x65, 0x64, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f,
-	0x62, 0x61, 0x7a, 0x65, 0x6c, 0x4d, 0x69, 0x78, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12,
-	0x18, 0x0a, 0x07, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09,
-	0x52, 0x07, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x73, 0x12, 0x44, 0x0a, 0x1f, 0x66, 0x6f, 0x72,
-	0x63, 0x65, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x62, 0x61, 0x7a, 0x65, 0x6c,
-	0x5f, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x07, 0x20, 0x01,
-	0x28, 0x08, 0x52, 0x1b, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65,
-	0x42, 0x61, 0x7a, 0x65, 0x6c, 0x4d, 0x69, 0x78, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12,
-	0x79, 0x0a, 0x18, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f,
-	0x6c, 0x69, 0x73, 0x74, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28,
-	0x0e, 0x32, 0x36, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f,
-	0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, 0x6e,
-	0x66, 0x69, 0x67, 0x2e, 0x4e, 0x69, 0x6e, 0x6a, 0x61, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x4c,
-	0x69, 0x73, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x3a, 0x08, 0x4e, 0x4f, 0x54, 0x5f, 0x55,
-	0x53, 0x45, 0x44, 0x52, 0x15, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74,
-	0x4c, 0x69, 0x73, 0x74, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x74, 0x0a, 0x15, 0x4e, 0x69,
-	0x6e, 0x6a, 0x61, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6f, 0x75,
-	0x72, 0x63, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x4e, 0x4f, 0x54, 0x5f, 0x55, 0x53, 0x45, 0x44, 0x10,
-	0x00, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x49, 0x4e, 0x4a, 0x41, 0x5f, 0x4c, 0x4f, 0x47, 0x10, 0x01,
-	0x12, 0x16, 0x0a, 0x12, 0x45, 0x56, 0x45, 0x4e, 0x4c, 0x59, 0x5f, 0x44, 0x49, 0x53, 0x54, 0x52,
-	0x49, 0x42, 0x55, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x45, 0x58, 0x54, 0x45,
-	0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x03, 0x12, 0x13, 0x0a, 0x0f, 0x48,
-	0x49, 0x4e, 0x54, 0x5f, 0x46, 0x52, 0x4f, 0x4d, 0x5f, 0x53, 0x4f, 0x4f, 0x4e, 0x47, 0x10, 0x04,
-	0x22, 0x6f, 0x0a, 0x12, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72,
-	0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x32, 0x0a, 0x15, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f,
-	0x70, 0x68, 0x79, 0x73, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x68, 0x79, 0x73,
-	0x69, 0x63, 0x61, 0x6c, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x76,
-	0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x70, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01,
-	0x28, 0x05, 0x52, 0x0d, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x70, 0x75,
-	0x73, 0x22, 0xca, 0x02, 0x0a, 0x08, 0x50, 0x65, 0x72, 0x66, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x20,
-	0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e,
-	0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
-	0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69,
-	0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54,
-	0x69, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x72, 0x65, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65,
-	0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x72, 0x65, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65,
-	0x12, 0x21, 0x0a, 0x0a, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x05,
-	0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x18, 0x01, 0x52, 0x09, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79,
-	0x55, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x17, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73,
-	0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x06,
-	0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69,
-	0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65,
-	0x73, 0x73, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x15,
-	0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63,
-	0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x22, 0x0a, 0x0d, 0x6e, 0x6f, 0x6e, 0x5f, 0x7a, 0x65, 0x72,
-	0x6f, 0x5f, 0x65, 0x78, 0x69, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6e, 0x6f,
-	0x6e, 0x5a, 0x65, 0x72, 0x6f, 0x45, 0x78, 0x69, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x72, 0x72,
-	0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x61,
-	0x0a, 0x0c, 0x50, 0x65, 0x72, 0x66, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x12, 0x12,
-	0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x74, 0x69,
-	0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x06, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03,
-	0x28, 0x0b, 0x32, 0x25, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64,
-	0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x50, 0x65, 0x72, 0x66, 0x43, 0x6f, 0x75,
-	0x6e, 0x74, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x52, 0x06, 0x67, 0x72, 0x6f, 0x75, 0x70,
-	0x73, 0x22, 0x64, 0x0a, 0x10, 0x50, 0x65, 0x72, 0x66, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72,
-	0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20,
-	0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3c, 0x0a, 0x08, 0x63, 0x6f, 0x75,
-	0x6e, 0x74, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x6f,
-	0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
-	0x73, 0x2e, 0x50, 0x65, 0x72, 0x66, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x52, 0x08, 0x63,
-	0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x22, 0x37, 0x0a, 0x0b, 0x50, 0x65, 0x72, 0x66, 0x43,
-	0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61,
-	0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65,
-	0x22, 0xb9, 0x03, 0x0a, 0x13, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x6f,
-	0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
-	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x10,
-	0x75, 0x73, 0x65, 0x72, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73,
-	0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x75, 0x73, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65,
-	0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d,
-	0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01,
-	0x28, 0x04, 0x52, 0x10, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x69,
-	0x63, 0x72, 0x6f, 0x73, 0x12, 0x1c, 0x0a, 0x0a, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x73, 0x73, 0x5f,
-	0x6b, 0x62, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x52, 0x73, 0x73,
-	0x4b, 0x62, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65,
-	0x5f, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d,
-	0x69, 0x6e, 0x6f, 0x72, 0x50, 0x61, 0x67, 0x65, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x2a,
-	0x0a, 0x11, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x61, 0x75,
-	0x6c, 0x74, 0x73, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d, 0x61, 0x6a, 0x6f, 0x72,
-	0x50, 0x61, 0x67, 0x65, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x69, 0x6f,
-	0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, 0x6b, 0x62, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52,
-	0x09, 0x69, 0x6f, 0x49, 0x6e, 0x70, 0x75, 0x74, 0x4b, 0x62, 0x12, 0x20, 0x0a, 0x0c, 0x69, 0x6f,
-	0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x6b, 0x62, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04,
-	0x52, 0x0a, 0x69, 0x6f, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x4b, 0x62, 0x12, 0x3c, 0x0a, 0x1a,
-	0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78,
-	0x74, 0x5f, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04,
-	0x52, 0x18, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x65,
-	0x78, 0x74, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x12, 0x40, 0x0a, 0x1c, 0x69, 0x6e,
-	0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78,
-	0x74, 0x5f, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04,
-	0x52, 0x1a, 0x69, 0x6e, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x6e,
-	0x74, 0x65, 0x78, 0x74, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x65, 0x73, 0x22, 0xe5, 0x01, 0x0a,
-	0x0e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12,
-	0x5b, 0x0a, 0x0c, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18,
-	0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2f, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75,
-	0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x4d, 0x6f, 0x64, 0x75,
-	0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64,
-	0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x3a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x52,
-	0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x1f, 0x0a, 0x0b,
-	0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28,
-	0x09, 0x52, 0x0a, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a,
-	0x0e, 0x6e, 0x75, 0x6d, 0x5f, 0x6f, 0x66, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18,
-	0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6e, 0x75, 0x6d, 0x4f, 0x66, 0x4d, 0x6f, 0x64, 0x75,
-	0x6c, 0x65, 0x73, 0x22, 0x2f, 0x0a, 0x0b, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x79, 0x73, 0x74,
-	0x65, 0x6d, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12,
-	0x09, 0x0a, 0x05, 0x53, 0x4f, 0x4f, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x4d, 0x41,
-	0x4b, 0x45, 0x10, 0x02, 0x22, 0x6c, 0x0a, 0x1a, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c,
-	0x55, 0x73, 0x65, 0x72, 0x4a, 0x6f, 0x75, 0x72, 0x6e, 0x65, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69,
-	0x63, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x3a, 0x0a, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
-	0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f,
-	0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x4d, 0x65,
-	0x74, 0x72, 0x69, 0x63, 0x73, 0x42, 0x61, 0x73, 0x65, 0x52, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69,
-	0x63, 0x73, 0x22, 0x62, 0x0a, 0x1b, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x55, 0x73,
-	0x65, 0x72, 0x4a, 0x6f, 0x75, 0x72, 0x6e, 0x65, 0x79, 0x73, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63,
-	0x73, 0x12, 0x43, 0x0a, 0x04, 0x63, 0x75, 0x6a, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
-	0x2f, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65,
-	0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x55, 0x73,
-	0x65, 0x72, 0x4a, 0x6f, 0x75, 0x72, 0x6e, 0x65, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
-	0x52, 0x04, 0x63, 0x75, 0x6a, 0x73, 0x22, 0x94, 0x03, 0x0a, 0x11, 0x53, 0x6f, 0x6f, 0x6e, 0x67,
-	0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x18, 0x0a, 0x07,
-	0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x6d,
-	0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e,
-	0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x08, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e,
-	0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6c, 0x6c, 0x6f,
-	0x63, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x74,
-	0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28,
-	0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x5f, 0x73, 0x69,
-	0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41,
-	0x6c, 0x6c, 0x6f, 0x63, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f,
-	0x68, 0x65, 0x61, 0x70, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52,
-	0x0b, 0x6d, 0x61, 0x78, 0x48, 0x65, 0x61, 0x70, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x35, 0x0a, 0x06,
-	0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x73,
+	0x62, 0x6c, 0x65, 0x12, 0x62, 0x0a, 0x17, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x65, 0x64,
+	0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x23,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69,
+	0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6d,
+	0x69, 0x7a, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
+	0x52, 0x15, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64,
+	0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x25, 0x0a, 0x0e, 0x74, 0x61, 0x72, 0x67, 0x65,
+	0x74, 0x5f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x18, 0x24, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x0d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x22, 0x30,
+	0x0a, 0x0c, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x56, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x12, 0x08,
+	0x0a, 0x04, 0x55, 0x53, 0x45, 0x52, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x53, 0x45, 0x52,
+	0x44, 0x45, 0x42, 0x55, 0x47, 0x10, 0x01, 0x12, 0x07, 0x0a, 0x03, 0x45, 0x4e, 0x47, 0x10, 0x02,
+	0x22, 0x3c, 0x0a, 0x04, 0x41, 0x72, 0x63, 0x68, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e,
+	0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x52, 0x4d, 0x10, 0x01, 0x12, 0x09,
+	0x0a, 0x05, 0x41, 0x52, 0x4d, 0x36, 0x34, 0x10, 0x02, 0x12, 0x07, 0x0a, 0x03, 0x58, 0x38, 0x36,
+	0x10, 0x03, 0x12, 0x0a, 0x0a, 0x06, 0x58, 0x38, 0x36, 0x5f, 0x36, 0x34, 0x10, 0x04, 0x22, 0xf2,
+	0x04, 0x0a, 0x0b, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x19,
+	0x0a, 0x08, 0x75, 0x73, 0x65, 0x5f, 0x67, 0x6f, 0x6d, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08,
+	0x52, 0x07, 0x75, 0x73, 0x65, 0x47, 0x6f, 0x6d, 0x61, 0x12, 0x17, 0x0a, 0x07, 0x75, 0x73, 0x65,
+	0x5f, 0x72, 0x62, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x75, 0x73, 0x65, 0x52,
+	0x62, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x5f,
+	0x67, 0x6f, 0x6d, 0x61, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x66, 0x6f, 0x72, 0x63,
+	0x65, 0x55, 0x73, 0x65, 0x47, 0x6f, 0x6d, 0x61, 0x12, 0x24, 0x0a, 0x0e, 0x62, 0x61, 0x7a, 0x65,
+	0x6c, 0x5f, 0x61, 0x73, 0x5f, 0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08,
+	0x52, 0x0c, 0x62, 0x61, 0x7a, 0x65, 0x6c, 0x41, 0x73, 0x4e, 0x69, 0x6e, 0x6a, 0x61, 0x12, 0x2a,
+	0x0a, 0x11, 0x62, 0x61, 0x7a, 0x65, 0x6c, 0x5f, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x62, 0x75,
+	0x69, 0x6c, 0x64, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x62, 0x61, 0x7a, 0x65, 0x6c,
+	0x4d, 0x69, 0x78, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x74, 0x61,
+	0x72, 0x67, 0x65, 0x74, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x74, 0x61, 0x72,
+	0x67, 0x65, 0x74, 0x73, 0x12, 0x44, 0x0a, 0x1f, 0x66, 0x6f, 0x72, 0x63, 0x65, 0x5f, 0x64, 0x69,
+	0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x62, 0x61, 0x7a, 0x65, 0x6c, 0x5f, 0x6d, 0x69, 0x78, 0x65,
+	0x64, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1b, 0x66,
+	0x6f, 0x72, 0x63, 0x65, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x42, 0x61, 0x7a, 0x65, 0x6c,
+	0x4d, 0x69, 0x78, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x12, 0x79, 0x0a, 0x18, 0x6e, 0x69,
+	0x6e, 0x6a, 0x61, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x5f,
+	0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x36, 0x2e, 0x73,
 	0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69,
-	0x63, 0x73, 0x2e, 0x50, 0x65, 0x72, 0x66, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x65, 0x76, 0x65,
-	0x6e, 0x74, 0x73, 0x12, 0x50, 0x0a, 0x11, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x62, 0x75, 0x69,
-	0x6c, 0x64, 0x73, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24,
-	0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74,
-	0x72, 0x69, 0x63, 0x73, 0x2e, 0x4d, 0x69, 0x78, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x73,
-	0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0f, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64,
-	0x73, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x46, 0x0a, 0x0d, 0x70, 0x65, 0x72, 0x66, 0x5f, 0x63, 0x6f,
-	0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x73,
-	0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69,
-	0x63, 0x73, 0x2e, 0x50, 0x65, 0x72, 0x66, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x52,
-	0x0c, 0x70, 0x65, 0x72, 0x66, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x22, 0xdb, 0x01,
-	0x0a, 0x10, 0x45, 0x78, 0x70, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x65, 0x74, 0x63, 0x68,
-	0x65, 0x72, 0x12, 0x4a, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01,
-	0x28, 0x0e, 0x32, 0x32, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64,
-	0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x45, 0x78, 0x70, 0x43, 0x6f, 0x6e, 0x66,
-	0x69, 0x67, 0x46, 0x65, 0x74, 0x63, 0x68, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67,
-	0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a,
-	0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x69,
-	0x63, 0x72, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6d, 0x69, 0x63, 0x72,
-	0x6f, 0x73, 0x22, 0x47, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x74, 0x61, 0x74,
-	0x75, 0x73, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x4f, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x10,
-	0x00, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x10, 0x01, 0x12, 0x09, 0x0a,
-	0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x4d, 0x49, 0x53, 0x53,
-	0x49, 0x4e, 0x47, 0x5f, 0x47, 0x43, 0x45, 0x52, 0x54, 0x10, 0x03, 0x22, 0x91, 0x01, 0x0a, 0x0f,
-	0x4d, 0x69, 0x78, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x12,
-	0x3d, 0x0a, 0x1b, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x65,
-	0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01,
-	0x20, 0x03, 0x28, 0x09, 0x52, 0x18, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64,
-	0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x3f,
-	0x0a, 0x1c, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x64, 0x69,
-	0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x02,
-	0x20, 0x03, 0x28, 0x09, 0x52, 0x19, 0x6d, 0x69, 0x78, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64,
-	0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x22,
-	0x8a, 0x02, 0x0a, 0x10, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68,
-	0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2e, 0x0a, 0x13, 0x65, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x64, 0x5f,
-	0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
-	0x04, 0x52, 0x11, 0x65, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x69,
-	0x63, 0x72, 0x6f, 0x73, 0x12, 0x39, 0x0a, 0x19, 0x63, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c,
-	0x5f, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f,
-	0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x16, 0x63, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61,
-	0x6c, 0x50, 0x61, 0x74, 0x68, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x12,
-	0x41, 0x0a, 0x0d, 0x63, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x70, 0x61, 0x74, 0x68,
-	0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62,
-	0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x4a, 0x6f, 0x62,
-	0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0c, 0x63, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x50, 0x61,
-	0x74, 0x68, 0x12, 0x48, 0x0a, 0x11, 0x6c, 0x6f, 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6e, 0x6e, 0x69,
-	0x6e, 0x67, 0x5f, 0x6a, 0x6f, 0x62, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e,
+	0x63, 0x73, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x4e,
+	0x69, 0x6e, 0x6a, 0x61, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x53, 0x6f,
+	0x75, 0x72, 0x63, 0x65, 0x3a, 0x08, 0x4e, 0x4f, 0x54, 0x5f, 0x55, 0x53, 0x45, 0x44, 0x52, 0x15,
+	0x6e, 0x69, 0x6e, 0x6a, 0x61, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x4c, 0x69, 0x73, 0x74, 0x53,
+	0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x47, 0x0a, 0x0e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x65,
+	0x6e, 0x76, 0x5f, 0x76, 0x61, 0x72, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e,
 	0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72,
-	0x69, 0x63, 0x73, 0x2e, 0x4a, 0x6f, 0x62, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0f, 0x6c, 0x6f, 0x6e,
-	0x67, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x4a, 0x6f, 0x62, 0x73, 0x22, 0x62, 0x0a, 0x07,
-	0x4a, 0x6f, 0x62, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2e, 0x0a, 0x13, 0x65, 0x6c, 0x61, 0x70, 0x73,
-	0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x18, 0x01,
-	0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x65, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x64, 0x54, 0x69, 0x6d,
-	0x65, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x6a, 0x6f, 0x62, 0x5f, 0x64,
-	0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
-	0x52, 0x0e, 0x6a, 0x6f, 0x62, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e,
-	0x42, 0x28, 0x5a, 0x26, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e,
-	0x67, 0x2f, 0x75, 0x69, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2f, 0x6d, 0x65, 0x74,
-	0x72, 0x69, 0x63, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	0x69, 0x63, 0x73, 0x2e, 0x53, 0x6f, 0x6f, 0x6e, 0x67, 0x45, 0x6e, 0x76, 0x56, 0x61, 0x72, 0x73,
+	0x52, 0x0c, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x45, 0x6e, 0x76, 0x56, 0x61, 0x72, 0x73, 0x12, 0x1d,
+	0x0a, 0x0a, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x0a, 0x20, 0x01,
+	0x28, 0x08, 0x52, 0x09, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x4f, 0x6e, 0x6c, 0x79, 0x22, 0x74, 0x0a,
+	0x15, 0x4e, 0x69, 0x6e, 0x6a, 0x61, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x4c, 0x69, 0x73, 0x74,
+	0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x0c, 0x0a, 0x08, 0x4e, 0x4f, 0x54, 0x5f, 0x55, 0x53,
+	0x45, 0x44, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x4e, 0x49, 0x4e, 0x4a, 0x41, 0x5f, 0x4c, 0x4f,
+	0x47, 0x10, 0x01, 0x12, 0x16, 0x0a, 0x12, 0x45, 0x56, 0x45, 0x4e, 0x4c, 0x59, 0x5f, 0x44, 0x49,
+	0x53, 0x54, 0x52, 0x49, 0x42, 0x55, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x11, 0x0a, 0x0d, 0x45,
+	0x58, 0x54, 0x45, 0x52, 0x4e, 0x41, 0x4c, 0x5f, 0x46, 0x49, 0x4c, 0x45, 0x10, 0x03, 0x12, 0x13,
+	0x0a, 0x0f, 0x48, 0x49, 0x4e, 0x54, 0x5f, 0x46, 0x52, 0x4f, 0x4d, 0x5f, 0x53, 0x4f, 0x4f, 0x4e,
+	0x47, 0x10, 0x04, 0x22, 0x67, 0x0a, 0x0c, 0x53, 0x6f, 0x6f, 0x6e, 0x67, 0x45, 0x6e, 0x76, 0x56,
+	0x61, 0x72, 0x73, 0x12, 0x27, 0x0a, 0x0f, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x63,
+	0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x70, 0x61,
+	0x72, 0x74, 0x69, 0x61, 0x6c, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x12, 0x2e, 0x0a, 0x13,
+	0x75, 0x73, 0x65, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x63, 0x6f, 0x6d, 0x70,
+	0x69, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x11, 0x75, 0x73, 0x65, 0x50, 0x61,
+	0x72, 0x74, 0x69, 0x61, 0x6c, 0x43, 0x6f, 0x6d, 0x70, 0x69, 0x6c, 0x65, 0x22, 0xed, 0x01, 0x0a,
+	0x12, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49,
+	0x6e, 0x66, 0x6f, 0x12, 0x32, 0x0a, 0x15, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x70, 0x68, 0x79,
+	0x73, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01,
+	0x28, 0x04, 0x52, 0x13, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x50, 0x68, 0x79, 0x73, 0x69, 0x63, 0x61,
+	0x6c, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x12, 0x25, 0x0a, 0x0e, 0x61, 0x76, 0x61, 0x69, 0x6c,
+	0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x70, 0x75, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52,
+	0x0d, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x70, 0x75, 0x73, 0x12, 0x3d,
+	0x0a, 0x08, 0x63, 0x70, 0x75, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x22, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d,
+	0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x43, 0x70, 0x75,
+	0x49, 0x6e, 0x66, 0x6f, 0x52, 0x07, 0x63, 0x70, 0x75, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x3d, 0x0a,
+	0x08, 0x6d, 0x65, 0x6d, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x22, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65,
+	0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x4d, 0x65, 0x6d, 0x49,
+	0x6e, 0x66, 0x6f, 0x52, 0x07, 0x6d, 0x65, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x22, 0x7e, 0x0a, 0x0d,
+	0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x43, 0x70, 0x75, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1b, 0x0a,
+	0x09, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x52, 0x08, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6d, 0x6f,
+	0x64, 0x65, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
+	0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x63, 0x70, 0x75,
+	0x5f, 0x63, 0x6f, 0x72, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x63, 0x70,
+	0x75, 0x43, 0x6f, 0x72, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18,
+	0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x22, 0x6c, 0x0a, 0x0d,
+	0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x4d, 0x65, 0x6d, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1b, 0x0a,
+	0x09, 0x6d, 0x65, 0x6d, 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04,
+	0x52, 0x08, 0x6d, 0x65, 0x6d, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x12, 0x19, 0x0a, 0x08, 0x6d, 0x65,
+	0x6d, 0x5f, 0x66, 0x72, 0x65, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x6d, 0x65,
+	0x6d, 0x46, 0x72, 0x65, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6d, 0x65, 0x6d, 0x5f, 0x61, 0x76, 0x61,
+	0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0c, 0x6d, 0x65,
+	0x6d, 0x41, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x22, 0xca, 0x02, 0x0a, 0x08, 0x50,
+	0x65, 0x72, 0x66, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72,
+	0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65,
+	0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d,
+	0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x1d, 0x0a,
+	0x0a, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28,
+	0x04, 0x52, 0x09, 0x73, 0x74, 0x61, 0x72, 0x74, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x1b, 0x0a, 0x09,
+	0x72, 0x65, 0x61, 0x6c, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52,
+	0x08, 0x72, 0x65, 0x61, 0x6c, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0a, 0x6d, 0x65, 0x6d,
+	0x6f, 0x72, 0x79, 0x5f, 0x75, 0x73, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x42, 0x02, 0x18,
+	0x01, 0x52, 0x09, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x55, 0x73, 0x65, 0x12, 0x60, 0x0a, 0x17,
+	0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72,
+	0x63, 0x65, 0x5f, 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 0x2e,
+	0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72,
+	0x69, 0x63, 0x73, 0x2e, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x6f, 0x75,
+	0x72, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x15, 0x70, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73,
+	0x65, 0x73, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x22,
+	0x0a, 0x0d, 0x6e, 0x6f, 0x6e, 0x5f, 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x65, 0x78, 0x69, 0x74, 0x18,
+	0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6e, 0x6f, 0x6e, 0x5a, 0x65, 0x72, 0x6f, 0x45, 0x78,
+	0x69, 0x74, 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73,
+	0x61, 0x67, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72,
+	0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x61, 0x0a, 0x0c, 0x50, 0x65, 0x72, 0x66, 0x43,
+	0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18,
+	0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x3d, 0x0a, 0x06, 0x67,
+	0x72, 0x6f, 0x75, 0x70, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x73, 0x6f,
+	0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
+	0x73, 0x2e, 0x50, 0x65, 0x72, 0x66, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x47, 0x72, 0x6f,
+	0x75, 0x70, 0x52, 0x06, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x73, 0x22, 0x64, 0x0a, 0x10, 0x50, 0x65,
+	0x72, 0x66, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x12,
+	0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
+	0x6d, 0x65, 0x12, 0x3c, 0x0a, 0x08, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x18, 0x02,
+	0x20, 0x03, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69,
+	0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x50, 0x65, 0x72, 0x66, 0x43,
+	0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x52, 0x08, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73,
+	0x22, 0x37, 0x0a, 0x0b, 0x50, 0x65, 0x72, 0x66, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x12,
+	0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e,
+	0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01,
+	0x28, 0x03, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0xb9, 0x03, 0x0a, 0x13, 0x50, 0x72,
+	0x6f, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, 0x6e, 0x66,
+	0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
+	0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x74, 0x69,
+	0x6d, 0x65, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52,
+	0x0e, 0x75, 0x73, 0x65, 0x72, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x12,
+	0x2c, 0x0a, 0x12, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d,
+	0x69, 0x63, 0x72, 0x6f, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x10, 0x73, 0x79, 0x73,
+	0x74, 0x65, 0x6d, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x12, 0x1c, 0x0a,
+	0x0a, 0x6d, 0x61, 0x78, 0x5f, 0x72, 0x73, 0x73, 0x5f, 0x6b, 0x62, 0x18, 0x04, 0x20, 0x01, 0x28,
+	0x04, 0x52, 0x08, 0x6d, 0x61, 0x78, 0x52, 0x73, 0x73, 0x4b, 0x62, 0x12, 0x2a, 0x0a, 0x11, 0x6d,
+	0x69, 0x6e, 0x6f, 0x72, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73,
+	0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x50, 0x61, 0x67,
+	0x65, 0x46, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x6d, 0x61, 0x6a, 0x6f, 0x72,
+	0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x06, 0x20, 0x01,
+	0x28, 0x04, 0x52, 0x0f, 0x6d, 0x61, 0x6a, 0x6f, 0x72, 0x50, 0x61, 0x67, 0x65, 0x46, 0x61, 0x75,
+	0x6c, 0x74, 0x73, 0x12, 0x1e, 0x0a, 0x0b, 0x69, 0x6f, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f,
+	0x6b, 0x62, 0x18, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x69, 0x6f, 0x49, 0x6e, 0x70, 0x75,
+	0x74, 0x4b, 0x62, 0x12, 0x20, 0x0a, 0x0c, 0x69, 0x6f, 0x5f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74,
+	0x5f, 0x6b, 0x62, 0x18, 0x08, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x69, 0x6f, 0x4f, 0x75, 0x74,
+	0x70, 0x75, 0x74, 0x4b, 0x62, 0x12, 0x3c, 0x0a, 0x1a, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61,
+	0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x73, 0x77, 0x69, 0x74, 0x63,
+	0x68, 0x65, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x04, 0x52, 0x18, 0x76, 0x6f, 0x6c, 0x75, 0x6e,
+	0x74, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x53, 0x77, 0x69, 0x74, 0x63,
+	0x68, 0x65, 0x73, 0x12, 0x40, 0x0a, 0x1c, 0x69, 0x6e, 0x76, 0x6f, 0x6c, 0x75, 0x6e, 0x74, 0x61,
+	0x72, 0x79, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x5f, 0x73, 0x77, 0x69, 0x74, 0x63,
+	0x68, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x1a, 0x69, 0x6e, 0x76, 0x6f, 0x6c,
+	0x75, 0x6e, 0x74, 0x61, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x74, 0x65, 0x78, 0x74, 0x53, 0x77, 0x69,
+	0x74, 0x63, 0x68, 0x65, 0x73, 0x22, 0xe5, 0x01, 0x0a, 0x0e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65,
+	0x54, 0x79, 0x70, 0x65, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x5b, 0x0a, 0x0c, 0x62, 0x75, 0x69, 0x6c,
+	0x64, 0x5f, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2f,
+	0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74,
+	0x72, 0x69, 0x63, 0x73, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x49,
+	0x6e, 0x66, 0x6f, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x3a,
+	0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x52, 0x0b, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x53,
+	0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x1f, 0x0a, 0x0b, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f,
+	0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6d, 0x6f, 0x64, 0x75,
+	0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x24, 0x0a, 0x0e, 0x6e, 0x75, 0x6d, 0x5f, 0x6f, 0x66,
+	0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c,
+	0x6e, 0x75, 0x6d, 0x4f, 0x66, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x22, 0x2f, 0x0a, 0x0b,
+	0x42, 0x75, 0x69, 0x6c, 0x64, 0x53, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x0b, 0x0a, 0x07, 0x55,
+	0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x4f, 0x4f, 0x4e,
+	0x47, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x4d, 0x41, 0x4b, 0x45, 0x10, 0x02, 0x22, 0x6c, 0x0a,
+	0x1a, 0x43, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x72, 0x4a, 0x6f, 0x75,
+	0x72, 0x6e, 0x65, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x6e,
+	0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12,
+	0x3a, 0x0a, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b,
+	0x32, 0x20, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d,
+	0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x42, 0x61,
+	0x73, 0x65, 0x52, 0x07, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x22, 0x62, 0x0a, 0x1b, 0x43,
+	0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x72, 0x4a, 0x6f, 0x75, 0x72, 0x6e,
+	0x65, 0x79, 0x73, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x43, 0x0a, 0x04, 0x63, 0x75,
+	0x6a, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67,
+	0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x43,
+	0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x55, 0x73, 0x65, 0x72, 0x4a, 0x6f, 0x75, 0x72, 0x6e,
+	0x65, 0x79, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x52, 0x04, 0x63, 0x75, 0x6a, 0x73, 0x22,
+	0x94, 0x03, 0x0a, 0x11, 0x53, 0x6f, 0x6f, 0x6e, 0x67, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65,
+	0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73,
+	0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12,
+	0x1a, 0x0a, 0x08, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28,
+	0x0d, 0x52, 0x08, 0x76, 0x61, 0x72, 0x69, 0x61, 0x6e, 0x74, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x74,
+	0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6c, 0x6c,
+	0x6f, 0x63, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x28, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c,
+	0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28,
+	0x04, 0x52, 0x0e, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x41, 0x6c, 0x6c, 0x6f, 0x63, 0x53, 0x69, 0x7a,
+	0x65, 0x12, 0x22, 0x0a, 0x0d, 0x6d, 0x61, 0x78, 0x5f, 0x68, 0x65, 0x61, 0x70, 0x5f, 0x73, 0x69,
+	0x7a, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x6d, 0x61, 0x78, 0x48, 0x65, 0x61,
+	0x70, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x35, 0x0a, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18,
+	0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75,
+	0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x50, 0x65, 0x72, 0x66,
+	0x49, 0x6e, 0x66, 0x6f, 0x52, 0x06, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x50, 0x0a, 0x11,
+	0x6d, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x73, 0x5f, 0x69, 0x6e, 0x66,
+	0x6f, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f,
+	0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x4d, 0x69,
+	0x78, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0f, 0x6d,
+	0x69, 0x78, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x46,
+	0x0a, 0x0d, 0x70, 0x65, 0x72, 0x66, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x18,
+	0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75,
+	0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x50, 0x65, 0x72, 0x66,
+	0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x52, 0x0c, 0x70, 0x65, 0x72, 0x66, 0x43, 0x6f,
+	0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x22, 0xdb, 0x01, 0x0a, 0x10, 0x45, 0x78, 0x70, 0x43, 0x6f,
+	0x6e, 0x66, 0x69, 0x67, 0x46, 0x65, 0x74, 0x63, 0x68, 0x65, 0x72, 0x12, 0x4a, 0x0a, 0x06, 0x73,
+	0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x32, 0x2e, 0x73, 0x6f,
+	0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
+	0x73, 0x2e, 0x45, 0x78, 0x70, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x46, 0x65, 0x74, 0x63, 0x68,
+	0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52,
+	0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e,
+	0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x66, 0x69, 0x6c, 0x65, 0x6e,
+	0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x18, 0x03, 0x20,
+	0x01, 0x28, 0x04, 0x52, 0x06, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x22, 0x47, 0x0a, 0x0c, 0x43,
+	0x6f, 0x6e, 0x66, 0x69, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0d, 0x0a, 0x09, 0x4e,
+	0x4f, 0x5f, 0x43, 0x4f, 0x4e, 0x46, 0x49, 0x47, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x43, 0x4f,
+	0x4e, 0x46, 0x49, 0x47, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10,
+	0x02, 0x12, 0x11, 0x0a, 0x0d, 0x4d, 0x49, 0x53, 0x53, 0x49, 0x4e, 0x47, 0x5f, 0x47, 0x43, 0x45,
+	0x52, 0x54, 0x10, 0x03, 0x22, 0x91, 0x01, 0x0a, 0x0f, 0x4d, 0x69, 0x78, 0x65, 0x64, 0x42, 0x75,
+	0x69, 0x6c, 0x64, 0x73, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x3d, 0x0a, 0x1b, 0x6d, 0x69, 0x78, 0x65,
+	0x64, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5f,
+	0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x18, 0x6d,
+	0x69, 0x78, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64,
+	0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x3f, 0x0a, 0x1c, 0x6d, 0x69, 0x78, 0x65, 0x64,
+	0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5f,
+	0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x19, 0x6d,
+	0x69, 0x78, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65,
+	0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x22, 0x8a, 0x02, 0x0a, 0x10, 0x43, 0x72, 0x69,
+	0x74, 0x69, 0x63, 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x2e, 0x0a,
+	0x13, 0x65, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x69,
+	0x63, 0x72, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x65, 0x6c, 0x61, 0x70,
+	0x73, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x12, 0x39, 0x0a,
+	0x19, 0x63, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x74,
+	0x69, 0x6d, 0x65, 0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04,
+	0x52, 0x16, 0x63, 0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x54, 0x69,
+	0x6d, 0x65, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x12, 0x41, 0x0a, 0x0d, 0x63, 0x72, 0x69, 0x74,
+	0x69, 0x63, 0x61, 0x6c, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32,
+	0x1c, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65,
+	0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x4a, 0x6f, 0x62, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0c, 0x63,
+	0x72, 0x69, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x50, 0x61, 0x74, 0x68, 0x12, 0x48, 0x0a, 0x11, 0x6c,
+	0x6f, 0x6e, 0x67, 0x5f, 0x72, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x6a, 0x6f, 0x62, 0x73,
+	0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62,
+	0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x4a, 0x6f, 0x62,
+	0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0f, 0x6c, 0x6f, 0x6e, 0x67, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e,
+	0x67, 0x4a, 0x6f, 0x62, 0x73, 0x22, 0x62, 0x0a, 0x07, 0x4a, 0x6f, 0x62, 0x49, 0x6e, 0x66, 0x6f,
+	0x12, 0x2e, 0x0a, 0x13, 0x65, 0x6c, 0x61, 0x70, 0x73, 0x65, 0x64, 0x5f, 0x74, 0x69, 0x6d, 0x65,
+	0x5f, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x11, 0x65,
+	0x6c, 0x61, 0x70, 0x73, 0x65, 0x64, 0x54, 0x69, 0x6d, 0x65, 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73,
+	0x12, 0x27, 0x0a, 0x0f, 0x6a, 0x6f, 0x62, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
+	0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x6a, 0x6f, 0x62, 0x44, 0x65,
+	0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0xb9, 0x05, 0x0a, 0x15, 0x4f, 0x70,
+	0x74, 0x69, 0x6d, 0x69, 0x7a, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x72,
+	0x69, 0x63, 0x73, 0x12, 0x42, 0x0a, 0x0d, 0x61, 0x6e, 0x61, 0x6c, 0x79, 0x73, 0x69, 0x73, 0x5f,
+	0x70, 0x65, 0x72, 0x66, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x73, 0x6f, 0x6f,
+	0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
+	0x2e, 0x50, 0x65, 0x72, 0x66, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0c, 0x61, 0x6e, 0x61, 0x6c, 0x79,
+	0x73, 0x69, 0x73, 0x50, 0x65, 0x72, 0x66, 0x12, 0x44, 0x0a, 0x0e, 0x70, 0x61, 0x63, 0x6b, 0x61,
+	0x67, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x65, 0x72, 0x66, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32,
+	0x1d, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65,
+	0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x50, 0x65, 0x72, 0x66, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0d,
+	0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x69, 0x6e, 0x67, 0x50, 0x65, 0x72, 0x66, 0x12, 0x68, 0x0a,
+	0x0d, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x18, 0x03,
+	0x20, 0x03, 0x28, 0x0b, 0x32, 0x43, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69,
+	0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6d,
+	0x69, 0x7a, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73,
+	0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x61, 0x74,
+	0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x0c, 0x74, 0x61, 0x72, 0x67, 0x65,
+	0x74, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x1a, 0xab, 0x03, 0x0a, 0x18, 0x54, 0x61, 0x72, 0x67,
+	0x65, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65,
+	0x73, 0x75, 0x6c, 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, 0x6f, 0x70, 0x74, 0x69,
+	0x6d, 0x69, 0x7a, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6f, 0x70, 0x74,
+	0x69, 0x6d, 0x69, 0x7a, 0x65, 0x64, 0x12, 0x35, 0x0a, 0x16, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x69,
+	0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x65,
+	0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x15, 0x6f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x61,
+	0x74, 0x69, 0x6f, 0x6e, 0x52, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x65, 0x12, 0x44, 0x0a,
+	0x0e, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x65, 0x72, 0x66, 0x18,
+	0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75,
+	0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x50, 0x65, 0x72, 0x66,
+	0x49, 0x6e, 0x66, 0x6f, 0x52, 0x0d, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x69, 0x6e, 0x67, 0x50,
+	0x65, 0x72, 0x66, 0x12, 0x7b, 0x0a, 0x0f, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x5f, 0x61, 0x72,
+	0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x52, 0x2e, 0x73,
+	0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69,
+	0x63, 0x73, 0x2e, 0x4f, 0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x65, 0x64, 0x42, 0x75, 0x69, 0x6c,
+	0x64, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x4f,
+	0x70, 0x74, 0x69, 0x6d, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x75, 0x6c,
+	0x74, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74,
+	0x52, 0x0e, 0x6f, 0x75, 0x74, 0x70, 0x75, 0x74, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74,
+	0x1a, 0x63, 0x0a, 0x0e, 0x4f, 0x75, 0x74, 0x70, 0x75, 0x74, 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, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x69, 0x6e,
+	0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x03,
+	0x20, 0x03, 0x28, 0x09, 0x52, 0x0f, 0x69, 0x6e, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x64, 0x4d, 0x6f,
+	0x64, 0x75, 0x6c, 0x65, 0x73, 0x22, 0x83, 0x01, 0x0a, 0x10, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74,
+	0x69, 0x6f, 0x6e, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f,
+	0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x61, 0x72, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09,
+	0x52, 0x0b, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x41, 0x72, 0x67, 0x73, 0x12, 0x4c, 0x0a,
+	0x0d, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x18, 0x02,
+	0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x5f, 0x62, 0x75, 0x69,
+	0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e, 0x41, 0x67, 0x67, 0x72, 0x65,
+	0x67, 0x61, 0x74, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x0c, 0x63,
+	0x68, 0x61, 0x6e, 0x67, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x73, 0x22, 0xc9, 0x01, 0x0a, 0x12,
+	0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x64, 0x46, 0x69, 0x6c, 0x65, 0x4c, 0x69,
+	0x73, 0x74, 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, 0x18, 0x0a, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28,
+	0x09, 0x52, 0x07, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x65,
+	0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x64,
+	0x65, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1f, 0x0a, 0x0b, 0x74, 0x6f, 0x74, 0x61,
+	0x6c, 0x5f, 0x64, 0x65, 0x6c, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0a, 0x74,
+	0x6f, 0x74, 0x61, 0x6c, 0x44, 0x65, 0x6c, 0x74, 0x61, 0x12, 0x36, 0x0a, 0x06, 0x63, 0x6f, 0x75,
+	0x6e, 0x74, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x73, 0x6f, 0x6f, 0x6e,
+	0x67, 0x5f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x2e,
+	0x46, 0x69, 0x6c, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x06, 0x63, 0x6f, 0x75, 0x6e, 0x74,
+	0x73, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x22, 0x8b, 0x01, 0x0a, 0x09, 0x46, 0x69, 0x6c, 0x65,
+	0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69,
+	0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73,
+	0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73,
+	0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x61, 0x64, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e,
+	0x73, 0x12, 0x24, 0x0a, 0x0d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f,
+	0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69,
+	0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x64, 0x65, 0x6c, 0x65, 0x74,
+	0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x64, 0x65, 0x6c, 0x65,
+	0x74, 0x69, 0x6f, 0x6e, 0x73, 0x42, 0x28, 0x5a, 0x26, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64,
+	0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x75, 0x69, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63,
+	0x73, 0x2f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
 }
 
 var (
@@ -2198,64 +3006,84 @@
 }
 
 var file_metrics_proto_enumTypes = make([]protoimpl.EnumInfo, 5)
-var file_metrics_proto_msgTypes = make([]protoimpl.MessageInfo, 16)
+var file_metrics_proto_msgTypes = make([]protoimpl.MessageInfo, 25)
 var file_metrics_proto_goTypes = []interface{}{
-	(MetricsBase_BuildVariant)(0),          // 0: soong_build_metrics.MetricsBase.BuildVariant
-	(MetricsBase_Arch)(0),                  // 1: soong_build_metrics.MetricsBase.Arch
-	(BuildConfig_NinjaWeightListSource)(0), // 2: soong_build_metrics.BuildConfig.NinjaWeightListSource
-	(ModuleTypeInfo_BuildSystem)(0),        // 3: soong_build_metrics.ModuleTypeInfo.BuildSystem
-	(ExpConfigFetcher_ConfigStatus)(0),     // 4: soong_build_metrics.ExpConfigFetcher.ConfigStatus
-	(*MetricsBase)(nil),                    // 5: soong_build_metrics.MetricsBase
-	(*BuildConfig)(nil),                    // 6: soong_build_metrics.BuildConfig
-	(*SystemResourceInfo)(nil),             // 7: soong_build_metrics.SystemResourceInfo
-	(*PerfInfo)(nil),                       // 8: soong_build_metrics.PerfInfo
-	(*PerfCounters)(nil),                   // 9: soong_build_metrics.PerfCounters
-	(*PerfCounterGroup)(nil),               // 10: soong_build_metrics.PerfCounterGroup
-	(*PerfCounter)(nil),                    // 11: soong_build_metrics.PerfCounter
-	(*ProcessResourceInfo)(nil),            // 12: soong_build_metrics.ProcessResourceInfo
-	(*ModuleTypeInfo)(nil),                 // 13: soong_build_metrics.ModuleTypeInfo
-	(*CriticalUserJourneyMetrics)(nil),     // 14: soong_build_metrics.CriticalUserJourneyMetrics
-	(*CriticalUserJourneysMetrics)(nil),    // 15: soong_build_metrics.CriticalUserJourneysMetrics
-	(*SoongBuildMetrics)(nil),              // 16: soong_build_metrics.SoongBuildMetrics
-	(*ExpConfigFetcher)(nil),               // 17: soong_build_metrics.ExpConfigFetcher
-	(*MixedBuildsInfo)(nil),                // 18: soong_build_metrics.MixedBuildsInfo
-	(*CriticalPathInfo)(nil),               // 19: soong_build_metrics.CriticalPathInfo
-	(*JobInfo)(nil),                        // 20: soong_build_metrics.JobInfo
+	(MetricsBase_BuildVariant)(0),                          // 0: soong_build_metrics.MetricsBase.BuildVariant
+	(MetricsBase_Arch)(0),                                  // 1: soong_build_metrics.MetricsBase.Arch
+	(BuildConfig_NinjaWeightListSource)(0),                 // 2: soong_build_metrics.BuildConfig.NinjaWeightListSource
+	(ModuleTypeInfo_BuildSystem)(0),                        // 3: soong_build_metrics.ModuleTypeInfo.BuildSystem
+	(ExpConfigFetcher_ConfigStatus)(0),                     // 4: soong_build_metrics.ExpConfigFetcher.ConfigStatus
+	(*MetricsBase)(nil),                                    // 5: soong_build_metrics.MetricsBase
+	(*BuildConfig)(nil),                                    // 6: soong_build_metrics.BuildConfig
+	(*SoongEnvVars)(nil),                                   // 7: soong_build_metrics.SoongEnvVars
+	(*SystemResourceInfo)(nil),                             // 8: soong_build_metrics.SystemResourceInfo
+	(*SystemCpuInfo)(nil),                                  // 9: soong_build_metrics.SystemCpuInfo
+	(*SystemMemInfo)(nil),                                  // 10: soong_build_metrics.SystemMemInfo
+	(*PerfInfo)(nil),                                       // 11: soong_build_metrics.PerfInfo
+	(*PerfCounters)(nil),                                   // 12: soong_build_metrics.PerfCounters
+	(*PerfCounterGroup)(nil),                               // 13: soong_build_metrics.PerfCounterGroup
+	(*PerfCounter)(nil),                                    // 14: soong_build_metrics.PerfCounter
+	(*ProcessResourceInfo)(nil),                            // 15: soong_build_metrics.ProcessResourceInfo
+	(*ModuleTypeInfo)(nil),                                 // 16: soong_build_metrics.ModuleTypeInfo
+	(*CriticalUserJourneyMetrics)(nil),                     // 17: soong_build_metrics.CriticalUserJourneyMetrics
+	(*CriticalUserJourneysMetrics)(nil),                    // 18: soong_build_metrics.CriticalUserJourneysMetrics
+	(*SoongBuildMetrics)(nil),                              // 19: soong_build_metrics.SoongBuildMetrics
+	(*ExpConfigFetcher)(nil),                               // 20: soong_build_metrics.ExpConfigFetcher
+	(*MixedBuildsInfo)(nil),                                // 21: soong_build_metrics.MixedBuildsInfo
+	(*CriticalPathInfo)(nil),                               // 22: soong_build_metrics.CriticalPathInfo
+	(*JobInfo)(nil),                                        // 23: soong_build_metrics.JobInfo
+	(*OptimizedBuildMetrics)(nil),                          // 24: soong_build_metrics.OptimizedBuildMetrics
+	(*ExecutionMetrics)(nil),                               // 25: soong_build_metrics.ExecutionMetrics
+	(*AggregatedFileList)(nil),                             // 26: soong_build_metrics.AggregatedFileList
+	(*FileCount)(nil),                                      // 27: soong_build_metrics.FileCount
+	(*OptimizedBuildMetrics_TargetOptimizationResult)(nil), // 28: soong_build_metrics.OptimizedBuildMetrics.TargetOptimizationResult
+	(*OptimizedBuildMetrics_TargetOptimizationResult_OutputArtifact)(nil), // 29: soong_build_metrics.OptimizedBuildMetrics.TargetOptimizationResult.OutputArtifact
 }
 var file_metrics_proto_depIdxs = []int32{
 	0,  // 0: soong_build_metrics.MetricsBase.target_build_variant:type_name -> soong_build_metrics.MetricsBase.BuildVariant
 	1,  // 1: soong_build_metrics.MetricsBase.target_arch:type_name -> soong_build_metrics.MetricsBase.Arch
 	1,  // 2: soong_build_metrics.MetricsBase.host_arch:type_name -> soong_build_metrics.MetricsBase.Arch
 	1,  // 3: soong_build_metrics.MetricsBase.host_2nd_arch:type_name -> soong_build_metrics.MetricsBase.Arch
-	8,  // 4: soong_build_metrics.MetricsBase.setup_tools:type_name -> soong_build_metrics.PerfInfo
-	8,  // 5: soong_build_metrics.MetricsBase.kati_runs:type_name -> soong_build_metrics.PerfInfo
-	8,  // 6: soong_build_metrics.MetricsBase.soong_runs:type_name -> soong_build_metrics.PerfInfo
-	8,  // 7: soong_build_metrics.MetricsBase.ninja_runs:type_name -> soong_build_metrics.PerfInfo
-	8,  // 8: soong_build_metrics.MetricsBase.total:type_name -> soong_build_metrics.PerfInfo
-	16, // 9: soong_build_metrics.MetricsBase.soong_build_metrics:type_name -> soong_build_metrics.SoongBuildMetrics
+	11, // 4: soong_build_metrics.MetricsBase.setup_tools:type_name -> soong_build_metrics.PerfInfo
+	11, // 5: soong_build_metrics.MetricsBase.kati_runs:type_name -> soong_build_metrics.PerfInfo
+	11, // 6: soong_build_metrics.MetricsBase.soong_runs:type_name -> soong_build_metrics.PerfInfo
+	11, // 7: soong_build_metrics.MetricsBase.ninja_runs:type_name -> soong_build_metrics.PerfInfo
+	11, // 8: soong_build_metrics.MetricsBase.total:type_name -> soong_build_metrics.PerfInfo
+	19, // 9: soong_build_metrics.MetricsBase.soong_build_metrics:type_name -> soong_build_metrics.SoongBuildMetrics
 	6,  // 10: soong_build_metrics.MetricsBase.build_config:type_name -> soong_build_metrics.BuildConfig
-	7,  // 11: soong_build_metrics.MetricsBase.system_resource_info:type_name -> soong_build_metrics.SystemResourceInfo
-	8,  // 12: soong_build_metrics.MetricsBase.bazel_runs:type_name -> soong_build_metrics.PerfInfo
-	17, // 13: soong_build_metrics.MetricsBase.exp_config_fetcher:type_name -> soong_build_metrics.ExpConfigFetcher
-	19, // 14: soong_build_metrics.MetricsBase.critical_path_info:type_name -> soong_build_metrics.CriticalPathInfo
-	2,  // 15: soong_build_metrics.BuildConfig.ninja_weight_list_source:type_name -> soong_build_metrics.BuildConfig.NinjaWeightListSource
-	12, // 16: soong_build_metrics.PerfInfo.processes_resource_info:type_name -> soong_build_metrics.ProcessResourceInfo
-	10, // 17: soong_build_metrics.PerfCounters.groups:type_name -> soong_build_metrics.PerfCounterGroup
-	11, // 18: soong_build_metrics.PerfCounterGroup.counters:type_name -> soong_build_metrics.PerfCounter
-	3,  // 19: soong_build_metrics.ModuleTypeInfo.build_system:type_name -> soong_build_metrics.ModuleTypeInfo.BuildSystem
-	5,  // 20: soong_build_metrics.CriticalUserJourneyMetrics.metrics:type_name -> soong_build_metrics.MetricsBase
-	14, // 21: soong_build_metrics.CriticalUserJourneysMetrics.cujs:type_name -> soong_build_metrics.CriticalUserJourneyMetrics
-	8,  // 22: soong_build_metrics.SoongBuildMetrics.events:type_name -> soong_build_metrics.PerfInfo
-	18, // 23: soong_build_metrics.SoongBuildMetrics.mixed_builds_info:type_name -> soong_build_metrics.MixedBuildsInfo
-	9,  // 24: soong_build_metrics.SoongBuildMetrics.perf_counters:type_name -> soong_build_metrics.PerfCounters
-	4,  // 25: soong_build_metrics.ExpConfigFetcher.status:type_name -> soong_build_metrics.ExpConfigFetcher.ConfigStatus
-	20, // 26: soong_build_metrics.CriticalPathInfo.critical_path:type_name -> soong_build_metrics.JobInfo
-	20, // 27: soong_build_metrics.CriticalPathInfo.long_running_jobs:type_name -> soong_build_metrics.JobInfo
-	28, // [28:28] is the sub-list for method output_type
-	28, // [28:28] is the sub-list for method input_type
-	28, // [28:28] is the sub-list for extension type_name
-	28, // [28:28] is the sub-list for extension extendee
-	0,  // [0:28] is the sub-list for field type_name
+	8,  // 11: soong_build_metrics.MetricsBase.system_resource_info:type_name -> soong_build_metrics.SystemResourceInfo
+	11, // 12: soong_build_metrics.MetricsBase.bazel_runs:type_name -> soong_build_metrics.PerfInfo
+	20, // 13: soong_build_metrics.MetricsBase.exp_config_fetcher:type_name -> soong_build_metrics.ExpConfigFetcher
+	22, // 14: soong_build_metrics.MetricsBase.critical_path_info:type_name -> soong_build_metrics.CriticalPathInfo
+	24, // 15: soong_build_metrics.MetricsBase.optimized_build_metrics:type_name -> soong_build_metrics.OptimizedBuildMetrics
+	2,  // 16: soong_build_metrics.BuildConfig.ninja_weight_list_source:type_name -> soong_build_metrics.BuildConfig.NinjaWeightListSource
+	7,  // 17: soong_build_metrics.BuildConfig.soong_env_vars:type_name -> soong_build_metrics.SoongEnvVars
+	9,  // 18: soong_build_metrics.SystemResourceInfo.cpu_info:type_name -> soong_build_metrics.SystemCpuInfo
+	10, // 19: soong_build_metrics.SystemResourceInfo.mem_info:type_name -> soong_build_metrics.SystemMemInfo
+	15, // 20: soong_build_metrics.PerfInfo.processes_resource_info:type_name -> soong_build_metrics.ProcessResourceInfo
+	13, // 21: soong_build_metrics.PerfCounters.groups:type_name -> soong_build_metrics.PerfCounterGroup
+	14, // 22: soong_build_metrics.PerfCounterGroup.counters:type_name -> soong_build_metrics.PerfCounter
+	3,  // 23: soong_build_metrics.ModuleTypeInfo.build_system:type_name -> soong_build_metrics.ModuleTypeInfo.BuildSystem
+	5,  // 24: soong_build_metrics.CriticalUserJourneyMetrics.metrics:type_name -> soong_build_metrics.MetricsBase
+	17, // 25: soong_build_metrics.CriticalUserJourneysMetrics.cujs:type_name -> soong_build_metrics.CriticalUserJourneyMetrics
+	11, // 26: soong_build_metrics.SoongBuildMetrics.events:type_name -> soong_build_metrics.PerfInfo
+	21, // 27: soong_build_metrics.SoongBuildMetrics.mixed_builds_info:type_name -> soong_build_metrics.MixedBuildsInfo
+	12, // 28: soong_build_metrics.SoongBuildMetrics.perf_counters:type_name -> soong_build_metrics.PerfCounters
+	4,  // 29: soong_build_metrics.ExpConfigFetcher.status:type_name -> soong_build_metrics.ExpConfigFetcher.ConfigStatus
+	23, // 30: soong_build_metrics.CriticalPathInfo.critical_path:type_name -> soong_build_metrics.JobInfo
+	23, // 31: soong_build_metrics.CriticalPathInfo.long_running_jobs:type_name -> soong_build_metrics.JobInfo
+	11, // 32: soong_build_metrics.OptimizedBuildMetrics.analysis_perf:type_name -> soong_build_metrics.PerfInfo
+	11, // 33: soong_build_metrics.OptimizedBuildMetrics.packaging_perf:type_name -> soong_build_metrics.PerfInfo
+	28, // 34: soong_build_metrics.OptimizedBuildMetrics.target_result:type_name -> soong_build_metrics.OptimizedBuildMetrics.TargetOptimizationResult
+	26, // 35: soong_build_metrics.ExecutionMetrics.changed_files:type_name -> soong_build_metrics.AggregatedFileList
+	27, // 36: soong_build_metrics.AggregatedFileList.counts:type_name -> soong_build_metrics.FileCount
+	11, // 37: soong_build_metrics.OptimizedBuildMetrics.TargetOptimizationResult.packaging_perf:type_name -> soong_build_metrics.PerfInfo
+	29, // 38: soong_build_metrics.OptimizedBuildMetrics.TargetOptimizationResult.output_artifact:type_name -> soong_build_metrics.OptimizedBuildMetrics.TargetOptimizationResult.OutputArtifact
+	39, // [39:39] is the sub-list for method output_type
+	39, // [39:39] is the sub-list for method input_type
+	39, // [39:39] is the sub-list for extension type_name
+	39, // [39:39] is the sub-list for extension extendee
+	0,  // [0:39] is the sub-list for field type_name
 }
 
 func init() { file_metrics_proto_init() }
@@ -2289,7 +3117,7 @@
 			}
 		}
 		file_metrics_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SystemResourceInfo); i {
+			switch v := v.(*SoongEnvVars); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -2301,7 +3129,7 @@
 			}
 		}
 		file_metrics_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*PerfInfo); i {
+			switch v := v.(*SystemResourceInfo); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -2313,7 +3141,7 @@
 			}
 		}
 		file_metrics_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*PerfCounters); i {
+			switch v := v.(*SystemCpuInfo); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -2325,7 +3153,7 @@
 			}
 		}
 		file_metrics_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*PerfCounterGroup); i {
+			switch v := v.(*SystemMemInfo); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -2337,7 +3165,7 @@
 			}
 		}
 		file_metrics_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*PerfCounter); i {
+			switch v := v.(*PerfInfo); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -2349,7 +3177,7 @@
 			}
 		}
 		file_metrics_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*ProcessResourceInfo); i {
+			switch v := v.(*PerfCounters); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -2361,7 +3189,7 @@
 			}
 		}
 		file_metrics_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*ModuleTypeInfo); i {
+			switch v := v.(*PerfCounterGroup); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -2373,7 +3201,7 @@
 			}
 		}
 		file_metrics_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*CriticalUserJourneyMetrics); i {
+			switch v := v.(*PerfCounter); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -2385,7 +3213,7 @@
 			}
 		}
 		file_metrics_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*CriticalUserJourneysMetrics); i {
+			switch v := v.(*ProcessResourceInfo); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -2397,7 +3225,7 @@
 			}
 		}
 		file_metrics_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*SoongBuildMetrics); i {
+			switch v := v.(*ModuleTypeInfo); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -2409,7 +3237,7 @@
 			}
 		}
 		file_metrics_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*ExpConfigFetcher); i {
+			switch v := v.(*CriticalUserJourneyMetrics); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -2421,7 +3249,7 @@
 			}
 		}
 		file_metrics_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*MixedBuildsInfo); i {
+			switch v := v.(*CriticalUserJourneysMetrics); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -2433,7 +3261,7 @@
 			}
 		}
 		file_metrics_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} {
-			switch v := v.(*CriticalPathInfo); i {
+			switch v := v.(*SoongBuildMetrics); i {
 			case 0:
 				return &v.state
 			case 1:
@@ -2445,6 +3273,42 @@
 			}
 		}
 		file_metrics_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ExpConfigFetcher); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_metrics_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*MixedBuildsInfo); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_metrics_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*CriticalPathInfo); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_metrics_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*JobInfo); i {
 			case 0:
 				return &v.state
@@ -2456,6 +3320,78 @@
 				return nil
 			}
 		}
+		file_metrics_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*OptimizedBuildMetrics); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_metrics_proto_msgTypes[20].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*ExecutionMetrics); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_metrics_proto_msgTypes[21].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*AggregatedFileList); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_metrics_proto_msgTypes[22].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*FileCount); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_metrics_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*OptimizedBuildMetrics_TargetOptimizationResult); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_metrics_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*OptimizedBuildMetrics_TargetOptimizationResult_OutputArtifact); 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{
@@ -2463,7 +3399,7 @@
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_metrics_proto_rawDesc,
 			NumEnums:      5,
-			NumMessages:   16,
+			NumMessages:   25,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
diff --git a/ui/metrics/metrics_proto/metrics.proto b/ui/metrics/metrics_proto/metrics.proto
index 11fcba7..7ff389a 100644
--- a/ui/metrics/metrics_proto/metrics.proto
+++ b/ui/metrics/metrics_proto/metrics.proto
@@ -137,6 +137,12 @@
   // Note that not all changed environment variables result in analysis retriggering.
   // If there was no previous build, this list will be empty.
   repeated string changed_environment_variable = 34;
+
+  // Metrics related to optimized builds.
+  optional OptimizedBuildMetrics optimized_build_metrics = 35;
+
+  // The target release information. e.g., trunk_staging.
+  optional string target_release = 36;
 }
 
 message BuildConfig {
@@ -174,6 +180,21 @@
   // EXTERNAL_FILE - ninja uses an external custom weight list
   // HINT_FROM_SOONG - ninja uses a prioritized module list from Soong
   optional NinjaWeightListSource ninja_weight_list_source = 8 [default = NOT_USED];
+
+  // Values of some build-affecting environment variables.
+  optional SoongEnvVars soong_env_vars = 9;
+
+  // Whether this build uses soong-only (no kati) mode (either via environment variable,
+  // command line flag or product config.
+  optional bool soong_only = 10;
+}
+
+message SoongEnvVars {
+  // SOONG_PARTIAL_COMPILE
+  optional string partial_compile = 1;
+
+  // SOONG_USE_PARTIAL_COMPILE
+  optional string use_partial_compile = 2;
 }
 
 message SystemResourceInfo {
@@ -182,6 +203,37 @@
 
   // The total of available cores for building
   optional int32 available_cpus = 2;
+
+  // Information about the machine's CPU(s).
+  optional SystemCpuInfo cpu_info = 3;
+
+  // Information about the machine's memory.
+  optional SystemMemInfo mem_info = 4;
+}
+
+message SystemCpuInfo {
+  // The vendor id
+  optional string vendor_id = 1;
+
+  // The model name
+  optional string model_name = 2;
+
+  // The number of CPU cores
+  optional int32 cpu_cores = 3;
+
+  // The CPU flags
+  optional string flags = 4;
+}
+
+message SystemMemInfo {
+  // The total system memory
+  optional uint64 mem_total = 1;
+
+  // The free system memory
+  optional uint64 mem_free = 2;
+
+  // The available system memory
+  optional uint64 mem_available = 3;
 }
 
 message PerfInfo {
@@ -385,3 +437,80 @@
   // Description of a job
   optional string job_description = 2;
 }
+
+message OptimizedBuildMetrics {
+  // The total time spent analyzing what/how to optimize everything.
+  optional PerfInfo analysis_perf = 1;
+  // The total time spent packaging artifacts.
+  optional PerfInfo packaging_perf = 2;
+  // Information for a single target (e.g. general-tests).
+  repeated TargetOptimizationResult target_result = 3;
+
+  message TargetOptimizationResult {
+    // Target name (e.g. general-tests).
+    optional string name = 1;
+    // Whether or not this target was optimized.
+    optional bool optimized = 2;
+    // Reasoning for why the target wasn't optimized if it wasn't
+    optional string optimization_rationale = 3;
+    // Time spent packaging this specific target (if it was optimized).
+    optional PerfInfo packaging_perf = 4;
+    // Information for each different artifact produced by this target (if it
+    // was optimized).
+    repeated OutputArtifact output_artifact = 5;
+
+    message OutputArtifact {
+      // Artifact file name (e.g. general-tests.zip)
+      optional string name = 1;
+      // Size of the file.
+      optional int64 size = 2;
+      // Lists of modules packaged into this artifact.
+      repeated string included_modules = 3;
+    }
+  }
+}
+
+// This is created by soong_ui from SoongExexcutionMetrics files.
+message ExecutionMetrics {
+  // The arguments provided on the command line.
+  repeated string command_args = 1;
+
+  // Changed files detected by the build.
+  optional AggregatedFileList changed_files = 2;
+}
+
+// This is created by soong_ui from the various
+// android.find_input_delta_proto.FileList metrics provided to it by
+// find_input_delta.
+message AggregatedFileList {
+  // The (possibly truncated list of) added files.
+  repeated string additions = 2;
+
+  // The (possibly truncated list of) changed files.
+  repeated string changes = 3;
+
+  // The (possibly truncated list of) deleted files.
+  repeated string deletions = 4;
+
+  // Count of files added/changed/deleted.
+  optional uint32 total_delta = 5;
+
+  // Counts by extension.
+  repeated FileCount counts = 6;
+
+  reserved 1;
+}
+
+message FileCount {
+  // The file extension
+  optional string extension = 1;
+
+  // Number of added files with this extension.
+  optional uint32 additions = 2;
+
+  // Number of modified files with this extension.
+  optional uint32 modifications = 3;
+
+  // Number of deleted files with this extension.
+  optional uint32 deletions = 4;
+}
diff --git a/ui/metrics/proc/status_linux_test.go b/ui/metrics/proc/status_linux_test.go
index 6709850..0edc400 100644
--- a/ui/metrics/proc/status_linux_test.go
+++ b/ui/metrics/proc/status_linux_test.go
@@ -1,7 +1,6 @@
 package proc
 
 import (
-	"fmt"
 	"path/filepath"
 	"reflect"
 	"strconv"
@@ -29,7 +28,6 @@
 		t.Fatalf("got %v, want nil for error", err)
 	}
 
-	fmt.Printf("%d %d\b", status.VmPeak, expectedStatus.VmPeak)
 	if !reflect.DeepEqual(status, expectedStatus) {
 		t.Errorf("got %v, expecting %v for ProcStatus", status, expectedStatus)
 	}
diff --git a/ui/status/log.go b/ui/status/log.go
index 14df346..7bfd396 100644
--- a/ui/status/log.go
+++ b/ui/status/log.go
@@ -22,6 +22,8 @@
 	"io/ioutil"
 	"os"
 	"strings"
+	"sync"
+	"time"
 
 	"google.golang.org/protobuf/proto"
 
@@ -31,7 +33,10 @@
 )
 
 type verboseLog struct {
-	w io.WriteCloser
+	w    *gzip.Writer
+	lock *sync.Mutex
+	data chan []string
+	stop chan bool
 }
 
 func NewVerboseLog(log logger.Logger, filename string) StatusOutput {
@@ -47,9 +52,42 @@
 
 	w := gzip.NewWriter(f)
 
-	return &verboseLog{
-		w: w,
+	l := &verboseLog{
+		w:    w,
+		lock: &sync.Mutex{},
+		data: make(chan []string),
+		stop: make(chan bool),
 	}
+	l.startWriter()
+	return l
+}
+
+func (v *verboseLog) startWriter() {
+	go func() {
+		tick := time.Tick(time.Second)
+		for {
+			select {
+			case <-v.stop:
+				close(v.data)
+				v.w.Close()
+				return
+			case <-tick:
+				v.w.Flush()
+			case dataList := <-v.data:
+				for _, data := range dataList {
+					fmt.Fprint(v.w, data)
+				}
+			}
+		}
+	}()
+}
+
+func (v *verboseLog) stopWriter() {
+	v.stop <- true
+}
+
+func (v *verboseLog) queueWrite(s ...string) {
+	v.data <- s
 }
 
 func (v *verboseLog) StartAction(action *Action, counts Counts) {}
@@ -60,27 +98,27 @@
 		cmd = result.Description
 	}
 
-	fmt.Fprintf(v.w, "[%d/%d] %s\n", counts.FinishedActions, counts.TotalActions, cmd)
+	v.queueWrite(fmt.Sprintf("[%d/%d] ", counts.FinishedActions, counts.TotalActions), cmd, "\n")
 
 	if result.Error != nil {
-		fmt.Fprintf(v.w, "FAILED: %s\n", strings.Join(result.Outputs, " "))
+		v.queueWrite("FAILED: ", strings.Join(result.Outputs, " "), "\n")
 	}
 
 	if result.Output != "" {
-		fmt.Fprintln(v.w, result.Output)
+		v.queueWrite(result.Output, "\n")
 	}
 }
 
 func (v *verboseLog) Flush() {
-	v.w.Close()
+	v.stopWriter()
 }
 
 func (v *verboseLog) Message(level MsgLevel, message string) {
-	fmt.Fprintf(v.w, "%s%s\n", level.Prefix(), message)
+	v.queueWrite(level.Prefix(), message, "\n")
 }
 
 func (v *verboseLog) Write(p []byte) (int, error) {
-	fmt.Fprint(v.w, string(p))
+	v.queueWrite(string(p))
 	return len(p), nil
 }
 
diff --git a/vnames.json b/vnames.json
index 096260f..9e006bb 100644
--- a/vnames.json
+++ b/vnames.json
@@ -1,5 +1,17 @@
 [
   {
+    "pattern": "out/(.*)/srcjars.xref/frameworks/base/services/core/(.*)/android/server/am/(.*)",
+    "vname": {
+      "path": "frameworks/base/services/core/@2@/android/server/am/@3@"
+    }
+  },
+  {
+    "pattern": "out/(.*)/srcjars.xref/frameworks/base/services/core/(.*)/android/server/wm/(.*)",
+    "vname": {
+      "path": "frameworks/base/services/core/@2@/android/server/wm/@3@"
+    }
+  },
+  {
     "pattern": "out/(.*)",
     "vname": {
       "root": "out",
diff --git a/xml/xml_test.go b/xml/xml_test.go
index a59a293..212b0c5 100644
--- a/xml/xml_test.go
+++ b/xml/xml_test.go
@@ -71,7 +71,7 @@
 		{rule: "xmllint-minimal", input: "baz.xml"},
 	} {
 		t.Run(tc.schemaType, func(t *testing.T) {
-			rule := result.ModuleForTests(tc.input, "android_arm64_armv8-a").Rule(tc.rule)
+			rule := result.ModuleForTests(t, tc.input, "android_arm64_armv8-a").Rule(tc.rule)
 			android.AssertStringEquals(t, "input", tc.input, rule.Input.String())
 			if tc.schemaType != "" {
 				android.AssertStringEquals(t, "schema", tc.schema, rule.Args[tc.schemaType])
@@ -79,6 +79,6 @@
 		})
 	}
 
-	m := result.ModuleForTests("foo.xml", "android_arm64_armv8-a").Module().(*prebuiltEtcXml)
-	android.AssertPathRelativeToTopEquals(t, "installDir", "out/soong/target/product/test_device/system/etc", m.InstallDirPath())
+	m := result.ModuleForTests(t, "foo.xml", "android_arm64_armv8-a").Module().(*prebuiltEtcXml)
+	android.AssertPathRelativeToTopEquals(t, "installDir", "out/target/product/test_device/system/etc", m.InstallDirPath())
 }
diff --git a/zip/cmd/main.go b/zip/cmd/main.go
index 37537ab..831f6d4 100644
--- a/zip/cmd/main.go
+++ b/zip/cmd/main.go
@@ -164,6 +164,7 @@
 	directories := flags.Bool("d", false, "include directories in zip")
 	compLevel := flags.Int("L", 5, "deflate compression level (0-9)")
 	emulateJar := flags.Bool("jar", false, "modify the resultant .zip to emulate the output of 'jar'")
+	sortEntries := flags.Bool("sort_entries", false, "sort the zip entries")
 	writeIfChanged := flags.Bool("write_if_changed", false, "only update resultant .zip if it has changed")
 	ignoreMissingFiles := flags.Bool("ignore_missing_files", false, "continue if a requested file does not exist")
 	symlinks := flags.Bool("symlinks", true, "store symbolic links in zip instead of following them")
@@ -228,6 +229,7 @@
 		FileArgs:                 fileArgsBuilder.FileArgs(),
 		OutputFilePath:           *out,
 		EmulateJar:               *emulateJar,
+		SortEntries:              *sortEntries,
 		SrcJar:                   *srcJar,
 		AddDirectoryEntriesToZip: *directories,
 		CompressionLevel:         *compLevel,
diff --git a/zip/zip.go b/zip/zip.go
index f91a5f2..e4e9585 100644
--- a/zip/zip.go
+++ b/zip/zip.go
@@ -272,6 +272,7 @@
 	FileArgs                 []FileArg
 	OutputFilePath           string
 	EmulateJar               bool
+	SortEntries              bool
 	SrcJar                   bool
 	AddDirectoryEntriesToZip bool
 	CompressionLevel         int
@@ -394,7 +395,7 @@
 		}
 	}
 
-	return z.write(w, pathMappings, args.ManifestSourcePath, args.EmulateJar, args.SrcJar, args.NumParallelJobs)
+	return z.write(w, pathMappings, args.ManifestSourcePath, args.EmulateJar, args.SortEntries, args.SrcJar, args.NumParallelJobs)
 }
 
 // Zip creates an output zip archive from given sources.
@@ -481,7 +482,8 @@
 	})
 }
 
-func (z *ZipWriter) write(f io.Writer, pathMappings []pathMapping, manifest string, emulateJar, srcJar bool,
+func (z *ZipWriter) write(f io.Writer, pathMappings []pathMapping, manifest string,
+	emulateJar, sortEntries, srcJar bool,
 	parallelJobs int) error {
 
 	z.errors = make(chan error)
@@ -511,12 +513,20 @@
 		return errors.New("must specify --jar when specifying a manifest via -m")
 	}
 
+	if emulateJar && sortEntries {
+		return errors.New("Cannot specify both --jar and --sort_entries (--jar implies sorting with a different algorithm)")
+	}
 	if emulateJar {
 		// manifest may be empty, in which case addManifest will fill in a default
 		pathMappings = append(pathMappings, pathMapping{jar.ManifestFile, manifest, zip.Deflate})
 
 		jarSort(pathMappings)
 	}
+	if sortEntries {
+		sort.SliceStable(pathMappings, func(i int, j int) bool {
+			return pathMappings[i].dest < pathMappings[j].dest
+		})
+	}
 
 	go func() {
 		var err error