Merge "Support symbolized odex preopt output" into main
diff --git a/Android.bp b/Android.bp
index b1db8e9..0d1ff02 100644
--- a/Android.bp
+++ b/Android.bp
@@ -104,6 +104,7 @@
 // Instantiate the dex_bootjars singleton module.
 dex_bootjars {
     name: "dex_bootjars",
+    no_full_install: true,
 }
 
 // Pseudo-test that's run on checkbuilds to ensure that get_clang_version can
@@ -121,17 +122,25 @@
 }
 
 // buildinfo.prop contains common properties for system/build.prop, like ro.build.version.*
+// TODO(b/322090587): merge this to gen_build_prop.py script.
 buildinfo_prop {
     name: "buildinfo.prop",
 
     // not installable because this will be included to system/build.prop
     installable: false,
 
+    product_config: ":product_config",
+
     // Currently, only microdroid can refer to buildinfo.prop
-    visibility: ["//packages/modules/Virtualization/microdroid"],
+    visibility: ["//packages/modules/Virtualization/build/microdroid"],
 }
 
 // container for apex_contributions selected using build flags
 all_apex_contributions {
     name: "all_apex_contributions",
 }
+
+product_config {
+    name: "product_config",
+    visibility: ["//device/google/cuttlefish/system_image"],
+}
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 317f5c4..e8cad7d 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -4,3 +4,4 @@
 
 [Hook Scripts]
 do_not_use_DO_NOT_MERGE = ${REPO_ROOT}/build/soong/scripts/check_do_not_merge.sh ${PREUPLOAD_COMMIT}
+aosp_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "."
diff --git a/README.md b/README.md
index 140822b..ad282a5 100644
--- a/README.md
+++ b/README.md
@@ -594,19 +594,13 @@
 by all of the vendor's other modules using the normal namespace and visibility
 rules.
 
-`soongConfigTraceMutator` enables modules affected by soong config variables to
-write outputs into a hashed directory path. It does this by recording accesses
-to soong config variables on each module, and then accumulating records of each
-module's all dependencies. `m soong_config_trace` builds information about
-hashes to `$OUT_DIR/soong/soong_config_trace.json`.
-
 ## Build logic
 
 The build logic is written in Go using the
-[blueprint](http://godoc.org/github.com/google/blueprint) framework.  Build
-logic receives module definitions parsed into Go structures using reflection
-and produces build rules.  The build rules are collected by blueprint and
-written to a [ninja](http://ninja-build.org) build file.
+[blueprint](https://android.googlesource.com/platform/build/blueprint)
+framework.  Build logic receives module definitions parsed into Go structures
+using reflection and produces build rules.  The build rules are collected by
+blueprint and written to a [ninja](http://ninja-build.org) build file.
 
 ## Environment Variables Config File
 
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 9f386ca..6eabd7c 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -3,5 +3,14 @@
     {
       "path": "packages/modules/SdkExtensions"
     }
+  ],
+
+  "postsubmit": [
+    {
+      "name": "MicrodroidHostTestCases"
+    },
+    {
+      "name": "MicrodroidTestApp"
+    }
   ]
 }
diff --git a/aconfig/aconfig_declarations.go b/aconfig/aconfig_declarations.go
index d29e312..9e3d291 100644
--- a/aconfig/aconfig_declarations.go
+++ b/aconfig/aconfig_declarations.go
@@ -15,7 +15,8 @@
 package aconfig
 
 import (
-	"fmt"
+	"path/filepath"
+	"slices"
 	"strings"
 
 	"android/soong/android"
@@ -23,9 +24,15 @@
 	"github.com/google/blueprint"
 )
 
+type AconfigReleaseConfigValue struct {
+	ReleaseConfig string
+	Values        []string `blueprint:"mutated"`
+}
+
 type DeclarationsModule struct {
 	android.ModuleBase
 	android.DefaultableModuleBase
+	blueprint.IncrementalModule
 
 	// Properties for "aconfig_declarations"
 	properties struct {
@@ -35,8 +42,10 @@
 		// Release config flag package
 		Package string
 
-		// Values from TARGET_RELEASE / RELEASE_ACONFIG_VALUE_SETS
-		Values []string `blueprint:"mutated"`
+		// Values for release configs / RELEASE_ACONFIG_VALUE_SETS
+		// The current release config is `ReleaseConfig: ""`, others
+		// are from RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS.
+		ReleaseConfigValues []AconfigReleaseConfigValue
 
 		// Container(system/vendor/apex) that this module belongs to
 		Container string
@@ -44,8 +53,6 @@
 		// The flags will only be repackaged if this prop is true.
 		Exportable bool
 	}
-
-	intermediatePath android.WritablePath
 }
 
 func DeclarationsFactory() android.Module {
@@ -60,6 +67,10 @@
 
 type implicitValuesTagType struct {
 	blueprint.BaseDependencyTag
+
+	// The release config name for these values.
+	// Empty string for the actual current release config.
+	ReleaseConfig string
 }
 
 var implicitValuesTag = implicitValuesTagType{}
@@ -73,8 +84,9 @@
 	if len(module.properties.Package) == 0 {
 		ctx.PropertyErrorf("package", "missing package property")
 	}
-	// TODO(b/311155208): Add mandatory check for container after all pre-existing
-	// ones are changed.
+	if len(module.properties.Container) == 0 {
+		ctx.PropertyErrorf("container", "missing container property")
+	}
 
 	// Add a dependency on the aconfig_value_sets defined in
 	// RELEASE_ACONFIG_VALUE_SETS, and add any aconfig_values that
@@ -83,17 +95,10 @@
 	if len(valuesFromConfig) > 0 {
 		ctx.AddDependency(ctx.Module(), implicitValuesTag, valuesFromConfig...)
 	}
-}
-
-func (module *DeclarationsModule) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		// The default output of this module is the intermediates format, which is
-		// not installable and in a private format that no other rules can handle
-		// correctly.
-		return []android.Path{module.intermediatePath}, nil
-	default:
-		return nil, fmt.Errorf("unsupported aconfig_declarations module reference tag %q", tag)
+	for rcName, valueSets := range ctx.Config().ReleaseAconfigExtraReleaseConfigsValueSets() {
+		if len(valueSets) > 0 {
+			ctx.AddDependency(ctx.Module(), implicitValuesTagType{ReleaseConfig: rcName}, valueSets...)
+		}
 	}
 }
 
@@ -115,60 +120,115 @@
 	return sb.String()
 }
 
+// Assemble the actual filename.
+// If `rcName` is not empty, then insert "-{rcName}" into the path before the
+// file extension.
+func assembleFileName(rcName, path string) string {
+	if rcName == "" {
+		return path
+	}
+	dir, file := filepath.Split(path)
+	rcName = "-" + rcName
+	ext := filepath.Ext(file)
+	base := file[:len(file)-len(ext)]
+	return dir + base + rcName + ext
+}
+
 func (module *DeclarationsModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	// Get the values that came from the global RELEASE_ACONFIG_VALUE_SETS flag
-	valuesFiles := make([]android.Path, 0)
+	// Determine which release configs we are processing.
+	//
+	// We always process the current release config (empty string).
+	// We may have been told to also create artifacts for some others.
+	configs := append([]string{""}, ctx.Config().ReleaseAconfigExtraReleaseConfigs()...)
+	slices.Sort(configs)
+
+	values := make(map[string][]string)
+	valuesFiles := make(map[string][]android.Path, 0)
+	providerData := android.AconfigReleaseDeclarationsProviderData{}
 	ctx.VisitDirectDeps(func(dep android.Module) {
 		if depData, ok := android.OtherModuleProvider(ctx, dep, valueSetProviderKey); ok {
-			paths, ok := depData.AvailablePackages[module.properties.Package]
-			if ok {
-				valuesFiles = append(valuesFiles, paths...)
-				for _, path := range paths {
-					module.properties.Values = append(module.properties.Values, path.String())
+			depTag := ctx.OtherModuleDependencyTag(dep)
+			for _, config := range configs {
+				tag := implicitValuesTagType{ReleaseConfig: config}
+				if depTag == tag {
+					paths, ok := depData.AvailablePackages[module.properties.Package]
+					if ok {
+						valuesFiles[config] = append(valuesFiles[config], paths...)
+						for _, path := range paths {
+							values[config] = append(values[config], path.String())
+						}
+					}
 				}
 			}
 		}
 	})
+	for _, config := range configs {
+		module.properties.ReleaseConfigValues = append(module.properties.ReleaseConfigValues, AconfigReleaseConfigValue{
+			ReleaseConfig: config,
+			Values:        values[config],
+		})
 
-	// Intermediate format
-	declarationFiles := android.PathsForModuleSrc(ctx, module.properties.Srcs)
-	intermediateCacheFilePath := android.PathForModuleOut(ctx, "intermediate.pb")
-	defaultPermission := ctx.Config().ReleaseAconfigFlagDefaultPermission()
-	inputFiles := make([]android.Path, len(declarationFiles))
-	copy(inputFiles, declarationFiles)
-	inputFiles = append(inputFiles, valuesFiles...)
-	args := map[string]string{
-		"release_version":    ctx.Config().ReleaseVersion(),
-		"package":            module.properties.Package,
-		"declarations":       android.JoinPathsWithPrefix(declarationFiles, "--declarations "),
-		"values":             joinAndPrefix(" --values ", module.properties.Values),
-		"default-permission": optionalVariable(" --default-permission ", defaultPermission),
+		// Intermediate format
+		declarationFiles := android.PathsForModuleSrc(ctx, module.properties.Srcs)
+		intermediateCacheFilePath := android.PathForModuleOut(ctx, assembleFileName(config, "intermediate.pb"))
+		var defaultPermission string
+		defaultPermission = ctx.Config().ReleaseAconfigFlagDefaultPermission()
+		if config != "" {
+			if confPerm, ok := ctx.Config().GetBuildFlag("RELEASE_ACONFIG_FLAG_DEFAULT_PERMISSION_" + config); ok {
+				defaultPermission = confPerm
+			}
+		}
+		inputFiles := make([]android.Path, len(declarationFiles))
+		copy(inputFiles, declarationFiles)
+		inputFiles = append(inputFiles, valuesFiles[config]...)
+		args := map[string]string{
+			"release_version":    ctx.Config().ReleaseVersion(),
+			"package":            module.properties.Package,
+			"declarations":       android.JoinPathsWithPrefix(declarationFiles, "--declarations "),
+			"values":             joinAndPrefix(" --values ", values[config]),
+			"default-permission": optionalVariable(" --default-permission ", defaultPermission),
+		}
+		if len(module.properties.Container) > 0 {
+			args["container"] = "--container " + module.properties.Container
+		}
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        aconfigRule,
+			Output:      intermediateCacheFilePath,
+			Inputs:      inputFiles,
+			Description: "aconfig_declarations",
+			Args:        args,
+		})
+
+		intermediateDumpFilePath := android.PathForModuleOut(ctx, assembleFileName(config, "intermediate.txt"))
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        aconfigTextRule,
+			Output:      intermediateDumpFilePath,
+			Inputs:      android.Paths{intermediateCacheFilePath},
+			Description: "aconfig_text",
+		})
+
+		providerData[config] = android.AconfigDeclarationsProviderData{
+			Package:                     module.properties.Package,
+			Container:                   module.properties.Container,
+			Exportable:                  module.properties.Exportable,
+			IntermediateCacheOutputPath: intermediateCacheFilePath,
+			IntermediateDumpOutputPath:  intermediateDumpFilePath,
+		}
 	}
-	if len(module.properties.Container) > 0 {
-		args["container"] = "--container " + module.properties.Container
-	}
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        aconfigRule,
-		Output:      intermediateCacheFilePath,
-		Inputs:      inputFiles,
-		Description: "aconfig_declarations",
-		Args:        args,
-	})
-
-	intermediateDumpFilePath := android.PathForModuleOut(ctx, "intermediate.txt")
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        aconfigTextRule,
-		Output:      intermediateDumpFilePath,
-		Inputs:      android.Paths{intermediateCacheFilePath},
-		Description: "aconfig_text",
-	})
-
-	android.SetProvider(ctx, android.AconfigDeclarationsProviderKey, android.AconfigDeclarationsProviderData{
-		Package:                     module.properties.Package,
-		Container:                   module.properties.Container,
-		Exportable:                  module.properties.Exportable,
-		IntermediateCacheOutputPath: intermediateCacheFilePath,
-		IntermediateDumpOutputPath:  intermediateDumpFilePath,
-	})
-
+	android.SetProvider(ctx, android.AconfigDeclarationsProviderKey, providerData[""])
+	android.SetProvider(ctx, android.AconfigReleaseDeclarationsProviderKey, providerData)
 }
+
+func (module *DeclarationsModule) BuildActionProviderKeys() []blueprint.AnyProviderKey {
+	return []blueprint.AnyProviderKey{android.AconfigDeclarationsProviderKey}
+}
+
+func (module *DeclarationsModule) PackageContextPath() string {
+	return pkgPath
+}
+
+func (module *DeclarationsModule) CachedRules() []blueprint.Rule {
+	return []blueprint.Rule{aconfigRule, aconfigTextRule}
+}
+
+var _ blueprint.Incremental = &DeclarationsModule{}
diff --git a/aconfig/aconfig_declarations_test.go b/aconfig/aconfig_declarations_test.go
index 5201fed..5483295 100644
--- a/aconfig/aconfig_declarations_test.go
+++ b/aconfig/aconfig_declarations_test.go
@@ -15,6 +15,7 @@
 package aconfig
 
 import (
+	"slices"
 	"strings"
 	"testing"
 
@@ -88,19 +89,141 @@
 	android.AssertStringEquals(t, "rule must contain container", rule.Args["container"], "--container com.android.foo")
 }
 
-func TestAconfigDeclarationsWithoutContainer(t *testing.T) {
-	bp := `
-		aconfig_declarations {
-			name: "module_name",
-			package: "com.example.package",
-			srcs: [
-				"foo.aconfig",
-			],
-		}
-	`
-	result := runTest(t, android.FixtureExpectsNoErrors, bp)
+func TestMandatoryProperties(t *testing.T) {
+	testCases := []struct {
+		name          string
+		expectedError string
+		bp            string
+	}{
+		{
+			name: "Srcs missing from aconfig_declarations",
+			bp: `
+				aconfig_declarations {
+					name: "my_aconfig_declarations_foo",
+					package: "com.example.package",
+					container: "otherapex",
+				}`,
+			expectedError: `missing source files`,
+		},
+		{
+			name: "Package missing from aconfig_declarations",
+			bp: `
+				aconfig_declarations {
+					name: "my_aconfig_declarations_foo",
+					container: "otherapex",
+					srcs: ["foo.aconfig"],
+				}`,
+			expectedError: `missing package property`,
+		},
+		{
+			name: "Container missing from aconfig_declarations",
+			bp: `
+				aconfig_declarations {
+					name: "my_aconfig_declarations_foo",
+					package: "com.example.package",
+					srcs: ["foo.aconfig"],
+				}`,
+			expectedError: `missing container property`,
+		},
+	}
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			errorHandler := android.FixtureExpectsAtLeastOneErrorMatchingPattern(test.expectedError)
+			android.GroupFixturePreparers(PrepareForTestWithAconfigBuildComponents).
+				ExtendWithErrorHandler(errorHandler).
+				RunTestWithBp(t, test.bp)
+		})
+	}
+}
 
-	module := result.ModuleForTests("module_name", "")
-	rule := module.Rule("aconfig")
-	android.AssertIntEquals(t, "rule must not contain container", len(rule.Args["container"]), 0)
+func TestAssembleFileName(t *testing.T) {
+	testCases := []struct {
+		name          string
+		releaseConfig string
+		path          string
+		expectedValue string
+	}{
+		{
+			name:          "active release config",
+			path:          "file.path",
+			expectedValue: "file.path",
+		},
+		{
+			name:          "release config FOO",
+			releaseConfig: "FOO",
+			path:          "file.path",
+			expectedValue: "file-FOO.path",
+		},
+	}
+	for _, test := range testCases {
+		actualValue := assembleFileName(test.releaseConfig, test.path)
+		if actualValue != test.expectedValue {
+			t.Errorf("Expected %q found %q", test.expectedValue, actualValue)
+		}
+	}
+}
+
+func TestGenerateAndroidBuildActions(t *testing.T) {
+	testCases := []struct {
+		name         string
+		buildFlags   map[string]string
+		bp           string
+		errorHandler android.FixtureErrorHandler
+	}{
+		{
+			name: "generate extra",
+			buildFlags: map[string]string{
+				"RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS": "config2",
+				"RELEASE_ACONFIG_VALUE_SETS":            "aconfig_value_set-config1",
+				"RELEASE_ACONFIG_VALUE_SETS_config2":    "aconfig_value_set-config2",
+			},
+			bp: `
+				aconfig_declarations {
+					name: "module_name",
+					package: "com.example.package",
+					container: "com.android.foo",
+					srcs: [
+						"foo.aconfig",
+						"bar.aconfig",
+					],
+				}
+				aconfig_value_set {
+					name: "aconfig_value_set-config1",
+					values: []
+				}
+				aconfig_value_set {
+					name: "aconfig_value_set-config2",
+					values: []
+				}
+			`,
+		},
+	}
+	for _, test := range testCases {
+		fixture := PrepareForTest(t, addBuildFlagsForTest(test.buildFlags))
+		if test.errorHandler != nil {
+			fixture = fixture.ExtendWithErrorHandler(test.errorHandler)
+		}
+		result := fixture.RunTestWithBp(t, test.bp)
+		module := result.ModuleForTests("module_name", "").Module().(*DeclarationsModule)
+		depData, _ := android.SingletonModuleProvider(result, module, android.AconfigReleaseDeclarationsProviderKey)
+		expectedKeys := []string{""}
+		for _, rc := range strings.Split(test.buildFlags["RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS"], " ") {
+			expectedKeys = append(expectedKeys, rc)
+		}
+		slices.Sort(expectedKeys)
+		actualKeys := []string{}
+		for rc := range depData {
+			actualKeys = append(actualKeys, rc)
+		}
+		slices.Sort(actualKeys)
+		android.AssertStringEquals(t, "provider keys", strings.Join(expectedKeys, " "), strings.Join(actualKeys, " "))
+		for _, rc := range actualKeys {
+			if !strings.HasSuffix(depData[rc].IntermediateCacheOutputPath.String(), assembleFileName(rc, "/intermediate.pb")) {
+				t.Errorf("Incorrect intermediates proto path in provider for release config %s: %s", rc, depData[rc].IntermediateCacheOutputPath.String())
+			}
+			if !strings.HasSuffix(depData[rc].IntermediateDumpOutputPath.String(), assembleFileName(rc, "/intermediate.txt")) {
+				t.Errorf("Incorrect intermediates text path in provider for release config %s: %s", rc, depData[rc].IntermediateDumpOutputPath.String())
+			}
+		}
+	}
 }
diff --git a/aconfig/all_aconfig_declarations.go b/aconfig/all_aconfig_declarations.go
index e771d05..0437c26 100644
--- a/aconfig/all_aconfig_declarations.go
+++ b/aconfig/all_aconfig_declarations.go
@@ -17,6 +17,7 @@
 import (
 	"android/soong/android"
 	"fmt"
+	"slices"
 )
 
 // A singleton module that collects all of the aconfig flags declared in the
@@ -27,70 +28,90 @@
 // 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{}
+	return &allAconfigDeclarationsSingleton{releaseMap: make(map[string]allAconfigReleaseDeclarationsSingleton)}
 }
 
-type allAconfigDeclarationsSingleton struct {
+type allAconfigReleaseDeclarationsSingleton struct {
 	intermediateBinaryProtoPath android.OutputPath
 	intermediateTextProtoPath   android.OutputPath
 }
 
+type allAconfigDeclarationsSingleton struct {
+	releaseMap map[string]allAconfigReleaseDeclarationsSingleton
+}
+
+func (this *allAconfigDeclarationsSingleton) sortedConfigNames() []string {
+	var names []string
+	for k := range this.releaseMap {
+		names = append(names, k)
+	}
+	slices.Sort(names)
+	return names
+}
+
 func (this *allAconfigDeclarationsSingleton) GenerateBuildActions(ctx android.SingletonContext) {
-	// Find all of the aconfig_declarations modules
-	var packages = make(map[string]int)
-	var cacheFiles android.Paths
-	ctx.VisitAllModules(func(module android.Module) {
-		decl, ok := android.SingletonModuleProvider(ctx, module, android.AconfigDeclarationsProviderKey)
-		if !ok {
-			return
+	for _, rcName := range append([]string{""}, ctx.Config().ReleaseAconfigExtraReleaseConfigs()...) {
+		// Find all of the aconfig_declarations modules
+		var packages = make(map[string]int)
+		var cacheFiles android.Paths
+		ctx.VisitAllModules(func(module android.Module) {
+			decl, ok := android.SingletonModuleProvider(ctx, module, android.AconfigReleaseDeclarationsProviderKey)
+			if !ok {
+				return
+			}
+			cacheFiles = append(cacheFiles, decl[rcName].IntermediateCacheOutputPath)
+			packages[decl[rcName].Package]++
+		})
+
+		var numOffendingPkg = 0
+		for pkg, cnt := range packages {
+			if cnt > 1 {
+				fmt.Printf("%d aconfig_declarations found for package %s\n", cnt, pkg)
+				numOffendingPkg++
+			}
 		}
-		cacheFiles = append(cacheFiles, decl.IntermediateCacheOutputPath)
-		packages[decl.Package]++
-	})
 
-	var numOffendingPkg = 0
-	for pkg, cnt := range packages {
-		if cnt > 1 {
-			fmt.Printf("%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."))
 		}
+
+		// Generate build action for aconfig (binary proto output)
+		paths := allAconfigReleaseDeclarationsSingleton{
+			intermediateBinaryProtoPath: android.PathForIntermediates(ctx, assembleFileName(rcName, "all_aconfig_declarations.pb")),
+			intermediateTextProtoPath:   android.PathForIntermediates(ctx, assembleFileName(rcName, "all_aconfig_declarations.textproto")),
+		}
+		this.releaseMap[rcName] = paths
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        AllDeclarationsRule,
+			Inputs:      cacheFiles,
+			Output:      this.releaseMap[rcName].intermediateBinaryProtoPath,
+			Description: "all_aconfig_declarations",
+			Args: map[string]string{
+				"cache_files": android.JoinPathsWithPrefix(cacheFiles, "--cache "),
+			},
+		})
+		ctx.Phony("all_aconfig_declarations", this.releaseMap[rcName].intermediateBinaryProtoPath)
+
+		// Generate build action for aconfig (text proto output)
+		ctx.Build(pctx, android.BuildParams{
+			Rule:        AllDeclarationsRuleTextProto,
+			Inputs:      cacheFiles,
+			Output:      this.releaseMap[rcName].intermediateTextProtoPath,
+			Description: "all_aconfig_declarations_textproto",
+			Args: map[string]string{
+				"cache_files": android.JoinPathsWithPrefix(cacheFiles, "--cache "),
+			},
+		})
+		ctx.Phony("all_aconfig_declarations_textproto", this.releaseMap[rcName].intermediateTextProtoPath)
 	}
-
-	if numOffendingPkg > 0 {
-		panic(fmt.Errorf("Only one aconfig_declarations allowed for each package."))
-	}
-
-	// Generate build action for aconfig (binary proto output)
-	this.intermediateBinaryProtoPath = android.PathForIntermediates(ctx, "all_aconfig_declarations.pb")
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        AllDeclarationsRule,
-		Inputs:      cacheFiles,
-		Output:      this.intermediateBinaryProtoPath,
-		Description: "all_aconfig_declarations",
-		Args: map[string]string{
-			"cache_files": android.JoinPathsWithPrefix(cacheFiles, "--cache "),
-		},
-	})
-	ctx.Phony("all_aconfig_declarations", this.intermediateBinaryProtoPath)
-
-	// Generate build action for aconfig (text proto output)
-	this.intermediateTextProtoPath = android.PathForIntermediates(ctx, "all_aconfig_declarations.textproto")
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        AllDeclarationsRuleTextProto,
-		Inputs:      cacheFiles,
-		Output:      this.intermediateTextProtoPath,
-		Description: "all_aconfig_declarations_textproto",
-		Args: map[string]string{
-			"cache_files": android.JoinPathsWithPrefix(cacheFiles, "--cache "),
-		},
-	})
-	ctx.Phony("all_aconfig_declarations_textproto", this.intermediateTextProtoPath)
 }
 
 func (this *allAconfigDeclarationsSingleton) MakeVars(ctx android.MakeVarsContext) {
-	ctx.DistForGoal("droid", this.intermediateBinaryProtoPath)
-	for _, goal := range []string{"docs", "droid", "sdk"} {
-		ctx.DistForGoalWithFilename(goal, this.intermediateBinaryProtoPath, "flags.pb")
-		ctx.DistForGoalWithFilename(goal, this.intermediateTextProtoPath, "flags.textproto")
+	for _, rcName := range this.sortedConfigNames() {
+		ctx.DistForGoal("droid", this.releaseMap[rcName].intermediateBinaryProtoPath)
+		for _, goal := range []string{"docs", "droid", "sdk"} {
+			ctx.DistForGoalWithFilename(goal, this.releaseMap[rcName].intermediateBinaryProtoPath, assembleFileName(rcName, "flags.pb"))
+			ctx.DistForGoalWithFilename(goal, this.releaseMap[rcName].intermediateTextProtoPath, assembleFileName(rcName, "flags.textproto"))
+		}
 	}
 }
diff --git a/aconfig/build_flags/Android.bp b/aconfig/build_flags/Android.bp
new file mode 100644
index 0000000..b3c7339
--- /dev/null
+++ b/aconfig/build_flags/Android.bp
@@ -0,0 +1,24 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+    name: "soong-aconfig-build_flags",
+    pkgPath: "android/soong/aconfig/build_flags",
+    deps: [
+        "blueprint",
+        "blueprint-pathtools",
+        "sbox_proto",
+        "soong",
+        "soong-android",
+    ],
+    srcs: [
+        "all_build_flag_declarations.go",
+        "build_flags.go",
+        "declarations.go",
+        "init.go",
+    ],
+    testSrcs: [
+    ],
+    pluginFor: ["soong_build"],
+}
diff --git a/aconfig/build_flags/all_build_flag_declarations.go b/aconfig/build_flags/all_build_flag_declarations.go
new file mode 100644
index 0000000..282c9dc
--- /dev/null
+++ b/aconfig/build_flags/all_build_flag_declarations.go
@@ -0,0 +1,78 @@
+// 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 build_flags
+
+import (
+	"android/soong/android"
+)
+
+// A singleton module that collects all of the build flags declared in the
+// tree into a single combined file for export to the external flag setting
+// server (inside Google it's Gantry).
+//
+// Note that this is ALL build_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 AllBuildFlagDeclarationsFactory() android.Singleton {
+	return &allBuildFlagDeclarationsSingleton{}
+}
+
+type allBuildFlagDeclarationsSingleton struct {
+	intermediateBinaryProtoPath android.OutputPath
+	intermediateTextProtoPath   android.OutputPath
+}
+
+func (this *allBuildFlagDeclarationsSingleton) GenerateBuildActions(ctx android.SingletonContext) {
+	// Find all of the build_flag_declarations modules
+	var intermediateFiles android.Paths
+	ctx.VisitAllModules(func(module android.Module) {
+		decl, ok := android.SingletonModuleProvider(ctx, module, BuildFlagDeclarationsProviderKey)
+		if !ok {
+			return
+		}
+		intermediateFiles = append(intermediateFiles, decl.IntermediateCacheOutputPath)
+	})
+
+	// Generate build action for build_flag (binary proto output)
+	this.intermediateBinaryProtoPath = android.PathForIntermediates(ctx, "all_build_flag_declarations.pb")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        allDeclarationsRule,
+		Inputs:      intermediateFiles,
+		Output:      this.intermediateBinaryProtoPath,
+		Description: "all_build_flag_declarations",
+		Args: map[string]string{
+			"intermediates": android.JoinPathsWithPrefix(intermediateFiles, "--intermediate "),
+		},
+	})
+	ctx.Phony("all_build_flag_declarations", this.intermediateBinaryProtoPath)
+
+	// Generate build action for build_flag (text proto output)
+	this.intermediateTextProtoPath = android.PathForIntermediates(ctx, "all_build_flag_declarations.textproto")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        allDeclarationsRuleTextProto,
+		Input:       this.intermediateBinaryProtoPath,
+		Output:      this.intermediateTextProtoPath,
+		Description: "all_build_flag_declarations_textproto",
+	})
+	ctx.Phony("all_build_flag_declarations_textproto", this.intermediateTextProtoPath)
+}
+
+func (this *allBuildFlagDeclarationsSingleton) MakeVars(ctx android.MakeVarsContext) {
+	ctx.DistForGoal("droid", this.intermediateBinaryProtoPath)
+	for _, goal := range []string{"docs", "droid", "sdk"} {
+		ctx.DistForGoalWithFilename(goal, this.intermediateBinaryProtoPath, "build_flags/all_flags.pb")
+		ctx.DistForGoalWithFilename(goal, this.intermediateTextProtoPath, "build_flags/all_flags.textproto")
+	}
+}
diff --git a/aconfig/build_flags/build_flags.go b/aconfig/build_flags/build_flags.go
new file mode 100644
index 0000000..e878b5a
--- /dev/null
+++ b/aconfig/build_flags/build_flags.go
@@ -0,0 +1,71 @@
+// 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 build_flags
+
+import (
+	"fmt"
+
+	"android/soong/android"
+)
+
+const (
+	outJsonFileName = "build_flags.json"
+)
+
+func init() {
+	registerBuildFlagsModuleType(android.InitRegistrationContext)
+}
+
+func registerBuildFlagsModuleType(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("build_flags_json", buildFlagsFactory)
+}
+
+type buildFlags struct {
+	android.ModuleBase
+
+	outputPath android.OutputPath
+}
+
+func buildFlagsFactory() android.Module {
+	module := &buildFlags{}
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	return module
+}
+
+func (m *buildFlags) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	// 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
+
+	// The 'release-config' command is called for every build, and generates the
+	// build_flags_<partition>.json file.
+	// Update the output file only if the source file is changed.
+	ctx.Build(pctx, android.BuildParams{
+		Rule:   android.CpIfChanged,
+		Input:  srcPath,
+		Output: m.outputPath,
+	})
+
+	installPath := android.PathForModuleInstall(ctx, "etc")
+	ctx.InstallFile(installPath, outJsonFileName, m.outputPath)
+}
+
+func (m *buildFlags) AndroidMkEntries() []android.AndroidMkEntries {
+	return []android.AndroidMkEntries{android.AndroidMkEntries{
+		Class:      "ETC",
+		OutputFile: android.OptionalPathForPath(m.outputPath),
+	}}
+}
diff --git a/aconfig/build_flags/declarations.go b/aconfig/build_flags/declarations.go
new file mode 100644
index 0000000..e927db2
--- /dev/null
+++ b/aconfig/build_flags/declarations.go
@@ -0,0 +1,103 @@
+// 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 build_flags
+
+import (
+	"strings"
+
+	"android/soong/android"
+
+	"github.com/google/blueprint"
+)
+
+type BuildFlagDeclarationsProviderData struct {
+	IntermediateCacheOutputPath android.WritablePath
+	IntermediateDumpOutputPath  android.WritablePath
+}
+
+var BuildFlagDeclarationsProviderKey = blueprint.NewProvider[BuildFlagDeclarationsProviderData]()
+
+type DeclarationsModule struct {
+	android.ModuleBase
+	android.DefaultableModuleBase
+
+	// Properties for "aconfig_declarations"
+	properties struct {
+		// aconfig files, relative to this Android.bp file
+		Srcs []string `android:"path"`
+	}
+}
+
+func DeclarationsFactory() android.Module {
+	module := &DeclarationsModule{}
+
+	android.InitAndroidModule(module)
+	android.InitDefaultableModule(module)
+	module.AddProperties(&module.properties)
+
+	return module
+}
+
+func joinAndPrefix(prefix string, values []string) string {
+	var sb strings.Builder
+	for _, v := range values {
+		sb.WriteString(prefix)
+		sb.WriteString(v)
+	}
+	return sb.String()
+}
+
+func optionalVariable(prefix string, value string) string {
+	var sb strings.Builder
+	if value != "" {
+		sb.WriteString(prefix)
+		sb.WriteString(value)
+	}
+	return sb.String()
+}
+
+func (module *DeclarationsModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	// Intermediate format
+	declarationFiles := android.PathsForModuleSrc(ctx, module.properties.Srcs)
+	intermediateCacheFilePath := android.PathForModuleOut(ctx, "build_flag_intermediate.pb")
+	inputFiles := make([]android.Path, len(declarationFiles))
+	copy(inputFiles, declarationFiles)
+
+	// TODO(lamont): generate the rc_proto.FlagArtifacts message for the sources.
+	args := map[string]string{
+		"release_version": ctx.Config().ReleaseVersion(),
+		"declarations":    android.JoinPathsWithPrefix(declarationFiles, "--decl "),
+	}
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        buildFlagRule,
+		Output:      intermediateCacheFilePath,
+		Inputs:      inputFiles,
+		Description: "build_flag_declarations",
+		Args:        args,
+	})
+
+	intermediateDumpFilePath := android.PathForModuleOut(ctx, "build_flag_intermediate.textproto")
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        buildFlagTextRule,
+		Output:      intermediateDumpFilePath,
+		Input:       intermediateCacheFilePath,
+		Description: "build_flag_declarations_text",
+	})
+
+	android.SetProvider(ctx, BuildFlagDeclarationsProviderKey, BuildFlagDeclarationsProviderData{
+		IntermediateCacheOutputPath: intermediateCacheFilePath,
+		IntermediateDumpOutputPath:  intermediateDumpFilePath,
+	})
+}
diff --git a/aconfig/build_flags/init.go b/aconfig/build_flags/init.go
new file mode 100644
index 0000000..dc1369c
--- /dev/null
+++ b/aconfig/build_flags/init.go
@@ -0,0 +1,79 @@
+// 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 build_flags
+
+import (
+	"android/soong/android"
+
+	"github.com/google/blueprint"
+)
+
+var (
+	pctx = android.NewPackageContext("android/soong/aconfig/build_flags")
+
+	// For build_flag_declarations: Generate cache file
+	buildFlagRule = pctx.AndroidStaticRule("build-flag-declarations",
+		blueprint.RuleParams{
+			Command: `${buildFlagDeclarations} ` +
+				` ${declarations}` +
+				` --format pb` +
+				` --output ${out}.tmp` +
+				` && ( if cmp -s ${out}.tmp ${out} ; then rm ${out}.tmp ; else mv ${out}.tmp ${out} ; fi )`,
+			CommandDeps: []string{
+				"${buildFlagDeclarations}",
+			},
+			Restat: true,
+		}, "release_version", "declarations")
+
+	buildFlagTextRule = pctx.AndroidStaticRule("build-flag-declarations-text",
+		blueprint.RuleParams{
+			Command: `${buildFlagDeclarations} --format=textproto` +
+				` --intermediate ${in}` +
+				` --format textproto` +
+				` --output ${out}.tmp` +
+				` && ( if cmp -s ${out}.tmp ${out} ; then rm ${out}.tmp ; else mv ${out}.tmp ${out} ; fi )`,
+			CommandDeps: []string{
+				"${buildFlagDeclarations}",
+			},
+			Restat: true,
+		})
+
+	allDeclarationsRule = pctx.AndroidStaticRule("all-build-flag-declarations-dump",
+		blueprint.RuleParams{
+			Command: `${buildFlagDeclarations} ${intermediates} --format pb --output ${out}`,
+			CommandDeps: []string{
+				"${buildFlagDeclarations}",
+			},
+		}, "intermediates")
+
+	allDeclarationsRuleTextProto = pctx.AndroidStaticRule("All_build_flag_declarations_dump_textproto",
+		blueprint.RuleParams{
+			Command: `${buildFlagDeclarations} --intermediate ${in} --format textproto --output ${out}`,
+			CommandDeps: []string{
+				"${buildFlagDeclarations}",
+			},
+		})
+)
+
+func init() {
+	RegisterBuildComponents(android.InitRegistrationContext)
+	pctx.Import("android/soong/android")
+	pctx.HostBinToolVariable("buildFlagDeclarations", "build-flag-declarations")
+}
+
+func RegisterBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("build_flag_declarations", DeclarationsFactory)
+	ctx.RegisterParallelSingletonType("all_build_flag_declarations", AllBuildFlagDeclarationsFactory)
+}
diff --git a/aconfig/codegen/Android.bp b/aconfig/codegen/Android.bp
index 0c78b94..5fac0a8 100644
--- a/aconfig/codegen/Android.bp
+++ b/aconfig/codegen/Android.bp
@@ -12,7 +12,6 @@
         "soong",
         "soong-aconfig",
         "soong-android",
-        "soong-bazel",
         "soong-java",
         "soong-rust",
     ],
diff --git a/aconfig/codegen/aconfig_declarations_group.go b/aconfig/codegen/aconfig_declarations_group.go
index 1c91bee..13daf47 100644
--- a/aconfig/codegen/aconfig_declarations_group.go
+++ b/aconfig/codegen/aconfig_declarations_group.go
@@ -15,7 +15,6 @@
 package codegen
 
 import (
-	"fmt"
 	"maps"
 
 	"android/soong/android"
@@ -40,11 +39,6 @@
 	android.DefaultableModuleBase
 
 	properties AconfigDeclarationsGroupProperties
-
-	aconfigDeclarationNames      []string
-	intermediateCacheOutputPaths android.Paths
-	javaSrcjars                  android.Paths
-	modeInfos                    map[string]android.ModeInfo
 }
 
 type AconfigDeclarationsGroupProperties struct {
@@ -77,63 +71,45 @@
 	ctx.AddDependency(ctx.Module(), rustAconfigLibraryTag, adg.properties.Rust_aconfig_libraries...)
 }
 
-func (adg *AconfigDeclarationsGroup) VisitDeps(ctx android.ModuleContext) {
-	adg.modeInfos = make(map[string]android.ModeInfo)
+func (adg *AconfigDeclarationsGroup) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	modeInfos := make(map[string]android.ModeInfo)
+	var aconfigDeclarationNames []string
+	var intermediateCacheOutputPaths android.Paths
+	var javaSrcjars android.Paths
 	ctx.VisitDirectDeps(func(dep android.Module) {
 		tag := ctx.OtherModuleDependencyTag(dep)
 		if provider, ok := android.OtherModuleProvider(ctx, dep, android.CodegenInfoProvider); ok {
 
 			// aconfig declaration names and cache files are collected for all aconfig library dependencies
-			adg.aconfigDeclarationNames = append(adg.aconfigDeclarationNames, provider.AconfigDeclarations...)
-			adg.intermediateCacheOutputPaths = append(adg.intermediateCacheOutputPaths, provider.IntermediateCacheOutputPaths...)
+			aconfigDeclarationNames = append(aconfigDeclarationNames, provider.AconfigDeclarations...)
+			intermediateCacheOutputPaths = append(intermediateCacheOutputPaths, provider.IntermediateCacheOutputPaths...)
 
 			switch tag {
 			case aconfigDeclarationsGroupTag:
 				// Will retrieve outputs from another language codegen modules when support is added
-				adg.javaSrcjars = append(adg.javaSrcjars, provider.Srcjars...)
-				maps.Copy(adg.modeInfos, provider.ModeInfos)
+				javaSrcjars = append(javaSrcjars, provider.Srcjars...)
+				maps.Copy(modeInfos, provider.ModeInfos)
 			case javaAconfigLibraryTag:
-				adg.javaSrcjars = append(adg.javaSrcjars, provider.Srcjars...)
-				maps.Copy(adg.modeInfos, provider.ModeInfos)
+				javaSrcjars = append(javaSrcjars, provider.Srcjars...)
+				maps.Copy(modeInfos, provider.ModeInfos)
 			case ccAconfigLibraryTag:
-				maps.Copy(adg.modeInfos, provider.ModeInfos)
+				maps.Copy(modeInfos, provider.ModeInfos)
 			case rustAconfigLibraryTag:
-				maps.Copy(adg.modeInfos, provider.ModeInfos)
+				maps.Copy(modeInfos, provider.ModeInfos)
 			}
 		}
 	})
-}
 
-func (adg *AconfigDeclarationsGroup) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	adg.VisitDeps(ctx)
-	adg.aconfigDeclarationNames = android.FirstUniqueStrings(adg.aconfigDeclarationNames)
-	adg.intermediateCacheOutputPaths = android.FirstUniquePaths(adg.intermediateCacheOutputPaths)
+	aconfigDeclarationNames = android.FirstUniqueStrings(aconfigDeclarationNames)
+	intermediateCacheOutputPaths = android.FirstUniquePaths(intermediateCacheOutputPaths)
 
 	android.SetProvider(ctx, android.CodegenInfoProvider, android.CodegenInfo{
-		AconfigDeclarations:          adg.aconfigDeclarationNames,
-		IntermediateCacheOutputPaths: adg.intermediateCacheOutputPaths,
-		Srcjars:                      adg.javaSrcjars,
-		ModeInfos:                    adg.modeInfos,
+		AconfigDeclarations:          aconfigDeclarationNames,
+		IntermediateCacheOutputPaths: intermediateCacheOutputPaths,
+		Srcjars:                      javaSrcjars,
+		ModeInfos:                    modeInfos,
 	})
-}
 
-var _ android.OutputFileProducer = (*AconfigDeclarationsGroup)(nil)
-
-func (adg *AconfigDeclarationsGroup) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		return adg.intermediateCacheOutputPaths, nil
-	case ".srcjars":
-		return adg.javaSrcjars, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %s", tag)
-	}
-}
-
-func (adg *AconfigDeclarationsGroup) Srcjars() android.Paths {
-	return adg.javaSrcjars
-}
-
-func (adg *AconfigDeclarationsGroup) AconfigDeclarations() []string {
-	return adg.aconfigDeclarationNames
+	ctx.SetOutputFiles(intermediateCacheOutputPaths, "")
+	ctx.SetOutputFiles(javaSrcjars, ".srcjars")
 }
diff --git a/aconfig/codegen/aconfig_declarations_group_test.go b/aconfig/codegen/aconfig_declarations_group_test.go
index ec7cea3..c69d21f 100644
--- a/aconfig/codegen/aconfig_declarations_group_test.go
+++ b/aconfig/codegen/aconfig_declarations_group_test.go
@@ -15,9 +15,10 @@
 package codegen
 
 import (
+	"testing"
+
 	"android/soong/android"
 	"android/soong/java"
-	"testing"
 )
 
 func TestAconfigDeclarationsGroup(t *testing.T) {
@@ -28,6 +29,7 @@
 		aconfig_declarations {
 			name: "foo-aconfig",
 			package: "com.example.package",
+			container: "com.android.foo",
 			srcs: ["foo.aconfig"],
 		}
 
@@ -39,6 +41,7 @@
 		aconfig_declarations {
 			name: "bar-aconfig",
 			package: "com.example.package",
+			container: "com.android.foo",
 			srcs: ["foo.aconfig"],
 		}
 
diff --git a/aconfig/codegen/cc_aconfig_library.go b/aconfig/codegen/cc_aconfig_library.go
index d5291fc..ec0a6b6 100644
--- a/aconfig/codegen/cc_aconfig_library.go
+++ b/aconfig/codegen/cc_aconfig_library.go
@@ -22,6 +22,7 @@
 	"github.com/google/blueprint/proptools"
 
 	"fmt"
+	"strconv"
 	"strings"
 )
 
@@ -87,11 +88,13 @@
 	if mode != "force-read-only" {
 		deps.SharedLibs = append(deps.SharedLibs, baseLibDep)
 
-		deps.SharedLibs = append(deps.SharedLibs, libAconfigStorageReadApiCcDep)
-		deps.SharedLibs = append(deps.SharedLibs, libLogDep)
-
-		deps.StaticLibs = append(deps.StaticLibs, libBaseDep)
 	}
+
+	// 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
@@ -153,6 +156,7 @@
 		Args: map[string]string{
 			"gendir": this.generatedDir.String(),
 			"mode":   mode,
+			"debug":  strconv.FormatBool(ctx.Config().ReleaseReadFromNewStorageCc()),
 		},
 	})
 
diff --git a/aconfig/codegen/cc_aconfig_library_test.go b/aconfig/codegen/cc_aconfig_library_test.go
index 2e7fdc2..2f6c1a6 100644
--- a/aconfig/codegen/cc_aconfig_library_test.go
+++ b/aconfig/codegen/cc_aconfig_library_test.go
@@ -50,6 +50,7 @@
 			aconfig_declarations {
 				name: "my_aconfig_declarations",
 				package: "com.example.package",
+				container: "com.android.foo",
 				srcs: ["foo.aconfig"],
 			}
 
@@ -73,11 +74,6 @@
     		srcs: ["libaconfig_storage_read_api_cc.cc"],
 			}
 
-			cc_library {
-    		name: "libaconfig_storage_protos_cc",
-    		srcs: ["libaconfig_storage_protos_cc.cc"],
-			}
-
 			cc_aconfig_library {
 				name: "my_cc_aconfig_library",
 				aconfig_declarations: "my_aconfig_declarations",
@@ -112,6 +108,7 @@
 			aconfig_declarations {
 				name: "my_aconfig_declarations",
 				package: "com.example.package",
+				container: "com.android.foo",
 				srcs: ["foo.aconfig"],
 			}
 
@@ -135,12 +132,6 @@
     		srcs: ["libaconfig_storage_read_api_cc.cc"],
 			}
 
-			cc_library {
-    		name: "libaconfig_storage_protos_cc",
-    		srcs: ["libaconfig_storage_protos_cc.cc"],
-			}
-
-
 			cc_aconfig_library {
 				name: "my_cc_aconfig_library",
 				aconfig_declarations: "my_aconfig_declarations",
@@ -167,6 +158,7 @@
 		aconfig_declarations {
 			name: "my_aconfig_declarations_bar",
 			package: "com.example.package",
+			container: "com.android.foo",
 			srcs: ["bar.aconfig"],
 		}
 
@@ -211,12 +203,6 @@
 			srcs: ["libaconfig_storage_read_api_cc.cc"],
 			vendor_available: true,
 		}
-
-		cc_library {
-			name: "libaconfig_storage_protos_cc",
-			srcs: ["libaconfig_storage_protos_cc.cc"],
-			vendor_available: true,
-		}
 	`
 	result := android.GroupFixturePreparers(
 		PrepareForTestWithAconfigBuildComponents,
@@ -241,6 +227,7 @@
 			aconfig_declarations {
 				name: "my_aconfig_declarations",
 				package: "com.example.package",
+				container: "com.android.foo",
 				srcs: ["foo.aconfig"],
 			}
 
@@ -249,6 +236,22 @@
 				aconfig_declarations: "my_aconfig_declarations",
 				mode: "force-read-only",
 			}
+
+
+			cc_library {
+    		                name: "libbase",
+    		                srcs: ["libbase.cc"],
+			}
+
+			cc_library {
+    		                name: "liblog",
+    		                srcs: ["liblog.cc"],
+			}
+
+			cc_library {
+    		                name: "libaconfig_storage_read_api_cc",
+    		                srcs: ["libaconfig_storage_read_api_cc.cc"],
+			}
 		`))
 
 	module := result.ModuleForTests("my_cc_aconfig_library", "android_arm64_armv8-a_shared").Module()
diff --git a/aconfig/codegen/init.go b/aconfig/codegen/init.go
index 73a8951..6182e14 100644
--- a/aconfig/codegen/init.go
+++ b/aconfig/codegen/init.go
@@ -49,11 +49,12 @@
 				` && ${aconfig} create-cpp-lib` +
 				`    --mode ${mode}` +
 				`    --cache ${in}` +
-				`    --out ${gendir}`,
+				`    --out ${gendir}` +
+				`    --allow-instrumentation ${debug}`,
 			CommandDeps: []string{
 				"$aconfig",
 			},
-		}, "gendir", "mode")
+		}, "gendir", "mode", "debug")
 
 	// For rust_aconfig_library: Generate Rust library
 	rustRule = pctx.AndroidStaticRule("rust_aconfig_library",
diff --git a/aconfig/codegen/java_aconfig_library.go b/aconfig/codegen/java_aconfig_library.go
index 9f42e21..673ac2a 100644
--- a/aconfig/codegen/java_aconfig_library.go
+++ b/aconfig/codegen/java_aconfig_library.go
@@ -15,8 +15,6 @@
 package codegen
 
 import (
-	"fmt"
-
 	"android/soong/android"
 	"android/soong/java"
 
@@ -80,7 +78,7 @@
 	// Get the values that came from the global RELEASE_ACONFIG_VALUE_SETS flag
 	declarationsModules := ctx.GetDirectDepsWithTag(declarationsTag)
 	if len(declarationsModules) != 1 {
-		panic(fmt.Errorf("Exactly one aconfig_declarations property required"))
+		panic("Exactly one aconfig_declarations property required")
 	}
 	declarations, _ := android.OtherModuleProvider(ctx, declarationsModules[0], android.AconfigDeclarationsProviderKey)
 
@@ -133,10 +131,6 @@
 	return srcJarPath, declarations.IntermediateCacheOutputPath
 }
 
-func (callbacks *JavaAconfigDeclarationsLibraryCallbacks) AconfigDeclarations() *string {
-	return proptools.StringPtr(callbacks.properties.Aconfig_declarations)
-}
-
 func isModeSupported(mode string) bool {
 	return android.InList(mode, aconfigSupportedModes)
 }
diff --git a/aconfig/codegen/java_aconfig_library_test.go b/aconfig/codegen/java_aconfig_library_test.go
index de45b5c..87b54a4 100644
--- a/aconfig/codegen/java_aconfig_library_test.go
+++ b/aconfig/codegen/java_aconfig_library_test.go
@@ -35,6 +35,7 @@
 			aconfig_declarations {
 				name: "my_aconfig_declarations_foo",
 				package: "com.example.package.foo",
+				container: "system",
 				srcs: ["foo.aconfig"],
 			}
 
@@ -46,6 +47,7 @@
 			aconfig_declarations {
 				name: "my_aconfig_declarations_bar",
 				package: "com.example.package.bar",
+				container: "system",
 				srcs: ["bar.aconfig"],
 			}
 
@@ -60,7 +62,7 @@
 	entry := android.AndroidMkEntriesForTest(t, result.TestContext, module)[0]
 
 	makeVar := entry.EntryMap["LOCAL_ACONFIG_FILES"]
-	android.EnsureListContainsSuffix(t, makeVar, "android_common/aconfig_merged.pb")
+	android.EnsureListContainsSuffix(t, makeVar, "android_common/system/aconfig_merged.pb")
 }
 
 func TestAndroidMkJavaLibrary(t *testing.T) {
@@ -175,6 +177,7 @@
 			aconfig_declarations {
 				name: "my_aconfig_declarations",
 				package: "com.example.package",
+				container: "com.android.foo",
 				srcs: ["foo.aconfig"],
 				exportable: true,
 			}
@@ -200,6 +203,7 @@
 			aconfig_declarations {
 				name: "my_aconfig_declarations",
 				package: "com.example.package",
+				container: "com.android.foo",
 				srcs: ["foo.aconfig"],
 			}
 
@@ -234,3 +238,52 @@
 func TestUnsupportedMode(t *testing.T) {
 	testCodegenModeWithError(t, "mode: `unsupported`,", "mode: \"unsupported\" is not a supported mode")
 }
+
+func TestMkEntriesMatchedContainer(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithAconfigBuildComponents,
+		java.PrepareForTestWithJavaDefaultModules).
+		ExtendWithErrorHandler(android.FixtureExpectsNoErrors).
+		RunTestWithBp(t, `
+			aconfig_declarations {
+				name: "my_aconfig_declarations_foo",
+				package: "com.example.package.foo",
+				container: "system",
+				srcs: ["foo.aconfig"],
+			}
+
+			java_aconfig_library {
+				name: "my_java_aconfig_library_foo",
+				aconfig_declarations: "my_aconfig_declarations_foo",
+			}
+
+			aconfig_declarations {
+				name: "my_aconfig_declarations_bar",
+				package: "com.example.package.bar",
+				container: "system_ext",
+				srcs: ["bar.aconfig"],
+			}
+
+			java_aconfig_library {
+				name: "my_java_aconfig_library_bar",
+				aconfig_declarations: "my_aconfig_declarations_bar",
+			}
+
+			java_library {
+				name: "my_module",
+				srcs: [
+					"src/foo.java",
+				],
+				static_libs: [
+					"my_java_aconfig_library_foo",
+					"my_java_aconfig_library_bar",
+				],
+				platform_apis: true,
+			}
+		`)
+
+	module := result.ModuleForTests("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_test.go b/aconfig/codegen/rust_aconfig_library_test.go
index fe28f94..523b464 100644
--- a/aconfig/codegen/rust_aconfig_library_test.go
+++ b/aconfig/codegen/rust_aconfig_library_test.go
@@ -46,6 +46,7 @@
 			aconfig_declarations {
 				name: "my_aconfig_declarations",
 				package: "com.example.package",
+				container: "com.android.foo",
 				srcs: ["foo.aconfig"],
 			}
 
@@ -131,6 +132,7 @@
 			aconfig_declarations {
 				name: "my_aconfig_declarations",
 				package: "com.example.package",
+				container: "com.android.foo",
 				srcs: ["foo.aconfig"],
 			}
 			rust_aconfig_library {
@@ -193,6 +195,7 @@
 			aconfig_declarations {
 				name: "my_aconfig_declarations",
 				package: "com.example.package",
+				container: "com.android.foo",
 				srcs: ["foo.aconfig"],
 			}
 			rust_aconfig_library {
diff --git a/aconfig/init.go b/aconfig/init.go
index 4655467..de155ab 100644
--- a/aconfig/init.go
+++ b/aconfig/init.go
@@ -15,13 +15,16 @@
 package aconfig
 
 import (
+	"encoding/gob"
+
 	"android/soong/android"
 
 	"github.com/google/blueprint"
 )
 
 var (
-	pctx = android.NewPackageContext("android/soong/aconfig")
+	pkgPath = "android/soong/aconfig"
+	pctx    = android.NewPackageContext(pkgPath)
 
 	// For aconfig_declarations: Generate cache file
 	aconfigRule = pctx.AndroidStaticRule("aconfig",
@@ -44,7 +47,7 @@
 	// For create-device-config-sysprops: Generate aconfig flag value map text file
 	aconfigTextRule = pctx.AndroidStaticRule("aconfig_text",
 		blueprint.RuleParams{
-			Command: `${aconfig} dump-cache --dedup --format='{fully_qualified_name}={state:bool}'` +
+			Command: `${aconfig} dump-cache --dedup --format='{fully_qualified_name}:{permission}={state:bool}'` +
 				` --cache ${in}` +
 				` --out ${out}.tmp` +
 				` && ( if cmp -s ${out}.tmp ${out} ; then rm ${out}.tmp ; else mv ${out}.tmp ${out} ; fi )`,
@@ -106,6 +109,9 @@
 	RegisterBuildComponents(android.InitRegistrationContext)
 	pctx.HostBinToolVariable("aconfig", "aconfig")
 	pctx.HostBinToolVariable("soong_zip", "soong_zip")
+
+	gob.Register(android.AconfigDeclarationsProviderData{})
+	gob.Register(android.ModuleOutPath{})
 }
 
 func RegisterBuildComponents(ctx android.RegistrationContext) {
diff --git a/aconfig/testing.go b/aconfig/testing.go
index f6489ec..4ceb6b3 100644
--- a/aconfig/testing.go
+++ b/aconfig/testing.go
@@ -23,7 +23,25 @@
 var PrepareForTestWithAconfigBuildComponents = android.FixtureRegisterWithContext(RegisterBuildComponents)
 
 func runTest(t *testing.T, errorHandler android.FixtureErrorHandler, bp string) *android.TestResult {
-	return android.GroupFixturePreparers(PrepareForTestWithAconfigBuildComponents).
+	return PrepareForTest(t).
 		ExtendWithErrorHandler(errorHandler).
 		RunTestWithBp(t, bp)
 }
+
+func PrepareForTest(t *testing.T, preparers ...android.FixturePreparer) android.FixturePreparer {
+	preparers = append([]android.FixturePreparer{PrepareForTestWithAconfigBuildComponents}, preparers...)
+	return android.GroupFixturePreparers(preparers...)
+}
+
+func addBuildFlagsForTest(buildFlags map[string]string) android.FixturePreparer {
+	return android.GroupFixturePreparers(
+		android.FixtureModifyProductVariables(func(vars android.FixtureProductVariables) {
+			if vars.BuildFlags == nil {
+				vars.BuildFlags = make(map[string]string)
+			}
+			for k, v := range buildFlags {
+				vars.BuildFlags[k] = v
+			}
+		}),
+	)
+}
diff --git a/android/Android.bp b/android/Android.bp
index f130d3a..774d24a 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -38,9 +38,13 @@
         "arch_list.go",
         "arch_module_context.go",
         "base_module_context.go",
+        "build_prop.go",
         "buildinfo_prop.go",
+        "compliance_metadata.go",
         "config.go",
+        "container.go",
         "test_config.go",
+        "configurable_properties.go",
         "configured_jars.go",
         "csuite_config.go",
         "deapexer.go",
@@ -60,6 +64,7 @@
         "license_metadata.go",
         "license_sdk_member.go",
         "licenses.go",
+        "logtags.go",
         "makevars.go",
         "metrics.go",
         "module.go",
@@ -81,6 +86,7 @@
         "plugin.go",
         "prebuilt.go",
         "prebuilt_build_tool.go",
+        "product_config.go",
         "proto.go",
         "provider.go",
         "raw_files.go",
@@ -108,6 +114,7 @@
         "androidmk_test.go",
         "apex_test.go",
         "arch_test.go",
+        "blueprint_e2e_test.go",
         "config_test.go",
         "configured_jars_test.go",
         "csuite_config_test.go",
diff --git a/android/aconfig_providers.go b/android/aconfig_providers.go
index 4c1782b..a47e80f 100644
--- a/android/aconfig_providers.go
+++ b/android/aconfig_providers.go
@@ -43,13 +43,9 @@
 
 var AconfigDeclarationsProviderKey = blueprint.NewProvider[AconfigDeclarationsProviderData]()
 
-// This is used to collect the aconfig declarations info on the transitive closure,
-// the data is keyed on the container.
-type AconfigTransitiveDeclarationsInfo struct {
-	AconfigFiles map[string]Paths
-}
+type AconfigReleaseDeclarationsProviderData map[string]AconfigDeclarationsProviderData
 
-var AconfigTransitiveDeclarationsInfoProvider = blueprint.NewProvider[AconfigTransitiveDeclarationsInfo]()
+var AconfigReleaseDeclarationsProviderKey = blueprint.NewProvider[AconfigReleaseDeclarationsProviderData]()
 
 type ModeInfo struct {
 	Container string
@@ -80,54 +76,15 @@
 	}
 }
 
-// CollectDependencyAconfigFiles is used by some module types to provide finer dependency graphing than
-// we can do in ModuleBase.
-func CollectDependencyAconfigFiles(ctx ModuleContext, mergedAconfigFiles *map[string]Paths) {
-	if *mergedAconfigFiles == nil {
-		*mergedAconfigFiles = make(map[string]Paths)
-	}
-	ctx.VisitDirectDepsIgnoreBlueprint(func(module Module) {
-		if dep, _ := OtherModuleProvider(ctx, module, AconfigDeclarationsProviderKey); dep.IntermediateCacheOutputPath != nil {
-			(*mergedAconfigFiles)[dep.Container] = append((*mergedAconfigFiles)[dep.Container], dep.IntermediateCacheOutputPath)
-			return
-		}
-		if dep, ok := OtherModuleProvider(ctx, module, aconfigPropagatingProviderKey); ok {
-			for container, v := range dep.AconfigFiles {
-				(*mergedAconfigFiles)[container] = append((*mergedAconfigFiles)[container], v...)
-			}
-		}
-		// We process these last, so that they determine the final value, eliminating any duplicates that we picked up
-		// from UpdateAndroidBuildActions.
-		if dep, ok := OtherModuleProvider(ctx, module, AconfigTransitiveDeclarationsInfoProvider); ok {
-			for container, v := range dep.AconfigFiles {
-				(*mergedAconfigFiles)[container] = append((*mergedAconfigFiles)[container], v...)
-			}
-		}
-	})
-
-	for _, container := range SortedKeys(*mergedAconfigFiles) {
-		aconfigFiles := (*mergedAconfigFiles)[container]
-		(*mergedAconfigFiles)[container] = mergeAconfigFiles(ctx, container, aconfigFiles, false)
-	}
-
-	SetProvider(ctx, AconfigTransitiveDeclarationsInfoProvider, AconfigTransitiveDeclarationsInfo{
-		AconfigFiles: *mergedAconfigFiles,
-	})
-}
-
-func SetAconfigFileMkEntries(m *ModuleBase, entries *AndroidMkEntries, aconfigFiles map[string]Paths) {
-	setAconfigFileMkEntries(m, entries, aconfigFiles)
-}
-
 type aconfigPropagatingDeclarationsInfo struct {
 	AconfigFiles map[string]Paths
 	ModeInfos    map[string]ModeInfo
 }
 
-var aconfigPropagatingProviderKey = blueprint.NewProvider[aconfigPropagatingDeclarationsInfo]()
+var AconfigPropagatingProviderKey = blueprint.NewProvider[aconfigPropagatingDeclarationsInfo]()
 
 func VerifyAconfigBuildMode(ctx ModuleContext, container string, module blueprint.Module, asError bool) {
-	if dep, ok := OtherModuleProvider(ctx, module, aconfigPropagatingProviderKey); ok {
+	if dep, ok := OtherModuleProvider(ctx, module, AconfigPropagatingProviderKey); ok {
 		for k, v := range dep.ModeInfos {
 			msg := fmt.Sprintf("%s/%s depends on %s/%s/%s across containers\n",
 				module.Name(), container, k, v.Container, v.Mode)
@@ -159,17 +116,14 @@
 		if dep, ok := OtherModuleProvider(ctx, module, AconfigDeclarationsProviderKey); ok {
 			mergedAconfigFiles[dep.Container] = append(mergedAconfigFiles[dep.Container], dep.IntermediateCacheOutputPath)
 		}
-		if dep, ok := OtherModuleProvider(ctx, module, aconfigPropagatingProviderKey); ok {
+		// If we were generating on-device artifacts for other release configs, we would need to add code here to propagate
+		// those artifacts as well.  See also b/298444886.
+		if dep, ok := OtherModuleProvider(ctx, module, AconfigPropagatingProviderKey); ok {
 			for container, v := range dep.AconfigFiles {
 				mergedAconfigFiles[container] = append(mergedAconfigFiles[container], v...)
 			}
 			propagateModeInfos(ctx, module, mergedModeInfos, dep.ModeInfos)
 		}
-		if dep, ok := OtherModuleProvider(ctx, module, AconfigTransitiveDeclarationsInfoProvider); ok {
-			for container, v := range dep.AconfigFiles {
-				mergedAconfigFiles[container] = append(mergedAconfigFiles[container], v...)
-			}
-		}
 	})
 	// We only need to set the provider if we have aconfig files.
 	if len(mergedAconfigFiles) > 0 {
@@ -178,15 +132,16 @@
 			mergedAconfigFiles[container] = mergeAconfigFiles(ctx, container, aconfigFiles, true)
 		}
 
-		SetProvider(ctx, aconfigPropagatingProviderKey, aconfigPropagatingDeclarationsInfo{
+		SetProvider(ctx, AconfigPropagatingProviderKey, aconfigPropagatingDeclarationsInfo{
 			AconfigFiles: mergedAconfigFiles,
 			ModeInfos:    mergedModeInfos,
 		})
+		ctx.Module().base().aconfigFilePaths = getAconfigFilePaths(ctx.Module().base(), mergedAconfigFiles)
 	}
 }
 
 func aconfigUpdateAndroidMkData(ctx fillInEntriesContext, mod Module, data *AndroidMkData) {
-	info, ok := SingletonModuleProvider(ctx, mod, aconfigPropagatingProviderKey)
+	info, ok := SingletonModuleProvider(ctx, mod, AconfigPropagatingProviderKey)
 	// If there is no aconfigPropagatingProvider, or there are no AconfigFiles, then we are done.
 	if !ok || len(info.AconfigFiles) == 0 {
 		return
@@ -217,7 +172,7 @@
 	if len(*entries) == 0 {
 		return
 	}
-	info, ok := SingletonModuleProvider(ctx, mod, aconfigPropagatingProviderKey)
+	info, ok := SingletonModuleProvider(ctx, mod, AconfigPropagatingProviderKey)
 	if !ok || len(info.AconfigFiles) == 0 {
 		return
 	}
@@ -225,7 +180,7 @@
 	for idx, _ := range *entries {
 		(*entries)[idx].ExtraEntries = append((*entries)[idx].ExtraEntries,
 			func(ctx AndroidMkExtraEntriesContext, entries *AndroidMkEntries) {
-				setAconfigFileMkEntries(mod.base(), entries, info.AconfigFiles)
+				entries.AddPaths("LOCAL_ACONFIG_FILES", getAconfigFilePaths(mod.base(), info.AconfigFiles))
 			},
 		)
 
@@ -255,10 +210,6 @@
 	return Paths{output}
 }
 
-func setAconfigFileMkEntries(m *ModuleBase, entries *AndroidMkEntries, aconfigFiles map[string]Paths) {
-	entries.AddPaths("LOCAL_ACONFIG_FILES", getAconfigFilePaths(m, aconfigFiles))
-}
-
 func getAconfigFilePaths(m *ModuleBase, aconfigFiles map[string]Paths) (paths Paths) {
 	// TODO(b/311155208): The default container here should be system.
 	container := "system"
diff --git a/android/androidmk.go b/android/androidmk.go
index 07f7c58..9699ce5 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -36,6 +36,7 @@
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/bootstrap"
 	"github.com/google/blueprint/pathtools"
+	"github.com/google/blueprint/proptools"
 )
 
 func init() {
@@ -498,6 +499,7 @@
 	Config() Config
 	moduleProvider(module blueprint.Module, provider blueprint.AnyProviderKey) (any, bool)
 	ModuleType(module blueprint.Module) string
+	OtherModulePropertyErrorf(module Module, property string, fmt string, args ...interface{})
 }
 
 func (a *AndroidMkEntries) fillInEntries(ctx fillInEntriesContext, mod blueprint.Module) {
@@ -513,7 +515,7 @@
 	if a.Include == "" {
 		a.Include = "$(BUILD_PREBUILT)"
 	}
-	a.Required = append(a.Required, amod.RequiredModuleNames()...)
+	a.Required = append(a.Required, amod.RequiredModuleNames(ctx)...)
 	a.Host_required = append(a.Host_required, amod.HostRequiredModuleNames()...)
 	a.Target_required = append(a.Target_required, amod.TargetRequiredModuleNames()...)
 
@@ -541,6 +543,11 @@
 		a.SetPath("LOCAL_SOONG_INSTALLED_MODULE", base.katiInstalls[len(base.katiInstalls)-1].to)
 		a.SetString("LOCAL_SOONG_INSTALL_PAIRS", base.katiInstalls.BuiltInstalled())
 		a.SetPaths("LOCAL_SOONG_INSTALL_SYMLINKS", base.katiSymlinks.InstallPaths().Paths())
+	} else {
+		// Soong may not have generated the install rule also when `no_full_install: true`.
+		// Mark this module as uninstallable in order to prevent Make from creating an
+		// install rule there.
+		a.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", proptools.Bool(base.commonProperties.No_full_install))
 	}
 
 	if len(base.testData) > 0 {
@@ -849,7 +856,7 @@
 	mod blueprint.Module, provider AndroidMkDataProvider) error {
 
 	amod := mod.(Module).base()
-	if shouldSkipAndroidMkProcessing(amod) {
+	if shouldSkipAndroidMkProcessing(ctx, amod) {
 		return nil
 	}
 
@@ -939,7 +946,7 @@
 
 func translateAndroidMkEntriesModule(ctx SingletonContext, w io.Writer, moduleInfoJSONs *[]*ModuleInfoJSON,
 	mod blueprint.Module, provider AndroidMkEntriesProvider) error {
-	if shouldSkipAndroidMkProcessing(mod.(Module).base()) {
+	if shouldSkipAndroidMkProcessing(ctx, mod.(Module).base()) {
 		return nil
 	}
 
@@ -961,11 +968,11 @@
 	return nil
 }
 
-func ShouldSkipAndroidMkProcessing(module Module) bool {
-	return shouldSkipAndroidMkProcessing(module.base())
+func ShouldSkipAndroidMkProcessing(ctx ConfigAndErrorContext, module Module) bool {
+	return shouldSkipAndroidMkProcessing(ctx, module.base())
 }
 
-func shouldSkipAndroidMkProcessing(module *ModuleBase) bool {
+func shouldSkipAndroidMkProcessing(ctx ConfigAndErrorContext, module *ModuleBase) bool {
 	if !module.commonProperties.NamespaceExportedToMake {
 		// TODO(jeffrygaston) do we want to validate that there are no modules being
 		// exported to Kati that depend on this module?
@@ -984,7 +991,7 @@
 		return true
 	}
 
-	return !module.Enabled() ||
+	return !module.Enabled(ctx) ||
 		module.commonProperties.HideFromMake ||
 		// Make does not understand LinuxBionic
 		module.Os() == LinuxBionic ||
diff --git a/android/androidmk_test.go b/android/androidmk_test.go
index ae2187f..72b8654 100644
--- a/android/androidmk_test.go
+++ b/android/androidmk_test.go
@@ -36,10 +36,6 @@
 	data       AndroidMkData
 	distFiles  TaggedDistFiles
 	outputFile OptionalPath
-
-	// The paths that will be used as the default dist paths if no tag is
-	// specified.
-	defaultDistPaths Paths
 }
 
 const (
@@ -51,6 +47,7 @@
 func (m *customModule) GenerateAndroidBuildActions(ctx ModuleContext) {
 
 	m.base().licenseMetadataFile = PathForOutput(ctx, "meta_lic")
+	var defaultDistPaths Paths
 
 	// If the dist_output_file: true then create an output file that is stored in
 	// the OutputFile property of the AndroidMkEntry.
@@ -62,7 +59,7 @@
 		// property in AndroidMkEntry when determining the default dist paths.
 		// Setting this first allows it to be overridden based on the
 		// default_dist_files setting replicating that previous behavior.
-		m.defaultDistPaths = Paths{path}
+		defaultDistPaths = Paths{path}
 	}
 
 	// Based on the setting of the default_dist_files property possibly create a
@@ -71,29 +68,40 @@
 	defaultDistFiles := proptools.StringDefault(m.properties.Default_dist_files, defaultDistFiles_Tagged)
 	switch defaultDistFiles {
 	case defaultDistFiles_None:
-		// Do nothing
+		m.setOutputFiles(ctx, defaultDistPaths)
 
 	case defaultDistFiles_Default:
 		path := PathForTesting("default-dist.out")
-		m.defaultDistPaths = Paths{path}
+		defaultDistPaths = Paths{path}
+		m.setOutputFiles(ctx, defaultDistPaths)
 		m.distFiles = MakeDefaultDistFiles(path)
 
 	case defaultDistFiles_Tagged:
 		// Module types that set AndroidMkEntry.DistFiles to the result of calling
 		// GenerateTaggedDistFiles(ctx) relied on no tag being treated as "" which
-		// meant that the default dist paths would be whatever was returned by
-		// OutputFiles(""). In order to preserve that behavior when treating no tag
-		// as being equal to DefaultDistTag this ensures that
-		// OutputFiles(DefaultDistTag) will return the same as OutputFiles("").
-		m.defaultDistPaths = PathsForTesting("one.out")
+		// meant that the default dist paths would be the same as empty-string-tag
+		// output files. In order to preserve that behavior when treating no tag
+		// as being equal to DefaultDistTag this ensures that DefaultDistTag output
+		// will be the same as empty-string-tag output.
+		defaultDistPaths = PathsForTesting("one.out")
+		m.setOutputFiles(ctx, defaultDistPaths)
 
 		// This must be called after setting defaultDistPaths/outputFile as
-		// GenerateTaggedDistFiles calls into OutputFiles(tag) which may use those
-		// fields.
+		// GenerateTaggedDistFiles calls into outputFiles property which may use
+		// those fields.
 		m.distFiles = m.GenerateTaggedDistFiles(ctx)
 	}
 }
 
+func (m *customModule) setOutputFiles(ctx ModuleContext, defaultDistPaths Paths) {
+	ctx.SetOutputFiles(PathsForTesting("one.out"), "")
+	ctx.SetOutputFiles(PathsForTesting("two.out", "three/four.out"), ".multiple")
+	ctx.SetOutputFiles(PathsForTesting("another.out"), ".another-tag")
+	if defaultDistPaths != nil {
+		ctx.SetOutputFiles(defaultDistPaths, DefaultDistTag)
+	}
+}
+
 func (m *customModule) AndroidMk() AndroidMkData {
 	return AndroidMkData{
 		Custom: func(w io.Writer, name, prefix, moduleDir string, data AndroidMkData) {
@@ -102,25 +110,6 @@
 	}
 }
 
-func (m *customModule) OutputFiles(tag string) (Paths, error) {
-	switch tag {
-	case DefaultDistTag:
-		if m.defaultDistPaths != nil {
-			return m.defaultDistPaths, nil
-		} else {
-			return nil, fmt.Errorf("default dist tag is not available")
-		}
-	case "":
-		return PathsForTesting("one.out"), nil
-	case ".multiple":
-		return PathsForTesting("two.out", "three/four.out"), nil
-	case ".another-tag":
-		return PathsForTesting("another.out"), nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
 func (m *customModule) AndroidMkEntries() []AndroidMkEntries {
 	return []AndroidMkEntries{
 		{
diff --git a/android/apex.go b/android/apex.go
index dc0aeed..ecab8e3 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -37,11 +37,7 @@
 // Accessible via `ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)`
 type ApexInfo struct {
 	// Name of the apex variation that this module (i.e. the apex variant of the module) is
-	// mutated into, or "" for a platform (i.e. non-APEX) variant. Note that this name and the
-	// Soong module name of the APEX can be different. That happens when there is
-	// `override_apex` that overrides `apex`. In that case, both Soong modules have the same
-	// apex variation name which usually is `com.android.foo`. This name is also the `name`
-	// in the path `/apex/<name>` where this apex is activated on at runtime.
+	// mutated into, or "" for a platform (i.e. non-APEX) variant.
 	//
 	// Also note that a module can be included in multiple APEXes, in which case, the module is
 	// mutated into one or more variants, each of which is for an APEX. The variants then can
@@ -88,6 +84,9 @@
 
 	// 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
 }
 
 // AllApexInfo holds the ApexInfo of all apexes that include this module.
@@ -354,7 +353,8 @@
 // ApexModuleBase provides the default implementation for the ApexModule interface. APEX-aware
 // modules are expected to include this struct and call InitApexModule().
 type ApexModuleBase struct {
-	ApexProperties ApexProperties
+	ApexProperties     ApexProperties
+	apexPropertiesLock sync.Mutex // protects ApexProperties during parallel apexDirectlyInAnyMutator
 
 	canHaveApexVariants bool
 
@@ -610,9 +610,15 @@
 		return ""
 	}
 
-	// If this module has no apex variations the use the platform variation.
 	if len(apexInfos) == 0 {
-		return ""
+		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
@@ -765,18 +771,22 @@
 
 // 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 direct dependencies with a CopyDirectlyInAnyApexTag dependency
-// tag.
+// 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 direct dependencies with a
+	// Copy DirectlyInAnyApex and InAnyApex from any transitive dependencies with a
 	// CopyDirectlyInAnyApexTag dependency tag.
-	mctx.VisitDirectDeps(func(dep Module) {
-		if _, ok := mctx.OtherModuleDependencyTag(dep).(CopyDirectlyInAnyApexTag); ok {
-			depBase := dep.(ApexModule).apexModuleBase()
+	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 {
diff --git a/android/apex_contributions.go b/android/apex_contributions.go
index dd09fbf..8b72f8e 100644
--- a/android/apex_contributions.go
+++ b/android/apex_contributions.go
@@ -15,8 +15,6 @@
 package android
 
 import (
-	"strings"
-
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
@@ -103,27 +101,24 @@
 }
 
 var (
-	acDepTag = apexContributionsDepTag{}
+	AcDepTag = apexContributionsDepTag{}
 )
 
 // Creates a dep to each selected apex_contributions
 func (a *allApexContributions) DepsMutator(ctx BottomUpMutatorContext) {
-	ctx.AddDependency(ctx.Module(), acDepTag, ctx.Config().AllApexContributions()...)
+	// 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) {
 	addContentsToProvider := func(p *PrebuiltSelectionInfoMap, m *apexContributions) {
 		for _, content := range m.Contents() {
-			// Coverage builds for TARGET_RELEASE=foo should always build from source,
-			// even if TARGET_RELEASE=foo uses prebuilt mainline modules.
-			// This is necessary because the checked-in prebuilts were generated with
-			// instrumentation turned off.
-			//
-			// Skip any prebuilt contents in coverage builds
-			if strings.HasPrefix(content, "prebuilt_") && (ctx.Config().JavaCoverageEnabled() || ctx.DeviceConfig().NativeCoverageEnabled()) {
-				continue
-			}
 			if !ctx.OtherModuleExists(content) && !ctx.Config().AllowMissingDependencies() {
 				ctx.ModuleErrorf("%s listed in apex_contributions %s does not exist\n", content, m.Name())
 			}
@@ -137,19 +132,13 @@
 	}
 
 	p := PrebuiltSelectionInfoMap{}
-	// 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.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())
-			}
-		})
-	}
+	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())
+		}
+	})
 	SetProvider(ctx, PrebuiltSelectionInfoProvider, p)
 }
 
diff --git a/android/api_levels.go b/android/api_levels.go
index fab5fc7..dc17238 100644
--- a/android/api_levels.go
+++ b/android/api_levels.go
@@ -291,6 +291,8 @@
 
 var ApiLevelUpsideDownCake = uncheckedFinalApiLevel(34)
 
+var ApiLevelVanillaIceCream = uncheckedFinalApiLevel(35)
+
 // ReplaceFinalizedCodenames returns the API level number associated with that API level
 // if the `raw` input is the codename of an API level has been finalized.
 // If the input is *not* a finalized codename, the input is returned unmodified.
diff --git a/android/arch.go b/android/arch.go
index cd8882b..6d896e5 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -19,6 +19,7 @@
 	"fmt"
 	"reflect"
 	"runtime"
+	"slices"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -486,7 +487,7 @@
 			// dependencies on OsType variants that are explicitly disabled in their
 			// properties. The CommonOS variant will still depend on disabled variants
 			// if they are disabled afterwards, e.g. in archMutator if
-			if module.Enabled() {
+			if module.Enabled(mctx) {
 				mctx.AddInterVariantDependency(commonOsToOsSpecificVariantTag, commonOSVariant, module)
 			}
 		}
@@ -511,7 +512,7 @@
 	var variants []Module
 	mctx.VisitDirectDeps(func(m Module) {
 		if mctx.OtherModuleDependencyTag(m) == commonOsToOsSpecificVariantTag {
-			if m.Enabled() {
+			if m.Enabled(mctx) {
 				variants = append(variants, m)
 			}
 		}
@@ -587,19 +588,21 @@
 	}
 
 	osTargets := mctx.Config().Targets[os]
+
 	image := base.commonProperties.ImageVariation
 	// Filter NativeBridge targets unless they are explicitly supported.
 	// Skip creating native bridge variants for non-core modules.
 	if os == Android && !(base.IsNativeBridgeSupported() && image == CoreVariation) {
+		osTargets = slices.DeleteFunc(slices.Clone(osTargets), func(t Target) bool {
+			return bool(t.NativeBridge)
+		})
+	}
 
-		var targets []Target
-		for _, t := range osTargets {
-			if !t.NativeBridge {
-				targets = append(targets, t)
-			}
-		}
-
-		osTargets = targets
+	// Filter HostCross targets if disabled.
+	if base.HostSupported() && !base.HostCrossSupported() {
+		osTargets = slices.DeleteFunc(slices.Clone(osTargets), func(t Target) bool {
+			return t.HostCross
+		})
 	}
 
 	// only the primary arch in the ramdisk / vendor_ramdisk / recovery partition
diff --git a/android/arch_list.go b/android/arch_list.go
index f4409a9..f1289a3 100644
--- a/android/arch_list.go
+++ b/android/arch_list.go
@@ -16,7 +16,6 @@
 
 var archVariants = map[ArchType][]string{
 	Arm: {
-		"armv7-a",
 		"armv7-a-neon",
 		"armv8-a",
 		"armv8-2a",
@@ -103,6 +102,7 @@
 		"kryo385",
 		"exynos-m1",
 		"exynos-m2",
+		"oryon",
 	},
 	X86:    {},
 	X86_64: {},
diff --git a/android/arch_test.go b/android/arch_test.go
index 5021a67..6134a06 100644
--- a/android/arch_test.go
+++ b/android/arch_test.go
@@ -332,6 +332,12 @@
 		}
 
 		module {
+			name: "nohostcross",
+			host_supported: true,
+			host_cross_supported: false,
+		}
+
+		module {
 			name: "baz",
 			device_supported: false,
 		}
@@ -355,13 +361,14 @@
 	`
 
 	testCases := []struct {
-		name          string
-		preparer      FixturePreparer
-		fooVariants   []string
-		barVariants   []string
-		bazVariants   []string
-		quxVariants   []string
-		firstVariants []string
+		name                string
+		preparer            FixturePreparer
+		fooVariants         []string
+		barVariants         []string
+		noHostCrossVariants []string
+		bazVariants         []string
+		quxVariants         []string
+		firstVariants       []string
 
 		multiTargetVariants    []string
 		multiTargetVariantsMap map[string][]string
@@ -373,6 +380,7 @@
 			preparer:            nil,
 			fooVariants:         []string{"android_arm64_armv8-a", "android_arm_armv7-a-neon"},
 			barVariants:         append(buildOSVariants, "android_arm64_armv8-a", "android_arm_armv7-a-neon"),
+			noHostCrossVariants: append(buildOSVariants, "android_arm64_armv8-a", "android_arm_armv7-a-neon"),
 			bazVariants:         nil,
 			quxVariants:         append(buildOS32Variants, "android_arm_armv7-a-neon"),
 			firstVariants:       append(buildOS64Variants, "android_arm64_armv8-a"),
@@ -390,6 +398,7 @@
 			}),
 			fooVariants:         nil,
 			barVariants:         buildOSVariants,
+			noHostCrossVariants: buildOSVariants,
 			bazVariants:         nil,
 			quxVariants:         buildOS32Variants,
 			firstVariants:       buildOS64Variants,
@@ -406,6 +415,7 @@
 			}),
 			fooVariants:         []string{"android_arm64_armv8-a", "android_arm_armv7-a-neon"},
 			barVariants:         []string{"linux_musl_x86_64", "linux_musl_arm64", "linux_musl_x86", "android_arm64_armv8-a", "android_arm_armv7-a-neon"},
+			noHostCrossVariants: []string{"linux_musl_x86_64", "linux_musl_x86", "android_arm64_armv8-a", "android_arm_armv7-a-neon"},
 			bazVariants:         nil,
 			quxVariants:         []string{"linux_musl_x86", "android_arm_armv7-a-neon"},
 			firstVariants:       []string{"linux_musl_x86_64", "linux_musl_arm64", "android_arm64_armv8-a"},
@@ -423,7 +433,7 @@
 		variants := ctx.ModuleVariantsForTests(name)
 		for _, variant := range variants {
 			m := ctx.ModuleForTests(name, variant)
-			if m.Module().Enabled() {
+			if m.Module().Enabled(PanickingConfigAndErrorContext(ctx)) {
 				ret = append(ret, variant)
 			}
 		}
@@ -461,6 +471,10 @@
 				t.Errorf("want bar variants:\n%q\ngot:\n%q\n", w, g)
 			}
 
+			if g, w := enabledVariants(ctx, "nohostcross"), tt.noHostCrossVariants; !reflect.DeepEqual(w, g) {
+				t.Errorf("want nohostcross variants:\n%q\ngot:\n%q\n", w, g)
+			}
+
 			if g, w := enabledVariants(ctx, "baz"), tt.bazVariants; !reflect.DeepEqual(w, g) {
 				t.Errorf("want baz variants:\n%q\ngot:\n%q\n", w, g)
 			}
@@ -533,7 +547,7 @@
 		variants := ctx.ModuleVariantsForTests(name)
 		for _, variant := range variants {
 			m := ctx.ModuleForTests(name, variant)
-			if m.Module().Enabled() {
+			if m.Module().Enabled(PanickingConfigAndErrorContext(ctx)) {
 				ret = append(ret, variant)
 			}
 		}
diff --git a/android/base_module_context.go b/android/base_module_context.go
index c5fe585..5506000 100644
--- a/android/base_module_context.go
+++ b/android/base_module_context.go
@@ -325,7 +325,7 @@
 		return nil
 	}
 
-	if !aModule.Enabled() {
+	if !aModule.Enabled(b) {
 		if t, ok := tag.(AllowDisabledModuleDependency); !ok || !t.AllowDisabledModuleDependency(aModule) {
 			if b.Config().AllowMissingDependencies() {
 				b.AddMissingDependencies([]string{b.OtherModuleName(aModule)})
@@ -536,7 +536,7 @@
 		return true
 	} else if tag == licensesTag {
 		return true
-	} else if tag == acDepTag {
+	} else if tag == AcDepTag {
 		return true
 	}
 	return false
diff --git a/android/blueprint_e2e_test.go b/android/blueprint_e2e_test.go
new file mode 100644
index 0000000..b274512
--- /dev/null
+++ b/android/blueprint_e2e_test.go
@@ -0,0 +1,105 @@
+// 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"
+)
+
+var testCases []struct {
+	name          string
+	fs            MockFS
+	expectedError string
+} = []struct {
+	name          string
+	fs            MockFS
+	expectedError string
+}{
+	{
+		name: "Can't reference variable before assignment",
+		fs: map[string][]byte{
+			"Android.bp": []byte(`
+x = foo
+foo = "hello"
+`),
+		},
+		expectedError: "undefined variable foo",
+	},
+	{
+		name: "Can't append to variable before assigned to",
+		fs: map[string][]byte{
+			"Android.bp": []byte(`
+foo += "world"
+foo = "hello"
+`),
+		},
+		expectedError: "modified non-existent variable \"foo\" with \\+=",
+	},
+	{
+		name: "Can't reassign variable",
+		fs: map[string][]byte{
+			"Android.bp": []byte(`
+foo = "hello"
+foo = "world"
+`),
+		},
+		expectedError: "variable already set, previous assignment:",
+	},
+	{
+		name: "Can't reassign variable in inherited scope",
+		fs: map[string][]byte{
+			"Android.bp": []byte(`
+foo = "hello"
+`),
+			"foo/Android.bp": []byte(`
+foo = "world"
+`),
+		},
+		expectedError: "variable already set in inherited scope, previous assignment:",
+	},
+	{
+		name: "Can't modify variable in inherited scope",
+		fs: map[string][]byte{
+			"Android.bp": []byte(`
+foo = "hello"
+`),
+			"foo/Android.bp": []byte(`
+foo += "world"
+`),
+		},
+		expectedError: "modified non-local variable \"foo\" with \\+=",
+	},
+	{
+		name: "Can't modify variable after referencing",
+		fs: map[string][]byte{
+			"Android.bp": []byte(`
+foo = "hello"
+x = foo
+foo += "world"
+`),
+		},
+		expectedError: "modified variable \"foo\" with \\+= after referencing",
+	},
+}
+
+func TestBlueprintErrors(t *testing.T) {
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			fixtures := FixtureMergeMockFs(tc.fs)
+			fixtures = fixtures.ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(tc.expectedError))
+			fixtures.RunTest(t)
+		})
+	}
+}
diff --git a/android/build_prop.go b/android/build_prop.go
new file mode 100644
index 0000000..45c17c3
--- /dev/null
+++ b/android/build_prop.go
@@ -0,0 +1,125 @@
+// 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() {
+	ctx := InitRegistrationContext
+	ctx.RegisterModuleType("build_prop", buildPropFactory)
+}
+
+type buildPropProperties struct {
+	// Output file name. Defaults to "build.prop"
+	Stem *string
+
+	// List of prop names to exclude. This affects not only common build properties but also
+	// properties in prop_files.
+	Block_list []string
+
+	// Path to the input prop files. The contents of the files are directly
+	// emitted to the output
+	Prop_files []string `android:"path"`
+
+	// Files to be appended at the end of build.prop. These files are appended after
+	// post_process_props without any further checking.
+	Footer_files []string `android:"path"`
+
+	// Path to a JSON file containing product configs.
+	Product_config *string `android:"path"`
+}
+
+type buildPropModule struct {
+	ModuleBase
+
+	properties buildPropProperties
+
+	outputFilePath OutputPath
+	installPath    InstallPath
+}
+
+func (p *buildPropModule) stem() string {
+	return proptools.StringDefault(p.properties.Stem, "build.prop")
+}
+
+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")
+		return
+	}
+
+	partition := p.PartitionTag(ctx.DeviceConfig())
+	if partition != "system" {
+		ctx.PropertyErrorf("partition", "unsupported partition %q: only \"system\" is supported", partition)
+		return
+	}
+
+	rule := NewRuleBuilder(pctx, ctx)
+
+	config := ctx.Config()
+
+	cmd := rule.Command().BuiltTool("gen_build_prop")
+
+	cmd.FlagWithInput("--build-hostname-file=", config.BuildHostnameFile(ctx))
+	cmd.FlagWithInput("--build-number-file=", config.BuildNumberFile(ctx))
+	// shouldn't depend on BuildFingerprintFile and BuildThumbprintFile to prevent from rebuilding
+	// on every incremental build.
+	cmd.FlagWithArg("--build-fingerprint-file=", config.BuildFingerprintFile(ctx).String())
+	// Export build thumbprint only if the product has specified at least one oem fingerprint property
+	// b/17888863
+	if shouldAddBuildThumbprint(config) {
+		// In the previous make implementation, a dependency was not added on the thumbprint file
+		cmd.FlagWithArg("--build-thumbprint-file=", config.BuildThumbprintFile(ctx).String())
+	}
+	cmd.FlagWithArg("--build-username=", config.Getenv("BUILD_USERNAME"))
+	// shouldn't depend on BUILD_DATETIME_FILE to prevent from rebuilding on every incremental
+	// build.
+	cmd.FlagWithArg("--date-file=", ctx.Config().Getenv("BUILD_DATETIME_FILE"))
+	cmd.FlagWithInput("--platform-preview-sdk-fingerprint-file=", ApiFingerprintPath(ctx))
+	cmd.FlagWithInput("--product-config=", PathForModuleSrc(ctx, proptools.String(p.properties.Product_config)))
+	cmd.FlagWithArg("--partition=", partition)
+	cmd.FlagWithOutput("--out=", p.outputFilePath)
+
+	postProcessCmd := rule.Command().BuiltTool("post_process_props")
+	if ctx.DeviceConfig().BuildBrokenDupSysprop() {
+		postProcessCmd.Flag("--allow-dup")
+	}
+	postProcessCmd.FlagWithArg("--sdk-version ", config.PlatformSdkVersion().String())
+	postProcessCmd.FlagWithInput("--kernel-version-file-for-uffd-gc ", PathForOutput(ctx, "dexpreopt/kernel_version_for_uffd_gc.txt"))
+	postProcessCmd.Text(p.outputFilePath.String())
+	postProcessCmd.Flags(p.properties.Block_list)
+
+	rule.Command().Text("echo").Text(proptools.NinjaAndShellEscape("# end of file")).FlagWithArg(">> ", p.outputFilePath.String())
+
+	rule.Build(ctx.ModuleName(), "generating build.prop")
+
+	p.installPath = PathForModuleInstall(ctx)
+	ctx.InstallFile(p.installPath, p.stem(), p.outputFilePath)
+
+	ctx.SetOutputFiles(Paths{p.outputFilePath}, "")
+}
+
+// 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 {
+	module := &buildPropModule{}
+	module.AddProperties(&module.properties)
+	InitAndroidArchModule(module, DeviceSupported, MultilibCommon)
+	return module
+}
diff --git a/android/buildinfo_prop.go b/android/buildinfo_prop.go
index 8288fc5..bba4c0d 100644
--- a/android/buildinfo_prop.go
+++ b/android/buildinfo_prop.go
@@ -15,24 +15,23 @@
 package android
 
 import (
-	"fmt"
-	"strings"
-
 	"github.com/google/blueprint/proptools"
 )
 
 func init() {
 	ctx := InitRegistrationContext
-	ctx.RegisterParallelSingletonModuleType("buildinfo_prop", buildinfoPropFactory)
+	ctx.RegisterModuleType("buildinfo_prop", buildinfoPropFactory)
 }
 
 type buildinfoPropProperties struct {
 	// Whether this module is directly installable to one of the partitions. Default: true.
 	Installable *bool
+
+	Product_config *string `android:"path"`
 }
 
 type buildinfoPropModule struct {
-	SingletonModuleBase
+	ModuleBase
 
 	properties buildinfoPropProperties
 
@@ -40,38 +39,10 @@
 	installPath    InstallPath
 }
 
-var _ OutputFileProducer = (*buildinfoPropModule)(nil)
-
 func (p *buildinfoPropModule) installable() bool {
 	return proptools.BoolDefault(p.properties.Installable, true)
 }
 
-// OutputFileProducer
-func (p *buildinfoPropModule) OutputFiles(tag string) (Paths, error) {
-	if tag != "" {
-		return nil, fmt.Errorf("unsupported tag %q", tag)
-	}
-	return Paths{p.outputFilePath}, nil
-}
-
-func getBuildVariant(config Config) string {
-	if config.Eng() {
-		return "eng"
-	} else if config.Debuggable() {
-		return "userdebug"
-	} else {
-		return "user"
-	}
-}
-
-func getBuildFlavor(config Config) string {
-	buildFlavor := config.DeviceProduct() + "-" + getBuildVariant(config)
-	if InList("address", config.SanitizeDevice()) && !strings.Contains(buildFlavor, "_asan") {
-		buildFlavor += "_asan"
-	}
-	return buildFlavor
-}
-
 func shouldAddBuildThumbprint(config Config) bool {
 	knownOemProperties := []string{
 		"ro.product.brand",
@@ -88,7 +59,13 @@
 }
 
 func (p *buildinfoPropModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	if ctx.ModuleName() != "buildinfo.prop" || ctx.ModuleDir() != "build/soong" {
+		ctx.ModuleErrorf("There can only be one buildinfo_prop module in build/soong")
+		return
+	}
 	p.outputFilePath = PathForModuleOut(ctx, p.Name()).OutputPath
+	ctx.SetOutputFiles(Paths{p.outputFilePath}, "")
+
 	if !ctx.Config().KatiEnabled() {
 		WriteFileRule(ctx, p.outputFilePath, "# no buildinfo.prop if kati is disabled")
 		return
@@ -97,60 +74,27 @@
 	rule := NewRuleBuilder(pctx, ctx)
 
 	config := ctx.Config()
-	buildVariant := getBuildVariant(config)
-	buildFlavor := getBuildFlavor(config)
 
 	cmd := rule.Command().BuiltTool("buildinfo")
 
-	if config.BoardUseVbmetaDigestInFingerprint() {
-		cmd.Flag("--use-vbmeta-digest-in-fingerprint")
-	}
-
-	cmd.FlagWithArg("--build-flavor=", buildFlavor)
 	cmd.FlagWithInput("--build-hostname-file=", config.BuildHostnameFile(ctx))
-	cmd.FlagWithArg("--build-id=", config.BuildId())
-	cmd.FlagWithArg("--build-keys=", config.BuildKeys())
-
-	// shouldn't depend on BuildNumberFile and BuildThumbprintFile to prevent from rebuilding
-	// on every incremental build.
-	cmd.FlagWithArg("--build-number-file=", config.BuildNumberFile(ctx).String())
+	// Note: depending on BuildNumberFile will cause the build.prop file to be rebuilt
+	// every build, but that's intentional.
+	cmd.FlagWithInput("--build-number-file=", config.BuildNumberFile(ctx))
+	// Export build thumbprint only if the product has specified at least one oem fingerprint property
+	// b/17888863
 	if shouldAddBuildThumbprint(config) {
+		// In the previous make implementation, a dependency was not added on the thumbprint file
 		cmd.FlagWithArg("--build-thumbprint-file=", config.BuildThumbprintFile(ctx).String())
 	}
-
-	cmd.FlagWithArg("--build-type=", config.BuildType())
 	cmd.FlagWithArg("--build-username=", config.Getenv("BUILD_USERNAME"))
-	cmd.FlagWithArg("--build-variant=", buildVariant)
-	cmd.FlagForEachArg("--cpu-abis=", config.DeviceAbi())
-
-	// shouldn't depend on BUILD_DATETIME_FILE to prevent from rebuilding on every incremental
-	// build.
+	// Technically we should also have a dependency on BUILD_DATETIME_FILE,
+	// but it can be either an absolute or relative path, which is hard to turn into
+	// a Path object. So just rely on the BuildNumberFile always changing to cause
+	// us to rebuild.
 	cmd.FlagWithArg("--date-file=", ctx.Config().Getenv("BUILD_DATETIME_FILE"))
-
-	if len(config.ProductLocales()) > 0 {
-		cmd.FlagWithArg("--default-locale=", config.ProductLocales()[0])
-	}
-
-	cmd.FlagForEachArg("--default-wifi-channels=", config.ProductDefaultWifiChannels())
-	cmd.FlagWithArg("--device=", config.DeviceName())
-	if config.DisplayBuildNumber() {
-		cmd.Flag("--display-build-number")
-	}
-
-	cmd.FlagWithArg("--platform-base-os=", config.PlatformBaseOS())
-	cmd.FlagWithArg("--platform-display-version=", config.PlatformDisplayVersionName())
-	cmd.FlagWithArg("--platform-min-supported-target-sdk-version=", config.PlatformMinSupportedTargetSdkVersion())
 	cmd.FlagWithInput("--platform-preview-sdk-fingerprint-file=", ApiFingerprintPath(ctx))
-	cmd.FlagWithArg("--platform-preview-sdk-version=", config.PlatformPreviewSdkVersion())
-	cmd.FlagWithArg("--platform-sdk-version=", config.PlatformSdkVersion().String())
-	cmd.FlagWithArg("--platform-security-patch=", config.PlatformSecurityPatch())
-	cmd.FlagWithArg("--platform-version=", config.PlatformVersionName())
-	cmd.FlagWithArg("--platform-version-codename=", config.PlatformSdkCodename())
-	cmd.FlagForEachArg("--platform-version-all-codenames=", config.PlatformVersionActiveCodenames())
-	cmd.FlagWithArg("--platform-version-known-codenames=", config.PlatformVersionKnownCodenames())
-	cmd.FlagWithArg("--platform-version-last-stable=", config.PlatformVersionLastStable())
-	cmd.FlagWithArg("--product=", config.DeviceProduct())
-
+	cmd.FlagWithInput("--product-config=", PathForModuleSrc(ctx, proptools.String(p.properties.Product_config)))
 	cmd.FlagWithOutput("--out=", p.outputFilePath)
 
 	rule.Build(ctx.ModuleName(), "generating buildinfo props")
@@ -163,12 +107,8 @@
 	ctx.InstallFile(p.installPath, p.Name(), p.outputFilePath)
 }
 
-func (f *buildinfoPropModule) GenerateSingletonBuildActions(ctx SingletonContext) {
-	// does nothing; buildinfo_prop is a singeton because two buildinfo modules don't make sense.
-}
-
 func (p *buildinfoPropModule) AndroidMkEntries() []AndroidMkEntries {
-	return []AndroidMkEntries{AndroidMkEntries{
+	return []AndroidMkEntries{{
 		Class:      "ETC",
 		OutputFile: OptionalPathForPath(p.outputFilePath),
 		ExtraEntries: []AndroidMkExtraEntriesFunc{
@@ -184,7 +124,7 @@
 // buildinfo_prop module generates a build.prop file, which contains a set of common
 // system/build.prop properties, such as ro.build.version.*.  Not all properties are implemented;
 // currently this module is only for microdroid.
-func buildinfoPropFactory() SingletonModule {
+func buildinfoPropFactory() Module {
 	module := &buildinfoPropModule{}
 	module.AddProperties(&module.properties)
 	InitAndroidModule(module)
diff --git a/android/compliance_metadata.go b/android/compliance_metadata.go
new file mode 100644
index 0000000..6ea6654
--- /dev/null
+++ b/android/compliance_metadata.go
@@ -0,0 +1,314 @@
+// 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 (
+	"bytes"
+	"encoding/csv"
+	"fmt"
+	"slices"
+	"strconv"
+	"strings"
+
+	"github.com/google/blueprint"
+)
+
+var (
+	// Constants of property names used in compliance metadata of modules
+	ComplianceMetadataProp = struct {
+		NAME                   string
+		PACKAGE                string
+		MODULE_TYPE            string
+		OS                     string
+		ARCH                   string
+		IS_PRIMARY_ARCH        string
+		VARIANT                string
+		IS_STATIC_LIB          string
+		INSTALLED_FILES        string
+		BUILT_FILES            string
+		STATIC_DEPS            string
+		STATIC_DEP_FILES       string
+		WHOLE_STATIC_DEPS      string
+		WHOLE_STATIC_DEP_FILES string
+		LICENSES               string
+
+		// module_type=package
+		PKG_DEFAULT_APPLICABLE_LICENSES string
+
+		// module_type=license
+		LIC_LICENSE_KINDS string
+		LIC_LICENSE_TEXT  string
+		LIC_PACKAGE_NAME  string
+
+		// module_type=license_kind
+		LK_CONDITIONS string
+		LK_URL        string
+	}{
+		"name",
+		"package",
+		"module_type",
+		"os",
+		"arch",
+		"is_primary_arch",
+		"variant",
+		"is_static_lib",
+		"installed_files",
+		"built_files",
+		"static_deps",
+		"static_dep_files",
+		"whole_static_deps",
+		"whole_static_dep_files",
+		"licenses",
+
+		"pkg_default_applicable_licenses",
+
+		"lic_license_kinds",
+		"lic_license_text",
+		"lic_package_name",
+
+		"lk_conditions",
+		"lk_url",
+	}
+
+	// A constant list of all property names in compliance metadata
+	// Order of properties here is the order of columns in the exported CSV file.
+	COMPLIANCE_METADATA_PROPS = []string{
+		ComplianceMetadataProp.NAME,
+		ComplianceMetadataProp.PACKAGE,
+		ComplianceMetadataProp.MODULE_TYPE,
+		ComplianceMetadataProp.OS,
+		ComplianceMetadataProp.ARCH,
+		ComplianceMetadataProp.VARIANT,
+		ComplianceMetadataProp.IS_STATIC_LIB,
+		ComplianceMetadataProp.IS_PRIMARY_ARCH,
+		// Space separated installed files
+		ComplianceMetadataProp.INSTALLED_FILES,
+		// Space separated built files
+		ComplianceMetadataProp.BUILT_FILES,
+		// Space separated module names of static dependencies
+		ComplianceMetadataProp.STATIC_DEPS,
+		// Space separated file paths of static dependencies
+		ComplianceMetadataProp.STATIC_DEP_FILES,
+		// Space separated module names of whole static dependencies
+		ComplianceMetadataProp.WHOLE_STATIC_DEPS,
+		// Space separated file paths of whole static dependencies
+		ComplianceMetadataProp.WHOLE_STATIC_DEP_FILES,
+		ComplianceMetadataProp.LICENSES,
+		// module_type=package
+		ComplianceMetadataProp.PKG_DEFAULT_APPLICABLE_LICENSES,
+		// module_type=license
+		ComplianceMetadataProp.LIC_LICENSE_KINDS,
+		ComplianceMetadataProp.LIC_LICENSE_TEXT, // resolve to file paths
+		ComplianceMetadataProp.LIC_PACKAGE_NAME,
+		// module_type=license_kind
+		ComplianceMetadataProp.LK_CONDITIONS,
+		ComplianceMetadataProp.LK_URL,
+	}
+)
+
+// ComplianceMetadataInfo provides all metadata of a module, e.g. name, module type, package, license,
+// dependencies, built/installed files, etc. It is a wrapper on a map[string]string with some utility
+// methods to get/set properties' values.
+type ComplianceMetadataInfo struct {
+	properties map[string]string
+}
+
+func NewComplianceMetadataInfo() *ComplianceMetadataInfo {
+	return &ComplianceMetadataInfo{
+		properties: map[string]string{},
+	}
+}
+
+func (c *ComplianceMetadataInfo) SetStringValue(propertyName string, value string) {
+	if !slices.Contains(COMPLIANCE_METADATA_PROPS, propertyName) {
+		panic(fmt.Errorf("Unknown metadata property: %s.", propertyName))
+	}
+	c.properties[propertyName] = value
+}
+
+func (c *ComplianceMetadataInfo) SetListValue(propertyName string, value []string) {
+	c.SetStringValue(propertyName, strings.TrimSpace(strings.Join(value, " ")))
+}
+
+func (c *ComplianceMetadataInfo) getStringValue(propertyName string) string {
+	if !slices.Contains(COMPLIANCE_METADATA_PROPS, propertyName) {
+		panic(fmt.Errorf("Unknown metadata property: %s.", propertyName))
+	}
+	return c.properties[propertyName]
+}
+
+func (c *ComplianceMetadataInfo) getAllValues() map[string]string {
+	return c.properties
+}
+
+var (
+	ComplianceMetadataProvider = blueprint.NewProvider[*ComplianceMetadataInfo]()
+)
+
+// buildComplianceMetadataProvider starts with the ModuleContext.ComplianceMetadataInfo() and fills in more common metadata
+// for different module types without accessing their private fields but through android.Module interface
+// and public/private fields of package android. The final metadata is stored to a module's ComplianceMetadataProvider.
+func buildComplianceMetadataProvider(ctx ModuleContext, m *ModuleBase) {
+	complianceMetadataInfo := ctx.ComplianceMetadataInfo()
+	complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.NAME, m.Name())
+	complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.PACKAGE, ctx.ModuleDir())
+	complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.MODULE_TYPE, ctx.ModuleType())
+
+	switch ctx.ModuleType() {
+	case "license":
+		licenseModule := m.module.(*licenseModule)
+		complianceMetadataInfo.SetListValue(ComplianceMetadataProp.LIC_LICENSE_KINDS, licenseModule.properties.License_kinds)
+		complianceMetadataInfo.SetListValue(ComplianceMetadataProp.LIC_LICENSE_TEXT, PathsForModuleSrc(ctx, licenseModule.properties.License_text).Strings())
+		complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.LIC_PACKAGE_NAME, String(licenseModule.properties.Package_name))
+	case "license_kind":
+		licenseKindModule := m.module.(*licenseKindModule)
+		complianceMetadataInfo.SetListValue(ComplianceMetadataProp.LK_CONDITIONS, licenseKindModule.properties.Conditions)
+		complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.LK_URL, licenseKindModule.properties.Url)
+	default:
+		complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.OS, ctx.Os().String())
+		complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.ARCH, ctx.Arch().String())
+		complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.IS_PRIMARY_ARCH, strconv.FormatBool(ctx.PrimaryArch()))
+		complianceMetadataInfo.SetStringValue(ComplianceMetadataProp.VARIANT, ctx.ModuleSubDir())
+		if m.primaryLicensesProperty != nil && m.primaryLicensesProperty.getName() == "licenses" {
+			complianceMetadataInfo.SetListValue(ComplianceMetadataProp.LICENSES, m.primaryLicensesProperty.getStrings())
+		}
+
+		var installed InstallPaths
+		installed = append(installed, m.module.FilesToInstall()...)
+		installed = append(installed, m.katiInstalls.InstallPaths()...)
+		installed = append(installed, m.katiSymlinks.InstallPaths()...)
+		installed = append(installed, m.katiInitRcInstalls.InstallPaths()...)
+		installed = append(installed, m.katiVintfInstalls.InstallPaths()...)
+		complianceMetadataInfo.SetListValue(ComplianceMetadataProp.INSTALLED_FILES, FirstUniqueStrings(installed.Strings()))
+	}
+	ctx.setProvider(ComplianceMetadataProvider, complianceMetadataInfo)
+}
+
+func init() {
+	RegisterComplianceMetadataSingleton(InitRegistrationContext)
+}
+
+func RegisterComplianceMetadataSingleton(ctx RegistrationContext) {
+	ctx.RegisterParallelSingletonType("compliance_metadata_singleton", complianceMetadataSingletonFactory)
+}
+
+var (
+	// sqlite3 command line tool
+	sqlite3 = pctx.HostBinToolVariable("sqlite3", "sqlite3")
+
+	// Command to import .csv files to sqlite3 database
+	importCsv = pctx.AndroidStaticRule("importCsv",
+		blueprint.RuleParams{
+			Command: `rm -rf $out && ` +
+				`${sqlite3} $out ".import --csv $in modules" && ` +
+				`${sqlite3} $out ".import --csv ${make_metadata} make_metadata" && ` +
+				`${sqlite3} $out ".import --csv ${make_modules} make_modules"`,
+			CommandDeps: []string{"${sqlite3}"},
+		}, "make_metadata", "make_modules")
+)
+
+func complianceMetadataSingletonFactory() Singleton {
+	return &complianceMetadataSingleton{}
+}
+
+type complianceMetadataSingleton struct {
+}
+
+func writerToCsv(csvWriter *csv.Writer, row []string) {
+	err := csvWriter.Write(row)
+	if err != nil {
+		panic(err)
+	}
+}
+
+// Collect compliance metadata from all Soong modules, write to a CSV file and
+// import compliance metadata from Make and Soong to a sqlite3 database.
+func (c *complianceMetadataSingleton) GenerateBuildActions(ctx SingletonContext) {
+	if !ctx.Config().HasDeviceProduct() {
+		return
+	}
+	var buffer bytes.Buffer
+	csvWriter := csv.NewWriter(&buffer)
+
+	// Collect compliance metadata of modules in Soong and write to out/soong/compliance-metadata/<product>/soong-modules.csv file.
+	columnNames := []string{"id"}
+	columnNames = append(columnNames, COMPLIANCE_METADATA_PROPS...)
+	writerToCsv(csvWriter, columnNames)
+
+	rowId := -1
+	ctx.VisitAllModules(func(module Module) {
+		if !module.Enabled(ctx) {
+			return
+		}
+		moduleType := ctx.ModuleType(module)
+		if moduleType == "package" {
+			metadataMap := map[string]string{
+				ComplianceMetadataProp.NAME:                            ctx.ModuleName(module),
+				ComplianceMetadataProp.MODULE_TYPE:                     ctx.ModuleType(module),
+				ComplianceMetadataProp.PKG_DEFAULT_APPLICABLE_LICENSES: strings.Join(module.base().primaryLicensesProperty.getStrings(), " "),
+			}
+			rowId = rowId + 1
+			metadata := []string{strconv.Itoa(rowId)}
+			for _, propertyName := range COMPLIANCE_METADATA_PROPS {
+				metadata = append(metadata, metadataMap[propertyName])
+			}
+			writerToCsv(csvWriter, metadata)
+			return
+		}
+		if provider, ok := ctx.moduleProvider(module, ComplianceMetadataProvider); ok {
+			metadataInfo := provider.(*ComplianceMetadataInfo)
+			rowId = rowId + 1
+			metadata := []string{strconv.Itoa(rowId)}
+			for _, propertyName := range COMPLIANCE_METADATA_PROPS {
+				metadata = append(metadata, metadataInfo.getStringValue(propertyName))
+			}
+			writerToCsv(csvWriter, metadata)
+			return
+		}
+	})
+	csvWriter.Flush()
+
+	deviceProduct := ctx.Config().DeviceProduct()
+	modulesCsv := PathForOutput(ctx, "compliance-metadata", deviceProduct, "soong-modules.csv")
+	WriteFileRuleVerbatim(ctx, modulesCsv, buffer.String())
+
+	// Metadata generated in Make
+	makeMetadataCsv := PathForOutput(ctx, "compliance-metadata", deviceProduct, "make-metadata.csv")
+	makeModulesCsv := PathForOutput(ctx, "compliance-metadata", deviceProduct, "make-modules.csv")
+
+	// Import metadata from Make and Soong to sqlite3 database
+	complianceMetadataDb := PathForOutput(ctx, "compliance-metadata", deviceProduct, "compliance-metadata.db")
+	ctx.Build(pctx, BuildParams{
+		Rule:  importCsv,
+		Input: modulesCsv,
+		Implicits: []Path{
+			makeMetadataCsv,
+			makeModulesCsv,
+		},
+		Output: complianceMetadataDb,
+		Args: map[string]string{
+			"make_metadata": makeMetadataCsv.String(),
+			"make_modules":  makeModulesCsv.String(),
+		},
+	})
+
+	// Phony rule "compliance-metadata.db". "m compliance-metadata.db" to create the compliance metadata database.
+	ctx.Build(pctx, BuildParams{
+		Rule:   blueprint.Phony,
+		Inputs: []Path{complianceMetadataDb},
+		Output: PathForPhony(ctx, "compliance-metadata.db"),
+	})
+
+}
diff --git a/android/config.go b/android/config.go
index 75d135f..0e3b0a1 100644
--- a/android/config.go
+++ b/android/config.go
@@ -22,7 +22,6 @@
 	"fmt"
 	"os"
 	"path/filepath"
-	"reflect"
 	"runtime"
 	"strconv"
 	"strings"
@@ -37,9 +36,7 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android/soongconfig"
-	"android/soong/bazel"
 	"android/soong/remoteexec"
-	"android/soong/starlark_fmt"
 )
 
 // Bool re-exports proptools.Bool for the android package.
@@ -201,6 +198,33 @@
 	return c.config.productVariables.ReleaseAconfigValueSets
 }
 
+func (c Config) ReleaseAconfigExtraReleaseConfigs() []string {
+	result := []string{}
+	if val, ok := c.config.productVariables.BuildFlags["RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS"]; ok {
+		if len(val) > 0 {
+			// Remove any duplicates from the list.
+			found := make(map[string]bool)
+			for _, k := range strings.Split(val, " ") {
+				if !found[k] {
+					found[k] = true
+					result = append(result, k)
+				}
+			}
+		}
+	}
+	return result
+}
+
+func (c Config) ReleaseAconfigExtraReleaseConfigsValueSets() map[string][]string {
+	result := make(map[string][]string)
+	for _, rcName := range c.ReleaseAconfigExtraReleaseConfigs() {
+		if value, ok := c.config.productVariables.BuildFlags["RELEASE_ACONFIG_VALUE_SETS_"+rcName]; ok {
+			result[rcName] = strings.Split(value, " ")
+		}
+	}
+	return result
+}
+
 // The flag default permission value passed to aconfig
 // derived from RELEASE_ACONFIG_FLAG_DEFAULT_PERMISSION
 func (c Config) ReleaseAconfigFlagDefaultPermission() string {
@@ -229,6 +253,11 @@
 	return c.config.productVariables.GetBuildFlagBool("RELEASE_NDK_ABI_MONITORED")
 }
 
+// Enable read flag from new storage, for C/C++
+func (c Config) ReleaseReadFromNewStorageCc() bool {
+	return c.config.productVariables.GetBuildFlagBool("RELEASE_READ_FROM_NEW_STORAGE_CC")
+}
+
 func (c Config) ReleaseHiddenApiExportableStubs() bool {
 	return c.config.productVariables.GetBuildFlagBool("RELEASE_HIDDEN_API_EXPORTABLE_STUBS") ||
 		Bool(c.config.productVariables.HiddenapiExportableStubs)
@@ -365,6 +394,7 @@
 	} else {
 		// Make a decoder for it
 		jsonDecoder := json.NewDecoder(configFileReader)
+		jsonDecoder.DisallowUnknownFields()
 		err = jsonDecoder.Decode(configurable)
 		if err != nil {
 			return fmt.Errorf("config file: %s did not parse correctly: %s", filename, err.Error())
@@ -407,7 +437,7 @@
 			proptools.StringPtr(String(configurable.Platform_sdk_codename))
 	}
 
-	return saveToBazelConfigFile(configurable, filepath.Dir(filename))
+	return nil
 }
 
 // atomically writes the config file in case two copies of soong_build are running simultaneously
@@ -441,81 +471,6 @@
 	return nil
 }
 
-type productVariableStarlarkRepresentation struct {
-	soongType   string
-	selectable  bool
-	archVariant bool
-}
-
-func saveToBazelConfigFile(config *ProductVariables, outDir string) error {
-	dir := filepath.Join(outDir, bazel.SoongInjectionDirName, "product_config")
-	err := createDirIfNonexistent(dir, os.ModePerm)
-	if err != nil {
-		return fmt.Errorf("Could not create dir %s: %s", dir, err)
-	}
-
-	allProductVariablesType := reflect.TypeOf((*ProductVariables)(nil)).Elem()
-	productVariablesInfo := make(map[string]productVariableStarlarkRepresentation)
-	p := variableProperties{}
-	t := reflect.TypeOf(p.Product_variables)
-	for i := 0; i < t.NumField(); i++ {
-		f := t.Field(i)
-		archVariant := proptools.HasTag(f, "android", "arch_variant")
-		if mainProductVariablesStructField, ok := allProductVariablesType.FieldByName(f.Name); ok {
-			productVariablesInfo[f.Name] = productVariableStarlarkRepresentation{
-				soongType:   stringRepresentationOfSimpleType(mainProductVariablesStructField.Type),
-				selectable:  true,
-				archVariant: archVariant,
-			}
-		} else {
-			panic("Unknown variable " + f.Name)
-		}
-	}
-
-	err = pathtools.WriteFileIfChanged(filepath.Join(dir, "product_variable_constants.bzl"), []byte(fmt.Sprintf(`
-# product_var_constant_info is a map of product variables to information about them. The fields are:
-# - soongType: The type of the product variable as it appears in soong's ProductVariables struct.
-#              examples are string, bool, int, *bool, *string, []string, etc. This may be an overly
-#              conservative estimation of the type, for example a *bool could oftentimes just be a
-#              bool that defaults to false.
-# - selectable: if this product variable can be selected on in Android.bp/build files. This means
-#               it's listed in the "variableProperties" soong struct. Currently all variables in
-#               this list are selectable because we only need the selectable ones at the moment,
-#               but the list may be expanded later.
-# - archVariant: If the variable is tagged as arch variant in the "variableProperties" struct.
-product_var_constant_info = %s
-product_var_constraints = [k for k, v in product_var_constant_info.items() if v.selectable]
-arch_variant_product_var_constraints = [k for k, v in product_var_constant_info.items() if v.selectable and v.archVariant]
-`, starlark_fmt.PrintAny(productVariablesInfo, 0))), 0644)
-	if err != nil {
-		return fmt.Errorf("Could not write .bzl config file %s", err)
-	}
-	err = pathtools.WriteFileIfChanged(filepath.Join(dir, "BUILD"),
-		[]byte(bazel.GeneratedBazelFileWarning), 0644)
-	if err != nil {
-		return fmt.Errorf("Could not write BUILD config file %s", err)
-	}
-
-	return nil
-}
-
-func stringRepresentationOfSimpleType(ty reflect.Type) string {
-	switch ty.Kind() {
-	case reflect.String:
-		return "string"
-	case reflect.Bool:
-		return "bool"
-	case reflect.Int:
-		return "int"
-	case reflect.Slice:
-		return "[]" + stringRepresentationOfSimpleType(ty.Elem())
-	case reflect.Pointer:
-		return "*" + stringRepresentationOfSimpleType(ty.Elem())
-	default:
-		panic("unimplemented type: " + ty.Kind().String())
-	}
-}
-
 // NullConfig returns a mostly empty Config for use by standalone tools like dexpreopt_gen that
 // use the android package.
 func NullConfig(outDir, soongOutDir string) Config {
@@ -813,15 +768,19 @@
 }
 
 func (c *config) IsEnvTrue(key string) bool {
-	value := c.Getenv(key)
+	value := strings.ToLower(c.Getenv(key))
 	return value == "1" || value == "y" || value == "yes" || value == "on" || value == "true"
 }
 
 func (c *config) IsEnvFalse(key string) bool {
-	value := c.Getenv(key)
+	value := strings.ToLower(c.Getenv(key))
 	return value == "0" || value == "n" || value == "no" || value == "off" || value == "false"
 }
 
+func (c *config) TargetsJava21() bool {
+	return c.IsEnvTrue("EXPERIMENTAL_TARGET_JAVA_VERSION_21")
+}
+
 // EnvDeps returns the environment variables this build depends on. The first
 // call to this function blocks future reads from the environment.
 func (c *config) EnvDeps() map[string]string {
@@ -847,6 +806,17 @@
 	return Bool(c.productVariables.DisplayBuildNumber)
 }
 
+// BuildFingerprintFile returns the path to a text file containing metadata
+// representing the current build's fingerprint.
+//
+// Rules that want to reference the build fingerprint should read from this file
+// without depending on it. They will run whenever their other dependencies
+// require them to run and get the current build fingerprint. This ensures they
+// don't rebuild on every incremental build when the build number changes.
+func (c *config) BuildFingerprintFile(ctx PathContext) Path {
+	return PathForArbitraryOutput(ctx, "target", "product", c.DeviceName(), String(c.productVariables.BuildFingerprintFile))
+}
+
 // BuildNumberFile returns the path to a text file containing metadata
 // representing the current build's number.
 //
@@ -1085,6 +1055,22 @@
 	return defaultDir.Join(ctx, "testkey.x509.pem"), defaultDir.Join(ctx, "testkey.pk8")
 }
 
+func (c *config) ExtraOtaKeys(ctx PathContext, recovery bool) []SourcePath {
+	var otaKeys []string
+	if recovery {
+		otaKeys = c.productVariables.ExtraOtaRecoveryKeys
+	} else {
+		otaKeys = c.productVariables.ExtraOtaKeys
+	}
+
+	otaPaths := make([]SourcePath, len(otaKeys))
+	for i, key := range otaKeys {
+		otaPaths[i] = PathForSource(ctx, key+".x509.pem")
+	}
+
+	return otaPaths
+}
+
 func (c *config) BuildKeys() string {
 	defaultCert := String(c.productVariables.DefaultAppCertificate)
 	if defaultCert == "" || defaultCert == filepath.Join(testKeyDir, "testkey") {
@@ -1324,10 +1310,6 @@
 	return c.productVariables.SourceRootDirs
 }
 
-func (c *config) IncludeTags() []string {
-	return c.productVariables.IncludeTags
-}
-
 func (c *config) HostStaticBinaries() bool {
 	return Bool(c.productVariables.HostStaticBinaries)
 }
@@ -1386,10 +1368,6 @@
 	return String(c.productVariables.PrebuiltHiddenApiDir)
 }
 
-func (c *config) IsVndkDeprecated() bool {
-	return !Bool(c.productVariables.KeepVndk)
-}
-
 func (c *config) VendorApiLevel() string {
 	return String(c.productVariables.VendorApiLevel)
 }
@@ -1457,10 +1435,6 @@
 	return c.config.productVariables.ExtraVndkVersions
 }
 
-func (c *deviceConfig) VndkUseCoreVariant() bool {
-	return Bool(c.config.productVariables.VndkUseCoreVariant) && Bool(c.config.productVariables.KeepVndk)
-}
-
 func (c *deviceConfig) SystemSdkVersions() []string {
 	return c.config.productVariables.DeviceSystemSdkVersions
 }
@@ -1907,10 +1881,10 @@
 }
 
 func (c *deviceConfig) ShippingApiLevel() ApiLevel {
-	if c.config.productVariables.ShippingApiLevel == nil {
+	if c.config.productVariables.Shipping_api_level == nil {
 		return NoneApiLevel
 	}
-	apiLevel, _ := strconv.Atoi(*c.config.productVariables.ShippingApiLevel)
+	apiLevel, _ := strconv.Atoi(*c.config.productVariables.Shipping_api_level)
 	return uncheckedFinalApiLevel(apiLevel)
 }
 
@@ -1958,6 +1932,10 @@
 	return c.config.productVariables.BuildBrokenDontCheckSystemSdk
 }
 
+func (c *deviceConfig) BuildBrokenDupSysprop() bool {
+	return c.config.productVariables.BuildBrokenDupSysprop
+}
+
 func (c *config) BuildWarningBadOptionalUsesLibsAllowlist() []string {
 	return c.productVariables.BuildWarningBadOptionalUsesLibsAllowlist
 }
@@ -2099,16 +2077,19 @@
 		"RELEASE_APEX_CONTRIBUTIONS_IPSEC",
 		"RELEASE_APEX_CONTRIBUTIONS_MEDIA",
 		"RELEASE_APEX_CONTRIBUTIONS_MEDIAPROVIDER",
+		"RELEASE_APEX_CONTRIBUTIONS_MODULE_METADATA",
 		"RELEASE_APEX_CONTRIBUTIONS_NETWORKSTACKGOOGLE",
 		"RELEASE_APEX_CONTRIBUTIONS_NEURALNETWORKS",
 		"RELEASE_APEX_CONTRIBUTIONS_ONDEVICEPERSONALIZATION",
 		"RELEASE_APEX_CONTRIBUTIONS_PERMISSION",
+		"RELEASE_APEX_CONTRIBUTIONS_PRIMARY_LIBS",
 		"RELEASE_APEX_CONTRIBUTIONS_REMOTEKEYPROVISIONING",
 		"RELEASE_APEX_CONTRIBUTIONS_RESOLV",
 		"RELEASE_APEX_CONTRIBUTIONS_SCHEDULING",
 		"RELEASE_APEX_CONTRIBUTIONS_SDKEXTENSIONS",
 		"RELEASE_APEX_CONTRIBUTIONS_SWCODEC",
 		"RELEASE_APEX_CONTRIBUTIONS_STATSD",
+		"RELEASE_APEX_CONTRIBUTIONS_TELEMETRY_TVP",
 		"RELEASE_APEX_CONTRIBUTIONS_TZDATA",
 		"RELEASE_APEX_CONTRIBUTIONS_UWB",
 		"RELEASE_APEX_CONTRIBUTIONS_WIFI",
@@ -2146,3 +2127,11 @@
 func (c *config) OemProperties() []string {
 	return c.productVariables.OemProperties
 }
+
+func (c *config) UseDebugArt() bool {
+	if c.productVariables.ArtTargetIncludeDebugBuild != nil {
+		return Bool(c.productVariables.ArtTargetIncludeDebugBuild)
+	}
+
+	return Bool(c.productVariables.Eng)
+}
diff --git a/android/config_test.go b/android/config_test.go
index 7d327a2..ca7c7f8 100644
--- a/android/config_test.go
+++ b/android/config_test.go
@@ -125,6 +125,43 @@
 	}
 }
 
+func TestReleaseAconfigExtraReleaseConfigs(t *testing.T) {
+	testCases := []struct {
+		name     string
+		flag     string
+		expected []string
+	}{
+		{
+			name:     "empty",
+			flag:     "",
+			expected: []string{},
+		},
+		{
+			name:     "specified",
+			flag:     "bar foo",
+			expected: []string{"bar", "foo"},
+		},
+		{
+			name:     "duplicates",
+			flag:     "foo bar foo",
+			expected: []string{"foo", "bar"},
+		},
+	}
+
+	for _, tc := range testCases {
+		fixture := GroupFixturePreparers(
+			FixtureModifyProductVariables(func(vars FixtureProductVariables) {
+				if vars.BuildFlags == nil {
+					vars.BuildFlags = make(map[string]string)
+				}
+				vars.BuildFlags["RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS"] = tc.flag
+			}),
+		)
+		actual := fixture.RunTest(t).Config.ReleaseAconfigExtraReleaseConfigs()
+		AssertArrayString(t, tc.name, tc.expected, actual)
+	}
+}
+
 func TestConfiguredJarList(t *testing.T) {
 	list1 := CreateTestConfiguredJarList([]string{"apex1:jarA"})
 
diff --git a/android/configurable_properties.go b/android/configurable_properties.go
new file mode 100644
index 0000000..dad42fa
--- /dev/null
+++ b/android/configurable_properties.go
@@ -0,0 +1,28 @@
+package android
+
+import "github.com/google/blueprint/proptools"
+
+// CreateSelectOsToBool is a utility function that makes it easy to create a
+// Configurable property value that maps from os to a bool. Use an empty string
+// to indicate a "default" case.
+func CreateSelectOsToBool(cases map[string]*bool) proptools.Configurable[bool] {
+	var resultCases []proptools.ConfigurableCase[bool]
+	for pattern, value := range cases {
+		if pattern == "" {
+			resultCases = append(resultCases, proptools.NewConfigurableCase(
+				[]proptools.ConfigurablePattern{proptools.NewDefaultConfigurablePattern()},
+				value,
+			))
+		} else {
+			resultCases = append(resultCases, proptools.NewConfigurableCase(
+				[]proptools.ConfigurablePattern{proptools.NewStringConfigurablePattern(pattern)},
+				value,
+			))
+		}
+	}
+
+	return proptools.NewConfigurable(
+		[]proptools.ConfigurableCondition{proptools.NewConfigurableCondition("os", nil)},
+		resultCases,
+	)
+}
diff --git a/android/container.go b/android/container.go
new file mode 100644
index 0000000..c4fdd9c
--- /dev/null
+++ b/android/container.go
@@ -0,0 +1,233 @@
+// 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 (
+	"reflect"
+	"slices"
+
+	"github.com/google/blueprint"
+)
+
+type StubsAvailableModule interface {
+	IsStubsModule() bool
+}
+
+// Returns true if the dependency module is a stubs module
+var depIsStubsModule = func(_ ModuleContext, _, dep Module) bool {
+	if stubsModule, ok := dep.(StubsAvailableModule); ok {
+		return stubsModule.IsStubsModule()
+	}
+	return false
+}
+
+// Labels of exception functions, which are used to determine special dependencies that allow
+// otherwise restricted inter-container dependencies
+type exceptionHandleFuncLabel int
+
+const (
+	checkStubs exceptionHandleFuncLabel = iota
+)
+
+// Functions cannot be used as a value passed in providers, because functions are not
+// hashable. As a workaround, the exceptionHandleFunc enum values are passed using providers,
+// and the corresponding functions are called from this map.
+var exceptionHandleFunctionsTable = map[exceptionHandleFuncLabel]func(ModuleContext, Module, Module) bool{
+	checkStubs: depIsStubsModule,
+}
+
+type InstallableModule interface {
+	EnforceApiContainerChecks() bool
+}
+
+type restriction struct {
+	// container of the dependency
+	dependency *container
+
+	// Error message to be emitted to the user when the dependency meets this restriction
+	errorMessage string
+
+	// List of labels of allowed exception functions that allows bypassing this restriction.
+	// If any of the functions mapped to each labels returns true, this dependency would be
+	// considered allowed and an error will not be thrown.
+	allowedExceptions []exceptionHandleFuncLabel
+}
+type container struct {
+	// The name of the container i.e. partition, api domain
+	name string
+
+	// Map of dependency restricted containers.
+	restricted []restriction
+}
+
+var (
+	VendorContainer = &container{
+		name:       VendorVariation,
+		restricted: nil,
+	}
+	SystemContainer = &container{
+		name: "system",
+		restricted: []restriction{
+			{
+				dependency: VendorContainer,
+				errorMessage: "Module belonging to the system partition other than HALs is " +
+					"not allowed to depend on the vendor partition module, in order to support " +
+					"independent development/update cycles and to support the Generic System " +
+					"Image. Try depending on HALs, VNDK or AIDL instead.",
+				allowedExceptions: []exceptionHandleFuncLabel{},
+			},
+		},
+	}
+	ProductContainer = &container{
+		name: ProductVariation,
+		restricted: []restriction{
+			{
+				dependency: VendorContainer,
+				errorMessage: "Module belonging to the product partition is not allowed to " +
+					"depend on the vendor partition module, as this may lead to security " +
+					"vulnerabilities. Try depending on the HALs or utilize AIDL instead.",
+				allowedExceptions: []exceptionHandleFuncLabel{},
+			},
+		},
+	}
+	ApexContainer = initializeApexContainer()
+	CtsContainer  = &container{
+		name: "cts",
+		restricted: []restriction{
+			{
+				dependency: SystemContainer,
+				errorMessage: "CTS module should not depend on the modules belonging to the " +
+					"system partition, including \"framework\". Depending on the system " +
+					"partition may lead to disclosure of implementation details and regression " +
+					"due to API changes across platform versions. Try depending on the stubs instead.",
+				allowedExceptions: []exceptionHandleFuncLabel{checkStubs},
+			},
+		},
+	}
+)
+
+func initializeApexContainer() *container {
+	apexContainer := &container{
+		name: "apex",
+		restricted: []restriction{
+			{
+				dependency: SystemContainer,
+				errorMessage: "Module belonging to Apex(es) is not allowed to depend on the " +
+					"modules belonging to the system partition. Either statically depend on the " +
+					"module or convert the depending module to java_sdk_library and depend on " +
+					"the stubs.",
+				allowedExceptions: []exceptionHandleFuncLabel{checkStubs},
+			},
+		},
+	}
+
+	apexContainer.restricted = append(apexContainer.restricted, restriction{
+		dependency: apexContainer,
+		errorMessage: "Module belonging to Apex(es) is not allowed to depend on the " +
+			"modules belonging to other Apex(es). Either include the depending " +
+			"module in the Apex or convert the depending module to java_sdk_library " +
+			"and depend on its stubs.",
+		allowedExceptions: []exceptionHandleFuncLabel{checkStubs},
+	})
+
+	return apexContainer
+}
+
+type ContainersInfo struct {
+	belongingContainers []*container
+
+	belongingApexes []ApexInfo
+}
+
+func (c *ContainersInfo) BelongingContainers() []*container {
+	return c.belongingContainers
+}
+
+var ContainersInfoProvider = blueprint.NewProvider[ContainersInfo]()
+
+// Determines if the module can be installed in the system partition or not.
+// Logic is identical to that of modulePartition(...) defined in paths.go
+func installInSystemPartition(ctx ModuleContext) bool {
+	module := ctx.Module()
+	return !module.InstallInTestcases() &&
+		!module.InstallInData() &&
+		!module.InstallInRamdisk() &&
+		!module.InstallInVendorRamdisk() &&
+		!module.InstallInDebugRamdisk() &&
+		!module.InstallInRecovery() &&
+		!module.InstallInVendor() &&
+		!module.InstallInOdm() &&
+		!module.InstallInProduct() &&
+		determineModuleKind(module.base(), ctx.blueprintBaseModuleContext()) == platformModule
+}
+
+func generateContainerInfo(ctx ModuleContext) ContainersInfo {
+	inSystem := installInSystemPartition(ctx)
+	inProduct := ctx.Module().InstallInProduct()
+	inVendor := ctx.Module().InstallInVendor()
+	inCts := false
+	inApex := false
+
+	if m, ok := ctx.Module().(ImageInterface); ok {
+		inProduct = inProduct || m.ProductVariantNeeded(ctx)
+		inVendor = inVendor || m.VendorVariantNeeded(ctx)
+	}
+
+	props := ctx.Module().GetProperties()
+	for _, prop := range props {
+		val := reflect.ValueOf(prop).Elem()
+		if val.Kind() == reflect.Struct {
+			testSuites := val.FieldByName("Test_suites")
+			if testSuites.IsValid() && testSuites.Kind() == reflect.Slice && slices.Contains(testSuites.Interface().([]string), "cts") {
+				inCts = true
+			}
+		}
+	}
+
+	var belongingApexes []ApexInfo
+	if apexInfo, ok := ModuleProvider(ctx, AllApexInfoProvider); ok {
+		belongingApexes = apexInfo.ApexInfos
+		inApex = true
+	}
+
+	containers := []*container{}
+	if inSystem {
+		containers = append(containers, SystemContainer)
+	}
+	if inProduct {
+		containers = append(containers, ProductContainer)
+	}
+	if inVendor {
+		containers = append(containers, VendorContainer)
+	}
+	if inCts {
+		containers = append(containers, CtsContainer)
+	}
+	if inApex {
+		containers = append(containers, ApexContainer)
+	}
+
+	return ContainersInfo{
+		belongingContainers: containers,
+		belongingApexes:     belongingApexes,
+	}
+}
+
+func setContainerInfo(ctx ModuleContext) {
+	if _, ok := ctx.Module().(InstallableModule); ok {
+		containersInfo := generateContainerInfo(ctx)
+		SetProvider(ctx, ContainersInfoProvider, containersInfo)
+	}
+}
diff --git a/android/early_module_context.go b/android/early_module_context.go
index cf1b5fc..23f4c90 100644
--- a/android/early_module_context.go
+++ b/android/early_module_context.go
@@ -173,5 +173,5 @@
 }
 
 func (e *earlyModuleContext) OtherModulePropertyErrorf(module Module, property string, fmt string, args ...interface{}) {
-	e.EarlyModuleContext.OtherModulePropertyErrorf(module, property, fmt, args)
+	e.EarlyModuleContext.OtherModulePropertyErrorf(module, property, fmt, args...)
 }
diff --git a/android/filegroup.go b/android/filegroup.go
index 86d7b4b..ff0f74e 100644
--- a/android/filegroup.go
+++ b/android/filegroup.go
@@ -19,6 +19,7 @@
 	"strings"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
 )
 
 func init() {
@@ -36,9 +37,9 @@
 
 type fileGroupProperties struct {
 	// srcs lists files that will be included in this filegroup
-	Srcs []string `android:"path"`
+	Srcs proptools.Configurable[[]string] `android:"path"`
 
-	Exclude_srcs []string `android:"path"`
+	Exclude_srcs proptools.Configurable[[]string] `android:"path"`
 
 	// 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,
@@ -56,9 +57,6 @@
 	DefaultableModuleBase
 	properties fileGroupProperties
 	srcs       Paths
-
-	// Aconfig files for all transitive deps.  Also exposed via TransitiveDeclarationsInfo
-	mergedAconfigFiles map[string]Paths
 }
 
 var _ SourceFileProducer = (*fileGroup)(nil)
@@ -92,12 +90,11 @@
 }
 
 func (fg *fileGroup) GenerateAndroidBuildActions(ctx ModuleContext) {
-	fg.srcs = PathsForModuleSrcExcludes(ctx, fg.properties.Srcs, fg.properties.Exclude_srcs)
+	fg.srcs = PathsForModuleSrcExcludes(ctx, fg.properties.Srcs.GetOrDefault(ctx, nil), fg.properties.Exclude_srcs.GetOrDefault(ctx, nil))
 	if fg.properties.Path != nil {
 		fg.srcs = PathsWithModuleSrcSubDir(ctx, fg.srcs, String(fg.properties.Path))
 	}
 	SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: fg.srcs.Strings()})
-	CollectDependencyAconfigFiles(ctx, &fg.mergedAconfigFiles)
 
 	var aconfigDeclarations []string
 	var intermediateCacheOutputPaths Paths
diff --git a/android/gen_notice.go b/android/gen_notice.go
index 1acc638..9adde9e 100644
--- a/android/gen_notice.go
+++ b/android/gen_notice.go
@@ -62,7 +62,7 @@
 				if mod == nil {
 					continue
 				}
-				if !mod.Enabled() { // don't depend on variants without build rules
+				if !mod.Enabled(ctx) { // don't depend on variants without build rules
 					continue
 				}
 				modules = append(modules, mod)
@@ -176,6 +176,7 @@
 	}
 	out := m.getStem() + m.getSuffix()
 	m.output = PathForModuleOut(ctx, out).OutputPath
+	ctx.SetOutputFiles(Paths{m.output}, "")
 }
 
 func GenNoticeFactory() Module {
@@ -193,16 +194,6 @@
 	return module
 }
 
-var _ OutputFileProducer = (*genNoticeModule)(nil)
-
-// Implements OutputFileProducer
-func (m *genNoticeModule) OutputFiles(tag string) (Paths, error) {
-	if tag == "" {
-		return Paths{m.output}, nil
-	}
-	return nil, fmt.Errorf("unrecognized tag %q", tag)
-}
-
 var _ AndroidMkEntriesProvider = (*genNoticeModule)(nil)
 
 // Implements AndroidMkEntriesProvider
diff --git a/android/image.go b/android/image.go
index bc6b8cd..0f03107 100644
--- a/android/image.go
+++ b/android/image.go
@@ -19,6 +19,12 @@
 	// ImageMutatorBegin is called before any other method in the ImageInterface.
 	ImageMutatorBegin(ctx BaseModuleContext)
 
+	// VendorVariantNeeded should return true if the module needs a vendor variant (installed on the vendor image).
+	VendorVariantNeeded(ctx BaseModuleContext) bool
+
+	// ProductVariantNeeded should return true if the module needs a product variant (installed on the product image).
+	ProductVariantNeeded(ctx BaseModuleContext) bool
+
 	// CoreVariantNeeded should return true if the module needs a core variant (installed on the system image).
 	CoreVariantNeeded(ctx BaseModuleContext) bool
 
@@ -44,12 +50,19 @@
 	ExtraImageVariations(ctx BaseModuleContext) []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 and "module" is the newly created
-	// variant itself.
-	SetImageVariation(ctx BaseModuleContext, variation string, module Module)
+	// module, "variation" is the name of the newly created variant. "variation" is set on the receiver.
+	SetImageVariation(ctx BaseModuleContext, variation string)
 }
 
 const (
+	// VendorVariation is the variant name used for /vendor code that does not
+	// compile against the VNDK.
+	VendorVariation string = "vendor"
+
+	// ProductVariation is the variant name used for /product code that does not
+	// compile against the VNDK.
+	ProductVariation string = "product"
+
 	// CoreVariation is the variant used for framework-private libraries, or
 	// SDK libraries. (which framework-private libraries can use), which
 	// will be installed to the system image.
@@ -95,6 +108,12 @@
 		if m.RecoveryVariantNeeded(ctx) {
 			variations = append(variations, RecoveryVariation)
 		}
+		if m.VendorVariantNeeded(ctx) {
+			variations = append(variations, VendorVariation)
+		}
+		if m.ProductVariantNeeded(ctx) {
+			variations = append(variations, ProductVariation)
+		}
 
 		extraVariations := m.ExtraImageVariations(ctx)
 		variations = append(variations, extraVariations...)
@@ -106,7 +125,7 @@
 		mod := ctx.CreateVariations(variations...)
 		for i, v := range variations {
 			mod[i].base().setImageVariation(v)
-			m.SetImageVariation(ctx, v, mod[i])
+			mod[i].(ImageInterface).SetImageVariation(ctx, v)
 		}
 	}
 }
diff --git a/android/license_metadata.go b/android/license_metadata.go
index eabb1b1..8056189 100644
--- a/android/license_metadata.go
+++ b/android/license_metadata.go
@@ -36,7 +36,7 @@
 func buildLicenseMetadata(ctx ModuleContext, licenseMetadataFile WritablePath) {
 	base := ctx.Module().base()
 
-	if !base.Enabled() {
+	if !base.Enabled(ctx) {
 		return
 	}
 
@@ -45,8 +45,7 @@
 	}
 
 	var outputFiles Paths
-	if outputFileProducer, ok := ctx.Module().(OutputFileProducer); ok {
-		outputFiles, _ = outputFileProducer.OutputFiles("")
+	if outputFiles, err := outputFilesForModule(ctx, ctx.Module(), ""); err == nil {
 		outputFiles = PathsIfNonNil(outputFiles...)
 	}
 
@@ -69,7 +68,7 @@
 		if dep == nil {
 			return
 		}
-		if !dep.Enabled() {
+		if !dep.Enabled(ctx) {
 			return
 		}
 
@@ -195,7 +194,7 @@
 
 	for _, path := range paths {
 		switch path.Ext() {
-		case ".zip", ".tar", ".tgz", ".tar.gz", ".img", ".srcszip", ".apex":
+		case ".zip", ".tar", ".tgz", ".tar.gz", ".img", ".srcszip", ".apex", ".capex":
 			return true
 		}
 	}
diff --git a/android/logtags.go b/android/logtags.go
new file mode 100644
index 0000000..d11cccf
--- /dev/null
+++ b/android/logtags.go
@@ -0,0 +1,56 @@
+// 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"
+
+func init() {
+	RegisterParallelSingletonType("logtags", LogtagsSingleton)
+}
+
+type LogtagsInfo struct {
+	Logtags Paths
+}
+
+var LogtagsProviderKey = blueprint.NewProvider[*LogtagsInfo]()
+
+func LogtagsSingleton() Singleton {
+	return &logtagsSingleton{}
+}
+
+type logtagsSingleton struct{}
+
+func MergedLogtagsPath(ctx PathContext) OutputPath {
+	return PathForIntermediates(ctx, "all-event-log-tags.txt")
+}
+
+func (l *logtagsSingleton) GenerateBuildActions(ctx SingletonContext) {
+	var allLogtags Paths
+	ctx.VisitAllModules(func(module Module) {
+		if !module.ExportedToMake() {
+			return
+		}
+		if logtagsInfo, ok := SingletonModuleProvider(ctx, module, LogtagsProviderKey); ok {
+			allLogtags = append(allLogtags, logtagsInfo.Logtags...)
+		}
+	})
+
+	builder := NewRuleBuilder(pctx, ctx)
+	builder.Command().
+		BuiltTool("merge-event-log-tags").
+		FlagWithOutput("-o ", MergedLogtagsPath(ctx)).
+		Inputs(SortedUniquePaths(allLogtags))
+	builder.Build("all-event-log-tags.txt", "merge logtags")
+}
diff --git a/android/makevars.go b/android/makevars.go
index 4039e7e..f92f458 100644
--- a/android/makevars.go
+++ b/android/makevars.go
@@ -98,6 +98,7 @@
 	BlueprintFile(module blueprint.Module) string
 
 	ModuleErrorf(module blueprint.Module, format string, args ...interface{})
+	OtherModulePropertyErrorf(module Module, property, format string, args ...interface{})
 	Errorf(format string, args ...interface{})
 
 	VisitAllModules(visit func(Module))
@@ -265,7 +266,7 @@
 	}
 
 	ctx.VisitAllModules(func(m Module) {
-		if provider, ok := m.(ModuleMakeVarsProvider); ok && m.Enabled() {
+		if provider, ok := m.(ModuleMakeVarsProvider); ok && m.Enabled(ctx) {
 			mctx := &makeVarsContext{
 				SingletonContext: ctx,
 			}
@@ -472,7 +473,7 @@
 
 # Values written by Soong to generate install rules that can be amended by Kati.
 
-
+EXTRA_INSTALL_ZIPS :=
 `)
 
 	preserveSymlinksFlag := "-d"
@@ -506,9 +507,13 @@
 		if extraFiles := install.extraFiles; extraFiles != nil {
 			fmt.Fprintf(buf, "\t( unzip -qDD -d '%s' '%s' 2>&1 | grep -v \"zipfile is empty\"; exit $${PIPESTATUS[0]} ) || \\\n", extraFiles.dir.String(), extraFiles.zip.String())
 			fmt.Fprintf(buf, "\t  ( code=$$?; if [ $$code -ne 0 -a $$code -ne 1 ]; then exit $$code; fi )\n")
+			fmt.Fprintf(buf, "EXTRA_INSTALL_ZIPS += %s:%s:%s\n", install.to.String(), extraFiles.dir.String(), extraFiles.zip.String())
 		}
+
 		fmt.Fprintln(buf)
 	}
+	fmt.Fprintf(buf, ".KATI_READONLY := EXTRA_INSTALL_ZIPS\n")
+	fmt.Fprintf(buf, "$(KATI_visibility_prefix EXTRA_INSTALL_ZIPS,build/make/core/Makefile)\n")
 
 	for _, symlink := range symlinks {
 		fmt.Fprintf(buf, "%s:", symlink.to.String())
diff --git a/android/module.go b/android/module.go
index 5fe379c..37e26f9 100644
--- a/android/module.go
+++ b/android/module.go
@@ -15,9 +15,6 @@
 package android
 
 import (
-	"crypto/md5"
-	"encoding/hex"
-	"encoding/json"
 	"fmt"
 	"net/url"
 	"path/filepath"
@@ -26,8 +23,6 @@
 	"sort"
 	"strings"
 
-	"android/soong/bazel"
-
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
@@ -60,7 +55,7 @@
 
 	base() *ModuleBase
 	Disable()
-	Enabled() bool
+	Enabled(ctx ConfigAndErrorContext) bool
 	Target() Target
 	MultiTargets() []Target
 
@@ -113,7 +108,7 @@
 	// Get information about the properties that can contain visibility rules.
 	visibilityProperties() []visibilityProperty
 
-	RequiredModuleNames() []string
+	RequiredModuleNames(ctx ConfigAndErrorContext) []string
 	HostRequiredModuleNames() []string
 	TargetRequiredModuleNames() []string
 
@@ -249,31 +244,6 @@
 	return l[:k+1]
 }
 
-// soongConfigTrace holds all references to VendorVars. Uses []string for blueprint:"mutated"
-type soongConfigTrace struct {
-	Bools   []string `json:",omitempty"`
-	Strings []string `json:",omitempty"`
-	IsSets  []string `json:",omitempty"`
-}
-
-func (c *soongConfigTrace) isEmpty() bool {
-	return len(c.Bools) == 0 && len(c.Strings) == 0 && len(c.IsSets) == 0
-}
-
-// Returns hash of serialized trace records (empty string if there's no trace recorded)
-func (c *soongConfigTrace) hash() string {
-	// Use MD5 for speed. We don't care collision or preimage attack
-	if c.isEmpty() {
-		return ""
-	}
-	j, err := json.Marshal(c)
-	if err != nil {
-		panic(fmt.Errorf("json marshal of %#v failed: %#v", *c, err))
-	}
-	hash := md5.Sum(j)
-	return hex.EncodeToString(hash[:])
-}
-
 type nameProperties struct {
 	// The name of the module.  Must be unique across all modules.
 	Name *string
@@ -287,7 +257,7 @@
 	// but are not usually required (e.g. superceded by a prebuilt) should not be
 	// disabled as that will prevent them from being built by the checkbuild target
 	// and so prevent early detection of changes that have broken those modules.
-	Enabled *bool `android:"arch_variant"`
+	Enabled proptools.Configurable[bool] `android:"arch_variant,replace_instead_of_append"`
 
 	// Controls the visibility of this module to other modules. Allowable values are one or more of
 	// these formats:
@@ -422,7 +392,7 @@
 	Vintf_fragments []string `android:"path"`
 
 	// names of other modules to install if this module is installed
-	Required []string `android:"arch_variant"`
+	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"`
@@ -484,6 +454,11 @@
 	// Set by osMutator.
 	CommonOSVariant bool `blueprint:"mutated"`
 
+	// When set to true, this module is not installed to the full install path (ex: under
+	// out/target/product/<name>/<partition>). It can be installed only to the packaging
+	// modules like android_filesystem.
+	No_full_install *bool
+
 	// When HideFromMake is set to true, no entry for this variant will be emitted in the
 	// generated Android.mk file.
 	HideFromMake bool `blueprint:"mutated"`
@@ -520,14 +495,6 @@
 	// constants in image.go, but can also be set to a custom value by individual module types.
 	ImageVariation string `blueprint:"mutated"`
 
-	// SoongConfigTrace records accesses to VendorVars (soong_config). The trace will be hashed
-	// and used as a subdir of PathForModuleOut.  Note that we mainly focus on incremental
-	// builds among similar products (e.g. aosp_cf_x86_64_phone and aosp_cf_x86_64_foldable),
-	// and there are variables other than soong_config, which isn't captured by soong config
-	// trace, but influence modules among products.
-	SoongConfigTrace     soongConfigTrace `blueprint:"mutated"`
-	SoongConfigTraceHash string           `blueprint:"mutated"`
-
 	// The team (defined by the owner/vendor) who owns the property.
 	Team *string `android:"path"`
 }
@@ -636,6 +603,11 @@
 	Device_supported *bool
 }
 
+type hostCrossProperties struct {
+	// If set to true, build a variant of the module for the host cross.  Defaults to true.
+	Host_cross_supported *bool
+}
+
 type Multilib string
 
 const (
@@ -751,6 +723,10 @@
 		m.AddProperties(&base.hostAndDeviceProperties)
 	}
 
+	if hod&hostCrossSupported != 0 {
+		m.AddProperties(&base.hostCrossProperties)
+	}
+
 	initArchModule(m)
 }
 
@@ -836,6 +812,7 @@
 	distProperties          distProperties
 	variableProperties      interface{}
 	hostAndDeviceProperties hostAndDeviceProperties
+	hostCrossProperties     hostCrossProperties
 
 	// Arch specific versions of structs in GetProperties() prior to
 	// initialization in InitAndroidArchModule, lets call it `generalProperties`.
@@ -844,9 +821,6 @@
 	// archPropRoot that is filled with arch specific values by the arch mutator.
 	archProperties [][]interface{}
 
-	// Properties specific to the Blueprint to BUILD migration.
-	bazelTargetModuleProperties bazel.BazelTargetModuleProperties
-
 	// Information about all the properties on the module that contains visibility rules that need
 	// checking.
 	visibilityPropertyInfo []visibilityProperty
@@ -897,6 +871,9 @@
 	installedInitRcPaths         InstallPaths
 	installedVintfFragmentsPaths InstallPaths
 
+	// Merged Aconfig files for all transitive deps.
+	aconfigFilePaths Paths
+
 	// set of dependency module:location mappings used to populate the license metadata for
 	// apex containers.
 	licenseInstallMap []string
@@ -907,6 +884,14 @@
 	// 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
+
+	// outputFiles stores the output of a module by tag and is used to set
+	// the OutputFilesProvider in GenerateBuildActions
+	outputFiles OutputFilesInfo
+
+	// complianceMetadataInfo is for different module types to dump metadata.
+	// See android.ModuleContext interface.
+	complianceMetadataInfo *ComplianceMetadataInfo
 }
 
 func (m *ModuleBase) AddJSONData(d *map[string]interface{}) {
@@ -1061,12 +1046,19 @@
 		// TODO(jiyong): the Make-side does this only when the required module is a shared
 		// library or a native test.
 		bothInAndroid := ctx.Device() && target.Os.Class == Device
-		nativeArch := InList(ctx.Arch().ArchType.Multilib, []string{"lib32", "lib64"})
+		nativeArch := InList(ctx.Arch().ArchType.Multilib, []string{"lib32", "lib64"}) &&
+			InList(target.Arch.ArchType.Multilib, []string{"lib32", "lib64"})
 		sameBitness := ctx.Arch().ArchType.Multilib == target.Arch.ArchType.Multilib
 		if bothInAndroid && nativeArch && !sameBitness {
 			return
 		}
 
+		// ... also don't make a dependency between native bridge arch and non-native bridge
+		// arches. b/342945184
+		if ctx.Target().NativeBridge != target.NativeBridge {
+			return
+		}
+
 		variation := target.Variations()
 		if ctx.OtherModuleFarDependencyVariantExists(variation, depName) {
 			ctx.AddFarVariationDependencies(variation, RequiredDepTag, depName)
@@ -1082,7 +1074,7 @@
 	hostTargets = append(hostTargets, ctx.Config().BuildOSCommonTarget)
 
 	if ctx.Device() {
-		for _, depName := range ctx.Module().RequiredModuleNames() {
+		for _, depName := range ctx.Module().RequiredModuleNames(ctx) {
 			for _, target := range deviceTargets {
 				addDep(target, depName)
 			}
@@ -1095,7 +1087,7 @@
 	}
 
 	if ctx.Host() {
-		for _, depName := range ctx.Module().RequiredModuleNames() {
+		for _, depName := range ctx.Module().RequiredModuleNames(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).
@@ -1216,27 +1208,16 @@
 		// the special tag name which represents that.
 		tag := proptools.StringDefault(dist.Tag, DefaultDistTag)
 
-		if outputFileProducer, ok := m.module.(OutputFileProducer); ok {
-			// Call the OutputFiles(tag) method to get the paths associated with the tag.
-			distFilesForTag, err := outputFileProducer.OutputFiles(tag)
-
-			// If the tag was not supported and is not DefaultDistTag then it is an error.
-			// Failing to find paths for DefaultDistTag is not an error. It just means
-			// that the module type requires the legacy behavior.
+		distFileForTagFromProvider, err := outputFilesForModuleFromProvider(ctx, m.module, tag)
+		if err != OutputFilesProviderNotSet {
 			if err != nil && tag != DefaultDistTag {
 				ctx.PropertyErrorf("dist.tag", "%s", err.Error())
+			} else {
+				distFiles = distFiles.addPathsForTag(tag, distFileForTagFromProvider...)
+				continue
 			}
-
-			distFiles = distFiles.addPathsForTag(tag, distFilesForTag...)
-		} else if tag != DefaultDistTag {
-			// If the tag was specified then it is an error if the module does not
-			// implement OutputFileProducer because there is no other way of accessing
-			// the paths for the specified tag.
-			ctx.PropertyErrorf("dist.tag",
-				"tag %s not supported because the module does not implement OutputFileProducer", tag)
 		}
 	}
-
 	return distFiles
 }
 
@@ -1328,7 +1309,11 @@
 	// hostEnabled is true if the host_supported property is true or the HostOrDeviceSupported
 	// value has the hostDefault bit set.
 	hostEnabled := proptools.BoolDefault(m.hostAndDeviceProperties.Host_supported, hod&hostDefault != 0)
-	return hod&hostCrossSupported != 0 && hostEnabled
+
+	// Default true for the Host_cross_supported property
+	hostCrossEnabled := proptools.BoolDefault(m.hostCrossProperties.Host_cross_supported, true)
+
+	return hod&hostCrossSupported != 0 && hostEnabled && hostCrossEnabled
 }
 
 func (m *ModuleBase) Platform() bool {
@@ -1392,14 +1377,11 @@
 	return partition
 }
 
-func (m *ModuleBase) Enabled() bool {
+func (m *ModuleBase) Enabled(ctx ConfigAndErrorContext) bool {
 	if m.commonProperties.ForcedDisabled {
 		return false
 	}
-	if m.commonProperties.Enabled == nil {
-		return !m.Os().DefaultDisabled
-	}
-	return *m.commonProperties.Enabled
+	return m.commonProperties.Enabled.GetOrDefault(m.ConfigurableEvaluator(ctx), !m.Os().DefaultDisabled)
 }
 
 func (m *ModuleBase) Disable() {
@@ -1603,8 +1585,8 @@
 	return m.base().commonProperties.ImageVariation == RecoveryVariation
 }
 
-func (m *ModuleBase) RequiredModuleNames() []string {
-	return m.base().commonProperties.Required
+func (m *ModuleBase) RequiredModuleNames(ctx ConfigAndErrorContext) []string {
+	return m.base().commonProperties.Required.GetOrDefault(m.ConfigurableEvaluator(ctx), nil)
 }
 
 func (m *ModuleBase) HostRequiredModuleNames() []string {
@@ -1643,7 +1625,7 @@
 		// 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(a) {
+		if !ctx.Config().KatiEnabled() || !shouldSkipAndroidMkProcessing(ctx, a) {
 			allCheckbuildFiles = append(allCheckbuildFiles, a.checkbuildFiles...)
 		}
 	})
@@ -1756,7 +1738,11 @@
 	}
 }
 
-func (m *ModuleBase) archModuleContextFactory(ctx blueprint.IncomingTransitionContext) archModuleContext {
+type archModuleContextFactoryContext interface {
+	Config() interface{}
+}
+
+func (m *ModuleBase) archModuleContextFactory(ctx archModuleContextFactoryContext) archModuleContext {
 	config := ctx.Config().(Config)
 	target := m.Target()
 	primaryArch := false
@@ -1785,6 +1771,8 @@
 		variables:         make(map[string]string),
 	}
 
+	setContainerInfo(ctx)
+
 	m.licenseMetadataFile = PathForModuleOut(ctx, "meta_lic")
 
 	dependencyInstallFiles, dependencyPackagingSpecs := m.computeInstallDeps(ctx)
@@ -1835,7 +1823,7 @@
 		checkDistProperties(ctx, fmt.Sprintf("dists[%d]", i), &m.distProperties.Dists[i])
 	}
 
-	if m.Enabled() {
+	if m.Enabled(ctx) {
 		// ensure all direct android.Module deps are enabled
 		ctx.VisitDirectDepsBlueprint(func(bm blueprint.Module) {
 			if m, ok := bm.(Module); ok {
@@ -1890,14 +1878,61 @@
 			}
 		}
 
-		m.module.GenerateAndroidBuildActions(ctx)
+		// Call aconfigUpdateAndroidBuildActions to collect merged aconfig files before being used
+		// in m.module.GenerateAndroidBuildActions
+		aconfigUpdateAndroidBuildActions(ctx)
 		if ctx.Failed() {
 			return
 		}
 
-		aconfigUpdateAndroidBuildActions(ctx)
-		if ctx.Failed() {
-			return
+		incrementalAnalysis := false
+		incrementalEnabled := false
+		var cacheKey *blueprint.BuildActionCacheKey = nil
+		var incrementalModule *blueprint.Incremental = nil
+		if ctx.bp.GetIncrementalEnabled() {
+			if im, ok := m.module.(blueprint.Incremental); ok {
+				incrementalModule = &im
+				incrementalEnabled = im.IncrementalSupported()
+				incrementalAnalysis = ctx.bp.GetIncrementalAnalysis() && incrementalEnabled
+			}
+		}
+		if incrementalEnabled {
+			hash, err := proptools.CalculateHash(m.GetProperties())
+			if err != nil {
+				ctx.ModuleErrorf("failed to calculate properties hash: %s", err)
+				return
+			}
+			cacheInput := new(blueprint.BuildActionCacheInput)
+			cacheInput.PropertiesHash = hash
+			ctx.VisitDirectDeps(func(module Module) {
+				cacheInput.ProvidersHash =
+					append(cacheInput.ProvidersHash, ctx.bp.OtherModuleProviderInitialValueHashes(module))
+			})
+			hash, err = proptools.CalculateHash(&cacheInput)
+			if err != nil {
+				ctx.ModuleErrorf("failed to calculate cache input hash: %s", err)
+				return
+			}
+			cacheKey = &blueprint.BuildActionCacheKey{
+				Id:        ctx.bp.ModuleId(),
+				InputHash: hash,
+			}
+		}
+
+		restored := false
+		if incrementalAnalysis && cacheKey != nil {
+			restored = ctx.bp.RestoreBuildActions(cacheKey, incrementalModule)
+		}
+
+		if !restored {
+			m.module.GenerateAndroidBuildActions(ctx)
+			if ctx.Failed() {
+				return
+			}
+		}
+
+		if incrementalEnabled && cacheKey != nil {
+			ctx.bp.CacheBuildActions(cacheKey, incrementalModule)
 		}
 
 		// Create the set of tagged dist files after calling GenerateAndroidBuildActions
@@ -1974,6 +2009,7 @@
 			TargetDependencies: targetRequired,
 			HostDependencies:   hostRequired,
 			Data:               data,
+			Required:           m.RequiredModuleNames(ctx),
 		}
 		SetProvider(ctx, ModuleInfoJSONProvider, m.moduleInfoJSON)
 	}
@@ -1981,6 +2017,12 @@
 	m.buildParams = ctx.buildParams
 	m.ruleParams = ctx.ruleParams
 	m.variables = ctx.variables
+
+	if m.outputFiles.DefaultOutputFiles != nil || m.outputFiles.TaggedOutputFiles != nil {
+		SetProvider(ctx, OutputFilesProvider, m.outputFiles)
+	}
+
+	buildComplianceMetadataProvider(ctx, m)
 }
 
 func SetJarJarPrefixHandler(handler func(ModuleContext)) {
@@ -2136,42 +2178,78 @@
 }
 
 func (e configurationEvalutor) PropertyErrorf(property string, fmt string, args ...interface{}) {
-	e.ctx.OtherModulePropertyErrorf(e.m, property, fmt, args)
+	e.ctx.OtherModulePropertyErrorf(e.m, property, fmt, args...)
 }
 
 func (e configurationEvalutor) EvaluateConfiguration(condition proptools.ConfigurableCondition, property string) proptools.ConfigurableValue {
 	ctx := e.ctx
 	m := e.m
-	switch condition.FunctionName {
-	case "release_variable":
-		if len(condition.Args) != 1 {
-			ctx.OtherModulePropertyErrorf(m, property, "release_variable requires 1 argument, found %d", len(condition.Args))
+	switch condition.FunctionName() {
+	case "release_flag":
+		if condition.NumArgs() != 1 {
+			ctx.OtherModulePropertyErrorf(m, property, "release_flag requires 1 argument, found %d", condition.NumArgs())
 			return proptools.ConfigurableValueUndefined()
 		}
-		if v, ok := ctx.Config().productVariables.BuildFlags[condition.Args[0]]; ok {
-			return proptools.ConfigurableValueString(v)
+		if ty, ok := ctx.Config().productVariables.BuildFlagTypes[condition.Arg(0)]; ok {
+			v := ctx.Config().productVariables.BuildFlags[condition.Arg(0)]
+			switch ty {
+			case "unspecified", "obsolete":
+				return proptools.ConfigurableValueUndefined()
+			case "string":
+				return proptools.ConfigurableValueString(v)
+			case "bool":
+				return proptools.ConfigurableValueBool(v == "true")
+			default:
+				panic("unhandled release flag type: " + ty)
+			}
 		}
 		return proptools.ConfigurableValueUndefined()
 	case "product_variable":
-		// TODO(b/323382414): Might add these on a case-by-case basis
-		ctx.OtherModulePropertyErrorf(m, property, "TODO(b/323382414): Product variables are not yet supported in selects")
-		return proptools.ConfigurableValueUndefined()
-	case "soong_config_variable":
-		if len(condition.Args) != 2 {
-			ctx.OtherModulePropertyErrorf(m, property, "soong_config_variable requires 2 arguments, found %d", len(condition.Args))
+		if condition.NumArgs() != 1 {
+			ctx.OtherModulePropertyErrorf(m, property, "product_variable requires 1 argument, found %d", condition.NumArgs())
 			return proptools.ConfigurableValueUndefined()
 		}
-		namespace := condition.Args[0]
-		variable := condition.Args[1]
+		variable := condition.Arg(0)
+		switch variable {
+		case "debuggable":
+			return proptools.ConfigurableValueBool(ctx.Config().Debuggable())
+		case "use_debug_art":
+			// TODO(b/234351700): Remove once ART does not have separated debug APEX
+			return proptools.ConfigurableValueBool(ctx.Config().UseDebugArt())
+		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))
+			return proptools.ConfigurableValueUndefined()
+		}
+	case "soong_config_variable":
+		if condition.NumArgs() != 2 {
+			ctx.OtherModulePropertyErrorf(m, property, "soong_config_variable requires 2 arguments, found %d", condition.NumArgs())
+			return proptools.ConfigurableValueUndefined()
+		}
+		namespace := condition.Arg(0)
+		variable := condition.Arg(1)
 		if n, ok := ctx.Config().productVariables.VendorVars[namespace]; ok {
 			if v, ok := n[variable]; ok {
-				return proptools.ConfigurableValueString(v)
+				ty := ""
+				if namespaces, ok := ctx.Config().productVariables.VendorVarTypes[namespace]; ok {
+					ty = namespaces[variable]
+				}
+				switch ty {
+				case "":
+					// strings are the default, we don't bother writing them to the soong variables json file
+					return proptools.ConfigurableValueString(v)
+				case "bool":
+					return proptools.ConfigurableValueBool(v == "true")
+				default:
+					panic("unhandled soong config variable type: " + ty)
+				}
+
 			}
 		}
 		return proptools.ConfigurableValueUndefined()
 	case "arch":
-		if len(condition.Args) != 0 {
-			ctx.OtherModulePropertyErrorf(m, property, "arch requires no arguments, found %d", len(condition.Args))
+		if condition.NumArgs() != 0 {
+			ctx.OtherModulePropertyErrorf(m, property, "arch requires no arguments, found %d", condition.NumArgs())
 			return proptools.ConfigurableValueUndefined()
 		}
 		if !m.base().ArchReady() {
@@ -2180,8 +2258,8 @@
 		}
 		return proptools.ConfigurableValueString(m.base().Arch().ArchType.Name)
 	case "os":
-		if len(condition.Args) != 0 {
-			ctx.OtherModulePropertyErrorf(m, property, "os requires no arguments, found %d", len(condition.Args))
+		if condition.NumArgs() != 0 {
+			ctx.OtherModulePropertyErrorf(m, property, "os requires no arguments, found %d", condition.NumArgs())
 			return proptools.ConfigurableValueUndefined()
 		}
 		// the arch mutator runs after the os mutator, we can just use this to enforce that os is ready.
@@ -2194,8 +2272,8 @@
 		// We currently don't have any other boolean variables (we should add support for typing
 		// the soong config variables), so add this fake one for testing the boolean select
 		// functionality.
-		if len(condition.Args) != 0 {
-			ctx.OtherModulePropertyErrorf(m, property, "boolean_var_for_testing requires 0 arguments, found %d", len(condition.Args))
+		if condition.NumArgs() != 0 {
+			ctx.OtherModulePropertyErrorf(m, property, "boolean_var_for_testing requires 0 arguments, found %d", condition.NumArgs())
 			return proptools.ConfigurableValueUndefined()
 		}
 
@@ -2302,7 +2380,7 @@
 	// The name of the module.
 	moduleName string
 
-	// The tag that will be passed to the module's OutputFileProducer.OutputFiles(tag) method.
+	// The tag that will be used to get the specific output file(s).
 	tag string
 }
 
@@ -2356,14 +2434,7 @@
 	Srcs() Paths
 }
 
-// A module that implements OutputFileProducer can be referenced from any property that is tagged with `android:"path"`
-// using the ":module" syntax or ":module{.tag}" syntax and provides a list of output files to be used as if they were
-// listed in the property.
-type OutputFileProducer interface {
-	OutputFiles(tag string) (Paths, error)
-}
-
-// OutputFilesForModule returns the paths from an OutputFileProducer with the given tag.  On error, including if the
+// 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 {
 	paths, err := outputFilesForModule(ctx, module, tag)
@@ -2374,7 +2445,7 @@
 	return paths
 }
 
-// OutputFileForModule returns the path from an OutputFileProducer with the given tag.  On error, including if the
+// 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 {
 	paths, err := outputFilesForModule(ctx, module, tag)
@@ -2410,24 +2481,84 @@
 }
 
 func outputFilesForModule(ctx PathContext, module blueprint.Module, tag string) (Paths, error) {
-	if outputFileProducer, ok := module.(OutputFileProducer); ok {
-		paths, err := outputFileProducer.OutputFiles(tag)
-		if err != nil {
-			return nil, fmt.Errorf("failed to get output file from module %q: %s",
-				pathContextName(ctx, module), err.Error())
-		}
-		return paths, nil
-	} else if sourceFileProducer, ok := module.(SourceFileProducer); ok {
+	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, not an OutputFileProducer, and so does not support tag %q", pathContextName(ctx, module), tag)
+			return nil, fmt.Errorf("module %q is a SourceFileProducer, which does not support tag %q", pathContextName(ctx, module), tag)
 		}
 		paths := sourceFileProducer.Srcs()
 		return paths, nil
 	} else {
-		return nil, fmt.Errorf("module %q is not an OutputFileProducer", pathContextName(ctx, module))
+		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
+// *inter-module-communication*.
+// If mctx module is the same as the param module the output files are obtained
+// 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) {
+	var outputFiles OutputFilesInfo
+	fromProperty := false
+
+	type OutputFilesProviderModuleContext interface {
+		OtherModuleProviderContext
+		Module() Module
+	}
+
+	if mctx, isMctx := ctx.(OutputFilesProviderModuleContext); isMctx {
+		if mctx.Module() != module {
+			outputFiles, _ = OtherModuleProvider(mctx, module, OutputFilesProvider)
+		} else {
+			outputFiles = mctx.Module().base().outputFiles
+			fromProperty = true
+		}
+	} else if cta, isCta := ctx.(*singletonContextAdaptor); isCta {
+		providerData, _ := cta.moduleProvider(module, OutputFilesProvider)
+		outputFiles, _ = providerData.(OutputFilesInfo)
+	} else {
+		return nil, fmt.Errorf("unsupported context %q in method outputFilesForModuleFromProvider", reflect.TypeOf(ctx))
+	}
+
+	if outputFiles.isEmpty() {
+		return nil, OutputFilesProviderNotSet
+	}
+
+	if tag == "" {
+		return outputFiles.DefaultOutputFiles, nil
+	} else if taggedOutputFiles, hasTag := outputFiles.TaggedOutputFiles[tag]; hasTag {
+		return taggedOutputFiles, nil
+	} else {
+		if fromProperty {
+			return nil, fmt.Errorf("unsupported tag %q for module getting its own output files", tag)
+		} else {
+			return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+		}
+	}
+}
+
+func (o OutputFilesInfo) isEmpty() bool {
+	return o.DefaultOutputFiles == nil && o.TaggedOutputFiles == nil
+}
+
+type OutputFilesInfo struct {
+	// default output files when tag is an empty string ""
+	DefaultOutputFiles Paths
+
+	// the corresponding output files for given tags
+	TaggedOutputFiles map[string]Paths
+}
+
+var OutputFilesProvider = blueprint.NewProvider[OutputFilesInfo]()
+
+// This is used to mark the case where OutputFilesProvider is not set on some modules.
+var OutputFilesProviderNotSet = fmt.Errorf("No output files from provider")
+
 // Modules can implement HostToolProvider and return a valid OptionalPath from HostToolPath() to
 // specify that they can be used as a tool by a genrule module.
 type HostToolProvider interface {
@@ -2439,8 +2570,6 @@
 
 func init() {
 	RegisterParallelSingletonType("buildtarget", BuildTargetSingleton)
-	RegisterParallelSingletonType("soongconfigtrace", soongConfigTraceSingletonFunc)
-	FinalDepsMutators(registerSoongConfigTraceMutator)
 }
 
 func BuildTargetSingleton() Singleton {
@@ -2535,7 +2664,7 @@
 	}
 	osDeps := map[osAndCross]Paths{}
 	ctx.VisitAllModules(func(module Module) {
-		if module.Enabled() {
+		if module.Enabled(ctx) {
 			key := osAndCross{os: module.Target().Os, hostCross: module.Target().HostCross}
 			osDeps[key] = append(osDeps[key], module.base().checkbuildFiles...)
 		}
@@ -2602,54 +2731,3 @@
 	bpctx := ctx.blueprintBaseModuleContext()
 	return blueprint.CheckBlueprintSyntax(bpctx.ModuleFactories(), filename, contents)
 }
-
-func registerSoongConfigTraceMutator(ctx RegisterMutatorsContext) {
-	ctx.BottomUp("soongconfigtrace", soongConfigTraceMutator).Parallel()
-}
-
-// soongConfigTraceMutator accumulates recorded soong_config trace from children. Also it normalizes
-// SoongConfigTrace to make it consistent.
-func soongConfigTraceMutator(ctx BottomUpMutatorContext) {
-	trace := &ctx.Module().base().commonProperties.SoongConfigTrace
-	ctx.VisitDirectDeps(func(m Module) {
-		childTrace := &m.base().commonProperties.SoongConfigTrace
-		trace.Bools = append(trace.Bools, childTrace.Bools...)
-		trace.Strings = append(trace.Strings, childTrace.Strings...)
-		trace.IsSets = append(trace.IsSets, childTrace.IsSets...)
-	})
-	trace.Bools = SortedUniqueStrings(trace.Bools)
-	trace.Strings = SortedUniqueStrings(trace.Strings)
-	trace.IsSets = SortedUniqueStrings(trace.IsSets)
-
-	ctx.Module().base().commonProperties.SoongConfigTraceHash = trace.hash()
-}
-
-// soongConfigTraceSingleton writes a map from each module's config hash value to trace data.
-func soongConfigTraceSingletonFunc() Singleton {
-	return &soongConfigTraceSingleton{}
-}
-
-type soongConfigTraceSingleton struct {
-}
-
-func (s *soongConfigTraceSingleton) GenerateBuildActions(ctx SingletonContext) {
-	outFile := PathForOutput(ctx, "soong_config_trace.json")
-
-	traces := make(map[string]*soongConfigTrace)
-	ctx.VisitAllModules(func(module Module) {
-		trace := &module.base().commonProperties.SoongConfigTrace
-		if !trace.isEmpty() {
-			hash := module.base().commonProperties.SoongConfigTraceHash
-			traces[hash] = trace
-		}
-	})
-
-	j, err := json.Marshal(traces)
-	if err != nil {
-		ctx.Errorf("json marshal to %q failed: %#v", outFile, err)
-		return
-	}
-
-	WriteFileRule(ctx, outFile, string(j))
-	ctx.Phony("soong_config_trace", outFile)
-}
diff --git a/android/module_context.go b/android/module_context.go
index d3e2770..253bebd 100644
--- a/android/module_context.go
+++ b/android/module_context.go
@@ -183,12 +183,11 @@
 	InstallInVendor() bool
 	InstallForceOS() (*OsType, *ArchType)
 
-	RequiredModuleNames() []string
+	RequiredModuleNames(ctx ConfigAndErrorContext) []string
 	HostRequiredModuleNames() []string
 	TargetRequiredModuleNames() []string
 
 	ModuleSubDir() string
-	SoongConfigTraceHash() string
 
 	Variable(pctx PackageContext, name, value string)
 	Rule(pctx PackageContext, name string, params blueprint.RuleParams, argNames ...string) blueprint.Rule
@@ -212,6 +211,15 @@
 	// GenerateAndroidBuildActions.  If it is called then the struct will be written out and included in
 	// the module-info.json generated by Make, and Make will not generate its own data for this module.
 	ModuleInfoJSON() *ModuleInfoJSON
+
+	// SetOutputFiles stores the outputFiles to outputFiles property, which is used
+	// to set the OutputFilesProvider later.
+	SetOutputFiles(outputFiles Paths, tag string)
+
+	// ComplianceMetadataInfo returns a ComplianceMetadataInfo instance for different module types to dump metadata,
+	// which usually happens in GenerateAndroidBuildActions() of a module type.
+	// See android.ModuleBase.complianceMetadataInfo
+	ComplianceMetadataInfo() *ComplianceMetadataInfo
 }
 
 type moduleContext struct {
@@ -373,10 +381,6 @@
 	return m.bp.ModuleSubDir()
 }
 
-func (m *moduleContext) SoongConfigTraceHash() string {
-	return m.module.base().commonProperties.SoongConfigTraceHash
-}
-
 func (m *moduleContext) InstallInData() bool {
 	return m.module.InstallInData()
 }
@@ -444,6 +448,21 @@
 	return false
 }
 
+// Tells whether this module is installed to the full install path (ex:
+// out/target/product/<name>/<partition>) or not. If this returns false, the install build rule is
+// not created and this module can only be installed to packaging modules like android_filesystem.
+func (m *moduleContext) requiresFullInstall() bool {
+	if m.skipInstall() {
+		return false
+	}
+
+	if proptools.Bool(m.module.base().commonProperties.No_full_install) {
+		return false
+	}
+
+	return true
+}
+
 func (m *moduleContext) InstallFile(installPath InstallPath, name string, srcPath Path,
 	deps ...InstallPath) InstallPath {
 	return m.installFile(installPath, name, srcPath, deps, false, true, nil)
@@ -467,6 +486,10 @@
 	return m.packageFile(fullInstallPath, srcPath, false)
 }
 
+func (m *moduleContext) getAconfigPaths() *Paths {
+	return &m.module.base().aconfigFilePaths
+}
+
 func (m *moduleContext) packageFile(fullInstallPath InstallPath, srcPath Path, executable bool) PackagingSpec {
 	licenseFiles := m.Module().EffectiveLicenseFiles()
 	spec := PackagingSpec{
@@ -476,6 +499,9 @@
 		executable:            executable,
 		effectiveLicenseFiles: &licenseFiles,
 		partition:             fullInstallPath.partition,
+		skipInstall:           m.skipInstall(),
+		aconfigPaths:          m.getAconfigPaths(),
+		archType:              m.target.Arch.ArchType,
 	}
 	m.packagingSpecs = append(m.packagingSpecs, spec)
 	return spec
@@ -489,7 +515,7 @@
 		m.module.base().hooks.runInstallHooks(m, srcPath, fullInstallPath, false)
 	}
 
-	if !m.skipInstall() {
+	if m.requiresFullInstall() {
 		deps = append(deps, InstallPaths(m.module.base().installFilesDepSet.ToList())...)
 		deps = append(deps, m.module.base().installedInitRcPaths...)
 		deps = append(deps, m.module.base().installedVintfFragmentsPaths...)
@@ -562,7 +588,7 @@
 	if err != nil {
 		panic(fmt.Sprintf("Unable to generate symlink between %q and %q: %s", fullInstallPath.Base(), srcPath.Base(), err))
 	}
-	if !m.skipInstall() {
+	if m.requiresFullInstall() {
 
 		if m.Config().KatiEnabled() {
 			// When creating the symlink rule in Soong but embedding in Make, write the rule to a
@@ -599,6 +625,9 @@
 		symlinkTarget:    relPath,
 		executable:       false,
 		partition:        fullInstallPath.partition,
+		skipInstall:      m.skipInstall(),
+		aconfigPaths:     m.getAconfigPaths(),
+		archType:         m.target.Arch.ArchType,
 	})
 
 	return fullInstallPath
@@ -610,7 +639,7 @@
 	fullInstallPath := installPath.Join(m, name)
 	m.module.base().hooks.runInstallHooks(m, nil, fullInstallPath, true)
 
-	if !m.skipInstall() {
+	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
@@ -640,6 +669,9 @@
 		symlinkTarget:    absPath,
 		executable:       false,
 		partition:        fullInstallPath.partition,
+		skipInstall:      m.skipInstall(),
+		aconfigPaths:     m.getAconfigPaths(),
+		archType:         m.target.Arch.ArchType,
 	})
 
 	return fullInstallPath
@@ -679,6 +711,33 @@
 	return moduleInfoJSON
 }
 
+func (m *moduleContext) SetOutputFiles(outputFiles Paths, tag string) {
+	if tag == "" {
+		if len(m.module.base().outputFiles.DefaultOutputFiles) > 0 {
+			m.ModuleErrorf("Module %s default OutputFiles cannot be overwritten", m.ModuleName())
+		}
+		m.module.base().outputFiles.DefaultOutputFiles = outputFiles
+	} else {
+		if m.module.base().outputFiles.TaggedOutputFiles == nil {
+			m.module.base().outputFiles.TaggedOutputFiles = make(map[string]Paths)
+		}
+		if _, exists := m.module.base().outputFiles.TaggedOutputFiles[tag]; exists {
+			m.ModuleErrorf("Module %s OutputFiles at tag %s cannot be overwritten", m.ModuleName(), tag)
+		} else {
+			m.module.base().outputFiles.TaggedOutputFiles[tag] = outputFiles
+		}
+	}
+}
+
+func (m *moduleContext) ComplianceMetadataInfo() *ComplianceMetadataInfo {
+	if complianceMetadataInfo := m.module.base().complianceMetadataInfo; complianceMetadataInfo != nil {
+		return complianceMetadataInfo
+	}
+	complianceMetadataInfo := NewComplianceMetadataInfo()
+	m.module.base().complianceMetadataInfo = complianceMetadataInfo
+	return complianceMetadataInfo
+}
+
 // Returns a list of paths expanded from globs and modules referenced using ":module" syntax.  The property must
 // be tagged with `android:"path" to support automatic source module dependency resolution.
 //
@@ -705,8 +764,8 @@
 	return OptionalPath{}
 }
 
-func (m *moduleContext) RequiredModuleNames() []string {
-	return m.module.RequiredModuleNames()
+func (m *moduleContext) RequiredModuleNames(ctx ConfigAndErrorContext) []string {
+	return m.module.RequiredModuleNames(ctx)
 }
 
 func (m *moduleContext) HostRequiredModuleNames() []string {
diff --git a/android/module_info_json.go b/android/module_info_json.go
index 1c0a38e..ee552dc 100644
--- a/android/module_info_json.go
+++ b/android/module_info_json.go
@@ -17,6 +17,7 @@
 	HostDependencies   []string `json:"host_dependencies,omitempty"`   // $(sort $(ALL_MODULES.$(m).HOST_REQUIRED_FROM_TARGET))
 	TargetDependencies []string `json:"target_dependencies,omitempty"` // $(sort $(ALL_MODULES.$(m).TARGET_REQUIRED_FROM_HOST))
 	Data               []string `json:"data,omitempty"`                // $(sort $(ALL_MODULES.$(m).TEST_DATA))
+	Required           []string `json:"required,omitempty"`            // $(sort $(ALL_MODULES.$(m).REQUIRED_FROM_TARGET))
 }
 
 type ModuleInfoJSON struct {
@@ -77,6 +78,7 @@
 	sortAndUnique(&moduleInfoJSONCopy.core.HostDependencies)
 	sortAndUnique(&moduleInfoJSONCopy.core.TargetDependencies)
 	sortAndUnique(&moduleInfoJSONCopy.core.Data)
+	sortAndUnique(&moduleInfoJSONCopy.core.Required)
 
 	sortAndUnique(&moduleInfoJSONCopy.Class)
 	sortAndUnique(&moduleInfoJSONCopy.Tags)
diff --git a/android/module_test.go b/android/module_test.go
index 1f3db5c..922ea21 100644
--- a/android/module_test.go
+++ b/android/module_test.go
@@ -935,31 +935,54 @@
 	}
 }
 
-type fakeBlueprintModule struct{}
-
-func (fakeBlueprintModule) Name() string { return "foo" }
-
-func (fakeBlueprintModule) GenerateBuildActions(blueprint.ModuleContext) {}
-
 type sourceProducerTestModule struct {
-	fakeBlueprintModule
-	source Path
+	ModuleBase
+	props struct {
+		// A represents the source file
+		A string
+	}
 }
 
-func (s sourceProducerTestModule) Srcs() Paths { return Paths{s.source} }
-
-type outputFileProducerTestModule struct {
-	fakeBlueprintModule
-	output map[string]Path
-	error  map[string]error
+func sourceProducerTestModuleFactory() Module {
+	module := &sourceProducerTestModule{}
+	module.AddProperties(&module.props)
+	InitAndroidModule(module)
+	return module
 }
 
-func (o outputFileProducerTestModule) OutputFiles(tag string) (Paths, error) {
-	return PathsIfNonNil(o.output[tag]), o.error[tag]
+func (s sourceProducerTestModule) GenerateAndroidBuildActions(ModuleContext) {}
+
+func (s sourceProducerTestModule) Srcs() Paths { return PathsForTesting(s.props.A) }
+
+type outputFilesTestModule struct {
+	ModuleBase
+	props struct {
+		// A represents the tag
+		A string
+		// B represents the output file for tag A
+		B string
+	}
+}
+
+func outputFilesTestModuleFactory() Module {
+	module := &outputFilesTestModule{}
+	module.AddProperties(&module.props)
+	InitAndroidModule(module)
+	return module
+}
+
+func (o outputFilesTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	if o.props.A != "" || o.props.B != "" {
+		ctx.SetOutputFiles(PathsForTesting(o.props.B), o.props.A)
+	}
+	// This is to simulate the case that some module uses an object to set its
+	// OutputFilesProvider, but the object itself is empty.
+	ctx.SetOutputFiles(Paths{}, "missing")
 }
 
 type pathContextAddMissingDependenciesWrapper struct {
 	PathContext
+	OtherModuleProviderContext
 	missingDeps []string
 }
 
@@ -970,52 +993,87 @@
 	return module.Name()
 }
 
+func (p *pathContextAddMissingDependenciesWrapper) Module() Module { return nil }
+
 func TestOutputFileForModule(t *testing.T) {
 	testcases := []struct {
 		name        string
-		module      blueprint.Module
+		bp          string
 		tag         string
-		env         map[string]string
-		config      func(*config)
 		expected    string
 		missingDeps []string
+		env         map[string]string
+		config      func(*config)
 	}{
 		{
-			name:     "SourceFileProducer",
-			module:   &sourceProducerTestModule{source: PathForTesting("foo.txt")},
-			expected: "foo.txt",
+			name: "SourceFileProducer",
+			bp: `spt_module {
+					name: "test_module",
+					a: "spt.txt",
+				}
+			`,
+			tag:      "",
+			expected: "spt.txt",
 		},
 		{
-			name:     "OutputFileProducer",
-			module:   &outputFileProducerTestModule{output: map[string]Path{"": PathForTesting("foo.txt")}},
-			expected: "foo.txt",
+			name: "OutputFileProviderEmptyStringTag",
+			bp: `oft_module {
+					name: "test_module",
+					a: "",
+					b: "empty.txt",
+				}
+		`,
+			tag:      "",
+			expected: "empty.txt",
 		},
 		{
-			name:     "OutputFileProducer_tag",
-			module:   &outputFileProducerTestModule{output: map[string]Path{"foo": PathForTesting("foo.txt")}},
+			name: "OutputFileProviderTag",
+			bp: `oft_module {
+					name: "test_module",
+					a: "foo",
+					b: "foo.txt",
+				}
+			`,
 			tag:      "foo",
 			expected: "foo.txt",
 		},
 		{
-			name: "OutputFileProducer_AllowMissingDependencies",
+			name: "OutputFileAllowMissingDependencies",
+			bp: `oft_module {
+				name: "test_module",
+			}
+		`,
+			tag:         "missing",
+			expected:    "missing_output_file/test_module",
+			missingDeps: []string{"test_module"},
 			config: func(config *config) {
 				config.TestProductVariables.Allow_missing_dependencies = boolPtr(true)
 			},
-			module:      &outputFileProducerTestModule{},
-			missingDeps: []string{"foo"},
-			expected:    "missing_output_file/foo",
 		},
 	}
+
 	for _, tt := range testcases {
-		config := TestConfig(buildDir, tt.env, "", nil)
-		if tt.config != nil {
-			tt.config(config.config)
-		}
-		ctx := &pathContextAddMissingDependenciesWrapper{
-			PathContext: PathContextForTesting(config),
-		}
-		got := OutputFileForModule(ctx, tt.module, tt.tag)
-		AssertPathRelativeToTopEquals(t, "expected source path", tt.expected, got)
-		AssertArrayString(t, "expected missing deps", tt.missingDeps, ctx.missingDeps)
+		t.Run(tt.name, func(t *testing.T) {
+			result := GroupFixturePreparers(
+				PrepareForTestWithDefaults,
+				FixtureRegisterWithContext(func(ctx RegistrationContext) {
+					ctx.RegisterModuleType("spt_module", sourceProducerTestModuleFactory)
+					ctx.RegisterModuleType("oft_module", outputFilesTestModuleFactory)
+				}),
+				FixtureWithRootAndroidBp(tt.bp),
+			).RunTest(t)
+
+			config := TestConfig(buildDir, tt.env, tt.bp, nil)
+			if tt.config != nil {
+				tt.config(config.config)
+			}
+			ctx := &pathContextAddMissingDependenciesWrapper{
+				PathContext:                PathContextForTesting(config),
+				OtherModuleProviderContext: result.TestContext.OtherModuleProviderAdaptor(),
+			}
+			got := OutputFileForModule(ctx, result.ModuleForTests("test_module", "").Module(), tt.tag)
+			AssertPathRelativeToTopEquals(t, "expected output path", tt.expected, got)
+			AssertArrayString(t, "expected missing deps", tt.missingDeps, ctx.missingDeps)
+		})
 	}
 }
diff --git a/android/mutator.go b/android/mutator.go
index 75ba650..b81dd12 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -293,15 +293,14 @@
 	// WalkDeps, etc.
 	AddInterVariantDependency(tag blueprint.DependencyTag, from, to blueprint.Module)
 
-	// ReplaceDependencies replaces all dependencies on the identical variant of the module with the
-	// specified name with the current variant of this module.  Replacements don't take effect until
-	// after the mutator pass is finished.
+	// 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.
 	ReplaceDependencies(string)
 
-	// ReplaceDependencies replaces all dependencies on the identical variant of the module with the
-	// specified name with the current variant of this module as long as the supplied predicate returns
-	// true.
-	//
+	// 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.
 	ReplaceDependenciesIf(string, blueprint.ReplaceDependencyPredicate)
 
@@ -401,6 +400,12 @@
 	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 {
@@ -575,6 +580,10 @@
 	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)
 }
@@ -595,11 +604,16 @@
 
 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)
-		base := am.base()
-		base.commonProperties.DebugMutators = append(base.commonProperties.DebugMutators, a.name)
-		base.commonProperties.DebugVariations = append(base.commonProperties.DebugVariations, variation)
 		a.mutator.Mutate(mctx, variation)
 	}
 }
@@ -674,13 +688,11 @@
 // on component modules to be added so that they can depend directly on a prebuilt
 // module.
 func componentDepsMutator(ctx BottomUpMutatorContext) {
-	if m := ctx.Module(); m.Enabled() {
-		m.ComponentDepsMutator(ctx)
-	}
+	ctx.Module().ComponentDepsMutator(ctx)
 }
 
 func depsMutator(ctx BottomUpMutatorContext) {
-	if m := ctx.Module(); m.Enabled() {
+	if m := ctx.Module(); m.Enabled(ctx) {
 		m.base().baseDepsMutator(ctx)
 		m.DepsMutator(ctx)
 	}
diff --git a/android/neverallow.go b/android/neverallow.go
index 62c5e59..0f363e7 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -212,7 +212,7 @@
 
 func createCcStubsRule() Rule {
 	ccStubsImplementationInstallableProjectsAllowedList := []string{
-		"packages/modules/Virtualization/vm_payload",
+		"packages/modules/Virtualization/libs/libvm_payload",
 	}
 
 	return NeverAllow().
@@ -237,6 +237,7 @@
 			Without("name", "init_first_stage").
 			Without("name", "init_first_stage.microdroid").
 			With("install_in_root", "true").
+			NotModuleType("prebuilt_root").
 			Because("install_in_root is only for init_first_stage."),
 	}
 }
diff --git a/android/override_module.go b/android/override_module.go
index 1341f53..f69f963 100644
--- a/android/override_module.go
+++ b/android/override_module.go
@@ -28,6 +28,7 @@
 // module based on it.
 
 import (
+	"fmt"
 	"sort"
 	"sync"
 
@@ -120,7 +121,7 @@
 	addOverride(o OverrideModule)
 	getOverrides() []OverrideModule
 
-	override(ctx BaseModuleContext, m Module, o OverrideModule)
+	override(ctx BaseModuleContext, bm OverridableModule, o OverrideModule)
 	GetOverriddenBy() string
 	GetOverriddenByModuleDir() string
 
@@ -191,15 +192,14 @@
 }
 
 // Overrides a base module with the given OverrideModule.
-func (b *OverridableModuleBase) override(ctx BaseModuleContext, m Module, o OverrideModule) {
-
+func (b *OverridableModuleBase) override(ctx BaseModuleContext, bm OverridableModule, o OverrideModule) {
 	for _, p := range b.overridableProperties {
 		for _, op := range o.getOverridingProperties() {
 			if proptools.TypeEqual(p, op) {
 				err := proptools.ExtendProperties(p, op, nil, proptools.OrderReplace)
 				if err != nil {
 					if propertyErr, ok := err.(*proptools.ExtendPropertyError); ok {
-						ctx.PropertyErrorf(propertyErr.Property, "%s", propertyErr.Err.Error())
+						ctx.OtherModulePropertyErrorf(bm, propertyErr.Property, "%s", propertyErr.Err.Error())
 					} else {
 						panic(err)
 					}
@@ -210,7 +210,7 @@
 	// Adds the base module to the overrides property, if exists, of the overriding module. See the
 	// comment on OverridableModuleBase.overridesProperty for details.
 	if b.overridesProperty != nil {
-		*b.overridesProperty = append(*b.overridesProperty, ctx.ModuleName())
+		*b.overridesProperty = append(*b.overridesProperty, ctx.OtherModuleName(bm))
 	}
 	b.overridableModuleProperties.OverriddenBy = o.Name()
 	b.overridableModuleProperties.OverriddenByModuleDir = o.ModuleDir()
@@ -235,7 +235,7 @@
 // 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("perform_override", performOverrideMutator).Parallel()
+	ctx.Transition("override", &overrideTransitionMutator{})
 	// overridableModuleDepsMutator calls OverridablePropertiesDepsMutator so that overridable modules can
 	// add deps from overridable properties.
 	ctx.BottomUp("overridable_deps", overridableModuleDepsMutator).Parallel()
@@ -253,6 +253,15 @@
 
 var overrideBaseDepTag overrideBaseDependencyTag
 
+// Override module should always override the source module.
+// Overrides are implemented as a variant of the overridden module, and the build actions are created in the
+// module context of the overridden module.
+// If we replace override module with the prebuilt of the overridden module, `GenerateAndroidBuildActions` for
+// the override module will have a very different meaning.
+func (tag overrideBaseDependencyTag) ReplaceSourceWithPrebuilt() bool {
+	return false
+}
+
 // Adds dependency on the base module to the overriding module so that they can be visited in the
 // next phase.
 func overrideModuleDepsMutator(ctx BottomUpMutatorContext) {
@@ -262,18 +271,6 @@
 			ctx.PropertyErrorf("base", "%q is not a valid module name", base)
 			return
 		}
-		// See if there's a prebuilt module that overrides this override module with prefer flag,
-		// in which case we call HideFromMake on the corresponding variant later.
-		ctx.VisitDirectDepsWithTag(PrebuiltDepTag, func(dep Module) {
-			prebuilt := GetEmbeddedPrebuilt(dep)
-			if prebuilt == nil {
-				panic("PrebuiltDepTag leads to a non-prebuilt module " + dep.Name())
-			}
-			if prebuilt.UsePrebuilt() {
-				module.setOverriddenByPrebuilt(dep)
-				return
-			}
-		})
 		baseModule := ctx.AddDependency(ctx.Module(), overrideBaseDepTag, *module.getOverrideModuleProperties().Base)[0]
 		if o, ok := baseModule.(OverridableModule); ok {
 			overrideModule := ctx.Module().(OverrideModule)
@@ -285,11 +282,13 @@
 
 // Now, goes through all overridable modules, finds all modules overriding them, creates a local
 // variant for each of them, and performs the actual overriding operation by calling override().
-func performOverrideMutator(ctx BottomUpMutatorContext) {
+type overrideTransitionMutator struct{}
+
+func (overrideTransitionMutator) Split(ctx BaseModuleContext) []string {
 	if b, ok := ctx.Module().(OverridableModule); ok {
 		overrides := b.getOverrides()
 		if len(overrides) == 0 {
-			return
+			return []string{""}
 		}
 		variants := make([]string, len(overrides)+1)
 		// The first variant is for the original, non-overridden, base module.
@@ -297,32 +296,74 @@
 		for i, o := range overrides {
 			variants[i+1] = o.(Module).Name()
 		}
-		mods := ctx.CreateLocalVariations(variants...)
-		// Make the original variation the default one to depend on if no other override module variant
-		// is specified.
-		ctx.AliasVariation(variants[0])
-		for i, o := range overrides {
-			mods[i+1].(OverridableModule).override(ctx, mods[i+1], o)
-			if prebuilt := o.getOverriddenByPrebuilt(); prebuilt != nil {
-				// The overriding module itself, too, is overridden by a prebuilt.
-				// Perform the same check for replacement
-				checkInvariantsForSourceAndPrebuilt(ctx, mods[i+1], prebuilt)
-				// Copy the flag and hide it in make
-				mods[i+1].ReplacedByPrebuilt()
-			}
-		}
+		return variants
 	} else if o, ok := ctx.Module().(OverrideModule); ok {
 		// Create a variant of the overriding module with its own name. This matches the above local
 		// variant name rule for overridden modules, and thus allows ReplaceDependencies to match the
 		// two.
-		ctx.CreateLocalVariations(o.Name())
-		// To allow dependencies to be added without having to know the above variation.
-		ctx.AliasVariation(o.Name())
+		return []string{o.Name()}
+	}
+
+	return []string{""}
+}
+
+func (overrideTransitionMutator) OutgoingTransition(ctx OutgoingTransitionContext, sourceVariation string) string {
+	if o, ok := ctx.Module().(OverrideModule); ok {
+		if ctx.DepTag() == overrideBaseDepTag {
+			return o.Name()
+		}
+	}
+
+	// Variations are always local and shouldn't affect the variant used for dependencies
+	return ""
+}
+
+func (overrideTransitionMutator) IncomingTransition(ctx IncomingTransitionContext, incomingVariation string) string {
+	if _, ok := ctx.Module().(OverridableModule); ok {
+		return incomingVariation
+	} else if o, ok := ctx.Module().(OverrideModule); ok {
+		// To allow dependencies to be added without having to know the variation.
+		return o.Name()
+	}
+
+	return ""
+}
+
+func (overrideTransitionMutator) Mutate(ctx BottomUpMutatorContext, variation string) {
+	if o, ok := ctx.Module().(OverrideModule); ok {
+		overridableDeps := ctx.GetDirectDepsWithTag(overrideBaseDepTag)
+		if len(overridableDeps) > 1 {
+			panic(fmt.Errorf("expected a single dependency with overrideBaseDepTag, found %q", overridableDeps))
+		} else if len(overridableDeps) == 1 {
+			b := overridableDeps[0].(OverridableModule)
+			b.override(ctx, b, o)
+
+			checkPrebuiltReplacesOverride(ctx, b)
+		}
+	}
+}
+
+func checkPrebuiltReplacesOverride(ctx BottomUpMutatorContext, b OverridableModule) {
+	// See if there's a prebuilt module that overrides this override module with prefer flag,
+	// in which case we call HideFromMake on the corresponding variant later.
+	prebuiltDeps := ctx.GetDirectDepsWithTag(PrebuiltDepTag)
+	for _, prebuiltDep := range prebuiltDeps {
+		prebuilt := GetEmbeddedPrebuilt(prebuiltDep)
+		if prebuilt == nil {
+			panic("PrebuiltDepTag leads to a non-prebuilt module " + prebuiltDep.Name())
+		}
+		if prebuilt.UsePrebuilt() {
+			// The overriding module itself, too, is overridden by a prebuilt.
+			// Perform the same check for replacement
+			checkInvariantsForSourceAndPrebuilt(ctx, b, prebuiltDep)
+			// Copy the flag and hide it in make
+			b.ReplacedByPrebuilt()
+		}
 	}
 }
 
 func overridableModuleDepsMutator(ctx BottomUpMutatorContext) {
-	if b, ok := ctx.Module().(OverridableModule); ok && b.Enabled() {
+	if b, ok := ctx.Module().(OverridableModule); ok && b.Enabled(ctx) {
 		b.OverridablePropertiesDepsMutator(ctx)
 	}
 }
diff --git a/android/packaging.go b/android/packaging.go
index a8fb28d..c247ed2 100644
--- a/android/packaging.go
+++ b/android/packaging.go
@@ -17,9 +17,11 @@
 import (
 	"fmt"
 	"path/filepath"
+	"sort"
 	"strings"
 
 	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
 )
 
 // PackagingSpec abstracts a request to place a built artifact at a certain path in a package. A
@@ -43,6 +45,36 @@
 	effectiveLicenseFiles *Paths
 
 	partition string
+
+	// Whether this packaging spec represents an installation of the srcPath (i.e. this struct
+	// is created via InstallFile or InstallSymlink) or a simple packaging (i.e. created via
+	// PackageFile).
+	skipInstall bool
+
+	// Paths of aconfig files for the built artifact
+	aconfigPaths *Paths
+
+	// ArchType of the module which produced this packaging spec
+	archType ArchType
+}
+
+func (p *PackagingSpec) Equals(other *PackagingSpec) bool {
+	if other == nil {
+		return false
+	}
+	if p.relPathInPackage != other.relPathInPackage {
+		return false
+	}
+	if p.srcPath != other.srcPath || p.symlinkTarget != other.symlinkTarget {
+		return false
+	}
+	if p.executable != other.executable {
+		return false
+	}
+	if p.partition != other.partition {
+		return false
+	}
+	return true
 }
 
 // Get file name of installed package
@@ -74,6 +106,15 @@
 	return p.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
+}
+
 type PackageModule interface {
 	Module
 	packagingBase() *PackagingBase
@@ -103,18 +144,24 @@
 	// for rare cases like when there's a dependency to a module which exists in certain repo
 	// checkouts, this is needed.
 	IgnoreMissingDependencies bool
+
+	// 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
 }
 
 type depsProperty struct {
 	// Modules to include in this package
-	Deps []string `android:"arch_variant"`
+	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"`
+	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 {
@@ -125,8 +172,8 @@
 }
 
 type PackagingProperties struct {
-	Deps     []string                    `android:"arch_variant"`
-	Multilib packagingMultilibProperties `android:"arch_variant"`
+	Deps     proptools.Configurable[[]string] `android:"arch_variant"`
+	Multilib packagingMultilibProperties      `android:"arch_variant"`
 	Arch     packagingArchProperties
 }
 
@@ -144,22 +191,55 @@
 // 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 {
-	var ret []string
-	if arch == ctx.Target().Arch.ArchType && len(ctx.MultiTargets()) == 0 {
-		ret = append(ret, p.properties.Deps...)
-	} else if arch.Multilib == "lib32" {
-		ret = append(ret, p.properties.Multilib.Lib32.Deps...)
-	} else if arch.Multilib == "lib64" {
-		ret = append(ret, p.properties.Multilib.Lib64.Deps...)
-	} else if arch == Common {
-		ret = append(ret, p.properties.Multilib.Common.Deps...)
+	get := func(prop proptools.Configurable[[]string]) []string {
+		return prop.GetOrDefault(ctx, nil)
 	}
 
-	for i, t := range ctx.MultiTargets() {
-		if t.Arch.ArchType == arch {
-			ret = append(ret, p.properties.Deps...)
-			if i == 0 {
-				ret = append(ret, p.properties.Multilib.First.Deps...)
+	var ret []string
+	if arch == ctx.Target().Arch.ArchType && len(ctx.MultiTargets()) == 0 {
+		ret = append(ret, get(p.properties.Deps)...)
+	} else if arch.Multilib == "lib32" {
+		ret = append(ret, get(p.properties.Multilib.Lib32.Deps)...)
+		// multilib.prefer32.deps are added for lib32 only when they support 32-bit arch
+		for _, dep := range get(p.properties.Multilib.Prefer32.Deps) {
+			if checkIfOtherModuleSupportsLib32(ctx, dep) {
+				ret = append(ret, dep)
+			}
+		}
+	} else if arch.Multilib == "lib64" {
+		ret = append(ret, get(p.properties.Multilib.Lib64.Deps)...)
+		// 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) {
+			if !checkIfOtherModuleSupportsLib32(ctx, dep) {
+				ret = append(ret, dep)
+			}
+		}
+	} else if arch == Common {
+		ret = append(ret, get(p.properties.Multilib.Common.Deps)...)
+	}
+
+	if p.DepsCollectFirstTargetOnly {
+		if len(get(p.properties.Multilib.First.Deps)) > 0 {
+			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)...)
+				if i == 0 {
+					ret = append(ret, get(p.properties.Deps)...)
+				}
+			}
+		}
+	} else {
+		if len(get(p.properties.Multilib.Both.Deps)) > 0 {
+			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)...)
+				if i == 0 {
+					ret = append(ret, get(p.properties.Multilib.First.Deps)...)
+				}
 			}
 		}
 	}
@@ -167,20 +247,20 @@
 	if ctx.Arch().ArchType == Common {
 		switch arch {
 		case Arm64:
-			ret = append(ret, p.properties.Arch.Arm64.Deps...)
+			ret = append(ret, get(p.properties.Arch.Arm64.Deps)...)
 		case Arm:
-			ret = append(ret, p.properties.Arch.Arm.Deps...)
+			ret = append(ret, get(p.properties.Arch.Arm.Deps)...)
 		case X86_64:
-			ret = append(ret, p.properties.Arch.X86_64.Deps...)
+			ret = append(ret, get(p.properties.Arch.X86_64.Deps)...)
 		case X86:
-			ret = append(ret, p.properties.Arch.X86.Deps...)
+			ret = append(ret, get(p.properties.Arch.X86.Deps)...)
 		}
 	}
 
 	return FirstUniqueStrings(ret)
 }
 
-func (p *PackagingBase) getSupportedTargets(ctx BaseModuleContext) []Target {
+func getSupportedTargets(ctx BaseModuleContext) []Target {
 	var ret []Target
 	// The current and the common OS targets are always supported
 	ret = append(ret, ctx.Target())
@@ -192,6 +272,28 @@
 	return ret
 }
 
+// getLib32Target returns the 32-bit target from the list of targets this module supports. If this
+// module doesn't support 32-bit target, nil is returned.
+func getLib32Target(ctx BaseModuleContext) *Target {
+	for _, t := range getSupportedTargets(ctx) {
+		if t.Arch.ArchType.Multilib == "lib32" {
+			return &t
+		}
+	}
+	return nil
+}
+
+// checkIfOtherModuleSUpportsLib32 returns true if 32-bit variant of dep exists.
+func checkIfOtherModuleSupportsLib32(ctx BaseModuleContext, dep string) bool {
+	t := getLib32Target(ctx)
+	if t == nil {
+		// This packaging module doesn't support 32bit. No point of checking if dep supports 32-bit
+		// or not.
+		return false
+	}
+	return ctx.OtherModuleFarDependencyVariantExists(t.Variations(), dep)
+}
+
 // PackagingItem is a marker interface for dependency tags.
 // Direct dependencies with a tag implementing PackagingItem are packaged in CopyDepsToZip().
 type PackagingItem interface {
@@ -212,7 +314,7 @@
 
 // See PackageModule.AddDeps
 func (p *PackagingBase) AddDeps(ctx BottomUpMutatorContext, depTag blueprint.DependencyTag) {
-	for _, t := range p.getSupportedTargets(ctx) {
+	for _, t := range getSupportedTargets(ctx) {
 		for _, dep := range p.getDepsForArch(ctx, t.Arch.ArchType) {
 			if p.IgnoreMissingDependencies && !ctx.OtherModuleExists(dep) {
 				continue
@@ -224,19 +326,45 @@
 
 func (p *PackagingBase) GatherPackagingSpecsWithFilter(ctx ModuleContext, filter func(PackagingSpec) bool) map[string]PackagingSpec {
 	m := make(map[string]PackagingSpec)
+
+	var arches []ArchType
+	for _, target := range getSupportedTargets(ctx) {
+		arches = append(arches, target.Arch.ArchType)
+	}
+
+	// filter out packaging specs for unsupported architecture
+	filterArch := func(ps PackagingSpec) bool {
+		for _, arch := range arches {
+			if arch == ps.archType {
+				return true
+			}
+		}
+		return false
+	}
+
 	ctx.VisitDirectDeps(func(child Module) {
 		if pi, ok := ctx.OtherModuleDependencyTag(child).(PackagingItem); !ok || !pi.IsPackagingItem() {
 			return
 		}
 		for _, ps := range child.TransitivePackagingSpecs() {
+			if !filterArch(ps) {
+				continue
+			}
+
 			if filter != nil {
 				if !filter(ps) {
 					continue
 				}
 			}
-			if _, ok := m[ps.relPathInPackage]; !ok {
-				m[ps.relPathInPackage] = ps
+			dstPath := ps.relPathInPackage
+			if existingPs, ok := m[dstPath]; ok {
+				if !existingPs.Equals(&ps) {
+					ctx.ModuleErrorf("packaging conflict at %v:\n%v\n%v", dstPath, existingPs, ps)
+				}
+				continue
 			}
+
+			m[dstPath] = ps
 		}
 	})
 	return m
@@ -250,31 +378,59 @@
 // CopySpecsToDir is a helper that will add commands to the rule builder to copy the PackagingSpec
 // entries into the specified directory.
 func (p *PackagingBase) CopySpecsToDir(ctx ModuleContext, builder *RuleBuilder, specs map[string]PackagingSpec, dir WritablePath) (entries []string) {
-	if len(specs) == 0 {
+	dirsToSpecs := make(map[WritablePath]map[string]PackagingSpec)
+	dirsToSpecs[dir] = specs
+	return p.CopySpecsToDirs(ctx, builder, dirsToSpecs)
+}
+
+// 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) {
+	empty := true
+	for _, specs := range dirsToSpecs {
+		if len(specs) > 0 {
+			empty = false
+			break
+		}
+	}
+	if empty {
 		return entries
 	}
+
 	seenDir := make(map[string]bool)
 	preparerPath := PathForModuleOut(ctx, "preparer.sh")
 	cmd := builder.Command().Tool(preparerPath)
 	var sb strings.Builder
 	sb.WriteString("set -e\n")
-	for _, k := range SortedKeys(specs) {
-		ps := specs[k]
-		destPath := filepath.Join(dir.String(), ps.relPathInPackage)
-		destDir := filepath.Dir(destPath)
-		entries = append(entries, ps.relPathInPackage)
-		if _, ok := seenDir[destDir]; !ok {
-			seenDir[destDir] = true
-			sb.WriteString(fmt.Sprintf("mkdir -p %s\n", destDir))
-		}
-		if ps.symlinkTarget == "" {
-			cmd.Implicit(ps.srcPath)
-			sb.WriteString(fmt.Sprintf("cp %s %s\n", ps.srcPath, destPath))
-		} else {
-			sb.WriteString(fmt.Sprintf("ln -sf %s %s\n", ps.symlinkTarget, destPath))
-		}
-		if ps.executable {
-			sb.WriteString(fmt.Sprintf("chmod a+x %s\n", destPath))
+
+	dirs := make([]WritablePath, 0, len(dirsToSpecs))
+	for dir, _ := range dirsToSpecs {
+		dirs = append(dirs, dir)
+	}
+	sort.Slice(dirs, func(i, j int) bool {
+		return dirs[i].String() < dirs[j].String()
+	})
+
+	for _, dir := range dirs {
+		specs := dirsToSpecs[dir]
+		for _, k := range SortedKeys(specs) {
+			ps := specs[k]
+			destPath := filepath.Join(dir.String(), ps.relPathInPackage)
+			destDir := filepath.Dir(destPath)
+			entries = append(entries, ps.relPathInPackage)
+			if _, ok := seenDir[destDir]; !ok {
+				seenDir[destDir] = true
+				sb.WriteString(fmt.Sprintf("mkdir -p %s\n", destDir))
+			}
+			if ps.symlinkTarget == "" {
+				cmd.Implicit(ps.srcPath)
+				sb.WriteString(fmt.Sprintf("cp %s %s\n", ps.srcPath, destPath))
+			} else {
+				sb.WriteString(fmt.Sprintf("ln -sf %s %s\n", ps.symlinkTarget, destPath))
+			}
+			if ps.executable {
+				sb.WriteString(fmt.Sprintf("chmod a+x %s\n", destPath))
+			}
 		}
 	}
 
diff --git a/android/packaging_test.go b/android/packaging_test.go
index 3833437..19b46fe 100644
--- a/android/packaging_test.go
+++ b/android/packaging_test.go
@@ -15,6 +15,7 @@
 package android
 
 import (
+	"strings"
 	"testing"
 
 	"github.com/google/blueprint"
@@ -67,18 +68,15 @@
 	entries []string
 }
 
-func packageMultiTargetTestModuleFactory() Module {
+func packageTestModuleFactory(multiTarget bool, depsCollectFirstTargetOnly bool) Module {
 	module := &packageTestModule{}
 	InitPackageModule(module)
-	InitAndroidMultiTargetsArchModule(module, DeviceSupported, MultilibCommon)
-	module.AddProperties(&module.properties)
-	return module
-}
-
-func packageTestModuleFactory() Module {
-	module := &packageTestModule{}
-	InitPackageModule(module)
-	InitAndroidArchModule(module, DeviceSupported, MultilibBoth)
+	module.DepsCollectFirstTargetOnly = depsCollectFirstTargetOnly
+	if multiTarget {
+		InitAndroidMultiTargetsArchModule(module, DeviceSupported, MultilibCommon)
+	} else {
+		InitAndroidArchModule(module, DeviceSupported, MultilibBoth)
+	}
 	module.AddProperties(&module.properties)
 	return module
 }
@@ -98,17 +96,24 @@
 	m.entries = m.CopyDepsToZip(ctx, m.GatherPackagingSpecs(ctx), zipFile)
 }
 
-func runPackagingTest(t *testing.T, multitarget bool, bp string, expected []string) {
+type testConfig struct {
+	multiTarget                bool
+	depsCollectFirstTargetOnly bool
+	debuggable                 bool
+}
+
+func runPackagingTest(t *testing.T, config testConfig, bp string, expected []string) {
 	t.Helper()
 
 	var archVariant string
-	var moduleFactory ModuleFactory
-	if multitarget {
+	if config.multiTarget {
 		archVariant = "android_common"
-		moduleFactory = packageMultiTargetTestModuleFactory
 	} else {
 		archVariant = "android_arm64_armv8-a"
-		moduleFactory = packageTestModuleFactory
+	}
+
+	moduleFactory := func() Module {
+		return packageTestModuleFactory(config.multiTarget, config.depsCollectFirstTargetOnly)
 	}
 
 	result := GroupFixturePreparers(
@@ -117,6 +122,9 @@
 			ctx.RegisterModuleType("component", componentTestModuleFactory)
 			ctx.RegisterModuleType("package_module", moduleFactory)
 		}),
+		FixtureModifyProductVariables(func(variables FixtureProductVariables) {
+			variables.Debuggable = proptools.BoolPtr(config.debuggable)
+		}),
 		FixtureWithRootAndroidBp(bp),
 	).RunTest(t)
 
@@ -128,8 +136,11 @@
 }
 
 func TestPackagingBaseMultiTarget(t *testing.T) {
-	multiTarget := true
-	runPackagingTest(t, multiTarget,
+	config := testConfig{
+		multiTarget:                true,
+		depsCollectFirstTargetOnly: false,
+	}
+	runPackagingTest(t, config,
 		`
 		component {
 			name: "foo",
@@ -141,7 +152,7 @@
 		}
 		`, []string{"lib64/foo"})
 
-	runPackagingTest(t, multiTarget,
+	runPackagingTest(t, config,
 		`
 		component {
 			name: "foo",
@@ -158,7 +169,7 @@
 		}
 		`, []string{"lib64/foo", "lib64/bar"})
 
-	runPackagingTest(t, multiTarget,
+	runPackagingTest(t, config,
 		`
 		component {
 			name: "foo",
@@ -176,7 +187,7 @@
 		}
 		`, []string{"lib32/foo", "lib32/bar", "lib64/foo", "lib64/bar"})
 
-	runPackagingTest(t, multiTarget,
+	runPackagingTest(t, config,
 		`
 		component {
 			name: "foo",
@@ -199,7 +210,7 @@
 		}
 		`, []string{"lib32/foo", "lib32/bar", "lib64/foo"})
 
-	runPackagingTest(t, multiTarget,
+	runPackagingTest(t, config,
 		`
 		component {
 			name: "foo",
@@ -221,7 +232,7 @@
 		}
 		`, []string{"lib32/foo", "lib64/foo", "lib64/bar"})
 
-	runPackagingTest(t, multiTarget,
+	runPackagingTest(t, config,
 		`
 		component {
 			name: "foo",
@@ -252,8 +263,11 @@
 }
 
 func TestPackagingBaseSingleTarget(t *testing.T) {
-	multiTarget := false
-	runPackagingTest(t, multiTarget,
+	config := testConfig{
+		multiTarget:                false,
+		depsCollectFirstTargetOnly: false,
+	}
+	runPackagingTest(t, config,
 		`
 		component {
 			name: "foo",
@@ -265,7 +279,7 @@
 		}
 		`, []string{"lib64/foo"})
 
-	runPackagingTest(t, multiTarget,
+	runPackagingTest(t, config,
 		`
 		component {
 			name: "foo",
@@ -282,7 +296,7 @@
 		}
 		`, []string{"lib64/foo", "lib64/bar"})
 
-	runPackagingTest(t, multiTarget,
+	runPackagingTest(t, config,
 		`
 		component {
 			name: "foo",
@@ -304,7 +318,7 @@
 		}
 		`, []string{"lib64/foo"})
 
-	runPackagingTest(t, multiTarget,
+	runPackagingTest(t, config,
 		`
 		component {
 			name: "foo",
@@ -325,7 +339,7 @@
 		}
 		`, []string{"lib64/foo", "lib64/bar"})
 
-	runPackagingTest(t, multiTarget,
+	runPackagingTest(t, config,
 		`
 		component {
 			name: "foo",
@@ -353,7 +367,7 @@
 		}
 		`, []string{"lib64/foo", "lib64/bar"})
 
-	runPackagingTest(t, multiTarget,
+	runPackagingTest(t, config,
 		`
 		component {
 			name: "foo",
@@ -374,8 +388,11 @@
 func TestPackagingWithSkipInstallDeps(t *testing.T) {
 	// package -[dep]-> foo -[dep]-> bar      -[dep]-> baz
 	// Packaging should continue transitively through modules that are not installed.
-	multiTarget := false
-	runPackagingTest(t, multiTarget,
+	config := testConfig{
+		multiTarget:                false,
+		depsCollectFirstTargetOnly: false,
+	}
+	runPackagingTest(t, config,
 		`
 		component {
 			name: "foo",
@@ -398,3 +415,238 @@
 		}
 		`, []string{"lib64/foo", "lib64/bar", "lib64/baz"})
 }
+
+func TestPackagingWithDepsCollectFirstTargetOnly(t *testing.T) {
+	config := testConfig{
+		multiTarget:                true,
+		depsCollectFirstTargetOnly: true,
+	}
+	runPackagingTest(t, config,
+		`
+		component {
+			name: "foo",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"],
+		}
+		`, []string{"lib64/foo"})
+
+	runPackagingTest(t, config,
+		`
+		component {
+			name: "foo",
+			deps: ["bar"],
+		}
+
+		component {
+			name: "bar",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"],
+		}
+		`, []string{"lib64/foo", "lib64/bar"})
+
+	runPackagingTest(t, config,
+		`
+		component {
+			name: "foo",
+			deps: ["bar"],
+		}
+
+		component {
+			name: "bar",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"],
+			compile_multilib: "both",
+		}
+		`, []string{"lib64/foo", "lib64/bar"})
+
+	runPackagingTest(t, config,
+		`
+		component {
+			name: "foo",
+		}
+
+		component {
+			name: "bar",
+			compile_multilib: "32",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"],
+			multilib: {
+				lib32: {
+					deps: ["bar"],
+				},
+			},
+			compile_multilib: "both",
+		}
+		`, []string{"lib32/bar", "lib64/foo"})
+
+	runPackagingTest(t, config,
+		`
+		component {
+			name: "foo",
+		}
+
+		component {
+			name: "bar",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"],
+			multilib: {
+				both: {
+					deps: ["bar"],
+				},
+			},
+			compile_multilib: "both",
+		}
+		`, []string{"lib64/foo", "lib32/bar", "lib64/bar"})
+
+	runPackagingTest(t, config,
+		`
+		component {
+			name: "foo",
+		}
+
+		component {
+			name: "bar",
+		}
+
+		component {
+			name: "baz",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"],
+			arch: {
+				arm64: {
+					deps: ["bar"],
+				},
+				x86_64: {
+					deps: ["baz"],
+				},
+			},
+			compile_multilib: "both",
+		}
+		`, []string{"lib64/foo", "lib64/bar"})
+}
+
+func TestDebuggableDeps(t *testing.T) {
+	bp := `
+		component {
+			name: "foo",
+		}
+
+		component {
+			name: "bar",
+			deps: ["baz"],
+		}
+
+		component {
+			name: "baz",
+		}
+
+		package_module {
+			name: "package",
+			deps: ["foo"] + select(product_variable("debuggable"), {
+				true: ["bar"],
+				default: [],
+			}),
+		}`
+	testcases := []struct {
+		debuggable bool
+		expected   []string
+	}{
+		{
+			debuggable: true,
+			expected:   []string{"lib64/foo", "lib64/bar", "lib64/baz"},
+		},
+		{
+			debuggable: false,
+			expected:   []string{"lib64/foo"},
+		},
+	}
+	for _, tc := range testcases {
+		config := testConfig{
+			debuggable: tc.debuggable,
+		}
+		runPackagingTest(t, config, bp, tc.expected)
+	}
+}
+
+func TestPrefer32Deps(t *testing.T) {
+	bpTemplate := `
+		component {
+			name: "foo",
+			compile_multilib: "both", // not needed but for clarity
+		}
+
+		component {
+			name: "foo_32only",
+			compile_multilib: "prefer32",
+		}
+
+		component {
+			name: "foo_64only",
+			compile_multilib: "64",
+		}
+
+		package_module {
+			name: "package",
+			compile_multilib: "%COMPILE_MULTILIB%",
+			multilib: {
+				prefer32: {
+					deps: %DEPS%,
+				},
+			},
+		}
+	`
+
+	testcases := []struct {
+		compileMultilib string
+		deps            []string
+		expected        []string
+	}{
+		{
+			compileMultilib: "first",
+			deps:            []string{"foo", "foo_64only"},
+			expected:        []string{"lib64/foo", "lib64/foo_64only"},
+		},
+		{
+			compileMultilib: "64",
+			deps:            []string{"foo", "foo_64only"},
+			expected:        []string{"lib64/foo", "lib64/foo_64only"},
+		},
+		{
+			compileMultilib: "32",
+			deps:            []string{"foo", "foo_32only"},
+			expected:        []string{"lib32/foo", "lib32/foo_32only"},
+		},
+		{
+			compileMultilib: "both",
+			deps:            []string{"foo", "foo_32only", "foo_64only"},
+			expected:        []string{"lib32/foo", "lib32/foo_32only", "lib64/foo_64only"},
+		},
+	}
+	for _, tc := range testcases {
+		config := testConfig{
+			multiTarget:                true,
+			depsCollectFirstTargetOnly: true,
+		}
+		bp := strings.Replace(bpTemplate, "%COMPILE_MULTILIB%", tc.compileMultilib, -1)
+		bp = strings.Replace(bp, "%DEPS%", `["`+strings.Join(tc.deps, `", "`)+`"]`, -1)
+		runPackagingTest(t, config, bp, tc.expected)
+	}
+}
diff --git a/android/paths.go b/android/paths.go
index 2b33f67..dda48dd 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -15,6 +15,9 @@
 package android
 
 import (
+	"bytes"
+	"encoding/gob"
+	"errors"
 	"fmt"
 	"os"
 	"path/filepath"
@@ -60,6 +63,7 @@
 
 	ModuleDir() string
 	ModuleErrorf(fmt string, args ...interface{})
+	OtherModulePropertyErrorf(module Module, property, fmt string, args ...interface{})
 }
 
 var _ EarlyModulePathContext = ModuleContext(nil)
@@ -277,6 +281,7 @@
 
 type genPathProvider interface {
 	genPathWithExt(ctx ModuleOutPathContext, subdir, ext string) ModuleGenPath
+	genPathWithExtAndTrimExt(ctx ModuleOutPathContext, subdir, ext string, trimExt string) ModuleGenPath
 }
 type objPathProvider interface {
 	objPathWithExt(ctx ModuleOutPathContext, subdir, ext string) ModuleObjPath
@@ -295,6 +300,16 @@
 	return PathForModuleGen(ctx)
 }
 
+// GenPathWithExtAndTrimExt derives a new file path in ctx's generated sources directory
+// from the current path, but with the new extension and trim the suffix.
+func GenPathWithExtAndTrimExt(ctx ModuleOutPathContext, subdir string, p Path, ext string, trimExt string) ModuleGenPath {
+	if path, ok := p.(genPathProvider); ok {
+		return path.genPathWithExtAndTrimExt(ctx, subdir, ext, trimExt)
+	}
+	ReportPathErrorf(ctx, "Tried to create generated file from unsupported path: %s(%s)", reflect.TypeOf(p).Name(), p)
+	return PathForModuleGen(ctx)
+}
+
 // ObjPathWithExt derives a new file path in ctx's object directory from the
 // current path, but with the new extension.
 func ObjPathWithExt(ctx ModuleOutPathContext, subdir string, p Path, ext string) ModuleObjPath {
@@ -448,8 +463,8 @@
 //   - glob, relative to the local module directory, resolves as filepath(s), relative to the local
 //     source directory.
 //   - other modules using the ":name{.tag}" syntax. These modules must implement SourceFileProducer
-//     or OutputFileProducer. These resolve as a filepath to an output filepath or generated source
-//     filepath.
+//     or set the OutputFilesProvider. These resolve as a filepath to an output filepath or generated
+//     source filepath.
 //
 // Properties passed as the paths argument must have been annotated with struct tag
 // `android:"path"` so that dependencies on SourceFileProducer modules will have already been handled by the
@@ -476,8 +491,8 @@
 //   - glob, relative to the local module directory, resolves as filepath(s), relative to the local
 //     source directory. Not valid in excludes.
 //   - other modules using the ":name{.tag}" syntax. These modules must implement SourceFileProducer
-//     or OutputFileProducer. These resolve as a filepath to an output filepath or generated source
-//     filepath.
+//     or set the OutputFilesProvider. These resolve as a filepath to an output filepath or generated
+//     source filepath.
 //
 // excluding the items (similarly resolved
 // Properties passed as the paths argument must have been annotated with struct tag
@@ -550,24 +565,18 @@
 	if module == nil {
 		return nil, missingDependencyError{[]string{moduleName}}
 	}
-	if aModule, ok := module.(Module); ok && !aModule.Enabled() {
+	if aModule, ok := module.(Module); ok && !aModule.Enabled(ctx) {
 		return nil, missingDependencyError{[]string{moduleName}}
 	}
-	if outProducer, ok := module.(OutputFileProducer); ok {
-		outputFiles, err := outProducer.OutputFiles(tag)
-		if err != nil {
-			return nil, fmt.Errorf("path dependency %q: %s", path, err)
-		}
-		return outputFiles, nil
-	} else if tag != "" {
-		return nil, fmt.Errorf("path dependency %q is not an output file producing module", path)
-	} else if goBinary, ok := module.(bootstrap.GoBinaryTool); ok {
+	if goBinary, ok := module.(bootstrap.GoBinaryTool); ok && tag == "" {
 		goBinaryPath := PathForGoBinary(ctx, goBinary)
 		return Paths{goBinaryPath}, nil
-	} else if srcProducer, ok := module.(SourceFileProducer); ok {
-		return srcProducer.Srcs(), nil
+	}
+	outputFiles, err := outputFilesForModule(ctx, module, tag)
+	if outputFiles != nil && err == nil {
+		return outputFiles, nil
 	} else {
-		return nil, fmt.Errorf("path dependency %q is not a source file producing module", path)
+		return nil, err
 	}
 }
 
@@ -611,8 +620,8 @@
 //   - glob, relative to the local module directory, resolves as filepath(s), relative to the local
 //     source directory. Not valid in excludes.
 //   - other modules using the ":name{.tag}" syntax. These modules must implement SourceFileProducer
-//     or OutputFileProducer. These resolve as a filepath to an output filepath or generated source
-//     filepath.
+//     or set the OutputFilesProvider. These resolve as a filepath to an output filepath or generated
+//     source filepath.
 //
 // and a list of the module names of missing module dependencies are returned as the second return.
 // Properties passed as the paths argument must have been annotated with struct tag
@@ -1062,6 +1071,28 @@
 	rel  string
 }
 
+func (p basePath) GobEncode() ([]byte, error) {
+	w := new(bytes.Buffer)
+	encoder := gob.NewEncoder(w)
+	err := errors.Join(encoder.Encode(p.path), encoder.Encode(p.rel))
+	if err != nil {
+		return nil, err
+	}
+
+	return w.Bytes(), nil
+}
+
+func (p *basePath) GobDecode(data []byte) error {
+	r := bytes.NewBuffer(data)
+	decoder := gob.NewDecoder(r)
+	err := errors.Join(decoder.Decode(&p.path), decoder.Decode(&p.rel))
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
 func (p basePath) Ext() string {
 	return filepath.Ext(p.path)
 }
@@ -1300,6 +1331,28 @@
 	fullPath string
 }
 
+func (p OutputPath) GobEncode() ([]byte, error) {
+	w := new(bytes.Buffer)
+	encoder := gob.NewEncoder(w)
+	err := errors.Join(encoder.Encode(p.basePath), encoder.Encode(p.soongOutDir), encoder.Encode(p.fullPath))
+	if err != nil {
+		return nil, err
+	}
+
+	return w.Bytes(), nil
+}
+
+func (p *OutputPath) GobDecode(data []byte) error {
+	r := bytes.NewBuffer(data)
+	decoder := gob.NewDecoder(r)
+	err := errors.Join(decoder.Decode(&p.basePath), decoder.Decode(&p.soongOutDir), decoder.Decode(&p.fullPath))
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
 func (p OutputPath) withRel(rel string) OutputPath {
 	p.basePath = p.basePath.withRel(rel)
 	p.fullPath = filepath.Join(p.fullPath, rel)
@@ -1507,6 +1560,17 @@
 	return PathForModuleGen(ctx, subdir, pathtools.ReplaceExtension(p.path, ext))
 }
 
+func (p SourcePath) genPathWithExtAndTrimExt(ctx ModuleOutPathContext, subdir, ext string, trimExt string) ModuleGenPath {
+	// If Trim_extension being set, force append Output_extension without replace original extension.
+	if trimExt != "" {
+		if ext != "" {
+			return PathForModuleGen(ctx, subdir, strings.TrimSuffix(p.path, trimExt)+"."+ext)
+		}
+		return PathForModuleGen(ctx, subdir, strings.TrimSuffix(p.path, trimExt))
+	}
+	return PathForModuleGen(ctx, subdir, pathtools.ReplaceExtension(p.path, ext))
+}
+
 func (p SourcePath) objPathWithExt(ctx ModuleOutPathContext, subdir, ext string) ModuleObjPath {
 	return PathForModuleObj(ctx, subdir, pathtools.ReplaceExtension(p.path, ext))
 }
@@ -1540,11 +1604,10 @@
 	ModuleName() string
 	ModuleDir() string
 	ModuleSubDir() string
-	SoongConfigTraceHash() string
 }
 
 func pathForModuleOut(ctx ModuleOutPathContext) OutputPath {
-	return PathForOutput(ctx, ".intermediates", ctx.ModuleDir(), ctx.ModuleName(), ctx.ModuleSubDir(), ctx.SoongConfigTraceHash())
+	return PathForOutput(ctx, ".intermediates", ctx.ModuleDir(), ctx.ModuleName(), ctx.ModuleSubDir())
 }
 
 // PathForModuleOut returns a Path representing the paths... under the module's
@@ -1594,6 +1657,17 @@
 	return PathForModuleGen(ctx, subdir, pathtools.ReplaceExtension(p.path, ext))
 }
 
+func (p ModuleGenPath) genPathWithExtAndTrimExt(ctx ModuleOutPathContext, subdir, ext string, trimExt string) ModuleGenPath {
+	// If Trim_extension being set, force append Output_extension without replace original extension.
+	if trimExt != "" {
+		if ext != "" {
+			return PathForModuleGen(ctx, subdir, strings.TrimSuffix(p.path, trimExt)+"."+ext)
+		}
+		return PathForModuleGen(ctx, subdir, strings.TrimSuffix(p.path, trimExt))
+	}
+	return PathForModuleGen(ctx, subdir, pathtools.ReplaceExtension(p.path, ext))
+}
+
 func (p ModuleGenPath) objPathWithExt(ctx ModuleOutPathContext, subdir, ext string) ModuleObjPath {
 	return PathForModuleObj(ctx, subdir, pathtools.ReplaceExtension(p.path, ext))
 }
diff --git a/android/paths_test.go b/android/paths_test.go
index 93b9b9a..941f0ca 100644
--- a/android/paths_test.go
+++ b/android/paths_test.go
@@ -1183,9 +1183,6 @@
 		Outs   []string
 		Tagged []string
 	}
-
-	outs   Paths
-	tagged Paths
 }
 
 func pathForModuleSrcOutputFileProviderModuleFactory() Module {
@@ -1196,24 +1193,17 @@
 }
 
 func (p *pathForModuleSrcOutputFileProviderModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	var outs, taggedOuts Paths
 	for _, out := range p.props.Outs {
-		p.outs = append(p.outs, PathForModuleOut(ctx, out))
+		outs = append(outs, PathForModuleOut(ctx, out))
 	}
 
 	for _, tagged := range p.props.Tagged {
-		p.tagged = append(p.tagged, PathForModuleOut(ctx, tagged))
+		taggedOuts = append(taggedOuts, PathForModuleOut(ctx, tagged))
 	}
-}
 
-func (p *pathForModuleSrcOutputFileProviderModule) OutputFiles(tag string) (Paths, error) {
-	switch tag {
-	case "":
-		return p.outs, nil
-	case ".tagged":
-		return p.tagged, nil
-	default:
-		return nil, fmt.Errorf("unsupported tag %q", tag)
-	}
+	ctx.SetOutputFiles(outs, "")
+	ctx.SetOutputFiles(taggedOuts, ".tagged")
 }
 
 type pathForModuleSrcTestCase struct {
diff --git a/android/prebuilt.go b/android/prebuilt.go
index 2b7b55b..51b86a5 100644
--- a/android/prebuilt.go
+++ b/android/prebuilt.go
@@ -275,7 +275,7 @@
 	srcPropertyName := proptools.PropertyNameForField(srcField)
 
 	srcsSupplier := func(ctx BaseModuleContext, _ Module) []string {
-		if !module.Enabled() {
+		if !module.Enabled(ctx) {
 			return nil
 		}
 		value := srcPropsValue.FieldByIndex(srcFieldIndex)
@@ -425,7 +425,7 @@
 	m := ctx.Module()
 	// If this module is a prebuilt, is enabled and has not been renamed to source then add a
 	// dependency onto the source if it is present.
-	if p := GetEmbeddedPrebuilt(m); p != nil && m.Enabled() && !p.properties.PrebuiltRenamedToSource {
+	if p := GetEmbeddedPrebuilt(m); p != nil && m.Enabled(ctx) && !p.properties.PrebuiltRenamedToSource {
 		bmn, _ := m.(baseModuleName)
 		name := bmn.BaseModuleName()
 		if ctx.OtherModuleReverseDependencyVariantExists(name) {
@@ -437,7 +437,7 @@
 		// 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(m, acDepTag, "all_apex_contributions")
+			ctx.AddDependency(m, AcDepTag, "all_apex_contributions")
 		}
 
 	}
@@ -474,7 +474,7 @@
 		}
 		// Propagate the provider received from `all_apex_contributions`
 		// to the source module
-		ctx.VisitDirectDepsWithTag(acDepTag, func(am Module) {
+		ctx.VisitDirectDepsWithTag(AcDepTag, func(am Module) {
 			psi, _ := OtherModuleProvider(ctx, am, PrebuiltSelectionInfoProvider)
 			SetProvider(ctx, PrebuiltSelectionInfoProvider, psi)
 		})
@@ -580,7 +580,7 @@
 		bmn, _ := m.(baseModuleName)
 		name := bmn.BaseModuleName()
 		psi := PrebuiltSelectionInfoMap{}
-		ctx.VisitDirectDepsWithTag(acDepTag, func(am Module) {
+		ctx.VisitDirectDepsWithTag(AcDepTag, func(am Module) {
 			psi, _ = OtherModuleProvider(ctx, am, PrebuiltSelectionInfoProvider)
 		})
 
@@ -702,7 +702,7 @@
 	}
 
 	// If source is not available or is disabled then always use the prebuilt.
-	if source == nil || !source.Enabled() {
+	if source == nil || !source.Enabled(ctx) {
 		return true
 	}
 
diff --git a/android/prebuilt_test.go b/android/prebuilt_test.go
index 2574ed4..6e4fc0c 100644
--- a/android/prebuilt_test.go
+++ b/android/prebuilt_test.go
@@ -15,7 +15,6 @@
 package android
 
 import (
-	"fmt"
 	"testing"
 
 	"github.com/google/blueprint"
@@ -351,7 +350,7 @@
 						}
 					})
 
-					moduleIsDisabled := !foo.Module().Enabled()
+					moduleIsDisabled := !foo.Module().Enabled(PanickingConfigAndErrorContext(result.TestContext))
 					deps := foo.Module().(*sourceModule).deps
 					if moduleIsDisabled {
 						if len(deps) > 0 {
@@ -494,7 +493,6 @@
 	properties struct {
 		Srcs []string `android:"path,arch_variant"`
 	}
-	src Path
 }
 
 func newPrebuiltModule() Module {
@@ -510,24 +508,17 @@
 }
 
 func (p *prebuiltModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	var src Path
 	if len(p.properties.Srcs) >= 1 {
-		p.src = p.prebuilt.SingleSourcePath(ctx)
+		src = p.prebuilt.SingleSourcePath(ctx)
 	}
+	ctx.SetOutputFiles(Paths{src}, "")
 }
 
 func (p *prebuiltModule) Prebuilt() *Prebuilt {
 	return &p.prebuilt
 }
 
-func (p *prebuiltModule) OutputFiles(tag string) (Paths, error) {
-	switch tag {
-	case "":
-		return Paths{p.src}, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
 type sourceModuleProperties struct {
 	Deps []string `android:"path,arch_variant"`
 }
@@ -610,45 +601,3 @@
 		}
 		`, selectMainlineModuleContritbutions)
 }
-
-// Test that apex_contributions of prebuilt modules are ignored in coverage builds
-func TestSourceIsSelectedInCoverageBuilds(t *testing.T) {
-	prebuiltMainlineContributions := GroupFixturePreparers(
-		FixtureModifyProductVariables(func(variables FixtureProductVariables) {
-			variables.BuildFlags = map[string]string{
-				"RELEASE_APEX_CONTRIBUTIONS_ADSERVICES": "my_prebuilt_apex_contributions",
-			}
-		}),
-		FixtureMergeEnv(map[string]string{
-			"EMMA_INSTRUMENT_FRAMEWORK": "true",
-		}),
-	)
-	bp := `
-		source {
-			name: "foo",
-		}
-		prebuilt {
-			name: "foo",
-			srcs: ["prebuilt_file"],
-		}
-		apex_contributions {
-			name: "my_prebuilt_apex_contributions",
-			api_domain: "my_mainline_module",
-			contents: [
-			  "prebuilt_foo",
-			],
-		}
-		all_apex_contributions {
-			name: "all_apex_contributions",
-		}
-		`
-	ctx := GroupFixturePreparers(
-		PrepareForTestWithArchMutator,
-		PrepareForTestWithPrebuilts,
-		FixtureRegisterWithContext(registerTestPrebuiltModules),
-		prebuiltMainlineContributions).RunTestWithBp(t, bp)
-	source := ctx.ModuleForTests("foo", "android_common").Module()
-	AssertBoolEquals(t, "Source should be preferred in coverage builds", true, !source.IsHideFromMake())
-	prebuilt := ctx.ModuleForTests("prebuilt_foo", "android_common").Module()
-	AssertBoolEquals(t, "Prebuilt should not be preferred in coverage builds", false, !prebuilt.IsHideFromMake())
-}
diff --git a/android/product_config.go b/android/product_config.go
new file mode 100644
index 0000000..20b29a7
--- /dev/null
+++ b/android/product_config.go
@@ -0,0 +1,58 @@
+// 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() {
+	ctx := InitRegistrationContext
+	ctx.RegisterModuleType("product_config", productConfigFactory)
+}
+
+type productConfigModule struct {
+	ModuleBase
+}
+
+func (p *productConfigModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	if ctx.ModuleName() != "product_config" || ctx.ModuleDir() != "build/soong" {
+		ctx.ModuleErrorf("There can only be one product_config module in build/soong")
+		return
+	}
+	outputFilePath := PathForModuleOut(ctx, p.Name()+".json").OutputPath
+
+	// DeviceProduct can be null so calling ctx.Config().DeviceProduct() may cause null dereference
+	targetProduct := proptools.String(ctx.Config().config.productVariables.DeviceProduct)
+	if targetProduct != "" {
+		targetProduct += "."
+	}
+	soongVariablesPath := PathForOutput(ctx, "soong."+targetProduct+"variables")
+	extraVariablesPath := PathForOutput(ctx, "soong."+targetProduct+"extra.variables")
+
+	rule := NewRuleBuilder(pctx, ctx)
+	rule.Command().BuiltTool("merge_json").
+		Output(outputFilePath).
+		Input(soongVariablesPath).
+		Input(extraVariablesPath).
+		rule.Build("product_config.json", "building product_config.json")
+
+	ctx.SetOutputFiles(Paths{outputFilePath}, "")
+}
+
+// product_config module exports product variables and extra variables as a JSON file.
+func productConfigFactory() Module {
+	module := &productConfigModule{}
+	InitAndroidModule(module)
+	return module
+}
diff --git a/android/register.go b/android/register.go
index d00c15f..eb6a35e 100644
--- a/android/register.go
+++ b/android/register.go
@@ -16,8 +16,9 @@
 
 import (
 	"fmt"
-	"github.com/google/blueprint"
 	"reflect"
+
+	"github.com/google/blueprint"
 )
 
 // A sortable component is one whose registration order affects the order in which it is executed
@@ -155,7 +156,6 @@
 func NewContext(config Config) *Context {
 	ctx := &Context{blueprint.NewContext(), config}
 	ctx.SetSrcDir(absSrcDir)
-	ctx.AddIncludeTags(config.IncludeTags()...)
 	ctx.AddSourceRootDirs(config.SourceRootDirs()...)
 	return ctx
 }
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 85e29bd..8b03124 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -58,6 +58,7 @@
 	sboxInputs       bool
 	sboxManifestPath WritablePath
 	missingDeps      []string
+	args             map[string]string
 }
 
 // NewRuleBuilder returns a newly created RuleBuilder.
@@ -78,6 +79,17 @@
 	return rb
 }
 
+// Set the phony_output argument.
+// This causes the output files to be ignored.
+// If the output isn't created, it's not treated as an error.
+// The build rule is run every time whether or not the output is created.
+func (rb *RuleBuilder) SetPhonyOutput() {
+	if rb.args == nil {
+		rb.args = make(map[string]string)
+	}
+	rb.args["phony_output"] = "true"
+}
+
 // RuleBuilderInstall is a tuple of install from and to locations.
 type RuleBuilderInstall struct {
 	From Path
@@ -726,6 +738,12 @@
 		commandString = proptools.NinjaEscape(commandString)
 	}
 
+	args_vars := make([]string, len(r.args))
+	i := 0
+	for k, _ := range r.args {
+		args_vars[i] = k
+		i++
+	}
 	r.ctx.Build(r.pctx, BuildParams{
 		Rule: r.ctx.Rule(r.pctx, name, blueprint.RuleParams{
 			Command:        commandString,
@@ -734,7 +752,7 @@
 			Rspfile:        proptools.NinjaEscape(rspFile),
 			RspfileContent: rspFileContent,
 			Pool:           pool,
-		}),
+		}, args_vars...),
 		Inputs:          rspFileInputs,
 		Implicits:       inputs,
 		OrderOnly:       r.OrderOnlys(),
@@ -744,6 +762,7 @@
 		Depfile:         depFile,
 		Deps:            depFormat,
 		Description:     desc,
+		Args:            r.args,
 	})
 }
 
diff --git a/android/sdk.go b/android/sdk.go
index 121470d..4bcbe2e 100644
--- a/android/sdk.go
+++ b/android/sdk.go
@@ -513,6 +513,9 @@
 	// SupportedLinkages returns the names of the linkage variants supported by this module.
 	SupportedLinkages() []string
 
+	// DisablesStrip returns true if the stripping needs to be disabled for this module.
+	DisablesStrip() bool
+
 	// ArePrebuiltsRequired returns true if prebuilts are required in the sdk snapshot, false
 	// otherwise.
 	ArePrebuiltsRequired() bool
@@ -618,6 +621,9 @@
 	// The names of linkage variants supported by this module.
 	SupportedLinkageNames []string
 
+	// StripDisabled returns true if the stripping needs to be disabled for this module.
+	StripDisabled bool
+
 	// When set to true BpPropertyNotRequired indicates that the member type does not require the
 	// property to be specifiable in an Android.bp file.
 	BpPropertyNotRequired bool
@@ -689,6 +695,10 @@
 	return b.SupportedLinkageNames
 }
 
+func (b *SdkMemberTypeBase) DisablesStrip() bool {
+	return b.StripDisabled
+}
+
 // registeredModuleExportsMemberTypes is the set of registered SdkMemberTypes for module_exports
 // modules.
 var registeredModuleExportsMemberTypes = &sdkRegistry{}
diff --git a/android/sdk_version.go b/android/sdk_version.go
index b2ff960..01b55d0 100644
--- a/android/sdk_version.go
+++ b/android/sdk_version.go
@@ -40,9 +40,15 @@
 // SdkKind represents a particular category of an SDK spec like public, system, test, etc.
 type SdkKind int
 
+// These are generally ordered from the narrower sdk version to the wider sdk version,
+// but not all entries have a strict subset/superset relationship.
+// For example, SdkTest and SdkModule do not have a strict subset/superset relationship but both
+// are supersets of SdkSystem.
+// The general trend should be kept when an additional sdk kind is added.
 const (
 	SdkInvalid SdkKind = iota
 	SdkNone
+	SdkToolchain // API surface provided by ART to compile other API domains
 	SdkCore
 	SdkCorePlatform
 	SdkIntraCore // API surface provided by one core module to another
@@ -53,7 +59,6 @@
 	SdkModule
 	SdkSystemServer
 	SdkPrivate
-	SdkToolchain // API surface provided by ART to compile other API domains
 )
 
 // String returns the string representation of this SdkKind
diff --git a/android/selects_test.go b/android/selects_test.go
index f912ce6..fc020a4 100644
--- a/android/selects_test.go
+++ b/android/selects_test.go
@@ -25,11 +25,14 @@
 
 func TestSelects(t *testing.T) {
 	testCases := []struct {
-		name          string
-		bp            string
-		provider      selectsTestProvider
-		vendorVars    map[string]map[string]string
-		expectedError string
+		name           string
+		bp             string
+		fs             MockFS
+		provider       selectsTestProvider
+		providers      map[string]selectsTestProvider
+		vendorVars     map[string]map[string]string
+		vendorVarTypes map[string]map[string]string
+		expectedError  string
 	}{
 		{
 			name: "basic string list",
@@ -96,6 +99,26 @@
 			},
 		},
 		{
+			name: "Expression in select",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
+					"a": "foo" + "bar",
+					default: "baz",
+				}),
+			}
+			`,
+			provider: selectsTestProvider{
+				my_string: proptools.StringPtr("foobar"),
+			},
+			vendorVars: map[string]map[string]string{
+				"my_namespace": {
+					"my_variable": "a",
+				},
+			},
+		},
+		{
 			name: "paths with module references",
 			bp: `
 			my_module_type {
@@ -110,20 +133,6 @@
 			expectedError: `"foo" depends on undefined module "c"`,
 		},
 		{
-			name: "Differing types",
-			bp: `
-			my_module_type {
-				name: "foo",
-				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
-					"a": "a.cpp",
-					"b": true,
-					default: "c.cpp",
-				}),
-			}
-			`,
-			expectedError: `Android.bp:8:5: Found select statement with differing types "string" and "bool" in its cases`,
-		},
-		{
 			name: "Select type doesn't match property type",
 			bp: `
 			my_module_type {
@@ -135,7 +144,7 @@
 				}),
 			}
 			`,
-			expectedError: `can't assign bool value to string property "my_string\[0\]"`,
+			expectedError: `can't assign bool value to string property`,
 		},
 		{
 			name: "String list non-default",
@@ -411,6 +420,42 @@
 			},
 		},
 		{
+			name: "defaults applied to multiple modules",
+			bp: `
+			my_module_type {
+				name: "foo2",
+				defaults: ["bar"],
+				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), {
+					"a": ["a1"],
+					default: ["b1"],
+				}),
+			}
+			my_module_type {
+				name: "foo",
+				defaults: ["bar"],
+				my_string_list: select(soong_config_variable("my_namespace", "my_variable"), {
+					"a": ["a1"],
+					default: ["b1"],
+				}),
+			}
+			my_defaults {
+				name: "bar",
+				my_string_list: select(soong_config_variable("my_namespace", "my_variable2"), {
+					"a": ["a2"],
+					default: ["b2"],
+				}),
+			}
+			`,
+			providers: map[string]selectsTestProvider{
+				"foo": {
+					my_string_list: &[]string{"b2", "b1"},
+				},
+				"foo2": {
+					my_string_list: &[]string{"b2", "b1"},
+				},
+			},
+		},
+		{
 			name: "Replacing string list",
 			bp: `
 			my_module_type {
@@ -509,6 +554,24 @@
 			},
 		},
 		{
+			name: "Unhandled string value",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
+					"foo": "a",
+					"bar": "b",
+				}),
+			}
+			`,
+			vendorVars: map[string]map[string]string{
+				"my_namespace": {
+					"my_variable": "baz",
+				},
+			},
+			expectedError: `my_string: soong_config_variable\("my_namespace", "my_variable"\) had value "baz", which was not handled by the select statement`,
+		},
+		{
 			name: "Select on boolean",
 			bp: `
 			my_module_type {
@@ -529,6 +592,31 @@
 			},
 		},
 		{
+			name: "Select on boolean soong config variable",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
+					true: "t",
+					false: "f",
+				}),
+			}
+			`,
+			vendorVars: map[string]map[string]string{
+				"my_namespace": {
+					"my_variable": "true",
+				},
+			},
+			vendorVarTypes: map[string]map[string]string{
+				"my_namespace": {
+					"my_variable": "bool",
+				},
+			},
+			provider: selectsTestProvider{
+				my_string: proptools.StringPtr("t"),
+			},
+		},
+		{
 			name: "Select on boolean false",
 			bp: `
 			my_module_type {
@@ -559,7 +647,7 @@
 				}),
 			}
 			`,
-			expectedError: "foo",
+			expectedError: `my_string: boolean_var_for_testing\(\) had value undefined, which was not handled by the select statement`,
 		},
 		{
 			name: "Select on boolean undefined with default",
@@ -596,31 +684,375 @@
 			},
 			expectedError: "Expected all branches of a select on condition boolean_var_for_testing\\(\\) to have type bool, found string",
 		},
+		{
+			name: "Assigning select to nonconfigurable bool",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_nonconfigurable_bool: select(arch(), {
+					"x86_64": true,
+					default: false,
+				}),
+			}
+			`,
+			expectedError: `can't assign select statement to non-configurable property "my_nonconfigurable_bool"`,
+		},
+		{
+			name: "Assigning select to nonconfigurable string",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_nonconfigurable_string: select(arch(), {
+					"x86_64": "x86!",
+					default: "unknown!",
+				}),
+			}
+			`,
+			expectedError: `can't assign select statement to non-configurable property "my_nonconfigurable_string"`,
+		},
+		{
+			name: "Assigning appended selects to nonconfigurable string",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_nonconfigurable_string: select(arch(), {
+					"x86_64": "x86!",
+					default: "unknown!",
+				}) + select(os(), {
+					"darwin": "_darwin!",
+					default: "unknown!",
+				}),
+			}
+			`,
+			expectedError: `can't assign select statement to non-configurable property "my_nonconfigurable_string"`,
+		},
+		{
+			name: "Assigning select to nonconfigurable string list",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_nonconfigurable_string_list: select(arch(), {
+					"x86_64": ["foo", "bar"],
+					default: ["baz", "qux"],
+				}),
+			}
+			`,
+			expectedError: `can't assign select statement to non-configurable property "my_nonconfigurable_string_list"`,
+		},
+		{
+			name: "Select in variable",
+			bp: `
+			my_second_variable = ["after.cpp"]
+			my_variable = select(soong_config_variable("my_namespace", "my_variable"), {
+				"a": ["a.cpp"],
+				"b": ["b.cpp"],
+				default: ["c.cpp"],
+			}) + my_second_variable
+			my_module_type {
+				name: "foo",
+				my_string_list: ["before.cpp"] + my_variable,
+			}
+			`,
+			provider: selectsTestProvider{
+				my_string_list: &[]string{"before.cpp", "a.cpp", "after.cpp"},
+			},
+			vendorVars: map[string]map[string]string{
+				"my_namespace": {
+					"my_variable": "a",
+				},
+			},
+		},
+		{
+			name: "Soong config value variable on configurable property",
+			bp: `
+			soong_config_module_type {
+				name: "soong_config_my_module_type",
+				module_type: "my_module_type",
+				config_namespace: "my_namespace",
+				value_variables: ["my_variable"],
+				properties: ["my_string", "my_string_list"],
+			}
+
+			soong_config_my_module_type {
+				name: "foo",
+				my_string_list: ["before.cpp"],
+				soong_config_variables: {
+					my_variable: {
+						my_string_list: ["after_%s.cpp"],
+						my_string: "%s.cpp",
+					},
+				},
+			}
+			`,
+			provider: selectsTestProvider{
+				my_string:      proptools.StringPtr("foo.cpp"),
+				my_string_list: &[]string{"before.cpp", "after_foo.cpp"},
+			},
+			vendorVars: map[string]map[string]string{
+				"my_namespace": {
+					"my_variable": "foo",
+				},
+			},
+		},
+		{
+			name: "Property appending with variable",
+			bp: `
+			my_variable = ["b.cpp"]
+			my_module_type {
+				name: "foo",
+				my_string_list: ["a.cpp"] + my_variable + select(soong_config_variable("my_namespace", "my_variable"), {
+					"a": ["a.cpp"],
+					"b": ["b.cpp"],
+					default: ["c.cpp"],
+				}),
+			}
+			`,
+			provider: selectsTestProvider{
+				my_string_list: &[]string{"a.cpp", "b.cpp", "c.cpp"},
+			},
+		},
+		{
+			name: "Test AppendSimpleValue",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string_list: ["a.cpp"] + select(soong_config_variable("my_namespace", "my_variable"), {
+					"a": ["a.cpp"],
+					"b": ["b.cpp"],
+					default: ["c.cpp"],
+				}),
+			}
+			`,
+			vendorVars: map[string]map[string]string{
+				"selects_test": {
+					"append_to_string_list": "foo.cpp",
+				},
+			},
+			provider: selectsTestProvider{
+				my_string_list: &[]string{"a.cpp", "c.cpp", "foo.cpp"},
+			},
+		},
+		{
+			name: "Arch variant bool",
+			bp: `
+			my_variable = ["b.cpp"]
+			my_module_type {
+				name: "foo",
+				arch_variant_configurable_bool: false,
+				target: {
+					bionic_arm64: {
+						enabled: true,
+					},
+				},
+			}
+			`,
+			provider: selectsTestProvider{
+				arch_variant_configurable_bool: proptools.BoolPtr(false),
+			},
+		},
+		{
+			name: "Simple string binding",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
+					any @ my_binding: "hello " + my_binding,
+					default: "goodbye",
+				})
+			}
+			`,
+			vendorVars: map[string]map[string]string{
+				"my_namespace": {
+					"my_variable": "world!",
+				},
+			},
+			provider: selectsTestProvider{
+				my_string: proptools.StringPtr("hello world!"),
+			},
+		},
+		{
+			name: "Any branch with binding not taken",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
+					any @ my_binding: "hello " + my_binding,
+					default: "goodbye",
+				})
+			}
+			`,
+			provider: selectsTestProvider{
+				my_string: proptools.StringPtr("goodbye"),
+			},
+		},
+		{
+			name: "Any branch without binding",
+			bp: `
+			my_module_type {
+				name: "foo",
+				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
+					any: "hello",
+					default: "goodbye",
+				})
+			}
+			`,
+			vendorVars: map[string]map[string]string{
+				"my_namespace": {
+					"my_variable": "world!",
+				},
+			},
+			provider: selectsTestProvider{
+				my_string: proptools.StringPtr("hello"),
+			},
+		},
+		{
+			name: "Binding conflicts with file-level variable",
+			bp: `
+			my_binding = "asdf"
+			my_module_type {
+				name: "foo",
+				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
+					any @ my_binding: "hello",
+					default: "goodbye",
+				})
+			}
+			`,
+			vendorVars: map[string]map[string]string{
+				"my_namespace": {
+					"my_variable": "world!",
+				},
+			},
+			expectedError: "variable already set in inherited scope, previous assignment",
+		},
+		{
+			name: "Binding in combination with file-level variable",
+			bp: `
+			my_var = " there "
+			my_module_type {
+				name: "foo",
+				my_string: select(soong_config_variable("my_namespace", "my_variable"), {
+					any @ my_binding: "hello" + my_var + my_binding,
+					default: "goodbye",
+				})
+			}
+			`,
+			vendorVars: map[string]map[string]string{
+				"my_namespace": {
+					"my_variable": "world!",
+				},
+			},
+			provider: selectsTestProvider{
+				my_string: proptools.StringPtr("hello there world!"),
+			},
+		},
+		{
+			name: "Bindings in subdirectory inherits variable",
+			fs: map[string][]byte{
+				"Android.bp": []byte(`
+my_var = "abcd"
+`),
+				"directoryB/Android.bp": []byte(`
+my_module_type {
+	name: "foo",
+	my_string: select(soong_config_variable("my_namespace", "variable_a"), {
+		any @ my_binding: my_var + my_binding,
+		default: "",
+	}),
+}
+`),
+			},
+			vendorVars: map[string]map[string]string{
+				"my_namespace": {
+					"variable_a": "e",
+				},
+			},
+			provider: selectsTestProvider{
+				my_string: proptools.StringPtr("abcde"),
+			},
+		},
+		{
+			name: "Cannot modify variable after referenced by select",
+			bp: `
+my_var = "foo"
+my_module_type {
+	name: "foo",
+	my_string: select(soong_config_variable("my_namespace", "variable_a"), {
+		"a": my_var,
+		default: "",
+	}),
+}
+my_var += "bar"
+`,
+			vendorVars: map[string]map[string]string{
+				"my_namespace": {
+					"variable_a": "b", // notably not the value that causes my_var to be referenced
+				},
+			},
+			expectedError: `modified variable "my_var" with \+= after referencing`,
+		},
+		{
+			name: "Cannot shadow variable with binding",
+			bp: `
+my_var = "foo"
+my_module_type {
+	name: "foo",
+	my_string: select(soong_config_variable("my_namespace", "variable_a"), {
+		any @ my_var: my_var,
+		default: "",
+	}),
+}
+`,
+			vendorVars: map[string]map[string]string{
+				"my_namespace": {
+					"variable_a": "a",
+				},
+			},
+			expectedError: `variable already set in inherited scope, previous assignment:`,
+		},
 	}
 
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
+			fs := tc.fs
+			if fs == nil {
+				fs = make(MockFS)
+			}
+			if tc.bp != "" {
+				fs["Android.bp"] = []byte(tc.bp)
+			}
 			fixtures := GroupFixturePreparers(
 				PrepareForTestWithDefaults,
 				PrepareForTestWithArchMutator,
+				PrepareForTestWithSoongConfigModuleBuildComponents,
 				FixtureRegisterWithContext(func(ctx RegistrationContext) {
 					ctx.RegisterModuleType("my_module_type", newSelectsMockModule)
 					ctx.RegisterModuleType("my_defaults", newSelectsMockModuleDefaults)
 				}),
 				FixtureModifyProductVariables(func(variables FixtureProductVariables) {
 					variables.VendorVars = tc.vendorVars
+					variables.VendorVarTypes = tc.vendorVarTypes
 				}),
+				FixtureMergeMockFs(fs),
 			)
 			if tc.expectedError != "" {
 				fixtures = fixtures.ExtendWithErrorHandler(FixtureExpectsOneErrorPattern(tc.expectedError))
 			}
-			result := fixtures.RunTestWithBp(t, tc.bp)
+			result := fixtures.RunTest(t)
 
 			if tc.expectedError == "" {
-				m := result.ModuleForTests("foo", "android_arm64_armv8-a")
-				p, _ := OtherModuleProvider(result.testContext.OtherModuleProviderAdaptor(), m.Module(), selectsTestProviderKey)
-				if !reflect.DeepEqual(p, tc.provider) {
-					t.Errorf("Expected:\n  %q\ngot:\n  %q", tc.provider.String(), p.String())
+				if len(tc.providers) == 0 {
+					tc.providers = map[string]selectsTestProvider{
+						"foo": tc.provider,
+					}
+				}
+
+				for moduleName := range tc.providers {
+					expected := tc.providers[moduleName]
+					m := result.ModuleForTests(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())
+					}
 				}
 			}
 		})
@@ -628,11 +1060,15 @@
 }
 
 type selectsTestProvider struct {
-	my_bool               *bool
-	my_string             *string
-	my_string_list        *[]string
-	my_paths              *[]string
-	replacing_string_list *[]string
+	my_bool                        *bool
+	my_string                      *string
+	my_string_list                 *[]string
+	my_paths                       *[]string
+	replacing_string_list          *[]string
+	arch_variant_configurable_bool *bool
+	my_nonconfigurable_bool        *bool
+	my_nonconfigurable_string      *string
+	my_nonconfigurable_string_list []string
 }
 
 func (p *selectsTestProvider) String() string {
@@ -644,23 +1080,45 @@
 	if p.my_string != nil {
 		myStringStr = *p.my_string
 	}
+	myNonconfigurableStringStr := "nil"
+	if p.my_nonconfigurable_string != nil {
+		myNonconfigurableStringStr = *p.my_nonconfigurable_string
+	}
 	return fmt.Sprintf(`selectsTestProvider {
 	my_bool: %v,
 	my_string: %s,
     my_string_list: %s,
     my_paths: %s,
 	replacing_string_list %s,
-}`, myBoolStr, myStringStr, p.my_string_list, p.my_paths, p.replacing_string_list)
+	arch_variant_configurable_bool %v
+	my_nonconfigurable_bool: %v,
+	my_nonconfigurable_string: %s,
+	my_nonconfigurable_string_list: %s,
+}`,
+		myBoolStr,
+		myStringStr,
+		p.my_string_list,
+		p.my_paths,
+		p.replacing_string_list,
+		p.arch_variant_configurable_bool,
+		p.my_nonconfigurable_bool,
+		myNonconfigurableStringStr,
+		p.my_nonconfigurable_string_list,
+	)
 }
 
 var selectsTestProviderKey = blueprint.NewProvider[selectsTestProvider]()
 
 type selectsMockModuleProperties struct {
-	My_bool               proptools.Configurable[bool]
-	My_string             proptools.Configurable[string]
-	My_string_list        proptools.Configurable[[]string]
-	My_paths              proptools.Configurable[[]string] `android:"path"`
-	Replacing_string_list proptools.Configurable[[]string] `android:"replace_instead_of_append,arch_variant"`
+	My_bool                        proptools.Configurable[bool]
+	My_string                      proptools.Configurable[string]
+	My_string_list                 proptools.Configurable[[]string]
+	My_paths                       proptools.Configurable[[]string] `android:"path"`
+	Replacing_string_list          proptools.Configurable[[]string] `android:"replace_instead_of_append,arch_variant"`
+	Arch_variant_configurable_bool proptools.Configurable[bool]     `android:"replace_instead_of_append,arch_variant"`
+	My_nonconfigurable_bool        *bool
+	My_nonconfigurable_string      *string
+	My_nonconfigurable_string_list []string
 }
 
 type selectsMockModule struct {
@@ -669,13 +1127,29 @@
 	properties selectsMockModuleProperties
 }
 
+func optionalToPtr[T any](o proptools.ConfigurableOptional[T]) *T {
+	if o.IsEmpty() {
+		return nil
+	}
+	x := o.Get()
+	return &x
+}
+
 func (p *selectsMockModule) GenerateAndroidBuildActions(ctx ModuleContext) {
+	toAppend := ctx.Config().VendorConfig("selects_test").String("append_to_string_list")
+	if toAppend != "" {
+		p.properties.My_string_list.AppendSimpleValue([]string{toAppend})
+	}
 	SetProvider(ctx, selectsTestProviderKey, selectsTestProvider{
-		my_bool:               p.properties.My_bool.Get(ctx),
-		my_string:             p.properties.My_string.Get(ctx),
-		my_string_list:        p.properties.My_string_list.Get(ctx),
-		my_paths:              p.properties.My_paths.Get(ctx),
-		replacing_string_list: p.properties.Replacing_string_list.Get(ctx),
+		my_bool:                        optionalToPtr(p.properties.My_bool.Get(ctx)),
+		my_string:                      optionalToPtr(p.properties.My_string.Get(ctx)),
+		my_string_list:                 optionalToPtr(p.properties.My_string_list.Get(ctx)),
+		my_paths:                       optionalToPtr(p.properties.My_paths.Get(ctx)),
+		replacing_string_list:          optionalToPtr(p.properties.Replacing_string_list.Get(ctx)),
+		arch_variant_configurable_bool: optionalToPtr(p.properties.Arch_variant_configurable_bool.Get(ctx)),
+		my_nonconfigurable_bool:        p.properties.My_nonconfigurable_bool,
+		my_nonconfigurable_string:      p.properties.My_nonconfigurable_string,
+		my_nonconfigurable_string_list: p.properties.My_nonconfigurable_string_list,
 	})
 }
 
diff --git a/android/singleton.go b/android/singleton.go
index 76df1eb..d364384 100644
--- a/android/singleton.go
+++ b/android/singleton.go
@@ -284,5 +284,5 @@
 }
 
 func (s *singletonContextAdaptor) OtherModulePropertyErrorf(module Module, property string, format string, args ...interface{}) {
-	s.blueprintSingletonContext().OtherModulePropertyErrorf(module, property, format, args)
+	s.blueprintSingletonContext().OtherModulePropertyErrorf(module, property, format, args...)
 }
diff --git a/android/soong_config_modules.go b/android/soong_config_modules.go
index 38db929..e0b1d7c 100644
--- a/android/soong_config_modules.go
+++ b/android/soong_config_modules.go
@@ -463,57 +463,6 @@
 	}).(map[string]blueprint.ModuleFactory)
 }
 
-// tracingConfig is a wrapper to soongconfig.SoongConfig which records all accesses to SoongConfig.
-type tracingConfig struct {
-	config    soongconfig.SoongConfig
-	boolSet   map[string]bool
-	stringSet map[string]string
-	isSetSet  map[string]bool
-}
-
-func (c *tracingConfig) Bool(name string) bool {
-	c.boolSet[name] = c.config.Bool(name)
-	return c.boolSet[name]
-}
-
-func (c *tracingConfig) String(name string) string {
-	c.stringSet[name] = c.config.String(name)
-	return c.stringSet[name]
-}
-
-func (c *tracingConfig) IsSet(name string) bool {
-	c.isSetSet[name] = c.config.IsSet(name)
-	return c.isSetSet[name]
-}
-
-func (c *tracingConfig) getTrace() soongConfigTrace {
-	ret := soongConfigTrace{}
-
-	for k, v := range c.boolSet {
-		ret.Bools = append(ret.Bools, fmt.Sprintf("%q:%t", k, v))
-	}
-	for k, v := range c.stringSet {
-		ret.Strings = append(ret.Strings, fmt.Sprintf("%q:%q", k, v))
-	}
-	for k, v := range c.isSetSet {
-		ret.IsSets = append(ret.IsSets, fmt.Sprintf("%q:%t", k, v))
-	}
-
-	return ret
-}
-
-func newTracingConfig(config soongconfig.SoongConfig) *tracingConfig {
-	c := tracingConfig{
-		config:    config,
-		boolSet:   make(map[string]bool),
-		stringSet: make(map[string]string),
-		isSetSet:  make(map[string]bool),
-	}
-	return &c
-}
-
-var _ soongconfig.SoongConfig = (*tracingConfig)(nil)
-
 // configModuleFactory takes an existing soongConfigModuleFactory and a
 // ModuleType to create a new ModuleFactory that uses a custom loadhook.
 func configModuleFactory(factory blueprint.ModuleFactory, moduleType *soongconfig.ModuleType) blueprint.ModuleFactory {
@@ -561,8 +510,8 @@
 		// conditional on Soong config variables by reading the product
 		// config variables from Make.
 		AddLoadHook(module, func(ctx LoadHookContext) {
-			tracingConfig := newTracingConfig(ctx.Config().VendorConfig(moduleType.ConfigNamespace))
-			newProps, err := soongconfig.PropertiesToApply(moduleType, conditionalProps, tracingConfig)
+			config := ctx.Config().VendorConfig(moduleType.ConfigNamespace)
+			newProps, err := soongconfig.PropertiesToApply(moduleType, conditionalProps, config)
 			if err != nil {
 				ctx.ModuleErrorf("%s", err)
 				return
@@ -570,8 +519,6 @@
 			for _, ps := range newProps {
 				ctx.AppendProperties(ps)
 			}
-
-			module.(Module).base().commonProperties.SoongConfigTrace = tracingConfig.getTrace()
 		})
 		return module, props
 	}
diff --git a/android/soong_config_modules_test.go b/android/soong_config_modules_test.go
index a6b2c51..04aafde 100644
--- a/android/soong_config_modules_test.go
+++ b/android/soong_config_modules_test.go
@@ -16,7 +16,6 @@
 
 import (
 	"fmt"
-	"path/filepath"
 	"testing"
 )
 
@@ -506,197 +505,3 @@
 		})
 	}
 }
-
-func TestSoongConfigModuleTrace(t *testing.T) {
-	bp := `
-		soong_config_module_type {
-			name: "acme_test",
-			module_type: "test",
-			config_namespace: "acme",
-			variables: ["board", "feature1", "FEATURE3", "unused_string_var"],
-			bool_variables: ["feature2", "unused_feature", "always_true"],
-			value_variables: ["size", "unused_size"],
-			properties: ["cflags", "srcs", "defaults"],
-		}
-
-		soong_config_module_type {
-			name: "acme_test_defaults",
-			module_type: "test_defaults",
-			config_namespace: "acme",
-			variables: ["board", "feature1", "FEATURE3", "unused_string_var"],
-			bool_variables: ["feature2", "unused_feature", "always_true"],
-			value_variables: ["size", "unused_size"],
-			properties: ["cflags", "srcs", "defaults"],
-		}
-
-		soong_config_string_variable {
-			name: "board",
-			values: ["soc_a", "soc_b", "soc_c"],
-		}
-
-		soong_config_string_variable {
-			name: "unused_string_var",
-			values: ["a", "b"],
-		}
-
-		soong_config_bool_variable {
-			name: "feature1",
-		}
-
-		soong_config_bool_variable {
-			name: "FEATURE3",
-		}
-
-		test_defaults {
-			name: "test_defaults",
-			cflags: ["DEFAULT"],
-		}
-
-		test {
-			name: "normal",
-			defaults: ["test_defaults"],
-		}
-
-		acme_test {
-			name: "board_1",
-			defaults: ["test_defaults"],
-			soong_config_variables: {
-				board: {
-					soc_a: {
-						cflags: ["-DSOC_A"],
-					},
-				},
-			},
-		}
-
-		acme_test {
-			name: "board_2",
-			defaults: ["test_defaults"],
-			soong_config_variables: {
-				board: {
-					soc_a: {
-						cflags: ["-DSOC_A"],
-					},
-				},
-			},
-		}
-
-		acme_test {
-			name: "size",
-			defaults: ["test_defaults"],
-			soong_config_variables: {
-				size: {
-					cflags: ["-DSIZE=%s"],
-				},
-			},
-		}
-
-		acme_test {
-			name: "board_and_size",
-			defaults: ["test_defaults"],
-			soong_config_variables: {
-				board: {
-					soc_a: {
-						cflags: ["-DSOC_A"],
-					},
-				},
-				size: {
-					cflags: ["-DSIZE=%s"],
-				},
-			},
-		}
-
-		acme_test_defaults {
-			name: "board_defaults",
-			soong_config_variables: {
-				board: {
-					soc_a: {
-						cflags: ["-DSOC_A"],
-					},
-				},
-			},
-		}
-
-		acme_test_defaults {
-			name: "size_defaults",
-			soong_config_variables: {
-				size: {
-					cflags: ["-DSIZE=%s"],
-				},
-			},
-		}
-
-		test {
-			name: "board_and_size_with_defaults",
-			defaults: ["board_defaults", "size_defaults"],
-		}
-    `
-
-	fixtureForVendorVars := func(vars map[string]map[string]string) FixturePreparer {
-		return FixtureModifyProductVariables(func(variables FixtureProductVariables) {
-			variables.VendorVars = vars
-		})
-	}
-
-	preparer := fixtureForVendorVars(map[string]map[string]string{
-		"acme": {
-			"board":    "soc_a",
-			"size":     "42",
-			"feature1": "true",
-			"feature2": "false",
-			// FEATURE3 unset
-			"unused_feature":    "true", // unused
-			"unused_size":       "1",    // unused
-			"unused_string_var": "a",    // unused
-			"always_true":       "true",
-		},
-	})
-
-	t.Run("soong config trace hash", func(t *testing.T) {
-		result := GroupFixturePreparers(
-			preparer,
-			PrepareForTestWithDefaults,
-			PrepareForTestWithSoongConfigModuleBuildComponents,
-			prepareForSoongConfigTestModule,
-			FixtureRegisterWithContext(func(ctx RegistrationContext) {
-				ctx.FinalDepsMutators(registerSoongConfigTraceMutator)
-			}),
-			FixtureWithRootAndroidBp(bp),
-		).RunTest(t)
-
-		// Hashes of modules not using soong config should be empty
-		normal := result.ModuleForTests("normal", "").Module().(*soongConfigTestModule)
-		AssertDeepEquals(t, "normal hash", normal.base().commonProperties.SoongConfigTraceHash, "")
-		AssertDeepEquals(t, "normal hash out", normal.outputPath.RelativeToTop().String(), "out/soong/.intermediates/normal/test")
-
-		board1 := result.ModuleForTests("board_1", "").Module().(*soongConfigTestModule)
-		board2 := result.ModuleForTests("board_2", "").Module().(*soongConfigTestModule)
-		size := result.ModuleForTests("size", "").Module().(*soongConfigTestModule)
-
-		// Trace mutator sets soong config trace hash correctly
-		board1Hash := board1.base().commonProperties.SoongConfigTrace.hash()
-		board1Output := board1.outputPath.RelativeToTop().String()
-		AssertDeepEquals(t, "board hash calc", board1Hash, board1.base().commonProperties.SoongConfigTraceHash)
-		AssertDeepEquals(t, "board hash path", board1Output, filepath.Join("out/soong/.intermediates/board_1", board1Hash, "test"))
-
-		sizeHash := size.base().commonProperties.SoongConfigTrace.hash()
-		sizeOutput := size.outputPath.RelativeToTop().String()
-		AssertDeepEquals(t, "size hash calc", sizeHash, size.base().commonProperties.SoongConfigTraceHash)
-		AssertDeepEquals(t, "size hash path", sizeOutput, filepath.Join("out/soong/.intermediates/size", sizeHash, "test"))
-
-		// Trace should be identical for modules using the same set of variables
-		AssertDeepEquals(t, "board trace", board1.base().commonProperties.SoongConfigTrace, board2.base().commonProperties.SoongConfigTrace)
-		AssertDeepEquals(t, "board hash", board1.base().commonProperties.SoongConfigTraceHash, board2.base().commonProperties.SoongConfigTraceHash)
-
-		// Trace hash should be different for different sets of soong variables
-		AssertBoolEquals(t, "board hash not equal to size hash", board1.base().commonProperties.SoongConfigTraceHash == size.commonProperties.SoongConfigTraceHash, false)
-
-		boardSize := result.ModuleForTests("board_and_size", "").Module().(*soongConfigTestModule)
-		boardSizeDefaults := result.ModuleForTests("board_and_size_with_defaults", "").Module()
-
-		// Trace should propagate
-		AssertDeepEquals(t, "board_size hash calc", boardSize.base().commonProperties.SoongConfigTrace.hash(), boardSize.base().commonProperties.SoongConfigTraceHash)
-		AssertDeepEquals(t, "board_size trace", boardSize.base().commonProperties.SoongConfigTrace, boardSizeDefaults.base().commonProperties.SoongConfigTrace)
-		AssertDeepEquals(t, "board_size hash", boardSize.base().commonProperties.SoongConfigTraceHash, boardSizeDefaults.base().commonProperties.SoongConfigTraceHash)
-	})
-}
diff --git a/android/soongconfig/Android.bp b/android/soongconfig/Android.bp
index 8fe1ff1..5a6df26 100644
--- a/android/soongconfig/Android.bp
+++ b/android/soongconfig/Android.bp
@@ -9,7 +9,6 @@
         "blueprint",
         "blueprint-parser",
         "blueprint-proptools",
-        "soong-bazel",
         "soong-starlark-format",
     ],
     srcs: [
diff --git a/android/soongconfig/modules.go b/android/soongconfig/modules.go
index c910974..f6046d0 100644
--- a/android/soongconfig/modules.go
+++ b/android/soongconfig/modules.go
@@ -733,11 +733,18 @@
 		case reflect.Bool:
 			// Nothing to do
 		case reflect.Struct:
-			fieldName = append(fieldName, propStruct.Type().Field(i).Name)
-			if err := s.printfIntoPropertyRecursive(fieldName, field, configValue); err != nil {
-				return err
+			if proptools.IsConfigurable(field.Type()) {
+				if err := proptools.PrintfIntoConfigurable(field.Interface(), configValue); err != nil {
+					fieldName = append(fieldName, propStruct.Type().Field(i).Name)
+					return fmt.Errorf("soong_config_variables.%s.%s: %s", s.variable, strings.Join(fieldName, "."), err)
+				}
+			} else {
+				fieldName = append(fieldName, propStruct.Type().Field(i).Name)
+				if err := s.printfIntoPropertyRecursive(fieldName, field, configValue); err != nil {
+					return err
+				}
+				fieldName = fieldName[:len(fieldName)-1]
 			}
-			fieldName = fieldName[:len(fieldName)-1]
 		default:
 			fieldName = append(fieldName, propStruct.Type().Field(i).Name)
 			return fmt.Errorf("soong_config_variables.%s.%s: unsupported property type %q", s.variable, strings.Join(fieldName, "."), kind)
@@ -817,11 +824,16 @@
 			}
 			field.Set(newField)
 		case reflect.Struct:
-			fieldName = append(fieldName, propStruct.Type().Field(i).Name)
-			if err := s.printfIntoPropertyRecursive(fieldName, field, configValues); err != nil {
-				return err
+			if proptools.IsConfigurable(field.Type()) {
+				fieldName = append(fieldName, propStruct.Type().Field(i).Name)
+				return fmt.Errorf("soong_config_variables.%s.%s: list variables are not supported on configurable properties", s.variable, strings.Join(fieldName, "."))
+			} else {
+				fieldName = append(fieldName, propStruct.Type().Field(i).Name)
+				if err := s.printfIntoPropertyRecursive(fieldName, field, configValues); err != nil {
+					return err
+				}
+				fieldName = fieldName[:len(fieldName)-1]
 			}
-			fieldName = fieldName[:len(fieldName)-1]
 		default:
 			fieldName = append(fieldName, propStruct.Type().Field(i).Name)
 			return fmt.Errorf("soong_config_variables.%s.%s: unsupported property type %q", s.variable, strings.Join(fieldName, "."), kind)
diff --git a/android/test_config.go b/android/test_config.go
index a15343a..f251038 100644
--- a/android/test_config.go
+++ b/android/test_config.go
@@ -50,7 +50,7 @@
 			AAPTCharacteristics:                 stringPtr("nosdcard"),
 			AAPTPrebuiltDPI:                     []string{"xhdpi", "xxhdpi"},
 			UncompressPrivAppDex:                boolPtr(true),
-			ShippingApiLevel:                    stringPtr("30"),
+			Shipping_api_level:                  stringPtr("30"),
 		},
 
 		outDir:       buildDir,
diff --git a/android/testing.go b/android/testing.go
index 7b4411e..e39a1a7 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -224,6 +224,10 @@
 	})
 }
 
+func (ctx *TestContext) OtherModulePropertyErrorf(module Module, property string, fmt_ string, args ...interface{}) {
+	panic(fmt.Sprintf(fmt_, args...))
+}
+
 // registeredComponentOrder defines the order in which a sortableComponent type is registered at
 // runtime and provides support for reordering the components registered for a test in the same
 // way.
@@ -1014,20 +1018,21 @@
 	return normalizeStringMapRelativeToTop(m.config, m.module.VariablesForTests())
 }
 
-// OutputFiles calls OutputFileProducer.OutputFiles on the encapsulated module, exits the test
-// immediately if there is an error and otherwise returns the result of calling Paths.RelativeToTop
+// OutputFiles checks if module base outputFiles property has any output
+// files can be used to return.
+// Exits the test immediately if there is an error and
+// otherwise returns the result of calling Paths.RelativeToTop
 // on the returned Paths.
 func (m TestingModule) OutputFiles(t *testing.T, tag string) Paths {
-	producer, ok := m.module.(OutputFileProducer)
-	if !ok {
-		t.Fatalf("%q must implement OutputFileProducer\n", m.module.Name())
-	}
-	paths, err := producer.OutputFiles(tag)
-	if err != nil {
-		t.Fatal(err)
+	outputFiles := m.Module().base().outputFiles
+	if tag == "" && outputFiles.DefaultOutputFiles != nil {
+		return outputFiles.DefaultOutputFiles.RelativeToTop()
+	} else if taggedOutputFiles, hasTag := outputFiles.TaggedOutputFiles[tag]; hasTag {
+		return taggedOutputFiles.RelativeToTop()
 	}
 
-	return paths.RelativeToTop()
+	t.Fatal(fmt.Errorf("No test output file has been set for tag %q", tag))
+	return nil
 }
 
 // TestingSingleton is wrapper around an android.Singleton that provides methods to find information about individual
@@ -1122,7 +1127,7 @@
 
 	entriesList := p.AndroidMkEntries()
 	aconfigUpdateAndroidMkEntries(ctx, mod.(Module), &entriesList)
-	for i, _ := range entriesList {
+	for i := range entriesList {
 		entriesList[i].fillInEntries(ctx, mod)
 	}
 	return entriesList
@@ -1287,3 +1292,21 @@
 		t.Errorf("%q is not found in %v", expected, result)
 	}
 }
+
+type panickingConfigAndErrorContext struct {
+	ctx *TestContext
+}
+
+func (ctx *panickingConfigAndErrorContext) OtherModulePropertyErrorf(module Module, property, fmt string, args ...interface{}) {
+	panic(ctx.ctx.PropertyErrorf(module, property, fmt, args...).Error())
+}
+
+func (ctx *panickingConfigAndErrorContext) Config() Config {
+	return ctx.ctx.Config()
+}
+
+func PanickingConfigAndErrorContext(ctx *TestContext) ConfigAndErrorContext {
+	return &panickingConfigAndErrorContext{
+		ctx: ctx,
+	}
+}
diff --git a/android/updatable_modules.go b/android/updatable_modules.go
index 1548170..dd7dc2c 100644
--- a/android/updatable_modules.go
+++ b/android/updatable_modules.go
@@ -33,4 +33,4 @@
 // * AOSP            - xx9990000
 // * x-mainline-prod - xx9990000
 // * master          - 990090000
-const DefaultUpdatableModuleVersion = "990090000"
+const DefaultUpdatableModuleVersion = "350090000"
diff --git a/android/util.go b/android/util.go
index 698a856..3c0af2f 100644
--- a/android/util.go
+++ b/android/util.go
@@ -24,6 +24,8 @@
 	"sort"
 	"strings"
 	"sync"
+
+	"github.com/google/blueprint/proptools"
 )
 
 // CopyOf returns a new slice that has the same contents as s.
@@ -199,6 +201,12 @@
 	return listsDiffer, diff1, diff2
 }
 
+// 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))
+}
+
 // Returns true if the given string s is prefixed with any string in the given prefix list.
 func HasAnyPrefix(s string, prefixList []string) bool {
 	for _, prefix := range prefixList {
@@ -302,6 +310,24 @@
 	return removed, result
 }
 
+// FirstUniqueFunc returns all unique elements of a slice, keeping the first copy of
+// each.  It does not modify the input slice. The eq function should return true
+// if two elements can be considered equal.
+func FirstUniqueFunc[SortableList ~[]Sortable, Sortable any](list SortableList, eq func(a, b Sortable) bool) SortableList {
+	k := 0
+outer:
+	for i := 0; i < len(list); i++ {
+		for j := 0; j < k; j++ {
+			if eq(list[i], list[j]) {
+				continue outer
+			}
+		}
+		list[k] = list[i]
+		k++
+	}
+	return list[:k]
+}
+
 // FirstUniqueStrings returns all unique elements of a slice of strings, keeping the first copy of
 // each.  It does not modify the input slice.
 func FirstUniqueStrings(list []string) []string {
@@ -524,25 +550,9 @@
 	return root, suffix, ext
 }
 
-func shard[T ~[]E, E any](toShard T, shardSize int) []T {
-	if len(toShard) == 0 {
-		return nil
-	}
-
-	ret := make([]T, 0, (len(toShard)+shardSize-1)/shardSize)
-	for len(toShard) > shardSize {
-		ret = append(ret, toShard[0:shardSize])
-		toShard = toShard[shardSize:]
-	}
-	if len(toShard) > 0 {
-		ret = append(ret, toShard)
-	}
-	return ret
-}
-
 // ShardPaths takes a Paths, and returns a slice of Paths where each one has at most shardSize paths.
 func ShardPaths(paths Paths, shardSize int) []Paths {
-	return shard(paths, shardSize)
+	return proptools.ShardBySize(paths, shardSize)
 }
 
 // ShardString takes a string and returns a slice of strings where the length of each one is
@@ -565,7 +575,7 @@
 // ShardStrings takes a slice of strings, and returns a slice of slices of strings where each one has at most shardSize
 // elements.
 func ShardStrings(s []string, shardSize int) [][]string {
-	return shard(s, shardSize)
+	return proptools.ShardBySize(s, shardSize)
 }
 
 // CheckDuplicate checks if there are duplicates in given string list.
diff --git a/android/util_test.go b/android/util_test.go
index 8e73d83..6537d69 100644
--- a/android/util_test.go
+++ b/android/util_test.go
@@ -818,3 +818,52 @@
 		})
 	}
 }
+
+var hasIntersectionTestCases = []struct {
+	name     string
+	l1       []string
+	l2       []string
+	expected bool
+}{
+	{
+		name:     "empty",
+		l1:       []string{"a", "b", "c"},
+		l2:       []string{},
+		expected: false,
+	},
+	{
+		name:     "both empty",
+		l1:       []string{},
+		l2:       []string{},
+		expected: false,
+	},
+	{
+		name:     "identical",
+		l1:       []string{"a", "b", "c"},
+		l2:       []string{"a", "b", "c"},
+		expected: true,
+	},
+	{
+		name:     "duplicates",
+		l1:       []string{"a", "a", "a"},
+		l2:       []string{"a", "b", "c"},
+		expected: true,
+	},
+	{
+		name:     "duplicates with no intersection",
+		l1:       []string{"d", "d", "d", "d"},
+		l2:       []string{"a", "b", "c"},
+		expected: false,
+	},
+}
+
+func TestHasIntersection(t *testing.T) {
+	for _, testCase := range hasIntersectionTestCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			hasIntersection := HasIntersection(testCase.l1, testCase.l2)
+			if !reflect.DeepEqual(hasIntersection, testCase.expected) {
+				t.Errorf("expected %#v, got %#v", testCase.expected, hasIntersection)
+			}
+		})
+	}
+}
diff --git a/android/variable.go b/android/variable.go
index 599f88e..b2173ec 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -55,19 +55,23 @@
 			Base_dir *string
 		}
 
+		Shipping_api_level struct {
+			Cflags []string
+		}
+
 		// unbundled_build is a catch-all property to annotate modules that don't build in one or
 		// more unbundled branches, usually due to dependencies missing from the manifest.
 		Unbundled_build struct {
-			Enabled *bool `android:"arch_variant"`
+			Enabled proptools.Configurable[bool] `android:"arch_variant,replace_instead_of_append"`
 		} `android:"arch_variant"`
 
 		// similar to `Unbundled_build`, but `Always_use_prebuilt_sdks` means that it uses prebuilt
 		// sdk specifically.
 		Always_use_prebuilt_sdks struct {
-			Enabled *bool `android:"arch_variant"`
+			Enabled proptools.Configurable[bool] `android:"arch_variant,replace_instead_of_append"`
 		} `android:"arch_variant"`
 
-		Malloc_not_svelte struct {
+		Malloc_low_memory struct {
 			Cflags              []string `android:"arch_variant"`
 			Shared_libs         []string `android:"arch_variant"`
 			Whole_static_libs   []string `android:"arch_variant"`
@@ -128,9 +132,11 @@
 				Keep_symbols                 *bool
 				Keep_symbols_and_debug_frame *bool
 			}
-			Static_libs       []string
-			Whole_static_libs []string
-			Shared_libs       []string
+			Static_libs         []string
+			Exclude_static_libs []string
+			Whole_static_libs   []string
+			Shared_libs         []string
+			Jni_libs            []string
 
 			Cmdline []string
 
@@ -181,8 +187,10 @@
 		// release_aidl_use_unfrozen is "true" when a device can
 		// use the unfrozen versions of AIDL interfaces.
 		Release_aidl_use_unfrozen struct {
-			Cflags []string
-			Cmd    *string
+			Cflags          []string
+			Cmd             *string
+			Required        []string
+			Vintf_fragments []string
 		}
 	} `android:"arch_variant"`
 }
@@ -193,11 +201,12 @@
 	// Suffix to add to generated Makefiles
 	Make_suffix *string `json:",omitempty"`
 
-	BuildId             *string `json:",omitempty"`
-	BuildNumberFile     *string `json:",omitempty"`
-	BuildHostnameFile   *string `json:",omitempty"`
-	BuildThumbprintFile *string `json:",omitempty"`
-	DisplayBuildNumber  *bool   `json:",omitempty"`
+	BuildId              *string `json:",omitempty"`
+	BuildFingerprintFile *string `json:",omitempty"`
+	BuildNumberFile      *string `json:",omitempty"`
+	BuildHostnameFile    *string `json:",omitempty"`
+	BuildThumbprintFile  *string `json:",omitempty"`
+	DisplayBuildNumber   *bool   `json:",omitempty"`
 
 	Platform_display_version_name          *string  `json:",omitempty"`
 	Platform_version_name                  *string  `json:",omitempty"`
@@ -267,8 +276,10 @@
 	AAPTPreferredConfig *string  `json:",omitempty"`
 	AAPTPrebuiltDPI     []string `json:",omitempty"`
 
-	DefaultAppCertificate           *string `json:",omitempty"`
-	MainlineSepolicyDevCertificates *string `json:",omitempty"`
+	DefaultAppCertificate           *string  `json:",omitempty"`
+	ExtraOtaKeys                    []string `json:",omitempty"`
+	ExtraOtaRecoveryKeys            []string `json:",omitempty"`
+	MainlineSepolicyDevCertificates *string  `json:",omitempty"`
 
 	AppsDefaultVersionName *string `json:",omitempty"`
 
@@ -278,7 +289,7 @@
 	Unbundled_build_image        *bool    `json:",omitempty"`
 	Always_use_prebuilt_sdks     *bool    `json:",omitempty"`
 	Skip_boot_jars_check         *bool    `json:",omitempty"`
-	Malloc_not_svelte            *bool    `json:",omitempty"`
+	Malloc_low_memory            *bool    `json:",omitempty"`
 	Malloc_zero_contents         *bool    `json:",omitempty"`
 	Malloc_pattern_fill_contents *bool    `json:",omitempty"`
 	Safestack                    *bool    `json:",omitempty"`
@@ -362,7 +373,6 @@
 
 	PgoAdditionalProfileDirs []string `json:",omitempty"`
 
-	VndkUseCoreVariant         *bool `json:",omitempty"`
 	VndkSnapshotBuildArtifacts *bool `json:",omitempty"`
 
 	DirectedVendorSnapshot bool            `json:",omitempty"`
@@ -393,7 +403,8 @@
 
 	PlatformSepolicyCompatVersions []string `json:",omitempty"`
 
-	VendorVars map[string]map[string]string `json:",omitempty"`
+	VendorVars     map[string]map[string]string `json:",omitempty"`
+	VendorVarTypes map[string]map[string]string `json:",omitempty"`
 
 	Ndk_abis *bool `json:",omitempty"`
 
@@ -439,7 +450,7 @@
 
 	PrebuiltHiddenApiDir *string `json:",omitempty"`
 
-	ShippingApiLevel *string `json:",omitempty"`
+	Shipping_api_level *string `json:",omitempty"`
 
 	BuildBrokenPluginValidation         []string `json:",omitempty"`
 	BuildBrokenClangAsFlags             bool     `json:",omitempty"`
@@ -453,6 +464,7 @@
 	BuildBrokenIncorrectPartitionImages bool     `json:",omitempty"`
 	BuildBrokenInputDirModules          []string `json:",omitempty"`
 	BuildBrokenDontCheckSystemSdk       bool     `json:",omitempty"`
+	BuildBrokenDupSysprop               bool     `json:",omitempty"`
 
 	BuildWarningBadOptionalUsesLibsAllowlist []string `json:",omitempty"`
 
@@ -471,7 +483,6 @@
 
 	IgnorePrefer32OnDevice bool `json:",omitempty"`
 
-	IncludeTags    []string `json:",omitempty"`
 	SourceRootDirs []string `json:",omitempty"`
 
 	AfdoProfiles []string `json:",omitempty"`
@@ -486,12 +497,12 @@
 
 	ReleaseDefaultModuleBuildFromSource *bool `json:",omitempty"`
 
-	KeepVndk *bool `json:",omitempty"`
-
 	CheckVendorSeappViolations *bool `json:",omitempty"`
 
 	BuildFlags map[string]string `json:",omitempty"`
 
+	BuildFlagTypes map[string]string `json:",omitempty"`
+
 	BuildFromSourceStub *bool `json:",omitempty"`
 
 	BuildIgnoreApexContributionContents *bool `json:",omitempty"`
@@ -509,6 +520,8 @@
 	BoardUseVbmetaDigestInFingerprint *bool `json:",omitempty"`
 
 	OemProperties []string `json:",omitempty"`
+
+	ArtTargetIncludeDebugBuild *bool `json:",omitempty"`
 }
 
 type PartitionQualifiedVariablesType struct {
@@ -612,7 +625,7 @@
 		AAPTCharacteristics: stringPtr("nosdcard"),
 		AAPTPrebuiltDPI:     []string{"xhdpi", "xxhdpi"},
 
-		Malloc_not_svelte:            boolPtr(true),
+		Malloc_low_memory:            boolPtr(false),
 		Malloc_zero_contents:         boolPtr(true),
 		Malloc_pattern_fill_contents: boolPtr(false),
 		Safestack:                    boolPtr(false),
diff --git a/androidmk/androidmk/android.go b/androidmk/androidmk/android.go
index 9d61e1c..570f36c 100644
--- a/androidmk/androidmk/android.go
+++ b/androidmk/androidmk/android.go
@@ -151,7 +151,7 @@
 			"LOCAL_CLANG_CFLAGS":                  "clang_cflags",
 			"LOCAL_YACCFLAGS":                     "yacc.flags",
 			"LOCAL_SANITIZE_RECOVER":              "sanitize.recover",
-			"LOCAL_LOGTAGS_FILES":                 "logtags",
+			"LOCAL_SOONG_LOGTAGS_FILES":           "logtags",
 			"LOCAL_EXPORT_HEADER_LIBRARY_HEADERS": "export_header_lib_headers",
 			"LOCAL_EXPORT_SHARED_LIBRARY_HEADERS": "export_shared_lib_headers",
 			"LOCAL_EXPORT_STATIC_LIBRARY_HEADERS": "export_static_lib_headers",
@@ -341,9 +341,6 @@
 
 		firstOperand := v.Args[0]
 		secondOperand := v.Args[1]
-		if firstOperand.Type() != bpparser.StringType {
-			return "global", value, nil
-		}
 
 		if _, ok := firstOperand.(*bpparser.Operator); ok {
 			return "global", value, nil
diff --git a/androidmk/androidmk/androidmk.go b/androidmk/androidmk/androidmk.go
index 2e8810f..6fb20dc 100644
--- a/androidmk/androidmk/androidmk.go
+++ b/androidmk/androidmk/androidmk.go
@@ -493,7 +493,6 @@
 				Name:      name,
 				NamePos:   pos,
 				Value:     value,
-				OrigValue: value,
 				EqualsPos: pos,
 				Assigner:  "+=",
 			}
@@ -506,7 +505,6 @@
 				Name:      name,
 				NamePos:   pos,
 				Value:     value,
-				OrigValue: value,
 				EqualsPos: pos,
 				Assigner:  "=",
 			}
diff --git a/androidmk/androidmk/values.go b/androidmk/androidmk/values.go
index 9618142..701c708 100644
--- a/androidmk/androidmk/values.go
+++ b/androidmk/androidmk/values.go
@@ -81,7 +81,7 @@
 			}
 			tmp := &bpparser.Variable{
 				Name:  name,
-				Value: &bpparser.String{},
+				Type_: bpparser.StringType,
 			}
 
 			if tmp.Name == "TOP" {
@@ -150,7 +150,7 @@
 					}
 					listOfListValues = append(listOfListValues, &bpparser.Variable{
 						Name:  name,
-						Value: &bpparser.List{},
+						Type_: bpparser.ListType,
 					})
 					listValue = &bpparser.List{}
 				}
@@ -215,7 +215,7 @@
 			}
 			return &bpparser.Variable{
 				Name:  name,
-				Value: &bpparser.Bool{},
+				Type_: bpparser.BoolType,
 			}, nil
 		} else {
 			return nil, fmt.Errorf("non-const bool expression %s", ms.Dump())
diff --git a/androidmk/parser/parser_test.go b/androidmk/parser/parser_test.go
index db3313d..fb03c23 100644
--- a/androidmk/parser/parser_test.go
+++ b/androidmk/parser/parser_test.go
@@ -86,20 +86,19 @@
 	},
 	{
 		name: "Blank line in rule's command",
-		in:   `all:
+		in: `all:
 	echo first line
 
 	echo second line`,
 		out: []Node{
 			&Rule{
-				Target: SimpleMakeString("all", NoPos),
-				RecipePos: NoPos,
-				Recipe: "echo first line\necho second line",
+				Target:        SimpleMakeString("all", NoPos),
+				RecipePos:     NoPos,
+				Recipe:        "echo first line\necho second line",
 				Prerequisites: SimpleMakeString("", NoPos),
 			},
 		},
 	},
-
 }
 
 func TestParse(t *testing.T) {
diff --git a/apex/Android.bp b/apex/Android.bp
index abae9e2..17fdfc3 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -37,6 +37,7 @@
         "apex_test.go",
         "bootclasspath_fragment_test.go",
         "classpath_element_test.go",
+        "container_test.go",
         "dexpreopt_bootjars_test.go",
         "platform_bootclasspath_test.go",
         "systemserver_classpath_fragment_test.go",
diff --git a/apex/aconfig_test.go b/apex/aconfig_test.go
index 726041c..14c0b63 100644
--- a/apex/aconfig_test.go
+++ b/apex/aconfig_test.go
@@ -23,6 +23,7 @@
 	"android/soong/genrule"
 	"android/soong/java"
 	"android/soong/rust"
+
 	"github.com/google/blueprint/proptools"
 )
 
@@ -173,10 +174,6 @@
 					name: "libaconfig_storage_read_api_cc",
 					srcs: ["libaconfig_storage_read_api_cc.cc"],
 				}
-				cc_library {
-					name: "libaconfig_storage_protos_cc",
-					srcs: ["libaconfig_storage_protos_cc.cc"],
-				}
 				aconfig_declarations {
 					name: "my_aconfig_declarations_bar",
 					package: "com.example.package",
@@ -436,10 +433,6 @@
 					name: "libaconfig_storage_read_api_cc",
 					srcs: ["libaconfig_storage_read_api_cc.cc"],
 				}
-				cc_library {
-					name: "libaconfig_storage_protos_cc",
-					srcs: ["libaconfig_storage_protos_cc.cc"],
-				}
 				aconfig_declarations {
 					name: "my_aconfig_declarations_foo",
 					package: "com.example.package",
@@ -501,10 +494,6 @@
 					name: "libaconfig_storage_read_api_cc",
 					srcs: ["libaconfig_storage_read_api_cc.cc"],
 				}
-				cc_library {
-					name: "libaconfig_storage_protos_cc",
-					srcs: ["libaconfig_storage_protos_cc.cc"],
-				}
 				aconfig_declarations {
 					name: "my_aconfig_declarations_foo",
 					package: "com.example.package",
diff --git a/apex/androidmk.go b/apex/androidmk.go
index 619be8d..4112108 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -218,7 +218,7 @@
 	var required []string
 	var targetRequired []string
 	var hostRequired []string
-	required = append(required, a.RequiredModuleNames()...)
+	required = append(required, a.required...)
 	targetRequired = append(targetRequired, a.TargetRequiredModuleNames()...)
 	hostRequired = append(hostRequired, a.HostRequiredModuleNames()...)
 	for _, fi := range a.filesInfo {
diff --git a/apex/apex.go b/apex/apex.go
index ef57d7e..c1a9d74 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -18,7 +18,6 @@
 
 import (
 	"fmt"
-	"log"
 	"path/filepath"
 	"regexp"
 	"sort"
@@ -137,10 +136,6 @@
 	// Rust binaries with prefer_rlib:true add unnecessary dependencies.
 	Unwanted_transitive_deps []string
 
-	// The minimum SDK version that this APEX must support at minimum. This is usually set to
-	// the SDK version that the APEX was first introduced.
-	Min_sdk_version *string
-
 	// Whether this APEX is considered updatable or not. When set to true, this will enforce
 	// additional rules for making sure that the APEX is truly updatable. To be updatable,
 	// min_sdk_version should be set as well. This will also disable the size optimizations like
@@ -162,8 +157,7 @@
 	// Default: true.
 	Installable *bool
 
-	// If set true, VNDK libs are considered as stable libs and are not included in this APEX.
-	// Should be only used in non-system apexes (e.g. vendor: true). Default is false.
+	// 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'.
@@ -388,6 +382,10 @@
 
 	// Trim against a specific Dynamic Common Lib APEX
 	Trim_against *string
+
+	// The minimum SDK version that this APEX must support at minimum. This is usually set to
+	// the SDK version that the APEX was first introduced.
+	Min_sdk_version *string
 }
 
 type apexBundle struct {
@@ -491,8 +489,8 @@
 
 	aconfigFiles []android.Path
 
-	// Single aconfig "cache file" merged from this module and all dependencies.
-	mergedAconfigFiles map[string]android.Paths
+	// Required modules, filled out during GenerateAndroidBuildActions and used in AndroidMk
+	required []string
 }
 
 // apexFileClass represents a type of file that can be included in APEX.
@@ -571,7 +569,7 @@
 	if module != nil {
 		ret.moduleDir = ctx.OtherModuleDir(module)
 		ret.partition = module.PartitionTag(ctx.DeviceConfig())
-		ret.requiredModuleNames = module.RequiredModuleNames()
+		ret.requiredModuleNames = module.RequiredModuleNames(ctx)
 		ret.targetRequiredModuleNames = module.TargetRequiredModuleNames()
 		ret.hostRequiredModuleNames = module.HostRequiredModuleNames()
 		ret.multilib = module.Target().Arch.ArchType.Multilib
@@ -702,7 +700,11 @@
 func addDependenciesForNativeModules(ctx android.BottomUpMutatorContext, nativeModules ApexNativeDependencies, target android.Target, imageVariation string) {
 	binVariations := target.Variations()
 	libVariations := append(target.Variations(), blueprint.Variation{Mutator: "link", Variation: "shared"})
-	rustLibVariations := append(target.Variations(), blueprint.Variation{Mutator: "rust_libraries", Variation: "dylib"})
+	rustLibVariations := append(
+		target.Variations(), []blueprint.Variation{
+			{Mutator: "rust_libraries", Variation: "dylib"},
+		}...,
+	)
 
 	// Append "image" variation
 	binVariations = append(binVariations, blueprint.Variation{Mutator: "image", Variation: imageVariation})
@@ -744,9 +746,9 @@
 
 	prefix := android.CoreVariation
 	if a.SocSpecific() || a.DeviceSpecific() {
-		prefix = cc.VendorVariation
+		prefix = android.VendorVariation
 	} else if a.ProductSpecific() {
-		prefix = cc.ProductVariation
+		prefix = android.ProductVariation
 	}
 
 	return prefix, ""
@@ -946,30 +948,6 @@
 		return
 	}
 
-	// Special casing for APEXes on non-system (e.g., vendor, odm, etc.) partitions. They are
-	// provided with a property named use_vndk_as_stable, which when set to true doesn't collect
-	// VNDK libraries as transitive dependencies. This option is useful for reducing the size of
-	// the non-system APEXes because the VNDK libraries won't be included (and duped) in the
-	// APEX, but shared across APEXes via the VNDK APEX.
-	useVndk := a.SocSpecific() || a.DeviceSpecific() || (a.ProductSpecific() && mctx.Config().EnforceProductPartitionInterface())
-	excludeVndkLibs := useVndk && a.useVndkAsStable(mctx)
-	if proptools.Bool(a.properties.Use_vndk_as_stable) {
-		if !useVndk {
-			mctx.PropertyErrorf("use_vndk_as_stable", "not supported for system/system_ext APEXes")
-		}
-		if a.minSdkVersionValue(mctx) != "" {
-			mctx.PropertyErrorf("use_vndk_as_stable", "not supported when min_sdk_version is set")
-		}
-		mctx.VisitDirectDepsWithTag(sharedLibTag, func(dep android.Module) {
-			if c, ok := dep.(*cc.Module); ok && c.IsVndk() {
-				mctx.PropertyErrorf("use_vndk_as_stable", "Trying to include a VNDK library(%s) while use_vndk_as_stable is true.", dep.Name())
-			}
-		})
-		if mctx.Failed() {
-			return
-		}
-	}
-
 	continueApexDepsWalk := func(child, parent android.Module) bool {
 		am, ok := child.(android.ApexModule)
 		if !ok || !am.CanHaveApexVariants() {
@@ -986,17 +964,6 @@
 		if !android.IsDepInSameApex(mctx, parent, child) {
 			return false
 		}
-		if excludeVndkLibs {
-			if c, ok := child.(*cc.Module); ok && c.IsVndk() {
-				return false
-			}
-		}
-
-		//TODO: b/296491928 Vendor APEX should use libbinder.ndk instead of libbinder once VNDK is fully deprecated.
-		if useVndk && mctx.Config().IsVndkDeprecated() && child.Name() == "libbinder" {
-			log.Print("Libbinder is linked from Vendor APEX ", a.Name(), " with module ", parent.Name())
-			return false
-		}
 
 		// By default, all the transitive dependencies are collected, unless filtered out
 		// above.
@@ -1033,6 +1000,11 @@
 	// be built for this apexBundle.
 
 	apexVariationName := mctx.ModuleName() // could be com.android.foo
+	if overridable, ok := mctx.Module().(android.OverridableModule); ok && overridable.GetOverriddenBy() != "" {
+		// use the overridden name com.mycompany.android.foo
+		apexVariationName = overridable.GetOverriddenBy()
+	}
+
 	a.properties.ApexVariationName = apexVariationName
 	testApexes := []string{}
 	if a.testApex {
@@ -1047,6 +1019,7 @@
 		InApexModules:     []string{a.Name()}, // could be com.mycompany.android.foo
 		ApexContents:      []*android.ApexContents{apexContents},
 		TestApexes:        testApexes,
+		BaseApexName:      mctx.ModuleName(),
 	}
 	mctx.WalkDeps(func(child, parent android.Module) bool {
 		if !continueApexDepsWalk(child, parent) {
@@ -1077,7 +1050,7 @@
 // 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() {
+	if !mctx.Module().Enabled(mctx) {
 		return
 	}
 
@@ -1094,10 +1067,10 @@
 // apexStrictUpdatibilityLintMutator propagates strict_updatability_linting to transitive deps of a mainline module
 // This check is enforced for updatable modules
 func apexStrictUpdatibilityLintMutator(mctx android.TopDownMutatorContext) {
-	if !mctx.Module().Enabled() {
+	if !mctx.Module().Enabled(mctx) {
 		return
 	}
-	if apex, ok := mctx.Module().(*apexBundle); ok && apex.checkStrictUpdatabilityLinting() {
+	if apex, ok := mctx.Module().(*apexBundle); ok && apex.checkStrictUpdatabilityLinting(mctx) {
 		mctx.WalkDeps(func(child, parent android.Module) bool {
 			// b/208656169 Do not propagate strict updatability linting to libcore/
 			// These libs are available on the classpath during compilation
@@ -1121,7 +1094,7 @@
 
 // enforceAppUpdatability propagates updatable=true to apps of updatable apexes
 func enforceAppUpdatability(mctx android.TopDownMutatorContext) {
-	if !mctx.Module().Enabled() {
+	if !mctx.Module().Enabled(mctx) {
 		return
 	}
 	if apex, ok := mctx.Module().(*apexBundle); ok && apex.Updatable() {
@@ -1175,6 +1148,7 @@
 		"test_com.android.os.statsd",
 		"test_com.android.permission",
 		"test_com.android.wifi",
+		"test_imgdiag_com.android.art",
 		"test_jitzygote_com.android.art",
 		// go/keep-sorted end
 	}
@@ -1191,15 +1165,16 @@
 	}
 )
 
-func (a *apexBundle) checkStrictUpdatabilityLinting() bool {
-	return a.Updatable() && !android.InList(a.ApexVariationName(), skipStrictUpdatabilityLintAllowlist)
+func (a *apexBundle) checkStrictUpdatabilityLinting(mctx android.TopDownMutatorContext) bool {
+	// The allowlist contains the base apex name, so use that instead of the ApexVariationName
+	return a.Updatable() && !android.InList(mctx.ModuleName(), skipStrictUpdatabilityLintAllowlist)
 }
 
 // apexUniqueVariationsMutator checks if any dependencies use unique apex variations. If so, use
 // unique apex variations for this module. See android/apex.go for more about unique apex variant.
 // TODO(jiyong): move this to android/apex.go?
 func apexUniqueVariationsMutator(mctx android.BottomUpMutatorContext) {
-	if !mctx.Module().Enabled() {
+	if !mctx.Module().Enabled(mctx) {
 		return
 	}
 	if am, ok := mctx.Module().(android.ApexModule); ok {
@@ -1211,7 +1186,7 @@
 // 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() {
+	if !mctx.Module().Enabled(mctx) {
 		return
 	}
 	if am, ok := mctx.Module().(android.ApexModule); ok {
@@ -1226,7 +1201,7 @@
 
 // TODO(jiyong): move this to android/apex.go?
 func apexTestForMutator(mctx android.BottomUpMutatorContext) {
-	if !mctx.Module().Enabled() {
+	if !mctx.Module().Enabled(mctx) {
 		return
 	}
 	if _, ok := mctx.Module().(android.ApexModule); ok {
@@ -1293,13 +1268,12 @@
 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) {
-		return []string{ai.ApexVariationName()}
-	} else if o, ok := ctx.Module().(*OverrideApex); ok {
-		apexBundleName := o.GetOverriddenModuleName()
-		if apexBundleName == "" {
-			ctx.ModuleErrorf("base property is not set")
+		if overridable, ok := ctx.Module().(android.OverridableModule); ok && overridable.GetOverriddenBy() != "" {
+			return []string{overridable.GetOverriddenBy()}
 		}
-		return []string{apexBundleName}
+		return []string{ai.ApexVariationName()}
+	} else if _, ok := ctx.Module().(*OverrideApex); ok {
+		return []string{ctx.ModuleName()}
 	}
 	return []string{""}
 }
@@ -1312,9 +1286,12 @@
 	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 o, ok := ctx.Module().(*OverrideApex); ok {
-		return o.GetOverriddenModuleName()
+	} else if _, ok := ctx.Module().(*OverrideApex); ok {
+		return ctx.Module().Name()
 	}
 
 	return ""
@@ -1340,7 +1317,7 @@
 // See android.UpdateDirectlyInAnyApex
 // TODO(jiyong): move this to android/apex.go?
 func apexDirectlyInAnyMutator(mctx android.BottomUpMutatorContext) {
-	if !mctx.Module().Enabled() {
+	if !mctx.Module().Enabled(mctx) {
 		return
 	}
 	if am, ok := mctx.Module().(android.ApexModule); ok {
@@ -1370,25 +1347,6 @@
 	return true
 }
 
-var _ android.OutputFileProducer = (*apexBundle)(nil)
-
-// Implements android.OutputFileProducer
-func (a *apexBundle) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "", android.DefaultDistTag:
-		// This is the default dist path.
-		return android.Paths{a.outputFile}, nil
-	case imageApexSuffix:
-		// uncompressed one
-		if a.outputApexFile != nil {
-			return android.Paths{a.outputApexFile}, nil
-		}
-		fallthrough
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
 var _ multitree.Exportable = (*apexBundle)(nil)
 
 func (a *apexBundle) Exportable() bool {
@@ -1404,7 +1362,7 @@
 var _ cc.Coverage = (*apexBundle)(nil)
 
 // Implements cc.Coverage
-func (a *apexBundle) IsNativeCoverageNeeded(ctx android.IncomingTransitionContext) bool {
+func (a *apexBundle) IsNativeCoverageNeeded(ctx cc.IsNativeCoverageNeededContext) bool {
 	return ctx.DeviceConfig().NativeCoverageEnabled()
 }
 
@@ -1639,7 +1597,8 @@
 
 func apexFileForPrebuiltEtc(ctx android.BaseModuleContext, prebuilt prebuilt_etc.PrebuiltEtcModule, outputFile android.Path) apexFile {
 	dirInApex := filepath.Join(prebuilt.BaseDir(), prebuilt.SubDir())
-	return newApexFile(ctx, outputFile, outputFile.Base(), dirInApex, etc, prebuilt)
+	makeModuleName := strings.ReplaceAll(filepath.Join(dirInApex, outputFile.Base()), "/", "_")
+	return newApexFile(ctx, outputFile, makeModuleName, dirInApex, etc, prebuilt)
 }
 
 func apexFileForCompatConfig(ctx android.BaseModuleContext, config java.PlatformCompatConfigIntf, depName string) apexFile {
@@ -1666,20 +1625,28 @@
 var _ javaModule = (*java.SdkLibraryImport)(nil)
 
 // apexFileForJavaModule creates an apexFile for a java module's dex implementation jar.
-func apexFileForJavaModule(ctx android.BaseModuleContext, module javaModule) apexFile {
+func apexFileForJavaModule(ctx android.ModuleContext, module javaModule) apexFile {
 	return apexFileForJavaModuleWithFile(ctx, module, module.DexJarBuildPath(ctx).PathOrNil())
 }
 
 // apexFileForJavaModuleWithFile creates an apexFile for a java module with the supplied file.
-func apexFileForJavaModuleWithFile(ctx android.BaseModuleContext, module javaModule, dexImplementationJar android.Path) apexFile {
+func apexFileForJavaModuleWithFile(ctx android.ModuleContext, module javaModule, dexImplementationJar android.Path) apexFile {
 	dirInApex := "javalib"
 	af := newApexFile(ctx, dexImplementationJar, module.BaseModuleName(), dirInApex, javaSharedLib, module)
 	af.jacocoReportClassesFile = module.JacocoReportClassesFile()
 	af.lintDepSets = module.LintDepSets()
 	af.customStem = module.Stem() + ".jar"
-	if dexpreopter, ok := module.(java.DexpreopterInterface); ok {
+	// 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())
+			install.PackageFile(ctx)
+		}
+	} else if dexpreopter, ok := module.(java.DexpreopterInterface); ok {
 		for _, install := range dexpreopter.DexpreoptBuiltInstalledForApex() {
 			af.requiredModuleNames = append(af.requiredModuleNames, install.FullModuleName())
+			install.PackageFile(ctx)
 		}
 	}
 	return af
@@ -1968,7 +1935,7 @@
 	if _, ok := depTag.(android.ExcludeFromApexContentsTag); ok {
 		return false
 	}
-	if mod, ok := child.(android.Module); ok && !mod.Enabled() {
+	if mod, ok := child.(android.Module); ok && !mod.Enabled(ctx) {
 		return false
 	}
 	depName := ctx.OtherModuleName(child)
@@ -2095,7 +2062,7 @@
 			}
 		case bpfTag:
 			if bpfProgram, ok := child.(bpf.BpfModule); ok {
-				filesToCopy, _ := bpfProgram.OutputFiles("")
+				filesToCopy := android.OutputFilesForModule(ctx, bpfProgram, "")
 				apex_sub_dir := bpfProgram.SubDir()
 				for _, bpfFile := range filesToCopy {
 					vctx.filesInfo = append(vctx.filesInfo, apexFileForBpfProgram(ctx, bpfFile, apex_sub_dir, bpfProgram))
@@ -2111,7 +2078,7 @@
 			}
 		case prebuiltTag:
 			if prebuilt, ok := child.(prebuilt_etc.PrebuiltEtcModule); ok {
-				filesToCopy, _ := prebuilt.OutputFiles("")
+				filesToCopy := android.OutputFilesForModule(ctx, prebuilt, "")
 				for _, etcFile := range filesToCopy {
 					vctx.filesInfo = append(vctx.filesInfo, apexFileForPrebuiltEtc(ctx, prebuilt, etcFile))
 				}
@@ -2127,20 +2094,10 @@
 			}
 		case testTag:
 			if ccTest, ok := child.(*cc.Module); ok {
-				if ccTest.IsTestPerSrcAllTestsVariation() {
-					// Multiple-output test module (where `test_per_src: true`).
-					//
-					// `ccTest` is the "" ("all tests") variation of a `test_per_src` module.
-					// We do not add this variation to `filesInfo`, as it has no output;
-					// however, we do add the other variations of this module as indirect
-					// dependencies (see below).
-				} else {
-					// Single-output test module (where `test_per_src: false`).
-					af := apexFileForExecutable(ctx, ccTest)
-					af.class = nativeTest
-					vctx.filesInfo = append(vctx.filesInfo, af)
-					addAconfigFiles(vctx, ctx, child)
-				}
+				af := apexFileForExecutable(ctx, ccTest)
+				af.class = nativeTest
+				vctx.filesInfo = append(vctx.filesInfo, af)
+				addAconfigFiles(vctx, ctx, child)
 				return true // track transitive dependencies
 			} else {
 				ctx.PropertyErrorf("tests", "%q is not a cc module", depName)
@@ -2176,15 +2133,6 @@
 	// 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.UseVndk() && a.useVndkAsStable(ctx) && ch.IsVndk() {
-				vctx.requireNativeLibs = append(vctx.requireNativeLibs, ":vndk")
-				return false
-			}
-
-			//TODO: b/296491928 Vendor APEX should use libbinder.ndk instead of libbinder once VNDK is fully deprecated.
-			if ch.InVendorOrProduct() && ctx.Config().IsVndkDeprecated() && child.Name() == "libbinder" {
-				return false
-			}
 			af := apexFileForNativeLibrary(ctx, ch, vctx.handleSpecialLibs)
 			af.transitiveDep = true
 
@@ -2238,26 +2186,13 @@
 			addAconfigFiles(vctx, ctx, child)
 			return true // track transitive dependencies
 		}
-	} else if cc.IsTestPerSrcDepTag(depTag) {
-		if ch, ok := child.(*cc.Module); ok {
-			af := apexFileForExecutable(ctx, ch)
-			// Handle modules created as `test_per_src` variations of a single test module:
-			// use the name of the generated test binary (`fileToCopy`) instead of the name
-			// of the original test module (`depName`, shared by all `test_per_src`
-			// variations of that module).
-			af.androidMkModuleName = filepath.Base(af.builtFile.String())
-			// these are not considered transitive dep
-			af.transitiveDep = false
-			vctx.filesInfo = append(vctx.filesInfo, af)
-			return true // track transitive dependencies
-		}
 	} else if cc.IsHeaderDepTag(depTag) {
 		// nothing
 	} else if java.IsJniDepTag(depTag) {
 		// Because APK-in-APEX embeds jni_libs transitively, we don't need to track transitive deps
 	} else if java.IsXmlPermissionsFileDepTag(depTag) {
 		if prebuilt, ok := child.(prebuilt_etc.PrebuiltEtcModule); ok {
-			filesToCopy, _ := prebuilt.OutputFiles("")
+			filesToCopy := android.OutputFilesForModule(ctx, prebuilt, "")
 			for _, etcFile := range filesToCopy {
 				vctx.filesInfo = append(vctx.filesInfo, apexFileForPrebuiltEtc(ctx, prebuilt, etcFile))
 			}
@@ -2320,7 +2255,7 @@
 }
 
 func addAconfigFiles(vctx *visitorContext, ctx android.ModuleContext, module blueprint.Module) {
-	if dep, ok := android.OtherModuleProvider(ctx, module, android.AconfigTransitiveDeclarationsInfoProvider); ok {
+	if dep, ok := android.OtherModuleProvider(ctx, module, android.AconfigPropagatingProviderKey); ok {
 		if len(dep.AconfigFiles) > 0 && dep.AconfigFiles[ctx.ModuleName()] != nil {
 			vctx.aconfigFiles = append(vctx.aconfigFiles, dep.AconfigFiles[ctx.ModuleName()]...)
 		}
@@ -2408,7 +2343,6 @@
 			return
 		}
 	}
-	android.CollectDependencyAconfigFiles(ctx, &a.mergedAconfigFiles)
 
 	////////////////////////////////////////////////////////////////////////////////////////////
 	// 3) some fields in apexBundle struct are configured
@@ -2431,6 +2365,10 @@
 	a.provideApexExportsInfo(ctx)
 
 	a.providePrebuiltInfo(ctx)
+
+	a.required = a.RequiredModuleNames(ctx)
+
+	a.setOutputFiles(ctx)
 }
 
 // Set prebuiltInfoProvider. This will be used by `apex_prebuiltinfo_singleton` to print out a metadata file
@@ -2459,6 +2397,18 @@
 	})
 }
 
+// Set output files to outputFiles property, which is later used to set the
+// OutputFilesProvider
+func (a *apexBundle) setOutputFiles(ctx android.ModuleContext) {
+	// default dist path
+	ctx.SetOutputFiles(android.Paths{a.outputFile}, "")
+	ctx.SetOutputFiles(android.Paths{a.outputFile}, android.DefaultDistTag)
+	// uncompressed one
+	if a.outputApexFile != nil {
+		ctx.SetOutputFiles(android.Paths{a.outputApexFile}, imageApexSuffix)
+	}
+}
+
 // apexBootclasspathFragmentFiles returns the list of apexFile structures defining the files that
 // the bootclasspath_fragment contributes to the apex.
 func apexBootclasspathFragmentFiles(ctx android.ModuleContext, module blueprint.Module) []apexFile {
@@ -2580,9 +2530,6 @@
 type Defaults struct {
 	android.ModuleBase
 	android.DefaultsModuleBase
-
-	// Single aconfig "cache file" merged from this module and all dependencies.
-	mergedAconfigFiles map[string]android.Paths
 }
 
 // apex_defaults provides defaultable properties to other apex modules.
@@ -2605,10 +2552,6 @@
 	android.OverrideModuleBase
 }
 
-func (d *Defaults) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	android.CollectDependencyAconfigFiles(ctx, &d.mergedAconfigFiles)
-}
-
 func (o *OverrideApex) GenerateAndroidBuildActions(_ android.ModuleContext) {
 	// All the overrides happen in the base module.
 }
@@ -2651,7 +2594,7 @@
 	// 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.
-	minApiLevel := android.MinSdkVersionFromValue(ctx, proptools.String(a.properties.Min_sdk_version))
+	minApiLevel := android.MinSdkVersionFromValue(ctx, proptools.String(a.overridableProperties.Min_sdk_version))
 	if minApiLevel.IsNone() {
 		return ""
 	}
@@ -2725,12 +2668,12 @@
 		if a.minSdkVersionValue(ctx) == "" {
 			ctx.PropertyErrorf("updatable", "updatable APEXes should set min_sdk_version as well")
 		}
+		if a.minSdkVersion(ctx).IsCurrent() {
+			ctx.PropertyErrorf("updatable", "updatable APEXes should not set min_sdk_version to current. Please use a finalized API level or a recognized in-development codename")
+		}
 		if a.UsePlatformApis() {
 			ctx.PropertyErrorf("updatable", "updatable APEXes can't use platform APIs")
 		}
-		if proptools.Bool(a.properties.Use_vndk_as_stable) {
-			ctx.PropertyErrorf("use_vndk_as_stable", "updatable APEXes can't use external VNDK libs")
-		}
 		if a.FutureUpdatable() {
 			ctx.PropertyErrorf("future_updatable", "Already updatable. Remove `future_updatable: true:`")
 		}
@@ -3013,12 +2956,3 @@
 func (a *apexBundle) IsTestApex() bool {
 	return a.testApex
 }
-
-func (a *apexBundle) useVndkAsStable(ctx android.BaseModuleContext) bool {
-	// VNDK cannot be linked if it is deprecated
-	if ctx.Config().IsVndkDeprecated() {
-		return false
-	}
-
-	return proptools.Bool(a.properties.Use_vndk_as_stable)
-}
diff --git a/apex/apex_singleton.go b/apex/apex_singleton.go
index e6ebff2..a8d89b1 100644
--- a/apex/apex_singleton.go
+++ b/apex/apex_singleton.go
@@ -46,6 +46,9 @@
 		Command: "cat $out.rsp | xargs cat" +
 			// Only track non-external dependencies, i.e. those that end up in the binary
 			" | grep -v '(external)'" +
+			// Allowlist androidx deps
+			" | grep -v '^androidx\\.'" +
+			" | grep -v '^prebuilt_androidx\\.'" +
 			// Ignore comments in any of the files
 			" | grep -v '^#'" +
 			" | sort -u -f >$out",
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 3e284b1..f62ee68 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -3671,34 +3671,13 @@
 
 func vndkLibrariesTxtFiles(vers ...string) (result string) {
 	for _, v := range vers {
-		if v == "current" {
-			for _, txt := range []string{"vndkcore", "vndksp", "vndkprivate", "vndkproduct"} {
-				result += `
-					` + txt + `_libraries_txt {
-						name: "` + txt + `.libraries.txt",
-						insert_vndk_version: true,
-					}
-				`
-			}
+		for _, txt := range []string{"llndk", "vndkcore", "vndksp", "vndkprivate", "vndkproduct"} {
 			result += `
-				llndk_libraries_txt {
-					name: "llndk.libraries.txt",
-				}
-				llndk_libraries_txt_for_apex {
-					name: "llndk.libraries.txt.apex",
-					stem: "llndk.libraries.txt",
-					insert_vndk_version: true,
-				}
-			`
-		} else {
-			for _, txt := range []string{"llndk", "vndkcore", "vndksp", "vndkprivate", "vndkproduct"} {
-				result += `
 					prebuilt_etc {
 						name: "` + txt + `.libraries.` + v + `.txt",
 						src: "dummy.txt",
 					}
 				`
-			}
 		}
 	}
 	return
@@ -3806,32 +3785,31 @@
 }
 
 func TestVndkApexDoesntSupportNativeBridgeSupported(t *testing.T) {
-	testApexError(t, `module "com.android.vndk.current" .*: native_bridge_supported: .* doesn't support native bridge binary`, `
+	testApexError(t, `module "com.android.vndk.v30" .*: native_bridge_supported: .* doesn't support native bridge binary`, `
 		apex_vndk {
-			name: "com.android.vndk.current",
-			key: "com.android.vndk.current.key",
+			name: "com.android.vndk.v30",
+			key: "com.android.vndk.v30.key",
 			file_contexts: ":myapex-file_contexts",
 			native_bridge_supported: true,
 		}
 
 		apex_key {
-			name: "com.android.vndk.current.key",
+			name: "com.android.vndk.v30.key",
 			public_key: "testkey.avbpubkey",
 			private_key: "testkey.pem",
 		}
 
-		cc_library {
+		vndk_prebuilt_shared {
 			name: "libvndk",
+			version: "30",
+			target_arch: "arm",
 			srcs: ["mylib.cpp"],
 			vendor_available: true,
 			product_available: true,
 			native_bridge_supported: true,
-			host_supported: true,
 			vndk: {
 				enabled: true,
 			},
-			system_shared_libs: [],
-			stl: "none",
 		}
 	`)
 }
@@ -5070,6 +5048,20 @@
 		// Make sure that the frameworks/base/Android.bp file exists as otherwise hidden API encoding
 		// is disabled.
 		android.FixtureAddTextFile("frameworks/base/Android.bp", ""),
+
+		// Make sure that we have atleast one platform library so that we can check the monolithic hiddenapi
+		// file creation.
+		java.FixtureConfigureBootJars("platform:foo"),
+		android.FixtureModifyMockFS(func(fs android.MockFS) {
+			fs["platform/Android.bp"] = []byte(`
+		java_library {
+			name: "foo",
+			srcs: ["Test.java"],
+			compile_dex: true,
+		}
+		`)
+			fs["platform/Test.java"] = nil
+		}),
 	)
 
 	checkBootDexJarPath := func(t *testing.T, ctx *android.TestContext, stem string, bootDexJarPath string) {
@@ -5164,7 +5156,7 @@
 		checkBootDexJarPath(t, ctx, "libbar", "out/soong/.intermediates/prebuilt_myapex.deapexer/android_common/deapexer/javalib/libbar.jar")
 
 		// Verify the correct module jars contribute to the hiddenapi index file.
-		checkHiddenAPIIndexFromClassesInputs(t, ctx, ``)
+		checkHiddenAPIIndexFromClassesInputs(t, ctx, `out/soong/.intermediates/platform/foo/android_common/javac/foo.jar`)
 		checkHiddenAPIIndexFromFlagsInputs(t, ctx, `
 			my-bootclasspath-fragment/index.csv
 			out/soong/.intermediates/frameworks/base/boot/platform-bootclasspath/android_common/hiddenapi-monolithic/index-from-classes.csv
@@ -5242,7 +5234,7 @@
 		checkBootDexJarPath(t, ctx, "libbar", "out/soong/.intermediates/prebuilt_myapex.deapexer/android_common/deapexer/javalib/libbar.jar")
 
 		// Verify the correct module jars contribute to the hiddenapi index file.
-		checkHiddenAPIIndexFromClassesInputs(t, ctx, ``)
+		checkHiddenAPIIndexFromClassesInputs(t, ctx, `out/soong/.intermediates/platform/foo/android_common/javac/foo.jar`)
 		checkHiddenAPIIndexFromFlagsInputs(t, ctx, `
 			my-bootclasspath-fragment/index.csv
 			out/soong/.intermediates/frameworks/base/boot/platform-bootclasspath/android_common/hiddenapi-monolithic/index-from-classes.csv
@@ -5431,7 +5423,7 @@
 		checkBootDexJarPath(t, ctx, "libbar", "out/soong/.intermediates/prebuilt_myapex.deapexer/android_common/deapexer/javalib/libbar.jar")
 
 		// Verify the correct module jars contribute to the hiddenapi index file.
-		checkHiddenAPIIndexFromClassesInputs(t, ctx, ``)
+		checkHiddenAPIIndexFromClassesInputs(t, ctx, `out/soong/.intermediates/platform/foo/android_common/javac/foo.jar`)
 		checkHiddenAPIIndexFromFlagsInputs(t, ctx, `
 			my-bootclasspath-fragment/index.csv
 			out/soong/.intermediates/frameworks/base/boot/platform-bootclasspath/android_common/hiddenapi-monolithic/index-from-classes.csv
@@ -5528,7 +5520,7 @@
 		checkBootDexJarPath(t, ctx, "libbar", "out/soong/.intermediates/my-bootclasspath-fragment/android_common_myapex/hiddenapi-modular/encoded/libbar.jar")
 
 		// Verify the correct module jars contribute to the hiddenapi index file.
-		checkHiddenAPIIndexFromClassesInputs(t, ctx, ``)
+		checkHiddenAPIIndexFromClassesInputs(t, ctx, `out/soong/.intermediates/platform/foo/android_common/javac/foo.jar`)
 		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
@@ -5622,13 +5614,26 @@
 			compile_dex: true,
 		}
 	`
+		// This test disables libbar, which causes the ComponentDepsMutator to add
+		// deps on libbar.stubs and other sub-modules that don't exist. We can
+		// enable AllowMissingDependencies to work around that, but enabling that
+		// causes extra checks for missing source files to dex_bootjars, so add those
+		// to the mock fs as well.
+		preparer2 := android.GroupFixturePreparers(
+			preparer,
+			android.PrepareForTestWithAllowMissingDependencies,
+			android.FixtureMergeMockFs(map[string][]byte{
+				"build/soong/scripts/check_boot_jars/package_allowed_list.txt": nil,
+				"frameworks/base/config/boot-profile.txt":                      nil,
+			}),
+		)
 
-		ctx := testDexpreoptWithApexes(t, bp, "", preparer, fragment)
+		ctx := testDexpreoptWithApexes(t, bp, "", preparer2, fragment)
 		checkBootDexJarPath(t, ctx, "libfoo", "out/soong/.intermediates/prebuilt_myapex.deapexer/android_common/deapexer/javalib/libfoo.jar")
 		checkBootDexJarPath(t, ctx, "libbar", "out/soong/.intermediates/prebuilt_myapex.deapexer/android_common/deapexer/javalib/libbar.jar")
 
 		// Verify the correct module jars contribute to the hiddenapi index file.
-		checkHiddenAPIIndexFromClassesInputs(t, ctx, ``)
+		checkHiddenAPIIndexFromClassesInputs(t, ctx, `out/soong/.intermediates/platform/foo/android_common/javac/foo.jar`)
 		checkHiddenAPIIndexFromFlagsInputs(t, ctx, `
 			my-bootclasspath-fragment/index.csv
 			out/soong/.intermediates/frameworks/base/boot/platform-bootclasspath/android_common/hiddenapi-monolithic/index-from-classes.csv
@@ -5723,7 +5728,6 @@
 			updatable: false,
 			tests: [
 				"mytest",
-				"mytests",
 			],
 		}
 
@@ -5766,25 +5770,6 @@
 				"testdata/baz"
 			],
 		}
-
-		cc_test {
-			name: "mytests",
-			gtest: false,
-			srcs: [
-				"mytest1.cpp",
-				"mytest2.cpp",
-				"mytest3.cpp",
-			],
-			test_per_src: true,
-			relative_install_path: "test",
-			system_shared_libs: [],
-			static_executable: true,
-			stl: "none",
-			data: [
-				":fg",
-				":fg2",
-			],
-		}
 	`)
 
 	apexRule := ctx.ModuleForTests("myapex", "android_common_myapex").Rule("apexRule")
@@ -5798,11 +5783,6 @@
 	ensureContains(t, copyCmds, "image.apex/bin/test/baz")
 	ensureContains(t, copyCmds, "image.apex/bin/test/bar/baz")
 
-	// Ensure that test deps built with `test_per_src` are copied into apex.
-	ensureContains(t, copyCmds, "image.apex/bin/test/mytest1")
-	ensureContains(t, copyCmds, "image.apex/bin/test/mytest2")
-	ensureContains(t, copyCmds, "image.apex/bin/test/mytest3")
-
 	// Ensure the module is correctly translated.
 	bundle := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*apexBundle)
 	data := android.AndroidMkDataForTest(t, ctx, bundle)
@@ -5812,9 +5792,6 @@
 	data.Custom(&builder, name, prefix, "", data)
 	androidMk := builder.String()
 	ensureContains(t, androidMk, "LOCAL_MODULE := mytest.myapex\n")
-	ensureContains(t, androidMk, "LOCAL_MODULE := mytest1.myapex\n")
-	ensureContains(t, androidMk, "LOCAL_MODULE := mytest2.myapex\n")
-	ensureContains(t, androidMk, "LOCAL_MODULE := mytest3.myapex\n")
 	ensureContains(t, androidMk, "LOCAL_MODULE := myapex\n")
 }
 
@@ -5916,6 +5893,7 @@
 			srcs: ["foo/bar/MyClass.java"],
 			sdk_version: "current",
 			system_modules: "none",
+			use_embedded_native_libs: true,
 			jni_libs: ["libjni"],
 			stl: "none",
 			apex_available: [ "myapex" ],
@@ -6470,7 +6448,7 @@
 		t.Errorf("expected to find defaultVersion %q; got %q", barExpectedDefaultVersion, barActualDefaultVersion)
 	}
 
-	overrideBarManifestRule := result.ModuleForTests("bar", "android_common_myoverrideapex_bar").Rule("apexManifestRule")
+	overrideBarManifestRule := result.ModuleForTests("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)
@@ -6850,7 +6828,7 @@
 	`, withManifestPackageNameOverrides([]string{"myapex:com.android.myapex"}))
 
 	originalVariant := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(android.OverridableModule)
-	overriddenVariant := ctx.ModuleForTests("myapex", "android_common_override_myapex_myapex").Module().(android.OverridableModule)
+	overriddenVariant := ctx.ModuleForTests("myapex", "android_common_override_myapex_override_myapex").Module().(android.OverridableModule)
 	if originalVariant.GetOverriddenBy() != "" {
 		t.Errorf("GetOverriddenBy should be empty, but was %q", originalVariant.GetOverriddenBy())
 	}
@@ -6858,7 +6836,7 @@
 		t.Errorf("GetOverriddenBy should be \"override_myapex\", but was %q", overriddenVariant.GetOverriddenBy())
 	}
 
-	module := ctx.ModuleForTests("myapex", "android_common_override_myapex_myapex")
+	module := ctx.ModuleForTests("myapex", "android_common_override_myapex_override_myapex")
 	apexRule := module.Rule("apexRule")
 	copyCmds := apexRule.Args["copy_commands"]
 
@@ -7119,6 +7097,46 @@
 	ensureMatches(t, contents, "<library\\n\\s+name=\\\"foo\\\"\\n\\s+file=\\\"/apex/myapex/javalib/foo.jar\\\"")
 }
 
+func TestJavaSDKLibraryOverrideApexes(t *testing.T) {
+	ctx := testApex(t, `
+		override_apex {
+			name: "mycompanyapex",
+			base: "myapex",
+		}
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			java_libs: ["foo"],
+			updatable: false,
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java"],
+			api_packages: ["foo"],
+			apex_available: [ "myapex" ],
+		}
+
+		prebuilt_apis {
+			name: "sdk",
+			api_dirs: ["100"],
+		}
+	`, withFiles(filesForSdkLibrary))
+
+	// 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")
+	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) {
 	ctx := testApex(t, `
 		apex {
@@ -7167,7 +7185,7 @@
 
 	// The bar library should depend on the implementation jar.
 	barLibrary := ctx.ModuleForTests("bar", "android_common_myapex").Rule("javac")
-	if expected, actual := `^-classpath [^:]*/turbine-combined/foo\.impl\.jar$`, barLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
+	if expected, actual := `^-classpath [^:]*/turbine-combined/foo\.jar$`, barLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
 		t.Errorf("expected %q, found %#q", expected, actual)
 	}
 }
@@ -7308,7 +7326,7 @@
 
 	// The bar library should depend on the implementation jar.
 	barLibrary := ctx.ModuleForTests("bar", "android_common_myapex").Rule("javac")
-	if expected, actual := `^-classpath [^:]*/turbine-combined/foo\.impl\.jar$`, barLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
+	if expected, actual := `^-classpath [^:]*/turbine-combined/foo\.jar$`, barLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
 		t.Errorf("expected %q, found %#q", expected, actual)
 	}
 }
@@ -8232,60 +8250,6 @@
 	`)
 }
 
-func Test_use_vndk_as_stable_shouldnt_be_used_for_updatable_vendor_apexes(t *testing.T) {
-	testApexError(t, `"myapex" .*: use_vndk_as_stable: updatable APEXes can't use external VNDK libs`, `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			updatable: true,
-			use_vndk_as_stable: true,
-			soc_specific: true,
-		}
-
-		apex_key {
-			name: "myapex.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-	`)
-}
-
-func Test_use_vndk_as_stable_shouldnt_be_used_with_min_sdk_version(t *testing.T) {
-	testApexError(t, `"myapex" .*: use_vndk_as_stable: not supported when min_sdk_version is set`, `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			updatable: false,
-			min_sdk_version: "29",
-			use_vndk_as_stable: true,
-			vendor: true,
-		}
-
-		apex_key {
-			name: "myapex.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-	`)
-}
-
-func Test_use_vndk_as_stable_shouldnt_be_used_for_non_vendor_apexes(t *testing.T) {
-	testApexError(t, `"myapex" .*: use_vndk_as_stable: not supported for system/system_ext APEXes`, `
-		apex {
-			name: "myapex",
-			key: "myapex.key",
-			updatable: false,
-			use_vndk_as_stable: true,
-		}
-
-		apex_key {
-			name: "myapex.key",
-			public_key: "testkey.avbpubkey",
-			private_key: "testkey.pem",
-		}
-	`)
-}
-
 func TestUpdatable_should_not_set_generate_classpaths_proto(t *testing.T) {
 	testApexError(t, `"mysystemserverclasspathfragment" .* it must not set generate_classpaths_proto to false`, `
 		apex {
@@ -8950,7 +8914,7 @@
 		t.Errorf("allowed_files_file: expected %q but got %q", expected, actual)
 	}
 
-	rule2 := ctx.ModuleForTests("myapex", "android_common_override_myapex_myapex").Rule("diffApexContentRule")
+	rule2 := ctx.ModuleForTests("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)
 	}
@@ -9241,7 +9205,7 @@
 								continue
 							}
 							mod := ctx.ModuleForTests(modName, variant).Module().(*cc.Module)
-							if !mod.Enabled() || mod.IsHideFromMake() {
+							if !mod.Enabled(android.PanickingConfigAndErrorContext(ctx)) || mod.IsHideFromMake() {
 								continue
 							}
 							for _, ent := range android.AndroidMkEntriesForTest(t, ctx, mod) {
@@ -9800,188 +9764,246 @@
 	}
 }
 
-// TODO(b/193460475): Re-enable this test
-//func TestApexStrictUpdtabilityLint(t *testing.T) {
-//	bpTemplate := `
-//		apex {
-//			name: "myapex",
-//			key: "myapex.key",
-//			java_libs: ["myjavalib"],
-//			updatable: %v,
-//			min_sdk_version: "29",
-//		}
-//		apex_key {
-//			name: "myapex.key",
-//		}
-//		java_library {
-//			name: "myjavalib",
-//			srcs: ["MyClass.java"],
-//			apex_available: [ "myapex" ],
-//			lint: {
-//				strict_updatability_linting: %v,
-//			},
-//			sdk_version: "current",
-//			min_sdk_version: "29",
-//		}
-//		`
-//	fs := android.MockFS{
-//		"lint-baseline.xml": nil,
-//	}
-//
-//	testCases := []struct {
-//		testCaseName              string
-//		apexUpdatable             bool
-//		javaStrictUpdtabilityLint bool
-//		lintFileExists            bool
-//		disallowedFlagExpected    bool
-//	}{
-//		{
-//			testCaseName:              "lint-baseline.xml does not exist, no disallowed flag necessary in lint cmd",
-//			apexUpdatable:             true,
-//			javaStrictUpdtabilityLint: true,
-//			lintFileExists:            false,
-//			disallowedFlagExpected:    false,
-//		},
-//		{
-//			testCaseName:              "non-updatable apex respects strict_updatability of javalib",
-//			apexUpdatable:             false,
-//			javaStrictUpdtabilityLint: false,
-//			lintFileExists:            true,
-//			disallowedFlagExpected:    false,
-//		},
-//		{
-//			testCaseName:              "non-updatable apex respects strict updatability of javalib",
-//			apexUpdatable:             false,
-//			javaStrictUpdtabilityLint: true,
-//			lintFileExists:            true,
-//			disallowedFlagExpected:    true,
-//		},
-//		{
-//			testCaseName:              "updatable apex sets strict updatability of javalib to true",
-//			apexUpdatable:             true,
-//			javaStrictUpdtabilityLint: false, // will be set to true by mutator
-//			lintFileExists:            true,
-//			disallowedFlagExpected:    true,
-//		},
-//	}
-//
-//	for _, testCase := range testCases {
-//		bp := fmt.Sprintf(bpTemplate, testCase.apexUpdatable, testCase.javaStrictUpdtabilityLint)
-//		fixtures := []android.FixturePreparer{}
-//		if testCase.lintFileExists {
-//			fixtures = append(fixtures, fs.AddToFixture())
-//		}
-//
-//		result := testApex(t, bp, fixtures...)
-//		myjavalib := result.ModuleForTests("myjavalib", "android_common_apex29")
-//		sboxProto := android.RuleBuilderSboxProtoForTests(t, myjavalib.Output("lint.sbox.textproto"))
-//		disallowedFlagActual := strings.Contains(*sboxProto.Commands[0].Command, "--baseline lint-baseline.xml --disallowed_issues NewApi")
-//
-//		if disallowedFlagActual != testCase.disallowedFlagExpected {
-//			t.Errorf("Failed testcase: %v \nActual lint cmd: %v", testCase.testCaseName, *sboxProto.Commands[0].Command)
-//		}
-//	}
-//}
-//
-//func TestUpdatabilityLintSkipLibcore(t *testing.T) {
-//	bp := `
-//		apex {
-//			name: "myapex",
-//			key: "myapex.key",
-//			java_libs: ["myjavalib"],
-//			updatable: true,
-//			min_sdk_version: "29",
-//		}
-//		apex_key {
-//			name: "myapex.key",
-//		}
-//		java_library {
-//			name: "myjavalib",
-//			srcs: ["MyClass.java"],
-//			apex_available: [ "myapex" ],
-//			sdk_version: "current",
-//			min_sdk_version: "29",
-//		}
-//		`
-//
-//	testCases := []struct {
-//		testCaseName           string
-//		moduleDirectory        string
-//		disallowedFlagExpected bool
-//	}{
-//		{
-//			testCaseName:           "lintable module defined outside libcore",
-//			moduleDirectory:        "",
-//			disallowedFlagExpected: true,
-//		},
-//		{
-//			testCaseName:           "lintable module defined in libcore root directory",
-//			moduleDirectory:        "libcore/",
-//			disallowedFlagExpected: false,
-//		},
-//		{
-//			testCaseName:           "lintable module defined in libcore child directory",
-//			moduleDirectory:        "libcore/childdir/",
-//			disallowedFlagExpected: true,
-//		},
-//	}
-//
-//	for _, testCase := range testCases {
-//		lintFileCreator := android.FixtureAddTextFile(testCase.moduleDirectory+"lint-baseline.xml", "")
-//		bpFileCreator := android.FixtureAddTextFile(testCase.moduleDirectory+"Android.bp", bp)
-//		result := testApex(t, "", lintFileCreator, bpFileCreator)
-//		myjavalib := result.ModuleForTests("myjavalib", "android_common_apex29")
-//		sboxProto := android.RuleBuilderSboxProtoForTests(t, myjavalib.Output("lint.sbox.textproto"))
-//		cmdFlags := fmt.Sprintf("--baseline %vlint-baseline.xml --disallowed_issues NewApi", testCase.moduleDirectory)
-//		disallowedFlagActual := strings.Contains(*sboxProto.Commands[0].Command, cmdFlags)
-//
-//		if disallowedFlagActual != testCase.disallowedFlagExpected {
-//			t.Errorf("Failed testcase: %v \nActual lint cmd: %v", testCase.testCaseName, *sboxProto.Commands[0].Command)
-//		}
-//	}
-//}
-//
-//// checks transtive deps of an apex coming from bootclasspath_fragment
-//func TestApexStrictUpdtabilityLintBcpFragmentDeps(t *testing.T) {
-//	bp := `
-//		apex {
-//			name: "myapex",
-//			key: "myapex.key",
-//			bootclasspath_fragments: ["mybootclasspathfragment"],
-//			updatable: true,
-//			min_sdk_version: "29",
-//		}
-//		apex_key {
-//			name: "myapex.key",
-//		}
-//		bootclasspath_fragment {
-//			name: "mybootclasspathfragment",
-//			contents: ["myjavalib"],
-//			apex_available: ["myapex"],
-//			hidden_api: {
-//				split_packages: ["*"],
-//			},
-//		}
-//		java_library {
-//			name: "myjavalib",
-//			srcs: ["MyClass.java"],
-//			apex_available: [ "myapex" ],
-//			sdk_version: "current",
-//			min_sdk_version: "29",
-//			compile_dex: true,
-//		}
-//		`
-//	fs := android.MockFS{
-//		"lint-baseline.xml": nil,
-//	}
-//
-//	result := testApex(t, bp, dexpreopt.FixtureSetApexBootJars("myapex:myjavalib"), fs.AddToFixture())
-//	myjavalib := result.ModuleForTests("myjavalib", "android_common_apex29")
-//	sboxProto := android.RuleBuilderSboxProtoForTests(t, myjavalib.Output("lint.sbox.textproto"))
-//	if !strings.Contains(*sboxProto.Commands[0].Command, "--baseline lint-baseline.xml --disallowed_issues NewApi") {
-//		t.Errorf("Strict updabality lint missing in myjavalib coming from bootclasspath_fragment mybootclasspath-fragment\nActual lint cmd: %v", *sboxProto.Commands[0].Command)
-//	}
-//}
+func TestApexStrictUpdtabilityLint(t *testing.T) {
+	bpTemplate := `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			java_libs: ["myjavalib"],
+			updatable: %v,
+			min_sdk_version: "29",
+		}
+		apex_key {
+			name: "myapex.key",
+		}
+		java_library {
+			name: "myjavalib",
+			srcs: ["MyClass.java"],
+			apex_available: [ "myapex" ],
+			lint: {
+				strict_updatability_linting: %v,
+				%s
+			},
+			sdk_version: "current",
+			min_sdk_version: "29",
+		}
+		`
+	fs := android.MockFS{
+		"lint-baseline.xml": nil,
+	}
+
+	testCases := []struct {
+		testCaseName              string
+		apexUpdatable             bool
+		javaStrictUpdtabilityLint bool
+		lintFileExists            bool
+		disallowedFlagExpected    bool
+	}{
+		{
+			testCaseName:              "lint-baseline.xml does not exist, no disallowed flag necessary in lint cmd",
+			apexUpdatable:             true,
+			javaStrictUpdtabilityLint: true,
+			lintFileExists:            false,
+			disallowedFlagExpected:    false,
+		},
+		{
+			testCaseName:              "non-updatable apex respects strict_updatability of javalib",
+			apexUpdatable:             false,
+			javaStrictUpdtabilityLint: false,
+			lintFileExists:            true,
+			disallowedFlagExpected:    false,
+		},
+		{
+			testCaseName:              "non-updatable apex respects strict updatability of javalib",
+			apexUpdatable:             false,
+			javaStrictUpdtabilityLint: true,
+			lintFileExists:            true,
+			disallowedFlagExpected:    true,
+		},
+		{
+			testCaseName:              "updatable apex sets strict updatability of javalib to true",
+			apexUpdatable:             true,
+			javaStrictUpdtabilityLint: false, // will be set to true by mutator
+			lintFileExists:            true,
+			disallowedFlagExpected:    true,
+		},
+	}
+
+	for _, testCase := range testCases {
+		fixtures := []android.FixturePreparer{}
+		baselineProperty := ""
+		if testCase.lintFileExists {
+			fixtures = append(fixtures, fs.AddToFixture())
+			baselineProperty = "baseline_filename: \"lint-baseline.xml\""
+		}
+		bp := fmt.Sprintf(bpTemplate, testCase.apexUpdatable, testCase.javaStrictUpdtabilityLint, baselineProperty)
+
+		result := testApex(t, bp, fixtures...)
+		myjavalib := result.ModuleForTests("myjavalib", "android_common_apex29")
+		sboxProto := android.RuleBuilderSboxProtoForTests(t, result, myjavalib.Output("lint.sbox.textproto"))
+		disallowedFlagActual := strings.Contains(*sboxProto.Commands[0].Command, "--baseline lint-baseline.xml --disallowed_issues NewApi")
+
+		if disallowedFlagActual != testCase.disallowedFlagExpected {
+			t.Errorf("Failed testcase: %v \nActual lint cmd: %v", testCase.testCaseName, *sboxProto.Commands[0].Command)
+		}
+	}
+}
+
+func TestUpdatabilityLintSkipLibcore(t *testing.T) {
+	bp := `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			java_libs: ["myjavalib"],
+			updatable: true,
+			min_sdk_version: "29",
+		}
+		apex_key {
+			name: "myapex.key",
+		}
+		java_library {
+			name: "myjavalib",
+			srcs: ["MyClass.java"],
+			apex_available: [ "myapex" ],
+			sdk_version: "current",
+			min_sdk_version: "29",
+			lint: {
+				baseline_filename: "lint-baseline.xml",
+			}
+		}
+		`
+
+	testCases := []struct {
+		testCaseName           string
+		moduleDirectory        string
+		disallowedFlagExpected bool
+	}{
+		{
+			testCaseName:           "lintable module defined outside libcore",
+			moduleDirectory:        "",
+			disallowedFlagExpected: true,
+		},
+		{
+			testCaseName:           "lintable module defined in libcore root directory",
+			moduleDirectory:        "libcore/",
+			disallowedFlagExpected: false,
+		},
+		{
+			testCaseName:           "lintable module defined in libcore child directory",
+			moduleDirectory:        "libcore/childdir/",
+			disallowedFlagExpected: true,
+		},
+	}
+
+	for _, testCase := range testCases {
+		lintFileCreator := android.FixtureAddTextFile(testCase.moduleDirectory+"lint-baseline.xml", "")
+		bpFileCreator := android.FixtureAddTextFile(testCase.moduleDirectory+"Android.bp", bp)
+		result := testApex(t, "", lintFileCreator, bpFileCreator)
+		myjavalib := result.ModuleForTests("myjavalib", "android_common_apex29")
+		sboxProto := android.RuleBuilderSboxProtoForTests(t, result, myjavalib.Output("lint.sbox.textproto"))
+		cmdFlags := fmt.Sprintf("--baseline %vlint-baseline.xml --disallowed_issues NewApi", testCase.moduleDirectory)
+		disallowedFlagActual := strings.Contains(*sboxProto.Commands[0].Command, cmdFlags)
+
+		if disallowedFlagActual != testCase.disallowedFlagExpected {
+			t.Errorf("Failed testcase: %v \nActual lint cmd: %v", testCase.testCaseName, *sboxProto.Commands[0].Command)
+		}
+	}
+}
+
+// checks transtive deps of an apex coming from bootclasspath_fragment
+func TestApexStrictUpdtabilityLintBcpFragmentDeps(t *testing.T) {
+	bp := `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			bootclasspath_fragments: ["mybootclasspathfragment"],
+			updatable: true,
+			min_sdk_version: "29",
+		}
+		apex_key {
+			name: "myapex.key",
+		}
+		bootclasspath_fragment {
+			name: "mybootclasspathfragment",
+			contents: ["myjavalib"],
+			apex_available: ["myapex"],
+			hidden_api: {
+				split_packages: ["*"],
+			},
+		}
+		java_library {
+			name: "myjavalib",
+			srcs: ["MyClass.java"],
+			apex_available: [ "myapex" ],
+			sdk_version: "current",
+			min_sdk_version: "29",
+			compile_dex: true,
+			lint: {
+				baseline_filename: "lint-baseline.xml",
+			}
+		}
+		`
+	fs := android.MockFS{
+		"lint-baseline.xml": nil,
+	}
+
+	result := testApex(t, bp, dexpreopt.FixtureSetApexBootJars("myapex:myjavalib"), fs.AddToFixture())
+	myjavalib := result.ModuleForTests("myjavalib", "android_common_apex29")
+	sboxProto := android.RuleBuilderSboxProtoForTests(t, result, myjavalib.Output("lint.sbox.textproto"))
+	if !strings.Contains(*sboxProto.Commands[0].Command, "--baseline lint-baseline.xml --disallowed_issues NewApi") {
+		t.Errorf("Strict updabality lint missing in myjavalib coming from bootclasspath_fragment mybootclasspath-fragment\nActual lint cmd: %v", *sboxProto.Commands[0].Command)
+	}
+}
+
+func TestApexLintBcpFragmentSdkLibDeps(t *testing.T) {
+	bp := `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			bootclasspath_fragments: ["mybootclasspathfragment"],
+			min_sdk_version: "29",
+		}
+		apex_key {
+			name: "myapex.key",
+		}
+		bootclasspath_fragment {
+			name: "mybootclasspathfragment",
+			contents: ["foo"],
+			apex_available: ["myapex"],
+			hidden_api: {
+				split_packages: ["*"],
+			},
+		}
+		java_sdk_library {
+			name: "foo",
+			srcs: ["MyClass.java"],
+			apex_available: [ "myapex" ],
+			sdk_version: "current",
+			min_sdk_version: "29",
+			compile_dex: true,
+		}
+		`
+	fs := android.MockFS{
+		"lint-baseline.xml": nil,
+	}
+
+	result := android.GroupFixturePreparers(
+		prepareForApexTest,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.PrepareForTestWithJacocoInstrumentation,
+		java.FixtureWithLastReleaseApis("foo"),
+		android.FixtureModifyConfig(func(config android.Config) {
+			config.SetApiLibraries([]string{"foo"})
+		}),
+		android.FixtureMergeMockFs(fs),
+	).RunTestWithBp(t, bp)
+
+	myapex := result.ModuleForTests("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",
+		lintReportInputs, "foo.impl")
+}
 
 // updatable apexes should propagate updatable=true to its apps
 func TestUpdatableApexEnforcesAppUpdatability(t *testing.T) {
@@ -10702,10 +10724,6 @@
 			name: "libaconfig_storage_read_api_cc",
 			srcs: ["libaconfig_storage_read_api_cc.cc"],
 		}
-		cc_library {
-			name: "libaconfig_storage_protos_cc",
-			srcs: ["libaconfig_storage_protos_cc.cc"],
-		}
 	`)
 
 	mod := ctx.ModuleForTests("myapex", "android_common_myapex")
@@ -11246,6 +11264,20 @@
 			android.FixtureMergeMockFs(map[string][]byte{
 				"system/sepolicy/apex/com.android.foo-file_contexts": nil,
 			}),
+			// Make sure that we have atleast one platform library so that we can check the monolithic hiddenapi
+			// file creation.
+			java.FixtureConfigureBootJars("platform:foo"),
+			android.FixtureModifyMockFS(func(fs android.MockFS) {
+				fs["platform/Android.bp"] = []byte(`
+		java_library {
+			name: "foo",
+			srcs: ["Test.java"],
+			compile_dex: true,
+		}
+		`)
+				fs["platform/Test.java"] = nil
+			}),
+
 			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
 				variables.BuildFlags = map[string]string{
 					"RELEASE_APEX_CONTRIBUTIONS_ADSERVICES": tc.selectedApexContributions,
@@ -11274,7 +11306,7 @@
 		variation := func(moduleName string) string {
 			ret := "android_common_com.android.foo"
 			if moduleName == "com.google.android.foo" {
-				ret = "android_common_com.google.android.foo_com.android.foo"
+				ret = "android_common_com.google.android.foo_com.google.android.foo"
 			}
 			return ret
 		}
@@ -11428,6 +11460,7 @@
 			aconfig_declarations {
 				name: "%[1]s",
 				package: "com.example.package",
+				container: "system",
 				srcs: [
 					"%[1]s.aconfig",
 				],
@@ -11538,3 +11571,288 @@
 		"depend on java_aconfig_library not passed as an input",
 		aconfigFlagArgs, fmt.Sprintf("%s/%s/intermediate.pb", outDir, "quux"))
 }
+
+func TestMultiplePrebuiltsWithSameBase(t *testing.T) {
+	ctx := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			prebuilts: ["myetc", "myetc2"],
+			min_sdk_version: "29",
+		}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		prebuilt_etc {
+			name: "myetc",
+			src: "myprebuilt",
+			filename: "myfilename",
+		}
+		prebuilt_etc {
+			name: "myetc2",
+			sub_dir: "mysubdir",
+			src: "myprebuilt",
+			filename: "myfilename",
+		}
+	`, withFiles(android.MockFS{
+		"packages/modules/common/build/allowed_deps.txt": nil,
+	}))
+
+	ab := ctx.ModuleForTests("myapex", "android_common_myapex").Module().(*apexBundle)
+	data := android.AndroidMkDataForTest(t, ctx, ab)
+	var builder strings.Builder
+	data.Custom(&builder, ab.BaseModuleName(), "TARGET_", "", data)
+	androidMk := builder.String()
+
+	android.AssertStringDoesContain(t, "not found", androidMk, "LOCAL_MODULE := etc_myfilename.myapex")
+	android.AssertStringDoesContain(t, "not found", androidMk, "LOCAL_MODULE := etc_mysubdir_myfilename.myapex")
+}
+
+func TestApexMinSdkVersionOverride(t *testing.T) {
+	checkMinSdkVersion := func(t *testing.T, module android.TestingModule, expectedMinSdkVersion string) {
+		args := module.Rule("apexRule").Args
+		optFlags := args["opt_flags"]
+		if !strings.Contains(optFlags, "--min_sdk_version "+expectedMinSdkVersion) {
+			t.Errorf("%s: Expected min_sdk_version=%s, got: %s", module.Module(), expectedMinSdkVersion, optFlags)
+		}
+	}
+
+	checkHasDep := func(t *testing.T, ctx *android.TestContext, m android.Module, wantDep android.Module) {
+		t.Helper()
+		found := false
+		ctx.VisitDirectDeps(m, func(dep blueprint.Module) {
+			if dep == wantDep {
+				found = true
+			}
+		})
+		if !found {
+			t.Errorf("Could not find a dependency from %v to %v\n", m, wantDep)
+		}
+	}
+
+	ctx := testApex(t, `
+		apex {
+			name: "com.android.apex30",
+			min_sdk_version: "30",
+			key: "apex30.key",
+			java_libs: ["javalib"],
+		}
+
+		java_library {
+			name: "javalib",
+			srcs: ["A.java"],
+			apex_available: ["com.android.apex30"],
+			min_sdk_version: "30",
+			sdk_version: "current",
+		}
+
+		override_apex {
+			name: "com.mycompany.android.apex30",
+			base: "com.android.apex30",
+		}
+
+		override_apex {
+			name: "com.mycompany.android.apex31",
+			base: "com.android.apex30",
+			min_sdk_version: "31",
+		}
+
+		apex_key {
+			name: "apex30.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+	`, android.FixtureMergeMockFs(android.MockFS{
+		"system/sepolicy/apex/com.android.apex30-file_contexts": nil,
+	}),
+	)
+
+	baseModule := ctx.ModuleForTests("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")
+	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")
+	checkMinSdkVersion(t, overridingModuleDifferentMinSdkVersion, "31")
+	checkHasDep(t, ctx, overridingModuleDifferentMinSdkVersion.Module(), javalibApex31Variant.Module())
+}
+
+func TestOverrideApexWithPrebuiltApexPreferred(t *testing.T) {
+	context := android.GroupFixturePreparers(
+		android.PrepareForIntegrationTestWithAndroid,
+		PrepareForTestWithApexBuildComponents,
+		android.FixtureMergeMockFs(android.MockFS{
+			"system/sepolicy/apex/foo-file_contexts": nil,
+		}),
+	)
+	res := context.RunTestWithBp(t, `
+		apex {
+			name: "foo",
+			key: "myapex.key",
+			apex_available_name: "com.android.foo",
+			variant_version: "0",
+			updatable: false,
+		}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+		prebuilt_apex {
+			name: "foo",
+			src: "foo.apex",
+			prefer: true,
+		}
+		override_apex {
+			name: "myoverrideapex",
+			base: "foo",
+		}
+	`)
+
+	java.CheckModuleHasDependency(t, res.TestContext, "myoverrideapex", "android_common_myoverrideapex_myoverrideapex", "foo")
+}
+
+func TestUpdatableApexMinSdkVersionCurrent(t *testing.T) {
+	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",
+			key: "myapex.key",
+			updatable: true,
+			min_sdk_version: "current",
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+	`)
+}
+
+func TestPrebuiltStubNoinstall(t *testing.T) {
+	testFunc := func(t *testing.T, expectLibfooOnSystemLib bool, fs android.MockFS) {
+		result := android.GroupFixturePreparers(
+			prepareForApexTest,
+			android.PrepareForTestWithAndroidMk,
+			android.PrepareForTestWithMakevars,
+			android.FixtureMergeMockFs(fs),
+		).RunTest(t)
+
+		ldRule := result.ModuleForTests("installedlib", "android_arm64_armv8-a_shared").Rule("ld")
+		android.AssertStringDoesContain(t, "", ldRule.Args["libFlags"], "android_arm64_armv8-a_shared/libfoo.so")
+
+		installRules := result.InstallMakeRulesForTesting(t)
+
+		var installedlibRule *android.InstallMakeRule
+		for i, rule := range installRules {
+			if rule.Target == "out/target/product/test_device/system/lib/installedlib.so" {
+				if installedlibRule != nil {
+					t.Errorf("Duplicate install rules for %s", rule.Target)
+				}
+				installedlibRule = &installRules[i]
+			}
+		}
+		if installedlibRule == nil {
+			t.Errorf("No install rule found for installedlib")
+			return
+		}
+
+		if expectLibfooOnSystemLib {
+			android.AssertStringListContains(t,
+				"installedlib doesn't have install dependency on libfoo impl",
+				installedlibRule.OrderOnlyDeps,
+				"out/target/product/test_device/system/lib/libfoo.so")
+		} else {
+			android.AssertStringListDoesNotContain(t,
+				"installedlib has install dependency on libfoo stub",
+				installedlibRule.Deps,
+				"out/target/product/test_device/system/lib/libfoo.so")
+			android.AssertStringListDoesNotContain(t,
+				"installedlib has order-only install dependency on libfoo stub",
+				installedlibRule.OrderOnlyDeps,
+				"out/target/product/test_device/system/lib/libfoo.so")
+		}
+	}
+
+	prebuiltLibfooBp := []byte(`
+		cc_prebuilt_library {
+			name: "libfoo",
+			prefer: true,
+			srcs: ["libfoo.so"],
+			stubs: {
+				versions: ["1"],
+			},
+			apex_available: ["apexfoo"],
+		}
+	`)
+
+	apexfooBp := []byte(`
+		apex {
+			name: "apexfoo",
+			key: "apexfoo.key",
+			native_shared_libs: ["libfoo"],
+			updatable: false,
+			compile_multilib: "both",
+		}
+		apex_key {
+			name: "apexfoo.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+	`)
+
+	installedlibBp := []byte(`
+		cc_library {
+			name: "installedlib",
+			shared_libs: ["libfoo"],
+		}
+	`)
+
+	t.Run("prebuilt stub (without source): no install", func(t *testing.T) {
+		testFunc(
+			t,
+			/*expectLibfooOnSystemLib=*/ false,
+			android.MockFS{
+				"prebuilts/module_sdk/art/current/Android.bp": prebuiltLibfooBp,
+				"apexfoo/Android.bp":                          apexfooBp,
+				"system/sepolicy/apex/apexfoo-file_contexts":  nil,
+				"Android.bp": installedlibBp,
+			},
+		)
+	})
+
+	disabledSourceLibfooBp := []byte(`
+		cc_library {
+			name: "libfoo",
+			enabled: false,
+			stubs: {
+				versions: ["1"],
+			},
+			apex_available: ["apexfoo"],
+		}
+	`)
+
+	t.Run("prebuilt stub (with disabled source): no install", func(t *testing.T) {
+		testFunc(
+			t,
+			/*expectLibfooOnSystemLib=*/ false,
+			android.MockFS{
+				"prebuilts/module_sdk/art/current/Android.bp": prebuiltLibfooBp,
+				"impl/Android.bp":                            disabledSourceLibfooBp,
+				"apexfoo/Android.bp":                         apexfooBp,
+				"system/sepolicy/apex/apexfoo-file_contexts": nil,
+				"Android.bp":                                 installedlibBp,
+			},
+		)
+	})
+}
diff --git a/apex/bootclasspath_fragment_test.go b/apex/bootclasspath_fragment_test.go
index 778c20a..919cb01 100644
--- a/apex/bootclasspath_fragment_test.go
+++ b/apex/bootclasspath_fragment_test.go
@@ -197,6 +197,12 @@
 			updatable: false,
 		}
 
+		override_apex {
+			name: "com.mycompany.android.art",
+			base: "com.android.art",
+			min_sdk_version: "33", // mycompany overrides the min_sdk_version
+		}
+
 		apex_key {
 			name: "com.android.art.key",
 			public_key: "testkey.avbpubkey",
@@ -325,6 +331,26 @@
 		checkCopiesToPredefinedLocationForArt(t, result.Config, module, "bar", "foo")
 	})
 
+	t.Run("boot image files from source of override apex", func(t *testing.T) {
+		result := android.GroupFixturePreparers(
+			commonPreparer,
+
+			// Configure some libraries in the art bootclasspath_fragment that match the source
+			// bootclasspath_fragment's contents property.
+			java.FixtureConfigureBootJars("com.android.art:foo", "com.android.art:bar"),
+			dexpreopt.FixtureSetTestOnlyArtBootImageJars("com.android.art:foo", "com.android.art:bar"),
+			addSource("foo", "bar"),
+			java.FixtureSetBootImageInstallDirOnDevice("art", "apex/com.android.art/javalib"),
+		).RunTest(t)
+
+		ensureExactContents(t, result.TestContext, "com.android.art", "android_common_com.mycompany.android.art_com.mycompany.android.art", []string{
+			"etc/boot-image.prof",
+			"etc/classpaths/bootclasspath.pb",
+			"javalib/bar.jar",
+			"javalib/foo.jar",
+		})
+	})
+
 	t.Run("generate boot image profile even if dexpreopt is disabled", func(t *testing.T) {
 		result := android.GroupFixturePreparers(
 			commonPreparer,
@@ -1340,4 +1366,89 @@
 	android.AssertStringDoesContain(t, "test", command, "--test-stub-classpath="+nonUpdatableTestStubs)
 }
 
+func TestBootclasspathFragmentProtoContainsMinSdkVersion(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithBootclasspathFragment,
+		prepareForTestWithMyapex,
+		// Configure bootclasspath jars to ensure that hidden API encoding is performed on them.
+		java.FixtureConfigureApexBootJars("myapex:foo", "myapex:bar"),
+		// Make sure that the frameworks/base/Android.bp file exists as otherwise hidden API encoding
+		// is disabled.
+		android.FixtureAddTextFile("frameworks/base/Android.bp", ""),
+
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.FixtureWithLastReleaseApis("foo", "bar"),
+	).RunTestWithBp(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			bootclasspath_fragments: [
+				"mybootclasspathfragment",
+			],
+			updatable: false,
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_sdk_library {
+			name: "foo",
+			srcs: ["b.java"],
+			shared_library: false,
+			public: {enabled: true},
+			apex_available: [
+				"myapex",
+			],
+			min_sdk_version: "33",
+		}
+
+		java_sdk_library {
+			name: "bar",
+			srcs: ["b.java"],
+			shared_library: false,
+			public: {enabled: true},
+			apex_available: [
+				"myapex",
+			],
+			min_sdk_version: "34",
+		}
+
+		bootclasspath_fragment {
+			name: "mybootclasspathfragment",
+			contents: [
+				"foo",
+				"bar",
+			],
+			apex_available: [
+				"myapex",
+			],
+			hidden_api: {
+				split_packages: ["*"],
+			},
+		}
+	`)
+
+	fragment := result.ModuleForTests("mybootclasspathfragment", "android_common_apex10000")
+	classPathProtoContent := android.ContentFromFileRuleForTests(t, result.TestContext, fragment.Output("bootclasspath.pb.textproto"))
+	// foo
+	ensureContains(t, classPathProtoContent, `jars {
+path: "/apex/myapex/javalib/foo.jar"
+classpath: BOOTCLASSPATH
+min_sdk_version: "33"
+max_sdk_version: ""
+}
+`)
+	// bar
+	ensureContains(t, classPathProtoContent, `jars {
+path: "/apex/myapex/javalib/bar.jar"
+classpath: BOOTCLASSPATH
+min_sdk_version: "34"
+max_sdk_version: ""
+}
+`)
+}
+
 // TODO(b/177892522) - add test for host apex.
diff --git a/apex/container_test.go b/apex/container_test.go
new file mode 100644
index 0000000..3931174
--- /dev/null
+++ b/apex/container_test.go
@@ -0,0 +1,329 @@
+// 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 apex
+
+import (
+	"android/soong/android"
+	"android/soong/java"
+	"fmt"
+	"testing"
+)
+
+var checkContainerMatch = func(t *testing.T, name string, container string, expected bool, actual bool) {
+	errorMessage := fmt.Sprintf("module %s container %s value differ", name, container)
+	android.AssertBoolEquals(t, errorMessage, expected, actual)
+}
+
+func TestApexDepsContainers(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForApexTest,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.FixtureWithLastReleaseApis("mybootclasspathlib"),
+	).RunTestWithBp(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			bootclasspath_fragments: [
+				"mybootclasspathfragment",
+			],
+			updatable: true,
+			min_sdk_version: "30",
+		}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+		bootclasspath_fragment {
+			name: "mybootclasspathfragment",
+			contents: [
+				"mybootclasspathlib",
+			],
+			apex_available: [
+				"myapex",
+			],
+			hidden_api: {
+				split_packages: ["*"],
+			},
+		}
+		java_sdk_library {
+			name: "mybootclasspathlib",
+			srcs: [
+				"mybootclasspathlib.java",
+			],
+			apex_available: [
+				"myapex",
+			],
+			compile_dex: true,
+			static_libs: [
+				"foo",
+				"baz",
+			],
+			libs: [
+				"bar",
+			],
+			min_sdk_version: "30",
+		}
+		java_library {
+			name: "foo",
+			srcs:[
+				"A.java",
+			],
+			apex_available: [
+				"myapex",
+			],
+			min_sdk_version: "30",
+		}
+		java_library {
+			name: "bar",
+			srcs:[
+				"A.java",
+			],
+			min_sdk_version: "30",
+		}
+		java_library {
+			name: "baz",
+			srcs:[
+				"A.java",
+			],
+			apex_available: [
+				"//apex_available:platform",
+				"myapex",
+			],
+			min_sdk_version: "30",
+		}
+	`)
+	testcases := []struct {
+		moduleName        string
+		variant           string
+		isSystemContainer bool
+		isApexContainer   bool
+	}{
+		{
+			moduleName:        "mybootclasspathlib",
+			variant:           "android_common_myapex",
+			isSystemContainer: true,
+			isApexContainer:   true,
+		},
+		{
+			moduleName:        "mybootclasspathlib.impl",
+			variant:           "android_common_apex30",
+			isSystemContainer: true,
+			isApexContainer:   true,
+		},
+		{
+			moduleName:        "mybootclasspathlib.stubs",
+			variant:           "android_common",
+			isSystemContainer: true,
+			isApexContainer:   false,
+		},
+		{
+			moduleName:        "foo",
+			variant:           "android_common_apex30",
+			isSystemContainer: true,
+			isApexContainer:   true,
+		},
+		{
+			moduleName:        "bar",
+			variant:           "android_common",
+			isSystemContainer: true,
+			isApexContainer:   false,
+		},
+		{
+			moduleName:        "baz",
+			variant:           "android_common_apex30",
+			isSystemContainer: true,
+			isApexContainer:   true,
+		},
+	}
+
+	for _, c := range testcases {
+		m := result.ModuleForTests(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))
+		checkContainerMatch(t, c.moduleName, "apex", c.isApexContainer, android.InList(android.ApexContainer, belongingContainers))
+	}
+}
+
+func TestNonUpdatableApexDepsContainers(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForApexTest,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.FixtureWithLastReleaseApis("mybootclasspathlib"),
+	).RunTestWithBp(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			bootclasspath_fragments: [
+				"mybootclasspathfragment",
+			],
+			updatable: false,
+		}
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+		bootclasspath_fragment {
+			name: "mybootclasspathfragment",
+			contents: [
+				"mybootclasspathlib",
+			],
+			apex_available: [
+				"myapex",
+			],
+			hidden_api: {
+				split_packages: ["*"],
+			},
+		}
+		java_sdk_library {
+			name: "mybootclasspathlib",
+			srcs: [
+				"mybootclasspathlib.java",
+			],
+			apex_available: [
+				"myapex",
+			],
+			compile_dex: true,
+			static_libs: [
+				"foo",
+			],
+			libs: [
+				"bar",
+			],
+		}
+		java_library {
+			name: "foo",
+			srcs:[
+				"A.java",
+			],
+			apex_available: [
+				"myapex",
+			],
+		}
+		java_library {
+			name: "bar",
+			srcs:[
+				"A.java",
+			],
+		}
+	`)
+	testcases := []struct {
+		moduleName        string
+		variant           string
+		isSystemContainer bool
+		isApexContainer   bool
+	}{
+		{
+			moduleName:        "mybootclasspathlib",
+			variant:           "android_common_myapex",
+			isSystemContainer: true,
+			isApexContainer:   true,
+		},
+		{
+			moduleName:        "mybootclasspathlib.impl",
+			variant:           "android_common_apex10000",
+			isSystemContainer: true,
+			isApexContainer:   true,
+		},
+		{
+			moduleName:        "mybootclasspathlib.stubs",
+			variant:           "android_common",
+			isSystemContainer: true,
+			isApexContainer:   false,
+		},
+		{
+			moduleName:        "foo",
+			variant:           "android_common_apex10000",
+			isSystemContainer: true,
+			isApexContainer:   true,
+		},
+		{
+			moduleName:        "bar",
+			variant:           "android_common",
+			isSystemContainer: true,
+			isApexContainer:   false,
+		},
+	}
+
+	for _, c := range testcases {
+		m := result.ModuleForTests(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))
+		checkContainerMatch(t, c.moduleName, "apex", c.isApexContainer, android.InList(android.ApexContainer, belongingContainers))
+	}
+}
+
+func TestUpdatableAndNonUpdatableApexesIdenticalMinSdkVersion(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForApexTest,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		android.FixtureMergeMockFs(android.MockFS{
+			"system/sepolicy/apex/myapex_non_updatable-file_contexts": nil,
+			"system/sepolicy/apex/myapex_updatable-file_contexts":     nil,
+		}),
+	).RunTestWithBp(t, `
+		apex {
+			name: "myapex_non_updatable",
+			key: "myapex_non_updatable.key",
+			java_libs: [
+				"foo",
+			],
+			updatable: false,
+			min_sdk_version: "30",
+		}
+		apex_key {
+			name: "myapex_non_updatable.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		apex {
+			name: "myapex_updatable",
+			key: "myapex_updatable.key",
+			java_libs: [
+				"foo",
+			],
+			updatable: true,
+			min_sdk_version: "30",
+		}
+		apex_key {
+			name: "myapex_updatable.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_library {
+			name: "foo",
+			srcs:[
+				"A.java",
+			],
+			apex_available: [
+				"myapex_non_updatable",
+				"myapex_updatable",
+			],
+			min_sdk_version: "30",
+			sdk_version: "current",
+		}
+	`)
+
+	fooApexVariant := result.ModuleForTests("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))
+	checkContainerMatch(t, "foo", "apex", true, android.InList(android.ApexContainer, belongingContainers))
+}
diff --git a/apex/platform_bootclasspath_test.go b/apex/platform_bootclasspath_test.go
index 2be9c10..4a20cf0 100644
--- a/apex/platform_bootclasspath_test.go
+++ b/apex/platform_bootclasspath_test.go
@@ -795,3 +795,157 @@
 			}
 		`)
 }
+
+// Source and prebuilt apex provide different set of boot jars
+func TestNonBootJarMissingInPrebuiltFragment(t *testing.T) {
+	bp := `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			bootclasspath_fragments: ["apex-fragment"],
+			updatable: false,
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_library {
+			name: "foo",
+			srcs: ["b.java"],
+			installable: true,
+			apex_available: ["myapex"],
+			permitted_packages: ["foo"],
+		}
+
+		java_library {
+			name: "bar",
+			srcs: ["b.java"],
+			installable: true,
+			apex_available: ["myapex"],
+			permitted_packages: ["bar"],
+		}
+
+		bootclasspath_fragment {
+			name: "apex-fragment",
+			contents: ["foo", "bar"],
+			apex_available:[ "myapex" ],
+			hidden_api: {
+				split_packages: ["*"],
+			},
+		}
+
+		prebuilt_apex {
+			name: "com.google.android.myapex", // mainline prebuilt selection logic in soong relies on the naming convention com.google.android
+			apex_name: "myapex",
+			source_apex_name: "myapex",
+			src: "myapex.apex",
+			exported_bootclasspath_fragments: ["apex-fragment"],
+		}
+
+		java_import {
+			name: "foo",
+			jars: ["foo.jar"],
+			apex_available: ["myapex"],
+			permitted_packages: ["foo"],
+		}
+
+		prebuilt_bootclasspath_fragment {
+			name: "apex-fragment",
+			contents: ["foo"], // Unlike the source fragment, this is missing bar
+			apex_available:[ "myapex" ],
+			hidden_api: {
+				annotation_flags: "my-bootclasspath-fragment/annotation-flags.csv",
+				metadata: "my-bootclasspath-fragment/metadata.csv",
+				index: "my-bootclasspath-fragment/index.csv",
+				stub_flags: "my-bootclasspath-fragment/stub-flags.csv",
+				all_flags: "my-bootclasspath-fragment/all-flags.csv",
+			},
+		}
+
+		// Another prebuilt apex, but this is not selected during the build.
+		prebuilt_apex {
+			name: "com.google.android.myapex.v2", // mainline prebuilt selection logic in soong relies on the naming convention com.google.android
+			apex_name: "myapex",
+			source_apex_name: "myapex",
+			src: "myapex.apex",
+			exported_bootclasspath_fragments: ["apex-fragment.v2"],
+		}
+
+		java_import {
+			name: "bar",
+			jars: ["bar.jar"],
+			apex_available: ["myapex"],
+			permitted_packages: ["bar"],
+		}
+
+		prebuilt_bootclasspath_fragment {
+			name: "apex-fragment.v2",
+			contents: ["bar"], // Unlike the source fragment, this is missing foo
+			apex_available:[ "myapex" ],
+			hidden_api: {
+				annotation_flags: "my-bootclasspath-fragment/annotation-flags.csv",
+				metadata: "my-bootclasspath-fragment/metadata.csv",
+				index: "my-bootclasspath-fragment/index.csv",
+				stub_flags: "my-bootclasspath-fragment/stub-flags.csv",
+				all_flags: "my-bootclasspath-fragment/all-flags.csv",
+			},
+		}
+
+
+		apex_contributions {
+			name: "my_apex_contributions",
+			api_domain: "myapex",
+			contents: [%v],
+		}
+	`
+	testCases := []struct {
+		desc                     string
+		configuredBootJars       []string
+		apexContributionContents string
+		errorExpected            bool
+	}{
+		{
+			desc:               "Source apex is selected, and APEX_BOOT_JARS is correctly configured for source apex builds",
+			configuredBootJars: []string{"myapex:foo", "myapex:bar"},
+		},
+		{
+			desc:               "Source apex is selected, and APEX_BOOT_JARS is missing bar",
+			configuredBootJars: []string{"myapex:foo"},
+			errorExpected:      true,
+		},
+		{
+			desc:                     "Prebuilt apex is selected, and APEX_BOOT_JARS is correctly configured for prebuilt apex build",
+			configuredBootJars:       []string{"myapex:foo"},
+			apexContributionContents: `"prebuilt_com.google.android.myapex"`,
+		},
+		{
+			desc:                     "Prebuilt apex is selected, and APEX_BOOT_JARS is missing foo",
+			configuredBootJars:       []string{"myapex:bar"},
+			apexContributionContents: `"prebuilt_com.google.android.myapex"`,
+			errorExpected:            true,
+		},
+	}
+
+	for _, tc := range testCases {
+		fixture := android.GroupFixturePreparers(
+			prepareForTestWithPlatformBootclasspath,
+			PrepareForTestWithApexBuildComponents,
+			prepareForTestWithMyapex,
+			java.FixtureConfigureApexBootJars(tc.configuredBootJars...),
+			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+				variables.BuildFlags = map[string]string{
+					"RELEASE_APEX_CONTRIBUTIONS_ART": "my_apex_contributions",
+				}
+			}),
+		)
+		if tc.errorExpected {
+			fixture = fixture.ExtendWithErrorHandler(
+				android.FixtureExpectsAtLeastOneErrorMatchingPattern(`in contents.*must also be declared in PRODUCT_APEX_BOOT_JARS`),
+			)
+		}
+		fixture.RunTestWithBp(t, fmt.Sprintf(bp, tc.apexContributionContents))
+	}
+}
diff --git a/apex/prebuilt.go b/apex/prebuilt.go
index ea847e1..65c23d3 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -197,6 +197,7 @@
 	// 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())
+		install.PackageFile(ctx)
 	}
 }
 
@@ -503,9 +504,6 @@
 	inputApex android.Path
 
 	provenanceMetaDataFile android.OutputPath
-
-	// Single aconfig "cache file" merged from this module and all dependencies.
-	mergedAconfigFiles map[string]android.Paths
 }
 
 type ApexFileProperties struct {
@@ -590,15 +588,6 @@
 	return false
 }
 
-func (p *Prebuilt) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		return android.Paths{p.outputApex}, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
 // prebuilt_apex imports an `.apex` file into the build graph as if it was built with apex.
 func PrebuiltFactory() android.Module {
 	module := &Prebuilt{}
@@ -838,7 +827,23 @@
 	android.SetProvider(ctx, android.PrebuiltInfoProvider, info)
 }
 
+// Uses an object provided by its deps to validate that the contents of bcpf have been added to the global
+// PRODUCT_APEX_BOOT_JARS
+// This validation will only run on the apex which is active for this product/release_config
+func validateApexClasspathFragments(ctx android.ModuleContext) {
+	ctx.VisitDirectDeps(func(m android.Module) {
+		if info, exists := android.OtherModuleProvider(ctx, m, java.ClasspathFragmentValidationInfoProvider); exists {
+			ctx.ModuleErrorf("%s in contents of %s must also be declared in PRODUCT_APEX_BOOT_JARS", info.UnknownJars, info.ClasspathFragmentModuleName)
+		}
+	})
+}
+
 func (p *Prebuilt) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	// Validate contents of classpath fragments
+	if !p.IsHideFromMake() {
+		validateApexClasspathFragments(ctx)
+	}
+
 	p.apexKeysPath = writeApexKeys(ctx, p)
 	// TODO(jungjw): Check the key validity.
 	p.inputApex = android.OptionalPathForModuleSrc(ctx, p.prebuiltCommonProperties.Selected_apex).Path()
@@ -882,7 +887,7 @@
 		p.provenanceMetaDataFile = provenance.GenerateArtifactProvenanceMetaData(ctx, p.inputApex, p.installedFile)
 	}
 
-	android.CollectDependencyAconfigFiles(ctx, &p.mergedAconfigFiles)
+	ctx.SetOutputFiles(android.Paths{p.outputApex}, "")
 }
 
 func (p *Prebuilt) ProvenanceMetaDataFile() android.OutputPath {
@@ -998,15 +1003,6 @@
 	return false
 }
 
-func (a *ApexSet) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		return android.Paths{a.outputApex}, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
 // prebuilt_apex imports an `.apex` file into the build graph as if it was built with apex.
 func apexSetFactory() android.Module {
 	module := &ApexSet{}
@@ -1064,6 +1060,11 @@
 }
 
 func (a *ApexSet) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	// Validate contents of classpath fragments
+	if !a.IsHideFromMake() {
+		validateApexClasspathFragments(ctx)
+	}
+
 	a.apexKeysPath = writeApexKeys(ctx, a)
 	a.installFilename = a.InstallFilename()
 	if !strings.HasSuffix(a.installFilename, imageApexSuffix) && !strings.HasSuffix(a.installFilename, imageCapexSuffix) {
@@ -1105,6 +1106,8 @@
 	for _, overridden := range a.prebuiltCommonProperties.Overrides {
 		a.compatSymlinks = append(a.compatSymlinks, makeCompatSymlinks(overridden, ctx)...)
 	}
+
+	ctx.SetOutputFiles(android.Paths{a.outputApex}, "")
 }
 
 type systemExtContext struct {
diff --git a/bazel/Android.bp b/bazel/Android.bp
index 4709f5c..f8273a8 100644
--- a/bazel/Android.bp
+++ b/bazel/Android.bp
@@ -6,22 +6,17 @@
     name: "soong-bazel",
     pkgPath: "android/soong/bazel",
     srcs: [
-        "aquery.go",
-        "bazel_proxy.go",
         "configurability.go",
-        "constants.go",
         "properties.go",
         "testing.go",
     ],
     testSrcs: [
-        "aquery_test.go",
         "properties_test.go",
     ],
     pluginFor: [
         "soong_build",
     ],
     deps: [
-        "bazel_analysis_v2_proto",
         "blueprint",
     ],
 }
diff --git a/bazel/aquery.go b/bazel/aquery.go
deleted file mode 100644
index 35942bc..0000000
--- a/bazel/aquery.go
+++ /dev/null
@@ -1,768 +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 bazel
-
-import (
-	"crypto/sha256"
-	"encoding/base64"
-	"encoding/json"
-	"errors"
-	"fmt"
-	"path/filepath"
-	"reflect"
-	"sort"
-	"strings"
-	"sync"
-
-	analysis_v2_proto "prebuilts/bazel/common/proto/analysis_v2"
-
-	"github.com/google/blueprint/metrics"
-	"github.com/google/blueprint/proptools"
-	"google.golang.org/protobuf/proto"
-)
-
-type artifactId int
-type depsetId int
-type pathFragmentId int
-
-// KeyValuePair represents Bazel's aquery proto, KeyValuePair.
-type KeyValuePair struct {
-	Key   string
-	Value string
-}
-
-// AqueryDepset is a depset definition from Bazel's aquery response. This is
-// akin to the `depSetOfFiles` in the response proto, except:
-//   - direct artifacts are enumerated by full path instead of by ID
-//   - it has a hash of the depset contents, instead of an int ID (for determinism)
-//
-// A depset is a data structure for efficient transitive handling of artifact
-// paths. A single depset consists of one or more artifact paths and one or
-// more "child" depsets.
-type AqueryDepset struct {
-	ContentHash            string
-	DirectArtifacts        []string
-	TransitiveDepSetHashes []string
-}
-
-// BuildStatement contains information to register a build statement corresponding (one to one)
-// with a Bazel action from Bazel's action graph.
-type BuildStatement struct {
-	Command      string
-	Depfile      *string
-	OutputPaths  []string
-	SymlinkPaths []string
-	Env          []*analysis_v2_proto.KeyValuePair
-	Mnemonic     string
-
-	// Inputs of this build statement, either as unexpanded depsets or expanded
-	// input paths. There should be no overlap between these fields; an input
-	// path should either be included as part of an unexpanded depset or a raw
-	// input path string, but not both.
-	InputDepsetHashes []string
-	InputPaths        []string
-	FileContents      string
-	// If ShouldRunInSbox is true, Soong will use sbox to created an isolated environment
-	// and run the mixed build action there
-	ShouldRunInSbox bool
-	// A list of files to add as implicit deps to the outputs of this BuildStatement.
-	// Unlike most properties in BuildStatement, these paths must be relative to the root of
-	// the whole out/ folder, instead of relative to ctx.Config().BazelContext.OutputBase()
-	ImplicitDeps []string
-	IsExecutable bool
-}
-
-// A helper type for aquery processing which facilitates retrieval of path IDs from their
-// less readable Bazel structures (depset and path fragment).
-type aqueryArtifactHandler struct {
-	// Maps depset id to AqueryDepset, a representation of depset which is
-	// post-processed for middleman artifact handling, unhandled artifact
-	// dropping, content hashing, etc.
-	depsetIdToAqueryDepset map[depsetId]AqueryDepset
-	emptyDepsetIds         map[depsetId]struct{}
-	// Maps content hash to AqueryDepset.
-	depsetHashToAqueryDepset map[string]AqueryDepset
-
-	// depsetIdToArtifactIdsCache is a memoization of depset flattening, because flattening
-	// may be an expensive operation.
-	depsetHashToArtifactPathsCache sync.Map
-	// Maps artifact ids to fully expanded paths.
-	artifactIdToPath map[artifactId]string
-}
-
-// The tokens should be substituted with the value specified here, instead of the
-// one returned in 'substitutions' of TemplateExpand action.
-var templateActionOverriddenTokens = map[string]string{
-	// Uses "python3" for %python_binary% instead of the value returned by aquery
-	// which is "py3wrapper.sh". See removePy3wrapperScript.
-	"%python_binary%": "python3",
-}
-
-const (
-	middlemanMnemonic = "Middleman"
-	// The file name of py3wrapper.sh, which is used by py_binary targets.
-	py3wrapperFileName = "/py3wrapper.sh"
-)
-
-func indexBy[K comparable, V any](values []V, keyFn func(v V) K) map[K]V {
-	m := map[K]V{}
-	for _, v := range values {
-		m[keyFn(v)] = v
-	}
-	return m
-}
-
-func newAqueryHandler(aqueryResult *analysis_v2_proto.ActionGraphContainer) (*aqueryArtifactHandler, error) {
-	pathFragments := indexBy(aqueryResult.PathFragments, func(pf *analysis_v2_proto.PathFragment) pathFragmentId {
-		return pathFragmentId(pf.Id)
-	})
-
-	artifactIdToPath := make(map[artifactId]string, len(aqueryResult.Artifacts))
-	for _, artifact := range aqueryResult.Artifacts {
-		artifactPath, err := expandPathFragment(pathFragmentId(artifact.PathFragmentId), pathFragments)
-		if err != nil {
-			return nil, err
-		}
-		if artifact.IsTreeArtifact &&
-			!strings.HasPrefix(artifactPath, "bazel-out/io_bazel_rules_go/") &&
-			!strings.HasPrefix(artifactPath, "bazel-out/rules_java_builtin/") {
-			// Since we're using ninja as an executor, we can't use tree artifacts. Ninja only
-			// considers a file/directory "dirty" when it's mtime changes. Directories' mtimes will
-			// only change when a file in the directory is added/removed, but not when files in
-			// the directory are changed, or when files in subdirectories are changed/added/removed.
-			// Bazel handles this by walking the directory and generating a hash for it after the
-			// action runs, which we would have to do as well if we wanted to support these
-			// artifacts in mixed builds.
-			//
-			// However, there are some bazel built-in rules that use tree artifacts. Allow those,
-			// but keep in mind that they'll have incrementality issues.
-			return nil, fmt.Errorf("tree artifacts are currently not supported in mixed builds: " + artifactPath)
-		}
-		artifactIdToPath[artifactId(artifact.Id)] = artifactPath
-	}
-
-	// Map middleman artifact ContentHash to input artifact depset ID.
-	// Middleman artifacts are treated as "substitute" artifacts for mixed builds. For example,
-	// if we find a middleman action which has inputs [foo, bar], and output [baz_middleman], then,
-	// for each other action which has input [baz_middleman], we add [foo, bar] to the inputs for
-	// that action instead.
-	middlemanIdToDepsetIds := map[artifactId][]uint32{}
-	for _, actionEntry := range aqueryResult.Actions {
-		if actionEntry.Mnemonic == middlemanMnemonic {
-			for _, outputId := range actionEntry.OutputIds {
-				middlemanIdToDepsetIds[artifactId(outputId)] = actionEntry.InputDepSetIds
-			}
-		}
-	}
-
-	depsetIdToDepset := indexBy(aqueryResult.DepSetOfFiles, func(d *analysis_v2_proto.DepSetOfFiles) depsetId {
-		return depsetId(d.Id)
-	})
-
-	aqueryHandler := aqueryArtifactHandler{
-		depsetIdToAqueryDepset:         map[depsetId]AqueryDepset{},
-		depsetHashToAqueryDepset:       map[string]AqueryDepset{},
-		depsetHashToArtifactPathsCache: sync.Map{},
-		emptyDepsetIds:                 make(map[depsetId]struct{}, 0),
-		artifactIdToPath:               artifactIdToPath,
-	}
-
-	// Validate and adjust aqueryResult.DepSetOfFiles values.
-	for _, depset := range aqueryResult.DepSetOfFiles {
-		_, err := aqueryHandler.populateDepsetMaps(depset, middlemanIdToDepsetIds, depsetIdToDepset)
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	return &aqueryHandler, nil
-}
-
-// Ensures that the handler's depsetIdToAqueryDepset map contains an entry for the given
-// depset.
-func (a *aqueryArtifactHandler) populateDepsetMaps(depset *analysis_v2_proto.DepSetOfFiles, middlemanIdToDepsetIds map[artifactId][]uint32, depsetIdToDepset map[depsetId]*analysis_v2_proto.DepSetOfFiles) (*AqueryDepset, error) {
-	if aqueryDepset, containsDepset := a.depsetIdToAqueryDepset[depsetId(depset.Id)]; containsDepset {
-		return &aqueryDepset, nil
-	}
-	transitiveDepsetIds := depset.TransitiveDepSetIds
-	directArtifactPaths := make([]string, 0, len(depset.DirectArtifactIds))
-	for _, id := range depset.DirectArtifactIds {
-		aId := artifactId(id)
-		path, pathExists := a.artifactIdToPath[aId]
-		if !pathExists {
-			return nil, fmt.Errorf("undefined input artifactId %d", aId)
-		}
-		// Filter out any inputs which are universally dropped, and swap middleman
-		// artifacts with their corresponding depsets.
-		if depsetsToUse, isMiddleman := middlemanIdToDepsetIds[aId]; isMiddleman {
-			// Swap middleman artifacts with their corresponding depsets and drop the middleman artifacts.
-			transitiveDepsetIds = append(transitiveDepsetIds, depsetsToUse...)
-		} else if strings.HasSuffix(path, py3wrapperFileName) ||
-			strings.HasPrefix(path, "../bazel_tools") {
-			continue
-			// Drop these artifacts.
-			// See go/python-binary-host-mixed-build for more details.
-			// 1) Drop py3wrapper.sh, just use python binary, the launcher script generated by the
-			// TemplateExpandAction handles everything necessary to launch a Pythin application.
-			// 2) ../bazel_tools: they have MODIFY timestamp 10years in the future and would cause the
-			// containing depset to always be considered newer than their outputs.
-		} else {
-			directArtifactPaths = append(directArtifactPaths, path)
-		}
-	}
-
-	childDepsetHashes := make([]string, 0, len(transitiveDepsetIds))
-	for _, id := range transitiveDepsetIds {
-		childDepsetId := depsetId(id)
-		childDepset, exists := depsetIdToDepset[childDepsetId]
-		if !exists {
-			if _, empty := a.emptyDepsetIds[childDepsetId]; empty {
-				continue
-			} else {
-				return nil, fmt.Errorf("undefined input depsetId %d (referenced by depsetId %d)", childDepsetId, depset.Id)
-			}
-		}
-		if childAqueryDepset, err := a.populateDepsetMaps(childDepset, middlemanIdToDepsetIds, depsetIdToDepset); err != nil {
-			return nil, err
-		} else if childAqueryDepset == nil {
-			continue
-		} else {
-			childDepsetHashes = append(childDepsetHashes, childAqueryDepset.ContentHash)
-		}
-	}
-	if len(directArtifactPaths) == 0 && len(childDepsetHashes) == 0 {
-		a.emptyDepsetIds[depsetId(depset.Id)] = struct{}{}
-		return nil, nil
-	}
-	aqueryDepset := AqueryDepset{
-		ContentHash:            depsetContentHash(directArtifactPaths, childDepsetHashes),
-		DirectArtifacts:        directArtifactPaths,
-		TransitiveDepSetHashes: childDepsetHashes,
-	}
-	a.depsetIdToAqueryDepset[depsetId(depset.Id)] = aqueryDepset
-	a.depsetHashToAqueryDepset[aqueryDepset.ContentHash] = aqueryDepset
-	return &aqueryDepset, nil
-}
-
-// getInputPaths flattens the depsets of the given IDs and returns all transitive
-// input paths contained in these depsets.
-// This is a potentially expensive operation, and should not be invoked except
-// for actions which need specialized input handling.
-func (a *aqueryArtifactHandler) getInputPaths(depsetIds []uint32) ([]string, error) {
-	var inputPaths []string
-
-	for _, id := range depsetIds {
-		inputDepSetId := depsetId(id)
-		depset := a.depsetIdToAqueryDepset[inputDepSetId]
-		inputArtifacts, err := a.artifactPathsFromDepsetHash(depset.ContentHash)
-		if err != nil {
-			return nil, err
-		}
-		for _, inputPath := range inputArtifacts {
-			inputPaths = append(inputPaths, inputPath)
-		}
-	}
-
-	return inputPaths, nil
-}
-
-func (a *aqueryArtifactHandler) artifactPathsFromDepsetHash(depsetHash string) ([]string, error) {
-	if result, exists := a.depsetHashToArtifactPathsCache.Load(depsetHash); exists {
-		return result.([]string), nil
-	}
-	if depset, exists := a.depsetHashToAqueryDepset[depsetHash]; exists {
-		result := depset.DirectArtifacts
-		for _, childHash := range depset.TransitiveDepSetHashes {
-			childArtifactIds, err := a.artifactPathsFromDepsetHash(childHash)
-			if err != nil {
-				return nil, err
-			}
-			result = append(result, childArtifactIds...)
-		}
-		a.depsetHashToArtifactPathsCache.Store(depsetHash, result)
-		return result, nil
-	} else {
-		return nil, fmt.Errorf("undefined input depset hash %s", depsetHash)
-	}
-}
-
-// AqueryBuildStatements returns a slice of BuildStatements and a slice of AqueryDepset
-// which should be registered (and output to a ninja file) to correspond with Bazel's
-// action graph, as described by the given action graph json proto.
-// BuildStatements are one-to-one with actions in the given action graph, and AqueryDepsets
-// are one-to-one with Bazel's depSetOfFiles objects.
-func AqueryBuildStatements(aqueryJsonProto []byte, eventHandler *metrics.EventHandler) ([]*BuildStatement, []AqueryDepset, error) {
-	aqueryProto := &analysis_v2_proto.ActionGraphContainer{}
-	err := proto.Unmarshal(aqueryJsonProto, aqueryProto)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	var aqueryHandler *aqueryArtifactHandler
-	{
-		eventHandler.Begin("init_handler")
-		defer eventHandler.End("init_handler")
-		aqueryHandler, err = newAqueryHandler(aqueryProto)
-		if err != nil {
-			return nil, nil, err
-		}
-	}
-
-	// allocate both length and capacity so each goroutine can write to an index independently without
-	// any need for synchronization for slice access.
-	buildStatements := make([]*BuildStatement, len(aqueryProto.Actions))
-	{
-		eventHandler.Begin("build_statements")
-		defer eventHandler.End("build_statements")
-		wg := sync.WaitGroup{}
-		var errOnce sync.Once
-		id2targets := make(map[uint32]string, len(aqueryProto.Targets))
-		for _, t := range aqueryProto.Targets {
-			id2targets[t.GetId()] = t.GetLabel()
-		}
-		for i, actionEntry := range aqueryProto.Actions {
-			wg.Add(1)
-			go func(i int, actionEntry *analysis_v2_proto.Action) {
-				if strings.HasPrefix(id2targets[actionEntry.TargetId], "@bazel_tools//") {
-					// bazel_tools are removed depsets in `populateDepsetMaps()` so skipping
-					// conversion to build statements as well
-					buildStatements[i] = nil
-				} else if buildStatement, aErr := aqueryHandler.actionToBuildStatement(actionEntry); aErr != nil {
-					errOnce.Do(func() {
-						aErr = fmt.Errorf("%s: [%s] [%s]", aErr.Error(), actionEntry.GetMnemonic(), id2targets[actionEntry.TargetId])
-						err = aErr
-					})
-				} else {
-					// set build statement at an index rather than appending such that each goroutine does not
-					// impact other goroutines
-					buildStatements[i] = buildStatement
-				}
-				wg.Done()
-			}(i, actionEntry)
-		}
-		wg.Wait()
-	}
-	if err != nil {
-		return nil, nil, err
-	}
-
-	depsetsByHash := map[string]AqueryDepset{}
-	depsets := make([]AqueryDepset, 0, len(aqueryHandler.depsetIdToAqueryDepset))
-	{
-		eventHandler.Begin("depsets")
-		defer eventHandler.End("depsets")
-		for _, aqueryDepset := range aqueryHandler.depsetIdToAqueryDepset {
-			if prevEntry, hasKey := depsetsByHash[aqueryDepset.ContentHash]; hasKey {
-				// Two depsets collide on hash. Ensure that their contents are identical.
-				if !reflect.DeepEqual(aqueryDepset, prevEntry) {
-					return nil, nil, fmt.Errorf("two different depsets have the same hash: %v, %v", prevEntry, aqueryDepset)
-				}
-			} else {
-				depsetsByHash[aqueryDepset.ContentHash] = aqueryDepset
-				depsets = append(depsets, aqueryDepset)
-			}
-		}
-	}
-
-	eventHandler.Do("build_statement_sort", func() {
-		// Build Statements and depsets must be sorted by their content hash to
-		// preserve determinism between builds (this will result in consistent ninja file
-		// output). Note they are not sorted by their original IDs nor their Bazel ordering,
-		// as Bazel gives nondeterministic ordering / identifiers in aquery responses.
-		sort.Slice(buildStatements, func(i, j int) bool {
-			// Sort all nil statements to the end of the slice
-			if buildStatements[i] == nil {
-				return false
-			} else if buildStatements[j] == nil {
-				return true
-			}
-			//For build statements, compare output lists. In Bazel, each output file
-			// may only have one action which generates it, so this will provide
-			// a deterministic ordering.
-			outputs_i := buildStatements[i].OutputPaths
-			outputs_j := buildStatements[j].OutputPaths
-			if len(outputs_i) != len(outputs_j) {
-				return len(outputs_i) < len(outputs_j)
-			}
-			if len(outputs_i) == 0 {
-				// No outputs for these actions, so compare commands.
-				return buildStatements[i].Command < buildStatements[j].Command
-			}
-			// There may be multiple outputs, but the output ordering is deterministic.
-			return outputs_i[0] < outputs_j[0]
-		})
-	})
-	eventHandler.Do("depset_sort", func() {
-		sort.Slice(depsets, func(i, j int) bool {
-			return depsets[i].ContentHash < depsets[j].ContentHash
-		})
-	})
-	return buildStatements, depsets, nil
-}
-
-// depsetContentHash computes and returns a SHA256 checksum of the contents of
-// the given depset. This content hash may serve as the depset's identifier.
-// Using a content hash for an identifier is superior for determinism. (For example,
-// using an integer identifier which depends on the order in which the depsets are
-// created would result in nondeterministic depset IDs.)
-func depsetContentHash(directPaths []string, transitiveDepsetHashes []string) string {
-	h := sha256.New()
-	// Use newline as delimiter, as paths cannot contain newline.
-	h.Write([]byte(strings.Join(directPaths, "\n")))
-	h.Write([]byte(strings.Join(transitiveDepsetHashes, "")))
-	fullHash := base64.RawURLEncoding.EncodeToString(h.Sum(nil))
-	return fullHash
-}
-
-func (a *aqueryArtifactHandler) depsetContentHashes(inputDepsetIds []uint32) ([]string, error) {
-	var hashes []string
-	for _, id := range inputDepsetIds {
-		dId := depsetId(id)
-		if aqueryDepset, exists := a.depsetIdToAqueryDepset[dId]; !exists {
-			if _, empty := a.emptyDepsetIds[dId]; !empty {
-				return nil, fmt.Errorf("undefined (not even empty) input depsetId %d", dId)
-			}
-		} else {
-			hashes = append(hashes, aqueryDepset.ContentHash)
-		}
-	}
-	return hashes, nil
-}
-
-// escapes the args received from aquery and creates a command string
-func commandString(actionEntry *analysis_v2_proto.Action) string {
-	argsEscaped := make([]string, len(actionEntry.Arguments))
-	for i, arg := range actionEntry.Arguments {
-		if arg == "" {
-			// If this is an empty string, add ''
-			// And not
-			// 1. (literal empty)
-			// 2. `''\'''\'''` (escaped version of '')
-			//
-			// If we had used (1), then this would appear as a whitespace when we strings.Join
-			argsEscaped[i] = "''"
-		} else {
-			argsEscaped[i] = proptools.ShellEscapeIncludingSpaces(arg)
-		}
-	}
-	return strings.Join(argsEscaped, " ")
-}
-
-func (a *aqueryArtifactHandler) normalActionBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
-	command := commandString(actionEntry)
-	inputDepsetHashes, err := a.depsetContentHashes(actionEntry.InputDepSetIds)
-	if err != nil {
-		return nil, err
-	}
-	outputPaths, depfile, err := a.getOutputPaths(actionEntry)
-	if err != nil {
-		return nil, err
-	}
-
-	buildStatement := &BuildStatement{
-		Command:           command,
-		Depfile:           depfile,
-		OutputPaths:       outputPaths,
-		InputDepsetHashes: inputDepsetHashes,
-		Env:               actionEntry.EnvironmentVariables,
-		Mnemonic:          actionEntry.Mnemonic,
-	}
-	if buildStatement.Mnemonic == "GoToolchainBinaryBuild" {
-		// Unlike b's execution root, mixed build execution root contains a symlink to prebuilts/go
-		// This causes issues for `GOCACHE=$(mktemp -d) go build ...`
-		// To prevent this, sandbox this action in mixed builds as well
-		buildStatement.ShouldRunInSbox = true
-	}
-	return buildStatement, nil
-}
-
-func (a *aqueryArtifactHandler) templateExpandActionBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
-	outputPaths, depfile, err := a.getOutputPaths(actionEntry)
-	if err != nil {
-		return nil, err
-	}
-	if len(outputPaths) != 1 {
-		return nil, fmt.Errorf("Expect 1 output to template expand action, got: output %q", outputPaths)
-	}
-	expandedTemplateContent := expandTemplateContent(actionEntry)
-	// The expandedTemplateContent is escaped for being used in double quotes and shell unescape,
-	// and the new line characters (\n) are also changed to \\n which avoids some Ninja escape on \n, which might
-	// change \n to space and mess up the format of Python programs.
-	// sed is used to convert \\n back to \n before saving to output file.
-	// See go/python-binary-host-mixed-build for more details.
-	command := fmt.Sprintf(`/bin/bash -c 'echo "%[1]s" | sed "s/\\\\n/\\n/g" > %[2]s && chmod a+x %[2]s'`,
-		escapeCommandlineArgument(expandedTemplateContent), outputPaths[0])
-	inputDepsetHashes, err := a.depsetContentHashes(actionEntry.InputDepSetIds)
-	if err != nil {
-		return nil, err
-	}
-
-	buildStatement := &BuildStatement{
-		Command:           command,
-		Depfile:           depfile,
-		OutputPaths:       outputPaths,
-		InputDepsetHashes: inputDepsetHashes,
-		Env:               actionEntry.EnvironmentVariables,
-		Mnemonic:          actionEntry.Mnemonic,
-	}
-	return buildStatement, nil
-}
-
-func (a *aqueryArtifactHandler) fileWriteActionBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
-	outputPaths, _, err := a.getOutputPaths(actionEntry)
-	var depsetHashes []string
-	if err == nil {
-		depsetHashes, err = a.depsetContentHashes(actionEntry.InputDepSetIds)
-	}
-	if err != nil {
-		return nil, err
-	}
-	return &BuildStatement{
-		Depfile:           nil,
-		OutputPaths:       outputPaths,
-		Env:               actionEntry.EnvironmentVariables,
-		Mnemonic:          actionEntry.Mnemonic,
-		InputDepsetHashes: depsetHashes,
-		FileContents:      actionEntry.FileContents,
-		IsExecutable:      actionEntry.IsExecutable,
-	}, nil
-}
-
-func (a *aqueryArtifactHandler) symlinkTreeActionBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
-	outputPaths, _, err := a.getOutputPaths(actionEntry)
-	if err != nil {
-		return nil, err
-	}
-	inputPaths, err := a.getInputPaths(actionEntry.InputDepSetIds)
-	if err != nil {
-		return nil, err
-	}
-	if len(inputPaths) != 1 || len(outputPaths) != 1 {
-		return nil, fmt.Errorf("Expect 1 input and 1 output to symlink action, got: input %q, output %q", inputPaths, outputPaths)
-	}
-	// The actual command is generated in bazelSingleton.GenerateBuildActions
-	return &BuildStatement{
-		Depfile:     nil,
-		OutputPaths: outputPaths,
-		Env:         actionEntry.EnvironmentVariables,
-		Mnemonic:    actionEntry.Mnemonic,
-		InputPaths:  inputPaths,
-	}, nil
-}
-
-type bazelSandwichJson struct {
-	Target         string   `json:"target"`
-	DependOnTarget *bool    `json:"depend_on_target,omitempty"`
-	ImplicitDeps   []string `json:"implicit_deps"`
-}
-
-func (a *aqueryArtifactHandler) unresolvedSymlinkActionBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
-	outputPaths, depfile, err := a.getOutputPaths(actionEntry)
-	if err != nil {
-		return nil, err
-	}
-	if len(actionEntry.InputDepSetIds) != 0 || len(outputPaths) != 1 {
-		return nil, fmt.Errorf("expected 0 inputs and 1 output to symlink action, got: input %q, output %q", actionEntry.InputDepSetIds, outputPaths)
-	}
-	target := actionEntry.UnresolvedSymlinkTarget
-	if target == "" {
-		return nil, fmt.Errorf("expected an unresolved_symlink_target, but didn't get one")
-	}
-	if filepath.Clean(target) != target {
-		return nil, fmt.Errorf("expected %q, got %q", filepath.Clean(target), target)
-	}
-	if strings.HasPrefix(target, "/") {
-		return nil, fmt.Errorf("no absolute symlinks allowed: %s", target)
-	}
-
-	out := outputPaths[0]
-	outDir := filepath.Dir(out)
-	var implicitDeps []string
-	if strings.HasPrefix(target, "bazel_sandwich:") {
-		j := bazelSandwichJson{}
-		err := json.Unmarshal([]byte(target[len("bazel_sandwich:"):]), &j)
-		if err != nil {
-			return nil, err
-		}
-		if proptools.BoolDefault(j.DependOnTarget, true) {
-			implicitDeps = append(implicitDeps, j.Target)
-		}
-		implicitDeps = append(implicitDeps, j.ImplicitDeps...)
-		dotDotsToReachCwd := ""
-		if outDir != "." {
-			dotDotsToReachCwd = strings.Repeat("../", strings.Count(outDir, "/")+1)
-		}
-		target = proptools.ShellEscapeIncludingSpaces(j.Target)
-		target = "{DOTDOTS_TO_OUTPUT_ROOT}" + dotDotsToReachCwd + target
-	} else {
-		target = proptools.ShellEscapeIncludingSpaces(target)
-	}
-
-	outDir = proptools.ShellEscapeIncludingSpaces(outDir)
-	out = proptools.ShellEscapeIncludingSpaces(out)
-	// Use absolute paths, because some soong actions don't play well with relative paths (for example, `cp -d`).
-	command := fmt.Sprintf("mkdir -p %[1]s && rm -f %[2]s && ln -sf %[3]s %[2]s", outDir, out, target)
-	symlinkPaths := outputPaths[:]
-
-	buildStatement := &BuildStatement{
-		Command:      command,
-		Depfile:      depfile,
-		OutputPaths:  outputPaths,
-		Env:          actionEntry.EnvironmentVariables,
-		Mnemonic:     actionEntry.Mnemonic,
-		SymlinkPaths: symlinkPaths,
-		ImplicitDeps: implicitDeps,
-	}
-	return buildStatement, nil
-}
-
-func (a *aqueryArtifactHandler) symlinkActionBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
-	outputPaths, depfile, err := a.getOutputPaths(actionEntry)
-	if err != nil {
-		return nil, err
-	}
-
-	inputPaths, err := a.getInputPaths(actionEntry.InputDepSetIds)
-	if err != nil {
-		return nil, err
-	}
-	if len(inputPaths) != 1 || len(outputPaths) != 1 {
-		return nil, fmt.Errorf("Expect 1 input and 1 output to symlink action, got: input %q, output %q", inputPaths, outputPaths)
-	}
-	out := outputPaths[0]
-	outDir := proptools.ShellEscapeIncludingSpaces(filepath.Dir(out))
-	out = proptools.ShellEscapeIncludingSpaces(out)
-	in := filepath.Join("$PWD", proptools.ShellEscapeIncludingSpaces(inputPaths[0]))
-	// Use absolute paths, because some soong actions don't play well with relative paths (for example, `cp -d`).
-	command := fmt.Sprintf("mkdir -p %[1]s && rm -f %[2]s && ln -sf %[3]s %[2]s", outDir, out, in)
-	symlinkPaths := outputPaths[:]
-
-	buildStatement := &BuildStatement{
-		Command:      command,
-		Depfile:      depfile,
-		OutputPaths:  outputPaths,
-		InputPaths:   inputPaths,
-		Env:          actionEntry.EnvironmentVariables,
-		Mnemonic:     actionEntry.Mnemonic,
-		SymlinkPaths: symlinkPaths,
-	}
-	return buildStatement, nil
-}
-
-func (a *aqueryArtifactHandler) getOutputPaths(actionEntry *analysis_v2_proto.Action) (outputPaths []string, depfile *string, err error) {
-	for _, outputId := range actionEntry.OutputIds {
-		outputPath, exists := a.artifactIdToPath[artifactId(outputId)]
-		if !exists {
-			err = fmt.Errorf("undefined outputId %d", outputId)
-			return
-		}
-		ext := filepath.Ext(outputPath)
-		if ext == ".d" {
-			if depfile != nil {
-				err = fmt.Errorf("found multiple potential depfiles %q, %q", *depfile, outputPath)
-				return
-			} else {
-				depfile = &outputPath
-			}
-		} else {
-			outputPaths = append(outputPaths, outputPath)
-		}
-	}
-	return
-}
-
-// expandTemplateContent substitutes the tokens in a template.
-func expandTemplateContent(actionEntry *analysis_v2_proto.Action) string {
-	replacerString := make([]string, len(actionEntry.Substitutions)*2)
-	for i, pair := range actionEntry.Substitutions {
-		value := pair.Value
-		if val, ok := templateActionOverriddenTokens[pair.Key]; ok {
-			value = val
-		}
-		replacerString[i*2] = pair.Key
-		replacerString[i*2+1] = value
-	}
-	replacer := strings.NewReplacer(replacerString...)
-	return replacer.Replace(actionEntry.TemplateContent)
-}
-
-// \->\\, $->\$, `->\`, "->\", \n->\\n, '->'"'"'
-var commandLineArgumentReplacer = strings.NewReplacer(
-	`\`, `\\`,
-	`$`, `\$`,
-	"`", "\\`",
-	`"`, `\"`,
-	"\n", "\\n",
-	`'`, `'"'"'`,
-)
-
-func escapeCommandlineArgument(str string) string {
-	return commandLineArgumentReplacer.Replace(str)
-}
-
-func (a *aqueryArtifactHandler) actionToBuildStatement(actionEntry *analysis_v2_proto.Action) (*BuildStatement, error) {
-	switch actionEntry.Mnemonic {
-	// Middleman actions are not handled like other actions; they are handled separately as a
-	// preparatory step so that their inputs may be relayed to actions depending on middleman
-	// artifacts.
-	case middlemanMnemonic:
-		return nil, nil
-	// PythonZipper is bogus action returned by aquery, ignore it (b/236198693)
-	case "PythonZipper":
-		return nil, nil
-	// Skip "Fail" actions, which are placeholder actions designed to always fail.
-	case "Fail":
-		return nil, nil
-	case "BaselineCoverage":
-		return nil, nil
-	case "Symlink", "SolibSymlink", "ExecutableSymlink":
-		return a.symlinkActionBuildStatement(actionEntry)
-	case "TemplateExpand":
-		if len(actionEntry.Arguments) < 1 {
-			return a.templateExpandActionBuildStatement(actionEntry)
-		}
-	case "FileWrite", "SourceSymlinkManifest", "RepoMappingManifest":
-		return a.fileWriteActionBuildStatement(actionEntry)
-	case "SymlinkTree":
-		return a.symlinkTreeActionBuildStatement(actionEntry)
-	case "UnresolvedSymlink":
-		return a.unresolvedSymlinkActionBuildStatement(actionEntry)
-	}
-
-	if len(actionEntry.Arguments) < 1 {
-		return nil, errors.New("received action with no command")
-	}
-	return a.normalActionBuildStatement(actionEntry)
-
-}
-
-func expandPathFragment(id pathFragmentId, pathFragmentsMap map[pathFragmentId]*analysis_v2_proto.PathFragment) (string, error) {
-	var labels []string
-	currId := id
-	// Only positive IDs are valid for path fragments. An ID of zero indicates a terminal node.
-	for currId > 0 {
-		currFragment, ok := pathFragmentsMap[currId]
-		if !ok {
-			return "", fmt.Errorf("undefined path fragment id %d", currId)
-		}
-		labels = append([]string{currFragment.Label}, labels...)
-		parentId := pathFragmentId(currFragment.ParentId)
-		if currId == parentId {
-			return "", fmt.Errorf("fragment cannot refer to itself as parent %#v", currFragment)
-		}
-		currId = parentId
-	}
-	return filepath.Join(labels...), nil
-}
diff --git a/bazel/aquery_test.go b/bazel/aquery_test.go
deleted file mode 100644
index cbd2791..0000000
--- a/bazel/aquery_test.go
+++ /dev/null
@@ -1,1411 +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 bazel
-
-import (
-	"encoding/json"
-	"fmt"
-	"reflect"
-	"sort"
-	"testing"
-
-	analysis_v2_proto "prebuilts/bazel/common/proto/analysis_v2"
-
-	"github.com/google/blueprint/metrics"
-	"google.golang.org/protobuf/proto"
-)
-
-func TestAqueryMultiArchGenrule(t *testing.T) {
-	// This input string is retrieved from a real build of bionic-related genrules.
-	const inputString = `
-{
- "Artifacts": [
-   { "Id": 1, "path_fragment_id": 1 },
-   { "Id": 2, "path_fragment_id": 6 },
-   { "Id": 3, "path_fragment_id": 8 },
-   { "Id": 4, "path_fragment_id": 12 },
-   { "Id": 5, "path_fragment_id": 19 },
-   { "Id": 6, "path_fragment_id": 20 },
-   { "Id": 7, "path_fragment_id": 21 }],
- "Actions": [{
-   "target_id": 1,
-   "action_key": "ab53f6ecbdc2ee8cb8812613b63205464f1f5083f6dca87081a0a398c0f1ecf7",
-   "Mnemonic": "Genrule",
-   "configuration_id": 1,
-   "Arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py arm ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-arm.S"],
-   "environment_variables": [{
-     "Key": "PATH",
-     "Value": "/bin:/usr/bin:/usr/local/bin"
-   }],
-   "input_dep_set_ids": [1],
-   "output_ids": [4],
-   "primary_output_id": 4
- }, {
-   "target_id": 2,
-   "action_key": "9f4309ce165dac458498cb92811c18b0b7919782cc37b82a42d2141b8cc90826",
-   "Mnemonic": "Genrule",
-   "configuration_id": 1,
-   "Arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py x86 ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-x86.S"],
-   "environment_variables": [{
-     "Key": "PATH",
-     "Value": "/bin:/usr/bin:/usr/local/bin"
-   }],
-   "input_dep_set_ids": [2],
-   "output_ids": [5],
-   "primary_output_id": 5
- }, {
-   "target_id": 3,
-   "action_key": "50d6c586103ebeed3a218195540bcc30d329464eae36377eb82f8ce7c36ac342",
-   "Mnemonic": "Genrule",
-   "configuration_id": 1,
-   "Arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py x86_64 ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-x86_64.S"],
-   "environment_variables": [{
-     "Key": "PATH",
-     "Value": "/bin:/usr/bin:/usr/local/bin"
-   }],
-   "input_dep_set_ids": [3],
-   "output_ids": [6],
-   "primary_output_id": 6
- }, {
-   "target_id": 4,
-   "action_key": "f30cbe442f5216f4223cf16a39112cad4ec56f31f49290d85cff587e48647ffa",
-   "Mnemonic": "Genrule",
-   "configuration_id": 1,
-   "Arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py arm64 ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-arm64.S"],
-   "environment_variables": [{
-     "Key": "PATH",
-     "Value": "/bin:/usr/bin:/usr/local/bin"
-   }],
-   "input_dep_set_ids": [4],
-   "output_ids": [7],
-   "primary_output_id": 7
- }],
- "Targets": [
-   { "Id": 1, "Label": "@sourceroot//bionic/libc:syscalls-arm", "rule_class_id": 1 },
-   { "Id": 2, "Label": "@sourceroot//bionic/libc:syscalls-x86", "rule_class_id": 1 },
-   { "Id": 3, "Label": "@sourceroot//bionic/libc:syscalls-x86_64", "rule_class_id": 1 },
-   { "Id": 4, "Label": "@sourceroot//bionic/libc:syscalls-arm64", "rule_class_id": 1 }],
- "dep_set_of_files": [
-   { "Id": 1, "direct_artifact_ids": [1, 2, 3] },
-   { "Id": 2, "direct_artifact_ids": [1, 2, 3] },
-   { "Id": 3, "direct_artifact_ids": [1, 2, 3] },
-   { "Id": 4, "direct_artifact_ids": [1, 2, 3] }],
- "Configuration": [{
-   "Id": 1,
-   "Mnemonic": "k8-fastbuild",
-   "platform_name": "k8",
-   "Checksum": "485c362832c178e367d972177f68e69e0981e51e67ef1c160944473db53fe046"
- }],
- "rule_classes": [{ "Id": 1, "Name": "genrule"}],
- "path_fragments": [
-   { "Id": 5, "Label": ".." },
-   { "Id": 4, "Label": "sourceroot", "parent_id": 5 },
-   { "Id": 3, "Label": "bionic", "parent_id": 4 },
-   { "Id": 2, "Label": "libc", "parent_id": 3 },
-   { "Id": 1, "Label": "SYSCALLS.TXT", "parent_id": 2 },
-   { "Id": 7, "Label": "tools", "parent_id": 2 },
-   { "Id": 6, "Label": "gensyscalls.py", "parent_id": 7 },
-   { "Id": 11, "Label": "bazel_tools", "parent_id": 5 },
-   { "Id": 10, "Label": "tools", "parent_id": 11 },
-   { "Id": 9, "Label": "genrule", "parent_id": 10 },
-   { "Id": 8, "Label": "genrule-setup.sh", "parent_id": 9 },
-   { "Id": 18, "Label": "bazel-out" },
-   { "Id": 17, "Label": "sourceroot", "parent_id": 18 },
-   { "Id": 16, "Label": "k8-fastbuild", "parent_id": 17 },
-   { "Id": 15, "Label": "bin", "parent_id": 16 },
-   { "Id": 14, "Label": "bionic", "parent_id": 15 },
-   { "Id": 13, "Label": "libc", "parent_id": 14 },
-   { "Id": 12, "Label": "syscalls-arm.S", "parent_id": 13 },
-   { "Id": 19, "Label": "syscalls-x86.S", "parent_id": 13 },
-   { "Id": 20, "Label": "syscalls-x86_64.S", "parent_id": 13 },
-   { "Id": 21, "Label": "syscalls-arm64.S", "parent_id": 13 }]
-}
-`
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	actualbuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{})
-	var expectedBuildStatements []*BuildStatement
-	for _, arch := range []string{"arm", "arm64", "x86", "x86_64"} {
-		expectedBuildStatements = append(expectedBuildStatements,
-			&BuildStatement{
-				Command: fmt.Sprintf(
-					"/bin/bash -c 'source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py %s ../sourceroot/bionic/libc/SYSCALLS.TXT > bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-%s.S'",
-					arch, arch),
-				OutputPaths: []string{
-					fmt.Sprintf("bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-%s.S", arch),
-				},
-				Env: []*analysis_v2_proto.KeyValuePair{
-					{Key: "PATH", Value: "/bin:/usr/bin:/usr/local/bin"},
-				},
-				Mnemonic: "Genrule",
-			})
-	}
-	assertBuildStatements(t, expectedBuildStatements, actualbuildStatements)
-
-	expectedFlattenedInputs := []string{
-		"../sourceroot/bionic/libc/SYSCALLS.TXT",
-		"../sourceroot/bionic/libc/tools/gensyscalls.py",
-	}
-	// In this example, each depset should have the same expected inputs.
-	for _, actualDepset := range actualDepsets {
-		actualFlattenedInputs := flattenDepsets([]string{actualDepset.ContentHash}, actualDepsets)
-		if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) {
-			t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs)
-		}
-	}
-}
-
-func TestInvalidOutputId(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 },
-   { "id": 2, "path_fragment_id": 2 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "action_x",
-   "mnemonic": "X",
-   "arguments": ["touch", "foo"],
-   "input_dep_set_ids": [1],
-   "output_ids": [3],
-   "primary_output_id": 3
- }],
- "dep_set_of_files": [
-   { "id": 1, "direct_artifact_ids": [1, 2] }],
- "path_fragments": [
-   { "id": 1, "label": "one" },
-   { "id": 2, "label": "two" }]
-}`
-
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
-	assertError(t, err, "undefined outputId 3: [X] []")
-}
-
-func TestInvalidInputDepsetIdFromAction(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 },
-   { "id": 2, "path_fragment_id": 2 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "action_x",
-   "mnemonic": "X",
-   "arguments": ["touch", "foo"],
-   "input_dep_set_ids": [2],
-   "output_ids": [1],
-   "primary_output_id": 1
- }],
- "targets": [{
-   "id": 1,
-   "label": "target_x"
- }],
- "dep_set_of_files": [
-   { "id": 1, "direct_artifact_ids": [1, 2] }],
- "path_fragments": [
-   { "id": 1, "label": "one" },
-   { "id": 2, "label": "two" }]
-}`
-
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
-	assertError(t, err, "undefined (not even empty) input depsetId 2: [X] [target_x]")
-}
-
-func TestInvalidInputDepsetIdFromDepset(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 },
-   { "id": 2, "path_fragment_id": 2 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "x",
-   "arguments": ["touch", "foo"],
-   "input_dep_set_ids": [1],
-   "output_ids": [1],
-   "primary_output_id": 1
- }],
- "dep_set_of_files": [
-   { "id": 1, "direct_artifact_ids": [1, 2], "transitive_dep_set_ids": [42] }],
- "path_fragments": [
-   { "id": 1, "label": "one"},
-   { "id": 2, "label": "two" }]
-}`
-
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
-	assertError(t, err, "undefined input depsetId 42 (referenced by depsetId 1)")
-}
-
-func TestInvalidInputArtifactId(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 },
-   { "id": 2, "path_fragment_id": 2 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "x",
-   "arguments": ["touch", "foo"],
-   "input_dep_set_ids": [1],
-   "output_ids": [1],
-   "primary_output_id": 1
- }],
- "dep_set_of_files": [
-   { "id": 1, "direct_artifact_ids": [1, 3] }],
- "path_fragments": [
-   { "id": 1, "label": "one" },
-   { "id": 2, "label": "two" }]
-}`
-
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
-	assertError(t, err, "undefined input artifactId 3")
-}
-
-func TestInvalidPathFragmentId(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 },
-   { "id": 2, "path_fragment_id": 2 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "x",
-   "arguments": ["touch", "foo"],
-   "input_dep_set_ids": [1],
-   "output_ids": [1],
-   "primary_output_id": 1
- }],
- "dep_set_of_files": [
-    { "id": 1, "direct_artifact_ids": [1, 2] }],
- "path_fragments": [
-   {  "id": 1, "label": "one" },
-   {  "id": 2, "label": "two", "parent_id": 3 }]
-}`
-
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
-	assertError(t, err, "undefined path fragment id 3")
-}
-
-func TestDepfiles(t *testing.T) {
-	const inputString = `
-{
-  "artifacts": [
-    { "id": 1, "path_fragment_id": 1 },
-    { "id": 2, "path_fragment_id": 2 },
-    { "id": 3, "path_fragment_id": 3 }],
-  "actions": [{
-    "target_Id": 1,
-    "action_Key": "x",
-    "mnemonic": "x",
-    "arguments": ["touch", "foo"],
-    "input_dep_set_ids": [1],
-    "output_ids": [2, 3],
-    "primary_output_id": 2
-  }],
-  "dep_set_of_files": [
-    { "id": 1, "direct_Artifact_Ids": [1, 2, 3] }],
-  "path_fragments": [
-    { "id": 1, "label": "one" },
-    { "id": 2, "label": "two" },
-    { "id": 3, "label": "two.d" }]
-}`
-
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
-	if err != nil {
-		t.Errorf("Unexpected error %q", err)
-		return
-	}
-	if expected := 1; len(actual) != expected {
-		t.Fatalf("Expected %d build statements, got %d", expected, len(actual))
-		return
-	}
-
-	bs := actual[0]
-	expectedDepfile := "two.d"
-	if bs.Depfile == nil {
-		t.Errorf("Expected depfile %q, but there was none found", expectedDepfile)
-	} else if *bs.Depfile != expectedDepfile {
-		t.Errorf("Expected depfile %q, but got %q", expectedDepfile, *bs.Depfile)
-	}
-}
-
-func TestMultipleDepfiles(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 },
-   { "id": 2, "path_fragment_id": 2 },
-   { "id": 3, "path_fragment_id": 3 },
-   { "id": 4, "path_fragment_id": 4 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "action_x",
-   "mnemonic": "X",
-   "arguments": ["touch", "foo"],
-   "input_dep_set_ids": [1],
-   "output_ids": [2,3,4],
-   "primary_output_id": 2
- }],
- "dep_set_of_files": [{
-   "id": 1,
-   "direct_artifact_ids": [1, 2, 3, 4]
- }],
- "path_fragments": [
-   { "id": 1, "label": "one" },
-   { "id": 2, "label": "two" },
-   { "id": 3, "label": "two.d" },
-   { "id": 4, "label": "other.d" }]
-}`
-
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
-	assertError(t, err, `found multiple potential depfiles "two.d", "other.d": [X] []`)
-}
-
-func TestTransitiveInputDepsets(t *testing.T) {
-	// The input aquery for this test comes from a proof-of-concept starlark rule which registers
-	// a single action with many inputs given via a deep depset.
-	const inputString = `
-{
- "artifacts": [
-  { "id": 1, "path_fragment_id": 1 },
-  { "id": 2, "path_fragment_id": 7 },
-  { "id": 3, "path_fragment_id": 8 },
-  { "id": 4, "path_fragment_id": 9 },
-  { "id": 5, "path_fragment_id": 10 },
-  { "id": 6, "path_fragment_id": 11 },
-  { "id": 7, "path_fragment_id": 12 },
-  { "id": 8, "path_fragment_id": 13 },
-  { "id": 9, "path_fragment_id": 14 },
-  { "id": 10, "path_fragment_id": 15 },
-  { "id": 11, "path_fragment_id": 16 },
-  { "id": 12, "path_fragment_id": 17 },
-  { "id": 13, "path_fragment_id": 18 },
-  { "id": 14, "path_fragment_id": 19 },
-  { "id": 15, "path_fragment_id": 20 },
-  { "id": 16, "path_fragment_id": 21 },
-  { "id": 17, "path_fragment_id": 22 },
-  { "id": 18, "path_fragment_id": 23 },
-  { "id": 19, "path_fragment_id": 24 },
-  { "id": 20, "path_fragment_id": 25 },
-  { "id": 21, "path_fragment_id": 26 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "3b826d17fadbbbcd8313e456b90ec47c078c438088891dd45b4adbcd8889dc50",
-   "mnemonic": "Action",
-   "configuration_id": 1,
-   "arguments": ["/bin/bash", "-c", "touch bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out"],
-   "input_dep_set_ids": [1],
-   "output_ids": [21],
-   "primary_output_id": 21
- }],
- "dep_set_of_files": [
-   { "id": 3, "direct_artifact_ids": [1, 2, 3, 4, 5] },
-   { "id": 4, "direct_artifact_ids": [6, 7, 8, 9, 10] },
-   { "id": 2, "transitive_dep_set_ids": [3, 4], "direct_artifact_ids": [11, 12, 13, 14, 15] },
-   { "id": 5, "direct_artifact_ids": [16, 17, 18, 19] },
-   { "id": 1, "transitive_dep_set_ids": [2, 5], "direct_artifact_ids": [20] }],
- "path_fragments": [
-   { "id": 6, "label": "bazel-out" },
-   { "id": 5, "label": "sourceroot", "parent_id": 6 },
-   { "id": 4, "label": "k8-fastbuild", "parent_id": 5 },
-   { "id": 3, "label": "bin", "parent_id": 4 },
-   { "id": 2, "label": "testpkg", "parent_id": 3 },
-   { "id": 1, "label": "test_1", "parent_id": 2 },
-   { "id": 7, "label": "test_2", "parent_id": 2 },
-   { "id": 8, "label": "test_3", "parent_id": 2 },
-   { "id": 9, "label": "test_4", "parent_id": 2 },
-   { "id": 10, "label": "test_5", "parent_id": 2 },
-   { "id": 11, "label": "test_6", "parent_id": 2 },
-   { "id": 12, "label": "test_7", "parent_id": 2 },
-	 { "id": 13, "label": "test_8", "parent_id": 2 },
-   { "id": 14, "label": "test_9", "parent_id": 2 },
-   { "id": 15, "label": "test_10", "parent_id": 2 },
-   { "id": 16, "label": "test_11", "parent_id": 2 },
-   { "id": 17, "label": "test_12", "parent_id": 2 },
-   { "id": 18, "label": "test_13", "parent_id": 2 },
-   { "id": 19, "label": "test_14", "parent_id": 2 },
-   { "id": 20, "label": "test_15", "parent_id": 2 },
-   { "id": 21, "label": "test_16", "parent_id": 2 },
-   { "id": 22, "label": "test_17", "parent_id": 2 },
-   { "id": 23, "label": "test_18", "parent_id": 2 },
-   { "id": 24, "label": "test_19", "parent_id": 2 },
-   { "id": 25, "label": "test_root", "parent_id": 2 },
-   { "id": 26,"label": "test_out", "parent_id": 2 }]
-}`
-
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	actualbuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{})
-
-	expectedBuildStatements := []*BuildStatement{
-		&BuildStatement{
-			Command:      "/bin/bash -c 'touch bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out'",
-			OutputPaths:  []string{"bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out"},
-			Mnemonic:     "Action",
-			SymlinkPaths: []string{},
-		},
-	}
-	assertBuildStatements(t, expectedBuildStatements, actualbuildStatements)
-
-	// Inputs for the action are test_{i} from 1 to 20, and test_root. These inputs
-	// are given via a deep depset, but the depset is flattened when returned as a
-	// BuildStatement slice.
-	var expectedFlattenedInputs []string
-	for i := 1; i < 20; i++ {
-		expectedFlattenedInputs = append(expectedFlattenedInputs, fmt.Sprintf("bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_%d", i))
-	}
-	expectedFlattenedInputs = append(expectedFlattenedInputs, "bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_root")
-
-	actualDepsetHashes := actualbuildStatements[0].InputDepsetHashes
-	actualFlattenedInputs := flattenDepsets(actualDepsetHashes, actualDepsets)
-	if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) {
-		t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs)
-	}
-}
-
-func TestSymlinkTree(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 },
-   { "id": 2, "path_fragment_id": 2 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "SymlinkTree",
-   "configuration_id": 1,
-   "input_dep_set_ids": [1],
-   "output_ids": [2],
-   "primary_output_id": 2,
-   "execution_platform": "//build/bazel/platforms:linux_x86_64"
- }],
- "path_fragments": [
-   { "id": 1, "label": "foo.manifest" },
-   { "id": 2, "label": "foo.runfiles/MANIFEST" }],
- "dep_set_of_files": [
-   { "id": 1, "direct_artifact_ids": [1] }]
-}
-`
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
-	if err != nil {
-		t.Errorf("Unexpected error %q", err)
-		return
-	}
-	assertBuildStatements(t, []*BuildStatement{
-		&BuildStatement{
-			Command:      "",
-			OutputPaths:  []string{"foo.runfiles/MANIFEST"},
-			Mnemonic:     "SymlinkTree",
-			InputPaths:   []string{"foo.manifest"},
-			SymlinkPaths: []string{},
-		},
-	}, actual)
-}
-
-func TestBazelToolsRemovalFromInputDepsets(t *testing.T) {
-	const inputString = `{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 10 },
-   { "id": 2, "path_fragment_id": 20 },
-   { "id": 3, "path_fragment_id": 30 },
-   { "id": 4, "path_fragment_id": 40 }],
- "dep_set_of_files": [{
-   "id": 1111,
-   "direct_artifact_ids": [3 , 4]
- }, {
-   "id": 2222,
-   "direct_artifact_ids": [3]
- }],
- "actions": [{
-   "target_id": 100,
-   "action_key": "x",
-   "input_dep_set_ids": [1111, 2222],
-   "mnemonic": "x",
-   "arguments": ["bogus", "command"],
-   "output_ids": [2],
-   "primary_output_id": 1
- }],
- "path_fragments": [
-   { "id": 10, "label": "input" },
-   { "id": 20, "label": "output" },
-   { "id": 30, "label": "dep1", "parent_id": 50 },
-   { "id": 40, "label": "dep2", "parent_id": 60 },
-   { "id": 50, "label": "bazel_tools", "parent_id": 60 },
-   { "id": 60, "label": ".."}
- ]
-}`
-	/* depsets
-	       1111  2222
-	       /  \   |
-	../dep2    ../bazel_tools/dep1
-	*/
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	actualBuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{})
-	if len(actualDepsets) != 1 {
-		t.Errorf("expected 1 depset but found %#v", actualDepsets)
-		return
-	}
-	dep2Found := false
-	for _, dep := range flattenDepsets([]string{actualDepsets[0].ContentHash}, actualDepsets) {
-		if dep == "../bazel_tools/dep1" {
-			t.Errorf("dependency %s expected to be removed but still exists", dep)
-		} else if dep == "../dep2" {
-			dep2Found = true
-		}
-	}
-	if !dep2Found {
-		t.Errorf("dependency ../dep2 expected but not found")
-	}
-
-	expectedBuildStatement := &BuildStatement{
-		Command:      "bogus command",
-		OutputPaths:  []string{"output"},
-		Mnemonic:     "x",
-		SymlinkPaths: []string{},
-	}
-	buildStatementFound := false
-	for _, actualBuildStatement := range actualBuildStatements {
-		if buildStatementEquals(actualBuildStatement, expectedBuildStatement) == "" {
-			buildStatementFound = true
-			break
-		}
-	}
-	if !buildStatementFound {
-		t.Errorf("expected but missing %#v in %#v", expectedBuildStatement, actualBuildStatements)
-		return
-	}
-}
-
-func TestBazelToolsRemovalFromTargets(t *testing.T) {
-	const inputString = `{
- "artifacts": [{ "id": 1, "path_fragment_id": 10 }],
- "targets": [
-   { "id": 100, "label": "targetX" },
-   { "id": 200, "label": "@bazel_tools//tool_y" }
-],
- "actions": [{
-   "target_id": 100,
-   "action_key": "actionX",
-   "arguments": ["bogus", "command"],
-   "mnemonic" : "x",
-   "output_ids": [1]
- }, {
-   "target_id": 200,
-   "action_key": "y"
- }],
- "path_fragments": [{ "id": 10, "label": "outputX"}]
-}`
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	actualBuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{})
-	if len(actualDepsets) != 0 {
-		t.Errorf("expected 0 depset but found %#v", actualDepsets)
-		return
-	}
-	expectedBuildStatement := &BuildStatement{
-		Command:      "bogus command",
-		OutputPaths:  []string{"outputX"},
-		Mnemonic:     "x",
-		SymlinkPaths: []string{},
-	}
-	buildStatementFound := false
-	for _, actualBuildStatement := range actualBuildStatements {
-		if buildStatementEquals(actualBuildStatement, expectedBuildStatement) == "" {
-			buildStatementFound = true
-			break
-		}
-	}
-	if !buildStatementFound {
-		t.Errorf("expected but missing %#v in %#v build statements", expectedBuildStatement, len(actualBuildStatements))
-		return
-	}
-}
-
-func TestBazelToolsRemovalFromTransitiveInputDepsets(t *testing.T) {
-	const inputString = `{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 10 },
-   { "id": 2, "path_fragment_id": 20 },
-   { "id": 3, "path_fragment_id": 30 }],
- "dep_set_of_files": [{
-   "id": 1111,
-   "transitive_dep_set_ids": [2222]
- }, {
-   "id": 2222,
-   "direct_artifact_ids": [3]
- }, {
-   "id": 3333,
-   "direct_artifact_ids": [3]
- }, {
-   "id": 4444,
-   "transitive_dep_set_ids": [3333]
- }],
- "actions": [{
-   "target_id": 100,
-   "action_key": "x",
-   "input_dep_set_ids": [1111, 4444],
-   "mnemonic": "x",
-   "arguments": ["bogus", "command"],
-   "output_ids": [2],
-   "primary_output_id": 1
- }],
- "path_fragments": [
-   { "id": 10, "label": "input" },
-   { "id": 20, "label": "output" },
-   { "id": 30, "label": "dep", "parent_id": 50 },
-   { "id": 50, "label": "bazel_tools", "parent_id": 60 },
-   { "id": 60, "label": ".."}
- ]
-}`
-	/* depsets
-	    1111    4444
-	     ||      ||
-	    2222    3333
-	      |      |
-	../bazel_tools/dep
-	Note: in dep_set_of_files:
-	  1111 appears BEFORE its dependency,2222 while
-	  4444 appears AFTER its dependency 3333
-	and this test shows that that order doesn't affect empty depset pruning
-	*/
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	actualBuildStatements, actualDepsets, _ := AqueryBuildStatements(data, &metrics.EventHandler{})
-	if len(actualDepsets) != 0 {
-		t.Errorf("expected 0 depsets but found %#v", actualDepsets)
-		return
-	}
-
-	expectedBuildStatement := &BuildStatement{
-		Command:     "bogus command",
-		OutputPaths: []string{"output"},
-		Mnemonic:    "x",
-	}
-	buildStatementFound := false
-	for _, actualBuildStatement := range actualBuildStatements {
-		if buildStatementEquals(actualBuildStatement, expectedBuildStatement) == "" {
-			buildStatementFound = true
-			break
-		}
-	}
-	if !buildStatementFound {
-		t.Errorf("expected but missing %#v in %#v", expectedBuildStatement, actualBuildStatements)
-		return
-	}
-}
-
-func TestMiddlemenAction(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 },
-   { "id": 2, "path_fragment_id": 2 },
-   { "id": 3, "path_fragment_id": 3 },
-   { "id": 4, "path_fragment_id": 4 },
-   { "id": 5, "path_fragment_id": 5 },
-   { "id": 6, "path_fragment_id": 6 }],
- "path_fragments": [
-   { "id": 1, "label": "middleinput_one" },
-   { "id": 2, "label": "middleinput_two" },
-   { "id": 3, "label": "middleman_artifact" },
-   { "id": 4, "label": "maininput_one" },
-   { "id": 5, "label": "maininput_two" },
-   { "id": 6, "label": "output" }],
- "dep_set_of_files": [
-   { "id": 1, "direct_artifact_ids": [1, 2] },
-   { "id": 2, "direct_artifact_ids": [3, 4, 5] }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "Middleman",
-   "arguments": ["touch", "foo"],
-   "input_dep_set_ids": [1],
-   "output_ids": [3],
-   "primary_output_id": 3
- }, {
-   "target_id": 2,
-   "action_key": "y",
-   "mnemonic": "Main action",
-   "arguments": ["touch", "foo"],
-   "input_dep_set_ids": [2],
-   "output_ids": [6],
-   "primary_output_id": 6
- }]
-}`
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	actualBuildStatements, actualDepsets, err := AqueryBuildStatements(data, &metrics.EventHandler{})
-	if err != nil {
-		t.Errorf("Unexpected error %q", err)
-		return
-	}
-	if expected := 2; len(actualBuildStatements) != expected {
-		t.Fatalf("Expected %d build statements, got %d %#v", expected, len(actualBuildStatements), actualBuildStatements)
-		return
-	}
-
-	expectedDepsetFiles := [][]string{
-		{"middleinput_one", "middleinput_two", "maininput_one", "maininput_two"},
-		{"middleinput_one", "middleinput_two"},
-	}
-	assertFlattenedDepsets(t, actualDepsets, expectedDepsetFiles)
-
-	bs := actualBuildStatements[0]
-	if len(bs.InputPaths) > 0 {
-		t.Errorf("Expected main action raw inputs to be empty, but got %q", bs.InputPaths)
-	}
-
-	expectedOutputs := []string{"output"}
-	if !reflect.DeepEqual(bs.OutputPaths, expectedOutputs) {
-		t.Errorf("Expected main action outputs %q, but got %q", expectedOutputs, bs.OutputPaths)
-	}
-
-	expectedFlattenedInputs := []string{"middleinput_one", "middleinput_two", "maininput_one", "maininput_two"}
-	actualFlattenedInputs := flattenDepsets(bs.InputDepsetHashes, actualDepsets)
-
-	if !reflect.DeepEqual(actualFlattenedInputs, expectedFlattenedInputs) {
-		t.Errorf("Expected flattened inputs %v, but got %v", expectedFlattenedInputs, actualFlattenedInputs)
-	}
-
-	bs = actualBuildStatements[1]
-	if bs != nil {
-		t.Errorf("Expected nil action for skipped")
-	}
-}
-
-// Returns the contents of given depsets in concatenated post order.
-func flattenDepsets(depsetHashesToFlatten []string, allDepsets []AqueryDepset) []string {
-	depsetsByHash := map[string]AqueryDepset{}
-	for _, depset := range allDepsets {
-		depsetsByHash[depset.ContentHash] = depset
-	}
-	var result []string
-	for _, depsetId := range depsetHashesToFlatten {
-		result = append(result, flattenDepset(depsetId, depsetsByHash)...)
-	}
-	return result
-}
-
-// Returns the contents of a given depset in post order.
-func flattenDepset(depsetHashToFlatten string, allDepsets map[string]AqueryDepset) []string {
-	depset := allDepsets[depsetHashToFlatten]
-	var result []string
-	for _, depsetId := range depset.TransitiveDepSetHashes {
-		result = append(result, flattenDepset(depsetId, allDepsets)...)
-	}
-	result = append(result, depset.DirectArtifacts...)
-	return result
-}
-
-func assertFlattenedDepsets(t *testing.T, actualDepsets []AqueryDepset, expectedDepsetFiles [][]string) {
-	t.Helper()
-	if len(actualDepsets) != len(expectedDepsetFiles) {
-		t.Errorf("Expected %d depsets, but got %d depsets", len(expectedDepsetFiles), len(actualDepsets))
-	}
-	for i, actualDepset := range actualDepsets {
-		actualFlattenedInputs := flattenDepsets([]string{actualDepset.ContentHash}, actualDepsets)
-		if !reflect.DeepEqual(actualFlattenedInputs, expectedDepsetFiles[i]) {
-			t.Errorf("Expected depset files: %v, but got %v", expectedDepsetFiles[i], actualFlattenedInputs)
-		}
-	}
-}
-
-func TestSimpleSymlink(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 3 },
-   { "id": 2, "path_fragment_id": 5 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "Symlink",
-   "input_dep_set_ids": [1],
-   "output_ids": [2],
-   "primary_output_id": 2
- }],
- "dep_set_of_files": [
-   { "id": 1, "direct_artifact_ids": [1] }],
- "path_fragments": [
-   { "id": 1, "label": "one" },
-   { "id": 2, "label": "file_subdir", "parent_id": 1 },
-   { "id": 3, "label": "file", "parent_id": 2 },
-   { "id": 4, "label": "symlink_subdir", "parent_id": 1 },
-   { "id": 5, "label": "symlink", "parent_id": 4 }]
-}`
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
-
-	if err != nil {
-		t.Errorf("Unexpected error %q", err)
-		return
-	}
-
-	expectedBuildStatements := []*BuildStatement{
-		&BuildStatement{
-			Command: "mkdir -p one/symlink_subdir && " +
-				"rm -f one/symlink_subdir/symlink && " +
-				"ln -sf $PWD/one/file_subdir/file one/symlink_subdir/symlink",
-			InputPaths:   []string{"one/file_subdir/file"},
-			OutputPaths:  []string{"one/symlink_subdir/symlink"},
-			SymlinkPaths: []string{"one/symlink_subdir/symlink"},
-			Mnemonic:     "Symlink",
-		},
-	}
-	assertBuildStatements(t, actual, expectedBuildStatements)
-}
-
-func TestSymlinkQuotesPaths(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 3 },
-   { "id": 2, "path_fragment_id": 5 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "SolibSymlink",
-   "input_dep_set_ids": [1],
-   "output_ids": [2],
-   "primary_output_id": 2
- }],
- "dep_set_of_files": [
-   { "id": 1, "direct_artifact_ids": [1] }],
- "path_fragments": [
-   { "id": 1, "label": "one" },
-   { "id": 2, "label": "file subdir", "parent_id": 1 },
-   { "id": 3, "label": "file", "parent_id": 2 },
-   { "id": 4, "label": "symlink subdir", "parent_id": 1 },
-   { "id": 5, "label": "symlink", "parent_id": 4 }]
-}`
-
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
-	if err != nil {
-		t.Errorf("Unexpected error %q", err)
-		return
-	}
-
-	expectedBuildStatements := []*BuildStatement{
-		&BuildStatement{
-			Command: "mkdir -p 'one/symlink subdir' && " +
-				"rm -f 'one/symlink subdir/symlink' && " +
-				"ln -sf $PWD/'one/file subdir/file' 'one/symlink subdir/symlink'",
-			InputPaths:   []string{"one/file subdir/file"},
-			OutputPaths:  []string{"one/symlink subdir/symlink"},
-			SymlinkPaths: []string{"one/symlink subdir/symlink"},
-			Mnemonic:     "SolibSymlink",
-		},
-	}
-	assertBuildStatements(t, expectedBuildStatements, actual)
-}
-
-func TestSymlinkMultipleInputs(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 },
-   { "id": 2, "path_fragment_id": 2 },
-   { "id": 3, "path_fragment_id": 3 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "action_x",
-   "mnemonic": "Symlink",
-   "input_dep_set_ids": [1],
-   "output_ids": [3],
-   "primary_output_id": 3
- }],
- "dep_set_of_files": [{ "id": 1, "direct_artifact_ids": [1,2] }],
- "path_fragments": [
-   { "id": 1, "label": "file" },
-   { "id": 2, "label": "other_file" },
-   { "id": 3, "label": "symlink" }]
-}`
-
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
-	assertError(t, err, `Expect 1 input and 1 output to symlink action, got: input ["file" "other_file"], output ["symlink"]: [Symlink] []`)
-}
-
-func TestSymlinkMultipleOutputs(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 },
-   { "id": 3, "path_fragment_id": 3 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "Symlink",
-   "input_dep_set_ids": [1],
-   "output_ids": [2,3],
-   "primary_output_id": 2
- }],
- "dep_set_of_files": [
-   { "id": 1, "direct_artifact_ids": [1] }],
- "path_fragments": [
-   { "id": 1, "label": "file" },
-   { "id": 2, "label": "symlink" },
-   { "id": 3,  "label": "other_symlink" }]
-}`
-
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
-	assertError(t, err, "undefined outputId 2: [Symlink] []")
-}
-
-func TestTemplateExpandActionSubstitutions(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [{
-   "id": 1,
-   "path_fragment_id": 1
- }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "TemplateExpand",
-   "configuration_id": 1,
-   "output_ids": [1],
-   "primary_output_id": 1,
-   "execution_platform": "//build/bazel/platforms:linux_x86_64",
-   "template_content": "Test template substitutions: %token1%, %python_binary%",
-   "substitutions": [
-     { "key": "%token1%", "value": "abcd" },
-     { "key": "%python_binary%", "value": "python3" }]
- }],
- "path_fragments": [
-   { "id": 1, "label": "template_file" }]
-}`
-
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
-	if err != nil {
-		t.Errorf("Unexpected error %q", err)
-		return
-	}
-
-	expectedBuildStatements := []*BuildStatement{
-		&BuildStatement{
-			Command: "/bin/bash -c 'echo \"Test template substitutions: abcd, python3\" | sed \"s/\\\\\\\\n/\\\\n/g\" > template_file && " +
-				"chmod a+x template_file'",
-			OutputPaths:  []string{"template_file"},
-			Mnemonic:     "TemplateExpand",
-			SymlinkPaths: []string{},
-		},
-	}
-	assertBuildStatements(t, expectedBuildStatements, actual)
-}
-
-func TestTemplateExpandActionNoOutput(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "TemplateExpand",
-   "configuration_id": 1,
-   "primary_output_id": 1,
-   "execution_platform": "//build/bazel/platforms:linux_x86_64",
-   "templateContent": "Test template substitutions: %token1%, %python_binary%",
-   "substitutions": [
-     { "key": "%token1%", "value": "abcd" },
-     { "key": "%python_binary%", "value": "python3" }]
- }],
- "path_fragments": [
-   { "id": 1, "label": "template_file" }]
-}`
-
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	_, _, err = AqueryBuildStatements(data, &metrics.EventHandler{})
-	assertError(t, err, `Expect 1 output to template expand action, got: output []: [TemplateExpand] []`)
-}
-
-func TestFileWrite(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "FileWrite",
-   "configuration_id": 1,
-   "output_ids": [1],
-   "primary_output_id": 1,
-   "execution_platform": "//build/bazel/platforms:linux_x86_64",
-   "file_contents": "file data\n"
- }],
- "path_fragments": [
-   { "id": 1, "label": "foo.manifest" }]
-}
-`
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
-	if err != nil {
-		t.Errorf("Unexpected error %q", err)
-		return
-	}
-	assertBuildStatements(t, []*BuildStatement{
-		&BuildStatement{
-			OutputPaths:  []string{"foo.manifest"},
-			Mnemonic:     "FileWrite",
-			FileContents: "file data\n",
-			SymlinkPaths: []string{},
-		},
-	}, actual)
-}
-
-func TestSourceSymlinkManifest(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 }],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "SourceSymlinkManifest",
-   "configuration_id": 1,
-   "output_ids": [1],
-   "primary_output_id": 1,
-   "execution_platform": "//build/bazel/platforms:linux_x86_64",
-   "file_contents": "symlink target\n"
- }],
- "path_fragments": [
-   { "id": 1, "label": "foo.manifest" }]
-}
-`
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
-	if err != nil {
-		t.Errorf("Unexpected error %q", err)
-		return
-	}
-	assertBuildStatements(t, []*BuildStatement{
-		&BuildStatement{
-			OutputPaths:  []string{"foo.manifest"},
-			Mnemonic:     "SourceSymlinkManifest",
-			SymlinkPaths: []string{},
-		},
-	}, actual)
-}
-
-func TestUnresolvedSymlink(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 }
- ],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "UnresolvedSymlink",
-   "configuration_id": 1,
-   "output_ids": [1],
-   "primary_output_id": 1,
-   "execution_platform": "//build/bazel/platforms:linux_x86_64",
-   "unresolved_symlink_target": "symlink/target"
- }],
- "path_fragments": [
-   { "id": 1, "label": "path/to/symlink" }
- ]
-}
-`
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
-	if err != nil {
-		t.Errorf("Unexpected error %q", err)
-		return
-	}
-	assertBuildStatements(t, []*BuildStatement{{
-		Command:      "mkdir -p path/to && rm -f path/to/symlink && ln -sf symlink/target path/to/symlink",
-		OutputPaths:  []string{"path/to/symlink"},
-		Mnemonic:     "UnresolvedSymlink",
-		SymlinkPaths: []string{"path/to/symlink"},
-	}}, actual)
-}
-
-func TestUnresolvedSymlinkBazelSandwich(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 }
- ],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "UnresolvedSymlink",
-   "configuration_id": 1,
-   "output_ids": [1],
-   "primary_output_id": 1,
-   "execution_platform": "//build/bazel/platforms:linux_x86_64",
-   "unresolved_symlink_target": "bazel_sandwich:{\"target\":\"target/product/emulator_x86_64/system\"}"
- }],
- "path_fragments": [
-   { "id": 1, "label": "path/to/symlink" }
- ]
-}
-`
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
-	if err != nil {
-		t.Errorf("Unexpected error %q", err)
-		return
-	}
-	assertBuildStatements(t, []*BuildStatement{{
-		Command:      "mkdir -p path/to && rm -f path/to/symlink && ln -sf {DOTDOTS_TO_OUTPUT_ROOT}../../target/product/emulator_x86_64/system path/to/symlink",
-		OutputPaths:  []string{"path/to/symlink"},
-		Mnemonic:     "UnresolvedSymlink",
-		SymlinkPaths: []string{"path/to/symlink"},
-		ImplicitDeps: []string{"target/product/emulator_x86_64/system"},
-	}}, actual)
-}
-
-func TestUnresolvedSymlinkBazelSandwichWithAlternativeDeps(t *testing.T) {
-	const inputString = `
-{
- "artifacts": [
-   { "id": 1, "path_fragment_id": 1 }
- ],
- "actions": [{
-   "target_id": 1,
-   "action_key": "x",
-   "mnemonic": "UnresolvedSymlink",
-   "configuration_id": 1,
-   "output_ids": [1],
-   "primary_output_id": 1,
-   "execution_platform": "//build/bazel/platforms:linux_x86_64",
-   "unresolved_symlink_target": "bazel_sandwich:{\"depend_on_target\":false,\"implicit_deps\":[\"target/product/emulator_x86_64/obj/PACKAGING/systemimage_intermediates/staging_dir.stamp\"],\"target\":\"target/product/emulator_x86_64/system\"}"
- }],
- "path_fragments": [
-   { "id": 1, "label": "path/to/symlink" }
- ]
-}
-`
-	data, err := JsonToActionGraphContainer(inputString)
-	if err != nil {
-		t.Error(err)
-		return
-	}
-	actual, _, err := AqueryBuildStatements(data, &metrics.EventHandler{})
-	if err != nil {
-		t.Errorf("Unexpected error %q", err)
-		return
-	}
-	assertBuildStatements(t, []*BuildStatement{{
-		Command:      "mkdir -p path/to && rm -f path/to/symlink && ln -sf {DOTDOTS_TO_OUTPUT_ROOT}../../target/product/emulator_x86_64/system path/to/symlink",
-		OutputPaths:  []string{"path/to/symlink"},
-		Mnemonic:     "UnresolvedSymlink",
-		SymlinkPaths: []string{"path/to/symlink"},
-		// Note that the target of the symlink, target/product/emulator_x86_64/system, is not listed here
-		ImplicitDeps: []string{"target/product/emulator_x86_64/obj/PACKAGING/systemimage_intermediates/staging_dir.stamp"},
-	}}, actual)
-}
-
-func assertError(t *testing.T, err error, expected string) {
-	t.Helper()
-	if err == nil {
-		t.Errorf("expected error '%s', but got no error", expected)
-	} else if err.Error() != expected {
-		t.Errorf("expected error:\n\t'%s', but got:\n\t'%s'", expected, err.Error())
-	}
-}
-
-// Asserts that the given actual build statements match the given expected build statements.
-// Build statement equivalence is determined using buildStatementEquals.
-func assertBuildStatements(t *testing.T, expected []*BuildStatement, actual []*BuildStatement) {
-	t.Helper()
-	if len(expected) != len(actual) {
-		t.Errorf("expected %d build statements, but got %d,\n expected: %#v,\n actual: %#v",
-			len(expected), len(actual), expected, actual)
-		return
-	}
-	type compareFn = func(i int, j int) bool
-	byCommand := func(slice []*BuildStatement) compareFn {
-		return func(i int, j int) bool {
-			if slice[i] == nil {
-				return false
-			} else if slice[j] == nil {
-				return false
-			}
-			return slice[i].Command < slice[j].Command
-		}
-	}
-	sort.SliceStable(expected, byCommand(expected))
-	sort.SliceStable(actual, byCommand(actual))
-	for i, actualStatement := range actual {
-		expectedStatement := expected[i]
-		if differingField := buildStatementEquals(actualStatement, expectedStatement); differingField != "" {
-			t.Errorf("%s differs\nunexpected build statement %#v.\nexpected: %#v",
-				differingField, actualStatement, expectedStatement)
-			return
-		}
-	}
-}
-
-func buildStatementEquals(first *BuildStatement, second *BuildStatement) string {
-	if (first == nil) != (second == nil) {
-		return "Nil"
-	}
-	if first.Mnemonic != second.Mnemonic {
-		return "Mnemonic"
-	}
-	if first.Command != second.Command {
-		return "Command"
-	}
-	// Ordering is significant for environment variables.
-	if !reflect.DeepEqual(first.Env, second.Env) {
-		return "Env"
-	}
-	// Ordering is irrelevant for input and output paths, so compare sets.
-	if !reflect.DeepEqual(sortedStrings(first.InputPaths), sortedStrings(second.InputPaths)) {
-		return "InputPaths"
-	}
-	if !reflect.DeepEqual(sortedStrings(first.OutputPaths), sortedStrings(second.OutputPaths)) {
-		return "OutputPaths"
-	}
-	if !reflect.DeepEqual(sortedStrings(first.SymlinkPaths), sortedStrings(second.SymlinkPaths)) {
-		return "SymlinkPaths"
-	}
-	if !reflect.DeepEqual(sortedStrings(first.ImplicitDeps), sortedStrings(second.ImplicitDeps)) {
-		return "ImplicitDeps"
-	}
-	if first.Depfile != second.Depfile {
-		return "Depfile"
-	}
-	return ""
-}
-
-func sortedStrings(stringSlice []string) []string {
-	sorted := make([]string, len(stringSlice))
-	copy(sorted, stringSlice)
-	sort.Strings(sorted)
-	return sorted
-}
-
-// Transform the json format to ActionGraphContainer
-func JsonToActionGraphContainer(inputString string) ([]byte, error) {
-	var aqueryProtoResult analysis_v2_proto.ActionGraphContainer
-	err := json.Unmarshal([]byte(inputString), &aqueryProtoResult)
-	if err != nil {
-		return []byte(""), err
-	}
-	data, _ := proto.Marshal(&aqueryProtoResult)
-	return data, err
-}
diff --git a/bazel/bazel_proxy.go b/bazel/bazel_proxy.go
deleted file mode 100644
index 229818d..0000000
--- a/bazel/bazel_proxy.go
+++ /dev/null
@@ -1,237 +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 bazel
-
-import (
-	"bytes"
-	"encoding/gob"
-	"fmt"
-	"net"
-	os_lib "os"
-	"os/exec"
-	"path/filepath"
-	"strings"
-	"time"
-)
-
-// Logs events of ProxyServer.
-type ServerLogger interface {
-	Fatal(v ...interface{})
-	Fatalf(format string, v ...interface{})
-	Println(v ...interface{})
-}
-
-// CmdRequest is a request to the Bazel Proxy server.
-type CmdRequest struct {
-	// Args to the Bazel command.
-	Argv []string
-	// Environment variables to pass to the Bazel invocation. Strings should be of
-	// the form "KEY=VALUE".
-	Env []string
-}
-
-// CmdResponse is a response from the Bazel Proxy server.
-type CmdResponse struct {
-	Stdout      string
-	Stderr      string
-	ErrorString string
-}
-
-// ProxyClient is a client which can issue Bazel commands to the Bazel
-// proxy server. Requests are issued (and responses received) via a unix socket.
-// See ProxyServer for more details.
-type ProxyClient struct {
-	outDir string
-}
-
-// ProxyServer is a server which runs as a background goroutine. Each
-// request to the server describes a Bazel command which the server should run.
-// The server then issues the Bazel command, and returns a response describing
-// the stdout/stderr of the command.
-// Client-server communication is done via a unix socket under the output
-// directory.
-// The server is intended to circumvent sandboxing for subprocesses of the
-// build. The build orchestrator (soong_ui) can launch a server to exist outside
-// of sandboxing, and sandboxed processes (such as soong_build) can issue
-// bazel commands through this socket tunnel. This allows a sandboxed process
-// to issue bazel requests to a bazel that resides outside of sandbox. This
-// is particularly useful to maintain a persistent Bazel server which lives
-// past the duration of a single build.
-// The ProxyServer will only live as long as soong_ui does; the
-// underlying Bazel server will live past the duration of the build.
-type ProxyServer struct {
-	logger          ServerLogger
-	outDir          string
-	workspaceDir    string
-	bazeliskVersion string
-	// The server goroutine will listen on this channel and stop handling requests
-	// once it is written to.
-	done chan struct{}
-}
-
-// NewProxyClient is a constructor for a ProxyClient.
-func NewProxyClient(outDir string) *ProxyClient {
-	return &ProxyClient{
-		outDir: outDir,
-	}
-}
-
-func unixSocketPath(outDir string) string {
-	return filepath.Join(outDir, "bazelsocket.sock")
-}
-
-// IssueCommand issues a request to the Bazel Proxy Server to issue a Bazel
-// request. Returns a response describing the output from the Bazel process
-// (if the Bazel process had an error, then the response will include an error).
-// Returns an error if there was an issue with the connection to the Bazel Proxy
-// server.
-func (b *ProxyClient) IssueCommand(req CmdRequest) (CmdResponse, error) {
-	var resp CmdResponse
-	var err error
-	// Check for connections every 1 second. This is chosen to be a relatively
-	// short timeout, because the proxy server should accept requests quite
-	// quickly.
-	d := net.Dialer{Timeout: 1 * time.Second}
-	var conn net.Conn
-	conn, err = d.Dial("unix", unixSocketPath(b.outDir))
-	if err != nil {
-		return resp, err
-	}
-	defer conn.Close()
-
-	enc := gob.NewEncoder(conn)
-	if err = enc.Encode(req); err != nil {
-		return resp, err
-	}
-	dec := gob.NewDecoder(conn)
-	err = dec.Decode(&resp)
-	return resp, err
-}
-
-// NewProxyServer is a constructor for a ProxyServer.
-func NewProxyServer(logger ServerLogger, outDir string, workspaceDir string, bazeliskVersion string) *ProxyServer {
-	if len(bazeliskVersion) > 0 {
-		logger.Println("** Using Bazelisk for this build, due to env var USE_BAZEL_VERSION=" + bazeliskVersion + " **")
-	}
-
-	return &ProxyServer{
-		logger:          logger,
-		outDir:          outDir,
-		workspaceDir:    workspaceDir,
-		done:            make(chan struct{}),
-		bazeliskVersion: bazeliskVersion,
-	}
-}
-
-func ExecBazel(bazelPath string, workspaceDir string, request CmdRequest) (stdout []byte, stderr []byte, cmdErr error) {
-	bazelCmd := exec.Command(bazelPath, request.Argv...)
-	bazelCmd.Dir = workspaceDir
-	bazelCmd.Env = request.Env
-
-	stderrBuffer := &bytes.Buffer{}
-	bazelCmd.Stderr = stderrBuffer
-
-	if output, err := bazelCmd.Output(); err != nil {
-		cmdErr = fmt.Errorf("bazel command failed: %s\n---command---\n%s\n---env---\n%s\n---stderr---\n%s---",
-			err, bazelCmd, strings.Join(bazelCmd.Env, "\n"), stderrBuffer)
-	} else {
-		stdout = output
-	}
-	stderr = stderrBuffer.Bytes()
-	return
-}
-
-func (b *ProxyServer) handleRequest(conn net.Conn) error {
-	defer conn.Close()
-
-	dec := gob.NewDecoder(conn)
-	var req CmdRequest
-	if err := dec.Decode(&req); err != nil {
-		return fmt.Errorf("Error decoding request: %s", err)
-	}
-
-	if len(b.bazeliskVersion) > 0 {
-		req.Env = append(req.Env, "USE_BAZEL_VERSION="+b.bazeliskVersion)
-	}
-	stdout, stderr, cmdErr := ExecBazel("./build/bazel/bin/bazel", b.workspaceDir, req)
-	errorString := ""
-	if cmdErr != nil {
-		errorString = cmdErr.Error()
-	}
-
-	resp := CmdResponse{string(stdout), string(stderr), errorString}
-	enc := gob.NewEncoder(conn)
-	if err := enc.Encode(&resp); err != nil {
-		return fmt.Errorf("Error encoding response: %s", err)
-	}
-	return nil
-}
-
-func (b *ProxyServer) listenUntilClosed(listener net.Listener) error {
-	for {
-		// Check for connections every 1 second. This is a blocking operation, so
-		// if the server is closed, the goroutine will not fully close until this
-		// deadline is reached. Thus, this deadline is short (but not too short
-		// so that the routine churns).
-		listener.(*net.UnixListener).SetDeadline(time.Now().Add(time.Second))
-		conn, err := listener.Accept()
-
-		select {
-		case <-b.done:
-			return nil
-		default:
-		}
-
-		if err != nil {
-			if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
-				// Timeout is normal and expected while waiting for client to establish
-				// a connection.
-				continue
-			} else {
-				b.logger.Fatalf("Listener error: %s", err)
-			}
-		}
-
-		err = b.handleRequest(conn)
-		if err != nil {
-			b.logger.Fatal(err)
-		}
-	}
-}
-
-// Start initializes the server unix socket and (in a separate goroutine)
-// handles requests on the socket until the server is closed. Returns an error
-// if a failure occurs during initialization. Will log any post-initialization
-// errors to the server's logger.
-func (b *ProxyServer) Start() error {
-	unixSocketAddr := unixSocketPath(b.outDir)
-	if err := os_lib.RemoveAll(unixSocketAddr); err != nil {
-		return fmt.Errorf("couldn't remove socket '%s': %s", unixSocketAddr, err)
-	}
-	listener, err := net.Listen("unix", unixSocketAddr)
-
-	if err != nil {
-		return fmt.Errorf("error listening on socket '%s': %s", unixSocketAddr, err)
-	}
-
-	go b.listenUntilClosed(listener)
-	return nil
-}
-
-// Close shuts down the server. This will stop the server from listening for
-// additional requests.
-func (b *ProxyServer) Close() {
-	b.done <- struct{}{}
-}
diff --git a/bazel/constants.go b/bazel/constants.go
deleted file mode 100644
index b10f256..0000000
--- a/bazel/constants.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package bazel
-
-type RunName string
-
-// Below is a list bazel execution run names used through out the
-// Platform Build systems. Each run name represents an unique key
-// to query the bazel metrics.
-const (
-	// Perform a bazel build of the phony root to generate symlink forests
-	// for dependencies of the bazel build.
-	BazelBuildPhonyRootRunName = RunName("bazel-build-phony-root")
-
-	// Perform aquery of the bazel build root to retrieve action information.
-	AqueryBuildRootRunName = RunName("aquery-buildroot")
-
-	// Perform cquery of the Bazel build root and its dependencies.
-	CqueryBuildRootRunName = RunName("cquery-buildroot")
-
-	// Run bazel as a ninja executer
-	BazelNinjaExecRunName = RunName("bazel-ninja-exec")
-
-	SoongInjectionDirName = "soong_injection"
-
-	GeneratedBazelFileWarning = "# GENERATED FOR BAZEL FROM SOONG. DO NOT EDIT."
-)
-
-// String returns the name of the run.
-func (c RunName) String() string {
-	return string(c)
-}
diff --git a/bazel/cquery/Android.bp b/bazel/cquery/Android.bp
deleted file mode 100644
index 74f7721..0000000
--- a/bazel/cquery/Android.bp
+++ /dev/null
@@ -1,17 +0,0 @@
-package {
-    default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-bootstrap_go_package {
-    name: "soong-cquery",
-    pkgPath: "android/soong/bazel/cquery",
-    srcs: [
-        "request_type.go",
-    ],
-    pluginFor: [
-        "soong_build",
-    ],
-    testSrcs: [
-        "request_type_test.go",
-    ],
-}
diff --git a/bazel/cquery/request_type.go b/bazel/cquery/request_type.go
deleted file mode 100644
index 791c6bc..0000000
--- a/bazel/cquery/request_type.go
+++ /dev/null
@@ -1,426 +0,0 @@
-package cquery
-
-import (
-	"encoding/json"
-	"fmt"
-	"strings"
-)
-
-var (
-	GetOutputFiles      = &getOutputFilesRequestType{}
-	GetCcInfo           = &getCcInfoType{}
-	GetApexInfo         = &getApexInfoType{}
-	GetCcUnstrippedInfo = &getCcUnstrippedInfoType{}
-	GetPrebuiltFileInfo = &getPrebuiltFileInfo{}
-)
-
-type CcAndroidMkInfo struct {
-	LocalStaticLibs      []string
-	LocalWholeStaticLibs []string
-	LocalSharedLibs      []string
-}
-
-type CcInfo struct {
-	CcAndroidMkInfo
-	OutputFiles          []string
-	CcObjectFiles        []string
-	CcSharedLibraryFiles []string
-	CcStaticLibraryFiles []string
-	Includes             []string
-	SystemIncludes       []string
-	Headers              []string
-	// Archives owned by the current target (not by its dependencies). These will
-	// be a subset of OutputFiles. (or static libraries, this will be equal to OutputFiles,
-	// but general cc_library will also have dynamic libraries in output files).
-	RootStaticArchives []string
-	// Dynamic libraries (.so files) created by the current target. These will
-	// be a subset of OutputFiles. (or shared libraries, this will be equal to OutputFiles,
-	// but general cc_library will also have dynamic libraries in output files).
-	RootDynamicLibraries []string
-	TidyFiles            []string
-	TocFile              string
-	UnstrippedOutput     string
-	AbiDiffFiles         []string
-}
-
-type getOutputFilesRequestType struct{}
-
-// Name returns a string name for this request type. Such request type names must be unique,
-// and must only consist of alphanumeric characters.
-func (g getOutputFilesRequestType) Name() string {
-	return "getOutputFiles"
-}
-
-// StarlarkFunctionBody returns a starlark function body to process this request type.
-// The returned string is the body of a Starlark function which obtains
-// all request-relevant information about a target and returns a string containing
-// this information.
-// The function should have the following properties:
-//   - The arguments are `target` (a configured target) and `id_string` (the label + configuration).
-//   - The return value must be a string.
-//   - The function body should not be indented outside of its own scope.
-func (g getOutputFilesRequestType) StarlarkFunctionBody() string {
-	return "return ', '.join([f.path for f in target.files.to_list()])"
-}
-
-// ParseResult returns a value obtained by parsing the result of the request's Starlark function.
-// The given rawString must correspond to the string output which was created by evaluating the
-// Starlark given in StarlarkFunctionBody.
-func (g getOutputFilesRequestType) ParseResult(rawString string) []string {
-	return splitOrEmpty(rawString, ", ")
-}
-
-type getCcInfoType struct{}
-
-// Name returns a string name for this request type. Such request type names must be unique,
-// and must only consist of alphanumeric characters.
-func (g getCcInfoType) Name() string {
-	return "getCcInfo"
-}
-
-// StarlarkFunctionBody returns a starlark function body to process this request type.
-// The returned string is the body of a Starlark function which obtains
-// all request-relevant information about a target and returns a string containing
-// this information.
-// The function should have the following properties:
-//   - The arguments are `target` (a configured target) and `id_string` (the label + configuration).
-//   - The return value must be a string.
-//   - The function body should not be indented outside of its own scope.
-func (g getCcInfoType) StarlarkFunctionBody() string {
-	return `
-outputFiles = [f.path for f in target.files.to_list()]
-p = providers(target)
-cc_info = p.get("CcInfo")
-if not cc_info:
-  fail("%s did not provide CcInfo" % id_string)
-
-includes = cc_info.compilation_context.includes.to_list()
-system_includes = cc_info.compilation_context.system_includes.to_list()
-headers = [f.path for f in cc_info.compilation_context.headers.to_list()]
-
-ccObjectFiles = []
-staticLibraries = []
-rootStaticArchives = []
-linker_inputs = cc_info.linking_context.linker_inputs.to_list()
-
-static_info_tag = "//build/bazel/rules/cc:cc_library_static.bzl%CcStaticLibraryInfo"
-if static_info_tag in p:
-  static_info = p[static_info_tag]
-  ccObjectFiles = [f.path for f in static_info.objects]
-  rootStaticArchives = [static_info.root_static_archive.path]
-else:
-  for linker_input in linker_inputs:
-    for library in linker_input.libraries:
-      for object in library.objects:
-        ccObjectFiles += [object.path]
-      if library.static_library:
-        staticLibraries.append(library.static_library.path)
-        if linker_input.owner == target.label:
-          rootStaticArchives.append(library.static_library.path)
-
-sharedLibraries = []
-rootSharedLibraries = []
-
-shared_info_tag = "//build/bazel/rules/cc:cc_library_shared.bzl%CcSharedLibraryOutputInfo"
-stubs_tag = "//build/bazel/rules/cc:cc_stub_library.bzl%CcStubInfo"
-unstripped_tag = "//build/bazel/rules/cc:stripped_cc_common.bzl%CcUnstrippedInfo"
-unstripped = ""
-
-if shared_info_tag in p:
-  shared_info = p[shared_info_tag]
-  path = shared_info.output_file.path
-  sharedLibraries.append(path)
-  rootSharedLibraries += [path]
-  unstripped = path
-  if unstripped_tag in p:
-    unstripped = p[unstripped_tag].unstripped.path
-elif stubs_tag in p:
-  rootSharedLibraries.extend([f.path for f in target.files.to_list()])
-else:
-  for linker_input in linker_inputs:
-    for library in linker_input.libraries:
-      if library.dynamic_library:
-        path = library.dynamic_library.path
-        sharedLibraries.append(path)
-        if linker_input.owner == target.label:
-          rootSharedLibraries.append(path)
-
-toc_file = ""
-toc_file_tag = "//build/bazel/rules/cc:generate_toc.bzl%CcTocInfo"
-if toc_file_tag in p:
-  toc_file = p[toc_file_tag].toc.path
-else:
-  # NOTE: It's OK if there's no ToC, as Soong just uses it for optimization
-  pass
-
-tidy_files = []
-clang_tidy_info = p.get("//build/bazel/rules/cc:clang_tidy.bzl%ClangTidyInfo")
-if clang_tidy_info:
-  tidy_files = [v.path for v in clang_tidy_info.transitive_tidy_files.to_list()]
-
-abi_diff_files = []
-abi_diff_info = p.get("//build/bazel/rules/abi:abi_dump.bzl%AbiDiffInfo")
-if abi_diff_info:
-  abi_diff_files = [f.path for f in abi_diff_info.diff_files.to_list()]
-
-local_static_libs = []
-local_whole_static_libs = []
-local_shared_libs = []
-androidmk_tag = "//build/bazel/rules/cc:cc_library_common.bzl%CcAndroidMkInfo"
-if androidmk_tag in p:
-    androidmk_info = p[androidmk_tag]
-    local_static_libs = androidmk_info.local_static_libs
-    local_whole_static_libs = androidmk_info.local_whole_static_libs
-    local_shared_libs = androidmk_info.local_shared_libs
-
-return json.encode({
-    "OutputFiles": outputFiles,
-    "CcObjectFiles": ccObjectFiles,
-    "CcSharedLibraryFiles": sharedLibraries,
-    "CcStaticLibraryFiles": staticLibraries,
-    "Includes": includes,
-    "SystemIncludes": system_includes,
-    "Headers": headers,
-    "RootStaticArchives": rootStaticArchives,
-    "RootDynamicLibraries": rootSharedLibraries,
-    "TidyFiles": [t for t in tidy_files],
-    "TocFile": toc_file,
-    "UnstrippedOutput": unstripped,
-    "AbiDiffFiles": abi_diff_files,
-    "LocalStaticLibs": [l for l in local_static_libs],
-    "LocalWholeStaticLibs": [l for l in local_whole_static_libs],
-    "LocalSharedLibs": [l for l in local_shared_libs],
-})`
-
-}
-
-// ParseResult returns a value obtained by parsing the result of the request's Starlark function.
-// The given rawString must correspond to the string output which was created by evaluating the
-// Starlark given in StarlarkFunctionBody.
-func (g getCcInfoType) ParseResult(rawString string) (CcInfo, error) {
-	var ccInfo CcInfo
-	if err := parseJson(rawString, &ccInfo); err != nil {
-		return ccInfo, err
-	}
-	return ccInfo, nil
-}
-
-// Query Bazel for the artifacts generated by the apex modules.
-type getApexInfoType struct{}
-
-// Name returns a string name for this request type. Such request type names must be unique,
-// and must only consist of alphanumeric characters.
-func (g getApexInfoType) Name() string {
-	return "getApexInfo"
-}
-
-// StarlarkFunctionBody returns a starlark function body to process this request type.
-// The returned string is the body of a Starlark function which obtains
-// all request-relevant information about a target and returns a string containing
-// this information. The function should have the following properties:
-//   - The arguments are `target` (a configured target) and `id_string` (the label + configuration).
-//   - The return value must be a string.
-//   - The function body should not be indented outside of its own scope.
-func (g getApexInfoType) StarlarkFunctionBody() string {
-	return `
-info = providers(target).get("//build/bazel/rules/apex:apex_info.bzl%ApexInfo")
-if not info:
-  fail("%s did not provide ApexInfo" % id_string)
-bundle_key_info = info.bundle_key_info
-container_key_info = info.container_key_info
-
-signed_compressed_output = "" # no .capex if the apex is not compressible, cannot be None as it needs to be json encoded.
-if info.signed_compressed_output:
-    signed_compressed_output = info.signed_compressed_output.path
-
-mk_info = providers(target).get("//build/bazel/rules/apex:apex_info.bzl%ApexMkInfo")
-if not mk_info:
-  fail("%s did not provide ApexMkInfo" % id_string)
-
-tidy_files = []
-clang_tidy_info = providers(target).get("//build/bazel/rules/cc:clang_tidy.bzl%ClangTidyInfo")
-if clang_tidy_info:
-    tidy_files = [v.path for v in clang_tidy_info.transitive_tidy_files.to_list()]
-
-return json.encode({
-    "signed_output": info.signed_output.path,
-    "signed_compressed_output": signed_compressed_output,
-    "unsigned_output": info.unsigned_output.path,
-    "provides_native_libs": [str(lib) for lib in info.provides_native_libs],
-    "requires_native_libs": [str(lib) for lib in info.requires_native_libs],
-    "bundle_key_info": [bundle_key_info.public_key.path, bundle_key_info.private_key.path],
-    "container_key_info": [container_key_info.pem.path, container_key_info.pk8.path, container_key_info.key_name],
-    "package_name": info.package_name,
-    "symbols_used_by_apex": info.symbols_used_by_apex.path,
-    "java_symbols_used_by_apex": info.java_symbols_used_by_apex.path,
-    "backing_libs": info.backing_libs.path,
-    "bundle_file": info.base_with_config_zip.path,
-    "installed_files": info.installed_files.path,
-    "make_modules_to_install": mk_info.make_modules_to_install,
-    "files_info": mk_info.files_info,
-    "tidy_files": [t for t in tidy_files],
-})`
-}
-
-type ApexInfo struct {
-	// From the ApexInfo provider
-	SignedOutput           string   `json:"signed_output"`
-	SignedCompressedOutput string   `json:"signed_compressed_output"`
-	UnsignedOutput         string   `json:"unsigned_output"`
-	ProvidesLibs           []string `json:"provides_native_libs"`
-	RequiresLibs           []string `json:"requires_native_libs"`
-	BundleKeyInfo          []string `json:"bundle_key_info"`
-	ContainerKeyInfo       []string `json:"container_key_info"`
-	PackageName            string   `json:"package_name"`
-	SymbolsUsedByApex      string   `json:"symbols_used_by_apex"`
-	JavaSymbolsUsedByApex  string   `json:"java_symbols_used_by_apex"`
-	BackingLibs            string   `json:"backing_libs"`
-	BundleFile             string   `json:"bundle_file"`
-	InstalledFiles         string   `json:"installed_files"`
-	TidyFiles              []string `json:"tidy_files"`
-
-	// From the ApexMkInfo provider
-	MakeModulesToInstall []string            `json:"make_modules_to_install"`
-	PayloadFilesInfo     []map[string]string `json:"files_info"`
-}
-
-// ParseResult returns a value obtained by parsing the result of the request's Starlark function.
-// The given rawString must correspond to the string output which was created by evaluating the
-// Starlark given in StarlarkFunctionBody.
-func (g getApexInfoType) ParseResult(rawString string) (ApexInfo, error) {
-	var info ApexInfo
-	err := parseJson(rawString, &info)
-	return info, err
-}
-
-// getCcUnstrippedInfoType implements cqueryRequest interface. It handles the
-// interaction with `bazel cquery` to retrieve CcUnstrippedInfo provided
-// by the` cc_binary` and `cc_shared_library` rules.
-type getCcUnstrippedInfoType struct{}
-
-func (g getCcUnstrippedInfoType) Name() string {
-	return "getCcUnstrippedInfo"
-}
-
-func (g getCcUnstrippedInfoType) StarlarkFunctionBody() string {
-	return `
-p = providers(target)
-output_path = target.files.to_list()[0].path
-
-unstripped = output_path
-unstripped_tag = "//build/bazel/rules/cc:stripped_cc_common.bzl%CcUnstrippedInfo"
-if unstripped_tag in p:
-    unstripped_info = p[unstripped_tag]
-    unstripped = unstripped_info.unstripped[0].files.to_list()[0].path
-
-local_static_libs = []
-local_whole_static_libs = []
-local_shared_libs = []
-androidmk_tag = "//build/bazel/rules/cc:cc_library_common.bzl%CcAndroidMkInfo"
-if androidmk_tag in p:
-    androidmk_info = p[androidmk_tag]
-    local_static_libs = androidmk_info.local_static_libs
-    local_whole_static_libs = androidmk_info.local_whole_static_libs
-    local_shared_libs = androidmk_info.local_shared_libs
-
-tidy_files = []
-clang_tidy_info = p.get("//build/bazel/rules/cc:clang_tidy.bzl%ClangTidyInfo")
-if clang_tidy_info:
-    tidy_files = [v.path for v in clang_tidy_info.transitive_tidy_files.to_list()]
-
-return json.encode({
-    "OutputFile":  output_path,
-    "UnstrippedOutput": unstripped,
-    "LocalStaticLibs": [l for l in local_static_libs],
-    "LocalWholeStaticLibs": [l for l in local_whole_static_libs],
-    "LocalSharedLibs": [l for l in local_shared_libs],
-    "TidyFiles": [t for t in tidy_files],
-})
-`
-}
-
-// ParseResult returns a value obtained by parsing the result of the request's Starlark function.
-// The given rawString must correspond to the string output which was created by evaluating the
-// Starlark given in StarlarkFunctionBody.
-func (g getCcUnstrippedInfoType) ParseResult(rawString string) (CcUnstrippedInfo, error) {
-	var info CcUnstrippedInfo
-	err := parseJson(rawString, &info)
-	return info, err
-}
-
-type CcUnstrippedInfo struct {
-	CcAndroidMkInfo
-	OutputFile       string
-	UnstrippedOutput string
-	TidyFiles        []string
-}
-
-// splitOrEmpty is a modification of strings.Split() that returns an empty list
-// if the given string is empty.
-func splitOrEmpty(s string, sep string) []string {
-	if len(s) < 1 {
-		return []string{}
-	} else {
-		return strings.Split(s, sep)
-	}
-}
-
-// parseJson decodes json string into the fields of the receiver.
-// Unknown attribute name causes panic.
-func parseJson(jsonString string, info interface{}) error {
-	decoder := json.NewDecoder(strings.NewReader(jsonString))
-	decoder.DisallowUnknownFields() //useful to detect typos, e.g. in unit tests
-	err := decoder.Decode(info)
-	if err != nil {
-		return fmt.Errorf("cannot parse cquery result '%s': %s", jsonString, err)
-	}
-	return nil
-}
-
-type getPrebuiltFileInfo struct{}
-
-// Name returns a string name for this request type. Such request type names must be unique,
-// and must only consist of alphanumeric characters.
-func (g getPrebuiltFileInfo) Name() string {
-	return "getPrebuiltFileInfo"
-}
-
-// StarlarkFunctionBody returns a starlark function body to process this request type.
-// The returned string is the body of a Starlark function which obtains
-// all request-relevant information about a target and returns a string containing
-// this information.
-// The function should have the following properties:
-//   - The arguments are `target` (a configured target) and `id_string` (the label + configuration).
-//   - The return value must be a string.
-//   - The function body should not be indented outside of its own scope.
-func (g getPrebuiltFileInfo) StarlarkFunctionBody() string {
-	return `
-p = providers(target)
-prebuilt_file_info = p.get("//build/bazel/rules:prebuilt_file.bzl%PrebuiltFileInfo")
-if not prebuilt_file_info:
-  fail("%s did not provide PrebuiltFileInfo" % id_string)
-
-return json.encode({
-	"Src": prebuilt_file_info.src.path,
-	"Dir": prebuilt_file_info.dir,
-	"Filename": prebuilt_file_info.filename,
-	"Installable": prebuilt_file_info.installable,
-})`
-}
-
-type PrebuiltFileInfo struct {
-	// TODO: b/207489266 - Fully support all properties in prebuilt_file
-	Src         string
-	Dir         string
-	Filename    string
-	Installable bool
-}
-
-// ParseResult returns a value obtained by parsing the result of the request's Starlark function.
-// The given rawString must correspond to the string output which was created by evaluating the
-// Starlark given in StarlarkFunctionBody.
-func (g getPrebuiltFileInfo) ParseResult(rawString string) (PrebuiltFileInfo, error) {
-	var info PrebuiltFileInfo
-	err := parseJson(rawString, &info)
-	return info, err
-}
diff --git a/bazel/cquery/request_type_test.go b/bazel/cquery/request_type_test.go
deleted file mode 100644
index e772bb7..0000000
--- a/bazel/cquery/request_type_test.go
+++ /dev/null
@@ -1,281 +0,0 @@
-package cquery
-
-import (
-	"encoding/json"
-	"reflect"
-	"strings"
-	"testing"
-)
-
-func TestGetOutputFilesParseResults(t *testing.T) {
-	t.Parallel()
-	testCases := []struct {
-		description    string
-		input          string
-		expectedOutput []string
-	}{
-		{
-			description:    "no result",
-			input:          "",
-			expectedOutput: []string{},
-		},
-		{
-			description:    "one result",
-			input:          "test",
-			expectedOutput: []string{"test"},
-		},
-		{
-			description:    "splits on comma with space",
-			input:          "foo, bar",
-			expectedOutput: []string{"foo", "bar"},
-		},
-	}
-	for _, tc := range testCases {
-		t.Run(tc.description, func(t *testing.T) {
-			actualOutput := GetOutputFiles.ParseResult(tc.input)
-			if !reflect.DeepEqual(tc.expectedOutput, actualOutput) {
-				t.Errorf("expected %#v != actual %#v", tc.expectedOutput, actualOutput)
-			}
-		})
-	}
-}
-
-func TestGetCcInfoParseResults(t *testing.T) {
-	t.Parallel()
-	testCases := []struct {
-		description    string
-		inputCcInfo    CcInfo
-		expectedOutput CcInfo
-	}{
-		{
-			description:    "no result",
-			inputCcInfo:    CcInfo{},
-			expectedOutput: CcInfo{},
-		},
-		{
-			description: "all items set",
-			inputCcInfo: CcInfo{
-				OutputFiles:          []string{"out1", "out2"},
-				CcObjectFiles:        []string{"object1", "object2"},
-				CcSharedLibraryFiles: []string{"shared_lib1", "shared_lib2"},
-				CcStaticLibraryFiles: []string{"static_lib1", "static_lib2"},
-				Includes:             []string{".", "dir/subdir"},
-				SystemIncludes:       []string{"system/dir", "system/other/dir"},
-				Headers:              []string{"dir/subdir/hdr.h"},
-				RootStaticArchives:   []string{"rootstaticarchive1"},
-				RootDynamicLibraries: []string{"rootdynamiclibrary1"},
-				TocFile:              "lib.so.toc",
-			},
-			expectedOutput: CcInfo{
-				OutputFiles:          []string{"out1", "out2"},
-				CcObjectFiles:        []string{"object1", "object2"},
-				CcSharedLibraryFiles: []string{"shared_lib1", "shared_lib2"},
-				CcStaticLibraryFiles: []string{"static_lib1", "static_lib2"},
-				Includes:             []string{".", "dir/subdir"},
-				SystemIncludes:       []string{"system/dir", "system/other/dir"},
-				Headers:              []string{"dir/subdir/hdr.h"},
-				RootStaticArchives:   []string{"rootstaticarchive1"},
-				RootDynamicLibraries: []string{"rootdynamiclibrary1"},
-				TocFile:              "lib.so.toc",
-			},
-		},
-	}
-	for _, tc := range testCases {
-		t.Run(tc.description, func(t *testing.T) {
-			jsonInput, _ := json.Marshal(tc.inputCcInfo)
-			actualOutput, err := GetCcInfo.ParseResult(string(jsonInput))
-			if err != nil {
-				t.Errorf("error parsing result: %q", err)
-			} else if err == nil && !reflect.DeepEqual(tc.expectedOutput, actualOutput) {
-				t.Errorf("expected %#v\n!= actual %#v", tc.expectedOutput, actualOutput)
-			}
-		})
-	}
-}
-
-func TestGetCcInfoParseResultsError(t *testing.T) {
-	t.Parallel()
-	testCases := []struct {
-		description   string
-		input         string
-		expectedError string
-	}{
-		{
-			description:   "not json",
-			input:         ``,
-			expectedError: `cannot parse cquery result '': EOF`,
-		},
-		{
-			description: "invalid field",
-			input: `{
-	"toc_file": "dir/file.so.toc"
-}`,
-			expectedError: `json: unknown field "toc_file"`,
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.description, func(t *testing.T) {
-			_, err := GetCcInfo.ParseResult(tc.input)
-			if !strings.Contains(err.Error(), tc.expectedError) {
-				t.Errorf("expected string %q in error message, got %q", tc.expectedError, err)
-			}
-		})
-	}
-}
-
-func TestGetApexInfoParseResults(t *testing.T) {
-	t.Parallel()
-	testCases := []struct {
-		description    string
-		input          string
-		expectedOutput ApexInfo
-	}{
-		{
-			description:    "no result",
-			input:          "{}",
-			expectedOutput: ApexInfo{},
-		},
-		{
-			description: "one result",
-			input: `{
-	"signed_output":"my.apex",
-	"unsigned_output":"my.apex.unsigned",
-	"requires_native_libs":["//bionic/libc:libc","//bionic/libdl:libdl"],
-	"bundle_key_info":["foo.pem", "foo.privkey"],
-	"container_key_info":["foo.x509.pem", "foo.pk8", "foo"],
-	"package_name":"package.name",
-	"symbols_used_by_apex": "path/to/my.apex_using.txt",
-	"backing_libs":"path/to/backing.txt",
-	"bundle_file": "dir/bundlefile.zip",
-	"installed_files":"path/to/installed-files.txt",
-	"provides_native_libs":[],
-	"make_modules_to_install": ["foo","bar"]
-}`,
-			expectedOutput: ApexInfo{
-				// ApexInfo
-				SignedOutput:      "my.apex",
-				UnsignedOutput:    "my.apex.unsigned",
-				RequiresLibs:      []string{"//bionic/libc:libc", "//bionic/libdl:libdl"},
-				ProvidesLibs:      []string{},
-				BundleKeyInfo:     []string{"foo.pem", "foo.privkey"},
-				ContainerKeyInfo:  []string{"foo.x509.pem", "foo.pk8", "foo"},
-				PackageName:       "package.name",
-				SymbolsUsedByApex: "path/to/my.apex_using.txt",
-				BackingLibs:       "path/to/backing.txt",
-				BundleFile:        "dir/bundlefile.zip",
-				InstalledFiles:    "path/to/installed-files.txt",
-
-				// ApexMkInfo
-				MakeModulesToInstall: []string{"foo", "bar"},
-			},
-		},
-	}
-	for _, tc := range testCases {
-		t.Run(tc.description, func(t *testing.T) {
-			actualOutput, err := GetApexInfo.ParseResult(tc.input)
-			if err != nil {
-				t.Errorf("Unexpected error %q", err)
-			}
-			if !reflect.DeepEqual(tc.expectedOutput, actualOutput) {
-				t.Errorf("expected %#v != actual %#v", tc.expectedOutput, actualOutput)
-			}
-		})
-	}
-}
-
-func TestGetApexInfoParseResultsError(t *testing.T) {
-	t.Parallel()
-	testCases := []struct {
-		description   string
-		input         string
-		expectedError string
-	}{
-		{
-			description:   "not json",
-			input:         ``,
-			expectedError: `cannot parse cquery result '': EOF`,
-		},
-		{
-			description: "invalid field",
-			input: `{
-	"fake_field": "path/to/file"
-}`,
-			expectedError: `json: unknown field "fake_field"`,
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.description, func(t *testing.T) {
-			_, err := GetApexInfo.ParseResult(tc.input)
-			if !strings.Contains(err.Error(), tc.expectedError) {
-				t.Errorf("expected string %q in error message, got %q", tc.expectedError, err)
-			}
-		})
-	}
-}
-
-func TestGetCcUnstrippedParseResults(t *testing.T) {
-	t.Parallel()
-	testCases := []struct {
-		description    string
-		input          string
-		expectedOutput CcUnstrippedInfo
-	}{
-		{
-			description:    "no result",
-			input:          "{}",
-			expectedOutput: CcUnstrippedInfo{},
-		},
-		{
-			description: "one result",
-			input:       `{"OutputFile":"myapp", "UnstrippedOutput":"myapp_unstripped"}`,
-			expectedOutput: CcUnstrippedInfo{
-				OutputFile:       "myapp",
-				UnstrippedOutput: "myapp_unstripped",
-			},
-		},
-	}
-	for _, tc := range testCases {
-		t.Run(tc.description, func(t *testing.T) {
-			actualOutput, err := GetCcUnstrippedInfo.ParseResult(tc.input)
-			if err != nil {
-				t.Errorf("Unexpected error %q", err)
-			}
-			if !reflect.DeepEqual(tc.expectedOutput, actualOutput) {
-				t.Errorf("expected %#v != actual %#v", tc.expectedOutput, actualOutput)
-			}
-		})
-	}
-}
-
-func TestGetCcUnstrippedParseResultsErrors(t *testing.T) {
-	t.Parallel()
-	testCases := []struct {
-		description   string
-		input         string
-		expectedError string
-	}{
-		{
-			description:   "not json",
-			input:         ``,
-			expectedError: `cannot parse cquery result '': EOF`,
-		},
-		{
-			description: "invalid field",
-			input: `{
-	"fake_field": "path/to/file"
-}`,
-			expectedError: `json: unknown field "fake_field"`,
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.description, func(t *testing.T) {
-			_, err := GetCcUnstrippedInfo.ParseResult(tc.input)
-			if !strings.Contains(err.Error(), tc.expectedError) {
-				t.Errorf("expected string %q in error message, got %q", tc.expectedError, err)
-			}
-		})
-	}
-}
diff --git a/bin/Android.bp b/bin/Android.bp
new file mode 100644
index 0000000..4d6d911
--- /dev/null
+++ b/bin/Android.bp
@@ -0,0 +1,26 @@
+// Copyright 2024 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+    default_team: "trendy_team_build",
+}
+
+filegroup {
+    name: "run_tool_with_logging_script",
+    visibility: [
+        "//build/soong/tests:__subpackages__",
+    ],
+    srcs: ["run_tool_with_logging"],
+}
diff --git a/bin/afind b/bin/afind
new file mode 100755
index 0000000..f5b8319
--- /dev/null
+++ b/bin/afind
@@ -0,0 +1,31 @@
+#!/bin/bash
+
+# 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.
+
+# prevent glob expansion in this script
+set -f
+
+dir=${1:-.}
+
+shift
+
+args=( $@ )
+if [[ ${#args[@]} -eq 0 ]] ; then
+    args=( -print )
+fi
+
+find "$dir" -name .repo -prune -o -name .git -prune -o -name out -prune -o ${args[@]}
+
+exit $?
diff --git a/bin/allmod b/bin/allmod
new file mode 100755
index 0000000..f7d25e5
--- /dev/null
+++ b/bin/allmod
@@ -0,0 +1,21 @@
+#!/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.
+
+# List all modules for the current device, as cached in all_modules.txt. If any build change is
+# made and it should be reflected in the output, you should run 'refreshmod' first.
+
+cat $ANDROID_PRODUCT_OUT/all_modules.txt 2>/dev/null
+
diff --git a/bin/aninja b/bin/aninja
new file mode 100755
index 0000000..5acb968
--- /dev/null
+++ b/bin/aninja
@@ -0,0 +1,38 @@
+#!/bin/bash -e
+
+# 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.
+
+# Common script utilities
+source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../../make/shell_utils.sh
+
+require_top
+require_lunch
+
+case $(uname -s) in
+    Darwin)
+        host_arch=darwin-x86
+        ;;
+    Linux)
+        host_arch=linux-x86
+        ;;
+    *)
+        >&2 echo Unknown host $(uname -s)
+        exit 1
+        ;;
+esac
+
+cd $(gettop)
+prebuilts/build-tools/${host_arch}/bin/ninja -f out/combined-${TARGET_PRODUCT}.ninja "$@"
+
diff --git a/bin/build-flag b/bin/build-flag
new file mode 100755
index 0000000..dc404bc
--- /dev/null
+++ b/bin/build-flag
@@ -0,0 +1,28 @@
+#!/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.
+
+source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../../make/shell_utils.sh
+require_top
+
+# Save the current PWD for use in soong_ui
+export ORIGINAL_PWD=${PWD}
+export TOP=$(gettop)
+source ${TOP}/build/soong/scripts/microfactory.bash
+
+soong_build_go build-flag android/soong/cmd/release_config/build_flag
+
+cd ${TOP}
+exec "$(getoutdir)/build-flag" "$@"
diff --git a/bin/build-flag-declarations b/bin/build-flag-declarations
new file mode 100755
index 0000000..222f083
--- /dev/null
+++ b/bin/build-flag-declarations
@@ -0,0 +1,28 @@
+#!/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.
+
+source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../../make/shell_utils.sh
+require_top
+
+# Save the current PWD for use in soong_ui
+export ORIGINAL_PWD=${PWD}
+export TOP=$(gettop)
+source ${TOP}/build/soong/scripts/microfactory.bash
+
+soong_build_go build-flag-declarations android/soong/cmd/release_config/build_flag_declarations
+
+cd ${TOP}
+exec "$(getoutdir)/build-flag-declarations" "$@"
diff --git a/bin/cgrep b/bin/cgrep
new file mode 100755
index 0000000..6c9130c
--- /dev/null
+++ b/bin/cgrep
@@ -0,0 +1,24 @@
+#!/bin/bash
+
+# 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.
+
+find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f \( \
+        -name '*.c' \
+        -o -name '*.cc' \
+        -o -name '*.cpp' \
+        -o -name '*.h' \
+        -o -name '*.hpp' \
+    \) -exec grep --color -n "$@" {} +
+exit $?
diff --git a/bin/core b/bin/core
new file mode 100755
index 0000000..01dbd13
--- /dev/null
+++ b/bin/core
@@ -0,0 +1,48 @@
+#!/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.
+
+# core - send SIGV and pull the core for process
+# $1 = PID of process (e.g., $(pid mediaserver))
+#
+# NOTE: coredump_setup must be called once per boot for core dumps to be
+#       enabled globally.
+
+set -e
+
+PID=$1;
+
+if [ -z "$PID" ]; then
+    printf "Expecting a PID!\n";
+    exit 1
+fi;
+
+CORENAME=core.$PID;
+COREPATH=/cores/$CORENAME;
+SIG=SEGV;
+
+coredump_enable $1;
+
+done=0;
+while [ $(adb shell "[ -d /proc/$PID ] && echo -n yes") ]; do
+    printf "\tSending SIG%s to %d...\n" $SIG $PID;
+    adb shell kill -$SIG $PID;
+    sleep 1;
+done;
+
+adb shell "while [ ! -f $COREPATH ] ; do echo waiting for $COREPATH to be generated; sleep 1; done"
+echo "Done: core is under $COREPATH on device.";
+
+
diff --git a/bin/coredump_enable b/bin/coredump_enable
new file mode 100755
index 0000000..da63a0c
--- /dev/null
+++ b/bin/coredump_enable
@@ -0,0 +1,32 @@
+#!/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.
+
+# coredump_enable - enable core dumps for the specified process
+# $1 = PID of process (e.g., $(pid mediaserver))
+#
+# NOTE: coredump_setup must have been called as well for a core
+#       dump to actually be generated.
+
+set -e
+
+PID=$1;
+if [ -z "$PID" ]; then
+    printf "Expecting a PID!\n";
+    exit 1
+fi;
+echo "Setting core limit for $PID to infinite...";
+adb shell /system/bin/ulimit -P $PID -c unlimited
+
diff --git a/bin/coredump_setup b/bin/coredump_setup
new file mode 100755
index 0000000..7647659
--- /dev/null
+++ b/bin/coredump_setup
@@ -0,0 +1,45 @@
+#!/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.
+
+# coredump_setup - enable core dumps globally for any process
+#                  that has the core-file-size limit set correctly
+#
+# NOTE: You must call also coredump_enable for a specific process
+#       if its core-file-size limit is not set already.
+# NOTE: Core dumps are written to ramdisk; they will not survive a reboot!
+
+set -e
+
+echo "Getting root...";
+adb root;
+adb wait-for-device;
+
+echo "Remounting root partition read-write...";
+adb shell mount -w -o remount -t rootfs rootfs;
+sleep 1;
+adb wait-for-device;
+adb shell mkdir -p /cores;
+adb shell mount -t tmpfs tmpfs /cores;
+adb shell chmod 0777 /cores;
+
+echo "Granting SELinux permission to dump in /cores...";
+adb shell restorecon -R /cores;
+
+echo "Set core pattern.";
+adb shell 'echo /cores/core.%p > /proc/sys/kernel/core_pattern';
+
+echo "Done."
+
diff --git a/bin/dirmods b/bin/dirmods
new file mode 100755
index 0000000..a6d4de3
--- /dev/null
+++ b/bin/dirmods
@@ -0,0 +1,62 @@
+#!/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.
+
+'''
+Lists all modules in the given directory or its decendant directories, as cached
+in module-info.json. If any build change is made, and it should be reflected in
+the output, you should run 'refreshmod' first.
+'''
+
+import sys
+sys.dont_write_bytecode = True
+
+import argparse
+import os
+
+import modinfo
+
+
+def main():
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument('path')
+    args = parser.parse_args()
+
+    d = os.path.normpath(args.path)
+    # Fix absolute path to be relative to build top
+    if os.path.isabs(d):
+        base = os.environ.get('ANDROID_BUILD_TOP')
+        if base:
+            base = os.path.normpath(base) + os.path.sep
+            if d.startswith(base):
+                d = d[len(base):]
+
+    prefix = d + '/'
+
+    module_info = modinfo.ReadModuleInfo()
+
+    results = set()
+    for m in module_info.values():
+        for path in m.get(u'path', []):
+            if path == d or path.startswith(prefix):
+                name = m.get(u'module_name')
+                if name:
+                    results.add(name)
+
+    for name in sorted(results):
+        print(name)
+
+if __name__ == "__main__":
+    main()
diff --git a/bin/get_abs_build_var b/bin/get_abs_build_var
new file mode 100755
index 0000000..8072cf1
--- /dev/null
+++ b/bin/get_abs_build_var
@@ -0,0 +1,26 @@
+#!/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 a build variable as an absolute path.
+
+# Common script utilities
+source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../../make/shell_utils.sh
+
+require_top
+
+$TOP/build/soong/soong_ui.bash --dumpvar-mode --abs $1
+
+exit $?
diff --git a/bin/get_build_var b/bin/get_build_var
new file mode 100755
index 0000000..9fdf55f
--- /dev/null
+++ b/bin/get_build_var
@@ -0,0 +1,26 @@
+#!/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 exact value of a build variable.
+
+# Common script utilities
+source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../../make/shell_utils.sh
+
+require_top
+
+$TOP/build/soong/soong_ui.bash --dumpvar-mode $1
+
+exit $?
diff --git a/bin/getlastscreenshot b/bin/getlastscreenshot
new file mode 100755
index 0000000..dfe9a6b
--- /dev/null
+++ b/bin/getlastscreenshot
@@ -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.
+
+# coredump_enable - enable core dumps for the specified process
+# $1 = PID of process (e.g., $(pid mediaserver))
+#
+# NOTE: coredump_setup must have been called as well for a core
+#       dump to actually be generated.
+
+set -e
+
+screenshot_path=$(getscreenshotpath)
+screenshot=`adb ${adbOptions} ls ${screenshot_path} | grep Screenshot_[0-9-]*.*\.png | sort -rk 3 | cut -d " " -f 4 | head -n 1`
+if [ "$screenshot" = "" ]; then
+    echo "No screenshots found."
+    exit 1
+fi
+echo "${screenshot}"
+adb ${adbOptions} pull ${screenshot_path}/${screenshot}
+
diff --git a/bin/getprebuilt b/bin/getprebuilt
new file mode 100755
index 0000000..68e65b4
--- /dev/null
+++ b/bin/getprebuilt
@@ -0,0 +1,18 @@
+#!/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_abs_build_var ANDROID_PREBUILTS
+
diff --git a/bin/getscreenshotpath b/bin/getscreenshotpath
new file mode 100755
index 0000000..ff8e327
--- /dev/null
+++ b/bin/getscreenshotpath
@@ -0,0 +1,26 @@
+#!/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.
+
+# coredump_enable - enable core dumps for the specified process
+# $1 = PID of process (e.g., $(pid mediaserver))
+#
+# NOTE: coredump_setup must have been called as well for a core
+#       dump to actually be generated.
+
+set -e
+
+echo "$(getsdcardpath)/Pictures/Screenshots"
+
diff --git a/bin/getsdcardpath b/bin/getsdcardpath
new file mode 100755
index 0000000..655659a
--- /dev/null
+++ b/bin/getsdcardpath
@@ -0,0 +1,26 @@
+#!/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.
+
+# coredump_enable - enable core dumps for the specified process
+# $1 = PID of process (e.g., $(pid mediaserver))
+#
+# NOTE: coredump_setup must have been called as well for a core
+#       dump to actually be generated.
+
+set -e
+
+adb ${adbOptions} shell echo -n \$\{EXTERNAL_STORAGE\}
+
diff --git a/bin/gettargetarch b/bin/gettargetarch
new file mode 100755
index 0000000..e53ce3f
--- /dev/null
+++ b/bin/gettargetarch
@@ -0,0 +1,18 @@
+#!/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_build_var TARGET_ARCH
+
diff --git a/bin/ggrep b/bin/ggrep
new file mode 100755
index 0000000..fce8c84
--- /dev/null
+++ b/bin/ggrep
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# 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.
+
+find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f \( \
+        -name '*.gradle' \
+    \) -exec grep --color -n "$@" {} +
+exit $?
diff --git a/bin/gogrep b/bin/gogrep
new file mode 100755
index 0000000..0265ccf
--- /dev/null
+++ b/bin/gogrep
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# 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.
+
+find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f \( \
+        -name '*.go' \
+    \) -exec grep --color -n "$@" {} +
+exit $?
diff --git a/bin/hmm b/bin/hmm
new file mode 100755
index 0000000..161bad6
--- /dev/null
+++ b/bin/hmm
@@ -0,0 +1,81 @@
+#!/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.
+
+# Common script utilities
+source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../../make/shell_utils.sh
+
+cat <<EOF
+
+Run "m help" for help with the build system itself.
+
+Invoke ". build/envsetup.sh" from your shell to add the following functions to your environment:
+- lunch:      lunch <product_name>-<release_type>-<build_variant>
+              Selects <product_name> as the product to build, and <build_variant> as the variant to
+              build, and stores those selections in the environment to be read by subsequent
+              invocations of 'm' etc.
+- tapas:      tapas [<App1> <App2> ...] [arm|x86|arm64|x86_64] [eng|userdebug|user]
+              Sets up the build environment for building unbundled apps (APKs).
+- banchan:    banchan <module1> [<module2> ...] \\
+                      [arm|x86|arm64|riscv64|x86_64|arm64_only|x86_64only] [eng|userdebug|user]
+              Sets up the build environment for building unbundled modules (APEXes).
+- croot:      Changes directory to the top of the tree, or a subdirectory thereof.
+- m:          Makes from the top of the tree.
+- mm:         Builds and installs all of the modules in the current directory, and their
+              dependencies.
+- mmm:        Builds and installs all of the modules in the supplied directories, and their
+              dependencies.
+              To limit the modules being built use the syntax: mmm dir/:target1,target2.
+- mma:        Same as 'mm'
+- mmma:       Same as 'mmm'
+- provision:  Flash device with all required partitions. Options will be passed on to fastboot.
+- cgrep:      Greps on all local C/C++ files.
+- ggrep:      Greps on all local Gradle files.
+- gogrep:     Greps on all local Go files.
+- jgrep:      Greps on all local Java files.
+- jsongrep:   Greps on all local Json files.
+- ktgrep:     Greps on all local Kotlin files.
+- resgrep:    Greps on all local res/*.xml files.
+- mangrep:    Greps on all local AndroidManifest.xml files.
+- mgrep:      Greps on all local Makefiles and *.bp files.
+- owngrep:    Greps on all local OWNERS files.
+- rsgrep:     Greps on all local Rust files.
+- sepgrep:    Greps on all local sepolicy files.
+- sgrep:      Greps on all local source files.
+- tomlgrep:   Greps on all local Toml files.
+- pygrep:     Greps on all local Python files.
+- godir:      Go to the directory containing a file.
+- allmod:     List all modules.
+- gomod:      Go to the directory containing a module.
+- pathmod:    Get the directory containing a module.
+- outmod:     Gets the location of a module's installed outputs with a certain extension.
+- dirmods:    Gets the modules defined in a given directory.
+- installmod: Adb installs a module's built APK.
+- refreshmod: Refresh list of modules for allmod/gomod/pathmod/outmod/installmod.
+- syswrite:   Remount partitions (e.g. system.img) as writable, rebooting if necessary.
+
+Environment options:
+- SANITIZE_HOST: Set to 'address' to use ASAN for all host modules.
+- ANDROID_QUIET_BUILD: set to 'true' to display only the essential messages.
+
+Look at the source to view more functions. The complete list is:
+EOF
+    T=$(gettop)
+    A=""
+    for i in `cat $T/build/envsetup.sh | sed -n "/^[[:blank:]]*function /s/function \([a-z_]*\).*/\1/p" | sort | uniq`; do
+      A="$A $i"
+    done
+    echo $A
+
diff --git a/bin/installmod b/bin/installmod
new file mode 100755
index 0000000..1d0d836
--- /dev/null
+++ b/bin/installmod
@@ -0,0 +1,54 @@
+#!/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.
+
+# adb install a module's apk, as cached in module-info.json. If any build change
+# is made, and it should be reflected in the output, you should run 'refreshmod' first.
+# Usage: installmod [adb install arguments] <module>
+# For example: installmod -r Dialer -> adb install -r /path/to/Dialer.apk
+
+if [[ $# -eq 0 ]]; then
+    echo "usage: installmod [adb install arguments] <module>" >&2
+    echo "" >&2
+    echo "Only flags to be passed after the \"install\" in adb install are supported," >&2
+    echo "with the exception of -s. If -s is passed it will be placed before the \"install\"." >&2
+    echo "-s must be the first flag passed if it exists." >&2
+    return 1
+fi
+
+local _path
+_path=$(outmod ${@:$#:1})
+if [ $? -ne 0 ]; then
+    return 1
+fi
+
+_path=$(echo "$_path" | grep -E \\.apk$ | head -n 1)
+if [ -z "$_path" ]; then
+    echo "Module '$1' does not produce a file ending with .apk (try 'refreshmod' if there have been build changes?)" >&2
+    return 1
+fi
+local serial_device=""
+if [[ "$1" == "-s" ]]; then
+    if [[ $# -le 2 ]]; then
+        echo "-s requires an argument" >&2
+        return 1
+    fi
+    serial_device="-s $2"
+    shift 2
+fi
+local length=$(( $# - 1 ))
+echo adb $serial_device install ${@:1:$length} $_path
+adb $serial_device install ${@:1:$length} $_path
+
diff --git a/bin/is64bit b/bin/is64bit
new file mode 100755
index 0000000..35bbcc3
--- /dev/null
+++ b/bin/is64bit
@@ -0,0 +1,32 @@
+#!/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.
+
+# Read the ELF header from /proc/$PID/exe to determine if the process is
+# 64-bit.
+
+set -e
+
+local PID="$1"
+if [ "$PID" ] ; then
+    if [[ "$(adb shell cat /proc/$PID/exe | xxd -l 1 -s 4 -p)" -eq "02" ]] ; then
+        echo "64"
+    else
+        echo ""
+    fi
+else
+    echo ""
+fi
+
diff --git a/bin/isviewserverstarted b/bin/isviewserverstarted
new file mode 100755
index 0000000..c7c82af
--- /dev/null
+++ b/bin/isviewserverstarted
@@ -0,0 +1,26 @@
+#!/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.
+
+# coredump_enable - enable core dumps for the specified process
+# $1 = PID of process (e.g., $(pid mediaserver))
+#
+# NOTE: coredump_setup must have been called as well for a core
+#       dump to actually be generated.
+
+set -e
+
+adb shell service call window 3
+
diff --git a/bin/jgrep b/bin/jgrep
new file mode 100755
index 0000000..afe70db
--- /dev/null
+++ b/bin/jgrep
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+# 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.
+
+find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f \( \
+        -name '*.java' \
+        -o -name '*.kt' \
+    \) -exec grep --color -n "$@" {} +
+exit $?
diff --git a/bin/jsongrep b/bin/jsongrep
new file mode 100755
index 0000000..6e14d0c
--- /dev/null
+++ b/bin/jsongrep
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# 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.
+
+find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f \( \
+        -name '*.json' \
+    \) -exec grep --color -n "$@" {} +
+exit $?
diff --git a/bin/key_back b/bin/key_back
new file mode 100755
index 0000000..2de8d07
--- /dev/null
+++ b/bin/key_back
@@ -0,0 +1,26 @@
+#!/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.
+
+# coredump_enable - enable core dumps for the specified process
+# $1 = PID of process (e.g., $(pid mediaserver))
+#
+# NOTE: coredump_setup must have been called as well for a core
+#       dump to actually be generated.
+
+set -e
+
+adb shell input keyevent 4
+
diff --git a/bin/key_home b/bin/key_home
new file mode 100755
index 0000000..653a5f9
--- /dev/null
+++ b/bin/key_home
@@ -0,0 +1,26 @@
+#!/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.
+
+# coredump_enable - enable core dumps for the specified process
+# $1 = PID of process (e.g., $(pid mediaserver))
+#
+# NOTE: coredump_setup must have been called as well for a core
+#       dump to actually be generated.
+
+set -e
+
+adb shell input keyevent 3
+
diff --git a/bin/key_menu b/bin/key_menu
new file mode 100755
index 0000000..29b2bc6
--- /dev/null
+++ b/bin/key_menu
@@ -0,0 +1,26 @@
+#!/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.
+
+# coredump_enable - enable core dumps for the specified process
+# $1 = PID of process (e.g., $(pid mediaserver))
+#
+# NOTE: coredump_setup must have been called as well for a core
+#       dump to actually be generated.
+
+set -e
+
+adb shell input keyevent 82
+
diff --git a/bin/ktgrep b/bin/ktgrep
new file mode 120000
index 0000000..9b51491
--- /dev/null
+++ b/bin/ktgrep
@@ -0,0 +1 @@
+jgrep
\ No newline at end of file
diff --git a/bin/list_products b/bin/list_products
new file mode 100755
index 0000000..cd8dd5c
--- /dev/null
+++ b/bin/list_products
@@ -0,0 +1,32 @@
+#!/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.
+
+# Common script utilities
+source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../../make/shell_utils.sh
+
+require_top
+
+# In almost call cases including get_build_var, TARGET_RELEASE is required,
+# but the list of available products is not dependent on the release config
+# (but note that the list of available release configs is dependent on the
+# product). So for list_products, we'll just set it to trunk_staging, which
+# exists everwhere, so we don't trigger the unspecified TARGET_RELEASE error.
+
+# We also unset TARGET_BUILD_APPS, so it doesn't interfere.
+
+TARGET_RELEASE=trunk_staging TARGET_BUILD_APPS= $TOP/build/soong/soong_ui.bash --dumpvar-mode all_named_products | sed 's/ /\n/g'
+
+exit $?
diff --git a/bin/list_releases b/bin/list_releases
new file mode 100755
index 0000000..ca18110
--- /dev/null
+++ b/bin/list_releases
@@ -0,0 +1,45 @@
+#!/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.
+
+if [[ $# -eq 1 ]]; then
+    # Override anything that's already set
+    export TARGET_PRODUCT=$1
+elif [[ -z $TARGET_PRODUCT ]]; then
+    echo "Usage: list_releases [PRODUCT]" 1>&2
+    echo "" 1>&2
+    echo "If the optional PRODUCT parameter is bit provided, then TARGET_PRODUCT" 1>&2
+    echo "must have been set, for example by lunch or banchan." 1>&2
+    exit 1
+fi
+
+
+
+# Common script utilities
+source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../../make/shell_utils.sh
+
+require_top
+
+# In almost call cases including get_build_var, TARGET_RELEASE is required,
+# but the list of available products is not dependent on the release config
+# (but note that the list of available release configs is dependent on the
+# product). So for list_products, we'll just set it to trunk_staging, which
+# exists everwhere, so we don't trigger the unspecified TARGET_RELEASE error.
+
+# We also unset TARGET_BUILD_APPS, so it doesn't interfere.
+
+TARGET_RELEASE=trunk_staging TARGET_BUILD_APPS= $TOP/build/soong/soong_ui.bash --dumpvar-mode ALL_RELEASE_CONFIGS_FOR_PRODUCT | sed 's/ /\n/g'
+
+exit $?
diff --git a/bin/list_variants b/bin/list_variants
new file mode 100755
index 0000000..ac89e6a
--- /dev/null
+++ b/bin/list_variants
@@ -0,0 +1,20 @@
+#!/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.
+
+echo user
+echo userdebug
+echo eng
+
diff --git a/bin/m b/bin/m
new file mode 100755
index 0000000..edcfce5
--- /dev/null
+++ b/bin/m
@@ -0,0 +1,24 @@
+#!/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.
+
+# Common script utilities
+source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../../make/shell_utils.sh
+
+require_top
+
+_wrap_build "$TOP/build/soong/soong_ui.bash" --build-mode --all-modules --dir="$(pwd)" "$@"
+
+exit $?
diff --git a/bin/mangrep b/bin/mangrep
new file mode 100755
index 0000000..a343000
--- /dev/null
+++ b/bin/mangrep
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# 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.
+
+find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f \( \
+        -name 'AndroidManifest.xml' \
+    \) -exec grep --color -n "$@" {} +
+exit $?
diff --git a/bin/mgrep b/bin/mgrep
new file mode 100755
index 0000000..793730d
--- /dev/null
+++ b/bin/mgrep
@@ -0,0 +1,25 @@
+#!/bin/bash
+
+# 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.
+
+find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f \( \
+        -name 'Makefile' \
+        -o -name 'Makefile.*' \
+        -o -name '*.make' \
+        -o -name '*.mak' \
+        -o -name '*.mk' \
+        -o -name '*.bp' \
+    \) -exec grep --color -n "$@" {} +
+exit $?
diff --git a/bin/mm b/bin/mm
new file mode 100755
index 0000000..6461b1e
--- /dev/null
+++ b/bin/mm
@@ -0,0 +1,24 @@
+#!/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.
+
+# Common script utilities
+source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../../make/shell_utils.sh
+
+require_top
+
+_wrap_build "$TOP/build/soong/soong_ui.bash" --build-mode --modules-in-a-dir-no-deps --dir="$(pwd)" "$@"
+
+exit $?
diff --git a/bin/mma b/bin/mma
new file mode 100755
index 0000000..6f1c934
--- /dev/null
+++ b/bin/mma
@@ -0,0 +1,24 @@
+#!/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.
+
+# Common script utilities
+source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../../make/shell_utils.sh
+
+require_top
+
+_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
new file mode 100755
index 0000000..ab3a632
--- /dev/null
+++ b/bin/mmm
@@ -0,0 +1,24 @@
+#!/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.
+
+# Common script utilities
+source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../../make/shell_utils.sh
+
+require_top
+
+_wrap_build "$TOP/build/soong/soong_ui.bash" --build-mode --modules-in-dirs-no-deps --dir="$(pwd)" "$@"
+
+exit $?
diff --git a/bin/mmma b/bin/mmma
new file mode 100755
index 0000000..d9190e5
--- /dev/null
+++ b/bin/mmma
@@ -0,0 +1,24 @@
+#!/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.
+
+# Common script utilities
+source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../../make/shell_utils.sh
+
+require_top
+
+_wrap_build "$TOP/build/soong/soong_ui.bash" --build-mode --modules-in-dirs --dir="$(pwd)" "$@"
+
+exit $?
diff --git a/bin/modinfo.py b/bin/modinfo.py
new file mode 100644
index 0000000..015129f
--- /dev/null
+++ b/bin/modinfo.py
@@ -0,0 +1,44 @@
+# 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 json
+import os
+import pathlib
+import sys
+
+
+def OpenModuleInfoFile():
+    product_out = os.getenv("ANDROID_PRODUCT_OUT")
+    if not product_out:
+        if os.getenv("QUIET_VERIFYMODINFO") != "true":
+            sys.stderr.write("No ANDROID_PRODUCT_OUT. Try running 'lunch' first.\n")
+        sys.exit(1)
+    try:
+        return open(pathlib.Path(product_out) / "module-info.json")
+    except (FileNotFoundError, PermissionError):
+        if os.getenv("QUIET_VERIFYMODINFO") != "true":
+            sys.stderr.write("Could not find module-info.json. Please run 'refreshmod' first.\n")
+        sys.exit(1)
+
+
+def ReadModuleInfo():
+    with OpenModuleInfoFile() as f:
+        return json.load(f)
+
+def GetModule(modules, module_name):
+    if module_name not in modules:
+        sys.stderr.write(f"Could not find module '{module_name}' (try 'refreshmod' if there have been build changes?)\n")
+        sys.exit(1)
+    return modules[module_name]
+
diff --git a/bin/outmod b/bin/outmod
new file mode 100755
index 0000000..022ff36
--- /dev/null
+++ b/bin/outmod
@@ -0,0 +1,42 @@
+#!/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.
+
+'''
+Lists the output files of a specific module in the android tree, as cached in
+module-info.json. If any build change is made, and it should be reflected in the
+output, you should run 'refreshmod' first.
+'''
+
+import sys
+sys.dont_write_bytecode = True
+
+import argparse
+import os
+
+import modinfo
+
+
+def main():
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument('module')
+    args = parser.parse_args()
+
+    for output in modinfo.GetModule(modinfo.ReadModuleInfo(), args.module)['installed']:
+        print(os.path.join(os.getenv("ANDROID_BUILD_TOP", ""), output))
+
+if __name__ == "__main__":
+    main()
+
diff --git a/bin/overrideflags b/bin/overrideflags
new file mode 100755
index 0000000..e16537b
--- /dev/null
+++ b/bin/overrideflags
@@ -0,0 +1,100 @@
+#!/bin/bash -e
+# 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.
+
+
+source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../../make/shell_utils.sh
+
+require_top
+
+function print_help() {
+    echo -e "overrideflags is used to set default value for local build."
+    echo -e "\nOptions:"
+    echo -e "\t--release-config  \tPath to release configuration directory. Required"
+    echo -e "\t--no-edit         \tIf present, skip editing flag value file."
+    echo -e "\t-h/--help         \tShow this help."
+}
+
+function main() {
+    while (($# > 0)); do
+        case $1 in
+        --release-config)
+            if [[ $# -le 1 ]]; then
+                echo "--release-config requires a path"
+                return 1
+            fi
+            local release_config_dir="$2"
+            shift 2
+            ;;
+        --no-edit)
+            local no_edit="true"
+            shift 1
+            ;;
+        -h|--help)
+            print_help
+            return
+            ;;
+        *)
+            echo "$1 is unrecognized"
+            print_help
+            return 1
+            ;;
+        esac
+    done
+
+
+
+    case $(uname -s) in
+        Darwin)
+            local host_arch=darwin-x86
+            ;;
+        Linux)
+            local host_arch=linux-x86
+            ;;
+        *)
+            >&2 echo Unknown host $(uname -s)
+            return
+            ;;
+    esac
+
+    if [[ -z "${release_config_dir}" ]]; then
+        echo "Please provide release configuration path by --release-config"
+        exit 1
+    elif [ ! -d "${release_config_dir}" ]; then
+        echo "${release_config_dir} is an invalid directory"
+        exit 1
+    fi
+    local T="$(gettop)"
+    local aconfig_dir="${T}"/build/make/tools/aconfig/
+    local overrideflag_py="${aconfig_dir}"/overrideflags/overrideflags.py
+    local overridefile="${release_config_dir}/aconfig/override_values.textproto"
+
+    # Edit override file
+    if [[ -z "${no_edit}" ]]; then
+        editor="${EDITOR:-$(which vim)}"
+
+        eval "${editor} ${overridefile}"
+        if [ $? -ne 0 ]; then
+            echo "Fail to set override values"
+            return 1
+        fi
+    fi
+
+    ${T}/prebuilts/build-tools/${host_arch}/bin/py3-cmd -u "${overrideflag_py}" \
+        --overrides "${overridefile}" \
+        --out "${release_config_dir}/aconfig"
+}
+
+
+main "$@"
diff --git a/bin/owngrep b/bin/owngrep
new file mode 100755
index 0000000..26ce6e8
--- /dev/null
+++ b/bin/owngrep
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# 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.
+
+find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f \( \
+        -name 'OWNERS' \
+    \) -exec grep --color -n "$@" {} +
+exit $?
diff --git a/bin/pathmod b/bin/pathmod
new file mode 100755
index 0000000..70cf958
--- /dev/null
+++ b/bin/pathmod
@@ -0,0 +1,41 @@
+#!/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.
+
+'''
+Get the path of a specific module in the android tree, as cached in module-info.json.
+If any build change is made, and it should be reflected in the output, you should run
+'refreshmod' first.  Note: This is the inverse of dirmods.
+'''
+
+import sys
+sys.dont_write_bytecode = True
+
+import argparse
+import os
+
+import modinfo
+
+
+def main():
+    parser = argparse.ArgumentParser(description=__doc__)
+    parser.add_argument('module')
+    args = parser.parse_args()
+
+    path = modinfo.GetModule(modinfo.ReadModuleInfo(), args.module)['path'][0]
+    print(os.path.join(os.getenv("ANDROID_BUILD_TOP", ""), path))
+
+if __name__ == "__main__":
+    main()
diff --git a/bin/pygrep b/bin/pygrep
new file mode 100755
index 0000000..e072289
--- /dev/null
+++ b/bin/pygrep
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# 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.
+
+find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f \( \
+        -name '*.py' \
+    \) -exec grep --color -n "$@" {} +
+exit $?
diff --git a/bin/qpid b/bin/qpid
new file mode 100755
index 0000000..b47cb6b
--- /dev/null
+++ b/bin/qpid
@@ -0,0 +1,44 @@
+#!/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.
+
+# adb install a module's apk, as cached in module-info.json. If any build change
+# is made, and it should be reflected in the output, you should run 'refreshmod' first.
+# Usage: installmod [adb install arguments] <module>
+# For example: installmod -r Dialer -> adb install -r /path/to/Dialer.apk
+
+function _impl() {
+    local prepend=''
+    local append=''
+    if [ "$1" = "--exact" ]; then
+        prepend=' '
+        append='$'
+        shift
+    elif [ "$1" = "--help" -o "$1" = "-h" ]; then
+        echo "usage: qpid [[--exact] <process name|pid>"
+        return 255
+    fi
+
+    local EXE="$1"
+    if [ "$EXE" ] ; then
+        _impl | \grep "$prepend$EXE$append"
+    else
+        adb shell ps \
+            | tr -d '\r' \
+            | sed -e 1d -e 's/^[^ ]* *\([0-9]*\).* \([^ ]*\)$/\1 \2/'
+    fi
+}
+
+_impl "$@"
diff --git a/bin/rcgrep b/bin/rcgrep
new file mode 100755
index 0000000..ff93e51
--- /dev/null
+++ b/bin/rcgrep
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# 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.
+
+find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f \( \
+        -name '*.rc' \
+    \) -exec grep --color -n "$@" {} +
+exit $?
diff --git a/bin/refreshmod b/bin/refreshmod
new file mode 100755
index 0000000..f511846
--- /dev/null
+++ b/bin/refreshmod
@@ -0,0 +1,31 @@
+#!/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.
+
+# Update module-info.json in out.
+
+# Common script utilities
+source $(cd $(dirname $BASH_SOURCE) &> /dev/null && pwd)/../../make/shell_utils.sh
+
+require_top
+
+if [ ! "$ANDROID_PRODUCT_OUT" ]; then
+    echo "No ANDROID_PRODUCT_OUT. Try running 'lunch' first." >&2
+    return 1
+fi
+
+echo "Refreshing modules (building module-info.json)" >&2
+
+_wrap_build $TOP/build/soong/soong_ui.bash --build-mode --all-modules --dir="$(pwd)" module-info
diff --git a/bin/resgrep b/bin/resgrep
new file mode 100755
index 0000000..600091f
--- /dev/null
+++ b/bin/resgrep
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+# 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.
+
+for dir in `find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -name res -type d`; do
+    find $dir -type f -name '*\.xml' -exec grep --color -n "$@" {} +
+done
diff --git a/bin/rsgrep b/bin/rsgrep
new file mode 100755
index 0000000..8c24151
--- /dev/null
+++ b/bin/rsgrep
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# 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.
+
+find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f \( \
+        -name '*.rs' \
+    \) -exec grep --color -n "$@" {} +
+exit $?
diff --git a/bin/run_tool_with_logging b/bin/run_tool_with_logging
new file mode 100755
index 0000000..2b2c8d8
--- /dev/null
+++ b/bin/run_tool_with_logging
@@ -0,0 +1,55 @@
+#!/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.
+
+# Run commands in a subshell for us to handle forced terminations with a trap
+# handler.
+(
+tool_tag="$1"
+shift
+tool_binary="$1"
+shift
+
+# If the logger is not configured, run the original command and return.
+if [[ -z "${ANDROID_TOOL_LOGGER}" ]]; then
+  "${tool_binary}" "${@}"
+   exit $?
+fi
+
+# Otherwise, run the original command and call the logger when done.
+start_time=$(date +%s.%N)
+logger=${ANDROID_TOOL_LOGGER}
+
+# Install a trap to call the logger even when the process terminates abnormally.
+# The logger is run in the background and its output suppressed to avoid
+# interference with the user flow.
+trap '
+exit_code=$?;
+# Remove the trap to prevent duplicate log.
+trap - EXIT;
+"${logger}" \
+  --tool_tag="${tool_tag}" \
+  --start_timestamp="${start_time}" \
+  --end_timestamp="$(date +%s.%N)" \
+  --tool_args="$*" \
+  --exit_code="${exit_code}" \
+  ${ANDROID_TOOL_LOGGER_EXTRA_ARGS} \
+  > /dev/null 2>&1 &
+exit ${exit_code}
+' SIGINT SIGTERM SIGQUIT EXIT
+
+# Run the original command.
+"${tool_binary}" "${@}"
+)
diff --git a/bin/sepgrep b/bin/sepgrep
new file mode 100755
index 0000000..0e0d1ba
--- /dev/null
+++ b/bin/sepgrep
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+# 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.
+
+find . -name .repo -prune -o -name .git -prune -o -path ./out -prune -o -name sepolicy -type d \
+    -exec grep --color -n -r --exclude-dir=\.git "$@" {} +
+exit $?
diff --git a/bin/sgrep b/bin/sgrep
new file mode 100755
index 0000000..f186553
--- /dev/null
+++ b/bin/sgrep
@@ -0,0 +1,36 @@
+#!/bin/bash
+
+# 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.
+
+find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f \( \
+        -name '*.c' \
+        -o -name '*.cc' \
+        -o -name '*.cpp' \
+        -o -name '*.h' \
+        -o -name '*.hpp' \
+        -o -name '*.S' \
+        -o -name '*.java' \
+        -o -name '*.kt' \
+        -o -name '*.xml' \
+        -o -name '*.sh' \
+        -o -name '*.mk' \
+        -o -name '*.bp' \
+        -o -name '*.aidl' \
+        -o -name '*.vts' \
+        -o -name '*.proto' \
+        -o -name '*.rs' \
+        -o -name '*.go' \
+    \) -exec grep --color -n "$@" {} +
+exit $?
diff --git a/bin/soongdbg b/bin/soongdbg
index bfdbbde..a73bdf9 100755
--- a/bin/soongdbg
+++ b/bin/soongdbg
@@ -32,11 +32,13 @@
                 dep.rdeps.add(node)
                 node.dep_tags.setdefault(dep, list()).append(d)
 
-    def find_paths(self, id1, id2):
+    def find_paths(self, id1, id2, tag_filter):
         # Throws KeyError if one of the names isn't found
         def recurse(node1, node2, visited):
             result = set()
             for dep in node1.rdeps:
+                if not matches_tag(dep, node1, tag_filter):
+                    continue
                 if dep == node2:
                     result.add(node2)
                 if dep not in visited:
@@ -214,6 +216,8 @@
                         help="jq query for each module metadata")
     parser.add_argument("--deptags", action="store_true",
                         help="show dependency tags (makes the graph much more complex)")
+    parser.add_argument("--tag", action="append",
+                        help="Limit output to these dependency tags.")
 
     group = parser.add_argument_group("output formats",
                                       "If no format is provided, a dot file will be written to"
@@ -259,13 +263,21 @@
         sys.stdout.write(text)
 
 
-def get_deps(nodes, root, maxdepth, reverse):
+def matches_tag(node, dep, tag_filter):
+    if not tag_filter:
+        return True
+    return not tag_filter.isdisjoint([t.tag_type for t in node.dep_tags[dep]])
+
+
+def get_deps(nodes, root, maxdepth, reverse, tag_filter):
     if root in nodes:
         return
     nodes.add(root)
     if maxdepth != 0:
         for dep in (root.rdeps if reverse else root.deps):
-            get_deps(nodes, dep, maxdepth-1, reverse)
+            if not matches_tag(root, dep, tag_filter):
+                continue
+            get_deps(nodes, dep, maxdepth-1, reverse, tag_filter)
 
 
 def new_module_formatter(args):
@@ -302,7 +314,7 @@
 
     def run(self, args):
         graph = load_graph()
-        print_nodes(args, graph.find_paths(args.module[0], args.module[1]),
+        print_nodes(args, graph.find_paths(args.module[0], args.module[1], set(args.tag)),
                     new_module_formatter(args))
 
 
@@ -328,7 +340,7 @@
                 sys.stderr.write(f"error: Can't find root: {id}\n")
                 err = True
                 continue
-            get_deps(nodes, root, args.depth, args.reverse)
+            get_deps(nodes, root, args.depth, args.reverse, set(args.tag))
         if err:
             sys.exit(1)
         print_nodes(args, nodes, new_module_formatter(args))
diff --git a/bin/startviewserver b/bin/startviewserver
new file mode 100755
index 0000000..4d612b8
--- /dev/null
+++ b/bin/startviewserver
@@ -0,0 +1,30 @@
+#!/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.
+
+# coredump_enable - enable core dumps for the specified process
+# $1 = PID of process (e.g., $(pid mediaserver))
+#
+# NOTE: coredump_setup must have been called as well for a core
+#       dump to actually be generated.
+
+set -e
+
+port=4939
+if [ $# -gt 0 ]; then
+    port=$1
+fi
+adb shell service call window 1 i32 $port
+
diff --git a/bin/stopviewserver b/bin/stopviewserver
new file mode 100755
index 0000000..a734e4b
--- /dev/null
+++ b/bin/stopviewserver
@@ -0,0 +1,26 @@
+#!/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.
+
+# coredump_enable - enable core dumps for the specified process
+# $1 = PID of process (e.g., $(pid mediaserver))
+#
+# NOTE: coredump_setup must have been called as well for a core
+#       dump to actually be generated.
+
+set -e
+
+adb shell service call window 2
+
diff --git a/bin/systemstack b/bin/systemstack
new file mode 100755
index 0000000..b259133
--- /dev/null
+++ b/bin/systemstack
@@ -0,0 +1,21 @@
+#!/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.
+
+# systemstack - dump the current stack trace of all threads in the system process
+# to the usual ANR traces file
+
+stacks system_server
+
diff --git a/bin/syswrite b/bin/syswrite
new file mode 100755
index 0000000..46201e3
--- /dev/null
+++ b/bin/syswrite
@@ -0,0 +1,25 @@
+#!/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.
+
+# syswrite - disable verity, reboot if needed, and remount image
+# Easy way to make system.img/etc writable
+
+adb wait-for-device && adb root && adb wait-for-device || exit 1
+if [[ $(adb disable-verity | grep -i "reboot") ]]; then
+  echo "rebooting"
+  adb reboot && adb wait-for-device && adb root && adb wait-for-device || exit 1
+fi
+adb remount || exit 1
diff --git a/bin/tomlgrep b/bin/tomlgrep
new file mode 100755
index 0000000..636ef22
--- /dev/null
+++ b/bin/tomlgrep
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+# 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.
+
+find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f \( \
+        -name '*.toml' \
+    \) -exec grep --color -n "$@" {} +
+exit $?
diff --git a/bin/treegrep b/bin/treegrep
new file mode 100755
index 0000000..b83d419
--- /dev/null
+++ b/bin/treegrep
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+# 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.
+
+find . -name .repo -prune -o -name .git -prune -o -name out -prune -o -type f \( \
+        -name '*.c' \
+        -o -name '*.cc' \
+        -o -name '*.cpp' \
+        -o -name '*.h' \
+        -o -name '*.hpp' \
+        -o -name '*.S' \
+        -o -name '*.java' \
+        -o -name '*.kt' \
+        -o -name '*.xml' \
+    \) -exec grep --color -n "$@" {} +
+exit $?
diff --git a/bpf/bpf.go b/bpf/bpf.go
index 38fbd88..09262e5 100644
--- a/bpf/bpf.go
+++ b/bpf/bpf.go
@@ -65,8 +65,6 @@
 type BpfModule interface {
 	android.Module
 
-	OutputFiles(tag string) (android.Paths, error)
-
 	// Returns the sub install directory if the bpf module is included by apex.
 	SubDir() string
 }
@@ -106,6 +104,14 @@
 
 func (bpf *bpf) ImageMutatorBegin(ctx android.BaseModuleContext) {}
 
+func (bpf *bpf) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
+	return proptools.Bool(bpf.properties.Vendor)
+}
+
+func (bpf *bpf) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
+	return false
+}
+
 func (bpf *bpf) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
 	return !proptools.Bool(bpf.properties.Vendor)
 }
@@ -127,13 +133,10 @@
 }
 
 func (bpf *bpf) ExtraImageVariations(ctx android.BaseModuleContext) []string {
-	if proptools.Bool(bpf.properties.Vendor) {
-		return []string{"vendor"}
-	}
 	return nil
 }
 
-func (bpf *bpf) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) {
+func (bpf *bpf) SetImageVariation(ctx android.BaseModuleContext, variation string) {
 	bpf.properties.VendorInternal = variation == "vendor"
 }
 
@@ -213,6 +216,8 @@
 	}
 
 	android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: srcs.Strings()})
+
+	ctx.SetOutputFiles(bpf.objs, "")
 }
 
 func (bpf *bpf) AndroidMk() android.AndroidMkData {
@@ -255,23 +260,10 @@
 	}
 }
 
-// Implements OutputFileFileProducer interface so that the obj output can be used in the data property
-// of other modules.
-func (bpf *bpf) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		return bpf.objs, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
 func (bpf *bpf) SubDir() string {
 	return bpf.properties.Sub_dir
 }
 
-var _ android.OutputFileProducer = (*bpf)(nil)
-
 func BpfFactory() android.Module {
 	module := &bpf{}
 
diff --git a/bpfix/bpfix/bpfix.go b/bpfix/bpfix/bpfix.go
index ddaa98a..9163ab7 100644
--- a/bpfix/bpfix/bpfix.go
+++ b/bpfix/bpfix/bpfix.go
@@ -286,7 +286,7 @@
 }
 
 func parse(name string, r io.Reader) (*parser.File, error) {
-	tree, errs := parser.Parse(name, r, parser.NewScope(nil))
+	tree, errs := parser.Parse(name, r)
 	if errs != nil {
 		s := "parse error: "
 		for _, err := range errs {
diff --git a/bpfix/bpfix/bpfix_test.go b/bpfix/bpfix/bpfix_test.go
index b5b49b1..f487d3c 100644
--- a/bpfix/bpfix/bpfix_test.go
+++ b/bpfix/bpfix/bpfix_test.go
@@ -46,7 +46,7 @@
 	}
 	`,
 		printListOfStrings(local_include_dirs), printListOfStrings(export_include_dirs))
-	tree, errs := parser.Parse("", strings.NewReader(input), parser.NewScope(nil))
+	tree, errs := parser.Parse("", strings.NewReader(input))
 	if len(errs) > 0 {
 		errs = append([]error{fmt.Errorf("failed to parse:\n%s", input)}, errs...)
 	}
@@ -167,7 +167,7 @@
 		return fixer, err
 	}
 
-	tree, errs := parser.Parse("<testcase>", bytes.NewBufferString(in), parser.NewScope(nil))
+	tree, errs := parser.Parse("<testcase>", bytes.NewBufferString(in))
 	if errs != nil {
 		return fixer, err
 	}
diff --git a/bpfix/cmd_lib/bpfix.go b/bpfix/cmd_lib/bpfix.go
index 1106d4a..41430f8 100644
--- a/bpfix/cmd_lib/bpfix.go
+++ b/bpfix/cmd_lib/bpfix.go
@@ -66,7 +66,7 @@
 		return err
 	}
 	r := bytes.NewBuffer(append([]byte(nil), src...))
-	file, errs := parser.Parse(filename, r, parser.NewScope(nil))
+	file, errs := parser.Parse(filename, r)
 	if len(errs) > 0 {
 		for _, err := range errs {
 			fmt.Fprintln(os.Stderr, err)
diff --git a/cc/Android.bp b/cc/Android.bp
index 9ce8933..3bbcaa9 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -51,6 +51,7 @@
         "vndk.go",
         "vndk_prebuilt.go",
 
+        "cmake_snapshot.go",
         "cmakelists.go",
         "compdb.go",
         "compiler.go",
@@ -92,6 +93,7 @@
         "binary_test.go",
         "cc_test.go",
         "cc_test_only_property_test.go",
+        "cmake_snapshot_test.go",
         "compiler_test.go",
         "gen_test.go",
         "genrule_test.go",
@@ -109,5 +111,12 @@
         "tidy_test.go",
         "vendor_public_library_test.go",
     ],
+    embedSrcs: [
+        "cmake_ext_add_aidl_library.txt",
+        "cmake_ext_append_flags.txt",
+        "cmake_main.txt",
+        "cmake_module_aidl.txt",
+        "cmake_module_cc.txt",
+    ],
     pluginFor: ["soong_build"],
 }
diff --git a/cc/afdo.go b/cc/afdo.go
index 00b2245..6921edf 100644
--- a/cc/afdo.go
+++ b/cc/afdo.go
@@ -176,6 +176,9 @@
 
 func (a *afdoTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
 	if m, ok := ctx.Module().(*Module); ok && m.afdo != nil {
+		if !m.Enabled(ctx) {
+			return
+		}
 		if variation == "" {
 			// The empty variation is either a module that has enabled AFDO for itself, or the non-AFDO
 			// variant of a dependency.
diff --git a/cc/androidmk.go b/cc/androidmk.go
index ef26366..4134653 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -88,7 +88,7 @@
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				if len(c.Properties.Logtags) > 0 {
-					entries.AddStrings("LOCAL_LOGTAGS_FILES", c.Properties.Logtags...)
+					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,
@@ -104,31 +104,18 @@
 					entries.AddStrings("LOCAL_RUNTIME_LIBRARIES", c.Properties.AndroidMkRuntimeLibs...)
 				}
 				entries.SetString("LOCAL_SOONG_LINK_TYPE", c.makeLinkType)
-				if c.InVendorOrProduct() {
-					if c.IsVndk() && !c.static() {
-						entries.SetString("LOCAL_SOONG_VNDK_VERSION", c.VndkVersion())
-						// VNDK libraries available to vendor are not installed because
-						// they are packaged in VNDK APEX and installed by APEX packages (apex/apex.go)
-						if !c.IsVndkExt() {
-							entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true)
-						}
-					}
-				}
 				if c.InVendor() {
 					entries.SetBool("LOCAL_IN_VENDOR", true)
 				} else if c.InProduct() {
 					entries.SetBool("LOCAL_IN_PRODUCT", true)
 				}
-				if c.Properties.IsSdkVariant && c.Properties.SdkAndPlatformVariantVisibleToMake {
-					// Make the SDK variant uninstallable so that there are not two rules to install
-					// to the same location.
-					entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", 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))")
 				}
-				android.SetAconfigFileMkEntries(c.AndroidModuleBase(), entries, c.mergedAconfigFiles)
+				entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", c.IsSkipInstall())
 			},
 		},
 		ExtraFooters: []android.AndroidMkExtraFootersFunc{
@@ -264,15 +251,6 @@
 		if library.coverageOutputFile.Valid() {
 			entries.SetString("LOCAL_PREBUILT_COVERAGE_ARCHIVE", library.coverageOutputFile.String())
 		}
-
-		if library.useCoreVariant {
-			entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true)
-			entries.SetBool("LOCAL_NO_NOTICE_FILE", true)
-			entries.SetBool("LOCAL_VNDK_DEPEND_ON_CORE_VARIANT", true)
-		}
-		if library.checkSameCoreVariant {
-			entries.SetBool("LOCAL_CHECK_SAME_VNDK_VARIANTS", true)
-		}
 	})
 
 	if library.shared() && !library.buildStubs() {
@@ -373,9 +351,6 @@
 	ctx.subAndroidMk(entries, test.testDecorator)
 
 	entries.Class = "NATIVE_TESTS"
-	if Bool(test.Properties.Test_per_src) {
-		entries.SubName = "_" + String(test.binaryDecorator.Properties.Stem)
-	}
 	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())
@@ -498,14 +473,14 @@
 	ctx.subAndroidMk(entries, p.libraryDecorator)
 	if p.shared() {
 		ctx.subAndroidMk(entries, &p.prebuiltLinker)
-		androidMkWriteAllowUndefinedSymbols(p.baseLinker, entries)
+		androidMkWritePrebuiltOptions(p.baseLinker, entries)
 	}
 }
 
 func (p *prebuiltBinaryLinker) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
 	ctx.subAndroidMk(entries, p.binaryDecorator)
 	ctx.subAndroidMk(entries, &p.prebuiltLinker)
-	androidMkWriteAllowUndefinedSymbols(p.baseLinker, entries)
+	androidMkWritePrebuiltOptions(p.baseLinker, entries)
 }
 
 func (a *apiLibraryDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
@@ -536,11 +511,17 @@
 	})
 }
 
-func androidMkWriteAllowUndefinedSymbols(linker *baseLinker, entries *android.AndroidMkEntries) {
+func androidMkWritePrebuiltOptions(linker *baseLinker, entries *android.AndroidMkEntries) {
 	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)
 		})
 	}
+	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)
+		})
+	}
 }
diff --git a/cc/binary.go b/cc/binary.go
index 7aa8e20..2ac9a45 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -18,6 +18,7 @@
 	"path/filepath"
 
 	"android/soong/android"
+
 	"github.com/google/blueprint"
 )
 
@@ -425,6 +426,10 @@
 	validations = append(validations, objs.tidyDepFiles...)
 	linkerDeps = append(linkerDeps, flags.LdFlagsDeps...)
 
+	if generatedLib := generateRustStaticlib(ctx, deps.RustRlibDeps); generatedLib != nil {
+		deps.StaticLibs = append(deps.StaticLibs, generatedLib)
+	}
+
 	// Register link action.
 	transformObjToDynamicBinary(ctx, objs.objFiles, sharedLibs, deps.StaticLibs,
 		deps.LateStaticLibs, deps.WholeStaticLibs, linkerDeps, deps.CrtBegin, deps.CrtEnd, true,
@@ -446,7 +451,7 @@
 }
 
 func (binary *binaryDecorator) strippedAllOutputFilePath() android.Path {
-	panic("Not implemented.")
+	return nil
 }
 
 func (binary *binaryDecorator) setSymlinkList(ctx ModuleContext) {
diff --git a/cc/builder.go b/cc/builder.go
index 845176e..367bda3 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -19,6 +19,7 @@
 // functions.
 
 import (
+	"fmt"
 	"path/filepath"
 	"runtime"
 	"strconv"
@@ -45,18 +46,18 @@
 		blueprint.RuleParams{
 			Depfile:     "${out}.d",
 			Deps:        blueprint.DepsGCC,
-			Command:     "$relPwd ${config.CcWrapper}$ccCmd -c $cFlags -MD -MF ${out}.d -o $out $in",
+			Command:     "$relPwd ${config.CcWrapper}$ccCmd -c $cFlags -MD -MF ${out}.d -o $out $in$postCmd",
 			CommandDeps: []string{"$ccCmd"},
 		},
-		"ccCmd", "cFlags")
+		"ccCmd", "cFlags", "postCmd")
 
 	// Rule to invoke gcc with given command and flags, but no dependencies.
 	ccNoDeps = pctx.AndroidStaticRule("ccNoDeps",
 		blueprint.RuleParams{
-			Command:     "$relPwd $ccCmd -c $cFlags -o $out $in",
+			Command:     "$relPwd $ccCmd -c $cFlags -o $out $in$postCmd",
 			CommandDeps: []string{"$ccCmd"},
 		},
-		"ccCmd", "cFlags")
+		"ccCmd", "cFlags", "postCmd")
 
 	// Rules to invoke ld to link binaries. Uses a .rsp file to list dependencies, as there may
 	// be many.
@@ -330,6 +331,15 @@
 			CommandDeps: []string{"$cxxExtractor", "$kytheVnames"},
 		},
 		"cFlags")
+
+	// Function pointer for producting staticlibs from rlibs. Corresponds to
+	// rust.TransformRlibstoStaticlib(), initialized in soong-rust (rust/builder.go init())
+	//
+	// This is required since soong-rust depends on soong-cc, so soong-cc cannot depend on soong-rust
+	// without resulting in a circular dependency. Setting this function pointer in soong-rust allows
+	// soong-cc to call into this particular function.
+	TransformRlibstoStaticlib (func(ctx android.ModuleContext, mainSrc android.Path, deps []RustRlibDep,
+		outputFile android.WritablePath) android.Path) = nil
 )
 
 func PwdPrefix() string {
@@ -390,6 +400,7 @@
 	gcovCoverage  bool
 	sAbiDump      bool
 	emitXrefs     bool
+	clangVerify   bool
 
 	assemblerWithCpp bool // True if .s files should be processed with the c preprocessor.
 
@@ -474,7 +485,7 @@
 		coverageFiles = make(android.Paths, 0, len(srcFiles))
 	}
 	var kytheFiles android.Paths
-	if flags.emitXrefs {
+	if flags.emitXrefs && ctx.Module() == ctx.PrimaryModule() {
 		kytheFiles = make(android.Paths, 0, len(srcFiles))
 	}
 
@@ -581,6 +592,7 @@
 		var moduleToolingFlags string
 
 		var ccCmd string
+		var postCmd string
 		tidy := flags.tidy
 		coverage := flags.gcovCoverage
 		dump := flags.sAbiDump
@@ -608,6 +620,10 @@
 			ccCmd = "clang++"
 			moduleFlags = cppflags
 			moduleToolingFlags = toolingCppflags
+		case ".rs":
+			// A source provider (e.g. rust_bindgen) may provide both rs and c files.
+			// Ignore the rs files.
+			continue
 		case ".h", ".hpp":
 			ctx.PropertyErrorf("srcs", "Header file %s is not supported, instead use export_include_dirs or local_include_dirs.", srcFile)
 			continue
@@ -621,6 +637,10 @@
 
 		ccCmd = "${config.ClangBin}/" + ccCmd
 
+		if flags.clangVerify {
+			postCmd = " && touch " + objFile.String()
+		}
+
 		var implicitOutputs android.WritablePaths
 		if coverage {
 			gcnoFile := android.ObjPathWithExt(ctx, subdir, srcFile, "gcno")
@@ -637,13 +657,14 @@
 			Implicits:       cFlagsDeps,
 			OrderOnly:       pathDeps,
 			Args: map[string]string{
-				"cFlags": shareFlags("cFlags", moduleFlags),
-				"ccCmd":  ccCmd, // short and not shared
+				"cFlags":  shareFlags("cFlags", moduleFlags),
+				"ccCmd":   ccCmd, // short and not shared
+				"postCmd": postCmd,
 			},
 		})
 
 		// Register post-process build statements (such as for tidy or kythe).
-		if emitXref {
+		if emitXref && ctx.Module() == ctx.PrimaryModule() {
 			kytheFile := android.ObjPathWithExt(ctx, subdir, srcFile, "kzip")
 			ctx.Build(pctx, android.BuildParams{
 				Rule:        kytheExtract,
@@ -774,6 +795,51 @@
 	}
 }
 
+// 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 {
+	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).
+		panic(fmt.Errorf(
+			"TransformRlibstoStaticlib is not set and rust deps are defined in static_libs for %s",
+			ctx.ModuleName()))
+
+	} else if len(rlibDeps) == 0 {
+		return nil
+	}
+
+	output := android.PathForModuleOut(ctx, "generated_rust_staticlib", "lib"+ctx.ModuleName()+"_rust_staticlib.a")
+	stemFile := output.ReplaceExtension(ctx, "rs")
+	crateNames := []string{}
+
+	// Collect crate names
+	for _, lib := range rlibDeps {
+		// Exclude libstd so this can support no_std builds.
+		if lib.CrateName != "libstd" {
+			crateNames = append(crateNames, lib.CrateName)
+		}
+	}
+
+	// Deduplicate any crateNames just to be safe
+	crateNames = android.FirstUniqueStrings(crateNames)
+
+	// Write the source file
+	android.WriteFileRule(ctx, stemFile, genRustStaticlibSrcFile(crateNames))
+
+	return TransformRlibstoStaticlib(ctx, stemFile, rlibDeps, output)
+}
+
+func genRustStaticlibSrcFile(crateNames []string) string {
+	lines := []string{
+		"// @Soong generated Source",
+		"#![no_std]", // pre-emptively set no_std to support both std and no_std.
+	}
+	for _, crate := range crateNames {
+		lines = append(lines, fmt.Sprintf("extern crate %s;", crate))
+	}
+	return strings.Join(lines, "\n")
+}
+
 // 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,
@@ -855,8 +921,8 @@
 // into a single .ldump sAbi dump file
 func transformDumpToLinkedDump(ctx android.ModuleContext, sAbiDumps android.Paths, soFile android.Path,
 	baseName string, exportedIncludeDirs []string, symbolFile android.OptionalPath,
-	excludedSymbolVersions, excludedSymbolTags []string,
-	api string) android.Path {
+	excludedSymbolVersions, excludedSymbolTags, includedSymbolTags []string,
+	api string, isLlndk bool) android.Path {
 
 	outputFile := android.PathForModuleOut(ctx, baseName+".lsdump")
 
@@ -874,6 +940,12 @@
 	for _, tag := range excludedSymbolTags {
 		symbolFilterStr += " --exclude-symbol-tag " + tag
 	}
+	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 e3954d7..64b2465 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -48,11 +48,10 @@
 	ctx.RegisterModuleType("cc_defaults", defaultsFactory)
 
 	ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
-		ctx.BottomUp("sdk", sdkMutator).Parallel()
-		ctx.BottomUp("vndk", VndkMutator).Parallel()
-		ctx.BottomUp("link", LinkageMutator).Parallel()
-		ctx.BottomUp("test_per_src", TestPerSrcMutator).Parallel()
-		ctx.BottomUp("version", versionMutator).Parallel()
+		ctx.Transition("sdk", &sdkTransitionMutator{})
+		ctx.BottomUp("llndk", llndkMutator).Parallel()
+		ctx.Transition("link", &linkageTransitionMutator{})
+		ctx.Transition("version", &versionTransitionMutator{})
 		ctx.BottomUp("begin", BeginMutator).Parallel()
 	})
 
@@ -144,6 +143,17 @@
 	LlndkHeaderLibs []string
 }
 
+// A struct which to collect flags for rlib dependencies
+type RustRlibDep struct {
+	LibPath   android.Path // path to the rlib
+	LinkDirs  []string     // flags required for dependency (e.g. -L flags)
+	CrateName string       // crateNames associated with rlibDeps
+}
+
+func EqRustRlibDeps(a RustRlibDep, b RustRlibDep) bool {
+	return a.LibPath == b.LibPath
+}
+
 // PathDeps is a struct containing file paths to dependencies of a module.
 // It's constructed in depsToPath() by traversing the direct dependencies of the current module.
 // It's used to construct flags for various build statements (such as for compiling and linking).
@@ -156,6 +166,8 @@
 	SharedLibsDeps, EarlySharedLibsDeps, LateSharedLibsDeps android.Paths
 	// Paths to .a files
 	StaticLibs, LateStaticLibs, WholeStaticLibs android.Paths
+	// Paths and crateNames for RustStaticLib dependencies
+	RustRlibDeps []RustRlibDep
 
 	// Transitive static library dependencies of static libraries for use in ordering.
 	TranstiveStaticLibrariesForOrdering *android.DepSet[android.Path]
@@ -184,6 +196,7 @@
 	ReexportedFlags            []string
 	ReexportedGeneratedHeaders android.Paths
 	ReexportedDeps             android.Paths
+	ReexportedRustRlibDeps     []RustRlibDep
 
 	// Paths to crt*.o files
 	CrtBegin, CrtEnd android.Paths
@@ -244,6 +257,7 @@
 	GcovCoverage  bool // True if coverage files should be generated.
 	SAbiDump      bool // True if header abi dumps should be generated.
 	EmitXrefs     bool // If true, generate Ninja rules to generate emitXrefs input files for Kythe
+	ClangVerify   bool // If true, append cflags "-Xclang -verify" and append "&& touch $out" to the clang command line.
 
 	// The instruction set required for clang ("arm" or "thumb").
 	RequiredInstructionSet string
@@ -269,6 +283,11 @@
 	// Deprecated. true is the default, false is invalid.
 	Clang *bool `android:"arch_variant"`
 
+	// Aggresively trade performance for smaller binary size.
+	// This should only be used for on-device binaries that are rarely executed and not
+	// performance critical.
+	Optimize_for_size *bool `android:"arch_variant"`
+
 	// 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
@@ -297,6 +316,7 @@
 
 	AndroidMkSharedLibs       []string `blueprint:"mutated"`
 	AndroidMkStaticLibs       []string `blueprint:"mutated"`
+	AndroidMkRlibs            []string `blueprint:"mutated"`
 	AndroidMkRuntimeLibs      []string `blueprint:"mutated"`
 	AndroidMkWholeStaticLibs  []string `blueprint:"mutated"`
 	AndroidMkHeaderLibs       []string `blueprint:"mutated"`
@@ -319,7 +339,7 @@
 
 	// *.logtags files, to combine together in order to generate the /system/etc/event-log-tags
 	// file
-	Logtags []string
+	Logtags []string `android:"path"`
 
 	// Make this module available when building for ramdisk.
 	// On device without a dedicated recovery partition, the module is only
@@ -339,6 +359,8 @@
 	Recovery_available *bool
 
 	// Used by imageMutator, set by ImageMutatorBegin()
+	VendorVariantNeeded        bool `blueprint:"mutated"`
+	ProductVariantNeeded       bool `blueprint:"mutated"`
 	CoreVariantNeeded          bool `blueprint:"mutated"`
 	RamdiskVariantNeeded       bool `blueprint:"mutated"`
 	VendorRamdiskVariantNeeded bool `blueprint:"mutated"`
@@ -352,9 +374,9 @@
 	// for building binaries that are started before APEXes are activated.
 	Bootstrap *bool
 
-	// Even if DeviceConfig().VndkUseCoreVariant() is set, this module must use vendor variant.
-	// see soong/cc/config/vndk.go
-	MustUseVendorVariant bool `blueprint:"mutated"`
+	// Allows this module to be included in CMake release snapshots to be built outside of Android
+	// build system and source tree.
+	Cmake_snapshot_supported *bool
 
 	Installable *bool `android:"arch_variant"`
 
@@ -457,23 +479,6 @@
 	// IsLLNDK is set to true for the vendor variant of a cc_library module that has LLNDK stubs.
 	IsLLNDK bool `blueprint:"mutated"`
 
-	// IsVNDKUsingCoreVariant is true for VNDK modules if the global VndkUseCoreVariant option is
-	// set and the module is not listed in VndkMustUseVendorVariantList.
-	IsVNDKUsingCoreVariant bool `blueprint:"mutated"`
-
-	// IsVNDKCore is set if a VNDK module does not set the vndk.support_system_process property.
-	IsVNDKCore bool `blueprint:"mutated"`
-
-	// IsVNDKSP is set if a VNDK module sets the vndk.support_system_process property.
-	IsVNDKSP bool `blueprint:"mutated"`
-
-	// IsVNDKPrivate is set if a VNDK module sets the vndk.private property or an LLNDK
-	// module sets the llndk.private property.
-	IsVNDKPrivate bool `blueprint:"mutated"`
-
-	// IsVNDKProduct is set if a VNDK module sets the product_available property.
-	IsVNDKProduct bool `blueprint:"mutated"`
-
 	// IsVendorPublicLibrary is set for the core and product variants of a library that has
 	// vendor_public_library stubs.
 	IsVendorPublicLibrary bool `blueprint:"mutated"`
@@ -500,12 +505,7 @@
 	useVndk() bool
 	isNdk(config android.Config) bool
 	IsLlndk() bool
-	IsLlndkPublic() bool
 	isImplementationForLLNDKPublic() bool
-	IsVndkPrivate() bool
-	isVndk() bool
-	isVndkSp() bool
-	IsVndkExt() bool
 	IsVendorPublicLibrary() bool
 	inProduct() bool
 	inVendor() bool
@@ -515,7 +515,6 @@
 	InVendorOrProduct() bool
 	selectedStl() string
 	baseModuleName() string
-	getVndkExtendsModuleName() string
 	isAfdoCompile(ctx ModuleContext) bool
 	isOrderfileCompile() bool
 	isCfi() bool
@@ -526,13 +525,13 @@
 	apexVariationName() string
 	apexSdkVersion() android.ApiLevel
 	bootstrap() bool
-	mustUseVendorVariant() bool
 	nativeCoverage() bool
 	directlyInAnyApex() bool
 	isPreventInstall() bool
 	isCfiAssemblySupportEnabled() bool
 	getSharedFlags() *SharedFlags
 	notInPlatform() bool
+	optimizeForSize() bool
 }
 
 type SharedFlags struct {
@@ -587,6 +586,7 @@
 	compilerDeps(ctx DepsContext, deps Deps) Deps
 	compilerFlags(ctx ModuleContext, flags Flags, deps PathDeps) Flags
 	compilerProps() []interface{}
+	baseCompilerProps() BaseCompilerProperties
 
 	appendCflags([]string)
 	appendAsflags([]string)
@@ -601,6 +601,7 @@
 	linkerDeps(ctx DepsContext, deps Deps) Deps
 	linkerFlags(ctx ModuleContext, flags Flags) Flags
 	linkerProps() []interface{}
+	baseLinkerProps() BaseLinkerProperties
 	useClangLld(actx ModuleContext) bool
 
 	link(ctx ModuleContext, flags Flags, deps PathDeps, objs Objects) android.Path
@@ -653,6 +654,7 @@
 	headerLibraryDependency = iota
 	sharedLibraryDependency
 	staticLibraryDependency
+	rlibLibraryDependency
 )
 
 func (k libraryDependencyKind) String() string {
@@ -663,6 +665,8 @@
 		return "sharedLibraryDependency"
 	case staticLibraryDependency:
 		return "staticLibraryDependency"
+	case rlibLibraryDependency:
+		return "rlibLibraryDependency"
 	default:
 		panic(fmt.Errorf("unknown libraryDependencyKind %d", k))
 	}
@@ -794,7 +798,6 @@
 	dataLibDepTag         = dependencyTag{name: "data lib"}
 	dataBinDepTag         = dependencyTag{name: "data bin"}
 	runtimeDepTag         = installDependencyTag{name: "runtime lib"}
-	testPerSrcDepTag      = dependencyTag{name: "test_per_src"}
 	stubImplDepTag        = dependencyTag{name: "stub_impl"}
 	JniFuzzLibTag         = dependencyTag{name: "jni_fuzz_lib_tag"}
 	FdoProfileTag         = dependencyTag{name: "fdo_profile"}
@@ -821,11 +824,6 @@
 	return depTag == runtimeDepTag
 }
 
-func IsTestPerSrcDepTag(depTag blueprint.DependencyTag) bool {
-	ccDepTag, ok := depTag.(dependencyTag)
-	return ok && ccDepTag == testPerSrcDepTag
-}
-
 // 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",
@@ -871,7 +869,6 @@
 	coverage  *coverage
 	fuzzer    *fuzzer
 	sabi      *sabi
-	vndkdep   *vndkdep
 	lto       *lto
 	afdo      *afdo
 	orderfile *orderfile
@@ -906,8 +903,9 @@
 
 	hideApexVariantFromMake bool
 
-	// Aconfig files for all transitive deps.  Also exposed via TransitiveDeclarationsInfo
-	mergedAconfigFiles map[string]android.Paths
+	logtagsPaths android.Paths
+
+	WholeRustStaticlib bool
 }
 
 func (c *Module) AddJSONData(d *map[string]interface{}) {
@@ -946,12 +944,7 @@
 		"InstallInVendorRamdisk": c.InstallInVendorRamdisk(),
 		"InstallInRecovery":      c.InstallInRecovery(),
 		"InstallInRoot":          c.InstallInRoot(),
-		"IsVndk":                 c.IsVndk(),
-		"IsVndkExt":              c.IsVndkExt(),
-		"IsVndkPrivate":          c.IsVndkPrivate(),
-		"IsVndkSp":               c.IsVndkSp(),
 		"IsLlndk":                c.IsLlndk(),
-		"IsLlndkPublic":          c.IsLlndkPublic(),
 		"IsVendorPublicLibrary":  c.IsVendorPublicLibrary(),
 		"ApexSdkVersion":         c.apexSdkVersion,
 		"TestFor":                c.TestFor(),
@@ -963,6 +956,7 @@
 		"WinMsgSrcs":             hasWinMsg,
 		"YaccSrsc":               hasYacc,
 		"OnlyCSrcs":              !(hasAidl || hasLex || hasProto || hasRenderscript || hasSysprop || hasWinMsg || hasYacc),
+		"OptimizeForSize":        c.OptimizeForSize(),
 	}
 }
 
@@ -978,8 +972,8 @@
 	return c.Properties.HideFromMake
 }
 
-func (c *Module) RequiredModuleNames() []string {
-	required := android.CopyOf(c.ModuleBase.RequiredModuleNames())
+func (c *Module) RequiredModuleNames(ctx android.ConfigAndErrorContext) []string {
+	required := android.CopyOf(c.ModuleBase.RequiredModuleNames(ctx))
 	if c.ImageVariation().Variation == android.CoreVariation {
 		required = append(required, c.Properties.Target.Platform.Required...)
 		required = removeListFromList(required, c.Properties.Target.Platform.Exclude_required)
@@ -1048,6 +1042,10 @@
 	return false
 }
 
+func (c *Module) OptimizeForSize() bool {
+	return Bool(c.Properties.Optimize_for_size)
+}
+
 func (c *Module) SdkVersion() string {
 	return String(c.Properties.Sdk_version)
 }
@@ -1108,6 +1106,14 @@
 	return false
 }
 
+func (c *Module) CrateName() string {
+	panic(fmt.Errorf("CrateName called on non-Rust module: %q", c.BaseModuleName()))
+}
+
+func (c *Module) ExportedCrateLinkDirs() []string {
+	panic(fmt.Errorf("ExportedCrateLinkDirs called on non-Rust module: %q", c.BaseModuleName()))
+}
+
 func (c *Module) IsFuzzModule() bool {
 	if _, ok := c.compiler.(*fuzzBinary); ok {
 		return true
@@ -1175,6 +1181,16 @@
 	panic(fmt.Errorf("BuildSharedVariant called on non-library module: %q", c.BaseModuleName()))
 }
 
+func (c *Module) BuildRlibVariant() bool {
+	// cc modules can never build rlib variants
+	return false
+}
+
+func (c *Module) IsRustFFI() bool {
+	// cc modules are not Rust modules
+	return false
+}
+
 func (c *Module) Module() android.Module {
 	return c
 }
@@ -1248,9 +1264,6 @@
 	if c.sabi != nil {
 		c.AddProperties(c.sabi.props()...)
 	}
-	if c.vndkdep != nil {
-		c.AddProperties(c.vndkdep.props()...)
-	}
 	if c.lto != nil {
 		c.AddProperties(c.lto.props()...)
 	}
@@ -1305,10 +1318,6 @@
 	return c.VendorProperties.IsLLNDK
 }
 
-func (c *Module) IsLlndkPublic() bool {
-	return c.VendorProperties.IsLLNDK && !c.VendorProperties.IsVNDKPrivate
-}
-
 func (m *Module) NeedsLlndkVariants() bool {
 	lib := moduleLibraryInterface(m)
 	return lib != nil && (lib.hasLLNDKStubs() || lib.hasLLNDKHeaders())
@@ -1355,31 +1364,6 @@
 		!Bool(library.Properties.Llndk.Private)
 }
 
-// Returns true for LLNDK-private, VNDK-SP-private, and VNDK-core-private.
-func (c *Module) IsVndkPrivate() bool {
-	// Check if VNDK-core-private or VNDK-SP-private
-	if c.IsVndk() {
-		return Bool(c.vndkdep.Properties.Vndk.Private)
-	}
-
-	// Check if LLNDK-private
-	if library, ok := c.library.(*libraryDecorator); ok && c.IsLlndk() {
-		return Bool(library.Properties.Llndk.Private)
-	}
-
-	return false
-}
-
-// IsVndk() returns true if this module has a vndk variant.
-// Note that IsVndk() returns true for all variants of vndk-enabled libraries. Not only vendor variant,
-// but also platform and product variants of vndk-enabled libraries return true for IsVndk().
-func (c *Module) IsVndk() bool {
-	if vndkdep := c.vndkdep; vndkdep != nil {
-		return vndkdep.isVndk()
-	}
-	return false
-}
-
 func (c *Module) isAfdoCompile(ctx ModuleContext) bool {
 	if afdo := c.afdo; afdo != nil {
 		return afdo.isAfdoCompile(ctx)
@@ -1395,17 +1379,11 @@
 }
 
 func (c *Module) isCfi() bool {
-	if sanitize := c.sanitize; sanitize != nil {
-		return Bool(sanitize.Properties.SanitizeMutated.Cfi)
-	}
-	return false
+	return c.sanitize.isSanitizerEnabled(cfi)
 }
 
 func (c *Module) isFuzzer() bool {
-	if sanitize := c.sanitize; sanitize != nil {
-		return Bool(sanitize.Properties.SanitizeMutated.Fuzzer)
-	}
-	return false
+	return c.sanitize.isSanitizerEnabled(Fuzzer)
 }
 
 func (c *Module) isNDKStubLibrary() bool {
@@ -1415,35 +1393,10 @@
 	return false
 }
 
-func (c *Module) IsVndkSp() bool {
-	if vndkdep := c.vndkdep; vndkdep != nil {
-		return vndkdep.isVndkSp()
-	}
-	return false
-}
-
-func (c *Module) IsVndkExt() bool {
-	if vndkdep := c.vndkdep; vndkdep != nil {
-		return vndkdep.isVndkExt()
-	}
-	return false
-}
-
 func (c *Module) SubName() string {
 	return c.Properties.SubName
 }
 
-func (c *Module) MustUseVendorVariant() bool {
-	return c.IsVndkSp() || c.Properties.MustUseVendorVariant
-}
-
-func (c *Module) getVndkExtendsModuleName() string {
-	if vndkdep := c.vndkdep; vndkdep != nil {
-		return vndkdep.getVndkExtendsModuleName()
-	}
-	return ""
-}
-
 func (c *Module) IsStubs() bool {
 	if lib := c.library; lib != nil {
 		return lib.buildStubs()
@@ -1587,6 +1540,10 @@
 	return ctx.mod.Object()
 }
 
+func (ctx *moduleContextImpl) optimizeForSize() bool {
+	return ctx.mod.OptimizeForSize()
+}
+
 func (ctx *moduleContextImpl) canUseSdk() bool {
 	return ctx.mod.canUseSdk()
 }
@@ -1597,14 +1554,6 @@
 
 func (ctx *moduleContextImpl) sdkVersion() string {
 	if ctx.ctx.Device() {
-		config := ctx.ctx.Config()
-		if !config.IsVndkDeprecated() && ctx.useVndk() {
-			vndkVer := ctx.mod.VndkVersion()
-			if inList(vndkVer, config.PlatformVersionActiveCodenames()) {
-				return "current"
-			}
-			return vndkVer
-		}
 		return String(ctx.mod.Properties.Sdk_version)
 	}
 	return ""
@@ -1621,7 +1570,7 @@
 
 	if ctx.ctx.Device() {
 		config := ctx.ctx.Config()
-		if config.IsVndkDeprecated() && ctx.inVendor() {
+		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()
@@ -1681,22 +1630,10 @@
 	return ctx.mod.IsLlndk()
 }
 
-func (ctx *moduleContextImpl) IsLlndkPublic() bool {
-	return ctx.mod.IsLlndkPublic()
-}
-
 func (ctx *moduleContextImpl) isImplementationForLLNDKPublic() bool {
 	return ctx.mod.isImplementationForLLNDKPublic()
 }
 
-func (ctx *moduleContextImpl) IsVndkPrivate() bool {
-	return ctx.mod.IsVndkPrivate()
-}
-
-func (ctx *moduleContextImpl) isVndk() bool {
-	return ctx.mod.IsVndk()
-}
-
 func (ctx *moduleContextImpl) isAfdoCompile(mctx ModuleContext) bool {
 	return ctx.mod.isAfdoCompile(mctx)
 }
@@ -1717,22 +1654,10 @@
 	return ctx.mod.isNDKStubLibrary()
 }
 
-func (ctx *moduleContextImpl) isVndkSp() bool {
-	return ctx.mod.IsVndkSp()
-}
-
-func (ctx *moduleContextImpl) IsVndkExt() bool {
-	return ctx.mod.IsVndkExt()
-}
-
 func (ctx *moduleContextImpl) IsVendorPublicLibrary() bool {
 	return ctx.mod.IsVendorPublicLibrary()
 }
 
-func (ctx *moduleContextImpl) mustUseVendorVariant() bool {
-	return ctx.mod.MustUseVendorVariant()
-}
-
 func (ctx *moduleContextImpl) selectedStl() string {
 	if stl := ctx.mod.stl; stl != nil {
 		return stl.Properties.SelectedStl
@@ -1748,10 +1673,6 @@
 	return ctx.mod.BaseModuleName()
 }
 
-func (ctx *moduleContextImpl) getVndkExtendsModuleName() string {
-	return ctx.mod.getVndkExtendsModuleName()
-}
-
 func (ctx *moduleContextImpl) isForPlatform() bool {
 	apexInfo, _ := android.ModuleProvider(ctx.ctx, android.ApexInfoProvider)
 	return apexInfo.IsForPlatform()
@@ -1816,7 +1737,6 @@
 	module.coverage = &coverage{}
 	module.fuzzer = &fuzzer{}
 	module.sabi = &sabi{}
-	module.vndkdep = &vndkdep{}
 	module.lto = &lto{}
 	module.afdo = &afdo{}
 	module.orderfile = &orderfile{}
@@ -1853,11 +1773,6 @@
 	return nil
 }
 
-func (c *Module) IsTestPerSrcAllTestsVariation() bool {
-	test, ok := c.linker.(testPerSrc)
-	return ok && test.isAllTestsVariation()
-}
-
 func (c *Module) DataPaths() []android.DataPath {
 	if p, ok := c.installer.(interface {
 		dataPaths() []android.DataPath
@@ -1952,8 +1867,10 @@
 		"libdl":         true,
 		"libz":          true,
 		// art apex
+		// TODO(b/234351700): Remove this when com.android.art.debug is gone.
 		"libandroidio":    true,
 		"libdexfile":      true,
+		"libdexfiled":     true, // com.android.art.debug only
 		"libnativebridge": true,
 		"libnativehelper": true,
 		"libnativeloader": true,
@@ -1990,13 +1907,14 @@
 	return false
 }
 
-func (d *Defaults) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	android.CollectDependencyAconfigFiles(ctx, &d.mergedAconfigFiles)
-}
-
 func (c *Module) GenerateAndroidBuildActions(actx android.ModuleContext) {
 	ctx := moduleContextFromAndroidModuleContext(actx, c)
 
+	c.logtagsPaths = android.PathsForModuleSrc(actx, c.Properties.Logtags)
+	android.SetProvider(ctx, android.LogtagsProviderKey, &android.LogtagsInfo{
+		Logtags: c.logtagsPaths,
+	})
+
 	// If Test_only is set on a module in bp file, respect the setting, otherwise
 	// see if is a known test module type.
 	testOnly := c.testModule || c.testLibrary()
@@ -2009,16 +1927,6 @@
 		TopLevelTarget: c.testModule,
 	})
 
-	// Handle the case of a test module split by `test_per_src` mutator.
-	//
-	// The `test_per_src` mutator adds an extra variation named "", depending on all the other
-	// `test_per_src` variations of the test module. Set `outputFile` to an empty path for this
-	// module and return early, as this module does not produce an output file per se.
-	if c.IsTestPerSrcAllTestsVariation() {
-		c.outputFile = android.OptionalPath{}
-		return
-	}
-
 	c.Properties.SubName = GetSubnameProperty(actx, c)
 	apexInfo, _ := android.ModuleProvider(actx, android.ApexInfoProvider)
 	if !apexInfo.IsForPlatform() {
@@ -2155,7 +2063,9 @@
 
 	android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: deps.GeneratedSources.Strings()})
 
-	android.CollectDependencyAconfigFiles(ctx, &c.mergedAconfigFiles)
+	if Bool(c.Properties.Cmake_snapshot_supported) {
+		android.SetProvider(ctx, cmakeSnapshotSourcesProvider, android.GlobFiles(ctx, ctx.ModuleDir()+"/**/*", nil))
+	}
 
 	c.maybeInstall(ctx, apexInfo)
 
@@ -2189,8 +2099,58 @@
 		if c.Properties.IsSdkVariant && c.Properties.SdkAndPlatformVariantVisibleToMake {
 			moduleInfoJSON.Uninstallable = true
 		}
-
 	}
+
+	buildComplianceMetadataInfo(ctx, c, deps)
+
+	c.setOutputFiles(ctx)
+}
+
+func (c *Module) setOutputFiles(ctx ModuleContext) {
+	if c.outputFile.Valid() {
+		ctx.SetOutputFiles(android.Paths{c.outputFile.Path()}, "")
+	} else {
+		ctx.SetOutputFiles(android.Paths{}, "")
+	}
+	if c.linker != nil {
+		ctx.SetOutputFiles(android.PathsIfNonNil(c.linker.unstrippedOutputFilePath()), "unstripped")
+		ctx.SetOutputFiles(android.PathsIfNonNil(c.linker.strippedAllOutputFilePath()), "stripped_all")
+	}
+}
+
+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.BUILT_FILES, c.outputFile.String())
+
+	// Static deps
+	staticDeps := ctx.GetDirectDepsWithTag(StaticDepTag(false))
+	staticDepNames := make([]string, 0, len(staticDeps))
+	for _, dep := range staticDeps {
+		staticDepNames = append(staticDepNames, dep.Name())
+	}
+
+	staticDepPaths := make([]string, 0, len(deps.StaticLibs))
+	for _, dep := range deps.StaticLibs {
+		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))
+	wholeStaticDepNames := make([]string, 0, len(wholeStaticDeps))
+	for _, dep := range wholeStaticDeps {
+		wholeStaticDepNames = append(wholeStaticDepNames, dep.Name())
+	}
+
+	wholeStaticDepPaths := make([]string, 0, len(deps.WholeStaticLibs))
+	for _, dep := range deps.WholeStaticLibs {
+		wholeStaticDepPaths = append(wholeStaticDepPaths, dep.String())
+	}
+	complianceMetadataInfo.SetListValue(android.ComplianceMetadataProp.WHOLE_STATIC_DEPS, android.FirstUniqueStrings(wholeStaticDepNames))
+	complianceMetadataInfo.SetListValue(android.ComplianceMetadataProp.WHOLE_STATIC_DEP_FILES, android.FirstUniqueStrings(wholeStaticDepPaths))
 }
 
 func (c *Module) maybeUnhideFromMake() {
@@ -2508,7 +2468,7 @@
 }
 
 func (c *Module) DepsMutator(actx android.BottomUpMutatorContext) {
-	if !c.Enabled() {
+	if !c.Enabled(actx) {
 		return
 	}
 
@@ -2580,7 +2540,7 @@
 	if c.ImageVariation().Variation == android.CoreVariation && c.Device() &&
 		c.Target().NativeBridge == android.NativeBridgeDisabled {
 		actx.AddVariationDependencies(
-			[]blueprint.Variation{{Mutator: "image", Variation: VendorVariation}},
+			[]blueprint.Variation{{Mutator: "image", Variation: android.VendorVariation}},
 			llndkHeaderLibTag,
 			deps.LlndkHeaderLibs...)
 	}
@@ -2594,14 +2554,15 @@
 	}
 
 	for _, lib := range deps.StaticLibs {
+		// Some dependencies listed in static_libs might actually be rust_ffi rlib variants.
 		depTag := libraryDependencyTag{Kind: staticLibraryDependency}
+
 		if inList(lib, deps.ReexportStaticLibHeaders) {
 			depTag.reexportFlags = true
 		}
 		if inList(lib, deps.ExcludeLibsForApex) {
 			depTag.excludeInApex = true
 		}
-
 		actx.AddVariationDependencies([]blueprint.Variation{
 			{Mutator: "link", Variation: "static"},
 		}, depTag, lib)
@@ -2735,15 +2696,6 @@
 		{Mutator: "link", Variation: "shared"},
 	}, ndkLateStubDepTag, apiLateNdkLibs...)
 
-	if vndkdep := c.vndkdep; vndkdep != nil {
-		if vndkdep.isVndkExt() {
-			actx.AddVariationDependencies([]blueprint.Variation{
-				c.ImageVariation(),
-				{Mutator: "link", Variation: "shared"},
-			}, vndkExtDepTag, vndkdep.getVndkExtendsModuleName())
-		}
-	}
-
 	if len(deps.AidlLibs) > 0 {
 		actx.AddDependency(
 			c,
@@ -2756,7 +2708,7 @@
 }
 
 func BeginMutator(ctx android.BottomUpMutatorContext) {
-	if c, ok := ctx.Module().(*Module); ok && c.Enabled() {
+	if c, ok := ctx.Module().(*Module); ok && c.Enabled(ctx) {
 		c.beginMutator(ctx)
 	}
 }
@@ -2781,20 +2733,6 @@
 		return
 	}
 
-	// VNDK is cc.Module supported only for now.
-	if ccFrom, ok := from.(*Module); ok && from.UseVndk() {
-		// Though allowed dependency is limited by the image mutator,
-		// each vendor and product module needs to check link-type
-		// for VNDK.
-		if ccTo, ok := to.(*Module); ok {
-			if ccFrom.vndkdep != nil {
-				ccFrom.vndkdep.vndkCheckLinkType(ctx, ccTo, tag)
-			}
-		} else if _, ok := to.(LinkableInterface); !ok {
-			ctx.ModuleErrorf("Attempting to link VNDK cc.Module with unsupported module type")
-		}
-		return
-	}
 	// TODO(b/244244438) : Remove this once all variants are implemented
 	if ccFrom, ok := from.(*Module); ok && ccFrom.isImportedApiLibrary() {
 		return
@@ -2949,7 +2887,7 @@
 			return true
 		}
 
-		if to.IsVndkSp() || to.IsLlndk() {
+		if to.IsLlndk() {
 			return false
 		}
 
@@ -3216,65 +3154,87 @@
 				default:
 					panic(fmt.Errorf("unexpected library dependency order %d", libDepTag.Order))
 				}
+
 			case libDepTag.static():
-				staticLibraryInfo, isStaticLib := android.OtherModuleProvider(ctx, dep, StaticLibraryInfoProvider)
-				if !isStaticLib {
-					if !ctx.Config().AllowMissingDependencies() {
-						ctx.ModuleErrorf("module %q is not a static library", depName)
-					} else {
-						ctx.AddMissingDependencies([]string{depName})
-					}
-					return
-				}
+				if ccDep.RustLibraryInterface() {
+					rlibDep := RustRlibDep{LibPath: linkFile.Path(), CrateName: ccDep.CrateName(), LinkDirs: ccDep.ExportedCrateLinkDirs()}
+					depPaths.RustRlibDeps = append(depPaths.RustRlibDeps, rlibDep)
+					depPaths.IncludeDirs = append(depPaths.IncludeDirs, depExporterInfo.IncludeDirs...)
+					if libDepTag.wholeStatic {
+						depPaths.ReexportedDirs = append(depPaths.ReexportedDirs, depExporterInfo.IncludeDirs...)
+						depPaths.ReexportedRustRlibDeps = append(depPaths.ReexportedRustRlibDeps, rlibDep)
 
-				// Stubs lib doesn't link to the static lib dependencies. Don't set
-				// linkFile, depFile, and ptr.
-				if c.IsStubs() {
-					break
-				}
-
-				linkFile = android.OptionalPathForPath(staticLibraryInfo.StaticLibrary)
-				if libDepTag.wholeStatic {
-					ptr = &depPaths.WholeStaticLibs
-					if len(staticLibraryInfo.Objects.objFiles) > 0 {
-						depPaths.WholeStaticLibObjs = depPaths.WholeStaticLibObjs.Append(staticLibraryInfo.Objects)
-					} else {
-						// This case normally catches prebuilt static
-						// libraries, but it can also occur when
-						// AllowMissingDependencies is on and the
-						// dependencies has no sources of its own
-						// but has a whole_static_libs dependency
-						// on a missing library.  We want to depend
-						// on the .a file so that there is something
-						// in the dependency tree that contains the
-						// error rule for the missing transitive
-						// dependency.
-						depPaths.WholeStaticLibsFromPrebuilts = append(depPaths.WholeStaticLibsFromPrebuilts, linkFile.Path())
+						// If whole_static, track this as we want to make sure that in a final linkage for a shared library,
+						// exported functions from the rust generated staticlib still exported.
+						if c.CcLibrary() && c.Shared() {
+							c.WholeRustStaticlib = true
+						}
 					}
-					depPaths.WholeStaticLibsFromPrebuilts = append(depPaths.WholeStaticLibsFromPrebuilts,
-						staticLibraryInfo.WholeStaticLibsFromPrebuilts...)
+
 				} else {
-					switch libDepTag.Order {
-					case earlyLibraryDependency:
-						panic(fmt.Errorf("early static libs not suppported"))
-					case normalLibraryDependency:
-						// static dependencies will be handled separately so they can be ordered
-						// using transitive dependencies.
-						ptr = nil
-						directStaticDeps = append(directStaticDeps, staticLibraryInfo)
-					case lateLibraryDependency:
-						ptr = &depPaths.LateStaticLibs
-					default:
-						panic(fmt.Errorf("unexpected library dependency order %d", libDepTag.Order))
+					staticLibraryInfo, isStaticLib := android.OtherModuleProvider(ctx, dep, StaticLibraryInfoProvider)
+					if !isStaticLib {
+						if !ctx.Config().AllowMissingDependencies() {
+							ctx.ModuleErrorf("module %q is not a static library", depName)
+						} else {
+							ctx.AddMissingDependencies([]string{depName})
+						}
+						return
 					}
-				}
-				if libDepTag.unexportedSymbols {
-					depPaths.LdFlags = append(depPaths.LdFlags,
-						"-Wl,--exclude-libs="+staticLibraryInfo.StaticLibrary.Base())
+
+					// Stubs lib doesn't link to the static lib dependencies. Don't set
+					// linkFile, depFile, and ptr.
+					if c.IsStubs() {
+						break
+					}
+
+					linkFile = android.OptionalPathForPath(staticLibraryInfo.StaticLibrary)
+					if libDepTag.wholeStatic {
+						ptr = &depPaths.WholeStaticLibs
+						if len(staticLibraryInfo.Objects.objFiles) > 0 {
+							depPaths.WholeStaticLibObjs = depPaths.WholeStaticLibObjs.Append(staticLibraryInfo.Objects)
+						} else {
+							// This case normally catches prebuilt static
+							// libraries, but it can also occur when
+							// AllowMissingDependencies is on and the
+							// dependencies has no sources of its own
+							// but has a whole_static_libs dependency
+							// on a missing library.  We want to depend
+							// on the .a file so that there is something
+							// in the dependency tree that contains the
+							// error rule for the missing transitive
+							// dependency.
+							depPaths.WholeStaticLibsFromPrebuilts = append(depPaths.WholeStaticLibsFromPrebuilts, linkFile.Path())
+						}
+						depPaths.WholeStaticLibsFromPrebuilts = append(depPaths.WholeStaticLibsFromPrebuilts,
+							staticLibraryInfo.WholeStaticLibsFromPrebuilts...)
+					} else {
+						switch libDepTag.Order {
+						case earlyLibraryDependency:
+							panic(fmt.Errorf("early static libs not supported"))
+						case normalLibraryDependency:
+							// static dependencies will be handled separately so they can be ordered
+							// using transitive dependencies.
+							ptr = nil
+							directStaticDeps = append(directStaticDeps, staticLibraryInfo)
+						case lateLibraryDependency:
+							ptr = &depPaths.LateStaticLibs
+						default:
+							panic(fmt.Errorf("unexpected library dependency order %d", libDepTag.Order))
+						}
+					}
+
+					// Collect any exported Rust rlib deps from static libraries which have been included as whole_static_libs
+					depPaths.RustRlibDeps = append(depPaths.RustRlibDeps, depExporterInfo.RustRlibDeps...)
+
+					if libDepTag.unexportedSymbols {
+						depPaths.LdFlags = append(depPaths.LdFlags,
+							"-Wl,--exclude-libs="+staticLibraryInfo.StaticLibrary.Base())
+					}
 				}
 			}
 
-			if libDepTag.static() && !libDepTag.wholeStatic {
+			if libDepTag.static() && !libDepTag.wholeStatic && !ccDep.RustLibraryInterface() {
 				if !ccDep.CcLibraryInterface() || !ccDep.Static() {
 					ctx.ModuleErrorf("module %q not a static library", depName)
 					return
@@ -3320,6 +3280,12 @@
 			depPaths.SystemIncludeDirs = append(depPaths.SystemIncludeDirs, depExporterInfo.SystemIncludeDirs...)
 			depPaths.GeneratedDeps = append(depPaths.GeneratedDeps, depExporterInfo.Deps...)
 			depPaths.Flags = append(depPaths.Flags, depExporterInfo.Flags...)
+			depPaths.RustRlibDeps = append(depPaths.RustRlibDeps, depExporterInfo.RustRlibDeps...)
+
+			// Only re-export RustRlibDeps for cc static libs
+			if c.static() {
+				depPaths.ReexportedRustRlibDeps = append(depPaths.ReexportedRustRlibDeps, depExporterInfo.RustRlibDeps...)
+			}
 
 			if libDepTag.reexportFlags {
 				reexportExporter(depExporterInfo)
@@ -3355,12 +3321,14 @@
 				c.Properties.AndroidMkSharedLibs = append(
 					c.Properties.AndroidMkSharedLibs, makeLibName)
 			case libDepTag.static():
-				if libDepTag.wholeStatic {
-					c.Properties.AndroidMkWholeStaticLibs = append(
-						c.Properties.AndroidMkWholeStaticLibs, makeLibName)
-				} else {
-					c.Properties.AndroidMkStaticLibs = append(
-						c.Properties.AndroidMkStaticLibs, makeLibName)
+				if !ccDep.RustLibraryInterface() {
+					if libDepTag.wholeStatic {
+						c.Properties.AndroidMkWholeStaticLibs = append(
+							c.Properties.AndroidMkWholeStaticLibs, makeLibName)
+					} else {
+						c.Properties.AndroidMkStaticLibs = append(
+							c.Properties.AndroidMkStaticLibs, makeLibName)
+					}
 				}
 			}
 		} else if !c.IsStubs() {
@@ -3392,11 +3360,14 @@
 	depPaths.IncludeDirs = android.FirstUniquePaths(depPaths.IncludeDirs)
 	depPaths.SystemIncludeDirs = android.FirstUniquePaths(depPaths.SystemIncludeDirs)
 	depPaths.GeneratedDeps = android.FirstUniquePaths(depPaths.GeneratedDeps)
+	depPaths.RustRlibDeps = android.FirstUniqueFunc(depPaths.RustRlibDeps, EqRustRlibDeps)
+
 	depPaths.ReexportedDirs = android.FirstUniquePaths(depPaths.ReexportedDirs)
 	depPaths.ReexportedSystemDirs = android.FirstUniquePaths(depPaths.ReexportedSystemDirs)
 	depPaths.ReexportedFlags = android.FirstUniqueStrings(depPaths.ReexportedFlags)
 	depPaths.ReexportedDeps = android.FirstUniquePaths(depPaths.ReexportedDeps)
 	depPaths.ReexportedGeneratedHeaders = android.FirstUniquePaths(depPaths.ReexportedGeneratedHeaders)
+	depPaths.ReexportedRustRlibDeps = android.FirstUniqueFunc(depPaths.ReexportedRustRlibDeps, EqRustRlibDeps)
 
 	if c.sabi != nil {
 		c.sabi.Properties.ReexportedIncludes = android.FirstUniqueStrings(c.sabi.Properties.ReexportedIncludes)
@@ -3573,12 +3544,7 @@
 		}
 	}
 
-	if ctx.DeviceConfig().VndkUseCoreVariant() && ccDep.IsVndk() && !ccDep.MustUseVendorVariant() &&
-		!c.InRamdisk() && !c.InVendorRamdisk() && !c.InRecovery() {
-		// The vendor module is a no-vendor-variant VNDK library.  Depend on the
-		// core module instead.
-		return libName
-	} else if ccDep.InVendorOrProduct() && nonSystemVariantsExist {
+	if ccDep.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()
@@ -3643,28 +3609,6 @@
 	return c.outputFile
 }
 
-func (c *Module) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		if c.outputFile.Valid() {
-			return android.Paths{c.outputFile.Path()}, nil
-		}
-		return android.Paths{}, nil
-	case "unstripped":
-		if c.linker != nil {
-			return android.PathsIfNonNil(c.linker.unstrippedOutputFilePath()), nil
-		}
-		return nil, nil
-	case "stripped_all":
-		if c.linker != nil {
-			return android.PathsIfNonNil(c.linker.strippedAllOutputFilePath()), nil
-		}
-		return nil, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
 func (c *Module) static() bool {
 	if static, ok := c.linker.(interface {
 		static() bool
@@ -3765,15 +3709,6 @@
 func GetMakeLinkType(actx android.ModuleContext, c LinkableInterface) string {
 	if c.InVendorOrProduct() {
 		if c.IsLlndk() {
-			if !c.IsLlndkPublic() {
-				return "native:vndk_private"
-			}
-			return "native:vndk"
-		}
-		if c.IsVndk() && !c.IsVndkExt() {
-			if c.IsVndkPrivate() {
-				return "native:vndk_private"
-			}
 			return "native:vndk"
 		}
 		if c.InProduct() {
@@ -3791,22 +3726,18 @@
 		// TODO(b/114741097): use the correct ndk stl once build errors have been fixed
 		//family, link := getNdkStlFamilyAndLinkType(c)
 		//return fmt.Sprintf("native:ndk:%s:%s", family, link)
-	} else if actx.DeviceConfig().VndkUseCoreVariant() && !c.MustUseVendorVariant() {
-		return "native:platform_vndk"
 	} else {
 		return "native:platform"
 	}
 }
 
 // Overrides ApexModule.IsInstallabeToApex()
-// Only shared/runtime libraries and "test_per_src" tests are installable to APEX.
+// Only shared/runtime libraries .
 func (c *Module) IsInstallableToApex() bool {
 	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()
-	} else if _, ok := c.linker.(testPerSrc); ok {
-		return true
 	}
 	return false
 }
@@ -3977,15 +3908,6 @@
 	return c.IsStubs() || c.Target().NativeBridge == android.NativeBridgeEnabled
 }
 
-// Overrides android.ApexModuleBase.UniqueApexVariations
-func (c *Module) UniqueApexVariations() bool {
-	// When a vendor APEX needs a VNDK lib in it (use_vndk_as_stable: false), it should be a unique
-	// APEX variation. Otherwise, another vendor APEX with use_vndk_as_stable:true may use a wrong
-	// variation of the VNDK lib because APEX variations are merged/grouped.
-	// TODO(b/274401041) Find a way to merge APEX variations for vendor apexes.
-	return c.UseVndk() && c.IsVndk()
-}
-
 func (c *Module) overriddenModules() []string {
 	if o, ok := c.linker.(overridable); ok {
 		return o.overriddenModules()
@@ -4055,9 +3977,6 @@
 	android.ModuleBase
 	android.DefaultsModuleBase
 	android.ApexModuleBase
-
-	// Aconfig files for all transitive deps.  Also exposed via TransitiveDeclarationsInfo
-	mergedAconfigFiles map[string]android.Paths
 }
 
 // cc_defaults provides a set of properties that can be inherited by other cc
@@ -4096,7 +4015,6 @@
 		&TidyProperties{},
 		&CoverageProperties{},
 		&SAbiProperties{},
-		&VndkProperties{},
 		&LTOProperties{},
 		&AfdoProperties{},
 		&OrderfileProperties{},
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 3d75bf5..ccdaae5 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -300,13 +300,9 @@
 	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 
 	ctx := testCcWithConfig(t, config)
-	module := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon").Module()
-	testBinary := module.(*Module).linker.(*testBinary)
-	outputFiles, err := module.(android.OutputFileProducer).OutputFiles("")
-	if err != nil {
-		t.Errorf("Expected cc_test to produce output files, error: %s", err)
-		return
-	}
+	testingModule := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon")
+	testBinary := testingModule.Module().(*Module).linker.(*testBinary)
+	outputFiles := testingModule.OutputFiles(t, "")
 	if len(outputFiles) != 1 {
 		t.Errorf("expected exactly one output file. output files: [%s]", outputFiles)
 		return
@@ -356,12 +352,10 @@
 	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 
 	ctx := testCcWithConfig(t, config)
-	module := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon").Module()
+	testingModule := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon")
+	module := testingModule.Module()
 	testBinary := module.(*Module).linker.(*testBinary)
-	outputFiles, err := module.(android.OutputFileProducer).OutputFiles("")
-	if err != nil {
-		t.Fatalf("Expected cc_test to produce output files, error: %s", err)
-	}
+	outputFiles := testingModule.OutputFiles(t, "")
 	if len(outputFiles) != 1 {
 		t.Fatalf("expected exactly one output file. output files: [%s]", outputFiles)
 	}
@@ -658,7 +652,7 @@
 	}{
 		{vendorVariant, "libvendor", "native:vendor"},
 		{vendorVariant, "libllndk", "native:vndk"},
-		{vendorVariant27, "prevndk.vndk.27.arm.binder32", "native:vndk"},
+		{vendorVariant27, "prevndk.vndk.27.arm.binder32", "native:vendor"},
 		{coreVariant, "libllndk", "native:platform"},
 	}
 	for _, test := range tests {
@@ -1407,12 +1401,10 @@
 	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 
 	ctx := testCcWithConfig(t, config)
-	module := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon").Module()
+	testingModule := ctx.ModuleForTests("main_test", "android_arm_armv7-a-neon")
+	module := testingModule.Module()
 	testBinary := module.(*Module).linker.(*testBinary)
-	outputFiles, err := module.(android.OutputFileProducer).OutputFiles("")
-	if err != nil {
-		t.Fatalf("Expected cc_test to produce output files, error: %s", err)
-	}
+	outputFiles := testingModule.OutputFiles(t, "")
 	if len(outputFiles) != 1 {
 		t.Errorf("expected exactly one output file. output files: [%s]", outputFiles)
 	}
@@ -2908,8 +2900,6 @@
 				PrepareForIntegrationTestWithCc,
 				android.FixtureAddTextFile("external/foo/Android.bp", bp),
 			).RunTest(t)
-			// Use the arm variant instead of the arm64 variant so that it gets headers from
-			// ndk_libandroid_support to test LateStaticLibs.
 			cflags := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_sdk_static").Output("obj/external/foo/foo.o").Args["cFlags"]
 
 			var includes []string
@@ -3120,12 +3110,8 @@
  `
 	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	ctx := testCcWithConfig(t, config)
-	module := ctx.ModuleForTests("test_lib", "android_arm_armv7-a-neon_shared").Module()
-	outputFile, err := module.(android.OutputFileProducer).OutputFiles("stripped_all")
-	if err != nil {
-		t.Errorf("Expected cc_library to produce output files, error: %s", err)
-		return
-	}
+	testingModule := ctx.ModuleForTests("test_lib", "android_arm_armv7-a-neon_shared")
+	outputFile := testingModule.OutputFiles(t, "stripped_all")
 	if !strings.HasSuffix(outputFile.Strings()[0], "/stripped_all/test_lib.so") {
 		t.Errorf("Unexpected output file: %s", outputFile.Strings()[0])
 		return
@@ -3218,3 +3204,32 @@
 	testSdkVersionFlag("libfoo", "30")
 	testSdkVersionFlag("libbar", "29")
 }
+
+func TestClangVerify(t *testing.T) {
+	t.Parallel()
+
+	ctx := testCc(t, `
+		cc_library {
+			name: "lib_no_clang_verify",
+			srcs: ["libnocv.cc"],
+		}
+
+		cc_library {
+			name: "lib_clang_verify",
+			srcs: ["libcv.cc"],
+			clang_verify: true,
+		}
+	`)
+
+	module := ctx.ModuleForTests("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"]
+	if strings.Contains(cFlags_cv, "-Xclang") && strings.Contains(cFlags_cv, "-verify") {
+		t.Errorf("expected %q in cflags, got %q", "-Xclang -verify", cFlags_cv)
+	}
+}
diff --git a/cc/cc_test_only_property_test.go b/cc/cc_test_only_property_test.go
index c14f34e..972e86b 100644
--- a/cc/cc_test_only_property_test.go
+++ b/cc/cc_test_only_property_test.go
@@ -78,38 +78,6 @@
 	}
 }
 
-func TestTestOnlyValueWithTestPerSrcProp(t *testing.T) {
-	t.Parallel()
-	ctx := android.GroupFixturePreparers(
-		prepareForCcTest,
-	).RunTestWithBp(t, `
-                // These should be test-only
-                cc_test { name: "cc-test",
-                          gtest: false,
-                          test_per_src: true,
-                          srcs: ["foo_test.cpp"],
-                          test_options: { unit_test: false, },
-                         }
-	`)
-
-	// Ensure all variation of test-per-src tests are marked test-only.
-	ctx.VisitAllModules(func(m blueprint.Module) {
-		testOnly := false
-		if provider, ok := android.OtherModuleProvider(ctx.TestContext.OtherModuleProviderAdaptor(), m, android.TestOnlyProviderKey); ok {
-			if provider.TestOnly {
-				testOnly = true
-			}
-		}
-		if module, ok := m.(*Module); ok {
-			if testModule, ok := module.installer.(*testBinary); ok {
-				if !testOnly && *testModule.Properties.Test_per_src {
-					t.Errorf("%v is not test-only but should be", m)
-				}
-			}
-		}
-	})
-}
-
 func TestTestOnlyInTeamsProto(t *testing.T) {
 	t.Parallel()
 	ctx := android.GroupFixturePreparers(
diff --git a/cc/ccdeps.go b/cc/ccdeps.go
index d30abba..469fe31 100644
--- a/cc/ccdeps.go
+++ b/cc/ccdeps.go
@@ -85,9 +85,8 @@
 	moduleDeps := ccDeps{}
 	moduleInfos := map[string]ccIdeInfo{}
 
-	// Track which projects have already had CMakeLists.txt generated to keep the first
-	// variant for each project.
-	seenProjects := map[string]bool{}
+	// Track if best variant (device arch match) has been found.
+	bestVariantFound := map[string]bool{}
 
 	pathToCC, _ := evalVariable(ctx, "${config.ClangBin}/")
 	moduleDeps.C_clang = fmt.Sprintf("%s%s", buildCMakePath(pathToCC), cClang)
@@ -96,7 +95,7 @@
 	ctx.VisitAllModules(func(module android.Module) {
 		if ccModule, ok := module.(*Module); ok {
 			if compiledModule, ok := ccModule.compiler.(CompiledInterface); ok {
-				generateCLionProjectData(ctx, compiledModule, ccModule, seenProjects, moduleInfos)
+				generateCLionProjectData(ctx, compiledModule, ccModule, bestVariantFound, moduleInfos)
 			}
 		}
 	})
@@ -180,26 +179,30 @@
 }
 
 func generateCLionProjectData(ctx android.SingletonContext, compiledModule CompiledInterface,
-	ccModule *Module, seenProjects map[string]bool, moduleInfos map[string]ccIdeInfo) {
+	ccModule *Module, bestVariantFound map[string]bool, moduleInfos map[string]ccIdeInfo) {
+	moduleName := ccModule.ModuleBase.Name()
 	srcs := compiledModule.Srcs()
+
+	// Skip if best variant has already been found.
+	if bestVariantFound[moduleName] {
+		return
+	}
+
+	// Skip if sources are empty.
 	if len(srcs) == 0 {
 		return
 	}
 
-	// Only keep the DeviceArch variant module.
-	if ctx.DeviceConfig().DeviceArch() != ccModule.ModuleBase.Arch().ArchType.Name {
+	// Check if device arch matches, in which case this is the best variant and takes precedence.
+	if ccModule.Device() && ccModule.ModuleBase.Arch().ArchType.Name == ctx.DeviceConfig().DeviceArch() {
+		bestVariantFound[moduleName] = true
+	} else if _, ok := moduleInfos[moduleName]; ok {
+		// Skip because this isn't the best variant and a previous one has already been added.
+		// Heuristically, ones that appear first are likely to be more relevant.
 		return
 	}
 
-	clionProjectLocation := getCMakeListsForModule(ccModule, ctx)
-	if seenProjects[clionProjectLocation] {
-		return
-	}
-
-	seenProjects[clionProjectLocation] = true
-
-	name := ccModule.ModuleBase.Name()
-	dpInfo := moduleInfos[name]
+	dpInfo := ccIdeInfo{}
 
 	dpInfo.Path = append(dpInfo.Path, path.Dir(ctx.BlueprintFile(ccModule)))
 	dpInfo.Srcs = append(dpInfo.Srcs, srcs.Strings()...)
@@ -216,9 +219,9 @@
 	dpInfo.Local_Cpp_flags = parseCompilerCCParameters(ctx, ccModule.flags.Local.CppFlags)
 	dpInfo.System_include_flags = parseCompilerCCParameters(ctx, ccModule.flags.SystemIncludeFlags)
 
-	dpInfo.Module_name = name
+	dpInfo.Module_name = moduleName
 
-	moduleInfos[name] = dpInfo
+	moduleInfos[moduleName] = dpInfo
 }
 
 type Deal struct {
diff --git a/cc/check.go b/cc/check.go
index 32d1f06..e3af3b2 100644
--- a/cc/check.go
+++ b/cc/check.go
@@ -45,7 +45,8 @@
 				ctx.PropertyErrorf(prop, "-Weverything is not allowed in Android.bp files.  "+
 					"Build with `m ANDROID_TEMPORARILY_ALLOW_WEVERYTHING=true` to experiment locally with -Weverything.")
 			}
-		} else if strings.HasPrefix(flag, "-target") || strings.HasPrefix(flag, "--target") {
+		} else if strings.HasPrefix(flag, "-target ") || strings.HasPrefix(flag, "--target ") ||
+			strings.HasPrefix(flag, "-target=") || strings.HasPrefix(flag, "--target=") {
 			ctx.PropertyErrorf(prop, "Bad flag: `%s`, use the correct target soong rule.", flag)
 		} else if strings.Contains(flag, " ") {
 			args := strings.Split(flag, " ")
@@ -63,6 +64,10 @@
 				if len(args) > 2 {
 					ctx.PropertyErrorf(prop, "`-mllvm` only takes one argument: `%s`", flag)
 				}
+			} else if args[0] == "-Xclang" {
+				if len(args) > 2 {
+					ctx.PropertyErrorf(prop, "`-Xclang` only takes one argument: `%s`", flag)
+				}
 			} else if strings.HasPrefix(flag, "-D") && strings.Contains(flag, "=") {
 				// Do nothing in this case.
 				// For now, we allow space characters in -DNAME=def form to allow use cases
diff --git a/cc/cmake_ext_add_aidl_library.txt b/cc/cmake_ext_add_aidl_library.txt
new file mode 100644
index 0000000..d5c134e
--- /dev/null
+++ b/cc/cmake_ext_add_aidl_library.txt
@@ -0,0 +1,77 @@
+if ("${CMAKE_HOST_SYSTEM_PROCESSOR}" MATCHES "^(arm|aarch)")
+    set(PREBUILTS_BIN_DIR "${CMAKE_CURRENT_SOURCE_DIR}/prebuilts/host/linux_musl-arm64/bin")
+else()
+    set(PREBUILTS_BIN_DIR "${CMAKE_CURRENT_SOURCE_DIR}/prebuilts/host/linux-x86/bin")
+endif()
+if (NOT AIDL_BIN)
+    find_program(AIDL_BIN aidl REQUIRED HINTS "${PREBUILTS_BIN_DIR}")
+endif()
+
+function(add_aidl_library NAME LANG AIDLROOT SOURCES AIDLFLAGS)
+    if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.20")
+        cmake_policy(SET CMP0116 NEW)
+    endif()
+
+    # Strip trailing slash
+    get_filename_component(AIDLROOT_TRAILING "${AIDLROOT}" NAME)
+    if ("${AIDLROOT_TRAILING}" STREQUAL "")
+        get_filename_component(AIDLROOT "${AIDLROOT}foo" DIRECTORY)
+    endif()
+
+    set(GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}/.intermediates/${NAME}-source")
+    set(GEN_SOURCES)
+    foreach (SOURCE ${SOURCES})
+        set(SOURCE_FULL ${AIDLROOT}/${SOURCE})
+        get_filename_component(SOURCE_WLE ${SOURCE} NAME_WLE)
+        get_filename_component(SOURCE_SUBDIR ${SOURCE} DIRECTORY)
+        set(GEN_SOURCE "${GEN_DIR}/${SOURCE_SUBDIR}/${SOURCE_WLE}.cpp")
+
+        file(READ "${SOURCE}" SOURCE_CONTENTS)
+        string(FIND "${SOURCE_CONTENTS}" "@VintfStability" VINTF_MATCH)
+        set(STABILITY_FLAG)
+        if (${VINTF_MATCH} GREATER_EQUAL 0)
+            set(STABILITY_FLAG --stability vintf)
+        endif()
+
+        set(DEPFILE_ARG)
+        if (NOT ${CMAKE_GENERATOR} STREQUAL "Unix Makefiles")
+            set(DEPFILE_ARG DEPFILE "${GEN_SOURCE}.d")
+        endif()
+
+        add_custom_command(
+            OUTPUT "${GEN_SOURCE}"
+            MAIN_DEPENDENCY "${SOURCE_FULL}"
+            ${DEPFILE_ARG}
+            COMMAND "${AIDL_BIN}"
+            ARGS
+            --lang=${LANG}
+            --include="${AIDLROOT}"
+            --dep="${GEN_SOURCE}.d"
+            --out="${GEN_DIR}"
+            --header_out="${GEN_DIR}/include"
+            --ninja
+            --structured
+            --min_sdk_version=current
+            ${STABILITY_FLAG}
+            ${AIDLFLAGS}
+            "${SOURCE_FULL}"
+        )
+        list(APPEND GEN_SOURCES "${GEN_SOURCE}")
+    endforeach()
+
+    add_library(${NAME} ${GEN_SOURCES})
+
+    target_include_directories(${NAME}
+        PUBLIC
+        "${GEN_DIR}/include"
+    )
+
+    if (${LANG} STREQUAL "ndk")
+        set(BINDER_LIB_NAME "libbinder_ndk_sdk")
+    else()
+        set(BINDER_LIB_NAME "libbinder_sdk")
+    endif()
+    target_link_libraries(${NAME}
+        ${BINDER_LIB_NAME}
+    )
+endfunction()
diff --git a/cc/cmake_ext_append_flags.txt b/cc/cmake_ext_append_flags.txt
new file mode 100644
index 0000000..2cfb1ac
--- /dev/null
+++ b/cc/cmake_ext_append_flags.txt
@@ -0,0 +1,10 @@
+include(CheckCXXCompilerFlag)
+
+macro(append_cxx_flags_if_supported VAR)
+  foreach(FLAG ${ARGN})
+    check_cxx_compiler_flag(${FLAG} HAS_FLAG${FLAG})
+    if(${HAS_FLAG${FLAG}})
+      list(APPEND ${VAR} ${FLAG})
+    endif()
+  endforeach()
+endmacro()
diff --git a/cc/cmake_main.txt b/cc/cmake_main.txt
new file mode 100644
index 0000000..eeabf53
--- /dev/null
+++ b/cc/cmake_main.txt
@@ -0,0 +1,27 @@
+cmake_minimum_required(VERSION 3.18)
+project(<<.M.Name>> CXX)
+set(CMAKE_CXX_STANDARD 20)
+enable_testing()
+
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
+include(AddAidlLibrary)
+include(AppendCxxFlagsIfSupported)
+include(FindThreads)
+
+if (NOT ANDROID_BUILD_TOP)
+    set(ANDROID_BUILD_TOP "${CMAKE_CURRENT_SOURCE_DIR}")
+endif()
+
+<<cflagsList .M.Name "_CFLAGS" .M.Properties.Cflags .M.Properties.Unportable_flags .M.Properties.Cflags_ignored>>
+
+<<range .Pprop.SystemPackages ->>
+find_package(<<.>> REQUIRED)
+<<end >>
+<<range .Pprop.PregeneratedPackages ->>
+add_subdirectory("${ANDROID_BUILD_TOP}/<<.>>" "<<.>>/build" EXCLUDE_FROM_ALL)
+<<end>>
+add_compile_options(${<<.M.Name>>_CFLAGS})
+link_libraries(${CMAKE_THREAD_LIBS_INIT})
+<<range $moduleDir, $value := .ModuleDirs ->>
+add_subdirectory(<<$moduleDir>>)
+<<end>>
diff --git a/cc/cmake_module_aidl.txt b/cc/cmake_module_aidl.txt
new file mode 100644
index 0000000..84755a3
--- /dev/null
+++ b/cc/cmake_module_aidl.txt
@@ -0,0 +1,11 @@
+# <<.M.Name>>
+
+<<setList .M.Name "_SRCS" "" (getAidlSources .M)>>
+
+<<setList .M.Name "_AIDLFLAGS" "" (getCompilerProperties .M).AidlInterface.Flags>>
+
+add_aidl_library(<<.M.Name>> <<(getCompilerProperties .M).AidlInterface.Lang>>
+    "${ANDROID_BUILD_TOP}/<<.Ctx.OtherModuleDir .M>>/<<(getCompilerProperties .M).AidlInterface.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
new file mode 100644
index 0000000..0dc45ae
--- /dev/null
+++ b/cc/cmake_module_cc.txt
@@ -0,0 +1,44 @@
+<<$srcs := getSources .M>>
+<<$includeDirs := getIncludeDirs .Ctx .M>>
+<<$cflags := getCflagsProperty .Ctx .M>>
+<<$deps := mapLibraries .Ctx .M (concat5
+(getLinkerProperties .M).Whole_static_libs
+(getLinkerProperties .M).Static_libs
+(getLinkerProperties .M).Shared_libs
+(getLinkerProperties .M).Header_libs
+(getExtraLibs .M)
+) .Pprop.LibraryMapping>>
+<<$moduleType := getModuleType .M>>
+<<$moduleTypeCmake := "executable">>
+<<if eq $moduleType "library">>
+<<$moduleTypeCmake = "library">>
+<<end>>
+
+# <<.M.Name>>
+<<if $srcs>>
+<<setList .M.Name "_SRCS" "${ANDROID_BUILD_TOP}/" (toStrings $srcs)>>
+add_<<$moduleTypeCmake>>(<<.M.Name>> ${<<.M.Name>>_SRCS})
+<<- else>>
+add_<<$moduleTypeCmake>>(<<.M.Name>> INTERFACE)
+<<- end>>
+<<- if eq $moduleType "library">>
+add_library(android::<<.M.Name>> ALIAS <<.M.Name>>)
+<<- else if eq $moduleType "test">>
+add_test(NAME <<.M.Name>> COMMAND <<.M.Name>>)
+<<- end>>
+<<print "">>
+
+<<- if $includeDirs>>
+<<setList .M.Name "_INCLUDES" "${ANDROID_BUILD_TOP}/" $includeDirs>>
+target_include_directories(<<.M.Name>> <<if $srcs>>PUBLIC<<else>>INTERFACE<<end>> ${<<.M.Name>>_INCLUDES})
+<<end>>
+
+<<- if and $srcs $cflags>>
+<<cflagsList .M.Name "_CFLAGS" $cflags .Snapshot.Properties.Unportable_flags .Snapshot.Properties.Cflags_ignored>>
+target_compile_options(<<.M.Name>> PRIVATE ${<<.M.Name>>_CFLAGS})
+<<end>>
+
+<<- if $deps>>
+<<setList .M.Name "_DEPENDENCIES" "" $deps>>
+target_link_libraries(<<.M.Name>> <<if not $srcs>>INTERFACE <<end ->> ${<<.M.Name>>_DEPENDENCIES})
+<<end>>
diff --git a/cc/cmake_snapshot.go b/cc/cmake_snapshot.go
new file mode 100644
index 0000000..fb2924a
--- /dev/null
+++ b/cc/cmake_snapshot.go
@@ -0,0 +1,573 @@
+// 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 (
+	"bytes"
+	_ "embed"
+	"fmt"
+	"path/filepath"
+	"slices"
+	"sort"
+	"strings"
+	"text/template"
+
+	"android/soong/android"
+
+	"github.com/google/blueprint"
+	"github.com/google/blueprint/proptools"
+)
+
+const veryVerbose bool = false
+
+//go:embed cmake_main.txt
+var templateCmakeMainRaw string
+var templateCmakeMain *template.Template = parseTemplate(templateCmakeMainRaw)
+
+//go:embed cmake_module_cc.txt
+var templateCmakeModuleCcRaw string
+var templateCmakeModuleCc *template.Template = parseTemplate(templateCmakeModuleCcRaw)
+
+//go:embed cmake_module_aidl.txt
+var templateCmakeModuleAidlRaw string
+var templateCmakeModuleAidl *template.Template = parseTemplate(templateCmakeModuleAidlRaw)
+
+//go:embed cmake_ext_add_aidl_library.txt
+var cmakeExtAddAidlLibrary string
+
+//go:embed cmake_ext_append_flags.txt
+var cmakeExtAppendFlags string
+
+var defaultUnportableFlags []string = []string{
+	"-Wno-c99-designator",
+	"-Wno-class-memaccess",
+	"-Wno-exit-time-destructors",
+	"-Winconsistent-missing-override",
+	"-Wno-inconsistent-missing-override",
+	"-Wreorder-init-list",
+	"-Wno-reorder-init-list",
+	"-Wno-restrict",
+	"-Wno-stringop-overread",
+	"-Wno-subobject-linkage",
+}
+
+var ignoredSystemLibs []string = []string{
+	"crtbegin_dynamic",
+	"crtend_android",
+	"libc",
+	"libc++",
+	"libc++_static",
+	"libc++demangle",
+	"libc_musl",
+	"libc_musl_crtbegin_so",
+	"libc_musl_crtbegin_static",
+	"libc_musl_crtend",
+	"libc_musl_crtend_so",
+	"libdl",
+	"libm",
+	"prebuilt_libclang_rt.builtins",
+	"prebuilt_libclang_rt.ubsan_minimal",
+}
+
+// Mapping entry between Android's library name and the one used when building outside Android tree.
+type LibraryMappingProperty struct {
+	// Android library name.
+	Android_name string
+
+	// Library name used when building outside Android.
+	Mapped_name string
+
+	// If the make file is already present in Android source tree, specify its location.
+	Package_pregenerated string
+
+	// If the package is expected to be installed on the build host OS, specify its name.
+	Package_system string
+}
+
+type CmakeSnapshotProperties struct {
+	// Host modules to add to the snapshot package. Their dependencies are pulled in automatically.
+	Modules_host []string
+
+	// System modules to add to the snapshot package. Their dependencies are pulled in automatically.
+	Modules_system []string
+
+	// Vendor modules to add to the snapshot package. Their dependencies are pulled in automatically.
+	Modules_vendor []string
+
+	// Host prebuilts to bundle with the snapshot. These are tools needed to build outside Android.
+	Prebuilts []string
+
+	// Global cflags to add when building outside Android.
+	Cflags []string
+
+	// Flags to skip when building outside Android.
+	Cflags_ignored []string
+
+	// Mapping between library names used in Android tree and externally.
+	Library_mapping []LibraryMappingProperty
+
+	// List of cflags that are not portable between compilers that could potentially be used to
+	// build a generated package. If left empty, it's initialized with a default list.
+	Unportable_flags []string
+
+	// Whether to include source code as part of the snapshot package.
+	Include_sources bool
+}
+
+var cmakeSnapshotSourcesProvider = blueprint.NewProvider[android.Paths]()
+
+type CmakeSnapshot struct {
+	android.ModuleBase
+
+	Properties CmakeSnapshotProperties
+
+	zipPath android.WritablePath
+}
+
+type cmakeProcessedProperties struct {
+	LibraryMapping       map[string]LibraryMappingProperty
+	PregeneratedPackages []string
+	SystemPackages       []string
+}
+
+type cmakeSnapshotDependencyTag struct {
+	blueprint.BaseDependencyTag
+	name string
+}
+
+var (
+	cmakeSnapshotModuleTag   = cmakeSnapshotDependencyTag{name: "cmake-snapshot-module"}
+	cmakeSnapshotPrebuiltTag = cmakeSnapshotDependencyTag{name: "cmake-snapshot-prebuilt"}
+)
+
+func parseTemplate(templateContents string) *template.Template {
+	funcMap := template.FuncMap{
+		"setList": func(name string, nameSuffix string, itemPrefix string, items []string) string {
+			var list strings.Builder
+			list.WriteString("set(" + name + nameSuffix)
+			templateListBuilder(&list, itemPrefix, items)
+			return list.String()
+		},
+		"toStrings": func(files android.Paths) []string {
+			return files.Strings()
+		},
+		"concat5": func(list1 []string, list2 []string, list3 []string, list4 []string, list5 []string) []string {
+			return append(append(append(append(list1, list2...), list3...), list4...), list5...)
+		},
+		"cflagsList": func(name string, nameSuffix string, flags []string,
+			unportableFlags []string, ignoredFlags []string) string {
+			if len(unportableFlags) == 0 {
+				unportableFlags = defaultUnportableFlags
+			}
+
+			var filteredPortable []string
+			var filteredUnportable []string
+			for _, flag := range flags {
+				if slices.Contains(ignoredFlags, flag) {
+					continue
+				} else if slices.Contains(unportableFlags, flag) {
+					filteredUnportable = append(filteredUnportable, flag)
+				} else {
+					filteredPortable = append(filteredPortable, flag)
+				}
+			}
+
+			var list strings.Builder
+
+			list.WriteString("set(" + name + nameSuffix)
+			templateListBuilder(&list, "", filteredPortable)
+
+			if len(filteredUnportable) > 0 {
+				list.WriteString("\nappend_cxx_flags_if_supported(" + name + nameSuffix)
+				templateListBuilder(&list, "", filteredUnportable)
+			}
+
+			return list.String()
+		},
+		"getSources": func(m *Module) android.Paths {
+			return m.compiler.(CompiledInterface).Srcs()
+		},
+		"getModuleType": getModuleType,
+		"getCompilerProperties": func(m *Module) BaseCompilerProperties {
+			return m.compiler.baseCompilerProps()
+		},
+		"getCflagsProperty": func(ctx android.ModuleContext, m *Module) []string {
+			cflags := m.compiler.baseCompilerProps().Cflags
+			return cflags.GetOrDefault(ctx, nil)
+		},
+		"getLinkerProperties": func(m *Module) BaseLinkerProperties {
+			return m.linker.baseLinkerProps()
+		},
+		"getExtraLibs":   getExtraLibs,
+		"getIncludeDirs": getIncludeDirs,
+		"mapLibraries": func(ctx android.ModuleContext, m *Module, libs []string, mapping map[string]LibraryMappingProperty) []string {
+			var mappedLibs []string
+			for _, lib := range libs {
+				mappedLib, exists := mapping[lib]
+				if exists {
+					lib = mappedLib.Mapped_name
+				} else {
+					if !ctx.OtherModuleExists(lib) {
+						ctx.OtherModuleErrorf(m, "Dependency %s doesn't exist", lib)
+					}
+					lib = "android::" + lib
+				}
+				if lib == "" {
+					continue
+				}
+				mappedLibs = append(mappedLibs, lib)
+			}
+			sort.Strings(mappedLibs)
+			mappedLibs = slices.Compact(mappedLibs)
+			return mappedLibs
+		},
+		"getAidlSources": func(m *Module) []string {
+			aidlInterface := m.compiler.baseCompilerProps().AidlInterface
+			aidlRoot := aidlInterface.AidlRoot + string(filepath.Separator)
+			if aidlInterface.AidlRoot == "" {
+				aidlRoot = ""
+			}
+			var sources []string
+			for _, src := range aidlInterface.Sources {
+				if !strings.HasPrefix(src, aidlRoot) {
+					panic(fmt.Sprintf("Aidl source '%v' doesn't start with '%v'", src, aidlRoot))
+				}
+				sources = append(sources, src[len(aidlRoot):])
+			}
+			return sources
+		},
+	}
+
+	return template.Must(template.New("").Delims("<<", ">>").Funcs(funcMap).Parse(templateContents))
+}
+
+func sliceWithPrefix(prefix string, slice []string) []string {
+	output := make([]string, len(slice))
+	for i, elem := range slice {
+		output[i] = prefix + elem
+	}
+	return output
+}
+
+func templateListBuilder(builder *strings.Builder, itemPrefix string, items []string) {
+	if len(items) > 0 {
+		builder.WriteString("\n")
+		for _, item := range items {
+			builder.WriteString("    " + itemPrefix + item + "\n")
+		}
+	}
+	builder.WriteString(")")
+}
+
+func executeTemplate(templ *template.Template, buffer *bytes.Buffer, data any) string {
+	buffer.Reset()
+	if err := templ.Execute(buffer, data); err != nil {
+		panic(err)
+	}
+	output := strings.TrimSpace(buffer.String())
+	buffer.Reset()
+	return output
+}
+
+func (m *CmakeSnapshot) DepsMutator(ctx android.BottomUpMutatorContext) {
+	deviceVariations := ctx.Config().AndroidFirstDeviceTarget.Variations()
+	deviceSystemVariations := append(deviceVariations, blueprint.Variation{"image", ""})
+	deviceVendorVariations := append(deviceVariations, blueprint.Variation{"image", "vendor"})
+	hostVariations := ctx.Config().BuildOSTarget.Variations()
+
+	ctx.AddVariationDependencies(hostVariations, cmakeSnapshotModuleTag, m.Properties.Modules_host...)
+	ctx.AddVariationDependencies(deviceSystemVariations, cmakeSnapshotModuleTag, m.Properties.Modules_system...)
+	ctx.AddVariationDependencies(deviceVendorVariations, cmakeSnapshotModuleTag, m.Properties.Modules_vendor...)
+
+	if len(m.Properties.Prebuilts) > 0 {
+		prebuilts := append(m.Properties.Prebuilts, "libc++")
+		ctx.AddVariationDependencies(hostVariations, cmakeSnapshotPrebuiltTag, prebuilts...)
+	}
+}
+
+func (m *CmakeSnapshot) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	var templateBuffer bytes.Buffer
+	var pprop cmakeProcessedProperties
+	m.zipPath = android.PathForModuleOut(ctx, ctx.ModuleName()+".zip")
+
+	// Process Library_mapping for more efficient lookups
+	pprop.LibraryMapping = map[string]LibraryMappingProperty{}
+	for _, elem := range m.Properties.Library_mapping {
+		pprop.LibraryMapping[elem.Android_name] = elem
+
+		if elem.Package_pregenerated != "" {
+			pprop.PregeneratedPackages = append(pprop.PregeneratedPackages, elem.Package_pregenerated)
+		}
+		sort.Strings(pprop.PregeneratedPackages)
+		pprop.PregeneratedPackages = slices.Compact(pprop.PregeneratedPackages)
+
+		if elem.Package_system != "" {
+			pprop.SystemPackages = append(pprop.SystemPackages, elem.Package_system)
+		}
+		sort.Strings(pprop.SystemPackages)
+		pprop.SystemPackages = slices.Compact(pprop.SystemPackages)
+	}
+
+	// Generating CMakeLists.txt rules for all modules in dependency tree
+	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)
+		if visited := visitedModules[moduleName]; visited {
+			return false // visit only once
+		}
+		visitedModules[moduleName] = true
+		dep, ok := dep_a.(*Module)
+		if !ok {
+			return false // not a cc module
+		}
+		if mapping, ok := pprop.LibraryMapping[moduleName]; ok {
+			if mapping.Package_pregenerated != "" {
+				pregeneratedModules = append(pregeneratedModules, dep)
+			}
+			return false // mapped to system or pregenerated (we'll handle these later)
+		}
+		if ctx.OtherModuleDependencyTag(dep) == cmakeSnapshotPrebuiltTag {
+			return false // we'll handle cmakeSnapshotPrebuiltTag later
+		}
+		if slices.Contains(ignoredSystemLibs, moduleName) {
+			return false // system libs built in-tree for Android
+		}
+		if dep.compiler == nil {
+			return false // unsupported module type (e.g. prebuilt)
+		}
+		isAidlModule := dep.compiler.baseCompilerProps().AidlInterface.Lang != ""
+
+		if !proptools.Bool(dep.Properties.Cmake_snapshot_supported) {
+			ctx.OtherModulePropertyErrorf(dep, "cmake_snapshot_supported",
+				"CMake snapshots not supported, despite being a dependency for %s",
+				ctx.OtherModuleName(parent))
+			return false
+		}
+
+		if veryVerbose {
+			fmt.Println("WalkDeps: " + ctx.OtherModuleName(parent) + " -> " + moduleName)
+		}
+
+		// Generate CMakeLists.txt fragment for this module
+		templateToUse := templateCmakeModuleCc
+		if isAidlModule {
+			templateToUse = templateCmakeModuleAidl
+		}
+		moduleFragment := executeTemplate(templateToUse, &templateBuffer, struct {
+			Ctx      *android.ModuleContext
+			M        *Module
+			Snapshot *CmakeSnapshot
+			Pprop    *cmakeProcessedProperties
+		}{
+			&ctx,
+			dep,
+			m,
+			&pprop,
+		})
+		moduleDir := ctx.OtherModuleDir(dep)
+		moduleDirs[moduleDir] = append(moduleDirs[moduleDir], moduleFragment)
+
+		if m.Properties.Include_sources {
+			files, _ := android.OtherModuleProvider(ctx, dep, cmakeSnapshotSourcesProvider)
+			for _, file := range files {
+				sourceFiles[file.String()] = file
+			}
+		}
+
+		// if it's AIDL module, no need to dive into their dependencies
+		return !isAidlModule
+	})
+
+	// Enumerate sources for pregenerated modules
+	if m.Properties.Include_sources {
+		for _, dep := range pregeneratedModules {
+			if !proptools.Bool(dep.Properties.Cmake_snapshot_supported) {
+				ctx.OtherModulePropertyErrorf(dep, "cmake_snapshot_supported",
+					"Pregenerated CMake snapshots not supported, despite being requested for %s",
+					ctx.ModuleName())
+				continue
+			}
+
+			files, _ := android.OtherModuleProvider(ctx, dep, cmakeSnapshotSourcesProvider)
+			for _, file := range files {
+				sourceFiles[file.String()] = file
+			}
+		}
+	}
+
+	// Merging CMakeLists.txt contents for every module directory
+	var makefilesList android.Paths
+	for _, moduleDir := range android.SortedKeys(moduleDirs) {
+		fragments := moduleDirs[moduleDir]
+		moduleCmakePath := android.PathForModuleGen(ctx, moduleDir, "CMakeLists.txt")
+		makefilesList = append(makefilesList, moduleCmakePath)
+		sort.Strings(fragments)
+		android.WriteFileRule(ctx, moduleCmakePath, strings.Join(fragments, "\n\n\n"))
+	}
+
+	// Generating top-level CMakeLists.txt
+	mainCmakePath := android.PathForModuleGen(ctx, "CMakeLists.txt")
+	makefilesList = append(makefilesList, mainCmakePath)
+	mainContents := executeTemplate(templateCmakeMain, &templateBuffer, struct {
+		Ctx        *android.ModuleContext
+		M          *CmakeSnapshot
+		ModuleDirs map[string][]string
+		Pprop      *cmakeProcessedProperties
+	}{
+		&ctx,
+		m,
+		moduleDirs,
+		&pprop,
+	})
+	android.WriteFileRule(ctx, mainCmakePath, mainContents)
+
+	// Generating CMake extensions
+	extPath := android.PathForModuleGen(ctx, "cmake", "AppendCxxFlagsIfSupported.cmake")
+	makefilesList = append(makefilesList, extPath)
+	android.WriteFileRuleVerbatim(ctx, extPath, cmakeExtAppendFlags)
+	extPath = android.PathForModuleGen(ctx, "cmake", "AddAidlLibrary.cmake")
+	makefilesList = append(makefilesList, extPath)
+	android.WriteFileRuleVerbatim(ctx, extPath, cmakeExtAddAidlLibrary)
+
+	// Generating the final zip file
+	zipRule := android.NewRuleBuilder(pctx, ctx)
+	zipCmd := zipRule.Command().
+		BuiltTool("soong_zip").
+		FlagWithOutput("-o ", m.zipPath)
+
+	// Packaging all sources into the zip file
+	if m.Properties.Include_sources {
+		var sourcesList android.Paths
+		for _, file := range android.SortedKeys(sourceFiles) {
+			path := sourceFiles[file]
+			sourcesList = append(sourcesList, path)
+		}
+
+		sourcesRspFile := android.PathForModuleObj(ctx, ctx.ModuleName()+"_sources.rsp")
+		zipCmd.FlagWithRspFileInputList("-r ", sourcesRspFile, sourcesList)
+	}
+
+	// Packaging all make files into the zip file
+	makefilesRspFile := android.PathForModuleObj(ctx, ctx.ModuleName()+"_makefiles.rsp")
+	zipCmd.
+		FlagWithArg("-C ", android.PathForModuleGen(ctx).OutputPath.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) {
+			for _, file := range dep.FilesToInstall() {
+				prebuiltsList = append(prebuiltsList, file)
+			}
+		})
+
+		prebuiltsRspFile := android.PathForModuleObj(ctx, ctx.ModuleName()+"_prebuilts.rsp")
+		zipCmd.
+			FlagWithArg("-C ", android.PathForArbitraryOutput(ctx).String()).
+			FlagWithArg("-P ", "prebuilts").
+			FlagWithRspFileInputList("-r ", prebuiltsRspFile, prebuiltsList)
+	}
+
+	// Finish generating the final zip file
+	zipRule.Build(m.zipPath.String(), "archiving "+ctx.ModuleName())
+
+	ctx.SetOutputFiles(android.Paths{m.zipPath}, "")
+}
+
+func (m *CmakeSnapshot) AndroidMkEntries() []android.AndroidMkEntries {
+	return []android.AndroidMkEntries{{
+		Class:      "DATA",
+		OutputFile: android.OptionalPathForPath(m.zipPath),
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+				entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true)
+			},
+		},
+	}}
+}
+
+func getModuleType(m *Module) string {
+	switch m.linker.(type) {
+	case *binaryDecorator:
+		return "executable"
+	case *libraryDecorator:
+		return "library"
+	case *testBinary:
+		return "test"
+	case *benchmarkDecorator:
+		return "test"
+	}
+	panic(fmt.Sprintf("Unexpected module type: %T", m.linker))
+}
+
+func getExtraLibs(m *Module) []string {
+	switch decorator := m.linker.(type) {
+	case *testBinary:
+		if decorator.testDecorator.gtest() {
+			return []string{
+				"libgtest",
+				"libgtest_main",
+			}
+		}
+	case *benchmarkDecorator:
+		return []string{"libgoogle-benchmark"}
+	}
+	return nil
+}
+
+func getIncludeDirs(ctx android.ModuleContext, m *Module) []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))
+	}
+	return nil
+}
+
+func cmakeSnapshotLoadHook(ctx android.LoadHookContext) {
+	props := struct {
+		Target struct {
+			Darwin struct {
+				Enabled *bool
+			}
+			Windows struct {
+				Enabled *bool
+			}
+		}
+	}{}
+	props.Target.Darwin.Enabled = proptools.BoolPtr(false)
+	props.Target.Windows.Enabled = proptools.BoolPtr(false)
+	ctx.AppendProperties(&props)
+}
+
+// cmake_snapshot allows defining source packages for release outside of Android build tree.
+// As a result of cmake_snapshot module build, a zip file is generated with CMake build definitions
+// for selected source modules, their dependencies and optionally also the source code itself.
+func CmakeSnapshotFactory() android.Module {
+	module := &CmakeSnapshot{}
+	module.AddProperties(&module.Properties)
+	android.AddLoadHook(module, cmakeSnapshotLoadHook)
+	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
+	return module
+}
+
+func init() {
+	android.InitRegistrationContext.RegisterModuleType("cc_cmake_snapshot", CmakeSnapshotFactory)
+}
diff --git a/cc/cmake_snapshot_test.go b/cc/cmake_snapshot_test.go
new file mode 100644
index 0000000..b6f4369
--- /dev/null
+++ b/cc/cmake_snapshot_test.go
@@ -0,0 +1,117 @@
+// 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 (
+	"runtime"
+	"strings"
+	"testing"
+
+	"android/soong/android"
+)
+
+func wasGenerated(t *testing.T, m *android.TestingModule, fileName string, ruleType string) {
+	t.Helper()
+	ruleName := "<nil>"
+	if rule := m.MaybeOutput(fileName).Rule; rule != nil {
+		ruleName = rule.String()
+	}
+	if !strings.HasSuffix(ruleName, ruleType) {
+		t.Errorf("Main Cmake file wasn't generated properly, expected rule %v, found %v", ruleType, ruleName)
+	}
+}
+
+func TestEmptyCmakeSnapshot(t *testing.T) {
+	t.Parallel()
+	result := PrepareForIntegrationTestWithCc.RunTestWithBp(t, `
+		cc_cmake_snapshot {
+			name: "foo",
+			modules_host: [],
+			modules_system: [],
+			modules_vendor: [],
+			prebuilts: ["libc++"],
+			include_sources: true,
+		}`)
+
+	if runtime.GOOS != "linux" {
+		t.Skip("CMake snapshots are only supported on Linux")
+	}
+
+	snapshotModule := result.ModuleForTests("foo", "linux_glibc_x86_64")
+
+	wasGenerated(t, &snapshotModule, "CMakeLists.txt", "rawFileCopy")
+	wasGenerated(t, &snapshotModule, "foo.zip", "")
+}
+
+func TestCmakeSnapshotWithBinary(t *testing.T) {
+	t.Parallel()
+	xtra := android.FixtureAddTextFile("some/module/Android.bp", `
+		cc_binary {
+			name: "foo_binary",
+			host_supported: true,
+			cmake_snapshot_supported: true,
+		}
+	`)
+	result := android.GroupFixturePreparers(PrepareForIntegrationTestWithCc, xtra).RunTestWithBp(t, `
+		cc_cmake_snapshot {
+			name: "foo",
+			modules_system: [
+				"foo_binary",
+			],
+			include_sources: true,
+		}`)
+
+	if runtime.GOOS != "linux" {
+		t.Skip("CMake snapshots are only supported on Linux")
+	}
+
+	snapshotModule := result.ModuleForTests("foo", "linux_glibc_x86_64")
+
+	wasGenerated(t, &snapshotModule, "some/module/CMakeLists.txt", "rawFileCopy")
+}
+
+func TestCmakeSnapshotAsTestData(t *testing.T) {
+	t.Parallel()
+	result := PrepareForIntegrationTestWithCc.RunTestWithBp(t, `
+		cc_test {
+			name: "foo_test",
+			gtest: false,
+			srcs: [
+				"foo_test.c",
+			],
+			data: [
+				":foo",
+			],
+			target: {
+				android: {enabled: false},
+			},
+		}
+
+		cc_cmake_snapshot {
+			name: "foo",
+			modules_system: [],
+			prebuilts: ["libc++"],
+			include_sources: true,
+		}`)
+
+	if runtime.GOOS != "linux" {
+		t.Skip("CMake snapshots are only supported on Linux")
+	}
+
+	snapshotModule := result.ModuleForTests("foo", "linux_glibc_x86_64")
+
+	wasGenerated(t, &snapshotModule, "CMakeLists.txt", "rawFileCopy")
+	wasGenerated(t, &snapshotModule, "foo.zip", "")
+}
diff --git a/cc/compiler.go b/cc/compiler.go
index 9a961cf..03f9899 100644
--- a/cc/compiler.go
+++ b/cc/compiler.go
@@ -50,7 +50,7 @@
 	Exclude_srcs []string `android:"path,arch_variant"`
 
 	// list of module-specific flags that will be used for C and C++ compiles.
-	Cflags []string `android:"arch_variant"`
+	Cflags proptools.Configurable[[]string] `android:"arch_variant"`
 
 	// list of module-specific flags that will be used for C++ compiles
 	Cppflags []string `android:"arch_variant"`
@@ -98,10 +98,10 @@
 
 	// list of generated headers to add to the include path. These are the names
 	// of genrule modules.
-	Generated_headers []string `android:"arch_variant,variant_prepend"`
+	Generated_headers proptools.Configurable[[]string] `android:"arch_variant,variant_prepend"`
 
 	// pass -frtti instead of -fno-rtti
-	Rtti *bool
+	Rtti *bool `android:"arch_variant"`
 
 	// C standard version to use. Can be a specific version (such as "gnu11"),
 	// "experimental" (which will use draft versions like C1x when available),
@@ -120,6 +120,10 @@
 	// ban targeting bpf in cc rules instead use bpf_rules. (b/323415017)
 	Bpf_target *bool
 
+	// Add "-Xclang -verify" to the cflags and appends "touch $out" to
+	// the clang command line.
+	Clang_verify bool
+
 	Yacc *YaccProperties
 	Lex  *LexProperties
 
@@ -141,6 +145,22 @@
 		Flags []string
 	}
 
+	// Populated by aidl_interface CPP backend to let other modules (e.g. cc_cmake_snapshot)
+	// access actual source files and not generated cpp intermediary sources.
+	AidlInterface struct {
+		// list of aidl_interface sources
+		Sources []string `blueprint:"mutated"`
+
+		// root directory of AIDL sources
+		AidlRoot string `blueprint:"mutated"`
+
+		// AIDL backend language (e.g. "cpp", "ndk")
+		Lang string `blueprint:"mutated"`
+
+		// list of flags passed to AIDL generator
+		Flags []string `blueprint:"mutated"`
+	} `blueprint:"mutated"`
+
 	Renderscript struct {
 		// list of directories that will be added to the llvm-rs-cc include paths
 		Include_dirs []string
@@ -152,12 +172,6 @@
 		Target_api *string
 	}
 
-	Debug, Release struct {
-		// list of module-specific flags that will be used for C and C++ compiles in debug or
-		// release builds
-		Cflags []string `android:"arch_variant"`
-	} `android:"arch_variant"`
-
 	Target struct {
 		Vendor, Product struct {
 			// list of source files that should only be used in vendor or
@@ -254,7 +268,7 @@
 }
 
 func (compiler *baseCompiler) appendCflags(flags []string) {
-	compiler.Properties.Cflags = append(compiler.Properties.Cflags, flags...)
+	compiler.Properties.Cflags.AppendSimpleValue(flags)
 }
 
 func (compiler *baseCompiler) appendAsflags(flags []string) {
@@ -265,6 +279,10 @@
 	return []interface{}{&compiler.Properties, &compiler.Proto}
 }
 
+func (compiler *baseCompiler) baseCompilerProps() BaseCompilerProperties {
+	return compiler.Properties
+}
+
 func includeBuildDirectory(prop *bool) bool {
 	return proptools.BoolDefault(prop, true)
 }
@@ -278,7 +296,7 @@
 func (compiler *baseCompiler) compilerDeps(ctx DepsContext, deps Deps) Deps {
 	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...)
+	deps.GeneratedHeaders = append(deps.GeneratedHeaders, compiler.Properties.Generated_headers.GetOrDefault(ctx, nil)...)
 	deps.AidlLibs = append(deps.AidlLibs, compiler.Properties.Aidl.Libs...)
 
 	android.ProtoDeps(ctx, &compiler.Proto)
@@ -348,7 +366,8 @@
 	compiler.srcsBeforeGen = android.PathsForModuleSrcExcludes(ctx, compiler.Properties.Srcs, compiler.Properties.Exclude_srcs)
 	compiler.srcsBeforeGen = append(compiler.srcsBeforeGen, deps.GeneratedSources...)
 
-	CheckBadCompilerFlags(ctx, "cflags", compiler.Properties.Cflags)
+	cflags := compiler.Properties.Cflags.GetOrDefault(ctx, nil)
+	CheckBadCompilerFlags(ctx, "cflags", cflags)
 	CheckBadCompilerFlags(ctx, "cppflags", compiler.Properties.Cppflags)
 	CheckBadCompilerFlags(ctx, "conlyflags", compiler.Properties.Conlyflags)
 	CheckBadCompilerFlags(ctx, "asflags", compiler.Properties.Asflags)
@@ -361,7 +380,7 @@
 
 	esc := proptools.NinjaAndShellEscapeList
 
-	flags.Local.CFlags = append(flags.Local.CFlags, esc(compiler.Properties.Cflags)...)
+	flags.Local.CFlags = append(flags.Local.CFlags, esc(cflags)...)
 	flags.Local.CppFlags = append(flags.Local.CppFlags, esc(compiler.Properties.Cppflags)...)
 	flags.Local.ConlyFlags = append(flags.Local.ConlyFlags, esc(compiler.Properties.Conlyflags)...)
 	flags.Local.AsFlags = append(flags.Local.AsFlags, esc(compiler.Properties.Asflags)...)
@@ -370,6 +389,11 @@
 	flags.Yacc = compiler.Properties.Yacc
 	flags.Lex = compiler.Properties.Lex
 
+	flags.ClangVerify = compiler.Properties.Clang_verify
+	if compiler.Properties.Clang_verify {
+		flags.Local.CFlags = append(flags.Local.CFlags, "-Xclang", "-verify")
+	}
+
 	// Include dir cflags
 	localIncludeDirs := android.PathsForModuleSrc(ctx, compiler.Properties.Local_include_dirs)
 	if len(localIncludeDirs) > 0 {
@@ -449,11 +473,6 @@
 		ctx.ModuleErrorf("%s", err)
 	}
 
-	CheckBadCompilerFlags(ctx, "release.cflags", compiler.Properties.Release.Cflags)
-
-	// TODO: debug
-	flags.Local.CFlags = append(flags.Local.CFlags, esc(compiler.Properties.Release.Cflags)...)
-
 	if !ctx.DeviceConfig().BuildBrokenClangCFlags() && len(compiler.Properties.Clang_cflags) != 0 {
 		ctx.PropertyErrorf("clang_cflags", "property is deprecated, see Changes.md file")
 	} else {
@@ -520,12 +539,10 @@
 		flags.Global.CommonFlags = append(flags.Global.CommonFlags, "${config.ExternalCflags}")
 	}
 
-	if tc.Bionic() {
-		if Bool(compiler.Properties.Rtti) {
-			flags.Local.CppFlags = append(flags.Local.CppFlags, "-frtti")
-		} else {
-			flags.Local.CppFlags = append(flags.Local.CppFlags, "-fno-rtti")
-		}
+	if Bool(compiler.Properties.Rtti) {
+		flags.Local.CppFlags = append(flags.Local.CppFlags, "-frtti")
+	} else {
+		flags.Local.CppFlags = append(flags.Local.CppFlags, "-fno-rtti")
 	}
 
 	flags.Global.AsFlags = append(flags.Global.AsFlags, "${config.CommonGlobalAsflags}")
@@ -664,6 +681,11 @@
 		flags.Local.CFlags = append(flags.Local.CFlags, "-fopenmp")
 	}
 
+	if ctx.optimizeForSize() {
+		flags.Local.CFlags = append(flags.Local.CFlags, "-Oz")
+		flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,-mllvm,-enable-ml-inliner=release")
+	}
+
 	// Exclude directories from manual binder interface allowed list.
 	//TODO(b/145621474): Move this check into IInterface.h when clang-tidy no longer uses absolute paths.
 	if android.HasAnyPrefix(ctx.ModuleDir(), allowedManualInterfacePaths) {
@@ -782,7 +804,7 @@
 	Header_libs []string `android:"arch_variant,variant_prepend"`
 
 	// list of clang flags required to correctly interpret the headers.
-	Cflags []string `android:"arch_variant"`
+	Cflags proptools.Configurable[[]string] `android:"arch_variant"`
 
 	// list of c++ specific clang flags required to correctly interpret the headers.
 	// This is provided primarily to make sure cppflags defined in cc_defaults are pulled in.
diff --git a/cc/config/Android.bp b/cc/config/Android.bp
index fdc94ad..289409f 100644
--- a/cc/config/Android.bp
+++ b/cc/config/Android.bp
@@ -15,7 +15,6 @@
         "global.go",
         "tidy.go",
         "toolchain.go",
-        "vndk.go",
 
         "bionic.go",
 
diff --git a/cc/config/darwin_host.go b/cc/config/darwin_host.go
index 47c61b0..4856669 100644
--- a/cc/config/darwin_host.go
+++ b/cc/config/darwin_host.go
@@ -50,6 +50,8 @@
 	darwinSupportedSdkVersions = []string{
 		"11",
 		"12",
+		"13",
+		"14",
 	}
 
 	darwinAvailableLibraries = append(
diff --git a/cc/config/global.go b/cc/config/global.go
index 16b5e09..bf2502f 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -136,6 +136,11 @@
 		// displaying logs in web browsers.
 		"-fmessage-length=0",
 
+		// Disable C++17 "relaxed template template argument matching" as a workaround for
+		// our out-dated libcxx.
+		// http://b/341084395
+		"-fno-relaxed-template-template-args",
+
 		// Using simple template names reduces the size of debug builds.
 		"-gsimple-template-names",
 
@@ -295,6 +300,9 @@
 		// New warnings to be fixed after clang-r475365
 		"-Wno-error=single-bit-bitfield-constant-conversion", // http://b/243965903
 		"-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",
 
 		// Irrelevant on Android because _we_ don't use exceptions, but causes
 		// lots of build noise because libcxx/libcxxabi do. This can probably
@@ -302,6 +310,9 @@
 		// until then because it causes warnings in the _callers_, not the
 		// project itself.
 		"-Wno-deprecated-dynamic-exception-spec",
+
+		// Allow using VLA CXX extension.
+		"-Wno-vla-cxx-extension",
 	}
 
 	noOverride64GlobalCflags = []string{}
@@ -386,8 +397,8 @@
 
 	// prebuilts/clang default settings.
 	ClangDefaultBase         = "prebuilts/clang/host"
-	ClangDefaultVersion      = "clang-r510928"
-	ClangDefaultShortVersion = "18"
+	ClangDefaultVersion      = "clang-r530567"
+	ClangDefaultShortVersion = "19"
 
 	// Directories with warnings from Android.bp files.
 	WarningAllowedProjects = []string{
diff --git a/cc/config/riscv64_device.go b/cc/config/riscv64_device.go
index 724676a..6a5293f 100644
--- a/cc/config/riscv64_device.go
+++ b/cc/config/riscv64_device.go
@@ -29,14 +29,10 @@
 		// This is already the driver's Android default, but duplicated here (and
 		// below) for ease of experimentation with additional extensions.
 		"-march=rv64gcv_zba_zbb_zbs",
-		// TODO: move to driver (https://github.com/google/android-riscv64/issues/111)
-		"-mno-strict-align",
 		// TODO: remove when qemu V works (https://gitlab.com/qemu-project/qemu/-/issues/1976)
 		// (Note that we'll probably want to wait for berberis to be good enough
 		// that most people don't care about qemu's V performance either!)
 		"-mno-implicit-float",
-		// TODO: remove when clang default changed (https://github.com/google/android-riscv64/issues/124)
-		"-mllvm -jump-is-expensive=false",
 	}
 
 	riscv64ArchVariantCflags = map[string][]string{}
diff --git a/cc/config/toolchain.go b/cc/config/toolchain.go
index 7dc990b..5d8c351 100644
--- a/cc/config/toolchain.go
+++ b/cc/config/toolchain.go
@@ -209,58 +209,58 @@
 	return list
 }
 
-func LibclangRuntimeLibrary(t Toolchain, library string) string {
+func LibclangRuntimeLibrary(library string) string {
 	return "libclang_rt." + library
 }
 
-func BuiltinsRuntimeLibrary(t Toolchain) string {
-	return LibclangRuntimeLibrary(t, "builtins")
+func BuiltinsRuntimeLibrary() string {
+	return LibclangRuntimeLibrary("builtins")
 }
 
-func AddressSanitizerRuntimeLibrary(t Toolchain) string {
-	return LibclangRuntimeLibrary(t, "asan")
+func AddressSanitizerRuntimeLibrary() string {
+	return LibclangRuntimeLibrary("asan")
 }
 
-func AddressSanitizerStaticRuntimeLibrary(t Toolchain) string {
-	return LibclangRuntimeLibrary(t, "asan.static")
+func AddressSanitizerStaticRuntimeLibrary() string {
+	return LibclangRuntimeLibrary("asan.static")
 }
 
-func AddressSanitizerCXXStaticRuntimeLibrary(t Toolchain) string {
-	return LibclangRuntimeLibrary(t, "asan_cxx.static")
+func AddressSanitizerCXXStaticRuntimeLibrary() string {
+	return LibclangRuntimeLibrary("asan_cxx.static")
 }
 
-func HWAddressSanitizerRuntimeLibrary(t Toolchain) string {
-	return LibclangRuntimeLibrary(t, "hwasan")
+func HWAddressSanitizerRuntimeLibrary() string {
+	return LibclangRuntimeLibrary("hwasan")
 }
 
-func HWAddressSanitizerStaticLibrary(t Toolchain) string {
-	return LibclangRuntimeLibrary(t, "hwasan_static")
+func HWAddressSanitizerStaticLibrary() string {
+	return LibclangRuntimeLibrary("hwasan_static")
 }
 
-func UndefinedBehaviorSanitizerRuntimeLibrary(t Toolchain) string {
-	return LibclangRuntimeLibrary(t, "ubsan_standalone")
+func UndefinedBehaviorSanitizerRuntimeLibrary() string {
+	return LibclangRuntimeLibrary("ubsan_standalone")
 }
 
-func UndefinedBehaviorSanitizerMinimalRuntimeLibrary(t Toolchain) string {
-	return LibclangRuntimeLibrary(t, "ubsan_minimal")
+func UndefinedBehaviorSanitizerMinimalRuntimeLibrary() string {
+	return LibclangRuntimeLibrary("ubsan_minimal")
 }
 
-func ThreadSanitizerRuntimeLibrary(t Toolchain) string {
-	return LibclangRuntimeLibrary(t, "tsan")
+func ThreadSanitizerRuntimeLibrary() string {
+	return LibclangRuntimeLibrary("tsan")
 }
 
-func ScudoRuntimeLibrary(t Toolchain) string {
-	return LibclangRuntimeLibrary(t, "scudo")
+func ScudoRuntimeLibrary() string {
+	return LibclangRuntimeLibrary("scudo")
 }
 
-func ScudoMinimalRuntimeLibrary(t Toolchain) string {
-	return LibclangRuntimeLibrary(t, "scudo_minimal")
+func ScudoMinimalRuntimeLibrary() string {
+	return LibclangRuntimeLibrary("scudo_minimal")
 }
 
-func LibFuzzerRuntimeLibrary(t Toolchain) string {
-	return LibclangRuntimeLibrary(t, "fuzzer")
+func LibFuzzerRuntimeLibrary() string {
+	return LibclangRuntimeLibrary("fuzzer")
 }
 
-func LibFuzzerRuntimeInterceptors(t Toolchain) string {
-	return LibclangRuntimeLibrary(t, "fuzzer_interceptors")
+func LibFuzzerRuntimeInterceptors() string {
+	return LibclangRuntimeLibrary("fuzzer_interceptors")
 }
diff --git a/cc/config/vndk.go b/cc/config/vndk.go
deleted file mode 100644
index dd612ce..0000000
--- a/cc/config/vndk.go
+++ /dev/null
@@ -1,45 +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 config
-
-// List of VNDK libraries that have different core variant and vendor variant.
-// For these libraries, the vendor variants must be installed even if the device
-// has VndkUseCoreVariant set.
-// Note that AIDL-generated modules must use vendor variants by default.
-var VndkMustUseVendorVariantList = []string{
-	"android.hardware.nfc@1.2",
-	"libbinder",
-	"libcrypto",
-	"libexpat",
-	"libgatekeeper",
-	"libgui",
-	"libhidlcache",
-	"libkeymaster_messages",
-	"libkeymaster_portable",
-	"libmedia_omx",
-	"libpuresoftkeymasterdevice",
-	"libselinux",
-	"libsoftkeymasterdevice",
-	"libsqlite",
-	"libssl",
-	"libstagefright_bufferpool@2.0",
-	"libstagefright_bufferqueue_helper",
-	"libstagefright_foundation",
-	"libstagefright_omx",
-	"libstagefright_omx_utils",
-	"libstagefright_xmlparser",
-	"libui",
-	"libxml2",
-}
diff --git a/cc/coverage.go b/cc/coverage.go
index f6092e4..a7618dd 100644
--- a/cc/coverage.go
+++ b/cc/coverage.go
@@ -23,26 +23,26 @@
 )
 
 var (
- 	clangCoverageHostLdFlags = []string{
- 		"-Wl,--no-as-needed",
- 		"-Wl,--wrap,open",
- 	}
- 	clangContinuousCoverageFlags = []string{
- 		"-mllvm",
- 		"-runtime-counter-relocation",
- 	}
- 	clangCoverageCFlags = []string{
- 		"-Wno-frame-larger-than=",
- 	}
- 	clangCoverageCommonFlags = []string{
- 		"-fcoverage-mapping",
- 		"-Wno-pass-failed",
- 		"-D__ANDROID_CLANG_COVERAGE__",
- 	}
- 	clangCoverageHWASanFlags = []string{
- 		"-mllvm",
- 		"-hwasan-globals=0",
- 	}
+	clangCoverageHostLdFlags = []string{
+		"-Wl,--no-as-needed",
+		"-Wl,--wrap,open",
+	}
+	clangContinuousCoverageFlags = []string{
+		"-mllvm",
+		"-runtime-counter-relocation",
+	}
+	clangCoverageCFlags = []string{
+		"-Wno-frame-larger-than=",
+	}
+	clangCoverageCommonFlags = []string{
+		"-fcoverage-mapping",
+		"-Wno-pass-failed",
+		"-D__ANDROID_CLANG_COVERAGE__",
+	}
+	clangCoverageHWASanFlags = []string{
+		"-mllvm",
+		"-hwasan-globals=0",
+	}
 )
 
 const profileInstrFlag = "-fprofile-instr-generate=/data/misc/trace/clang-%p-%m.profraw"
@@ -247,9 +247,19 @@
 	return properties
 }
 
+type IsNativeCoverageNeededContext interface {
+	Config() android.Config
+	DeviceConfig() android.DeviceConfig
+	Device() bool
+}
+
+var _ IsNativeCoverageNeededContext = android.IncomingTransitionContext(nil)
+var _ IsNativeCoverageNeededContext = android.BaseModuleContext(nil)
+var _ IsNativeCoverageNeededContext = android.BottomUpMutatorContext(nil)
+
 type UseCoverage interface {
 	android.Module
-	IsNativeCoverageNeeded(ctx android.IncomingTransitionContext) bool
+	IsNativeCoverageNeeded(ctx IsNativeCoverageNeededContext) bool
 }
 
 // Coverage is an interface for non-CC modules to implement to be mutated for coverage
diff --git a/cc/fuzz.go b/cc/fuzz.go
index 2436f33..92f2c5e 100644
--- a/cc/fuzz.go
+++ b/cc/fuzz.go
@@ -128,13 +128,13 @@
 	if ctx.Config().Getenv("FUZZ_FRAMEWORK") == "AFL" {
 		deps.HeaderLibs = append(deps.HeaderLibs, "libafl_headers")
 	} else {
-		deps.StaticLibs = append(deps.StaticLibs, config.LibFuzzerRuntimeLibrary(ctx.toolchain()))
+		deps.StaticLibs = append(deps.StaticLibs, config.LibFuzzerRuntimeLibrary())
 		// Fuzzers built with HWASAN should use the interceptors for better
 		// mutation based on signals in strcmp, memcpy, etc. This is only needed for
 		// fuzz targets, not generic HWASAN-ified binaries or libraries.
 		if module, ok := ctx.Module().(*Module); ok {
 			if module.IsSanitizerEnabled(Hwasan) {
-				deps.StaticLibs = append(deps.StaticLibs, config.LibFuzzerRuntimeInterceptors(ctx.toolchain()))
+				deps.StaticLibs = append(deps.StaticLibs, config.LibFuzzerRuntimeInterceptors())
 			}
 		}
 	}
@@ -433,7 +433,7 @@
 			return
 		}
 		// Discard non-fuzz targets.
-		if ok := fuzz.IsValid(ccModule.FuzzModuleStruct()); !ok {
+		if ok := fuzz.IsValid(ctx, ccModule.FuzzModuleStruct()); !ok {
 			return
 		}
 
@@ -597,7 +597,7 @@
 	ctx.WalkDeps(func(child, parent android.Module) 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_static)
+		// 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
diff --git a/cc/genrule.go b/cc/genrule.go
index 431a01c..fe3b127 100644
--- a/cc/genrule.go
+++ b/cc/genrule.go
@@ -62,6 +62,8 @@
 
 	android.InitApexModule(module)
 
+	android.InitDefaultableModule(module)
+
 	return module
 }
 
@@ -77,6 +79,14 @@
 
 func (g *GenruleExtraProperties) ImageMutatorBegin(ctx android.BaseModuleContext) {}
 
+func (g *GenruleExtraProperties) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
+	return Bool(g.Vendor_available) || Bool(g.Odm_available) || ctx.SocSpecific() || ctx.DeviceSpecific()
+}
+
+func (g *GenruleExtraProperties) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
+	return Bool(g.Product_available) || ctx.ProductSpecific()
+}
+
 func (g *GenruleExtraProperties) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
 	return !(ctx.SocSpecific() || ctx.DeviceSpecific() || ctx.ProductSpecific())
 }
@@ -100,19 +110,8 @@
 }
 
 func (g *GenruleExtraProperties) ExtraImageVariations(ctx android.BaseModuleContext) []string {
-	var variants []string
-	vendorVariantRequired := Bool(g.Vendor_available) || Bool(g.Odm_available) || ctx.SocSpecific() || ctx.DeviceSpecific()
-	productVariantRequired := Bool(g.Product_available) || ctx.ProductSpecific()
-
-	if vendorVariantRequired {
-		variants = append(variants, VendorVariation)
-	}
-	if productVariantRequired {
-		variants = append(variants, ProductVariation)
-	}
-
-	return variants
+	return nil
 }
 
-func (g *GenruleExtraProperties) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) {
+func (g *GenruleExtraProperties) SetImageVariation(ctx android.BaseModuleContext, variation string) {
 }
diff --git a/cc/image.go b/cc/image.go
index f8c5ca5..2e52ccc 100644
--- a/cc/image.go
+++ b/cc/image.go
@@ -17,8 +17,6 @@
 // functions to determine where a module is installed, etc.
 
 import (
-	"fmt"
-	"reflect"
 	"strings"
 
 	"android/soong/android"
@@ -41,18 +39,10 @@
 )
 
 const (
-	// VendorVariation is the variant name used for /vendor code that does not
-	// compile against the VNDK.
-	VendorVariation = "vendor"
-
 	// VendorVariationPrefix is the variant prefix used for /vendor code that compiles
 	// against the VNDK.
 	VendorVariationPrefix = "vendor."
 
-	// ProductVariation is the variant name used for /product code that does not
-	// compile against the VNDK.
-	ProductVariation = "product"
-
 	// ProductVariationPrefix is the variant prefix used for /product code that compiles
 	// against the VNDK.
 	ProductVariationPrefix = "product."
@@ -119,12 +109,12 @@
 
 // Returns true if the module is "product" variant. Usually these modules are installed in /product
 func (c *Module) InProduct() bool {
-	return c.Properties.ImageVariation == ProductVariation
+	return c.Properties.ImageVariation == android.ProductVariation
 }
 
 // Returns true if the module is "vendor" variant. Usually these modules are installed in /vendor
 func (c *Module) InVendor() bool {
-	return c.Properties.ImageVariation == VendorVariation
+	return c.Properties.ImageVariation == android.VendorVariation
 }
 
 // Returns true if the module is "vendor" or "product" variant. This replaces previous UseVndk usages
@@ -157,52 +147,6 @@
 	return c.ModuleBase.InstallInRecovery()
 }
 
-func visitPropsAndCompareVendorAndProductProps(v reflect.Value) bool {
-	if v.Kind() != reflect.Struct {
-		return true
-	}
-	for i := 0; i < v.NumField(); i++ {
-		prop := v.Field(i)
-		if prop.Kind() == reflect.Struct && v.Type().Field(i).Name == "Target" {
-			vendor_prop := prop.FieldByName("Vendor")
-			product_prop := prop.FieldByName("Product")
-			if vendor_prop.Kind() != reflect.Struct && product_prop.Kind() != reflect.Struct {
-				// Neither Target.Vendor nor Target.Product is defined
-				continue
-			}
-			if vendor_prop.Kind() != reflect.Struct || product_prop.Kind() != reflect.Struct ||
-				!reflect.DeepEqual(vendor_prop.Interface(), product_prop.Interface()) {
-				// If only one of either Target.Vendor or Target.Product is
-				// defined or they have different values, it fails the build
-				// since VNDK must have the same properties for both vendor
-				// and product variants.
-				return false
-			}
-		} else if !visitPropsAndCompareVendorAndProductProps(prop) {
-			// Visit the substructures to find Target.Vendor and Target.Product
-			return false
-		}
-	}
-	return true
-}
-
-// In the case of VNDK, vendor and product variants must have the same properties.
-// VNDK installs only one file and shares it for both vendor and product modules on
-// runtime. We may not define different versions of a VNDK lib for each partition.
-// This function is used only for the VNDK modules that is available to both vendor
-// and product partitions.
-func (c *Module) compareVendorAndProductProps() bool {
-	if !c.IsVndk() && !Bool(c.VendorProperties.Product_available) {
-		panic(fmt.Errorf("This is only for product available VNDK libs. %q is not a VNDK library or not product available", c.Name()))
-	}
-	for _, properties := range c.GetProperties() {
-		if !visitPropsAndCompareVendorAndProductProps(reflect.ValueOf(properties).Elem()) {
-			return false
-		}
-	}
-	return true
-}
-
 // ImageMutatableModule provides a common image mutation interface for  LinkableInterface modules.
 type ImageMutatableModule interface {
 	android.Module
@@ -255,67 +199,20 @@
 
 	// SetCoreVariantNeeded sets whether the Core Variant is needed.
 	SetCoreVariantNeeded(b bool)
+
+	// SetProductVariantNeeded sets whether the Product Variant is needed.
+	SetProductVariantNeeded(b bool)
+
+	// SetVendorVariantNeeded sets whether the Vendor Variant is needed.
+	SetVendorVariantNeeded(b bool)
 }
 
 var _ ImageMutatableModule = (*Module)(nil)
 
 func (m *Module) ImageMutatorBegin(mctx android.BaseModuleContext) {
-	m.CheckVndkProperties(mctx)
 	MutateImage(mctx, m)
 }
 
-// CheckVndkProperties checks whether the VNDK-related properties are set correctly.
-// If properties are not set correctly, results in a module context property error.
-func (m *Module) CheckVndkProperties(mctx android.BaseModuleContext) {
-	vendorSpecific := mctx.SocSpecific() || mctx.DeviceSpecific()
-	productSpecific := mctx.ProductSpecific()
-
-	if vndkdep := m.vndkdep; vndkdep != nil {
-		if vndkdep.isVndk() {
-			if vendorSpecific || productSpecific {
-				if !vndkdep.isVndkExt() {
-					mctx.PropertyErrorf("vndk",
-						"must set `extends: \"...\"` to vndk extension")
-				} else if Bool(m.VendorProperties.Vendor_available) {
-					mctx.PropertyErrorf("vendor_available",
-						"must not set at the same time as `vndk: {extends: \"...\"}`")
-				} else if Bool(m.VendorProperties.Product_available) {
-					mctx.PropertyErrorf("product_available",
-						"must not set at the same time as `vndk: {extends: \"...\"}`")
-				}
-			} else {
-				if vndkdep.isVndkExt() {
-					mctx.PropertyErrorf("vndk",
-						"must set `vendor: true` or `product_specific: true` to set `extends: %q`",
-						m.getVndkExtendsModuleName())
-				}
-				if !Bool(m.VendorProperties.Vendor_available) {
-					mctx.PropertyErrorf("vndk",
-						"vendor_available must be set to true when `vndk: {enabled: true}`")
-				}
-				if Bool(m.VendorProperties.Product_available) {
-					// If a VNDK module creates both product and vendor variants, they
-					// must have the same properties since they share a single VNDK
-					// library on runtime.
-					if !m.compareVendorAndProductProps() {
-						mctx.ModuleErrorf("product properties must have the same values with the vendor properties for VNDK modules")
-					}
-				}
-			}
-		} else {
-			if vndkdep.isVndkSp() {
-				mctx.PropertyErrorf("vndk",
-					"must set `enabled: true` to set `support_system_process: true`")
-			}
-			if vndkdep.isVndkExt() {
-				mctx.PropertyErrorf("vndk",
-					"must set `enabled: true` to set `extends: %q`",
-					m.getVndkExtendsModuleName())
-			}
-		}
-	}
-}
-
 func (m *Module) VendorAvailable() bool {
 	return Bool(m.VendorProperties.Vendor_available)
 }
@@ -368,6 +265,14 @@
 	m.Properties.CoreVariantNeeded = b
 }
 
+func (m *Module) SetProductVariantNeeded(b bool) {
+	m.Properties.ProductVariantNeeded = b
+}
+
+func (m *Module) SetVendorVariantNeeded(b bool) {
+	m.Properties.VendorVariantNeeded = b
+}
+
 func (m *Module) SnapshotVersion(mctx android.BaseModuleContext) string {
 	if snapshot, ok := m.linker.(SnapshotInterface); ok {
 		return snapshot.Version()
@@ -420,43 +325,36 @@
 		}
 	}
 
+	var vendorVariantNeeded bool = false
+	var productVariantNeeded bool = false
 	var coreVariantNeeded bool = false
 	var ramdiskVariantNeeded bool = false
 	var vendorRamdiskVariantNeeded bool = false
 	var recoveryVariantNeeded bool = false
 
-	var vendorVariants []string
-	var productVariants []string
-
-	needVndkVersionVendorVariantForLlndk := false
-
 	if m.NeedsLlndkVariants() {
 		// This is an LLNDK library.  The implementation of the library will be on /system,
 		// and vendor and product variants will be created with LLNDK stubs.
 		// The LLNDK libraries need vendor variants even if there is no VNDK.
 		coreVariantNeeded = true
-		vendorVariants = append(vendorVariants, "")
-		productVariants = append(productVariants, "")
-		// Generate vendor variants for boardVndkVersion only if the VNDK snapshot does not
-		// provide the LLNDK stub libraries.
-		if needVndkVersionVendorVariantForLlndk {
-			vendorVariants = append(vendorVariants, "")
-		}
+		vendorVariantNeeded = true
+		productVariantNeeded = true
+
 	} else if m.NeedsVendorPublicLibraryVariants() {
 		// A vendor public library has the implementation on /vendor, with stub variants
 		// for system and product.
 		coreVariantNeeded = true
-		vendorVariants = append(vendorVariants, "")
-		productVariants = append(productVariants, "")
+		vendorVariantNeeded = true
+		productVariantNeeded = true
 	} else if m.IsSnapshotPrebuilt() {
 		// Make vendor variants only for the versions in BOARD_VNDK_VERSION and
 		// PRODUCT_EXTRA_VNDK_VERSIONS.
 		if m.InstallInRecovery() {
 			recoveryVariantNeeded = true
 		} else {
-			vendorVariants = append(vendorVariants, m.SnapshotVersion(mctx))
+			m.AppendExtraVariant(VendorVariationPrefix + m.SnapshotVersion(mctx))
 		}
-	} else if m.HasNonSystemVariants() && !m.IsVndkExt() {
+	} else if m.HasNonSystemVariants() {
 		// This will be available to /system unless it is product_specific
 		// which will be handled later.
 		coreVariantNeeded = true
@@ -465,16 +363,16 @@
 		// BOARD_VNDK_VERSION. The other modules are regarded as AOSP, or
 		// PLATFORM_VNDK_VERSION.
 		if m.HasVendorVariant() {
-			vendorVariants = append(vendorVariants, "")
+			vendorVariantNeeded = true
 		}
 
 		// product_available modules are available to /product.
 		if m.HasProductVariant() {
-			productVariants = append(productVariants, "")
+			productVariantNeeded = true
 		}
 	} else if vendorSpecific && m.SdkVersion() == "" {
 		// This will be available in /vendor (or /odm) only
-		vendorVariants = append(vendorVariants, "")
+		vendorVariantNeeded = true
 	} else {
 		// This is either in /system (or similar: /data), or is a
 		// module built with the NDK. Modules built with the NDK
@@ -485,7 +383,7 @@
 	if coreVariantNeeded && productSpecific && m.SdkVersion() == "" {
 		// The module has "product_specific: true" that does not create core variant.
 		coreVariantNeeded = false
-		productVariants = append(productVariants, "")
+		productVariantNeeded = true
 	}
 
 	if m.RamdiskAvailable() {
@@ -515,36 +413,32 @@
 		coreVariantNeeded = false
 	}
 
-	for _, variant := range android.FirstUniqueStrings(vendorVariants) {
-		if variant == "" {
-			m.AppendExtraVariant(VendorVariation)
-		} else {
-			m.AppendExtraVariant(VendorVariationPrefix + variant)
-		}
-	}
-
-	for _, variant := range android.FirstUniqueStrings(productVariants) {
-		if variant == "" {
-			m.AppendExtraVariant(ProductVariation)
-		} else {
-			m.AppendExtraVariant(ProductVariationPrefix + variant)
-		}
-	}
-
 	m.SetRamdiskVariantNeeded(ramdiskVariantNeeded)
 	m.SetVendorRamdiskVariantNeeded(vendorRamdiskVariantNeeded)
 	m.SetRecoveryVariantNeeded(recoveryVariantNeeded)
 	m.SetCoreVariantNeeded(coreVariantNeeded)
+	m.SetProductVariantNeeded(productVariantNeeded)
+	m.SetVendorVariantNeeded(vendorVariantNeeded)
 
 	// Disable the module if no variants are needed.
 	if !ramdiskVariantNeeded &&
 		!recoveryVariantNeeded &&
 		!coreVariantNeeded &&
+		!productVariantNeeded &&
+		!vendorVariantNeeded &&
 		len(m.ExtraVariants()) == 0 {
 		m.Disable()
 	}
 }
 
+func (c *Module) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
+	return c.Properties.VendorVariantNeeded
+}
+
+func (c *Module) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
+	return c.Properties.ProductVariantNeeded
+}
+
 func (c *Module) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
 	return c.Properties.CoreVariantNeeded
 }
@@ -628,30 +522,29 @@
 	}
 }
 
-func (c *Module) SetImageVariation(ctx android.BaseModuleContext, variant string, module android.Module) {
-	m := module.(*Module)
+func (c *Module) SetImageVariation(ctx android.BaseModuleContext, variant string) {
 	if variant == android.RamdiskVariation {
-		m.MakeAsPlatform()
-		squashRamdiskSrcs(m)
+		c.MakeAsPlatform()
+		squashRamdiskSrcs(c)
 	} else if variant == android.VendorRamdiskVariation {
-		m.MakeAsPlatform()
-		squashVendorRamdiskSrcs(m)
+		c.MakeAsPlatform()
+		squashVendorRamdiskSrcs(c)
 	} else if variant == android.RecoveryVariation {
-		m.MakeAsPlatform()
-		squashRecoverySrcs(m)
-	} else if strings.HasPrefix(variant, VendorVariation) {
-		m.Properties.ImageVariation = VendorVariation
+		c.MakeAsPlatform()
+		squashRecoverySrcs(c)
+	} else if strings.HasPrefix(variant, android.VendorVariation) {
+		c.Properties.ImageVariation = android.VendorVariation
 
 		if strings.HasPrefix(variant, VendorVariationPrefix) {
-			m.Properties.VndkVersion = strings.TrimPrefix(variant, VendorVariationPrefix)
+			c.Properties.VndkVersion = strings.TrimPrefix(variant, VendorVariationPrefix)
 		}
-		squashVendorSrcs(m)
-	} else if strings.HasPrefix(variant, ProductVariation) {
-		m.Properties.ImageVariation = ProductVariation
+		squashVendorSrcs(c)
+	} else if strings.HasPrefix(variant, android.ProductVariation) {
+		c.Properties.ImageVariation = android.ProductVariation
 		if strings.HasPrefix(variant, ProductVariationPrefix) {
-			m.Properties.VndkVersion = strings.TrimPrefix(variant, ProductVariationPrefix)
+			c.Properties.VndkVersion = strings.TrimPrefix(variant, ProductVariationPrefix)
 		}
-		squashProductSrcs(m)
+		squashProductSrcs(c)
 	}
 
 	if c.NeedsVendorPublicLibraryVariants() &&
diff --git a/cc/library.go b/cc/library.go
index 44bbdfc..6dd5b56 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -19,6 +19,7 @@
 	"io"
 	"path/filepath"
 	"regexp"
+	"slices"
 	"strconv"
 	"strings"
 	"sync"
@@ -149,7 +150,7 @@
 
 	Sanitized Sanitized `android:"arch_variant"`
 
-	Cflags []string `android:"arch_variant"`
+	Cflags proptools.Configurable[[]string] `android:"arch_variant"`
 
 	Enabled            *bool    `android:"arch_variant"`
 	Whole_static_libs  []string `android:"arch_variant"`
@@ -190,7 +191,7 @@
 	// be added to the include path (using -I) for this module and any module that links
 	// against this module.  Directories listed in export_include_dirs do not need to be
 	// listed in local_include_dirs.
-	Export_include_dirs []string `android:"arch_variant,variant_prepend"`
+	Export_include_dirs proptools.Configurable[[]string] `android:"arch_variant,variant_prepend"`
 
 	// list of directories that will be added to the system include path
 	// using -isystem for this module and any module that links against this module.
@@ -274,11 +275,12 @@
 type flagExporter struct {
 	Properties FlagExporterProperties
 
-	dirs       android.Paths // Include directories to be included with -I
-	systemDirs android.Paths // System include directories to be included with -isystem
-	flags      []string      // Exported raw flags.
-	deps       android.Paths
-	headers    android.Paths
+	dirs         android.Paths // Include directories to be included with -I
+	systemDirs   android.Paths // System include directories to be included with -isystem
+	flags        []string      // Exported raw flags.
+	deps         android.Paths
+	headers      android.Paths
+	rustRlibDeps []RustRlibDep
 }
 
 // exportedIncludes returns the effective include paths for this module and
@@ -291,7 +293,7 @@
 	if ctx.inProduct() && f.Properties.Target.Product.Override_export_include_dirs != nil {
 		return android.PathsForModuleSrc(ctx, f.Properties.Target.Product.Override_export_include_dirs)
 	}
-	return android.PathsForModuleSrc(ctx, f.Properties.Export_include_dirs)
+	return android.PathsForModuleSrc(ctx, f.Properties.Export_include_dirs.GetOrDefault(ctx, nil))
 }
 
 func (f *flagExporter) exportedSystemIncludes(ctx ModuleContext) android.Paths {
@@ -339,6 +341,10 @@
 	f.deps = append(f.deps, deps...)
 }
 
+func (f *flagExporter) reexportRustStaticDeps(deps ...RustRlibDep) {
+	f.rustRlibDeps = append(f.rustRlibDeps, deps...)
+}
+
 // addExportedGeneratedHeaders does nothing but collects generated header files.
 // This can be differ to exportedDeps which may contain phony files to minimize ninja.
 func (f *flagExporter) addExportedGeneratedHeaders(headers ...android.Path) {
@@ -356,6 +362,8 @@
 		// Used sparingly, for extra files that need to be explicitly exported to dependers,
 		// or for phony files to minimize ninja.
 		Deps: f.deps,
+		// Used for exporting rlib deps of static libraries to dependents.
+		RustRlibDeps: f.rustRlibDeps,
 		// For exported generated headers, such as exported aidl headers, proto headers, or
 		// sysprop headers.
 		GeneratedHeaders: f.headers,
@@ -412,11 +420,6 @@
 
 	postInstallCmds []string
 
-	// If useCoreVariant is true, the vendor variant of a VNDK library is
-	// not installed.
-	useCoreVariant       bool
-	checkSameCoreVariant bool
-
 	skipAPIDefine bool
 
 	// Decorated interfaces
@@ -462,9 +465,9 @@
 	}
 
 	if library.static() {
-		flags.Local.CFlags = append(flags.Local.CFlags, library.StaticProperties.Static.Cflags...)
+		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...)
+		flags.Local.CFlags = append(flags.Local.CFlags, library.SharedProperties.Shared.Cflags.GetOrDefault(ctx, nil)...)
 	}
 
 	if library.shared() {
@@ -709,7 +712,7 @@
 	setStubsVersion(string)
 	stubsVersion() string
 
-	stubsVersions(ctx android.BaseMutatorContext) []string
+	stubsVersions(ctx android.BaseModuleContext) []string
 	setAllStubsVersions([]string)
 	allStubsVersions() []string
 
@@ -1132,9 +1135,18 @@
 	linkerDeps = append(linkerDeps, deps.EarlySharedLibsDeps...)
 	linkerDeps = append(linkerDeps, deps.SharedLibsDeps...)
 	linkerDeps = append(linkerDeps, deps.LateSharedLibsDeps...)
+
+	if generatedLib := generateRustStaticlib(ctx, deps.RustRlibDeps); generatedLib != nil && !library.buildStubs() {
+		if ctx.Module().(*Module).WholeRustStaticlib {
+			deps.WholeStaticLibs = append(deps.WholeStaticLibs, generatedLib)
+		} else {
+			deps.StaticLibs = append(deps.StaticLibs, generatedLib)
+		}
+	}
+
 	transformObjToDynamicBinary(ctx, objs.objFiles, sharedLibs,
-		deps.StaticLibs, deps.LateStaticLibs, deps.WholeStaticLibs,
-		linkerDeps, deps.CrtBegin, deps.CrtEnd, false, builderFlags, outputFile, implicitOutputs, objs.tidyDepFiles)
+		deps.StaticLibs, deps.LateStaticLibs, deps.WholeStaticLibs, linkerDeps, deps.CrtBegin,
+		deps.CrtEnd, false, builderFlags, outputFile, implicitOutputs, objs.tidyDepFiles)
 
 	objs.coverageFiles = append(objs.coverageFiles, deps.StaticLibObjs.coverageFiles...)
 	objs.coverageFiles = append(objs.coverageFiles, deps.WholeStaticLibObjs.coverageFiles...)
@@ -1249,28 +1261,29 @@
 
 func (library *libraryDecorator) linkLlndkSAbiDumpFiles(ctx ModuleContext,
 	deps PathDeps, sAbiDumpFiles android.Paths, soFile android.Path, libFileName string,
-	excludeSymbolVersions, excludeSymbolTags []string) android.Path {
+	excludeSymbolVersions, excludeSymbolTags []string,
+	vendorApiLevel string) android.Path {
 	// NDK symbols in version 34 are LLNDK symbols. Those in version 35 are not.
-	// TODO(b/314010764): Add parameters to read LLNDK symbols from the symbol file.
 	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...),
-		"34")
+		[]string{"llndk=" + vendorApiLevel}, "34", true /* isLlndk */)
 }
 
 func (library *libraryDecorator) linkApexSAbiDumpFiles(ctx ModuleContext,
 	deps PathDeps, sAbiDumpFiles android.Paths, soFile android.Path, libFileName string,
-	excludeSymbolVersions, excludeSymbolTags []string, sdkVersion string) android.Path {
+	excludeSymbolVersions, excludeSymbolTags []string,
+	sdkVersion string) android.Path {
 	return transformDumpToLinkedDump(ctx,
 		sAbiDumpFiles, soFile, libFileName+".apex",
 		library.exportedIncludeDirsForAbiCheck(ctx),
 		android.OptionalPathForModuleSrc(ctx, library.Properties.Stubs.Symbol_file),
 		append([]string{"*_PLATFORM", "*_PRIVATE"}, excludeSymbolVersions...),
 		append([]string{"platform-only"}, excludeSymbolTags...),
-		sdkVersion)
+		[]string{"apex", "systemapi"}, sdkVersion, false /* isLlndk */)
 }
 
 func getRefAbiDumpFile(ctx android.ModuleInstallPathContext,
@@ -1321,7 +1334,7 @@
 // sourceAbiDiff registers a build statement to compare linked sAbi dump files (.lsdump).
 func (library *libraryDecorator) sourceAbiDiff(ctx android.ModuleContext,
 	sourceDump, referenceDump android.Path,
-	baseName, nameExt string, isLlndkOrNdk, allowExtensions bool,
+	baseName, nameExt string, isLlndk, allowExtensions bool,
 	sourceVersion, errorMessage string) {
 
 	extraFlags := []string{"-target-version", sourceVersion}
@@ -1333,7 +1346,7 @@
 			"-allow-unreferenced-changes",
 			"-allow-unreferenced-elf-symbol-changes")
 	}
-	if isLlndkOrNdk {
+	if isLlndk {
 		extraFlags = append(extraFlags, "-consider-opaque-types-different")
 	}
 	if allowExtensions {
@@ -1349,38 +1362,49 @@
 
 func (library *libraryDecorator) crossVersionAbiDiff(ctx android.ModuleContext,
 	sourceDump, referenceDump android.Path,
-	baseName string, isLlndkOrNdk bool, sourceVersion, prevVersion string) {
+	baseName, nameExt string, isLlndk bool, sourceVersion, prevDumpDir string) {
 
-	errorMessage := "error: Please follow https://android.googlesource.com/platform/development/+/main/vndk/tools/header-checker/README.md#configure-cross_version-abi-check to resolve the ABI difference between your source code and version " + prevVersion + "."
+	errorMessage := "error: Please follow https://android.googlesource.com/platform/development/+/main/vndk/tools/header-checker/README.md#configure-cross_version-abi-check to resolve the difference between your source code and the ABI dumps in " + prevDumpDir
 
-	library.sourceAbiDiff(ctx, sourceDump, referenceDump, baseName, prevVersion,
-		isLlndkOrNdk, true /* allowExtensions */, sourceVersion, errorMessage)
+	library.sourceAbiDiff(ctx, sourceDump, referenceDump, baseName, nameExt,
+		isLlndk, true /* allowExtensions */, sourceVersion, errorMessage)
 }
 
 func (library *libraryDecorator) sameVersionAbiDiff(ctx android.ModuleContext,
 	sourceDump, referenceDump android.Path,
-	baseName, nameExt string, isLlndkOrNdk bool) {
+	baseName, nameExt string, isLlndk bool, lsdumpTagName string) {
 
 	libName := strings.TrimSuffix(baseName, filepath.Ext(baseName))
-	errorMessage := "error: Please update ABI references with: $$ANDROID_BUILD_TOP/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l " + libName
+	errorMessage := "error: Please update ABI references with: $$ANDROID_BUILD_TOP/development/vndk/tools/header-checker/utils/create_reference_dumps.py --lib " + libName + " --lib-variant " + lsdumpTagName
+
+	targetRelease := ctx.Config().Getenv("TARGET_RELEASE")
+	if targetRelease != "" {
+		errorMessage += " --release " + targetRelease
+	}
 
 	library.sourceAbiDiff(ctx, sourceDump, referenceDump, baseName, nameExt,
-		isLlndkOrNdk, false /* allowExtensions */, "current", errorMessage)
+		isLlndk, false /* allowExtensions */, "current", errorMessage)
 }
 
 func (library *libraryDecorator) optInAbiDiff(ctx android.ModuleContext,
 	sourceDump, referenceDump android.Path,
-	baseName, nameExt string, refDumpDir string) {
+	baseName, nameExt string, refDumpDir string, lsdumpTagName string) {
 
 	libName := strings.TrimSuffix(baseName, filepath.Ext(baseName))
-	errorMessage := "error: Please update ABI references with: $$ANDROID_BUILD_TOP/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l " + libName + " -ref-dump-dir $$ANDROID_BUILD_TOP/" + refDumpDir
+	errorMessage := "error: Please update ABI references with: $$ANDROID_BUILD_TOP/development/vndk/tools/header-checker/utils/create_reference_dumps.py --lib " + libName + " --lib-variant " + lsdumpTagName + " --ref-dump-dir $$ANDROID_BUILD_TOP/" + refDumpDir
+
+	targetRelease := ctx.Config().Getenv("TARGET_RELEASE")
+	if targetRelease != "" {
+		errorMessage += " --release " + targetRelease
+	}
+
 	// Most opt-in libraries do not have dumps for all default architectures.
 	if ctx.Config().HasDeviceProduct() {
-		errorMessage += " -products " + ctx.Config().DeviceProduct()
+		errorMessage += " --product " + ctx.Config().DeviceProduct()
 	}
 
 	library.sourceAbiDiff(ctx, sourceDump, referenceDump, baseName, nameExt,
-		false /* isLlndkOrNdk */, false /* allowExtensions */, "current", errorMessage)
+		false /* isLlndk */, false /* allowExtensions */, "current", errorMessage)
 }
 
 func (library *libraryDecorator) linkSAbiDumpFiles(ctx ModuleContext, deps PathDeps, objs Objects, fileName string, soFile android.Path) {
@@ -1397,18 +1421,20 @@
 			android.OptionalPathForModuleSrc(ctx, library.symbolFileForAbiCheck(ctx)),
 			headerAbiChecker.Exclude_symbol_versions,
 			headerAbiChecker.Exclude_symbol_tags,
-			currSdkVersion)
+			[]string{} /* includeSymbolTags */, currSdkVersion, false /* isLlndk */)
 
 		var llndkDump, apexVariantDump android.Path
 		tags := classifySourceAbiDump(ctx)
+		optInTags := []lsdumpTag{}
 		for _, tag := range tags {
-			if tag == llndkLsdumpTag {
+			if tag == llndkLsdumpTag && currVendorVersion != "" {
 				if llndkDump == nil {
 					// 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)
+						headerAbiChecker.Exclude_symbol_tags,
+						currVendorVersion)
 				}
 				addLsdumpPath(string(tag) + ":" + llndkDump.String())
 			} else if tag == apexLsdumpTag {
@@ -1421,6 +1447,9 @@
 				}
 				addLsdumpPath(string(tag) + ":" + apexVariantDump.String())
 			} else {
+				if tag.dirName() == "" {
+					optInTags = append(optInTags, tag)
+				}
 				addLsdumpPath(string(tag) + ":" + implDump.String())
 			}
 		}
@@ -1434,7 +1463,6 @@
 			dumpDir := filepath.Join("prebuilts", "abi-dumps", dumpDirName)
 			isLlndk := (tag == llndkLsdumpTag)
 			isApex := (tag == apexLsdumpTag)
-			isNdk := (tag == ndkLsdumpTag)
 			binderBitness := ctx.DeviceConfig().BinderBitness()
 			nameExt := ""
 			if isLlndk {
@@ -1466,7 +1494,7 @@
 			prevDumpFile := getRefAbiDumpFile(ctx, prevDumpDir, fileName)
 			if prevDumpFile.Valid() {
 				library.crossVersionAbiDiff(ctx, sourceDump, prevDumpFile.Path(),
-					fileName, isLlndk || isNdk, currVersion, nameExt+prevVersion)
+					fileName, nameExt+prevVersion, isLlndk, currVersion, prevDumpDir)
 			}
 			// Check against the current version.
 			sourceDump = implDump
@@ -1486,9 +1514,20 @@
 			currDumpFile := getRefAbiDumpFile(ctx, currDumpDir, fileName)
 			if currDumpFile.Valid() {
 				library.sameVersionAbiDiff(ctx, sourceDump, currDumpFile.Path(),
-					fileName, nameExt, isLlndk || isNdk)
+					fileName, nameExt, isLlndk, string(tag))
 			}
 		}
+
+		// Assert that a module is tagged with at most one of platformLsdumpTag, productLsdumpTag, or vendorLsdumpTag.
+		if len(headerAbiChecker.Ref_dump_dirs) > 0 && len(optInTags) != 1 {
+			ctx.ModuleErrorf("Expect exactly one opt-in lsdump tag when ref_dump_dirs are specified: %s", optInTags)
+			return
+		}
+		// Ensure that a module tagged with only platformLsdumpTag has ref_dump_dirs.
+		// Android.bp in vendor projects should be cleaned up before this is enforced for vendorLsdumpTag and productLsdumpTag.
+		if len(headerAbiChecker.Ref_dump_dirs) == 0 && len(tags) == 1 && tags[0] == platformLsdumpTag {
+			ctx.ModuleErrorf("header_abi_checker is explicitly enabled, but no ref_dump_dirs are specified.")
+		}
 		// Check against the opt-in reference dumps.
 		for i, optInDumpDir := range headerAbiChecker.Ref_dump_dirs {
 			optInDumpDirPath := android.PathForModuleSrc(ctx, optInDumpDir)
@@ -1500,7 +1539,7 @@
 			}
 			library.optInAbiDiff(ctx,
 				implDump, optInDumpFile.Path(),
-				fileName, "opt"+strconv.Itoa(i), optInDumpDirPath.String())
+				fileName, "opt"+strconv.Itoa(i), optInDumpDirPath.String(), string(optInTags[0]))
 		}
 	}
 }
@@ -1554,14 +1593,19 @@
 		// override the module's export_include_dirs with llndk.override_export_include_dirs
 		// if it is set.
 		if override := library.Properties.Llndk.Override_export_include_dirs; override != nil {
-			library.flagExporter.Properties.Export_include_dirs = override
+			library.flagExporter.Properties.Export_include_dirs = proptools.NewConfigurable[[]string](
+				nil,
+				[]proptools.ConfigurableCase[[]string]{
+					proptools.NewConfigurableCase[[]string](nil, &override),
+				},
+			)
 		}
 
 		if Bool(library.Properties.Llndk.Export_headers_as_system) {
 			library.flagExporter.Properties.Export_system_include_dirs = append(
 				library.flagExporter.Properties.Export_system_include_dirs,
-				library.flagExporter.Properties.Export_include_dirs...)
-			library.flagExporter.Properties.Export_include_dirs = nil
+				library.flagExporter.Properties.Export_include_dirs.GetOrDefault(ctx, nil)...)
+			library.flagExporter.Properties.Export_include_dirs = proptools.NewConfigurable[[]string](nil, nil)
 		}
 	}
 
@@ -1569,7 +1613,12 @@
 		// override the module's export_include_dirs with vendor_public_library.override_export_include_dirs
 		// if it is set.
 		if override := library.Properties.Vendor_public_library.Override_export_include_dirs; override != nil {
-			library.flagExporter.Properties.Export_include_dirs = override
+			library.flagExporter.Properties.Export_include_dirs = proptools.NewConfigurable[[]string](
+				nil,
+				[]proptools.ConfigurableCase[[]string]{
+					proptools.NewConfigurableCase[[]string](nil, &override),
+				},
+			)
 		}
 	}
 
@@ -1592,6 +1641,10 @@
 	library.reexportDeps(deps.ReexportedDeps...)
 	library.addExportedGeneratedHeaders(deps.ReexportedGeneratedHeaders...)
 
+	if library.static() && len(deps.ReexportedRustRlibDeps) > 0 {
+		library.reexportRustStaticDeps(deps.ReexportedRustRlibDeps...)
+	}
+
 	// Optionally export aidl headers.
 	if Bool(library.Properties.Aidl.Export_aidl_headers) {
 		if library.baseCompiler.hasAidl(deps) {
@@ -1714,43 +1767,7 @@
 
 func (library *libraryDecorator) install(ctx ModuleContext, file android.Path) {
 	if library.shared() {
-		if ctx.Device() && ctx.useVndk() {
-			// set subDir for VNDK extensions
-			if ctx.IsVndkExt() {
-				if ctx.isVndkSp() {
-					library.baseInstaller.subDir = "vndk-sp"
-				} else {
-					library.baseInstaller.subDir = "vndk"
-				}
-			}
-
-			// In some cases we want to use core variant for VNDK-Core libs.
-			// Skip product variant since VNDKs use only the vendor variant.
-			if ctx.isVndk() && !ctx.isVndkSp() && !ctx.IsVndkExt() && !ctx.inProduct() {
-				mayUseCoreVariant := true
-
-				if ctx.mustUseVendorVariant() {
-					mayUseCoreVariant = false
-				}
-
-				if ctx.Config().CFIEnabledForPath(ctx.ModuleDir()) {
-					mayUseCoreVariant = false
-				}
-
-				if mayUseCoreVariant {
-					library.checkSameCoreVariant = true
-					if ctx.DeviceConfig().VndkUseCoreVariant() {
-						library.useCoreVariant = true
-					}
-				}
-			}
-
-			// do not install vndk libs
-			// vndk libs are packaged into VNDK APEX
-			if ctx.isVndk() && !ctx.IsVndkExt() && !ctx.Config().IsVndkDeprecated() && !ctx.inProduct() {
-				return
-			}
-		} else if library.hasStubsVariants() && !ctx.Host() && ctx.directlyInAnyApex() {
+		if library.hasStubsVariants() && !ctx.Host() && ctx.directlyInAnyApex() {
 			// 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.
@@ -1887,7 +1904,7 @@
 	return BoolDefault(library.Properties.Stubs.Implementation_installable, true)
 }
 
-func (library *libraryDecorator) stubsVersions(ctx android.BaseMutatorContext) []string {
+func (library *libraryDecorator) stubsVersions(ctx android.BaseModuleContext) []string {
 	if !library.hasStubsVariants() {
 		return nil
 	}
@@ -2048,26 +2065,26 @@
 
 // connects a shared library to a static library in order to reuse its .o files to avoid
 // compiling source files twice.
-func reuseStaticLibrary(mctx android.BottomUpMutatorContext, static, shared *Module) {
-	if staticCompiler, ok := static.compiler.(*libraryDecorator); ok {
-		sharedCompiler := shared.compiler.(*libraryDecorator)
+func reuseStaticLibrary(ctx android.BottomUpMutatorContext, shared *Module) {
+	if sharedCompiler, ok := shared.compiler.(*libraryDecorator); ok {
 
 		// Check libraries in addition to cflags, since libraries may be exporting different
 		// include directories.
-		if len(staticCompiler.StaticProperties.Static.Cflags) == 0 &&
-			len(sharedCompiler.SharedProperties.Shared.Cflags) == 0 &&
-			len(staticCompiler.StaticProperties.Static.Whole_static_libs) == 0 &&
+		if len(sharedCompiler.StaticProperties.Static.Cflags.GetOrDefault(ctx, nil)) == 0 &&
+			len(sharedCompiler.SharedProperties.Shared.Cflags.GetOrDefault(ctx, nil)) == 0 &&
+			len(sharedCompiler.StaticProperties.Static.Whole_static_libs) == 0 &&
 			len(sharedCompiler.SharedProperties.Shared.Whole_static_libs) == 0 &&
-			len(staticCompiler.StaticProperties.Static.Static_libs) == 0 &&
+			len(sharedCompiler.StaticProperties.Static.Static_libs) == 0 &&
 			len(sharedCompiler.SharedProperties.Shared.Static_libs) == 0 &&
-			len(staticCompiler.StaticProperties.Static.Shared_libs) == 0 &&
+			len(sharedCompiler.StaticProperties.Static.Shared_libs) == 0 &&
 			len(sharedCompiler.SharedProperties.Shared.Shared_libs) == 0 &&
 			// Compare System_shared_libs properties with nil because empty lists are
 			// semantically significant for them.
-			staticCompiler.StaticProperties.Static.System_shared_libs == nil &&
+			sharedCompiler.StaticProperties.Static.System_shared_libs == nil &&
 			sharedCompiler.SharedProperties.Shared.System_shared_libs == nil {
 
-			mctx.AddInterVariantDependency(reuseObjTag, shared, static)
+			// TODO: namespaces?
+			ctx.AddVariationDependencies([]blueprint.Variation{{"link", "static"}}, reuseObjTag, ctx.ModuleName())
 			sharedCompiler.baseCompiler.Properties.OriginalSrcs =
 				sharedCompiler.baseCompiler.Properties.Srcs
 			sharedCompiler.baseCompiler.Properties.Srcs = nil
@@ -2075,19 +2092,21 @@
 		}
 
 		// This dep is just to reference static variant from shared variant
-		mctx.AddInterVariantDependency(staticVariantTag, shared, static)
+		ctx.AddVariationDependencies([]blueprint.Variation{{"link", "static"}}, staticVariantTag, ctx.ModuleName())
 	}
 }
 
-// LinkageMutator adds "static" or "shared" variants for modules depending
+// linkageTransitionMutator adds "static" or "shared" variants for modules depending
 // on whether the module can be built as a static library or a shared library.
-func LinkageMutator(mctx android.BottomUpMutatorContext) {
+type linkageTransitionMutator struct{}
+
+func (linkageTransitionMutator) Split(ctx android.BaseModuleContext) []string {
 	ccPrebuilt := false
-	if m, ok := mctx.Module().(*Module); ok && m.linker != nil {
+	if m, ok := ctx.Module().(*Module); ok && m.linker != nil {
 		_, ccPrebuilt = m.linker.(prebuiltLibraryInterface)
 	}
 	if ccPrebuilt {
-		library := mctx.Module().(*Module).linker.(prebuiltLibraryInterface)
+		library := ctx.Module().(*Module).linker.(prebuiltLibraryInterface)
 
 		// Differentiate between header only and building an actual static/shared library
 		buildStatic := library.buildStatic()
@@ -2096,72 +2115,118 @@
 			// Always create both the static and shared variants for prebuilt libraries, and then disable the one
 			// that is not being used.  This allows them to share the name of a cc_library module, which requires that
 			// all the variants of the cc_library also exist on the prebuilt.
-			modules := mctx.CreateLocalVariations("static", "shared")
-			static := modules[0].(*Module)
-			shared := modules[1].(*Module)
-
-			static.linker.(prebuiltLibraryInterface).setStatic()
-			shared.linker.(prebuiltLibraryInterface).setShared()
-
-			if buildShared {
-				mctx.AliasVariation("shared")
-			} else if buildStatic {
-				mctx.AliasVariation("static")
-			}
-
-			if !buildStatic {
-				static.linker.(prebuiltLibraryInterface).disablePrebuilt()
-			}
-			if !buildShared {
-				shared.linker.(prebuiltLibraryInterface).disablePrebuilt()
-			}
+			return []string{"static", "shared"}
 		} else {
 			// Header only
 		}
-
-	} else if library, ok := mctx.Module().(LinkableInterface); ok && library.CcLibraryInterface() {
-
+	} else if library, ok := ctx.Module().(LinkableInterface); ok && (library.CcLibraryInterface() || library.RustLibraryInterface()) {
 		// Non-cc.Modules may need an empty variant for their mutators.
 		variations := []string{}
 		if library.NonCcVariants() {
 			variations = append(variations, "")
 		}
-
 		isLLNDK := false
-		if m, ok := mctx.Module().(*Module); ok {
+		if m, ok := ctx.Module().(*Module); ok {
 			isLLNDK = m.IsLlndk()
 		}
 		buildStatic := library.BuildStaticVariant() && !isLLNDK
 		buildShared := library.BuildSharedVariant()
 		if buildStatic && buildShared {
-			variations := append([]string{"static", "shared"}, variations...)
-
-			modules := mctx.CreateLocalVariations(variations...)
-			static := modules[0].(LinkableInterface)
-			shared := modules[1].(LinkableInterface)
-
-			static.SetStatic()
-			shared.SetShared()
-
-			if _, ok := library.(*Module); ok {
-				reuseStaticLibrary(mctx, static.(*Module), shared.(*Module))
-			}
-			mctx.AliasVariation("shared")
+			variations = append([]string{"static", "shared"}, variations...)
+			return variations
 		} else if buildStatic {
-			variations := append([]string{"static"}, variations...)
-
-			modules := mctx.CreateLocalVariations(variations...)
-			modules[0].(LinkableInterface).SetStatic()
-			mctx.AliasVariation("static")
+			variations = append([]string{"static"}, variations...)
 		} else if buildShared {
-			variations := append([]string{"shared"}, variations...)
+			variations = append([]string{"shared"}, variations...)
+		}
 
-			modules := mctx.CreateLocalVariations(variations...)
-			modules[0].(LinkableInterface).SetShared()
-			mctx.AliasVariation("shared")
-		} else if len(variations) > 0 {
-			mctx.CreateLocalVariations(variations...)
-			mctx.AliasVariation(variations[0])
+		if len(variations) > 0 {
+			return variations
+		}
+	}
+	return []string{""}
+}
+
+func (linkageTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+	return ""
+}
+
+func (linkageTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
+	ccPrebuilt := false
+	if m, ok := ctx.Module().(*Module); ok && m.linker != nil {
+		_, ccPrebuilt = m.linker.(prebuiltLibraryInterface)
+	}
+	if ccPrebuilt {
+		if incomingVariation != "" {
+			return incomingVariation
+		}
+		library := ctx.Module().(*Module).linker.(prebuiltLibraryInterface)
+		if library.buildShared() {
+			return "shared"
+		} else if library.buildStatic() {
+			return "static"
+		}
+		return ""
+	} else if library, ok := ctx.Module().(LinkableInterface); ok && library.CcLibraryInterface() {
+		isLLNDK := false
+		if m, ok := ctx.Module().(*Module); ok {
+			isLLNDK = m.IsLlndk()
+		}
+		buildStatic := library.BuildStaticVariant() && !isLLNDK
+		buildShared := library.BuildSharedVariant()
+		if library.BuildRlibVariant() && library.IsRustFFI() && !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.
+			return ""
+		}
+		if incomingVariation != "" {
+			return incomingVariation
+		}
+		if buildShared {
+			return "shared"
+		} else if buildStatic {
+			return "static"
+		}
+		return ""
+	}
+	return ""
+}
+
+func (linkageTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
+	ccPrebuilt := false
+	if m, ok := ctx.Module().(*Module); ok && m.linker != nil {
+		_, ccPrebuilt = m.linker.(prebuiltLibraryInterface)
+	}
+	if ccPrebuilt {
+		library := ctx.Module().(*Module).linker.(prebuiltLibraryInterface)
+		if variation == "static" {
+			library.setStatic()
+			if !library.buildStatic() {
+				library.disablePrebuilt()
+			}
+		} else if variation == "shared" {
+			library.setShared()
+			if !library.buildShared() {
+				library.disablePrebuilt()
+			}
+		}
+	} else if library, ok := ctx.Module().(LinkableInterface); ok && library.CcLibraryInterface() {
+		if variation == "static" {
+			library.SetStatic()
+		} else if variation == "shared" {
+			library.SetShared()
+			var isLLNDK bool
+			if m, ok := ctx.Module().(*Module); ok {
+				isLLNDK = m.IsLlndk()
+			}
+			buildStatic := library.BuildStaticVariant() && !isLLNDK
+			buildShared := library.BuildSharedVariant()
+			if buildStatic && buildShared {
+				if _, ok := library.(*Module); ok {
+					reuseStaticLibrary(ctx, library.(*Module))
+				}
+			}
 		}
 	}
 }
@@ -2185,60 +2250,14 @@
 	}
 }
 
-func createVersionVariations(mctx android.BottomUpMutatorContext, versions []string) {
-	// "" is for the non-stubs (implementation) variant for system modules, or the LLNDK variant
-	// for LLNDK modules.
-	variants := append(android.CopyOf(versions), "")
-
-	m := mctx.Module().(*Module)
-	isLLNDK := m.IsLlndk()
-	isVendorPublicLibrary := m.IsVendorPublicLibrary()
-	isImportedApiLibrary := m.isImportedApiLibrary()
-
-	modules := mctx.CreateLocalVariations(variants...)
-	for i, m := range modules {
-
-		if variants[i] != "" || isLLNDK || isVendorPublicLibrary || isImportedApiLibrary {
-			// A stubs or LLNDK stubs variant.
-			c := m.(*Module)
-			c.sanitize = nil
-			c.stl = nil
-			c.Properties.PreventInstall = true
-			lib := moduleLibraryInterface(m)
-			isLatest := i == (len(versions) - 1)
-			lib.setBuildStubs(isLatest)
-
-			if variants[i] != "" {
-				// A non-LLNDK stubs module is hidden from make and has a dependency from the
-				// implementation module to the stubs module.
-				c.Properties.HideFromMake = true
-				lib.setStubsVersion(variants[i])
-				mctx.AddInterVariantDependency(stubImplDepTag, modules[len(modules)-1], modules[i])
-			}
-		}
-	}
-	mctx.AliasVariation("")
-	latestVersion := ""
-	if len(versions) > 0 {
-		latestVersion = versions[len(versions)-1]
-	}
-	mctx.CreateAliasVariation("latest", latestVersion)
-}
-
-func createPerApiVersionVariations(mctx android.BottomUpMutatorContext, minSdkVersion string) {
+func perApiVersionVariations(mctx android.BaseModuleContext, minSdkVersion string) []string {
 	from, err := nativeApiLevelFromUser(mctx, minSdkVersion)
 	if err != nil {
 		mctx.PropertyErrorf("min_sdk_version", err.Error())
-		return
+		return []string{""}
 	}
 
-	versionStrs := ndkLibraryVersions(mctx, from)
-	modules := mctx.CreateLocalVariations(versionStrs...)
-
-	for i, module := range modules {
-		module.(*Module).Properties.Sdk_version = StringPtr(versionStrs[i])
-		module.(*Module).Properties.Min_sdk_version = StringPtr(versionStrs[i])
-	}
+	return ndkLibraryVersions(mctx, from)
 }
 
 func canBeOrLinkAgainstVersionVariants(module interface {
@@ -2268,7 +2287,7 @@
 }
 
 // setStubsVersions normalizes the versions in the Stubs.Versions property into MutatedProperties.AllStubsVersions.
-func setStubsVersions(mctx android.BottomUpMutatorContext, library libraryInterface, module *Module) {
+func setStubsVersions(mctx android.BaseModuleContext, library libraryInterface, module *Module) {
 	if !library.buildShared() || !canBeVersionVariant(module) {
 		return
 	}
@@ -2281,25 +2300,98 @@
 	library.setAllStubsVersions(versions)
 }
 
-// versionMutator splits a module into the mandatory non-stubs variant
+// versionTransitionMutator splits a module into the mandatory non-stubs variant
 // (which is unnamed) and zero or more stubs variants.
-func versionMutator(mctx android.BottomUpMutatorContext) {
-	if mctx.Os() != android.Android {
-		return
+type versionTransitionMutator struct{}
+
+func (versionTransitionMutator) Split(ctx android.BaseModuleContext) []string {
+	if ctx.Os() != android.Android {
+		return []string{""}
 	}
 
-	m, ok := mctx.Module().(*Module)
-	if library := moduleLibraryInterface(mctx.Module()); library != nil && canBeVersionVariant(m) {
-		setStubsVersions(mctx, library, m)
+	m, ok := ctx.Module().(*Module)
+	if library := moduleLibraryInterface(ctx.Module()); library != nil && canBeVersionVariant(m) {
+		setStubsVersions(ctx, library, m)
 
-		createVersionVariations(mctx, library.allStubsVersions())
-		return
+		return append(slices.Clone(library.allStubsVersions()), "")
+	} else if ok && m.SplitPerApiLevel() && m.IsSdkVariant() {
+		return perApiVersionVariations(ctx, m.MinSdkVersion())
 	}
 
-	if ok {
-		if m.SplitPerApiLevel() && m.IsSdkVariant() {
-			createPerApiVersionVariations(mctx, m.MinSdkVersion())
+	return []string{""}
+}
+
+func (versionTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+	return ""
+}
+
+func (versionTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
+	if ctx.Os() != android.Android {
+		return ""
+	}
+	m, ok := ctx.Module().(*Module)
+	if library := moduleLibraryInterface(ctx.Module()); library != nil && canBeVersionVariant(m) {
+		if incomingVariation == "latest" {
+			latestVersion := ""
+			versions := library.allStubsVersions()
+			if len(versions) > 0 {
+				latestVersion = versions[len(versions)-1]
+			}
+			return latestVersion
 		}
+		return incomingVariation
+	} else if ok && m.SplitPerApiLevel() && m.IsSdkVariant() {
+		// If this module only has variants with versions and the incoming dependency doesn't specify which one
+		// is needed then assume the latest version.
+		if incomingVariation == "" {
+			return android.FutureApiLevel.String()
+		}
+		return incomingVariation
+	}
+
+	return ""
+}
+
+func (versionTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
+	// Optimization: return early if this module can't be affected.
+	if ctx.Os() != android.Android {
+		return
+	}
+
+	m, ok := ctx.Module().(*Module)
+	if library := moduleLibraryInterface(ctx.Module()); library != nil && canBeVersionVariant(m) {
+		isLLNDK := m.IsLlndk()
+		isVendorPublicLibrary := m.IsVendorPublicLibrary()
+		isImportedApiLibrary := m.isImportedApiLibrary()
+
+		if variation != "" || isLLNDK || isVendorPublicLibrary || isImportedApiLibrary {
+			// A stubs or LLNDK stubs variant.
+			if m.sanitize != nil {
+				m.sanitize.Properties.ForceDisable = true
+			}
+			if m.stl != nil {
+				m.stl.Properties.Stl = StringPtr("none")
+			}
+			m.Properties.PreventInstall = true
+			lib := moduleLibraryInterface(m)
+			allStubsVersions := library.allStubsVersions()
+			isLatest := len(allStubsVersions) > 0 && variation == allStubsVersions[len(allStubsVersions)-1]
+			lib.setBuildStubs(isLatest)
+		}
+		if variation != "" {
+			// A non-LLNDK stubs module is hidden from make
+			library.setStubsVersion(variation)
+			m.Properties.HideFromMake = true
+		} 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())
+			}
+		}
+	} else if ok && m.SplitPerApiLevel() && m.IsSdkVariant() {
+		m.Properties.Sdk_version = StringPtr(variation)
+		m.Properties.Min_sdk_version = StringPtr(variation)
 	}
 }
 
diff --git a/cc/library_sdk_member.go b/cc/library_sdk_member.go
index a65b1ba..1f71c19 100644
--- a/cc/library_sdk_member.go
+++ b/cc/library_sdk_member.go
@@ -31,6 +31,7 @@
 		SupportsSdk:           true,
 		HostOsDependent:       true,
 		SupportedLinkageNames: []string{"shared"},
+		StripDisabled:         true,
 	},
 	prebuiltModuleType: "cc_prebuilt_library_shared",
 }
diff --git a/cc/library_stub.go b/cc/library_stub.go
index cddb1b5..6367825 100644
--- a/cc/library_stub.go
+++ b/cc/library_stub.go
@@ -20,6 +20,8 @@
 
 	"android/soong/android"
 	"android/soong/multitree"
+
+	"github.com/google/blueprint/proptools"
 )
 
 var (
@@ -122,7 +124,7 @@
 // The directories are not guaranteed to exist during Soong analysis.
 func (d *apiLibraryDecorator) exportIncludes(ctx ModuleContext) {
 	exporterProps := d.flagExporter.Properties
-	for _, dir := range exporterProps.Export_include_dirs {
+	for _, dir := range exporterProps.Export_include_dirs.GetOrDefault(ctx, nil) {
 		d.dirs = append(d.dirs, android.MaybeExistentPathForSource(ctx, ctx.ModuleDir(), dir))
 	}
 	// system headers
@@ -178,16 +180,21 @@
 				in = variantMod.Src()
 
 				// Copy LLDNK properties to cc_api_library module
-				d.libraryDecorator.flagExporter.Properties.Export_include_dirs = append(
-					d.libraryDecorator.flagExporter.Properties.Export_include_dirs,
+				exportIncludeDirs := append(d.libraryDecorator.flagExporter.Properties.Export_include_dirs.GetOrDefault(ctx, nil),
 					variantMod.exportProperties.Export_include_dirs...)
+				d.libraryDecorator.flagExporter.Properties.Export_include_dirs = proptools.NewConfigurable[[]string](
+					nil,
+					[]proptools.ConfigurableCase[[]string]{
+						proptools.NewConfigurableCase[[]string](nil, &exportIncludeDirs),
+					},
+				)
 
 				// Export headers as system include dirs if specified. Mostly for libc
 				if Bool(variantMod.exportProperties.Export_headers_as_system) {
 					d.libraryDecorator.flagExporter.Properties.Export_system_include_dirs = append(
 						d.libraryDecorator.flagExporter.Properties.Export_system_include_dirs,
-						d.libraryDecorator.flagExporter.Properties.Export_include_dirs...)
-					d.libraryDecorator.flagExporter.Properties.Export_include_dirs = nil
+						d.libraryDecorator.flagExporter.Properties.Export_include_dirs.GetOrDefault(ctx, nil)...)
+					d.libraryDecorator.flagExporter.Properties.Export_include_dirs = proptools.NewConfigurable[[]string](nil, nil)
 				}
 			}
 		}
@@ -296,7 +303,7 @@
 	return d.hasApexStubs()
 }
 
-func (d *apiLibraryDecorator) stubsVersions(ctx android.BaseMutatorContext) []string {
+func (d *apiLibraryDecorator) stubsVersions(ctx android.BaseModuleContext) []string {
 	m, ok := ctx.Module().(*Module)
 
 	if !ok {
@@ -487,6 +494,12 @@
 
 // Implement ImageInterface to generate image variants
 func (v *CcApiVariant) ImageMutatorBegin(ctx android.BaseModuleContext) {}
+func (v *CcApiVariant) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
+	return String(v.properties.Variant) == "llndk"
+}
+func (v *CcApiVariant) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
+	return String(v.properties.Variant) == "llndk"
+}
 func (v *CcApiVariant) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
 	return inList(String(v.properties.Variant), []string{"ndk", "apex"})
 }
@@ -494,15 +507,6 @@
 func (v *CcApiVariant) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool { return false }
 func (v *CcApiVariant) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool  { return false }
 func (v *CcApiVariant) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool      { return false }
-func (v *CcApiVariant) ExtraImageVariations(ctx android.BaseModuleContext) []string {
-	var variations []string
-
-	if String(v.properties.Variant) == "llndk" {
-		variations = append(variations, VendorVariation)
-		variations = append(variations, ProductVariation)
-	}
-
-	return variations
-}
-func (v *CcApiVariant) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) {
+func (v *CcApiVariant) ExtraImageVariations(ctx android.BaseModuleContext) []string   { return nil }
+func (v *CcApiVariant) SetImageVariation(ctx android.BaseModuleContext, variation string) {
 }
diff --git a/cc/linkable.go b/cc/linkable.go
index 10cc38f..1672366 100644
--- a/cc/linkable.go
+++ b/cc/linkable.go
@@ -73,6 +73,12 @@
 	// RustLibraryInterface returns true if this is a Rust library module
 	RustLibraryInterface() bool
 
+	// CrateName returns the crateName for a Rust library, panics if not a Rust library.
+	CrateName() string
+
+	// DepFlags returns a slice of Rustc string flags, panics if not a Rust library
+	ExportedCrateLinkDirs() []string
+
 	// BaseModuleName returns the android.ModuleBase.BaseModuleName() value for this module.
 	BaseModuleName() string
 
@@ -88,12 +94,16 @@
 	SelectedStl() string
 
 	BuildStaticVariant() bool
+	BuildRlibVariant() bool
 	BuildSharedVariant() bool
 	SetStatic()
 	SetShared()
 	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
 
@@ -130,9 +140,6 @@
 	// IsLlndk returns true for both LLNDK (public) and LLNDK-private libs.
 	IsLlndk() bool
 
-	// IsLlndkPublic returns true only for LLNDK (public) libs.
-	IsLlndkPublic() bool
-
 	// HasLlndkStubs returns true if this library has a variant that will build LLNDK stubs.
 	HasLlndkStubs() bool
 
@@ -156,13 +163,6 @@
 	// Bootstrap tests if this module is allowed to use non-APEX version of libraries.
 	Bootstrap() bool
 
-	// IsVndkSp returns true if this is a VNDK-SP module.
-	IsVndkSp() bool
-
-	MustUseVendorVariant() bool
-	IsVndk() bool
-	IsVndkExt() bool
-	IsVndkPrivate() bool
 	IsVendorPublicLibrary() bool
 	IsVndkPrebuiltLibrary() bool
 	HasVendorVariant() bool
@@ -380,6 +380,7 @@
 	SystemIncludeDirs android.Paths // System include directories to be included with -isystem
 	Flags             []string      // Exported raw flags.
 	Deps              android.Paths
+	RustRlibDeps      []RustRlibDep
 	GeneratedHeaders  android.Paths
 }
 
diff --git a/cc/linker.go b/cc/linker.go
index 9686697..d2974c2 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -62,6 +62,10 @@
 	// This flag should only be necessary for compiling low-level libraries like libc.
 	Allow_undefined_symbols *bool `android:"arch_variant"`
 
+	// ignore max page size. By default, max page size must be the
+	// max page size set for the target.
+	Ignore_max_page_size *bool `android:"arch_variant"`
+
 	// don't link in libclang_rt.builtins-*.a
 	No_libcrt *bool `android:"arch_variant"`
 
@@ -116,7 +120,7 @@
 			// product variant of the C/C++ module.
 			Static_libs []string
 
-			// list of ehader libs that only should be used to build vendor or product
+			// list of header libs that only should be used to build vendor or product
 			// variant of the C/C++ module.
 			Header_libs []string
 
@@ -165,7 +169,7 @@
 			Exclude_runtime_libs []string
 		}
 		Ramdisk struct {
-			// list of static libs that only should be used to build the recovery
+			// list of static libs that only should be used to build the ramdisk
 			// variant of the C/C++ module.
 			Static_libs []string
 
@@ -183,7 +187,7 @@
 		}
 		Vendor_ramdisk struct {
 			// list of shared libs that should not be used to build
-			// the recovery variant of the C/C++ module.
+			// the vendor ramdisk variant of the C/C++ module.
 			Exclude_shared_libs []string
 
 			// list of static libs that should not be used to build
@@ -287,6 +291,10 @@
 	return []interface{}{&linker.Properties, &linker.dynamicProperties}
 }
 
+func (linker *baseLinker) baseLinkerProps() BaseLinkerProperties {
+	return linker.Properties
+}
+
 func (linker *baseLinker) linkerDeps(ctx DepsContext, deps Deps) Deps {
 	deps.WholeStaticLibs = append(deps.WholeStaticLibs, linker.Properties.Whole_static_libs...)
 	deps.HeaderLibs = append(deps.HeaderLibs, linker.Properties.Header_libs...)
@@ -330,6 +338,7 @@
 		deps.StaticLibs = removeListFromList(deps.StaticLibs, linker.Properties.Target.Vendor.Exclude_static_libs)
 		deps.HeaderLibs = append(deps.HeaderLibs, linker.Properties.Target.Vendor.Header_libs...)
 		deps.HeaderLibs = removeListFromList(deps.HeaderLibs, linker.Properties.Target.Vendor.Exclude_header_libs)
+		deps.ReexportHeaderLibHeaders = removeListFromList(deps.ReexportHeaderLibHeaders, linker.Properties.Target.Vendor.Exclude_header_libs)
 		deps.ReexportStaticLibHeaders = removeListFromList(deps.ReexportStaticLibHeaders, linker.Properties.Target.Vendor.Exclude_static_libs)
 		deps.WholeStaticLibs = removeListFromList(deps.WholeStaticLibs, linker.Properties.Target.Vendor.Exclude_static_libs)
 		deps.RuntimeLibs = removeListFromList(deps.RuntimeLibs, linker.Properties.Target.Vendor.Exclude_runtime_libs)
@@ -342,6 +351,7 @@
 		deps.StaticLibs = append(deps.StaticLibs, linker.Properties.Target.Product.Static_libs...)
 		deps.StaticLibs = removeListFromList(deps.StaticLibs, linker.Properties.Target.Product.Exclude_static_libs)
 		deps.HeaderLibs = removeListFromList(deps.HeaderLibs, linker.Properties.Target.Product.Exclude_header_libs)
+		deps.ReexportHeaderLibHeaders = removeListFromList(deps.ReexportHeaderLibHeaders, linker.Properties.Target.Product.Exclude_header_libs)
 		deps.ReexportStaticLibHeaders = removeListFromList(deps.ReexportStaticLibHeaders, linker.Properties.Target.Product.Exclude_static_libs)
 		deps.WholeStaticLibs = removeListFromList(deps.WholeStaticLibs, linker.Properties.Target.Product.Exclude_static_libs)
 		deps.RuntimeLibs = removeListFromList(deps.RuntimeLibs, linker.Properties.Target.Product.Exclude_runtime_libs)
@@ -396,7 +406,7 @@
 	if ctx.toolchain().Bionic() {
 		// libclang_rt.builtins has to be last on the command line
 		if linker.Properties.libCrt() && !ctx.header() {
-			deps.UnexportedStaticLibs = append(deps.UnexportedStaticLibs, config.BuiltinsRuntimeLibrary(ctx.toolchain()))
+			deps.UnexportedStaticLibs = append(deps.UnexportedStaticLibs, config.BuiltinsRuntimeLibrary())
 		}
 
 		if inList("libdl", deps.SharedLibs) {
@@ -419,7 +429,7 @@
 		}
 	} else if ctx.toolchain().Musl() {
 		if linker.Properties.libCrt() && !ctx.header() {
-			deps.UnexportedStaticLibs = append(deps.UnexportedStaticLibs, config.BuiltinsRuntimeLibrary(ctx.toolchain()))
+			deps.UnexportedStaticLibs = append(deps.UnexportedStaticLibs, config.BuiltinsRuntimeLibrary())
 		}
 	}
 
diff --git a/cc/llndk_library.go b/cc/llndk_library.go
index 9e727a1..85c3edf 100644
--- a/cc/llndk_library.go
+++ b/cc/llndk_library.go
@@ -15,13 +15,15 @@
 package cc
 
 import (
-	"android/soong/android"
+	"fmt"
 	"strings"
+
+	"android/soong/android"
+	"android/soong/etc"
 )
 
 var (
 	llndkLibrarySuffix = ".llndk"
-	llndkHeadersSuffix = ".llndk"
 )
 
 // Holds properties to describe a stub shared library based on the provided version file.
@@ -78,3 +80,147 @@
 	ctx.Strict("LLNDK_MOVED_TO_APEX_LIBRARIES",
 		strings.Join(android.SortedKeys(movedToApexLlndkLibraries), " "))
 }
+
+func init() {
+	RegisterLlndkLibraryTxtType(android.InitRegistrationContext)
+}
+
+func RegisterLlndkLibraryTxtType(ctx android.RegistrationContext) {
+	ctx.RegisterParallelSingletonModuleType("llndk_libraries_txt", llndkLibrariesTxtFactory)
+}
+
+type llndkLibrariesTxtModule struct {
+	android.SingletonModuleBase
+
+	outputFile  android.OutputPath
+	moduleNames []string
+	fileNames   []string
+}
+
+var _ etc.PrebuiltEtcModule = &llndkLibrariesTxtModule{}
+
+// llndk_libraries_txt is a singleton module whose content is a list of LLNDK libraries
+// generated by Soong but can be referenced by other modules.
+// For example, apex_vndk can depend on these files as prebuilt.
+// Make uses LLNDK_LIBRARIES to determine which libraries to install.
+// HWASAN is only part of the LL-NDK in builds in which libc depends on HWASAN.
+// Therefore, by removing the library here, we cause it to only be installed if libc
+// depends on it.
+func llndkLibrariesTxtFactory() android.SingletonModule {
+	m := &llndkLibrariesTxtModule{}
+	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
+	return m
+}
+
+func (txt *llndkLibrariesTxtModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	filename := txt.Name()
+
+	txt.outputFile = android.PathForModuleOut(ctx, filename).OutputPath
+
+	installPath := android.PathForModuleInstall(ctx, "etc")
+	ctx.InstallFile(installPath, filename, txt.outputFile)
+
+	ctx.SetOutputFiles(android.Paths{txt.outputFile}, "")
+}
+
+func getVndkFileName(m *Module) (string, error) {
+	if library, ok := m.linker.(*libraryDecorator); ok {
+		return library.getLibNameHelper(m.BaseModuleName(), true, false) + ".so", nil
+	}
+	if prebuilt, ok := m.linker.(*prebuiltLibraryLinker); ok {
+		return prebuilt.libraryDecorator.getLibNameHelper(m.BaseModuleName(), true, false) + ".so", nil
+	}
+	return "", fmt.Errorf("VNDK library should have libraryDecorator or prebuiltLibraryLinker as linker: %T", m.linker)
+}
+
+func (txt *llndkLibrariesTxtModule) GenerateSingletonBuildActions(ctx android.SingletonContext) {
+	if txt.outputFile.String() == "" {
+		// Skip if target file path is empty
+		return
+	}
+
+	ctx.VisitAllModules(func(m android.Module) {
+		if c, ok := m.(*Module); ok && c.VendorProperties.IsLLNDK && !c.Header() && !c.IsVndkPrebuiltLibrary() {
+			filename, err := getVndkFileName(c)
+			if err != nil {
+				ctx.ModuleErrorf(m, "%s", err)
+			}
+
+			if !strings.HasPrefix(ctx.ModuleName(m), "libclang_rt.hwasan") {
+				txt.moduleNames = append(txt.moduleNames, ctx.ModuleName(m))
+			}
+			txt.fileNames = append(txt.fileNames, filename)
+		}
+	})
+	txt.moduleNames = android.SortedUniqueStrings(txt.moduleNames)
+	txt.fileNames = android.SortedUniqueStrings(txt.fileNames)
+
+	android.WriteFileRule(ctx, txt.outputFile, strings.Join(txt.fileNames, "\n"))
+}
+
+func (txt *llndkLibrariesTxtModule) AndroidMkEntries() []android.AndroidMkEntries {
+	return []android.AndroidMkEntries{{
+		Class:      "ETC",
+		OutputFile: android.OptionalPathForPath(txt.outputFile),
+		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
+			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+				entries.SetString("LOCAL_MODULE_STEM", txt.outputFile.Base())
+			},
+		},
+	}}
+}
+
+func (txt *llndkLibrariesTxtModule) MakeVars(ctx android.MakeVarsContext) {
+	ctx.Strict("LLNDK_LIBRARIES", strings.Join(txt.moduleNames, " "))
+}
+
+// PrebuiltEtcModule interface
+func (txt *llndkLibrariesTxtModule) BaseDir() string {
+	return "etc"
+}
+
+// PrebuiltEtcModule interface
+func (txt *llndkLibrariesTxtModule) SubDir() string {
+	return ""
+}
+
+func llndkMutator(mctx android.BottomUpMutatorContext) {
+	m, ok := mctx.Module().(*Module)
+	if !ok {
+		return
+	}
+
+	if shouldSkipLlndkMutator(mctx, m) {
+		return
+	}
+
+	lib, isLib := m.linker.(*libraryDecorator)
+	prebuiltLib, isPrebuiltLib := m.linker.(*prebuiltLibraryLinker)
+
+	if m.InVendorOrProduct() && isLib && lib.hasLLNDKStubs() {
+		m.VendorProperties.IsLLNDK = true
+	}
+	if m.InVendorOrProduct() && isPrebuiltLib && prebuiltLib.hasLLNDKStubs() {
+		m.VendorProperties.IsLLNDK = true
+	}
+
+	if vndkprebuilt, ok := m.linker.(*vndkPrebuiltLibraryDecorator); ok {
+		if !Bool(vndkprebuilt.properties.Vndk.Enabled) {
+			m.VendorProperties.IsLLNDK = true
+		}
+	}
+}
+
+// Check for modules that mustn't be LLNDK
+func shouldSkipLlndkMutator(mctx android.BottomUpMutatorContext, m *Module) bool {
+	if !m.Enabled(mctx) {
+		return true
+	}
+	if !m.Device() {
+		return true
+	}
+	if m.Target().NativeBridge == android.NativeBridgeEnabled {
+		return true
+	}
+	return false
+}
diff --git a/cc/lto.go b/cc/lto.go
index a084db7..f3af7d2 100644
--- a/cc/lto.go
+++ b/cc/lto.go
@@ -54,6 +54,9 @@
 
 	// Use -fwhole-program-vtables cflag.
 	Whole_program_vtables *bool
+
+	// Use --lto-O0 flag.
+	Lto_O0 *bool
 }
 
 type lto struct {
@@ -106,12 +109,8 @@
 		ltoCFlags := []string{"-flto=thin", "-fsplit-lto-unit"}
 		var ltoLdFlags []string
 
-		// The module did not explicitly turn on LTO. Only leverage LTO's
-		// better dead code elimination and CFG simplification, but do
-		// not perform costly optimizations for a balance between compile
-		// time, binary size and performance.
-		// Apply the same for Eng builds as well.
-		if !lto.ThinLTO() || ctx.Config().Eng() {
+		// Do not perform costly LTO optimizations for Eng builds.
+		if Bool(lto.Properties.Lto_O0) || ctx.optimizeForSize() || ctx.Config().Eng() {
 			ltoLdFlags = append(ltoLdFlags, "-Wl,--lto-O0")
 		}
 
@@ -144,7 +143,7 @@
 
 		if !ctx.Config().IsEnvFalse("THINLTO_USE_MLGO") {
 			// Register allocation MLGO flags for ARM64.
-			if ctx.Arch().ArchType == android.Arm64 {
+			if ctx.Arch().ArchType == android.Arm64 && !ctx.optimizeForSize() {
 				ltoLdFlags = append(ltoLdFlags, "-Wl,-mllvm,-regalloc-enable-advisor=release")
 			}
 			// Flags for training MLGO model.
diff --git a/cc/makevars.go b/cc/makevars.go
index 9251d6a..cd13965 100644
--- a/cc/makevars.go
+++ b/cc/makevars.go
@@ -28,6 +28,16 @@
 	modulesWarningsAllowedKey    = android.NewOnceKey("ModulesWarningsAllowed")
 	modulesUsingWnoErrorKey      = android.NewOnceKey("ModulesUsingWnoError")
 	modulesMissingProfileFileKey = android.NewOnceKey("ModulesMissingProfileFile")
+	sanitizerVariables           = map[string]string{
+		"ADDRESS_SANITIZER_RUNTIME_LIBRARY":   config.AddressSanitizerRuntimeLibrary(),
+		"HWADDRESS_SANITIZER_RUNTIME_LIBRARY": config.HWAddressSanitizerRuntimeLibrary(),
+		"HWADDRESS_SANITIZER_STATIC_LIBRARY":  config.HWAddressSanitizerStaticLibrary(),
+		"UBSAN_RUNTIME_LIBRARY":               config.UndefinedBehaviorSanitizerRuntimeLibrary(),
+		"UBSAN_MINIMAL_RUNTIME_LIBRARY":       config.UndefinedBehaviorSanitizerMinimalRuntimeLibrary(),
+		"TSAN_RUNTIME_LIBRARY":                config.ThreadSanitizerRuntimeLibrary(),
+		"SCUDO_RUNTIME_LIBRARY":               config.ScudoRuntimeLibrary(),
+		"SCUDO_MINIMAL_RUNTIME_LIBRARY":       config.ScudoMinimalRuntimeLibrary(),
+	}
 )
 
 func init() {
@@ -128,7 +138,6 @@
 	ctx.Strict("CLANG_COVERAGE_HWASAN_FLAGS", strings.Join(clangCoverageHWASanFlags, " "))
 
 	ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_CFLAGS", strings.Join(asanCflags, " "))
-	ctx.Strict("ADDRESS_SANITIZER_CONFIG_EXTRA_LDFLAGS", strings.Join(asanLdflags, " "))
 
 	ctx.Strict("HWADDRESS_SANITIZER_CONFIG_EXTRA_CFLAGS", strings.Join(hwasanCflags, " "))
 	ctx.Strict("HWADDRESS_SANITIZER_GLOBAL_OPTIONS", strings.Join(hwasanGlobalOptions, ","))
@@ -261,43 +270,9 @@
 	}, " "))
 
 	if target.Os.Class == android.Device {
-		sanitizerVariables := map[string]string{
-			"ADDRESS_SANITIZER_RUNTIME_LIBRARY":   config.AddressSanitizerRuntimeLibrary(toolchain),
-			"HWADDRESS_SANITIZER_RUNTIME_LIBRARY": config.HWAddressSanitizerRuntimeLibrary(toolchain),
-			"HWADDRESS_SANITIZER_STATIC_LIBRARY":  config.HWAddressSanitizerStaticLibrary(toolchain),
-			"UBSAN_RUNTIME_LIBRARY":               config.UndefinedBehaviorSanitizerRuntimeLibrary(toolchain),
-			"UBSAN_MINIMAL_RUNTIME_LIBRARY":       config.UndefinedBehaviorSanitizerMinimalRuntimeLibrary(toolchain),
-			"TSAN_RUNTIME_LIBRARY":                config.ThreadSanitizerRuntimeLibrary(toolchain),
-			"SCUDO_RUNTIME_LIBRARY":               config.ScudoRuntimeLibrary(toolchain),
-			"SCUDO_MINIMAL_RUNTIME_LIBRARY":       config.ScudoMinimalRuntimeLibrary(toolchain),
-		}
-
 		for variable, value := range sanitizerVariables {
 			ctx.Strict(secondPrefix+variable, value)
 		}
-
-		sanitizerLibs := android.SortedStringValues(sanitizerVariables)
-		var sanitizerLibStems []string
-		ctx.VisitAllModules(func(m android.Module) {
-			if !m.Enabled() {
-				return
-			}
-
-			ccModule, _ := m.(*Module)
-			if ccModule == nil || ccModule.library == nil || !ccModule.library.shared() {
-				return
-			}
-
-			if android.InList(strings.TrimPrefix(ctx.ModuleName(m), "prebuilt_"), sanitizerLibs) &&
-				m.Target().Os == target.Os && m.Target().Arch.ArchType == target.Arch.ArchType {
-				outputFile := ccModule.outputFile
-				if outputFile.Valid() {
-					sanitizerLibStems = append(sanitizerLibStems, outputFile.Path().Base())
-				}
-			}
-		})
-		sanitizerLibStems = android.SortedUniqueStrings(sanitizerLibStems)
-		ctx.Strict(secondPrefix+"SANITIZER_STEMS", strings.Join(sanitizerLibStems, " "))
 	}
 
 	// This is used by external/gentoo/...
diff --git a/cc/ndk_abi.go b/cc/ndk_abi.go
index 86166dc..5beeab1 100644
--- a/cc/ndk_abi.go
+++ b/cc/ndk_abi.go
@@ -40,7 +40,7 @@
 func (n *ndkAbiDumpSingleton) GenerateBuildActions(ctx android.SingletonContext) {
 	var depPaths android.Paths
 	ctx.VisitAllModules(func(module android.Module) {
-		if !module.Enabled() {
+		if !module.Enabled(ctx) {
 			return
 		}
 
@@ -78,7 +78,7 @@
 func (n *ndkAbiDiffSingleton) GenerateBuildActions(ctx android.SingletonContext) {
 	var depPaths android.Paths
 	ctx.VisitAllModules(func(module android.Module) {
-		if m, ok := module.(android.Module); ok && !m.Enabled() {
+		if m, ok := module.(android.Module); ok && !m.Enabled(ctx) {
 			return
 		}
 
diff --git a/cc/ndk_library.go b/cc/ndk_library.go
index 25231fd..b822e5c 100644
--- a/cc/ndk_library.go
+++ b/cc/ndk_library.go
@@ -133,7 +133,7 @@
 	return strings.TrimSuffix(name, ndkLibrarySuffix)
 }
 
-func ndkLibraryVersions(ctx android.BaseMutatorContext, from android.ApiLevel) []string {
+func ndkLibraryVersions(ctx android.BaseModuleContext, from android.ApiLevel) []string {
 	var versions []android.ApiLevel
 	versionStrs := []string{}
 	for _, version := range ctx.Config().AllSupportedApiLevels() {
@@ -147,8 +147,8 @@
 	return versionStrs
 }
 
-func (this *stubDecorator) stubsVersions(ctx android.BaseMutatorContext) []string {
-	if !ctx.Module().Enabled() {
+func (this *stubDecorator) stubsVersions(ctx android.BaseModuleContext) []string {
+	if !ctx.Module().Enabled(ctx) {
 		return nil
 	}
 	if ctx.Target().NativeBridge == android.NativeBridgeEnabled {
diff --git a/cc/ndk_sysroot.go b/cc/ndk_sysroot.go
index e815172..3c48f68 100644
--- a/cc/ndk_sysroot.go
+++ b/cc/ndk_sysroot.go
@@ -150,7 +150,7 @@
 	var installPaths android.Paths
 	var licensePaths android.Paths
 	ctx.VisitAllModules(func(module android.Module) {
-		if m, ok := module.(android.Module); ok && !m.Enabled() {
+		if m, ok := module.(android.Module); ok && !m.Enabled(ctx) {
 			return
 		}
 
diff --git a/cc/object.go b/cc/object.go
index 6c0391f..8b23295 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -220,7 +220,7 @@
 }
 
 func (object *objectLinker) strippedAllOutputFilePath() android.Path {
-	panic("Not implemented.")
+	return nil
 }
 
 func (object *objectLinker) nativeCoverage() bool {
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index cbb5d58..e023a32 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -177,7 +177,7 @@
 				implicits = append(implicits, importLibOutputFile)
 
 				ctx.Build(pctx, android.BuildParams{
-					Rule:        android.Cp,
+					Rule:        android.CpExecutable,
 					Description: "prebuilt import library",
 					Input:       importLibSrc,
 					Output:      importLibOutputFile,
@@ -188,7 +188,7 @@
 			}
 
 			ctx.Build(pctx, android.BuildParams{
-				Rule:        android.Cp,
+				Rule:        android.CpExecutable,
 				Description: "prebuilt shared library",
 				Implicits:   implicits,
 				Input:       in,
@@ -205,17 +205,6 @@
 				TableOfContents: p.tocFile,
 			})
 
-			// TODO(b/220898484): Mainline module sdk prebuilts of stub libraries use a stub
-			// library as their source and must not be installed, but other prebuilts like
-			// libclang_rt.* libraries set `stubs` property because they are LLNDK libraries,
-			// but use an implementation library as their source and need to be installed.
-			// This discrepancy should be resolved without the prefix hack below.
-			isModuleSdkPrebuilts := android.HasAnyPrefix(ctx.ModuleDir(), []string{
-				"prebuilts/runtime/mainline/", "prebuilts/module_sdk/"})
-			if p.hasStubsVariants() && !p.buildStubs() && !ctx.Host() && isModuleSdkPrebuilts {
-				ctx.Module().MakeUninstallable()
-			}
-
 			return outputFile
 		}
 	}
diff --git a/cc/prebuilt_test.go b/cc/prebuilt_test.go
index 71b7e43..86e6af9 100644
--- a/cc/prebuilt_test.go
+++ b/cc/prebuilt_test.go
@@ -385,112 +385,6 @@
 	assertString(t, static2.OutputFile().Path().Base(), "libf.hwasan.a")
 }
 
-func TestPrebuiltStubNoinstall(t *testing.T) {
-	testFunc := func(t *testing.T, expectLibfooOnSystemLib bool, fs android.MockFS) {
-		result := android.GroupFixturePreparers(
-			prepareForPrebuiltTest,
-			android.PrepareForTestWithMakevars,
-			android.FixtureMergeMockFs(fs),
-		).RunTest(t)
-
-		ldRule := result.ModuleForTests("installedlib", "android_arm64_armv8-a_shared").Rule("ld")
-		android.AssertStringDoesContain(t, "", ldRule.Args["libFlags"], "android_arm64_armv8-a_shared/libfoo.so")
-
-		installRules := result.InstallMakeRulesForTesting(t)
-		var installedlibRule *android.InstallMakeRule
-		for i, rule := range installRules {
-			if rule.Target == "out/target/product/test_device/system/lib/installedlib.so" {
-				if installedlibRule != nil {
-					t.Errorf("Duplicate install rules for %s", rule.Target)
-				}
-				installedlibRule = &installRules[i]
-			}
-		}
-		if installedlibRule == nil {
-			t.Errorf("No install rule found for installedlib")
-			return
-		}
-
-		if expectLibfooOnSystemLib {
-			android.AssertStringListContains(t,
-				"installedlib doesn't have install dependency on libfoo impl",
-				installedlibRule.OrderOnlyDeps,
-				"out/target/product/test_device/system/lib/libfoo.so")
-		} else {
-			android.AssertStringListDoesNotContain(t,
-				"installedlib has install dependency on libfoo stub",
-				installedlibRule.Deps,
-				"out/target/product/test_device/system/lib/libfoo.so")
-			android.AssertStringListDoesNotContain(t,
-				"installedlib has order-only install dependency on libfoo stub",
-				installedlibRule.OrderOnlyDeps,
-				"out/target/product/test_device/system/lib/libfoo.so")
-		}
-	}
-
-	prebuiltLibfooBp := []byte(`
-		cc_prebuilt_library {
-			name: "libfoo",
-			prefer: true,
-			srcs: ["libfoo.so"],
-			stubs: {
-				versions: ["1"],
-			},
-		}
-	`)
-
-	installedlibBp := []byte(`
-		cc_library {
-			name: "installedlib",
-			shared_libs: ["libfoo"],
-		}
-	`)
-
-	t.Run("prebuilt stub (without source): no install", func(t *testing.T) {
-		testFunc(
-			t,
-			/*expectLibfooOnSystemLib=*/ false,
-			android.MockFS{
-				"prebuilts/module_sdk/art/current/Android.bp": prebuiltLibfooBp,
-				"Android.bp": installedlibBp,
-			},
-		)
-	})
-
-	disabledSourceLibfooBp := []byte(`
-		cc_library {
-			name: "libfoo",
-			enabled: false,
-			stubs: {
-				versions: ["1"],
-			},
-		}
-	`)
-
-	t.Run("prebuilt stub (with disabled source): no install", func(t *testing.T) {
-		testFunc(
-			t,
-			/*expectLibfooOnSystemLib=*/ false,
-			android.MockFS{
-				"prebuilts/module_sdk/art/current/Android.bp": prebuiltLibfooBp,
-				"impl/Android.bp": disabledSourceLibfooBp,
-				"Android.bp":      installedlibBp,
-			},
-		)
-	})
-
-	t.Run("prebuilt impl (with `stubs` property set): install", func(t *testing.T) {
-		testFunc(
-			t,
-			/*expectLibfooOnSystemLib=*/ true,
-			android.MockFS{
-				"impl/Android.bp": prebuiltLibfooBp,
-				"Android.bp":      installedlibBp,
-			},
-		)
-	})
-}
-
 func TestPrebuiltBinaryNoSrcsNoError(t *testing.T) {
 	const bp = `
 cc_prebuilt_binary {
diff --git a/cc/sabi.go b/cc/sabi.go
index cd9bf63..64eab41 100644
--- a/cc/sabi.go
+++ b/cc/sabi.go
@@ -31,7 +31,6 @@
 const (
 	apexLsdumpTag     lsdumpTag = "APEX"
 	llndkLsdumpTag    lsdumpTag = "LLNDK"
-	ndkLsdumpTag      lsdumpTag = "NDK"
 	platformLsdumpTag lsdumpTag = "PLATFORM"
 	productLsdumpTag  lsdumpTag = "PRODUCT"
 	vendorLsdumpTag   lsdumpTag = "VENDOR"
@@ -42,12 +41,8 @@
 	switch *tag {
 	case apexLsdumpTag:
 		return "platform"
-	case ndkLsdumpTag:
-		return "ndk"
 	case llndkLsdumpTag:
 		return "vndk"
-	case platformLsdumpTag:
-		return "platform"
 	default:
 		return ""
 	}
@@ -137,13 +132,10 @@
 		if m.isImplementationForLLNDKPublic() {
 			result = append(result, llndkLsdumpTag)
 		}
-		if m.IsNdk(ctx.Config()) {
-			result = append(result, ndkLsdumpTag)
-		}
-		// APEX and opt-in platform dumps are placed in the same directory.
 		if m.library.hasStubsVariants() {
 			result = append(result, apexLsdumpTag)
-		} else if headerAbiChecker.enabled() {
+		}
+		if headerAbiChecker.enabled() {
 			result = append(result, platformLsdumpTag)
 		}
 	} else if headerAbiChecker.enabled() {
diff --git a/cc/sanitize.go b/cc/sanitize.go
index db046ec..64a313b 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -25,6 +25,7 @@
 
 	"android/soong/android"
 	"android/soong/cc/config"
+	"android/soong/etc"
 )
 
 var (
@@ -35,7 +36,6 @@
 	asanCflags = []string{
 		"-fno-omit-frame-pointer",
 	}
-	asanLdflags = []string{"-Wl,-u,__asan_preinit"}
 
 	// DO NOT ADD MLLVM FLAGS HERE! ADD THEM BELOW TO hwasanCommonFlags.
 	hwasanCflags = []string{
@@ -55,7 +55,6 @@
 		// higher number of "optimized out" stack variables.
 		// b/112437883.
 		"-instcombine-lower-dbg-declare=0",
-		"-hwasan-use-after-scope=1",
 		"-dom-tree-reachability-max-bbs-to-explore=128",
 	}
 
@@ -82,7 +81,8 @@
 		"-fno-sanitize-recover=integer,undefined"}
 	hwasanGlobalOptions = []string{"heap_history_size=1023", "stack_history_size=512",
 		"export_memory_stats=0", "max_malloc_fill_size=131072", "malloc_fill_byte=0"}
-	memtagStackCommonFlags = []string{"-march=armv8-a+memtag", "-mllvm", "-dom-tree-reachability-max-bbs-to-explore=128"}
+	memtagStackCommonFlags = []string{"-march=armv8-a+memtag"}
+	memtagStackLlvmFlags   = []string{"-dom-tree-reachability-max-bbs-to-explore=128"}
 
 	hostOnlySanitizeFlags   = []string{"-fno-sanitize-recover=all"}
 	deviceOnlySanitizeFlags = []string{"-fsanitize-trap=all"}
@@ -176,11 +176,11 @@
 
 func (t SanitizerType) registerMutators(ctx android.RegisterMutatorsContext) {
 	switch t {
-	case cfi, Hwasan, Asan, tsan, Fuzzer, scs:
+	case cfi, Hwasan, Asan, tsan, Fuzzer, scs, Memtag_stack:
 		sanitizer := &sanitizerSplitMutator{t}
 		ctx.TopDown(t.variationName()+"_markapexes", sanitizer.markSanitizableApexesMutator)
 		ctx.Transition(t.variationName(), sanitizer)
-	case Memtag_heap, Memtag_stack, Memtag_globals, intOverflow:
+	case Memtag_heap, Memtag_globals, intOverflow:
 		// do nothing
 	default:
 		panic(fmt.Errorf("unknown SanitizerType %d", t))
@@ -382,7 +382,19 @@
 	Sanitize        SanitizeUserProps         `android:"arch_variant"`
 	SanitizeMutated sanitizeMutatedProperties `blueprint:"mutated"`
 
-	SanitizerEnabled  bool     `blueprint:"mutated"`
+	// ForceDisable is set by the version mutator to disable sanitization of stubs variants
+	ForceDisable bool `blueprint:"mutated"`
+
+	// SanitizerEnabled is set by begin() if any of the sanitize boolean properties are set after
+	// applying the logic that enables globally enabled sanitizers and disables any unsupported
+	// sanitizers.
+	// TODO(b/349906293): this has some unintuitive behavior.  It is set in begin() before the sanitize
+	//  mutator is run if any of the individual sanitizes  properties are set, and then the individual
+	//  sanitize properties are cleared in the non-sanitized variants, but this value is never cleared.
+	//  That results in SanitizerEnabled being set in variants that have no sanitizers enabled, causing
+	//  some of the sanitizer logic in flags() to be applied to the non-sanitized variant.
+	SanitizerEnabled bool `blueprint:"mutated"`
+
 	MinimalRuntimeDep bool     `blueprint:"mutated"`
 	BuiltinsDep       bool     `blueprint:"mutated"`
 	UbsanRuntimeDep   bool     `blueprint:"mutated"`
@@ -407,6 +419,9 @@
 
 	android.RegisterMakeVarsProvider(pctx, cfiMakeVarsProvider)
 	android.RegisterMakeVarsProvider(pctx, hwasanMakeVarsProvider)
+	android.RegisterMakeVarsProvider(pctx, memtagStackMakeVarsProvider)
+
+	RegisterSanitizerLibrariesTxtType(android.InitRegistrationContext)
 }
 
 func (sanitize *sanitize) props() []interface{} {
@@ -451,6 +466,10 @@
 	s := &sanitize.Properties.SanitizeMutated
 	s.copyUserPropertiesToMutated(&sanitize.Properties.Sanitize)
 
+	if sanitize.Properties.ForceDisable {
+		return
+	}
+
 	// Don't apply sanitizers to NDK code.
 	if ctx.useSdk() {
 		s.Never = BoolPtr(true)
@@ -677,16 +696,14 @@
 		s.Integer_overflow = nil
 	}
 
-	// Also disable CFI for VNDK variants of components
-	if ctx.isVndk() && ctx.useVndk() {
-		s.Cfi = nil
-		s.Diag.Cfi = nil
-	}
-
-	// HWASan ramdisk (which is built from recovery) goes over some bootloader limit.
-	// Keep libc instrumented so that ramdisk / vendor_ramdisk / recovery can run hwasan-instrumented code if necessary.
-	if (ctx.inRamdisk() || ctx.inVendorRamdisk() || ctx.inRecovery()) && !strings.HasPrefix(ctx.ModuleDir(), "bionic/libc") {
-		s.Hwaddress = nil
+	if ctx.inRamdisk() || ctx.inVendorRamdisk() || ctx.inRecovery() {
+		// HWASan ramdisk (which is built from recovery) goes over some bootloader limit.
+		// Keep libc instrumented so that ramdisk / vendor_ramdisk / recovery can run hwasan-instrumented code if necessary.
+		if !strings.HasPrefix(ctx.ModuleDir(), "bionic/libc") {
+			s.Hwaddress = nil
+		}
+		// Memtag stack in ramdisk makes pKVM unhappy.
+		s.Memtag_stack = nil
 	}
 
 	if ctx.staticBinary() {
@@ -763,6 +780,10 @@
 }
 
 func (s *sanitize) flags(ctx ModuleContext, flags Flags) Flags {
+	if s.Properties.ForceDisable {
+		return flags
+	}
+
 	if !s.Properties.SanitizerEnabled && !s.Properties.UbsanRuntimeDep {
 		return flags
 	}
@@ -775,16 +796,17 @@
 			flags.RequiredInstructionSet = "arm"
 		}
 		flags.Local.CFlags = append(flags.Local.CFlags, asanCflags...)
-		flags.Local.LdFlags = append(flags.Local.LdFlags, asanLdflags...)
 
 		if Bool(sanProps.Writeonly) {
 			flags.Local.CFlags = append(flags.Local.CFlags, "-mllvm", "-asan-instrument-reads=0")
 		}
 
 		if ctx.Host() {
-			// -nodefaultlibs (provided with libc++) prevents the driver from linking
-			// libraries needed with -fsanitize=address. http://b/18650275 (WAI)
-			flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,--no-as-needed")
+			if !ctx.Darwin() { // ld64.lld doesn't know about '--no-as-needed'
+				// -nodefaultlibs (provided with libc++) prevents the driver from linking
+				// libraries needed with -fsanitize=address. http://b/18650275 (WAI)
+				flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,--no-as-needed")
+			}
 		} else {
 			flags.Local.CFlags = append(flags.Local.CFlags, "-mllvm", "-asan-globals=0")
 			if ctx.bootstrap() {
@@ -858,7 +880,7 @@
 
 		flags.Local.CFlags = append(flags.Local.CFlags, cfiCflags...)
 		flags.Local.AsFlags = append(flags.Local.AsFlags, cfiAsflags...)
-		flags.CFlagsDeps = append(flags.CFlagsDeps, android.PathForSource(ctx, cfiBlocklistPath + "/" + cfiBlocklistFilename))
+		flags.CFlagsDeps = append(flags.CFlagsDeps, android.PathForSource(ctx, cfiBlocklistPath+"/"+cfiBlocklistFilename))
 		if Bool(s.Properties.Sanitize.Config.Cfi_assembly_support) {
 			flags.Local.CFlags = append(flags.Local.CFlags, cfiAssemblySupportFlag)
 		}
@@ -879,6 +901,13 @@
 		flags.Local.CFlags = append(flags.Local.CFlags, memtagStackCommonFlags...)
 		flags.Local.AsFlags = append(flags.Local.AsFlags, memtagStackCommonFlags...)
 		flags.Local.LdFlags = append(flags.Local.LdFlags, memtagStackCommonFlags...)
+
+		for _, flag := range memtagStackLlvmFlags {
+			flags.Local.CFlags = append(flags.Local.CFlags, "-mllvm", flag)
+		}
+		for _, flag := range memtagStackLlvmFlags {
+			flags.Local.LdFlags = append(flags.Local.LdFlags, "-Wl,-mllvm,"+flag)
+		}
 	}
 
 	if (Bool(sanProps.Memtag_heap) || Bool(sanProps.Memtag_stack) || Bool(sanProps.Memtag_globals)) && ctx.binary() {
@@ -1095,7 +1124,7 @@
 	if s == nil {
 		return false
 	}
-	if proptools.Bool(s.Properties.SanitizeMutated.Never) {
+	if s.Properties.ForceDisable || proptools.Bool(s.Properties.SanitizeMutated.Never) {
 		return false
 	}
 
@@ -1303,6 +1332,8 @@
 					hwasanStaticLibs(mctx.Config()).add(c, c.Module().Name())
 				} else if s.sanitizer == cfi {
 					cfiStaticLibs(mctx.Config()).add(c, c.Module().Name())
+				} else if s.sanitizer == Memtag_stack {
+					memtagStackStaticLibs(mctx.Config()).add(c, c.Module().Name())
 				}
 			}
 		} else if c.IsSanitizerEnabled(s.sanitizer) {
@@ -1318,7 +1349,7 @@
 }
 
 func (c *Module) SanitizeNever() bool {
-	return Bool(c.sanitize.Properties.SanitizeMutated.Never)
+	return c.sanitize.Properties.ForceDisable || Bool(c.sanitize.Properties.SanitizeMutated.Never)
 }
 
 func (c *Module) IsSanitizerExplicitlyDisabled(t SanitizerType) bool {
@@ -1329,6 +1360,9 @@
 func sanitizerRuntimeDepsMutator(mctx android.TopDownMutatorContext) {
 	// Change this to PlatformSanitizable when/if non-cc modules support ubsan sanitizers.
 	if c, ok := mctx.Module().(*Module); ok && c.sanitize != nil {
+		if c.sanitize.Properties.ForceDisable {
+			return
+		}
 		isSanitizableDependencyTag := c.SanitizableDepTagChecker()
 		mctx.WalkDeps(func(child, parent android.Module) bool {
 			if !isSanitizableDependencyTag(mctx.OtherModuleDependencyTag(child)) {
@@ -1339,7 +1373,7 @@
 			if !ok || !d.static() {
 				return false
 			}
-			if d.sanitize != nil {
+			if d.sanitize != nil && !d.sanitize.Properties.ForceDisable {
 				if enableMinimalRuntime(d.sanitize) {
 					// If a static dependency is built with the minimal runtime,
 					// make sure we include the ubsan minimal runtime.
@@ -1371,9 +1405,13 @@
 // Add the dependency to the runtime library for each of the sanitizer variants
 func sanitizerRuntimeMutator(mctx android.BottomUpMutatorContext) {
 	if c, ok := mctx.Module().(*Module); ok && c.sanitize != nil {
-		if !c.Enabled() {
+		if !c.Enabled(mctx) {
 			return
 		}
+		if c.sanitize.Properties.ForceDisable {
+			return
+		}
+
 		var sanitizers []string
 		var diagSanitizers []string
 
@@ -1508,25 +1546,25 @@
 		if Bool(sanProps.Address) {
 			if toolchain.Musl() || (c.staticBinary() && toolchain.Bionic()) {
 				// Use a static runtime for musl to match what clang does for glibc.
-				addStaticDeps(config.AddressSanitizerStaticRuntimeLibrary(toolchain), false)
-				addStaticDeps(config.AddressSanitizerCXXStaticRuntimeLibrary(toolchain), false)
+				addStaticDeps(config.AddressSanitizerStaticRuntimeLibrary(), false)
+				addStaticDeps(config.AddressSanitizerCXXStaticRuntimeLibrary(), false)
 			} else {
-				runtimeSharedLibrary = config.AddressSanitizerRuntimeLibrary(toolchain)
+				runtimeSharedLibrary = config.AddressSanitizerRuntimeLibrary()
 			}
 		} else if Bool(sanProps.Hwaddress) {
 			if c.staticBinary() {
-				addStaticDeps(config.HWAddressSanitizerStaticLibrary(toolchain), true)
+				addStaticDeps(config.HWAddressSanitizerStaticLibrary(), true)
 				addStaticDeps("libdl", false)
 			} else {
-				runtimeSharedLibrary = config.HWAddressSanitizerRuntimeLibrary(toolchain)
+				runtimeSharedLibrary = config.HWAddressSanitizerRuntimeLibrary()
 			}
 		} else if Bool(sanProps.Thread) {
-			runtimeSharedLibrary = config.ThreadSanitizerRuntimeLibrary(toolchain)
+			runtimeSharedLibrary = config.ThreadSanitizerRuntimeLibrary()
 		} else if Bool(sanProps.Scudo) {
 			if len(diagSanitizers) == 0 && !c.sanitize.Properties.UbsanRuntimeDep {
-				runtimeSharedLibrary = config.ScudoMinimalRuntimeLibrary(toolchain)
+				runtimeSharedLibrary = config.ScudoMinimalRuntimeLibrary()
 			} else {
-				runtimeSharedLibrary = config.ScudoRuntimeLibrary(toolchain)
+				runtimeSharedLibrary = config.ScudoRuntimeLibrary()
 			}
 		} else if len(diagSanitizers) > 0 || c.sanitize.Properties.UbsanRuntimeDep ||
 			Bool(sanProps.Fuzzer) ||
@@ -1539,20 +1577,20 @@
 				// Also manually add a static runtime for musl to match what clang does for glibc.
 				// Otherwise dlopening libraries that depend on libclang_rt.ubsan_standalone.so fails with:
 				// Error relocating ...: initial-exec TLS resolves to dynamic definition
-				addStaticDeps(config.UndefinedBehaviorSanitizerRuntimeLibrary(toolchain)+".static", true)
+				addStaticDeps(config.UndefinedBehaviorSanitizerRuntimeLibrary()+".static", true)
 			} else {
-				runtimeSharedLibrary = config.UndefinedBehaviorSanitizerRuntimeLibrary(toolchain)
+				runtimeSharedLibrary = config.UndefinedBehaviorSanitizerRuntimeLibrary()
 			}
 		}
 
 		if enableMinimalRuntime(c.sanitize) || c.sanitize.Properties.MinimalRuntimeDep {
-			addStaticDeps(config.UndefinedBehaviorSanitizerMinimalRuntimeLibrary(toolchain), true)
+			addStaticDeps(config.UndefinedBehaviorSanitizerMinimalRuntimeLibrary(), true)
 		}
 		if c.sanitize.Properties.BuiltinsDep {
-			addStaticDeps(config.BuiltinsRuntimeLibrary(toolchain), true)
+			addStaticDeps(config.BuiltinsRuntimeLibrary(), true)
 		}
 
-		if runtimeSharedLibrary != "" && (toolchain.Bionic() || toolchain.Musl() || c.sanitize.Properties.UbsanRuntimeDep) {
+		if runtimeSharedLibrary != "" && (toolchain.Bionic() || toolchain.Musl()) {
 			// UBSan is supported on non-bionic linux host builds as well
 
 			// Adding dependency to the runtime library. We are using *FarVariation*
@@ -1715,6 +1753,14 @@
 	}).(*sanitizerStaticLibsMap)
 }
 
+var memtagStackStaticLibsKey = android.NewOnceKey("memtagStackStaticLibs")
+
+func memtagStackStaticLibs(config android.Config) *sanitizerStaticLibsMap {
+	return config.Once(memtagStackStaticLibsKey, func() interface{} {
+		return newSanitizerStaticLibsMap(Memtag_stack)
+	}).(*sanitizerStaticLibsMap)
+}
+
 func enableMinimalRuntime(sanitize *sanitize) bool {
 	if sanitize.isSanitizerEnabled(Asan) {
 		return false
@@ -1761,3 +1807,122 @@
 func hwasanMakeVarsProvider(ctx android.MakeVarsContext) {
 	hwasanStaticLibs(ctx.Config()).exportToMake(ctx)
 }
+
+func memtagStackMakeVarsProvider(ctx android.MakeVarsContext) {
+	memtagStackStaticLibs(ctx.Config()).exportToMake(ctx)
+}
+
+type sanitizerLibrariesTxtModule struct {
+	android.ModuleBase
+
+	outputFile android.OutputPath
+}
+
+var _ etc.PrebuiltEtcModule = (*sanitizerLibrariesTxtModule)(nil)
+
+func RegisterSanitizerLibrariesTxtType(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("sanitizer_libraries_txt", sanitizerLibrariesTxtFactory)
+}
+
+func sanitizerLibrariesTxtFactory() android.Module {
+	m := &sanitizerLibrariesTxtModule{}
+	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
+	return m
+}
+
+type sanitizerLibraryDependencyTag struct {
+	blueprint.BaseDependencyTag
+}
+
+func (t sanitizerLibraryDependencyTag) AllowDisabledModuleDependency(target android.Module) bool {
+	return true
+}
+
+var _ android.AllowDisabledModuleDependency = (*sanitizerLibraryDependencyTag)(nil)
+
+func (txt *sanitizerLibrariesTxtModule) DepsMutator(actx android.BottomUpMutatorContext) {
+	targets := actx.Config().Targets[android.Android]
+	depTag := sanitizerLibraryDependencyTag{}
+
+	for _, target := range targets {
+		variation := append(target.Variations(),
+			blueprint.Variation{Mutator: "image", Variation: ""},
+			blueprint.Variation{Mutator: "sdk", Variation: ""},
+			blueprint.Variation{Mutator: "link", Variation: "shared"},
+		)
+		for _, lib := range android.SortedStringValues(sanitizerVariables) {
+			if actx.OtherModuleFarDependencyVariantExists(variation, lib) {
+				actx.AddFarVariationDependencies(variation, depTag, lib)
+			}
+
+			prebuiltLibName := "prebuilt_" + lib
+			if actx.OtherModuleFarDependencyVariantExists(variation, prebuiltLibName) {
+				actx.AddFarVariationDependencies(variation, depTag, prebuiltLibName)
+			}
+		}
+	}
+
+}
+
+func (txt *sanitizerLibrariesTxtModule) getSanitizerLibs(ctx android.ModuleContext) string {
+	var sanitizerLibStems []string
+
+	ctx.VisitDirectDepsIf(func(m android.Module) bool {
+		if !m.Enabled(ctx) {
+			return false
+		}
+
+		ccModule, _ := m.(*Module)
+		if ccModule == nil || ccModule.library == nil || !ccModule.library.shared() {
+			return false
+		}
+
+		targets := ctx.Config().Targets[android.Android]
+
+		for _, target := range targets {
+			if m.Target().Os == target.Os && m.Target().Arch.ArchType == target.Arch.ArchType {
+				return true
+			}
+		}
+
+		return false
+	}, func(m android.Module) {
+		ccModule, _ := m.(*Module)
+		outputFile := ccModule.outputFile
+		if outputFile.Valid() {
+			sanitizerLibStems = append(sanitizerLibStems, outputFile.Path().Base())
+		}
+	})
+
+	sanitizerLibStems = android.SortedUniqueStrings(sanitizerLibStems)
+	return strings.Join(sanitizerLibStems, "\n")
+}
+
+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))
+
+	installPath := android.PathForModuleInstall(ctx, "etc")
+	ctx.InstallFile(installPath, filename, txt.outputFile)
+
+	ctx.SetOutputFiles(android.Paths{txt.outputFile}, "")
+}
+
+func (txt *sanitizerLibrariesTxtModule) AndroidMkEntries() []android.AndroidMkEntries {
+	return []android.AndroidMkEntries{{
+		Class:      "ETC",
+		OutputFile: android.OptionalPathForPath(txt.outputFile),
+	}}
+}
+
+// PrebuiltEtcModule interface
+func (txt *sanitizerLibrariesTxtModule) BaseDir() string {
+	return "etc"
+}
+
+// PrebuiltEtcModule interface
+func (txt *sanitizerLibrariesTxtModule) SubDir() string {
+	return ""
+}
diff --git a/cc/sdk.go b/cc/sdk.go
index ce0fdc2..dc1261d 100644
--- a/cc/sdk.go
+++ b/cc/sdk.go
@@ -19,12 +19,86 @@
 	"android/soong/genrule"
 )
 
-// sdkMutator sets a creates a platform and an SDK variant for modules
+// sdkTransitionMutator creates a platform and an SDK variant for modules
 // that set sdk_version, and ignores sdk_version for the platform
 // variant.  The SDK variant will be used for embedding in APKs
 // that may be installed on older platforms.  Apexes use their own
 // variants that enforce backwards compatibility.
-func sdkMutator(ctx android.BottomUpMutatorContext) {
+type sdkTransitionMutator struct{}
+
+func (sdkTransitionMutator) Split(ctx android.BaseModuleContext) []string {
+	if ctx.Os() != android.Android {
+		return []string{""}
+	}
+
+	switch m := ctx.Module().(type) {
+	case LinkableInterface:
+		if m.AlwaysSdk() {
+			if !m.UseSdk() && !m.SplitPerApiLevel() {
+				ctx.ModuleErrorf("UseSdk() must return true when AlwaysSdk is set, did the factory forget to set Sdk_version?")
+			}
+			return []string{"sdk"}
+		} else if m.UseSdk() || m.SplitPerApiLevel() {
+			return []string{"", "sdk"}
+		} else {
+			return []string{""}
+		}
+	case *genrule.Module:
+		if p, ok := m.Extra.(*GenruleExtraProperties); ok {
+			if String(p.Sdk_version) != "" {
+				return []string{"", "sdk"}
+			} else {
+				return []string{""}
+			}
+		}
+	case *CcApiVariant:
+		ccApiVariant, _ := ctx.Module().(*CcApiVariant)
+		if String(ccApiVariant.properties.Variant) == "ndk" {
+			return []string{"sdk"}
+		} else {
+			return []string{""}
+		}
+	}
+
+	return []string{""}
+}
+
+func (sdkTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+	return sourceVariation
+}
+
+func (sdkTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
+	if ctx.Os() != android.Android {
+		return ""
+	}
+	switch m := ctx.Module().(type) {
+	case LinkableInterface:
+		if m.AlwaysSdk() {
+			return "sdk"
+		} else if m.UseSdk() || m.SplitPerApiLevel() {
+			return incomingVariation
+		}
+	case *genrule.Module:
+		if p, ok := m.Extra.(*GenruleExtraProperties); ok {
+			if String(p.Sdk_version) != "" {
+				return incomingVariation
+			}
+		}
+	case *CcApiVariant:
+		ccApiVariant, _ := ctx.Module().(*CcApiVariant)
+		if String(ccApiVariant.properties.Variant) == "ndk" {
+			return "sdk"
+		}
+	}
+
+	if ctx.IsAddingDependency() {
+		return incomingVariation
+	} else {
+		return ""
+	}
+}
+
+func (sdkTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
 	if ctx.Os() != android.Android {
 		return
 	}
@@ -33,56 +107,45 @@
 	case LinkableInterface:
 		ccModule, isCcModule := ctx.Module().(*Module)
 		if m.AlwaysSdk() {
-			if !m.UseSdk() && !m.SplitPerApiLevel() {
-				ctx.ModuleErrorf("UseSdk() must return true when AlwaysSdk is set, did the factory forget to set Sdk_version?")
+			if variation != "sdk" {
+				ctx.ModuleErrorf("tried to create variation %q for module with AlwaysSdk set, expected \"sdk\"", variation)
 			}
-			modules := ctx.CreateVariations("sdk")
-			modules[0].(*Module).Properties.IsSdkVariant = true
+
+			ccModule.Properties.IsSdkVariant = true
 		} else if m.UseSdk() || m.SplitPerApiLevel() {
-			modules := ctx.CreateVariations("", "sdk")
+			if variation == "" {
+				// Clear the sdk_version property for the platform (non-SDK) variant so later code
+				// doesn't get confused by it.
+				ccModule.Properties.Sdk_version = nil
+			} else {
+				// Mark the SDK variant.
+				ccModule.Properties.IsSdkVariant = true
 
-			// Clear the sdk_version property for the platform (non-SDK) variant so later code
-			// doesn't get confused by it.
-			modules[0].(*Module).Properties.Sdk_version = nil
-
-			// Mark the SDK variant.
-			modules[1].(*Module).Properties.IsSdkVariant = true
+				// SDK variant never gets installed because the variant is to be embedded in
+				// APKs, not to be installed to the platform.
+				ccModule.Properties.PreventInstall = true
+			}
 
 			if ctx.Config().UnbundledBuildApps() {
-				// For an unbundled apps build, hide the platform variant from Make.
-				modules[0].(*Module).Properties.HideFromMake = true
-				modules[0].(*Module).Properties.PreventInstall = true
+				if variation == "" {
+					// For an unbundled apps build, hide the platform variant from Make
+					// so that other Make modules don't link against it, but against the
+					// SDK variant.
+					ccModule.Properties.HideFromMake = true
+				}
 			} else {
-				// For a platform build, mark the SDK variant so that it gets a ".sdk" suffix when
-				// exposed to Make.
-				modules[1].(*Module).Properties.SdkAndPlatformVariantVisibleToMake = true
-				modules[1].(*Module).Properties.PreventInstall = true
+				if variation == "sdk" {
+					// For a platform build, mark the SDK variant so that it gets a ".sdk" suffix when
+					// exposed to Make.
+					ccModule.Properties.SdkAndPlatformVariantVisibleToMake = true
+				}
 			}
-			ctx.AliasVariation("")
 		} else {
 			if isCcModule {
 				// Clear the sdk_version property for modules that don't have an SDK variant so
 				// later code doesn't get confused by it.
 				ccModule.Properties.Sdk_version = nil
 			}
-			ctx.CreateVariations("")
-			ctx.AliasVariation("")
-		}
-	case *genrule.Module:
-		if p, ok := m.Extra.(*GenruleExtraProperties); ok {
-			if String(p.Sdk_version) != "" {
-				ctx.CreateVariations("", "sdk")
-			} else {
-				ctx.CreateVariations("")
-			}
-			ctx.AliasVariation("")
-		}
-	case *CcApiVariant:
-		ccApiVariant, _ := ctx.Module().(*CcApiVariant)
-		if String(ccApiVariant.properties.Variant) == "ndk" {
-			ctx.CreateVariations("sdk")
-		} else {
-			ctx.CreateVariations("")
 		}
 	}
 }
diff --git a/cc/test.go b/cc/test.go
index 3a1a3af..f5bb761 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -15,11 +15,9 @@
 package cc
 
 import (
+	"github.com/google/blueprint/proptools"
 	"path/filepath"
 	"strconv"
-	"strings"
-
-	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
 	"android/soong/tradefed"
@@ -75,13 +73,9 @@
 }
 
 type TestBinaryProperties struct {
-	// Create a separate binary for each source file.  Useful when there is
-	// global state that can not be torn down and reset between each test suite.
-	Test_per_src *bool
-
 	// Disables the creation of a test-specific directory when used with
 	// relative_install_path. Useful if several tests need to be in the same
-	// directory, but test_per_src doesn't work.
+	// directory.
 	No_named_install_directory *bool
 
 	// list of files or filegroup modules that provide data that should be installed alongside
@@ -174,86 +168,14 @@
 	return module.Init()
 }
 
-type testPerSrc interface {
-	testPerSrc() bool
-	srcs() []string
-	isAllTestsVariation() bool
-	setSrc(string, string)
-	unsetSrc()
-}
-
-func (test *testBinary) testPerSrc() bool {
-	return Bool(test.Properties.Test_per_src)
-}
-
-func (test *testBinary) srcs() []string {
-	return test.baseCompiler.Properties.Srcs
-}
-
 func (test *testBinary) dataPaths() []android.DataPath {
 	return test.data
 }
 
-func (test *testBinary) isAllTestsVariation() bool {
-	stem := test.binaryDecorator.Properties.Stem
-	return stem != nil && *stem == ""
-}
-
-func (test *testBinary) setSrc(name, src string) {
-	test.baseCompiler.Properties.Srcs = []string{src}
-	test.binaryDecorator.Properties.Stem = StringPtr(name)
-}
-
-func (test *testBinary) unsetSrc() {
-	test.baseCompiler.Properties.Srcs = nil
-	test.binaryDecorator.Properties.Stem = StringPtr("")
-}
-
 func (test *testBinary) testBinary() bool {
 	return true
 }
 
-var _ testPerSrc = (*testBinary)(nil)
-
-func TestPerSrcMutator(mctx android.BottomUpMutatorContext) {
-	if m, ok := mctx.Module().(*Module); ok {
-		if test, ok := m.linker.(testPerSrc); ok {
-			numTests := len(test.srcs())
-			if test.testPerSrc() && numTests > 0 {
-				if duplicate, found := android.CheckDuplicate(test.srcs()); found {
-					mctx.PropertyErrorf("srcs", "found a duplicate entry %q", duplicate)
-					return
-				}
-				testNames := make([]string, numTests)
-				for i, src := range test.srcs() {
-					testNames[i] = strings.TrimSuffix(filepath.Base(src), filepath.Ext(src))
-				}
-				// In addition to creating one variation per test source file,
-				// create an additional "all tests" variation named "", and have it
-				// depends on all other test_per_src variations. This is useful to
-				// create subsequent dependencies of a given module on all
-				// test_per_src variations created above: by depending on
-				// variation "", that module will transitively depend on all the
-				// other test_per_src variations without the need to know their
-				// name or even their number.
-				testNames = append(testNames, "")
-				tests := mctx.CreateLocalVariations(testNames...)
-				allTests := tests[numTests]
-				allTests.(*Module).linker.(testPerSrc).unsetSrc()
-				// Prevent the "all tests" variation from being installable nor
-				// exporting to Make, as it won't create any output file.
-				allTests.(*Module).Properties.PreventInstall = true
-				allTests.(*Module).Properties.HideFromMake = true
-				for i, src := range test.srcs() {
-					tests[i].(*Module).linker.(testPerSrc).setSrc(testNames[i], src)
-					mctx.AddInterVariantDependency(testPerSrcDepTag, allTests, tests[i])
-				}
-				mctx.AliasVariation("")
-			}
-		}
-	}
-}
-
 type testDecorator struct {
 	LinkerProperties    TestLinkerProperties
 	InstallerProperties TestInstallerProperties
@@ -359,6 +281,12 @@
 func (test *testBinary) linkerFlags(ctx ModuleContext, flags Flags) Flags {
 	flags = test.binaryDecorator.linkerFlags(ctx, flags)
 	flags = test.testDecorator.linkerFlags(ctx, flags)
+
+	// Add a default rpath to allow tests to dlopen libraries specified in data_libs.
+	// Host modules already get an rpath specified in linker.go.
+	if !ctx.Host() {
+		flags.Global.LdFlags = append(flags.Global.LdFlags, `-Wl,-rpath,\$$ORIGIN`)
+	}
 	return flags
 }
 
@@ -376,10 +304,6 @@
 	}
 	moduleInfoJSON.TestConfig = append(moduleInfoJSON.TestConfig, test.extraTestConfigs.Strings()...)
 
-	if Bool(test.Properties.Test_per_src) {
-		moduleInfoJSON.SubName = "_" + String(test.binaryDecorator.Properties.Stem)
-	}
-
 	moduleInfoJSON.DataDependencies = append(moduleInfoJSON.DataDependencies, test.Properties.Data_bins...)
 
 	if len(test.InstallerProperties.Test_suites) > 0 {
diff --git a/cc/testing.go b/cc/testing.go
index 20c435a..02f9924 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -35,6 +35,7 @@
 
 	ctx.RegisterModuleType("prebuilt_build_tool", android.NewPrebuiltBuildTool)
 	ctx.RegisterModuleType("cc_benchmark", BenchmarkFactory)
+	ctx.RegisterModuleType("cc_cmake_snapshot", CmakeSnapshotFactory)
 	ctx.RegisterModuleType("cc_object", ObjectFactory)
 	ctx.RegisterModuleType("cc_genrule", GenRuleFactory)
 	ctx.RegisterModuleType("ndk_prebuilt_shared_stl", NdkPrebuiltSharedStlFactory)
@@ -299,6 +300,7 @@
 			system_shared_libs: [],
 			stl: "none",
 			vendor_available: true,
+			vendor_ramdisk_available: true,
 			product_available: true,
 			recovery_available: true,
 			host_supported: true,
@@ -553,7 +555,7 @@
 		ctx.RegisterModuleType("cc_test_library", TestLibraryFactory)
 		ctx.RegisterModuleType("vndk_prebuilt_shared", VndkPrebuiltSharedFactory)
 
-		RegisterVndkLibraryTxtTypes(ctx)
+		RegisterLlndkLibraryTxtType(ctx)
 	}),
 
 	// Additional files needed in tests that disallow non-existent source files.
@@ -570,17 +572,17 @@
 
 	// Additional files needed in tests that disallow non-existent source.
 	android.MockFS{
-		"defaults/cc/common/libc.map.txt":      nil,
-		"defaults/cc/common/libdl.map.txt":     nil,
-		"defaults/cc/common/libft2.map.txt":    nil,
-		"defaults/cc/common/libm.map.txt":      nil,
-		"defaults/cc/common/ndk_libc++_shared": nil,
-		"defaults/cc/common/crtbegin_so.c":     nil,
-		"defaults/cc/common/crtbegin.c":        nil,
-		"defaults/cc/common/crtend_so.c":       nil,
-		"defaults/cc/common/crtend.c":          nil,
-		"defaults/cc/common/crtbrand.c":        nil,
-		"external/compiler-rt/lib/cfi/cfi_blocklist.txt":   nil,
+		"defaults/cc/common/libc.map.txt":                nil,
+		"defaults/cc/common/libdl.map.txt":               nil,
+		"defaults/cc/common/libft2.map.txt":              nil,
+		"defaults/cc/common/libm.map.txt":                nil,
+		"defaults/cc/common/ndk_libc++_shared":           nil,
+		"defaults/cc/common/crtbegin_so.c":               nil,
+		"defaults/cc/common/crtbegin.c":                  nil,
+		"defaults/cc/common/crtend_so.c":                 nil,
+		"defaults/cc/common/crtend.c":                    nil,
+		"defaults/cc/common/crtbrand.c":                  nil,
+		"external/compiler-rt/lib/cfi/cfi_blocklist.txt": nil,
 
 		"defaults/cc/common/libclang_rt.ubsan_minimal.android_arm64.a": nil,
 		"defaults/cc/common/libclang_rt.ubsan_minimal.android_arm.a":   nil,
@@ -701,7 +703,7 @@
 	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
 	ctx.RegisterModuleType("vndk_prebuilt_shared", VndkPrebuiltSharedFactory)
 
-	RegisterVndkLibraryTxtTypes(ctx)
+	RegisterLlndkLibraryTxtType(ctx)
 
 	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
 	android.RegisterPrebuiltMutators(ctx)
diff --git a/cc/tidy.go b/cc/tidy.go
index 76ac7d5..ec1e8a2 100644
--- a/cc/tidy.go
+++ b/cc/tidy.go
@@ -220,7 +220,7 @@
 
 	// (1) Collect all obj/tidy files into OS-specific groups.
 	ctx.VisitAllModuleVariants(module, func(variant android.Module) {
-		if ctx.Config().KatiEnabled() && android.ShouldSkipAndroidMkProcessing(variant) {
+		if ctx.Config().KatiEnabled() && android.ShouldSkipAndroidMkProcessing(ctx, variant) {
 			return
 		}
 		if m, ok := variant.(*Module); ok {
diff --git a/cc/util.go b/cc/util.go
index 3ede8ff..8ffacae 100644
--- a/cc/util.go
+++ b/cc/util.go
@@ -68,6 +68,7 @@
 		needTidyFiles:   in.NeedTidyFiles,
 		sAbiDump:        in.SAbiDump,
 		emitXrefs:       in.EmitXrefs,
+		clangVerify:     in.ClangVerify,
 
 		systemIncludeFlags: strings.Join(in.SystemIncludeFlags, " "),
 
diff --git a/cc/vndk.go b/cc/vndk.go
index 14b44b6..9d196a0 100644
--- a/cc/vndk.go
+++ b/cc/vndk.go
@@ -15,26 +15,17 @@
 package cc
 
 import (
-	"errors"
-	"fmt"
 	"strings"
 
 	"android/soong/android"
-	"android/soong/cc/config"
-	"android/soong/etc"
-
-	"github.com/google/blueprint"
-	"github.com/google/blueprint/proptools"
 )
 
 const (
-	llndkLibrariesTxt                = "llndk.libraries.txt"
-	llndkLibrariesTxtForApex         = "llndk.libraries.txt.apex"
-	vndkCoreLibrariesTxt             = "vndkcore.libraries.txt"
-	vndkSpLibrariesTxt               = "vndksp.libraries.txt"
-	vndkPrivateLibrariesTxt          = "vndkprivate.libraries.txt"
-	vndkProductLibrariesTxt          = "vndkproduct.libraries.txt"
-	vndkUsingCoreVariantLibrariesTxt = "vndkcorevariant.libraries.txt"
+	llndkLibrariesTxt       = "llndk.libraries.txt"
+	vndkCoreLibrariesTxt    = "vndkcore.libraries.txt"
+	vndkSpLibrariesTxt      = "vndksp.libraries.txt"
+	vndkPrivateLibrariesTxt = "vndkprivate.libraries.txt"
+	vndkProductLibrariesTxt = "vndkproduct.libraries.txt"
 )
 
 func VndkLibrariesTxtModules(vndkVersion string, ctx android.BaseModuleContext) []string {
@@ -84,505 +75,9 @@
 	}
 }
 
-type vndkdep struct {
-	Properties VndkProperties
-}
-
-func (vndk *vndkdep) props() []interface{} {
-	return []interface{}{&vndk.Properties}
-}
-
-func (vndk *vndkdep) isVndk() bool {
-	return Bool(vndk.Properties.Vndk.Enabled)
-}
-
-func (vndk *vndkdep) isVndkSp() bool {
-	return Bool(vndk.Properties.Vndk.Support_system_process)
-}
-
-func (vndk *vndkdep) isVndkExt() bool {
-	return vndk.Properties.Vndk.Extends != nil
-}
-
-func (vndk *vndkdep) getVndkExtendsModuleName() string {
-	return String(vndk.Properties.Vndk.Extends)
-}
-
-func (vndk *vndkdep) typeName() string {
-	if !vndk.isVndk() {
-		return "native:vendor"
-	}
-	if !vndk.isVndkExt() {
-		if !vndk.isVndkSp() {
-			return "native:vendor:vndk"
-		}
-		return "native:vendor:vndksp"
-	}
-	if !vndk.isVndkSp() {
-		return "native:vendor:vndkext"
-	}
-	return "native:vendor:vndkspext"
-}
-
-// VNDK link type check from a module with UseVndk() == true.
-func (vndk *vndkdep) vndkCheckLinkType(ctx android.BaseModuleContext, to *Module, tag blueprint.DependencyTag) {
-	if to.linker == nil {
-		return
-	}
-	if !vndk.isVndk() {
-		// Non-VNDK modules those installed to /vendor, /system/vendor,
-		// /product or /system/product cannot depend on VNDK-private modules
-		// that include VNDK-core-private, VNDK-SP-private and LLNDK-private.
-		if to.IsVndkPrivate() {
-			ctx.ModuleErrorf("non-VNDK module should not link to %q which has `private: true`", to.Name())
-		}
-	}
-	if lib, ok := to.linker.(*libraryDecorator); !ok || !lib.shared() {
-		// Check only shared libraries.
-		// Other (static) libraries are allowed to link.
-		return
-	}
-
-	if to.IsLlndk() {
-		// LL-NDK libraries are allowed to link
-		return
-	}
-
-	if !to.UseVndk() {
-		ctx.ModuleErrorf("(%s) should not link to %q which is not a vendor-available library",
-			vndk.typeName(), to.Name())
-		return
-	}
-	if tag == vndkExtDepTag {
-		// Ensure `extends: "name"` property refers a vndk module that has vendor_available
-		// and has identical vndk properties.
-		if to.vndkdep == nil || !to.vndkdep.isVndk() {
-			ctx.ModuleErrorf("`extends` refers a non-vndk module %q", to.Name())
-			return
-		}
-		if vndk.isVndkSp() != to.vndkdep.isVndkSp() {
-			ctx.ModuleErrorf(
-				"`extends` refers a module %q with mismatched support_system_process",
-				to.Name())
-			return
-		}
-		if to.IsVndkPrivate() {
-			ctx.ModuleErrorf(
-				"`extends` refers module %q which has `private: true`",
-				to.Name())
-			return
-		}
-	}
-	if to.vndkdep == nil {
-		return
-	}
-
-	// Check the dependencies of VNDK shared libraries.
-	if err := vndkIsVndkDepAllowed(vndk, to.vndkdep); err != nil {
-		ctx.ModuleErrorf("(%s) should not link to %q (%s): %v",
-			vndk.typeName(), to.Name(), to.vndkdep.typeName(), err)
-		return
-	}
-}
-
-func vndkIsVndkDepAllowed(from *vndkdep, to *vndkdep) error {
-	// Check the dependencies of VNDK, VNDK-Ext, VNDK-SP, VNDK-SP-Ext and vendor modules.
-	if from.isVndkExt() {
-		if from.isVndkSp() {
-			if to.isVndk() && !to.isVndkSp() {
-				return errors.New("VNDK-SP extensions must not depend on VNDK or VNDK extensions")
-			}
-			return nil
-		}
-		// VNDK-Ext may depend on VNDK, VNDK-Ext, VNDK-SP, VNDK-SP-Ext, or vendor libs.
-		return nil
-	}
-	if from.isVndk() {
-		if to.isVndkExt() {
-			return errors.New("VNDK-core and VNDK-SP must not depend on VNDK extensions")
-		}
-		if from.isVndkSp() {
-			if !to.isVndkSp() {
-				return errors.New("VNDK-SP must only depend on VNDK-SP")
-			}
-			return nil
-		}
-		if !to.isVndk() {
-			return errors.New("VNDK-core must only depend on VNDK-core or VNDK-SP")
-		}
-		return nil
-	}
-	// Vendor modules may depend on VNDK, VNDK-Ext, VNDK-SP, VNDK-SP-Ext, or vendor libs.
-	return nil
-}
-
-type moduleListerFunc func(ctx android.SingletonContext) (moduleNames, fileNames []string)
-
-var (
-	llndkLibraries                = vndkModuleLister(func(m *Module) bool { return m.VendorProperties.IsLLNDK && !m.Header() })
-	vndkSPLibraries               = vndkModuleLister(func(m *Module) bool { return m.VendorProperties.IsVNDKSP })
-	vndkCoreLibraries             = vndkModuleLister(func(m *Module) bool { return m.VendorProperties.IsVNDKCore })
-	vndkPrivateLibraries          = vndkModuleLister(func(m *Module) bool { return m.VendorProperties.IsVNDKPrivate })
-	vndkProductLibraries          = vndkModuleLister(func(m *Module) bool { return m.VendorProperties.IsVNDKProduct })
-	vndkUsingCoreVariantLibraries = vndkModuleLister(func(m *Module) bool { return m.VendorProperties.IsVNDKUsingCoreVariant })
-)
-
-// vndkModuleLister takes a predicate that operates on a Module and returns a moduleListerFunc
-// that produces a list of module names and output file names for which the predicate returns true.
-func vndkModuleLister(predicate func(*Module) bool) moduleListerFunc {
-	return func(ctx android.SingletonContext) (moduleNames, fileNames []string) {
-		ctx.VisitAllModules(func(m android.Module) {
-			if c, ok := m.(*Module); ok && predicate(c) && !c.IsVndkPrebuiltLibrary() {
-				filename, err := getVndkFileName(c)
-				if err != nil {
-					ctx.ModuleErrorf(m, "%s", err)
-				}
-				moduleNames = append(moduleNames, ctx.ModuleName(m))
-				fileNames = append(fileNames, filename)
-			}
-		})
-		moduleNames = android.SortedUniqueStrings(moduleNames)
-		fileNames = android.SortedUniqueStrings(fileNames)
-		return
-	}
-}
-
-// vndkModuleListRemover takes a moduleListerFunc and a prefix and returns a moduleListerFunc
-// that returns the same lists as the input moduleListerFunc, but with  modules with the
-// given prefix removed.
-func vndkModuleListRemover(lister moduleListerFunc, prefix string) moduleListerFunc {
-	return func(ctx android.SingletonContext) (moduleNames, fileNames []string) {
-		moduleNames, fileNames = lister(ctx)
-		filter := func(in []string) []string {
-			out := make([]string, 0, len(in))
-			for _, lib := range in {
-				if strings.HasPrefix(lib, prefix) {
-					continue
-				}
-				out = append(out, lib)
-			}
-			return out
-		}
-		return filter(moduleNames), filter(fileNames)
-	}
-}
-
-var vndkMustUseVendorVariantListKey = android.NewOnceKey("vndkMustUseVendorVariantListKey")
-
-func vndkMustUseVendorVariantList(cfg android.Config) []string {
-	return cfg.Once(vndkMustUseVendorVariantListKey, func() interface{} {
-		return config.VndkMustUseVendorVariantList
-	}).([]string)
-}
-
-// test may call this to override global configuration(config.VndkMustUseVendorVariantList)
-// when it is called, it must be before the first call to vndkMustUseVendorVariantList()
-func setVndkMustUseVendorVariantListForTest(config android.Config, mustUseVendorVariantList []string) {
-	config.Once(vndkMustUseVendorVariantListKey, func() interface{} {
-		return mustUseVendorVariantList
-	})
-}
-
-func processVndkLibrary(mctx android.BottomUpMutatorContext, m *Module) {
-	if m.InProduct() {
-		// We may skip the steps for the product variants because they
-		// are already covered by the vendor variants.
-		return
-	}
-
-	name := m.BaseModuleName()
-
-	if lib := m.library; lib != nil && lib.hasStubsVariants() && name != "libz" {
-		// b/155456180 libz is the ONLY exception here. We don't want to make
-		// libz an LLNDK library because we in general can't guarantee that
-		// libz will behave consistently especially about the compression.
-		// i.e. the compressed output might be different across releases.
-		// As the library is an external one, it's risky to keep the compatibility
-		// promise if it becomes an LLNDK.
-		mctx.PropertyErrorf("vndk.enabled", "This library provides stubs. Shouldn't be VNDK. Consider making it as LLNDK")
-	}
-
-	if inList(name, vndkMustUseVendorVariantList(mctx.Config())) {
-		m.Properties.MustUseVendorVariant = true
-	}
-	if mctx.DeviceConfig().VndkUseCoreVariant() && !m.Properties.MustUseVendorVariant {
-		m.VendorProperties.IsVNDKUsingCoreVariant = true
-	}
-
-	if m.vndkdep.isVndkSp() {
-		m.VendorProperties.IsVNDKSP = true
-	} else {
-		m.VendorProperties.IsVNDKCore = true
-	}
-	if m.IsVndkPrivate() {
-		m.VendorProperties.IsVNDKPrivate = true
-	}
-	if Bool(m.VendorProperties.Product_available) {
-		m.VendorProperties.IsVNDKProduct = true
-	}
-}
-
-// Check for modules that mustn't be VNDK
-func shouldSkipVndkMutator(m *Module) bool {
-	if !m.Enabled() {
-		return true
-	}
-	if !m.Device() {
-		// Skip non-device modules
-		return true
-	}
-	if m.Target().NativeBridge == android.NativeBridgeEnabled {
-		// Skip native_bridge modules
-		return true
-	}
-	return false
-}
-
-func IsForVndkApex(mctx android.BottomUpMutatorContext, m *Module) bool {
-	if shouldSkipVndkMutator(m) {
-		return false
-	}
-
-	// TODO(b/142675459): Use enabled: to select target device in vndk_prebuilt_shared
-	// When b/142675459 is landed, remove following check
-	if p, ok := m.linker.(*vndkPrebuiltLibraryDecorator); ok {
-		// prebuilt vndk modules should match with device
-		if !p.MatchesWithDevice(mctx.DeviceConfig()) {
-			return false
-		}
-	}
-
-	if lib, ok := m.linker.(libraryInterface); ok {
-		// VNDK APEX doesn't need stub variants
-		if lib.buildStubs() {
-			return false
-		}
-		useCoreVariant := mctx.DeviceConfig().VndkUseCoreVariant() && !m.MustUseVendorVariant()
-		return lib.shared() && m.InVendor() && m.IsVndk() && !m.IsVndkExt() && !useCoreVariant
-	}
-	return false
-}
-
-// gather list of vndk-core, vndk-sp, and ll-ndk libs
-func VndkMutator(mctx android.BottomUpMutatorContext) {
-	m, ok := mctx.Module().(*Module)
-	if !ok {
-		return
-	}
-
-	if shouldSkipVndkMutator(m) {
-		return
-	}
-
-	lib, isLib := m.linker.(*libraryDecorator)
-	prebuiltLib, isPrebuiltLib := m.linker.(*prebuiltLibraryLinker)
-
-	if m.InVendorOrProduct() && isLib && lib.hasLLNDKStubs() {
-		m.VendorProperties.IsLLNDK = true
-		m.VendorProperties.IsVNDKPrivate = Bool(lib.Properties.Llndk.Private)
-	}
-	if m.InVendorOrProduct() && isPrebuiltLib && prebuiltLib.hasLLNDKStubs() {
-		m.VendorProperties.IsLLNDK = true
-		m.VendorProperties.IsVNDKPrivate = Bool(prebuiltLib.Properties.Llndk.Private)
-	}
-
-	if m.IsVndkPrebuiltLibrary() && !m.IsVndk() {
-		m.VendorProperties.IsLLNDK = true
-		// TODO(b/280697209): copy "llndk.private" flag to vndk_prebuilt_shared
-	}
-
-	if (isLib && lib.buildShared()) || (isPrebuiltLib && prebuiltLib.buildShared()) {
-		if m.vndkdep != nil && m.vndkdep.isVndk() && !m.vndkdep.isVndkExt() {
-			processVndkLibrary(mctx, m)
-			return
-		}
-	}
-}
-
-func init() {
-	RegisterVndkLibraryTxtTypes(android.InitRegistrationContext)
-}
-
-func RegisterVndkLibraryTxtTypes(ctx android.RegistrationContext) {
-	ctx.RegisterParallelSingletonModuleType("llndk_libraries_txt", llndkLibrariesTxtFactory)
-	ctx.RegisterParallelSingletonModuleType("llndk_libraries_txt_for_apex", llndkLibrariesTxtApexOnlyFactory)
-	ctx.RegisterParallelSingletonModuleType("vndksp_libraries_txt", vndkSPLibrariesTxtFactory)
-	ctx.RegisterParallelSingletonModuleType("vndkcore_libraries_txt", vndkCoreLibrariesTxtFactory)
-	ctx.RegisterParallelSingletonModuleType("vndkprivate_libraries_txt", vndkPrivateLibrariesTxtFactory)
-	ctx.RegisterParallelSingletonModuleType("vndkproduct_libraries_txt", vndkProductLibrariesTxtFactory)
-	ctx.RegisterParallelSingletonModuleType("vndkcorevariant_libraries_txt", vndkUsingCoreVariantLibrariesTxtFactory)
-}
-
-type vndkLibrariesTxt struct {
-	android.SingletonModuleBase
-
-	lister               moduleListerFunc
-	makeVarName          string
-	filterOutFromMakeVar string
-
-	properties VndkLibrariesTxtProperties
-
-	outputFile  android.OutputPath
-	moduleNames []string
-	fileNames   []string
-}
-
-type VndkLibrariesTxtProperties struct {
-	Insert_vndk_version *bool
-	Stem                *string
-}
-
-var _ etc.PrebuiltEtcModule = &vndkLibrariesTxt{}
-var _ android.OutputFileProducer = &vndkLibrariesTxt{}
-
-// llndk_libraries_txt is a singleton module whose content is a list of LLNDK libraries
-// generated by Soong.
-// Make uses LLNDK_LIBRARIES to determine which libraries to install.
-// HWASAN is only part of the LLNDK in builds in which libc depends on HWASAN.
-// Therefore, by removing the library here, we cause it to only be installed if libc
-// depends on it.
-func llndkLibrariesTxtFactory() android.SingletonModule {
-	return newVndkLibrariesWithMakeVarFilter(llndkLibraries, "LLNDK_LIBRARIES", "libclang_rt.hwasan")
-}
-
-// llndk_libraries_txt_for_apex is a singleton module that provide the same LLNDK libraries list
-// with the llndk_libraries_txt, but skips setting make variable LLNDK_LIBRARIES. So, it must not
-// be used without installing llndk_libraries_txt singleton.
-// We include llndk_libraries_txt by default to install the llndk.libraries.txt file to system/etc.
-// This singleton module is to install the llndk.libraries.<ver>.txt file to vndk apex.
-func llndkLibrariesTxtApexOnlyFactory() android.SingletonModule {
-	return newVndkLibrariesWithMakeVarFilter(llndkLibraries, "", "libclang_rt.hwasan")
-}
-
-// vndksp_libraries_txt is a singleton module whose content is a list of VNDKSP libraries
-// generated by Soong but can be referenced by other modules.
-// For example, apex_vndk can depend on these files as prebuilt.
-func vndkSPLibrariesTxtFactory() android.SingletonModule {
-	return newVndkLibrariesTxt(vndkSPLibraries, "VNDK_SAMEPROCESS_LIBRARIES")
-}
-
-// vndkcore_libraries_txt is a singleton module whose content is a list of VNDK core libraries
-// generated by Soong but can be referenced by other modules.
-// For example, apex_vndk can depend on these files as prebuilt.
-func vndkCoreLibrariesTxtFactory() android.SingletonModule {
-	return newVndkLibrariesTxt(vndkCoreLibraries, "VNDK_CORE_LIBRARIES")
-}
-
-// vndkprivate_libraries_txt is a singleton module whose content is a list of VNDK private libraries
-// generated by Soong but can be referenced by other modules.
-// For example, apex_vndk can depend on these files as prebuilt.
-func vndkPrivateLibrariesTxtFactory() android.SingletonModule {
-	return newVndkLibrariesTxt(vndkPrivateLibraries, "VNDK_PRIVATE_LIBRARIES")
-}
-
-// vndkproduct_libraries_txt is a singleton module whose content is a list of VNDK product libraries
-// generated by Soong but can be referenced by other modules.
-// For example, apex_vndk can depend on these files as prebuilt.
-func vndkProductLibrariesTxtFactory() android.SingletonModule {
-	return newVndkLibrariesTxt(vndkProductLibraries, "VNDK_PRODUCT_LIBRARIES")
-}
-
-// vndkcorevariant_libraries_txt is a singleton module whose content is a list of VNDK libraries
-// that are using the core variant, generated by Soong but can be referenced by other modules.
-// For example, apex_vndk can depend on these files as prebuilt.
-func vndkUsingCoreVariantLibrariesTxtFactory() android.SingletonModule {
-	return newVndkLibrariesTxt(vndkUsingCoreVariantLibraries, "VNDK_USING_CORE_VARIANT_LIBRARIES")
-}
-
-func newVndkLibrariesWithMakeVarFilter(lister moduleListerFunc, makeVarName string, filter string) android.SingletonModule {
-	m := &vndkLibrariesTxt{
-		lister:               lister,
-		makeVarName:          makeVarName,
-		filterOutFromMakeVar: filter,
-	}
-	m.AddProperties(&m.properties)
-	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
-	return m
-}
-
-func newVndkLibrariesTxt(lister moduleListerFunc, makeVarName string) android.SingletonModule {
-	return newVndkLibrariesWithMakeVarFilter(lister, makeVarName, "")
-}
-
 func insertVndkVersion(filename string, vndkVersion string) string {
 	if index := strings.LastIndex(filename, "."); index != -1 {
 		return filename[:index] + "." + vndkVersion + filename[index:]
 	}
 	return filename
 }
-
-func (txt *vndkLibrariesTxt) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	filename := proptools.StringDefault(txt.properties.Stem, txt.Name())
-
-	txt.outputFile = android.PathForModuleOut(ctx, filename).OutputPath
-
-	installPath := android.PathForModuleInstall(ctx, "etc")
-	ctx.InstallFile(installPath, filename, txt.outputFile)
-}
-
-func (txt *vndkLibrariesTxt) GenerateSingletonBuildActions(ctx android.SingletonContext) {
-	txt.moduleNames, txt.fileNames = txt.lister(ctx)
-	android.WriteFileRule(ctx, txt.outputFile, strings.Join(txt.fileNames, "\n"))
-}
-
-func (txt *vndkLibrariesTxt) AndroidMkEntries() []android.AndroidMkEntries {
-	return []android.AndroidMkEntries{android.AndroidMkEntries{
-		Class:      "ETC",
-		OutputFile: android.OptionalPathForPath(txt.outputFile),
-		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
-			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_STEM", txt.outputFile.Base())
-			},
-		},
-	}}
-}
-
-func (txt *vndkLibrariesTxt) MakeVars(ctx android.MakeVarsContext) {
-	if txt.makeVarName == "" {
-		return
-	}
-
-	filter := func(modules []string, prefix string) []string {
-		if prefix == "" {
-			return modules
-		}
-		var result []string
-		for _, module := range modules {
-			if strings.HasPrefix(module, prefix) {
-				continue
-			} else {
-				result = append(result, module)
-			}
-		}
-		return result
-	}
-	ctx.Strict(txt.makeVarName, strings.Join(filter(txt.moduleNames, txt.filterOutFromMakeVar), " "))
-}
-
-// PrebuiltEtcModule interface
-func (txt *vndkLibrariesTxt) OutputFile() android.OutputPath {
-	return txt.outputFile
-}
-
-// PrebuiltEtcModule interface
-func (txt *vndkLibrariesTxt) BaseDir() string {
-	return "etc"
-}
-
-// PrebuiltEtcModule interface
-func (txt *vndkLibrariesTxt) SubDir() string {
-	return ""
-}
-
-func (txt *vndkLibrariesTxt) OutputFiles(tag string) (android.Paths, error) {
-	return android.Paths{txt.outputFile}, nil
-}
-func getVndkFileName(m *Module) (string, error) {
-	if library, ok := m.linker.(*libraryDecorator); ok {
-		return library.getLibNameHelper(m.BaseModuleName(), true, false) + ".so", nil
-	}
-	if prebuilt, ok := m.linker.(*prebuiltLibraryLinker); ok {
-		return prebuilt.libraryDecorator.getLibNameHelper(m.BaseModuleName(), true, false) + ".so", nil
-	}
-	return "", fmt.Errorf("VNDK library should have libraryDecorator or prebuiltLibraryLinker as linker: %T", m.linker)
-}
diff --git a/cc/vndk_prebuilt.go b/cc/vndk_prebuilt.go
index 4d2412f..e7dff40 100644
--- a/cc/vndk_prebuilt.go
+++ b/cc/vndk_prebuilt.go
@@ -49,6 +49,8 @@
 //	    },
 //	}
 type vndkPrebuiltProperties struct {
+	VndkProperties
+
 	// VNDK snapshot version.
 	Version *string
 
@@ -268,3 +270,14 @@
 func init() {
 	android.RegisterModuleType("vndk_prebuilt_shared", VndkPrebuiltSharedFactory)
 }
+
+func IsForVndkApex(mctx android.BottomUpMutatorContext, m *Module) bool {
+	if !m.Enabled(mctx) {
+		return true
+	}
+
+	if p, ok := m.linker.(*vndkPrebuiltLibraryDecorator); ok {
+		return p.MatchesWithDevice(mctx.DeviceConfig()) && Bool(p.properties.Vndk.Enabled)
+	}
+	return false
+}
diff --git a/cmd/release_config/build_flag/main.go b/cmd/release_config/build_flag/main.go
index 6f909af..5d183ee 100644
--- a/cmd/release_config/build_flag/main.go
+++ b/cmd/release_config/build_flag/main.go
@@ -1,10 +1,12 @@
 package main
 
 import (
+	"cmp"
 	"flag"
 	"fmt"
 	"os"
 	"path/filepath"
+	"slices"
 	"strings"
 
 	rc_lib "android/soong/cmd/release_config/release_config_lib"
@@ -36,6 +38,21 @@
 
 	// Disable warning messages
 	quiet bool
+
+	// Show all release configs
+	allReleases bool
+
+	// Call get_build_var PRODUCT_RELEASE_CONFIG_MAPS to get the
+	// product-specific map directories.
+	useGetBuildVar bool
+
+	// Panic on errors.
+	debug bool
+
+	// Allow missing release config.
+	// If true, and we cannot find the named release config, values for
+	// `trunk_staging` will be used.
+	allowMissing bool
 }
 
 type CommandFunc func(*rc_lib.ReleaseConfigs, Flags, string, []string) error
@@ -60,6 +77,14 @@
 	return "", fmt.Errorf("Could not determine directory from %s", path)
 }
 
+func MarshalFlagDefaultValue(config *rc_lib.ReleaseConfig, name string) (ret string, err error) {
+	fa, ok := config.FlagArtifacts[name]
+	if !ok {
+		return "", fmt.Errorf("%s not found in %s", name, config.Name)
+	}
+	return rc_lib.MarshalValue(fa.Traces[0].Value), nil
+}
+
 func MarshalFlagValue(config *rc_lib.ReleaseConfig, name string) (ret string, err error) {
 	fa, ok := config.FlagArtifacts[name]
 	if !ok {
@@ -68,19 +93,41 @@
 	return rc_lib.MarshalValue(fa.Value), nil
 }
 
+// Returns a list of ReleaseConfig objects for which to process flags.
 func GetReleaseArgs(configs *rc_lib.ReleaseConfigs, commonFlags Flags) ([]*rc_lib.ReleaseConfig, error) {
 	var all bool
-	relFlags := flag.NewFlagSet("set", flag.ExitOnError)
-	relFlags.BoolVar(&all, "all", false, "Display all flags")
+	relFlags := flag.NewFlagSet("releaseFlags", flag.ExitOnError)
+	relFlags.BoolVar(&all, "all", false, "Display all releases")
 	relFlags.Parse(commonFlags.targetReleases)
 	var ret []*rc_lib.ReleaseConfig
-	if all {
+	if all || commonFlags.allReleases {
+		sortMap := map[string]int{
+			"trunk_staging": 0,
+			"trunk_food":    10,
+			"trunk":         20,
+			// Anything not listed above, uses this for key 1 in the sort.
+			"-default": 100,
+		}
+
 		for _, config := range configs.ReleaseConfigs {
 			ret = append(ret, config)
 		}
+		slices.SortFunc(ret, func(a, b *rc_lib.ReleaseConfig) int {
+			mapValue := func(v *rc_lib.ReleaseConfig) int {
+				if v, ok := sortMap[v.Name]; ok {
+					return v
+				}
+				return sortMap["-default"]
+			}
+			if n := cmp.Compare(mapValue(a), mapValue(b)); n != 0 {
+				return n
+			}
+			return cmp.Compare(a.Name, b.Name)
+		})
 		return ret, nil
 	}
 	for _, arg := range relFlags.Args() {
+		// Return releases in the order that they were given.
 		config, err := configs.GetReleaseConfig(arg)
 		if err != nil {
 			return nil, err
@@ -92,12 +139,17 @@
 
 func GetCommand(configs *rc_lib.ReleaseConfigs, commonFlags Flags, cmd string, args []string) error {
 	isTrace := cmd == "trace"
+	isSet := cmd == "set"
+
 	var all bool
-	getFlags := flag.NewFlagSet("set", flag.ExitOnError)
+	getFlags := flag.NewFlagSet("get", flag.ExitOnError)
 	getFlags.BoolVar(&all, "all", false, "Display all flags")
 	getFlags.Parse(args)
 	args = getFlags.Args()
 
+	if isSet {
+		commonFlags.allReleases = true
+	}
 	releaseConfigList, err := GetReleaseArgs(configs, commonFlags)
 	if err != nil {
 		return err
@@ -111,25 +163,77 @@
 		for _, fa := range configs.FlagArtifacts {
 			args = append(args, *fa.FlagDeclaration.Name)
 		}
+		slices.Sort(args)
 	}
 
-	showName := len(releaseConfigList) > 1 || len(args) > 1
-	for _, config := range releaseConfigList {
-		var configName string
-		if len(releaseConfigList) > 1 {
-			configName = fmt.Sprintf("%s.", config.Name)
-		}
+	var maxVariableNameLen, maxReleaseNameLen int
+	var releaseNameFormat, variableNameFormat string
+	valueFormat := "%s"
+	showReleaseName := len(releaseConfigList) > 1
+	showVariableName := len(args) > 1
+	if showVariableName {
 		for _, arg := range args {
+			maxVariableNameLen = max(len(arg), maxVariableNameLen)
+		}
+		variableNameFormat = fmt.Sprintf("%%-%ds ", maxVariableNameLen)
+		valueFormat = "'%s'"
+	}
+	if showReleaseName {
+		for _, config := range releaseConfigList {
+			maxReleaseNameLen = max(len(config.Name), maxReleaseNameLen)
+		}
+		releaseNameFormat = fmt.Sprintf("%%-%ds ", maxReleaseNameLen)
+		valueFormat = "'%s'"
+	}
+
+	outputOneLine := func(variable, release, value, valueFormat string) {
+		var outStr string
+		if showVariableName {
+			outStr += fmt.Sprintf(variableNameFormat, variable)
+		}
+		if showReleaseName {
+			outStr += fmt.Sprintf(releaseNameFormat, release)
+		}
+		outStr += fmt.Sprintf(valueFormat, value)
+		fmt.Println(outStr)
+	}
+
+	for _, arg := range args {
+		if _, ok := configs.FlagArtifacts[arg]; !ok {
+			return fmt.Errorf("%s is not a defined build flag", arg)
+		}
+	}
+
+	for _, arg := range args {
+		for _, config := range releaseConfigList {
+			if isSet {
+				// If this is from the set command, format the output as:
+				// <default>           ""
+				// trunk_staging       ""
+				// trunk               ""
+				//
+				// ap1a                ""
+				// ...
+				switch {
+				case config.Name == "trunk_staging":
+					defaultValue, err := MarshalFlagDefaultValue(config, arg)
+					if err != nil {
+						return err
+					}
+					outputOneLine(arg, "<default>", defaultValue, valueFormat)
+				case config.AconfigFlagsOnly:
+					continue
+				case config.Name == "trunk":
+					fmt.Println()
+				}
+			}
 			val, err := MarshalFlagValue(config, arg)
-			if err != nil {
-				return err
-			}
-			if showName {
-				fmt.Printf("%s%s=%s\n", configName, arg, val)
+			if err == nil {
+				outputOneLine(arg, config.Name, val, valueFormat)
 			} else {
-				fmt.Printf("%s\n", val)
+				outputOneLine(arg, config.Name, "REDACTED", "%s")
 			}
-			if isTrace {
+			if err == nil && isTrace {
 				for _, trace := range config.FlagArtifacts[arg].Traces {
 					fmt.Printf("  => \"%s\" in %s\n", rc_lib.MarshalValue(trace.Value), *trace.Source)
 				}
@@ -141,6 +245,8 @@
 
 func SetCommand(configs *rc_lib.ReleaseConfigs, commonFlags Flags, cmd string, args []string) error {
 	var valueDir string
+	var redacted bool
+	var value string
 	if len(commonFlags.targetReleases) > 1 {
 		return fmt.Errorf("set command only allows one --release argument.  Got: %s", strings.Join(commonFlags.targetReleases, " "))
 	}
@@ -148,82 +254,141 @@
 
 	setFlags := flag.NewFlagSet("set", flag.ExitOnError)
 	setFlags.StringVar(&valueDir, "dir", "", "Directory in which to place the value")
+	setFlags.BoolVar(&redacted, "redacted", false, "Whether the flag should be redacted")
 	setFlags.Parse(args)
 	setArgs := setFlags.Args()
-	if len(setArgs) != 2 {
+	if redacted {
+		if len(setArgs) != 1 {
+			return fmt.Errorf("set command expected '--redacted=true flag', got: --redacted=true %s", strings.Join(setArgs, " "))
+		}
+	} else if len(setArgs) != 2 {
 		return fmt.Errorf("set command expected flag and value, got: %s", strings.Join(setArgs, " "))
 	}
 	name := setArgs[0]
-	value := setArgs[1]
+	if !redacted {
+		value = setArgs[1]
+	}
 	release, err := configs.GetReleaseConfig(targetRelease)
 	targetRelease = release.Name
 	if err != nil {
 		return err
 	}
+	if release.AconfigFlagsOnly {
+		return fmt.Errorf("%s does not allow build flag overrides", targetRelease)
+	}
 	flagArtifact, ok := release.FlagArtifacts[name]
 	if !ok {
 		return fmt.Errorf("Unknown build flag %s", name)
 	}
 	if valueDir == "" {
-		mapDir, err := GetMapDir(*flagArtifact.Traces[len(flagArtifact.Traces)-1].Source)
+		mapDir, err := configs.GetFlagValueDirectory(release, flagArtifact)
 		if err != nil {
 			return err
 		}
 		valueDir = mapDir
 	}
 
+	var updatedFiles []string
+	rcPath := filepath.Join(valueDir, "release_configs", fmt.Sprintf("%s.textproto", targetRelease))
+	// Create the release config declaration only if necessary.
+	if _, err = os.Stat(rcPath); err != nil {
+		if err = os.MkdirAll(filepath.Dir(rcPath), 0775); err != nil {
+			return err
+		}
+		rcValue := &rc_proto.ReleaseConfig{
+			Name: proto.String(targetRelease),
+		}
+		err = rc_lib.WriteMessage(rcPath, rcValue)
+		if err != nil {
+			return err
+		}
+		updatedFiles = append(updatedFiles, rcPath)
+	}
+
 	flagValue := &rc_proto.FlagValue{
-		Name:  proto.String(name),
-		Value: rc_lib.UnmarshalValue(value),
+		Name: proto.String(name),
+	}
+	if redacted {
+		flagValue.Redacted = proto.Bool(true)
+	} else {
+		flagValue.Value = rc_lib.UnmarshalValue(value)
 	}
 	flagPath := filepath.Join(valueDir, "flag_values", targetRelease, fmt.Sprintf("%s.textproto", name))
-	return rc_lib.WriteMessage(flagPath, flagValue)
+	err = rc_lib.WriteMessage(flagPath, flagValue)
+	if err != nil {
+		return err
+	}
+
+	// Reload the release configs.
+	configs, err = rc_lib.ReadReleaseConfigMaps(commonFlags.maps, commonFlags.targetReleases[0], commonFlags.useGetBuildVar, commonFlags.allowMissing)
+	if err != nil {
+		return err
+	}
+	err = GetCommand(configs, commonFlags, cmd, []string{name})
+	if err != nil {
+		return err
+	}
+	updatedFiles = append(updatedFiles, flagPath)
+	fmt.Printf("Added/Updated: %s\n", strings.Join(updatedFiles, " "))
+	return nil
 }
 
 func main() {
-	var err error
 	var commonFlags Flags
 	var configs *rc_lib.ReleaseConfigs
+	topDir, err := rc_lib.GetTopDir()
 
-	outEnv := os.Getenv("OUT_DIR")
-	if outEnv == "" {
-		outEnv = "out"
-	}
 	// Handle the common arguments
-	flag.StringVar(&commonFlags.top, "top", ".", "path to top of workspace")
+	flag.StringVar(&commonFlags.top, "top", topDir, "path to top of workspace")
 	flag.BoolVar(&commonFlags.quiet, "quiet", false, "disable warning messages")
 	flag.Var(&commonFlags.maps, "map", "path to a release_config_map.textproto. may be repeated")
-	flag.StringVar(&commonFlags.outDir, "out_dir", rc_lib.GetDefaultOutDir(), "basepath for the output. Multiple formats are created")
+	flag.StringVar(&commonFlags.outDir, "out-dir", rc_lib.GetDefaultOutDir(), "basepath for the output. Multiple formats are created")
 	flag.Var(&commonFlags.targetReleases, "release", "TARGET_RELEASE for this build")
+	flag.BoolVar(&commonFlags.allowMissing, "allow-missing", false, "Use trunk_staging values if release not found")
+	flag.BoolVar(&commonFlags.allReleases, "all-releases", false, "operate on all releases. (Ignored for set command)")
+	flag.BoolVar(&commonFlags.useGetBuildVar, "use-get-build-var", true, "use get_build_var PRODUCT_RELEASE_CONFIG_MAPS to get needed maps")
+	flag.BoolVar(&commonFlags.debug, "debug", false, "turn on debugging output for errors")
 	flag.Parse()
 
+	errorExit := func(err error) {
+		if commonFlags.debug {
+			panic(err)
+		}
+		fmt.Fprintf(os.Stderr, "%s\n", err)
+		os.Exit(1)
+	}
+
 	if commonFlags.quiet {
 		rc_lib.DisableWarnings()
 	}
 
 	if len(commonFlags.targetReleases) == 0 {
-		commonFlags.targetReleases = rc_lib.StringList{"trunk_staging"}
+		release, ok := os.LookupEnv("TARGET_RELEASE")
+		if ok {
+			commonFlags.targetReleases = rc_lib.StringList{release}
+		} else {
+			commonFlags.targetReleases = rc_lib.StringList{"trunk_staging"}
+		}
 	}
 
 	if err = os.Chdir(commonFlags.top); err != nil {
-		panic(err)
+		errorExit(err)
 	}
 
 	// Get the current state of flagging.
 	relName := commonFlags.targetReleases[0]
 	if relName == "--all" || relName == "-all" {
-		// If the users said `--release --all`, grab trunk staging for simplicity.
-		relName = "trunk_staging"
+		commonFlags.allReleases = true
 	}
-	configs, err = rc_lib.ReadReleaseConfigMaps(commonFlags.maps, relName)
+	configs, err = rc_lib.ReadReleaseConfigMaps(commonFlags.maps, relName, commonFlags.useGetBuildVar, commonFlags.allowMissing)
 	if err != nil {
-		panic(err)
+		errorExit(err)
 	}
 
 	if cmd, ok := commandMap[flag.Arg(0)]; ok {
 		args := flag.Args()
 		if err = cmd(configs, commonFlags, args[0], args[1:]); err != nil {
-			panic(err)
+			errorExit(err)
 		}
 	}
 }
diff --git a/cmd/release_config/build_flag_declarations/Android.bp b/cmd/release_config/build_flag_declarations/Android.bp
new file mode 100644
index 0000000..e4f999f
--- /dev/null
+++ b/cmd/release_config/build_flag_declarations/Android.bp
@@ -0,0 +1,32 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+blueprint_go_binary {
+    name: "build-flag-declarations",
+    deps: [
+        "golang-protobuf-encoding-prototext",
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+        "soong-cmd-release_config-proto",
+        "soong-cmd-release_config-lib",
+    ],
+    srcs: [
+        "main.go",
+    ],
+}
+
+bootstrap_go_package {
+    name: "soong-cmd-release_config-build_flag_declarations",
+    pkgPath: "android/soong/cmd/release_config/build_flag_declarations",
+    deps: [
+        "golang-protobuf-encoding-prototext",
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+        "soong-cmd-release_config-proto",
+        "soong-cmd-release_config-lib",
+    ],
+    srcs: [
+        "main.go",
+    ],
+}
diff --git a/cmd/release_config/build_flag_declarations/main.go b/cmd/release_config/build_flag_declarations/main.go
new file mode 100644
index 0000000..cc286b6
--- /dev/null
+++ b/cmd/release_config/build_flag_declarations/main.go
@@ -0,0 +1,81 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"os"
+
+	rc_lib "android/soong/cmd/release_config/release_config_lib"
+	rc_proto "android/soong/cmd/release_config/release_config_proto"
+)
+
+type Flags struct {
+	// The path to the top of the workspace.  Default: ".".
+	top string
+
+	// Output file.
+	output string
+
+	// Format for output file
+	format string
+
+	// List of flag_declaration files to add.
+	decls rc_lib.StringList
+
+	// List of flag_artifacts files to merge.
+	intermediates rc_lib.StringList
+
+	// Disable warning messages
+	quiet bool
+
+	// Panic on errors.
+	debug bool
+}
+
+func main() {
+	var flags Flags
+	topDir, err := rc_lib.GetTopDir()
+
+	// Handle the common arguments
+	flag.StringVar(&flags.top, "top", topDir, "path to top of workspace")
+	flag.Var(&flags.decls, "decl", "path to a flag_declaration file. May be repeated")
+	flag.Var(&flags.intermediates, "intermediate", "path to a flag_artifacts file (output from a prior run). May be repeated")
+	flag.StringVar(&flags.format, "format", "pb", "output file format")
+	flag.StringVar(&flags.output, "output", "build_flags.pb", "output file")
+	flag.BoolVar(&flags.debug, "debug", false, "turn on debugging output for errors")
+	flag.BoolVar(&flags.quiet, "quiet", false, "disable warning messages")
+	flag.Parse()
+
+	errorExit := func(err error) {
+		if flags.debug {
+			panic(err)
+		}
+		fmt.Fprintf(os.Stderr, "%s\n", err)
+		os.Exit(1)
+	}
+
+	if flags.quiet {
+		rc_lib.DisableWarnings()
+	}
+
+	if err = os.Chdir(flags.top); err != nil {
+		errorExit(err)
+	}
+
+	flagArtifacts := rc_lib.FlagArtifactsFactory("")
+	intermediates := []*rc_proto.FlagDeclarationArtifacts{}
+	for _, intermediate := range flags.intermediates {
+		fda := rc_lib.FlagDeclarationArtifactsFactory(intermediate)
+		intermediates = append(intermediates, fda)
+	}
+	for _, decl := range flags.decls {
+		fa := rc_lib.FlagArtifactFactory(decl)
+		(*flagArtifacts)[*fa.FlagDeclaration.Name] = fa
+	}
+
+	message := flagArtifacts.GenerateFlagDeclarationArtifacts(intermediates)
+	err = rc_lib.WriteFormattedMessage(flags.output, flags.format, message)
+	if err != nil {
+		errorExit(err)
+	}
+}
diff --git a/cmd/release_config/crunch_flags/main.go b/cmd/release_config/crunch_flags/main.go
index 29290a4..cd39ffd 100644
--- a/cmd/release_config/crunch_flags/main.go
+++ b/cmd/release_config/crunch_flags/main.go
@@ -15,18 +15,30 @@
 	"google.golang.org/protobuf/proto"
 )
 
-// When a flag declaration has an initial value that is a string, the default workflow is PREBUILT.
-// If the flag name starts with any of prefixes in manualFlagNamePrefixes, it is MANUAL.
-var manualFlagNamePrefixes []string = []string{
-	"RELEASE_ACONFIG_",
-	"RELEASE_PLATFORM_",
-}
+var (
+	// When a flag declaration has an initial value that is a string, the default workflow is PREBUILT.
+	// If the flag name starts with any of prefixes in manualFlagNamePrefixes, it is MANUAL.
+	manualFlagNamePrefixes []string = []string{
+		"RELEASE_ACONFIG_",
+		"RELEASE_PLATFORM_",
+		"RELEASE_BUILD_FLAGS_",
+	}
 
-var defaultFlagNamespace string = "android_UNKNOWN"
+	// Set `aconfig_flags_only: true` in these release configs.
+	aconfigFlagsOnlyConfigs map[string]bool = map[string]bool{
+		"trunk_food": true,
+	}
+
+	// Default namespace value.  This is intentionally invalid.
+	defaultFlagNamespace string = "android_UNKNOWN"
+
+	// What is the current name for "next".
+	nextName string = "ap3a"
+)
 
 func RenameNext(name string) string {
 	if name == "next" {
-		return "ap3a"
+		return nextName
 	}
 	return name
 }
@@ -102,10 +114,13 @@
 			description = ""
 			continue
 		}
-		declValue := matches[declRegexp.SubexpIndex("value")]
 		declName := matches[declRegexp.SubexpIndex("name")]
-		container := rc_proto.Container(rc_proto.Container_value[matches[declRegexp.SubexpIndex("container")]])
+		declValue := matches[declRegexp.SubexpIndex("value")]
 		description = strings.TrimSpace(description)
+		containers := []string{strings.ToLower(matches[declRegexp.SubexpIndex("container")])}
+		if containers[0] == "all" {
+			containers = []string{"product", "system", "system_ext", "vendor"}
+		}
 		var namespace string
 		var ok bool
 		if namespace, ok = namespaceMap[declName]; !ok {
@@ -115,14 +130,16 @@
 			Name:        proto.String(declName),
 			Namespace:   proto.String(namespace),
 			Description: proto.String(description),
-			Container:   &container,
+			Containers:  containers,
 		}
 		description = ""
 		// Most build flags are `workflow: PREBUILT`.
 		workflow := rc_proto.Workflow(rc_proto.Workflow_PREBUILT)
 		switch {
 		case declName == "RELEASE_ACONFIG_VALUE_SETS":
-			rootAconfigModule = declValue[1 : len(declValue)-1]
+			if strings.HasPrefix(declValue, "\"") {
+				rootAconfigModule = declValue[1 : len(declValue)-1]
+			}
 			continue
 		case strings.HasPrefix(declValue, "\""):
 			// String values mean that the flag workflow is (most likely) either MANUAL or PREBUILT.
@@ -202,6 +219,9 @@
 				fmt.Printf("%s: Unexpected value %s=%s\n", path, valName, valValue)
 			}
 			if flagValue != nil {
+				if releaseProto.GetAconfigFlagsOnly() {
+					return fmt.Errorf("%s does not allow build flag overrides", RenameNext(name))
+				}
 				valPath := filepath.Join(dir, "flag_values", RenameNext(name), fmt.Sprintf("%s.textproto", valName))
 				err := WriteFile(valPath, flagValue)
 				if err != nil {
@@ -213,6 +233,12 @@
 	return err
 }
 
+var (
+	allContainers = func() []string {
+		return []string{"product", "system", "system_ext", "vendor"}
+	}()
+)
+
 func ProcessReleaseConfigMap(dir string, descriptionMap map[string]string) error {
 	path := filepath.Join(dir, "release_config_map.mk")
 	if _, err := os.Stat(path); err != nil {
@@ -235,16 +261,16 @@
 		return err
 	}
 	cleanDir := strings.TrimLeft(dir, "../")
-	var defaultContainer rc_proto.Container
+	var defaultContainers []string
 	switch {
 	case strings.HasPrefix(cleanDir, "build/") || cleanDir == "vendor/google_shared/build":
-		defaultContainer = rc_proto.Container(rc_proto.Container_ALL)
+		defaultContainers = allContainers
 	case cleanDir == "vendor/google/release":
-		defaultContainer = rc_proto.Container(rc_proto.Container_ALL)
+		defaultContainers = allContainers
 	default:
-		defaultContainer = rc_proto.Container(rc_proto.Container_VENDOR)
+		defaultContainers = []string{"vendor"}
 	}
-	releaseConfigMap := &rc_proto.ReleaseConfigMap{DefaultContainer: &defaultContainer}
+	releaseConfigMap := &rc_proto.ReleaseConfigMap{DefaultContainers: defaultContainers}
 	// If we find a description for the directory, include it.
 	if description, ok := descriptionMap[cleanDir]; ok {
 		releaseConfigMap.Description = proto.String(description)
@@ -276,6 +302,9 @@
 		releaseConfig := &rc_proto.ReleaseConfig{
 			Name: proto.String(RenameNext(name)),
 		}
+		if aconfigFlagsOnlyConfigs[name] {
+			releaseConfig.AconfigFlagsOnly = proto.Bool(true)
+		}
 		configFiles := config[configRegexp.SubexpIndex("files")]
 		files := strings.Split(strings.ReplaceAll(configFiles, "$(local_dir)", dir+"/"), " ")
 		configInherits := config[configRegexp.SubexpIndex("inherits")]
@@ -302,15 +331,26 @@
 	var dirs rc_lib.StringList
 	var namespacesFile string
 	var descriptionsFile string
+	var debug bool
+	defaultTopDir, err := rc_lib.GetTopDir()
 
-	flag.StringVar(&top, "top", ".", "path to top of workspace")
+	flag.StringVar(&top, "top", defaultTopDir, "path to top of workspace")
 	flag.Var(&dirs, "dir", "directory to process, relative to the top of the workspace")
 	flag.StringVar(&namespacesFile, "namespaces", "", "location of file with 'flag_name namespace' information")
 	flag.StringVar(&descriptionsFile, "descriptions", "", "location of file with 'directory description' information")
+	flag.BoolVar(&debug, "debug", false, "turn on debugging output for errors")
 	flag.Parse()
 
+	errorExit := func(err error) {
+		if debug {
+			panic(err)
+		}
+		fmt.Fprintf(os.Stderr, "%s\n", err)
+		os.Exit(1)
+	}
+
 	if err = os.Chdir(top); err != nil {
-		panic(err)
+		errorExit(err)
 	}
 	if len(dirs) == 0 {
 		dirs = rc_lib.StringList{"build/release", "vendor/google_shared/build/release", "vendor/google/release"}
@@ -320,12 +360,12 @@
 	if namespacesFile != "" {
 		data, err := os.ReadFile(namespacesFile)
 		if err != nil {
-			panic(err)
+			errorExit(err)
 		}
 		for idx, line := range strings.Split(string(data), "\n") {
 			fields := strings.Split(line, " ")
 			if len(fields) > 2 {
-				panic(fmt.Errorf("line %d: too many fields: %s", idx, line))
+				errorExit(fmt.Errorf("line %d: too many fields: %s", idx, line))
 			}
 			namespaceMap[fields[0]] = fields[1]
 		}
@@ -337,7 +377,7 @@
 	if descriptionsFile != "" {
 		data, err := os.ReadFile(descriptionsFile)
 		if err != nil {
-			panic(err)
+			errorExit(err)
 		}
 		for _, line := range strings.Split(string(data), "\n") {
 			if strings.TrimSpace(line) != "" {
@@ -351,12 +391,12 @@
 	for _, dir := range dirs {
 		err = ProcessBuildFlags(dir, namespaceMap)
 		if err != nil {
-			panic(err)
+			errorExit(err)
 		}
 
 		err = ProcessReleaseConfigMap(dir, descriptionMap)
 		if err != nil {
-			panic(err)
+			errorExit(err)
 		}
 	}
 }
diff --git a/cmd/release_config/release_config/main.go b/cmd/release_config/release_config/main.go
index 22e72a5..d06b2b7 100644
--- a/cmd/release_config/release_config/main.go
+++ b/cmd/release_config/release_config/main.go
@@ -31,9 +31,11 @@
 	var outputDir string
 	var err error
 	var configs *rc_lib.ReleaseConfigs
-	var json, pb, textproto bool
+	var json, pb, textproto, inheritance bool
 	var product string
 	var allMake bool
+	var useBuildVar, allowMissing bool
+	var guard bool
 
 	defaultRelease := os.Getenv("TARGET_RELEASE")
 	if defaultRelease == "" {
@@ -45,11 +47,16 @@
 	flag.BoolVar(&quiet, "quiet", false, "disable warning messages")
 	flag.Var(&releaseConfigMapPaths, "map", "path to a release_config_map.textproto. may be repeated")
 	flag.StringVar(&targetRelease, "release", defaultRelease, "TARGET_RELEASE for this build")
+	flag.BoolVar(&allowMissing, "allow-missing", false, "Use trunk_staging values if release not found")
 	flag.StringVar(&outputDir, "out_dir", rc_lib.GetDefaultOutDir(), "basepath for the output. Multiple formats are created")
 	flag.BoolVar(&textproto, "textproto", true, "write artifacts as text protobuf")
 	flag.BoolVar(&json, "json", true, "write artifacts as json")
 	flag.BoolVar(&pb, "pb", true, "write artifacts as binary protobuf")
-	flag.BoolVar(&allMake, "all_make", true, "write makefiles for all release configs")
+	flag.BoolVar(&allMake, "all_make", false, "write makefiles for all release configs")
+	flag.BoolVar(&inheritance, "inheritance", true, "write inheritance graph")
+	flag.BoolVar(&useBuildVar, "use_get_build_var", false, "use get_build_var PRODUCT_RELEASE_CONFIG_MAPS")
+	flag.BoolVar(&guard, "guard", true, "whether to guard with RELEASE_BUILD_FLAGS_IN_PROTOBUF")
+
 	flag.Parse()
 
 	if quiet {
@@ -59,7 +66,7 @@
 	if err = os.Chdir(top); err != nil {
 		panic(err)
 	}
-	configs, err = rc_lib.ReadReleaseConfigMaps(releaseConfigMapPaths, targetRelease)
+	configs, err = rc_lib.ReadReleaseConfigMaps(releaseConfigMapPaths, targetRelease, useBuildVar, allowMissing)
 	if err != nil {
 		panic(err)
 	}
@@ -67,27 +74,39 @@
 	if err != nil {
 		panic(err)
 	}
-	releaseName := config.Name
 	err = os.MkdirAll(outputDir, 0775)
 	if err != nil {
 		panic(err)
 	}
 
-	if err = config.WritePartitionBuildFlags(outputDir, product, targetRelease); err != nil {
+	makefilePath := filepath.Join(outputDir, fmt.Sprintf("release_config-%s-%s.varmk", product, targetRelease))
+	useProto, ok := config.FlagArtifacts["RELEASE_BUILD_FLAGS_IN_PROTOBUF"]
+	if guard && (!ok || rc_lib.MarshalValue(useProto.Value) == "") {
+		// We were told to guard operation and either we have no build flag, or it is False.
+		// Write an empty file so that release_config.mk will use the old process.
+		os.WriteFile(makefilePath, []byte{}, 0644)
+		return
+	}
+	// Write the makefile where release_config.mk is going to look for it.
+	err = config.WriteMakefile(makefilePath, targetRelease, configs)
+	if err != nil {
 		panic(err)
 	}
-
 	if allMake {
-		for k, _ := range configs.ReleaseConfigs {
-			makefilePath := filepath.Join(outputDir, fmt.Sprintf("release_config-%s-%s.mk", product, k))
-			err = configs.WriteMakefile(makefilePath, k)
-			if err != nil {
-				panic(err)
+		// Write one makefile per release config, using the canonical release name.
+		for _, c := range configs.GetSortedReleaseConfigs() {
+			if c.Name != targetRelease {
+				makefilePath = filepath.Join(outputDir, fmt.Sprintf("release_config-%s-%s.varmk", product, c.Name))
+				err = config.WriteMakefile(makefilePath, c.Name, configs)
+				if err != nil {
+					panic(err)
+				}
 			}
 		}
-	} else {
-		makefilePath := filepath.Join(outputDir, fmt.Sprintf("release_config-%s-%s.mk", product, releaseName))
-		err = configs.WriteMakefile(makefilePath, targetRelease)
+	}
+	if inheritance {
+		inheritPath := filepath.Join(outputDir, fmt.Sprintf("inheritance_graph-%s.dot", product))
+		err = configs.WriteInheritanceGraph(inheritPath)
 		if err != nil {
 			panic(err)
 		}
@@ -110,4 +129,8 @@
 			panic(err)
 		}
 	}
+	if err = config.WritePartitionBuildFlags(outputDir); err != nil {
+		panic(err)
+	}
+
 }
diff --git a/cmd/release_config/release_config_lib/Android.bp b/cmd/release_config/release_config_lib/Android.bp
index 0c67e11..17251bd 100644
--- a/cmd/release_config/release_config_lib/Android.bp
+++ b/cmd/release_config/release_config_lib/Android.bp
@@ -24,6 +24,7 @@
         "golang-protobuf-reflect-protoreflect",
         "golang-protobuf-runtime-protoimpl",
         "soong-cmd-release_config-proto",
+        "blueprint-pathtools",
     ],
     srcs: [
         "flag_artifact.go",
diff --git a/cmd/release_config/release_config_lib/flag_artifact.go b/cmd/release_config/release_config_lib/flag_artifact.go
index cba1b5c..93c50cd 100644
--- a/cmd/release_config/release_config_lib/flag_artifact.go
+++ b/cmd/release_config/release_config_lib/flag_artifact.go
@@ -15,7 +15,9 @@
 package release_config_lib
 
 import (
+	"cmp"
 	"fmt"
+	"slices"
 
 	rc_proto "android/soong/cmd/release_config/release_config_proto"
 
@@ -45,6 +47,94 @@
 // Key is flag name.
 type FlagArtifacts map[string]*FlagArtifact
 
+func FlagArtifactFactory(declPath string) *FlagArtifact {
+	fd := &rc_proto.FlagDeclaration{}
+	fa := &FlagArtifact{
+		FlagDeclaration:  fd,
+		DeclarationIndex: -1,
+		Traces:           []*rc_proto.Tracepoint{},
+	}
+	if declPath != "" {
+		LoadMessage(declPath, fd)
+		fa.Value = fd.GetValue()
+		fa.Traces = append(fa.Traces, &rc_proto.Tracepoint{Source: proto.String(declPath), Value: fa.Value})
+	}
+	return fa
+}
+
+func FlagArtifactsFactory(artifactsPath string) *FlagArtifacts {
+	ret := make(FlagArtifacts)
+	if artifactsPath != "" {
+		fas := &rc_proto.FlagArtifacts{}
+		LoadMessage(artifactsPath, fas)
+		for _, fa_pb := range fas.Flags {
+			fa := &FlagArtifact{}
+			fa.FlagDeclaration = fa_pb.GetFlagDeclaration()
+			if val := fa_pb.GetValue(); val != nil {
+				fa.Value = val
+			}
+			if traces := fa_pb.GetTraces(); traces != nil {
+				fa.Traces = traces
+			}
+			ret[*fa.FlagDeclaration.Name] = fa
+		}
+	}
+	return &ret
+}
+
+func (fas *FlagArtifacts) SortedFlagNames() []string {
+	var names []string
+	for k, _ := range *fas {
+		names = append(names, k)
+	}
+	slices.Sort(names)
+	return names
+}
+
+func (fa *FlagArtifact) GenerateFlagDeclarationArtifact() *rc_proto.FlagDeclarationArtifact {
+	ret := &rc_proto.FlagDeclarationArtifact{
+		Name:            fa.FlagDeclaration.Name,
+		DeclarationPath: fa.Traces[0].Source,
+	}
+	if namespace := fa.FlagDeclaration.GetNamespace(); namespace != "" {
+		ret.Namespace = proto.String(namespace)
+	}
+	if description := fa.FlagDeclaration.GetDescription(); description != "" {
+		ret.Description = proto.String(description)
+	}
+	if workflow := fa.FlagDeclaration.GetWorkflow(); workflow != rc_proto.Workflow_Workflow_Unspecified {
+		ret.Workflow = &workflow
+	}
+	if containers := fa.FlagDeclaration.GetContainers(); containers != nil {
+		ret.Containers = containers
+	}
+	return ret
+}
+
+func FlagDeclarationArtifactsFactory(path string) *rc_proto.FlagDeclarationArtifacts {
+	ret := &rc_proto.FlagDeclarationArtifacts{}
+	if path != "" {
+		LoadMessage(path, ret)
+	} else {
+		ret.FlagDeclarationArtifactList = []*rc_proto.FlagDeclarationArtifact{}
+	}
+	return ret
+}
+
+func (fas *FlagArtifacts) GenerateFlagDeclarationArtifacts(intermediates []*rc_proto.FlagDeclarationArtifacts) *rc_proto.FlagDeclarationArtifacts {
+	ret := &rc_proto.FlagDeclarationArtifacts{FlagDeclarationArtifactList: []*rc_proto.FlagDeclarationArtifact{}}
+	for _, fa := range *fas {
+		ret.FlagDeclarationArtifactList = append(ret.FlagDeclarationArtifactList, fa.GenerateFlagDeclarationArtifact())
+	}
+	for _, fda := range intermediates {
+		ret.FlagDeclarationArtifactList = append(ret.FlagDeclarationArtifactList, fda.FlagDeclarationArtifactList...)
+	}
+	slices.SortFunc(ret.FlagDeclarationArtifactList, func(a, b *rc_proto.FlagDeclarationArtifact) int {
+		return cmp.Compare(*a.Name, *b.Name)
+	})
+	return ret
+}
+
 // Create a clone of the flag artifact.
 //
 // Returns:
@@ -54,9 +144,11 @@
 	value := &rc_proto.Value{}
 	proto.Merge(value, src.Value)
 	return &FlagArtifact{
-		FlagDeclaration: src.FlagDeclaration,
-		Traces:          src.Traces,
-		Value:           value,
+		FlagDeclaration:  src.FlagDeclaration,
+		Traces:           src.Traces,
+		Value:            value,
+		DeclarationIndex: src.DeclarationIndex,
+		Redacted:         src.Redacted,
 	}
 }
 
diff --git a/cmd/release_config/release_config_lib/flag_declaration.go b/cmd/release_config/release_config_lib/flag_declaration.go
index 97d4d4c..22001bf 100644
--- a/cmd/release_config/release_config_lib/flag_declaration.go
+++ b/cmd/release_config/release_config_lib/flag_declaration.go
@@ -18,10 +18,21 @@
 	rc_proto "android/soong/cmd/release_config/release_config_proto"
 )
 
+var (
+	// Allowlist: these flags may have duplicate (identical) declarations
+	// without generating an error.  This will be removed once all such
+	// declarations have been fixed.
+	DuplicateDeclarationAllowlist = map[string]bool{}
+)
+
 func FlagDeclarationFactory(protoPath string) (fd *rc_proto.FlagDeclaration) {
 	fd = &rc_proto.FlagDeclaration{}
 	if protoPath != "" {
 		LoadMessage(protoPath, fd)
 	}
+	// If the input didn't specify a value, create one (== UnspecifiedValue).
+	if fd.Value == nil {
+		fd.Value = &rc_proto.Value{Val: &rc_proto.Value_UnspecifiedValue{false}}
+	}
 	return fd
 }
diff --git a/cmd/release_config/release_config_lib/flag_value.go b/cmd/release_config/release_config_lib/flag_value.go
index e155e77..76363ce 100644
--- a/cmd/release_config/release_config_lib/flag_value.go
+++ b/cmd/release_config/release_config_lib/flag_value.go
@@ -52,6 +52,9 @@
 }
 
 func MarshalValue(value *rc_proto.Value) string {
+	if value == nil {
+		return ""
+	}
 	switch val := value.Val.(type) {
 	case *rc_proto.Value_UnspecifiedValue:
 		// Value was never set.
@@ -71,3 +74,22 @@
 		return ""
 	}
 }
+
+// Returns a string representation of the type of the value for make
+func ValueType(value *rc_proto.Value) string {
+	if value == nil || value.Val == nil {
+		return "unspecified"
+	}
+	switch value.Val.(type) {
+	case *rc_proto.Value_UnspecifiedValue:
+		return "unspecified"
+	case *rc_proto.Value_StringValue:
+		return "string"
+	case *rc_proto.Value_BoolValue:
+		return "bool"
+	case *rc_proto.Value_Obsolete:
+		return "obsolete"
+	default:
+		panic("Unhandled type")
+	}
+}
diff --git a/cmd/release_config/release_config_lib/flag_value_test.go b/cmd/release_config/release_config_lib/flag_value_test.go
index aaa4caf..8a98baf 100644
--- a/cmd/release_config/release_config_lib/flag_value_test.go
+++ b/cmd/release_config/release_config_lib/flag_value_test.go
@@ -24,7 +24,7 @@
 	"google.golang.org/protobuf/proto"
 )
 
-type testCaseFlagValue struct {
+type testCaseFlagValueFactory struct {
 	protoPath string
 	name      string
 	data      []byte
@@ -32,14 +32,14 @@
 	err       error
 }
 
-func (tc testCaseFlagValue) assertProtoEqual(t *testing.T, expected, actual proto.Message) {
+func (tc testCaseFlagValueFactory) assertProtoEqual(t *testing.T, expected, actual proto.Message) {
 	if !proto.Equal(expected, actual) {
 		t.Errorf("Expected %q found %q", expected, actual)
 	}
 }
 
-func TestFlagValue(t *testing.T) {
-	testCases := []testCaseFlagValue{
+func TestFlagValueFactory(t *testing.T) {
+	testCases := []testCaseFlagValueFactory{
 		{
 			name:      "stringVal",
 			protoPath: "build/release/flag_values/test/RELEASE_FOO.textproto",
@@ -65,3 +65,50 @@
 		tc.assertProtoEqual(t, &tc.expected, &actual.proto)
 	}
 }
+
+type testCaseMarshalValue struct {
+	name     string
+	value    *rc_proto.Value
+	expected string
+}
+
+func TestMarshalValue(t *testing.T) {
+	testCases := []testCaseMarshalValue{
+		{
+			name:     "nil",
+			value:    nil,
+			expected: "",
+		},
+		{
+			name:     "unspecified",
+			value:    &rc_proto.Value{},
+			expected: "",
+		},
+		{
+			name:     "false",
+			value:    &rc_proto.Value{Val: &rc_proto.Value_BoolValue{false}},
+			expected: "",
+		},
+		{
+			name:     "true",
+			value:    &rc_proto.Value{Val: &rc_proto.Value_BoolValue{true}},
+			expected: "true",
+		},
+		{
+			name:     "string",
+			value:    &rc_proto.Value{Val: &rc_proto.Value_StringValue{"BAR"}},
+			expected: "BAR",
+		},
+		{
+			name:     "obsolete",
+			value:    &rc_proto.Value{Val: &rc_proto.Value_Obsolete{true}},
+			expected: " #OBSOLETE",
+		},
+	}
+	for _, tc := range testCases {
+		actual := MarshalValue(tc.value)
+		if actual != tc.expected {
+			t.Errorf("Expected %q found %q", tc.expected, actual)
+		}
+	}
+}
diff --git a/cmd/release_config/release_config_lib/release_config.go b/cmd/release_config/release_config_lib/release_config.go
index a7a05ae..adf0e62 100644
--- a/cmd/release_config/release_config_lib/release_config.go
+++ b/cmd/release_config/release_config_lib/release_config.go
@@ -17,7 +17,9 @@
 import (
 	"cmp"
 	"fmt"
+	"os"
 	"path/filepath"
+	"regexp"
 	"slices"
 	"sort"
 	"strings"
@@ -29,7 +31,7 @@
 
 // One directory's contribution to the a release config.
 type ReleaseConfigContribution struct {
-	// Paths to files providing this config.
+	// Path of the file providing this config contribution.
 	path string
 
 	// The index of the config directory where this release config
@@ -62,9 +64,16 @@
 	// The names of release configs that we inherit
 	InheritNames []string
 
+	// True if this release config only allows inheritance and aconfig flag
+	// overrides. Build flag value overrides are an error.
+	AconfigFlagsOnly bool
+
 	// Unmarshalled flag artifacts
 	FlagArtifacts FlagArtifacts
 
+	// The files used by this release config
+	FilesUsedMap map[string]bool
+
 	// Generated release config
 	ReleaseConfigArtifact *rc_proto.ReleaseConfigArtifact
 
@@ -73,20 +82,39 @@
 
 	// Partitioned artifacts for {partition}/etc/build_flags.json
 	PartitionBuildFlags map[string]*rc_proto.FlagArtifacts
+
+	// 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
 }
 
 func ReleaseConfigFactory(name string, index int) (c *ReleaseConfig) {
-	return &ReleaseConfig{Name: name, DeclarationIndex: index}
+	return &ReleaseConfig{
+		Name:             name,
+		DeclarationIndex: index,
+		FilesUsedMap:     make(map[string]bool),
+		PriorStagesMap:   make(map[string]bool),
+	}
 }
 
 func (config *ReleaseConfig) InheritConfig(iConfig *ReleaseConfig) error {
+	for f := range iConfig.FilesUsedMap {
+		config.FilesUsedMap[f] = true
+	}
 	for _, fa := range iConfig.FlagArtifacts {
 		name := *fa.FlagDeclaration.Name
 		myFa, ok := config.FlagArtifacts[name]
 		if !ok {
 			return fmt.Errorf("Could not inherit flag %s from %s", name, iConfig.Name)
 		}
-		if len(fa.Traces) > 1 {
+		if name == "RELEASE_ACONFIG_VALUE_SETS" {
+			// If there is a value assigned, add the trace.
+			if len(fa.Value.GetStringValue()) > 0 {
+				myFa.Traces = append(myFa.Traces, fa.Traces...)
+				myFa.Value = &rc_proto.Value{Val: &rc_proto.Value_StringValue{
+					myFa.Value.GetStringValue() + " " + fa.Value.GetStringValue()}}
+			}
+		} else if len(fa.Traces) > 1 {
 			// A value was assigned. Set our value.
 			myFa.Traces = append(myFa.Traces, fa.Traces[1:]...)
 			myFa.Value = fa.Value
@@ -95,6 +123,10 @@
 	return nil
 }
 
+func (config *ReleaseConfig) GetSortedFileList() []string {
+	return SortedMapKeys(config.FilesUsedMap)
+}
+
 func (config *ReleaseConfig) GenerateReleaseConfig(configs *ReleaseConfigs) error {
 	if config.ReleaseConfigArtifact != nil {
 		return nil
@@ -105,29 +137,15 @@
 	config.compileInProgress = true
 	isRoot := config.Name == "root"
 
+	// Is this a build-prefix release config, such as 'ap3a'?
+	isBuildPrefix, err := regexp.MatchString("^[a-z][a-z][0-9][0-9a-z]$", config.Name)
+	if err != nil {
+		return err
+	}
 	// Start with only the flag declarations.
 	config.FlagArtifacts = configs.FlagArtifacts.Clone()
-	// Add RELEASE_ACONFIG_VALUE_SETS
-	workflowManual := rc_proto.Workflow(rc_proto.Workflow_MANUAL)
-	container := rc_proto.Container(rc_proto.Container_ALL)
-	releaseAconfigValueSets := FlagArtifact{
-		FlagDeclaration: &rc_proto.FlagDeclaration{
-			Name:        proto.String("RELEASE_ACONFIG_VALUE_SETS"),
-			Namespace:   proto.String("android_UNKNOWN"),
-			Description: proto.String("Aconfig value sets assembled by release-config"),
-			Workflow:    &workflowManual,
-			Container:   &container,
-			Value:       &rc_proto.Value{Val: &rc_proto.Value_StringValue{""}},
-		},
-		DeclarationIndex: -1,
-		Traces: []*rc_proto.Tracepoint{
-			&rc_proto.Tracepoint{
-				Source: proto.String("$release-config"),
-				Value:  &rc_proto.Value{Val: &rc_proto.Value_StringValue{""}},
-			},
-		},
-	}
-	config.FlagArtifacts["RELEASE_ACONFIG_VALUE_SETS"] = &releaseAconfigValueSets
+	releaseAconfigValueSets := config.FlagArtifacts["RELEASE_ACONFIG_VALUE_SETS"]
+	releasePlatformVersion := config.FlagArtifacts["RELEASE_PLATFORM_VERSION"]
 
 	// Generate any configs we need to inherit.  This will detect loops in
 	// the config.
@@ -135,7 +153,7 @@
 	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")
+	_, err = configs.GetReleaseConfig("root")
 	if err == nil && !isRoot {
 		config.InheritNames = append([]string{"root"}, config.InheritNames...)
 	}
@@ -143,53 +161,98 @@
 		if _, ok := myInheritsSet[inherit]; ok {
 			continue
 		}
+		if isBuildPrefix && configs.Aliases[inherit] != nil {
+			return fmt.Errorf("%s cannot inherit from alias %s", config.Name, inherit)
+		}
 		myInherits = append(myInherits, inherit)
 		myInheritsSet[inherit] = true
 		iConfig, err := configs.GetReleaseConfig(inherit)
 		if err != nil {
 			return err
 		}
-		iConfig.GenerateReleaseConfig(configs)
-		if err := config.InheritConfig(iConfig); err != nil {
+		err = iConfig.GenerateReleaseConfig(configs)
+		if err != nil {
+			return err
+		}
+		err = config.InheritConfig(iConfig)
+		if err != nil {
 			return err
 		}
 	}
+
+	// If we inherited nothing, then we need to mark the global files as used for this
+	// config.  If we inherited, then we already marked them as part of inheritance.
+	if len(config.InheritNames) == 0 {
+		for f := range configs.FilesUsedMap {
+			config.FilesUsedMap[f] = true
+		}
+	}
+
 	contributionsToApply = append(contributionsToApply, config.Contributions...)
 
-	myAconfigValueSets := strings.Split(releaseAconfigValueSets.Value.GetStringValue(), " ")
-	myAconfigValueSetsMap := map[string]bool{}
-	for _, v := range myAconfigValueSets {
-		myAconfigValueSetsMap[v] = true
-	}
+	workflowManual := rc_proto.Workflow(rc_proto.Workflow_MANUAL)
 	myDirsMap := make(map[int]bool)
-	for _, contrib := range contributionsToApply {
-		if len(contrib.proto.AconfigValueSets) > 0 {
-			contribAconfigValueSets := []string{}
-			for _, v := range contrib.proto.AconfigValueSets {
-				if _, ok := myAconfigValueSetsMap[v]; !ok {
-					contribAconfigValueSets = append(contribAconfigValueSets, v)
-					myAconfigValueSetsMap[v] = true
-				}
+	myValueDirsMap := make(map[int]bool)
+	if isBuildPrefix && releasePlatformVersion != nil {
+		if MarshalValue(releasePlatformVersion.Value) != strings.ToUpper(config.Name) {
+			value := FlagValue{
+				path: config.Contributions[0].path,
+				proto: rc_proto.FlagValue{
+					Name:  releasePlatformVersion.FlagDeclaration.Name,
+					Value: UnmarshalValue(strings.ToUpper(config.Name)),
+				},
 			}
-			myAconfigValueSets = append(myAconfigValueSets, contribAconfigValueSets...)
-			releaseAconfigValueSets.Traces = append(
-				releaseAconfigValueSets.Traces,
-				&rc_proto.Tracepoint{
-					Source: proto.String(contrib.path),
-					Value:  &rc_proto.Value{Val: &rc_proto.Value_StringValue{strings.Join(contribAconfigValueSets, " ")}},
-				})
+			if err := releasePlatformVersion.UpdateValue(value); err != nil {
+				return err
+			}
+		}
+	}
+	for _, contrib := range contributionsToApply {
+		contribAconfigValueSets := []string{}
+		// Gather the aconfig_value_sets from this contribution, allowing duplicates for simplicity.
+		for _, v := range contrib.proto.AconfigValueSets {
+			contribAconfigValueSets = append(contribAconfigValueSets, v)
+		}
+		contribAconfigValueSetsString := strings.Join(contribAconfigValueSets, " ")
+		releaseAconfigValueSets.Value = &rc_proto.Value{Val: &rc_proto.Value_StringValue{
+			releaseAconfigValueSets.Value.GetStringValue() + " " + contribAconfigValueSetsString}}
+		releaseAconfigValueSets.Traces = append(
+			releaseAconfigValueSets.Traces,
+			&rc_proto.Tracepoint{
+				Source: proto.String(contrib.path),
+				Value:  &rc_proto.Value{Val: &rc_proto.Value_StringValue{contribAconfigValueSetsString}},
+			})
+
+		for _, priorStage := range contrib.proto.PriorStages {
+			config.PriorStagesMap[priorStage] = true
 		}
 		myDirsMap[contrib.DeclarationIndex] = true
+		// This path *could* provide a value for this release config.
+		myValueDirsMap[contrib.DeclarationIndex] = true
+		if config.AconfigFlagsOnly {
+			// AconfigFlagsOnly allows very very few build flag values, all of them are part of aconfig flags.
+			allowedFlags := map[string]bool{
+				"RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS": true,
+			}
+			for _, fv := range contrib.FlagValues {
+				if !allowedFlags[*fv.proto.Name] {
+					return fmt.Errorf("%s does not allow build flag overrides", config.Name)
+				}
+			}
+		}
 		for _, value := range contrib.FlagValues {
 			name := *value.proto.Name
 			fa, ok := config.FlagArtifacts[name]
 			if !ok {
 				return fmt.Errorf("Setting value for undefined flag %s in %s\n", name, value.path)
 			}
+			// Record that flag declarations from fa.DeclarationIndex were included in this release config.
 			myDirsMap[fa.DeclarationIndex] = true
+			// Do not set myValueDirsMap, since it just records that we *could* provide values here.
 			if fa.DeclarationIndex > contrib.DeclarationIndex {
 				// Setting location is to the left of declaration.
-				return fmt.Errorf("Setting value for flag %s not allowed in %s\n", name, value.path)
+				return fmt.Errorf("Setting value for flag %s (declared in %s) not allowed in %s\n",
+					name, filepath.Dir(configs.ReleaseConfigMaps[fa.DeclarationIndex].path), value.path)
 			}
 			if isRoot && *fa.FlagDeclaration.Workflow != workflowManual {
 				// The "root" release config can only contain workflow: MANUAL flags.
@@ -203,47 +266,47 @@
 			}
 		}
 	}
+	// Now remove any duplicates from the actual value of RELEASE_ACONFIG_VALUE_SETS
+	myAconfigValueSets := []string{}
+	myAconfigValueSetsMap := map[string]bool{}
+	for _, v := range strings.Split(releaseAconfigValueSets.Value.GetStringValue(), " ") {
+		if v == "" || myAconfigValueSetsMap[v] {
+			continue
+		}
+		myAconfigValueSetsMap[v] = true
+		myAconfigValueSets = append(myAconfigValueSets, v)
+	}
 	releaseAconfigValueSets.Value = &rc_proto.Value{Val: &rc_proto.Value_StringValue{strings.TrimSpace(strings.Join(myAconfigValueSets, " "))}}
 
 	directories := []string{}
+	valueDirectories := []string{}
 	for idx, confDir := range configs.configDirs {
 		if _, ok := myDirsMap[idx]; ok {
 			directories = append(directories, confDir)
 		}
+		if _, ok := myValueDirsMap[idx]; ok {
+			valueDirectories = append(valueDirectories, confDir)
+		}
 	}
 
 	// Now build the per-partition artifacts
 	config.PartitionBuildFlags = make(map[string]*rc_proto.FlagArtifacts)
-	addPartitionArtifact := func(container string, artifact *rc_proto.FlagArtifact) {
-		if _, ok := config.PartitionBuildFlags[container]; !ok {
-			config.PartitionBuildFlags[container] = &rc_proto.FlagArtifacts{}
-		}
-		config.PartitionBuildFlags[container].FlagArtifacts = append(config.PartitionBuildFlags[container].FlagArtifacts, artifact)
-	}
 	for _, v := range config.FlagArtifacts {
-		container := strings.ToLower(rc_proto.Container_name[int32(v.FlagDeclaration.GetContainer())])
 		artifact, err := v.MarshalWithoutTraces()
 		if err != nil {
 			return err
 		}
-		switch container {
-		case "all":
-			for cVal, cName := range rc_proto.Container_name {
-				// Skip unspecified, and "ALL", but place the flag in the rest.
-				if cVal == 0 || cName == "ALL" {
-					continue
-				}
-				cName = strings.ToLower(cName)
-				addPartitionArtifact(cName, artifact)
+		for _, container := range v.FlagDeclaration.Containers {
+			if _, ok := config.PartitionBuildFlags[container]; !ok {
+				config.PartitionBuildFlags[container] = &rc_proto.FlagArtifacts{}
 			}
-		default:
-			addPartitionArtifact(container, artifact)
+			config.PartitionBuildFlags[container].Flags = append(config.PartitionBuildFlags[container].Flags, artifact)
 		}
 	}
 	config.ReleaseConfigArtifact = &rc_proto.ReleaseConfigArtifact{
 		Name:       proto.String(config.Name),
 		OtherNames: config.OtherNames,
-		FlagArtifacts: func() []*rc_proto.FlagArtifact {
+		Flags: func() []*rc_proto.FlagArtifact {
 			ret := []*rc_proto.FlagArtifact{}
 			flagNames := []string{}
 			for k := range config.FlagArtifacts {
@@ -263,19 +326,108 @@
 		AconfigValueSets: myAconfigValueSets,
 		Inherits:         myInherits,
 		Directories:      directories,
+		ValueDirectories: valueDirectories,
+		PriorStages:      SortedMapKeys(config.PriorStagesMap),
 	}
 
 	config.compileInProgress = false
 	return nil
 }
 
-func (config *ReleaseConfig) WritePartitionBuildFlags(outDir, product, targetRelease string) error {
+// Write the makefile for this targetRelease.
+func (config *ReleaseConfig) WriteMakefile(outFile, targetRelease string, configs *ReleaseConfigs) error {
+	makeVars := make(map[string]string)
+
+	myFlagArtifacts := config.FlagArtifacts.Clone()
+
+	// Add any RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS variables.
+	var extraAconfigReleaseConfigs []string
+	if extraAconfigValueSetsValue, ok := config.FlagArtifacts["RELEASE_ACONFIG_EXTRA_RELEASE_CONFIGS"]; ok {
+		if val := MarshalValue(extraAconfigValueSetsValue.Value); len(val) > 0 {
+			extraAconfigReleaseConfigs = strings.Split(val, " ")
+		}
+	}
+	for _, rcName := range extraAconfigReleaseConfigs {
+		rc, err := configs.GetReleaseConfig(rcName)
+		if err != nil {
+			return err
+		}
+		myFlagArtifacts["RELEASE_ACONFIG_VALUE_SETS_"+rcName] = rc.FlagArtifacts["RELEASE_ACONFIG_VALUE_SETS"]
+		myFlagArtifacts["RELEASE_ACONFIG_FLAG_DEFAULT_PERMISSION_"+rcName] = rc.FlagArtifacts["RELEASE_ACONFIG_FLAG_DEFAULT_PERMISSION"]
+	}
+
+	// Sort the flags by name first.
+	names := myFlagArtifacts.SortedFlagNames()
+	partitions := make(map[string][]string)
+
+	vNames := []string{}
+	addVar := func(name, suffix, value string) {
+		fullName := fmt.Sprintf("_ALL_RELEASE_FLAGS.%s.%s", name, suffix)
+		vNames = append(vNames, fullName)
+		makeVars[fullName] = value
+	}
+
+	for _, name := range names {
+		flag := myFlagArtifacts[name]
+		decl := flag.FlagDeclaration
+
+		for _, container := range decl.Containers {
+			partitions[container] = append(partitions[container], name)
+		}
+		value := MarshalValue(flag.Value)
+		makeVars[name] = value
+		addVar(name, "TYPE", ValueType(flag.Value))
+		addVar(name, "PARTITIONS", strings.Join(decl.Containers, " "))
+		addVar(name, "DEFAULT", MarshalValue(decl.Value))
+		addVar(name, "VALUE", value)
+		addVar(name, "DECLARED_IN", *flag.Traces[0].Source)
+		addVar(name, "SET_IN", *flag.Traces[len(flag.Traces)-1].Source)
+		addVar(name, "NAMESPACE", *decl.Namespace)
+	}
+	pNames := []string{}
+	for k := range partitions {
+		pNames = append(pNames, k)
+	}
+	slices.Sort(pNames)
+
+	// Now sort the make variables, and output them.
+	slices.Sort(vNames)
+
+	// Write the flags as:
+	//   _ALL_RELELASE_FLAGS
+	//   _ALL_RELEASE_FLAGS.PARTITIONS.*
+	//   all _ALL_RELEASE_FLAGS.*, sorted by name
+	//   Final flag values, sorted by name.
+	data := fmt.Sprintf("# TARGET_RELEASE=%s\n", config.Name)
+	if targetRelease != config.Name {
+		data += fmt.Sprintf("# User specified TARGET_RELEASE=%s\n", targetRelease)
+	}
+	// 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(), " "))
+	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 {
+		data += fmt.Sprintf("_ALL_RELEASE_FLAGS.PARTITIONS.%s :=$= %s\n", pName, strings.Join(partitions[pName], " "))
+	}
+	for _, vName := range vNames {
+		data += fmt.Sprintf("%s :=$= %s\n", vName, makeVars[vName])
+	}
+	data += "\n\n# Values for all build flags\n"
+	for _, name := range names {
+		data += fmt.Sprintf("%s :=$= %s\n", name, makeVars[name])
+	}
+	return os.WriteFile(outFile, []byte(data), 0644)
+}
+
+func (config *ReleaseConfig) WritePartitionBuildFlags(outDir string) error {
 	var err error
 	for partition, flags := range config.PartitionBuildFlags {
-		slices.SortFunc(flags.FlagArtifacts, func(a, b *rc_proto.FlagArtifact) int {
+		slices.SortFunc(flags.Flags, func(a, b *rc_proto.FlagArtifact) int {
 			return cmp.Compare(*a.FlagDeclaration.Name, *b.FlagDeclaration.Name)
 		})
-		if err = WriteMessage(filepath.Join(outDir, fmt.Sprintf("build_flags_%s-%s-%s.json", partition, config.Name, product)), flags); err != nil {
+		// The json file name must not be modified as this is read from
+		// build_flags_json module
+		if err = WriteMessage(filepath.Join(outDir, fmt.Sprintf("build_flags_%s.json", partition)), flags); err != nil {
 			return err
 		}
 	}
diff --git a/cmd/release_config/release_config_lib/release_configs.go b/cmd/release_config/release_config_lib/release_configs.go
index 3204b18..97eb8f1 100644
--- a/cmd/release_config/release_config_lib/release_configs.go
+++ b/cmd/release_config/release_config_lib/release_configs.go
@@ -42,6 +42,10 @@
 
 	// Flags declared this directory's flag_declarations/*.textproto
 	FlagDeclarations []rc_proto.FlagDeclaration
+
+	// Potential aconfig and build flag contributions in this map directory.
+	// This is used to detect errors.
+	FlagValueDirs map[string][]string
 }
 
 type ReleaseConfigDirMap map[string]int
@@ -67,12 +71,88 @@
 	// Map of directory to *ReleaseConfigMap
 	releaseConfigMapsMap map[string]*ReleaseConfigMap
 
+	// The files used by all release configs
+	FilesUsedMap map[string]bool
+
 	// The list of config directories used.
 	configDirs []string
 
 	// A map from the config directory to its order in the list of config
 	// directories.
 	configDirIndexes ReleaseConfigDirMap
+
+	// True if we should allow a missing primary release config.  In this
+	// case, we will substitute `trunk_staging` values, but the release
+	// config will not be in ALL_RELEASE_CONFIGS_FOR_PRODUCT.
+	allowMissing bool
+}
+
+func (configs *ReleaseConfigs) WriteInheritanceGraph(outFile string) error {
+	data := []string{}
+	usedAliases := make(map[string]bool)
+	priorStages := make(map[string][]string)
+	for _, config := range configs.ReleaseConfigs {
+		if config.Name == "root" {
+			continue
+		}
+		var fillColor string
+		inherits := []string{}
+		for _, inherit := range config.InheritNames {
+			if inherit == "root" {
+				continue
+			}
+			data = append(data, fmt.Sprintf(`"%s" -> "%s"`, config.Name, inherit))
+			inherits = append(inherits, inherit)
+			// If inheriting an alias, add a link from the alias to that release config.
+			if name, found := configs.Aliases[inherit]; found {
+				if !usedAliases[inherit] {
+					usedAliases[inherit] = true
+					data = append(data, fmt.Sprintf(`"%s" -> "%s"`, inherit, *name))
+					data = append(data,
+						fmt.Sprintf(`"%s" [ label="%s\ncurrently: %s" shape=oval ]`,
+							inherit, inherit, *name))
+				}
+			}
+		}
+		// Add links for all of the advancement progressions.
+		for priorStage := range config.PriorStagesMap {
+			data = append(data, fmt.Sprintf(`"%s" -> "%s" [ style=dashed color="#81c995" ]`,
+				priorStage, config.Name))
+			priorStages[config.Name] = append(priorStages[config.Name], priorStage)
+		}
+		label := config.Name
+		if len(inherits) > 0 {
+			label += "\\ninherits: " + strings.Join(inherits, " ")
+		}
+		if len(config.OtherNames) > 0 {
+			label += "\\nother names: " + strings.Join(config.OtherNames, " ")
+		}
+		switch config.Name {
+		case *configs.Artifact.ReleaseConfig.Name:
+			// The active release config has a light blue fill.
+			fillColor = `fillcolor="#d2e3fc" `
+		case "trunk", "trunk_staging":
+			// Certain workflow stages have a light green fill.
+			fillColor = `fillcolor="#ceead6" `
+		default:
+			// Look for "next" and "*_next", make them light green as well.
+			for _, n := range config.OtherNames {
+				if n == "next" || strings.HasSuffix(n, "_next") {
+					fillColor = `fillcolor="#ceead6" `
+				}
+			}
+		}
+		data = append(data,
+			fmt.Sprintf(`"%s" [ label="%s" %s]`, config.Name, label, fillColor))
+	}
+	slices.Sort(data)
+	data = append([]string{
+		"digraph {",
+		"graph [ ratio=.5 ]",
+		"node [ shape=box style=filled fillcolor=white colorscheme=svg fontcolor=black ]",
+	}, data...)
+	data = append(data, "}")
+	return os.WriteFile(outFile, []byte(strings.Join(data, "\n")), 0644)
 }
 
 // Write the "all_release_configs" artifact.
@@ -95,14 +175,40 @@
 }
 
 func ReleaseConfigsFactory() (c *ReleaseConfigs) {
-	return &ReleaseConfigs{
+	configs := ReleaseConfigs{
 		Aliases:              make(map[string]*string),
 		FlagArtifacts:        make(map[string]*FlagArtifact),
 		ReleaseConfigs:       make(map[string]*ReleaseConfig),
 		releaseConfigMapsMap: make(map[string]*ReleaseConfigMap),
 		configDirs:           []string{},
 		configDirIndexes:     make(ReleaseConfigDirMap),
+		FilesUsedMap:         make(map[string]bool),
 	}
+	workflowManual := rc_proto.Workflow(rc_proto.Workflow_MANUAL)
+	releaseAconfigValueSets := FlagArtifact{
+		FlagDeclaration: &rc_proto.FlagDeclaration{
+			Name:        proto.String("RELEASE_ACONFIG_VALUE_SETS"),
+			Namespace:   proto.String("android_UNKNOWN"),
+			Description: proto.String("Aconfig value sets assembled by release-config"),
+			Workflow:    &workflowManual,
+			Containers:  []string{"system", "system_ext", "product", "vendor"},
+			Value:       &rc_proto.Value{Val: &rc_proto.Value_UnspecifiedValue{false}},
+		},
+		DeclarationIndex: -1,
+		Traces:           []*rc_proto.Tracepoint{},
+	}
+	configs.FlagArtifacts["RELEASE_ACONFIG_VALUE_SETS"] = &releaseAconfigValueSets
+	return &configs
+}
+
+func (configs *ReleaseConfigs) GetSortedReleaseConfigs() (ret []*ReleaseConfig) {
+	for _, config := range configs.ReleaseConfigs {
+		ret = append(ret, config)
+	}
+	slices.SortFunc(ret, func(a, b *ReleaseConfig) int {
+		return cmp.Compare(a.Name, b.Name)
+	})
+	return ret
 }
 
 func ReleaseConfigMapFactory(protoPath string) (m *ReleaseConfigMap) {
@@ -116,11 +222,46 @@
 	return m
 }
 
-func (configs *ReleaseConfigs) LoadReleaseConfigMap(path string, ConfigDirIndex int) error {
-	m := ReleaseConfigMapFactory(path)
-	if m.proto.DefaultContainer == nil {
-		return fmt.Errorf("Release config map %s lacks default_container", path)
+// Find the top of the release config contribution directory.
+// Returns the parent of the flag_declarations and flag_values directories.
+func (configs *ReleaseConfigs) GetDirIndex(path string) (int, error) {
+	for p := path; p != "."; p = filepath.Dir(p) {
+		if idx, ok := configs.configDirIndexes[p]; ok {
+			return idx, nil
+		}
 	}
+	return -1, fmt.Errorf("Could not determine release config directory from %s", path)
+}
+
+// Determine the default directory for writing a flag value.
+//
+// Returns the path of the highest-Indexed one of:
+//   - Where the flag is declared
+//   - Where the release config is first declared
+//   - The last place the value is being written.
+func (configs *ReleaseConfigs) GetFlagValueDirectory(config *ReleaseConfig, flag *FlagArtifact) (string, error) {
+	current, err := configs.GetDirIndex(*flag.Traces[len(flag.Traces)-1].Source)
+	if err != nil {
+		return "", err
+	}
+	index := max(flag.DeclarationIndex, config.DeclarationIndex, current)
+	return configs.configDirs[index], nil
+}
+
+func (configs *ReleaseConfigs) LoadReleaseConfigMap(path string, ConfigDirIndex int) error {
+	if _, err := os.Stat(path); err != nil {
+		return fmt.Errorf("%s does not exist\n", path)
+	}
+	m := ReleaseConfigMapFactory(path)
+	if m.proto.DefaultContainers == nil {
+		return fmt.Errorf("Release config map %s lacks default_containers", path)
+	}
+	for _, container := range m.proto.DefaultContainers {
+		if !validContainer(container) {
+			return fmt.Errorf("Release config map %s has invalid container %s", path, container)
+		}
+	}
+	configs.FilesUsedMap[path] = true
 	dir := filepath.Dir(path)
 	// Record any aliases, checking for duplicates.
 	for _, alias := range m.proto.Aliases {
@@ -135,33 +276,50 @@
 		configs.Aliases[name] = alias.Target
 	}
 	var err error
+	// Temporarily allowlist duplicate flag declaration files to prevent
+	// more from entering the tree while we work to clean up the duplicates
+	// that already exist.
+	dupFlagFile := filepath.Join(dir, "duplicate_allowlist.txt")
+	data, err := os.ReadFile(dupFlagFile)
+	if err == nil {
+		for _, flag := range strings.Split(string(data), "\n") {
+			flag = strings.TrimSpace(flag)
+			if strings.HasPrefix(flag, "//") || strings.HasPrefix(flag, "#") {
+				continue
+			}
+			DuplicateDeclarationAllowlist[flag] = true
+		}
+	}
 	err = WalkTextprotoFiles(dir, "flag_declarations", func(path string, d fs.DirEntry, err error) error {
 		flagDeclaration := FlagDeclarationFactory(path)
 		// Container must be specified.
-		if flagDeclaration.Container == nil {
-			flagDeclaration.Container = m.proto.DefaultContainer
+		if flagDeclaration.Containers == nil {
+			flagDeclaration.Containers = m.proto.DefaultContainers
+		} else {
+			for _, container := range flagDeclaration.Containers {
+				if !validContainer(container) {
+					return fmt.Errorf("Flag declaration %s has invalid container %s", path, container)
+				}
+			}
 		}
-		// TODO: once we have namespaces initialized, we can throw an error here.
-		if flagDeclaration.Namespace == nil {
-			flagDeclaration.Namespace = proto.String("android_UNKNOWN")
-		}
-		// If the input didn't specify a value, create one (== UnspecifiedValue).
-		if flagDeclaration.Value == nil {
-			flagDeclaration.Value = &rc_proto.Value{Val: &rc_proto.Value_UnspecifiedValue{false}}
-		}
+
 		m.FlagDeclarations = append(m.FlagDeclarations, *flagDeclaration)
 		name := *flagDeclaration.Name
+		if name == "RELEASE_ACONFIG_VALUE_SETS" {
+			return fmt.Errorf("%s: %s is a reserved build flag", path, name)
+		}
 		if def, ok := configs.FlagArtifacts[name]; !ok {
 			configs.FlagArtifacts[name] = &FlagArtifact{FlagDeclaration: flagDeclaration, DeclarationIndex: ConfigDirIndex}
-		} else if !proto.Equal(def.FlagDeclaration, flagDeclaration) {
-			return fmt.Errorf("Duplicate definition of %s", *flagDeclaration.Name)
+		} else if !proto.Equal(def.FlagDeclaration, flagDeclaration) || !DuplicateDeclarationAllowlist[name] {
+			return fmt.Errorf("Duplicate definition of %s in %s", *flagDeclaration.Name, path)
 		}
 		// Set the initial value in the flag artifact.
+		configs.FilesUsedMap[path] = true
 		configs.FlagArtifacts[name].UpdateValue(
 			FlagValue{path: path, proto: rc_proto.FlagValue{
 				Name: proto.String(name), Value: flagDeclaration.Value}})
 		if configs.FlagArtifacts[name].Redacted {
-			return fmt.Errorf("%s may not be redacted by default.", *flagDeclaration.Name)
+			return fmt.Errorf("%s may not be redacted by default.", name)
 		}
 		return nil
 	})
@@ -169,6 +327,21 @@
 		return err
 	}
 
+	subDirs := func(subdir string) (ret []string) {
+		if flagVersions, err := os.ReadDir(filepath.Join(dir, subdir)); err == nil {
+			for _, e := range flagVersions {
+				if e.IsDir() && validReleaseConfigName(e.Name()) {
+					ret = append(ret, e.Name())
+				}
+			}
+		}
+		return
+	}
+	m.FlagValueDirs = map[string][]string{
+		"aconfig":     subDirs("aconfig"),
+		"flag_values": subDirs("flag_values"),
+	}
+
 	err = WalkTextprotoFiles(dir, "release_configs", func(path string, d fs.DirEntry, err error) error {
 		releaseConfigContribution := &ReleaseConfigContribution{path: path, DeclarationIndex: ConfigDirIndex}
 		LoadMessage(path, &releaseConfigContribution.proto)
@@ -180,7 +353,18 @@
 			configs.ReleaseConfigs[name] = ReleaseConfigFactory(name, ConfigDirIndex)
 		}
 		config := configs.ReleaseConfigs[name]
-		config.InheritNames = append(config.InheritNames, releaseConfigContribution.proto.Inherits...)
+		config.FilesUsedMap[path] = true
+		inheritNames := make(map[string]bool)
+		for _, inh := range config.InheritNames {
+			inheritNames[inh] = true
+		}
+		// If this contribution says to inherit something we already inherited, we do not want the duplicate.
+		for _, cInh := range releaseConfigContribution.proto.Inherits {
+			if !inheritNames[cInh] {
+				config.InheritNames = append(config.InheritNames, cInh)
+				inheritNames[cInh] = true
+			}
+		}
 
 		// Only walk flag_values/{RELEASE} for defined releases.
 		err2 := WalkTextprotoFiles(dir, filepath.Join("flag_values", name), func(path string, d fs.DirEntry, err error) error {
@@ -188,12 +372,19 @@
 			if fmt.Sprintf("%s.textproto", *flagValue.proto.Name) != filepath.Base(path) {
 				return fmt.Errorf("%s incorrectly sets value for flag %s", path, *flagValue.proto.Name)
 			}
+			if *flagValue.proto.Name == "RELEASE_ACONFIG_VALUE_SETS" {
+				return fmt.Errorf("%s: %s is a reserved build flag", path, *flagValue.proto.Name)
+			}
+			config.FilesUsedMap[path] = true
 			releaseConfigContribution.FlagValues = append(releaseConfigContribution.FlagValues, flagValue)
 			return nil
 		})
 		if err2 != nil {
 			return err2
 		}
+		if releaseConfigContribution.proto.GetAconfigFlagsOnly() {
+			config.AconfigFlagsOnly = true
+		}
 		m.ReleaseConfigContributions[name] = releaseConfigContribution
 		config.Contributions = append(config.Contributions, releaseConfigContribution)
 		return nil
@@ -215,99 +406,22 @@
 	if config, ok := configs.ReleaseConfigs[name]; ok {
 		return config, nil
 	}
+	if configs.allowMissing {
+		if config, ok := configs.ReleaseConfigs["trunk_staging"]; ok {
+			return config, nil
+		}
+	}
 	return nil, fmt.Errorf("Missing config %s.  Trace=%v", name, trace)
 }
 
-// Write the makefile for this targetRelease.
-func (configs *ReleaseConfigs) WriteMakefile(outFile, targetRelease string) error {
-	makeVars := make(map[string]string)
+func (configs *ReleaseConfigs) GetAllReleaseNames() []string {
 	var allReleaseNames []string
 	for _, v := range configs.ReleaseConfigs {
 		allReleaseNames = append(allReleaseNames, v.Name)
 		allReleaseNames = append(allReleaseNames, v.OtherNames...)
 	}
-	config, err := configs.GetReleaseConfig(targetRelease)
-	if err != nil {
-		return err
-	}
-
-	myFlagArtifacts := config.FlagArtifacts.Clone()
-	// Sort the flags by name first.
-	names := []string{}
-	for k, _ := range myFlagArtifacts {
-		names = append(names, k)
-	}
-	slices.SortFunc(names, func(a, b string) int {
-		return cmp.Compare(a, b)
-	})
-	partitions := make(map[string][]string)
-
-	vNames := []string{}
-	addVar := func(name, suffix, value string) {
-		fullName := fmt.Sprintf("_ALL_RELEASE_FLAGS.%s.%s", name, suffix)
-		vNames = append(vNames, fullName)
-		makeVars[fullName] = value
-	}
-
-	for _, name := range names {
-		flag := myFlagArtifacts[name]
-		decl := flag.FlagDeclaration
-
-		// cName := strings.ToLower(rc_proto.Container_name[decl.GetContainer()])
-		cName := strings.ToLower(decl.Container.String())
-		if cName == strings.ToLower(rc_proto.Container_ALL.String()) {
-			partitions["product"] = append(partitions["product"], name)
-			partitions["system"] = append(partitions["system"], name)
-			partitions["system_ext"] = append(partitions["system_ext"], name)
-			partitions["vendor"] = append(partitions["vendor"], name)
-		} else {
-			partitions[cName] = append(partitions[cName], name)
-		}
-		value := MarshalValue(flag.Value)
-		makeVars[name] = value
-		addVar(name, "PARTITIONS", cName)
-		addVar(name, "DEFAULT", MarshalValue(decl.Value))
-		addVar(name, "VALUE", value)
-		addVar(name, "DECLARED_IN", *flag.Traces[0].Source)
-		addVar(name, "SET_IN", *flag.Traces[len(flag.Traces)-1].Source)
-		addVar(name, "NAMESPACE", *decl.Namespace)
-	}
-	pNames := []string{}
-	for k, _ := range partitions {
-		pNames = append(pNames, k)
-	}
-	slices.SortFunc(pNames, func(a, b string) int {
-		return cmp.Compare(a, b)
-	})
-
-	// Now sort the make variables, and output them.
-	slices.SortFunc(vNames, func(a, b string) int {
-		return cmp.Compare(a, b)
-	})
-
-	// Write the flags as:
-	//   _ALL_RELELASE_FLAGS
-	//   _ALL_RELEASE_FLAGS.PARTITIONS.*
-	//   all _ALL_RELEASE_FLAGS.*, sorted by name
-	//   Final flag values, sorted by name.
-	data := fmt.Sprintf("# TARGET_RELEASE=%s\n", config.Name)
-	if targetRelease != config.Name {
-		data += fmt.Sprintf("# User specified TARGET_RELEASE=%s\n", targetRelease)
-	}
-	// The variable _all_release_configs will get deleted during processing, so do not mark it read-only.
-	data += fmt.Sprintf("_all_release_configs := %s\n", strings.Join(allReleaseNames, " "))
-	data += fmt.Sprintf("_ALL_RELEASE_FLAGS :=$= %s\n", strings.Join(names, " "))
-	for _, pName := range pNames {
-		data += fmt.Sprintf("_ALL_RELEASE_FLAGS.PARTITIONS.%s :=$= %s\n", pName, strings.Join(partitions[pName], " "))
-	}
-	for _, vName := range vNames {
-		data += fmt.Sprintf("%s :=$= %s\n", vName, makeVars[vName])
-	}
-	data += "\n\n# Values for all build flags\n"
-	for _, name := range names {
-		data += fmt.Sprintf("%s :=$= %s\n", name, makeVars[name])
-	}
-	return os.WriteFile(outFile, []byte(data), 0644)
+	slices.Sort(allReleaseNames)
+	return allReleaseNames
 }
 
 func (configs *ReleaseConfigs) GenerateReleaseConfigs(targetRelease string) error {
@@ -327,28 +441,49 @@
 		configs.ReleaseConfigs[name].OtherNames = aliases
 	}
 
-	for _, config := range configs.ReleaseConfigs {
-		err := config.GenerateReleaseConfig(configs)
+	sortedReleaseConfigs := configs.GetSortedReleaseConfigs()
+	for _, c := range sortedReleaseConfigs {
+		err := c.GenerateReleaseConfig(configs)
 		if err != nil {
 			return err
 		}
 	}
 
+	// Look for ignored flagging values.  Gather the entire list to make it easier to fix them.
+	errors := []string{}
+	for _, contrib := range configs.ReleaseConfigMaps {
+		dirName := filepath.Dir(contrib.path)
+		for k, names := range contrib.FlagValueDirs {
+			for _, rcName := range names {
+				if config, err := configs.GetReleaseConfig(rcName); 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",
+							filepath.Join(dirName, k, rcName), dirName, config.Name))
+					}
+				}
+
+			}
+		}
+	}
+	if len(errors) > 0 {
+		return fmt.Errorf("%s", strings.Join(errors, "\n"))
+	}
+
 	releaseConfig, err := configs.GetReleaseConfig(targetRelease)
 	if err != nil {
 		return err
 	}
+	orc := []*rc_proto.ReleaseConfigArtifact{}
+	for _, c := range sortedReleaseConfigs {
+		if c.Name != releaseConfig.Name {
+			orc = append(orc, c.ReleaseConfigArtifact)
+		}
+	}
+
 	configs.Artifact = rc_proto.ReleaseConfigsArtifact{
-		ReleaseConfig: releaseConfig.ReleaseConfigArtifact,
-		OtherReleaseConfigs: func() []*rc_proto.ReleaseConfigArtifact {
-			orc := []*rc_proto.ReleaseConfigArtifact{}
-			for name, config := range configs.ReleaseConfigs {
-				if name != releaseConfig.Name {
-					orc = append(orc, config.ReleaseConfigArtifact)
-				}
-			}
-			return orc
-		}(),
+		ReleaseConfig:       releaseConfig.ReleaseConfigArtifact,
+		OtherReleaseConfigs: orc,
 		ReleaseConfigMapsMap: func() map[string]*rc_proto.ReleaseConfigMap {
 			ret := make(map[string]*rc_proto.ReleaseConfigMap)
 			for k, v := range configs.releaseConfigMapsMap {
@@ -360,20 +495,27 @@
 	return nil
 }
 
-func ReadReleaseConfigMaps(releaseConfigMapPaths StringList, targetRelease string) (*ReleaseConfigs, error) {
+func ReadReleaseConfigMaps(releaseConfigMapPaths StringList, targetRelease string, useBuildVar, allowMissing bool) (*ReleaseConfigs, error) {
 	var err error
 
 	if len(releaseConfigMapPaths) == 0 {
-		releaseConfigMapPaths = GetDefaultMapPaths()
+		releaseConfigMapPaths, err = GetDefaultMapPaths(useBuildVar)
+		if err != nil {
+			return nil, err
+		}
 		if len(releaseConfigMapPaths) == 0 {
 			return nil, fmt.Errorf("No maps found")
 		}
-		fmt.Printf("No --map argument provided.  Using: --map %s\n", strings.Join(releaseConfigMapPaths, " --map "))
+		if !useBuildVar {
+			warnf("No --map argument provided.  Using: --map %s\n", strings.Join(releaseConfigMapPaths, " --map "))
+		}
 	}
 
 	configs := ReleaseConfigsFactory()
+	configs.allowMissing = allowMissing
 	mapsRead := make(map[string]bool)
-	for idx, releaseConfigMapPath := range releaseConfigMapPaths {
+	var idx int
+	for _, releaseConfigMapPath := range releaseConfigMapPaths {
 		// Maintain an ordered list of release config directories.
 		configDir := filepath.Dir(releaseConfigMapPath)
 		if mapsRead[configDir] {
@@ -382,10 +524,13 @@
 		mapsRead[configDir] = true
 		configs.configDirIndexes[configDir] = idx
 		configs.configDirs = append(configs.configDirs, configDir)
+		// Force the path to be the textproto path, so that both the scl and textproto formats can coexist.
+		releaseConfigMapPath = filepath.Join(configDir, "release_config_map.textproto")
 		err = configs.LoadReleaseConfigMap(releaseConfigMapPath, idx)
 		if err != nil {
 			return nil, err
 		}
+		idx += 1
 	}
 
 	// Now that we have all of the release config maps, can meld them and generate the artifacts.
diff --git a/cmd/release_config/release_config_lib/util.go b/cmd/release_config/release_config_lib/util.go
index 86940da..b149293 100644
--- a/cmd/release_config/release_config_lib/util.go
+++ b/cmd/release_config/release_config_lib/util.go
@@ -19,14 +19,22 @@
 	"fmt"
 	"io/fs"
 	"os"
+	"os/exec"
 	"path/filepath"
+	"regexp"
+	"slices"
 	"strings"
 
+	"github.com/google/blueprint/pathtools"
 	"google.golang.org/protobuf/encoding/prototext"
 	"google.golang.org/protobuf/proto"
 )
 
-var disableWarnings bool
+var (
+	disableWarnings        bool
+	containerRegexp, _     = regexp.Compile("^[a-z][a-z0-9]*([._][a-z][a-z0-9]*)*$")
+	releaseConfigRegexp, _ = regexp.Compile("^[a-z][a-z0-9]*([._][a-z0-9]*)*$")
+)
 
 type StringList []string
 
@@ -53,13 +61,41 @@
 //
 //	error: any error encountered.
 func WriteMessage(path string, message proto.Message) (err error) {
+	format := filepath.Ext(path)
+	if len(format) > 1 {
+		// Strip any leading dot.
+		format = format[1:]
+	}
+	return WriteFormattedMessage(path, format, message)
+}
+
+// Write a marshalled message to a file.
+//
+// Marshal the message using the given format.
+//
+// Args:
+//
+//	path string: the path of the file to write to.  Directories are not created.
+//	  Supported extensions are: ".json", ".pb", and ".textproto".
+//	format string: one of "json", "pb", or "textproto".
+//	message proto.Message: the message to write.
+//
+// Returns:
+//
+//	error: any error encountered.
+func WriteFormattedMessage(path, format string, message proto.Message) (err error) {
 	var data []byte
-	switch filepath.Ext(path) {
-	case ".json":
+	if _, err := os.Stat(filepath.Dir(path)); err != nil {
+		if err = os.MkdirAll(filepath.Dir(path), 0775); err != nil {
+			return err
+		}
+	}
+	switch format {
+	case "json":
 		data, err = json.MarshalIndent(message, "", "  ")
-	case ".pb":
+	case "pb", "binaryproto", "protobuf":
 		data, err = proto.Marshal(message)
-	case ".textproto":
+	case "textproto":
 		data, err = prototext.MarshalOptions{Multiline: true}.Marshal(message)
 	default:
 		return fmt.Errorf("Unknown message format for %s", path)
@@ -67,7 +103,7 @@
 	if err != nil {
 		return err
 	}
-	return os.WriteFile(path, data, 0644)
+	return pathtools.WriteFileIfChanged(path, data, 0644)
 }
 
 // Read a message from a file.
@@ -90,7 +126,7 @@
 	switch filepath.Ext(path) {
 	case ".json":
 		return json.Unmarshal(data, message)
-	case ".pb":
+	case ".pb", ".protobuf", ".binaryproto":
 		return proto.Unmarshal(data, message)
 	case ".textproto":
 		return prototext.Unmarshal(data, message)
@@ -121,6 +157,9 @@
 	disableWarnings = true
 }
 
+// warnf will log to stdout if warnings are enabled. In make code,
+// stdout is redirected to a file, so the warnings will not be shown
+// in the terminal.
 func warnf(format string, args ...any) (n int, err error) {
 	if !disableWarnings {
 		return fmt.Printf(format, args...)
@@ -128,6 +167,23 @@
 	return 0, nil
 }
 
+func SortedMapKeys(inputMap map[string]bool) []string {
+	ret := []string{}
+	for k := range inputMap {
+		ret = append(ret, k)
+	}
+	slices.Sort(ret)
+	return ret
+}
+
+func validContainer(container string) bool {
+	return containerRegexp.MatchString(container)
+}
+
+func validReleaseConfigName(name string) bool {
+	return releaseConfigRegexp.MatchString(name)
+}
+
 // Returns the default value for release config artifacts.
 func GetDefaultOutDir() string {
 	outEnv := os.Getenv("OUT_DIR")
@@ -137,22 +193,65 @@
 	return filepath.Join(outEnv, "soong", "release-config")
 }
 
+// Find the top of the workspace.
+//
+// This mirrors the logic in build/envsetup.sh's gettop().
+func GetTopDir() (topDir string, err error) {
+	workingDir, err := os.Getwd()
+	if err != nil {
+		return
+	}
+	topFile := "build/make/core/envsetup.mk"
+	for topDir = workingDir; topDir != "/"; topDir = filepath.Dir(topDir) {
+		if _, err = os.Stat(filepath.Join(topDir, topFile)); err == nil {
+			return filepath.Rel(workingDir, topDir)
+		}
+	}
+	return "", fmt.Errorf("Unable to locate top of workspace")
+}
+
 // Return the default list of map files to use.
-func GetDefaultMapPaths() StringList {
-	var defaultMapPaths StringList
-	defaultLocations := StringList{
+func GetDefaultMapPaths(queryMaps bool) (defaultMapPaths StringList, err error) {
+	var defaultLocations StringList
+	workingDir, err := os.Getwd()
+	if err != nil {
+		return
+	}
+	defer func() {
+		os.Chdir(workingDir)
+	}()
+	topDir, err := GetTopDir()
+	os.Chdir(topDir)
+
+	defaultLocations = StringList{
 		"build/release/release_config_map.textproto",
 		"vendor/google_shared/build/release/release_config_map.textproto",
 		"vendor/google/release/release_config_map.textproto",
 	}
 	for _, path := range defaultLocations {
-		if _, err := os.Stat(path); err == nil {
+		if _, err = os.Stat(path); err == nil {
 			defaultMapPaths = append(defaultMapPaths, path)
 		}
 	}
-	prodMaps := os.Getenv("PRODUCT_RELEASE_CONFIG_MAPS")
-	if prodMaps != "" {
+
+	var prodMaps string
+	if queryMaps {
+		getBuildVar := exec.Command("build/soong/soong_ui.bash", "--dumpvar-mode", "PRODUCT_RELEASE_CONFIG_MAPS")
+		var stdout strings.Builder
+		getBuildVar.Stdin = strings.NewReader("")
+		getBuildVar.Stdout = &stdout
+		getBuildVar.Stderr = os.Stderr
+		err = getBuildVar.Run()
+		if err != nil {
+			return
+		}
+		prodMaps = stdout.String()
+	} else {
+		prodMaps = os.Getenv("PRODUCT_RELEASE_CONFIG_MAPS")
+	}
+	prodMaps = strings.TrimSpace(prodMaps)
+	if len(prodMaps) > 0 {
 		defaultMapPaths = append(defaultMapPaths, strings.Split(prodMaps, " ")...)
 	}
-	return defaultMapPaths
+	return
 }
diff --git a/cmd/release_config/release_config_proto/Android.bp b/cmd/release_config/release_config_proto/Android.bp
index 8c47f2a..c34d203 100644
--- a/cmd/release_config/release_config_proto/Android.bp
+++ b/cmd/release_config/release_config_proto/Android.bp
@@ -24,6 +24,8 @@
         "golang-protobuf-runtime-protoimpl",
     ],
     srcs: [
+        "build_flags_common.pb.go",
+        "build_flags_declarations.pb.go",
         "build_flags_src.pb.go",
         "build_flags_out.pb.go",
     ],
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
new file mode 100644
index 0000000..82fbcfa
--- /dev/null
+++ b/cmd/release_config/release_config_proto/build_flags_common.pb.go
@@ -0,0 +1,169 @@
+//
+// 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: build_flags_common.proto
+
+package release_config_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 Workflow int32
+
+const (
+	Workflow_Workflow_Unspecified Workflow = 0
+	// Boolean value flags that progress from false to true.
+	Workflow_LAUNCH Workflow = 1
+	// String value flags that get updated with new version strings to control
+	// prebuilt inclusion.
+	Workflow_PREBUILT Workflow = 2
+	// Manually managed outside flags.  These are likely to be found in a
+	// different directory than flags with other workflows.
+	Workflow_MANUAL Workflow = 3
+)
+
+// Enum value maps for Workflow.
+var (
+	Workflow_name = map[int32]string{
+		0: "Workflow_Unspecified",
+		1: "LAUNCH",
+		2: "PREBUILT",
+		3: "MANUAL",
+	}
+	Workflow_value = map[string]int32{
+		"Workflow_Unspecified": 0,
+		"LAUNCH":               1,
+		"PREBUILT":             2,
+		"MANUAL":               3,
+	}
+)
+
+func (x Workflow) Enum() *Workflow {
+	p := new(Workflow)
+	*p = x
+	return p
+}
+
+func (x Workflow) String() string {
+	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
+}
+
+func (Workflow) Descriptor() protoreflect.EnumDescriptor {
+	return file_build_flags_common_proto_enumTypes[0].Descriptor()
+}
+
+func (Workflow) Type() protoreflect.EnumType {
+	return &file_build_flags_common_proto_enumTypes[0]
+}
+
+func (x Workflow) Number() protoreflect.EnumNumber {
+	return protoreflect.EnumNumber(x)
+}
+
+// Deprecated: Do not use.
+func (x *Workflow) UnmarshalJSON(b []byte) error {
+	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
+	if err != nil {
+		return err
+	}
+	*x = Workflow(num)
+	return nil
+}
+
+// Deprecated: Use Workflow.Descriptor instead.
+func (Workflow) EnumDescriptor() ([]byte, []int) {
+	return file_build_flags_common_proto_rawDescGZIP(), []int{0}
+}
+
+var File_build_flags_common_proto protoreflect.FileDescriptor
+
+var file_build_flags_common_proto_rawDesc = []byte{
+	0x0a, 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, 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, 0x2a, 0x4a, 0x0a, 0x08, 0x77, 0x6f, 0x72, 0x6b,
+	0x66, 0x6c, 0x6f, 0x77, 0x12, 0x18, 0x0a, 0x14, 0x57, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77,
+	0x5f, 0x55, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x10, 0x00, 0x12, 0x0a,
+	0x0a, 0x06, 0x4c, 0x41, 0x55, 0x4e, 0x43, 0x48, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x50, 0x52,
+	0x45, 0x42, 0x55, 0x49, 0x4c, 0x54, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x4d, 0x41, 0x4e, 0x55,
+	0x41, 0x4c, 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 (
+	file_build_flags_common_proto_rawDescOnce sync.Once
+	file_build_flags_common_proto_rawDescData = file_build_flags_common_proto_rawDesc
+)
+
+func file_build_flags_common_proto_rawDescGZIP() []byte {
+	file_build_flags_common_proto_rawDescOnce.Do(func() {
+		file_build_flags_common_proto_rawDescData = protoimpl.X.CompressGZIP(file_build_flags_common_proto_rawDescData)
+	})
+	return file_build_flags_common_proto_rawDescData
+}
+
+var file_build_flags_common_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
+var file_build_flags_common_proto_goTypes = []interface{}{
+	(Workflow)(0), // 0: android.release_config_proto.workflow
+}
+var file_build_flags_common_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_build_flags_common_proto_init() }
+func file_build_flags_common_proto_init() {
+	if File_build_flags_common_proto != nil {
+		return
+	}
+	type x struct{}
+	out := protoimpl.TypeBuilder{
+		File: protoimpl.DescBuilder{
+			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+			RawDescriptor: file_build_flags_common_proto_rawDesc,
+			NumEnums:      1,
+			NumMessages:   0,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_build_flags_common_proto_goTypes,
+		DependencyIndexes: file_build_flags_common_proto_depIdxs,
+		EnumInfos:         file_build_flags_common_proto_enumTypes,
+	}.Build()
+	File_build_flags_common_proto = out.File
+	file_build_flags_common_proto_rawDesc = nil
+	file_build_flags_common_proto_goTypes = nil
+	file_build_flags_common_proto_depIdxs = nil
+}
diff --git a/cmd/release_config/release_config_proto/build_flags_common.proto b/cmd/release_config/release_config_proto/build_flags_common.proto
new file mode 100644
index 0000000..d5d6101
--- /dev/null
+++ b/cmd/release_config/release_config_proto/build_flags_common.proto
@@ -0,0 +1,36 @@
+//
+// 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.release_config_proto;
+option go_package = "android/soong/release_config/release_config_proto";
+
+// This protobuf file defines common messages used in the rest of the build flag
+// protos.
+
+enum workflow {
+  Workflow_Unspecified = 0;
+
+  // Boolean value flags that progress from false to true.
+  LAUNCH = 1;
+
+  // String value flags that get updated with new version strings to control
+  // prebuilt inclusion.
+  PREBUILT = 2;
+
+  // Manually managed outside flags.  These are likely to be found in a
+  // different directory than flags with other workflows.
+  MANUAL = 3;
+}
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
new file mode 100644
index 0000000..d2de89a
--- /dev/null
+++ b/cmd/release_config/release_config_proto/build_flags_declarations.pb.go
@@ -0,0 +1,301 @@
+//
+// 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: build_flags_declarations.proto
+
+package release_config_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 FlagDeclarationArtifact struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The name of the flag.
+	// See # name for format detail
+	Name *string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
+	// Namespace the flag belongs to (required)
+	// See # namespace for format detail
+	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"`
+	// Where the flag was declared.
+	DeclarationPath *string `protobuf:"bytes,5,opt,name=declaration_path,json=declarationPath" json:"declaration_path,omitempty"`
+	// Workflow for this flag.
+	Workflow *Workflow `protobuf:"varint,205,opt,name=workflow,enum=android.release_config_proto.Workflow" json:"workflow,omitempty"`
+	// The container for this flag.  This overrides any default container given
+	// in the release_config_map message.
+	Containers []string `protobuf:"bytes,206,rep,name=containers" json:"containers,omitempty"`
+}
+
+func (x *FlagDeclarationArtifact) Reset() {
+	*x = FlagDeclarationArtifact{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_build_flags_declarations_proto_msgTypes[0]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *FlagDeclarationArtifact) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*FlagDeclarationArtifact) ProtoMessage() {}
+
+func (x *FlagDeclarationArtifact) ProtoReflect() protoreflect.Message {
+	mi := &file_build_flags_declarations_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 FlagDeclarationArtifact.ProtoReflect.Descriptor instead.
+func (*FlagDeclarationArtifact) Descriptor() ([]byte, []int) {
+	return file_build_flags_declarations_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *FlagDeclarationArtifact) GetName() string {
+	if x != nil && x.Name != nil {
+		return *x.Name
+	}
+	return ""
+}
+
+func (x *FlagDeclarationArtifact) GetNamespace() string {
+	if x != nil && x.Namespace != nil {
+		return *x.Namespace
+	}
+	return ""
+}
+
+func (x *FlagDeclarationArtifact) GetDescription() string {
+	if x != nil && x.Description != nil {
+		return *x.Description
+	}
+	return ""
+}
+
+func (x *FlagDeclarationArtifact) GetDeclarationPath() string {
+	if x != nil && x.DeclarationPath != nil {
+		return *x.DeclarationPath
+	}
+	return ""
+}
+
+func (x *FlagDeclarationArtifact) GetWorkflow() Workflow {
+	if x != nil && x.Workflow != nil {
+		return *x.Workflow
+	}
+	return Workflow_Workflow_Unspecified
+}
+
+func (x *FlagDeclarationArtifact) GetContainers() []string {
+	if x != nil {
+		return x.Containers
+	}
+	return nil
+}
+
+type FlagDeclarationArtifacts struct {
+	state         protoimpl.MessageState
+	sizeCache     protoimpl.SizeCache
+	unknownFields protoimpl.UnknownFields
+
+	// The artifacts
+	FlagDeclarationArtifactList []*FlagDeclarationArtifact `protobuf:"bytes,1,rep,name=flag_declaration_artifact_list,json=flagDeclarationArtifactList" json:"flag_declaration_artifact_list,omitempty"`
+}
+
+func (x *FlagDeclarationArtifacts) Reset() {
+	*x = FlagDeclarationArtifacts{}
+	if protoimpl.UnsafeEnabled {
+		mi := &file_build_flags_declarations_proto_msgTypes[1]
+		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+		ms.StoreMessageInfo(mi)
+	}
+}
+
+func (x *FlagDeclarationArtifacts) String() string {
+	return protoimpl.X.MessageStringOf(x)
+}
+
+func (*FlagDeclarationArtifacts) ProtoMessage() {}
+
+func (x *FlagDeclarationArtifacts) ProtoReflect() protoreflect.Message {
+	mi := &file_build_flags_declarations_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 FlagDeclarationArtifacts.ProtoReflect.Descriptor instead.
+func (*FlagDeclarationArtifacts) Descriptor() ([]byte, []int) {
+	return file_build_flags_declarations_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *FlagDeclarationArtifacts) GetFlagDeclarationArtifactList() []*FlagDeclarationArtifact {
+	if x != nil {
+		return x.FlagDeclarationArtifactList
+	}
+	return nil
+}
+
+var File_build_flags_declarations_proto protoreflect.FileDescriptor
+
+var file_build_flags_declarations_proto_rawDesc = []byte{
+	0x0a, 0x1e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x5f, 0x64, 0x65,
+	0x63, 0x6c, 0x61, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
+	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, 0x8e, 0x02, 0x0a, 0x19, 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, 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, 0x77, 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, 0x9a, 0x01, 0x0a, 0x1a, 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, 0x73, 0x12, 0x7c, 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, 0x37, 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,
+	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, 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 (
+	file_build_flags_declarations_proto_rawDescOnce sync.Once
+	file_build_flags_declarations_proto_rawDescData = file_build_flags_declarations_proto_rawDesc
+)
+
+func file_build_flags_declarations_proto_rawDescGZIP() []byte {
+	file_build_flags_declarations_proto_rawDescOnce.Do(func() {
+		file_build_flags_declarations_proto_rawDescData = protoimpl.X.CompressGZIP(file_build_flags_declarations_proto_rawDescData)
+	})
+	return file_build_flags_declarations_proto_rawDescData
+}
+
+var file_build_flags_declarations_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_build_flags_declarations_proto_goTypes = []interface{}{
+	(*FlagDeclarationArtifact)(nil),  // 0: android.release_config_proto.flag_declaration_artifact
+	(*FlagDeclarationArtifacts)(nil), // 1: android.release_config_proto.flag_declaration_artifacts
+	(Workflow)(0),                    // 2: android.release_config_proto.workflow
+}
+var file_build_flags_declarations_proto_depIdxs = []int32{
+	2, // 0: android.release_config_proto.flag_declaration_artifact.workflow:type_name -> android.release_config_proto.workflow
+	0, // 1: android.release_config_proto.flag_declaration_artifacts.flag_declaration_artifact_list:type_name -> android.release_config_proto.flag_declaration_artifact
+	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_build_flags_declarations_proto_init() }
+func file_build_flags_declarations_proto_init() {
+	if File_build_flags_declarations_proto != nil {
+		return
+	}
+	file_build_flags_common_proto_init()
+	if !protoimpl.UnsafeEnabled {
+		file_build_flags_declarations_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*FlagDeclarationArtifact); i {
+			case 0:
+				return &v.state
+			case 1:
+				return &v.sizeCache
+			case 2:
+				return &v.unknownFields
+			default:
+				return nil
+			}
+		}
+		file_build_flags_declarations_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
+			switch v := v.(*FlagDeclarationArtifacts); 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_build_flags_declarations_proto_rawDesc,
+			NumEnums:      0,
+			NumMessages:   2,
+			NumExtensions: 0,
+			NumServices:   0,
+		},
+		GoTypes:           file_build_flags_declarations_proto_goTypes,
+		DependencyIndexes: file_build_flags_declarations_proto_depIdxs,
+		MessageInfos:      file_build_flags_declarations_proto_msgTypes,
+	}.Build()
+	File_build_flags_declarations_proto = out.File
+	file_build_flags_declarations_proto_rawDesc = nil
+	file_build_flags_declarations_proto_goTypes = nil
+	file_build_flags_declarations_proto_depIdxs = nil
+}
diff --git a/cmd/release_config/release_config_proto/build_flags_declarations.proto b/cmd/release_config/release_config_proto/build_flags_declarations.proto
new file mode 100644
index 0000000..233158e
--- /dev/null
+++ b/cmd/release_config/release_config_proto/build_flags_declarations.proto
@@ -0,0 +1,75 @@
+//
+// 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.release_config_proto;
+option go_package = "android/soong/release_config/release_config_proto";
+
+import "build_flags_common.proto";
+
+// This protobuf file defines messages used to represent the
+// all_build_flag_declarations artifact for use in automated systems, such as
+// Gantry.
+//
+// The following format requirements apply across various message fields:
+//
+// # name: name of the flag
+//
+//    format: an uppercase string in SNAKE_CASE format starting with RELEASE_,
+//      no consecutive underscores, and no leading digit. For example
+//      RELEASE_MY_PACKAGE_FLAG is a valid name, while MY_PACKAGE_FLAG, and
+//      RELEASE_MY_PACKAGE__FLAG are invalid.
+//
+// # package: package to which the flag belongs
+//
+//    format: lowercase strings in snake_case format, delimited by dots, no
+//      consecutive underscores and no leading digit in each string. For example
+//      com.android.mypackage is a valid name while com.android.myPackage,
+//      com.android.1mypackage are invalid
+
+message flag_declaration_artifact {
+  // The name of the flag.
+  // See # name for format detail
+  optional string name = 1;
+
+  // Namespace the flag belongs to (required)
+  // See # namespace for format detail
+  optional string namespace = 2;
+
+  // Text description of the flag's purpose.
+  optional string description = 3;
+
+  // reserve this for bug, if needed.
+  reserved 4;
+
+  // Where the flag was declared.
+  optional string declaration_path = 5;
+
+  // Workflow for this flag.
+  optional workflow workflow = 205;
+
+  // The container for this flag.  This overrides any default container given
+  // in the release_config_map message.
+  repeated string containers = 206;
+
+  // The package associated with this flag.
+  // (when Gantry is ready for it) optional string package = 207;
+  reserved 207;
+}
+
+message flag_declaration_artifacts {
+  // The artifacts
+  repeated flag_declaration_artifact flag_declaration_artifact_list = 1;
+}
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 483cffa..c63ea26 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
@@ -1,3 +1,7 @@
+//
+// 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
 //
@@ -11,7 +15,7 @@
 
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.30.0
+// 	protoc-gen-go v1.33.0
 // 	protoc        v3.21.12
 // source: build_flags_out.proto
 
@@ -159,7 +163,7 @@
 	unknownFields protoimpl.UnknownFields
 
 	// The artifacts
-	FlagArtifacts []*FlagArtifact `protobuf:"bytes,1,rep,name=flag_artifacts,json=flagArtifacts" json:"flag_artifacts,omitempty"`
+	Flags []*FlagArtifact `protobuf:"bytes,1,rep,name=flags" json:"flags,omitempty"`
 }
 
 func (x *FlagArtifacts) Reset() {
@@ -194,9 +198,9 @@
 	return file_build_flags_out_proto_rawDescGZIP(), []int{2}
 }
 
-func (x *FlagArtifacts) GetFlagArtifacts() []*FlagArtifact {
+func (x *FlagArtifacts) GetFlags() []*FlagArtifact {
 	if x != nil {
-		return x.FlagArtifacts
+		return x.Flags
 	}
 	return nil
 }
@@ -213,15 +217,24 @@
 	OtherNames []string `protobuf:"bytes,2,rep,name=other_names,json=otherNames" json:"other_names,omitempty"`
 	// The complete set of build flags in this release config, after all
 	// inheritance and other processing is complete.
-	FlagArtifacts []*FlagArtifact `protobuf:"bytes,3,rep,name=flag_artifacts,json=flagArtifacts" json:"flag_artifacts,omitempty"`
+	Flags []*FlagArtifact `protobuf:"bytes,3,rep,name=flags" json:"flags,omitempty"`
 	// The (complete) list of aconfig_value_sets Soong modules to use.
 	AconfigValueSets []string `protobuf:"bytes,4,rep,name=aconfig_value_sets,json=aconfigValueSets" json:"aconfig_value_sets,omitempty"`
 	// The names of the release_config_artifacts from which we inherited.
 	// Included for reference only.
 	Inherits []string `protobuf:"bytes,5,rep,name=inherits" json:"inherits,omitempty"`
-	// The release config directories used for this config.
+	// The release config directories used for this config.  This includes
+	// directories that provide flag declarations, but do not provide any flag
+	// values specific to this release config.
 	// For example, "build/release".
 	Directories []string `protobuf:"bytes,6,rep,name=directories" json:"directories,omitempty"`
+	// 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,7,rep,name=prior_stages,json=priorStages" json:"prior_stages,omitempty"`
+	// The release config directories that contribute directly to this release
+	// 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"`
 }
 
 func (x *ReleaseConfigArtifact) Reset() {
@@ -270,9 +283,9 @@
 	return nil
 }
 
-func (x *ReleaseConfigArtifact) GetFlagArtifacts() []*FlagArtifact {
+func (x *ReleaseConfigArtifact) GetFlags() []*FlagArtifact {
 	if x != nil {
-		return x.FlagArtifacts
+		return x.Flags
 	}
 	return nil
 }
@@ -298,6 +311,20 @@
 	return nil
 }
 
+func (x *ReleaseConfigArtifact) GetPriorStages() []string {
+	if x != nil {
+		return x.PriorStages
+	}
+	return nil
+}
+
+func (x *ReleaseConfigArtifact) GetValueDirectories() []string {
+	if x != nil {
+		return x.ValueDirectories
+	}
+	return nil
+}
+
 type ReleaseConfigsArtifact struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -392,64 +419,69 @@
 	0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x28, 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, 0x74, 0x72, 0x61, 0x63, 0x65, 0x70, 0x6f, 0x69, 0x6e,
-	0x74, 0x52, 0x06, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x22, 0x64, 0x0a, 0x0e, 0x66, 0x6c, 0x61,
-	0x67, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x12, 0x52, 0x0a, 0x0e, 0x66,
-	0x6c, 0x61, 0x67, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x18, 0x01, 0x20,
-	0x03, 0x28, 0x0b, 0x32, 0x2b, 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, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74,
-	0x52, 0x0d, 0x66, 0x6c, 0x61, 0x67, 0x41, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x22,
-	0x8e, 0x02, 0x0a, 0x17, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66,
-	0x69, 0x67, 0x5f, 0x61, 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, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02,
-	0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x73,
-	0x12, 0x52, 0x0a, 0x0e, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63,
-	0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 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, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x61, 0x72, 0x74,
-	0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x0d, 0x66, 0x6c, 0x61, 0x67, 0x41, 0x72, 0x74, 0x69, 0x66,
-	0x61, 0x63, 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, 0x04, 0x20, 0x03, 0x28, 0x09,
-	0x52, 0x10, 0x61, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x65,
-	0x74, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x68, 0x65, 0x72, 0x69, 0x74, 0x73, 0x18, 0x05,
-	0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x69, 0x6e, 0x68, 0x65, 0x72, 0x69, 0x74, 0x73, 0x12, 0x20,
-	0x0a, 0x0b, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20,
-	0x03, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73,
-	0x22, 0xe8, 0x03, 0x0a, 0x18, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e,
-	0x66, 0x69, 0x67, 0x73, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x12, 0x5c, 0x0a,
-	0x0e, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18,
-	0x01, 0x20, 0x01, 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, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e,
-	0x66, 0x69, 0x67, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x0d, 0x72, 0x65,
-	0x6c, 0x65, 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x69, 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, 0x35, 0x2e, 0x61, 0x6e, 0x64,
+	0x74, 0x52, 0x06, 0x74, 0x72, 0x61, 0x63, 0x65, 0x73, 0x22, 0x63, 0x0a, 0x0e, 0x66, 0x6c, 0x61,
+	0x67, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x73, 0x12, 0x41, 0x0a, 0x05, 0x66,
+	0x6c, 0x61, 0x67, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 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, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73,
-	0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x61, 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, 0x87, 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, 0x50, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f,
+	0x66, 0x69, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x61,
+	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, 0xdd,
+	0x02, 0x0a, 0x17, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69,
+	0x67, 0x5f, 0x61, 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, 0x72, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20,
+	0x03, 0x28, 0x09, 0x52, 0x0a, 0x6f, 0x74, 0x68, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12,
+	0x41, 0x0a, 0x05, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b,
+	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, 0x66, 0x6c,
+	0x61, 0x67, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x05, 0x66, 0x6c, 0x61,
+	0x67, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x61, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x76, 0x61,
+	0x6c, 0x75, 0x65, 0x5f, 0x73, 0x65, 0x74, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x10,
+	0x61, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x65, 0x74, 0x73,
+	0x12, 0x1a, 0x0a, 0x08, 0x69, 0x6e, 0x68, 0x65, 0x72, 0x69, 0x74, 0x73, 0x18, 0x05, 0x20, 0x03,
+	0x28, 0x09, 0x52, 0x08, 0x69, 0x6e, 0x68, 0x65, 0x72, 0x69, 0x74, 0x73, 0x12, 0x20, 0x0a, 0x0b,
+	0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28,
+	0x09, 0x52, 0x0b, 0x64, 0x69, 0x72, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x12, 0x21,
+	0x0a, 0x0c, 0x70, 0x72, 0x69, 0x6f, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x67, 0x65, 0x73, 0x18, 0x07,
+	0x20, 0x03, 0x28, 0x09, 0x52, 0x0b, 0x70, 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, 0xe8,
+	0x03, 0x0a, 0x18, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69,
+	0x67, 0x73, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x12, 0x5c, 0x0a, 0x0e, 0x72,
+	0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20,
+	0x01, 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, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69,
+	0x67, 0x5f, 0x61, 0x72, 0x74, 0x69, 0x66, 0x61, 0x63, 0x74, 0x52, 0x0d, 0x72, 0x65, 0x6c, 0x65,
+	0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x69, 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, 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, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f,
-	0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x5f, 0x61, 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, 0x79, 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,
-	0x46, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30,
-	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, 0x72, 0x65,
-	0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x6d, 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,
+	0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x61, 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, 0x87, 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, 0x50, 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, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f,
+	0x6e, 0x66, 0x69, 0x67, 0x73, 0x5f, 0x61, 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, 0x79,
+	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, 0x46, 0x0a,
+	0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x30, 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, 0x72, 0x65, 0x6c, 0x65,
+	0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x6d, 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 (
@@ -481,8 +513,8 @@
 	7,  // 1: android.release_config_proto.flag_artifact.flag_declaration:type_name -> android.release_config_proto.flag_declaration
 	6,  // 2: android.release_config_proto.flag_artifact.value:type_name -> android.release_config_proto.value
 	0,  // 3: android.release_config_proto.flag_artifact.traces:type_name -> android.release_config_proto.tracepoint
-	1,  // 4: android.release_config_proto.flag_artifacts.flag_artifacts:type_name -> android.release_config_proto.flag_artifact
-	1,  // 5: android.release_config_proto.release_config_artifact.flag_artifacts:type_name -> android.release_config_proto.flag_artifact
+	1,  // 4: android.release_config_proto.flag_artifacts.flags:type_name -> android.release_config_proto.flag_artifact
+	1,  // 5: android.release_config_proto.release_config_artifact.flags:type_name -> android.release_config_proto.flag_artifact
 	3,  // 6: android.release_config_proto.release_configs_artifact.release_config:type_name -> android.release_config_proto.release_config_artifact
 	3,  // 7: android.release_config_proto.release_configs_artifact.other_release_configs:type_name -> android.release_config_proto.release_config_artifact
 	5,  // 8: android.release_config_proto.release_configs_artifact.release_config_maps_map:type_name -> android.release_config_proto.release_configs_artifact.ReleaseConfigMapsMapEntry
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 6f34d6f..4dc84e9 100644
--- a/cmd/release_config/release_config_proto/build_flags_out.proto
+++ b/cmd/release_config/release_config_proto/build_flags_out.proto
@@ -1,3 +1,7 @@
+//
+// 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
 //
@@ -54,7 +58,8 @@
 
 message flag_artifacts {
   // The artifacts
-  repeated flag_artifact flag_artifacts = 1;
+  repeated flag_artifact flags = 1;
+  reserved "flag_artifacts";
 }
 
 message release_config_artifact {
@@ -67,7 +72,8 @@
 
   // The complete set of build flags in this release config, after all
   // inheritance and other processing is complete.
-  repeated flag_artifact flag_artifacts = 3;
+  repeated flag_artifact flags = 3;
+  reserved "flag_artifacts";
 
   // The (complete) list of aconfig_value_sets Soong modules to use.
   repeated string aconfig_value_sets = 4;
@@ -76,9 +82,20 @@
   // Included for reference only.
   repeated string inherits = 5;
 
-  // The release config directories used for this config.
+  // The release config directories used for this config.  This includes
+  // directories that provide flag declarations, but do not provide any flag
+  // values specific to this release config.
   // For example, "build/release".
   repeated string directories = 6;
+
+  // 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 = 7;
+
+  // The release config directories that contribute directly to this release
+  // config.  The listed directories contain at least a `release_config` message
+  // for this release config.
+  repeated string value_directories = 8;
 }
 
 message release_configs_artifact {
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 ca2005c..bc5f5c0 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
@@ -1,3 +1,7 @@
+//
+// 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
 //
@@ -11,7 +15,7 @@
 
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.30.0
+// 	protoc-gen-go v1.33.0
 // 	protoc        v3.21.12
 // source: build_flags_src.proto
 
@@ -31,143 +35,6 @@
 	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
 )
 
-type Workflow int32
-
-const (
-	Workflow_UNSPECIFIED_workflow Workflow = 0
-	// Boolean value flags that progress from false to true.
-	Workflow_LAUNCH Workflow = 1
-	// String value flags that get updated with new version strings to control
-	// prebuilt inclusion.
-	Workflow_PREBUILT Workflow = 2
-	// Manually managed outside flags.  These are likely to be found in a
-	// different directory than flags with other workflows.
-	Workflow_MANUAL Workflow = 3
-)
-
-// Enum value maps for Workflow.
-var (
-	Workflow_name = map[int32]string{
-		0: "UNSPECIFIED_workflow",
-		1: "LAUNCH",
-		2: "PREBUILT",
-		3: "MANUAL",
-	}
-	Workflow_value = map[string]int32{
-		"UNSPECIFIED_workflow": 0,
-		"LAUNCH":               1,
-		"PREBUILT":             2,
-		"MANUAL":               3,
-	}
-)
-
-func (x Workflow) Enum() *Workflow {
-	p := new(Workflow)
-	*p = x
-	return p
-}
-
-func (x Workflow) String() string {
-	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
-}
-
-func (Workflow) Descriptor() protoreflect.EnumDescriptor {
-	return file_build_flags_src_proto_enumTypes[0].Descriptor()
-}
-
-func (Workflow) Type() protoreflect.EnumType {
-	return &file_build_flags_src_proto_enumTypes[0]
-}
-
-func (x Workflow) Number() protoreflect.EnumNumber {
-	return protoreflect.EnumNumber(x)
-}
-
-// Deprecated: Do not use.
-func (x *Workflow) UnmarshalJSON(b []byte) error {
-	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
-	if err != nil {
-		return err
-	}
-	*x = Workflow(num)
-	return nil
-}
-
-// Deprecated: Use Workflow.Descriptor instead.
-func (Workflow) EnumDescriptor() ([]byte, []int) {
-	return file_build_flags_src_proto_rawDescGZIP(), []int{0}
-}
-
-type Container int32
-
-const (
-	Container_UNSPECIFIED_container Container = 0
-	// All containers
-	Container_ALL Container = 1
-	// Specific containers
-	Container_PRODUCT    Container = 2
-	Container_SYSTEM     Container = 3
-	Container_SYSTEM_EXT Container = 4
-	Container_VENDOR     Container = 5
-)
-
-// Enum value maps for Container.
-var (
-	Container_name = map[int32]string{
-		0: "UNSPECIFIED_container",
-		1: "ALL",
-		2: "PRODUCT",
-		3: "SYSTEM",
-		4: "SYSTEM_EXT",
-		5: "VENDOR",
-	}
-	Container_value = map[string]int32{
-		"UNSPECIFIED_container": 0,
-		"ALL":                   1,
-		"PRODUCT":               2,
-		"SYSTEM":                3,
-		"SYSTEM_EXT":            4,
-		"VENDOR":                5,
-	}
-)
-
-func (x Container) Enum() *Container {
-	p := new(Container)
-	*p = x
-	return p
-}
-
-func (x Container) String() string {
-	return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
-}
-
-func (Container) Descriptor() protoreflect.EnumDescriptor {
-	return file_build_flags_src_proto_enumTypes[1].Descriptor()
-}
-
-func (Container) Type() protoreflect.EnumType {
-	return &file_build_flags_src_proto_enumTypes[1]
-}
-
-func (x Container) Number() protoreflect.EnumNumber {
-	return protoreflect.EnumNumber(x)
-}
-
-// Deprecated: Do not use.
-func (x *Container) UnmarshalJSON(b []byte) error {
-	num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
-	if err != nil {
-		return err
-	}
-	*x = Container(num)
-	return nil
-}
-
-// Deprecated: Use Container.Descriptor instead.
-func (Container) EnumDescriptor() ([]byte, []int) {
-	return file_build_flags_src_proto_rawDescGZIP(), []int{1}
-}
-
 type Value struct {
 	state         protoimpl.MessageState
 	sizeCache     protoimpl.SizeCache
@@ -298,7 +165,7 @@
 	Workflow *Workflow `protobuf:"varint,205,opt,name=workflow,enum=android.release_config_proto.Workflow" json:"workflow,omitempty"`
 	// The container for this flag.  This overrides any default container given
 	// in the release_config_map message.
-	Container *Container `protobuf:"varint,206,opt,name=container,enum=android.release_config_proto.Container" json:"container,omitempty"`
+	Containers []string `protobuf:"bytes,206,rep,name=containers" json:"containers,omitempty"`
 }
 
 func (x *FlagDeclaration) Reset() {
@@ -365,14 +232,14 @@
 	if x != nil && x.Workflow != nil {
 		return *x.Workflow
 	}
-	return Workflow_UNSPECIFIED_workflow
+	return Workflow_Workflow_Unspecified
 }
 
-func (x *FlagDeclaration) GetContainer() Container {
-	if x != nil && x.Container != nil {
-		return *x.Container
+func (x *FlagDeclaration) GetContainers() []string {
+	if x != nil {
+		return x.Containers
 	}
-	return Container_UNSPECIFIED_container
+	return nil
 }
 
 type FlagValue struct {
@@ -457,6 +324,11 @@
 	// List of names of the aconfig_value_set soong module(s) for this
 	// contribution.
 	AconfigValueSets []string `protobuf:"bytes,3,rep,name=aconfig_value_sets,json=aconfigValueSets" json:"aconfig_value_sets,omitempty"`
+	// Only aconfig flags are allowed in this release config.
+	AconfigFlagsOnly *bool `protobuf:"varint,4,opt,name=aconfig_flags_only,json=aconfigFlagsOnly" json:"aconfig_flags_only,omitempty"`
+	// 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"`
 }
 
 func (x *ReleaseConfig) Reset() {
@@ -512,6 +384,20 @@
 	return nil
 }
 
+func (x *ReleaseConfig) GetAconfigFlagsOnly() bool {
+	if x != nil && x.AconfigFlagsOnly != nil {
+		return *x.AconfigFlagsOnly
+	}
+	return false
+}
+
+func (x *ReleaseConfig) GetPriorStages() []string {
+	if x != nil {
+		return x.PriorStages
+	}
+	return nil
+}
+
 // Any aliases.  These are used for continuous integration builder config.
 type ReleaseAlias struct {
 	state         protoimpl.MessageState
@@ -581,7 +467,7 @@
 	// Description of this map and its intended use.
 	Description *string `protobuf:"bytes,2,opt,name=description" json:"description,omitempty"`
 	// The default container for flags declared here.
-	DefaultContainer *Container `protobuf:"varint,3,opt,name=default_container,json=defaultContainer,enum=android.release_config_proto.Container" json:"default_container,omitempty"`
+	DefaultContainers []string `protobuf:"bytes,3,rep,name=default_containers,json=defaultContainers" json:"default_containers,omitempty"`
 }
 
 func (x *ReleaseConfigMap) Reset() {
@@ -630,11 +516,11 @@
 	return ""
 }
 
-func (x *ReleaseConfigMap) GetDefaultContainer() Container {
-	if x != nil && x.DefaultContainer != nil {
-		return *x.DefaultContainer
+func (x *ReleaseConfigMap) GetDefaultContainers() []string {
+	if x != nil {
+		return x.DefaultContainers
 	}
-	return Container_UNSPECIFIED_container
+	return nil
 }
 
 var File_build_flags_src_proto protoreflect.FileDescriptor
@@ -643,84 +529,74 @@
 	0x0a, 0x15, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x5f, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x5f, 0x73, 0x72,
 	0x63, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 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, 0x22, 0xa5, 0x01, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12,
-	0x2e, 0x0a, 0x11, 0x75, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x76,
-	0x61, 0x6c, 0x75, 0x65, 0x18, 0xc8, 0x01, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x10, 0x75,
-	0x6e, 0x73, 0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12,
-	0x24, 0x0a, 0x0c, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18,
-	0xc9, 0x01, 0x20, 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67,
-	0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x20, 0x0a, 0x0a, 0x62, 0x6f, 0x6f, 0x6c, 0x5f, 0x76, 0x61,
-	0x6c, 0x75, 0x65, 0x18, 0xca, 0x01, 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, 0xbd, 0x02,
-	0x0a, 0x10, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x64, 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, 0x76, 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, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x52, 0x08, 0x77,
-	0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x46, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61,
-	0x69, 0x6e, 0x65, 0x72, 0x18, 0xce, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x27, 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, 0x63, 0x6f, 0x6e, 0x74, 0x61,
-	0x69, 0x6e, 0x65, 0x72, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x4a,
-	0x04, 0x08, 0x04, 0x10, 0x05, 0x4a, 0x06, 0x08, 0xcf, 0x01, 0x10, 0xd0, 0x01, 0x22, 0x79, 0x0a,
-	0x0a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x76, 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, 0x76,
-	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, 0x6e, 0x0a, 0x0e, 0x72, 0x65, 0x6c, 0x65,
-	0x61, 0x73, 0x65, 0x5f, 0x63, 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, 0x22, 0x3b, 0x0a, 0x0d, 0x72, 0x65, 0x6c, 0x65,
-	0x61, 0x73, 0x65, 0x5f, 0x61, 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, 0xd3, 0x01, 0x0a, 0x12, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73,
-	0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x6d, 0x61, 0x70, 0x12, 0x45, 0x0a, 0x07,
-	0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 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, 0x72, 0x65, 0x6c,
-	0x65, 0x61, 0x73, 0x65, 0x5f, 0x61, 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, 0x54, 0x0a, 0x11, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74,
-	0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e,
-	0x32, 0x27, 0x2e, 0x61, 0x6e, 0x64, 0x72, 0x6f, 0x69, 0x64, 0x2e, 0x72, 0x65, 0x6c, 0x65, 0x61,
+	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,
+	0xa5, 0x01, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x2e, 0x0a, 0x11, 0x75, 0x6e, 0x73,
+	0x70, 0x65, 0x63, 0x69, 0x66, 0x69, 0x65, 0x64, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0xc8,
+	0x01, 0x20, 0x01, 0x28, 0x08, 0x48, 0x00, 0x52, 0x10, 0x75, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x69,
+	0x66, 0x69, 0x65, 0x64, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x24, 0x0a, 0x0c, 0x73, 0x74, 0x72,
+	0x69, 0x6e, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0xc9, 0x01, 0x20, 0x01, 0x28, 0x09,
+	0x48, 0x00, 0x52, 0x0b, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12,
+	0x20, 0x0a, 0x0a, 0x62, 0x6f, 0x6f, 0x6c, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0xca, 0x01,
+	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, 0x96, 0x02, 0x0a, 0x10, 0x66, 0x6c, 0x61, 0x67,
+	0x5f, 0x64, 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,
-	0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x52, 0x10, 0x64, 0x65, 0x66, 0x61, 0x75,
-	0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x2a, 0x4a, 0x0a, 0x08, 0x77,
-	0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x12, 0x18, 0x0a, 0x14, 0x55, 0x4e, 0x53, 0x50, 0x45,
-	0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x5f, 0x77, 0x6f, 0x72, 0x6b, 0x66, 0x6c, 0x6f, 0x77, 0x10,
-	0x00, 0x12, 0x0a, 0x0a, 0x06, 0x4c, 0x41, 0x55, 0x4e, 0x43, 0x48, 0x10, 0x01, 0x12, 0x0c, 0x0a,
-	0x08, 0x50, 0x52, 0x45, 0x42, 0x55, 0x49, 0x4c, 0x54, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x4d,
-	0x41, 0x4e, 0x55, 0x41, 0x4c, 0x10, 0x03, 0x2a, 0x64, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61,
-	0x69, 0x6e, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x15, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46,
-	0x49, 0x45, 0x44, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x10, 0x00, 0x12,
-	0x07, 0x0a, 0x03, 0x41, 0x4c, 0x4c, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x52, 0x4f, 0x44,
-	0x55, 0x43, 0x54, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x10,
-	0x03, 0x12, 0x0e, 0x0a, 0x0a, 0x53, 0x59, 0x53, 0x54, 0x45, 0x4d, 0x5f, 0x45, 0x58, 0x54, 0x10,
-	0x04, 0x12, 0x0a, 0x0a, 0x06, 0x56, 0x45, 0x4e, 0x44, 0x4f, 0x52, 0x10, 0x05, 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,
+	0x76, 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, 0x77,
+	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, 0x79, 0x0a, 0x0a, 0x66, 0x6c, 0x61, 0x67, 0x5f, 0x76, 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, 0x76, 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, 0xbf, 0x01, 0x0a, 0x0e,
+	0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 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, 0x3b, 0x0a,
+	0x0d, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x61, 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, 0xac, 0x01, 0x0a, 0x12, 0x72,
+	0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x6d, 0x61,
+	0x70, 0x12, 0x45, 0x0a, 0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03,
+	0x28, 0x0b, 0x32, 0x2b, 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, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x52,
+	0x07, 0x61, 0x6c, 0x69, 0x61, 0x73, 0x65, 0x73, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63,
+	0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64,
+	0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x2d, 0x0a, 0x12, 0x64, 0x65,
+	0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73,
+	0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x11, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x43,
+	0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x42, 0x33, 0x5a, 0x31, 0x61, 0x6e, 0x64,
+	0x72, 0x6f, 0x69, 0x64, 0x2f, 0x73, 0x6f, 0x6f, 0x6e, 0x67, 0x2f, 0x72, 0x65, 0x6c, 0x65, 0x61,
+	0x73, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x72, 0x65, 0x6c, 0x65, 0x61, 0x73,
+	0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f,
 }
 
 var (
@@ -735,30 +611,26 @@
 	return file_build_flags_src_proto_rawDescData
 }
 
-var file_build_flags_src_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
 var file_build_flags_src_proto_msgTypes = make([]protoimpl.MessageInfo, 6)
 var file_build_flags_src_proto_goTypes = []interface{}{
-	(Workflow)(0),            // 0: android.release_config_proto.workflow
-	(Container)(0),           // 1: android.release_config_proto.container
-	(*Value)(nil),            // 2: android.release_config_proto.value
-	(*FlagDeclaration)(nil),  // 3: android.release_config_proto.flag_declaration
-	(*FlagValue)(nil),        // 4: android.release_config_proto.flag_value
-	(*ReleaseConfig)(nil),    // 5: android.release_config_proto.release_config
-	(*ReleaseAlias)(nil),     // 6: android.release_config_proto.release_alias
-	(*ReleaseConfigMap)(nil), // 7: android.release_config_proto.release_config_map
+	(*Value)(nil),            // 0: android.release_config_proto.value
+	(*FlagDeclaration)(nil),  // 1: android.release_config_proto.flag_declaration
+	(*FlagValue)(nil),        // 2: android.release_config_proto.flag_value
+	(*ReleaseConfig)(nil),    // 3: android.release_config_proto.release_config
+	(*ReleaseAlias)(nil),     // 4: android.release_config_proto.release_alias
+	(*ReleaseConfigMap)(nil), // 5: android.release_config_proto.release_config_map
+	(Workflow)(0),            // 6: android.release_config_proto.workflow
 }
 var file_build_flags_src_proto_depIdxs = []int32{
-	2, // 0: android.release_config_proto.flag_declaration.value:type_name -> android.release_config_proto.value
-	0, // 1: android.release_config_proto.flag_declaration.workflow:type_name -> android.release_config_proto.workflow
-	1, // 2: android.release_config_proto.flag_declaration.container:type_name -> android.release_config_proto.container
-	2, // 3: android.release_config_proto.flag_value.value:type_name -> android.release_config_proto.value
-	6, // 4: android.release_config_proto.release_config_map.aliases:type_name -> android.release_config_proto.release_alias
-	1, // 5: android.release_config_proto.release_config_map.default_container:type_name -> android.release_config_proto.container
-	6, // [6:6] is the sub-list for method output_type
-	6, // [6:6] is the sub-list for method input_type
-	6, // [6:6] is the sub-list for extension type_name
-	6, // [6:6] is the sub-list for extension extendee
-	0, // [0:6] is the sub-list for field type_name
+	0, // 0: android.release_config_proto.flag_declaration.value:type_name -> android.release_config_proto.value
+	6, // 1: android.release_config_proto.flag_declaration.workflow:type_name -> android.release_config_proto.workflow
+	0, // 2: android.release_config_proto.flag_value.value:type_name -> android.release_config_proto.value
+	4, // 3: android.release_config_proto.release_config_map.aliases:type_name -> android.release_config_proto.release_alias
+	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
 }
 
 func init() { file_build_flags_src_proto_init() }
@@ -766,6 +638,7 @@
 	if File_build_flags_src_proto != nil {
 		return
 	}
+	file_build_flags_common_proto_init()
 	if !protoimpl.UnsafeEnabled {
 		file_build_flags_src_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
 			switch v := v.(*Value); i {
@@ -851,14 +724,13 @@
 		File: protoimpl.DescBuilder{
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_build_flags_src_proto_rawDesc,
-			NumEnums:      2,
+			NumEnums:      0,
 			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 92edc2a..4fad478 100644
--- a/cmd/release_config/release_config_proto/build_flags_src.proto
+++ b/cmd/release_config/release_config_proto/build_flags_src.proto
@@ -1,3 +1,7 @@
+//
+// 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
 //
@@ -13,6 +17,8 @@
 package android.release_config_proto;
 option go_package = "android/soong/release_config/release_config_proto";
 
+import "build_flags_common.proto";
+
 // This protobuf file defines messages used to represent the build flags used by
 // a release in a more human-editable form.  It is used for on-disk files in the
 // source tree.
@@ -38,34 +44,6 @@
 //      com.android.mypackage is a valid name while com.android.myPackage,
 //      com.android.1mypackage are invalid
 
-enum workflow {
-  UNSPECIFIED_workflow = 0;
-
-  // Boolean value flags that progress from false to true.
-  LAUNCH = 1;
-
-  // String value flags that get updated with new version strings to control
-  // prebuilt inclusion.
-  PREBUILT = 2;
-
-  // Manually managed outside flags.  These are likely to be found in a
-  // different directory than flags with other workflows.
-  MANUAL = 3;
-}
-
-enum container {
-  UNSPECIFIED_container = 0;
-
-  // All containers
-  ALL = 1;
-
-  // Specific containers
-  PRODUCT = 2;
-  SYSTEM = 3;
-  SYSTEM_EXT = 4;
-  VENDOR = 5;
-}
-
 message value {
   oneof val {
     bool unspecified_value = 200;
@@ -100,7 +78,7 @@
 
   // The container for this flag.  This overrides any default container given
   // in the release_config_map message.
-  optional container container = 206;
+  repeated string containers = 206;
 
   // The package associated with this flag.
   // (when Gantry is ready for it) optional string package = 207;
@@ -132,6 +110,13 @@
   // List of names of the aconfig_value_set soong module(s) for this
   // contribution.
   repeated string aconfig_value_sets = 3;
+
+  // Only aconfig flags are allowed in this release config.
+  optional bool aconfig_flags_only = 4;
+
+  // 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;
 }
 
 // Any aliases.  These are used for continuous integration builder config.
@@ -152,7 +137,7 @@
   optional string description = 2;
 
   // The default container for flags declared here.
-  optional container default_container = 3;
+  repeated string default_containers = 3;
 
   // If needed, we can add these fields instead of hardcoding the location.
   // Flag declarations: `flag_declarations/*.textproto`
diff --git a/cmd/release_config/release_config_proto/regen.sh b/cmd/release_config/release_config_proto/regen.sh
index 1846c4d..23e3115 100644
--- a/cmd/release_config/release_config_proto/regen.sh
+++ b/cmd/release_config/release_config_proto/regen.sh
@@ -1,3 +1,3 @@
 #!/bin/bash
 
-aprotoc --go_out=paths=source_relative:. build_flags_src.proto build_flags_out.proto
+aprotoc --go_out=paths=source_relative:. build_flags_src.proto build_flags_out.proto build_flags_common.proto build_flags_declarations.proto
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index d64010e..a8be7ec 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -16,6 +16,7 @@
 
 import (
 	"bytes"
+	"encoding/json"
 	"errors"
 	"flag"
 	"fmt"
@@ -28,11 +29,11 @@
 	"android/soong/android/allowlists"
 	"android/soong/bp2build"
 	"android/soong/shared"
-
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/bootstrap"
 	"github.com/google/blueprint/deptools"
 	"github.com/google/blueprint/metrics"
+	"github.com/google/blueprint/proptools"
 	androidProtobuf "google.golang.org/protobuf/android"
 )
 
@@ -49,6 +50,14 @@
 	cmdlineArgs android.CmdArgs
 )
 
+const configCacheFile = "config.cache"
+
+type ConfigCache struct {
+	EnvDepsHash                  uint64
+	ProductVariableFileTimestamp int64
+	SoongBuildFileTimestamp      int64
+}
+
 func init() {
 	// Flags that make sense in every mode
 	flag.StringVar(&topDir, "top", "", "Top directory of the Android source tree")
@@ -82,6 +91,7 @@
 	// Flags that probably shouldn't be flags of soong_build, but we haven't found
 	// the time to remove them yet
 	flag.BoolVar(&cmdlineArgs.RunGoTests, "t", false, "build and run go tests during bootstrap")
+	flag.BoolVar(&cmdlineArgs.IncrementalBuildActions, "incremental-build-actions", false, "generate build actions incrementally")
 
 	// Disable deterministic randomization in the protobuf package, so incremental
 	// builds with unrelated Soong changes don't trigger large rebuilds (since we
@@ -98,7 +108,6 @@
 	ctx := android.NewContext(configuration)
 	ctx.SetNameInterface(newNameResolver(configuration))
 	ctx.SetAllowMissingDependencies(configuration.AllowMissingDependencies())
-	ctx.AddIncludeTags(configuration.IncludeTags()...)
 	ctx.AddSourceRootDirs(configuration.SourceRootDirs()...)
 	return ctx
 }
@@ -108,7 +117,7 @@
 	case "always":
 		return true
 	case "depend":
-		if _, err := os.Stat(filepath.Join(ctx.Config().OutDir(), ".ninja_log")); errors.Is(err, os.ErrNotExist) {
+		if _, err := os.Stat(filepath.Join(topDir, ctx.Config().OutDir(), ".ninja_log")); errors.Is(err, os.ErrNotExist) {
 			return true
 		}
 	}
@@ -219,6 +228,60 @@
 	maybeQuit(err, "error writing depfile '%s'", depFile)
 }
 
+// Check if there are changes to the environment file, product variable file and
+// soong_build binary, in which case no incremental will be performed.
+func incrementalValid(config android.Config, configCacheFile string) (*ConfigCache, bool) {
+	var newConfigCache ConfigCache
+	data, err := os.ReadFile(shared.JoinPath(topDir, usedEnvFile))
+	if err != nil {
+		// Clean build
+		if os.IsNotExist(err) {
+			data = []byte{}
+		} else {
+			maybeQuit(err, "")
+		}
+	}
+
+	newConfigCache.EnvDepsHash, err = proptools.CalculateHash(data)
+	newConfigCache.ProductVariableFileTimestamp = getFileTimestamp(filepath.Join(topDir, cmdlineArgs.SoongVariables))
+	newConfigCache.SoongBuildFileTimestamp = getFileTimestamp(filepath.Join(topDir, config.HostToolDir(), "soong_build"))
+	//TODO(b/344917959): out/soong/dexpreopt.config might need to be checked as well.
+
+	file, err := os.Open(configCacheFile)
+	if err != nil && os.IsNotExist(err) {
+		return &newConfigCache, false
+	}
+	maybeQuit(err, "")
+	defer file.Close()
+
+	var configCache ConfigCache
+	decoder := json.NewDecoder(file)
+	err = decoder.Decode(&configCache)
+	maybeQuit(err, "")
+
+	return &newConfigCache, newConfigCache == configCache
+}
+
+func getFileTimestamp(file string) int64 {
+	stat, err := os.Stat(file)
+	if err == nil {
+		return stat.ModTime().UnixMilli()
+	} else if !os.IsNotExist(err) {
+		maybeQuit(err, "")
+	}
+	return 0
+}
+
+func writeConfigCache(configCache *ConfigCache, configCacheFile string) {
+	file, err := os.Create(configCacheFile)
+	maybeQuit(err, "")
+	defer file.Close()
+
+	encoder := json.NewEncoder(file)
+	err = encoder.Encode(*configCache)
+	maybeQuit(err, "")
+}
+
 // runSoongOnlyBuild runs the standard Soong build in a number of different modes.
 func runSoongOnlyBuild(ctx *android.Context, extraNinjaDeps []string) string {
 	ctx.EventHandler.Begin("soong_build")
@@ -320,8 +383,26 @@
 	ctx := newContext(configuration)
 	android.StartBackgroundMetrics(configuration)
 
+	var configCache *ConfigCache
+	configFile := filepath.Join(topDir, ctx.Config().OutDir(), configCacheFile)
+	incremental := false
+	ctx.SetIncrementalEnabled(cmdlineArgs.IncrementalBuildActions)
+	if cmdlineArgs.IncrementalBuildActions {
+		configCache, incremental = incrementalValid(ctx.Config(), configFile)
+	}
+	ctx.SetIncrementalAnalysis(incremental)
+
 	ctx.Register()
 	finalOutputFile := runSoongOnlyBuild(ctx, extraNinjaDeps)
+
+	if ctx.GetIncrementalEnabled() {
+		data, err := shared.EnvFileContents(configuration.EnvDeps())
+		maybeQuit(err, "")
+		configCache.EnvDepsHash, err = proptools.CalculateHash(data)
+		maybeQuit(err, "")
+		writeConfigCache(configCache, configFile)
+	}
+
 	writeMetrics(configuration, ctx.EventHandler, metricsDir)
 
 	writeUsedEnvironmentFile(configuration)
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index fe3f8f7..2d3156a 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -155,7 +155,6 @@
 
 	// Create a new trace file writer, making it log events to the log instance.
 	trace := tracer.New(log)
-	defer trace.Close()
 
 	// Create a new Status instance, which manages action counts and event output channels.
 	stat := &status.Status{}
@@ -194,14 +193,29 @@
 	soongMetricsFile := filepath.Join(logsDir, c.logsPrefix+"soong_metrics")
 	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")
 
 	metricsFiles := []string{
 		buildErrorFile,        // build error strings
 		rbeMetricsFile,        // high level metrics related to remote build execution.
 		soongMetricsFile,      // high level metrics related to this build system.
 		soongBuildMetricsFile, // high level metrics related to soong build
+		buildTraceFile,
 	}
 
+	defer func() {
+		stat.Finish()
+		criticalPath.WriteToMetrics(met)
+		met.Dump(soongMetricsFile)
+		if !config.SkipMetricsUpload() {
+			build.UploadMetrics(buildCtx, config, c.simpleOutput, buildStarted, metricsFiles...)
+		}
+	}()
+
+	// This has to come after the metrics uploading function, so that
+	// build.trace.gz is closed and ready for upload.
+	defer trace.Close()
+
 	os.MkdirAll(logsDir, 0777)
 
 	log.SetOutput(filepath.Join(logsDir, c.logsPrefix+"soong.log"))
@@ -222,16 +236,7 @@
 		config = freshConfig()
 	}
 
-	defer func() {
-		stat.Finish()
-		criticalPath.WriteToMetrics(met)
-		met.Dump(soongMetricsFile)
-		if !config.SkipMetricsUpload() {
-			build.UploadMetrics(buildCtx, config, c.simpleOutput, buildStarted, metricsFiles...)
-		}
-	}()
 	c.run(buildCtx, config, args)
-
 }
 
 // This function must not modify config, since product config may cause us to recreate the config,
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index f3afb5d..5616483 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -531,12 +531,12 @@
 		return false
 	}
 
-	if contains(global.SpeedApps, name) || contains(global.SystemServerApps, name) {
+	if contains(global.SystemServerApps, name) {
 		return false
 	}
 
 	for _, f := range global.PatternsOnSystemOther {
-		if makefileMatch(filepath.Join(SystemPartition, f), dexLocation) {
+		if makefileMatch("/"+f, dexLocation) || makefileMatch(filepath.Join(SystemPartition, f), dexLocation) {
 			return true
 		}
 	}
diff --git a/dexpreopt/dexpreopt_test.go b/dexpreopt/dexpreopt_test.go
index eff2416..6f7d3bb 100644
--- a/dexpreopt/dexpreopt_test.go
+++ b/dexpreopt/dexpreopt_test.go
@@ -153,7 +153,7 @@
 			moduleTests: []moduleTest{
 				{module: systemModule, expectedPartition: "system_other/system"},
 				{module: systemProductModule, expectedPartition: "system_other/system/product"},
-				{module: productModule, expectedPartition: "product"},
+				{module: productModule, expectedPartition: "system_other/product"},
 			},
 		},
 	}
diff --git a/docs/java.dot b/docs/java.dot
new file mode 100644
index 0000000..ad7628d
--- /dev/null
+++ b/docs/java.dot
@@ -0,0 +1,127 @@
+digraph java {
+	//rankdir="LR";
+	//splines="false";
+	//cluster=true;
+	//node [ ordering="in" ];
+	node [ shape="rect" style="rounded" color="blue" ];
+
+	{
+		rank="same";
+		lib_java_sources [ label="library\njava sources" group="lib" ];
+		lib2_java_sources [ label="library\njava sources" group="lib2" ];
+		app_java_sources [ label="app\njava sources" group="app" ];
+	}
+
+	node [ group="lib"];
+	{
+		rank="same";
+		lib_java_classes [ label="library java\n.class files" ];
+		lib_java_headers [ label="library java\nheader .class files" ];
+	}
+
+	node [ group="lib2"];
+	{
+		rank="same";
+		lib_spacer [ style=invis width=4 ];
+		lib2_java_classes [ label="library java\n.class files" ];
+		lib2_java_headers [ label="library java\nheader .class files" ];
+	}
+	{
+		rank="same";
+		lib2_combined_classes [ label="combined library\n.class files" ];
+		lib2_combined_headers [ label="combined library\nheader .class files" ];
+	}
+
+	node [ group="app"];
+	{
+		rank="same";
+		lib2_spacer [ style=invis width=4 ];
+		app_java_classes [ label="app java\n.class files" ];
+	}
+	{
+		rank="same";
+		app_combined_classes [ label="combined app and library\n.class files" ];
+	}
+	{
+		rank="same";
+		app_dex [ label="app classes.dex files" ];
+	}
+
+
+	node [ shape="rect" style="" color="black" ];
+	node [ group="lib"];
+	{
+		rank="same";
+		lib_turbine_action [ label="turbine" ];
+		lib_javac_action [ label="javac" ];
+	}
+
+	node [ group="lib2"];
+	{
+		rank="same";
+		lib2_turbine_action [ label="turbine" ];
+		lib2_javac_action [ label="javac" ];
+	}
+	{
+		rank="same";
+		lib2_combine_action [ label="merge_zips" ];
+		lib2_combine_headers_action [ label="merge_zips" ];
+	}
+
+	node [ group="app"];
+	{
+		rank="same";
+		app_javac_action [ label="javac" ];
+	}
+	{
+		rank="same";
+		app_combine_action [ label="merge_zips" ];
+	}
+	{
+		rank="same";
+		app_r8_action [ label="r8" ];
+	}
+
+	// library
+
+	lib_java_sources -> lib_turbine_action [ weight=100 ];
+	lib_turbine_action -> lib_java_headers [ weight=100 ];
+
+	lib_java_sources -> lib_javac_action [ weight=1000 ];
+	lib_javac_action -> lib_java_classes [ weight=100 ];
+
+	lib_java_headers -> lib_spacer [ style=invis ];
+
+	// library 2
+
+	lib_java_headers -> lib2_turbine_action [ weight=0 ];
+	lib2_java_sources -> lib2_turbine_action [ weight=100 ];
+	lib2_turbine_action -> lib2_java_headers [ weight=100 ];
+
+	lib_java_headers -> lib2_javac_action [ weight=0 ];
+	lib2_java_sources -> lib2_javac_action [ weight=1000 ];
+	lib2_javac_action ->lib2_java_classes [ weight=100 ];
+
+	lib_java_classes -> lib2_combine_action [ weight=0 ];
+	lib2_java_classes -> lib2_combine_action [ weight=100 ];
+	lib2_combine_action -> lib2_combined_classes [ weight=100 ];
+
+	lib_java_headers -> lib2_combine_headers_action [ weight=0 ];
+	lib2_java_headers -> lib2_combine_headers_action [ weight=100 ];
+	lib2_combine_headers_action -> lib2_combined_headers [ weight=100 ];
+
+	lib2_combined_headers -> lib2_spacer [ style=invis ];
+
+	// app
+
+	lib2_combined_headers -> app_javac_action [ weight=0 ];
+	app_java_sources -> app_javac_action [ weight=1000 ];
+	app_javac_action -> app_java_classes [ weight=100 ];
+
+	lib2_combined_classes -> app_combine_action [ weight=0 ];
+	app_java_classes -> app_combine_action [ weight=100 ];
+	app_combine_action -> app_combined_classes [ weight=100 ];
+
+	app_combined_classes -> app_r8_action;
+	app_r8_action -> app_dex [ weight=100 ];
+}
diff --git a/docs/kotlin.dot b/docs/kotlin.dot
new file mode 100644
index 0000000..7a23c16
--- /dev/null
+++ b/docs/kotlin.dot
@@ -0,0 +1,196 @@
+digraph java {
+	//rankdir="LR";
+	//splines="false";
+	//cluster=true;
+	ranksep="0.75 equally"
+	//node [ ordering="in" ];
+	node [ shape="rect" style="rounded" color="blue" ];
+	{
+		rank="same";
+		lib_java_sources [ label="library\njava sources" group="lib" ];
+		lib_kotlin_sources [ label="library\nkotlin sources" group="lib" ];
+		lib2_java_sources [ label="library\njava sources" group="lib2" ];
+		lib2_kotlin_sources [ label="library\nkotlin sources" group="lib2" ];
+		app_java_sources [ label="app\njava sources" group="app" ];
+		app_kotlin_sources [ label="app\nkotlin sources" group="app" ];
+	}
+
+	node [ group="lib"];
+	{
+		rank="same";
+		lib_kotlin_classes [ label="library kotlin\n.class files" ];
+		lib_kotlin_headers [ label="library kotlin\nheader .class files" ];
+	}
+	{
+		rank="same";
+		lib_java_classes [ label="library java\n.class files" ];
+		lib_java_headers [ label="library java\nheader .class files" ];
+	}
+	{
+		rank="same";
+		lib_combined_classes [ label="combined library\n.class files" ];
+		lib_combined_headers [ label="combined library\nheader .class files" ];
+	}
+
+	node [ group="lib2"];
+	{
+		rank="same";
+		lib_spacer [ style=invis width=4 ];
+		lib2_kotlin_classes [ label="library kotlin\n.class files" ];
+		lib2_kotlin_headers [ label="library kotlin\nheader .class files" ];
+	}
+	{
+		rank="same";
+		lib2_java_classes [ label="library java\n.class files" ];
+		lib2_java_headers [ label="library java\nheader .class files" ];
+	}
+	{
+		rank="same";
+		lib2_combined_classes [ label="combined library\n.class files" ];
+		lib2_combined_headers [ label="combined library\nheader .class files" ];
+	}
+
+	node [ group="app"];
+	{
+		rank="same";
+		lib2_spacer [ style=invis width=4 ];
+		app_kotlin_classes [ label="app kotlin\n.class files" ];
+		app_kotlin_headers [ label="app kotlin\nheader .class files" ]	}
+	{
+		rank="same";
+		app_java_classes [ label="app java\n.class files" ];
+	}
+	{
+		rank="same";
+		app_combined_classes [ label="combined app and library\n.class files" ];
+	}
+	{
+		rank="same";
+		app_dex [ label="app classes.dex files" ];
+	}
+
+
+	node [ shape="rect" style="" color="black" ];
+	node [ group="lib"];
+	{
+		rank="same";
+		lib_kotlinc_action [ label="kotlinc" ];
+	}
+	{
+		rank="same";
+		lib_turbine_action [ label="turbine" ];
+		lib_javac_action [ label="javac" ];
+	}
+	{
+		rank="same";
+		lib_combine_action [ label="merge_zips" ];
+		lib_combine_headers_action [ label="merge_zips" ];
+	}
+
+	node [ group="lib2"];
+	{
+		rank="same";
+		lib2_kotlinc_action [ label="kotlinc" ];
+	}
+	{
+		rank="same";
+		lib2_turbine_action [ label="turbine" ];
+		lib2_javac_action [ label="javac" ];
+	}
+	{
+		rank="same";
+		lib2_combine_action [ label="merge_zips" ];
+		lib2_combine_headers_action [ label="merge_zips" ];
+	}
+
+	node [ group="app"];
+	{
+		rank="same";
+		app_kotlinc_action [ label="kotlinc" ];
+	}
+	{
+		rank="same";
+		app_javac_action [ label="javac" ];
+	}
+	{
+		rank="same";
+		app_combine_action [ label="merge_zips" ];
+	}
+	{
+		rank="same";
+		app_r8_action [ label="r8" ];
+	}
+
+	// library
+
+	lib_kotlin_sources -> lib_kotlinc_action [ weight=100 ];
+	lib_java_sources -> lib_kotlinc_action;
+	lib_kotlinc_action -> lib_kotlin_classes, lib_kotlin_headers [ weight=100 ];
+
+	lib_kotlin_headers -> lib_turbine_action [ weight=0 ];
+	lib_java_sources -> lib_turbine_action [ weight=100 ];
+	lib_turbine_action -> lib_java_headers [ weight=100 ];
+
+	lib_kotlin_headers -> lib_javac_action [ weight=0 ];
+	lib_java_sources -> lib_javac_action [ weight=1000 ];
+	lib_javac_action -> lib_java_classes [ weight=100 ];
+
+	lib_kotlin_classes -> lib_combine_action [ weight = 0 ];
+	lib_java_classes -> lib_combine_action [ weight = 100 ];
+	lib_combine_action -> lib_combined_classes [ weight=100 ];
+
+	lib_kotlin_headers -> lib_combine_headers_action [ weight = 0 ];
+	lib_java_headers -> lib_combine_headers_action [ weight = 100 ];
+	lib_combine_headers_action -> lib_combined_headers [ weight=100 ];
+
+	lib_combined_headers -> lib_spacer [ style=invis ];
+
+	// library 2
+
+	lib_combined_headers -> lib2_kotlinc_action [ weight=0 ];
+	lib2_kotlin_sources -> lib2_kotlinc_action [ weight=100 ];
+	lib2_java_sources  -> lib2_kotlinc_action;
+	lib2_kotlinc_action -> lib2_kotlin_classes, lib2_kotlin_headers [ weight=100 ];
+
+	lib_combined_headers -> lib2_turbine_action [ weight=0 ];
+	lib2_kotlin_headers -> lib2_turbine_action [ weight=0 ];
+	lib2_java_sources -> lib2_turbine_action [ weight=100 ];
+	lib2_turbine_action -> lib2_java_headers [ weight=100 ];
+
+	lib_combined_headers -> lib2_javac_action [ weight=0 ];
+	lib2_kotlin_headers -> lib2_javac_action [ weight=0 ];
+	lib2_java_sources -> lib2_javac_action [ weight=1000 ];
+	lib2_javac_action ->lib2_java_classes [ weight=100 ];
+
+	lib_combined_classes -> lib2_combine_action [ weight=0 ];
+	lib2_kotlin_classes -> lib2_combine_action [ weight=0 ];
+	lib2_java_classes -> lib2_combine_action [ weight=100 ];
+	lib2_combine_action -> lib2_combined_classes [ weight=100 ];
+
+	lib_combined_headers -> lib2_combine_headers_action [ weight=0 ];
+	lib2_kotlin_headers -> lib2_combine_headers_action [ weight=0 ];
+	lib2_java_headers -> lib2_combine_headers_action [ weight=100 ];
+	lib2_combine_headers_action -> lib2_combined_headers [ weight=100 ];
+
+	lib2_combined_headers -> lib2_spacer [ style=invis ];
+
+	// app
+
+	lib2_combined_headers -> app_kotlinc_action [ weight=0 ];
+	app_kotlin_sources -> app_kotlinc_action [ weight=100 ];
+	app_java_sources -> app_kotlinc_action;
+	app_kotlinc_action -> app_kotlin_headers, app_kotlin_classes [ weight=100 ];
+
+	lib2_combined_headers -> app_javac_action [ weight=0 ];
+	app_kotlin_headers -> app_javac_action [ weight=0 ];
+	app_java_sources -> app_javac_action [ weight=1000 ];
+	app_javac_action -> app_java_classes [ weight=100 ];
+
+	lib2_combined_classes -> app_combine_action [ weight=0 ];
+	app_kotlin_classes -> app_combine_action [ weight=0 ];
+	app_java_classes -> app_combine_action [ weight=100 ];
+	app_combine_action -> app_combined_classes [ weight=100 ];
+
+	app_combined_classes -> app_r8_action;
+	app_r8_action -> app_dex [ weight=100 ];
+}
diff --git a/docs/kotlin_with_annotation_processors.dot b/docs/kotlin_with_annotation_processors.dot
new file mode 100644
index 0000000..70c9bf3
--- /dev/null
+++ b/docs/kotlin_with_annotation_processors.dot
@@ -0,0 +1,277 @@
+digraph java {
+	//rankdir="LR";
+	//splines="false";
+	//cluster=true;
+	ranksep="0.75 equally"
+	//node [ ordering="in" ];
+	node [ shape="rect" style="rounded" color="blue" ];
+	{
+		rank="same";
+		lib_java_sources [ label="library\njava sources" group="lib" ];
+		lib_kotlin_sources [ label="library\nkotlin sources" group="lib" ];
+		lib2_java_sources [ label="library\njava sources" group="lib2" ];
+		lib2_kotlin_sources [ label="library\nkotlin sources" group="lib2" ];
+		app_java_sources [ label="app\njava sources" group="app" ];
+		app_kotlin_sources [ label="app\nkotlin sources" group="app" ];
+	}
+
+	node [ group="lib"];
+	{
+		rank="same";
+		lib_kotlin_stubs [ label="library\nkotlin stubs" ];
+	}
+	{
+		rank="same";
+		lib_apt_src_jar [ label="library annotation\nprocessor sources" ];
+	}
+	{
+		rank="same";
+		lib_kotlin_classes [ label="library kotlin\n.class files" ];
+		lib_kotlin_headers [ label="library kotlin\nheader .class files" ];
+	}
+	{
+		rank="same";
+		lib_java_classes [ label="library java\n.class files" ];
+		lib_java_headers [ label="library java\nheader .class files" ];
+	}
+	{
+		rank="same";
+		lib_combined_classes [ label="combined library\n.class files" ];
+		lib_combined_headers [ label="combined library\nheader .class files" ];
+	}
+
+	node [ group="lib2"];
+	{
+		rank="same";
+		lib_spacer [ style=invis width=4 ];
+		lib2_kotlin_stubs [ label="library\nkotlin stubs" ];
+	}
+	{
+		rank="same";
+		lib2_apt_src_jar [ label="library annotation\nprocessor sources" ];
+	}
+	{
+		rank="same";
+		lib2_kotlin_classes [ label="library kotlin\n.class files" ];
+		lib2_kotlin_headers [ label="library kotlin\nheader .class files" ];
+	}
+	{
+		rank="same";
+		lib2_java_classes [ label="library java\n.class files" ];
+		lib2_java_headers [ label="library java\nheader .class files" ];
+	}
+	{
+		rank="same";
+		lib2_combined_classes [ label="combined library\n.class files" ];
+		lib2_combined_headers [ label="combined library\nheader .class files" ];
+	}
+
+	node [ group="app"];
+	{
+		rank="same";
+		lib2_spacer [ style=invis width=4 ];
+		app_kotlin_stubs [ label="app\nkotlin stubs" ];
+	}
+	{
+		rank="same";
+		app_apt_src_jar [ label="app annotation\nprocessor sources" ];
+	}
+	{
+		rank="same";
+		app_kotlin_classes [ label="app kotlin\n.class files" ];
+		app_kotlin_headers [ label="app kotlin\nheader .class files" ]	}
+	{
+		rank="same";
+		app_java_classes [ label="app java\n.class files" ];
+	}
+	{
+		rank="same";
+		app_combined_classes [ label="combined app and library\n.class files" ];
+	}
+	{
+		rank="same";
+		app_dex [ label="app classes.dex files" ];
+	}
+
+
+	node [ shape="rect" style="" color="black" ];
+	node [ group="lib"];
+	{
+		rank="same";
+		lib_kapt_action [ label="kapt" ];
+	}
+	{
+		rank="same";
+		lib_turbine_apt_action [ label="turbine apt" ];
+	}
+	{
+		rank="same";
+		lib_kotlinc_action [ label="kotlinc" ];
+	}
+	{
+		rank="same";
+		lib_turbine_action [ label="turbine" ];
+		lib_javac_action [ label="javac" ];
+	}
+	{
+		rank="same";
+		lib_combine_action [ label="merge_zips" ];
+		lib_combine_headers_action [ label="merge_zips" ];
+	}
+
+	node [ group="lib2"];
+	{
+		rank="same";
+		lib2_kapt_action [ label="kapt" ];
+	}
+	{
+		rank="same";
+		lib2_turbine_apt_action [ label="turbine apt" ];
+	}
+	{
+		rank="same";
+		lib2_kotlinc_action [ label="kotlinc" ];
+	}
+	{
+		rank="same";
+		lib2_turbine_action [ label="turbine" ];
+		lib2_javac_action [ label="javac" ];
+	}
+	{
+		rank="same";
+		lib2_combine_action [ label="merge_zips" ];
+		lib2_combine_headers_action [ label="merge_zips" ];
+	}
+
+	node [ group="app"];
+	{
+		rank="same";
+		app_kapt_action [ label="kapt" ];
+	}
+	{
+		rank="same";
+		app_turbine_apt_action [ label="turbine apt" ];
+	}
+	{
+		rank="same";
+		app_kotlinc_action [ label="kotlinc" ];
+	}
+	{
+		rank="same";
+		app_javac_action [ label="javac" ];
+	}
+	{
+		rank="same";
+		app_combine_action [ label="merge_zips" ];
+	}
+	{
+		rank="same";
+		app_r8_action [ label="r8" ];
+	}
+
+	// library
+
+	lib_kotlin_sources -> lib_kapt_action [ weight=0 ];
+	lib_java_sources -> lib_kapt_action;
+	lib_kapt_action -> lib_kotlin_stubs [ weight=100 ];
+
+	lib_kotlin_stubs -> lib_turbine_apt_action [ weight=100 ];
+	lib_turbine_apt_action -> lib_apt_src_jar [ weight=100 ];
+
+	lib_apt_src_jar -> lib_kotlinc_action [ weight=0 ];
+	lib_kotlin_sources -> lib_kotlinc_action [ weight=100 ];
+	lib_java_sources -> lib_kotlinc_action;
+	lib_kotlinc_action -> lib_kotlin_classes, lib_kotlin_headers [ weight=100 ];
+
+	lib_apt_src_jar -> lib_turbine_action [ weight=0 ];
+	lib_kotlin_headers -> lib_turbine_action [ weight=0 ];
+	lib_java_sources -> lib_turbine_action [ weight=100 ];
+	lib_turbine_action -> lib_java_headers [ weight=100 ];
+
+	lib_apt_src_jar -> lib_javac_action [ weight=0 ];
+	lib_kotlin_headers -> lib_javac_action [ weight=0 ];
+	lib_java_sources -> lib_javac_action [ weight=1000 ];
+	lib_javac_action -> lib_java_classes [ weight=100 ];
+
+	lib_kotlin_classes -> lib_combine_action [ weight = 0 ];
+	lib_java_classes -> lib_combine_action [ weight = 100 ];
+	lib_combine_action -> lib_combined_classes [ weight=100 ];
+
+	lib_kotlin_headers -> lib_combine_headers_action [ weight = 0 ];
+	lib_java_headers -> lib_combine_headers_action [ weight = 100 ];
+	lib_combine_headers_action -> lib_combined_headers [ weight=100 ];
+
+	lib_combined_headers -> lib_spacer [ style=invis ];
+
+	// library 2
+
+	lib_combined_headers -> lib2_kapt_action [ weight=0 ];
+	lib2_kotlin_sources -> lib2_kapt_action [ weight=0 ];
+	lib2_java_sources -> lib2_kapt_action;
+	lib2_kapt_action -> lib2_kotlin_stubs [ weight=100 ];
+
+	lib_combined_headers -> lib2_turbine_apt_action [ weight=0 ];
+	lib2_kotlin_stubs -> lib2_turbine_apt_action [ weight=100 ];
+	lib2_turbine_apt_action -> lib2_apt_src_jar [ weight=100 ];
+
+	lib_combined_headers -> lib2_kotlinc_action [ weight=0 ];
+	lib2_apt_src_jar -> lib2_kotlinc_action [ weight=0 ];
+	lib2_kotlin_sources -> lib2_kotlinc_action [ weight=100 ];
+	lib2_java_sources  -> lib2_kotlinc_action;
+	lib2_kotlinc_action -> lib2_kotlin_classes, lib2_kotlin_headers [ weight=100 ];
+
+	lib_combined_headers -> lib2_turbine_action [ weight=0 ];
+	lib2_apt_src_jar -> lib2_turbine_action [ weight=0 ];
+	lib2_kotlin_headers -> lib2_turbine_action [ weight=0 ];
+	lib2_java_sources -> lib2_turbine_action [ weight=100 ];
+	lib2_turbine_action -> lib2_java_headers [ weight=100 ];
+
+	lib_combined_headers -> lib2_javac_action [ weight=0 ];
+	lib2_apt_src_jar -> lib2_javac_action [ weight=0 ];
+	lib2_kotlin_headers -> lib2_javac_action [ weight=0 ];
+	lib2_java_sources -> lib2_javac_action [ weight=1000 ];
+	lib2_javac_action ->lib2_java_classes [ weight=100 ];
+
+	lib_combined_classes -> lib2_combine_action [ weight=0 ];
+	lib2_kotlin_classes -> lib2_combine_action [ weight=0 ];
+	lib2_java_classes -> lib2_combine_action [ weight=100 ];
+	lib2_combine_action -> lib2_combined_classes [ weight=100 ];
+
+	lib_combined_headers -> lib2_combine_headers_action [ weight=0 ];
+	lib2_kotlin_headers -> lib2_combine_headers_action [ weight=0 ];
+	lib2_java_headers -> lib2_combine_headers_action [ weight=100 ];
+	lib2_combine_headers_action -> lib2_combined_headers [ weight=100 ];
+
+	lib2_combined_headers -> lib2_spacer [ style=invis ];
+
+	// app
+
+	lib2_combined_headers -> app_kapt_action [ weight=0 ];
+	app_kotlin_sources -> app_kapt_action [ weight=0 ];
+	app_java_sources -> app_kapt_action;
+	app_kapt_action -> app_kotlin_stubs [ weight=100 ];
+
+	lib2_combined_headers -> app_turbine_apt_action [ weight=0 ];
+	app_kotlin_stubs -> app_turbine_apt_action [ weight=100 ];
+	app_turbine_apt_action -> app_apt_src_jar [ weight=100 ];
+
+	lib2_combined_headers -> app_kotlinc_action [ weight=0 ];
+	app_apt_src_jar -> app_kotlinc_action [ weight=0 ];
+	app_kotlin_sources -> app_kotlinc_action [ weight=100 ];
+	app_java_sources -> app_kotlinc_action;
+	app_kotlinc_action -> app_kotlin_headers, app_kotlin_classes [ weight=100 ];
+
+	lib2_combined_headers -> app_javac_action [ weight=0 ];
+	app_apt_src_jar -> app_javac_action [ weight=0 ];
+	app_kotlin_headers -> app_javac_action [ weight=0 ];
+	app_java_sources -> app_javac_action [ weight=1000 ];
+	app_javac_action -> app_java_classes [ weight=100 ];
+
+	lib2_combined_classes -> app_combine_action [ weight=0 ];
+	app_kotlin_classes -> app_combine_action [ weight=0 ];
+	app_java_classes -> app_combine_action [ weight=100 ];
+	app_combine_action -> app_combined_classes [ weight=100 ];
+
+	app_combined_classes -> app_r8_action;
+	app_r8_action -> app_dex [ weight=100 ];
+}
diff --git a/docs/resources.md b/docs/resources.md
new file mode 100644
index 0000000..c7cb0cf
--- /dev/null
+++ b/docs/resources.md
@@ -0,0 +1,89 @@
+## Soong Android Resource Compilation
+
+The Android build process involves several steps to compile resources into a format that the Android app can use
+efficiently in android_library, android_app and android_test modules.  See the
+[resources documentation](https://developer.android.com/guide/topics/resources/providing-resources) for general
+information on resources (with a focus on building with Gradle).
+
+For all modules, AAPT2 compiles resources provided by directories listed in the resource_dirs directory (which is
+implicitly set to `["res"]` if unset, but can be overridden by setting the `resource_dirs` property).
+
+## android_library with resource processor
+For an android_library with resource processor enabled (currently by setting `use_resource_processor: true`, but will be
+enabled by default in the future):
+- AAPT2 generates the `package-res.apk` file with a resource table that contains all resources from the current
+android_library module.  `package-res.apk` files from transitive dependencies are passed to AAPT2 with the `-I` flag to
+resolve references to resources from dependencies.
+- AAPT2 generates an R.txt file that lists all the resources provided by the current android_library module.
+- ResourceProcessorBusyBox reads the `R.txt` file for the current android_library and produces an `R.jar` with an
+`R.class` in the package listed in the android_library's `AndroidManifest.xml` file that contains java fields for each
+resource ID.  The resource IDs are non-final, as the final IDs will not be known until the resource table of the final
+android_app or android_test module is built.
+- The android_library's java and/or kotlin code is compiled with the generated `R.jar` in the classpath, along with the
+`R.jar` files from all transitive android_library dependencies.
+
+## android_app or android_test with resource processor
+For an android_app or android_test with resource processor enabled (currently by setting `use_resource_processor: true`,
+but will be enabled by default in the future):
+- AAPT2 generates the `package-res.apk` file with a resource table that contains all resources from the current
+android_app or android_test, as well as all transitive android_library modules referenced via `static_libs`.  The
+current module is overlaid on dependencies so that resources from the current module replace resources from dependencies
+in the case of conflicts.
+- AAPT2 generates an R.txt file that lists all the resources provided by the current android_app or android_test, as
+well as all transitive android_library modules referenced via `static_libs`.  The R.txt file contains the final resource
+ID for each resource.
+- ResourceProcessorBusyBox reads the `R.txt` file for the current android_app or android_test, as well as all transitive
+android_library modules referenced via `static_libs`, and produces an `R.jar` with an `R.class` in the package listed in
+the android_app or android_test's `AndroidManifest.xml` file that contains java fields for all local or transitive
+resource IDs.  In addition, it creates an `R.class` in the package listed in each android_library dependency's
+`AndroidManifest.xml` file that contains final resource IDs for the resources that were found in that library.
+- The android_app or android_test's java and/or kotlin code is compiled with the current module's `R.jar` in the
+classpath, but not the `R.jar` files from transitive android_library dependencies.  The `R.jar` file is also merged into
+the program  classes that are dexed and placed in the final APK.
+
+## android_app, android_test or android_library without resource processor
+For an android_app, android_test or android_library without resource processor enabled (current the default, or
+explicitly set with `use_resource_processor: false`):
+- AAPT2 generates the `package-res.apk` file with a resource table that contains all resources from the current
+android_app, android_test or android_library module, as well as all transitive android_library modules referenced via
+`static_libs`.  The current module is overlaid on dependencies so that resources from the current module replace
+resources from dependencies in the case of conflicts.
+- AAPT2 generates an `R.java` file in the package listed in each the current module's `AndroidManifest.xml` file that
+contains resource IDs for all resources from the current module as well as all transitive android_library modules
+referenced via `static_libs`.  The same `R.java` containing all local and transitive resources is also duplicated into
+every package listed in an `AndroidManifest.xml` file in any static `android_library` dependency.
+- The module's java and/or kotlin code is compiled along with all the generated `R.java` files.
+
+
+## Downsides of legacy resource compilation without resource processor
+
+Compiling resources without using the resource processor results in a generated R.java source file for every transitive
+package that contains every transitive resource.  For modules with large transitive dependency trees this can be tens of
+thousands of resource IDs duplicated in tens to a hundred java sources.  These java sources all have to be compiled in
+every successive module in the dependency tree, and then the final R8 step has to drop hundreds of thousands of
+unreferenced fields.  This results in significant build time and disk usage increases over building with resource
+processor.
+
+## Converting to compilation with resource processor
+
+### Reference resources using the package name of the module that includes them.
+Converting an android_library module to build with resource processor requires fixing any references to resources
+provided by android_library dependencies to reference the R classes using the package name found in the
+`AndroidManifest.xml` file of the dependency.  For example, when referencing an androidx resource:
+```java
+View.inflate(mContext, R.layout.preference, null));
+```
+must be replaced with:
+```java
+View.inflate(mContext, androidx.preference.R.layout.preference, null));
+```
+
+### Use unique package names for each module in `AndroidManifest.xml`
+
+Each module will produce an `R.jar` containing an `R.class` in the package specified in it's `AndroidManifest.xml`.
+If multiple modules use the same package name they will produce conflicting `R.class` files, which can cause some
+resource IDs to appear to be missing.
+
+If existing code has multiple modules that contribute resources to the same package, one option is to move all the
+resources into a single resources-only `android_library` module with no code, and then depend on that from all the other
+modules.
\ No newline at end of file
diff --git a/elf/Android.bp b/elf/Android.bp
index 6450be1..6d3f4f0 100644
--- a/elf/Android.bp
+++ b/elf/Android.bp
@@ -20,6 +20,7 @@
     name: "soong-elf",
     pkgPath: "android/soong/elf",
     srcs: [
+        "build_id_dir.go",
         "elf.go",
     ],
     testSrcs: [
diff --git a/elf/build_id_dir.go b/elf/build_id_dir.go
new file mode 100644
index 0000000..5fb7dda
--- /dev/null
+++ b/elf/build_id_dir.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 elf
+
+import (
+	"io/fs"
+	"os"
+	"path/filepath"
+	"strings"
+	"sync"
+	"time"
+)
+
+func UpdateBuildIdDir(path string) error {
+	path = filepath.Clean(path)
+	buildIdPath := path + "/.build-id"
+
+	// Collect the list of files and build-id symlinks. If the symlinks are
+	// up to date (newer than the symbol files), there is nothing to do.
+	var buildIdFiles, symbolFiles []string
+	var buildIdMtime, symbolsMtime time.Time
+	filepath.WalkDir(path, func(path string, entry fs.DirEntry, err error) error {
+		if entry == nil || entry.IsDir() {
+			return nil
+		}
+		info, err := entry.Info()
+		if err != nil {
+			return err
+		}
+		mtime := info.ModTime()
+		if strings.HasPrefix(path, buildIdPath) {
+			if buildIdMtime.Compare(mtime) < 0 {
+				buildIdMtime = mtime
+			}
+			buildIdFiles = append(buildIdFiles, path)
+		} else {
+			if symbolsMtime.Compare(mtime) < 0 {
+				symbolsMtime = mtime
+			}
+			symbolFiles = append(symbolFiles, path)
+		}
+		return nil
+	})
+	if symbolsMtime.Compare(buildIdMtime) < 0 {
+		return nil
+	}
+
+	// Collect build-id -> file mapping from ELF files in the symbols directory.
+	concurrency := 8
+	done := make(chan error)
+	buildIdToFile := make(map[string]string)
+	var mu sync.Mutex
+	for i := 0; i != concurrency; i++ {
+		go func(paths []string) {
+			for _, path := range paths {
+				id, err := Identifier(path, true)
+				if err != nil {
+					done <- err
+					return
+				}
+				if id == "" {
+					continue
+				}
+				mu.Lock()
+				oldPath := buildIdToFile[id]
+				if oldPath == "" || oldPath > path {
+					buildIdToFile[id] = path
+				}
+				mu.Unlock()
+			}
+			done <- nil
+		}(symbolFiles[len(symbolFiles)*i/concurrency : len(symbolFiles)*(i+1)/concurrency])
+	}
+
+	// Collect previously generated build-id -> file mapping from the .build-id directory.
+	// We will use this for incremental updates. If we see anything in the .build-id
+	// directory that we did not expect, we'll delete it and start over.
+	prevBuildIdToFile := make(map[string]string)
+out:
+	for _, buildIdFile := range buildIdFiles {
+		if !strings.HasSuffix(buildIdFile, ".debug") {
+			prevBuildIdToFile = nil
+			break
+		}
+		buildId := buildIdFile[len(buildIdPath)+1 : len(buildIdFile)-6]
+		for i, ch := range buildId {
+			if i == 2 {
+				if ch != '/' {
+					prevBuildIdToFile = nil
+					break out
+				}
+			} else {
+				if (ch < '0' || ch > '9') && (ch < 'a' || ch > 'f') {
+					prevBuildIdToFile = nil
+					break out
+				}
+			}
+		}
+		target, err := os.Readlink(buildIdFile)
+		if err != nil || !strings.HasPrefix(target, "../../") {
+			prevBuildIdToFile = nil
+			break
+		}
+		prevBuildIdToFile[buildId[0:2]+buildId[3:]] = path + target[5:]
+	}
+	if prevBuildIdToFile == nil {
+		err := os.RemoveAll(buildIdPath)
+		if err != nil {
+			return err
+		}
+		prevBuildIdToFile = make(map[string]string)
+	}
+
+	// Wait for build-id collection from ELF files to finish.
+	for i := 0; i != concurrency; i++ {
+		err := <-done
+		if err != nil {
+			return err
+		}
+	}
+
+	// Delete old symlinks.
+	for id, _ := range prevBuildIdToFile {
+		if buildIdToFile[id] == "" {
+			symlinkDir := buildIdPath + "/" + id[:2]
+			symlinkPath := symlinkDir + "/" + id[2:] + ".debug"
+			if err := os.Remove(symlinkPath); err != nil {
+				return err
+			}
+		}
+	}
+
+	// Add new symlinks and update changed symlinks.
+	for id, path := range buildIdToFile {
+		prevPath := prevBuildIdToFile[id]
+		if prevPath == path {
+			continue
+		}
+		symlinkDir := buildIdPath + "/" + id[:2]
+		symlinkPath := symlinkDir + "/" + id[2:] + ".debug"
+		if prevPath == "" {
+			if err := os.MkdirAll(symlinkDir, 0755); err != nil {
+				return err
+			}
+		} else {
+			if err := os.Remove(symlinkPath); err != nil {
+				return err
+			}
+		}
+
+		target, err := filepath.Rel(symlinkDir, path)
+		if err != nil {
+			return err
+		}
+		if err := os.Symlink(target, symlinkPath); err != nil {
+			return err
+		}
+	}
+	return nil
+}
diff --git a/etc/Android.bp b/etc/Android.bp
index 97788e4..f02c12a 100644
--- a/etc/Android.bp
+++ b/etc/Android.bp
@@ -11,8 +11,9 @@
         "soong-android",
     ],
     srcs: [
-        "prebuilt_etc.go",
         "install_symlink.go",
+        "otacerts_zip.go",
+        "prebuilt_etc.go",
     ],
     testSrcs: [
         "prebuilt_etc_test.go",
diff --git a/etc/otacerts_zip.go b/etc/otacerts_zip.go
new file mode 100644
index 0000000..b6f175a
--- /dev/null
+++ b/etc/otacerts_zip.go
@@ -0,0 +1,146 @@
+// 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/proptools"
+)
+
+func init() {
+	RegisterOtacertsZipBuildComponents(android.InitRegistrationContext)
+}
+
+func RegisterOtacertsZipBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("otacerts_zip", otacertsZipFactory)
+}
+
+type otacertsZipProperties struct {
+	// Make this module available when building for recovery.
+	// Only the recovery partition is available.
+	Recovery_available *bool
+
+	// Optional subdirectory under which the zip file is installed into.
+	Relative_install_path *string
+
+	// Optional name for the installed file. If unspecified, otacerts.zip is used.
+	Filename *string
+}
+
+type otacertsZipModule struct {
+	android.ModuleBase
+
+	properties otacertsZipProperties
+	outputPath android.OutputPath
+}
+
+// otacerts_zip collects key files defined in PRODUCT_DEFAULT_DEV_CERTIFICATE
+// and PRODUCT_EXTRA_OTA_KEYS for system or PRODUCT_EXTRA_RECOVERY_KEYS for
+// recovery image. The output file (otacerts.zip by default) is installed into
+// the relative_install_path directory under the etc directory of the target
+// partition.
+func otacertsZipFactory() android.Module {
+	module := &otacertsZipModule{}
+	module.AddProperties(&module.properties)
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	return module
+}
+
+var _ android.ImageInterface = (*otacertsZipModule)(nil)
+
+func (m *otacertsZipModule) ImageMutatorBegin(ctx android.BaseModuleContext) {}
+
+func (m *otacertsZipModule) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
+	return false
+}
+
+func (m *otacertsZipModule) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
+	return false
+}
+
+func (m *otacertsZipModule) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
+	return !m.ModuleBase.InstallInRecovery()
+}
+
+func (m *otacertsZipModule) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+	return false
+}
+
+func (m *otacertsZipModule) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+	return false
+}
+
+func (m *otacertsZipModule) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
+	return false
+}
+
+func (m *otacertsZipModule) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
+	return proptools.Bool(m.properties.Recovery_available) || m.ModuleBase.InstallInRecovery()
+}
+
+func (m *otacertsZipModule) ExtraImageVariations(ctx android.BaseModuleContext) []string {
+	return nil
+}
+
+func (m *otacertsZipModule) SetImageVariation(ctx android.BaseModuleContext, variation string) {
+}
+
+func (m *otacertsZipModule) InRecovery() bool {
+	return m.ModuleBase.InRecovery() || m.ModuleBase.InstallInRecovery()
+}
+
+func (m *otacertsZipModule) InstallInRecovery() bool {
+	return m.InRecovery()
+}
+
+func (m *otacertsZipModule) outputFileName() string {
+	// Use otacerts.zip if not specified.
+	return proptools.StringDefault(m.properties.Filename, "otacerts.zip")
+}
+
+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
+
+	rule := android.NewRuleBuilder(pctx, ctx)
+	cmd := rule.Command().BuiltTool("soong_zip").
+		FlagWithOutput("-o ", m.outputPath).
+		Flag("-j ").
+		Flag("-symlinks=false ")
+	for _, src := range srcPaths {
+		cmd.FlagWithInput("-f ", src)
+	}
+	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)
+}
+
+func (m *otacertsZipModule) AndroidMkEntries() []android.AndroidMkEntries {
+	nameSuffix := ""
+	if m.InRecovery() {
+		nameSuffix = ".recovery"
+	}
+	return []android.AndroidMkEntries{android.AndroidMkEntries{
+		Class:      "ETC",
+		SubName:    nameSuffix,
+		OutputFile: android.OptionalPathForPath(m.outputPath),
+	}}
+}
diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index d1c1d85..fc6d1f7 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -50,12 +50,17 @@
 	ctx.RegisterModuleType("prebuilt_etc", PrebuiltEtcFactory)
 	ctx.RegisterModuleType("prebuilt_etc_host", PrebuiltEtcHostFactory)
 	ctx.RegisterModuleType("prebuilt_etc_cacerts", PrebuiltEtcCaCertsFactory)
+	ctx.RegisterModuleType("prebuilt_avb", PrebuiltAvbFactory)
 	ctx.RegisterModuleType("prebuilt_root", PrebuiltRootFactory)
 	ctx.RegisterModuleType("prebuilt_root_host", PrebuiltRootHostFactory)
 	ctx.RegisterModuleType("prebuilt_usr_share", PrebuiltUserShareFactory)
 	ctx.RegisterModuleType("prebuilt_usr_share_host", PrebuiltUserShareHostFactory)
 	ctx.RegisterModuleType("prebuilt_usr_hyphendata", PrebuiltUserHyphenDataFactory)
+	ctx.RegisterModuleType("prebuilt_usr_keylayout", PrebuiltUserKeyLayoutFactory)
+	ctx.RegisterModuleType("prebuilt_usr_keychars", PrebuiltUserKeyCharsFactory)
+	ctx.RegisterModuleType("prebuilt_usr_idc", PrebuiltUserIdcFactory)
 	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)
@@ -70,11 +75,11 @@
 type prebuiltEtcProperties struct {
 	// Source file of this prebuilt. Can reference a genrule type module with the ":module" syntax.
 	// Mutually exclusive with srcs.
-	Src *string `android:"path,arch_variant"`
+	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.
-	Srcs []string `android:"path,arch_variant"`
+	Srcs 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).
@@ -121,6 +126,15 @@
 	Relative_install_path *string `android:"arch_variant"`
 }
 
+type prebuiltRootProperties struct {
+	// Install this module to the root directory, without partition subdirs.  When this module is
+	// added to PRODUCT_PACKAGES, this module will be installed to $PRODUCT_OUT/root, which will
+	// then be copied to the root of system.img. When this module is packaged by other modules like
+	// android_filesystem, this module will be installed to the root ("/"), unlike normal
+	// prebuilt_root modules which are installed to the partition subdir (e.g. "/system/").
+	Install_in_root *bool
+}
+
 type PrebuiltEtcModule interface {
 	android.Module
 
@@ -129,17 +143,18 @@
 
 	// Returns the sub install directory relative to BaseDir().
 	SubDir() string
-
-	// Returns an android.OutputPath to the intermediate file, which is the renamed prebuilt source
-	// file.
-	OutputFiles(tag string) (android.Paths, error)
 }
 
 type PrebuiltEtc struct {
 	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.
+	rootProperties prebuiltRootProperties
+
 	subdirProperties prebuiltSubdirProperties
 
 	sourceFilePaths android.Paths
@@ -154,10 +169,9 @@
 	installDirPath         android.InstallPath
 	additionalDependencies *android.Paths
 
-	makeClass string
+	usedSrcsProperty bool
 
-	// Aconfig files for all transitive deps.  Also exposed via TransitiveDeclarationsInfo
-	mergedAconfigFiles map[string]android.Paths
+	makeClass string
 }
 
 type Defaults struct {
@@ -217,6 +231,14 @@
 
 func (p *PrebuiltEtc) ImageMutatorBegin(ctx android.BaseModuleContext) {}
 
+func (p *PrebuiltEtc) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
+	return false
+}
+
+func (p *PrebuiltEtc) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
+	return false
+}
+
 func (p *PrebuiltEtc) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
 	return !p.ModuleBase.InstallInRecovery() && !p.ModuleBase.InstallInRamdisk() &&
 		!p.ModuleBase.InstallInVendorRamdisk() && !p.ModuleBase.InstallInDebugRamdisk()
@@ -234,6 +256,10 @@
 	return proptools.Bool(p.properties.Debug_ramdisk_available) || p.ModuleBase.InstallInDebugRamdisk()
 }
 
+func (p *PrebuiltEtc) InstallInRoot() bool {
+	return proptools.Bool(p.rootProperties.Install_in_root)
+}
+
 func (p *PrebuiltEtc) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
 	return proptools.Bool(p.properties.Recovery_available) || p.ModuleBase.InstallInRecovery()
 }
@@ -242,14 +268,14 @@
 	return nil
 }
 
-func (p *PrebuiltEtc) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) {
+func (p *PrebuiltEtc) SetImageVariation(ctx android.BaseModuleContext, variation string) {
 }
 
 func (p *PrebuiltEtc) SourceFilePath(ctx android.ModuleContext) android.Path {
-	if len(p.properties.Srcs) > 0 {
+	if len(p.properties.Srcs.GetOrDefault(ctx, nil)) > 0 {
 		panic(fmt.Errorf("SourceFilePath not available on multi-source prebuilt %q", p.Name()))
 	}
-	return android.PathForModuleSrc(ctx, proptools.String(p.properties.Src))
+	return android.PathForModuleSrc(ctx, p.properties.Src.GetOrDefault(ctx, ""))
 }
 
 func (p *PrebuiltEtc) InstallDirPath() android.InstallPath {
@@ -263,23 +289,12 @@
 }
 
 func (p *PrebuiltEtc) OutputFile() android.OutputPath {
-	if len(p.properties.Srcs) > 0 {
+	if p.usedSrcsProperty {
 		panic(fmt.Errorf("OutputFile not available on multi-source prebuilt %q", p.Name()))
 	}
 	return p.outputFilePaths[0]
 }
 
-var _ android.OutputFileProducer = (*PrebuiltEtc)(nil)
-
-func (p *PrebuiltEtc) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		return p.outputFilePaths.Paths(), nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
 func (p *PrebuiltEtc) SubDir() string {
 	if subDir := proptools.String(p.subdirProperties.Sub_dir); subDir != "" {
 		return subDir
@@ -318,7 +333,9 @@
 func (p *PrebuiltEtc) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	var installs []installProperties
 
-	if p.properties.Src != nil && len(p.properties.Srcs) > 0 {
+	srcProperty := p.properties.Src.Get(ctx)
+	srcsProperty := p.properties.Srcs.GetOrDefault(ctx, nil)
+	if srcProperty.IsPresent() && len(srcsProperty) > 0 {
 		ctx.PropertyErrorf("src", "src is set. Cannot set srcs")
 	}
 
@@ -330,8 +347,8 @@
 
 	filename := proptools.String(p.properties.Filename)
 	filenameFromSrc := proptools.Bool(p.properties.Filename_from_src)
-	if p.properties.Src != nil {
-		p.sourceFilePaths = android.PathsForModuleSrc(ctx, []string{proptools.String(p.properties.Src)})
+	if srcProperty.IsPresent() {
+		p.sourceFilePaths = android.PathsForModuleSrc(ctx, []string{srcProperty.Get()})
 		// If the source was not found, set a fake source path to
 		// support AllowMissingDependencies executions.
 		if len(p.sourceFilePaths) == 0 {
@@ -366,7 +383,8 @@
 			symlinks:       p.properties.Symlinks,
 		}
 		installs = append(installs, ip)
-	} else if len(p.properties.Srcs) > 0 {
+	} else if len(srcsProperty) > 0 {
+		p.usedSrcsProperty = true
 		if filename != "" {
 			ctx.PropertyErrorf("filename", "filename cannot be set when using srcs")
 		}
@@ -376,7 +394,7 @@
 		if p.properties.Filename_from_src != nil {
 			ctx.PropertyErrorf("filename_from_src", "filename_from_src is implicitly set to true when using srcs")
 		}
-		p.sourceFilePaths = android.PathsForModuleSrc(ctx, p.properties.Srcs)
+		p.sourceFilePaths = android.PathsForModuleSrc(ctx, srcsProperty)
 		for _, src := range p.sourceFilePaths {
 			filename := src.Base()
 			output := android.PathForModuleOut(ctx, filename).OutputPath
@@ -418,7 +436,8 @@
 	for _, ip := range installs {
 		ip.addInstallRules(ctx)
 	}
-	android.CollectDependencyAconfigFiles(ctx, &p.mergedAconfigFiles)
+
+	ctx.SetOutputFiles(p.outputFilePaths.Paths(), "")
 }
 
 type installProperties struct {
@@ -483,7 +502,6 @@
 				if p.additionalDependencies != nil {
 					entries.AddStrings("LOCAL_ADDITIONAL_DEPENDENCIES", p.additionalDependencies.Strings()...)
 				}
-				android.SetAconfigFileMkEntries(p.AndroidModuleBase(), entries, p.mergedAconfigFiles)
 			},
 		},
 	}}
@@ -502,6 +520,13 @@
 func InitPrebuiltRootModule(p *PrebuiltEtc) {
 	p.installDirBase = "."
 	p.AddProperties(&p.properties)
+	p.AddProperties(&p.rootProperties)
+}
+
+func InitPrebuiltAvbModule(p *PrebuiltEtc) {
+	p.installDirBase = "avb"
+	p.AddProperties(&p.properties)
+	p.rootProperties.Install_in_root = proptools.BoolPtr(true)
 }
 
 // prebuilt_etc is for a prebuilt artifact that is installed in
@@ -554,6 +579,20 @@
 	return module
 }
 
+// Generally, a <partition> directory will contain a `system` subdirectory, but the <partition> of
+// `prebuilt_avb` will not have a `system` subdirectory.
+// Ultimately, prebuilt_avb will install the prebuilt artifact to the `avb` subdirectory under the
+// root directory of the partition: <partition_root>/avb.
+// prebuilt_avb does not allow adding any other subdirectories.
+func PrebuiltAvbFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltAvbModule(module)
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
+	return module
+}
+
 // prebuilt_root is for a prebuilt artifact that is installed in
 // <partition>/ directory. Can't have any sub directories.
 func PrebuiltRootFactory() android.Module {
@@ -609,6 +648,39 @@
 	return module
 }
 
+// prebuilt_usr_keylayout is for a prebuilt artifact that is installed in
+// <partition>/usr/keylayout/<sub_dir> directory.
+func PrebuiltUserKeyLayoutFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "usr/keylayout")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+// prebuilt_usr_keychars is for a prebuilt artifact that is installed in
+// <partition>/usr/keychars/<sub_dir> directory.
+func PrebuiltUserKeyCharsFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "usr/keychars")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	android.InitDefaultableModule(module)
+	return module
+}
+
+// prebuilt_usr_idc is for a prebuilt artifact that is installed in
+// <partition>/usr/idc/<sub_dir> directory.
+func PrebuiltUserIdcFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "usr/idc")
+	// 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{}
@@ -619,6 +691,15 @@
 	return module
 }
 
+// prebuilt_overlay is for a prebuilt artifact in <partition>/overlay directory.
+func PrebuiltOverlayFactory() android.Module {
+	module := &PrebuiltEtc{}
+	InitPrebuiltEtcModule(module, "overlay")
+	// This module is device-only
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibFirst)
+	return module
+}
+
 // prebuilt_firmware installs a firmware file to <partition>/etc/firmware directory for system
 // image.
 // If soc_specific property is set to true, the firmware file is installed to the
diff --git a/etc/prebuilt_etc_test.go b/etc/prebuilt_etc_test.go
index dd9958c..e739afe 100644
--- a/etc/prebuilt_etc_test.go
+++ b/etc/prebuilt_etc_test.go
@@ -244,6 +244,31 @@
 	`)
 }
 
+func TestPrebuiltAvbInstallDirPath(t *testing.T) {
+	result := prepareForPrebuiltEtcTest.RunTestWithBp(t, `
+		prebuilt_avb {
+			name: "foo.conf",
+			src: "foo.conf",
+			filename: "foo.conf",
+			//recovery: true,
+		}
+	`)
+
+	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)
+}
+
+func TestPrebuiltAvdInstallDirPathValidate(t *testing.T) {
+	prepareForPrebuiltEtcTest.ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern("filename cannot contain separator")).RunTestWithBp(t, `
+		prebuilt_avb {
+			name: "foo.conf",
+			src: "foo.conf",
+			filename: "foo/bar.conf",
+		}
+	`)
+}
+
 func TestPrebuiltUserShareInstallDirPath(t *testing.T) {
 	result := prepareForPrebuiltEtcTest.RunTestWithBp(t, `
 		prebuilt_usr_share {
@@ -287,6 +312,48 @@
 	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
 }
 
+func TestPrebuiltPrebuiltUserKeyLayoutInstallDirPath(t *testing.T) {
+	result := prepareForPrebuiltEtcTest.RunTestWithBp(t, `
+	prebuilt_usr_keylayout {
+			name: "foo.conf",
+			src: "foo.conf",
+			sub_dir: "bar",
+		}
+	`)
+
+	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)
+}
+
+func TestPrebuiltPrebuiltUserKeyCharsInstallDirPath(t *testing.T) {
+	result := prepareForPrebuiltEtcTest.RunTestWithBp(t, `
+	prebuilt_usr_keychars {
+			name: "foo.conf",
+			src: "foo.conf",
+			sub_dir: "bar",
+		}
+	`)
+
+	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)
+}
+
+func TestPrebuiltPrebuiltUserIdcInstallDirPath(t *testing.T) {
+	result := prepareForPrebuiltEtcTest.RunTestWithBp(t, `
+	prebuilt_usr_idc {
+			name: "foo.conf",
+			src: "foo.conf",
+			sub_dir: "bar",
+		}
+	`)
+
+	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)
+}
+
 func TestPrebuiltFontInstallDirPath(t *testing.T) {
 	result := prepareForPrebuiltEtcTest.RunTestWithBp(t, `
 		prebuilt_font {
@@ -300,6 +367,19 @@
 	android.AssertPathRelativeToTopEquals(t, "install dir", expected, p.installDirPath)
 }
 
+func TestPrebuiltOverlayInstallDirPath(t *testing.T) {
+	result := prepareForPrebuiltEtcTest.RunTestWithBp(t, `
+		prebuilt_overlay {
+			name: "foo.conf",
+			src: "foo.conf",
+		}
+	`)
+
+	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)
+}
+
 func TestPrebuiltFirmwareDirPath(t *testing.T) {
 	targetPath := "out/soong/target/product/test_device"
 	tests := []struct {
diff --git a/filesystem/Android.bp b/filesystem/Android.bp
index 854a366..a08f7cf 100644
--- a/filesystem/Android.bp
+++ b/filesystem/Android.bp
@@ -15,6 +15,7 @@
         "soong-phony", // for testing
     ],
     srcs: [
+        "aconfig_files.go",
         "avb_add_hash_footer.go",
         "avb_gen_vbmeta_image.go",
         "bootimg.go",
diff --git a/filesystem/aconfig_files.go b/filesystem/aconfig_files.go
new file mode 100644
index 0000000..5c047bc
--- /dev/null
+++ b/filesystem/aconfig_files.go
@@ -0,0 +1,84 @@
+// 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"
+	"strings"
+
+	"github.com/google/blueprint/proptools"
+)
+
+func (f *filesystem) buildAconfigFlagsFiles(ctx android.ModuleContext, builder *android.RuleBuilder, specs map[string]android.PackagingSpec, dir android.OutputPath) {
+	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")
+
+	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)
+	f.appendToEntry(ctx, installAconfigFlagsPath)
+
+	installAconfigStorageDir := dir.Join(ctx, "etc", "aconfig")
+	sb.WriteString("mkdir -p ")
+	sb.WriteString(installAconfigStorageDir.String())
+	sb.WriteRune('\n')
+
+	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)
+		f.appendToEntry(ctx, outputPath)
+	}
+	generatePartitionAconfigStorageFile("package_map", "package.map")
+	generatePartitionAconfigStorageFile("flag_map", "flag.map")
+	generatePartitionAconfigStorageFile("flag_val", "flag.val")
+
+	android.WriteExecutableFileRuleVerbatim(ctx, aconfigFlagsBuilderPath, sb.String())
+}
diff --git a/filesystem/avb_gen_vbmeta_image.go b/filesystem/avb_gen_vbmeta_image.go
index 985f0ea..a7fd782 100644
--- a/filesystem/avb_gen_vbmeta_image.go
+++ b/filesystem/avb_gen_vbmeta_image.go
@@ -81,6 +81,8 @@
 	a.output = android.PathForModuleOut(ctx, a.installFileName()).OutputPath
 	cmd.FlagWithOutput("--output_vbmeta_image ", a.output)
 	builder.Build("avbGenVbmetaImage", fmt.Sprintf("avbGenVbmetaImage %s", ctx.ModuleName()))
+
+	ctx.SetOutputFiles([]android.Path{a.output}, "")
 }
 
 var _ android.AndroidMkEntriesProvider = (*avbGenVbmetaImage)(nil)
@@ -99,16 +101,6 @@
 	}}
 }
 
-var _ android.OutputFileProducer = (*avbGenVbmetaImage)(nil)
-
-// Implements android.OutputFileProducer
-func (a *avbGenVbmetaImage) OutputFiles(tag string) (android.Paths, error) {
-	if tag == "" {
-		return []android.Path{a.output}, nil
-	}
-	return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-}
-
 type avbGenVbmetaImageDefaults struct {
 	android.ModuleBase
 	android.DefaultsModuleBase
diff --git a/filesystem/bootimg.go b/filesystem/bootimg.go
index 352b451..e796ab9 100644
--- a/filesystem/bootimg.go
+++ b/filesystem/bootimg.go
@@ -123,6 +123,8 @@
 
 	b.installDir = android.PathForModuleInstall(ctx, "etc")
 	ctx.InstallFile(b.installDir, b.installFileName(), b.output)
+
+	ctx.SetOutputFiles([]android.Path{b.output}, "")
 }
 
 func (b *bootimg) buildBootImage(ctx android.ModuleContext, vendor bool) android.OutputPath {
@@ -292,13 +294,3 @@
 	}
 	return nil
 }
-
-var _ android.OutputFileProducer = (*bootimg)(nil)
-
-// Implements android.OutputFileProducer
-func (b *bootimg) OutputFiles(tag string) (android.Paths, error) {
-	if tag == "" {
-		return []android.Path{b.output}, nil
-	}
-	return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-}
diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go
index cadf9c24..5c7ef43 100644
--- a/filesystem/filesystem.go
+++ b/filesystem/filesystem.go
@@ -60,7 +60,9 @@
 	output     android.OutputPath
 	installDir android.InstallPath
 
-	// For testing. Keeps the result of CopySpecsToDir()
+	fileListFile android.OutputPath
+
+	// Keeps the entries installed from this filesystem
 	entries []string
 }
 
@@ -106,7 +108,7 @@
 	Base_dir *string
 
 	// Directories to be created under root. e.g. /dev, /proc, etc.
-	Dirs []string
+	Dirs proptools.Configurable[[]string]
 
 	// Symbolic links to be created under root with "ln -sf <target> <name>".
 	Symlinks []symlinkDefinition
@@ -127,6 +129,13 @@
 	// 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
+
+	// Install aconfig_flags.pb file for the modules installed in this partition.
+	Gen_aconfig_flags_pb *bool
+
 	Fsverity fsverityProperties
 }
 
@@ -137,6 +146,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
 	initFilesystemModule(module)
 	return module
 }
@@ -144,6 +154,7 @@
 func initFilesystemModule(module *filesystem) {
 	module.AddProperties(&module.properties)
 	android.InitPackageModule(module)
+	module.PackagingBase.DepsCollectFirstTargetOnly = true
 	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	android.InitDefaultableModule(module)
 }
@@ -189,6 +200,12 @@
 	return proptools.StringDefault(f.properties.Partition_name, f.Name())
 }
 
+func (f *filesystem) filterInstallablePackagingSpec(ps android.PackagingSpec) bool {
+	// Filesystem module respects the installation semantic. A PackagingSpec from a module with
+	// IsSkipInstall() is skipped.
+	return !ps.SkipInstall()
+}
+
 var pctx = android.NewPackageContext("android/soong/filesystem")
 
 func (f *filesystem) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -206,6 +223,26 @@
 
 	f.installDir = android.PathForModuleInstall(ctx, "etc")
 	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())
+}
+
+func (f *filesystem) appendToEntry(ctx android.ModuleContext, installedFile android.OutputPath) {
+	partitionBaseDir := android.PathForModuleOut(ctx, "root", f.partitionName()).String() + "/"
+
+	relPath, inTargetPartition := strings.CutPrefix(installedFile.String(), partitionBaseDir)
+	if inTargetPartition {
+		f.entries = append(f.entries, relPath)
+	}
+}
+
+func (f *filesystem) installedFilesList() string {
+	installedFilePaths := android.FirstUniqueStrings(f.entries)
+	slices.Sort(installedFilePaths)
+
+	return strings.Join(installedFilePaths, "\n")
 }
 
 func validatePartitionType(ctx android.ModuleContext, p partition) {
@@ -228,7 +265,7 @@
 // already in `rootDir`.
 func (f *filesystem) buildNonDepsFiles(ctx android.ModuleContext, builder *android.RuleBuilder, rootDir android.OutputPath) {
 	// create dirs and symlinks
-	for _, dir := range f.properties.Dirs {
+	for _, dir := range f.properties.Dirs.GetOrDefault(ctx, nil) {
 		// OutputPath.Join verifies dir
 		builder.Command().Text("mkdir -p").Text(rootDir.Join(ctx, dir).String())
 	}
@@ -252,17 +289,19 @@
 		builder.Command().Textf("(! [ -e %s -o -L %s ] || (echo \"%s already exists from an earlier stage of the build\" && exit 1))", dst, dst, dst)
 		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)
 	}
 
 	// create extra files if there's any
 	if f.buildExtraFiles != nil {
 		rootForExtraFiles := android.PathForModuleGen(ctx, "root-extra").OutputPath
 		extraFiles := f.buildExtraFiles(ctx, rootForExtraFiles)
-		for _, f := range extraFiles {
-			rel, err := filepath.Rel(rootForExtraFiles.String(), f.String())
+		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", f, rootForExtraFiles)
+				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").
@@ -273,6 +312,25 @@
 	}
 }
 
+func (f *filesystem) copyPackagingSpecs(ctx android.ModuleContext, builder *android.RuleBuilder, specs map[string]android.PackagingSpec, rootDir, rebasedDir android.WritablePath) []string {
+	rootDirSpecs := make(map[string]android.PackagingSpec)
+	rebasedDirSpecs := make(map[string]android.PackagingSpec)
+
+	for rel, spec := range specs {
+		if spec.Partition() == "root" {
+			rootDirSpecs[rel] = spec
+		} else {
+			rebasedDirSpecs[rel] = spec
+		}
+	}
+
+	dirsToSpecs := make(map[android.WritablePath]map[string]android.PackagingSpec)
+	dirsToSpecs[rootDir] = rootDirSpecs
+	dirsToSpecs[rebasedDir] = rebasedDirSpecs
+
+	return f.CopySpecsToDirs(ctx, builder, dirsToSpecs)
+}
+
 func (f *filesystem) buildImageUsingBuildImage(ctx android.ModuleContext) android.OutputPath {
 	rootDir := android.PathForModuleOut(ctx, "root").OutputPath
 	rebasedDir := rootDir
@@ -283,11 +341,13 @@
 	// 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.CopySpecsToDir(ctx, builder, specs, rebasedDir)
+	f.entries = f.copyPackagingSpecs(ctx, builder, specs, rootDir, rebasedDir)
 
 	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)
 
 	// run host_init_verifier
 	// Ideally we should have a concept of pluggable linters that verify the generated image.
@@ -424,10 +484,12 @@
 	// 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.CopySpecsToDir(ctx, builder, specs, rebasedDir)
+	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)
 
 	output := android.PathForModuleOut(ctx, f.installFileName()).OutputPath
 	cmd := builder.Command().
@@ -485,6 +547,39 @@
 		Text(android.PathForArbitraryOutput(ctx, stagingDir).String())
 }
 
+func (f *filesystem) buildEventLogtagsFile(ctx android.ModuleContext, builder *android.RuleBuilder, rebasedDir android.OutputPath) {
+	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))
+
+	for _, path := range android.SortedKeys(logtagsFilePaths) {
+		cmd.Text(path)
+	}
+
+	f.appendToEntry(ctx, eventLogtagsPath)
+}
+
 type partition interface {
 	PartitionType() string
 }
@@ -506,21 +601,12 @@
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 				entries.SetString("LOCAL_MODULE_PATH", f.installDir.String())
 				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", f.installFileName())
+				entries.SetString("LOCAL_FILESYSTEM_FILELIST", f.fileListFile.String())
 			},
 		},
 	}}
 }
 
-var _ android.OutputFileProducer = (*filesystem)(nil)
-
-// Implements android.OutputFileProducer
-func (f *filesystem) OutputFiles(tag string) (android.Paths, error) {
-	if tag == "" {
-		return []android.Path{f.output}, nil
-	}
-	return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-}
-
 // Filesystem is the public interface for the filesystem struct. Currently, it's only for the apex
 // package to have access to the output file.
 type Filesystem interface {
@@ -565,7 +651,7 @@
 
 var _ cc.UseCoverage = (*filesystem)(nil)
 
-func (*filesystem) IsNativeCoverageNeeded(ctx android.IncomingTransitionContext) bool {
+func (*filesystem) IsNativeCoverageNeeded(ctx cc.IsNativeCoverageNeededContext) bool {
 	return ctx.Device() && ctx.DeviceConfig().NativeCoverageEnabled()
 }
 
diff --git a/filesystem/filesystem_test.go b/filesystem/filesystem_test.go
index 3a5071d..2dc8c21 100644
--- a/filesystem/filesystem_test.go
+++ b/filesystem/filesystem_test.go
@@ -299,43 +299,6 @@
 		cmd, "--include_descriptors_from_image ")
 }
 
-func TestFileSystemShouldInstallCoreVariantIfTargetBuildAppsIsSet(t *testing.T) {
-	context := android.GroupFixturePreparers(
-		fixture,
-		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
-			variables.Unbundled_build_apps = []string{"bar"}
-		}),
-	)
-	result := context.RunTestWithBp(t, `
-		android_system_image {
-			name: "myfilesystem",
-			deps: [
-				"libfoo",
-			],
-			linker_config_src: "linker.config.json",
-		}
-
-		cc_library {
-			name: "libfoo",
-			shared_libs: [
-				"libbar",
-			],
-			stl: "none",
-		}
-
-		cc_library {
-			name: "libbar",
-			sdk_version: "9",
-			stl: "none",
-		}
-	`)
-
-	inputs := result.ModuleForTests("myfilesystem", "android_common").Output("myfilesystem.img").Implicits
-	android.AssertStringListContains(t, "filesystem should have libbar even for unbundled build",
-		inputs.Strings(),
-		"out/soong/.intermediates/libbar/android_arm64_armv8-a_shared/libbar.so")
-}
-
 func TestFileSystemWithCoverageVariants(t *testing.T) {
 	context := android.GroupFixturePreparers(
 		fixture,
@@ -479,3 +442,121 @@
 		}
 	`)
 }
+
+func TestPreventDuplicatedEntries(t *testing.T) {
+	fixture.ExtendWithErrorHandler(android.FixtureExpectsOneErrorPattern(
+		"packaging conflict at")).
+		RunTestWithBp(t, `
+		android_filesystem {
+			name: "fs",
+			deps: [
+				"foo",
+				"foo_dup",
+			],
+		}
+
+		cc_binary {
+			name: "foo",
+		}
+
+		cc_binary {
+			name: "foo_dup",
+			stem: "foo",
+		}
+	`)
+}
+
+func TestTrackPhonyAsRequiredDep(t *testing.T) {
+	result := fixture.RunTestWithBp(t, `
+		android_filesystem {
+			name: "fs",
+			deps: ["foo"],
+		}
+
+		cc_binary {
+			name: "foo",
+			required: ["phony"],
+		}
+
+		phony {
+			name: "phony",
+			required: ["libbar"],
+		}
+
+		cc_library {
+			name: "libbar",
+		}
+	`)
+
+	fs := result.ModuleForTests("fs", "android_common").Module().(*filesystem)
+	expected := []string{
+		"bin/foo",
+		"lib64/libbar.so",
+	}
+	for _, e := range expected {
+		android.AssertStringListContains(t, "missing entry", fs.entries, e)
+	}
+}
+
+func TestFilterOutUnsupportedArches(t *testing.T) {
+	result := fixture.RunTestWithBp(t, `
+		android_filesystem {
+			name: "fs_64_only",
+			deps: ["foo"],
+		}
+
+		android_filesystem {
+			name: "fs_64_32",
+			compile_multilib: "both",
+			deps: ["foo"],
+		}
+
+		cc_binary {
+			name: "foo",
+			required: ["phony"],
+		}
+
+		phony {
+			name: "phony",
+			required: [
+				"libbar",
+				"app",
+			],
+		}
+
+		cc_library {
+			name: "libbar",
+		}
+
+		android_app {
+			name: "app",
+			srcs: ["a.java"],
+			platform_apis: true,
+		}
+	`)
+	testcases := []struct {
+		fsName     string
+		expected   []string
+		unexpected []string
+	}{
+		{
+			fsName:     "fs_64_only",
+			expected:   []string{"app/app/app.apk", "bin/foo", "lib64/libbar.so"},
+			unexpected: []string{"lib/libbar.so"},
+		},
+		{
+			fsName:     "fs_64_32",
+			expected:   []string{"app/app/app.apk", "bin/foo", "lib64/libbar.so", "lib/libbar.so"},
+			unexpected: []string{},
+		},
+	}
+	for _, c := range testcases {
+		fs := result.ModuleForTests(c.fsName, "android_common").Module().(*filesystem)
+		for _, e := range c.expected {
+			android.AssertStringListContains(t, "missing entry", fs.entries, e)
+		}
+		for _, e := range c.unexpected {
+			android.AssertStringListDoesNotContain(t, "unexpected entry", fs.entries, e)
+		}
+	}
+}
diff --git a/filesystem/fsverity_metadata.go b/filesystem/fsverity_metadata.go
index 3e50ff7..d7bb654 100644
--- a/filesystem/fsverity_metadata.go
+++ b/filesystem/fsverity_metadata.go
@@ -87,6 +87,7 @@
 		sb.WriteRune(' ')
 		sb.WriteString(srcPath.String())
 		sb.WriteRune('\n')
+		f.appendToEntry(ctx, destPath)
 	}
 
 	// STEP 2: generate signed BuildManifest.apk
@@ -108,6 +109,7 @@
 	sb.WriteString(" --output ")
 	sb.WriteString(manifestPbPath.String())
 	sb.WriteRune(' ')
+	f.appendToEntry(ctx, manifestPbPath)
 
 	manifestGeneratorListPath := android.PathForModuleOut(ctx, "fsverity_manifest.list")
 	f.writeManifestGeneratorListFile(ctx, manifestGeneratorListPath.OutputPath, matchedSpecs, rebasedDir)
@@ -115,15 +117,18 @@
 	sb.WriteString(manifestGeneratorListPath.String())
 	sb.WriteRune('\n')
 	cmd.Implicit(manifestGeneratorListPath)
+	f.appendToEntry(ctx, manifestGeneratorListPath.OutputPath)
 
 	// 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 ")
@@ -150,12 +155,15 @@
 	sb.WriteString(f.partitionName())
 	sb.WriteRune('\n')
 
+	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())
@@ -165,5 +173,7 @@
 	sb.WriteString(keyPath.String())
 	sb.WriteRune('\n')
 
+	f.appendToEntry(ctx, idsigPath)
+
 	android.WriteExecutableFileRuleVerbatim(ctx, fsverityBuilderPath, sb.String())
 }
diff --git a/filesystem/logical_partition.go b/filesystem/logical_partition.go
index e2f7d7b..e483fe4 100644
--- a/filesystem/logical_partition.go
+++ b/filesystem/logical_partition.go
@@ -185,6 +185,8 @@
 
 	l.installDir = android.PathForModuleInstall(ctx, "etc")
 	ctx.InstallFile(l.installDir, l.installFileName(), l.output)
+
+	ctx.SetOutputFiles([]android.Path{l.output}, "")
 }
 
 // Add a rule that converts the filesystem for the given partition to the given rule builder. The
@@ -231,13 +233,3 @@
 func (l *logicalPartition) SignedOutputPath() android.Path {
 	return nil // logical partition is not signed by itself
 }
-
-var _ android.OutputFileProducer = (*logicalPartition)(nil)
-
-// Implements android.OutputFileProducer
-func (l *logicalPartition) OutputFiles(tag string) (android.Paths, error) {
-	if tag == "" {
-		return []android.Path{l.output}, nil
-	}
-	return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-}
diff --git a/filesystem/raw_binary.go b/filesystem/raw_binary.go
index 1544ea7..ad36c29 100644
--- a/filesystem/raw_binary.go
+++ b/filesystem/raw_binary.go
@@ -15,8 +15,6 @@
 package filesystem
 
 import (
-	"fmt"
-
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 
@@ -88,6 +86,8 @@
 	r.output = outputFile
 	r.installDir = android.PathForModuleInstall(ctx, "etc")
 	ctx.InstallFile(r.installDir, r.installFileName(), r.output)
+
+	ctx.SetOutputFiles([]android.Path{r.output}, "")
 }
 
 var _ android.AndroidMkEntriesProvider = (*rawBinary)(nil)
@@ -109,13 +109,3 @@
 func (r *rawBinary) SignedOutputPath() android.Path {
 	return nil
 }
-
-var _ android.OutputFileProducer = (*rawBinary)(nil)
-
-// Implements android.OutputFileProducer
-func (r *rawBinary) OutputFiles(tag string) (android.Paths, error) {
-	if tag == "" {
-		return []android.Path{r.output}, nil
-	}
-	return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-}
diff --git a/filesystem/system_image.go b/filesystem/system_image.go
index 5028a49..69d922d 100644
--- a/filesystem/system_image.go
+++ b/filesystem/system_image.go
@@ -94,9 +94,10 @@
 	return output
 }
 
-// Filter the result of GatherPackagingSpecs to discard items targeting outside "system" partition.
-// Note that "apex" module installs its contents to "apex"(fake partition) as well
+// 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 ps.Partition() == "system"
+	return s.filesystem.filterInstallablePackagingSpec(ps) &&
+		(ps.Partition() == "system" || ps.Partition() == "root")
 }
diff --git a/filesystem/vbmeta.go b/filesystem/vbmeta.go
index 43a2f37..0c6e7f4 100644
--- a/filesystem/vbmeta.go
+++ b/filesystem/vbmeta.go
@@ -211,6 +211,8 @@
 
 	v.installDir = android.PathForModuleInstall(ctx, "etc")
 	ctx.InstallFile(v.installDir, v.installFileName(), v.output)
+
+	ctx.SetOutputFiles([]android.Path{v.output}, "")
 }
 
 // Returns the embedded shell command that prints the rollback index
@@ -288,13 +290,3 @@
 func (v *vbmeta) SignedOutputPath() android.Path {
 	return v.OutputPath() // vbmeta is always signed
 }
-
-var _ android.OutputFileProducer = (*vbmeta)(nil)
-
-// Implements android.OutputFileProducer
-func (v *vbmeta) OutputFiles(tag string) (android.Paths, error) {
-	if tag == "" {
-		return []android.Path{v.output}, nil
-	}
-	return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-}
diff --git a/fuzz/fuzz_common.go b/fuzz/fuzz_common.go
index 47fd8f4..306d65e 100644
--- a/fuzz/fuzz_common.go
+++ b/fuzz/fuzz_common.go
@@ -449,10 +449,10 @@
 	}
 }
 
-func IsValid(fuzzModule FuzzModule) bool {
+func IsValid(ctx android.ConfigAndErrorContext, fuzzModule FuzzModule) bool {
 	// Discard ramdisk + vendor_ramdisk + recovery modules, they're duplicates of
 	// fuzz targets we're going to package anyway.
-	if !fuzzModule.Enabled() || fuzzModule.InRamdisk() || fuzzModule.InVendorRamdisk() || fuzzModule.InRecovery() {
+	if !fuzzModule.Enabled(ctx) || fuzzModule.InRamdisk() || fuzzModule.InVendorRamdisk() || fuzzModule.InRecovery() {
 		return false
 	}
 
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 43f4fe5..5b40768 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -126,7 +126,7 @@
 	//  $(out): a single output file.
 	//  $(genDir): the sandbox directory for this tool; contains $(out).
 	//  $$: a literal $
-	Cmd *string
+	Cmd proptools.Configurable[string] `android:"replace_instead_of_append"`
 
 	// name of the modules (if any) that produces the host executable.   Leave empty for
 	// prebuilts or scripts that do not need a module to build them.
@@ -180,9 +180,6 @@
 
 	subName string
 	subDir  string
-
-	// Aconfig files for all transitive deps.  Also exposed via TransitiveDeclarationsInfo
-	mergedAconfigFiles map[string]android.Paths
 }
 
 type taskFunc func(ctx android.ModuleContext, rawCommand string, srcFiles android.Paths) []generateTask
@@ -216,21 +213,7 @@
 	return g.outputDeps
 }
 
-func (g *Module) OutputFiles(tag string) (android.Paths, error) {
-	if tag == "" {
-		return append(android.Paths{}, g.outputFiles...), nil
-	}
-	// otherwise, tag should match one of outputs
-	for _, outputFile := range g.outputFiles {
-		if outputFile.Rel() == tag {
-			return android.Paths{outputFile}, nil
-		}
-	}
-	return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-}
-
 var _ android.SourceFileProducer = (*Module)(nil)
-var _ android.OutputFileProducer = (*Module)(nil)
 
 func toolDepsMutator(ctx android.BottomUpMutatorContext) {
 	if g, ok := ctx.Module().(*Module); ok {
@@ -299,7 +282,7 @@
 				case android.HostToolProvider:
 					// A HostToolProvider provides the path to a tool, which will be copied
 					// into the sandbox.
-					if !t.(android.Module).Enabled() {
+					if !t.(android.Module).Enabled(ctx) {
 						if ctx.Config().AllowMissingDependencies() {
 							ctx.AddMissingDependencies([]string{tool})
 						} else {
@@ -406,7 +389,7 @@
 	var outputFiles android.WritablePaths
 	var zipArgs strings.Builder
 
-	cmd := String(g.properties.Cmd)
+	cmd := g.properties.Cmd.GetOrDefault(ctx, "")
 	if g.CmdModifier != nil {
 		cmd = g.CmdModifier(ctx, cmd)
 	}
@@ -588,24 +571,19 @@
 		})
 		g.outputDeps = android.Paths{phonyFile}
 	}
-	android.CollectDependencyAconfigFiles(ctx, &g.mergedAconfigFiles)
+
+	g.setOutputFiles(ctx)
 }
 
-func (g *Module) AndroidMkEntries() []android.AndroidMkEntries {
-	ret := android.AndroidMkEntries{
-		OutputFile: android.OptionalPathForPath(g.outputFiles[0]),
-		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
-			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				android.SetAconfigFileMkEntries(g.AndroidModuleBase(), entries, g.mergedAconfigFiles)
-			},
-		},
+func (g *Module) setOutputFiles(ctx android.ModuleContext) {
+	if len(g.outputFiles) == 0 {
+		return
 	}
-
-	return []android.AndroidMkEntries{ret}
-}
-
-func (g *Module) AndroidModuleBase() *android.ModuleBase {
-	return &g.ModuleBase
+	ctx.SetOutputFiles(g.outputFiles, "")
+	// non-empty-string-tag should match one of the outputs
+	for _, files := range g.outputFiles {
+		ctx.SetOutputFiles(android.Paths{files}, files.Rel())
+	}
 }
 
 // Collect information for opening IDE project files in java/jdeps.go.
@@ -665,13 +643,15 @@
 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, module android.Module) {
+func (x noopImageInterface) SetImageVariation(ctx android.BaseModuleContext, variation string) {
 }
 
 func NewGenSrcs() *Module {
@@ -714,13 +694,13 @@
 			rule := getSandboxedRuleBuilder(ctx, android.NewRuleBuilder(pctx, ctx).Sbox(genDir, nil))
 
 			for _, in := range shard {
-				outFile := android.GenPathWithExt(ctx, finalSubDir, in, String(properties.Output_extension))
+				outFile := android.GenPathWithExtAndTrimExt(ctx, finalSubDir, in, String(properties.Output_extension), String(properties.Trim_extension))
 
 				// If sharding is enabled, then outFile is the path to the output file in
 				// the shard directory, and copyTo is the path to the output file in the
 				// final directory.
 				if len(shards) > 1 {
-					shardFile := android.GenPathWithExt(ctx, genSubDir, in, String(properties.Output_extension))
+					shardFile := android.GenPathWithExtAndTrimExt(ctx, genSubDir, in, String(properties.Output_extension), String(properties.Trim_extension))
 					copyTo = append(copyTo, outFile)
 					outFile = shardFile
 				}
@@ -774,6 +754,7 @@
 func GenSrcsFactory() android.Module {
 	m := NewGenSrcs()
 	android.InitAndroidModule(m)
+	android.InitDefaultableModule(m)
 	return m
 }
 
@@ -786,6 +767,9 @@
 
 	// Additional files needed for build that are not tooling related.
 	Data []string `android:"path"`
+
+	// Trim the matched extension for each input file, and it should start with ".".
+	Trim_extension *string
 }
 
 const defaultShardSize = 50
diff --git a/genrule/genrule_test.go b/genrule/genrule_test.go
index 2dc6a79..fba9aec 100644
--- a/genrule/genrule_test.go
+++ b/genrule/genrule_test.go
@@ -894,6 +894,155 @@
 	)
 }
 
+func TestGenSrcsWithTrimExtAndOutpuExtension(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForGenRuleTest,
+		android.FixtureMergeMockFs(android.MockFS{
+			"external-protos/path/Android.bp": []byte(`
+				filegroup {
+					name: "external-protos",
+					srcs: [
+					    "baz.a.b.c.proto/baz.a.b.c.proto",
+					    "bar.a.b.c.proto",
+					    "qux.ext.a.b.c.proto",
+					],
+				}
+			`),
+			"package-dir/Android.bp": []byte(`
+				gensrcs {
+					name: "module-name",
+					cmd: "mkdir -p $(genDir) && cat $(in) >> $(genDir)/$(out)",
+					srcs: [
+						"src/foo.a.b.c.proto",
+						":external-protos",
+					],
+
+					trim_extension: ".a.b.c.proto",
+					output_extension: "proto.h",
+				}
+			`),
+		}),
+	).RunTest(t)
+
+	exportedIncludeDir := "out/soong/.intermediates/package-dir/module-name/gen/gensrcs"
+	gen := result.Module("module-name", "").(*Module)
+
+	android.AssertPathsRelativeToTopEquals(
+		t,
+		"include path",
+		[]string{exportedIncludeDir},
+		gen.exportedIncludeDirs,
+	)
+	android.AssertPathsRelativeToTopEquals(
+		t,
+		"files",
+		[]string{
+			exportedIncludeDir + "/package-dir/src/foo.proto.h",
+			exportedIncludeDir + "/external-protos/path/baz.a.b.c.proto/baz.proto.h",
+			exportedIncludeDir + "/external-protos/path/bar.proto.h",
+			exportedIncludeDir + "/external-protos/path/qux.ext.proto.h",
+		},
+		gen.outputFiles,
+	)
+}
+
+func TestGenSrcsWithTrimExtButNoOutpuExtension(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForGenRuleTest,
+		android.FixtureMergeMockFs(android.MockFS{
+			"external-protos/path/Android.bp": []byte(`
+				filegroup {
+					name: "external-protos",
+					srcs: [
+					    "baz.a.b.c.proto/baz.a.b.c.proto",
+					    "bar.a.b.c.proto",
+					    "qux.ext.a.b.c.proto",
+					],
+				}
+			`),
+			"package-dir/Android.bp": []byte(`
+				gensrcs {
+					name: "module-name",
+					cmd: "mkdir -p $(genDir) && cat $(in) >> $(genDir)/$(out)",
+					srcs: [
+						"src/foo.a.b.c.proto",
+						":external-protos",
+					],
+
+					trim_extension: ".a.b.c.proto",
+				}
+			`),
+		}),
+	).RunTest(t)
+
+	exportedIncludeDir := "out/soong/.intermediates/package-dir/module-name/gen/gensrcs"
+	gen := result.Module("module-name", "").(*Module)
+
+	android.AssertPathsRelativeToTopEquals(
+		t,
+		"include path",
+		[]string{exportedIncludeDir},
+		gen.exportedIncludeDirs,
+	)
+	android.AssertPathsRelativeToTopEquals(
+		t,
+		"files",
+		[]string{
+			exportedIncludeDir + "/package-dir/src/foo",
+			exportedIncludeDir + "/external-protos/path/baz.a.b.c.proto/baz",
+			exportedIncludeDir + "/external-protos/path/bar",
+			exportedIncludeDir + "/external-protos/path/qux.ext",
+		},
+		gen.outputFiles,
+	)
+}
+
+func TestGenSrcsWithOutpuExtension(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForGenRuleTest,
+		android.FixtureMergeMockFs(android.MockFS{
+			"external-protos/path/Android.bp": []byte(`
+				filegroup {
+					name: "external-protos",
+					srcs: ["baz/baz.a.b.c.proto", "bar.a.b.c.proto"],
+				}
+			`),
+			"package-dir/Android.bp": []byte(`
+				gensrcs {
+					name: "module-name",
+					cmd: "mkdir -p $(genDir) && cat $(in) >> $(genDir)/$(out)",
+					srcs: [
+						"src/foo.a.b.c.proto",
+						":external-protos",
+					],
+
+					output_extension: "proto.h",
+				}
+			`),
+		}),
+	).RunTest(t)
+
+	exportedIncludeDir := "out/soong/.intermediates/package-dir/module-name/gen/gensrcs"
+	gen := result.Module("module-name", "").(*Module)
+
+	android.AssertPathsRelativeToTopEquals(
+		t,
+		"include path",
+		[]string{exportedIncludeDir},
+		gen.exportedIncludeDirs,
+	)
+	android.AssertPathsRelativeToTopEquals(
+		t,
+		"files",
+		[]string{
+			exportedIncludeDir + "/package-dir/src/foo.a.b.c.proto.h",
+			exportedIncludeDir + "/external-protos/path/baz/baz.a.b.c.proto.h",
+			exportedIncludeDir + "/external-protos/path/bar.a.b.c.proto.h",
+		},
+		gen.outputFiles,
+	)
+}
+
 func TestPrebuiltTool(t *testing.T) {
 	testcases := []struct {
 		name             string
@@ -1105,12 +1254,6 @@
 	t.outputFile = ctx.InstallFile(android.PathForModuleInstall(ctx, "bin"), ctx.ModuleName(), android.PathForOutput(ctx, ctx.ModuleName()))
 }
 
-func (t *testOutputProducer) OutputFiles(tag string) (android.Paths, error) {
-	return android.Paths{t.outputFile}, nil
-}
-
-var _ android.OutputFileProducer = (*testOutputProducer)(nil)
-
 type useSource struct {
 	android.ModuleBase
 	props struct {
diff --git a/go.mod b/go.mod
index 1174958..aa43066 100644
--- a/go.mod
+++ b/go.mod
@@ -1,10 +1,9 @@
 module android/soong
 
-go 1.21
+go 1.22
 
 require (
 	github.com/google/blueprint v0.0.0
 	google.golang.org/protobuf v0.0.0
-	prebuilts/bazel/common/proto/analysis_v2 v0.0.0
 	go.starlark.net v0.0.0
 )
diff --git a/go.work b/go.work
index 7c6022b..46a135b 100644
--- a/go.work
+++ b/go.work
@@ -1,12 +1,10 @@
-go 1.21
+go 1.22
 
 use (
 	.
 	../../external/go-cmp
 	../../external/golang-protobuf
 	../../external/starlark-go
-	../../prebuilts/bazel/common/proto/analysis_v2
-	../../prebuilts/bazel/common/proto/build
 	../blueprint
 )
 
@@ -15,7 +13,5 @@
 	github.com/google/blueprint v0.0.0 => ../blueprint
 	github.com/google/go-cmp v0.0.0 => ../../external/go-cmp
 	google.golang.org/protobuf v0.0.0 => ../../external/golang-protobuf
-	prebuilts/bazel/common/proto/analysis_v2 v0.0.0 => ../../prebuilts/bazel/common/proto/analysis_v2
-	prebuilts/bazel/common/proto/build v0.0.0 => ../../prebuilts/bazel/common/proto/build
 	go.starlark.net v0.0.0 => ../../external/starlark-go
 )
diff --git a/java/Android.bp b/java/Android.bp
index 54b36ab..9603815 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -87,6 +87,7 @@
         "app_set_test.go",
         "app_test.go",
         "code_metadata_test.go",
+        "container_test.go",
         "bootclasspath_fragment_test.go",
         "device_host_converter_test.go",
         "dex_test.go",
@@ -100,6 +101,7 @@
         "hiddenapi_singleton_test.go",
         "jacoco_test.go",
         "java_test.go",
+        "jarjar_test.go",
         "jdeps_test.go",
         "kotlin_test.go",
         "lint_test.go",
diff --git a/java/aapt2.go b/java/aapt2.go
index f704fc6..61cf373 100644
--- a/java/aapt2.go
+++ b/java/aapt2.go
@@ -69,7 +69,7 @@
 
 // aapt2Compile compiles resources and puts the results in the requested directory.
 func aapt2Compile(ctx android.ModuleContext, dir android.Path, paths android.Paths,
-	flags []string, productToFilter string) android.WritablePaths {
+	flags []string, productToFilter string, featureFlagsPaths android.Paths) android.WritablePaths {
 	if productToFilter != "" && productToFilter != "default" {
 		// --filter-product leaves only product-specific resources. Product-specific resources only exist
 		// in value resources (values/*.xml), so filter value resource files only. Ignore other types of
@@ -85,6 +85,10 @@
 		flags = append([]string{"--filter-product " + productToFilter}, flags...)
 	}
 
+	for _, featureFlagsPath := range android.SortedUniquePaths(featureFlagsPaths) {
+		flags = append(flags, "--feature-flags", "@"+featureFlagsPath.String())
+	}
+
 	// Shard the input paths so that they can be processed in parallel. If we shard them into too
 	// small chunks, the additional cost of spinning up aapt2 outweighs the performance gain. The
 	// current shard size, 100, seems to be a good balance between the added cost and the gain.
@@ -112,6 +116,7 @@
 		ctx.Build(pctx, android.BuildParams{
 			Rule:        aapt2CompileRule,
 			Description: "aapt2 compile " + dir.String() + shardDesc,
+			Implicits:   featureFlagsPaths,
 			Inputs:      shard,
 			Outputs:     outPaths,
 			Args: map[string]string{
diff --git a/java/aar.go b/java/aar.go
index 47c64bf..b69b7c2 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -440,7 +440,8 @@
 	var compiledResDirs []android.Paths
 	for _, dir := range resDirs {
 		a.resourceFiles = append(a.resourceFiles, dir.files...)
-		compiledResDirs = append(compiledResDirs, aapt2Compile(ctx, dir.dir, dir.files, compileFlags, a.filterProduct()).Paths())
+		compiledResDirs = append(compiledResDirs, aapt2Compile(ctx, dir.dir, dir.files,
+			compileFlags, a.filterProduct(), opts.aconfigTextFiles).Paths())
 	}
 
 	for i, zip := range resZips {
@@ -499,7 +500,8 @@
 	}
 
 	for _, dir := range overlayDirs {
-		compiledOverlay = append(compiledOverlay, aapt2Compile(ctx, dir.dir, dir.files, compileFlags, a.filterProduct()).Paths()...)
+		compiledOverlay = append(compiledOverlay, aapt2Compile(ctx, dir.dir, dir.files,
+			compileFlags, a.filterProduct(), opts.aconfigTextFiles).Paths()...)
 	}
 
 	var splitPackages android.WritablePaths
@@ -798,18 +800,6 @@
 	aarFile android.WritablePath
 }
 
-var _ android.OutputFileProducer = (*AndroidLibrary)(nil)
-
-// For OutputFileProducer interface
-func (a *AndroidLibrary) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case ".aar":
-		return []android.Path{a.aarFile}, nil
-	default:
-		return a.Library.OutputFiles(tag)
-	}
-}
-
 var _ AndroidLibraryDependency = (*AndroidLibrary)(nil)
 
 func (a *AndroidLibrary) DepsMutator(ctx android.BottomUpMutatorContext) {
@@ -831,12 +821,13 @@
 	if a.usesLibrary.shouldDisableDexpreopt {
 		a.dexpreopter.disableDexpreopt()
 	}
+	aconfigTextFilePaths := getAconfigFilePaths(ctx)
 	a.aapt.buildActions(ctx,
 		aaptBuildActionOptions{
 			sdkContext:                     android.SdkContext(a),
 			classLoaderContexts:            a.classLoaderContexts,
 			enforceDefaultTargetSdkVersion: false,
-			aconfigTextFiles:               getAconfigFilePaths(ctx),
+			aconfigTextFiles:               aconfigTextFilePaths,
 			usesLibrary:                    &a.usesLibrary,
 		},
 	)
@@ -906,6 +897,17 @@
 			JniPackages: prebuiltJniPackages,
 		})
 	}
+
+	android.SetProvider(ctx, FlagsPackagesProvider, FlagsPackages{
+		AconfigTextFiles: aconfigTextFilePaths,
+	})
+
+	a.setOutputFiles(ctx)
+}
+
+func (a *AndroidLibrary) setOutputFiles(ctx android.ModuleContext) {
+	ctx.SetOutputFiles([]android.Path{a.aarFile}, ".aar")
+	setOutputFiles(ctx, a.Library.Module)
 }
 
 func (a *AndroidLibrary) IDEInfo(dpInfo *android.IdeInfo) {
@@ -1014,23 +1016,6 @@
 
 	usesLibrary
 	classLoaderContexts dexpreopt.ClassLoaderContextMap
-
-	// Single aconfig "cache file" merged from this module and all dependencies.
-	mergedAconfigFiles map[string]android.Paths
-}
-
-var _ android.OutputFileProducer = (*AARImport)(nil)
-
-// For OutputFileProducer interface
-func (a *AARImport) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case ".aar":
-		return []android.Path{a.aarPath}, nil
-	case "":
-		return []android.Path{a.implementationAndResourcesJarFile}, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
 }
 
 func (a *AARImport) SdkVersion(ctx android.EarlyModuleContext) android.SdkSpec {
@@ -1386,7 +1371,9 @@
 	android.SetProvider(ctx, JniPackageProvider, JniPackageInfo{
 		JniPackages: a.jniPackages,
 	})
-	android.CollectDependencyAconfigFiles(ctx, &a.mergedAconfigFiles)
+
+	ctx.SetOutputFiles([]android.Path{a.implementationAndResourcesJarFile}, "")
+	ctx.SetOutputFiles([]android.Path{a.aarPath}, ".aar")
 }
 
 func (a *AARImport) HeaderJars() android.Paths {
diff --git a/java/aar_test.go b/java/aar_test.go
index d6dbe3c..ebad310 100644
--- a/java/aar_test.go
+++ b/java/aar_test.go
@@ -15,8 +15,9 @@
 package java
 
 import (
-	"android/soong/android"
 	"testing"
+
+	"android/soong/android"
 )
 
 func TestAarImportProducesJniPackages(t *testing.T) {
@@ -98,6 +99,7 @@
 		aconfig_declarations {
 			name: "bar",
 			package: "com.example.package.bar",
+			container: "com.android.foo",
 			srcs: [
 				"bar.aconfig",
 			],
@@ -105,6 +107,7 @@
 		aconfig_declarations {
 			name: "baz",
 			package: "com.example.package.baz",
+			container: "com.android.foo",
 			srcs: [
 				"baz.aconfig",
 			],
@@ -156,21 +159,21 @@
 	bar := result.ModuleForTests("bar", "android_common")
 	baz := result.ModuleForTests("baz", "android_common")
 
-	fooOutputPath := android.OutputFileForModule(android.PathContext(nil), foo.Module(), "")
-	barOutputPath := android.OutputFileForModule(android.PathContext(nil), bar.Module(), "")
-	bazOutputPath := android.OutputFileForModule(android.PathContext(nil), baz.Module(), "")
+	fooOutputPaths := foo.OutputFiles(t, "")
+	barOutputPaths := bar.OutputFiles(t, "")
+	bazOutputPaths := baz.OutputFiles(t, "")
 
-	android.AssertPathRelativeToTopEquals(t, "foo output path",
-		"out/soong/.intermediates/foo/android_common/withres/foo.jar", fooOutputPath)
-	android.AssertPathRelativeToTopEquals(t, "bar output path",
-		"out/soong/.intermediates/bar/android_common/aar/bar.jar", barOutputPath)
-	android.AssertPathRelativeToTopEquals(t, "baz output path",
-		"out/soong/.intermediates/baz/android_common/withres/baz.jar", bazOutputPath)
+	android.AssertPathsRelativeToTopEquals(t, "foo output path",
+		[]string{"out/soong/.intermediates/foo/android_common/withres/foo.jar"}, fooOutputPaths)
+	android.AssertPathsRelativeToTopEquals(t, "bar output path",
+		[]string{"out/soong/.intermediates/bar/android_common/aar/bar.jar"}, barOutputPaths)
+	android.AssertPathsRelativeToTopEquals(t, "baz output path",
+		[]string{"out/soong/.intermediates/baz/android_common/withres/baz.jar"}, bazOutputPaths)
 
 	android.AssertStringEquals(t, "foo relative output path",
-		"foo.jar", fooOutputPath.Rel())
+		"foo.jar", fooOutputPaths[0].Rel())
 	android.AssertStringEquals(t, "bar relative output path",
-		"bar.jar", barOutputPath.Rel())
+		"bar.jar", barOutputPaths[0].Rel())
 	android.AssertStringEquals(t, "baz relative output path",
-		"baz.jar", bazOutputPath.Rel())
+		"baz.jar", bazOutputPaths[0].Rel())
 }
diff --git a/java/android_manifest.go b/java/android_manifest.go
index 8599003..0c77968 100644
--- a/java/android_manifest.go
+++ b/java/android_manifest.go
@@ -71,12 +71,15 @@
 	return targetSdkVersionLevel.IsPreview() && (ctx.Config().UnbundledBuildApps() || includedInMts(ctx.Module()))
 }
 
-// Helper function that casts android.Module to java.androidTestApp
-// If this type conversion is possible, it queries whether the test app is included in an MTS suite
+// Helper function that returns true if android_test, android_test_helper_app, java_test are in an MTS suite.
 func includedInMts(module android.Module) bool {
 	if test, ok := module.(androidTestApp); ok {
 		return test.includedInTestSuite("mts")
 	}
+	// java_test
+	if test, ok := module.(*Test); ok {
+		return android.PrefixInList(test.testProperties.Test_suites, "mts")
+	}
 	return false
 }
 
diff --git a/java/androidmk.go b/java/androidmk.go
index a52d439..a1bc904 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -17,7 +17,6 @@
 import (
 	"fmt"
 	"io"
-	"strings"
 
 	"android/soong/android"
 
@@ -92,11 +91,7 @@
 			ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 				func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 					if len(library.logtagsSrcs) > 0 {
-						var logtags []string
-						for _, l := range library.logtagsSrcs {
-							logtags = append(logtags, l.Rel())
-						}
-						entries.AddStrings("LOCAL_LOGTAGS_FILES", logtags...)
+						entries.AddStrings("LOCAL_SOONG_LOGTAGS_FILES", library.logtagsSrcs.Strings()...)
 					}
 
 					if library.installFile == nil {
@@ -128,7 +123,6 @@
 					if library.dexpreopter.configPath != nil {
 						entries.SetPath("LOCAL_SOONG_DEXPREOPT_CONFIG", library.dexpreopter.configPath)
 					}
-					android.SetAconfigFileMkEntries(&library.ModuleBase, entries, library.mergedAconfigFiles)
 				},
 			},
 		})
@@ -302,7 +296,6 @@
 					if len(binary.dexpreopter.builtInstalled) > 0 {
 						entries.SetString("LOCAL_SOONG_BUILT_INSTALLED", binary.dexpreopter.builtInstalled)
 					}
-					android.SetAconfigFileMkEntries(&binary.ModuleBase, entries, binary.mergedAconfigFiles)
 				},
 			},
 			ExtraFooters: []android.AndroidMkExtraFootersFunc{
@@ -420,22 +413,11 @@
 					jniSymbols := app.JNISymbolsInstalls(app.installPathForJNISymbols.String())
 					entries.SetString("LOCAL_SOONG_JNI_LIBS_SYMBOLS", jniSymbols.String())
 				} else {
+					var names []string
 					for _, jniLib := range app.jniLibs {
-						entries.AddStrings("LOCAL_SOONG_JNI_LIBS_"+jniLib.target.Arch.ArchType.String(), jniLib.name)
-						var partitionTag string
-
-						// Mimic the creation of partition_tag in build/make,
-						// which defaults to an empty string when the partition is system.
-						// Otherwise, capitalize with a leading _
-						if jniLib.partition == "system" {
-							partitionTag = ""
-						} else {
-							split := strings.Split(jniLib.partition, "/")
-							partitionTag = "_" + strings.ToUpper(split[len(split)-1])
-						}
-						entries.AddStrings("LOCAL_SOONG_JNI_LIBS_PARTITION_"+jniLib.target.Arch.ArchType.String(),
-							jniLib.name+":"+partitionTag)
+						names = append(names, jniLib.name)
 					}
+					entries.AddStrings("LOCAL_REQUIRED_MODULES", names...)
 				}
 
 				if len(app.jniCoverageOutputs) > 0 {
@@ -454,9 +436,7 @@
 
 				entries.SetOptionalPaths("LOCAL_SOONG_LINT_REPORTS", app.linter.reports)
 
-				if app.Name() != "framework-res" {
-					android.SetAconfigFileMkEntries(&app.ModuleBase, entries, app.mergedAconfigFiles)
-				}
+				entries.AddStrings("LOCAL_SOONG_LOGTAGS_FILES", app.logtagsSrcs.Strings()...)
 			},
 		},
 		ExtraFooters: []android.AndroidMkExtraFootersFunc{
@@ -533,7 +513,6 @@
 		entries.SetPath("LOCAL_FULL_MANIFEST_FILE", a.mergedManifestFile)
 		entries.SetPath("LOCAL_SOONG_EXPORT_PROGUARD_FLAGS", a.combinedExportedProguardFlagsFile)
 		entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", true)
-		android.SetAconfigFileMkEntries(&a.ModuleBase, entries, a.mergedAconfigFiles)
 	})
 
 	return entriesList
diff --git a/java/androidmk_test.go b/java/androidmk_test.go
index 1232cd1..243a279 100644
--- a/java/androidmk_test.go
+++ b/java/androidmk_test.go
@@ -20,8 +20,6 @@
 
 	"android/soong/android"
 	"android/soong/cc"
-
-	"github.com/google/blueprint/proptools"
 )
 
 func TestRequired(t *testing.T) {
@@ -161,8 +159,8 @@
 		moduleName string
 		expected   []string
 	}{
-		{"foo-shared_library", []string{"foo-shared_library.xml"}},
-		{"foo-no_shared_library", nil},
+		{"foo-shared_library", []string{"foo-shared_library.impl", "foo-shared_library.xml"}},
+		{"foo-no_shared_library", []string{"foo-no_shared_library.impl"}},
 	}
 	for _, tc := range testCases {
 		mod := result.ModuleForTests(tc.moduleName, "android_common").Module()
@@ -256,148 +254,50 @@
 	}
 }
 
-func TestJniPartition(t *testing.T) {
-	bp := `
-		cc_library {
-			name: "libjni_system",
-			system_shared_libs: [],
-			sdk_version: "current",
-			stl: "none",
-		}
-
-		cc_library {
-			name: "libjni_system_ext",
-			system_shared_libs: [],
-			sdk_version: "current",
-			stl: "none",
-			system_ext_specific: true,
-		}
-
-		cc_library {
-			name: "libjni_odm",
-			system_shared_libs: [],
-			sdk_version: "current",
-			stl: "none",
-			device_specific: true,
-		}
-
-		cc_library {
-			name: "libjni_product",
-			system_shared_libs: [],
-			sdk_version: "current",
-			stl: "none",
-			product_specific: true,
-		}
-
-		cc_library {
-			name: "libjni_vendor",
-			system_shared_libs: [],
-			sdk_version: "current",
-			stl: "none",
-			soc_specific: true,
-		}
-
-		android_app {
-			name: "test_app_system_jni_system",
-			privileged: true,
-			platform_apis: true,
-			certificate: "platform",
-			jni_libs: ["libjni_system"],
-		}
-
-		android_app {
-			name: "test_app_system_jni_system_ext",
-			privileged: true,
-			platform_apis: true,
-			certificate: "platform",
-			jni_libs: ["libjni_system_ext"],
-		}
-
-		android_app {
-			name: "test_app_system_ext_jni_system",
-			privileged: true,
-			platform_apis: true,
-			certificate: "platform",
-			jni_libs: ["libjni_system"],
-			system_ext_specific: true
-		}
-
-		android_app {
-			name: "test_app_system_ext_jni_system_ext",
-			sdk_version: "core_platform",
-			jni_libs: ["libjni_system_ext"],
-			system_ext_specific: true
-		}
-
-		android_app {
-			name: "test_app_product_jni_product",
-			sdk_version: "core_platform",
-			jni_libs: ["libjni_product"],
-			product_specific: true
-		}
-
-		android_app {
-			name: "test_app_vendor_jni_odm",
-			sdk_version: "core_platform",
-			jni_libs: ["libjni_odm"],
-			soc_specific: true
-		}
-
-		android_app {
-			name: "test_app_odm_jni_vendor",
-			sdk_version: "core_platform",
-			jni_libs: ["libjni_vendor"],
-			device_specific: true
-		}
-		android_app {
-			name: "test_app_system_jni_multiple",
-			privileged: true,
-			platform_apis: true,
-			certificate: "platform",
-			jni_libs: ["libjni_system", "libjni_system_ext"],
-		}
-		android_app {
-			name: "test_app_vendor_jni_multiple",
-			sdk_version: "core_platform",
-			jni_libs: ["libjni_odm", "libjni_vendor"],
-			soc_specific: true
-		}
-		`
-	arch := "arm64"
+func TestJniAsRequiredDeps(t *testing.T) {
 	ctx := android.GroupFixturePreparers(
 		PrepareForTestWithJavaDefaultModules,
 		cc.PrepareForTestWithCcDefaultModules,
 		android.PrepareForTestWithAndroidMk,
-		android.FixtureModifyConfig(func(config android.Config) {
-			config.TestProductVariables.DeviceArch = proptools.StringPtr(arch)
-		}),
-	).
-		RunTestWithBp(t, bp)
-	testCases := []struct {
-		name           string
-		partitionNames []string
-		partitionTags  []string
+	).RunTestWithBp(t, `
+		android_app {
+			name: "app",
+			jni_libs: ["libjni"],
+			platform_apis: true,
+		}
+
+		android_app {
+			name: "app_embedded",
+			jni_libs: ["libjni"],
+			platform_apis: true,
+			use_embedded_native_libs: true,
+		}
+
+		cc_library {
+			name: "libjni",
+			system_shared_libs: [],
+			stl: "none",
+		}
+		`)
+
+	testcases := []struct {
+		name     string
+		expected []string
 	}{
-		{"test_app_system_jni_system", []string{"libjni_system"}, []string{""}},
-		{"test_app_system_jni_system_ext", []string{"libjni_system_ext"}, []string{"_SYSTEM_EXT"}},
-		{"test_app_system_ext_jni_system", []string{"libjni_system"}, []string{""}},
-		{"test_app_system_ext_jni_system_ext", []string{"libjni_system_ext"}, []string{"_SYSTEM_EXT"}},
-		{"test_app_product_jni_product", []string{"libjni_product"}, []string{"_PRODUCT"}},
-		{"test_app_vendor_jni_odm", []string{"libjni_odm"}, []string{"_ODM"}},
-		{"test_app_odm_jni_vendor", []string{"libjni_vendor"}, []string{"_VENDOR"}},
-		{"test_app_system_jni_multiple", []string{"libjni_system", "libjni_system_ext"}, []string{"", "_SYSTEM_EXT"}},
-		{"test_app_vendor_jni_multiple", []string{"libjni_odm", "libjni_vendor"}, []string{"_ODM", "_VENDOR"}},
+		{
+			name:     "app",
+			expected: []string{"libjni"},
+		},
+		{
+			name:     "app_embedded",
+			expected: nil,
+		},
 	}
 
-	for _, test := range testCases {
-		t.Run(test.name, func(t *testing.T) {
-			mod := ctx.ModuleForTests(test.name, "android_common").Module()
-			entry := android.AndroidMkEntriesForTest(t, ctx.TestContext, mod)[0]
-			for i := range test.partitionNames {
-				actual := entry.EntryMap["LOCAL_SOONG_JNI_LIBS_PARTITION_"+arch][i]
-				expected := test.partitionNames[i] + ":" + test.partitionTags[i]
-				android.AssertStringEquals(t, "Expected and actual differ", expected, actual)
-			}
-		})
+	for _, tc := range testcases {
+		mod := ctx.ModuleForTests(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 50d1a2f..19dc8d5 100644
--- a/java/app.go
+++ b/java/app.go
@@ -47,6 +47,13 @@
 		}, "packageName")
 )
 
+type FlagsPackages struct {
+	// Paths to the aconfig dump output text files that are consumed by aapt2
+	AconfigTextFiles android.Paths
+}
+
+var FlagsPackagesProvider = blueprint.NewProvider[FlagsPackages]()
+
 func RegisterAppBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("android_app", AndroidAppFactory)
 	ctx.RegisterModuleType("android_test", AndroidTestFactory)
@@ -274,16 +281,37 @@
 		variation := append(jniTarget.Variations(),
 			blueprint.Variation{Mutator: "link", Variation: "shared"})
 
-		// If the app builds against an Android SDK use the SDK variant of JNI dependencies
-		// unless jni_uses_platform_apis is set.
-		// Don't require the SDK variant for apps that are shipped on vendor, etc., as they already
-		// have stable APIs through the VNDK.
-		if (usesSDK && !a.RequiresStableAPIs(ctx) &&
-			!Bool(a.appProperties.Jni_uses_platform_apis)) ||
-			Bool(a.appProperties.Jni_uses_sdk_apis) {
+		// Test whether to use the SDK variant or the non-SDK variant of JNI dependencies.
+		// Many factors are considered here.
+		// 1. Basically, the selection follows whether the app has sdk_version set or not.
+		jniUsesSdkVariant := usesSDK
+		// 2. However, jni_uses_platform_apis and jni_uses_sdk_apis can override it
+		if Bool(a.appProperties.Jni_uses_sdk_apis) {
+			jniUsesSdkVariant = true
+		}
+		if Bool(a.appProperties.Jni_uses_platform_apis) {
+			jniUsesSdkVariant = false
+		}
+		// 3. Then the use of SDK variant is again prohibited for the following cases:
+		// 3.1. the app is shipped on unbundled partitions like vendor. Since the entire
+		// partition (not only the app) is considered unbudled, there's no need to use the
+		// SDK variant.
+		// 3.2. the app doesn't support embedding the JNI libs
+		if a.RequiresStableAPIs(ctx) || !a.shouldEmbedJnis(ctx) {
+			jniUsesSdkVariant = false
+		}
+		if jniUsesSdkVariant {
 			variation = append(variation, blueprint.Variation{Mutator: "sdk", Variation: "sdk"})
 		}
-		ctx.AddFarVariationDependencies(variation, jniLibTag, a.appProperties.Jni_libs...)
+
+		// Use the installable dep tag when the JNIs are not embedded
+		var tag dependencyTag
+		if a.shouldEmbedJnis(ctx) {
+			tag = jniLibTag
+		} else {
+			tag = jniInstallTag
+		}
+		ctx.AddFarVariationDependencies(variation, tag, a.appProperties.Jni_libs...)
 	}
 	for _, aconfig_declaration := range a.aaptProperties.Flags_packages {
 		ctx.AddDependency(ctx.Module(), aconfigDeclarationTag, aconfig_declaration)
@@ -317,7 +345,35 @@
 	}
 }
 
+// TODO(b/156476221): Remove this allowlist
+var (
+	missingMinSdkVersionMtsAllowlist = []string{
+		"CellBroadcastReceiverGoogleUnitTests",
+		"CellBroadcastReceiverUnitTests",
+		"CtsBatterySavingTestCases",
+		"CtsDeviceAndProfileOwnerApp23",
+		"CtsDeviceAndProfileOwnerApp30",
+		"CtsIntentSenderApp",
+		"CtsJobSchedulerTestCases",
+		"CtsMimeMapTestCases",
+		"CtsTareTestCases",
+		"LibStatsPullTests",
+		"MediaProviderClientTests",
+		"TeleServiceTests",
+		"TestExternalImsServiceApp",
+		"TestSmsRetrieverApp",
+		"TetheringPrivilegedTests",
+	}
+)
+
+func checkMinSdkVersionMts(ctx android.ModuleContext, minSdkVersion android.ApiLevel) {
+	if includedInMts(ctx.Module()) && !minSdkVersion.Specified() && !android.InList(ctx.ModuleName(), missingMinSdkVersionMtsAllowlist) {
+		ctx.PropertyErrorf("min_sdk_version", "min_sdk_version is a required property for tests included in MTS")
+	}
+}
+
 func (a *AndroidTestHelperApp) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	checkMinSdkVersionMts(ctx, a.MinSdkVersion(ctx))
 	applicationId := a.appTestHelperAppProperties.Manifest_values.ApplicationId
 	if applicationId != nil {
 		if a.overridableAppProperties.Package_name != nil {
@@ -334,27 +390,17 @@
 
 func (a *AndroidApp) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	a.checkAppSdkVersions(ctx)
+	a.checkEmbedJnis(ctx)
 	a.generateAndroidBuildActions(ctx)
 	a.generateJavaUsedByApex(ctx)
 }
 
-func (a *AndroidApp) MinSdkVersion(ctx android.EarlyModuleContext) android.ApiLevel {
-	defaultMinSdkVersion := a.Module.MinSdkVersion(ctx)
-	if proptools.Bool(a.appProperties.Updatable) {
-		overrideApiLevel := android.MinSdkVersionFromValue(ctx, ctx.DeviceConfig().ApexGlobalMinSdkVersionOverride())
-		if !overrideApiLevel.IsNone() && overrideApiLevel.CompareTo(defaultMinSdkVersion) > 0 {
-			return overrideApiLevel
-		}
-	}
-	return defaultMinSdkVersion
-}
-
 func (a *AndroidApp) checkAppSdkVersions(ctx android.ModuleContext) {
 	if a.Updatable() {
 		if !a.SdkVersion(ctx).Stable() {
 			ctx.PropertyErrorf("sdk_version", "Updatable apps must use stable SDKs, found %v", a.SdkVersion(ctx))
 		}
-		if String(a.deviceProperties.Min_sdk_version) == "" {
+		if String(a.overridableProperties.Min_sdk_version) == "" {
 			ctx.PropertyErrorf("updatable", "updatable apps must set min_sdk_version.")
 		}
 
@@ -378,6 +424,17 @@
 	a.checkSdkVersions(ctx)
 }
 
+// Ensures that use_embedded_native_libs are set for apk-in-apex
+func (a *AndroidApp) checkEmbedJnis(ctx android.BaseModuleContext) {
+	apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
+	apkInApex := !apexInfo.IsForPlatform()
+	hasJnis := len(a.appProperties.Jni_libs) > 0
+
+	if apkInApex && hasJnis && !Bool(a.appProperties.Use_embedded_native_libs) {
+		ctx.ModuleErrorf("APK in APEX should have use_embedded_native_libs: true")
+	}
+}
+
 // If an updatable APK sets min_sdk_version, min_sdk_vesion of JNI libs should match with it.
 // This check is enforced for "updatable" APKs (including APK-in-APEX).
 func (a *AndroidApp) checkJniLibsSdkVersion(ctx android.ModuleContext, minSdkVersion android.ApiLevel) {
@@ -433,9 +490,9 @@
 }
 
 func (a *AndroidApp) shouldEmbedJnis(ctx android.BaseModuleContext) bool {
-	apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
 	return ctx.Config().UnbundledBuild() || Bool(a.appProperties.Use_embedded_native_libs) ||
-		!apexInfo.IsForPlatform() || a.appProperties.AlwaysPackageNativeLibs
+		Bool(a.appProperties.Updatable) ||
+		a.appProperties.AlwaysPackageNativeLibs
 }
 
 func generateAaptRenamePackageFlags(packageName string, renameResourcesPackage bool) []string {
@@ -456,18 +513,27 @@
 }
 
 func getAconfigFilePaths(ctx android.ModuleContext) (aconfigTextFilePaths android.Paths) {
-	ctx.VisitDirectDepsWithTag(aconfigDeclarationTag, func(dep android.Module) {
-		if provider, ok := android.OtherModuleProvider(ctx, dep, android.AconfigDeclarationsProviderKey); ok {
-			aconfigTextFilePaths = append(aconfigTextFilePaths, provider.IntermediateDumpOutputPath)
-		} else {
-			ctx.ModuleErrorf("Only aconfig_declarations module type is allowed for "+
-				"flags_packages property, but %s is not aconfig_declarations module type",
-				dep.Name(),
-			)
+	ctx.VisitDirectDeps(func(dep android.Module) {
+		tag := ctx.OtherModuleDependencyTag(dep)
+		switch tag {
+		case staticLibTag:
+			if flagPackages, ok := android.OtherModuleProvider(ctx, dep, FlagsPackagesProvider); ok {
+				aconfigTextFilePaths = append(aconfigTextFilePaths, flagPackages.AconfigTextFiles...)
+			}
+
+		case aconfigDeclarationTag:
+			if provider, ok := android.OtherModuleProvider(ctx, dep, android.AconfigDeclarationsProviderKey); ok {
+				aconfigTextFilePaths = append(aconfigTextFilePaths, provider.IntermediateDumpOutputPath)
+			} else {
+				ctx.ModuleErrorf("Only aconfig_declarations module type is allowed for "+
+					"flags_packages property, but %s is not aconfig_declarations module type",
+					dep.Name(),
+				)
+			}
 		}
 	})
 
-	return aconfigTextFilePaths
+	return android.FirstUniquePaths(aconfigTextFilePaths)
 }
 
 func (a *AndroidApp) aaptBuildActions(ctx android.ModuleContext) {
@@ -517,11 +583,18 @@
 	a.aapt.splitNames = a.appProperties.Package_splits
 	a.aapt.LoggingParent = String(a.overridableAppProperties.Logging_parent)
 	if a.Updatable() {
-		a.aapt.defaultManifestVersion = android.DefaultUpdatableModuleVersion
+		if override := ctx.Config().Getenv("OVERRIDE_APEX_MANIFEST_DEFAULT_VERSION"); override != "" {
+			a.aapt.defaultManifestVersion = override
+		} else {
+			a.aapt.defaultManifestVersion = android.DefaultUpdatableModuleVersion
+		}
 	}
 
 	// Use non final ids if we are doing optimized shrinking and are using R8.
-	nonFinalIds := Bool(a.dexProperties.Optimize.Optimized_shrink_resources) && a.dexer.effectiveOptimizeEnabled()
+	nonFinalIds := a.dexProperties.optimizedResourceShrinkingEnabled(ctx) && a.dexer.effectiveOptimizeEnabled()
+
+	aconfigTextFilePaths := getAconfigFilePaths(ctx)
+
 	a.aapt.buildActions(ctx,
 		aaptBuildActionOptions{
 			sdkContext:                     android.SdkContext(a),
@@ -530,13 +603,17 @@
 			enforceDefaultTargetSdkVersion: a.enforceDefaultTargetSdkVersion(),
 			forceNonFinalResourceIDs:       nonFinalIds,
 			extraLinkFlags:                 aaptLinkFlags,
-			aconfigTextFiles:               getAconfigFilePaths(ctx),
+			aconfigTextFiles:               aconfigTextFilePaths,
 			usesLibrary:                    &a.usesLibrary,
 		},
 	)
 
 	// apps manifests are handled by aapt, don't let Module see them
 	a.properties.Manifest = nil
+
+	android.SetProvider(ctx, FlagsPackagesProvider, FlagsPackages{
+		AconfigTextFiles: aconfigTextFilePaths,
+	})
 }
 
 func (a *AndroidApp) proguardBuildActions(ctx android.ModuleContext) {
@@ -552,7 +629,7 @@
 	staticLibProguardFlagFiles = android.FirstUniquePaths(staticLibProguardFlagFiles)
 
 	a.Module.extraProguardFlagsFiles = append(a.Module.extraProguardFlagsFiles, staticLibProguardFlagFiles...)
-	if !Bool(a.dexProperties.Optimize.Optimized_shrink_resources) {
+	if !(a.dexProperties.optimizedResourceShrinkingEnabled(ctx)) {
 		// When using the optimized shrinking the R8 enqueuer will traverse the xml files that become
 		// live for code references and (transitively) mark these as live.
 		// In this case we explicitly don't wan't the aapt2 generated keep files (which would keep the now
@@ -591,7 +668,7 @@
 	var packageResources = a.exportPackage
 
 	if ctx.ModuleName() != "framework-res" {
-		if a.dexProperties.resourceShrinkingEnabled() {
+		if a.dexProperties.resourceShrinkingEnabled(ctx) {
 			protoFile := android.PathForModuleOut(ctx, packageResources.Base()+".proto.apk")
 			aapt2Convert(ctx, protoFile, packageResources, "proto")
 			a.dexer.resourcesInput = android.OptionalPathForPath(protoFile)
@@ -614,7 +691,7 @@
 		}
 
 		a.Module.compile(ctx, extraSrcJars, extraClasspathJars, extraCombinedJars)
-		if a.dexProperties.resourceShrinkingEnabled() {
+		if a.dexProperties.resourceShrinkingEnabled(ctx) {
 			binaryResources := android.PathForModuleOut(ctx, packageResources.Base()+".binary.out.apk")
 			aapt2Convert(ctx, binaryResources, a.dexer.resourcesOutput.Path(), "binary")
 			packageResources = binaryResources
@@ -829,7 +906,9 @@
 
 	dexJarFile, packageResources := a.dexBuildActions(ctx)
 
-	jniLibs, prebuiltJniPackages, certificates := collectAppDeps(ctx, a, a.shouldEmbedJnis(ctx), !Bool(a.appProperties.Jni_uses_platform_apis))
+	// 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)
+	jniLibs, prebuiltJniPackages, certificates := collectAppDeps(ctx, a, a.shouldEmbedJnis(ctx), checkNativeSdkVersion)
 	jniJarFile := a.jniBuildActions(jniLibs, prebuiltJniPackages, ctx)
 
 	if ctx.Failed() {
@@ -911,6 +990,22 @@
 			installed := ctx.InstallFile(a.installDir, extra.Base(), extra)
 			extraInstalledPaths = append(extraInstalledPaths, installed)
 		}
+		// If we don't embed jni libs, make sure that those are installed along with the
+		// app, and also place symlinks to the installed paths under the lib/<arch>
+		// directory of the app installation directory. ex:
+		// /system/app/MyApp/lib/arm64/libfoo.so -> /system/lib64/libfoo.so
+		if !a.embeddedJniLibs {
+			for _, jniLib := range jniLibs {
+				archStr := jniLib.target.Arch.ArchType.String()
+				symlinkDir := a.installDir.Join(ctx, "lib", archStr)
+				for _, installedLib := range jniLib.installPaths {
+					// install the symlink itself
+					symlinkName := installedLib.Base()
+					symlinkTarget := android.InstallPathToOnDevicePath(ctx, installedLib)
+					ctx.InstallAbsoluteSymlink(symlinkDir, symlinkName, symlinkTarget)
+				}
+			}
+		}
 		ctx.InstallFile(a.installDir, a.outputFile.Base(), a.outputFile, extraInstalledPaths...)
 	}
 
@@ -922,6 +1017,22 @@
 			isPrebuilt:     false,
 		},
 	)
+
+	a.setOutputFiles(ctx)
+}
+
+func (a *AndroidApp) setOutputFiles(ctx android.ModuleContext) {
+	ctx.SetOutputFiles([]android.Path{a.proguardOptionsFile}, ".aapt.proguardOptionsFile")
+	if a.aaptSrcJar != nil {
+		ctx.SetOutputFiles([]android.Path{a.aaptSrcJar}, ".aapt.srcjar")
+	}
+	if a.rJar != nil {
+		ctx.SetOutputFiles([]android.Path{a.rJar}, ".aapt.jar")
+	}
+	ctx.SetOutputFiles([]android.Path{a.outputFile}, ".apk")
+	ctx.SetOutputFiles([]android.Path{a.exportPackage}, ".export-package.apk")
+	ctx.SetOutputFiles([]android.Path{a.aapt.manifestPath}, ".manifest.xml")
+	setOutputFiles(ctx, a.Library.Module)
 }
 
 type appDepsInterface interface {
@@ -998,6 +1109,7 @@
 						coverageFile:   dep.CoverageOutputFile(),
 						unstrippedFile: dep.UnstrippedOutputFile(),
 						partition:      dep.Partition(),
+						installPaths:   dep.FilesToInstall(),
 					})
 				} else if ctx.Config().AllowMissingDependencies() {
 					ctx.AddMissingDependencies([]string{otherName})
@@ -1111,36 +1223,11 @@
 	return a.Library.DepIsInSameApex(ctx, dep)
 }
 
-// For OutputFileProducer interface
-func (a *AndroidApp) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	// In some instances, it can be useful to reference the aapt-generated flags from another
-	// target, e.g., system server implements services declared in the framework-res manifest.
-	case ".aapt.proguardOptionsFile":
-		return []android.Path{a.proguardOptionsFile}, nil
-	case ".aapt.srcjar":
-		if a.aaptSrcJar != nil {
-			return []android.Path{a.aaptSrcJar}, nil
-		}
-	case ".aapt.jar":
-		if a.rJar != nil {
-			return []android.Path{a.rJar}, nil
-		}
-	case ".apk":
-		return []android.Path{a.outputFile}, nil
-	case ".export-package.apk":
-		return []android.Path{a.exportPackage}, nil
-	case ".manifest.xml":
-		return []android.Path{a.aapt.manifestPath}, nil
-	}
-	return a.Library.OutputFiles(tag)
-}
-
 func (a *AndroidApp) Privileged() bool {
 	return Bool(a.appProperties.Privileged)
 }
 
-func (a *AndroidApp) IsNativeCoverageNeeded(ctx android.IncomingTransitionContext) bool {
+func (a *AndroidApp) IsNativeCoverageNeeded(ctx cc.IsNativeCoverageNeededContext) bool {
 	return ctx.Device() && ctx.DeviceConfig().NativeCoverageEnabled()
 }
 
@@ -1243,6 +1330,11 @@
 			Manifest:       proptools.StringPtr(":" + rroManifestName),
 			Resource_dirs:  a.aaptProperties.Resource_dirs,
 		}
+		if !Bool(a.aaptProperties.Aapt_include_all_resources) {
+			for _, aaptConfig := range ctx.Config().ProductAAPTConfig() {
+				rroProperties.Aaptflags = append(rroProperties.Aaptflags, "-c", aaptConfig)
+			}
+		}
 		ctx.CreateModule(RuntimeResourceOverlayFactory, &rroProperties)
 	})
 
@@ -1297,6 +1389,7 @@
 }
 
 func (a *AndroidTest) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	checkMinSdkVersionMts(ctx, a.MinSdkVersion(ctx))
 	var configs []tradefed.Config
 	if a.appTestProperties.Instrumentation_target_package != nil {
 		a.additionalAaptFlags = append(a.additionalAaptFlags,
@@ -1334,6 +1427,8 @@
 		HostRequiredModuleNames: a.HostRequiredModuleNames(),
 		TestSuites:              a.testProperties.Test_suites,
 		IsHost:                  false,
+		LocalCertificate:        a.certificate.AndroidMkString(),
+		IsUnitTest:              Bool(a.testProperties.Test_options.Unit_test),
 	})
 	android.SetProvider(ctx, android.TestOnlyProviderKey, android.TestModuleInformation{
 		TestOnly:       true,
diff --git a/java/app_import.go b/java/app_import.go
index bb07c42..fa87997 100644
--- a/java/app_import.go
+++ b/java/app_import.go
@@ -17,7 +17,6 @@
 // This file contains the module implementations for android_app_import and android_test_import.
 
 import (
-	"fmt"
 	"reflect"
 	"strings"
 
@@ -87,9 +86,6 @@
 	hideApexVariantFromMake bool
 
 	provenanceMetaDataFile android.OutputPath
-
-	// Single aconfig "cache file" merged from this module and all dependencies.
-	mergedAconfigFiles map[string]android.Paths
 }
 
 type AndroidAppImportProperties struct {
@@ -416,7 +412,6 @@
 		artifactPath := android.PathForModuleSrc(ctx, *a.properties.Apk)
 		a.provenanceMetaDataFile = provenance.GenerateArtifactProvenanceMetaData(ctx, artifactPath, a.installPath)
 	}
-	android.CollectDependencyAconfigFiles(ctx, &a.mergedAconfigFiles)
 
 	providePrebuiltInfo(ctx,
 		prebuiltInfoProps{
@@ -426,6 +421,8 @@
 		},
 	)
 
+	ctx.SetOutputFiles([]android.Path{a.outputFile}, "")
+
 	// TODO: androidmk converter jni libs
 }
 
@@ -465,15 +462,6 @@
 	return a.outputFile
 }
 
-func (a *AndroidAppImport) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		return []android.Path{a.outputFile}, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
 func (a *AndroidAppImport) JacocoReportClassesFile() android.Path {
 	return nil
 }
diff --git a/java/app_import_test.go b/java/app_import_test.go
index 5de50e7..496fc13 100644
--- a/java/app_import_test.go
+++ b/java/app_import_test.go
@@ -509,7 +509,7 @@
 
 			variant := ctx.ModuleForTests("foo", "android_common")
 			if test.expected == "" {
-				if variant.Module().Enabled() {
+				if variant.Module().Enabled(android.PanickingConfigAndErrorContext(ctx)) {
 					t.Error("module should have been disabled, but wasn't")
 				}
 				rule := variant.MaybeRule("genProvenanceMetaData")
@@ -586,7 +586,7 @@
 
 			variant := ctx.ModuleForTests("foo", "android_common")
 			if test.expected == "" {
-				if variant.Module().Enabled() {
+				if variant.Module().Enabled(android.PanickingConfigAndErrorContext(ctx)) {
 					t.Error("module should have been disabled, but wasn't")
 				}
 				rule := variant.MaybeRule("genProvenanceMetaData")
@@ -629,7 +629,7 @@
 	if !a.prebuilt.UsePrebuilt() {
 		t.Errorf("prebuilt foo module is not active")
 	}
-	if !a.Enabled() {
+	if !a.Enabled(android.PanickingConfigAndErrorContext(ctx)) {
 		t.Errorf("prebuilt foo module is disabled")
 	}
 }
diff --git a/java/app_test.go b/java/app_test.go
index eab40e7..6b7d522 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -119,10 +119,7 @@
 		foo.Output(expectedOutput)
 	}
 
-	outputFiles, err := foo.Module().(*AndroidApp).OutputFiles("")
-	if err != nil {
-		t.Fatal(err)
-	}
+	outputFiles := foo.OutputFiles(t, "")
 	android.AssertPathsRelativeToTopEquals(t, `OutputFiles("")`, expectedOutputs, outputFiles)
 }
 
@@ -519,6 +516,49 @@
 	testJavaError(t, `"libjni" .*: links "libbar" built against newer API version "current"`, bp)
 }
 
+func TestUpdatableApps_ApplyDefaultUpdatableModuleVersion(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+	).RunTestWithBp(t, `
+		android_app {
+			name: "com.android.foo",
+			srcs: ["a.java"],
+			sdk_version: "current",
+			min_sdk_version: "31",
+			updatable: true,
+		}
+	`)
+	foo := result.ModuleForTests("com.android.foo", "android_common").Rule("manifestFixer")
+	android.AssertStringDoesContain(t,
+		"com.android.foo: expected manifest fixer to set override-placeholder-version to android.DefaultUpdatableModuleVersion",
+		foo.BuildParams.Args["args"],
+		fmt.Sprintf("--override-placeholder-version %s", android.DefaultUpdatableModuleVersion),
+	)
+}
+
+func TestUpdatableApps_ApplyOverrideApexManifestDefaultVersion(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		android.FixtureMergeEnv(map[string]string{
+			"OVERRIDE_APEX_MANIFEST_DEFAULT_VERSION": "1234",
+		}),
+	).RunTestWithBp(t, `
+		android_app {
+			name: "com.android.foo",
+			srcs: ["a.java"],
+			sdk_version: "current",
+			min_sdk_version: "31",
+			updatable: true,
+		}
+	`)
+	foo := result.ModuleForTests("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"],
+		"--override-placeholder-version 1234",
+	)
+}
+
 func TestResourceDirs(t *testing.T) {
 	testCases := []struct {
 		name      string
@@ -4146,6 +4186,7 @@
 	bpTemplate := `
 	%v {
 		name: "mytest",
+		min_sdk_version: "34",
 		target_sdk_version: "%v",
 		test_suites: ["othersuite", "%v"],
 	}
@@ -4322,54 +4363,17 @@
 	)
 }
 
-func TestApexGlobalMinSdkVersionOverride(t *testing.T) {
-	result := android.GroupFixturePreparers(
-		PrepareForTestWithJavaDefaultModules,
-		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
-			variables.ApexGlobalMinSdkVersionOverride = proptools.StringPtr("Tiramisu")
-		}),
-	).RunTestWithBp(t, `
-		android_app {
-			name: "com.android.bar",
-			srcs: ["a.java"],
-			sdk_version: "current",
-		}
-		android_app {
-			name: "com.android.foo",
-			srcs: ["a.java"],
-			sdk_version: "current",
-			min_sdk_version: "S",
-			updatable: true,
-		}
-		override_android_app {
-			name: "com.android.go.foo",
-			base: "com.android.foo",
-		}
-	`)
-	foo := result.ModuleForTests("com.android.foo", "android_common").Rule("manifestFixer")
-	fooOverride := result.ModuleForTests("com.android.foo", "android_common_com.android.go.foo").Rule("manifestFixer")
-	bar := result.ModuleForTests("com.android.bar", "android_common").Rule("manifestFixer")
-
-	android.AssertStringDoesContain(t,
-		"expected manifest fixer to set com.android.bar minSdkVersion to S",
-		bar.BuildParams.Args["args"],
-		"--minSdkVersion  S",
-	)
-	android.AssertStringDoesContain(t,
-		"com.android.foo: expected manifest fixer to set minSdkVersion to T",
-		foo.BuildParams.Args["args"],
-		"--minSdkVersion  T",
-	)
-	android.AssertStringDoesContain(t,
-		"com.android.go.foo: expected manifest fixer to set minSdkVersion to T",
-		fooOverride.BuildParams.Args["args"],
-		"--minSdkVersion  T",
-	)
-
-}
-
 func TestAppFlagsPackages(t *testing.T) {
-	ctx := testApp(t, `
+	ctx := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		android.FixtureMergeMockFs(
+			map[string][]byte{
+				"res/layout/layout.xml":         nil,
+				"res/values/strings.xml":        nil,
+				"res/values-en-rUS/strings.xml": nil,
+			},
+		),
+	).RunTestWithBp(t, `
 		android_app {
 			name: "foo",
 			srcs: ["a.java"],
@@ -4382,6 +4386,7 @@
 		aconfig_declarations {
 			name: "bar",
 			package: "com.example.package.bar",
+			container: "com.android.foo",
 			srcs: [
 				"bar.aconfig",
 			],
@@ -4389,6 +4394,7 @@
 		aconfig_declarations {
 			name: "baz",
 			package: "com.example.package.baz",
+			container: "com.android.foo",
 			srcs: [
 				"baz.aconfig",
 			],
@@ -4399,10 +4405,10 @@
 
 	// android_app module depends on aconfig_declarations listed in flags_packages
 	android.AssertBoolEquals(t, "foo expected to depend on bar", true,
-		CheckModuleHasDependency(t, ctx, "foo", "android_common", "bar"))
+		CheckModuleHasDependency(t, ctx.TestContext, "foo", "android_common", "bar"))
 
 	android.AssertBoolEquals(t, "foo expected to depend on baz", true,
-		CheckModuleHasDependency(t, ctx, "foo", "android_common", "baz"))
+		CheckModuleHasDependency(t, ctx.TestContext, "foo", "android_common", "baz"))
 
 	aapt2LinkRule := foo.Rule("android/soong/java.aapt2Link")
 	linkInFlags := aapt2LinkRule.Args["inFlags"]
@@ -4411,6 +4417,90 @@
 		linkInFlags,
 		"--feature-flags @out/soong/.intermediates/bar/intermediate.txt --feature-flags @out/soong/.intermediates/baz/intermediate.txt",
 	)
+
+	aapt2CompileRule := foo.Rule("android/soong/java.aapt2Compile")
+	compileFlags := aapt2CompileRule.Args["cFlags"]
+	android.AssertStringDoesContain(t,
+		"aapt2 compile command expected to pass feature flags arguments",
+		compileFlags,
+		"--feature-flags @out/soong/.intermediates/bar/intermediate.txt --feature-flags @out/soong/.intermediates/baz/intermediate.txt",
+	)
+}
+
+func TestAppFlagsPackagesPropagation(t *testing.T) {
+	ctx := testApp(t, `
+		aconfig_declarations {
+			name: "foo",
+			package: "com.example.package.foo",
+			container: "com.android.foo",
+			srcs: [
+				"foo.aconfig",
+			],
+		}
+		aconfig_declarations {
+			name: "bar",
+			package: "com.example.package.bar",
+			container: "com.android.bar",
+			srcs: [
+				"bar.aconfig",
+			],
+		}
+		aconfig_declarations {
+			name: "baz",
+			package: "com.example.package.baz",
+			container: "com.android.baz",
+			srcs: [
+				"baz.aconfig",
+			],
+		}
+		android_library {
+			name: "foo_lib",
+			srcs: ["a.java"],
+			sdk_version: "current",
+			flags_packages: [
+				"foo",
+			],
+		}
+		android_library {
+			name: "bar_lib",
+			srcs: ["a.java"],
+			sdk_version: "current",
+			flags_packages: [
+				"bar",
+			],
+		}
+		android_app {
+			name: "baz_app",
+			srcs: ["a.java"],
+			sdk_version: "current",
+			flags_packages: [
+				"baz",
+			],
+			static_libs: [
+				"bar_lib",
+			],
+			libs: [
+				"foo_lib",
+			],
+		}
+	`)
+
+	bazApp := ctx.ModuleForTests("baz_app", "android_common")
+
+	// android_app module depends on aconfig_declarations listed in flags_packages
+	// and that of static libs, but not libs
+	aapt2LinkRule := bazApp.Rule("android/soong/java.aapt2Link")
+	linkInFlags := aapt2LinkRule.Args["inFlags"]
+	android.AssertStringDoesContain(t,
+		"aapt2 link command expected to pass feature flags arguments of flags_packages and that of its static libs",
+		linkInFlags,
+		"--feature-flags @out/soong/.intermediates/bar/intermediate.txt --feature-flags @out/soong/.intermediates/baz/intermediate.txt",
+	)
+	android.AssertStringDoesNotContain(t,
+		"aapt2 link command expected to not pass feature flags arguments of flags_packages of its libs",
+		linkInFlags,
+		"--feature-flags @out/soong/.intermediates/foo/intermediate.txt",
+	)
 }
 
 // Test that dexpreopt is disabled if an optional_uses_libs exists, but does not provide an implementation.
@@ -4490,3 +4580,77 @@
 		t.Errorf("Module output does not contain expected apk %s", "foo-new.apk")
 	}
 }
+
+func TestAppMinSdkVersionOverride(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+	).RunTestWithBp(t, `
+		android_app {
+			name: "com.android.foo",
+			srcs: ["a.java"],
+			sdk_version: "current",
+			min_sdk_version: "31",
+			updatable: true,
+		}
+		override_android_app {
+			name: "com.android.go.foo",
+			base: "com.android.foo",
+			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")
+
+	android.AssertStringDoesContain(t,
+		"com.android.foo: expected manifest fixer to set minSdkVersion to T",
+		foo.BuildParams.Args["args"],
+		"--minSdkVersion  31",
+	)
+	android.AssertStringDoesContain(t,
+		"com.android.go.foo: expected manifest fixer to set minSdkVersion to T",
+		fooOverride.BuildParams.Args["args"],
+		"--minSdkVersion  33",
+	)
+
+}
+
+func TestNotApplyDefaultUpdatableModuleVersion(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+	).RunTestWithBp(t, `
+		android_app {
+			name: "com.android.foo",
+			srcs: ["a.java"],
+			sdk_version: "current",
+			min_sdk_version: "31",
+		}
+	`)
+	foo := result.ModuleForTests("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 TestNotApplyOverrideApexManifestDefaultVersion(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithJavaDefaultModules,
+		android.FixtureMergeEnv(map[string]string{
+			"OVERRIDE_APEX_MANIFEST_DEFAULT_VERSION": "1234",
+		}),
+	).RunTestWithBp(t, `
+		android_app {
+			name: "com.android.foo",
+			srcs: ["a.java"],
+			sdk_version: "current",
+			min_sdk_version: "31",
+		}
+	`)
+	foo := result.ModuleForTests("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",
+	)
+}
diff --git a/java/base.go b/java/base.go
index 938ac5e..02df147 100644
--- a/java/base.go
+++ b/java/base.go
@@ -91,9 +91,16 @@
 	// if not blank, run jarjar using the specified rules file
 	Jarjar_rules *string `android:"path,arch_variant"`
 
+	// java class names to rename with jarjar when a reverse dependency has a jarjar_prefix
+	// property.
+	Jarjar_rename []string
+
 	// if not blank, used as prefix to generate repackage rule
 	Jarjar_prefix *string
 
+	// if set to true, skip the jarjar repackaging
+	Skip_jarjar_repackage *bool
+
 	// If not blank, set the java version passed to javac as -source and -target
 	Java_version *string
 
@@ -229,10 +236,6 @@
 	// If the SDK kind is empty, it will be set to public.
 	Sdk_version *string
 
-	// if not blank, set the minimum version of the sdk that the compiled artifacts will run against.
-	// Defaults to sdk_version if not set. See sdk_version for possible values.
-	Min_sdk_version *string
-
 	// if not blank, set the maximum version of the sdk that the compiled artifacts will run against.
 	// Defaults to empty string "". See sdk_version for possible values.
 	Max_sdk_version *string
@@ -312,6 +315,10 @@
 	// Otherwise, both the overridden and the overriding modules will have the same output name, which
 	// can cause the duplicate output error.
 	Stem *string
+
+	// if not blank, set the minimum version of the sdk that the compiled artifacts will run against.
+	// Defaults to sdk_version if not set. See sdk_version for possible values.
+	Min_sdk_version *string
 }
 
 // Functionality common to Module and Import
@@ -537,9 +544,6 @@
 	// or the module should override Stem().
 	stem string
 
-	// Single aconfig "cache file" merged from this module and all dependencies.
-	mergedAconfigFiles map[string]android.Paths
-
 	// Values that will be set in the JarJarProvider data for jarjar repackaging,
 	// and merged with our dependencies' rules.
 	jarjarRenameRules map[string]string
@@ -550,6 +554,22 @@
 	// java_aconfig_library or java_library modules that are statically linked
 	// to this module. Does not contain cache files from all transitive dependencies.
 	aconfigCacheFiles android.Paths
+
+	// List of soong module dependencies required to compile the current module.
+	// This information is printed out to `Dependencies` field in module_bp_java_deps.json
+	compileDepNames []string
+}
+
+var _ android.InstallableModule = (*Module)(nil)
+
+// To satisfy the InstallableModule interface
+func (j *Module) EnforceApiContainerChecks() bool {
+	return true
+}
+
+// Overrides android.ModuleBase.InstallInProduct()
+func (j *Module) InstallInProduct() bool {
+	return j.ProductSpecific()
 }
 
 func (j *Module) CheckStableSdkVersion(ctx android.BaseModuleContext) error {
@@ -652,35 +672,21 @@
 	android.SetProvider(ctx, hiddenAPIPropertyInfoProvider, hiddenAPIInfo)
 }
 
-func (j *Module) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		return append(android.Paths{j.outputFile}, j.extraOutputFiles...), nil
-	case android.DefaultDistTag:
-		return android.Paths{j.outputFile}, nil
-	case ".jar":
-		return android.Paths{j.implementationAndResourcesJar}, nil
-	case ".hjar":
-		return android.Paths{j.headerJarFile}, nil
-	case ".proguard_map":
-		if j.dexer.proguardDictionary.Valid() {
-			return android.Paths{j.dexer.proguardDictionary.Path()}, nil
-		}
-		return nil, fmt.Errorf("%q was requested, but no output file was found.", tag)
-	case ".generated_srcjars":
-		return j.properties.Generated_srcjars, nil
-	case ".lint":
-		if j.linter.outputs.xml != nil {
-			return android.Paths{j.linter.outputs.xml}, nil
-		}
-		return nil, fmt.Errorf("%q was requested, but no output file was found.", tag)
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+// 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")
+	if m.dexer.proguardDictionary.Valid() {
+		ctx.SetOutputFiles(android.Paths{m.dexer.proguardDictionary.Path()}, ".proguard_map")
+	}
+	ctx.SetOutputFiles(m.properties.Generated_srcjars, ".generated_srcjars")
+	if m.linter.outputs.xml != nil {
+		ctx.SetOutputFiles(android.Paths{m.linter.outputs.xml}, ".lint")
 	}
 }
 
-var _ android.OutputFileProducer = (*Module)(nil)
-
 func InitJavaModule(module android.DefaultableModule, hod android.HostOrDeviceSupported) {
 	initJavaModule(module, hod, false)
 }
@@ -718,6 +724,7 @@
 	// doesn't make sense) or framework libraries (e.g. libraries found in the InstrumentFrameworkModules list) unless EMMA_INSTRUMENT_FRAMEWORK is true.
 	apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
 	isJacocoAgent := ctx.ModuleName() == "jacocoagent"
+
 	if j.DirectlyInAnyApex() && !isJacocoAgent && !apexInfo.IsForPlatform() {
 		if !inList(ctx.ModuleName(), config.InstrumentFrameworkModules) {
 			return true
@@ -741,8 +748,8 @@
 }
 
 func (j *Module) MinSdkVersion(ctx android.EarlyModuleContext) android.ApiLevel {
-	if j.deviceProperties.Min_sdk_version != nil {
-		return android.ApiLevelFrom(ctx, *j.deviceProperties.Min_sdk_version)
+	if j.overridableProperties.Min_sdk_version != nil {
+		return android.ApiLevelFrom(ctx, *j.overridableProperties.Min_sdk_version)
 	}
 	return j.SdkVersion(ctx).ApiLevel
 }
@@ -1111,11 +1118,13 @@
 	jarjarProviderData := j.collectJarJarRules(ctx)
 	if jarjarProviderData != nil {
 		android.SetProvider(ctx, JarJarProvider, *jarjarProviderData)
-		text := getJarJarRuleText(jarjarProviderData)
-		if text != "" {
-			ruleTextFile := android.PathForModuleOut(ctx, "repackaged-jarjar", "repackaging.txt")
-			android.WriteFileRule(ctx, ruleTextFile, text)
-			j.repackageJarjarRules = ruleTextFile
+		if !proptools.Bool(j.properties.Skip_jarjar_repackage) {
+			text := getJarJarRuleText(jarjarProviderData)
+			if text != "" {
+				ruleTextFile := android.PathForModuleOut(ctx, "repackaged-jarjar", "repackaging.txt")
+				android.WriteFileRule(ctx, ruleTextFile, text)
+				j.repackageJarjarRules = ruleTextFile
+			}
 		}
 	}
 
@@ -1652,11 +1661,28 @@
 				classesJar:    implementationAndResourcesJar,
 				jarName:       jarName,
 			}
-			dexOutputFile = j.dexer.compileDex(ctx, params)
+			if j.GetProfileGuided() && j.optimizeOrObfuscateEnabled() && !j.EnableProfileRewriting() {
+				ctx.PropertyErrorf("enable_profile_rewriting",
+					"Enable_profile_rewriting must be true when profile_guided dexpreopt and R8 optimization/obfuscation is turned on. The attached profile should be sourced from an unoptimized/unobfuscated APK.",
+				)
+			}
+			if j.EnableProfileRewriting() {
+				profile := j.GetProfile()
+				if profile == "" || !j.GetProfileGuided() {
+					ctx.PropertyErrorf("enable_profile_rewriting", "Profile and Profile_guided must be set when enable_profile_rewriting is true")
+				}
+				params.artProfileInput = &profile
+			}
+			dexOutputFile, dexArtProfileOutput := j.dexer.compileDex(ctx, params)
 			if ctx.Failed() {
 				return
 			}
 
+			// If r8/d8 provides a profile that matches the optimized dex, use that for dexpreopt.
+			if dexArtProfileOutput != nil {
+				j.dexpreopter.SetRewrittenProfile(*dexArtProfileOutput)
+			}
+
 			// merge dex jar with resources if necessary
 			if j.resourceJar != nil {
 				jars := android.Paths{dexOutputFile, j.resourceJar}
@@ -1682,7 +1708,11 @@
 			j.dexJarFile = makeDexJarPathFromPath(dexOutputFile)
 
 			// Dexpreopting
-			j.dexpreopt(ctx, android.RemoveOptionalPrebuiltPrefix(ctx.ModuleName()), dexOutputFile)
+			libName := android.RemoveOptionalPrebuiltPrefix(ctx.ModuleName())
+			if j.SdkLibraryName() != nil && strings.HasSuffix(ctx.ModuleName(), ".impl") {
+				libName = strings.TrimSuffix(libName, ".impl")
+			}
+			j.dexpreopt(ctx, libName, dexOutputFile)
 
 			outputFile = dexOutputFile
 		} else {
@@ -1730,8 +1760,6 @@
 
 	ctx.CheckbuildFile(outputFile)
 
-	android.CollectDependencyAconfigFiles(ctx, &j.mergedAconfigFiles)
-
 	android.SetProvider(ctx, JavaInfoProvider, JavaInfo{
 		HeaderJars:                          android.PathsIfNonNil(j.headerJarFile),
 		RepackagedHeaderJars:                android.PathsIfNonNil(j.repackagedHeaderJarFile),
@@ -1835,7 +1863,7 @@
 	classes := android.PathForModuleOut(ctx, "javac", jarName).OutputPath
 	TransformJavaToClasses(ctx, classes, idx, srcFiles, srcJars, annoSrcJar, flags, extraJarDeps)
 
-	if ctx.Config().EmitXrefRules() {
+	if ctx.Config().EmitXrefRules() && ctx.Module() == ctx.PrimaryModule() {
 		extractionFile := android.PathForModuleOut(ctx, kzipName)
 		emitXrefRule(ctx, extractionFile, idx, srcFiles, srcJars, flags, extraJarDeps)
 		j.kytheFiles = append(j.kytheFiles, extractionFile)
@@ -2041,10 +2069,7 @@
 }
 
 func (j *Module) CompilerDeps() []string {
-	jdeps := []string{}
-	jdeps = append(jdeps, j.properties.Libs...)
-	jdeps = append(jdeps, j.properties.Static_libs...)
-	return jdeps
+	return j.compileDepNames
 }
 
 func (j *Module) hasCode(ctx android.ModuleContext) bool {
@@ -2388,6 +2413,11 @@
 			}
 		}
 
+		if android.InList(tag, compileDependencyTags) {
+			// Add the dependency name to compileDepNames so that it can be recorded in module_bp_java_deps.json
+			j.compileDepNames = append(j.compileDepNames, otherName)
+		}
+
 		addCLCFromDep(ctx, module, j.classLoaderContexts)
 		addMissingOptionalUsesLibsFromDep(ctx, module, &j.usesLibrary)
 	})
@@ -2527,7 +2557,7 @@
 				case Implementation:
 					return RenameUseInclude, "info"
 				default:
-					//fmt.Printf("LJ: %v -> %v StubsLinkType unknown\n", module, m)
+					//fmt.Printf("collectDirectDepsProviders: %v -> %v StubsLinkType unknown\n", module, m)
 					// Fall through to the heuristic logic.
 				}
 				switch reflect.TypeOf(m).String() {
@@ -2629,8 +2659,7 @@
 	// Gather repackage information from deps
 	result := collectDirectDepsProviders(ctx)
 
-	// Update that with entries we've stored for ourself
-	for orig, renamed := range module.jarjarRenameRules {
+	add := func(orig string, renamed string) {
 		if result == nil {
 			result = &JarJarProviderData{
 				Rename: make(map[string]string),
@@ -2639,12 +2668,22 @@
 		if renamed != "" {
 			if preexisting, exists := (*result).Rename[orig]; exists && preexisting != renamed {
 				ctx.ModuleErrorf("Conflicting jarjar rules inherited for class: %s (%s and %s)", orig, renamed, preexisting)
-				continue
+				return
 			}
 		}
 		(*result).Rename[orig] = renamed
 	}
 
+	// Update that with entries we've stored for ourself
+	for orig, renamed := range module.jarjarRenameRules {
+		add(orig, renamed)
+	}
+
+	// Update that with entries given in the jarjar_rename property.
+	for _, orig := range module.properties.Jarjar_rename {
+		add(orig, "")
+	}
+
 	// If there are no renamings, then jarjar_prefix does nothing, so skip the extra work.
 	if result == nil {
 		return nil
diff --git a/java/boot_jars.go b/java/boot_jars.go
index 5d40ec3..6223ded 100644
--- a/java/boot_jars.go
+++ b/java/boot_jars.go
@@ -21,8 +21,8 @@
 // isActiveModule returns true if the given module should be considered for boot
 // jars, i.e. if it's enabled and the preferred one in case of source and
 // prebuilt alternatives.
-func isActiveModule(module android.Module) bool {
-	if !module.Enabled() {
+func isActiveModule(ctx android.ConfigAndErrorContext, module android.Module) bool {
+	if !module.Enabled(ctx) {
 		return false
 	}
 	return android.IsModulePreferred(module)
diff --git a/java/bootclasspath.go b/java/bootclasspath.go
index c7dc3af..77ddf5c 100644
--- a/java/bootclasspath.go
+++ b/java/bootclasspath.go
@@ -127,7 +127,10 @@
 // added by addDependencyOntoApexModulePair.
 func gatherApexModulePairDepsWithTag(ctx android.BaseModuleContext, tag blueprint.DependencyTag) []android.Module {
 	var modules []android.Module
-	ctx.VisitDirectDepsIf(isActiveModule, func(module android.Module) {
+	isActiveModulePred := func(module android.Module) bool {
+		return isActiveModule(ctx, module)
+	}
+	ctx.VisitDirectDepsIf(isActiveModulePred, func(module android.Module) {
 		t := ctx.OtherModuleDependencyTag(module)
 		if t == tag {
 			modules = append(modules, module)
diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go
index cc3da76..16209b7 100644
--- a/java/bootclasspath_fragment.go
+++ b/java/bootclasspath_fragment.go
@@ -474,7 +474,7 @@
 	// Only perform a consistency check if this module is the active module. That will prevent an
 	// unused prebuilt that was created without instrumentation from breaking an instrumentation
 	// build.
-	if isActiveModule(ctx.Module()) {
+	if isActiveModule(ctx, ctx.Module()) {
 		b.bootclasspathFragmentPropertyCheck(ctx)
 	}
 
@@ -519,15 +519,21 @@
 // empty string if this module should not provide a boot image profile.
 func (b *BootclasspathFragmentModule) getProfileProviderApex(ctx android.BaseModuleContext) string {
 	// Only use the profile from the module that is preferred.
-	if !isActiveModule(ctx.Module()) {
+	if !isActiveModule(ctx, ctx.Module()) {
 		return ""
 	}
 
 	// Bootclasspath fragment modules that are for the platform do not produce boot related files.
-	apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
-	for _, apex := range apexInfo.InApexVariants {
-		if isProfileProviderApex(ctx, apex) {
-			return apex
+	apexInfos, _ := android.ModuleProvider(ctx, android.AllApexInfoProvider)
+	if apexInfos == nil {
+		return ""
+	}
+
+	for _, apexInfo := range apexInfos.ApexInfos {
+		for _, apex := range apexInfo.InApexVariants {
+			if isProfileProviderApex(ctx, apex) {
+				return apex
+			}
 		}
 	}
 
@@ -590,13 +596,36 @@
 		// So ignore it even if it is not in PRODUCT_APEX_BOOT_JARS.
 		// TODO(b/202896428): Add better way to handle this.
 		_, unknown = android.RemoveFromList("android.car-module", unknown)
-		if isActiveModule(ctx.Module()) && len(unknown) > 0 {
-			ctx.ModuleErrorf("%s in contents must also be declared in PRODUCT_APEX_BOOT_JARS", unknown)
+		if isApexVariant(ctx) && len(unknown) > 0 {
+			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) {
+				// source bcpf, and prebuilt apex are not selected.
+				ctx.ModuleErrorf("%s in contents must also be declared in PRODUCT_APEX_BOOT_JARS", unknown)
+			}
 		}
 	}
 	return jars
 }
 
+var ClasspathFragmentValidationInfoProvider = blueprint.NewProvider[ClasspathFragmentValidationInfo]()
+
+type ClasspathFragmentValidationInfo struct {
+	ClasspathFragmentModuleName string
+	UnknownJars                 []string
+}
+
+// Set a provider with the list of jars that have not been added to PRODUCT_APEX_BOOT_JARS
+// The validation will be done in the ctx of the top-level _selected_ apex
+func providerClasspathFragmentValidationInfoProvider(ctx android.ModuleContext, unknown []string) {
+	info := ClasspathFragmentValidationInfo{
+		ClasspathFragmentModuleName: ctx.ModuleName(),
+		UnknownJars:                 unknown,
+	}
+	android.SetProvider(ctx, ClasspathFragmentValidationInfoProvider, info)
+}
+
 // generateHiddenAPIBuildActions generates all the hidden API related build rules.
 func (b *BootclasspathFragmentModule) generateHiddenAPIBuildActions(ctx android.ModuleContext, contents []android.Module, fragments []android.Module) *HiddenAPIOutput {
 
diff --git a/java/classpath_fragment.go b/java/classpath_fragment.go
index 0ebab4d..18a5dae 100644
--- a/java/classpath_fragment.go
+++ b/java/classpath_fragment.go
@@ -128,19 +128,21 @@
 				return m.Name() == configuredJars.Jar(i)
 			}, func(m android.Module) {
 				if s, ok := m.(*SdkLibrary); ok {
+					minSdkVersion := s.MinSdkVersion(ctx)
+					maxSdkVersion := s.MaxSdkVersion(ctx)
 					// TODO(208456999): instead of mapping "current" to latest, min_sdk_version should never be set to "current"
-					if s.minSdkVersion.Specified() {
-						if s.minSdkVersion.IsCurrent() {
+					if minSdkVersion.Specified() {
+						if minSdkVersion.IsCurrent() {
 							jar.minSdkVersion = ctx.Config().DefaultAppTargetSdk(ctx).String()
 						} else {
-							jar.minSdkVersion = s.minSdkVersion.String()
+							jar.minSdkVersion = minSdkVersion.String()
 						}
 					}
-					if s.maxSdkVersion.Specified() {
-						if s.maxSdkVersion.IsCurrent() {
+					if maxSdkVersion.Specified() {
+						if maxSdkVersion.IsCurrent() {
 							jar.maxSdkVersion = ctx.Config().DefaultAppTargetSdk(ctx).String()
 						} else {
-							jar.maxSdkVersion = s.maxSdkVersion.String()
+							jar.maxSdkVersion = maxSdkVersion.String()
 						}
 					}
 				}
@@ -151,10 +153,14 @@
 	return jars
 }
 
+func (c *ClasspathFragmentBase) outputFilename() string {
+	return strings.ToLower(c.classpathType.String()) + ".pb"
+}
+
 func (c *ClasspathFragmentBase) generateClasspathProtoBuildActions(ctx android.ModuleContext, configuredJars android.ConfiguredJarList, jars []classpathJar) {
 	generateProto := proptools.BoolDefault(c.properties.Generate_classpaths_proto, true)
 	if generateProto {
-		outputFilename := strings.ToLower(c.classpathType.String()) + ".pb"
+		outputFilename := c.outputFilename()
 		c.outputFilepath = android.PathForModuleOut(ctx, outputFilename).OutputPath
 		c.installDirPath = android.PathForModuleInstall(ctx, "etc", "classpaths")
 
@@ -181,6 +187,10 @@
 	android.SetProvider(ctx, ClasspathFragmentProtoContentInfoProvider, classpathProtoInfo)
 }
 
+func (c *ClasspathFragmentBase) installClasspathProto(ctx android.ModuleContext) android.InstallPath {
+	return ctx.InstallFile(c.installDirPath, c.outputFilename(), c.outputFilepath)
+}
+
 func writeClasspathsTextproto(ctx android.ModuleContext, output android.WritablePath, jars []classpathJar) {
 	var content strings.Builder
 
diff --git a/java/code_metadata_test.go b/java/code_metadata_test.go
index 0ef348a..99b1f52 100644
--- a/java/code_metadata_test.go
+++ b/java/code_metadata_test.go
@@ -7,6 +7,7 @@
 	"android/soong/android"
 	soongTesting "android/soong/testing"
 	"android/soong/testing/code_metadata_internal_proto"
+
 	"google.golang.org/protobuf/proto"
 )
 
diff --git a/java/container_test.go b/java/container_test.go
new file mode 100644
index 0000000..3441855
--- /dev/null
+++ b/java/container_test.go
@@ -0,0 +1,129 @@
+// 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 java
+
+import (
+	"android/soong/android"
+	"fmt"
+	"testing"
+)
+
+var checkContainerMatch = func(t *testing.T, name string, container string, expected bool, actual bool) {
+	errorMessage := fmt.Sprintf("module %s container %s value differ", name, container)
+	android.AssertBoolEquals(t, errorMessage, expected, actual)
+}
+
+func TestJavaContainersModuleProperties(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+	).RunTestWithBp(t, `
+		java_library {
+			name: "foo",
+			srcs: ["A.java"],
+		}
+		java_library {
+			name: "foo_vendor",
+			srcs: ["A.java"],
+			vendor: true,
+			sdk_version: "current",
+		}
+		java_library {
+			name: "foo_soc_specific",
+			srcs: ["A.java"],
+			soc_specific: true,
+			sdk_version: "current",
+		}
+		java_library {
+			name: "foo_product_specific",
+			srcs: ["A.java"],
+			product_specific: true,
+			sdk_version: "current",
+		}
+		java_test {
+			name: "foo_cts_test",
+			srcs: ["A.java"],
+			test_suites: [
+				"cts",
+			],
+		}
+		java_test {
+			name: "foo_non_cts_test",
+			srcs: ["A.java"],
+			test_suites: [
+				"general-tests",
+			],
+		}
+	`)
+
+	testcases := []struct {
+		moduleName         string
+		isSystemContainer  bool
+		isVendorContainer  bool
+		isProductContainer bool
+		isCts              bool
+	}{
+		{
+			moduleName:         "foo",
+			isSystemContainer:  true,
+			isVendorContainer:  false,
+			isProductContainer: false,
+			isCts:              false,
+		},
+		{
+			moduleName:         "foo_vendor",
+			isSystemContainer:  false,
+			isVendorContainer:  true,
+			isProductContainer: false,
+			isCts:              false,
+		},
+		{
+			moduleName:         "foo_soc_specific",
+			isSystemContainer:  false,
+			isVendorContainer:  true,
+			isProductContainer: false,
+			isCts:              false,
+		},
+		{
+			moduleName:         "foo_product_specific",
+			isSystemContainer:  false,
+			isVendorContainer:  false,
+			isProductContainer: true,
+			isCts:              false,
+		},
+		{
+			moduleName:         "foo_cts_test",
+			isSystemContainer:  false,
+			isVendorContainer:  false,
+			isProductContainer: false,
+			isCts:              true,
+		},
+		{
+			moduleName:         "foo_non_cts_test",
+			isSystemContainer:  false,
+			isVendorContainer:  false,
+			isProductContainer: false,
+			isCts:              false,
+		},
+	}
+
+	for _, c := range testcases {
+		m := result.ModuleForTests(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))
+		checkContainerMatch(t, c.moduleName, "vendor", c.isVendorContainer, android.InList(android.VendorContainer, belongingContainers))
+		checkContainerMatch(t, c.moduleName, "product", c.isProductContainer, android.InList(android.ProductContainer, belongingContainers))
+	}
+}
diff --git a/java/dex.go b/java/dex.go
index 6caaa7f..7bb6925 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -91,6 +91,10 @@
 
 	// Exclude kotlinc generate files: *.kotlin_module, *.kotlin_builtins. Defaults to false.
 	Exclude_kotlinc_generated_files *bool
+
+	// Disable dex container (also known as "multi-dex").
+	// This may be necessary as a temporary workaround to mask toolchain bugs (see b/341652226).
+	No_dex_container *bool
 }
 
 type dexer struct {
@@ -111,8 +115,16 @@
 	return BoolDefault(d.dexProperties.Optimize.Enabled, d.dexProperties.Optimize.EnabledByDefault)
 }
 
-func (d *DexProperties) resourceShrinkingEnabled() bool {
-	return BoolDefault(d.Optimize.Optimized_shrink_resources, Bool(d.Optimize.Shrink_resources))
+func (d *DexProperties) resourceShrinkingEnabled(ctx android.ModuleContext) bool {
+	return !ctx.Config().Eng() && BoolDefault(d.Optimize.Optimized_shrink_resources, Bool(d.Optimize.Shrink_resources))
+}
+
+func (d *DexProperties) optimizedResourceShrinkingEnabled(ctx android.ModuleContext) bool {
+	return d.resourceShrinkingEnabled(ctx) && Bool(d.Optimize.Optimized_shrink_resources)
+}
+
+func (d *dexer) optimizeOrObfuscateEnabled() bool {
+	return d.effectiveOptimizeEnabled() && (proptools.Bool(d.dexProperties.Optimize.Optimize) || proptools.Bool(d.dexProperties.Optimize.Obfuscate))
 }
 
 var d8, d8RE = pctx.MultiCommandRemoteStaticRules("d8",
@@ -172,7 +184,7 @@
 		"$r8Template": &remoteexec.REParams{
 			Labels:          map[string]string{"type": "compile", "compiler": "r8"},
 			Inputs:          []string{"$implicits", "${config.R8Jar}"},
-			OutputFiles:     []string{"${outUsage}", "${outConfig}", "${outDict}", "${resourcesOutput}"},
+			OutputFiles:     []string{"${outUsage}", "${outConfig}", "${outDict}", "${resourcesOutput}", "${outR8ArtProfile}"},
 			ExecStrategy:    "${config.RER8ExecStrategy}",
 			ToolchainInputs: []string{"${config.JavaCmd}"},
 			Platform:        map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"},
@@ -192,7 +204,7 @@
 			Platform:     map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"},
 		},
 	}, []string{"outDir", "outDict", "outConfig", "outUsage", "outUsageZip", "outUsageDir",
-		"r8Flags", "zipFlags", "mergeZipsFlags", "resourcesOutput"}, []string{"implicits"})
+		"r8Flags", "zipFlags", "mergeZipsFlags", "resourcesOutput", "outR8ArtProfile"}, []string{"implicits"})
 
 func (d *dexer) dexCommonFlags(ctx android.ModuleContext,
 	dexParams *compileDexParams) (flags []string, deps android.Paths) {
@@ -249,17 +261,25 @@
 	return flags, deps
 }
 
-func d8Flags(flags javaBuilderFlags) (d8Flags []string, d8Deps android.Paths) {
+func (d *dexer) d8Flags(ctx android.ModuleContext, dexParams *compileDexParams) (d8Flags []string, d8Deps android.Paths, artProfileOutput *android.OutputPath) {
+	flags := dexParams.flags
 	d8Flags = append(d8Flags, flags.bootClasspath.FormRepeatedClassPath("--lib ")...)
 	d8Flags = append(d8Flags, flags.dexClasspath.FormRepeatedClassPath("--lib ")...)
 
 	d8Deps = append(d8Deps, flags.bootClasspath...)
 	d8Deps = append(d8Deps, flags.dexClasspath...)
 
-	return d8Flags, d8Deps
+	if flags, deps, profileOutput := d.addArtProfile(ctx, dexParams); profileOutput != nil {
+		d8Flags = append(d8Flags, flags...)
+		d8Deps = append(d8Deps, deps...)
+		artProfileOutput = profileOutput
+	}
+
+	return d8Flags, d8Deps, artProfileOutput
 }
 
-func (d *dexer) r8Flags(ctx android.ModuleContext, flags javaBuilderFlags) (r8Flags []string, r8Deps android.Paths) {
+func (d *dexer) r8Flags(ctx android.ModuleContext, dexParams *compileDexParams) (r8Flags []string, r8Deps android.Paths, artProfileOutput *android.OutputPath) {
+	flags := dexParams.flags
 	opt := d.dexProperties.Optimize
 
 	// When an app contains references to APIs that are not in the SDK specified by
@@ -371,18 +391,44 @@
 		}
 	}
 
-	return r8Flags, r8Deps
+	if flags, deps, profileOutput := d.addArtProfile(ctx, dexParams); profileOutput != nil {
+		r8Flags = append(r8Flags, flags...)
+		r8Deps = append(r8Deps, deps...)
+		artProfileOutput = profileOutput
+	}
+
+	return r8Flags, r8Deps, artProfileOutput
 }
 
 type compileDexParams struct {
-	flags         javaBuilderFlags
-	sdkVersion    android.SdkSpec
-	minSdkVersion android.ApiLevel
-	classesJar    android.Path
-	jarName       string
+	flags           javaBuilderFlags
+	sdkVersion      android.SdkSpec
+	minSdkVersion   android.ApiLevel
+	classesJar      android.Path
+	jarName         string
+	artProfileInput *string
 }
 
-func (d *dexer) compileDex(ctx android.ModuleContext, dexParams *compileDexParams) android.OutputPath {
+// 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)
+	}
+	return flags, deps, artProfileOutputPath
+
+}
+
+// Return the compiled dex jar and (optional) profile _after_ r8 optimization
+func (d *dexer) compileDex(ctx android.ModuleContext, dexParams *compileDexParams) (android.OutputPath, *android.OutputPath) {
 
 	// Compile classes.jar into classes.dex and then javalib.jar
 	javalibJar := android.PathForModuleOut(ctx, "dex", dexParams.jarName).OutputPath
@@ -402,6 +448,7 @@
 	}
 
 	useR8 := d.effectiveOptimizeEnabled()
+	var artProfileOutputPath *android.OutputPath
 	if useR8 {
 		proguardDictionary := android.PathForModuleOut(ctx, "proguard_dictionary")
 		d.proguardDictionary = android.OptionalPathForPath(proguardDictionary)
@@ -414,8 +461,12 @@
 		d.proguardUsageZip = android.OptionalPathForPath(proguardUsageZip)
 		resourcesOutput := android.PathForModuleOut(ctx, "package-res-shrunken.apk")
 		d.resourcesOutput = android.OptionalPathForPath(resourcesOutput)
-		r8Flags, r8Deps := d.r8Flags(ctx, dexParams.flags)
-		r8Deps = append(r8Deps, commonDeps...)
+		implicitOutputs := android.WritablePaths{
+			proguardDictionary,
+			proguardUsageZip,
+			proguardConfiguration,
+		}
+		r8Flags, r8Deps, r8ArtProfileOutputPath := d.r8Flags(ctx, dexParams)
 		rule := r8
 		args := map[string]string{
 			"r8Flags":        strings.Join(append(commonFlags, r8Flags...), " "),
@@ -428,14 +479,21 @@
 			"outDir":         outDir.String(),
 			"mergeZipsFlags": mergeZipsFlags,
 		}
+		if r8ArtProfileOutputPath != nil {
+			artProfileOutputPath = r8ArtProfileOutputPath
+			implicitOutputs = append(
+				implicitOutputs,
+				artProfileOutputPath,
+			)
+			// Add the implicit r8 Art profile output to args so that r8RE knows
+			// about this implicit output
+			args["outR8ArtProfile"] = artProfileOutputPath.String()
+		}
+
 		if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_R8") {
 			rule = r8RE
 			args["implicits"] = strings.Join(r8Deps.Strings(), ",")
 		}
-		implicitOutputs := android.WritablePaths{
-			proguardDictionary,
-			proguardUsageZip,
-			proguardConfiguration}
 		if d.resourcesInput.Valid() {
 			implicitOutputs = append(implicitOutputs, resourcesOutput)
 			args["resourcesOutput"] = resourcesOutput.String()
@@ -450,18 +508,27 @@
 			Args:            args,
 		})
 	} else {
-		d8Flags, d8Deps := d8Flags(dexParams.flags)
+		implicitOutputs := android.WritablePaths{}
+		d8Flags, d8Deps, d8ArtProfileOutputPath := d.d8Flags(ctx, dexParams)
+		if d8ArtProfileOutputPath != nil {
+			artProfileOutputPath = d8ArtProfileOutputPath
+			implicitOutputs = append(
+				implicitOutputs,
+				artProfileOutputPath,
+			)
+		}
 		d8Deps = append(d8Deps, commonDeps...)
 		rule := d8
 		if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_D8") {
 			rule = d8RE
 		}
 		ctx.Build(pctx, android.BuildParams{
-			Rule:        rule,
-			Description: "d8",
-			Output:      javalibJar,
-			Input:       dexParams.classesJar,
-			Implicits:   d8Deps,
+			Rule:            rule,
+			Description:     "d8",
+			Output:          javalibJar,
+			Input:           dexParams.classesJar,
+			ImplicitOutputs: implicitOutputs,
+			Implicits:       d8Deps,
 			Args: map[string]string{
 				"d8Flags":        strings.Join(append(commonFlags, d8Flags...), " "),
 				"zipFlags":       zipFlags,
@@ -476,5 +543,5 @@
 		javalibJar = alignedJavalibJar
 	}
 
-	return javalibJar
+	return javalibJar, artProfileOutputPath
 }
diff --git a/java/dex_test.go b/java/dex_test.go
index 1ecdae0..4862d06 100644
--- a/java/dex_test.go
+++ b/java/dex_test.go
@@ -662,3 +662,54 @@
 	android.AssertStringDoesContain(t, "expected aarimports's proguard flags",
 		appR8.Args["r8Flags"], "proguard.txt")
 }
+
+func TestR8FlagsArtProfile(t *testing.T) {
+	result := PrepareForTestWithJavaDefaultModules.RunTestWithBp(t, `
+		android_app {
+			name: "app",
+			srcs: ["foo.java"],
+			platform_apis: true,
+			dex_preopt: {
+				profile_guided: true,
+				profile: "profile.txt.prof",
+				enable_profile_rewriting: true,
+			},
+		}
+	`)
+
+	app := result.ModuleForTests("app", "android_common")
+	appR8 := app.Rule("r8")
+	android.AssertStringDoesContain(t, "expected --art-profile in app r8 flags",
+		appR8.Args["r8Flags"], "--art-profile")
+
+	appDexpreopt := app.Rule("dexpreopt")
+	android.AssertStringDoesContain(t,
+		"expected --art-profile output to be used to create .prof binary",
+		appDexpreopt.RuleParams.Command,
+		"--create-profile-from=out/soong/.intermediates/app/android_common/profile.prof.txt --output-profile-type=app",
+	)
+}
+
+// This test checks that users explicitly set `enable_profile_rewriting` to true when the following are true
+// 1. optimize or obfuscate is enabled AND
+// 2. dex_preopt.profile_guided is enabled
+//
+// 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) {
+	testJavaError(t,
+		"Enable_profile_rewriting must be true when profile_guided dexpreopt and R8 optimization/obfuscation is turned on",
+		`
+android_app {
+	name: "app",
+	srcs: ["foo.java"],
+	platform_apis: true,
+	dex_preopt: {
+		profile_guided: true,
+		profile: "profile.txt.prof",
+		// enable_profile_rewriting is not set, this is an error
+	},
+	optimize: {
+		optimize: true,
+	}
+}`)
+}
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index 25e95db..7949244 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -19,6 +19,8 @@
 	"sort"
 	"strings"
 
+	"github.com/google/blueprint/proptools"
+
 	"android/soong/android"
 	"android/soong/dexpreopt"
 )
@@ -94,6 +96,10 @@
 	}
 }
 
+func (install dexpreopterInstall) PackageFile(ctx android.ModuleContext) android.PackagingSpec {
+	return ctx.PackageFile(install.installDirOnDevice, install.installFileOnDevice, install.outputPathOnHost)
+}
+
 type Dexpreopter struct {
 	dexpreopter
 }
@@ -139,6 +145,10 @@
 	// The path to the profile that dexpreopter accepts. It must be in the binary format. If this is
 	// set, it overrides the profile settings in `dexpreoptProperties`.
 	inputProfilePathOnHost android.Path
+
+	// The path to the profile that matches the dex optimized by r8/d8. It is in text format. If this is
+	// set, it will be converted to a binary profile which will be subsequently used for dexpreopt.
+	rewrittenProfile android.Path
 }
 
 type DexpreoptProperties struct {
@@ -158,6 +168,11 @@
 		// defaults to searching for a file that matches the name of this module in the default
 		// profile location set by PRODUCT_DEX_PREOPT_PROFILE_DIR, or empty if not found.
 		Profile *string `android:"path"`
+
+		// If set to true, r8/d8 will use `profile` as input to generate a new profile that matches
+		// the optimized dex.
+		// The new profile will be subsequently used as the profile to dexpreopt the dex file.
+		Enable_profile_rewriting *bool
 	}
 
 	Dex_preopt_result struct {
@@ -196,8 +211,10 @@
 	}
 	apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
 	psi := android.PrebuiltSelectionInfoMap{}
-	ctx.VisitDirectDepsWithTag(android.PrebuiltDepTag, func(am android.Module) {
-		psi, _ = android.OtherModuleProvider(ctx, am, android.PrebuiltSelectionInfoProvider)
+	ctx.VisitDirectDeps(func(am android.Module) {
+		if prebuiltSelectionInfo, ok := android.OtherModuleProvider(ctx, am, android.PrebuiltSelectionInfoProvider); ok {
+			psi = prebuiltSelectionInfo
+		}
 	})
 	// Find the apex variant for this module
 	_, apexVariantsWithoutTestApexes, _ := android.ListSetDifference(apexInfo.InApexVariants, apexInfo.TestApexes)
@@ -260,6 +277,20 @@
 		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 {
@@ -285,10 +316,6 @@
 	dexpreopt.RegisterToolDeps(ctx)
 }
 
-func (d *dexpreopter) odexOnSystemOther(ctx android.ModuleContext, libName string, installPath android.InstallPath) bool {
-	return dexpreopt.OdexOnSystemOtherByName(libName, android.InstallPathToOnDevicePath(ctx, installPath), dexpreopt.GetGlobalConfig(ctx))
-}
-
 // Returns the install path of the dex jar of a module.
 //
 // Do not rely on `ApexInfo.ApexVariationName` because it can be something like "apex1000", rather
@@ -405,13 +432,17 @@
 	if d.inputProfilePathOnHost != nil {
 		profileClassListing = android.OptionalPathForPath(d.inputProfilePathOnHost)
 	} else if BoolDefault(d.dexpreoptProperties.Dex_preopt.Profile_guided, true) && !forPrebuiltApex(ctx) {
-		// If dex_preopt.profile_guided is not set, default it based on the existence of the
-		// dexprepot.profile option or the profile class listing.
-		if String(d.dexpreoptProperties.Dex_preopt.Profile) != "" {
+		// If enable_profile_rewriting is set, use the rewritten profile instead of the checked-in profile
+		if d.EnableProfileRewriting() {
+			profileClassListing = android.OptionalPathForPath(d.GetRewrittenProfile())
+			profileIsTextListing = true
+		} else if profile := d.GetProfile(); profile != "" {
+			// If dex_preopt.profile_guided is not set, default it based on the existence of the
+			// dexprepot.profile option or the profile class listing.
 			profileClassListing = android.OptionalPathForPath(
-				android.PathForModuleSrc(ctx, String(d.dexpreoptProperties.Dex_preopt.Profile)))
+				android.PathForModuleSrc(ctx, profile))
 			profileBootListing = android.ExistentPathForSource(ctx,
-				ctx.ModuleDir(), String(d.dexpreoptProperties.Dex_preopt.Profile)+"-boot")
+				ctx.ModuleDir(), profile+"-boot")
 			profileIsTextListing = true
 		} else if global.ProfileDir != "" {
 			profileClassListing = android.ExistentPathForSource(ctx,
@@ -500,7 +531,7 @@
 	// 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)
+	copyApexSystemServerJarDex := !disableSourceApexVariant(ctx) && !ctx.Module().IsHideFromMake()
 	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(
 		ctx, globalSoong, global, dexpreoptConfig, appProductPackages, copyApexSystemServerJarDex)
 	if err != nil {
@@ -514,12 +545,29 @@
 	// Use the path of the dex file to determine the library name
 	isApexSystemServerJar := global.AllApexSystemServerJars(ctx).ContainsJar(dexJarStem)
 
+	dexpreoptPartition := d.installPath.Partition()
+	// dexpreoptPartition is set to empty for dexpreopts of system APEX and system_other.
+	// In case of system APEX, however, we can set it to "system" manually.
+	// 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"
+	}
 	for _, install := range dexpreoptRule.Installs() {
 		// Remove the "/" prefix because the path should be relative to $ANDROID_PRODUCT_OUT.
 		installDir := strings.TrimPrefix(filepath.Dir(install.To), "/")
+		partition := dexpreoptPartition
+		if strings.HasPrefix(installDir, partition+"/") {
+			installDir = strings.TrimPrefix(installDir, partition+"/")
+		} else {
+			// If the partition for the installDir is different from the install partition, set the
+			// partition empty to install the dexpreopt files to the desired partition.
+			// TODO(b/346439786): Define and use the dexpreopt module type to avoid this mismatch.
+			partition = ""
+		}
 		installBase := filepath.Base(install.To)
 		arch := filepath.Base(installDir)
-		installPath := android.PathForModuleInPartitionInstall(ctx, "", installDir)
+		installPath := android.PathForModuleInPartitionInstall(ctx, partition, installDir)
 		isProfile := strings.HasSuffix(installBase, ".prof")
 
 		if isProfile {
@@ -553,6 +601,37 @@
 	}
 }
 
+func getModuleInstallPathInfo(ctx android.ModuleContext, fullInstallPath string) (android.InstallPath, string, string) {
+	installPath := android.PathForModuleInstall(ctx)
+	installDir, installBase := filepath.Split(strings.TrimPrefix(fullInstallPath, "/"))
+
+	if !strings.HasPrefix(installDir, installPath.Partition()+"/") {
+		// Return empty filename if the install partition is not for the target image.
+		return installPath, "", ""
+	}
+	relDir, err := filepath.Rel(installPath.Partition(), installDir)
+	if err != nil {
+		panic(err)
+	}
+	return installPath, relDir, installBase
+}
+
+// RuleBuilder.Install() adds output-to-install copy pairs to a list for Make. To share this
+// information with PackagingSpec in soong, call PackageFile for them.
+// The install path and the target install partition of the module must be the same.
+func packageFile(ctx android.ModuleContext, install android.RuleBuilderInstall) {
+	installPath, relDir, name := getModuleInstallPathInfo(ctx, install.To)
+	// Empty name means the install partition is not for the target image.
+	// For the system image, files for "apex" and "system_other" are skipped here.
+	// The skipped "apex" files are for testing only, for example,
+	// "/apex/art_boot_images/javalib/x86/boot.vdex".
+	// TODO(b/320196894): Files for "system_other" are skipped because soong creates the system
+	// image only for now.
+	if name != "" {
+		ctx.PackageFile(installPath.Join(ctx, relDir), name, install.From)
+	}
+}
+
 func (d *dexpreopter) DexpreoptBuiltInstalledForApex() []dexpreopterInstall {
 	return d.builtInstalledForApex
 }
@@ -572,3 +651,23 @@
 func (d *dexpreopter) disableDexpreopt() {
 	d.shouldDisableDexpreopt = true
 }
+
+func (d *dexpreopter) EnableProfileRewriting() bool {
+	return proptools.Bool(d.dexpreoptProperties.Dex_preopt.Enable_profile_rewriting)
+}
+
+func (d *dexpreopter) GetProfile() string {
+	return proptools.String(d.dexpreoptProperties.Dex_preopt.Profile)
+}
+
+func (d *dexpreopter) GetProfileGuided() bool {
+	return proptools.Bool(d.dexpreoptProperties.Dex_preopt.Profile_guided)
+}
+
+func (d *dexpreopter) GetRewrittenProfile() android.Path {
+	return d.rewrittenProfile
+}
+
+func (d *dexpreopter) SetRewrittenProfile(p android.Path) {
+	d.rewrittenProfile = p
+}
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index f7e3cb9..defa82c 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -562,7 +562,7 @@
 	return ctx.Config().Once(dexBootJarsFragmentsKey, func() interface{} {
 		fragments := make(map[string]android.Module)
 		ctx.WalkDeps(func(child, parent android.Module) bool {
-			if !isActiveModule(child) {
+			if !isActiveModule(ctx, child) {
 				return false
 			}
 			tag := ctx.OtherModuleDependencyTag(child)
@@ -612,6 +612,9 @@
 			profileInstalls:            profileInstalls,
 			profileLicenseMetadataFile: android.OptionalPathForPath(ctx.LicenseMetadataFile()),
 		})
+		for _, install := range profileInstalls {
+			packageFile(ctx, install)
+		}
 	}
 }
 
@@ -929,6 +932,35 @@
 	return apexNameToApexExportsInfoMap
 }
 
+func packageFileForTargetImage(ctx android.ModuleContext, image *bootImageVariant) {
+	if image.target.Os != ctx.Os() {
+		// This is not for the target device.
+		return
+	}
+
+	for _, install := range image.installs {
+		packageFile(ctx, install)
+	}
+
+	for _, install := range image.vdexInstalls {
+		if image.target.Arch.ArchType.Name != ctx.DeviceConfig().DeviceArch() {
+			// Note that the vdex files are identical between architectures. If the target image is
+			// not for the primary architecture create symlinks to share the vdex of the primary
+			// architecture with the other architectures.
+			//
+			// Assuming that the install path has the architecture name with it, replace the
+			// architecture name with the primary architecture name to find the source vdex file.
+			installPath, relDir, name := getModuleInstallPathInfo(ctx, install.To)
+			if name != "" {
+				srcRelDir := strings.Replace(relDir, image.target.Arch.ArchType.Name, ctx.DeviceConfig().DeviceArch(), 1)
+				ctx.InstallSymlink(installPath.Join(ctx, relDir), name, installPath.Join(ctx, srcRelDir, name))
+			}
+		} else {
+			packageFile(ctx, install)
+		}
+	}
+}
+
 // Generate boot image build rules for a specific target.
 func buildBootImageVariant(ctx android.ModuleContext, image *bootImageVariant, profile android.Path) bootImageVariantOutputs {
 
@@ -1123,9 +1155,10 @@
 	image.installs = rule.Installs()
 	image.vdexInstalls = vdexInstalls
 	image.unstrippedInstalls = unstrippedInstalls
+	packageFileForTargetImage(ctx, image)
 
 	// Only set the licenseMetadataFile from the active module.
-	if isActiveModule(ctx.Module()) {
+	if isActiveModule(ctx, ctx.Module()) {
 		image.licenseMetadataFile = android.OptionalPathForPath(ctx.LicenseMetadataFile())
 	}
 
diff --git a/java/droiddoc.go b/java/droiddoc.go
index 176779e..730f236 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -223,17 +223,6 @@
 	exportableStubsSrcJar android.WritablePath
 }
 
-func (j *Javadoc) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		return android.Paths{j.stubsSrcJar}, nil
-	case ".docs.zip":
-		return android.Paths{j.docZip}, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
 // javadoc converts .java source files to documentation using javadoc.
 func JavadocFactory() android.Module {
 	module := &Javadoc{}
@@ -254,8 +243,6 @@
 	return module
 }
 
-var _ android.OutputFileProducer = (*Javadoc)(nil)
-
 func (j *Javadoc) SdkVersion(ctx android.EarlyModuleContext) android.SdkSpec {
 	return android.SdkSpecFrom(ctx, String(j.properties.Sdk_version))
 }
@@ -585,6 +572,9 @@
 	zipSyncCleanupCmd(rule, srcJarDir)
 
 	rule.Build("javadoc", "javadoc")
+
+	ctx.SetOutputFiles(android.Paths{j.stubsSrcJar}, "")
+	ctx.SetOutputFiles(android.Paths{j.docZip}, ".docs.zip")
 }
 
 // Droiddoc
@@ -616,15 +606,6 @@
 	return module
 }
 
-func (d *Droiddoc) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "", ".docs.zip":
-		return android.Paths{d.Javadoc.docZip}, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
 func (d *Droiddoc) DepsMutator(ctx android.BottomUpMutatorContext) {
 	d.Javadoc.addDeps(ctx)
 
@@ -876,6 +857,9 @@
 	zipSyncCleanupCmd(rule, srcJarDir)
 
 	rule.Build("javadoc", desc)
+
+	ctx.SetOutputFiles(android.Paths{d.Javadoc.docZip}, "")
+	ctx.SetOutputFiles(android.Paths{d.Javadoc.docZip}, ".docs.zip")
 }
 
 // Exported Droiddoc Directory
diff --git a/java/droiddoc_test.go b/java/droiddoc_test.go
index 8d1f591..e584640 100644
--- a/java/droiddoc_test.go
+++ b/java/droiddoc_test.go
@@ -69,11 +69,7 @@
 			"bar-doc/a.java": nil,
 			"bar-doc/b.java": nil,
 		})
-	barStubs := ctx.ModuleForTests("bar-stubs", "android_common")
-	barStubsOutputs, err := barStubs.Module().(*Droidstubs).OutputFiles("")
-	if err != nil {
-		t.Errorf("Unexpected error %q retrieving \"bar-stubs\" output file", err)
-	}
+	barStubsOutputs := ctx.ModuleForTests("bar-stubs", "android_common").OutputFiles(t, "")
 	if len(barStubsOutputs) != 1 {
 		t.Errorf("Expected one output from \"bar-stubs\" got %s", barStubsOutputs)
 	}
diff --git a/java/droidstubs.go b/java/droidstubs.go
index ffd3caf..d622903 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -106,9 +106,6 @@
 	everythingArtifacts stubsArtifacts
 	exportableArtifacts stubsArtifacts
 
-	// Single aconfig "cache file" merged from this module and all dependencies.
-	mergedAconfigFiles map[string]android.Paths
-
 	exportableApiFile        android.WritablePath
 	exportableRemovedApiFile android.WritablePath
 }
@@ -200,6 +197,10 @@
 	// a list of aconfig_declarations module names that the stubs generated in this module
 	// depend on.
 	Aconfig_declarations []string
+
+	// List of hard coded filegroups containing Metalava config files that are passed to every
+	// Metalava invocation that this module performs. See addMetalavaConfigFilesToCmd.
+	ConfigFiles []string `android:"path" blueprint:"mutated"`
 }
 
 // Used by xsd_config
@@ -262,6 +263,7 @@
 
 	module.AddProperties(&module.properties,
 		&module.Javadoc.properties)
+	module.properties.ConfigFiles = getMetalavaConfigFilegroupReference()
 	module.initModuleAndImport(module)
 
 	InitDroiddocModule(module, android.HostAndDeviceSupported)
@@ -282,70 +284,11 @@
 	module.AddProperties(&module.properties,
 		&module.Javadoc.properties)
 
+	module.properties.ConfigFiles = getMetalavaConfigFilegroupReference()
 	InitDroiddocModule(module, android.HostSupported)
 	return module
 }
 
-func getStubsTypeAndTag(tag string) (StubsType, string, error) {
-	if len(tag) == 0 {
-		return Everything, "", nil
-	}
-	if tag[0] != '.' {
-		return Unavailable, "", fmt.Errorf("tag must begin with \".\"")
-	}
-
-	stubsType := Everything
-	// Check if the tag has a stubs type prefix (e.g. ".exportable")
-	for st := Everything; st <= Exportable; st++ {
-		if strings.HasPrefix(tag, "."+st.String()) {
-			stubsType = st
-		}
-	}
-
-	return stubsType, strings.TrimPrefix(tag, "."+stubsType.String()), nil
-}
-
-// Droidstubs' tag supports specifying with the stubs type.
-// While supporting the pre-existing tags, it also supports tags with
-// the stubs type prefix. Some examples are shown below:
-// {.annotations.zip} - pre-existing behavior. Returns the path to the
-// annotation zip.
-// {.exportable} - Returns the path to the exportable stubs src jar.
-// {.exportable.annotations.zip} - Returns the path to the exportable
-// annotations zip file.
-// {.runtime.api_versions.xml} - Runtime stubs does not generate api versions
-// xml file. For unsupported combinations, the default everything output file
-// is returned.
-func (d *Droidstubs) OutputFiles(tag string) (android.Paths, error) {
-	stubsType, prefixRemovedTag, err := getStubsTypeAndTag(tag)
-	if err != nil {
-		return nil, err
-	}
-	switch prefixRemovedTag {
-	case "":
-		stubsSrcJar, err := d.StubsSrcJar(stubsType)
-		return android.Paths{stubsSrcJar}, err
-	case ".docs.zip":
-		docZip, err := d.DocZip(stubsType)
-		return android.Paths{docZip}, err
-	case ".api.txt", android.DefaultDistTag:
-		// This is the default dist path for dist properties that have no tag property.
-		apiFilePath, err := d.ApiFilePath(stubsType)
-		return android.Paths{apiFilePath}, err
-	case ".removed-api.txt":
-		removedApiFilePath, err := d.RemovedApiFilePath(stubsType)
-		return android.Paths{removedApiFilePath}, err
-	case ".annotations.zip":
-		annotationsZip, err := d.AnnotationsZip(stubsType)
-		return android.Paths{annotationsZip}, err
-	case ".api_versions.xml":
-		apiVersionsXmlFilePath, err := d.ApiVersionsXmlFilePath(stubsType)
-		return android.Paths{apiVersionsXmlFilePath}, err
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
 func (d *Droidstubs) AnnotationsZip(stubsType StubsType) (ret android.Path, err error) {
 	switch stubsType {
 	case Everything:
@@ -604,6 +547,11 @@
 	}
 }
 
+// AndroidPlusUpdatableJar is the name of some extra jars added into `module-lib` and
+// `system-server` directories that contain all the APIs provided by the platform and updatable
+// modules because the `android.jar` files do not. See b/337836752.
+const AndroidPlusUpdatableJar = "android-plus-updatable.jar"
+
 func (d *Droidstubs) apiLevelsGenerationFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, stubsType StubsType, apiVersionsXml android.WritablePath) {
 	if len(d.properties.Api_levels_annotations_dirs) == 0 {
 		ctx.PropertyErrorf("api_levels_annotations_dirs",
@@ -614,25 +562,72 @@
 
 	filename := proptools.StringDefault(d.properties.Api_levels_jar_filename, "android.jar")
 
+	// TODO: Avoid the duplication of API surfaces, reuse apiScope.
+	// Add all relevant --android-jar-pattern patterns for Metalava.
+	// When parsing a stub jar for a specific version, Metalava picks the first pattern that defines
+	// an actual file present on disk (in the order the patterns were passed). For system APIs for
+	// privileged apps that are only defined since API level 21 (Lollipop), fallback to public stubs
+	// for older releases. Similarly, module-lib falls back to system API.
+	var sdkDirs []string
+	apiLevelsSdkType := proptools.StringDefault(d.properties.Api_levels_sdk_type, "public")
+	switch apiLevelsSdkType {
+	case "system-server":
+		sdkDirs = []string{"system-server", "module-lib", "system", "public"}
+	case "module-lib":
+		sdkDirs = []string{"module-lib", "system", "public"}
+	case "system":
+		sdkDirs = []string{"system", "public"}
+	case "public":
+		sdkDirs = []string{"public"}
+	default:
+		ctx.PropertyErrorf("api_levels_sdk_type", "needs to be one of %v", allowedApiLevelSdkTypes)
+		return
+	}
+
+	// Construct a pattern to match the appropriate extensions that should be included in the
+	// generated api-versions.xml file.
+	//
+	// Use the first item in the sdkDirs array as that is the sdk type for the target API levels
+	// being generated but has the advantage over `Api_levels_sdk_type` as it has been validated.
+	// The exception is for system-server which needs to include module-lib and system-server. That
+	// is because while system-server extends module-lib the system-server extension directory only
+	// contains service-* modules which provide system-server APIs it does not list the modules which
+	// only provide a module-lib, so they have to be included separately.
+	extensionSurfacesPattern := sdkDirs[0]
+	if apiLevelsSdkType == "system-server" {
+		// Take the first two items in sdkDirs, which are system-server and module-lib, and construct
+		// a pattern that will match either.
+		extensionSurfacesPattern = strings.Join(sdkDirs[0:2], "|")
+	}
+	extensionsPattern := fmt.Sprintf(`/extensions/[0-9]+/(%s)/.*\.jar`, extensionSurfacesPattern)
+
 	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() + `/extensions/[0-9]+/public/.*\.jar`)
+			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 {
+				// 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"
 					}
 					cmd.Implicit(dep)
-				}
-				if dep.Base() == filename {
+				} else if depBase == filename {
+					// Check to see if it matches a dessert release for an SDK, e.g. Android, Car, Wear, etc..
 					cmd.Implicit(dep)
-				}
-				if filename != "android.jar" && dep.Base() == "android.jar" {
+				} 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
@@ -650,29 +645,25 @@
 		}
 	})
 
-	// Add all relevant --android-jar-pattern patterns for Metalava.
-	// When parsing a stub jar for a specific version, Metalava picks the first pattern that defines
-	// an actual file present on disk (in the order the patterns were passed). For system APIs for
-	// privileged apps that are only defined since API level 21 (Lollipop), fallback to public stubs
-	// for older releases. Similarly, module-lib falls back to system API.
-	var sdkDirs []string
-	switch proptools.StringDefault(d.properties.Api_levels_sdk_type, "public") {
-	case "system-server":
-		sdkDirs = []string{"system-server", "module-lib", "system", "public"}
-	case "module-lib":
-		sdkDirs = []string{"module-lib", "system", "public"}
-	case "system":
-		sdkDirs = []string{"system", "public"}
-	case "public":
-		sdkDirs = []string{"public"}
-	default:
-		ctx.PropertyErrorf("api_levels_sdk_type", "needs to be one of %v", allowedApiLevelSdkTypes)
-		return
-	}
-
+	// 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..
 	for _, sdkDir := range sdkDirs {
 		for _, dir := range dirs {
-			cmd.FlagWithArg("--android-jar-pattern ", fmt.Sprintf("%s/%%/%s/%s", dir, sdkDir, filename))
+			addPattern := func(jarFilename string) {
+				cmd.FlagWithArg("--android-jar-pattern ", fmt.Sprintf("%s/%%/%s/%s", dir, sdkDir, jarFilename))
+			}
+
+			if sdkDir == "module-lib" || sdkDir == "system-server" {
+				// The module-lib and system-server android.jars do not include the updatable modules (as
+				// doing so in the source would introduce dependency cycles and the prebuilts have to
+				// match the sources). So, instead an additional `android-plus-updatable.jar` will be used
+				// that does include the updatable modules and this pattern will match that. This pattern
+				// is added in addition to the following pattern to decouple this change from the change
+				// to add the `android-plus-updatable.jar`.
+				addPattern(AndroidPlusUpdatableJar)
+			}
+
+			addPattern(filename)
 		}
 	}
 
@@ -709,7 +700,7 @@
 }
 
 func metalavaCmd(ctx android.ModuleContext, rule *android.RuleBuilder, srcs android.Paths,
-	srcJarList android.Path, homeDir android.WritablePath, params stubsCommandConfigParams) *android.RuleBuilderCommand {
+	srcJarList android.Path, homeDir android.WritablePath, params stubsCommandConfigParams, configFiles android.Paths) *android.RuleBuilderCommand {
 	rule.Command().Text("rm -rf").Flag(homeDir.String())
 	rule.Command().Text("mkdir -p").Flag(homeDir.String())
 
@@ -753,9 +744,26 @@
 
 	cmd.Flag(config.MetalavaFlags)
 
+	addMetalavaConfigFilesToCmd(cmd, configFiles)
+
 	return cmd
 }
 
+// MetalavaConfigFilegroup is the name of the filegroup in build/soong/java/metalava that lists
+// the configuration files to pass to Metalava.
+const MetalavaConfigFilegroup = "metalava-config-files"
+
+// Get a reference to the MetalavaConfigFilegroup suitable for use in a property.
+func getMetalavaConfigFilegroupReference() []string {
+	return []string{":" + MetalavaConfigFilegroup}
+}
+
+// addMetalavaConfigFilesToCmd adds --config-file options to use the config files list in the
+// MetalavaConfigFilegroup filegroup.
+func addMetalavaConfigFilesToCmd(cmd *android.RuleBuilderCommand, configFiles android.Paths) {
+	cmd.FlagForEachInput("--config-file ", configFiles)
+}
+
 // 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
@@ -827,7 +835,10 @@
 	srcJarList := zipSyncCmd(ctx, rule, params.srcJarDir, d.Javadoc.srcJars)
 
 	homeDir := android.PathForModuleOut(ctx, params.stubConfig.stubsType.String(), "home")
-	cmd := metalavaCmd(ctx, rule, d.Javadoc.srcFiles, srcJarList, homeDir, params.stubConfig)
+
+	configFiles := android.PathsForModuleSrc(ctx, d.properties.ConfigFiles)
+
+	cmd := metalavaCmd(ctx, rule, d.Javadoc.srcFiles, srcJarList, homeDir, params.stubConfig, configFiles)
 	cmd.Implicits(d.Javadoc.implicits)
 
 	d.stubsFlags(ctx, cmd, params.stubsDir, params.stubConfig.stubsType, params.stubConfig.checkApi)
@@ -948,6 +959,7 @@
 func (d *Droidstubs) everythingOptionalCmd(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, doApiLint bool, doCheckReleased bool) {
 
 	// Add API lint options.
+	treatDocumentationIssuesAsErrors := false
 	if doApiLint {
 		var newSince android.Paths
 		if d.properties.Check_api.Api_lint.New_since != nil {
@@ -961,7 +973,7 @@
 		// TODO(b/154317059): Clean up this allowlist by baselining and/or checking in last-released.
 		if d.Name() != "android.car-system-stubs-docs" &&
 			d.Name() != "android.car-stubs-docs" {
-			cmd.Flag("--lints-as-errors")
+			treatDocumentationIssuesAsErrors = true
 			cmd.Flag("--warnings-as-errors") // Most lints are actually warnings.
 		}
 
@@ -1007,6 +1019,10 @@
 		cmd.FlagWithArg("--error-message:api-lint ", msg)
 	}
 
+	if !treatDocumentationIssuesAsErrors {
+		treatDocumentationIssuesAsWarningErrorWhenNew(cmd)
+	}
+
 	// Add "check released" options. (Detect incompatible API changes from the last public release)
 	if doCheckReleased {
 		baselineFile := android.OptionalPathForModuleSrc(ctx, d.properties.Check_api.Last_released.Baseline_file)
@@ -1032,6 +1048,22 @@
 	}
 }
 
+// HIDDEN_DOCUMENTATION_ISSUES is the set of documentation related issues that should always be
+// hidden as they are very noisy and provide little value.
+var HIDDEN_DOCUMENTATION_ISSUES = []string{
+	"Deprecated",
+	"IntDef",
+	"Nullable",
+}
+
+func treatDocumentationIssuesAsWarningErrorWhenNew(cmd *android.RuleBuilderCommand) {
+	// Treat documentation issues as warnings, but error when new.
+	cmd.Flag("--error-when-new-category").Flag("Documentation")
+
+	// Hide some documentation issues that generated a lot of noise for little benefit.
+	cmd.FlagForEachArg("--hide ", HIDDEN_DOCUMENTATION_ISSUES)
+}
+
 // Sandbox rule for generating exportable stubs and other artifacts
 func (d *Droidstubs) exportableStubCmd(ctx android.ModuleContext, params stubsCommandConfigParams) {
 	optionalCmdParams := stubsCommandParams{
@@ -1102,6 +1134,9 @@
 		}
 	}
 
+	// Treat documentation issues as warnings, but error when new.
+	treatDocumentationIssuesAsWarningErrorWhenNew(cmd)
+
 	if params.stubConfig.generateStubs {
 		rule.Command().
 			BuiltTool("soong_zip").
@@ -1294,7 +1329,46 @@
 
 		rule.Build("nullabilityWarningsCheck", "nullability warnings check")
 	}
-	android.CollectDependencyAconfigFiles(ctx, &d.mergedAconfigFiles)
+
+	d.setOutputFiles(ctx)
+}
+
+// This method sets the outputFiles property, which is used to set the
+// OutputFilesProvider later.
+// Droidstubs' tag supports specifying with the stubs type.
+// While supporting the pre-existing tags, it also supports tags with
+// the stubs type prefix. Some examples are shown below:
+// {.annotations.zip} - pre-existing behavior. Returns the path to the
+// annotation zip.
+// {.exportable} - Returns the path to the exportable stubs src jar.
+// {.exportable.annotations.zip} - Returns the path to the exportable
+// annotations zip file.
+// {.runtime.api_versions.xml} - Runtime stubs does not generate api versions
+// xml file. For unsupported combinations, the default everything output file
+// is returned.
+func (d *Droidstubs) setOutputFiles(ctx android.ModuleContext) {
+	tagToOutputFileFunc := map[string]func(StubsType) (android.Path, error){
+		"":                     d.StubsSrcJar,
+		".docs.zip":            d.DocZip,
+		".api.txt":             d.ApiFilePath,
+		android.DefaultDistTag: d.ApiFilePath,
+		".removed-api.txt":     d.RemovedApiFilePath,
+		".annotations.zip":     d.AnnotationsZip,
+		".api_versions.xml":    d.ApiVersionsXmlFilePath,
+	}
+	stubsTypeToPrefix := map[StubsType]string{
+		Everything: "",
+		Exportable: ".exportable",
+	}
+	for _, tag := range android.SortedKeys(tagToOutputFileFunc) {
+		for _, stubType := range android.SortedKeys(stubsTypeToPrefix) {
+			tagWithPrefix := stubsTypeToPrefix[stubType] + tag
+			outputFile, err := tagToOutputFileFunc[tag](stubType)
+			if err == nil {
+				ctx.SetOutputFiles(android.Paths{outputFile}, tagWithPrefix)
+			}
+		}
+	}
 }
 
 func (d *Droidstubs) createApiContribution(ctx android.DefaultableHookContext) {
@@ -1322,7 +1396,7 @@
 // use a strict naming convention
 var (
 	droidstubsModuleNamingToSdkKind = map[string]android.SdkKind{
-		//public is commented out since the core libraries use public in their java_sdk_library names
+		// public is commented out since the core libraries use public in their java_sdk_library names
 		"intracore":     android.SdkIntraCore,
 		"intra.core":    android.SdkIntraCore,
 		"system_server": android.SdkSystemServer,
@@ -1385,17 +1459,6 @@
 	stubsSrcJar android.Path
 }
 
-func (p *PrebuiltStubsSources) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	// prebuilt droidstubs does not output "exportable" stubs.
-	// Output the "everything" stubs srcjar file if the tag is ".exportable".
-	case "", ".exportable":
-		return android.Paths{p.stubsSrcJar}, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
 func (d *PrebuiltStubsSources) StubsSrcJar(_ StubsType) (android.Path, error) {
 	return d.stubsSrcJar, nil
 }
@@ -1434,6 +1497,11 @@
 		rule.Build("zip src", "Create srcjar from prebuilt source")
 		p.stubsSrcJar = outPath
 	}
+
+	ctx.SetOutputFiles(android.Paths{p.stubsSrcJar}, "")
+	// prebuilt droidstubs does not output "exportable" stubs.
+	// Output the "everything" stubs srcjar file if the tag is ".exportable".
+	ctx.SetOutputFiles(android.Paths{p.stubsSrcJar}, ".exportable")
 }
 
 func (p *PrebuiltStubsSources) Prebuilt() *android.Prebuilt {
diff --git a/java/droidstubs_test.go b/java/droidstubs_test.go
index 8da695f..6a14f36 100644
--- a/java/droidstubs_test.go
+++ b/java/droidstubs_test.go
@@ -379,6 +379,7 @@
 	aconfig_declarations {
 		name: "bar",
 		package: "com.example.package",
+		container: "com.android.foo",
 		srcs: [
 			"bar.aconfig",
 		],
@@ -434,6 +435,7 @@
 	aconfig_declarations {
 		name: "bar",
 		package: "com.example.package",
+		container: "com.android.foo",
 		srcs: [
 			"bar.aconfig",
 		],
diff --git a/java/fuzz.go b/java/fuzz.go
index fb31ce7..d37c558 100644
--- a/java/fuzz.go
+++ b/java/fuzz.go
@@ -179,7 +179,7 @@
 			javaFuzzModule.ApexModuleBase,
 		}
 
-		if ok := fuzz.IsValid(fuzzModuleValidator); !ok {
+		if ok := fuzz.IsValid(ctx, fuzzModuleValidator); !ok {
 			return
 		}
 
diff --git a/java/gen.go b/java/gen.go
index 68a9b53..1b4f4c7 100644
--- a/java/gen.go
+++ b/java/gen.go
@@ -27,7 +27,6 @@
 
 func init() {
 	pctx.SourcePathVariable("logtagsCmd", "build/make/tools/java-event-log-tags.py")
-	pctx.SourcePathVariable("mergeLogtagsCmd", "build/make/tools/merge-event-log-tags.py")
 	pctx.SourcePathVariable("logtagsLib", "build/make/tools/event_log_tags.py")
 }
 
@@ -37,12 +36,6 @@
 			Command:     "$logtagsCmd -o $out $in",
 			CommandDeps: []string{"$logtagsCmd", "$logtagsLib"},
 		})
-
-	mergeLogtags = pctx.AndroidStaticRule("mergeLogtags",
-		blueprint.RuleParams{
-			Command:     "$mergeLogtagsCmd -o $out $in",
-			CommandDeps: []string{"$mergeLogtagsCmd", "$logtagsLib"},
-		})
 )
 
 func genAidl(ctx android.ModuleContext, aidlFiles android.Paths, aidlGlobalFlags string, aidlIndividualFlags map[string]string, deps android.Paths) android.Paths {
@@ -178,37 +171,9 @@
 		outSrcFiles = append(outSrcFiles, srcJarFiles...)
 	}
 
+	android.SetProvider(ctx, android.LogtagsProviderKey, &android.LogtagsInfo{
+		Logtags: j.logtagsSrcs,
+	})
+
 	return outSrcFiles
 }
-
-func LogtagsSingleton() android.Singleton {
-	return &logtagsSingleton{}
-}
-
-type logtagsProducer interface {
-	logtags() android.Paths
-}
-
-func (j *Module) logtags() android.Paths {
-	return j.logtagsSrcs
-}
-
-var _ logtagsProducer = (*Module)(nil)
-
-type logtagsSingleton struct{}
-
-func (l *logtagsSingleton) GenerateBuildActions(ctx android.SingletonContext) {
-	var allLogtags android.Paths
-	ctx.VisitAllModules(func(module android.Module) {
-		if logtags, ok := module.(logtagsProducer); ok {
-			allLogtags = append(allLogtags, logtags.logtags()...)
-		}
-	})
-
-	ctx.Build(pctx, android.BuildParams{
-		Rule:        mergeLogtags,
-		Description: "merge logtags",
-		Output:      android.PathForIntermediates(ctx, "all-event-log-tags.txt"),
-		Inputs:      allLogtags,
-	})
-}
diff --git a/java/hiddenapi_modular.go b/java/hiddenapi_modular.go
index ae587ea..4144de8 100644
--- a/java/hiddenapi_modular.go
+++ b/java/hiddenapi_modular.go
@@ -1255,8 +1255,9 @@
 	rule := android.NewRuleBuilder(pctx, ctx)
 	rule.Command().
 		BuiltTool("metalava").
+		Text("signature-to-dex").
 		Inputs(removedTxtFiles).
-		FlagWithOutput("--dex-api ", output)
+		FlagWithOutput("--out ", output)
 	rule.Build("modular-hiddenapi-removed-dex-signatures"+suffix, "modular hiddenapi removed dex signatures"+suffix)
 	return android.OptionalPathForPath(output)
 }
@@ -1428,7 +1429,7 @@
 		// should not contribute to anything. So, rather than have a missing dex jar cause a Soong
 		// failure defer the error reporting to Ninja. Unless the prebuilt build target is explicitly
 		// built Ninja should never use the dex jar file.
-		if !isActiveModule(module) {
+		if !isActiveModule(ctx, module) {
 			return true
 		}
 
diff --git a/java/hiddenapi_singleton.go b/java/hiddenapi_singleton.go
index 8cb78cd..7d21b7a 100644
--- a/java/hiddenapi_singleton.go
+++ b/java/hiddenapi_singleton.go
@@ -150,6 +150,10 @@
 	// Strip a prebuilt_ prefix so that this can match a prebuilt module that has not been renamed.
 	name = android.RemoveOptionalPrebuiltPrefix(name)
 
+	// Strip the ".impl" suffix, so that the implementation library of the java_sdk_library is
+	// treated identical to the top level java_sdk_library.
+	name = strings.TrimSuffix(name, ".impl")
+
 	// Ignore any module that is not listed in the boot image configuration.
 	index := configuredBootJars.IndexOfJar(name)
 	if index == -1 {
diff --git a/java/hiddenapi_singleton_test.go b/java/hiddenapi_singleton_test.go
index c1fee21..6229797 100644
--- a/java/hiddenapi_singleton_test.go
+++ b/java/hiddenapi_singleton_test.go
@@ -198,13 +198,22 @@
 				hiddenApiFixtureFactory,
 				tc.preparer,
 				prepareForTestWithDefaultPlatformBootclasspath,
+				// Make sure that we have atleast one platform library so that we can check the monolithic hiddenapi
+				// file creation.
+				FixtureConfigureBootJars("platform:foo"),
 				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
 					variables.Always_use_prebuilt_sdks = proptools.BoolPtr(tc.unbundledBuild)
 					variables.BuildFlags = map[string]string{
 						"RELEASE_HIDDEN_API_EXPORTABLE_STUBS": "true",
 					}
 				}),
-			).RunTest(t)
+			).RunTestWithBp(t, `
+		java_library {
+			name: "foo",
+			srcs: ["a.java"],
+			compile_dex: true,
+		}
+		`)
 
 			hiddenAPI := result.ModuleForTests("platform-bootclasspath", "android_common")
 			hiddenapiRule := hiddenAPI.Rule("platform-bootclasspath-monolithic-hiddenapi-stub-flags")
@@ -303,7 +312,7 @@
 	`)
 
 	checkDexEncoded := func(t *testing.T, name, unencodedDexJar, encodedDexJar string) {
-		moduleForTests := result.ModuleForTests(name, "android_common")
+		moduleForTests := result.ModuleForTests(name+".impl", "android_common")
 
 		encodeDexRule := moduleForTests.Rule("hiddenAPIEncodeDex")
 		actualUnencodedDexJar := encodeDexRule.Input
@@ -319,18 +328,8 @@
 
 	// 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/android_common/aligned/foo.jar"
-		expectedEncodedDexJar := "out/soong/.intermediates/foo/android_common/hiddenapi/foo.jar"
+		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)
 	})
-
-	// The dex jar of the child implementation java_library of the java_sdk_library is not currently
-	// dex encoded.
-	t.Run("foo.impl", func(t *testing.T) {
-		fooImpl := result.ModuleForTests("foo.impl", "android_common")
-		encodeDexRule := fooImpl.MaybeRule("hiddenAPIEncodeDex")
-		if encodeDexRule.Rule != nil {
-			t.Errorf("foo.impl is not expected to be encoded")
-		}
-	})
 }
diff --git a/java/jacoco.go b/java/jacoco.go
index a820b38..696a0cc 100644
--- a/java/jacoco.go
+++ b/java/jacoco.go
@@ -53,7 +53,7 @@
 	}
 
 	j, ok := ctx.Module().(instrumentable)
-	if !ctx.Module().Enabled() || !ok {
+	if !ctx.Module().Enabled(ctx) || !ok {
 		return
 	}
 
diff --git a/java/jarjar_test.go b/java/jarjar_test.go
new file mode 100644
index 0000000..82bfa2b
--- /dev/null
+++ b/java/jarjar_test.go
@@ -0,0 +1,85 @@
+// 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 (
+	"fmt"
+	"testing"
+
+	"android/soong/android"
+)
+
+func AssertJarJarRename(t *testing.T, result *android.TestResult, libName, original, expectedRename string) {
+	module := result.ModuleForTests(libName, "android_common")
+
+	provider, found := android.OtherModuleProvider(result.OtherModuleProviderAdaptor(), module.Module(), JarJarProvider)
+	android.AssertBoolEquals(t, fmt.Sprintf("found provider (%s)", libName), true, found)
+
+	renamed, found := provider.Rename[original]
+	android.AssertBoolEquals(t, fmt.Sprintf("found rename (%s)", libName), true, found)
+	android.AssertStringEquals(t, fmt.Sprintf("renamed (%s)", libName), expectedRename, renamed)
+}
+
+func TestJarJarRenameDifferentModules(t *testing.T) {
+	t.Parallel()
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+	).RunTestWithBp(t, `
+		java_library {
+			name: "their_lib",
+			jarjar_rename: ["com.example.a"],
+		}
+
+		java_library {
+			name: "boundary_lib",
+			jarjar_prefix: "RENAME",
+			static_libs: ["their_lib"],
+		}
+
+		java_library {
+			name: "my_lib",
+			static_libs: ["boundary_lib"],
+		}
+	`)
+
+	original := "com.example.a"
+	renamed := "RENAME.com.example.a"
+	AssertJarJarRename(t, result, "their_lib", original, "")
+	AssertJarJarRename(t, result, "boundary_lib", original, renamed)
+	AssertJarJarRename(t, result, "my_lib", original, renamed)
+}
+
+func TestJarJarRenameSameModule(t *testing.T) {
+	t.Parallel()
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+	).RunTestWithBp(t, `
+		java_library {
+			name: "their_lib",
+			jarjar_rename: ["com.example.a"],
+			jarjar_prefix: "RENAME",
+		}
+
+		java_library {
+			name: "my_lib",
+			static_libs: ["their_lib"],
+		}
+	`)
+
+	original := "com.example.a"
+	renamed := "RENAME.com.example.a"
+	AssertJarJarRename(t, result, "their_lib", original, renamed)
+	AssertJarJarRename(t, result, "my_lib", original, renamed)
+}
diff --git a/java/java.go b/java/java.go
index 725e25a..b320732 100644
--- a/java/java.go
+++ b/java/java.go
@@ -75,7 +75,6 @@
 		ctx.BottomUp("jacoco_deps", jacocoDepsMutator).Parallel()
 	})
 
-	ctx.RegisterParallelSingletonType("logtags", LogtagsSingleton)
 	ctx.RegisterParallelSingletonType("kythe_java_extract", kytheExtractJavaFactory)
 }
 
@@ -270,7 +269,7 @@
 	ImplementationAndResourcesJars android.Paths
 
 	// ImplementationJars is a list of jars that contain the implementations of classes in the
-	//module.
+	// module.
 	ImplementationJars android.Paths
 
 	// ResourceJars is a list of jars that contain the resources included in the module.
@@ -367,14 +366,14 @@
 	toolchain bool
 
 	static bool
+
+	installable bool
 }
 
-// installDependencyTag is a dependency tag that is annotated to cause the installed files of the
-// dependency to be installed when the parent module is installed.
-type installDependencyTag struct {
-	blueprint.BaseDependencyTag
-	android.InstallAlwaysNeededDependencyTag
-	name string
+var _ android.InstallNeededDependencyTag = (*dependencyTag)(nil)
+
+func (d dependencyTag) InstallDepNeeded() bool {
+	return d.installable
 }
 
 func (d dependencyTag) LicenseAnnotations() []android.LicenseAnnotation {
@@ -406,7 +405,7 @@
 }
 
 func IsJniDepTag(depTag blueprint.DependencyTag) bool {
-	return depTag == jniLibTag
+	return depTag == jniLibTag || depTag == jniInstallTag
 }
 
 var (
@@ -435,8 +434,8 @@
 	javaApiContributionTag  = dependencyTag{name: "java-api-contribution"}
 	depApiSrcsTag           = dependencyTag{name: "dep-api-srcs"}
 	aconfigDeclarationTag   = dependencyTag{name: "aconfig-declaration"}
-	jniInstallTag           = installDependencyTag{name: "jni install"}
-	binaryInstallTag        = installDependencyTag{name: "binary install"}
+	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)
@@ -444,6 +443,30 @@
 	usesLibCompat30OptTag   = makeUsesLibraryDependencyTag(30, true)
 )
 
+// A list of tags for deps used for compiling a module.
+// Any dependency tags that modifies the following properties of `deps` in `Module.collectDeps` should be
+// added to this list:
+// - bootClasspath
+// - classpath
+// - java9Classpath
+// - systemModules
+// - kotlin deps...
+var (
+	compileDependencyTags = []blueprint.DependencyTag{
+		sdkLibTag,
+		libTag,
+		staticLibTag,
+		bootClasspathTag,
+		systemModulesTag,
+		java9LibTag,
+		kotlinStdlibTag,
+		kotlinAnnotationsTag,
+		kotlinPluginTag,
+		syspropPublicStubDepTag,
+		instrumentationForTag,
+	}
+)
+
 func IsLibDepTag(depTag blueprint.DependencyTag) bool {
 	return depTag == libTag || depTag == sdkLibTag
 }
@@ -492,6 +515,7 @@
 	coverageFile   android.OptionalPath
 	unstrippedFile android.Path
 	partition      string
+	installPaths   android.InstallPaths
 }
 
 func sdkDeps(ctx android.BottomUpMutatorContext, sdkContext android.SdkContext, d dexer) {
@@ -567,6 +591,12 @@
 		return normalizeJavaVersion(ctx, javaVersion)
 	} 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.
+		return JAVA_VERSION_21
 	} else {
 		return JAVA_VERSION_17
 	}
@@ -675,6 +705,10 @@
 
 var _ android.ApexModule = (*Library)(nil)
 
+func (j *Library) CheckDepsMinSdkVersion(ctx android.ModuleContext) {
+	CheckMinSdkVersion(ctx, j)
+}
+
 // Provides access to the list of permitted packages from apex boot jars.
 type PermittedPackagesForUpdatableBootJars interface {
 	PermittedPackagesForUpdatableBootJars() []string
@@ -903,6 +937,12 @@
 	j.minSdkVersion = j.MinSdkVersion(ctx)
 	j.maxSdkVersion = j.MaxSdkVersion(ctx)
 
+	// Check min_sdk_version of the transitive dependencies if this module is created from
+	// java_sdk_library.
+	if j.overridableProperties.Min_sdk_version != nil && j.SdkLibraryName() != nil {
+		j.CheckDepsMinSdkVersion(ctx)
+	}
+
 	// SdkLibrary.GenerateAndroidBuildActions(ctx) sets the stubsLinkType to Unknown.
 	// If the stubsLinkType has already been set to Unknown, the stubsLinkType should
 	// not be overridden.
@@ -933,8 +973,12 @@
 	j.checkSdkVersions(ctx)
 	j.checkHeadersOnly(ctx)
 	if ctx.Device() {
+		libName := j.Name()
+		if j.SdkLibraryName() != nil && strings.HasSuffix(libName, ".impl") {
+			libName = proptools.String(j.SdkLibraryName())
+		}
 		j.dexpreopter.installPath = j.dexpreopter.getInstallPath(
-			ctx, j.Name(), android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar"))
+			ctx, libName, android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar"))
 		j.dexpreopter.isSDKLibrary = j.deviceProperties.IsSDKLibrary
 		setUncompressDex(ctx, &j.dexpreopter, &j.dexer)
 		j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex
@@ -945,8 +989,26 @@
 	}
 	j.compile(ctx, nil, nil, nil)
 
-	exclusivelyForApex := !apexInfo.IsForPlatform()
-	if (Bool(j.properties.Installable) || ctx.Host()) && !exclusivelyForApex {
+	// 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())
+	}
+
+	android.SetProvider(ctx, android.TestOnlyProviderKey, android.TestModuleInformation{
+		TestOnly:       Bool(j.sourceProperties.Test_only),
+		TopLevelTarget: j.sourceProperties.Top_level_test_target,
+	})
+
+	setOutputFiles(ctx, j.Module)
+}
+
+func (j *Library) setInstallRules(ctx android.ModuleContext, installModuleName string) {
+	apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
+
+	if (Bool(j.properties.Installable) || ctx.Host()) && apexInfo.IsForPlatform() {
 		var extraInstallDeps android.InstallPaths
 		if j.InstallMixin != nil {
 			extraInstallDeps = j.InstallMixin(ctx, j.outputFile)
@@ -963,22 +1025,27 @@
 			if !ctx.Host() {
 				archDir = ctx.DeviceConfig().DeviceArch()
 			}
-			installDir = android.PathForModuleInstall(ctx, ctx.ModuleName(), archDir)
+			installDir = android.PathForModuleInstall(ctx, installModuleName, archDir)
 		} else {
 			installDir = android.PathForModuleInstall(ctx, "framework")
 		}
 		j.installFile = ctx.InstallFile(installDir, j.Stem()+".jar", j.outputFile, extraInstallDeps...)
 	}
-
-	android.SetProvider(ctx, android.TestOnlyProviderKey, android.TestModuleInformation{
-		TestOnly:       Bool(j.sourceProperties.Test_only),
-		TopLevelTarget: j.sourceProperties.Top_level_test_target,
-	})
 }
 
 func (j *Library) DepsMutator(ctx android.BottomUpMutatorContext) {
 	j.usesLibrary.deps(ctx, false)
 	j.deps(ctx)
+
+	if j.SdkLibraryName() != nil && strings.HasSuffix(j.Name(), ".impl") {
+		if dexpreopt.IsDex2oatNeeded(ctx) {
+			dexpreopt.RegisterToolDeps(ctx)
+		}
+		prebuiltSdkLibExists := ctx.OtherModuleExists(android.PrebuiltNameFromSource(proptools.String(j.SdkLibraryName())))
+		if prebuiltSdkLibExists && ctx.OtherModuleExists("all_apex_contributions") {
+			ctx.AddDependency(ctx.Module(), android.AcDepTag, "all_apex_contributions")
+		}
+	}
 }
 
 const (
@@ -1062,7 +1129,7 @@
 
 	// If the min_sdk_version was set then add the canonical representation of the API level to the
 	// snapshot.
-	if j.deviceProperties.Min_sdk_version != nil {
+	if j.overridableProperties.Min_sdk_version != nil {
 		canonical, err := android.ReplaceFinalizedCodenames(ctx.SdkModuleContext().Config(), j.minSdkVersion.String())
 		if err != nil {
 			ctx.ModuleErrorf("%s", err)
@@ -1315,7 +1382,7 @@
 	return true
 }
 
-func (j *TestHost) IsNativeCoverageNeeded(ctx android.IncomingTransitionContext) bool {
+func (j *TestHost) IsNativeCoverageNeeded(ctx cc.IsNativeCoverageNeededContext) bool {
 	return ctx.DeviceConfig().NativeCoverageEnabled()
 }
 
@@ -1460,13 +1527,16 @@
 		InstalledFiles:      j.data,
 		OutputFile:          j.outputFile,
 		TestConfig:          j.testConfig,
-		RequiredModuleNames: j.RequiredModuleNames(),
+		RequiredModuleNames: j.RequiredModuleNames(ctx),
 		TestSuites:          j.testProperties.Test_suites,
 		IsHost:              true,
+		LocalSdkVersion:     j.sdkVersion.String(),
+		IsUnitTest:          Bool(j.testProperties.Test_options.Unit_test),
 	})
 }
 
 func (j *Test) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	checkMinSdkVersionMts(ctx, j.MinSdkVersion(ctx))
 	j.generateAndroidBuildActionsWithConfig(ctx, nil)
 	android.SetProvider(ctx, testing.TestModuleProviderKey, testing.TestModuleProviderData{})
 }
@@ -1792,6 +1862,8 @@
 		// libraries.  This is verified by TestBinary.
 		j.binaryFile = ctx.InstallExecutable(android.PathForModuleInstall(ctx, "bin"),
 			ctx.ModuleName()+ext, j.wrapperFile)
+
+		setOutputFiles(ctx, j.Library.Module)
 	}
 }
 
@@ -1967,12 +2039,17 @@
 	// List of aconfig_declarations module names that the stubs generated in this module
 	// depend on.
 	Aconfig_declarations []string
+
+	// List of hard coded filegroups containing Metalava config files that are passed to every
+	// Metalava invocation that this module performs. See addMetalavaConfigFilesToCmd.
+	ConfigFiles []string `android:"path" blueprint:"mutated"`
 }
 
 func ApiLibraryFactory() android.Module {
 	module := &ApiLibrary{}
-	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	module.AddProperties(&module.properties)
+	module.properties.ConfigFiles = getMetalavaConfigFilegroupReference()
+	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	module.initModuleAndImport(module)
 	android.InitDefaultableModule(module)
 	return module
@@ -1988,7 +2065,7 @@
 
 func metalavaStubCmd(ctx android.ModuleContext, rule *android.RuleBuilder,
 	srcs android.Paths, homeDir android.WritablePath,
-	classpath android.Paths) *android.RuleBuilderCommand {
+	classpath android.Paths, configFiles android.Paths) *android.RuleBuilderCommand {
 	rule.Command().Text("rm -rf").Flag(homeDir.String())
 	rule.Command().Text("mkdir -p").Flag(homeDir.String())
 
@@ -2027,6 +2104,8 @@
 		FlagWithArg("--hide ", "InvalidNullabilityOverride").
 		FlagWithArg("--hide ", "ChangedDefault")
 
+	addMetalavaConfigFilesToCmd(cmd, configFiles)
+
 	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
@@ -2137,7 +2216,7 @@
 
 // Map where key is the api scope name and value is the int value
 // representing the order of the api scope, narrowest to the widest
-var scopeOrderMap = allApiScopes.MapToIndex(
+var scopeOrderMap = AllApiScopes.MapToIndex(
 	func(s *apiScope) string { return s.name })
 
 func (al *ApiLibrary) sortApiFilesByApiScope(ctx android.ModuleContext, srcFilesInfo []JavaApiImportInfo) []JavaApiImportInfo {
@@ -2238,14 +2317,16 @@
 		ctx.ModuleErrorf("Error: %s has an empty api file.", ctx.ModuleName())
 	}
 
-	cmd := metalavaStubCmd(ctx, rule, srcFiles, homeDir, systemModulesPaths)
+	configFiles := android.PathsForModuleSrc(ctx, al.properties.ConfigFiles)
+
+	cmd := metalavaStubCmd(ctx, rule, srcFiles, homeDir, systemModulesPaths, configFiles)
 
 	al.stubsFlags(ctx, cmd, stubsDir)
 
-	migratingNullability := String(al.properties.Previous_api) != ""
-	if migratingNullability {
-		previousApi := android.PathForModuleSrc(ctx, String(al.properties.Previous_api))
-		cmd.FlagWithInput("--migrate-nullness ", previousApi)
+	previousApi := String(al.properties.Previous_api)
+	if previousApi != "" {
+		previousApiFiles := android.PathsForModuleSrc(ctx, []string{previousApi})
+		cmd.FlagForEachInput("--migrate-nullness ", previousApiFiles)
 	}
 
 	al.addValidation(ctx, cmd, al.validationPaths)
@@ -2298,7 +2379,7 @@
 		classesJar:    al.stubsJar,
 		jarName:       ctx.ModuleName() + ".jar",
 	}
-	dexOutputFile := al.dexer.compileDex(ctx, dexParams)
+	dexOutputFile, _ := al.dexer.compileDex(ctx, dexParams)
 	uncompressed := true
 	al.initHiddenAPI(ctx, makeDexJarPathFromPath(dexOutputFile), al.stubsJar, &uncompressed)
 	dexOutputFile = al.hiddenAPIEncodeDex(ctx, dexOutputFile)
@@ -2338,10 +2419,35 @@
 	return android.FutureApiLevel
 }
 
+func (al *ApiLibrary) IDEInfo(i *android.IdeInfo) {
+	i.Deps = append(i.Deps, al.ideDeps()...)
+	i.Libs = append(i.Libs, al.properties.Libs...)
+	i.Static_libs = append(i.Static_libs, al.properties.Static_libs...)
+	i.SrcJars = append(i.SrcJars, al.stubsSrcJar.String())
+}
+
+// deps of java_api_library for module_bp_java_deps.json
+func (al *ApiLibrary) ideDeps() []string {
+	ret := []string{}
+	ret = append(ret, al.properties.Libs...)
+	ret = append(ret, al.properties.Static_libs...)
+	if al.properties.System_modules != nil {
+		ret = append(ret, proptools.String(al.properties.System_modules))
+	}
+	if al.properties.Full_api_surface_stub != nil {
+		ret = append(ret, proptools.String(al.properties.Full_api_surface_stub))
+	}
+	// Other non java_library dependencies like java_api_contribution are ignored for now.
+	return ret
+}
+
 // implement the following interfaces for hiddenapi processing
 var _ hiddenAPIModule = (*ApiLibrary)(nil)
 var _ UsesLibraryDependency = (*ApiLibrary)(nil)
 
+// implement the following interface for IDE completion.
+var _ android.IDEInfo = (*ApiLibrary)(nil)
+
 //
 // Java prebuilts
 //
@@ -2682,7 +2788,7 @@
 				jarName:       jarName,
 			}
 
-			dexOutputFile = j.dexer.compileDex(ctx, dexParams)
+			dexOutputFile, _ = j.dexer.compileDex(ctx, dexParams)
 			if ctx.Failed() {
 				return
 			}
@@ -2708,6 +2814,9 @@
 		StubsLinkType:                  j.stubsLinkType,
 		// TODO(b/289117800): LOCAL_ACONFIG_FILES for prebuilts
 	})
+
+	ctx.SetOutputFiles(android.Paths{j.combinedImplementationFile}, "")
+	ctx.SetOutputFiles(android.Paths{j.combinedImplementationFile}, ".jar")
 }
 
 func (j *Import) maybeInstall(ctx android.ModuleContext, jarName string, outputFile android.Path) {
@@ -2728,17 +2837,6 @@
 	ctx.InstallFile(installDir, jarName, outputFile)
 }
 
-func (j *Import) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "", ".jar":
-		return android.Paths{j.combinedImplementationFile}, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
-var _ android.OutputFileProducer = (*Import)(nil)
-
 func (j *Import) HeaderJars() android.Paths {
 	return android.PathsIfNonNil(j.combinedHeaderFile)
 }
diff --git a/java/java_test.go b/java/java_test.go
index a1192bb..33079f3 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -2801,6 +2801,7 @@
 	aconfig_declarations {
 		name: "bar",
 		package: "com.example.package",
+		container: "com.android.foo",
 		srcs: [
 			"bar.aconfig",
 		],
@@ -2992,23 +2993,23 @@
 	bar := result.ModuleForTests("bar", "android_common")
 	baz := result.ModuleForTests("baz", "android_common")
 
-	fooOutputPath := android.OutputFileForModule(android.PathContext(nil), foo.Module(), "")
-	barOutputPath := android.OutputFileForModule(android.PathContext(nil), bar.Module(), "")
-	bazOutputPath := android.OutputFileForModule(android.PathContext(nil), baz.Module(), "")
+	fooOutputPaths := foo.OutputFiles(t, "")
+	barOutputPaths := bar.OutputFiles(t, "")
+	bazOutputPaths := baz.OutputFiles(t, "")
 
-	android.AssertPathRelativeToTopEquals(t, "foo output path",
-		"out/soong/.intermediates/foo/android_common/javac/foo.jar", fooOutputPath)
-	android.AssertPathRelativeToTopEquals(t, "bar output path",
-		"out/soong/.intermediates/bar/android_common/combined/bar.jar", barOutputPath)
-	android.AssertPathRelativeToTopEquals(t, "baz output path",
-		"out/soong/.intermediates/baz/android_common/combined/baz.jar", bazOutputPath)
+	android.AssertPathsRelativeToTopEquals(t, "foo output path",
+		[]string{"out/soong/.intermediates/foo/android_common/javac/foo.jar"}, fooOutputPaths)
+	android.AssertPathsRelativeToTopEquals(t, "bar output path",
+		[]string{"out/soong/.intermediates/bar/android_common/combined/bar.jar"}, barOutputPaths)
+	android.AssertPathsRelativeToTopEquals(t, "baz output path",
+		[]string{"out/soong/.intermediates/baz/android_common/combined/baz.jar"}, bazOutputPaths)
 
 	android.AssertStringEquals(t, "foo relative output path",
-		"foo.jar", fooOutputPath.Rel())
+		"foo.jar", fooOutputPaths[0].Rel())
 	android.AssertStringEquals(t, "bar relative output path",
-		"bar.jar", barOutputPath.Rel())
+		"bar.jar", barOutputPaths[0].Rel())
 	android.AssertStringEquals(t, "baz relative output path",
-		"baz.jar", bazOutputPath.Rel())
+		"baz.jar", bazOutputPaths[0].Rel())
 }
 
 func assertTestOnlyAndTopLevel(t *testing.T, ctx *android.TestResult, expectedTestOnly []string, expectedTopLevel []string) {
diff --git a/java/jdeps.go b/java/jdeps.go
index 91f7ce7..3400263 100644
--- a/java/jdeps.go
+++ b/java/jdeps.go
@@ -48,7 +48,7 @@
 	moduleInfos := make(map[string]android.IdeInfo)
 
 	ctx.VisitAllModules(func(module android.Module) {
-		if !module.Enabled() {
+		if !module.Enabled(ctx) {
 			return
 		}
 
diff --git a/java/jdeps_test.go b/java/jdeps_test.go
index 874d1d7..e180224 100644
--- a/java/jdeps_test.go
+++ b/java/jdeps_test.go
@@ -22,28 +22,46 @@
 )
 
 func TestCollectJavaLibraryPropertiesAddLibsDeps(t *testing.T) {
-	expected := []string{"Foo", "Bar"}
-	module := LibraryFactory().(*Library)
-	module.properties.Libs = append(module.properties.Libs, expected...)
+	ctx, _ := testJava(t,
+		`
+		java_library {name: "Foo"}
+		java_library {name: "Bar"}
+		java_library {
+			name: "javalib",
+			libs: ["Foo", "Bar"],
+		}
+	`)
+	module := ctx.ModuleForTests("javalib", "android_common").Module().(*Library)
 	dpInfo := &android.IdeInfo{}
 
 	module.IDEInfo(dpInfo)
 
-	if !reflect.DeepEqual(dpInfo.Deps, expected) {
-		t.Errorf("Library.IDEInfo() Deps = %v, want %v", dpInfo.Deps, expected)
+	for _, expected := range []string{"Foo", "Bar"} {
+		if !android.InList(expected, dpInfo.Deps) {
+			t.Errorf("Library.IDEInfo() Deps = %v, %v not found", dpInfo.Deps, expected)
+		}
 	}
 }
 
 func TestCollectJavaLibraryPropertiesAddStaticLibsDeps(t *testing.T) {
-	expected := []string{"Foo", "Bar"}
-	module := LibraryFactory().(*Library)
-	module.properties.Static_libs = append(module.properties.Static_libs, expected...)
+	ctx, _ := testJava(t,
+		`
+		java_library {name: "Foo"}
+		java_library {name: "Bar"}
+		java_library {
+			name: "javalib",
+			static_libs: ["Foo", "Bar"],
+		}
+	`)
+	module := ctx.ModuleForTests("javalib", "android_common").Module().(*Library)
 	dpInfo := &android.IdeInfo{}
 
 	module.IDEInfo(dpInfo)
 
-	if !reflect.DeepEqual(dpInfo.Deps, expected) {
-		t.Errorf("Library.IDEInfo() Deps = %v, want %v", dpInfo.Deps, expected)
+	for _, expected := range []string{"Foo", "Bar"} {
+		if !android.InList(expected, dpInfo.Deps) {
+			t.Errorf("Library.IDEInfo() Deps = %v, %v not found", dpInfo.Deps, expected)
+		}
 	}
 }
 
diff --git a/java/lint.go b/java/lint.go
index 31e7f35..2eea07d 100644
--- a/java/lint.go
+++ b/java/lint.go
@@ -319,25 +319,19 @@
 	cmd.FlagWithInput("@",
 		android.PathForSource(ctx, "build/soong/java/lint_defaults.txt"))
 
-	if l.compileSdkKind == android.SdkPublic {
-		cmd.FlagForEachArg("--error_check ", l.extraMainlineLintErrors)
-	} else {
-		// TODO(b/268261262): Remove this branch. We're demoting NewApi to a warning due to pre-existing issues that need to be fixed.
-		cmd.FlagForEachArg("--warning_check ", l.extraMainlineLintErrors)
-	}
+	cmd.FlagForEachArg("--error_check ", l.extraMainlineLintErrors)
 	cmd.FlagForEachArg("--disable_check ", l.properties.Lint.Disabled_checks)
 	cmd.FlagForEachArg("--warning_check ", l.properties.Lint.Warning_checks)
 	cmd.FlagForEachArg("--error_check ", l.properties.Lint.Error_checks)
 	cmd.FlagForEachArg("--fatal_check ", l.properties.Lint.Fatal_checks)
 
-	// TODO(b/193460475): Re-enable strict updatability linting
-	//if l.GetStrictUpdatabilityLinting() {
-	//	// Verify the module does not baseline issues that endanger safe updatability.
-	//	if baselinePath := l.getBaselineFilepath(ctx); baselinePath.Valid() {
-	//		cmd.FlagWithInput("--baseline ", baselinePath.Path())
-	//		cmd.FlagForEachArg("--disallowed_issues ", updatabilityChecks)
-	//	}
-	//}
+	if l.GetStrictUpdatabilityLinting() {
+		// Verify the module does not baseline issues that endanger safe updatability.
+		if l.properties.Lint.Baseline_filename != nil {
+			cmd.FlagWithInput("--baseline ", android.PathForModuleSrc(ctx, *l.properties.Lint.Baseline_filename))
+			cmd.FlagForEachArg("--disallowed_issues ", updatabilityChecks)
+		}
+	}
 
 	return lintPaths{
 		projectXML: projectXMLPath,
@@ -612,7 +606,7 @@
 		apiVersionsDb := findModuleOrErr(ctx, files.apiVersionsModule)
 		if apiVersionsDb == nil {
 			if !ctx.Config().AllowMissingDependencies() {
-				ctx.Errorf("lint: missing module api_versions_public")
+				ctx.Errorf("lint: missing module %s", files.apiVersionsModule)
 			}
 			return
 		}
@@ -620,7 +614,7 @@
 		sdkAnnotations := findModuleOrErr(ctx, files.annotationsModule)
 		if sdkAnnotations == nil {
 			if !ctx.Config().AllowMissingDependencies() {
-				ctx.Errorf("lint: missing module sdk-annotations.zip")
+				ctx.Errorf("lint: missing module %s", files.annotationsModule)
 			}
 			return
 		}
diff --git a/java/lint_test.go b/java/lint_test.go
index 751b139..b51753f 100644
--- a/java/lint_test.go
+++ b/java/lint_test.go
@@ -91,9 +91,8 @@
 		t.Error("did not use the correct file for baseline")
 	}
 
-	if !strings.Contains(*sboxProto.Commands[0].Command, "--warning_check NewApi") {
-		// TODO(b/268261262): Change this to check for --error_check
-		t.Error("should check NewApi warnings")
+	if !strings.Contains(*sboxProto.Commands[0].Command, "--error_check NewApi") {
+		t.Error("should check NewApi errors")
 	}
 
 	if !strings.Contains(*sboxProto.Commands[0].Command, "--error_check SomeCheck") {
@@ -153,52 +152,55 @@
 	}
 }
 
-// TODO(b/193460475): Re-enable this test
-//func TestJavaLintStrictUpdatabilityLinting(t *testing.T) {
-//	bp := `
-//		java_library {
-//			name: "foo",
-//			srcs: [
-//				"a.java",
-//			],
-//			static_libs: ["bar"],
-//			min_sdk_version: "29",
-//			sdk_version: "current",
-//			lint: {
-//				strict_updatability_linting: true,
-//			},
-//		}
-//
-//		java_library {
-//			name: "bar",
-//			srcs: [
-//				"a.java",
-//			],
-//			min_sdk_version: "29",
-//			sdk_version: "current",
-//		}
-//	`
-//	fs := android.MockFS{
-//		"lint-baseline.xml": nil,
-//	}
-//
-//	result := android.GroupFixturePreparers(PrepareForTestWithJavaDefaultModules, fs.AddToFixture()).
-//		RunTestWithBp(t, bp)
-//
-//	foo := result.ModuleForTests("foo", "android_common")
-//	sboxProto := android.RuleBuilderSboxProtoForTests(t, foo.Output("lint.sbox.textproto"))
-//	if !strings.Contains(*sboxProto.Commands[0].Command,
-//		"--baseline lint-baseline.xml --disallowed_issues NewApi") {
-//		t.Error("did not restrict baselining NewApi")
-//	}
-//
-//	bar := result.ModuleForTests("bar", "android_common")
-//	sboxProto = android.RuleBuilderSboxProtoForTests(t, bar.Output("lint.sbox.textproto"))
-//	if !strings.Contains(*sboxProto.Commands[0].Command,
-//		"--baseline lint-baseline.xml --disallowed_issues NewApi") {
-//		t.Error("did not restrict baselining NewApi")
-//	}
-//}
+func TestJavaLintStrictUpdatabilityLinting(t *testing.T) {
+	bp := `
+		java_library {
+			name: "foo",
+			srcs: [
+				"a.java",
+			],
+			static_libs: ["bar"],
+			min_sdk_version: "29",
+			sdk_version: "current",
+			lint: {
+				strict_updatability_linting: true,
+				baseline_filename: "lint-baseline.xml",
+			},
+		}
+
+		java_library {
+			name: "bar",
+			srcs: [
+				"a.java",
+			],
+			min_sdk_version: "29",
+			sdk_version: "current",
+			lint: {
+				baseline_filename: "lint-baseline.xml",
+			}
+		}
+	`
+	fs := android.MockFS{
+		"lint-baseline.xml": nil,
+	}
+
+	result := android.GroupFixturePreparers(PrepareForTestWithJavaDefaultModules, fs.AddToFixture()).
+		RunTestWithBp(t, bp)
+
+	foo := result.ModuleForTests("foo", "android_common")
+	sboxProto := android.RuleBuilderSboxProtoForTests(t, result.TestContext, foo.Output("lint.sbox.textproto"))
+	if !strings.Contains(*sboxProto.Commands[0].Command,
+		"--baseline lint-baseline.xml --disallowed_issues NewApi") {
+		t.Error("did not restrict baselining NewApi")
+	}
+
+	bar := result.ModuleForTests("bar", "android_common")
+	sboxProto = android.RuleBuilderSboxProtoForTests(t, result.TestContext, bar.Output("lint.sbox.textproto"))
+	if !strings.Contains(*sboxProto.Commands[0].Command,
+		"--baseline lint-baseline.xml --disallowed_issues NewApi") {
+		t.Error("did not restrict baselining NewApi")
+	}
+}
 
 func TestJavaLintDatabaseSelectionFull(t *testing.T) {
 	testCases := []struct {
diff --git a/java/metalava/Android.bp b/java/metalava/Android.bp
new file mode 100644
index 0000000..ccbd191
--- /dev/null
+++ b/java/metalava/Android.bp
@@ -0,0 +1,18 @@
+// 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.
+
+filegroup {
+    name: "metalava-config-files",
+    srcs: ["*-config.xml"],
+}
diff --git a/java/metalava/OWNERS b/java/metalava/OWNERS
new file mode 100644
index 0000000..e8c438e
--- /dev/null
+++ b/java/metalava/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 463936
+
+file:platform/tools/metalava:/OWNERS
diff --git a/java/metalava/main-config.xml b/java/metalava/main-config.xml
new file mode 100644
index 0000000..c61196f
--- /dev/null
+++ b/java/metalava/main-config.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ 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.
+  -->
+
+<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"/>
diff --git a/java/metalava/source-model-selection-config.xml b/java/metalava/source-model-selection-config.xml
new file mode 100644
index 0000000..c61196f
--- /dev/null
+++ b/java/metalava/source-model-selection-config.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ 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.
+  -->
+
+<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"/>
diff --git a/java/platform_bootclasspath.go b/java/platform_bootclasspath.go
index 4db426e..38553a6 100644
--- a/java/platform_bootclasspath.go
+++ b/java/platform_bootclasspath.go
@@ -15,8 +15,6 @@
 package java
 
 import (
-	"fmt"
-
 	"android/soong/android"
 	"android/soong/dexpreopt"
 )
@@ -57,9 +55,6 @@
 
 	// Path to the monolithic hiddenapi-unsupported.csv file.
 	hiddenAPIMetadataCSV android.OutputPath
-
-	// Path to a srcjar containing all the transitive sources of the bootclasspath.
-	srcjar android.OutputPath
 }
 
 type platformBootclasspathProperties struct {
@@ -76,8 +71,6 @@
 	return m
 }
 
-var _ android.OutputFileProducer = (*platformBootclasspathModule)(nil)
-
 func (b *platformBootclasspathModule) AndroidMkEntries() (entries []android.AndroidMkEntries) {
 	entries = append(entries, android.AndroidMkEntries{
 		Class: "FAKE",
@@ -89,22 +82,6 @@
 	return
 }
 
-// Make the hidden API files available from the platform-bootclasspath module.
-func (b *platformBootclasspathModule) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "hiddenapi-flags.csv":
-		return android.Paths{b.hiddenAPIFlagsCSV}, nil
-	case "hiddenapi-index.csv":
-		return android.Paths{b.hiddenAPIIndexCSV}, nil
-	case "hiddenapi-metadata.csv":
-		return android.Paths{b.hiddenAPIMetadataCSV}, nil
-	case ".srcjar":
-		return android.Paths{b.srcjar}, nil
-	}
-
-	return nil, fmt.Errorf("unknown tag %s", tag)
-}
-
 func (b *platformBootclasspathModule) 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")
@@ -198,8 +175,8 @@
 	}
 	jarArgs := resourcePathsToJarArgs(transitiveSrcFiles)
 	jarArgs = append(jarArgs, "-srcjar") // Move srcfiles to the right package
-	b.srcjar = android.PathForModuleOut(ctx, ctx.ModuleName()+"-transitive.srcjar").OutputPath
-	TransformResourcesToJar(ctx, b.srcjar, jarArgs, transitiveSrcFiles)
+	srcjar := android.PathForModuleOut(ctx, ctx.ModuleName()+"-transitive.srcjar").OutputPath
+	TransformResourcesToJar(ctx, srcjar, jarArgs, transitiveSrcFiles)
 
 	// Gather all the fragments dependencies.
 	b.fragments = gatherApexModulePairDepsWithTag(ctx, bootclasspathFragmentDepTag)
@@ -213,6 +190,11 @@
 
 	bootDexJarByModule := b.generateHiddenAPIBuildActions(ctx, b.configuredModules, b.fragments)
 	buildRuleForBootJarsPackageCheck(ctx, bootDexJarByModule)
+
+	ctx.SetOutputFiles(android.Paths{b.hiddenAPIFlagsCSV}, "hiddenapi-flags.csv")
+	ctx.SetOutputFiles(android.Paths{b.hiddenAPIIndexCSV}, "hiddenapi-index.csv")
+	ctx.SetOutputFiles(android.Paths{b.hiddenAPIMetadataCSV}, "hiddenapi-metadata.csv")
+	ctx.SetOutputFiles(android.Paths{srcjar}, ".srcjar")
 }
 
 // Generate classpaths.proto config
@@ -221,6 +203,7 @@
 	// ART and platform boot jars must have a corresponding entry in DEX2OATBOOTCLASSPATH
 	classpathJars := configuredJarListToClasspathJars(ctx, configuredJars, BOOTCLASSPATH, DEX2OATBOOTCLASSPATH)
 	b.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, configuredJars, classpathJars)
+	b.classpathFragmentBase().installClasspathProto(ctx)
 }
 
 func (b *platformBootclasspathModule) configuredJars(ctx android.ModuleContext) android.ConfiguredJarList {
@@ -293,6 +276,15 @@
 
 // generateHiddenAPIBuildActions generates all the hidden API related build rules.
 func (b *platformBootclasspathModule) generateHiddenAPIBuildActions(ctx android.ModuleContext, modules []android.Module, fragments []android.Module) bootDexJarByModule {
+	createEmptyHiddenApiFiles := func() {
+		paths := android.OutputPaths{b.hiddenAPIFlagsCSV, b.hiddenAPIIndexCSV, b.hiddenAPIMetadataCSV}
+		for _, path := range paths {
+			ctx.Build(pctx, android.BuildParams{
+				Rule:   android.Touch,
+				Output: path,
+			})
+		}
+	}
 
 	// Save the paths to the monolithic files for retrieval via OutputFiles().
 	b.hiddenAPIFlagsCSV = hiddenAPISingletonPaths(ctx).flags
@@ -305,13 +297,7 @@
 	// optimization that can be used to reduce the incremental build time but as its name suggests it
 	// can be unsafe to use, e.g. when the changes affect anything that goes on the bootclasspath.
 	if ctx.Config().DisableHiddenApiChecks() {
-		paths := android.OutputPaths{b.hiddenAPIFlagsCSV, b.hiddenAPIIndexCSV, b.hiddenAPIMetadataCSV}
-		for _, path := range paths {
-			ctx.Build(pctx, android.BuildParams{
-				Rule:   android.Touch,
-				Output: path,
-			})
-		}
+		createEmptyHiddenApiFiles()
 		return bootDexJarByModule
 	}
 
@@ -324,6 +310,13 @@
 	// the fragments will have already provided the flags that are needed.
 	classesJars := monolithicInfo.ClassesJars
 
+	if len(classesJars) == 0 {
+		// This product does not include any monolithic jars. Monolithic hiddenapi flag generation is not required.
+		// However, generate an empty file so that the dist tags in f/b/boot/Android.bp can be resolved, and `m dist` works.
+		createEmptyHiddenApiFiles()
+		return bootDexJarByModule
+	}
+
 	// Create the input to pass to buildRuleToGenerateHiddenAPIStubFlagsFile
 	input := newHiddenAPIFlagInput()
 
diff --git a/java/platform_bootclasspath_test.go b/java/platform_bootclasspath_test.go
index 37ff639..0d2acae 100644
--- a/java/platform_bootclasspath_test.go
+++ b/java/platform_bootclasspath_test.go
@@ -353,7 +353,7 @@
 
 	// All the intermediate rules use the same inputs.
 	expectedIntermediateInputs := `
-		out/soong/.intermediates/bar/android_common/javac/bar.jar
+		out/soong/.intermediates/bar.impl/android_common/javac/bar.jar
 		out/soong/.intermediates/foo-hiddenapi-annotations/android_common/javac/foo-hiddenapi-annotations.jar
 		out/soong/.intermediates/foo/android_common/javac/foo.jar
 	`
diff --git a/java/platform_compat_config.go b/java/platform_compat_config.go
index 2fc6c02..67ed84e 100644
--- a/java/platform_compat_config.go
+++ b/java/platform_compat_config.go
@@ -15,10 +15,10 @@
 package java
 
 import (
-	"fmt"
 	"path/filepath"
 
 	"android/soong/android"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
@@ -60,6 +60,8 @@
 	installDirPath android.InstallPath
 	configFile     android.OutputPath
 	metadataFile   android.OutputPath
+
+	installConfigFile android.InstallPath
 }
 
 func (p *platformCompatConfig) compatConfigMetadata() android.Path {
@@ -105,21 +107,15 @@
 		FlagWithOutput("--merged-config ", p.metadataFile)
 
 	p.installDirPath = android.PathForModuleInstall(ctx, "etc", "compatconfig")
+	p.installConfigFile = android.PathForModuleInstall(ctx, "etc", "compatconfig", p.configFile.Base())
 	rule.Build(configFileName, "Extract compat/compat_config.xml and install it")
-
+	ctx.InstallFile(p.installDirPath, p.configFile.Base(), p.configFile)
 }
 
 func (p *platformCompatConfig) AndroidMkEntries() []android.AndroidMkEntries {
 	return []android.AndroidMkEntries{android.AndroidMkEntries{
 		Class:      "ETC",
 		OutputFile: android.OptionalPathForPath(p.configFile),
-		Include:    "$(BUILD_PREBUILT)",
-		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
-			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-				entries.SetString("LOCAL_MODULE_PATH", p.installDirPath.String())
-				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", p.configFile.Base())
-			},
-		},
 	}}
 }
 
@@ -233,7 +229,7 @@
 	var compatConfigMetadata android.Paths
 
 	ctx.VisitAllModules(func(module android.Module) {
-		if !module.Enabled() {
+		if !module.Enabled(ctx) {
 			return
 		}
 		if c, ok := module.(platformCompatConfigMetadataProvider); ok {
@@ -265,7 +261,7 @@
 
 func (p *platformCompatConfigSingleton) MakeVars(ctx android.MakeVarsContext) {
 	if p.metadata != nil {
-		ctx.Strict("INTERNAL_PLATFORM_MERGED_COMPAT_CONFIG", p.metadata.String())
+		ctx.DistForGoal("droidcore", p.metadata)
 	}
 }
 
@@ -283,32 +279,23 @@
 	android.ModuleBase
 
 	properties globalCompatConfigProperties
-
-	outputFilePath android.OutputPath
 }
 
 func (c *globalCompatConfig) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	filename := String(c.properties.Filename)
 
 	inputPath := platformCompatConfigPath(ctx)
-	c.outputFilePath = android.PathForModuleOut(ctx, filename).OutputPath
+	outputFilePath := android.PathForModuleOut(ctx, filename).OutputPath
 
 	// This ensures that outputFilePath has the correct name for others to
 	// use, as the source file may have a different name.
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   android.Cp,
-		Output: c.outputFilePath,
+		Output: outputFilePath,
 		Input:  inputPath,
 	})
-}
 
-func (h *globalCompatConfig) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		return android.Paths{h.outputFilePath}, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
+	ctx.SetOutputFiles(android.Paths{outputFilePath}, "")
 }
 
 // global_compat_config provides access to the merged compat config xml file generated by the build.
diff --git a/java/robolectric.go b/java/robolectric.go
index 18386c9..4cad5b1 100644
--- a/java/robolectric.go
+++ b/java/robolectric.go
@@ -29,8 +29,12 @@
 )
 
 func init() {
-	android.RegisterModuleType("android_robolectric_test", RobolectricTestFactory)
-	android.RegisterModuleType("android_robolectric_runtimes", robolectricRuntimesFactory)
+	RegisterRobolectricBuildComponents(android.InitRegistrationContext)
+}
+
+func RegisterRobolectricBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("android_robolectric_test", RobolectricTestFactory)
+	ctx.RegisterModuleType("android_robolectric_runtimes", robolectricRuntimesFactory)
 }
 
 var robolectricDefaultLibs = []string{
@@ -74,6 +78,8 @@
 
 	// Use strict mode to limit access of Robolectric API directly. See go/roboStrictMode
 	Strict_mode *bool
+
+	Jni_libs []string
 }
 
 type robolectricTest struct {
@@ -116,7 +122,7 @@
 
 	if v := String(r.robolectricProperties.Robolectric_prebuilt_version); v != "" {
 		ctx.AddVariationDependencies(nil, libTag, fmt.Sprintf(robolectricPrebuiltLibPattern, v))
-	} else if !proptools.Bool(r.robolectricProperties.Strict_mode) {
+	} else if !proptools.BoolDefault(r.robolectricProperties.Strict_mode, true) {
 		if proptools.Bool(r.robolectricProperties.Upstream) {
 			ctx.AddVariationDependencies(nil, libTag, robolectricCurrentLib+"_upstream")
 		} else {
@@ -124,8 +130,11 @@
 		}
 	}
 
-	if proptools.Bool(r.robolectricProperties.Strict_mode) {
+	if proptools.BoolDefault(r.robolectricProperties.Strict_mode, true) {
 		ctx.AddVariationDependencies(nil, roboRuntimeOnlyTag, robolectricCurrentLib+"_upstream")
+	} else {
+		// opting out from strict mode, robolectric_non_strict_mode_permission lib should be added
+		ctx.AddVariationDependencies(nil, libTag, "robolectric_non_strict_mode_permission")
 	}
 
 	ctx.AddVariationDependencies(nil, libTag, robolectricDefaultLibs...)
@@ -134,6 +143,10 @@
 
 	ctx.AddFarVariationDependencies(ctx.Config().BuildOSCommonTarget.Variations(),
 		roboRuntimesTag, "robolectric-android-all-prebuilts")
+
+	for _, lib := range r.robolectricProperties.Jni_libs {
+		ctx.AddVariationDependencies(ctx.Config().BuildOSTarget.Variations(), jniLibTag, lib)
+	}
 }
 
 func (r *robolectricTest) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -267,6 +280,12 @@
 		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)
+		installDeps = append(installDeps, installJni)
+	}
+
 	r.installFile = ctx.InstallFile(installPath, ctx.ModuleName()+".jar", r.combinedJar, installDeps...)
 	android.SetProvider(ctx, testing.TestModuleProviderKey, testing.TestModuleProviderData{})
 }
diff --git a/java/robolectric_test.go b/java/robolectric_test.go
new file mode 100644
index 0000000..4775bac
--- /dev/null
+++ b/java/robolectric_test.go
@@ -0,0 +1,98 @@
+// 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 java
+
+import (
+	"runtime"
+	"testing"
+
+	"android/soong/android"
+)
+
+var prepareRobolectricRuntime = android.GroupFixturePreparers(
+	android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+		RegisterRobolectricBuildComponents(ctx)
+	}),
+	android.FixtureAddTextFile("robolectric/Android.bp", `
+	java_library {
+		name: "Robolectric_all-target_upstream",
+		srcs: ["Robo.java"]
+	}
+
+	java_library {
+		name: "mockito-robolectric-prebuilt",
+		srcs: ["Mockito.java"]
+	}
+
+	java_library {
+		name: "truth",
+		srcs: ["Truth.java"]
+	}
+
+	java_library {
+		name: "junitxml",
+		srcs: ["JUnitXml.java"]
+	}
+
+	java_library_host {
+		name: "robolectric-host-android_all",
+		srcs: ["Runtime.java"]
+	}
+
+	android_robolectric_runtimes {
+		name: "robolectric-android-all-prebuilts",
+		jars: ["android-all/Runtime.jar"],
+		lib: "robolectric-host-android_all",
+	}
+	`),
+)
+
+func TestRobolectricJniTest(t *testing.T) {
+	if runtime.GOOS != "linux" {
+		t.Skip("requires linux")
+	}
+
+	ctx := android.GroupFixturePreparers(
+		PrepareForIntegrationTestWithJava,
+		prepareRobolectricRuntime,
+	).RunTestWithBp(t, `
+	android_app {
+		name: "inst-target",
+		srcs: ["App.java"],
+		platform_apis: true,
+	}
+
+	cc_library_shared {
+		name: "jni-lib1",
+		host_supported: true,
+		srcs: ["jni.cpp"],
+	}
+
+	android_robolectric_test {
+		name: "robo-test",
+		instrumentation_for: "inst-target",
+		srcs: ["FooTest.java"],
+		jni_libs: [
+			"jni-lib1"
+		],
+	}
+	`)
+
+	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.Output(installPathPrefix + "/robo-test/lib64/jni-lib1.so")
+}
diff --git a/java/rro.go b/java/rro.go
index 72170fc..0fc6e1c 100644
--- a/java/rro.go
+++ b/java/rro.go
@@ -150,12 +150,13 @@
 		aaptLinkFlags = append(aaptLinkFlags,
 			"--rename-overlay-category "+*r.overridableProperties.Category)
 	}
+	aconfigTextFilePaths := getAconfigFilePaths(ctx)
 	r.aapt.buildActions(ctx,
 		aaptBuildActionOptions{
 			sdkContext:                     r,
 			enforceDefaultTargetSdkVersion: false,
 			extraLinkFlags:                 aaptLinkFlags,
-			aconfigTextFiles:               getAconfigFilePaths(ctx),
+			aconfigTextFiles:               aconfigTextFilePaths,
 		},
 	)
 
@@ -176,6 +177,10 @@
 	partition := rroPartition(ctx)
 	r.installDir = android.PathForModuleInPartitionInstall(ctx, partition, "overlay", String(r.properties.Theme))
 	ctx.InstallFile(r.installDir, r.outputFile.Base(), r.outputFile)
+
+	android.SetProvider(ctx, FlagsPackagesProvider, FlagsPackages{
+		AconfigTextFiles: aconfigTextFilePaths,
+	})
 }
 
 func (r *RuntimeResourceOverlay) SdkVersion(ctx android.EarlyModuleContext) android.SdkSpec {
diff --git a/java/rro_test.go b/java/rro_test.go
index d697ec6..742c839 100644
--- a/java/rro_test.go
+++ b/java/rro_test.go
@@ -421,6 +421,7 @@
 		aconfig_declarations {
 			name: "bar",
 			package: "com.example.package.bar",
+			container: "com.android.foo",
 			srcs: [
 				"bar.aconfig",
 			],
@@ -428,6 +429,7 @@
 		aconfig_declarations {
 			name: "baz",
 			package: "com.example.package.baz",
+			container: "com.android.foo",
 			srcs: [
 				"baz.aconfig",
 			],
diff --git a/java/sdk.go b/java/sdk.go
index d972c19..4ef4ee2 100644
--- a/java/sdk.go
+++ b/java/sdk.go
@@ -65,6 +65,12 @@
 		return JAVA_VERSION_9
 	} 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.
+		return JAVA_VERSION_21
 	} else {
 		return JAVA_VERSION_17
 	}
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 113071f..3931456 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -118,9 +118,6 @@
 	// The tag to use to depend on the stubs source module (if separate from the API module).
 	stubsSourceTag scopeDependencyTag
 
-	// The tag to use to depend on the API file generating module (if separate from the stubs source module).
-	apiFileTag scopeDependencyTag
-
 	// The tag to use to depend on the stubs source and API module.
 	stubsSourceAndApiTag scopeDependencyTag
 
@@ -195,11 +192,6 @@
 		apiScope:         scope,
 		depInfoExtractor: (*scopePaths).extractStubsSourceInfoFromDep,
 	}
-	scope.apiFileTag = scopeDependencyTag{
-		name:             name + "-api",
-		apiScope:         scope,
-		depInfoExtractor: (*scopePaths).extractApiInfoFromDep,
-	}
 	scope.stubsSourceAndApiTag = scopeDependencyTag{
 		name:             name + "-stubs-source-and-api",
 		apiScope:         scope,
@@ -324,6 +316,16 @@
 	return ret
 }
 
+func (scopes apiScopes) ConvertStubsLibraryExportableToEverything(name string) string {
+	for _, scope := range scopes {
+		if strings.HasSuffix(name, scope.exportableStubsLibraryModuleNameSuffix()) {
+			return strings.TrimSuffix(name, scope.exportableStubsLibraryModuleNameSuffix()) +
+				scope.stubsLibraryModuleNameSuffix()
+		}
+	}
+	return name
+}
+
 var (
 	scopeByName    = make(map[string]*apiScope)
 	allScopeNames  []string
@@ -418,7 +420,7 @@
 		},
 		kind: android.SdkSystemServer,
 	})
-	allApiScopes = apiScopes{
+	AllApiScopes = apiScopes{
 		apiScopePublic,
 		apiScopeSystem,
 		apiScopeTest,
@@ -536,9 +538,6 @@
 	// of the API.
 	Api_packages []string
 
-	// list of package names that must be hidden from the API
-	Hidden_api_packages []string
-
 	// the relative path to the directory containing the api specification files.
 	// Defaults to "api".
 	Api_dir *string
@@ -794,12 +793,6 @@
 	return combinedError
 }
 
-func (paths *scopePaths) extractApiInfoFromDep(ctx android.ModuleContext, dep android.Module) error {
-	return paths.treatDepAsApiStubsProvider(dep, func(provider ApiStubsProvider) error {
-		return paths.extractApiInfoFromApiStubsProvider(provider, Everything)
-	})
-}
-
 func (paths *scopePaths) extractStubsSourceInfoFromApiStubsProviders(provider ApiStubsSrcProvider, stubsType StubsType) error {
 	stubsSrcJar, err := provider.StubsSrcJar(stubsType)
 	if err == nil {
@@ -809,22 +802,23 @@
 }
 
 func (paths *scopePaths) extractStubsSourceInfoFromDep(ctx android.ModuleContext, dep android.Module) error {
+	stubsType := Everything
+	if ctx.Config().ReleaseHiddenApiExportableStubs() {
+		stubsType = Exportable
+	}
 	return paths.treatDepAsApiStubsSrcProvider(dep, func(provider ApiStubsSrcProvider) error {
-		return paths.extractStubsSourceInfoFromApiStubsProviders(provider, Everything)
+		return paths.extractStubsSourceInfoFromApiStubsProviders(provider, stubsType)
 	})
 }
 
 func (paths *scopePaths) extractStubsSourceAndApiInfoFromApiStubsProvider(ctx android.ModuleContext, dep android.Module) error {
+	stubsType := Everything
 	if ctx.Config().ReleaseHiddenApiExportableStubs() {
-		return paths.treatDepAsApiStubsProvider(dep, func(provider ApiStubsProvider) error {
-			extractApiInfoErr := paths.extractApiInfoFromApiStubsProvider(provider, Exportable)
-			extractStubsSourceInfoErr := paths.extractStubsSourceInfoFromApiStubsProviders(provider, Exportable)
-			return errors.Join(extractApiInfoErr, extractStubsSourceInfoErr)
-		})
+		stubsType = Exportable
 	}
 	return paths.treatDepAsApiStubsProvider(dep, func(provider ApiStubsProvider) error {
-		extractApiInfoErr := paths.extractApiInfoFromApiStubsProvider(provider, Everything)
-		extractStubsSourceInfoErr := paths.extractStubsSourceInfoFromApiStubsProviders(provider, Everything)
+		extractApiInfoErr := paths.extractApiInfoFromApiStubsProvider(provider, stubsType)
+		extractStubsSourceInfoErr := paths.extractStubsSourceInfoFromApiStubsProviders(provider, stubsType)
 		return errors.Join(extractApiInfoErr, extractStubsSourceInfoErr)
 	})
 }
@@ -950,6 +944,10 @@
 	// Path to the header jars of the implementation library
 	// 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
 }
 
 func (c *commonToSdkLibraryAndImport) initCommon(module commonSdkLibraryAndImportModule) {
@@ -996,6 +994,10 @@
 	c.doctagPaths = android.PathsForModuleSrc(ctx, c.commonSdkLibraryProperties.Doctag_files)
 }
 
+func (c *commonToSdkLibraryAndImport) getImplLibraryModule() *Library {
+	return c.implLibraryModule
+}
+
 // Module name of the runtime implementation library
 func (c *commonToSdkLibraryAndImport) implLibraryModuleName() string {
 	return c.module.RootLibraryName() + ".impl"
@@ -1081,57 +1083,26 @@
 	return regexp.MustCompile(fmt.Sprintf(`^\.(%s)\.(%s)$`, scopesRegexp, componentsRegexp))
 }()
 
-// For OutputFileProducer interface
-//
-// .<scope>.<component name>, for all ComponentNames (for example: .public.removed-api.txt)
-func (c *commonToSdkLibraryAndImport) commonOutputFiles(tag string) (android.Paths, error) {
-	if groups := tagSplitter.FindStringSubmatch(tag); groups != nil {
-		scopeName := groups[1]
-		component := groups[2]
-
-		if scope, ok := scopeByName[scopeName]; ok {
-			paths := c.findScopePaths(scope)
-			if paths == nil {
-				return nil, fmt.Errorf("%q does not provide api scope %s", c.module.RootLibraryName(), scopeName)
-			}
-
-			switch component {
-			case stubsSourceComponentName:
-				if paths.stubsSrcJar.Valid() {
-					return android.Paths{paths.stubsSrcJar.Path()}, nil
-				}
-
-			case apiTxtComponentName:
-				if paths.currentApiFilePath.Valid() {
-					return android.Paths{paths.currentApiFilePath.Path()}, nil
-				}
-
-			case removedApiTxtComponentName:
-				if paths.removedApiFilePath.Valid() {
-					return android.Paths{paths.removedApiFilePath.Path()}, nil
-				}
-
-			case annotationsComponentName:
-				if paths.annotationsZip.Valid() {
-					return android.Paths{paths.annotationsZip.Path()}, nil
-				}
-			}
-
-			return nil, fmt.Errorf("%s not available for api scope %s", component, scopeName)
-		} else {
-			return nil, fmt.Errorf("unknown scope %s in %s", scope, tag)
+func (module *commonToSdkLibraryAndImport) setOutputFiles(ctx android.ModuleContext) {
+	if module.doctagPaths != nil {
+		ctx.SetOutputFiles(module.doctagPaths, ".doctags")
+	}
+	for _, scopeName := range android.SortedKeys(scopeByName) {
+		paths := module.findScopePaths(scopeByName[scopeName])
+		if paths == nil {
+			continue
 		}
-
-	} else {
-		switch tag {
-		case ".doctags":
-			if c.doctagPaths != nil {
-				return c.doctagPaths, nil
-			} else {
-				return nil, fmt.Errorf("no doctag_files specified on %s", c.module.RootLibraryName())
+		componentToOutput := map[string]android.OptionalPath{
+			stubsSourceComponentName:   paths.stubsSrcJar,
+			apiTxtComponentName:        paths.currentApiFilePath,
+			removedApiTxtComponentName: paths.removedApiFilePath,
+			annotationsComponentName:   paths.annotationsZip,
+		}
+		for _, component := range android.SortedKeys(componentToOutput) {
+			if componentToOutput[component].Valid() {
+				ctx.SetOutputFiles(android.Paths{componentToOutput[component].Path()}, "."+scopeName+"."+component)
 			}
 		}
-		return nil, nil
 	}
 }
 
@@ -1196,7 +1167,7 @@
 	paths := c.findClosestScopePath(apiScope)
 	if paths == nil {
 		var scopes []string
-		for _, s := range allApiScopes {
+		for _, s := range AllApiScopes {
 			if c.findScopePaths(s) != nil {
 				scopes = append(scopes, s.name)
 			}
@@ -1372,6 +1343,8 @@
 
 	// sharedLibrary returns true if this can be used as a shared library.
 	sharedLibrary() bool
+
+	getImplLibraryModule() *Library
 }
 
 type SdkLibrary struct {
@@ -1383,6 +1356,8 @@
 	scopeToProperties map[*apiScope]*ApiScopeProperties
 
 	commonToSdkLibraryAndImport
+
+	builtInstalledForApex []dexpreopterInstall
 }
 
 var _ SdkLibraryDependency = (*SdkLibrary)(nil)
@@ -1391,11 +1366,25 @@
 	return module.sdkLibraryProperties.Generate_system_and_test_apis
 }
 
+func (module *SdkLibrary) DexJarBuildPath(ctx android.ModuleErrorfContext) OptionalDexJarPath {
+	if module.implLibraryModule != nil {
+		return module.implLibraryModule.DexJarBuildPath(ctx)
+	}
+	return makeUnsetDexJarPath()
+}
+
+func (module *SdkLibrary) DexJarInstallPath() android.Path {
+	if module.implLibraryModule != nil {
+		return module.implLibraryModule.DexJarInstallPath()
+	}
+	return nil
+}
+
 func (module *SdkLibrary) getGeneratedApiScopes(ctx android.EarlyModuleContext) apiScopes {
 	// Check to see if any scopes have been explicitly enabled. If any have then all
 	// must be.
 	anyScopesExplicitlyEnabled := false
-	for _, scope := range allApiScopes {
+	for _, scope := range AllApiScopes {
 		scopeProperties := module.scopeToProperties[scope]
 		if scopeProperties.Enabled != nil {
 			anyScopesExplicitlyEnabled = true
@@ -1405,7 +1394,7 @@
 
 	var generatedScopes apiScopes
 	enabledScopes := make(map[*apiScope]struct{})
-	for _, scope := range allApiScopes {
+	for _, scope := range AllApiScopes {
 		scopeProperties := module.scopeToProperties[scope]
 		// If any scopes are explicitly enabled then ignore the legacy enabled status.
 		// This is to ensure that any new usages of this module type do not rely on legacy
@@ -1425,7 +1414,7 @@
 
 	// Now check to make sure that any scope that is extended by an enabled scope is also
 	// enabled.
-	for _, scope := range allApiScopes {
+	for _, scope := range AllApiScopes {
 		if _, ok := enabledScopes[scope]; ok {
 			extends := scope.extends
 			if extends != nil {
@@ -1442,6 +1431,10 @@
 var _ android.ModuleWithMinSdkVersionCheck = (*SdkLibrary)(nil)
 
 func (module *SdkLibrary) CheckMinSdkVersion(ctx android.ModuleContext) {
+	CheckMinSdkVersion(ctx, &module.Library)
+}
+
+func CheckMinSdkVersion(ctx android.ModuleContext, module *Library) {
 	android.CheckMinSdkVersion(ctx, module.MinSdkVersion(ctx), func(c android.ModuleContext, do android.PayloadDepsCallback) {
 		ctx.WalkDeps(func(child android.Module, parent android.Module) bool {
 			isExternal := !module.depIsInSameApex(ctx, child)
@@ -1474,6 +1467,18 @@
 
 var implLibraryTag = sdkLibraryComponentTag{name: "impl-library"}
 
+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"
+}
+
 // Add the dependencies on the child modules in the component deps mutator.
 func (module *SdkLibrary) ComponentDepsMutator(ctx android.BottomUpMutatorContext) {
 	for _, apiScope := range module.getGeneratedApiScopes(ctx) {
@@ -1511,6 +1516,13 @@
 
 // Add other dependencies as normal.
 func (module *SdkLibrary) DepsMutator(ctx android.BottomUpMutatorContext) {
+	// If the module does not create an implementation library or defaults to stubs,
+	// mark the top level sdk library as stubs module as the module will provide stubs via
+	// "magic" when listed as a dependency in the Android.bp files.
+	notCreateImplLib := proptools.Bool(module.sdkLibraryProperties.Api_only)
+	preferStubs := proptools.Bool(module.sdkLibraryProperties.Default_to_stubs)
+	module.properties.Is_stubs_module = proptools.BoolPtr(notCreateImplLib || preferStubs)
+
 	var missingApiModules []string
 	for _, apiScope := range module.getGeneratedApiScopes(ctx) {
 		if apiScope.unstable {
@@ -1538,24 +1550,6 @@
 		m += "Please see the documentation of the prebuilt_apis module type (and a usage example in prebuilts/sdk) for a convenient way to generate these."
 		ctx.ModuleErrorf(m)
 	}
-	if module.requiresRuntimeImplementationLibrary() {
-		// Only add the deps for the library if it is actually going to be built.
-		module.Library.deps(ctx)
-	}
-}
-
-func (module *SdkLibrary) OutputFiles(tag string) (android.Paths, error) {
-	paths, err := module.commonOutputFiles(tag)
-	if paths != nil || err != nil {
-		return paths, err
-	}
-	if module.requiresRuntimeImplementationLibrary() {
-		return module.Library.OutputFiles(tag)
-	}
-	if tag == "" {
-		return nil, nil
-	}
-	return nil, fmt.Errorf("unsupported module reference tag %q", tag)
 }
 
 func (module *SdkLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -1565,18 +1559,12 @@
 		// TODO (b/331665856): Implement a principled solution for this.
 		module.HideFromMake()
 	}
-	if proptools.String(module.deviceProperties.Min_sdk_version) != "" {
-		module.CheckMinSdkVersion(ctx)
-	}
 
 	module.generateCommonBuildActions(ctx)
 
-	// Only build an implementation library if required.
-	if module.requiresRuntimeImplementationLibrary() {
-		// stubsLinkType must be set before calling Library.GenerateAndroidBuildActions
-		module.Library.stubsLinkType = Unknown
-		module.Library.GenerateAndroidBuildActions(ctx)
-	}
+	module.stem = proptools.StringDefault(module.overridableProperties.Stem, ctx.ModuleName())
+
+	module.provideHiddenAPIPropertyInfo(ctx)
 
 	// Collate the components exported by this module. All scope specific modules are exported but
 	// the impl and xml component modules are not.
@@ -1603,10 +1591,49 @@
 		if tag == implLibraryTag {
 			if dep, ok := android.OtherModuleProvider(ctx, to, JavaInfoProvider); ok {
 				module.implLibraryHeaderJars = append(module.implLibraryHeaderJars, dep.HeaderJars...)
+				module.implLibraryModule = to.(*Library)
+				android.SetProvider(ctx, JavaInfoProvider, dep)
 			}
 		}
 	})
 
+	apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
+	if !apexInfo.IsForPlatform() {
+		module.hideApexVariantFromMake = true
+	}
+
+	if module.implLibraryModule != 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.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
+
+		// 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.linter.outputs.depSets = module.implLibraryModule.LintDepSets()
+
+		if !module.Host() {
+			module.hostdexInstallFile = module.implLibraryModule.hostdexInstallFile
+		}
+
+		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.
 	exportedComponentInfo := android.ExportedComponentsInfo{Components: android.SortedKeys(exportedComponents)}
 	android.SetProvider(ctx, android.ExportedComponentsInfoProvider, exportedComponentInfo)
@@ -1634,6 +1661,14 @@
 		}
 	}
 	android.SetProvider(ctx, android.AdditionalSdkInfoProvider, android.AdditionalSdkInfo{additionalSdkInfo})
+	module.setOutputFiles(ctx)
+	if module.requiresRuntimeImplementationLibrary() && module.implLibraryModule != nil {
+		setOutputFiles(ctx, module.implLibraryModule.Module)
+	}
+}
+
+func (module *SdkLibrary) BuiltInstalledForApex() []dexpreopterInstall {
+	return module.builtInstalledForApex
 }
 
 func (module *SdkLibrary) AndroidMkEntries() []android.AndroidMkEntries {
@@ -1641,8 +1676,9 @@
 		return nil
 	}
 	entriesList := module.Library.AndroidMkEntries()
+	entries := &entriesList[0]
+	entries.Required = append(entries.Required, module.implLibraryModuleName())
 	if module.sharedLibrary() {
-		entries := &entriesList[0]
 		entries.Required = append(entries.Required, module.xmlPermissionsModuleName())
 	}
 	return entriesList
@@ -1759,24 +1795,22 @@
 	props := struct {
 		Name           *string
 		Visibility     []string
-		Instrument     bool
 		Libs           []string
 		Static_libs    []string
 		Apex_available []string
+		Stem           *string
 	}{
 		Name:       proptools.StringPtr(module.implLibraryModuleName()),
 		Visibility: visibility,
-		// Set the instrument property to ensure it is instrumented when instrumentation is required.
-		Instrument: true,
-		// Set the impl_only libs. Note that the module's "Libs" get appended as well, via the
-		// addition of &module.properties below.
-		Libs: module.sdkLibraryProperties.Impl_only_libs,
-		// Set the impl_only static libs. Note that the module's "static_libs" get appended as well, via the
-		// addition of &module.properties below.
-		Static_libs: module.sdkLibraryProperties.Impl_only_static_libs,
+
+		Libs: append(module.properties.Libs, module.sdkLibraryProperties.Impl_only_libs...),
+
+		Static_libs: append(module.properties.Static_libs, module.sdkLibraryProperties.Impl_only_static_libs...),
 		// Pass the apex_available settings down so that the impl library can be statically
 		// embedded within a library that is added to an APEX. Needed for updatable-media.
 		Apex_available: module.ApexAvailable(),
+
+		Stem: proptools.StringPtr(module.Name()),
 	}
 
 	properties := []interface{}{
@@ -1786,6 +1820,7 @@
 		&module.dexProperties,
 		&module.dexpreoptProperties,
 		&module.linter.properties,
+		&module.overridableProperties,
 		&props,
 		module.sdkComponentPropertiesForChildLibrary(),
 	}
@@ -1935,10 +1970,6 @@
 	if len(module.sdkLibraryProperties.Api_packages) != 0 {
 		droidstubsArgs = append(droidstubsArgs, "--stub-packages "+strings.Join(module.sdkLibraryProperties.Api_packages, ":"))
 	}
-	if len(module.sdkLibraryProperties.Hidden_api_packages) != 0 {
-		droidstubsArgs = append(droidstubsArgs,
-			android.JoinWithPrefix(module.sdkLibraryProperties.Hidden_api_packages, " --hide-package "))
-	}
 	droidstubsArgs = append(droidstubsArgs, module.sdkLibraryProperties.Droiddoc_options...)
 	disabledWarnings := []string{"HiddenSuperclass"}
 	if proptools.BoolDefault(module.sdkLibraryProperties.Api_lint.Legacy_errors_allowed, true) {
@@ -2167,6 +2198,9 @@
 	if depTag == xmlPermissionsFileTag {
 		return true
 	}
+	if dep.Name() == module.implLibraryModuleName() {
+		return true
+	}
 	return module.Library.DepIsInSameApex(mctx, dep)
 }
 
@@ -2294,7 +2328,7 @@
 // once for public API level and once for system API level
 func (module *SdkLibrary) CreateInternalModules(mctx android.DefaultableHookContext) {
 	// If the module has been disabled then don't create any child modules.
-	if !module.Enabled() {
+	if !module.Enabled(mctx) {
 		return
 	}
 
@@ -2503,7 +2537,7 @@
 
 	// Initialize the map from scope to scope specific properties.
 	scopeToProperties := make(map[*apiScope]*ApiScopeProperties)
-	for _, scope := range allApiScopes {
+	for _, scope := range AllApiScopes {
 		scopeToProperties[scope] = scope.scopeSpecificProperties(module)
 	}
 	module.scopeToProperties = scopeToProperties
@@ -2592,10 +2626,6 @@
 
 	commonToSdkLibraryAndImport
 
-	// 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 xml permissions module created by the source module.
 	// Is nil if the source module does not exist.
 	xmlPermissionsFileModule *sdkLibraryXml
@@ -2624,7 +2654,7 @@
 // Dynamically create a structure type for each apiscope in allApiScopes.
 func createAllScopePropertiesStructType() reflect.Type {
 	var fields []reflect.StructField
-	for _, apiScope := range allApiScopes {
+	for _, apiScope := range AllApiScopes {
 		field := reflect.StructField{
 			Name: apiScope.fieldName,
 			Type: reflect.TypeOf(sdkLibraryScopeProperties{}),
@@ -2642,7 +2672,7 @@
 	allScopePropertiesStruct := allScopePropertiesPtr.Elem()
 	scopeProperties := make(map[*apiScope]*sdkLibraryScopeProperties)
 
-	for _, apiScope := range allApiScopes {
+	for _, apiScope := range AllApiScopes {
 		field := allScopePropertiesStruct.FieldByName(apiScope.fieldName)
 		scopeProperties[apiScope] = field.Addr().Interface().(*sdkLibraryScopeProperties)
 	}
@@ -2866,18 +2896,6 @@
 
 var _ hiddenAPIModule = (*SdkLibraryImport)(nil)
 
-func (module *SdkLibraryImport) OutputFiles(tag string) (android.Paths, error) {
-	paths, err := module.commonOutputFiles(tag)
-	if paths != nil || err != nil {
-		return paths, err
-	}
-	if module.implLibraryModule != nil {
-		return module.implLibraryModule.OutputFiles(tag)
-	} else {
-		return nil, nil
-	}
-}
-
 func (module *SdkLibraryImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	module.generateCommonBuildActions(ctx)
 
@@ -2960,6 +2978,11 @@
 			}
 		}
 	}
+
+	module.setOutputFiles(ctx)
+	if module.implLibraryModule != nil {
+		setOutputFiles(ctx, module.implLibraryModule.Module)
+	}
 }
 
 func (module *SdkLibraryImport) sdkJars(ctx android.BaseModuleContext, sdkVersion android.SdkSpec, headerJars bool) android.Paths {
@@ -3169,11 +3192,6 @@
 	return "permissions"
 }
 
-// from android.PrebuiltEtcModule
-func (module *sdkLibraryXml) OutputFiles(tag string) (android.Paths, error) {
-	return android.OutputPaths{module.outputFilePath}.Paths(), nil
-}
-
 var _ etc.PrebuiltEtcModule = (*sdkLibraryXml)(nil)
 
 // from android.ApexModule
@@ -3201,7 +3219,7 @@
 		// TODO(b/146468504): ApexVariationName() is only a soong module name, not apex name.
 		// In most cases, this works fine. But when apex_name is set or override_apex is used
 		// this can be wrong.
-		return fmt.Sprintf("/apex/%s/javalib/%s.jar", apexInfo.ApexVariationName, implName)
+		return fmt.Sprintf("/apex/%s/javalib/%s.jar", apexInfo.BaseApexName, implName)
 	}
 	partition := "system"
 	if module.SocSpecific() {
@@ -3317,6 +3335,8 @@
 
 	module.installDirPath = android.PathForModuleInstall(ctx, "etc", module.SubDir())
 	ctx.PackageFile(module.installDirPath, libName+".xml", module.outputFilePath)
+
+	ctx.SetOutputFiles(android.OutputPaths{module.outputFilePath}.Paths(), "")
 }
 
 func (module *sdkLibraryXml) AndroidMkEntries() []android.AndroidMkEntries {
@@ -3524,7 +3544,7 @@
 	s.Stem = sdk.distStem()
 
 	s.Scopes = make(map[*apiScope]*scopeProperties)
-	for _, apiScope := range allApiScopes {
+	for _, apiScope := range AllApiScopes {
 		paths := sdk.findScopePaths(apiScope)
 		if paths == nil {
 			continue
@@ -3560,7 +3580,8 @@
 	s.Min_device_sdk = sdk.commonSdkLibraryProperties.Min_device_sdk
 	s.Max_device_sdk = sdk.commonSdkLibraryProperties.Max_device_sdk
 
-	if sdk.dexpreopter.dexpreoptProperties.Dex_preopt_result.Profile_guided {
+	implLibrary := sdk.getImplLibraryModule()
+	if implLibrary != nil && implLibrary.dexpreopter.dexpreoptProperties.Dex_preopt_result.Profile_guided {
 		s.DexPreoptProfileGuided = proptools.BoolPtr(true)
 	}
 }
@@ -3585,7 +3606,7 @@
 
 	stem := s.Stem
 
-	for _, apiScope := range allApiScopes {
+	for _, apiScope := range AllApiScopes {
 		if properties, ok := s.Scopes[apiScope]; ok {
 			scopeSet := propertySet.AddPropertySet(apiScope.propertyName)
 
diff --git a/java/sdk_library_test.go b/java/sdk_library_test.go
index 0f163e6..a8a1494 100644
--- a/java/sdk_library_test.go
+++ b/java/sdk_library_test.go
@@ -186,13 +186,13 @@
 	// test if quuz have created the api_contribution module
 	result.ModuleForTests(apiScopePublic.stubsSourceModuleName("quuz")+".api.contribution", "")
 
-	fooDexJar := result.ModuleForTests("foo", "android_common").Rule("d8")
-	// tests if kotlinc generated files are NOT excluded from output of foo.
-	android.AssertStringDoesNotContain(t, "foo dex", fooDexJar.BuildParams.Args["mergeZipsFlags"], "-stripFile META-INF/*.kotlin_module")
+	fooImplDexJar := result.ModuleForTests("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")
 
-	barDexJar := result.ModuleForTests("bar", "android_common").Rule("d8")
-	// tests if kotlinc generated files are excluded from output of bar.
-	android.AssertStringDoesContain(t, "bar dex", barDexJar.BuildParams.Args["mergeZipsFlags"], "-stripFile META-INF/*.kotlin_module")
+	barImplDexJar := result.ModuleForTests("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) {
@@ -492,7 +492,7 @@
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("foo"),
 	).
-		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "bar" variant "android_common": path dependency ":foo{.public.annotations.zip}": annotations.zip not available for api scope public`)).
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "bar" variant "android_common": unsupported module reference tag ".public.annotations.zip"`)).
 		RunTestWithBp(t, `
 		java_sdk_library {
 			name: "foo",
@@ -517,7 +517,7 @@
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("foo"),
 	).
-		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`"foo" does not provide api scope system`)).
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "bar" variant "android_common": unsupported module reference tag ".system.stubs.source"`)).
 		RunTestWithBp(t, `
 		java_sdk_library {
 			name: "foo",
@@ -606,7 +606,7 @@
 
 	t.Run("stubs.source", func(t *testing.T) {
 		prepareForJavaTest.
-			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`stubs.source not available for api scope public`)).
+			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "foo" is not a SourceFileProducer or having valid output file for tag ".public.stubs.source"`)).
 			RunTestWithBp(t, bp+`
 				java_library {
 					name: "bar",
@@ -621,7 +621,7 @@
 
 	t.Run("api.txt", func(t *testing.T) {
 		prepareForJavaTest.
-			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`api.txt not available for api scope public`)).
+			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "foo" is not a SourceFileProducer or having valid output file for tag ".public.api.txt"`)).
 			RunTestWithBp(t, bp+`
 				java_library {
 					name: "bar",
@@ -635,7 +635,7 @@
 
 	t.Run("removed-api.txt", func(t *testing.T) {
 		prepareForJavaTest.
-			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`removed-api.txt not available for api scope public`)).
+			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "foo" is not a SourceFileProducer or having valid output file for tag ".public.removed-api.txt"`)).
 			RunTestWithBp(t, bp+`
 				java_library {
 					name: "bar",
@@ -1457,11 +1457,11 @@
 	preparer.RunTestWithBp(t, `
 		java_sdk_library {
 			name: "sdklib",
-            srcs: ["a.java"],
-            static_libs: ["util"],
-            min_sdk_version: "30",
+			srcs: ["a.java"],
+			static_libs: ["util"],
+			min_sdk_version: "30",
 			unsafe_ignore_missing_latest_api: true,
-        }
+		}
 
 		java_library {
 			name: "util",
@@ -1715,6 +1715,7 @@
 		aconfig_declarations {
 			name: "bar",
 			package: "com.example.package",
+			container: "com.android.foo",
 			srcs: [
 				"bar.aconfig",
 			],
diff --git a/java/systemserver_classpath_fragment.go b/java/systemserver_classpath_fragment.go
index b291e70..bad2cf1 100644
--- a/java/systemserver_classpath_fragment.go
+++ b/java/systemserver_classpath_fragment.go
@@ -69,6 +69,7 @@
 	configuredJars = configuredJars.AppendList(&standaloneConfiguredJars)
 	classpathJars = append(classpathJars, standaloneClasspathJars...)
 	p.classpathFragmentBase().generateClasspathProtoBuildActions(ctx, configuredJars, classpathJars)
+	p.classpathFragmentBase().installClasspathProto(ctx)
 }
 
 func (p *platformSystemServerClasspathModule) configuredJars(ctx android.ModuleContext) android.ConfiguredJarList {
diff --git a/java/testing.go b/java/testing.go
index 5ae326d..7a42e4c 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -52,6 +52,8 @@
 	android.MockFS{
 		// Needed for linter used by java_library.
 		"build/soong/java/lint_defaults.txt": nil,
+		// Needed for java components that invoke Metalava.
+		"build/soong/java/metalava/Android.bp": []byte(`filegroup {name: "metalava-config-files"}`),
 		// Needed for apps that do not provide their own.
 		"build/make/target/product/security": nil,
 		// Required to generate Java used-by API coverage
diff --git a/linkerconfig/linkerconfig.go b/linkerconfig/linkerconfig.go
index 98aa408..87c0814 100644
--- a/linkerconfig/linkerconfig.go
+++ b/linkerconfig/linkerconfig.go
@@ -15,7 +15,6 @@
 package linkerconfig
 
 import (
-	"fmt"
 	"sort"
 	"strings"
 
@@ -73,17 +72,6 @@
 	return l.outputFilePath
 }
 
-var _ android.OutputFileProducer = (*linkerConfig)(nil)
-
-func (l *linkerConfig) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		return android.Paths{l.outputFilePath}, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
 func (l *linkerConfig) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	input := android.PathForModuleSrc(ctx, android.String(l.properties.Src))
 	output := android.PathForModuleOut(ctx, "linker.config.pb").OutputPath
@@ -98,6 +86,8 @@
 		l.SkipInstall()
 	}
 	ctx.InstallFile(l.installDirPath, l.outputFilePath.Base(), l.outputFilePath)
+
+	ctx.SetOutputFiles(android.Paths{l.outputFilePath}, "")
 }
 
 func BuildLinkerConfig(ctx android.ModuleContext, builder *android.RuleBuilder,
@@ -114,7 +104,7 @@
 	// 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) {
+		if c, ok := m.(*cc.Module); ok && (cc.IsStubTarget(c) || c.HasLlndkStubs()) {
 			for _, ps := range c.PackagingSpecs() {
 				provideLibs = append(provideLibs, ps.FileName())
 			}
diff --git a/multitree/api_surface.go b/multitree/api_surface.go
index f739a24..0f605d8 100644
--- a/multitree/api_surface.go
+++ b/multitree/api_surface.go
@@ -16,8 +16,6 @@
 
 import (
 	"android/soong/android"
-	"fmt"
-
 	"github.com/google/blueprint"
 )
 
@@ -40,7 +38,6 @@
 	ExportableModuleBase
 	properties apiSurfaceProperties
 
-	allOutputs    android.Paths
 	taggedOutputs map[string]android.Paths
 }
 
@@ -86,15 +83,9 @@
 		Inputs: allOutputs,
 	})
 
-	surface.allOutputs = allOutputs
 	surface.taggedOutputs = contributionFiles
-}
 
-func (surface *ApiSurface) OutputFiles(tag string) (android.Paths, error) {
-	if tag != "" {
-		return nil, fmt.Errorf("unknown tag: %q", tag)
-	}
-	return surface.allOutputs, nil
+	ctx.SetOutputFiles(allOutputs, "")
 }
 
 func (surface *ApiSurface) TaggedOutputs() map[string]android.Paths {
@@ -105,7 +96,6 @@
 	return true
 }
 
-var _ android.OutputFileProducer = (*ApiSurface)(nil)
 var _ Exportable = (*ApiSurface)(nil)
 
 type ApiContribution interface {
diff --git a/multitree/export.go b/multitree/export.go
index aecade5..8be8f70 100644
--- a/multitree/export.go
+++ b/multitree/export.go
@@ -50,7 +50,6 @@
 
 type ExportableModule interface {
 	android.Module
-	android.OutputFileProducer
 	Exportable
 }
 
diff --git a/phony/phony.go b/phony/phony.go
index 5469238..b421176 100644
--- a/phony/phony.go
+++ b/phony/phony.go
@@ -49,7 +49,7 @@
 }
 
 func (p *phony) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	p.requiredModuleNames = ctx.RequiredModuleNames()
+	p.requiredModuleNames = ctx.RequiredModuleNames(ctx)
 	p.hostRequiredModuleNames = ctx.HostRequiredModuleNames()
 	p.targetRequiredModuleNames = ctx.TargetRequiredModuleNames()
 }
diff --git a/provenance/provenance_singleton.go b/provenance/provenance_singleton.go
index 97345af..679632c 100644
--- a/provenance/provenance_singleton.go
+++ b/provenance/provenance_singleton.go
@@ -18,6 +18,7 @@
 
 import (
 	"android/soong/android"
+
 	"github.com/google/blueprint"
 )
 
@@ -68,6 +69,15 @@
 
 func (p *provenanceInfoSingleton) GenerateBuildActions(context android.SingletonContext) {
 	allMetaDataFiles := make([]android.Path, 0)
+	moduleFilter := func(module android.Module) bool {
+		if !module.Enabled(context) || module.IsSkipInstall() {
+			return false
+		}
+		if p, ok := module.(ProvenanceMetadata); ok {
+			return p.ProvenanceMetaDataFile().String() != ""
+		}
+		return false
+	}
 	context.VisitAllModulesIf(moduleFilter, func(module android.Module) {
 		if p, ok := module.(ProvenanceMetadata); ok {
 			allMetaDataFiles = append(allMetaDataFiles, p.ProvenanceMetaDataFile())
@@ -91,16 +101,6 @@
 	context.Phony("droidcore", android.PathForPhony(context, "provenance_metadata"))
 }
 
-func moduleFilter(module android.Module) bool {
-	if !module.Enabled() || module.IsSkipInstall() {
-		return false
-	}
-	if p, ok := module.(ProvenanceMetadata); ok {
-		return p.ProvenanceMetaDataFile().String() != ""
-	}
-	return false
-}
-
 func GenerateArtifactProvenanceMetaData(ctx android.ModuleContext, artifactPath android.Path, installedFile android.InstallPath) android.OutputPath {
 	onDevicePathOfInstalledFile := android.InstallPathToOnDevicePath(ctx, installedFile)
 	artifactMetaDataFile := android.PathForIntermediates(ctx, "provenance_metadata", ctx.ModuleDir(), ctx.ModuleName(), "provenance_metadata.textproto")
diff --git a/provenance/tools/Android.bp b/provenance/tools/Android.bp
index 0eddd76..b42e543 100644
--- a/provenance/tools/Android.bp
+++ b/provenance/tools/Android.bp
@@ -23,11 +23,6 @@
     srcs: [
         "gen_provenance_metadata.py",
     ],
-    version: {
-        py3: {
-            embedded_launcher: true,
-        },
-    },
     libs: [
         "provenance_metadata_proto",
         "libprotobuf-python",
diff --git a/python/binary.go b/python/binary.go
index c84eeee..5f60761 100644
--- a/python/binary.go
+++ b/python/binary.go
@@ -71,9 +71,6 @@
 	installedDest android.Path
 
 	androidMkSharedLibs []string
-
-	// Aconfig files for all transitive deps.  Also exposed via TransitiveDeclarationsInfo
-	mergedAconfigFiles map[string]android.Paths
 }
 
 var _ android.AndroidMkEntriesProvider = (*PythonBinaryModule)(nil)
@@ -106,7 +103,7 @@
 	p.buildBinary(ctx)
 	p.installedDest = ctx.InstallFile(installDir(ctx, "bin", "", ""),
 		p.installSource.Base(), p.installSource)
-	android.CollectDependencyAconfigFiles(ctx, &p.mergedAconfigFiles)
+	ctx.SetOutputFiles(android.Paths{p.installSource}, "")
 }
 
 func (p *PythonBinaryModule) buildBinary(ctx android.ModuleContext) {
@@ -170,7 +167,6 @@
 			entries.SetString("LOCAL_MODULE_STEM", stem)
 			entries.AddStrings("LOCAL_SHARED_LIBRARIES", p.androidMkSharedLibs...)
 			entries.SetBool("LOCAL_CHECK_ELF_FILES", false)
-			android.SetAconfigFileMkEntries(&p.ModuleBase, entries, p.mergedAconfigFiles)
 		})
 
 	return []android.AndroidMkEntries{entries}
@@ -192,16 +188,6 @@
 	return android.OptionalPathForPath(p.installedDest)
 }
 
-// OutputFiles returns output files based on given tag, returns an error if tag is unsupported.
-func (p *PythonBinaryModule) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		return android.Paths{p.installSource}, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
 func (p *PythonBinaryModule) isEmbeddedLauncherEnabled() bool {
 	return BoolDefault(p.properties.Embedded_launcher, true)
 }
diff --git a/python/python.go b/python/python.go
index e14fdf3..8726f02 100644
--- a/python/python.go
+++ b/python/python.go
@@ -38,7 +38,7 @@
 
 // Exported to support other packages using Python modules in tests.
 func RegisterPythonPreDepsMutators(ctx android.RegisterMutatorsContext) {
-	ctx.BottomUp("python_version", versionSplitMutator()).Parallel()
+	ctx.Transition("python_version", &versionSplitTransitionMutator{})
 }
 
 // the version-specific properties that apply to python modules.
@@ -245,7 +245,6 @@
 	protoExt                 = ".proto"
 	pyVersion2               = "PY2"
 	pyVersion3               = "PY3"
-	pyVersion2And3           = "PY2ANDPY3"
 	internalPath             = "internal"
 )
 
@@ -253,46 +252,68 @@
 	getBaseProperties() *BaseProperties
 }
 
-// versionSplitMutator creates version variants for modules and appends the version-specific
-// properties for a given variant to the properties in the variant module
-func versionSplitMutator() func(android.BottomUpMutatorContext) {
-	return func(mctx android.BottomUpMutatorContext) {
-		if base, ok := mctx.Module().(basePropertiesProvider); ok {
-			props := base.getBaseProperties()
-			var versionNames []string
-			// collect version specific properties, so that we can merge version-specific properties
-			// into the module's overall properties
-			var versionProps []VersionProperties
-			// 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) {
-				versionNames = append(versionNames, pyVersion3)
-				versionProps = append(versionProps, props.Version.Py3)
+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.DeviceConfig().BuildBrokenUsesSoongPython2Modules() &&
+				ctx.ModuleName() != "py2-cmd" &&
+				ctx.ModuleName() != "py2-stdlib" {
+				ctx.PropertyErrorf("version.py2.enabled", "Python 2 is no longer supported, please convert to python 3. This error can be temporarily overridden by setting BUILD_BROKEN_USES_SOONG_PYTHON2_MODULES := true in the product configuration")
 			}
-			if proptools.BoolDefault(props.Version.Py2.Enabled, false) {
-				if !mctx.DeviceConfig().BuildBrokenUsesSoongPython2Modules() &&
-					mctx.ModuleName() != "py2-cmd" &&
-					mctx.ModuleName() != "py2-stdlib" {
-					mctx.PropertyErrorf("version.py2.enabled", "Python 2 is no longer supported, please convert to python 3. This error can be temporarily overridden by setting BUILD_BROKEN_USES_SOONG_PYTHON2_MODULES := true in the product configuration")
-				}
-				versionNames = append(versionNames, pyVersion2)
-				versionProps = append(versionProps, props.Version.Py2)
-			}
-			modules := mctx.CreateLocalVariations(versionNames...)
-			// Alias module to the first variant
-			if len(versionNames) > 0 {
-				mctx.AliasVariation(versionNames[0])
-			}
-			for i, v := range versionNames {
-				// set the actual version for Python module.
-				newProps := modules[i].(basePropertiesProvider).getBaseProperties()
-				newProps.Actual_version = v
-				// append versioned properties for the Python module to the overall properties
-				err := proptools.AppendMatchingProperties([]interface{}{newProps}, &versionProps[i], nil)
-				if err != nil {
-					panic(err)
-				}
-			}
+			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)
 		}
 	}
 }
@@ -506,8 +527,8 @@
 	}
 
 	for _, d := range expandedData {
-		if d.Ext() == pyExt || d.Ext() == protoExt {
-			ctx.PropertyErrorf("data", "found (.py|.proto) file: %q!", d.String())
+		if d.Ext() == pyExt {
+			ctx.PropertyErrorf("data", "found (.py) file: %q!", d.String())
 			continue
 		}
 		runfilesPath := filepath.Join(pkgPath, d.Rel())
@@ -523,19 +544,19 @@
 	relativeRootMap := make(map[string]android.Paths)
 	var protoSrcs android.Paths
 	addPathMapping := func(path pathMapping) {
-		// handle proto sources separately
-		if path.src.Ext() == protoExt {
-			protoSrcs = append(protoSrcs, path.src)
-		} else {
-			relativeRoot := strings.TrimSuffix(path.src.String(), path.src.Rel())
-			relativeRootMap[relativeRoot] = append(relativeRootMap[relativeRoot], path.src)
-		}
+		relativeRoot := strings.TrimSuffix(path.src.String(), path.src.Rel())
+		relativeRootMap[relativeRoot] = append(relativeRootMap[relativeRoot], path.src)
 	}
 
 	// "srcs" or "data" properties may contain filegroups so it might happen that
 	// the root directory for each source path is different.
 	for _, path := range p.srcsPathMappings {
-		addPathMapping(path)
+		// handle proto sources separately
+		if path.src.Ext() == protoExt {
+			protoSrcs = append(protoSrcs, path.src)
+		} else {
+			addPathMapping(path)
+		}
 	}
 	for _, path := range p.dataPathMappings {
 		addPathMapping(path)
diff --git a/python/python_test.go b/python/python_test.go
index c0b7295..6a6bd1d 100644
--- a/python/python_test.go
+++ b/python/python_test.go
@@ -50,7 +50,7 @@
 		" 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|.proto) file: %q!"
+	badDataFileExtErr = moduleVariantErrTemplate + "data: found (.py) file: %q!"
 	bpFile            = "Android.bp"
 
 	data = []struct {
diff --git a/python/scripts/precompile_python.py b/python/scripts/precompile_python.py
index 07b8fe9..b3cf950 100644
--- a/python/scripts/precompile_python.py
+++ b/python/scripts/precompile_python.py
@@ -30,6 +30,7 @@
     # Date was chosen to be the same as
     # https://cs.android.com/android/platform/superproject/main/+/main:build/soong/jar/jar.go;l=36;drc=2863e4535eb65e15f955dc8ed48fa99b1d2a1db5
     info = zipfile.ZipInfo(filename=name, date_time=(2008, 1, 1, 0, 0, 0))
+    info.compress_type = zipfile.ZIP_DEFLATED
 
     if not info.filename.endswith('.py'):
         outzip.writestr(info, infile.read())
diff --git a/python/test.go b/python/test.go
index 2b939e7..85decf9 100644
--- a/python/test.go
+++ b/python/test.go
@@ -18,6 +18,7 @@
 	"fmt"
 
 	"android/soong/testing"
+
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
@@ -151,7 +152,6 @@
 	// just use buildBinary() so that the binary is not installed into the location
 	// it would be for regular binaries.
 	p.PythonLibraryModule.GenerateAndroidBuildActions(ctx)
-	android.CollectDependencyAconfigFiles(ctx, &p.mergedAconfigFiles)
 	p.buildBinary(ctx)
 
 	var configs []tradefed.Option
@@ -227,7 +227,6 @@
 			}
 
 			entries.SetBoolIfTrue("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", !BoolDefault(p.binaryProperties.Auto_gen_config, true))
-			android.SetAconfigFileMkEntries(&p.ModuleBase, entries, p.mergedAconfigFiles)
 
 			p.testProperties.Test_options.SetAndroidMkEntries(entries)
 		})
diff --git a/python/tests/Android.bp b/python/tests/Android.bp
index e5569ba..056f7ed 100644
--- a/python/tests/Android.bp
+++ b/python/tests/Android.bp
@@ -27,9 +27,4 @@
     test_options: {
         unit_test: false,
     },
-    version: {
-        py3: {
-            embedded_launcher: true,
-        },
-    },
 }
diff --git a/python/tests/dont_import_folder_of_entrypoint/Android.bp b/python/tests/dont_import_folder_of_entrypoint/Android.bp
index e54e9b2..ab2e314 100644
--- a/python/tests/dont_import_folder_of_entrypoint/Android.bp
+++ b/python/tests/dont_import_folder_of_entrypoint/Android.bp
@@ -10,17 +10,3 @@
         "mypkg/mymodule.py",
     ],
 }
-
-python_test_host {
-    name: "py_dont_import_folder_of_entrypoint_test_embedded_launcher",
-    main: "mypkg/main.py",
-    srcs: [
-        "mypkg/main.py",
-        "mypkg/mymodule.py",
-    ],
-    version: {
-        py3: {
-            embedded_launcher: true,
-        },
-    },
-}
diff --git a/rust/afdo.go b/rust/afdo.go
index 6116c5e..6bd4bae 100644
--- a/rust/afdo.go
+++ b/rust/afdo.go
@@ -39,7 +39,7 @@
 		return
 	}
 
-	if mod, ok := ctx.Module().(*Module); ok && mod.Enabled() {
+	if mod, ok := ctx.Module().(*Module); ok && mod.Enabled(ctx) {
 		fdoProfileName, err := actx.DeviceConfig().AfdoProfile(actx.ModuleName())
 		if err != nil {
 			ctx.ModuleErrorf("%s", err.Error())
diff --git a/rust/androidmk.go b/rust/androidmk.go
index 021dd60..8de6b60 100644
--- a/rust/androidmk.go
+++ b/rust/androidmk.go
@@ -69,7 +69,6 @@
 				} else if mod.InProduct() {
 					entries.SetBool("LOCAL_IN_PRODUCT", true)
 				}
-				android.SetAconfigFileMkEntries(mod.AndroidModuleBase(), entries, mod.mergedAconfigFiles)
 			},
 		},
 	}
diff --git a/rust/benchmark.go b/rust/benchmark.go
index c0f1e24..8c3e515 100644
--- a/rust/benchmark.go
+++ b/rust/benchmark.go
@@ -22,7 +22,7 @@
 type BenchmarkProperties struct {
 	// Disables the creation of a test-specific directory when used with
 	// relative_install_path. Useful if several tests need to be in the same
-	// directory, but test_per_src doesn't work.
+	// directory.
 	No_named_install_directory *bool
 
 	// the name of the test configuration (for example "AndroidBenchmark.xml") that should be
diff --git a/rust/binary.go b/rust/binary.go
index 9969513..cba29a0 100644
--- a/rust/binary.go
+++ b/rust/binary.go
@@ -134,6 +134,9 @@
 	ret := buildOutput{outputFile: outputFile}
 	crateRootPath := crateRootPath(ctx, binary)
 
+	// Ensure link dirs are not duplicated
+	deps.linkDirs = android.FirstUniqueStrings(deps.linkDirs)
+
 	flags.RustFlags = append(flags.RustFlags, deps.depFlags...)
 	flags.LinkFlags = append(flags.LinkFlags, deps.depLinkFlags...)
 	flags.LinkFlags = append(flags.LinkFlags, deps.linkObjects...)
diff --git a/rust/bindgen.go b/rust/bindgen.go
index eaed1b9..f1579cc 100644
--- a/rust/bindgen.go
+++ b/rust/bindgen.go
@@ -29,7 +29,7 @@
 	defaultBindgenFlags = []string{""}
 
 	// bindgen should specify its own Clang revision so updating Clang isn't potentially blocked on bindgen failures.
-	bindgenClangVersion = "clang-r510928"
+	bindgenClangVersion = "clang-r530567"
 
 	_ = pctx.VariableFunc("bindgenClangVersion", func(ctx android.PackageVarContext) string {
 		if override := ctx.Config().Getenv("LLVM_BINDGEN_PREBUILTS_VERSION"); override != "" {
@@ -102,8 +102,17 @@
 	// "my_bindgen [flags] wrapper_header.h -o [output_path] -- [clang flags]"
 	Custom_bindgen string
 
-	// flag to indicate if bindgen should handle `static inline` functions (default is false)
-	Handle_static_inline bool
+	// flag to indicate if bindgen should handle `static inline` functions (default is false).
+	// If true, Static_inline_library must be set.
+	Handle_static_inline *bool
+
+	// module name of the corresponding cc_library_static which includes the static_inline wrapper
+	// generated functions from bindgen. Must be used together with handle_static_inline.
+	//
+	// If there are no static inline functions provided through the header file,
+	// then bindgen (as of 0.69.2) will silently fail to output a .c file, and
+	// the cc_library_static depending on this module will fail compilation.
+	Static_inline_library *string
 }
 
 type bindgenDecorator struct {
@@ -159,6 +168,18 @@
 
 	var cflags []string
 	var implicits android.Paths
+	var implicitOutputs android.WritablePaths
+	var validations android.Paths
+
+	if Bool(b.Properties.Handle_static_inline) && b.Properties.Static_inline_library == nil {
+		ctx.PropertyErrorf("handle_static_inline",
+			"requires declaring static_inline_library to the corresponding cc_library module that includes the generated C source from bindgen.")
+	}
+
+	if b.Properties.Static_inline_library != nil && !Bool(b.Properties.Handle_static_inline) {
+		ctx.PropertyErrorf("static_inline_library",
+			"requires declaring handle_static_inline.")
+	}
 
 	implicits = append(implicits, deps.depGeneratedHeaders...)
 
@@ -215,7 +236,8 @@
 	esc := proptools.NinjaAndShellEscapeList
 
 	// Filter out invalid cflags
-	for _, flag := range b.ClangProperties.Cflags {
+	cflagsProp := b.ClangProperties.Cflags.GetOrDefault(ctx, nil)
+	for _, flag := range cflagsProp {
 		if flag == "-x c++" || flag == "-xc++" {
 			ctx.PropertyErrorf("cflags",
 				"-x c++ should not be specified in cflags; setting cpp_std specifies this is a C++ header, or change the file extension to '.hpp' or '.hh'")
@@ -227,7 +249,7 @@
 	}
 
 	// Module defined clang flags and include paths
-	cflags = append(cflags, esc(b.ClangProperties.Cflags)...)
+	cflags = append(cflags, esc(cflagsProp)...)
 	for _, include := range b.ClangProperties.Local_include_dirs {
 		cflags = append(cflags, "-I"+android.PathForModuleSrc(ctx, include).String())
 		implicits = append(implicits, android.PathForModuleSrc(ctx, include))
@@ -235,8 +257,11 @@
 
 	bindgenFlags := defaultBindgenFlags
 	bindgenFlags = append(bindgenFlags, esc(b.Properties.Bindgen_flags)...)
-	if b.Properties.Handle_static_inline {
-		bindgenFlags = append(bindgenFlags, "--experimental --wrap-static-fns")
+	if Bool(b.Properties.Handle_static_inline) {
+		outputStaticFnsFile := android.PathForModuleOut(ctx, b.BaseSourceProvider.getStem(ctx)+".c")
+		implicitOutputs = append(implicitOutputs, outputStaticFnsFile)
+		validations = append(validations, outputStaticFnsFile)
+		bindgenFlags = append(bindgenFlags, []string{"--experimental", "--wrap-static-fns", "--wrap-static-fns-path=" + outputStaticFnsFile.String()}...)
 	}
 
 	// cat reads from stdin if its command line is empty,
@@ -285,11 +310,13 @@
 	}
 
 	ctx.Build(pctx, android.BuildParams{
-		Rule:        bindgen,
-		Description: strings.Join([]string{cmdDesc, wrapperFile.Path().Rel()}, " "),
-		Output:      outputFile,
-		Input:       wrapperFile.Path(),
-		Implicits:   implicits,
+		Rule:            bindgen,
+		Description:     strings.Join([]string{cmdDesc, wrapperFile.Path().Rel()}, " "),
+		Output:          outputFile,
+		Input:           wrapperFile.Path(),
+		Implicits:       implicits,
+		ImplicitOutputs: implicitOutputs,
+		Validations:     validations,
 		Args: map[string]string{
 			"cmd":       cmd,
 			"flags":     strings.Join(bindgenFlags, " "),
@@ -299,6 +326,14 @@
 	})
 
 	b.BaseSourceProvider.OutputFiles = android.Paths{outputFile}
+
+	// Append any additional implicit outputs after the entry point source.
+	// We append any generated .c file here so it can picked up by cc_library_static modules.
+	// Those CC modules need to be sure not to pass any included .rs files to Clang.
+	// We don't have to worry about the additional .c files for Rust modules as only the entry point
+	// is passed to rustc.
+	b.BaseSourceProvider.OutputFiles = append(b.BaseSourceProvider.OutputFiles, implicitOutputs.Paths()...)
+
 	return outputFile
 }
 
@@ -350,6 +385,14 @@
 		deps = muslDeps(ctx, deps, false)
 	}
 
+	if !ctx.RustModule().Source() && b.Properties.Static_inline_library != nil {
+		// This is not the source variant, so add the static inline library as a dependency.
+		//
+		// 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.SharedLibs = append(deps.SharedLibs, b.ClangProperties.Shared_libs...)
 	deps.StaticLibs = append(deps.StaticLibs, b.ClangProperties.Static_libs...)
 	deps.HeaderLibs = append(deps.HeaderLibs, b.ClangProperties.Header_libs...)
diff --git a/rust/bindgen_test.go b/rust/bindgen_test.go
index 11cfe4e..2b7362f 100644
--- a/rust/bindgen_test.go
+++ b/rust/bindgen_test.go
@@ -228,7 +228,6 @@
 	// we may be able to check libbinder.RuleParams.Command to see if it contains $(cat /dev/null flag_file.txt)
 }
 
-
 func TestBindgenHandleStaticInlining(t *testing.T) {
 	ctx := testRust(t, `
 		rust_bindgen {
@@ -237,12 +236,54 @@
 			crate_name: "bindgen",
 			stem: "libbindgen",
 			source_stem: "bindings",
+			handle_static_inline: true,
+			static_inline_library: "libbindgen_staticfns"
+		}
+
+		cc_library_static {
+			name: "libbindgen_staticfns",
+			srcs: [":libbindgen"],
+			include_dirs: ["src/"],
+		}
+	`)
+	libbindgen := ctx.ModuleForTests("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"])
+	}
+
+	if !strings.Contains(libbindgen.Args["flags"], "--wrap-static-fns-path") {
+		t.Errorf("missing flag to define path for static inlining C source from bindgen (--wrap-static-fns-path): flags %#v", libbindgen.Args["flags"])
+	}
+
+}
+
+func TestBindgenStaticInlineProperties(t *testing.T) {
+	// Make sure handle_static_inline without static_inline_library generates an error
+	testRustError(t, "requires declaring static_inline_library to the corresponding cc_library module that includes the generated C source from bindgen", `
+		rust_bindgen {
+			name: "libbindgen",
+			wrapper_src: "src/any.h",
+			crate_name: "bindgen",
+			stem: "libbindgen",
+			source_stem: "bindings",
 			handle_static_inline: true
 		}
 	`)
-	libbindgen := ctx.ModuleForTests("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"])
-	}
+	testRustError(t, "requires declaring handle_static_inline", `
+		rust_bindgen {
+			name: "libbindgen",
+			wrapper_src: "src/any.h",
+			crate_name: "bindgen",
+			stem: "libbindgen",
+			source_stem: "bindings",
+			static_inline_library: "libbindgen_staticfns"
+		}
+
+		cc_library_static {
+			name: "libbindgen_staticfns",
+			srcs: [":libbindgen"],
+			include_dirs: ["src/"],
+		}
+	`)
 }
diff --git a/rust/builder.go b/rust/builder.go
index 4f45e33..f469f56 100644
--- a/rust/builder.go
+++ b/rust/builder.go
@@ -21,6 +21,7 @@
 	"github.com/google/blueprint"
 
 	"android/soong/android"
+	"android/soong/cc"
 	"android/soong/rust/config"
 )
 
@@ -118,42 +119,129 @@
 
 func init() {
 	pctx.HostBinToolVariable("SoongZipCmd", "soong_zip")
+	cc.TransformRlibstoStaticlib = TransformRlibstoStaticlib
+}
+
+type transformProperties struct {
+	crateName       string
+	targetTriple    string
+	is64Bit         bool
+	bootstrap       bool
+	inRecovery      bool
+	inRamdisk       bool
+	inVendorRamdisk bool
+	cargoOutDir     android.OptionalPath
+	synthetic       bool
+	crateType       string
+}
+
+// Populates a standard transformProperties struct for Rust modules
+func getTransformProperties(ctx ModuleContext, crateType string) transformProperties {
+	module := ctx.RustModule()
+	return transformProperties{
+		crateName:       module.CrateName(),
+		is64Bit:         ctx.toolchain().Is64Bit(),
+		targetTriple:    ctx.toolchain().RustTriple(),
+		bootstrap:       module.Bootstrap(),
+		inRecovery:      module.InRecovery(),
+		inRamdisk:       module.InRamdisk(),
+		inVendorRamdisk: module.InVendorRamdisk(),
+		cargoOutDir:     module.compiler.cargoOutDir(),
+
+		// crateType indicates what type of crate to build
+		crateType: crateType,
+
+		// synthetic indicates whether this is an actual Rust module or not
+		synthetic: false,
+	}
 }
 
 func TransformSrcToBinary(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
 	outputFile android.WritablePath) buildOutput {
-	flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto=thin")
+	if ctx.RustModule().compiler.Thinlto() {
+		flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto=thin")
+	}
 
-	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "bin")
+	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, getTransformProperties(ctx, "bin"))
 }
 
 func TransformSrctoRlib(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
 	outputFile android.WritablePath) buildOutput {
-	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "rlib")
+	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, getTransformProperties(ctx, "rlib"))
+}
+
+func TransformRlibstoStaticlib(ctx android.ModuleContext, mainSrc android.Path, deps []cc.RustRlibDep,
+	outputFile android.WritablePath) android.Path {
+
+	var rustPathDeps PathDeps
+	var rustFlags Flags
+
+	for _, rlibDep := range deps {
+		rustPathDeps.RLibs = append(rustPathDeps.RLibs, RustLibrary{Path: rlibDep.LibPath, CrateName: rlibDep.CrateName})
+		rustPathDeps.linkDirs = append(rustPathDeps.linkDirs, rlibDep.LinkDirs...)
+	}
+
+	ccModule := ctx.(cc.ModuleContext).Module().(*cc.Module)
+	toolchain := config.FindToolchain(ctx.Os(), ctx.Arch())
+	t := transformProperties{
+		// Crate name can be a predefined value as this is a staticlib and
+		// it does not need to be unique. The crate name is used for name
+		// mangling, but it is mixed with the metadata for that purpose, which we
+		// already set to the module name.
+		crateName:       "generated_rust_staticlib",
+		is64Bit:         toolchain.Is64Bit(),
+		targetTriple:    toolchain.RustTriple(),
+		bootstrap:       ccModule.Bootstrap(),
+		inRecovery:      ccModule.InRecovery(),
+		inRamdisk:       ccModule.InRamdisk(),
+		inVendorRamdisk: ccModule.InVendorRamdisk(),
+
+		// crateType indicates what type of crate to build
+		crateType: "staticlib",
+
+		// synthetic indicates whether this is an actual Rust module or not
+		synthetic: true,
+	}
+
+	rustFlags = CommonDefaultFlags(ctx, toolchain, rustFlags)
+	rustFlags = CommonLibraryCompilerFlags(ctx, rustFlags)
+	rustFlags.GlobalRustFlags = append(rustFlags.GlobalRustFlags, "-C lto=thin")
+
+	rustFlags.EmitXrefs = false
+
+	return transformSrctoCrate(ctx, mainSrc, rustPathDeps, rustFlags, outputFile, t).outputFile
 }
 
 func TransformSrctoDylib(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
 	outputFile android.WritablePath) buildOutput {
-	flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto=thin")
+	if ctx.RustModule().compiler.Thinlto() {
+		flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto=thin")
+	}
 
-	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "dylib")
+	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, getTransformProperties(ctx, "dylib"))
 }
 
 func TransformSrctoStatic(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
 	outputFile android.WritablePath) buildOutput {
-	flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto=thin")
-	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "staticlib")
+	if ctx.RustModule().compiler.Thinlto() {
+		flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto=thin")
+	}
+
+	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, getTransformProperties(ctx, "staticlib"))
 }
 
 func TransformSrctoShared(ctx ModuleContext, mainSrc android.Path, deps PathDeps, flags Flags,
 	outputFile android.WritablePath) buildOutput {
-	flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto=thin")
-	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "cdylib")
+	if ctx.RustModule().compiler.Thinlto() {
+		flags.GlobalRustFlags = append(flags.GlobalRustFlags, "-C lto=thin")
+	}
+
+	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, getTransformProperties(ctx, "cdylib"))
 }
 
 func TransformSrctoProcMacro(ctx ModuleContext, mainSrc android.Path, deps PathDeps,
 	flags Flags, outputFile android.WritablePath) buildOutput {
-	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, "proc-macro")
+	return transformSrctoCrate(ctx, mainSrc, deps, flags, outputFile, getTransformProperties(ctx, "proc-macro"))
 }
 
 func rustLibsToPaths(libs RustLibraries) android.Paths {
@@ -185,18 +273,18 @@
 	return libFlags
 }
 
-func rustEnvVars(ctx ModuleContext, deps PathDeps) []string {
+func rustEnvVars(ctx android.ModuleContext, deps PathDeps, crateName string, cargoOutDir android.OptionalPath) []string {
 	var envVars []string
 
 	// libstd requires a specific environment variable to be set. This is
 	// not officially documented and may be removed in the future. See
 	// https://github.com/rust-lang/rust/blob/master/library/std/src/env.rs#L866.
-	if ctx.RustModule().CrateName() == "std" {
-		envVars = append(envVars, "STD_ENV_ARCH="+config.StdEnvArch[ctx.RustModule().Arch().ArchType])
+	if crateName == "std" {
+		envVars = append(envVars, "STD_ENV_ARCH="+config.StdEnvArch[ctx.Arch().ArchType])
 	}
 
-	if len(deps.SrcDeps) > 0 {
-		moduleGenDir := ctx.RustModule().compiler.cargoOutDir()
+	if len(deps.SrcDeps) > 0 && cargoOutDir.Valid() {
+		moduleGenDir := cargoOutDir
 		// We must calculate an absolute path for OUT_DIR since Rust's include! macro (which normally consumes this)
 		// assumes that paths are relative to the source file.
 		var outDirPrefix string
@@ -215,13 +303,15 @@
 
 	envVars = append(envVars, "ANDROID_RUST_VERSION="+config.GetRustVersion(ctx))
 
-	if ctx.RustModule().compiler.cargoEnvCompat() {
-		if bin, ok := ctx.RustModule().compiler.(*binaryDecorator); ok {
+	if rustMod, ok := ctx.Module().(*Module); ok && rustMod.compiler.cargoEnvCompat() {
+		// We only emulate cargo environment variables for 3p code, which is only ever built
+		// by defining a Rust module, so we only need to set these for true Rust modules.
+		if bin, ok := rustMod.compiler.(*binaryDecorator); ok {
 			envVars = append(envVars, "CARGO_BIN_NAME="+bin.getStem(ctx))
 		}
-		envVars = append(envVars, "CARGO_CRATE_NAME="+ctx.RustModule().CrateName())
-		envVars = append(envVars, "CARGO_PKG_NAME="+ctx.RustModule().CrateName())
-		pkgVersion := ctx.RustModule().compiler.cargoPkgVersion()
+		envVars = append(envVars, "CARGO_CRATE_NAME="+crateName)
+		envVars = append(envVars, "CARGO_PKG_NAME="+crateName)
+		pkgVersion := rustMod.compiler.cargoPkgVersion()
 		if pkgVersion != "" {
 			envVars = append(envVars, "CARGO_PKG_VERSION="+pkgVersion)
 
@@ -245,8 +335,8 @@
 	return envVars
 }
 
-func transformSrctoCrate(ctx ModuleContext, main android.Path, deps PathDeps, flags Flags,
-	outputFile android.WritablePath, crateType string) buildOutput {
+func transformSrctoCrate(ctx android.ModuleContext, main android.Path, deps PathDeps, flags Flags,
+	outputFile android.WritablePath, t transformProperties) buildOutput {
 
 	var inputs android.Paths
 	var implicits android.Paths
@@ -256,23 +346,21 @@
 	var earlyLinkFlags string
 
 	output.outputFile = outputFile
-	crateName := ctx.RustModule().CrateName()
-	targetTriple := ctx.toolchain().RustTriple()
 
-	envVars := rustEnvVars(ctx, deps)
+	envVars := rustEnvVars(ctx, deps, t.crateName, t.cargoOutDir)
 
 	inputs = append(inputs, main)
 
 	// Collect rustc flags
 	rustcFlags = append(rustcFlags, flags.GlobalRustFlags...)
 	rustcFlags = append(rustcFlags, flags.RustFlags...)
-	rustcFlags = append(rustcFlags, "--crate-type="+crateType)
-	if crateName != "" {
-		rustcFlags = append(rustcFlags, "--crate-name="+crateName)
+	rustcFlags = append(rustcFlags, "--crate-type="+t.crateType)
+	if t.crateName != "" {
+		rustcFlags = append(rustcFlags, "--crate-name="+t.crateName)
 	}
-	if targetTriple != "" {
-		rustcFlags = append(rustcFlags, "--target="+targetTriple)
-		linkFlags = append(linkFlags, "-target "+targetTriple)
+	if t.targetTriple != "" {
+		rustcFlags = append(rustcFlags, "--target="+t.targetTriple)
+		linkFlags = append(linkFlags, "-target "+t.targetTriple)
 	}
 
 	// Suppress an implicit sysroot
@@ -302,9 +390,9 @@
 	linkFlags = append(linkFlags, flags.LinkFlags...)
 
 	// Check if this module needs to use the bootstrap linker
-	if ctx.RustModule().Bootstrap() && !ctx.RustModule().InRecovery() && !ctx.RustModule().InRamdisk() && !ctx.RustModule().InVendorRamdisk() {
+	if t.bootstrap && !t.inRecovery && !t.inRamdisk && !t.inVendorRamdisk {
 		dynamicLinker := "-Wl,-dynamic-linker,/system/bin/bootstrap/linker"
-		if ctx.toolchain().Is64Bit() {
+		if t.is64Bit {
 			dynamicLinker += "64"
 		}
 		linkFlags = append(linkFlags, dynamicLinker)
@@ -326,49 +414,56 @@
 
 	orderOnly = append(orderOnly, deps.SharedLibs...)
 
-	if len(deps.SrcDeps) > 0 {
-		moduleGenDir := ctx.RustModule().compiler.cargoOutDir()
-		var outputs android.WritablePaths
+	if !t.synthetic {
+		// Only worry about OUT_DIR for actual Rust modules.
+		// Libraries built from cc use generated source, and do not utilize OUT_DIR.
+		if len(deps.SrcDeps) > 0 {
+			var outputs android.WritablePaths
 
-		for _, genSrc := range deps.SrcDeps {
-			if android.SuffixInList(outputs.Strings(), genSubDir+genSrc.Base()) {
-				ctx.PropertyErrorf("srcs",
-					"multiple source providers generate the same filename output: "+genSrc.Base())
+			for _, genSrc := range deps.SrcDeps {
+				if android.SuffixInList(outputs.Strings(), genSubDir+genSrc.Base()) {
+					ctx.PropertyErrorf("srcs",
+						"multiple source providers generate the same filename output: "+genSrc.Base())
+				}
+				outputs = append(outputs, android.PathForModuleOut(ctx, genSubDir+genSrc.Base()))
 			}
-			outputs = append(outputs, android.PathForModuleOut(ctx, genSubDir+genSrc.Base()))
-		}
 
-		ctx.Build(pctx, android.BuildParams{
-			Rule:        cp,
-			Description: "cp " + moduleGenDir.Path().Rel(),
-			Outputs:     outputs,
-			Inputs:      deps.SrcDeps,
-			Args: map[string]string{
-				"outDir": moduleGenDir.String(),
-			},
-		})
-		implicits = append(implicits, outputs.Paths()...)
+			ctx.Build(pctx, android.BuildParams{
+				Rule:        cp,
+				Description: "cp " + t.cargoOutDir.Path().Rel(),
+				Outputs:     outputs,
+				Inputs:      deps.SrcDeps,
+				Args: map[string]string{
+					"outDir": t.cargoOutDir.String(),
+				},
+			})
+			implicits = append(implicits, outputs.Paths()...)
+		}
 	}
 
-	if flags.Clippy {
-		clippyFile := android.PathForModuleOut(ctx, outputFile.Base()+".clippy")
-		ctx.Build(pctx, android.BuildParams{
-			Rule:            clippyDriver,
-			Description:     "clippy " + main.Rel(),
-			Output:          clippyFile,
-			ImplicitOutputs: nil,
-			Inputs:          inputs,
-			Implicits:       implicits,
-			OrderOnly:       orderOnly,
-			Args: map[string]string{
-				"rustcFlags":  strings.Join(rustcFlags, " "),
-				"libFlags":    strings.Join(libFlags, " "),
-				"clippyFlags": strings.Join(flags.ClippyFlags, " "),
-				"envVars":     strings.Join(envVars, " "),
-			},
-		})
-		// Declare the clippy build as an implicit dependency of the original crate.
-		implicits = append(implicits, clippyFile)
+	if !t.synthetic {
+		// Only worry about clippy for actual Rust modules.
+		// Libraries built from cc use generated source, and don't need to run clippy.
+		if flags.Clippy {
+			clippyFile := android.PathForModuleOut(ctx, outputFile.Base()+".clippy")
+			ctx.Build(pctx, android.BuildParams{
+				Rule:            clippyDriver,
+				Description:     "clippy " + main.Rel(),
+				Output:          clippyFile,
+				ImplicitOutputs: nil,
+				Inputs:          inputs,
+				Implicits:       implicits,
+				OrderOnly:       orderOnly,
+				Args: map[string]string{
+					"rustcFlags":  strings.Join(rustcFlags, " "),
+					"libFlags":    strings.Join(libFlags, " "),
+					"clippyFlags": strings.Join(flags.ClippyFlags, " "),
+					"envVars":     strings.Join(envVars, " "),
+				},
+			})
+			// Declare the clippy build as an implicit dependency of the original crate.
+			implicits = append(implicits, clippyFile)
+		}
 	}
 
 	ctx.Build(pctx, android.BuildParams{
@@ -389,25 +484,28 @@
 		},
 	})
 
-	if flags.EmitXrefs {
-		kytheFile := android.PathForModuleOut(ctx, outputFile.Base()+".kzip")
-		ctx.Build(pctx, android.BuildParams{
-			Rule:        kytheExtract,
-			Description: "Xref Rust extractor " + main.Rel(),
-			Output:      kytheFile,
-			Inputs:      inputs,
-			Implicits:   implicits,
-			OrderOnly:   orderOnly,
-			Args: map[string]string{
-				"rustcFlags": strings.Join(rustcFlags, " "),
-				"linkFlags":  strings.Join(linkFlags, " "),
-				"libFlags":   strings.Join(libFlags, " "),
-				"crtBegin":   strings.Join(deps.CrtBegin.Strings(), " "),
-				"crtEnd":     strings.Join(deps.CrtEnd.Strings(), " "),
-				"envVars":    strings.Join(envVars, " "),
-			},
-		})
-		output.kytheFile = kytheFile
+	if !t.synthetic {
+		// Only emit xrefs for true Rust modules.
+		if flags.EmitXrefs {
+			kytheFile := android.PathForModuleOut(ctx, outputFile.Base()+".kzip")
+			ctx.Build(pctx, android.BuildParams{
+				Rule:        kytheExtract,
+				Description: "Xref Rust extractor " + main.Rel(),
+				Output:      kytheFile,
+				Inputs:      inputs,
+				Implicits:   implicits,
+				OrderOnly:   orderOnly,
+				Args: map[string]string{
+					"rustcFlags": strings.Join(rustcFlags, " "),
+					"linkFlags":  strings.Join(linkFlags, " "),
+					"libFlags":   strings.Join(libFlags, " "),
+					"crtBegin":   strings.Join(deps.CrtBegin.Strings(), " "),
+					"crtEnd":     strings.Join(deps.CrtEnd.Strings(), " "),
+					"envVars":    strings.Join(envVars, " "),
+				},
+			})
+			output.kytheFile = kytheFile
+		}
 	}
 	return output
 }
@@ -422,6 +520,9 @@
 	// this flag.
 	rustdocFlags = append(rustdocFlags, "-Z", "unstable-options", "--enable-index-page")
 
+	// Ensure we use any special-case code-paths for Soong.
+	rustdocFlags = append(rustdocFlags, "--cfg", "soong")
+
 	targetTriple := ctx.toolchain().RustTriple()
 
 	// Collect rustc flags
@@ -457,7 +558,7 @@
 		Args: map[string]string{
 			"rustdocFlags": strings.Join(rustdocFlags, " "),
 			"outDir":       docDir.String(),
-			"envVars":      strings.Join(rustEnvVars(ctx, deps), " "),
+			"envVars":      strings.Join(rustEnvVars(ctx, deps, crateName, ctx.RustModule().compiler.cargoOutDir()), " "),
 		},
 	})
 
diff --git a/rust/builder_test.go b/rust/builder_test.go
index 639f6d4..ae5ccde 100644
--- a/rust/builder_test.go
+++ b/rust/builder_test.go
@@ -46,6 +46,9 @@
 }
 
 func TestCompilationOutputFiles(t *testing.T) {
+
+	// Note: Rustdoc output is produced for the PrimaryModule, so if the variant
+	// order changes, then it may be produced for a different variant.
 	ctx := testRust(t, `
 		rust_library {
 			name: "libfizz_buzz",
@@ -62,6 +65,11 @@
 			crate_name: "rust_ffi",
 			srcs: ["lib.rs"],
 		}
+		rust_ffi_static {
+			name: "librust_ffi_static",
+			crate_name: "rust_ffi",
+			srcs: ["lib.rs"],
+		}
 	`)
 	testcases := []struct {
 		testName      string
@@ -115,14 +123,24 @@
 			},
 		},
 		{
-			testName:   "rust_ffi static",
-			moduleName: "librust_ffi",
-			variant:    "android_arm64_armv8-a_static",
+			testName:   "rust_ffi_static rlib",
+			moduleName: "librust_ffi_static",
+			variant:    "android_arm64_armv8-a_rlib_rlib-std",
 			expectedFiles: []string{
-				"out/soong/.intermediates/librust_ffi/android_arm64_armv8-a_static/librust_ffi.a",
-				"out/soong/.intermediates/librust_ffi/android_arm64_armv8-a_static/librust_ffi.a.clippy",
-				"out/soong/.intermediates/librust_ffi/android_arm64_armv8-a_static/meta_lic",
-				"out/soong/.intermediates/librust_ffi/android_arm64_armv8-a_static/rustdoc.timestamp",
+				"out/soong/.intermediates/librust_ffi_static/android_arm64_armv8-a_rlib_rlib-std/librust_ffi_static.rlib",
+				"out/soong/.intermediates/librust_ffi_static/android_arm64_armv8-a_rlib_rlib-std/librust_ffi_static.rlib.clippy",
+				"out/soong/.intermediates/librust_ffi_static/android_arm64_armv8-a_rlib_rlib-std/meta_lic",
+				"out/soong/.intermediates/librust_ffi_static/android_arm64_armv8-a_rlib_rlib-std/rustdoc.timestamp",
+			},
+		},
+		{
+			testName:   "rust_ffi rlib",
+			moduleName: "librust_ffi",
+			variant:    "android_arm64_armv8-a_rlib_rlib-std",
+			expectedFiles: []string{
+				"out/soong/.intermediates/librust_ffi/android_arm64_armv8-a_rlib_rlib-std/librust_ffi.rlib",
+				"out/soong/.intermediates/librust_ffi/android_arm64_armv8-a_rlib_rlib-std/librust_ffi.rlib.clippy",
+				"out/soong/.intermediates/librust_ffi/android_arm64_armv8-a_rlib_rlib-std/meta_lic",
 			},
 		},
 		{
@@ -135,6 +153,7 @@
 				"out/soong/.intermediates/librust_ffi/android_arm64_armv8-a_shared/unstripped/librust_ffi.so",
 				"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",
 			},
 		},
diff --git a/rust/compiler.go b/rust/compiler.go
index 03fdf2b..a2546a1 100644
--- a/rust/compiler.go
+++ b/rust/compiler.go
@@ -47,6 +47,7 @@
 	edition() string
 	features() []string
 	rustdoc(ctx ModuleContext, flags Flags, deps PathDeps) android.OptionalPath
+	Thinlto() bool
 
 	// Output directory in which source-generated code from dependencies is
 	// copied. This is equivalent to Cargo's OUT_DIR variable.
@@ -196,7 +197,7 @@
 	Features []string `android:"arch_variant"`
 
 	// list of configuration options to enable for this crate. To enable features, use the "features" property.
-	Cfgs []string `android:"arch_variant"`
+	Cfgs proptools.Configurable[[]string] `android:"arch_variant"`
 
 	// specific rust edition that should be used if the default version is not desired
 	Edition *string `android:"arch_variant"`
@@ -231,6 +232,15 @@
 
 	// If cargo_env_compat is true, sets the CARGO_PKG_VERSION env var to this value.
 	Cargo_pkg_version *string
+
+	// Control whether LTO is used for the final (Rust) linkage. This does not impact
+	// cross-language LTO.
+	Lto struct {
+		// Whether thin LTO should be enabled. By default this is true.
+		// LTO provides such a large code size benefit for Rust, this should always
+		// be enabled for production builds unless there's a clear need to disable it.
+		Thin *bool `android:"arch_variant"`
+	} `android:"arch_variant"`
 }
 
 type baseCompiler struct {
@@ -273,6 +283,11 @@
 	return false
 }
 
+// Thin LTO is enabled by default.
+func (compiler *baseCompiler) Thinlto() bool {
+	return BoolDefault(compiler.Properties.Lto.Thin, true)
+}
+
 func (compiler *baseCompiler) SetDisabled() {
 	panic("baseCompiler does not implement SetDisabled()")
 }
@@ -322,9 +337,9 @@
 	return []interface{}{&compiler.Properties}
 }
 
-func (compiler *baseCompiler) cfgsToFlags() []string {
-	flags := []string{}
-	for _, cfg := range compiler.Properties.Cfgs {
+func cfgsToFlags(cfgs []string) []string {
+	flags := make([]string, 0, len(cfgs))
+	for _, cfg := range cfgs {
 		flags = append(flags, "--cfg '"+cfg+"'")
 	}
 
@@ -351,23 +366,62 @@
 	return flags
 }
 
-func (compiler *baseCompiler) cfgFlags(ctx ModuleContext, flags Flags) Flags {
-	if ctx.RustModule().InVendorOrProduct() {
-		compiler.Properties.Cfgs = append(compiler.Properties.Cfgs, "android_vndk")
-		if ctx.RustModule().InVendor() {
-			compiler.Properties.Cfgs = append(compiler.Properties.Cfgs, "android_vendor")
-		} else if ctx.RustModule().InProduct() {
-			compiler.Properties.Cfgs = append(compiler.Properties.Cfgs, "android_product")
+func CommonDefaultCfgFlags(flags Flags, vendor bool, product bool) Flags {
+	var cfgs []string
+	if vendor || product {
+		cfgs = append(cfgs, "android_vndk")
+		if vendor {
+			cfgs = append(cfgs, "android_vendor")
+		} else if product {
+			cfgs = append(cfgs, "android_product")
 		}
 	}
 
-	flags.RustFlags = append(flags.RustFlags, compiler.cfgsToFlags()...)
-	flags.RustdocFlags = append(flags.RustdocFlags, compiler.cfgsToFlags()...)
+	flags.RustFlags = append(flags.RustFlags, cfgsToFlags(cfgs)...)
+	flags.RustdocFlags = append(flags.RustdocFlags, cfgsToFlags(cfgs)...)
+	return flags
+}
+
+func (compiler *baseCompiler) cfgFlags(ctx ModuleContext, flags Flags) Flags {
+	flags = CommonDefaultCfgFlags(flags, ctx.RustModule().InVendor(), ctx.RustModule().InProduct())
+
+	cfgFlags := cfgsToFlags(compiler.Properties.Cfgs.GetOrDefault(ctx, nil))
+	flags.RustFlags = append(flags.RustFlags, cfgFlags...)
+	flags.RustdocFlags = append(flags.RustdocFlags, cfgFlags...)
+
+	return flags
+}
+
+func CommonDefaultFlags(ctx android.ModuleContext, toolchain config.Toolchain, flags Flags) Flags {
+	flags.GlobalRustFlags = append(flags.GlobalRustFlags, config.GlobalRustFlags...)
+	flags.GlobalRustFlags = append(flags.GlobalRustFlags, toolchain.ToolchainRustFlags())
+	flags.GlobalLinkFlags = append(flags.GlobalLinkFlags, toolchain.ToolchainLinkFlags())
+	flags.EmitXrefs = ctx.Config().EmitXrefRules()
+
+	if ctx.Host() && !ctx.Windows() {
+		flags.LinkFlags = append(flags.LinkFlags, cc.RpathFlags(ctx)...)
+	}
+
+	if ctx.Os() == android.Linux {
+		// Add -lc, -lrt, -ldl, -lpthread, -lm and -lgcc_s to glibc builds to match
+		// the default behavior of device builds.
+		flags.LinkFlags = append(flags.LinkFlags, config.LinuxHostGlobalLinkFlags...)
+	} else if ctx.Os() == android.Darwin {
+		// Add -lc, -ldl, -lpthread and -lm to glibc darwin builds to match the default
+		// behavior of device builds.
+		flags.LinkFlags = append(flags.LinkFlags,
+			"-lc",
+			"-ldl",
+			"-lpthread",
+			"-lm",
+		)
+	}
 	return flags
 }
 
 func (compiler *baseCompiler) compilerFlags(ctx ModuleContext, flags Flags) Flags {
 
+	flags = CommonDefaultFlags(ctx, ctx.toolchain(), flags)
 	lintFlags, err := config.RustcLintsForDir(ctx.ModuleDir(), compiler.Properties.Lints)
 	if err != nil {
 		ctx.PropertyErrorf("lints", err.Error())
@@ -396,29 +450,7 @@
 	flags.RustFlags = append(flags.RustFlags, "--edition="+compiler.edition())
 	flags.RustdocFlags = append(flags.RustdocFlags, "--edition="+compiler.edition())
 	flags.LinkFlags = append(flags.LinkFlags, compiler.Properties.Ld_flags...)
-	flags.GlobalRustFlags = append(flags.GlobalRustFlags, config.GlobalRustFlags...)
-	flags.GlobalRustFlags = append(flags.GlobalRustFlags, ctx.toolchain().ToolchainRustFlags())
-	flags.GlobalLinkFlags = append(flags.GlobalLinkFlags, ctx.toolchain().ToolchainLinkFlags())
-	flags.EmitXrefs = ctx.Config().EmitXrefRules()
 
-	if ctx.Host() && !ctx.Windows() {
-		flags.LinkFlags = append(flags.LinkFlags, cc.RpathFlags(ctx)...)
-	}
-
-	if ctx.Os() == android.Linux {
-		// Add -lc, -lrt, -ldl, -lpthread, -lm and -lgcc_s to glibc builds to match
-		// the default behavior of device builds.
-		flags.LinkFlags = append(flags.LinkFlags, config.LinuxHostGlobalLinkFlags...)
-	} else if ctx.Os() == android.Darwin {
-		// Add -lc, -ldl, -lpthread and -lm to glibc darwin builds to match the default
-		// behavior of device builds.
-		flags.LinkFlags = append(flags.LinkFlags,
-			"-lc",
-			"-ldl",
-			"-lpthread",
-			"-lm",
-		)
-	}
 	return flags
 }
 
@@ -568,11 +600,11 @@
 	compiler.installDeps = append(compiler.installDeps, installedData...)
 }
 
-func (compiler *baseCompiler) getStem(ctx ModuleContext) string {
+func (compiler *baseCompiler) getStem(ctx android.ModuleContext) string {
 	return compiler.getStemWithoutSuffix(ctx) + String(compiler.Properties.Suffix)
 }
 
-func (compiler *baseCompiler) getStemWithoutSuffix(ctx BaseModuleContext) string {
+func (compiler *baseCompiler) getStemWithoutSuffix(ctx android.BaseModuleContext) string {
 	stem := ctx.ModuleName()
 	if String(compiler.Properties.Stem) != "" {
 		stem = String(compiler.Properties.Stem)
diff --git a/rust/compiler_test.go b/rust/compiler_test.go
index 89f4d1a..4caa12b 100644
--- a/rust/compiler_test.go
+++ b/rust/compiler_test.go
@@ -63,6 +63,35 @@
 	}
 }
 
+func TestLtoFlag(t *testing.T) {
+	ctx := testRust(t, `
+		rust_library_host {
+			name: "libfoo",
+			srcs: ["foo.rs"],
+			crate_name: "foo",
+			lto: {
+				thin: false,
+			}
+		}
+
+		rust_library_host {
+			name: "libfoo_lto",
+			srcs: ["foo.rs"],
+			crate_name: "foo",
+		}
+		`)
+
+	libfoo := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib").Rule("rustc")
+	libfooLto := ctx.ModuleForTests("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"])
+	}
+	if !strings.Contains(libfooLto.Args["rustcFlags"], "-C lto=thin") {
+		t.Fatalf("libfoo expected to enable lto by default -- rustcFlags: %#v", libfooLto.Args["rustcFlags"])
+	}
+}
+
 // Test that we reject multiple source files.
 func TestEnforceSingleSourceFile(t *testing.T) {
 
diff --git a/rust/config/darwin_host.go b/rust/config/darwin_host.go
index 03bea82..a4bc187 100644
--- a/rust/config/darwin_host.go
+++ b/rust/config/darwin_host.go
@@ -21,7 +21,7 @@
 )
 
 var (
-	DarwinRustFlags     = []string{}
+	DarwinRustFlags     = []string{"-C split-debuginfo=off"}
 	DarwinRustLinkFlags = []string{
 		"-B${cc_config.MacToolPath}",
 	}
diff --git a/rust/config/global.go b/rust/config/global.go
index ba08560..6943467 100644
--- a/rust/config/global.go
+++ b/rust/config/global.go
@@ -24,7 +24,7 @@
 var (
 	pctx = android.NewPackageContext("android/soong/rust/config")
 
-	RustDefaultVersion = "1.77.1"
+	RustDefaultVersion = "1.78.0"
 	RustDefaultBase    = "prebuilts/rust/"
 	DefaultEdition     = "2021"
 	Stdlibs            = []string{
@@ -53,6 +53,9 @@
 		"--color=always",
 		"-Z dylib-lto",
 		"-Z link-native-libraries=no",
+
+		// cfg flag to indicate that we are building in AOSP with Soong
+		"--cfg soong",
 	}
 
 	LinuxHostGlobalLinkFlags = []string{
diff --git a/rust/doc.go b/rust/doc.go
index 6970d79..fe20523 100644
--- a/rust/doc.go
+++ b/rust/doc.go
@@ -38,7 +38,7 @@
 		FlagWithArg("-D ", docDir.String())
 
 	ctx.VisitAllModules(func(module android.Module) {
-		if !module.Enabled() {
+		if !module.Enabled(ctx) {
 			return
 		}
 
diff --git a/rust/fuzz_test.go b/rust/fuzz_test.go
index ee28c6d..6cb8b93 100644
--- a/rust/fuzz_test.go
+++ b/rust/fuzz_test.go
@@ -114,6 +114,12 @@
 				srcs: ["foo.rs"],
 				shared_libs: ["libcc_transitive_dep"],
 			}
+			rust_ffi_static {
+				name: "libtest_fuzzing_static",
+				crate_name: "test_fuzzing",
+				srcs: ["foo.rs"],
+				shared_libs: ["libcc_transitive_dep"],
+			}
 			cc_fuzz {
 				name: "fuzz_shared_libtest",
 				shared_libs: ["libtest_fuzzing"],
@@ -122,11 +128,15 @@
 				name: "fuzz_static_libtest",
 				static_libs: ["libtest_fuzzing"],
 			}
-
+			cc_fuzz {
+				name: "fuzz_staticffi_libtest",
+				static_libs: ["libtest_fuzzing_static"],
+			}
 	`)
 
 	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)
 
 	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())
@@ -134,4 +144,7 @@
 	if !strings.Contains(fuzz_static_libtest.FuzzSharedLibraries().String(), ":libcc_transitive_dep.so") {
 		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())
+	}
 }
diff --git a/rust/image.go b/rust/image.go
index e0d267d..26929b1 100644
--- a/rust/image.go
+++ b/rust/image.go
@@ -77,6 +77,14 @@
 	mod.Properties.CoreVariantNeeded = b
 }
 
+func (mod *Module) SetProductVariantNeeded(b bool) {
+	mod.Properties.ProductVariantNeeded = b
+}
+
+func (mod *Module) SetVendorVariantNeeded(b bool) {
+	mod.Properties.VendorVariantNeeded = b
+}
+
 func (mod *Module) SnapshotVersion(mctx android.BaseModuleContext) string {
 	if snapshot, ok := mod.compiler.(cc.SnapshotInterface); ok {
 		return snapshot.Version()
@@ -86,6 +94,14 @@
 	}
 }
 
+func (mod *Module) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
+	return mod.Properties.VendorVariantNeeded
+}
+
+func (mod *Module) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
+	return mod.Properties.ProductVariantNeeded
+}
+
 func (mod *Module) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
 	return mod.Properties.VendorRamdiskVariantNeeded
 }
@@ -184,12 +200,12 @@
 }
 
 func (mod *Module) InProduct() bool {
-	return mod.Properties.ImageVariation == cc.ProductVariation
+	return mod.Properties.ImageVariation == android.ProductVariation
 }
 
 // Returns true if the module is "vendor" variant. Usually these modules are installed in /vendor
 func (mod *Module) InVendor() bool {
-	return mod.Properties.ImageVariation == cc.VendorVariation
+	return mod.Properties.ImageVariation == android.VendorVariation
 }
 
 // Returns true if the module is "vendor" or "product" variant.
@@ -197,21 +213,20 @@
 	return mod.InVendor() || mod.InProduct()
 }
 
-func (mod *Module) SetImageVariation(ctx android.BaseModuleContext, variant string, module android.Module) {
-	m := module.(*Module)
+func (mod *Module) SetImageVariation(ctx android.BaseModuleContext, variant string) {
 	if variant == android.VendorRamdiskVariation {
-		m.MakeAsPlatform()
+		mod.MakeAsPlatform()
 	} else if variant == android.RecoveryVariation {
-		m.MakeAsPlatform()
-	} else if strings.HasPrefix(variant, cc.VendorVariation) {
-		m.Properties.ImageVariation = cc.VendorVariation
+		mod.MakeAsPlatform()
+	} else if strings.HasPrefix(variant, android.VendorVariation) {
+		mod.Properties.ImageVariation = android.VendorVariation
 		if strings.HasPrefix(variant, cc.VendorVariationPrefix) {
-			m.Properties.VndkVersion = strings.TrimPrefix(variant, cc.VendorVariationPrefix)
+			mod.Properties.VndkVersion = strings.TrimPrefix(variant, cc.VendorVariationPrefix)
 		}
-	} else if strings.HasPrefix(variant, cc.ProductVariation) {
-		m.Properties.ImageVariation = cc.ProductVariation
+	} else if strings.HasPrefix(variant, android.ProductVariation) {
+		mod.Properties.ImageVariation = android.ProductVariation
 		if strings.HasPrefix(variant, cc.ProductVariationPrefix) {
-			m.Properties.VndkVersion = strings.TrimPrefix(variant, cc.ProductVariationPrefix)
+			mod.Properties.VndkVersion = strings.TrimPrefix(variant, cc.ProductVariationPrefix)
 		}
 	}
 }
diff --git a/rust/image_test.go b/rust/image_test.go
index ba94906..d84eb10 100644
--- a/rust/image_test.go
+++ b/rust/image_test.go
@@ -22,33 +22,47 @@
 	"android/soong/cc"
 )
 
-// Test that cc modules can link against vendor_available rust_ffi_static libraries.
+// Test that cc modules can depend on vendor_available rust_ffi_rlib/rust_ffi_static libraries.
 func TestVendorLinkage(t *testing.T) {
 	ctx := testRust(t, `
 			cc_binary {
-				name: "fizz_vendor",
+				name: "fizz_vendor_available",
+				static_libs: [
+					"libfoo_vendor",
+					"libfoo_vendor_static"
+				],
+				vendor_available: true,
+			}
+			cc_binary {
+				name: "fizz_soc_specific",
 				static_libs: ["libfoo_vendor"],
 				soc_specific: true,
 			}
-			rust_ffi_static {
+			rust_ffi_rlib {
 				name: "libfoo_vendor",
 				crate_name: "foo",
 				srcs: ["foo.rs"],
 				vendor_available: true,
 			}
+			rust_ffi_static {
+				name: "libfoo_vendor_static",
+				crate_name: "foo",
+				srcs: ["foo.rs"],
+				vendor_available: true,
+			}
 		`)
 
-	vendorBinary := ctx.ModuleForTests("fizz_vendor", "android_vendor_arm64_armv8-a").Module().(*cc.Module)
+	vendorBinary := ctx.ModuleForTests("fizz_vendor_available", "android_vendor_arm64_armv8-a").Module().(*cc.Module)
 
-	if !android.InList("libfoo_vendor.vendor", vendorBinary.Properties.AndroidMkStaticLibs) {
-		t.Errorf("vendorBinary should have a dependency on libfoo_vendor: %#v", vendorBinary.Properties.AndroidMkStaticLibs)
+	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)
 	}
 }
 
 // Test that variants which use the vndk emit the appropriate cfg flag.
 func TestImageCfgFlag(t *testing.T) {
 	ctx := testRust(t, `
-			rust_ffi_static {
+			rust_ffi_shared {
 				name: "libfoo",
 				crate_name: "foo",
 				srcs: ["foo.rs"],
@@ -57,7 +71,7 @@
 			}
 		`)
 
-	vendor := ctx.ModuleForTests("libfoo", "android_vendor_arm64_armv8-a_static").Rule("rustc")
+	vendor := ctx.ModuleForTests("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"])
@@ -69,7 +83,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_static").Rule("rustc")
+	product := ctx.ModuleForTests("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"])
 	}
@@ -80,7 +94,7 @@
 		t.Errorf("missing \"--cfg 'android_product'\" for libfoo product variant, rustcFlags: %#v", product.Args["rustcFlags"])
 	}
 
-	system := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_static").Rule("rustc")
+	system := ctx.ModuleForTests("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"])
 	}
@@ -93,27 +107,36 @@
 
 }
 
-// Test that cc modules can link against vendor_ramdisk_available rust_ffi_static libraries.
+// Test that cc modules can link against vendor_ramdisk_available rust_ffi_rlib and rust_ffi_static libraries.
 func TestVendorRamdiskLinkage(t *testing.T) {
 	ctx := testRust(t, `
-			cc_library_static {
+			cc_library_shared {
 				name: "libcc_vendor_ramdisk",
-				static_libs: ["libfoo_vendor_ramdisk"],
+				static_libs: [
+					"libfoo_vendor_ramdisk",
+					"libfoo_static_vendor_ramdisk"
+				],
 				system_shared_libs: [],
 				vendor_ramdisk_available: true,
 			}
-			rust_ffi_static {
+			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",
+				crate_name: "foo",
+				srcs: ["foo.rs"],
+				vendor_ramdisk_available: true,
+			}
 		`)
 
-	vendorRamdiskLibrary := ctx.ModuleForTests("libcc_vendor_ramdisk", "android_vendor_ramdisk_arm64_armv8-a_static").Module().(*cc.Module)
+	vendorRamdiskLibrary := ctx.ModuleForTests("libcc_vendor_ramdisk", "android_vendor_ramdisk_arm64_armv8-a_shared").Module().(*cc.Module)
 
-	if !android.InList("libfoo_vendor_ramdisk.vendor_ramdisk", vendorRamdiskLibrary.Properties.AndroidMkStaticLibs) {
-		t.Errorf("libcc_vendor_ramdisk should have a dependency on libfoo_vendor_ramdisk")
+	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")
 	}
 }
 
diff --git a/rust/library.go b/rust/library.go
index 6be4917..50d5a72 100644
--- a/rust/library.go
+++ b/rust/library.go
@@ -20,6 +20,8 @@
 	"regexp"
 	"strings"
 
+	"github.com/google/blueprint"
+
 	"android/soong/android"
 	"android/soong/cc"
 )
@@ -37,10 +39,15 @@
 	android.RegisterModuleType("rust_library_host_rlib", RustLibraryRlibHostFactory)
 	android.RegisterModuleType("rust_ffi", RustFFIFactory)
 	android.RegisterModuleType("rust_ffi_shared", RustFFISharedFactory)
-	android.RegisterModuleType("rust_ffi_static", RustFFIStaticFactory)
+	android.RegisterModuleType("rust_ffi_rlib", RustFFIRlibFactory)
 	android.RegisterModuleType("rust_ffi_host", RustFFIHostFactory)
 	android.RegisterModuleType("rust_ffi_host_shared", RustFFISharedHostFactory)
-	android.RegisterModuleType("rust_ffi_host_static", RustFFIStaticHostFactory)
+	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)
 }
 
 type VariantLibraryProperties struct {
@@ -81,8 +88,6 @@
 	VariantIsRlib bool `blueprint:"mutated"`
 	// This variant is a shared library
 	VariantIsShared bool `blueprint:"mutated"`
-	// This variant is a static library
-	VariantIsStatic bool `blueprint:"mutated"`
 	// This variant is a source provider
 	VariantIsSource bool `blueprint:"mutated"`
 
@@ -104,6 +109,8 @@
 	includeDirs       android.Paths
 	sourceProvider    SourceProvider
 
+	isFFI bool
+
 	// table-of-contents file for cdylib crates to optimize out relinking when possible
 	tocFile android.OptionalPath
 }
@@ -143,6 +150,8 @@
 	BuildOnlyShared()
 
 	toc() android.OptionalPath
+
+	isFFILibrary() bool
 }
 
 func (library *libraryDecorator) nativeCoverage() bool {
@@ -170,7 +179,7 @@
 }
 
 func (library *libraryDecorator) static() bool {
-	return library.MutatedProperties.VariantIsStatic
+	return false
 }
 
 func (library *libraryDecorator) source() bool {
@@ -196,14 +205,12 @@
 func (library *libraryDecorator) setRlib() {
 	library.MutatedProperties.VariantIsRlib = true
 	library.MutatedProperties.VariantIsDylib = false
-	library.MutatedProperties.VariantIsStatic = false
 	library.MutatedProperties.VariantIsShared = false
 }
 
 func (library *libraryDecorator) setDylib() {
 	library.MutatedProperties.VariantIsRlib = false
 	library.MutatedProperties.VariantIsDylib = true
-	library.MutatedProperties.VariantIsStatic = false
 	library.MutatedProperties.VariantIsShared = false
 }
 
@@ -220,17 +227,13 @@
 }
 
 func (library *libraryDecorator) setShared() {
-	library.MutatedProperties.VariantIsStatic = false
 	library.MutatedProperties.VariantIsShared = true
 	library.MutatedProperties.VariantIsRlib = false
 	library.MutatedProperties.VariantIsDylib = false
 }
 
 func (library *libraryDecorator) setStatic() {
-	library.MutatedProperties.VariantIsStatic = true
-	library.MutatedProperties.VariantIsShared = false
-	library.MutatedProperties.VariantIsRlib = false
-	library.MutatedProperties.VariantIsDylib = false
+	panic(fmt.Errorf("static variant is not supported for rust modules, use the rlib variant instead"))
 }
 
 func (library *libraryDecorator) setSource() {
@@ -250,7 +253,7 @@
 }
 
 func (library *libraryDecorator) stdLinkage(ctx *depsContext) RustLinkage {
-	if library.static() || library.MutatedProperties.VariantIsStaticStd {
+	if library.static() || library.MutatedProperties.VariantIsStaticStd || (library.rlib() && library.isFFILibrary()) {
 		return RlibLinkage
 	} else if library.baseCompiler.preferRlib() {
 		return RlibLinkage
@@ -270,8 +273,8 @@
 	return module.Init()
 }
 
-// rust_ffi produces all FFI variants (rust_ffi_shared and
-// rust_ffi_static).
+// rust_ffi produces all FFI variants (rust_ffi_shared, rust_ffi_static, and
+// rust_ffi_rlib).
 func RustFFIFactory() android.Module {
 	module, library := NewRustLibrary(android.HostAndDeviceSupported)
 	library.BuildOnlyFFI()
@@ -300,14 +303,6 @@
 	return module.Init()
 }
 
-// rust_ffi_static produces a static library (Rust crate type
-// "staticlib").
-func RustFFIStaticFactory() android.Module {
-	module, library := NewRustLibrary(android.HostAndDeviceSupported)
-	library.BuildOnlyStatic()
-	return module.Init()
-}
-
 // rust_library_host produces all Rust variants for the host
 // (rust_library_dylib_host and rust_library_rlib_host).
 func RustLibraryHostFactory() android.Module {
@@ -317,7 +312,7 @@
 }
 
 // rust_ffi_host produces all FFI variants for the host
-// (rust_ffi_static_host and rust_ffi_shared_host).
+// (rust_ffi_rlib_host, rust_ffi_static_host, and rust_ffi_shared_host).
 func RustFFIHostFactory() android.Module {
 	module, library := NewRustLibrary(android.HostSupported)
 	library.BuildOnlyFFI()
@@ -340,14 +335,6 @@
 	return module.Init()
 }
 
-// rust_ffi_static_host produces a static library for the host (Rust
-// crate type "staticlib").
-func RustFFIStaticHostFactory() android.Module {
-	module, library := NewRustLibrary(android.HostSupported)
-	library.BuildOnlyStatic()
-	return module.Init()
-}
-
 // rust_ffi_shared_host produces an shared library for the host (Rust
 // crate type "cdylib").
 func RustFFISharedHostFactory() android.Module {
@@ -356,11 +343,33 @@
 	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()
+
+	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()
+}
+
 func (library *libraryDecorator) BuildOnlyFFI() {
 	library.MutatedProperties.BuildDylib = false
-	library.MutatedProperties.BuildRlib = false
+	// we build rlibs for later static ffi linkage.
+	library.MutatedProperties.BuildRlib = true
 	library.MutatedProperties.BuildShared = true
-	library.MutatedProperties.BuildStatic = true
+	library.MutatedProperties.BuildStatic = false
+
+	library.isFFI = true
 }
 
 func (library *libraryDecorator) BuildOnlyRust() {
@@ -389,6 +398,8 @@
 	library.MutatedProperties.BuildDylib = false
 	library.MutatedProperties.BuildShared = false
 	library.MutatedProperties.BuildStatic = true
+
+	library.isFFI = true
 }
 
 func (library *libraryDecorator) BuildOnlyShared() {
@@ -396,6 +407,12 @@
 	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) {
@@ -446,17 +463,35 @@
 	return library.getStem(ctx) + ctx.toolchain().SharedLibSuffix()
 }
 
+// Library cfg flags common to all variants
+func CommonLibraryCfgFlags(ctx android.ModuleContext, flags Flags) Flags {
+	return flags
+}
+
 func (library *libraryDecorator) cfgFlags(ctx ModuleContext, flags Flags) Flags {
 	flags = library.baseCompiler.cfgFlags(ctx, flags)
+	flags = CommonLibraryCfgFlags(ctx, flags)
+
+	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.
-		library.baseCompiler.Properties.Cfgs = append(library.baseCompiler.Properties.Cfgs, "android_dylib")
+		cfgs = append(cfgs, "android_dylib")
 	}
 
-	flags.RustFlags = append(flags.RustFlags, library.baseCompiler.cfgsToFlags()...)
-	flags.RustdocFlags = append(flags.RustdocFlags, library.baseCompiler.cfgsToFlags()...)
+	cfgFlags := cfgsToFlags(cfgs)
+
+	flags.RustFlags = append(flags.RustFlags, cfgFlags...)
+	flags.RustdocFlags = append(flags.RustdocFlags, cfgFlags...)
+
+	return flags
+}
+
+// Common flags applied to all libraries irrespective of properties or variant should be included here
+func CommonLibraryCompilerFlags(ctx android.ModuleContext, flags Flags) Flags {
+	flags.RustFlags = append(flags.RustFlags, "-C metadata="+ctx.ModuleName())
 
 	return flags
 }
@@ -464,11 +499,13 @@
 func (library *libraryDecorator) compilerFlags(ctx ModuleContext, flags Flags) Flags {
 	flags = library.baseCompiler.compilerFlags(ctx, flags)
 
-	flags.RustFlags = append(flags.RustFlags, "-C metadata="+ctx.ModuleName())
-	if library.shared() || library.static() {
+	flags = CommonLibraryCompilerFlags(ctx, flags)
+
+	if library.isFFI {
 		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)...)
 	}
+
 	if library.shared() {
 		if ctx.Darwin() {
 			flags.LinkFlags = append(
@@ -494,6 +531,9 @@
 		deps.srcProviderFiles = append(deps.srcProviderFiles, library.sourceProvider.Srcs()...)
 	}
 
+	// Ensure link dirs are not duplicated
+	deps.linkDirs = android.FirstUniqueStrings(deps.linkDirs)
+
 	// Calculate output filename
 	if library.rlib() {
 		fileName = library.getStem(ctx) + ctx.toolchain().RlibSuffix()
@@ -549,9 +589,10 @@
 		library.flagExporter.exportLinkObjects(deps.linkObjects...)
 	}
 
-	if library.static() || library.shared() {
+	// 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{
-			IncludeDirs: library.includeDirs,
+			IncludeDirs: android.FirstUniquePaths(library.includeDirs),
 		})
 	}
 
@@ -653,26 +694,28 @@
 	}
 }
 
-// LibraryMutator mutates the libraries into variants according to the
-// build{Rlib,Dylib} attributes.
-func LibraryMutator(mctx android.BottomUpMutatorContext) {
-	// Only mutate on Rust libraries.
-	m, ok := mctx.Module().(*Module)
+type libraryTransitionMutator struct{}
+
+func (libraryTransitionMutator) Split(ctx android.BaseModuleContext) []string {
+	m, ok := ctx.Module().(*Module)
 	if !ok || m.compiler == nil {
-		return
+		return []string{""}
 	}
 	library, ok := m.compiler.(libraryInterface)
 	if !ok {
-		return
+		return []string{""}
+	}
+
+	// Don't produce rlib/dylib/source variants for shared or static variants
+	if library.shared() || library.static() {
+		return []string{""}
 	}
 
 	var variants []string
 	// The source variant is used for SourceProvider modules. The other variants (i.e. rlib and dylib)
 	// depend on this variant. It must be the first variant to be declared.
-	sourceVariant := false
 	if m.sourceProvider != nil {
-		variants = append(variants, "source")
-		sourceVariant = true
+		variants = append(variants, sourceVariation)
 	}
 	if library.buildRlib() {
 		variants = append(variants, rlibVariation)
@@ -682,68 +725,134 @@
 	}
 
 	if len(variants) == 0 {
+		return []string{""}
+	}
+
+	return variants
+}
+
+func (libraryTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+	return ""
+}
+
+func (libraryTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
+	m, ok := ctx.Module().(*Module)
+	if !ok || m.compiler == nil {
+		return ""
+	}
+	library, ok := m.compiler.(libraryInterface)
+	if !ok {
+		return ""
+	}
+
+	if incomingVariation == "" {
+		if m.sourceProvider != nil {
+			return sourceVariation
+		}
+		if library.shared() {
+			return ""
+		}
+		if library.buildRlib() {
+			return rlibVariation
+		}
+		if library.buildDylib() {
+			return dylibVariation
+		}
+	}
+	return incomingVariation
+}
+
+func (libraryTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
+	m, ok := ctx.Module().(*Module)
+	if !ok || m.compiler == nil {
 		return
 	}
-	modules := mctx.CreateLocalVariations(variants...)
+	library, ok := m.compiler.(libraryInterface)
+	if !ok {
+		return
+	}
 
-	// The order of the variations (modules) matches the variant names provided. Iterate
-	// through the new variation modules and set their mutated properties.
-	for i, v := range modules {
-		switch variants[i] {
-		case rlibVariation:
-			v.(*Module).compiler.(libraryInterface).setRlib()
-		case dylibVariation:
-			v.(*Module).compiler.(libraryInterface).setDylib()
-			if v.(*Module).ModuleBase.ImageVariation().Variation == android.VendorRamdiskVariation {
-				// TODO(b/165791368)
-				// Disable dylib Vendor Ramdisk variations until we support these.
-				v.(*Module).Disable()
-			}
-
-		case "source":
-			v.(*Module).compiler.(libraryInterface).setSource()
-			// The source variant does not produce any library.
-			// Disable the compilation steps.
-			v.(*Module).compiler.SetDisabled()
+	switch variation {
+	case rlibVariation:
+		library.setRlib()
+	case dylibVariation:
+		library.setDylib()
+		if m.ModuleBase.ImageVariation().Variation == android.VendorRamdiskVariation {
+			// TODO(b/165791368)
+			// Disable dylib Vendor Ramdisk variations until we support these.
+			m.Disable()
 		}
+
+	case sourceVariation:
+		library.setSource()
+		// The source variant does not produce any library.
+		// Disable the compilation steps.
+		m.compiler.SetDisabled()
 	}
 
 	// If a source variant is created, add an inter-variant dependency
 	// between the other variants and the source variant.
-	if sourceVariant {
-		sv := modules[0]
-		for _, v := range modules[1:] {
-			if !v.Enabled() {
-				continue
-			}
-			mctx.AddInterVariantDependency(sourceDepTag, v, sv)
-		}
-		// Alias the source variation so it can be named directly in "srcs" properties.
-		mctx.AliasVariation("source")
+	if m.sourceProvider != nil && variation != sourceVariation {
+		ctx.AddVariationDependencies(
+			[]blueprint.Variation{
+				{"rust_libraries", sourceVariation},
+			},
+			sourceDepTag, ctx.ModuleName())
 	}
 }
 
-func LibstdMutator(mctx android.BottomUpMutatorContext) {
-	if m, ok := mctx.Module().(*Module); ok && m.compiler != nil && !m.compiler.Disabled() {
-		switch library := m.compiler.(type) {
-		case libraryInterface:
-			// Only create a variant if a library is actually being built.
-			if library.rlib() && !library.sysroot() {
-				variants := []string{"rlib-std", "dylib-std"}
-				modules := mctx.CreateLocalVariations(variants...)
+type libstdTransitionMutator struct{}
 
-				rlib := modules[0].(*Module)
-				dylib := modules[1].(*Module)
-				rlib.compiler.(libraryInterface).setRlibStd()
-				dylib.compiler.(libraryInterface).setDylibStd()
-				if dylib.ModuleBase.ImageVariation().Variation == android.VendorRamdiskVariation {
-					// TODO(b/165791368)
-					// Disable rlibs that link against dylib-std on vendor ramdisk variations until those dylib
-					// variants are properly supported.
-					dylib.Disable()
+func (libstdTransitionMutator) Split(ctx android.BaseModuleContext) []string {
+	if m, ok := ctx.Module().(*Module); ok && m.compiler != nil && !m.compiler.Disabled() {
+		// 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"}
 				}
-				rlib.Properties.RustSubName += RlibStdlibSuffix
 			}
 		}
 	}
+	return []string{""}
+}
+
+func (libstdTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+	return ""
+}
+
+func (libstdTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
+	if m, ok := ctx.Module().(*Module); ok && m.compiler != nil && !m.compiler.Disabled() {
+		if library, ok := m.compiler.(libraryInterface); ok {
+			if library.shared() {
+				return ""
+			}
+			if library.rlib() && !library.sysroot() {
+				if incomingVariation != "" {
+					return incomingVariation
+				}
+				return "rlib-std"
+			}
+		}
+	}
+	return ""
+}
+
+func (libstdTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
+	if variation == "rlib-std" {
+		rlib := ctx.Module().(*Module)
+		rlib.compiler.(libraryInterface).setRlibStd()
+		rlib.Properties.RustSubName += RlibStdlibSuffix
+	} else if variation == "dylib-std" {
+		dylib := ctx.Module().(*Module)
+		dylib.compiler.(libraryInterface).setDylibStd()
+		if dylib.ModuleBase.ImageVariation().Variation == android.VendorRamdiskVariation {
+			// TODO(b/165791368)
+			// Disable rlibs that link against dylib-std on vendor ramdisk variations until those dylib
+			// variants are properly supported.
+			dylib.Disable()
+		}
+	}
 }
diff --git a/rust/library_test.go b/rust/library_test.go
index 7275b66..35a420c 100644
--- a/rust/library_test.go
+++ b/rust/library_test.go
@@ -34,18 +34,22 @@
 			name: "libfoo.ffi",
 			srcs: ["foo.rs"],
 			crate_name: "foo"
+		}
+		rust_ffi_host_static {
+			name: "libfoo.ffi_static",
+			srcs: ["foo.rs"],
+			crate_name: "foo"
 		}`)
 
 	// 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")
-	libfooStatic := ctx.ModuleForTests("libfoo.ffi", "linux_glibc_x86_64_static").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")
 
 	rlibCrateType := "rlib"
 	dylibCrateType := "dylib"
 	sharedCrateType := "cdylib"
-	staticCrateType := "staticlib"
 
 	// Test crate type for rlib is correct.
 	if !strings.Contains(libfooRlib.Args["rustcFlags"], "crate-type="+rlibCrateType) {
@@ -57,9 +61,9 @@
 		t.Errorf("missing crate-type for static variant, expecting %#v, rustcFlags: %#v", dylibCrateType, libfooDylib.Args["rustcFlags"])
 	}
 
-	// Test crate type for C static libraries is correct.
-	if !strings.Contains(libfooStatic.Args["rustcFlags"], "crate-type="+staticCrateType) {
-		t.Errorf("missing crate-type for static variant, expecting %#v, rustcFlags: %#v", staticCrateType, libfooStatic.Args["rustcFlags"])
+	// Test crate type for FFI rlibs is correct
+	if !strings.Contains(libfooFFIRlib.Args["rustcFlags"], "crate-type="+rlibCrateType) {
+		t.Errorf("missing crate-type for static variant, expecting %#v, rustcFlags: %#v", rlibCrateType, libfooFFIRlib.Args["rustcFlags"])
 	}
 
 	// Test crate type for C shared libraries is correct.
@@ -188,19 +192,19 @@
 			crate_name: "foo",
 		}`)
 
-	libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_static")
+	libfoo := ctx.ModuleForTests("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 static libraries. Rlib deps are: %#v",
+		t.Errorf("Static libstd rlib expected to be a dependency of Rust rlib libraries. Rlib deps are: %#v",
 			libfoo.Module().(*Module).Properties.AndroidMkDylibs)
 	}
 }
 
 func TestNativeDependencyOfRlib(t *testing.T) {
 	ctx := testRust(t, `
-		rust_ffi_static {
-			name: "libffi_static",
-			crate_name: "ffi_static",
+		rust_ffi_rlib {
+			name: "libffi_rlib",
+			crate_name: "ffi_rlib",
 			rlibs: ["librust_rlib"],
 			srcs: ["foo.rs"],
 		}
@@ -208,27 +212,27 @@
 			name: "librust_rlib",
 			crate_name: "rust_rlib",
 			srcs: ["foo.rs"],
-			shared_libs: ["shared_cc_dep"],
-			static_libs: ["static_cc_dep"],
+			shared_libs: ["libshared_cc_dep"],
+			static_libs: ["libstatic_cc_dep"],
 		}
 		cc_library_shared {
-			name: "shared_cc_dep",
+			name: "libshared_cc_dep",
 			srcs: ["foo.cpp"],
 		}
 		cc_library_static {
-			name: "static_cc_dep",
+			name: "libstatic_cc_dep",
 			srcs: ["foo.cpp"],
 		}
 		`)
 
 	rustRlibRlibStd := ctx.ModuleForTests("librust_rlib", "android_arm64_armv8-a_rlib_rlib-std")
 	rustRlibDylibStd := ctx.ModuleForTests("librust_rlib", "android_arm64_armv8-a_rlib_dylib-std")
-	ffiStatic := ctx.ModuleForTests("libffi_static", "android_arm64_armv8-a_static")
+	ffiRlib := ctx.ModuleForTests("libffi_rlib", "android_arm64_armv8-a_rlib_rlib-std")
 
 	modules := []android.TestingModule{
 		rustRlibRlibStd,
 		rustRlibDylibStd,
-		ffiStatic,
+		ffiRlib,
 	}
 
 	// librust_rlib specifies -L flag to cc deps output directory on rustc command
@@ -239,17 +243,17 @@
 	// TODO: We could consider removing these flags
 	for _, module := range modules {
 		if !strings.Contains(module.Rule("rustc").Args["libFlags"],
-			"-L out/soong/.intermediates/shared_cc_dep/android_arm64_armv8-a_shared/") {
+			"-L out/soong/.intermediates/libshared_cc_dep/android_arm64_armv8-a_shared/") {
 			t.Errorf(
-				"missing -L flag for shared_cc_dep, rustcFlags: %#v",
-				rustRlibRlibStd.Rule("rustc").Args["libFlags"],
+				"missing -L flag for libshared_cc_dep of %s, rustcFlags: %#v",
+				module.Module().Name(), rustRlibRlibStd.Rule("rustc").Args["libFlags"],
 			)
 		}
 		if !strings.Contains(module.Rule("rustc").Args["libFlags"],
-			"-L out/soong/.intermediates/static_cc_dep/android_arm64_armv8-a_static/") {
+			"-L out/soong/.intermediates/libstatic_cc_dep/android_arm64_armv8-a_static/") {
 			t.Errorf(
-				"missing -L flag for static_cc_dep, rustcFlags: %#v",
-				rustRlibRlibStd.Rule("rustc").Args["libFlags"],
+				"missing -L flag for libstatic_cc_dep of %s, rustcFlags: %#v",
+				module.Module().Name(), rustRlibRlibStd.Rule("rustc").Args["libFlags"],
 			)
 		}
 	}
@@ -286,31 +290,40 @@
 				"libbar",
 				"librlib_only",
 			],
+		}
+		rust_ffi_host_static {
+			name: "libfoo.ffi.static",
+			srcs: ["foo.rs"],
+			crate_name: "foo",
+			rustlibs: [
+				"libbar",
+				"librlib_only",
+			],
 		}`)
 
 	libfooRlib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_rlib_rlib-std")
 	libfooDylib := ctx.ModuleForTests("libfoo", "linux_glibc_x86_64_dylib")
-	libfooStatic := ctx.ModuleForTests("libfoo.ffi", "linux_glibc_x86_64_static")
+	libfooFFIRlib := ctx.ModuleForTests("libfoo.ffi", "linux_glibc_x86_64_rlib_rlib-std")
 	libfooShared := ctx.ModuleForTests("libfoo.ffi", "linux_glibc_x86_64_shared")
 
-	for _, static := range []android.TestingModule{libfooRlib, libfooStatic} {
+	for _, static := range []android.TestingModule{libfooRlib, libfooFFIRlib} {
 		if !android.InList("libbar.rlib-std", static.Module().(*Module).Properties.AndroidMkRlibs) {
-			t.Errorf("libbar not present as rlib dependency in static lib")
+			t.Errorf("libbar not present as rlib dependency in static lib: %s", static.Module().Name())
 		}
 		if android.InList("libbar", static.Module().(*Module).Properties.AndroidMkDylibs) {
-			t.Errorf("libbar present as dynamic dependency in static lib")
+			t.Errorf("libbar present as dynamic dependency in static lib: %s", static.Module().Name())
 		}
 	}
 
 	for _, dyn := range []android.TestingModule{libfooDylib, libfooShared} {
 		if !android.InList("libbar", dyn.Module().(*Module).Properties.AndroidMkDylibs) {
-			t.Errorf("libbar not present as dynamic dependency in dynamic lib")
+			t.Errorf("libbar not present as dynamic dependency in dynamic lib: %s", dyn.Module().Name())
 		}
 		if android.InList("libbar", dyn.Module().(*Module).Properties.AndroidMkRlibs) {
-			t.Errorf("libbar present as rlib dependency in dynamic lib")
+			t.Errorf("libbar present as rlib dependency in dynamic lib: %s", dyn.Module().Name())
 		}
 		if !android.InList("librlib_only", dyn.Module().(*Module).Properties.AndroidMkRlibs) {
-			t.Errorf("librlib_only should be selected by rustlibs as an rlib.")
+			t.Errorf("librlib_only should be selected by rustlibs as an rlib: %s.", dyn.Module().Name())
 		}
 	}
 }
@@ -361,6 +374,12 @@
 			crate_name: "bar",
 			rustlibs: ["libfoo"],
 		}
+		rust_ffi_static {
+			name: "libbar_static",
+			srcs: ["foo.rs"],
+			crate_name: "bar",
+			rustlibs: ["libfoo"],
+		}
 		rust_ffi {
 			name: "libbar.prefer_rlib",
 			srcs: ["foo.rs"],
@@ -374,7 +393,7 @@
 	libfooRlibDynamic := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_rlib_dylib-std").Module().(*Module)
 
 	libbarShared := ctx.ModuleForTests("libbar", "android_arm64_armv8-a_shared").Module().(*Module)
-	libbarStatic := ctx.ModuleForTests("libbar", "android_arm64_armv8-a_static").Module().(*Module)
+	libbarFFIRlib := ctx.ModuleForTests("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)
@@ -392,11 +411,11 @@
 	if !android.InList("libstd", libbarShared.Properties.AndroidMkDylibs) {
 		t.Errorf("Device rust_ffi_shared does not link libstd as an dylib")
 	}
-	if !android.InList("libstd", libbarStatic.Properties.AndroidMkRlibs) {
-		t.Errorf("Device rust_ffi_static does not link libstd as an rlib")
+	if !android.InList("libstd", libbarFFIRlib.Properties.AndroidMkRlibs) {
+		t.Errorf("Device rust_ffi_rlib does not link libstd as an rlib")
 	}
-	if !android.InList("libfoo.rlib-std", libbarStatic.Properties.AndroidMkRlibs) {
-		t.Errorf("Device rust_ffi_static does not link dependent rustlib rlib-std variant")
+	if !android.InList("libfoo.rlib-std", libbarFFIRlib.Properties.AndroidMkRlibs) {
+		t.Errorf("Device rust_ffi_rlib 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")
diff --git a/rust/proc_macro.go b/rust/proc_macro.go
index b491449..1ff6637 100644
--- a/rust/proc_macro.go
+++ b/rust/proc_macro.go
@@ -76,6 +76,7 @@
 	srcPath := crateRootPath(ctx, procMacro)
 	ret := TransformSrctoProcMacro(ctx, srcPath, deps, flags, outputFile)
 	procMacro.baseCompiler.unstrippedOutputFile = outputFile
+
 	return ret
 }
 
diff --git a/rust/project_json.go b/rust/project_json.go
index ad9b690..24dcc89 100644
--- a/rust/project_json.go
+++ b/rust/project_json.go
@@ -96,7 +96,7 @@
 		var childId int
 		cInfo, known := singleton.knownCrates[rChild.Name()]
 		if !known {
-			childId, ok = singleton.addCrate(ctx, rChild, make(map[string]int))
+			childId, ok = singleton.addCrate(ctx, rChild)
 			if !ok {
 				return
 			}
@@ -119,7 +119,7 @@
 	if !ok {
 		return nil, false
 	}
-	if !rModule.Enabled() {
+	if !rModule.Enabled(ctx) {
 		return nil, false
 	}
 	return rModule, true
@@ -128,7 +128,8 @@
 // addCrate adds a crate to singleton.project.Crates ensuring that required
 // dependencies are also added. It returns the index of the new crate in
 // singleton.project.Crates
-func (singleton *projectGeneratorSingleton) addCrate(ctx android.SingletonContext, rModule *Module, deps map[string]int) (int, bool) {
+func (singleton *projectGeneratorSingleton) addCrate(ctx android.SingletonContext, rModule *Module) (int, bool) {
+	deps := make(map[string]int)
 	rootModule, err := rModule.compiler.checkedCrateRootPath()
 	if err != nil {
 		return 0, false
@@ -180,7 +181,7 @@
 	if cInfo, ok := singleton.knownCrates[module.Name()]; ok {
 		// If we have a new device variant, override the old one
 		if !cInfo.Device && rModule.Device() {
-			singleton.addCrate(ctx, rModule, cInfo.Deps)
+			singleton.addCrate(ctx, rModule)
 			return
 		}
 		crate := singleton.project.Crates[cInfo.Idx]
@@ -188,7 +189,7 @@
 		singleton.project.Crates[cInfo.Idx] = crate
 		return
 	}
-	singleton.addCrate(ctx, rModule, make(map[string]int))
+	singleton.addCrate(ctx, rModule)
 }
 
 func (singleton *projectGeneratorSingleton) GenerateBuildActions(ctx android.SingletonContext) {
diff --git a/rust/rust.go b/rust/rust.go
index c2b6151..3402adc 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -16,6 +16,7 @@
 
 import (
 	"fmt"
+	"strconv"
 	"strings"
 
 	"android/soong/bloaty"
@@ -36,20 +37,24 @@
 
 func init() {
 	android.RegisterModuleType("rust_defaults", defaultsFactory)
-	android.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
-		ctx.BottomUp("rust_libraries", LibraryMutator).Parallel()
-		ctx.BottomUp("rust_stdlinkage", LibstdMutator).Parallel()
-		ctx.BottomUp("rust_begin", BeginMutator).Parallel()
-	})
-	android.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
-		ctx.BottomUp("rust_sanitizers", rustSanitizerRuntimeMutator).Parallel()
-	})
+	android.PreDepsMutators(registerPreDepsMutators)
+	android.PostDepsMutators(registerPostDepsMutators)
 	pctx.Import("android/soong/android")
 	pctx.Import("android/soong/rust/config")
 	pctx.ImportAs("cc_config", "android/soong/cc/config")
 	android.InitRegistrationContext.RegisterParallelSingletonType("kythe_rust_extract", kytheExtractRustFactory)
 }
 
+func registerPreDepsMutators(ctx android.RegisterMutatorsContext) {
+	ctx.Transition("rust_libraries", &libraryTransitionMutator{})
+	ctx.Transition("rust_stdlinkage", &libstdTransitionMutator{})
+	ctx.BottomUp("rust_begin", BeginMutator).Parallel()
+}
+
+func registerPostDepsMutators(ctx android.RegisterMutatorsContext) {
+	ctx.BottomUp("rust_sanitizers", rustSanitizerRuntimeMutator).Parallel()
+}
+
 type Flags struct {
 	GlobalRustFlags []string // Flags that apply globally to rust
 	GlobalLinkFlags []string // Flags that apply globally to linker
@@ -80,6 +85,8 @@
 	RustSubName string `blueprint:"mutated"`
 
 	// Set by imageMutator
+	ProductVariantNeeded       bool     `blueprint:"mutated"`
+	VendorVariantNeeded        bool     `blueprint:"mutated"`
 	CoreVariantNeeded          bool     `blueprint:"mutated"`
 	VendorRamdiskVariantNeeded bool     `blueprint:"mutated"`
 	RamdiskVariantNeeded       bool     `blueprint:"mutated"`
@@ -158,6 +165,8 @@
 	sourceProvider   SourceProvider
 	subAndroidMkOnce map[SubAndroidMkProvider]bool
 
+	exportedLinkDirs []string
+
 	// Output file to be installed, may be stripped or unstripped.
 	outputFile android.OptionalPath
 
@@ -172,9 +181,6 @@
 	apexSdkVersion android.ApiLevel
 
 	transitiveAndroidMkSharedLibs *android.DepSet[string]
-
-	// Aconfig files for all transitive deps.  Also exposed via TransitiveDeclarationsInfo
-	mergedAconfigFiles map[string]android.Paths
 }
 
 func (mod *Module) Header() bool {
@@ -207,35 +213,14 @@
 	return false
 }
 
-func (mod *Module) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		if mod.sourceProvider != nil && (mod.compiler == nil || mod.compiler.Disabled()) {
-			return mod.sourceProvider.Srcs(), nil
-		} else {
-			if mod.OutputFile().Valid() {
-				return android.Paths{mod.OutputFile().Path()}, nil
-			}
-			return android.Paths{}, nil
-		}
-	case "unstripped":
-		if mod.compiler != nil {
-			return android.PathsIfNonNil(mod.compiler.unstrippedOutputFilePath()), nil
-		}
-		return nil, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
 func (mod *Module) SelectedStl() string {
 	return ""
 }
 
 func (mod *Module) NonCcVariants() bool {
 	if mod.compiler != nil {
-		if _, ok := mod.compiler.(libraryInterface); ok {
-			return false
+		if library, ok := mod.compiler.(libraryInterface); ok {
+			return library.buildRlib() || library.buildDylib()
 		}
 	}
 	panic(fmt.Errorf("NonCcVariants called on non-library module: %q", mod.BaseModuleName()))
@@ -342,27 +327,10 @@
 	return Bool(mod.Properties.Bootstrap)
 }
 
-func (mod *Module) MustUseVendorVariant() bool {
-	return true
-}
-
 func (mod *Module) SubName() string {
 	return mod.Properties.SubName
 }
 
-func (mod *Module) IsVndk() bool {
-	// TODO(b/165791368)
-	return false
-}
-
-func (mod *Module) IsVndkExt() bool {
-	return false
-}
-
-func (mod *Module) IsVndkSp() bool {
-	return false
-}
-
 func (mod *Module) IsVndkPrebuiltLibrary() bool {
 	// Rust modules do not provide VNDK prebuilts
 	return false
@@ -385,10 +353,6 @@
 	return false
 }
 
-func (c *Module) IsLlndkPublic() bool {
-	return false
-}
-
 func (mod *Module) KernelHeadersDecorator() bool {
 	return false
 }
@@ -460,11 +424,16 @@
 	depFlags     []string
 	depLinkFlags []string
 
-	// linkDirs are link paths passed via -L to rustc. linkObjects are objects passed directly to the linker.
+	// 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
 
+	// exportedLinkDirs are exported linkDirs for direct rlib dependencies to
+	// cc_library_static dependants of rlibs.
+	// Track them separately from linkDirs so superfluous -L flags don't get emitted.
+	exportedLinkDirs []string
+
 	// Used by bindgen modules which call clang
 	depClangFlags         []string
 	depIncludePaths       android.Paths
@@ -497,6 +466,7 @@
 
 type flagExporter struct {
 	linkDirs    []string
+	ccLinkDirs  []string
 	linkObjects []string
 }
 
@@ -535,7 +505,7 @@
 
 var _ cc.Coverage = (*Module)(nil)
 
-func (mod *Module) IsNativeCoverageNeeded(ctx android.IncomingTransitionContext) bool {
+func (mod *Module) IsNativeCoverageNeeded(ctx cc.IsNativeCoverageNeededContext) bool {
 	return mod.coverage != nil && mod.coverage.Properties.NeedCoverageVariant
 }
 
@@ -543,6 +513,10 @@
 	return mod.Properties.VndkVersion
 }
 
+func (mod *Module) ExportedCrateLinkDirs() []string {
+	return mod.exportedLinkDirs
+}
+
 func (mod *Module) PreventInstall() bool {
 	return mod.Properties.PreventInstall
 }
@@ -657,15 +631,6 @@
 	return nil
 }
 
-func (mod *Module) IncludeDirs() android.Paths {
-	if mod.compiler != nil {
-		if library, ok := mod.compiler.(*libraryDecorator); ok {
-			return library.includeDirs
-		}
-	}
-	panic(fmt.Errorf("IncludeDirs called on non-library module: %q", mod.BaseModuleName()))
-}
-
 func (mod *Module) SetStatic() {
 	if mod.compiler != nil {
 		if library, ok := mod.compiler.(libraryInterface); ok {
@@ -695,6 +660,24 @@
 	panic(fmt.Errorf("BuildStaticVariant called on non-library module: %q", mod.BaseModuleName()))
 }
 
+func (mod *Module) BuildRlibVariant() bool {
+	if mod.compiler != nil {
+		if library, ok := mod.compiler.(libraryInterface); ok {
+			return library.buildRlib()
+		}
+	}
+	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 {
@@ -914,6 +897,10 @@
 	}
 
 	deps := mod.depsToPaths(ctx)
+	// Export linkDirs for CC rust generatedlibs
+	mod.exportedLinkDirs = append(mod.exportedLinkDirs, deps.exportedLinkDirs...)
+	mod.exportedLinkDirs = append(mod.exportedLinkDirs, deps.linkDirs...)
+
 	flags := Flags{
 		Toolchain: toolchain,
 	}
@@ -991,6 +978,9 @@
 			if ctx.Failed() {
 				return
 			}
+			// Export your own directory as a linkDir
+			mod.exportedLinkDirs = append(mod.exportedLinkDirs, linkPathFromFilePath(mod.OutputFile().Path()))
+
 		}
 
 		ctx.Phony("rust", ctx.RustModule().OutputFile().Path())
@@ -999,7 +989,60 @@
 		android.SetProvider(ctx, testing.TestModuleProviderKey, testing.TestModuleProviderData{})
 	}
 
-	android.CollectDependencyAconfigFiles(ctx, &mod.mergedAconfigFiles)
+	mod.setOutputFiles(ctx)
+
+	buildComplianceMetadataInfo(ctx, mod, deps)
+}
+
+func (mod *Module) setOutputFiles(ctx ModuleContext) {
+	if mod.sourceProvider != nil && (mod.compiler == nil || mod.compiler.Disabled()) {
+		ctx.SetOutputFiles(mod.sourceProvider.Srcs(), "")
+	} else if mod.OutputFile().Valid() {
+		ctx.SetOutputFiles(android.Paths{mod.OutputFile().Path()}, "")
+	} else {
+		ctx.SetOutputFiles(android.Paths{}, "")
+	}
+	if mod.compiler != nil {
+		ctx.SetOutputFiles(android.PathsIfNonNil(mod.compiler.unstrippedOutputFilePath()), "unstripped")
+	}
+}
+
+func buildComplianceMetadataInfo(ctx *moduleContext, mod *Module, deps PathDeps) {
+	// Dump metadata that can not be done in android/compliance-metadata.go
+	metadataInfo := ctx.ComplianceMetadataInfo()
+	metadataInfo.SetStringValue(android.ComplianceMetadataProp.IS_STATIC_LIB, strconv.FormatBool(mod.Static()))
+	metadataInfo.SetStringValue(android.ComplianceMetadataProp.BUILT_FILES, mod.outputFile.String())
+
+	// Static libs
+	staticDeps := ctx.GetDirectDepsWithTag(rlibDepTag)
+	staticDepNames := make([]string, 0, len(staticDeps))
+	for _, dep := range staticDeps {
+		staticDepNames = append(staticDepNames, dep.Name())
+	}
+	ccStaticDeps := ctx.GetDirectDepsWithTag(cc.StaticDepTag(false))
+	for _, dep := range ccStaticDeps {
+		staticDepNames = append(staticDepNames, dep.Name())
+	}
+
+	staticDepPaths := make([]string, 0, len(deps.StaticLibs)+len(deps.RLibs))
+	// C static libraries
+	for _, dep := range deps.StaticLibs {
+		staticDepPaths = append(staticDepPaths, dep.String())
+	}
+	// Rust static libraries
+	for _, dep := range deps.RLibs {
+		staticDepPaths = append(staticDepPaths, dep.Path.String())
+	}
+	metadataInfo.SetListValue(android.ComplianceMetadataProp.STATIC_DEPS, android.FirstUniqueStrings(staticDepNames))
+	metadataInfo.SetListValue(android.ComplianceMetadataProp.STATIC_DEP_FILES, android.FirstUniqueStrings(staticDepPaths))
+
+	// C Whole static libs
+	ccWholeStaticDeps := ctx.GetDirectDepsWithTag(cc.StaticDepTag(true))
+	wholeStaticDepNames := make([]string, 0, len(ccWholeStaticDeps))
+	for _, dep := range ccStaticDeps {
+		wholeStaticDepNames = append(wholeStaticDepNames, dep.Name())
+	}
+	metadataInfo.SetListValue(android.ComplianceMetadataProp.STATIC_DEPS, android.FirstUniqueStrings(staticDepNames))
 }
 
 func (mod *Module) deps(ctx DepsContext) Deps {
@@ -1068,7 +1111,6 @@
 	rlibDepTag          = dependencyTag{name: "rlibTag", library: true}
 	dylibDepTag         = dependencyTag{name: "dylib", library: true, dynamic: true}
 	procMacroDepTag     = dependencyTag{name: "procMacro", procMacro: true}
-	testPerSrcDepTag    = dependencyTag{name: "rust_unit_tests"}
 	sourceDepTag        = dependencyTag{name: "source"}
 	dataLibDepTag       = dependencyTag{name: "data lib"}
 	dataBinDepTag       = dependencyTag{name: "data bin"}
@@ -1090,10 +1132,11 @@
 }
 
 var (
-	rlibVariation  = "rlib"
-	dylibVariation = "dylib"
-	rlibAutoDep    = autoDep{variation: rlibVariation, depTag: rlibDepTag}
-	dylibAutoDep   = autoDep{variation: dylibVariation, depTag: dylibDepTag}
+	sourceVariation = "source"
+	rlibVariation   = "rlib"
+	dylibVariation  = "dylib"
+	rlibAutoDep     = autoDep{variation: rlibVariation, depTag: rlibDepTag}
+	dylibAutoDep    = autoDep{variation: dylibVariation, depTag: dylibDepTag}
 )
 
 type autoDeppable interface {
@@ -1214,7 +1257,6 @@
 	ctx.VisitDirectDeps(func(dep android.Module) {
 		depName := ctx.OtherModuleName(dep)
 		depTag := ctx.OtherModuleDependencyTag(dep)
-
 		if _, exists := skipModuleList[depName]; exists {
 			return
 		}
@@ -1223,12 +1265,12 @@
 			return
 		}
 
-		if rustDep, ok := dep.(*Module); ok && !rustDep.CcLibraryInterface() {
+		if rustDep, ok := dep.(*Module); ok && !rustDep.Static() && !rustDep.Shared() {
 			//Handle Rust Modules
 			makeLibName := rustMakeLibName(ctx, mod, rustDep, depName+rustDep.Properties.RustSubName)
 
-			switch depTag {
-			case dylibDepTag:
+			switch {
+			case depTag == dylibDepTag:
 				dylib, ok := rustDep.compiler.(libraryInterface)
 				if !ok || !dylib.dylib() {
 					ctx.ModuleErrorf("mod %q not an dylib library", depName)
@@ -1238,8 +1280,7 @@
 				mod.Properties.AndroidMkDylibs = append(mod.Properties.AndroidMkDylibs, makeLibName)
 				mod.Properties.SnapshotDylibs = append(mod.Properties.SnapshotDylibs, cc.BaseLibName(depName))
 
-			case rlibDepTag:
-
+			case depTag == rlibDepTag:
 				rlib, ok := rustDep.compiler.(libraryInterface)
 				if !ok || !rlib.rlib() {
 					ctx.ModuleErrorf("mod %q not an rlib library", makeLibName)
@@ -1249,14 +1290,30 @@
 				mod.Properties.AndroidMkRlibs = append(mod.Properties.AndroidMkRlibs, makeLibName)
 				mod.Properties.SnapshotRlibs = append(mod.Properties.SnapshotRlibs, cc.BaseLibName(depName))
 
-			case procMacroDepTag:
+				// 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()))
+
+			case depTag == procMacroDepTag:
 				directProcMacroDeps = append(directProcMacroDeps, rustDep)
 				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()))
 
-			case sourceDepTag:
+			case depTag == sourceDepTag:
 				if _, ok := mod.sourceProvider.(*protobufDecorator); ok {
 					collectIncludedProtos(mod, rustDep)
 				}
+			case cc.IsStaticDepTag(depTag):
+				// Rust FFI rlibs should not be declared in a Rust modules
+				// "static_libs" list as we can't handle them properly at the
+				// moment (for example, they only produce an rlib-std variant).
+				// Instead, a normal rust_library variant should be used.
+				ctx.PropertyErrorf("static_libs",
+					"found '%s' in static_libs; use a rust_library module in rustlibs instead of a rust_ffi module in static_libs",
+					depName)
+
 			}
 
 			transitiveAndroidMkSharedLibs = append(transitiveAndroidMkSharedLibs, rustDep.transitiveAndroidMkSharedLibs)
@@ -1281,12 +1338,12 @@
 				directSrcProvidersDeps = append(directSrcProvidersDeps, rustDep)
 			}
 
+			exportedInfo, _ := android.OtherModuleProvider(ctx, dep, FlagExporterInfoProvider)
 			//Append the dependencies exportedDirs, except for proc-macros which target a different arch/OS
 			if depTag != procMacroDepTag {
-				exportedInfo, _ := android.OtherModuleProvider(ctx, dep, FlagExporterInfoProvider)
-				depPaths.linkDirs = append(depPaths.linkDirs, exportedInfo.LinkDirs...)
 				depPaths.depFlags = append(depPaths.depFlags, exportedInfo.Flags...)
 				depPaths.linkObjects = append(depPaths.linkObjects, exportedInfo.LinkObjects...)
+				depPaths.linkDirs = append(depPaths.linkDirs, exportedInfo.LinkDirs...)
 			}
 
 			if depTag == dylibDepTag || depTag == rlibDepTag || depTag == procMacroDepTag {
@@ -1296,6 +1353,7 @@
 					lib.exportLinkDirs(linkDir)
 				}
 			}
+
 			if depTag == sourceDepTag {
 				if _, ok := mod.sourceProvider.(*protobufDecorator); ok && mod.Source() {
 					if _, ok := rustDep.sourceProvider.(*protobufDecorator); ok {
@@ -1475,7 +1533,7 @@
 
 	var srcProviderDepFiles android.Paths
 	for _, dep := range directSrcProvidersDeps {
-		srcs, _ := dep.OutputFiles("")
+		srcs := android.OutputFilesForModule(ctx, dep, "")
 		srcProviderDepFiles = append(srcProviderDepFiles, srcs...)
 	}
 	for _, dep := range directSrcDeps {
@@ -1575,6 +1633,7 @@
 
 	// dylibs
 	dylibDepVariations := append(commonDepVariations, blueprint.Variation{Mutator: "rust_libraries", Variation: dylibVariation})
+
 	for _, lib := range deps.Dylibs {
 		actx.AddVariationDependencies(dylibDepVariations, dylibDepTag, lib)
 	}
@@ -1594,7 +1653,6 @@
 					// otherwise select the rlib variant.
 					autoDepVariations := append(commonDepVariations,
 						blueprint.Variation{Mutator: "rust_libraries", Variation: autoDep.variation})
-
 					if actx.OtherModuleDependencyVariantExists(autoDepVariations, lib) {
 						actx.AddVariationDependencies(autoDepVariations, autoDep.depTag, lib)
 
@@ -1608,8 +1666,11 @@
 		} else if _, ok := mod.sourceProvider.(*protobufDecorator); ok {
 			for _, lib := range deps.Rustlibs {
 				srcProviderVariations := append(commonDepVariations,
-					blueprint.Variation{Mutator: "rust_libraries", Variation: "source"})
+					blueprint.Variation{Mutator: "rust_libraries", Variation: sourceVariation})
 
+				// Only add rustlib dependencies if they're source providers themselves.
+				// This is used to track which crate names need to be added to the source generated
+				// in the rust_protobuf mod.rs.
 				if actx.OtherModuleDependencyVariantExists(srcProviderVariations, lib) {
 					actx.AddVariationDependencies(srcProviderVariations, sourceDepTag, lib)
 				}
@@ -1697,7 +1758,7 @@
 }
 
 func BeginMutator(ctx android.BottomUpMutatorContext) {
-	if mod, ok := ctx.Module().(*Module); ok && mod.Enabled() {
+	if mod, ok := ctx.Module().(*Module); ok && mod.Enabled(ctx) {
 		mod.beginMutator(ctx)
 	}
 }
@@ -1855,5 +1916,3 @@
 var BoolDefault = proptools.BoolDefault
 var String = proptools.String
 var StringPtr = proptools.StringPtr
-
-var _ android.OutputFileProducer = (*Module)(nil)
diff --git a/rust/rust_test.go b/rust/rust_test.go
index 6d083f6..eeedf3f 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -60,6 +60,7 @@
 // testRust returns a TestContext in which a basic environment has been setup.
 // This environment contains a few mocked files. See rustMockedFiles for the list of these files.
 func testRust(t *testing.T, bp string) *android.TestContext {
+	t.Helper()
 	skipTestIfOsNotSupported(t)
 	result := android.GroupFixturePreparers(
 		prepareForRustTest,
@@ -150,15 +151,11 @@
 			host_supported: true,
 			name: "cc_stubs_dep",
 		}
-		rust_ffi_host_static {
+		cc_library_host_static {
 			name: "libstatic",
-			srcs: ["foo.rs"],
-			crate_name: "static",
 		}
-		rust_ffi_host_static {
+		cc_library_host_static {
 			name: "libwholestatic",
-			srcs: ["foo.rs"],
-			crate_name: "wholestatic",
 		}
 		rust_ffi_host_shared {
 			name: "libshared",
@@ -435,6 +432,115 @@
 	}
 }
 
+func TestRustRlibs(t *testing.T) {
+	ctx := testRust(t, `
+		rust_ffi_rlib {
+			name: "libbar",
+			crate_name: "bar",
+			srcs: ["src/lib.rs"],
+			export_include_dirs: ["bar_includes"]
+		}
+
+		rust_ffi_rlib {
+			name: "libfoo",
+			crate_name: "foo",
+			srcs: ["src/lib.rs"],
+			export_include_dirs: ["foo_includes"]
+		}
+
+		rust_ffi_rlib {
+			name: "libbuzz",
+			crate_name: "buzz",
+			srcs: ["src/lib.rs"],
+			export_include_dirs: ["buzz_includes"]
+		}
+
+		cc_library_shared {
+			name: "libcc_shared",
+			srcs:["foo.c"],
+			static_libs: ["libbar"],
+		}
+
+		cc_library_static {
+			name: "libcc_static",
+			srcs:["foo.c"],
+			static_libs: ["libbuzz"],
+			whole_static_libs: ["libfoo"],
+		}
+
+		cc_binary {
+			name: "ccBin",
+			srcs:["foo.c"],
+			static_libs: ["libcc_static", "libbar"],
+		}
+		`)
+
+	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")
+
+	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"])
+	}
+
+	// Make sure there's a rustc command, and it's producing a staticlib
+	if !strings.Contains(libcc_shared_rustc.Args["rustcFlags"], "crate-type=staticlib") {
+		t.Errorf("missing crate-type for static variant, expecting %#v, rustcFlags: %#v",
+			"staticlib", libcc_shared_rustc.Args["rustcFlags"])
+	}
+
+	// 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") {
+		t.Errorf("missing generated static library in linker step libFlags %#v, libFlags: %#v",
+			"libcc_shared.generated_rust_staticlib.a", libcc_shared_ld.Args["libFlags"])
+	}
+
+	// Make sure the static lib includes are in the cc command
+	if !strings.Contains(libcc_shared_cc.Args["cFlags"], "-Ibar_includes") {
+		t.Errorf("missing rlibs includes, expecting %#v, cFlags: %#v",
+			"-Ibar_includes", libcc_shared_cc.Args["cFlags"])
+	}
+
+	// Make sure there's a rustc command, and it's producing a staticlib
+	if !strings.Contains(ccbin_rustc.Args["rustcFlags"], "crate-type=staticlib") {
+		t.Errorf("missing crate-type for static variant, expecting %#v, rustcFlags: %#v", "staticlib", ccbin_rustc.Args["rustcFlags"])
+	}
+
+	// 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") {
+		t.Errorf("missing generated static library in linker step libFlags, expecting %#v, libFlags: %#v",
+			"ccBin.generated_rust_staticlib.a", ccbin_ld.Args["libFlags"])
+	}
+
+	// Make sure the static lib includes are in the ld command
+	if !strings.Contains(ccbin_cc.Args["cFlags"], "-Ibar_includes") {
+		t.Errorf("missing rlibs includes, expecting %#v, cFlags: %#v",
+			"-Ibar_includes", ccbin_cc.Args)
+	}
+
+	// Make sure that direct dependencies and indirect whole static dependencies are
+	// propagating correctly to the generated rlib.
+	if !strings.Contains(ccbin_rustc.Args["libFlags"], "--extern foo=") {
+		t.Errorf("Missing indirect whole_static_lib dependency libfoo when writing generated Rust staticlib: %#v", ccbin_rustc.Args["libFlags"])
+	}
+	if strings.Contains(ccbin_rustc.Args["libFlags"], "--extern buzz=") {
+		t.Errorf("Indirect static_lib dependency libbuzz found when writing generated Rust staticlib: %#v", ccbin_rustc.Args["libFlags"])
+	}
+	if !strings.Contains(ccbin_rustc.Args["libFlags"], "--extern bar=") {
+		t.Errorf("Missing direct dependency libbar when writing generated Rust staticlib: %#v", ccbin_rustc.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)
+	}
+}
+
 func assertString(t *testing.T, got, expected string) {
 	t.Helper()
 	if got != expected {
diff --git a/rust/sanitize.go b/rust/sanitize.go
index bfd3971..c086880 100644
--- a/rust/sanitize.go
+++ b/rust/sanitize.go
@@ -258,7 +258,7 @@
 
 func rustSanitizerRuntimeMutator(mctx android.BottomUpMutatorContext) {
 	if mod, ok := mctx.Module().(*Module); ok && mod.sanitize != nil {
-		if !mod.Enabled() {
+		if !mod.Enabled(mctx) {
 			return
 		}
 
diff --git a/rust/test.go b/rust/test.go
index 2583893..b7ddd06 100644
--- a/rust/test.go
+++ b/rust/test.go
@@ -27,7 +27,7 @@
 type TestProperties struct {
 	// Disables the creation of a test-specific directory when used with
 	// relative_install_path. Useful if several tests need to be in the same
-	// directory, but test_per_src doesn't work.
+	// directory.
 	No_named_install_directory *bool
 
 	// the name of the test configuration (for example "AndroidTest.xml") that should be
@@ -116,7 +116,8 @@
 }
 
 func (test *testDecorator) install(ctx ModuleContext) {
-	testInstallBase := "/data/local/tests/unrestricted"
+	// TODO: (b/167308193) Switch to /data/local/tests/unrestricted as the default install base.
+	testInstallBase := "/data/local/tmp"
 	if ctx.RustModule().InVendorOrProduct() {
 		testInstallBase = "/data/local/tests/vendor"
 	}
diff --git a/rust/test_test.go b/rust/test_test.go
index 6d0ebcf..dc796c8 100644
--- a/rust/test_test.go
+++ b/rust/test_test.go
@@ -106,12 +106,9 @@
 
 	ctx := testRust(t, bp)
 
-	module := ctx.ModuleForTests("main_test", "android_arm64_armv8-a").Module()
-	testBinary := module.(*Module).compiler.(*testDecorator)
-	outputFiles, err := module.(android.OutputFileProducer).OutputFiles("")
-	if err != nil {
-		t.Fatalf("Expected rust_test to produce output files, error: %s", err)
-	}
+	testingModule := ctx.ModuleForTests("main_test", "android_arm64_armv8-a")
+	testBinary := testingModule.Module().(*Module).compiler.(*testDecorator)
+	outputFiles := testingModule.OutputFiles(t, "")
 	if len(outputFiles) != 1 {
 		t.Fatalf("expected exactly one output file. output files: [%s]", outputFiles)
 	}
@@ -168,12 +165,10 @@
  `
 
 	ctx := testRust(t, bp)
-	module := ctx.ModuleForTests("main_test", "android_arm64_armv8-a").Module()
+	testingModule := ctx.ModuleForTests("main_test", "android_arm64_armv8-a")
+	module := testingModule.Module()
 	testBinary := module.(*Module).compiler.(*testDecorator)
-	outputFiles, err := module.(android.OutputFileProducer).OutputFiles("")
-	if err != nil {
-		t.Fatalf("Expected rust_test to produce output files, error: %s", err)
-	}
+	outputFiles := testingModule.OutputFiles(t, "")
 	if len(outputFiles) != 1 {
 		t.Fatalf("expected exactly one output file. output files: [%s]", outputFiles)
 	}
diff --git a/rust/testing.go b/rust/testing.go
index 5837dcc..32cc823 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -49,16 +49,28 @@
 func GatherRequiredDepsForTest() string {
 	bp := `
 		rust_prebuilt_library {
-				name: "libstd",
-				crate_name: "std",
-				rlib: {
-					srcs: ["libstd.rlib"],
-				},
-				dylib: {
-					srcs: ["libstd.so"],
-				},
-				host_supported: true,
-				sysroot: true,
+			name: "libstd",
+			crate_name: "std",
+			rlib: {
+				srcs: ["libstd/libstd.rlib"],
+			},
+			dylib: {
+				srcs: ["libstd/libstd.so"],
+			},
+			host_supported: true,
+			sysroot: true,
+		}
+		rust_prebuilt_library {
+			name: "libcore.sysroot",
+			crate_name: "core",
+			rlib: {
+				srcs: ["libcore/libcore.rlib"],
+			},
+			dylib: {
+				srcs: ["libcore/libcore.so"],
+			},
+			host_supported: true,
+			sysroot: true,
 		}
 		//////////////////////////////
 		// Device module requirements
@@ -176,25 +188,20 @@
 	ctx.RegisterModuleType("rust_fuzz_host", RustFuzzHostFactory)
 	ctx.RegisterModuleType("rust_ffi", RustFFIFactory)
 	ctx.RegisterModuleType("rust_ffi_shared", RustFFISharedFactory)
-	ctx.RegisterModuleType("rust_ffi_static", RustFFIStaticFactory)
+	ctx.RegisterModuleType("rust_ffi_rlib", RustFFIRlibFactory)
+	ctx.RegisterModuleType("rust_ffi_static", RustFFIRlibFactory)
 	ctx.RegisterModuleType("rust_ffi_host", RustFFIHostFactory)
 	ctx.RegisterModuleType("rust_ffi_host_shared", RustFFISharedHostFactory)
-	ctx.RegisterModuleType("rust_ffi_host_static", RustFFIStaticHostFactory)
+	ctx.RegisterModuleType("rust_ffi_host_rlib", RustFFIRlibHostFactory)
+	ctx.RegisterModuleType("rust_ffi_host_static", RustFFIRlibHostFactory)
 	ctx.RegisterModuleType("rust_proc_macro", ProcMacroFactory)
 	ctx.RegisterModuleType("rust_protobuf", RustProtobufFactory)
 	ctx.RegisterModuleType("rust_protobuf_host", RustProtobufHostFactory)
 	ctx.RegisterModuleType("rust_prebuilt_library", PrebuiltLibraryFactory)
 	ctx.RegisterModuleType("rust_prebuilt_dylib", PrebuiltDylibFactory)
 	ctx.RegisterModuleType("rust_prebuilt_rlib", PrebuiltRlibFactory)
-	ctx.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
-		// rust mutators
-		ctx.BottomUp("rust_libraries", LibraryMutator).Parallel()
-		ctx.BottomUp("rust_stdlinkage", LibstdMutator).Parallel()
-		ctx.BottomUp("rust_begin", BeginMutator).Parallel()
-	})
+	ctx.PreDepsMutators(registerPreDepsMutators)
 	ctx.RegisterParallelSingletonType("rust_project_generator", rustProjectGeneratorSingleton)
 	ctx.RegisterParallelSingletonType("kythe_rust_extract", kytheExtractRustFactory)
-	ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
-		ctx.BottomUp("rust_sanitizers", rustSanitizerRuntimeMutator).Parallel()
-	})
+	ctx.PostDepsMutators(registerPostDepsMutators)
 }
diff --git a/scripts/Android.bp b/scripts/Android.bp
index d039a81..91aa195 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -29,11 +29,6 @@
         "manifest_fixer_test.py",
         "manifest_fixer.py",
     ],
-    version: {
-        py3: {
-            embedded_launcher: true,
-        },
-    },
     libs: [
         "manifest_utils",
     ],
@@ -214,11 +209,6 @@
     srcs: [
         "conv_linker_config.py",
     ],
-    version: {
-        py3: {
-            embedded_launcher: true,
-        },
-    },
     libs: [
         "linker_config_proto",
     ],
@@ -299,20 +289,30 @@
     srcs: [
         "merge_directories.py",
     ],
-    version: {
-        py3: {
-            embedded_launcher: true,
-        },
-    },
+}
+
+python_binary_host {
+    name: "merge_json",
+    main: "merge_json.py",
+    srcs: [
+        "merge_json.py",
+    ],
+}
+
+python_binary_host {
+    name: "gen_build_prop",
+    main: "gen_build_prop.py",
+    srcs: ["gen_build_prop.py"],
 }
 
 python_binary_host {
     name: "buildinfo",
     main: "buildinfo.py",
     srcs: ["buildinfo.py"],
-    version: {
-        py3: {
-            embedded_launcher: true,
-        },
-    },
+}
+
+python_binary_host {
+    name: "extra_install_zips_file_list",
+    main: "extra_install_zips_file_list.py",
+    srcs: ["extra_install_zips_file_list.py"],
 }
diff --git a/scripts/buildinfo.py b/scripts/buildinfo.py
index e4fb0da..8a24b63 100755
--- a/scripts/buildinfo.py
+++ b/scripts/buildinfo.py
@@ -18,55 +18,90 @@
 
 import argparse
 import contextlib
+import json
+import os
 import subprocess
 
+TEST_KEY_DIR = "build/make/target/product/security"
+
+def get_build_variant(product_config):
+  if product_config["Eng"]:
+    return "eng"
+  elif product_config["Debuggable"]:
+    return "userdebug"
+  else:
+    return "user"
+
+def get_build_flavor(product_config):
+  build_flavor = product_config["DeviceProduct"] + "-" + get_build_variant(product_config)
+  if "address" in product_config.get("SanitizeDevice", []) and "_asan" not in build_flavor:
+    build_flavor += "_asan"
+  return build_flavor
+
+def get_build_keys(product_config):
+  default_cert = product_config.get("DefaultAppCertificate", "")
+  if default_cert == "" or default_cert == os.path.join(TEST_KEY_DIR, "testKey"):
+    return "test-keys"
+  return "dev-keys"
+
 def parse_args():
   """Parse commandline arguments."""
   parser = argparse.ArgumentParser()
-  parser.add_argument('--use-vbmeta-digest-in-fingerprint', action='store_true')
-  parser.add_argument('--build-flavor', required=True)
   parser.add_argument('--build-hostname-file', required=True, type=argparse.FileType('r')),
-  parser.add_argument('--build-id', required=True)
-  parser.add_argument('--build-keys', required=True)
   parser.add_argument('--build-number-file', required=True, type=argparse.FileType('r'))
   parser.add_argument('--build-thumbprint-file', type=argparse.FileType('r'))
-  parser.add_argument('--build-type', required=True)
   parser.add_argument('--build-username', required=True)
-  parser.add_argument('--build-variant', required=True)
-  parser.add_argument('--cpu-abis', action='append', required=True)
   parser.add_argument('--date-file', required=True, type=argparse.FileType('r'))
-  parser.add_argument('--default-locale')
-  parser.add_argument('--default-wifi-channels', action='append', default=[])
-  parser.add_argument('--device', required=True)
-  parser.add_argument("--display-build-number", action='store_true')
-  parser.add_argument('--platform-base-os', required=True)
-  parser.add_argument('--platform-display-version', required=True)
-  parser.add_argument('--platform-min-supported-target-sdk-version', required=True)
   parser.add_argument('--platform-preview-sdk-fingerprint-file',
                       required=True,
                       type=argparse.FileType('r'))
-  parser.add_argument('--platform-preview-sdk-version', required=True)
-  parser.add_argument('--platform-sdk-version', required=True)
-  parser.add_argument('--platform-security-patch', required=True)
-  parser.add_argument('--platform-version', required=True)
-  parser.add_argument('--platform-version-codename',required=True)
-  parser.add_argument('--platform-version-all-codenames', action='append', required=True)
-  parser.add_argument('--platform-version-known-codenames', required=True)
-  parser.add_argument('--platform-version-last-stable', required=True)
-  parser.add_argument('--product', required=True)
-
+  parser.add_argument('--product-config', required=True, type=argparse.FileType('r'))
   parser.add_argument('--out', required=True, type=argparse.FileType('w'))
 
-  return parser.parse_args()
+  option = parser.parse_args()
+
+  product_config = json.load(option.product_config)
+  build_flags = product_config["BuildFlags"]
+
+  option.build_flavor = get_build_flavor(product_config)
+  option.build_keys = get_build_keys(product_config)
+  option.build_id = product_config["BuildId"]
+  option.build_type = product_config["BuildType"]
+  option.build_variant = get_build_variant(product_config)
+  option.build_version_tags = product_config["BuildVersionTags"]
+  option.cpu_abis = product_config["DeviceAbi"]
+  option.default_locale = None
+  if len(product_config.get("ProductLocales", [])) > 0:
+    option.default_locale = product_config["ProductLocales"][0]
+  option.default_wifi_channels = product_config.get("ProductDefaultWifiChannels", [])
+  option.device = product_config["DeviceName"]
+  option.display_build_number = product_config["DisplayBuildNumber"]
+  option.platform_base_os = product_config["Platform_base_os"]
+  option.platform_display_version = product_config["Platform_display_version_name"]
+  option.platform_min_supported_target_sdk_version = build_flags["RELEASE_PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION"]
+  option.platform_preview_sdk_version = product_config["Platform_preview_sdk_version"]
+  option.platform_sdk_version = product_config["Platform_sdk_version"]
+  option.platform_security_patch = product_config["Platform_security_patch"]
+  option.platform_version = product_config["Platform_version_name"]
+  option.platform_version_codename = product_config["Platform_sdk_codename"]
+  option.platform_version_all_codenames = product_config["Platform_version_active_codenames"]
+  option.platform_version_known_codenames = product_config["Platform_version_known_codenames"]
+  option.platform_version_last_stable = product_config["Platform_version_last_stable"]
+  option.product = product_config["DeviceProduct"]
+  option.use_vbmeta_digest_in_fingerprint = product_config["BoardUseVbmetaDigestInFingerprint"]
+
+  return option
 
 def main():
   option = parse_args()
 
   build_hostname = option.build_hostname_file.read().strip()
   build_number = option.build_number_file.read().strip()
-  build_version_tags = option.build_keys
+  build_version_tags_list = option.build_version_tags
   if option.build_type == "debug":
-    build_version_tags = "debug," + build_version_tags
+    build_version_tags_list.append("debug")
+  build_version_tags_list.append(option.build_keys)
+  build_version_tags = ",".join(sorted(set(build_version_tags_list)))
 
   raw_date = option.date_file.read().strip()
   date = subprocess.check_output(["date", "-d", f"@{raw_date}"], text=True).strip()
@@ -97,7 +132,7 @@
 
       # Dev. branches should have DISPLAY_BUILD_NUMBER set
       if option.display_build_number:
-        print(f"ro.build.display.id?={option.build_id} {build_number} {option.build_keys}")
+        print(f"ro.build.display.id?={option.build_id}.{build_number} {option.build_keys}")
       else:
         print(f"ro.build.display.id?={option.build_id} {option.build_keys}")
     else:
diff --git a/scripts/check_boot_jars/package_allowed_list.txt b/scripts/check_boot_jars/package_allowed_list.txt
index 47eae07..bb88cce 100644
--- a/scripts/check_boot_jars/package_allowed_list.txt
+++ b/scripts/check_boot_jars/package_allowed_list.txt
@@ -3,52 +3,7 @@
 
 ###################################################
 # core-libart.jar & core-oj.jar
-java\.awt\.font
-java\.beans
-java\.io
-java\.lang
-java\.lang\.annotation
-java\.lang\.constant
-java\.lang\.invoke
-java\.lang\.ref
-java\.lang\.reflect
-java\.lang\.runtime
-java\.math
-java\.net
-java\.nio
-java\.nio\.file
-java\.nio\.file\.spi
-java\.nio\.file\.attribute
-java\.nio\.channels
-java\.nio\.channels\.spi
-java\.nio\.charset
-java\.nio\.charset\.spi
-java\.security
-java\.security\.acl
-java\.security\.cert
-java\.security\.interfaces
-java\.security\.spec
-java\.sql
-java\.text
-java\.text\.spi
-java\.time
-java\.time\.chrono
-java\.time\.format
-java\.time\.temporal
-java\.time\.zone
-java\.util
-java\.util\.concurrent
-java\.util\.concurrent\.atomic
-java\.util\.concurrent\.locks
-java\.util\.function
-java\.util\.jar
-java\.util\.logging
-java\.util\.prefs
-java\.util\.random
-java\.util\.regex
-java\.util\.spi
-java\.util\.stream
-java\.util\.zip
+java(\..*)?
 # TODO: Remove javax.annotation.processing if possible, see http://b/132338110:
 javax\.annotation\.processing
 javax\.crypto
@@ -72,18 +27,7 @@
 javax\.xml\.transform\.stream
 javax\.xml\.validation
 javax\.xml\.xpath
-jdk\.internal
-jdk\.internal\.access
-jdk\.internal\.math
-jdk\.internal\.misc
-jdk\.internal\.ref
-jdk\.internal\.reflect
-jdk\.internal\.util
-jdk\.internal\.util\.jar
-jdk\.internal\.util\.random
-jdk\.internal\.vm\.annotation
-jdk\.net
-jdk\.random
+jdk\..*
 org\.w3c\.dom
 org\.w3c\.dom\.ls
 org\.w3c\.dom\.traversal
diff --git a/scripts/extra_install_zips_file_list.py b/scripts/extra_install_zips_file_list.py
new file mode 100755
index 0000000..148d6cc
--- /dev/null
+++ b/scripts/extra_install_zips_file_list.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+
+import argparse
+import os
+import sys
+import zipfile
+from typing import List
+
+def list_files_in_zip(zipfile_path: str) -> List[str]:
+    with zipfile.ZipFile(zipfile_path, 'r') as zf:
+        return zf.namelist()
+
+def main():
+    parser = argparse.ArgumentParser(
+        description='Lists paths to all files inside an EXTRA_INSTALL_ZIPS zip file relative to a partition staging directory. '
+        'This script is just a helper because its difficult to implement this logic in make code.'
+    )
+    parser.add_argument('staging_dir',
+        help='Path to the partition staging directory')
+    parser.add_argument('extra_install_zips', nargs='*',
+        help='The value of EXTRA_INSTALL_ZIPS from make. '
+        'It should be a list of primary_file:extraction_dir:zip_file trios. '
+        'The primary file will be ignored by this script, you should ensure that '
+        'the list of trios given to this script is already filtered by relevant primary files.')
+    args = parser.parse_args()
+
+    staging_dir = args.staging_dir.removesuffix('/') + '/'
+
+    for zip_trio in args.extra_install_zips:
+        _, d, z = zip_trio.split(':')
+        d = d.removesuffix('/') + '/'
+
+        if d.startswith(staging_dir):
+            d = os.path.relpath(d, staging_dir)
+            if d == '.':
+                d = ''
+            for f in list_files_in_zip(z):
+                print(os.path.join(d, f))
+
+
+if __name__ == "__main__":
+    main()
diff --git a/scripts/gen-kotlin-build-file.py b/scripts/gen-kotlin-build-file.py
index 83b4cd8..99afdca 100644
--- a/scripts/gen-kotlin-build-file.py
+++ b/scripts/gen-kotlin-build-file.py
@@ -79,7 +79,7 @@
         elif src.endswith('.kt'):
           f.write('    <sources path="%s"/>\n' % path)
         else:
-          raise RuntimeError('unknown source file type %s' % file)
+          raise RuntimeError(f'unknown source file type {src} from rspfile {rsp_file}')
 
     for rsp_file in args.common_srcs:
       for src in NinjaRspFileReader(rsp_file):
diff --git a/scripts/gen_build_prop.py b/scripts/gen_build_prop.py
new file mode 100644
index 0000000..9ea56cb
--- /dev/null
+++ b/scripts/gen_build_prop.py
@@ -0,0 +1,565 @@
+#!/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.
+#
+"""A tool for generating {partition}/build.prop"""
+
+import argparse
+import contextlib
+import json
+import os
+import subprocess
+import sys
+
+TEST_KEY_DIR = "build/make/target/product/security"
+
+def get_build_variant(product_config):
+  if product_config["Eng"]:
+    return "eng"
+  elif product_config["Debuggable"]:
+    return "userdebug"
+  else:
+    return "user"
+
+def get_build_flavor(product_config):
+  build_flavor = product_config["DeviceProduct"] + "-" + get_build_variant(product_config)
+  if "address" in product_config.get("SanitizeDevice", []) and "_asan" not in build_flavor:
+    build_flavor += "_asan"
+  return build_flavor
+
+def get_build_keys(product_config):
+  default_cert = product_config.get("DefaultAppCertificate", "")
+  if default_cert == "" or default_cert == os.path.join(TEST_KEY_DIR, "testKey"):
+    return "test-keys"
+  return "dev-keys"
+
+def parse_args():
+  """Parse commandline arguments."""
+  parser = argparse.ArgumentParser()
+  parser.add_argument("--build-fingerprint-file", required=True, type=argparse.FileType("r"))
+  parser.add_argument("--build-hostname-file", required=True, type=argparse.FileType("r"))
+  parser.add_argument("--build-number-file", required=True, type=argparse.FileType("r"))
+  parser.add_argument("--build-thumbprint-file", type=argparse.FileType("r"))
+  parser.add_argument("--build-username", required=True)
+  parser.add_argument("--date-file", required=True, type=argparse.FileType("r"))
+  parser.add_argument("--platform-preview-sdk-fingerprint-file", required=True, type=argparse.FileType("r"))
+  parser.add_argument("--prop-files", action="append", type=argparse.FileType("r"), default=[])
+  parser.add_argument("--product-config", required=True, type=argparse.FileType("r"))
+  parser.add_argument("--partition", required=True)
+  parser.add_argument("--build-broken-dup-sysprop", action="store_true", default=False)
+
+  parser.add_argument("--out", required=True, type=argparse.FileType("w"))
+
+  args = parser.parse_args()
+
+  # post process parse_args requiring manual handling
+  args.config = json.load(args.product_config)
+  config = args.config
+
+  config["BuildFlavor"] = get_build_flavor(config)
+  config["BuildKeys"] = get_build_keys(config)
+  config["BuildVariant"] = get_build_variant(config)
+
+  config["BuildFingerprint"] = args.build_fingerprint_file.read().strip()
+  config["BuildHostname"] = args.build_hostname_file.read().strip()
+  config["BuildNumber"] = args.build_number_file.read().strip()
+  config["BuildUsername"] = args.build_username
+
+  build_version_tags_list = config["BuildVersionTags"]
+  if config["BuildType"] == "debug":
+    build_version_tags_list.append("debug")
+  build_version_tags_list.append(config["BuildKeys"])
+  build_version_tags = ",".join(sorted(set(build_version_tags_list)))
+  config["BuildVersionTags"] = build_version_tags
+
+  raw_date = args.date_file.read().strip()
+  config["Date"] = subprocess.check_output(["date", "-d", f"@{raw_date}"], text=True).strip()
+  config["DateUtc"] = subprocess.check_output(["date", "-d", f"@{raw_date}", "+%s"], text=True).strip()
+
+  # build_desc is human readable strings that describe this build. This has the same info as the
+  # build fingerprint.
+  # e.g. "aosp_cf_x86_64_phone-userdebug VanillaIceCream MAIN eng.20240319.143939 test-keys"
+  config["BuildDesc"] = f"{config['DeviceProduct']}-{config['BuildVariant']} " \
+                        f"{config['Platform_version_name']} {config['BuildId']} " \
+                        f"{config['BuildNumber']} {config['BuildVersionTags']}"
+
+  config["PlatformPreviewSdkFingerprint"] = args.platform_preview_sdk_fingerprint_file.read().strip()
+
+  if args.build_thumbprint_file:
+    config["BuildThumbprint"] = args.build_thumbprint_file.read().strip()
+
+  append_additional_system_props(args)
+  append_additional_vendor_props(args)
+  append_additional_product_props(args)
+
+  return args
+
+def generate_common_build_props(args):
+  print("####################################")
+  print("# from generate_common_build_props")
+  print("# These properties identify this partition image.")
+  print("####################################")
+
+  config = args.config
+  partition = args.partition
+
+  if partition == "system":
+    print(f"ro.product.{partition}.brand={config['SystemBrand']}")
+    print(f"ro.product.{partition}.device={config['SystemDevice']}")
+    print(f"ro.product.{partition}.manufacturer={config['SystemManufacturer']}")
+    print(f"ro.product.{partition}.model={config['SystemModel']}")
+    print(f"ro.product.{partition}.name={config['SystemName']}")
+  else:
+    print(f"ro.product.{partition}.brand={config['ProductBrand']}")
+    print(f"ro.product.{partition}.device={config['DeviceName']}")
+    print(f"ro.product.{partition}.manufacturer={config['ProductManufacturer']}")
+    print(f"ro.product.{partition}.model={config['ProductModel']}")
+    print(f"ro.product.{partition}.name={config['DeviceProduct']}")
+
+  if partition != "system":
+    if config["ModelForAttestation"]:
+        print(f"ro.product.model_for_attestation={config['ModelForAttestation']}")
+    if config["BrandForAttestation"]:
+        print(f"ro.product.brand_for_attestation={config['BrandForAttestation']}")
+    if config["NameForAttestation"]:
+        print(f"ro.product.name_for_attestation={config['NameForAttestation']}")
+    if config["DeviceForAttestation"]:
+        print(f"ro.product.device_for_attestation={config['DeviceForAttestation']}")
+    if config["ManufacturerForAttestation"]:
+        print(f"ro.product.manufacturer_for_attestation={config['ManufacturerForAttestation']}")
+
+  if config["ZygoteForce64"]:
+    if partition == "vendor":
+      print(f"ro.{partition}.product.cpu.abilist={config['DeviceAbiList64']}")
+      print(f"ro.{partition}.product.cpu.abilist32=")
+      print(f"ro.{partition}.product.cpu.abilist64={config['DeviceAbiList64']}")
+  else:
+    if partition == "system" or partition == "vendor" or partition == "odm":
+      print(f"ro.{partition}.product.cpu.abilist={config['DeviceAbiList']}")
+      print(f"ro.{partition}.product.cpu.abilist32={config['DeviceAbiList32']}")
+      print(f"ro.{partition}.product.cpu.abilist64={config['DeviceAbiList64']}")
+
+  print(f"ro.{partition}.build.date={config['Date']}")
+  print(f"ro.{partition}.build.date.utc={config['DateUtc']}")
+  # Allow optional assignments for ARC forward-declarations (b/249168657)
+  # TODO: Remove any tag-related inconsistencies once the goals from
+  # go/arc-android-sigprop-changes have been achieved.
+  print(f"ro.{partition}.build.fingerprint?={config['BuildFingerprint']}")
+  print(f"ro.{partition}.build.id?={config['BuildId']}")
+  print(f"ro.{partition}.build.tags?={config['BuildVersionTags']}")
+  print(f"ro.{partition}.build.type={config['BuildVariant']}")
+  print(f"ro.{partition}.build.version.incremental={config['BuildNumber']}")
+  print(f"ro.{partition}.build.version.release={config['Platform_version_last_stable']}")
+  print(f"ro.{partition}.build.version.release_or_codename={config['Platform_version_name']}")
+  print(f"ro.{partition}.build.version.sdk={config['Platform_sdk_version']}")
+
+def generate_build_info(args):
+  print()
+  print("####################################")
+  print("# from gen_build_prop.py:generate_build_info")
+  print("####################################")
+  print("# begin build properties")
+
+  config = args.config
+  build_flags = config["BuildFlags"]
+
+  # The ro.build.id will be set dynamically by init, by appending the unique vbmeta digest.
+  if config["BoardUseVbmetaDigestInFingerprint"]:
+    print(f"ro.build.legacy.id={config['BuildId']}")
+  else:
+    print(f"ro.build.id?={config['BuildId']}")
+
+  # ro.build.display.id is shown under Settings -> About Phone
+  if config["BuildVariant"] == "user":
+    # User builds should show:
+    # release build number or branch.buld_number non-release builds
+
+    # Dev. branches should have DISPLAY_BUILD_NUMBER set
+    if config["DisplayBuildNumber"]:
+      print(f"ro.build.display.id?={config['BuildId']}.{config['BuildNumber']} {config['BuildKeys']}")
+    else:
+      print(f"ro.build.display.id?={config['BuildId']} {config['BuildKeys']}")
+  else:
+    # Non-user builds should show detailed build information (See build desc above)
+    print(f"ro.build.display.id?={config['BuildDesc']}")
+  print(f"ro.build.version.incremental={config['BuildNumber']}")
+  print(f"ro.build.version.sdk={config['Platform_sdk_version']}")
+  print(f"ro.build.version.preview_sdk={config['Platform_preview_sdk_version']}")
+  print(f"ro.build.version.preview_sdk_fingerprint={config['PlatformPreviewSdkFingerprint']}")
+  print(f"ro.build.version.codename={config['Platform_sdk_codename']}")
+  print(f"ro.build.version.all_codenames={','.join(config['Platform_version_active_codenames'])}")
+  print(f"ro.build.version.known_codenames={config['Platform_version_known_codenames']}")
+  print(f"ro.build.version.release={config['Platform_version_last_stable']}")
+  print(f"ro.build.version.release_or_codename={config['Platform_version_name']}")
+  print(f"ro.build.version.release_or_preview_display={config['Platform_display_version_name']}")
+  print(f"ro.build.version.security_patch={config['Platform_security_patch']}")
+  print(f"ro.build.version.base_os={config['Platform_base_os']}")
+  print(f"ro.build.version.min_supported_target_sdk={build_flags['RELEASE_PLATFORM_MIN_SUPPORTED_TARGET_SDK_VERSION']}")
+  print(f"ro.build.date={config['Date']}")
+  print(f"ro.build.date.utc={config['DateUtc']}")
+  print(f"ro.build.type={config['BuildVariant']}")
+  print(f"ro.build.user={config['BuildUsername']}")
+  print(f"ro.build.host={config['BuildHostname']}")
+  # TODO: Remove any tag-related optional property declarations once the goals
+  # from go/arc-android-sigprop-changes have been achieved.
+  print(f"ro.build.tags?={config['BuildVersionTags']}")
+  # ro.build.flavor are used only by the test harness to distinguish builds.
+  # Only add _asan for a sanitized build if it isn't already a part of the
+  # flavor (via a dedicated lunch config for example).
+  print(f"ro.build.flavor={config['BuildFlavor']}")
+
+  # These values are deprecated, use "ro.product.cpu.abilist"
+  # instead (see below).
+  print(f"# ro.product.cpu.abi and ro.product.cpu.abi2 are obsolete,")
+  print(f"# use ro.product.cpu.abilist instead.")
+  print(f"ro.product.cpu.abi={config['DeviceAbi'][0]}")
+  if len(config["DeviceAbi"]) > 1:
+    print(f"ro.product.cpu.abi2={config['DeviceAbi'][1]}")
+
+  if config["ProductLocales"]:
+    print(f"ro.product.locale={config['ProductLocales'][0]}")
+  print(f"ro.wifi.channels={' '.join(config['ProductDefaultWifiChannels'])}")
+
+  print(f"# ro.build.product is obsolete; use ro.product.device")
+  print(f"ro.build.product={config['DeviceName']}")
+
+  print(f"# Do not try to parse description or thumbprint")
+  print(f"ro.build.description?={config['BuildDesc']}")
+  if "build_thumbprint" in config:
+    print(f"ro.build.thumbprint={config['BuildThumbprint']}")
+
+  print(f"# end build properties")
+
+def write_properties_from_file(file):
+  print()
+  print("####################################")
+  print(f"# from {file.name}")
+  print("####################################")
+  print(file.read(), end="")
+
+def write_properties_from_variable(name, props, build_broken_dup_sysprop):
+  print()
+  print("####################################")
+  print(f"# from variable {name}")
+  print("####################################")
+
+  # Implement the legacy behavior when BUILD_BROKEN_DUP_SYSPROP is on.
+  # Optional assignments are all converted to normal assignments and
+  # when their duplicates the first one wins.
+  if build_broken_dup_sysprop:
+    processed_props = []
+    seen_props = set()
+    for line in props:
+      line = line.replace("?=", "=")
+      key, value = line.split("=", 1)
+      if key in seen_props:
+        continue
+      seen_props.add(key)
+      processed_props.append(line)
+    props = processed_props
+
+  for line in props:
+    print(line)
+
+def append_additional_system_props(args):
+  props = []
+
+  config = args.config
+
+  # Add the product-defined properties to the build properties.
+  if config["PropertySplitEnabled"] or config["VendorImageFileSystemType"]:
+    if "PRODUCT_PROPERTY_OVERRIDES" in config:
+      props += config["PRODUCT_PROPERTY_OVERRIDES"]
+
+  props.append(f"ro.treble.enabled={'true' if config['FullTreble'] else 'false'}")
+  # Set ro.llndk.api_level to show the maximum vendor API level that the LLNDK
+  # in the system partition supports.
+  if config["VendorApiLevel"]:
+    props.append(f"ro.llndk.api_level={config['VendorApiLevel']}")
+
+  # Sets ro.actionable_compatible_property.enabled to know on runtime whether
+  # the allowed list of actionable compatible properties is enabled or not.
+  props.append("ro.actionable_compatible_property.enabled=true")
+
+  # Enable core platform API violation warnings on userdebug and eng builds.
+  if config["BuildVariant"] != "user":
+    props.append("persist.debug.dalvik.vm.core_platform_api_policy=just-warn")
+
+  # Define ro.sanitize.<name> properties for all global sanitizers.
+  for sanitize_target in config["SanitizeDevice"]:
+    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:
+  #
+  #     PRODUCT_PRODUCT_PROPERTIES += ro.postinstall.fstab.prefix=/product
+  #
+  # It then uses ${ro.postinstall.fstab.prefix}/etc/fstab.postinstall to
+  # mount system_other partition.
+  props.append("ro.postinstall.fstab.prefix=/system")
+
+  enable_target_debugging = True
+  if config["BuildVariant"] == "user" or config["BuildVariant"] == "userdebug":
+    # Target is secure in user builds.
+    props.append("ro.secure=1")
+    props.append("security.perf_harden=1")
+
+    if config["BuildVariant"] == "user":
+      # Disable debugging in plain user builds.
+      props.append("ro.adb.secure=1")
+      enable_target_debugging = False
+
+    # Disallow mock locations by default for user builds
+    props.append("ro.allow.mock.location=0")
+  else:
+    # Turn on checkjni for non-user builds.
+    props.append("ro.kernel.android.checkjni=1")
+    # Set device insecure for non-user builds.
+    props.append("ro.secure=0")
+    # Allow mock locations by default for non user builds
+    props.append("ro.allow.mock.location=1")
+
+  if enable_target_debugging:
+    # Enable Dalvik lock contention logging.
+    props.append("dalvik.vm.lockprof.threshold=500")
+
+    # Target is more debuggable and adbd is on by default
+    props.append("ro.debuggable=1")
+  else:
+    # Target is less debuggable and adbd is off by default
+    props.append("ro.debuggable=0")
+
+  if config["BuildVariant"] == "eng":
+    if "ro.setupwizard.mode=ENABLED" in props:
+      # Don't require the setup wizard on eng builds
+      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")
+
+  if config["SdkBuild"]:
+    props.append("xmpp.auto-presence=true")
+    props.append("ro.config.nocheckin=yes")
+
+  props.append("net.bt.name=Android")
+
+  # This property is set by flashing debug boot image, so default to false.
+  props.append("ro.force.debuggable=0")
+
+  config["ADDITIONAL_SYSTEM_PROPERTIES"] = props
+
+def append_additional_vendor_props(args):
+  props = []
+
+  config = args.config
+  build_flags = config["BuildFlags"]
+
+  # Add cpu properties for bionic and ART.
+  props.append(f"ro.bionic.arch={config['DeviceArch']}")
+  props.append(f"ro.bionic.cpu_variant={config['DeviceCpuVariantRuntime']}")
+  props.append(f"ro.bionic.2nd_arch={config['DeviceSecondaryArch']}")
+  props.append(f"ro.bionic.2nd_cpu_variant={config['DeviceSecondaryCpuVariantRuntime']}")
+
+  props.append(f"persist.sys.dalvik.vm.lib.2=libart.so")
+  props.append(f"dalvik.vm.isa.{config['DeviceArch']}.variant={config['Dex2oatTargetCpuVariantRuntime']}")
+  if config["Dex2oatTargetInstructionSetFeatures"]:
+    props.append(f"dalvik.vm.isa.{config['DeviceArch']}.features={config['Dex2oatTargetInstructionSetFeatures']}")
+
+  if config["DeviceSecondaryArch"]:
+    props.append(f"dalvik.vm.isa.{config['DeviceSecondaryArch']}.variant={config['SecondaryDex2oatCpuVariantRuntime']}")
+    if config["SecondaryDex2oatInstructionSetFeatures"]:
+      props.append(f"dalvik.vm.isa.{config['DeviceSecondaryArch']}.features={config['SecondaryDex2oatInstructionSetFeatures']}")
+
+  # Although these variables are prefixed with TARGET_RECOVERY_, they are also needed under charger
+  # mode (via libminui).
+  if config["RecoveryDefaultRotation"]:
+    props.append(f"ro.minui.default_rotation={config['RecoveryDefaultRotation']}")
+
+  if config["RecoveryOverscanPercent"]:
+    props.append(f"ro.minui.overscan_percent={config['RecoveryOverscanPercent']}")
+
+  if config["RecoveryPixelFormat"]:
+    props.append(f"ro.minui.pixel_format={config['RecoveryPixelFormat']}")
+
+  if "UseDynamicPartitions" in config:
+    props.append(f"ro.boot.dynamic_partitions={'true' if config['UseDynamicPartitions'] else 'false'}")
+
+  if "RetrofitDynamicPartitions" in config:
+    props.append(f"ro.boot.dynamic_partitions_retrofit={'true' if config['RetrofitDynamicPartitions'] else 'false'}")
+
+  if config["ShippingApiLevel"]:
+    props.append(f"ro.product.first_api_level={config['ShippingApiLevel']}")
+
+  if config["ShippingVendorApiLevel"]:
+    props.append(f"ro.vendor.api_level={config['ShippingVendorApiLevel']}")
+
+  if config["BuildVariant"] != "user" and config["BuildDebugfsRestrictionsEnabled"]:
+    props.append(f"ro.product.debugfs_restrictions.enabled=true")
+
+  # Vendors with GRF must define BOARD_SHIPPING_API_LEVEL for the vendor API level.
+  # This must not be defined for the non-GRF devices.
+  # The values of the GRF properties will be verified by post_process_props.py
+  if config["BoardShippingApiLevel"]:
+    props.append(f"ro.board.first_api_level={config['ProductShippingApiLevel']}")
+
+  # 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']}")
+
+  # RELEASE_BOARD_API_LEVEL_FROZEN is true when the vendor API surface is frozen.
+  if build_flags["RELEASE_BOARD_API_LEVEL_FROZEN"]:
+    props.append(f"ro.board.api_frozen=true")
+
+  # Set build prop. This prop is read by ota_from_target_files when generating OTA,
+  # to decide if VABC should be disabled.
+  if config["DontUseVabcOta"]:
+    props.append(f"ro.vendor.build.dont_use_vabc=true")
+
+  # Set the flag in vendor. So VTS would know if the new fingerprint format is in use when
+  # the system images are replaced by GSI.
+  if config["BoardUseVbmetaDigestInFingerprint"]:
+    props.append(f"ro.vendor.build.fingerprint_has_digest=1")
+
+  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'}")
+
+  if config["ScreenDensity"]:
+    props.append(f"ro.sf.lcd_density={config['ScreenDensity']}")
+
+  if "AbOtaUpdater" in config:
+    props.append(f"ro.build.ab_update={'true' if config['AbOtaUpdater'] else 'false'}")
+    if config["AbOtaUpdater"]:
+      props.append(f"ro.vendor.build.ab_ota_partitions={config['AbOtaPartitions']}")
+
+  config["ADDITIONAL_VENDOR_PROPERTIES"] = props
+
+def append_additional_product_props(args):
+  props = []
+
+  config = args.config
+
+  # Add the system server compiler filter if they are specified for the product.
+  if config["SystemServerCompilerFilter"]:
+    props.append(f"dalvik.vm.systemservercompilerfilter={config['SystemServerCompilerFilter']}")
+
+  # Add the 16K developer args if it is defined for the product.
+  props.append(f"ro.product.build.16k_page.enabled={'true' if config['Product16KDeveloperOption'] else 'false'}")
+
+  props.append(f"ro.build.characteristics={config['AAPTCharacteristics']}")
+
+  if "AbOtaUpdater" in config and config["AbOtaUpdater"]:
+    props.append(f"ro.product.ab_ota_partitions={config['AbOtaPartitions']}")
+
+  # Set this property for VTS to skip large page size tests on unsupported devices.
+  props.append(f"ro.product.cpu.pagesize.max={config['DeviceMaxPageSizeSupported']}")
+
+  if config["NoBionicPageSizeMacro"]:
+    props.append(f"ro.product.build.no_bionic_page_size_macro=true")
+
+  # If the value is "default", it will be mangled by post_process_props.py.
+  props.append(f"ro.dalvik.vm.enable_uffd_gc={config['EnableUffdGc']}")
+
+  config["ADDITIONAL_PRODUCT_PROPERTIES"] = props
+
+def build_system_prop(args):
+  config = args.config
+
+  # Order matters here. When there are duplicates, the last one wins.
+  # TODO(b/117892318): don't allow duplicates so that the ordering doesn't matter
+  variables = [
+    "ADDITIONAL_SYSTEM_PROPERTIES",
+    "PRODUCT_SYSTEM_PROPERTIES",
+    # TODO(b/117892318): deprecate this
+    "PRODUCT_SYSTEM_DEFAULT_PROPERTIES",
+  ]
+
+  if not config["PropertySplitEnabled"]:
+    variables += [
+      "ADDITIONAL_VENDOR_PROPERTIES",
+      "PRODUCT_VENDOR_PROPERTIES",
+    ]
+
+  build_prop(args, gen_build_info=True, gen_common_build_props=True, variables=variables)
+
+'''
+def build_vendor_prop(args):
+  config = args.config
+
+  # Order matters here. When there are duplicates, the last one wins.
+  # TODO(b/117892318): don't allow duplicates so that the ordering doesn't matter
+  variables = []
+  if config["PropertySplitEnabled"]:
+    variables += [
+      "ADDITIONAL_VENDOR_PROPERTIES",
+      "PRODUCT_VENDOR_PROPERTIES",
+      # TODO(b/117892318): deprecate this
+      "PRODUCT_DEFAULT_PROPERTY_OVERRIDES",
+      "PRODUCT_PROPERTY_OVERRIDES",
+    ]
+
+  build_prop(args, gen_build_info=False, gen_common_build_props=True, variables=variables)
+
+def build_product_prop(args):
+  config = args.config
+
+  # Order matters here. When there are duplicates, the last one wins.
+  # TODO(b/117892318): don't allow duplicates so that the ordering doesn't matter
+  variables = [
+    "ADDITIONAL_PRODUCT_PROPERTIES",
+    "PRODUCT_PRODUCT_PROPERTIES",
+  ]
+  build_prop(args, gen_build_info=False, gen_common_build_props=True, variables=variables)
+'''
+
+def build_prop(args, gen_build_info, gen_common_build_props, variables):
+  config = args.config
+
+  if gen_common_build_props:
+    generate_common_build_props(args)
+
+  if gen_build_info:
+    generate_build_info(args)
+
+  for prop_file in args.prop_files:
+    write_properties_from_file(prop_file)
+
+  for variable in variables:
+    if variable in config:
+      write_properties_from_variable(variable, config[variable], args.build_broken_dup_sysprop)
+
+def main():
+  args = parse_args()
+
+  with contextlib.redirect_stdout(args.out):
+    if args.partition == "system":
+      build_system_prop(args)
+      '''
+    elif args.partition == "vendor":
+      build_vendor_prop(args)
+    elif args.partition == "product":
+      build_product_prop(args)
+      '''
+    else:
+      sys.exit(f"not supported partition {args.partition}")
+
+if __name__ == "__main__":
+  main()
diff --git a/scripts/hiddenapi/Android.bp b/scripts/hiddenapi/Android.bp
index 1e89efe..43edf44 100644
--- a/scripts/hiddenapi/Android.bp
+++ b/scripts/hiddenapi/Android.bp
@@ -18,19 +18,9 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-python_defaults {
-    name: "hiddenapi_defaults",
-    version: {
-        py3: {
-            embedded_launcher: true,
-        },
-    },
-}
-
 python_binary_host {
     name: "analyze_bcpf",
     main: "analyze_bcpf.py",
-    defaults: ["hiddenapi_defaults"],
     srcs: ["analyze_bcpf.py"],
     // Make sure that the bpmodify tool is built.
     data: [":bpmodify"],
@@ -42,7 +32,6 @@
 python_test_host {
     name: "analyze_bcpf_test",
     main: "analyze_bcpf_test.py",
-    defaults: ["hiddenapi_defaults"],
     srcs: [
         "analyze_bcpf.py",
         "analyze_bcpf_test.py",
@@ -60,21 +49,18 @@
 python_binary_host {
     name: "merge_csv",
     main: "merge_csv.py",
-    defaults: ["hiddenapi_defaults"],
     srcs: ["merge_csv.py"],
 }
 
 python_binary_host {
     name: "generate_hiddenapi_lists",
     main: "generate_hiddenapi_lists.py",
-    defaults: ["hiddenapi_defaults"],
     srcs: ["generate_hiddenapi_lists.py"],
 }
 
 python_test_host {
     name: "generate_hiddenapi_lists_test",
     main: "generate_hiddenapi_lists_test.py",
-    defaults: ["hiddenapi_defaults"],
     srcs: [
         "generate_hiddenapi_lists.py",
         "generate_hiddenapi_lists_test.py",
@@ -92,7 +78,6 @@
 python_test_host {
     name: "signature_trie_test",
     main: "signature_trie_test.py",
-    defaults: ["hiddenapi_defaults"],
     srcs: ["signature_trie_test.py"],
     libs: ["signature_trie"],
     test_options: {
@@ -103,7 +88,6 @@
 python_binary_host {
     name: "verify_overlaps",
     main: "verify_overlaps.py",
-    defaults: ["hiddenapi_defaults"],
     srcs: ["verify_overlaps.py"],
     libs: [
         "signature_trie",
@@ -113,7 +97,6 @@
 python_test_host {
     name: "verify_overlaps_test",
     main: "verify_overlaps_test.py",
-    defaults: ["hiddenapi_defaults"],
     srcs: [
         "verify_overlaps.py",
         "verify_overlaps_test.py",
@@ -129,14 +112,12 @@
 python_binary_host {
     name: "signature_patterns",
     main: "signature_patterns.py",
-    defaults: ["hiddenapi_defaults"],
     srcs: ["signature_patterns.py"],
 }
 
 python_test_host {
     name: "signature_patterns_test",
     main: "signature_patterns_test.py",
-    defaults: ["hiddenapi_defaults"],
     srcs: [
         "signature_patterns.py",
         "signature_patterns_test.py",
diff --git a/scripts/merge_json.py b/scripts/merge_json.py
new file mode 100644
index 0000000..7e2f6eb
--- /dev/null
+++ b/scripts/merge_json.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+#
+# 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.
+#
+"""A tool for merging two or more JSON files."""
+
+import argparse
+import logging
+import json
+import sys
+
+def parse_args():
+  """Parse commandline arguments."""
+
+  parser = argparse.ArgumentParser()
+  parser.add_argument("output", help="output JSON file", type=argparse.FileType("w"))
+  parser.add_argument("input", help="input JSON files", nargs="+", type=argparse.FileType("r"))
+  return parser.parse_args()
+
+def main():
+  """Program entry point."""
+  args = parse_args()
+  merged_dict = {}
+  has_error = False
+  logger = logging.getLogger(__name__)
+
+  for json_file in args.input:
+    try:
+      data = json.load(json_file)
+    except json.JSONDecodeError as e:
+      logger.error(f"Error parsing JSON in file: {json_file.name}. Reason: {e}")
+      has_error = True
+      continue
+
+    for key, value in data.items():
+      if key not in merged_dict:
+        merged_dict[key] = value
+      elif merged_dict[key] == value:
+        logger.warning(f"Duplicate key '{key}' with identical values found.")
+      else:
+        logger.error(f"Conflicting values for key '{key}': {merged_dict[key]} != {value}")
+        has_error = True
+
+  if has_error:
+    sys.exit(1)
+
+  json.dump(merged_dict, args.output)
+
+if __name__ == "__main__":
+  main()
diff --git a/scripts/run-ckati.sh b/scripts/run-ckati.sh
index b670c8a..70f5a7a 100755
--- a/scripts/run-ckati.sh
+++ b/scripts/run-ckati.sh
@@ -73,12 +73,12 @@
   --writable out/ \
   -f build/make/core/main.mk \
   "${tracing[@]}" \
-  ANDROID_JAVA_HOME=prebuilts/jdk/jdk17/linux-x86 \
+  ANDROID_JAVA_HOME=prebuilts/jdk/jdk21/linux-x86 \
   ASAN_SYMBOLIZER_PATH=$PWD/prebuilts/clang/host/linux-x86/llvm-binutils-stable/llvm-symbolizer \
   BUILD_DATETIME_FILE="$timestamp_file" \
   BUILD_HOSTNAME=$(hostname) \
   BUILD_USERNAME="$USER" \
-  JAVA_HOME=$PWD/prebuilts/jdk/jdk17/linux-x86 \
+  JAVA_HOME=$PWD/prebuilts/jdk/jdk21/linux-x86 \
   KATI_PACKAGE_MK_DIR="{$out}/target/product/${target_device}/CONFIG/kati_packaging" \
   OUT_DIR="$out" \
   PATH="$PWD/prebuilts/build-tools/path/linux-x86:$PWD/${out}/.path" \
diff --git a/scripts/run-soong-tests-with-go-tools.sh b/scripts/run-soong-tests-with-go-tools.sh
index 93c622e..1fbb1fc 100755
--- a/scripts/run-soong-tests-with-go-tools.sh
+++ b/scripts/run-soong-tests-with-go-tools.sh
@@ -74,6 +74,6 @@
     (cd "$dir";
      eval ${network_jail} -- ${GOROOT}/bin/go build ./...
      eval ${network_jail} -- ${GOROOT}/bin/go test ./...
-     eval ${network_jail} -- ${GOROOT}/bin/go test -race -short ./...
+     eval ${network_jail} -- ${GOROOT}/bin/go test -race -timeout 20m -short ./...
     )
 done
diff --git a/sdk/bootclasspath_fragment_sdk_test.go b/sdk/bootclasspath_fragment_sdk_test.go
index 8d3bbfa..7048a15 100644
--- a/sdk/bootclasspath_fragment_sdk_test.go
+++ b/sdk/bootclasspath_fragment_sdk_test.go
@@ -281,6 +281,19 @@
 				"RELEASE_HIDDEN_API_EXPORTABLE_STUBS": "true",
 			}
 		}),
+		// Make sure that we have atleast one platform library so that we can check the monolithic hiddenapi
+		// file creation.
+		java.FixtureConfigureBootJars("platform:foo"),
+		android.FixtureModifyMockFS(func(fs android.MockFS) {
+			fs["platform/Android.bp"] = []byte(`
+		java_library {
+			name: "foo",
+			srcs: ["Test.java"],
+			compile_dex: true,
+		}
+		`)
+			fs["platform/Test.java"] = nil
+		}),
 
 		android.FixtureWithRootAndroidBp(sdk+`
 			apex {
@@ -1095,7 +1108,7 @@
 
 	bcpf := result.ModuleForTests("mybootclasspathfragment", "android_common")
 	rule := bcpf.Output("out/soong/.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi" + suffix + "/stub-flags.csv")
-	android.AssertPathsRelativeToTopEquals(t, "stub flags inputs", expectedStubFlagsInputs, rule.Implicits)
+	android.AssertPathsRelativeToTopEquals(t, "stub flags inputs", android.SortedUniqueStrings(expectedStubFlagsInputs), android.SortedUniquePaths(rule.Implicits))
 
 	CheckSnapshot(t, result, "mysdk", "",
 		checkAndroidBpContents(expectedSdkSnapshot),
@@ -1153,7 +1166,7 @@
 		// of the snapshot.
 		expectedStubFlagsInputs := []string{
 			"out/soong/.intermediates/mysdklibrary.stubs.exportable/android_common/dex/mysdklibrary.stubs.exportable.jar",
-			"out/soong/.intermediates/mysdklibrary/android_common/aligned/mysdklibrary.jar",
+			"out/soong/.intermediates/mysdklibrary.impl/android_common/aligned/mysdklibrary.jar",
 		}
 
 		testSnapshotWithBootClasspathFragment_MinSdkVersion(t, "S",
@@ -1234,9 +1247,9 @@
 		// they are both part of the snapshot.
 		expectedStubFlagsInputs := []string{
 			"out/soong/.intermediates/mynewsdklibrary.stubs.exportable/android_common/dex/mynewsdklibrary.stubs.exportable.jar",
-			"out/soong/.intermediates/mynewsdklibrary/android_common/aligned/mynewsdklibrary.jar",
+			"out/soong/.intermediates/mynewsdklibrary.impl/android_common/aligned/mynewsdklibrary.jar",
 			"out/soong/.intermediates/mysdklibrary.stubs.exportable/android_common/dex/mysdklibrary.stubs.exportable.jar",
-			"out/soong/.intermediates/mysdklibrary/android_common/aligned/mysdklibrary.jar",
+			"out/soong/.intermediates/mysdklibrary.impl/android_common/aligned/mysdklibrary.jar",
 		}
 
 		testSnapshotWithBootClasspathFragment_MinSdkVersion(t, "Tiramisu",
diff --git a/sdk/cc_sdk_test.go b/sdk/cc_sdk_test.go
index 9490d12..5d76930 100644
--- a/sdk/cc_sdk_test.go
+++ b/sdk/cc_sdk_test.go
@@ -148,6 +148,9 @@
             srcs: ["linux_glibc/x86_64/lib/sdkmember.so"],
         },
     },
+    strip: {
+        none: true,
+    },
 }
 `),
 		checkAllCopyRules(`
@@ -368,6 +371,9 @@
             srcs: ["arm/lib/mynativelib.so"],
         },
     },
+    strip: {
+        none: true,
+    },
 }
 `),
 		checkAllCopyRules(`
@@ -455,6 +461,9 @@
             },
         },
     },
+    strip: {
+        none: true,
+    },
 }
 `),
 		checkAllCopyRules(`
@@ -697,6 +706,9 @@
             srcs: ["x86_64/lib/mynativelib.so"],
         },
     },
+    strip: {
+        none: true,
+    },
 }
 `),
 		checkAllCopyRules(`
@@ -824,6 +836,9 @@
             export_include_dirs: ["arm/include_gen/mynativelib/android_arm_armv7-a-neon_shared/gen/aidl"],
         },
     },
+    strip: {
+        none: true,
+    },
 }
 `),
 		checkAllCopyRules(`
@@ -932,6 +947,9 @@
             srcs: ["arm/lib/mynativelib.so"],
         },
     },
+    strip: {
+        none: true,
+    },
 }
 
 cc_prebuilt_library_shared {
@@ -950,6 +968,9 @@
             srcs: ["arm/lib/myothernativelib.so"],
         },
     },
+    strip: {
+        none: true,
+    },
 }
 
 cc_prebuilt_library_shared {
@@ -967,6 +988,9 @@
             srcs: ["arm/lib/mysystemnativelib.so"],
         },
     },
+    strip: {
+        none: true,
+    },
 }
 `),
 		checkAllCopyRules(`
@@ -1041,6 +1065,9 @@
             export_include_dirs: ["x86/include_gen/mynativelib/linux_glibc_x86_shared/gen/aidl"],
         },
     },
+    strip: {
+        none: true,
+    },
 }
 `),
 		checkAllCopyRules(`
@@ -1127,6 +1154,9 @@
             srcs: ["windows/x86_64/lib/mynativelib.dll"],
         },
     },
+    strip: {
+        none: true,
+    },
 }
 `),
 		checkAllCopyRules(`
@@ -2021,6 +2051,9 @@
             srcs: ["arm/lib/sslnil.so"],
         },
     },
+    strip: {
+        none: true,
+    },
 }
 
 cc_prebuilt_library_shared {
@@ -2038,6 +2071,9 @@
             srcs: ["arm/lib/sslempty.so"],
         },
     },
+    strip: {
+        none: true,
+    },
 }
 
 cc_prebuilt_library_shared {
@@ -2055,6 +2091,9 @@
             srcs: ["arm/lib/sslnonempty.so"],
         },
     },
+    strip: {
+        none: true,
+    },
 }
 `))
 
@@ -2114,6 +2153,9 @@
             srcs: ["linux_glibc/x86/lib/sslvariants.so"],
         },
     },
+    strip: {
+        none: true,
+    },
 }
 `),
 	)
@@ -2154,6 +2196,7 @@
     prefer: false,
     visibility: ["//visibility:public"],
     apex_available: ["//apex_available:platform"],
+    stl: "none",
     compile_multilib: "both",
     stubs: {
         versions: [
@@ -2171,6 +2214,9 @@
             srcs: ["arm/lib/stubslib.so"],
         },
     },
+    strip: {
+        none: true,
+    },
 }
 `))
 }
@@ -2242,6 +2288,9 @@
             srcs: ["linux_glibc/x86/lib/stubslib.so"],
         },
     },
+    strip: {
+        none: true,
+    },
 }
 `),
 	)
@@ -2298,6 +2347,9 @@
             srcs: ["linux_glibc/x86/lib/mylib-host.so"],
         },
     },
+    strip: {
+        none: true,
+    },
 }
 `),
 		checkAllCopyRules(`
@@ -2355,6 +2407,9 @@
             srcs: ["arm/lib/mynativelib.so"],
         },
     },
+    strip: {
+        none: true,
+    },
 }
 `),
 		checkAllCopyRules(`
diff --git a/sdk/sdk.go b/sdk/sdk.go
index fd16ab6..5b644fd 100644
--- a/sdk/sdk.go
+++ b/sdk/sdk.go
@@ -193,6 +193,10 @@
 		// Generate the snapshot from the member info.
 		s.buildSnapshot(ctx, sdkVariants)
 	}
+
+	if s.snapshotFile.Valid() {
+		ctx.SetOutputFiles([]android.Path{s.snapshotFile.Path()}, "")
+	}
 }
 
 func (s *sdk) AndroidMkEntries() []android.AndroidMkEntries {
@@ -222,18 +226,6 @@
 	}}
 }
 
-func (s *sdk) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		if s.snapshotFile.Valid() {
-			return []android.Path{s.snapshotFile.Path()}, nil
-		}
-		return nil, fmt.Errorf("snapshot file not defined. This is most likely because this isn't the common_os variant of this module")
-	default:
-		return nil, fmt.Errorf("unknown tag %q", tag)
-	}
-}
-
 // gatherTraits gathers the traits from the dynamically generated trait specific properties.
 //
 // Returns a map from member name to the set of required traits.
diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go
index f9d49d9..4894210 100644
--- a/sdk/sdk_test.go
+++ b/sdk/sdk_test.go
@@ -519,4 +519,140 @@
 		)
 	})
 
+	t.Run("test replacing exportable module", func(t *testing.T) {
+		result := android.GroupFixturePreparers(
+			prepareForSdkTestWithJava,
+			java.PrepareForTestWithJavaDefaultModules,
+			java.PrepareForTestWithJavaSdkLibraryFiles,
+			java.FixtureWithLastReleaseApis("mysdklibrary", "anothersdklibrary"),
+			android.FixtureWithRootAndroidBp(`
+			sdk {
+				name: "mysdk",
+				bootclasspath_fragments: ["mybootclasspathfragment"],
+			}
+
+			bootclasspath_fragment {
+				name: "mybootclasspathfragment",
+				apex_available: ["myapex"],
+				contents: ["mysdklibrary"],
+				hidden_api: {
+					split_packages: ["*"],
+				},
+				core_platform_api: {
+					stub_libs: [
+						"anothersdklibrary.stubs.exportable",
+					],
+				},
+				api: {
+					stub_libs: [
+						"anothersdklibrary",
+					],
+				},
+			}
+
+			java_sdk_library {
+				name: "mysdklibrary",
+				srcs: ["Test.java"],
+				compile_dex: true,
+				min_sdk_version: "S",
+				public: {enabled: true},
+				permitted_packages: ["mysdklibrary"],
+			}
+
+			java_sdk_library {
+				name: "anothersdklibrary",
+				srcs: ["Test.java"],
+				compile_dex: true,
+				min_sdk_version: "S",
+				public: {enabled: true},
+				system: {enabled: true},
+				module_lib: {enabled: true},
+			}
+		`),
+			android.FixtureMergeEnv(map[string]string{
+				"SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE": "S",
+			}),
+			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+				variables.BuildFlags = map[string]string{
+					"RELEASE_HIDDEN_API_EXPORTABLE_STUBS": "true",
+				}
+				variables.Platform_version_active_codenames = []string{"UpsideDownCake", "Tiramisu", "S-V2"}
+			}),
+		).RunTest(t)
+
+		CheckSnapshot(t, result, "mysdk", "",
+			checkAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+prebuilt_bootclasspath_fragment {
+    name: "mybootclasspathfragment",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["myapex"],
+    contents: ["mysdklibrary"],
+    api: {
+        stub_libs: ["anothersdklibrary"],
+    },
+    core_platform_api: {
+        stub_libs: ["anothersdklibrary.stubs"],
+    },
+    hidden_api: {
+        annotation_flags: "hiddenapi/annotation-flags.csv",
+        metadata: "hiddenapi/metadata.csv",
+        index: "hiddenapi/index.csv",
+        stub_flags: "hiddenapi/stub-flags.csv",
+        all_flags: "hiddenapi/all-flags.csv",
+    },
+}
+
+java_sdk_library_import {
+    name: "mysdklibrary",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    shared_library: true,
+    compile_dex: true,
+    permitted_packages: ["mysdklibrary"],
+    public: {
+        jars: ["sdk_library/public/mysdklibrary-stubs.jar"],
+        stub_srcs: ["sdk_library/public/mysdklibrary_stub_sources"],
+        current_api: "sdk_library/public/mysdklibrary.txt",
+        removed_api: "sdk_library/public/mysdklibrary-removed.txt",
+        sdk_version: "current",
+    },
+}
+
+java_sdk_library_import {
+    name: "anothersdklibrary",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    shared_library: true,
+    compile_dex: true,
+    public: {
+        jars: ["sdk_library/public/anothersdklibrary-stubs.jar"],
+        stub_srcs: ["sdk_library/public/anothersdklibrary_stub_sources"],
+        current_api: "sdk_library/public/anothersdklibrary.txt",
+        removed_api: "sdk_library/public/anothersdklibrary-removed.txt",
+        sdk_version: "current",
+    },
+    system: {
+        jars: ["sdk_library/system/anothersdklibrary-stubs.jar"],
+        stub_srcs: ["sdk_library/system/anothersdklibrary_stub_sources"],
+        current_api: "sdk_library/system/anothersdklibrary.txt",
+        removed_api: "sdk_library/system/anothersdklibrary-removed.txt",
+        sdk_version: "system_current",
+    },
+    module_lib: {
+        jars: ["sdk_library/module-lib/anothersdklibrary-stubs.jar"],
+        stub_srcs: ["sdk_library/module-lib/anothersdklibrary_stub_sources"],
+        current_api: "sdk_library/module-lib/anothersdklibrary.txt",
+        removed_api: "sdk_library/module-lib/anothersdklibrary-removed.txt",
+        sdk_version: "module_current",
+    },
+}
+`),
+		)
+	})
+
 }
diff --git a/sdk/update.go b/sdk/update.go
index afecf9f..198c8d4 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -434,6 +434,14 @@
 		prebuiltModule := memberType.AddPrebuiltModule(memberCtx, member)
 		s.createMemberSnapshot(memberCtx, member, prebuiltModule.(*bpModule))
 
+		// Set stripper to none to skip stripping for generated snapshots.
+		// Mainline prebuilts (cc_prebuilt_library_shared) are not strippable in older platforms.
+		// Thus, stripping should be skipped when being used as prebuilts.
+		if memberType.DisablesStrip() {
+			stripPropertySet := prebuiltModule.(*bpModule).AddPropertySet("strip")
+			stripPropertySet.AddProperty("none", true)
+		}
+
 		if member.memberType != android.LicenseModuleSdkMemberType && !builder.isInternalMember(member.name) {
 			// More exceptions
 			// 1. Skip BCP and SCCP fragments
@@ -480,6 +488,12 @@
 		// Transform the module module to make it suitable for use in the snapshot.
 		module = transformModule(module, snapshotTransformer)
 		module = transformModule(module, emptyClasspathContentsTransformation{})
+
+		targetApiLevel, err := android.ApiLevelFromUserWithConfig(ctx.Config(), s.targetBuildRelease(ctx).name)
+		if err == nil && targetApiLevel.LessThan(android.ApiLevelVanillaIceCream) {
+			module = transformModule(module, replaceExportablePropertiesTransformer{})
+		}
+
 		if module != nil {
 			bpFile.AddModule(module)
 		}
@@ -804,6 +818,50 @@
 	}
 }
 
+type replaceExportablePropertiesTransformer struct {
+	identityTransformation
+}
+
+var _ bpTransformer = (*replaceExportablePropertiesTransformer)(nil)
+
+func handleExportableProperties[T any](value T) any {
+	switch v := any(value).(type) {
+	case string:
+		return java.AllApiScopes.ConvertStubsLibraryExportableToEverything(v)
+	case *bpPropertySet:
+		v.properties = handleExportableProperties(v.properties).(map[string]interface{})
+		return v
+	case []string:
+		result := make([]string, len(v))
+		for i, elem := range v {
+			result[i] = handleExportableProperties(elem).(string)
+		}
+		return result
+	case []any:
+		result := make([]any, len(v))
+		for i, elem := range v {
+			result[i] = handleExportableProperties(elem)
+		}
+		return result
+	case map[string]any:
+		result := make(map[string]any)
+		for k, val := range v {
+			result[k] = handleExportableProperties(val)
+		}
+		return result
+	default:
+		return value
+	}
+}
+
+func (t replaceExportablePropertiesTransformer) transformPropertySetAfterContents(name string, propertySet *bpPropertySet, tag android.BpPropertyTag) (*bpPropertySet, android.BpPropertyTag) {
+	if name == "name" {
+		return propertySet, tag
+	}
+	propertySet.properties = handleExportableProperties(propertySet.properties).(map[string]interface{})
+	return propertySet, tag
+}
+
 func generateBpContents(bpFile *bpFile) string {
 	contents := &generatedContents{}
 	contents.IndentedPrintf("// This is auto-generated. DO NOT EDIT.\n")
diff --git a/sh/sh_binary.go b/sh/sh_binary.go
index 3cbbc45..2e48d83 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -15,7 +15,6 @@
 package sh
 
 import (
-	"fmt"
 	"path/filepath"
 	"strings"
 
@@ -100,6 +99,12 @@
 
 	// Make this module available when building for recovery.
 	Recovery_available *bool
+
+	// The name of the image this module is built for
+	ImageVariation string `blueprint:"mutated"`
+
+	// Suffix for the name of Android.mk entries generated by this module
+	SubName string `blueprint:"mutated"`
 }
 
 type TestProperties struct {
@@ -188,15 +193,6 @@
 	return s.outputFilePath
 }
 
-func (s *ShBinary) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		return android.Paths{s.outputFilePath}, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
 func (s *ShBinary) SubDir() string {
 	return proptools.String(s.properties.Sub_dir)
 }
@@ -216,16 +212,24 @@
 
 func (s *ShBinary) ImageMutatorBegin(ctx android.BaseModuleContext) {}
 
+func (s *ShBinary) VendorVariantNeeded(ctx android.BaseModuleContext) bool {
+	return s.InstallInVendor()
+}
+
+func (s *ShBinary) ProductVariantNeeded(ctx android.BaseModuleContext) bool {
+	return s.InstallInProduct()
+}
+
 func (s *ShBinary) CoreVariantNeeded(ctx android.BaseModuleContext) bool {
-	return !s.ModuleBase.InstallInRecovery() && !s.ModuleBase.InstallInRamdisk()
+	return !s.InstallInRecovery() && !s.InstallInRamdisk() && !s.InstallInVendorRamdisk() && !s.ModuleBase.InstallInVendor()
 }
 
 func (s *ShBinary) RamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
-	return proptools.Bool(s.properties.Ramdisk_available) || s.ModuleBase.InstallInRamdisk()
+	return proptools.Bool(s.properties.Ramdisk_available) || s.InstallInRamdisk()
 }
 
 func (s *ShBinary) VendorRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
-	return proptools.Bool(s.properties.Vendor_ramdisk_available) || s.ModuleBase.InstallInVendorRamdisk()
+	return proptools.Bool(s.properties.Vendor_ramdisk_available) || s.InstallInVendorRamdisk()
 }
 
 func (s *ShBinary) DebugRamdiskVariantNeeded(ctx android.BaseModuleContext) bool {
@@ -233,14 +237,36 @@
 }
 
 func (s *ShBinary) RecoveryVariantNeeded(ctx android.BaseModuleContext) bool {
-	return proptools.Bool(s.properties.Recovery_available) || s.ModuleBase.InstallInRecovery()
+	return proptools.Bool(s.properties.Recovery_available) || s.InstallInRecovery()
 }
 
 func (s *ShBinary) ExtraImageVariations(ctx android.BaseModuleContext) []string {
 	return nil
 }
 
-func (s *ShBinary) SetImageVariation(ctx android.BaseModuleContext, variation string, module android.Module) {
+func (s *ShBinary) SetImageVariation(ctx android.BaseModuleContext, variation string) {
+	s.properties.ImageVariation = variation
+}
+
+// Overrides ModuleBase.InstallInRamdisk() so that the install rule respects
+// Ramdisk_available property for ramdisk variant
+func (s *ShBinary) InstallInRamdisk() bool {
+	return s.ModuleBase.InstallInRamdisk() ||
+		(proptools.Bool(s.properties.Ramdisk_available) && s.properties.ImageVariation == android.RamdiskVariation)
+}
+
+// Overrides ModuleBase.InstallInVendorRamdisk() so that the install rule respects
+// Vendor_ramdisk_available property for vendor ramdisk variant
+func (s *ShBinary) InstallInVendorRamdisk() bool {
+	return s.ModuleBase.InstallInVendorRamdisk() ||
+		(proptools.Bool(s.properties.Vendor_ramdisk_available) && s.properties.ImageVariation == android.VendorRamdiskVariation)
+}
+
+// Overrides ModuleBase.InstallInRecovery() so that the install rule respects
+// Recovery_available property for recovery variant
+func (s *ShBinary) InstallInRecovery() bool {
+	return s.ModuleBase.InstallInRecovery() ||
+		(proptools.Bool(s.properties.Recovery_available) && s.properties.ImageVariation == android.RecoveryVariation)
 }
 
 func (s *ShBinary) generateAndroidBuildActions(ctx android.ModuleContext) {
@@ -270,7 +296,22 @@
 		Output: s.outputFilePath,
 		Input:  s.sourceFilePath,
 	})
+
+	s.properties.SubName = s.GetSubname(ctx)
+
 	android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: []string{s.sourceFilePath.String()}})
+
+	ctx.SetOutputFiles(android.Paths{s.outputFilePath}, "")
+}
+
+func (s *ShBinary) GetSubname(ctx android.ModuleContext) string {
+	ret := ""
+	if s.properties.ImageVariation != "" {
+		if s.properties.ImageVariation != android.VendorVariation {
+			ret = "." + s.properties.ImageVariation
+		}
+	}
+	return ret
 }
 
 func (s *ShBinary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -286,7 +327,7 @@
 }
 
 func (s *ShBinary) AndroidMkEntries() []android.AndroidMkEntries {
-	return []android.AndroidMkEntries{android.AndroidMkEntries{
+	return []android.AndroidMkEntries{{
 		Class:      "EXECUTABLES",
 		OutputFile: android.OptionalPathForPath(s.outputFilePath),
 		Include:    "$(BUILD_SYSTEM)/soong_cc_rust_prebuilt.mk",
@@ -297,6 +338,7 @@
 				entries.SetBoolIfTrue("LOCAL_UNINSTALLABLE_MODULE", !s.Installable())
 			},
 		},
+		SubName: s.properties.SubName,
 	}}
 }
 
diff --git a/shared/Android.bp b/shared/Android.bp
index 3c84f55..d5e8614 100644
--- a/shared/Android.bp
+++ b/shared/Android.bp
@@ -15,7 +15,6 @@
         "paths_test.go",
     ],
     deps: [
-        "soong-bazel",
         "golang-protobuf-proto",
     ],
 }
diff --git a/shared/paths.go b/shared/paths.go
index fca8b4c..1ee66d5 100644
--- a/shared/paths.go
+++ b/shared/paths.go
@@ -18,8 +18,6 @@
 
 import (
 	"path/filepath"
-
-	"android/soong/bazel"
 )
 
 // A SharedPaths represents a list of paths that are shared between
@@ -49,11 +47,3 @@
 func TempDirForOutDir(outDir string) (tempPath string) {
 	return filepath.Join(outDir, ".temp")
 }
-
-// BazelMetricsFilename returns the bazel profile filename based
-// on the action name. This is to help to store a set of bazel
-// profiles since bazel may execute multiple times during a single
-// build.
-func BazelMetricsFilename(s SharedPaths, actionName bazel.RunName) string {
-	return filepath.Join(s.BazelMetricsDir(), actionName.String()+"_bazel_profile.gz")
-}
diff --git a/snapshot/host_fake_snapshot.go b/snapshot/host_fake_snapshot.go
index 63cd4e1..278247e 100644
--- a/snapshot/host_fake_snapshot.go
+++ b/snapshot/host_fake_snapshot.go
@@ -116,7 +116,7 @@
 			prebuilts[android.RemoveOptionalPrebuiltPrefix(module.Name())] = true
 			return
 		}
-		if !module.Enabled() || module.IsHideFromMake() {
+		if !module.Enabled(ctx) || module.IsHideFromMake() {
 			return
 		}
 		apexInfo, _ := android.SingletonModuleProvider(ctx, module, android.ApexInfoProvider)
@@ -129,12 +129,12 @@
 			if !seen[outFile] {
 				seen[outFile] = true
 				outputs = append(outputs, WriteStringToFileRule(ctx, "", outFile))
-				jsonData = append(jsonData, hostSnapshotFakeJsonFlags{*hostJsonDesc(module), false})
+				jsonData = append(jsonData, hostSnapshotFakeJsonFlags{*hostJsonDesc(ctx, module), false})
 			}
 		}
 	})
 	// Update any module prebuilt information
-	for idx, _ := range jsonData {
+	for idx := range jsonData {
 		if _, ok := prebuilts[jsonData[idx].ModuleName]; ok {
 			// Prebuilt exists for this module
 			jsonData[idx].Prebuilt = true
diff --git a/snapshot/host_snapshot.go b/snapshot/host_snapshot.go
index edcc163..1ecab7d 100644
--- a/snapshot/host_snapshot.go
+++ b/snapshot/host_snapshot.go
@@ -101,7 +101,7 @@
 
 	// Create JSON file based on the direct dependencies
 	ctx.VisitDirectDeps(func(dep android.Module) {
-		desc := hostJsonDesc(dep)
+		desc := hostJsonDesc(ctx, dep)
 		if desc != nil {
 			jsonData = append(jsonData, *desc)
 		}
@@ -209,7 +209,7 @@
 
 // Create JSON description for given module, only create descriptions for binary modules
 // and rust_proc_macro modules which provide a valid HostToolPath
-func hostJsonDesc(m android.Module) *SnapshotJsonFlags {
+func hostJsonDesc(ctx android.ConfigAndErrorContext, m android.Module) *SnapshotJsonFlags {
 	path := hostToolPath(m)
 	relPath := hostRelativePathString(m)
 	procMacro := false
@@ -226,7 +226,7 @@
 		props := &SnapshotJsonFlags{
 			ModuleStemName:      moduleStem,
 			Filename:            path.String(),
-			Required:            append(m.HostRequiredModuleNames(), m.RequiredModuleNames()...),
+			Required:            append(m.HostRequiredModuleNames(), m.RequiredModuleNames(ctx)...),
 			RelativeInstallPath: relPath,
 			RustProcMacro:       procMacro,
 			CrateName:           crateName,
diff --git a/sysprop/sysprop_library.go b/sysprop/sysprop_library.go
index b9b68be..84f20c5 100644
--- a/sysprop/sysprop_library.go
+++ b/sysprop/sysprop_library.go
@@ -49,8 +49,6 @@
 	android.ModuleBase
 
 	properties syspropGenProperties
-
-	genSrcjars android.Paths
 }
 
 type syspropRustGenRule struct {
@@ -59,7 +57,6 @@
 	properties rustLibraryProperties
 }
 
-var _ android.OutputFileProducer = (*syspropJavaGenRule)(nil)
 var _ rust.SourceProvider = (*syspropRustGenRule)(nil)
 
 var (
@@ -100,6 +97,7 @@
 		}
 	})
 
+	var genSrcjars android.Paths
 	for _, syspropFile := range android.PathsForModuleSrc(ctx, g.properties.Srcs) {
 		srcJarFile := android.GenPathWithExt(ctx, "sysprop", syspropFile, "srcjar")
 
@@ -114,8 +112,10 @@
 			},
 		})
 
-		g.genSrcjars = append(g.genSrcjars, srcJarFile)
+		genSrcjars = append(genSrcjars, srcJarFile)
 	}
+
+	ctx.SetOutputFiles(genSrcjars, "")
 }
 
 func (g *syspropJavaGenRule) DepsMutator(ctx android.BottomUpMutatorContext) {
@@ -124,15 +124,6 @@
 	ctx.AddFarVariationDependencies(nil, nil, proptools.String(g.properties.Check_api))
 }
 
-func (g *syspropJavaGenRule) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		return g.genSrcjars, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
 func syspropJavaGenFactory() android.Module {
 	g := &syspropJavaGenRule{}
 	g.AddProperties(&g.properties)
diff --git a/tests/Android.bp b/tests/Android.bp
new file mode 100644
index 0000000..458cf4b
--- /dev/null
+++ b/tests/Android.bp
@@ -0,0 +1,34 @@
+// Copyright 2024 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+    default_team: "trendy_team_build",
+}
+
+python_test_host {
+    name: "run_tool_with_logging_test",
+    main: "run_tool_with_logging_test.py",
+    pkg_path: "testdata",
+    srcs: [
+        "run_tool_with_logging_test.py",
+    ],
+    test_options: {
+        unit_test: true,
+    },
+    data: [
+        ":run_tool_with_logging_script",
+        ":tool_event_logger",
+    ],
+}
diff --git a/tests/run_tool_with_logging_test.py b/tests/run_tool_with_logging_test.py
new file mode 100644
index 0000000..57a6d62
--- /dev/null
+++ b/tests/run_tool_with_logging_test.py
@@ -0,0 +1,337 @@
+# 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.
+
+import dataclasses
+import glob
+from importlib import resources
+import logging
+import os
+from pathlib import Path
+import re
+import shutil
+import signal
+import stat
+import subprocess
+import sys
+import tempfile
+import textwrap
+import time
+import unittest
+
+EXII_RETURN_CODE = 0
+INTERRUPTED_RETURN_CODE = 130
+
+
+class RunToolWithLoggingTest(unittest.TestCase):
+
+  @classmethod
+  def setUpClass(cls):
+    super().setUpClass()
+    # Configure to print logging to stdout.
+    logging.basicConfig(filename=None, level=logging.DEBUG)
+    console = logging.StreamHandler(sys.stdout)
+    logging.getLogger("").addHandler(console)
+
+  def setUp(self):
+    super().setUp()
+    self.working_dir = tempfile.TemporaryDirectory()
+    # Run all the tests from working_dir which is our temp Android build top.
+    os.chdir(self.working_dir.name)
+
+    self.logging_script_path = self._import_executable("run_tool_with_logging")
+
+  def tearDown(self):
+    self.working_dir.cleanup()
+    super().tearDown()
+
+  def test_does_not_log_when_logger_var_empty(self):
+    test_tool = TestScript.create(self.working_dir)
+
+    self._run_script_and_wait(f"""
+      export ANDROID_TOOL_LOGGER=""
+      {self.logging_script_path} "FAKE_TOOL" {test_tool.executable} arg1 arg2
+    """)
+
+    test_tool.assert_called_once_with_args("arg1 arg2")
+
+  def test_does_not_log_with_logger_unset(self):
+    test_tool = TestScript.create(self.working_dir)
+
+    self._run_script_and_wait(f"""
+      unset ANDROID_TOOL_LOGGER
+      {self.logging_script_path} "FAKE_TOOL" {test_tool.executable} arg1 arg2
+    """)
+
+    test_tool.assert_called_once_with_args("arg1 arg2")
+
+  def test_log_success_with_logger_enabled(self):
+    test_tool = TestScript.create(self.working_dir)
+    test_logger = TestScript.create(self.working_dir)
+
+    self._run_script_and_wait(f"""
+      export ANDROID_TOOL_LOGGER="{test_logger.executable}"
+      {self.logging_script_path} "FAKE_TOOL" {test_tool.executable} arg1 arg2
+    """)
+
+    test_tool.assert_called_once_with_args("arg1 arg2")
+    expected_logger_args = (
+        "--tool_tag=FAKE_TOOL --start_timestamp=\d+\.\d+ --end_timestamp="
+        "\d+\.\d+ --tool_args=arg1 arg2 --exit_code=0"
+    )
+    test_logger.assert_called_once_with_args(expected_logger_args)
+
+  def test_run_tool_output_is_same_with_and_without_logging(self):
+    test_tool = TestScript.create(self.working_dir, "echo 'tool called'")
+    test_logger = TestScript.create(self.working_dir)
+
+    run_tool_with_logging_stdout, run_tool_with_logging_stderr = (
+        self._run_script_and_wait(f"""
+      export ANDROID_TOOL_LOGGER="{test_logger.executable}"
+      {self.logging_script_path} "FAKE_TOOL" {test_tool.executable} arg1 arg2
+    """)
+    )
+
+    run_tool_without_logging_stdout, run_tool_without_logging_stderr = (
+        self._run_script_and_wait(f"""
+      export ANDROID_TOOL_LOGGER="{test_logger.executable}"
+      {test_tool.executable} arg1 arg2
+    """)
+    )
+
+    self.assertEqual(
+        run_tool_with_logging_stdout, run_tool_without_logging_stdout
+    )
+    self.assertEqual(
+        run_tool_with_logging_stderr, run_tool_without_logging_stderr
+    )
+
+  def test_logger_output_is_suppressed(self):
+    test_tool = TestScript.create(self.working_dir)
+    test_logger = TestScript.create(self.working_dir, "echo 'logger called'")
+
+    run_tool_with_logging_output, _ = self._run_script_and_wait(f"""
+      export ANDROID_TOOL_LOGGER="{test_logger.executable}"
+      {self.logging_script_path} "FAKE_TOOL" {test_tool.executable} arg1 arg2
+    """)
+
+    self.assertNotIn("logger called", run_tool_with_logging_output)
+
+  def test_logger_error_is_suppressed(self):
+    test_tool = TestScript.create(self.working_dir)
+    test_logger = TestScript.create(
+        self.working_dir, "echo 'logger failed' > /dev/stderr; exit 1"
+    )
+
+    _, err = self._run_script_and_wait(f"""
+      export ANDROID_TOOL_LOGGER="{test_logger.executable}"
+      {self.logging_script_path} "FAKE_TOOL" {test_tool.executable} arg1 arg2
+    """)
+
+    self.assertNotIn("logger failed", err)
+
+  def test_log_success_when_tool_interrupted(self):
+    test_tool = TestScript.create(self.working_dir, script_body="sleep 100")
+    test_logger = TestScript.create(self.working_dir)
+
+    process = self._run_script(f"""
+      export ANDROID_TOOL_LOGGER="{test_logger.executable}"
+      {self.logging_script_path} "FAKE_TOOL" {test_tool.executable} arg1 arg2
+    """)
+
+    pgid = os.getpgid(process.pid)
+    # Give sometime for the subprocess to start.
+    time.sleep(1)
+    # Kill the subprocess and any processes created in the same group.
+    os.killpg(pgid, signal.SIGINT)
+
+    returncode, _, _ = self._wait_for_process(process)
+    self.assertEqual(returncode, INTERRUPTED_RETURN_CODE)
+
+    expected_logger_args = (
+        "--tool_tag=FAKE_TOOL --start_timestamp=\d+\.\d+ --end_timestamp="
+        "\d+\.\d+ --tool_args=arg1 arg2 --exit_code=130"
+    )
+    test_logger.assert_called_once_with_args(expected_logger_args)
+
+  def test_logger_can_be_toggled_on(self):
+    test_tool = TestScript.create(self.working_dir)
+    test_logger = TestScript.create(self.working_dir)
+
+    self._run_script_and_wait(f"""
+      export ANDROID_TOOL_LOGGER=""
+      export ANDROID_TOOL_LOGGER="{test_logger.executable}"
+      {self.logging_script_path} "FAKE_TOOL" {test_tool.executable} arg1 arg2
+    """)
+
+    test_logger.assert_called_with_times(1)
+
+  def test_logger_can_be_toggled_off(self):
+    test_tool = TestScript.create(self.working_dir)
+    test_logger = TestScript.create(self.working_dir)
+
+    self._run_script_and_wait(f"""
+      export ANDROID_TOOL_LOGGER="{test_logger.executable}"
+      export ANDROID_TOOL_LOGGER=""
+      {self.logging_script_path} "FAKE_TOOL" {test_tool.executable} arg1 arg2
+    """)
+
+    test_logger.assert_not_called()
+
+  def test_integration_tool_event_logger_dry_run(self):
+    test_tool = TestScript.create(self.working_dir)
+    logger_path = self._import_executable("tool_event_logger")
+
+    self._run_script_and_wait(f"""
+      TMPDIR="{self.working_dir.name}"
+      export ANDROID_TOOL_LOGGER="{logger_path}"
+      export ANDROID_TOOL_LOGGER_EXTRA_ARGS="--dry_run"
+      {self.logging_script_path} "FAKE_TOOL" {test_tool.executable} arg1 arg2
+    """)
+
+    self._assert_logger_dry_run()
+
+  def test_tool_args_do_not_fail_logger(self):
+    test_tool = TestScript.create(self.working_dir)
+    logger_path = self._import_executable("tool_event_logger")
+
+    self._run_script_and_wait(f"""
+      TMPDIR="{self.working_dir.name}"
+      export ANDROID_TOOL_LOGGER="{logger_path}"
+      export ANDROID_TOOL_LOGGER_EXTRA_ARGS="--dry_run"
+      {self.logging_script_path} "FAKE_TOOL" {test_tool.executable} --tool-arg1
+    """)
+
+    self._assert_logger_dry_run()
+
+  def _import_executable(self, executable_name: str) -> Path:
+    # logger = "tool_event_logger"
+    executable_path = Path(self.working_dir.name).joinpath(executable_name)
+    with resources.as_file(
+        resources.files("testdata").joinpath(executable_name)
+    ) as p:
+      shutil.copy(p, executable_path)
+    Path.chmod(executable_path, 0o755)
+    return executable_path
+
+  def _assert_logger_dry_run(self):
+    log_files = glob.glob(self.working_dir.name + "/tool_event_logger_*/*.log")
+    self.assertEqual(len(log_files), 1)
+
+    with open(log_files[0], "r") as f:
+      lines = f.readlines()
+      self.assertEqual(len(lines), 1)
+      self.assertIn("dry run", lines[0])
+
+  def _run_script_and_wait(self, test_script: str) -> tuple[str, str]:
+    process = self._run_script(test_script)
+    returncode, out, err = self._wait_for_process(process)
+    logging.debug("script stdout: %s", out)
+    logging.debug("script stderr: %s", err)
+    self.assertEqual(returncode, EXII_RETURN_CODE)
+    return out, err
+
+  def _run_script(self, test_script: str) -> subprocess.Popen:
+    return subprocess.Popen(
+        test_script,
+        shell=True,
+        stdout=subprocess.PIPE,
+        stderr=subprocess.PIPE,
+        text=True,
+        start_new_session=True,
+        executable="/bin/bash",
+    )
+
+  def _wait_for_process(
+      self, process: subprocess.Popen
+  ) -> tuple[int, str, str]:
+    pgid = os.getpgid(process.pid)
+    out, err = process.communicate()
+    # Wait for all process in the same group to complete since the logger runs
+    # as a separate detached process.
+    self._wait_for_process_group(pgid)
+    return (process.returncode, out, err)
+
+  def _wait_for_process_group(self, pgid: int, timeout: int = 5):
+    """Waits for all subprocesses within the process group to complete."""
+    start_time = time.time()
+    while True:
+      if time.time() - start_time > timeout:
+        raise TimeoutError(
+            f"Process group did not complete after {timeout} seconds"
+        )
+      for pid in os.listdir("/proc"):
+        if pid.isdigit():
+          try:
+            if os.getpgid(int(pid)) == pgid:
+              time.sleep(0.1)
+              break
+          except (FileNotFoundError, PermissionError, ProcessLookupError):
+            pass
+      else:
+        # All processes have completed.
+        break
+
+
+@dataclasses.dataclass
+class TestScript:
+  executable: Path
+  output_file: Path
+
+  def create(temp_dir: Path, script_body: str = ""):
+    with tempfile.NamedTemporaryFile(dir=temp_dir.name, delete=False) as f:
+      output_file = f.name
+
+    with tempfile.NamedTemporaryFile(dir=temp_dir.name, delete=False) as f:
+      executable = f.name
+      executable_contents = textwrap.dedent(f"""
+      #!/bin/bash
+
+      echo "${{@}}" >> {output_file}
+      {script_body}
+      """)
+      f.write(executable_contents.encode("utf-8"))
+
+    Path.chmod(f.name, os.stat(f.name).st_mode | stat.S_IEXEC)
+
+    return TestScript(executable, output_file)
+
+  def assert_called_with_times(self, expected_call_times: int):
+    lines = self._read_contents_from_output_file()
+    assert len(lines) == expected_call_times, (
+        f"Expect to call {expected_call_times} times, but actually called"
+        f" {len(lines)} times."
+    )
+
+  def assert_called_with_args(self, expected_args: str):
+    lines = self._read_contents_from_output_file()
+    assert len(lines) > 0
+    assert re.search(expected_args, lines[0]), (
+        f"Expect to call with args {expected_args}, but actually called with"
+        f" args {lines[0]}."
+    )
+
+  def assert_not_called(self):
+    self.assert_called_with_times(0)
+
+  def assert_called_once_with_args(self, expected_args: str):
+    self.assert_called_with_times(1)
+    self.assert_called_with_args(expected_args)
+
+  def _read_contents_from_output_file(self) -> list[str]:
+    with open(self.output_file, "r") as f:
+      return f.readlines()
+
+
+if __name__ == "__main__":
+  unittest.main()
diff --git a/tradefed/providers.go b/tradefed/providers.go
index 66cb625..0abac12 100644
--- a/tradefed/providers.go
+++ b/tradefed/providers.go
@@ -6,7 +6,8 @@
 	"github.com/google/blueprint"
 )
 
-// Output files we need from a base test that we derive from.
+// 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
@@ -19,8 +20,14 @@
 	RequiredModuleNames     []string
 	// List of test suites base uses.
 	TestSuites []string
-	// Used for bases that are Host
+	// True indicates the base modules is built for Host.
 	IsHost bool
+	// Base's sdk version for AndroidMkEntries, generally only used for Host modules.
+	LocalSdkVersion string
+	// Base's certificate for AndroidMkEntries, generally only used for device modules.
+	LocalCertificate string
+	// Indicates if the base module was a unit test.
+	IsUnitTest bool
 }
 
 var BaseTestProviderKey = blueprint.NewProvider[BaseTestProviderData]()
diff --git a/tradefed_modules/test_module_config.go b/tradefed_modules/test_module_config.go
index 9127f67..f9622d3 100644
--- a/tradefed_modules/test_module_config.go
+++ b/tradefed_modules/test_module_config.go
@@ -5,6 +5,8 @@
 	"android/soong/tradefed"
 	"encoding/json"
 	"fmt"
+	"io"
+	"strings"
 
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
@@ -23,14 +25,17 @@
 type testModuleConfigModule struct {
 	android.ModuleBase
 	android.DefaultableModuleBase
-	base android.Module
 
 	tradefedProperties
 
 	// Our updated testConfig.
 	testConfig android.OutputPath
-	manifest   android.InstallPath
+	manifest   android.OutputPath
 	provider   tradefed.BaseTestProviderData
+
+	supportFiles android.InstallPaths
+
+	isHost bool
 }
 
 // Host is mostly the same as non-host, just some diffs for AddDependency and
@@ -94,7 +99,6 @@
 
 // Takes base's Tradefed Config xml file and generates a new one with the test properties
 // appeneded from this module.
-// Rewrite the name of the apk in "test-file-name" to be our module's name, rather than the original one.
 func (m *testModuleConfigModule) fixTestConfig(ctx android.ModuleContext, baseTestConfig android.Path) android.OutputPath {
 	// Test safe to do when no test_runner_options, but check for that earlier?
 	fixedConfig := android.PathForModuleOut(ctx, "test_config_fixer", ctx.ModuleName()+".config")
@@ -106,9 +110,8 @@
 	}
 	xmlTestModuleConfigSnippet, _ := json.Marshal(options)
 	escaped := proptools.NinjaAndShellEscape(string(xmlTestModuleConfigSnippet))
-	command.FlagWithArg("--test-file-name=", ctx.ModuleName()+".apk").
-		FlagWithArg("--orig-test-file-name=", *m.tradefedProperties.Base+".apk").
-		FlagWithArg("--test-runner-options=", escaped)
+	command.FlagWithArg("--test-runner-options=", escaped)
+
 	rule.Build("fix_test_config", "fix test config")
 	return fixedConfig.OutputPath
 }
@@ -160,35 +163,17 @@
 
 }
 
-// Any test suites in base should not be repeated in the derived class, except "general-tests".
-// We may restrict derived tests to only be "general-tests" as it doesn't make sense to add a slice
-// of a test to compatibility suite.
+// Ensure at least one test_suite is listed.  Ideally it should be general-tests
+// or device-tests, whichever is listed in base and prefer general-tests if both are listed.
+// However this is not enforced yet.
 //
-// Returns ErrorMessage, false on problems
-// Returns _, true if okay.
+// Returns true if okay and reports errors via ModuleErrorf.
 func (m *testModuleConfigModule) validateTestSuites(ctx android.ModuleContext) bool {
 	if len(m.tradefedProperties.Test_suites) == 0 {
-		ctx.ModuleErrorf("At least one test-suite must be set or this won't run. Use \"general-tests\"")
+		ctx.ModuleErrorf("At least one test-suite must be set or this won't run. Use \"general-tests\" or \"device-tests\"")
 		return false
 	}
 
-	derivedSuites := make(map[string]bool)
-	// See if any suites in base is also in derived (other than general-tests)
-	for _, s := range m.tradefedProperties.Test_suites {
-		if s != "general-tests" {
-			derivedSuites[s] = true
-		}
-	}
-	if len(derivedSuites) == 0 {
-		return true
-	}
-	for _, baseSuite := range m.provider.TestSuites {
-		if derivedSuites[baseSuite] {
-			ctx.ModuleErrorf("TestSuite %s exists in the base, do not add it here", baseSuite)
-			return false
-		}
-	}
-
 	return true
 }
 
@@ -208,6 +193,7 @@
 	module.AddProperties(&module.tradefedProperties)
 	android.InitAndroidMultiTargetsArchModule(module, android.HostSupported, android.MultilibCommon)
 	android.InitDefaultableModule(module)
+	module.isHost = true
 
 	return module
 }
@@ -216,39 +202,66 @@
 var _ android.AndroidMkEntriesProvider = (*testModuleConfigModule)(nil)
 
 func (m *testModuleConfigModule) AndroidMkEntries() []android.AndroidMkEntries {
-	// We rely on base writing LOCAL_COMPATIBILITY_SUPPORT_FILES for its data files
-	entriesList := m.base.(android.AndroidMkEntriesProvider).AndroidMkEntries()
-	entries := &entriesList[0]
-	entries.OutputFile = android.OptionalPathForPath(m.provider.OutputFile)
-	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		entries.SetString("LOCAL_MODULE", m.Name()) //  out module name, not base's
+	appClass := "APPS"
+	include := "$(BUILD_SYSTEM)/soong_app_prebuilt.mk"
+	if m.isHost {
+		appClass = "JAVA_LIBRARIES"
+		include = "$(BUILD_SYSTEM)/soong_java_prebuilt.mk"
+	}
+	return []android.AndroidMkEntries{{
+		Class:      appClass,
+		OutputFile: android.OptionalPathForPath(m.manifest),
+		Include:    include,
+		Required:   []string{*m.Base},
+		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)
+				}
+				if m.provider.LocalCertificate != "" {
+					entries.SetString("LOCAL_CERTIFICATE", m.provider.LocalCertificate)
+				}
 
-		// Out update config file with extra options.
-		entries.SetPath("LOCAL_FULL_TEST_CONFIG", m.testConfig)
-		entries.SetString("LOCAL_MODULE_TAGS", "tests")
+				entries.SetBoolIfTrue("LOCAL_IS_UNIT_TEST", m.provider.IsUnitTest)
+				entries.AddCompatibilityTestSuites(m.tradefedProperties.Test_suites...)
 
-		// Don't append to base's test-suites, only use the ones we define, so clear it before
-		// appending to it.
-		entries.SetString("LOCAL_COMPATIBILITY_SUITE", "")
-		entries.AddCompatibilityTestSuites(m.tradefedProperties.Test_suites...)
+				// 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 len(m.provider.HostRequiredModuleNames) > 0 {
-			entries.AddStrings("LOCAL_HOST_REQUIRED_MODULES", m.provider.HostRequiredModuleNames...)
-		}
-		if len(m.provider.RequiredModuleNames) > 0 {
-			entries.AddStrings("LOCAL_REQUIRED_MODULES", m.provider.RequiredModuleNames...)
-		}
-
-		if m.provider.IsHost == false {
-			// Not needed for jar_host_test
-			//
-			// Clear the JNI symbols because they belong to base not us. Either transform the names in the string
-			// or clear the variable because we don't need it, we are copying bases libraries not generating
-			// new ones.
-			entries.SetString("LOCAL_SOONG_JNI_LIBS_SYMBOLS", "")
-		}
-	})
-	return entriesList
+				// 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.
+				// tasks/general-tests.mk, tasks/devices-tests.mk also use these to figure out
+				// which testcase files to put in a zip for running tests on another machine.
+				//
+				// We need our files to end up in the zip, but we don't want \.mk files to
+				// `install` files for us.
+				// So we create a new make variable to indicate these should be in the zip
+				// but not installed.
+				entries.AddStrings("LOCAL_SOONG_INSTALLED_COMPATIBILITY_SUPPORT_FILES", m.supportFiles.Strings()...)
+			},
+		},
+		// Ensure each of our supportFiles depends on the installed file in base so that our symlinks will always
+		// resolve.  The provider gives us the .intermediate path for the support file in base, we change it to
+		// the installed path with a string substitution.
+		ExtraFooters: []android.AndroidMkExtraFootersFunc{
+			func(w io.Writer, name, prefix, moduleDir string) {
+				for _, f := range m.supportFiles.Strings() {
+					// convert out/.../testcases/FrameworksServicesTests_contentprotection/file1.apk
+					// to      out/.../testcases/FrameworksServicesTests/file1.apk
+					basePath := strings.Replace(f, "/"+m.Name()+"/", "/"+*m.Base+"/", 1)
+					fmt.Fprintf(w, "%s: %s\n", f, basePath)
+				}
+			},
+		},
+	}}
 }
 
 func (m *testModuleConfigHostModule) DepsMutator(ctx android.BottomUpMutatorContext) {
@@ -266,7 +279,7 @@
 //   - written via soong_java_prebuilt.mk
 //
 // 4) out/host/linux-x86/testcases/derived-module/* # data dependencies from base.
-//   - written via soong_java_prebuilt.mk
+//   - written via our InstallSymlink
 func (m *testModuleConfigHostModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	m.validateBase(ctx, &testModuleConfigHostTag, "java_test_host", true)
 	m.generateManifestAndConfig(ctx)
@@ -278,7 +291,6 @@
 	ctx.VisitDirectDepsWithTag(*depTag, func(dep android.Module) {
 		if provider, ok := android.OtherModuleProvider(ctx, dep, tradefed.BaseTestProviderKey); ok {
 			if baseShouldBeHost == provider.IsHost {
-				m.base = dep
 				m.provider = provider
 			} else {
 				if baseShouldBeHost {
@@ -295,7 +307,9 @@
 
 // Actions to write:
 //  1. manifest file to testcases dir
-//  2. New Module.config / AndroidTest.xml file with our options.
+//  2. Symlink to base.apk under base's arch dir
+//  3. Symlink to all data dependencies
+//  4. New Module.config / AndroidTest.xml file with our options.
 func (m *testModuleConfigModule) generateManifestAndConfig(ctx android.ModuleContext) {
 	// Keep before early returns.
 	android.SetProvider(ctx, android.TestOnlyProviderKey, android.TestModuleInformation{
@@ -310,7 +324,6 @@
 	if m.provider.TestConfig == nil {
 		return
 	}
-
 	// 1) A manifest file listing the base, write text to a tiny file.
 	installDir := android.PathForModuleInstall(ctx, ctx.ModuleName())
 	manifest := android.PathForModuleOut(ctx, "test_module_config.manifest")
@@ -319,9 +332,53 @@
 	//    Assume the primary install file is last
 	// so we need to Install our file last.
 	ctx.InstallFile(installDir, manifest.Base(), manifest)
+	m.manifest = manifest.OutputPath
 
-	// 2) Module.config / AndroidTest.xml
+	// 2) Symlink to base.apk
+	baseApk := m.provider.OutputFile
+
+	// Typically looks like this for baseApk
+	// 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()
+	}
+	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
+	// FrameworksServicesTests
+	// ├── data
+	// │   └── broken_shortcut.xml
+	// ├── JobTestApp.apk
+	for _, f := range m.provider.InstalledFiles {
+		symlinkName := f.Rel()
+		target := installedBaseRelativeToHere(symlinkName, *m.tradefedProperties.Base)
+		installedPath := ctx.InstallAbsoluteSymlink(installDir, symlinkName, target)
+		m.supportFiles = append(m.supportFiles, installedPath)
+	}
+
+	// 4) Module.config / AndroidTest.xml
 	m.testConfig = m.fixTestConfig(ctx, m.provider.TestConfig)
 }
 
 var _ android.AndroidMkEntriesProvider = (*testModuleConfigHostModule)(nil)
+
+// 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".
+//
+//	 ThisDir/file1
+//	 ThisDir/subdir/file2
+//	would return "../base/file1" or "../../subdir/file2"
+func installedBaseRelativeToHere(targetFileName string, base string) string {
+	backup := strings.Repeat("../", strings.Count(targetFileName, "/")+1)
+	return fmt.Sprintf("%s%s/%s", backup, base, targetFileName)
+}
diff --git a/tradefed_modules/test_module_config_test.go b/tradefed_modules/test_module_config_test.go
index 6997228..97179f5 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"
+	"fmt"
 	"strconv"
 	"strings"
 	"testing"
@@ -69,15 +70,36 @@
 	entries := android.AndroidMkEntriesForTest(t, ctx.TestContext, derived.Module())[0]
 
 	// Ensure some entries from base are there, specifically support files for data and helper apps.
-	assertEntryPairValues(t, entries.EntryMap["LOCAL_COMPATIBILITY_SUPPORT_FILES"], []string{"HelperApp.apk", "data/testfile"})
+	// 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"},
+		entries.EntryMap["LOCAL_SOONG_INSTALLED_COMPATIBILITY_SUPPORT_FILES"])
+	android.AssertArrayString(t, "", entries.EntryMap["LOCAL_COMPATIBILITY_SUPPORT_FILES"], []string{})
+
+	android.AssertArrayString(t, "", entries.EntryMap["LOCAL_REQUIRED_MODULES"], []string{"base"})
+	android.AssertArrayString(t, "", entries.EntryMap["LOCAL_CERTIFICATE"], []string{"build/make/target/product/security/testkey.x509.pem"})
+	android.AssertStringEquals(t, "", entries.Class, "APPS")
 
 	// And some new derived entries are there.
 	android.AssertArrayString(t, "", entries.EntryMap["LOCAL_MODULE_TAGS"], []string{"tests"})
 
-	// And ones we override
-	android.AssertArrayString(t, "", entries.EntryMap["LOCAL_SOONG_JNI_LIBS_SYMBOLS"], []string{""})
-
 	android.AssertStringMatches(t, "", entries.EntryMap["LOCAL_FULL_TEST_CONFIG"][0], "derived_test/android_common/test_config_fixer/derived_test.config")
+
+	// 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)
+	}
+	android.AssertArrayString(t, fmt.Sprintf("%s", ctx.Config.SoongOutDir()), convertedActual, []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",
+		"",
+	})
 }
 
 // Make sure we call test-config-fixer with the right args.
@@ -92,7 +114,7 @@
 	derived := ctx.ModuleForTests("derived_test", "android_common")
 	rule_cmd := derived.Rule("fix_test_config").RuleParams.Command
 	android.AssertStringDoesContain(t, "Bad FixConfig rule inputs", rule_cmd,
-		`--test-file-name=derived_test.apk --orig-test-file-name=base.apk --test-runner-options='[{"Name":"exclude-filter","Key":"","Value":"android.test.example.devcodelab.DevCodelabTest#testHelloFail"},{"Name":"include-annotation","Key":"","Value":"android.platform.test.annotations.LargeTest"}]'`)
+		`--test-runner-options='[{"Name":"exclude-filter","Key":"","Value":"android.test.example.devcodelab.DevCodelabTest#testHelloFail"},{"Name":"include-annotation","Key":"","Value":"android.platform.test.annotations.LargeTest"}]'`)
 }
 
 // Ensure we error for a base we don't support.
@@ -195,8 +217,14 @@
 			name: "base",
 			sdk_version: "current",
                         srcs: ["a.java"],
+                        data: [":HelperApp", "data/testfile"],
 		}
 
+                android_test_helper_app {
+                        name: "HelperApp",
+                        srcs: ["helper.java"],
+                }
+
                 test_module_config {
                         name: "derived_test",
                         base: "base",
@@ -220,8 +248,12 @@
 		derived := ctx.ModuleForTests("derived_test", "android_common")
 		entries := android.AndroidMkEntriesForTest(t, ctx.TestContext, derived.Module())[0]
 		// All these should be the same in both derived tests
-		assertEntryPairValues(t, entries.EntryMap["LOCAL_COMPATIBILITY_SUPPORT_FILES"], []string{"HelperApp.apk", "data/testfile"})
-		android.AssertArrayString(t, "", entries.EntryMap["LOCAL_SOONG_JNI_LIBS_SYMBOLS"], []string{""})
+		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"},
+			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")
 		// And this one, the module name.
@@ -232,8 +264,11 @@
 		derived := ctx.ModuleForTests("another_derived_test", "android_common")
 		entries := android.AndroidMkEntriesForTest(t, ctx.TestContext, derived.Module())[0]
 		// All these should be the same in both derived tests
-		assertEntryPairValues(t, entries.EntryMap["LOCAL_COMPATIBILITY_SUPPORT_FILES"], []string{"HelperApp.apk", "data/testfile"})
-		android.AssertArrayString(t, "", entries.EntryMap["LOCAL_SOONG_JNI_LIBS_SYMBOLS"], []string{""})
+		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"},
+			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")
 		// And this one, the module name.
@@ -269,6 +304,8 @@
 	allEntries := android.AndroidMkEntriesForTest(t, ctx.TestContext, mod)
 	entries := allEntries[0]
 	android.AssertArrayString(t, "", entries.EntryMap["LOCAL_MODULE"], []string{"derived_test"})
+	android.AssertArrayString(t, "", entries.EntryMap["LOCAL_SDK_VERSION"], []string{"private_current"})
+	android.AssertStringEquals(t, "", entries.Class, "JAVA_LIBRARIES")
 
 	if !mod.Host() {
 		t.Errorf("host bit is not set for a java_test_host module.")
@@ -325,30 +362,6 @@
 		RunTestWithBp(t, badBp)
 }
 
-func TestModuleConfigHostDuplicateTestSuitesGiveErrors(t *testing.T) {
-	badBp := `
-		java_test_host {
-			name: "base",
-                        srcs: ["a.java"],
-                        test_suites: ["general-tests", "some-compat"],
-		}
-
-                test_module_config_host {
-                        name: "derived_test",
-                        base: "base",
-                        exclude_filters: ["android.test.example.devcodelab.DevCodelabTest#testHelloFail"],
-                        include_annotations: ["android.platform.test.annotations.LargeTest"],
-                        test_suites: ["general-tests", "some-compat"],
-                }`
-
-	android.GroupFixturePreparers(
-		java.PrepareForTestWithJavaDefaultModules,
-		android.FixtureRegisterWithContext(RegisterTestModuleConfigBuildComponents),
-	).ExtendWithErrorHandler(
-		android.FixtureExpectsAtLeastOneErrorMatchingPattern("TestSuite some-compat exists in the base")).
-		RunTestWithBp(t, badBp)
-}
-
 func TestTestOnlyProvider(t *testing.T) {
 	t.Parallel()
 	ctx := android.GroupFixturePreparers(
@@ -409,16 +422,3 @@
 		t.Errorf("test-only: Expected but not found: %v, Found but not expected: %v", left, right)
 	}
 }
-
-// Use for situations where the entries map contains pairs:  [srcPath:installedPath1, srcPath2:installedPath2]
-// and we want to compare the RHS of the pairs, i.e. installedPath1, installedPath2
-func assertEntryPairValues(t *testing.T, actual []string, expected []string) {
-	for i, e := range actual {
-		parts := strings.Split(e, ":")
-		if len(parts) != 2 {
-			t.Errorf("Expected entry to have a value delimited by :, received: %s", e)
-			return
-		}
-		android.AssertStringEquals(t, "", parts[1], expected[i])
-	}
-}
diff --git a/ui/build/Android.bp b/ui/build/Android.bp
index ee286f6..fcf29c5 100644
--- a/ui/build/Android.bp
+++ b/ui/build/Android.bp
@@ -36,6 +36,7 @@
         "blueprint-bootstrap",
         "blueprint-microfactory",
         "soong-android",
+        "soong-elf",
         "soong-finder",
         "soong-remoteexec",
         "soong-shared",
diff --git a/ui/build/androidmk_denylist.go b/ui/build/androidmk_denylist.go
index a29f413..bbac2db 100644
--- a/ui/build/androidmk_denylist.go
+++ b/ui/build/androidmk_denylist.go
@@ -19,10 +19,13 @@
 )
 
 var androidmk_denylist []string = []string{
+	"bionic/",
 	"chained_build_config/",
 	"cts/",
 	"dalvik/",
 	"developers/",
+	"development/",
+	"device/sample/",
 	"frameworks/",
 	// Do not block other directories in kernel/, see b/319658303.
 	"kernel/configs/",
@@ -31,6 +34,10 @@
 	"libcore/",
 	"libnativehelper/",
 	"pdk/",
+	"prebuilts/",
+	"sdk/",
+	"test/",
+	"trusty/",
 	// Add back toolchain/ once defensive Android.mk files are removed
 	//"toolchain/",
 }
diff --git a/ui/build/build.go b/ui/build/build.go
index 9a9eccd..c7319ed 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -21,8 +21,8 @@
 	"path/filepath"
 	"sync"
 	"text/template"
-	"time"
 
+	"android/soong/elf"
 	"android/soong/ui/metrics"
 )
 
@@ -66,9 +66,12 @@
 	// (to allow for source control that uses something other than numbers),
 	// but must be a single word and a valid file name.
 	//
-	// If no BUILD_NUMBER is set, create a useful "I am an engineering build
-	// from this date/time" value.  Make it start with a non-digit so that
-	// anyone trying to parse it as an integer will probably get "0".
+	// If no BUILD_NUMBER is set, create a useful "I am an engineering build"
+	// value.  Make it start with a non-digit so that anyone trying to parse
+	// it as an integer will probably get "0". This value used to contain
+	// a timestamp, but now that more dependencies are tracked in order to
+	// reduce the importance of `m installclean`, changing it every build
+	// causes unnecessary rebuilds for local development.
 	buildNumber, ok := config.environ.Get("BUILD_NUMBER")
 	if ok {
 		writeValueIfChanged(ctx, config, config.OutDir(), "file_name_tag.txt", buildNumber)
@@ -77,7 +80,7 @@
 		if username, ok = config.environ.Get("BUILD_USERNAME"); !ok {
 			ctx.Fatalln("Missing BUILD_USERNAME")
 		}
-		buildNumber = fmt.Sprintf("eng.%.6s.%s", username, time.Now().Format("20060102.150405" /* YYYYMMDD.HHMMSS */))
+		buildNumber = fmt.Sprintf("eng.%.6s.00000000.000000", username)
 		writeValueIfChanged(ctx, config, config.OutDir(), "file_name_tag.txt", username)
 	}
 	// Write the build number to a file so it can be read back in
@@ -342,6 +345,7 @@
 			installCleanIfNecessary(ctx, config)
 		}
 		runNinjaForBuild(ctx, config)
+		updateBuildIdDir(ctx, config)
 	}
 
 	if what&RunDistActions != 0 {
@@ -349,6 +353,16 @@
 	}
 }
 
+func updateBuildIdDir(ctx Context, config Config) {
+	ctx.BeginTrace(metrics.RunShutdownTool, "update_build_id_dir")
+	defer ctx.EndTrace()
+
+	symbolsDir := filepath.Join(config.ProductOut(), "symbols")
+	if err := elf.UpdateBuildIdDir(symbolsDir); err != nil {
+		ctx.Printf("failed to update %s/.build-id: %v", symbolsDir, err)
+	}
+}
+
 func evaluateWhatToRun(config Config, verboseln func(v ...interface{})) int {
 	//evaluate what to run
 	what := 0
diff --git a/ui/build/config.go b/ui/build/config.go
index 7426a78..2470f84 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -85,6 +85,7 @@
 	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
@@ -98,9 +99,10 @@
 	// Autodetected
 	totalRAM uint64
 
-	brokenDupRules     bool
-	brokenUsesNetwork  bool
-	brokenNinjaEnvVars []string
+	brokenDupRules       bool
+	brokenUsesNetwork    bool
+	brokenNinjaEnvVars   []string
+	brokenMissingOutputs bool
 
 	pathReplaced bool
 
@@ -119,6 +121,10 @@
 	// There's quite a bit of overlap with module-info.json and soong module graph. We
 	// could consider merging them.
 	moduleDebugFile string
+
+	// Whether to use n2 instead of ninja.  This is controlled with the
+	// environment variable SOONG_USE_N2
+	useN2 bool
 }
 
 type NinjaWeightListSource uint
@@ -281,6 +287,10 @@
 		ret.moduleDebugFile, _ = filepath.Abs(shared.JoinPath(ret.SoongOutDir(), "soong-debug-info.json"))
 	}
 
+	if os.Getenv("SOONG_USE_N2") == "true" {
+		ret.useN2 = true
+	}
+
 	ret.environ.Unset(
 		// We're already using it
 		"USE_SOONG_UI",
@@ -311,6 +321,7 @@
 		"DISPLAY",
 		"GREP_OPTIONS",
 		"JAVAC",
+		"LEX",
 		"NDK_ROOT",
 		"POSIXLY_CORRECT",
 
@@ -336,6 +347,9 @@
 
 		// We read it here already, don't let others share in the fun
 		"GENERATE_SOONG_DEBUG",
+
+		// Use config.useN2 instead.
+		"SOONG_USE_N2",
 	)
 
 	if ret.UseGoma() || ret.ForceUseGoma() {
@@ -811,6 +825,8 @@
 			}
 		} else if arg == "--build-from-source-stub" {
 			c.buildFromSourceStub = true
+		} else if arg == "--incremental-build-actions" {
+			c.incrementalBuildActions = true
 		} else if strings.HasPrefix(arg, "--build-command=") {
 			buildCmd := strings.TrimPrefix(arg, "--build-command=")
 			// remove quotations
@@ -1164,14 +1180,6 @@
 	c.sourceRootDirs = i
 }
 
-func (c *configImpl) GetIncludeTags() []string {
-	return c.includeTags
-}
-
-func (c *configImpl) SetIncludeTags(i []string) {
-	c.includeTags = i
-}
-
 func (c *configImpl) GetLogsPrefix() string {
 	return c.logsPrefix
 }
@@ -1251,6 +1259,11 @@
 }
 
 func (c *configImpl) canSupportRBE() bool {
+	// Only supported on linux
+	if runtime.GOOS != "linux" {
+		return false
+	}
+
 	// Do not use RBE with prod credentials in scenarios when stubby doesn't exist, since
 	// its unlikely that we will be able to obtain necessary creds without stubby.
 	authType, _ := c.rbeAuth()
@@ -1496,6 +1509,15 @@
 	}
 }
 
+func (c *configImpl) SoongExtraVarsFile() string {
+	targetProduct, err := c.TargetProductOrErr()
+	if err != nil {
+		return filepath.Join(c.SoongOutDir(), "soong.extra.variables")
+	} else {
+		return filepath.Join(c.SoongOutDir(), "soong."+targetProduct+".extra.variables")
+	}
+}
+
 func (c *configImpl) SoongNinjaFile() string {
 	targetProduct, err := c.TargetProductOrErr()
 	if err != nil {
@@ -1599,6 +1621,14 @@
 	return c.brokenNinjaEnvVars
 }
 
+func (c *configImpl) SetBuildBrokenMissingOutputs(val bool) {
+	c.brokenMissingOutputs = val
+}
+
+func (c *configImpl) BuildBrokenMissingOutputs() bool {
+	return c.brokenMissingOutputs
+}
+
 func (c *configImpl) SetTargetDeviceDir(dir string) {
 	c.targetDeviceDir = dir
 }
diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go
index e17bd54..e77df44 100644
--- a/ui/build/dumpvars.go
+++ b/ui/build/dumpvars.go
@@ -147,7 +147,6 @@
 var BannerVars = []string{
 	"PLATFORM_VERSION_CODENAME",
 	"PLATFORM_VERSION",
-	"PRODUCT_INCLUDE_TAGS",
 	"PRODUCT_SOURCE_ROOT_DIRS",
 	"TARGET_PRODUCT",
 	"TARGET_BUILD_VARIANT",
@@ -236,6 +235,11 @@
 		"BUILD_BROKEN_SRC_DIR_IS_WRITABLE",
 		"BUILD_BROKEN_SRC_DIR_RW_ALLOWLIST",
 
+		// Whether missing outputs should be treated as warnings
+		// instead of errors.
+		// `true` will relegate missing outputs to warnings.
+		"BUILD_BROKEN_MISSING_OUTPUTS",
+
 		// Not used, but useful to be in the soong.log
 		"TARGET_BUILD_TYPE",
 		"HOST_ARCH",
@@ -301,6 +305,6 @@
 	config.SetBuildBrokenDupRules(makeVars["BUILD_BROKEN_DUP_RULES"] == "true")
 	config.SetBuildBrokenUsesNetwork(makeVars["BUILD_BROKEN_USES_NETWORK"] == "true")
 	config.SetBuildBrokenNinjaUsesEnvVars(strings.Fields(makeVars["BUILD_BROKEN_NINJA_USES_ENV_VARS"]))
-	config.SetIncludeTags(strings.Fields(makeVars["PRODUCT_INCLUDE_TAGS"]))
 	config.SetSourceRootDirs(strings.Fields(makeVars["PRODUCT_SOURCE_ROOT_DIRS"]))
+	config.SetBuildBrokenMissingOutputs(makeVars["BUILD_BROKEN_MISSING_OUTPUTS"] == "true")
 }
diff --git a/ui/build/ninja.go b/ui/build/ninja.go
index 551b8ab..1935e72 100644
--- a/ui/build/ninja.go
+++ b/ui/build/ninja.go
@@ -56,6 +56,17 @@
 		"-d", "stats",
 		"--frontend_file", fifo,
 	}
+	if config.useN2 {
+		executable = config.PrebuiltBuildTool("n2")
+		args = []string{
+			"-d", "trace",
+			// TODO: implement these features, or remove them.
+			//"-d", "keepdepfile",
+			//"-d", "keeprsp",
+			//"-d", "stats",
+			"--frontend-file", fifo,
+		}
+	}
 
 	args = append(args, config.NinjaArgs()...)
 
@@ -72,10 +83,22 @@
 
 	args = append(args, "-f", config.CombinedNinjaFile())
 
-	args = append(args,
-		"-o", "usesphonyoutputs=yes",
-		"-w", "dupbuild=err",
-		"-w", "missingdepfile=err")
+	if !config.useN2 {
+		args = append(args,
+			"-o", "usesphonyoutputs=yes",
+			"-w", "dupbuild=err",
+			"-w", "missingdepfile=err")
+	}
+
+	if !config.BuildBrokenMissingOutputs() {
+		// Missing outputs will be treated as errors.
+		// BUILD_BROKEN_MISSING_OUTPUTS can be used to bypass this check.
+		if !config.useN2 {
+			args = append(args,
+				"-w", "missingoutfile=err",
+			)
+		}
+	}
 
 	cmd := Command(ctx, config, "ninja", executable, args...)
 
@@ -89,16 +112,22 @@
 
 	switch config.NinjaWeightListSource() {
 	case NINJA_LOG:
-		cmd.Args = append(cmd.Args, "-o", "usesninjalogasweightlist=yes")
+		if !config.useN2 {
+			cmd.Args = append(cmd.Args, "-o", "usesninjalogasweightlist=yes")
+		}
 	case EVENLY_DISTRIBUTED:
 		// pass empty weight list means ninja considers every tasks's weight as 1(default value).
-		cmd.Args = append(cmd.Args, "-o", "usesweightlist=/dev/null")
+		if !config.useN2 {
+			cmd.Args = append(cmd.Args, "-o", "usesweightlist=/dev/null")
+		}
 	case EXTERNAL_FILE:
 		fallthrough
 	case HINT_FROM_SOONG:
 		// The weight list is already copied/generated.
-		ninjaWeightListPath := filepath.Join(config.OutDir(), ninjaWeightListFileName)
-		cmd.Args = append(cmd.Args, "-o", "usesweightlist="+ninjaWeightListPath)
+		if !config.useN2 {
+			ninjaWeightListPath := filepath.Join(config.OutDir(), ninjaWeightListFileName)
+			cmd.Args = append(cmd.Args, "-o", "usesweightlist="+ninjaWeightListPath)
+		}
 	}
 
 	// Allow both NINJA_ARGS and NINJA_EXTRA_ARGS, since both have been
@@ -198,11 +227,16 @@
 			// We don't want this build broken flag to cause reanalysis, so allow it through to the
 			// actions.
 			"BUILD_BROKEN_INCORRECT_PARTITION_IMAGES",
+			"SOONG_USE_N2",
+			"RUST_BACKTRACE",
 		}, config.BuildBrokenNinjaUsesEnvVars()...)...)
 	}
 
 	cmd.Environment.Set("DIST_DIR", config.DistDir())
 	cmd.Environment.Set("SHELL", "/bin/bash")
+	if config.useN2 {
+		cmd.Environment.Set("RUST_BACKTRACE", "1")
+	}
 
 	// Print the environment variables that Ninja is operating in.
 	ctx.Verboseln("Ninja environment: ")
diff --git a/ui/build/paths/config.go b/ui/build/paths/config.go
index 81c678d..6c9a1eb 100644
--- a/ui/build/paths/config.go
+++ b/ui/build/paths/config.go
@@ -86,28 +86,28 @@
 // This list specifies whether a particular binary from $PATH is allowed to be
 // run during the build. For more documentation, see path_interposer.go .
 var Configuration = map[string]PathConfig{
-	"bash":           Allowed,
-	"diff":           Allowed,
-	"dlv":            Allowed,
-	"expr":           Allowed,
-	"fuser":          Allowed,
-	"gcert":          Allowed,
-	"gcertstatus":    Allowed,
-	"gcloud":         Allowed,
-	"git":            Allowed,
-	"hexdump":        Allowed,
-	"jar":            Allowed,
-	"java":           Allowed,
-	"javap":          Allowed,
-	"lsof":           Allowed,
-	"openssl":        Allowed,
-	"pstree":         Allowed,
-	"rsync":          Allowed,
-	"sh":             Allowed,
-	"stubby":         Allowed,
-	"tr":             Allowed,
-	"unzip":          Allowed,
-	"zip":            Allowed,
+	"bash":        Allowed,
+	"diff":        Allowed,
+	"dlv":         Allowed,
+	"expr":        Allowed,
+	"fuser":       Allowed,
+	"gcert":       Allowed,
+	"gcertstatus": Allowed,
+	"gcloud":      Allowed,
+	"git":         Allowed,
+	"hexdump":     Allowed,
+	"jar":         Allowed,
+	"java":        Allowed,
+	"javap":       Allowed,
+	"lsof":        Allowed,
+	"openssl":     Allowed,
+	"pstree":      Allowed,
+	"rsync":       Allowed,
+	"sh":          Allowed,
+	"stubby":      Allowed,
+	"tr":          Allowed,
+	"unzip":       Allowed,
+	"zip":         Allowed,
 
 	// Host toolchain is removed. In-tree toolchain should be used instead.
 	// GCC also can't find cc1 with this implementation.
diff --git a/ui/build/rbe.go b/ui/build/rbe.go
index 5142a41..8fa147f 100644
--- a/ui/build/rbe.go
+++ b/ui/build/rbe.go
@@ -159,12 +159,6 @@
 		fmt.Fprintln(ctx.Writer, "")
 		return
 	}
-	if config.GoogleProdCredsExist() {
-		return
-	}
-	fmt.Fprintln(ctx.Writer, "")
-	fmt.Fprintln(ctx.Writer, "\033[33mWARNING: Missing LOAS credentials, please run `gcert`. This is required for a successful build execution. See go/rbe-android-default-announcement for more information.\033[0m")
-	fmt.Fprintln(ctx.Writer, "")
 }
 
 // DumpRBEMetrics creates a metrics protobuf file containing RBE related metrics.
diff --git a/ui/build/soong.go b/ui/build/soong.go
index 79584c6..e18cc25 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -15,7 +15,6 @@
 package build
 
 import (
-	"android/soong/ui/tracer"
 	"fmt"
 	"io/fs"
 	"os"
@@ -26,7 +25,8 @@
 	"sync/atomic"
 	"time"
 
-	"android/soong/bazel"
+	"android/soong/ui/tracer"
+
 	"android/soong/ui/metrics"
 	"android/soong/ui/metrics/metrics_proto"
 	"android/soong/ui/status"
@@ -270,7 +270,13 @@
 	} else if !exists {
 		// The tree is out of date for the current epoch, delete files used by bootstrap
 		// and force the primary builder to rerun.
-		os.Remove(config.SoongNinjaFile())
+		soongNinjaFile := config.SoongNinjaFile()
+		os.Remove(soongNinjaFile)
+		for _, file := range blueprint.GetNinjaShardFiles(soongNinjaFile) {
+			if ok, _ := fileExists(file); ok {
+				os.Remove(file)
+			}
+		}
 		for _, globFile := range bootstrapGlobFileList(config) {
 			os.Remove(globFile)
 		}
@@ -308,6 +314,9 @@
 	if config.ensureAllowlistIntegrity {
 		mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--ensure-allowlist-integrity")
 	}
+	if config.incrementalBuildActions {
+		mainSoongBuildExtraArgs = append(mainSoongBuildExtraArgs, "--incremental-build-actions")
+	}
 
 	queryviewDir := filepath.Join(config.SoongOutDir(), "queryview")
 
@@ -394,7 +403,6 @@
 	}
 
 	blueprintCtx := blueprint.NewContext()
-	blueprintCtx.AddIncludeTags(config.GetIncludeTags()...)
 	blueprintCtx.AddSourceRootDirs(config.GetSourceRootDirs()...)
 	blueprintCtx.SetIgnoreUnknownModuleTypes(true)
 	blueprintConfig := BlueprintConfig{
@@ -594,10 +602,6 @@
 
 		checkEnvironmentFile(ctx, soongBuildEnv, config.UsedEnvFile(soongBuildTag))
 
-		// Remove bazel files in the event that bazel is disabled for the build.
-		// These files may have been left over from a previous bazel-enabled build.
-		cleanBazelFiles(config)
-
 		if config.JsonModuleGraph() {
 			checkEnvironmentFile(ctx, soongBuildEnv, config.UsedEnvFile(jsonModuleGraphTag))
 		}
@@ -634,6 +638,22 @@
 			"--frontend_file", fifo,
 			"-f", filepath.Join(config.SoongOutDir(), "bootstrap.ninja"),
 		}
+		if config.useN2 {
+			ninjaArgs = []string{
+				// TODO: implement these features, or remove them.
+				//"-d", "keepdepfile",
+				//"-d", "stats",
+				//"-o", "usesphonyoutputs=yes",
+				//"-o", "preremoveoutputs=yes",
+				//"-w", "dupbuild=err",
+				//"-w", "outputdir=err",
+				//"-w", "missingoutfile=err",
+				"-v",
+				"-j", strconv.Itoa(config.Parallel()),
+				"--frontend-file", fifo,
+				"-f", filepath.Join(config.SoongOutDir(), "bootstrap.ninja"),
+			}
+		}
 
 		if extra, ok := config.Environment().Get("SOONG_UI_NINJA_ARGS"); ok {
 			ctx.Printf(`CAUTION: arguments in $SOONG_UI_NINJA_ARGS=%q, e.g. "-n", can make soong_build FAIL or INCORRECT`, extra)
@@ -641,8 +661,13 @@
 		}
 
 		ninjaArgs = append(ninjaArgs, targets...)
+		ninjaCmd := config.PrebuiltBuildTool("ninja")
+		if config.useN2 {
+			ninjaCmd = config.PrebuiltBuildTool("n2")
+		}
+
 		cmd := Command(ctx, config, "soong bootstrap",
-			config.PrebuiltBuildTool("ninja"), ninjaArgs...)
+			ninjaCmd, ninjaArgs...)
 
 		var ninjaEnv Environment
 
@@ -680,8 +705,15 @@
 
 	loadSoongBuildMetrics(ctx, config, beforeSoongTimestamp)
 
-	distGzipFile(ctx, config, config.SoongNinjaFile(), "soong")
+	soongNinjaFile := config.SoongNinjaFile()
+	distGzipFile(ctx, config, soongNinjaFile, "soong")
+	for _, file := range blueprint.GetNinjaShardFiles(soongNinjaFile) {
+		if ok, _ := fileExists(file); ok {
+			distGzipFile(ctx, config, file, "soong")
+		}
+	}
 	distFile(ctx, config, config.SoongVarsFile(), "soong")
+	distFile(ctx, config, config.SoongExtraVarsFile(), "soong")
 
 	if !config.SkipKati() {
 		distGzipFile(ctx, config, config.SoongAndroidMk(), "soong")
@@ -742,18 +774,6 @@
 	}
 }
 
-func cleanBazelFiles(config Config) {
-	files := []string{
-		shared.JoinPath(config.SoongOutDir(), "bp2build"),
-		shared.JoinPath(config.SoongOutDir(), "workspace"),
-		shared.JoinPath(config.SoongOutDir(), bazel.SoongInjectionDirName),
-		shared.JoinPath(config.OutDir(), "bazel")}
-
-	for _, f := range files {
-		os.RemoveAll(f)
-	}
-}
-
 func runMicrofactory(ctx Context, config Config, name string, pkg string, mapping map[string]string) {
 	ctx.BeginTrace(metrics.RunSoong, name)
 	defer ctx.EndTrace()
diff --git a/ui/build/test_build.go b/ui/build/test_build.go
index 3095139..687ad6f 100644
--- a/ui/build/test_build.go
+++ b/ui/build/test_build.go
@@ -64,7 +64,8 @@
 	outDir := config.OutDir()
 	modulePathsDir := filepath.Join(outDir, ".module_paths")
 	rawFilesDir := filepath.Join(outDir, "soong", "raw")
-	variablesFilePath := filepath.Join(outDir, "soong", "soong.variables")
+	variablesFilePath := config.SoongVarsFile()
+	extraVariablesFilePath := config.SoongExtraVarsFile()
 
 	// dexpreopt.config is an input to the soong_docs action, which runs the
 	// soong_build primary builder. However, this file is created from $(shell)
@@ -79,6 +80,10 @@
 	// bpglob is built explicitly using Microfactory
 	bpglob := filepath.Join(config.SoongOutDir(), "bpglob")
 
+	// release-config files are generated from the initial lunch or Kati phase
+	// before running soong and ninja.
+	releaseConfigDir := filepath.Join(outDir, "soong", "release-config")
+
 	danglingRules := make(map[string]bool)
 
 	scanner := bufio.NewScanner(stdout)
@@ -91,9 +96,11 @@
 		if strings.HasPrefix(line, modulePathsDir) ||
 			strings.HasPrefix(line, rawFilesDir) ||
 			line == variablesFilePath ||
+			line == extraVariablesFilePath ||
 			line == dexpreoptConfigFilePath ||
 			line == buildDatetimeFilePath ||
-			line == bpglob {
+			line == bpglob ||
+			strings.HasPrefix(line, releaseConfigDir) {
 			// Leaf node is in one of Soong's bootstrap directories, which do not have
 			// full build rules in the primary build.ninja file.
 			continue
diff --git a/ui/terminal/format.go b/ui/terminal/format.go
index 241a1dd..01f8b0d 100644
--- a/ui/terminal/format.go
+++ b/ui/terminal/format.go
@@ -23,26 +23,28 @@
 )
 
 type formatter struct {
-	format string
-	quiet  bool
-	start  time.Time
+	colorize bool
+	format   string
+	quiet    bool
+	start    time.Time
 }
 
 // newFormatter returns a formatter for formatting output to
 // the terminal in a format similar to Ninja.
 // format takes nearly all the same options as NINJA_STATUS.
 // %c is currently unsupported.
-func newFormatter(format string, quiet bool) formatter {
+func newFormatter(colorize bool, format string, quiet bool) formatter {
 	return formatter{
-		format: format,
-		quiet:  quiet,
-		start:  time.Now(),
+		colorize: colorize,
+		format:   format,
+		quiet:    quiet,
+		start:    time.Now(),
 	}
 }
 
 func (s formatter) message(level status.MsgLevel, message string) string {
 	if level >= status.ErrorLvl {
-		return fmt.Sprintf("FAILED: %s", message)
+		return fmt.Sprintf("%s %s", s.failedString(), message)
 	} else if level > status.StatusLvl {
 		return fmt.Sprintf("%s%s", level.Prefix(), message)
 	} else if level == status.StatusLvl {
@@ -127,9 +129,9 @@
 	if result.Error != nil {
 		targets := strings.Join(result.Outputs, " ")
 		if s.quiet || result.Command == "" {
-			ret = fmt.Sprintf("FAILED: %s\n%s", targets, result.Output)
+			ret = fmt.Sprintf("%s %s\n%s", s.failedString(), targets, result.Output)
 		} else {
-			ret = fmt.Sprintf("FAILED: %s\n%s\n%s", targets, result.Command, result.Output)
+			ret = fmt.Sprintf("%s %s\n%s\n%s", s.failedString(), targets, result.Command, result.Output)
 		}
 	} else if result.Output != "" {
 		ret = result.Output
@@ -141,3 +143,11 @@
 
 	return ret
 }
+
+func (s formatter) failedString() string {
+	failed := "FAILED:"
+	if s.colorize {
+		failed = ansi.red() + ansi.bold() + failed + ansi.regular()
+	}
+	return failed
+}
diff --git a/ui/terminal/status.go b/ui/terminal/status.go
index 2ad174f..92f2994 100644
--- a/ui/terminal/status.go
+++ b/ui/terminal/status.go
@@ -27,9 +27,10 @@
 // statusFormat takes nearly all the same options as NINJA_STATUS.
 // %c is currently unsupported.
 func NewStatusOutput(w io.Writer, statusFormat string, forceSimpleOutput, quietBuild, forceKeepANSI bool) status.StatusOutput {
-	formatter := newFormatter(statusFormat, quietBuild)
+	canUseSmartFormatting := !forceSimpleOutput && isSmartTerminal(w)
+	formatter := newFormatter(canUseSmartFormatting, statusFormat, quietBuild)
 
-	if !forceSimpleOutput && isSmartTerminal(w) {
+	if canUseSmartFormatting {
 		return NewSmartStatusOutput(w, formatter)
 	} else {
 		return NewSimpleStatusOutput(w, formatter, forceKeepANSI)
diff --git a/ui/terminal/status_test.go b/ui/terminal/status_test.go
index 8dd1809..991eca0 100644
--- a/ui/terminal/status_test.go
+++ b/ui/terminal/status_test.go
@@ -58,7 +58,7 @@
 		{
 			name:   "action with error",
 			calls:  actionsWithError,
-			smart:  "\r\x1b[1m[  0% 0/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action2\x1b[0m\x1b[K\r\x1b[1m[ 66% 2/3] action2\x1b[0m\x1b[K\nFAILED: f1 f2\ntouch f1 f2\nerror1\nerror2\n\r\x1b[1m[ 66% 2/3] action3\x1b[0m\x1b[K\r\x1b[1m[100% 3/3] action3\x1b[0m\x1b[K\n",
+			smart:  "\r\x1b[1m[  0% 0/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action1\x1b[0m\x1b[K\r\x1b[1m[ 33% 1/3] action2\x1b[0m\x1b[K\r\x1b[1m[ 66% 2/3] action2\x1b[0m\x1b[K\n\x1b[31m\x1b[1mFAILED:\x1b[0m f1 f2\ntouch f1 f2\nerror1\nerror2\n\r\x1b[1m[ 66% 2/3] action3\x1b[0m\x1b[K\r\x1b[1m[100% 3/3] action3\x1b[0m\x1b[K\n",
 			simple: "[ 33% 1/3] action1\n[ 66% 2/3] action2\nFAILED: f1 f2\ntouch f1 f2\nerror1\nerror2\n[100% 3/3] action3\n",
 		},
 		{
@@ -70,7 +70,7 @@
 		{
 			name:   "messages",
 			calls:  actionsWithMessages,
-			smart:  "\r\x1b[1m[  0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\r\x1b[1mstatus\x1b[0m\x1b[K\r\x1b[Kprint\nFAILED: error\n\r\x1b[1m[ 50% 1/2] action2\x1b[0m\x1b[K\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\n",
+			smart:  "\r\x1b[1m[  0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\r\x1b[1mstatus\x1b[0m\x1b[K\r\x1b[Kprint\n\x1b[31m\x1b[1mFAILED:\x1b[0m error\n\r\x1b[1m[ 50% 1/2] action2\x1b[0m\x1b[K\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\n",
 			simple: "[ 50% 1/2] action1\nstatus\nprint\nFAILED: error\n[100% 2/2] action2\n",
 		},
 		{
@@ -362,7 +362,7 @@
 
 	stat.Flush()
 
-	w := "\r\x1b[1m[  0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[  0% 0/2] action2\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\nFAILED: \nOutput1\n\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\nThere was 1 action that completed after the action that failed. See verbose.log.gz for its output.\n"
+	w := "\r\x1b[1m[  0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[  0% 0/2] action2\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\n\x1b[31m\x1b[1mFAILED:\x1b[0m \nOutput1\n\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\nThere was 1 action that completed after the action that failed. See verbose.log.gz for its output.\n"
 
 	if g := smart.String(); g != w {
 		t.Errorf("want:\n%q\ngot:\n%q", w, g)
@@ -407,7 +407,7 @@
 
 	stat.Flush()
 
-	w := "\r\x1b[1m[  0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[  0% 0/2] action2\x1b[0m\x1b[K\r\x1b[1m[  0% 0/2] action3\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\nFAILED: \nOutput1\n\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\r\x1b[1m[150% 3/2] action3\x1b[0m\x1b[K\nThere were 2 actions that completed after the action that failed. See verbose.log.gz for their output.\n"
+	w := "\r\x1b[1m[  0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[  0% 0/2] action2\x1b[0m\x1b[K\r\x1b[1m[  0% 0/2] action3\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\n\x1b[31m\x1b[1mFAILED:\x1b[0m \nOutput1\n\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\r\x1b[1m[150% 3/2] action3\x1b[0m\x1b[K\nThere were 2 actions that completed after the action that failed. See verbose.log.gz for their output.\n"
 
 	if g := smart.String(); g != w {
 		t.Errorf("want:\n%q\ngot:\n%q", w, g)
@@ -445,7 +445,7 @@
 
 	stat.Flush()
 
-	w := "\r\x1b[1m[  0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[  0% 0/2] action2\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\nFAILED: \nOutput1\n\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\nFAILED: \nOutput2\n"
+	w := "\r\x1b[1m[  0% 0/2] action1\x1b[0m\x1b[K\r\x1b[1m[  0% 0/2] action2\x1b[0m\x1b[K\r\x1b[1m[ 50% 1/2] action1\x1b[0m\x1b[K\n\x1b[31m\x1b[1mFAILED:\x1b[0m \nOutput1\n\r\x1b[1m[100% 2/2] action2\x1b[0m\x1b[K\n\x1b[31m\x1b[1mFAILED:\x1b[0m \nOutput2\n"
 
 	if g := smart.String(); g != w {
 		t.Errorf("want:\n%q\ngot:\n%q", w, g)