Merge "Only link the profile extras lib for device variant for rust." into main
diff --git a/.gitignore b/.gitignore
index 5d2bc0d..89de74e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,5 @@
 *.iml
 *.ipr
 *.iws
-
+*.swp
+/.vscode
diff --git a/README.md b/README.md
index 93260e6..140822b 100644
--- a/README.md
+++ b/README.md
@@ -449,6 +449,7 @@
     config_namespace: "acme",
     variables: ["board"],
     bool_variables: ["feature"],
+    list_variables: ["impl"],
     value_variables: ["width"],
     properties: ["cflags", "srcs"],
 }
@@ -460,24 +461,40 @@
 ```
 
 This example describes a new `acme_cc_defaults` module type that extends the
-`cc_defaults` module type, with three additional conditionals based on
-variables `board`, `feature` and `width`, which can affect properties `cflags`
-and `srcs`. Additionally, each conditional will contain a `conditions_default`
-property can affect `cflags` and `srcs` in the following conditions:
+`cc_defaults` module type, with four additional conditionals based on variables
+`board`, `feature`, `impl` and `width` which can affect properties `cflags` and
+`srcs`. The four types of soong variables control properties in the following
+ways.
 
-* bool variable (e.g. `feature`): the variable is unspecified or not set to a true value
+* bool variable (e.g. `feature`): Properties are applied if set to `true`.
+* list variable (e.g. `impl`): (lists of strings properties only) Properties are
+  applied for each value in the list, using `%s` substitution. For example, if
+  the property is `["%s.cpp", "%s.h"]` and the list value is `foo bar`,
+  the result is `["foo.cpp", "foo.h", "bar.cpp", "bar.h"]`.
+* value variable (e.g. `width`): (strings or lists of strings) The value are
+  directly substituted into properties using `%s`.
+* string variable (e.g. `board`): Properties are applied only if they match the
+  variable's value.
+
+Additionally, each conditional containing a `conditions_default` property can
+affect `cflags` and `srcs` in the following conditions:
+
+* bool variable (e.g. `feature`): the variable is unspecified or not set to
+  `true`
+* list variable (e.g. `impl`): the variable is unspecified
 * value variable (e.g. `width`): the variable is unspecified
-* string variable (e.g. `board`): the variable is unspecified or the variable is set to a string unused in the
-given module. For example, with `board`, if the `board`
-conditional contains the properties `soc_a` and `conditions_default`, when
-board=soc_b, the `cflags` and `srcs` values under `conditions_default` will be
-used. To specify that no properties should be amended for `soc_b`, you can set
-`soc_b: {},`.
+* string variable (e.g. `board`): the variable is unspecified or the variable is
+  set to a string unused in the given module. For example, with `board`, if the
+  `board` conditional contains the properties `soc_a` and `conditions_default`,
+  when `board` is `soc_b`, the `cflags` and `srcs` values under
+  `conditions_default` is used. To specify that no properties should be amended
+  for `soc_b`, you can set `soc_b: {},`.
 
 The values of the variables can be set from a product's `BoardConfig.mk` file:
 ```
 $(call soong_config_set,acme,board,soc_a)
 $(call soong_config_set,acme,feature,true)
+$(call soong_config_set,acme,impl,foo.cpp bar.cpp)
 $(call soong_config_set,acme,width,200)
 ```
 
@@ -519,6 +536,12 @@
                 cflags: ["-DWIDTH=DEFAULT"],
             },
         },
+        impl: {
+            srcs: ["impl/%s"],
+            conditions_default: {
+                srcs: ["impl/default.cpp"],
+            },
+        },
     },
 }
 
@@ -530,7 +553,8 @@
 ```
 
 With the `BoardConfig.mk` snippet above, `libacme_foo` would build with
-`cflags: "-DGENERIC -DSOC_A -DFEATURE -DWIDTH=200"`.
+`cflags: "-DGENERIC -DSOC_A -DFEATURE -DWIDTH=200"` and
+`srcs: ["*.cpp", "impl/foo.cpp", "impl/bar.cpp"]`.
 
 Alternatively, with `DefaultBoardConfig.mk`:
 
@@ -539,12 +563,14 @@
 SOONG_CONFIG_acme += \
     board \
     feature \
+    impl \
     width \
 
 SOONG_CONFIG_acme_feature := false
 ```
 
-then `libacme_foo` would build with `cflags: "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT"`.
+then `libacme_foo` would build with `cflags: "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT"`
+and `srcs: ["*.cpp", "impl/default.cpp"]`.
 
 Alternatively, with `DefaultBoardConfig.mk`:
 
@@ -553,13 +579,15 @@
 SOONG_CONFIG_acme += \
     board \
     feature \
+    impl \
     width \
 
 SOONG_CONFIG_acme_board := soc_c
+SOONG_CONFIG_acme_impl := baz
 ```
 
 then `libacme_foo` would build with `cflags: "-DGENERIC -DSOC_DEFAULT
--DFEATURE_DEFAULT -DSIZE=DEFAULT"`.
+-DFEATURE_DEFAULT -DSIZE=DEFAULT"` and `srcs: ["*.cpp", "impl/baz.cpp"]`.
 
 `soong_config_module_type` modules will work best when used to wrap defaults
 modules (`cc_defaults`, `java_defaults`, etc.), which can then be referenced
diff --git a/aconfig/codegen/cc_aconfig_library.go b/aconfig/codegen/cc_aconfig_library.go
index 80e4926..2d3ee39 100644
--- a/aconfig/codegen/cc_aconfig_library.go
+++ b/aconfig/codegen/cc_aconfig_library.go
@@ -33,6 +33,11 @@
 
 const baseLibDep = "server_configurable_flags"
 
+const libBaseDep = "libbase"
+const libLogDep = "liblog"
+const libAconfigStorageReadApiCcDep = "libaconfig_storage_read_api_cc"
+const libAconfigStorageProtosCcDep = "libaconfig_storage_protos_cc"
+
 type CcAconfigLibraryProperties struct {
 	// name of the aconfig_declarations module to generate a library for
 	Aconfig_declarations string
@@ -82,6 +87,11 @@
 	// Add a dependency for the aconfig flags base library if it is not forced read only
 	if mode != "force-read-only" {
 		deps.SharedLibs = append(deps.SharedLibs, baseLibDep)
+
+		deps.SharedLibs = append(deps.SharedLibs, libBaseDep)
+		deps.SharedLibs = append(deps.SharedLibs, libLogDep)
+		deps.SharedLibs = append(deps.SharedLibs, libAconfigStorageReadApiCcDep)
+		deps.SharedLibs = append(deps.SharedLibs, libAconfigStorageProtosCcDep)
 	}
 	// TODO: It'd be really nice if we could reexport this library and not make everyone do it.
 
diff --git a/aconfig/codegen/cc_aconfig_library_test.go b/aconfig/codegen/cc_aconfig_library_test.go
index 05449bc..2e7fdc2 100644
--- a/aconfig/codegen/cc_aconfig_library_test.go
+++ b/aconfig/codegen/cc_aconfig_library_test.go
@@ -58,6 +58,26 @@
     		srcs: ["server_configurable_flags.cc"],
 			}
 
+			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"],
+			}
+
+			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",
@@ -100,6 +120,27 @@
     		srcs: ["server_configurable_flags.cc"],
 			}
 
+			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"],
+			}
+
+			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",
@@ -152,6 +193,30 @@
 			srcs: ["server_configurable_flags.cc"],
 			vendor_available: true,
 		}
+
+		cc_library {
+			name: "libbase",
+			srcs: ["libbase.cc"],
+			vendor_available: true,
+		}
+
+		cc_library {
+			name: "liblog",
+			srcs: ["liblog.cc"],
+			vendor_available: true,
+		}
+
+		cc_library {
+			name: "libaconfig_storage_read_api_cc",
+			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,
diff --git a/android/Android.bp b/android/Android.bp
index 4c59592..f130d3a 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -138,6 +138,7 @@
         "selects_test.go",
         "singleton_module_test.go",
         "soong_config_modules_test.go",
+        "test_suites_test.go",
         "util_test.go",
         "variable_test.go",
         "visibility_test.go",
diff --git a/android/all_teams.go b/android/all_teams.go
index 0c433a6..d4bf7d0 100644
--- a/android/all_teams.go
+++ b/android/all_teams.go
@@ -79,11 +79,6 @@
 	ctx.VisitAllModules(func(module Module) {
 		bpFile := ctx.BlueprintFile(module)
 
-		testModInfo := TestModuleInformation{}
-		if tmi, ok := SingletonModuleProvider(ctx, module, TestOnlyProviderKey); ok {
-			testModInfo = tmi
-		}
-
 		// Package Modules and Team Modules are stored in a map so we can look them up by name for
 		// modules without a team.
 		if pack, ok := module.(*packageModule); ok {
@@ -97,6 +92,23 @@
 			return
 		}
 
+		testModInfo := TestModuleInformation{}
+		if tmi, ok := SingletonModuleProvider(ctx, module, TestOnlyProviderKey); ok {
+			testModInfo = tmi
+		}
+
+		// Some modules, like java_test_host don't set the provider when the module isn't enabled:
+		//                                                test_only, top_level
+		//     AVFHostTestCases{os:linux_glibc,arch:common} {true true}
+		//     AVFHostTestCases{os:windows,arch:common} {false false}
+		// Generally variant information of true override false or unset.
+		if testModInfo.TestOnly == false {
+			if prevValue, exists := t.teams_for_mods[module.Name()]; exists {
+				if prevValue.testOnly == true {
+					return
+				}
+			}
+		}
 		entry := moduleTeamAndTestInfo{
 			bpFile:             bpFile,
 			testOnly:           testModInfo.TestOnly,
diff --git a/android/all_teams_test.go b/android/all_teams_test.go
index 9c2b38e..96ed92f 100644
--- a/android/all_teams_test.go
+++ b/android/all_teams_test.go
@@ -25,6 +25,8 @@
 	t.Parallel()
 	ctx := GroupFixturePreparers(
 		prepareForTestWithTeamAndFakes,
+		// This adds two variants, one armv7-a-neon, one armv8-a
+		PrepareForTestWithArchMutator,
 		FixtureRegisterWithContext(func(ctx RegistrationContext) {
 			ctx.RegisterParallelSingletonType("all_teams", AllTeamsFactory)
 		}),
@@ -52,10 +54,35 @@
 			name: "noteam",
                         test_only: true,
 		}
+                // write the test-only provider value once
 		fake {
-			name: "test-and-team-and-top",
+                        name: "test-and-team-and-top1",
                         test_only: true,
                         team: "team2",
+                        arch: {arm: { skip: false},
+                               arm64: { skip: true}},
+		}
+                // write the test-only provider once, but on the other arch
+		fake {
+                        name: "test-and-team-and-top2",
+                        test_only: true,
+                        team: "team2",
+                        arch: {arm: { skip: true},
+                               arm64: { skip: false}},
+		}
+                // write the test-only provider value twice
+		fake {
+                        name: "test-and-team-and-top3",
+                        test_only: true,
+                        team: "team2",
+		}
+                // Don't write the test-only provider value
+		fake {
+                        name: "test-and-team-and-top4",
+                        test_only: true,
+                        team: "team2",
+                        arch: {arm: { skip: true},
+                               arm64: { skip: true}},
 		}
 	`)
 
@@ -63,12 +90,16 @@
 	teams = getTeamProtoOutput(t, ctx)
 
 	// map of module name -> trendy team name.
-	actualTeams := make(map[string]*string)
+	actualTeams := make(map[string]string)
 	actualTests := []string{}
 	actualTopLevelTests := []string{}
 
 	for _, teamProto := range teams.Teams {
-		actualTeams[teamProto.GetTargetName()] = teamProto.TrendyTeamId
+		if teamProto.TrendyTeamId != nil {
+			actualTeams[teamProto.GetTargetName()] = *teamProto.TrendyTeamId
+		} else {
+			actualTeams[teamProto.GetTargetName()] = ""
+		}
 		if teamProto.GetTestOnly() {
 			actualTests = append(actualTests, teamProto.GetTargetName())
 		}
@@ -76,16 +107,23 @@
 			actualTopLevelTests = append(actualTopLevelTests, teamProto.GetTargetName())
 		}
 	}
-	expectedTeams := map[string]*string{
-		"main_test":             proto.String("cool_team"),
-		"tool":                  proto.String("22222"),
-		"test-and-team-and-top": proto.String("22222"),
-		"noteam":                nil,
+	expectedTeams := map[string]string{
+		"main_test":              "cool_team",
+		"tool":                   "22222",
+		"test-and-team-and-top1": "22222",
+		"test-and-team-and-top2": "22222",
+		"test-and-team-and-top3": "22222",
+		"test-and-team-and-top4": "22222",
+		"noteam":                 "",
 	}
 
 	expectedTests := []string{
 		"noteam",
-		"test-and-team-and-top",
+		"test-and-team-and-top1",
+		"test-and-team-and-top2",
+		"test-and-team-and-top3",
+		// There should be no test-and-team-top4 as we skip writing all variants
+		// test-only for all variants
 	}
 	AssertDeepEquals(t, "compare maps", expectedTeams, actualTeams)
 	AssertDeepEquals(t, "test matchup", expectedTests, actualTests)
@@ -230,12 +268,16 @@
 	ModuleBase
 
 	sourceProperties SourceProperties
+	props            struct {
+		// If true, don't write test-only value in provider
+		Skip bool `android:"arch_variant"`
+	}
 }
 
 func fakeFactory() Module {
 	module := &fakeForTests{}
-	module.AddProperties(&module.sourceProperties)
-	InitAndroidModule(module)
+	module.AddProperties(&module.sourceProperties, &module.props)
+	InitAndroidArchModule(module, HostAndDeviceSupported, MultilibBoth)
 
 	return module
 }
@@ -250,7 +292,7 @@
 func (f *fakeForTests) GenerateAndroidBuildActions(ctx ModuleContext) {
 	if Bool(f.sourceProperties.Test_only) {
 		SetProvider(ctx, TestOnlyProviderKey, TestModuleInformation{
-			TestOnly:       Bool(f.sourceProperties.Test_only),
+			TestOnly:       Bool(f.sourceProperties.Test_only) && !f.props.Skip,
 			TopLevelTarget: false,
 		})
 	}
diff --git a/android/apex.go b/android/apex.go
index c0acada..dc0aeed 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -90,7 +90,13 @@
 	TestApexes []string
 }
 
-var ApexInfoProvider = blueprint.NewMutatorProvider[ApexInfo]("apex")
+// AllApexInfo holds the ApexInfo of all apexes that include this module.
+type AllApexInfo struct {
+	ApexInfos []ApexInfo
+}
+
+var ApexInfoProvider = blueprint.NewMutatorProvider[ApexInfo]("apex_mutate")
+var AllApexInfoProvider = blueprint.NewMutatorProvider[*AllApexInfo]("apex_info")
 
 func (i ApexInfo) AddJSONData(d *map[string]interface{}) {
 	(*d)["Apex"] = map[string]interface{}{
@@ -586,75 +592,131 @@
 	return merged, aliases
 }
 
-// CreateApexVariations mutates a given module into multiple apex variants each of which is for an
-// apexBundle (and/or the platform) where the module is part of.
-func CreateApexVariations(mctx BottomUpMutatorContext, module ApexModule) []Module {
+// IncomingApexTransition is called by apexTransitionMutator.IncomingTransition on modules that can be in apexes.
+// The incomingVariation can be either the name of an apex if the dependency is coming directly from an apex
+// module, or it can be the name of an apex variation (e.g. apex10000) if it is coming from another module that
+// is in the apex.
+func IncomingApexTransition(ctx IncomingTransitionContext, incomingVariation string) string {
+	module := ctx.Module().(ApexModule)
 	base := module.apexModuleBase()
 
+	var apexInfos []ApexInfo
+	if allApexInfos, ok := ModuleProvider(ctx, AllApexInfoProvider); ok {
+		apexInfos = allApexInfos.ApexInfos
+	}
+
+	// Dependencies from platform variations go to the platform variation.
+	if incomingVariation == "" {
+		return ""
+	}
+
+	// If this module has no apex variations the use the platform variation.
+	if len(apexInfos) == 0 {
+		return ""
+	}
+
+	// Convert the list of apex infos into from the AllApexInfoProvider into the merged list
+	// of apex variations and the aliases from apex names to apex variations.
+	var aliases [][2]string
+	if !module.UniqueApexVariations() && !base.ApexProperties.UniqueApexVariationsForDeps {
+		apexInfos, aliases = mergeApexVariations(apexInfos)
+	}
+
+	// Check if the incoming variation matches an apex name, and if so use the corresponding
+	// apex variation.
+	aliasIndex := slices.IndexFunc(aliases, func(alias [2]string) bool {
+		return alias[0] == incomingVariation
+	})
+	if aliasIndex >= 0 {
+		return aliases[aliasIndex][1]
+	}
+
+	// Check if the incoming variation matches an apex variation.
+	apexIndex := slices.IndexFunc(apexInfos, func(info ApexInfo) bool {
+		return info.ApexVariationName == incomingVariation
+	})
+	if apexIndex >= 0 {
+		return incomingVariation
+	}
+
+	return ""
+}
+
+func MutateApexTransition(ctx BaseModuleContext, variation string) {
+	module := ctx.Module().(ApexModule)
+	base := module.apexModuleBase()
+	platformVariation := variation == ""
+
+	var apexInfos []ApexInfo
+	if allApexInfos, ok := ModuleProvider(ctx, AllApexInfoProvider); ok {
+		apexInfos = allApexInfos.ApexInfos
+	}
+
 	// Shortcut
-	if len(base.apexInfos) == 0 {
-		return nil
+	if len(apexInfos) == 0 {
+		return
 	}
 
 	// Do some validity checks.
 	// TODO(jiyong): is this the right place?
-	base.checkApexAvailableProperty(mctx)
+	base.checkApexAvailableProperty(ctx)
 
-	apexInfos := base.apexInfos
-	// base.apexInfos is only needed to propagate the list of apexes from apexInfoMutator to
-	// apexMutator. It is no longer accurate after mergeApexVariations, and won't be copied to
-	// all but the first created variant. Clear it so it doesn't accidentally get used later.
-	base.apexInfos = nil
-
-	slices.SortFunc(apexInfos, func(a, b ApexInfo) int {
-		return strings.Compare(a.ApexVariationName, b.ApexVariationName)
-	})
-
-	var aliases [][2]string
-	if !mctx.Module().(ApexModule).UniqueApexVariations() && !base.ApexProperties.UniqueApexVariationsForDeps {
-		apexInfos, aliases = mergeApexVariations(apexInfos)
+	if !module.UniqueApexVariations() && !base.ApexProperties.UniqueApexVariationsForDeps {
+		apexInfos, _ = mergeApexVariations(apexInfos)
 	}
 
 	var inApex ApexMembership
 	for _, a := range apexInfos {
 		for _, apexContents := range a.ApexContents {
-			inApex = inApex.merge(apexContents.contents[mctx.ModuleName()])
+			inApex = inApex.merge(apexContents.contents[ctx.ModuleName()])
 		}
 	}
 	base.ApexProperties.InAnyApex = true
 	base.ApexProperties.DirectlyInAnyApex = inApex == directlyInApex
 
-	defaultVariation := ""
-	mctx.SetDefaultDependencyVariation(&defaultVariation)
+	if platformVariation && !ctx.Host() && !module.AvailableFor(AvailableToPlatform) {
+		// Do not install the module for platform, but still allow it to output
+		// uninstallable AndroidMk entries in certain cases when they have side
+		// effects.  TODO(jiyong): move this routine to somewhere else
+		module.MakeUninstallable()
+	}
+	if !platformVariation {
+		var thisApexInfo ApexInfo
 
-	variations := []string{defaultVariation}
-	testApexes := []string{}
+		apexIndex := slices.IndexFunc(apexInfos, func(info ApexInfo) bool {
+			return info.ApexVariationName == variation
+		})
+		if apexIndex >= 0 {
+			thisApexInfo = apexInfos[apexIndex]
+		} else {
+			panic(fmt.Errorf("failed to find apexInfo for incoming variation %q", variation))
+		}
+
+		SetProvider(ctx, ApexInfoProvider, thisApexInfo)
+	}
+
+	// Set the value of TestApexes in every single apex variant.
+	// This allows each apex variant to be aware of the test apexes in the user provided apex_available.
+	var testApexes []string
 	for _, a := range apexInfos {
-		variations = append(variations, a.ApexVariationName)
 		testApexes = append(testApexes, a.TestApexes...)
 	}
-	modules := mctx.CreateVariations(variations...)
-	for i, mod := range modules {
-		platformVariation := i == 0
-		if platformVariation && !mctx.Host() && !mod.(ApexModule).AvailableFor(AvailableToPlatform) {
-			// Do not install the module for platform, but still allow it to output
-			// uninstallable AndroidMk entries in certain cases when they have side
-			// effects.  TODO(jiyong): move this routine to somewhere else
-			mod.MakeUninstallable()
-		}
-		if !platformVariation {
-			mctx.SetVariationProvider(mod, ApexInfoProvider, apexInfos[i-1])
-		}
-		// Set the value of TestApexes in every single apex variant.
-		// This allows each apex variant to be aware of the test apexes in the user provided apex_available.
-		mod.(ApexModule).apexModuleBase().ApexProperties.TestApexes = testApexes
-	}
+	base.ApexProperties.TestApexes = testApexes
 
-	for _, alias := range aliases {
-		mctx.CreateAliasVariation(alias[0], alias[1])
-	}
+}
 
-	return modules
+func ApexInfoMutator(ctx TopDownMutatorContext, module ApexModule) {
+	base := module.apexModuleBase()
+	if len(base.apexInfos) > 0 {
+		apexInfos := slices.Clone(base.apexInfos)
+		slices.SortFunc(apexInfos, func(a, b ApexInfo) int {
+			return strings.Compare(a.ApexVariationName, b.ApexVariationName)
+		})
+		SetProvider(ctx, AllApexInfoProvider, &AllApexInfo{apexInfos})
+		// base.apexInfos is only needed to propagate the list of apexes from the apex module to its
+		// contents within apexInfoMutator. Clear it so it doesn't accidentally get used later.
+		base.apexInfos = nil
+	}
 }
 
 // UpdateUniqueApexVariationsForDeps sets UniqueApexVariationsForDeps if any dependencies that are
@@ -665,13 +727,16 @@
 	// InApexVariants list in common. It is used instead of DepIsInSameApex because it needs to
 	// determine if the dep is in the same APEX due to being directly included, not only if it
 	// is included _because_ it is a dependency.
-	anyInSameApex := func(a, b []ApexInfo) bool {
-		collectApexes := func(infos []ApexInfo) []string {
-			var ret []string
-			for _, info := range infos {
-				ret = append(ret, info.InApexVariants...)
+	anyInSameApex := func(a, b ApexModule) bool {
+		collectApexes := func(m ApexModule) []string {
+			if allApexInfo, ok := OtherModuleProvider(mctx, m, AllApexInfoProvider); ok {
+				var ret []string
+				for _, info := range allApexInfo.ApexInfos {
+					ret = append(ret, info.InApexVariants...)
+				}
+				return ret
 			}
-			return ret
+			return nil
 		}
 
 		aApexes := collectApexes(a)
@@ -689,7 +754,7 @@
 	// If any of the dependencies requires unique apex variations, so does this module.
 	mctx.VisitDirectDeps(func(dep Module) {
 		if depApexModule, ok := dep.(ApexModule); ok {
-			if anyInSameApex(depApexModule.apexModuleBase().apexInfos, am.apexModuleBase().apexInfos) &&
+			if anyInSameApex(depApexModule, am) &&
 				(depApexModule.UniqueApexVariations() ||
 					depApexModule.apexModuleBase().ApexProperties.UniqueApexVariationsForDeps) {
 				am.apexModuleBase().ApexProperties.UniqueApexVariationsForDeps = true
diff --git a/android/arch.go b/android/arch.go
index 27ce4d4..cd8882b 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -16,16 +16,11 @@
 
 import (
 	"encoding"
-	"encoding/json"
 	"fmt"
 	"reflect"
 	"runtime"
-	"sort"
 	"strings"
 
-	"android/soong/bazel"
-	"android/soong/starlark_fmt"
-
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/bootstrap"
 	"github.com/google/blueprint/proptools"
@@ -980,12 +975,18 @@
 			panic(fmt.Errorf("unexpected tag format %q", field.Tag))
 		}
 		// these tags don't need to be present in the runtime generated struct type.
-		values = RemoveListFromList(values, []string{"arch_variant", "variant_prepend", "path", "replace_instead_of_append"})
+		// However replace_instead_of_append does, because it's read by the blueprint
+		// property extending util functions, which can operate on these generated arch
+		// property structs.
+		values = RemoveListFromList(values, []string{"arch_variant", "variant_prepend", "path"})
 		if len(values) > 0 {
-			panic(fmt.Errorf("unknown tags %q in field %q", values, prefix+field.Name))
+			if values[0] != "replace_instead_of_append" || len(values) > 1 {
+				panic(fmt.Errorf("unknown tags %q in field %q", values, prefix+field.Name))
+			}
+			field.Tag = `android:"replace_instead_of_append"`
+		} else {
+			field.Tag = ``
 		}
-
-		field.Tag = ``
 		return true, field
 	}
 	return false, field
@@ -1899,428 +1900,8 @@
 	return buildTargets, nil
 }
 
-func (m *ModuleBase) getArchPropertySet(propertySet interface{}, archType ArchType) interface{} {
-	archString := archType.Field
-	for i := range m.archProperties {
-		if m.archProperties[i] == nil {
-			// Skip over nil properties
-			continue
-		}
-
-		// Not archProperties are usable; this function looks for properties of a very specific
-		// form, and ignores the rest.
-		for _, archProperty := range m.archProperties[i] {
-			// archPropValue is a property struct, we are looking for the form:
-			// `arch: { arm: { key: value, ... }}`
-			archPropValue := reflect.ValueOf(archProperty).Elem()
-
-			// Unwrap src so that it should looks like a pointer to `arm: { key: value, ... }`
-			src := archPropValue.FieldByName("Arch").Elem()
-
-			// Step into non-nil pointers to structs in the src value.
-			if src.Kind() == reflect.Ptr {
-				if src.IsNil() {
-					continue
-				}
-				src = src.Elem()
-			}
-
-			// Find the requested field (e.g. arm, x86) in the src struct.
-			src = src.FieldByName(archString)
-
-			// We only care about structs.
-			if !src.IsValid() || src.Kind() != reflect.Struct {
-				continue
-			}
-
-			// If the value of the field is a struct then step into the
-			// BlueprintEmbed field. The special "BlueprintEmbed" name is
-			// used by createArchPropTypeDesc to embed the arch properties
-			// in the parent struct, so the src arch prop should be in this
-			// field.
-			//
-			// See createArchPropTypeDesc for more details on how Arch-specific
-			// module properties are processed from the nested props and written
-			// into the module's archProperties.
-			src = src.FieldByName("BlueprintEmbed")
-
-			// Clone the destination prop, since we want a unique prop struct per arch.
-			propertySetClone := reflect.New(reflect.ValueOf(propertySet).Elem().Type()).Interface()
-
-			// Copy the located property struct into the cloned destination property struct.
-			err := proptools.ExtendMatchingProperties([]interface{}{propertySetClone}, src.Interface(), nil, proptools.OrderReplace)
-			if err != nil {
-				// This is fine, it just means the src struct doesn't match the type of propertySet.
-				continue
-			}
-
-			return propertySetClone
-		}
-	}
-	// No property set was found specific to the given arch, so return an empty
-	// property set.
-	return reflect.New(reflect.ValueOf(propertySet).Elem().Type()).Interface()
-}
-
-// getMultilibPropertySet returns a property set struct matching the type of
-// `propertySet`, containing multilib-specific module properties for the given architecture.
-// If no multilib-specific properties exist for the given architecture, returns an empty property
-// set matching `propertySet`'s type.
-func (m *ModuleBase) getMultilibPropertySet(propertySet interface{}, archType ArchType) interface{} {
-	// archType.Multilib is lowercase (for example, lib32) but property struct field is
-	// capitalized, such as Lib32, so use strings.Title to capitalize it.
-	multiLibString := strings.Title(archType.Multilib)
-
-	for i := range m.archProperties {
-		if m.archProperties[i] == nil {
-			// Skip over nil properties
-			continue
-		}
-
-		// Not archProperties are usable; this function looks for properties of a very specific
-		// form, and ignores the rest.
-		for _, archProperties := range m.archProperties[i] {
-			// archPropValue is a property struct, we are looking for the form:
-			// `multilib: { lib32: { key: value, ... }}`
-			archPropValue := reflect.ValueOf(archProperties).Elem()
-
-			// Unwrap src so that it should looks like a pointer to `lib32: { key: value, ... }`
-			src := archPropValue.FieldByName("Multilib").Elem()
-
-			// Step into non-nil pointers to structs in the src value.
-			if src.Kind() == reflect.Ptr {
-				if src.IsNil() {
-					// Ignore nil pointers.
-					continue
-				}
-				src = src.Elem()
-			}
-
-			// Find the requested field (e.g. lib32) in the src struct.
-			src = src.FieldByName(multiLibString)
-
-			// We only care about valid struct pointers.
-			if !src.IsValid() || src.Kind() != reflect.Ptr || src.Elem().Kind() != reflect.Struct {
-				continue
-			}
-
-			// Get the zero value for the requested property set.
-			propertySetClone := reflect.New(reflect.ValueOf(propertySet).Elem().Type()).Interface()
-
-			// Copy the located property struct into the "zero" property set struct.
-			err := proptools.ExtendMatchingProperties([]interface{}{propertySetClone}, src.Interface(), nil, proptools.OrderReplace)
-
-			if err != nil {
-				// This is fine, it just means the src struct doesn't match.
-				continue
-			}
-
-			return propertySetClone
-		}
-	}
-
-	// There were no multilib properties specifically matching the given archtype.
-	// Return zeroed value.
-	return reflect.New(reflect.ValueOf(propertySet).Elem().Type()).Interface()
-}
-
 // ArchVariantContext defines the limited context necessary to retrieve arch_variant properties.
 type ArchVariantContext interface {
 	ModuleErrorf(fmt string, args ...interface{})
 	PropertyErrorf(property, fmt string, args ...interface{})
 }
-
-// ArchVariantProperties represents a map of arch-variant config strings to a property interface{}.
-type ArchVariantProperties map[string]interface{}
-
-// ConfigurationAxisToArchVariantProperties represents a map of bazel.ConfigurationAxis to
-// ArchVariantProperties, such that each independent arch-variant axis maps to the
-// configs/properties for that axis.
-type ConfigurationAxisToArchVariantProperties map[bazel.ConfigurationAxis]ArchVariantProperties
-
-// GetArchVariantProperties returns a ConfigurationAxisToArchVariantProperties where the
-// arch-variant properties correspond to the values of the properties of the 'propertySet' struct
-// that are specific to that axis/configuration. Each axis is independent, containing
-// non-overlapping configs that correspond to the various "arch-variant" support, at this time:
-//
-//	arches (including multilib)
-//	oses
-//	arch+os combinations
-//
-// For example, passing a struct { Foo bool, Bar string } will return an interface{} that can be
-// type asserted back into the same struct, containing the config-specific property value specified
-// by the module if defined.
-//
-// Arch-specific properties may come from an arch stanza or a multilib stanza; properties
-// in these stanzas are combined.
-// For example: `arch: { x86: { Foo: ["bar"] } }, multilib: { lib32: {` Foo: ["baz"] } }`
-// will result in `Foo: ["bar", "baz"]` being returned for architecture x86, if the given
-// propertyset contains `Foo []string`.
-func (m *ModuleBase) GetArchVariantProperties(ctx ArchVariantContext, propertySet interface{}) ConfigurationAxisToArchVariantProperties {
-	// Return value of the arch types to the prop values for that arch.
-	axisToProps := ConfigurationAxisToArchVariantProperties{}
-
-	// Nothing to do for non-arch-specific modules.
-	if !m.ArchSpecific() {
-		return axisToProps
-	}
-
-	dstType := reflect.ValueOf(propertySet).Type()
-	var archProperties []interface{}
-
-	// First find the property set in the module that corresponds to the requested
-	// one. m.archProperties[i] corresponds to m.GetProperties()[i].
-	for i, generalProp := range m.GetProperties() {
-		srcType := reflect.ValueOf(generalProp).Type()
-		if srcType == dstType {
-			archProperties = m.archProperties[i]
-			axisToProps[bazel.NoConfigAxis] = ArchVariantProperties{"": generalProp}
-			break
-		}
-	}
-
-	if archProperties == nil {
-		// This module does not have the property set requested
-		return axisToProps
-	}
-
-	archToProp := ArchVariantProperties{}
-	// For each arch type (x86, arm64, etc.)
-	for _, arch := range ArchTypeList() {
-		// Arch properties are sometimes sharded (see createArchPropTypeDesc() ).
-		// Iterate over every shard and extract a struct with the same type as the
-		// input one that contains the data specific to that arch.
-		propertyStructs := make([]reflect.Value, 0)
-		archFeaturePropertyStructs := make(map[string][]reflect.Value, 0)
-		for _, archProperty := range archProperties {
-			archTypeStruct, ok := getArchTypeStruct(ctx, archProperty, arch)
-			if ok {
-				propertyStructs = append(propertyStructs, archTypeStruct)
-
-				// For each feature this arch supports (arm: neon, x86: ssse3, sse4, ...)
-				for _, feature := range archFeatures[arch] {
-					prefix := "arch." + arch.Name + "." + feature
-					if featureProperties, ok := getChildPropertyStruct(ctx, archTypeStruct, feature, prefix); ok {
-						archFeaturePropertyStructs[feature] = append(archFeaturePropertyStructs[feature], featureProperties)
-					}
-				}
-			}
-			multilibStruct, ok := getMultilibStruct(ctx, archProperty, arch)
-			if ok {
-				propertyStructs = append(propertyStructs, multilibStruct)
-			}
-		}
-
-		archToProp[arch.Name] = mergeStructs(ctx, propertyStructs, propertySet)
-
-		// In soong, if multiple features match the current configuration, they're
-		// all used. In bazel, we have to have unambiguous select() statements, so
-		// we can't have two features that are both active in the same select().
-		// One alternative is to split out each feature into a separate select(),
-		// but then it's difficult to support exclude_srcs, which may need to
-		// exclude things from the regular arch select() statement if a certain
-		// feature is active. Instead, keep the features in the same select
-		// statement as the arches, but emit the power set of all possible
-		// combinations of features, so that bazel can match the most precise one.
-		allFeatures := make([]string, 0, len(archFeaturePropertyStructs))
-		for feature := range archFeaturePropertyStructs {
-			allFeatures = append(allFeatures, feature)
-		}
-		for _, features := range bazel.PowerSetWithoutEmptySet(allFeatures) {
-			sort.Strings(features)
-			propsForCurrentFeatureSet := make([]reflect.Value, 0)
-			propsForCurrentFeatureSet = append(propsForCurrentFeatureSet, propertyStructs...)
-			for _, feature := range features {
-				propsForCurrentFeatureSet = append(propsForCurrentFeatureSet, archFeaturePropertyStructs[feature]...)
-			}
-			archToProp[arch.Name+"-"+strings.Join(features, "-")] =
-				mergeStructs(ctx, propsForCurrentFeatureSet, propertySet)
-		}
-	}
-	axisToProps[bazel.ArchConfigurationAxis] = archToProp
-
-	osToProp := ArchVariantProperties{}
-	archOsToProp := ArchVariantProperties{}
-
-	linuxStructs := getTargetStructs(ctx, archProperties, "Linux")
-	bionicStructs := getTargetStructs(ctx, archProperties, "Bionic")
-	hostStructs := getTargetStructs(ctx, archProperties, "Host")
-	hostLinuxStructs := getTargetStructs(ctx, archProperties, "Host_linux")
-	hostNotWindowsStructs := getTargetStructs(ctx, archProperties, "Not_windows")
-
-	// For android, linux, ...
-	for _, os := range osTypeList {
-		if os == CommonOS {
-			// It looks like this OS value is not used in Blueprint files
-			continue
-		}
-		osStructs := make([]reflect.Value, 0)
-
-		osSpecificStructs := getTargetStructs(ctx, archProperties, os.Field)
-		if os.Class == Host {
-			osStructs = append(osStructs, hostStructs...)
-		}
-		if os.Linux() {
-			osStructs = append(osStructs, linuxStructs...)
-		}
-		if os.Bionic() {
-			osStructs = append(osStructs, bionicStructs...)
-		}
-		if os.Linux() && os.Class == Host {
-			osStructs = append(osStructs, hostLinuxStructs...)
-		}
-
-		if os == LinuxMusl {
-			osStructs = append(osStructs, getTargetStructs(ctx, archProperties, "Musl")...)
-		}
-		if os == Linux {
-			osStructs = append(osStructs, getTargetStructs(ctx, archProperties, "Glibc")...)
-		}
-
-		osStructs = append(osStructs, osSpecificStructs...)
-
-		if os.Class == Host && os != Windows {
-			osStructs = append(osStructs, hostNotWindowsStructs...)
-		}
-		osToProp[os.Name] = mergeStructs(ctx, osStructs, propertySet)
-
-		// For arm, x86, ...
-		for _, arch := range osArchTypeMap[os] {
-			osArchStructs := make([]reflect.Value, 0)
-
-			// Auto-combine with Linux_ and Bionic_ targets. This potentially results in
-			// repetition and select() bloat, but use of Linux_* and Bionic_* targets is rare.
-			// TODO(b/201423152): Look into cleanup.
-			if os.Linux() {
-				targetField := "Linux_" + arch.Name
-				targetStructs := getTargetStructs(ctx, archProperties, targetField)
-				osArchStructs = append(osArchStructs, targetStructs...)
-			}
-			if os.Bionic() {
-				targetField := "Bionic_" + arch.Name
-				targetStructs := getTargetStructs(ctx, archProperties, targetField)
-				osArchStructs = append(osArchStructs, targetStructs...)
-			}
-			if os == LinuxMusl {
-				targetField := "Musl_" + arch.Name
-				targetStructs := getTargetStructs(ctx, archProperties, targetField)
-				osArchStructs = append(osArchStructs, targetStructs...)
-			}
-			if os == Linux {
-				targetField := "Glibc_" + arch.Name
-				targetStructs := getTargetStructs(ctx, archProperties, targetField)
-				osArchStructs = append(osArchStructs, targetStructs...)
-			}
-
-			targetField := GetCompoundTargetField(os, arch)
-			targetName := fmt.Sprintf("%s_%s", os.Name, arch.Name)
-			targetStructs := getTargetStructs(ctx, archProperties, targetField)
-			osArchStructs = append(osArchStructs, targetStructs...)
-
-			archOsToProp[targetName] = mergeStructs(ctx, osArchStructs, propertySet)
-		}
-	}
-
-	axisToProps[bazel.OsConfigurationAxis] = osToProp
-	axisToProps[bazel.OsArchConfigurationAxis] = archOsToProp
-	return axisToProps
-}
-
-// Returns a struct matching the propertySet interface, containing properties specific to the targetName
-// For example, given these arguments:
-//
-//	propertySet = BaseCompilerProperties
-//	targetName = "android_arm"
-//
-// And given this Android.bp fragment:
-//
-//	target:
-//	   android_arm: {
-//	      srcs: ["foo.c"],
-//	   }
-//	   android_arm64: {
-//	      srcs: ["bar.c"],
-//	  }
-//	}
-//
-// This would return a BaseCompilerProperties with BaseCompilerProperties.Srcs = ["foo.c"]
-func getTargetStructs(ctx ArchVariantContext, archProperties []interface{}, targetName string) []reflect.Value {
-	var propertyStructs []reflect.Value
-	for _, archProperty := range archProperties {
-		archPropValues := reflect.ValueOf(archProperty).Elem()
-		targetProp := archPropValues.FieldByName("Target").Elem()
-		targetStruct, ok := getChildPropertyStruct(ctx, targetProp, targetName, targetName)
-		if ok {
-			propertyStructs = append(propertyStructs, targetStruct)
-		} else {
-			return []reflect.Value{}
-		}
-	}
-
-	return propertyStructs
-}
-
-func mergeStructs(ctx ArchVariantContext, propertyStructs []reflect.Value, propertySet interface{}) interface{} {
-	// Create a new instance of the requested property set
-	value := reflect.New(reflect.ValueOf(propertySet).Elem().Type()).Interface()
-
-	// Merge all the structs together
-	for _, propertyStruct := range propertyStructs {
-		mergePropertyStruct(ctx, value, propertyStruct)
-	}
-
-	return value
-}
-
-func printArchTypeStarlarkDict(dict map[ArchType][]string) string {
-	valDict := make(map[string]string, len(dict))
-	for k, v := range dict {
-		valDict[k.String()] = starlark_fmt.PrintStringList(v, 1)
-	}
-	return starlark_fmt.PrintDict(valDict, 0)
-}
-
-func printArchTypeNestedStarlarkDict(dict map[ArchType]map[string][]string) string {
-	valDict := make(map[string]string, len(dict))
-	for k, v := range dict {
-		valDict[k.String()] = starlark_fmt.PrintStringListDict(v, 1)
-	}
-	return starlark_fmt.PrintDict(valDict, 0)
-}
-
-func printArchConfigList(arches []archConfig) string {
-	jsonOut, err := json.MarshalIndent(arches, "", starlark_fmt.Indention(1))
-	if err != nil {
-		panic(fmt.Errorf("Error converting arch configs %#v to json: %q", arches, err))
-	}
-	return fmt.Sprintf("json.decode('''%s''')", string(jsonOut))
-}
-
-func StarlarkArchConfigurations() string {
-	return fmt.Sprintf(`
-_arch_to_variants = %s
-
-_arch_to_cpu_variants = %s
-
-_arch_to_features = %s
-
-_android_arch_feature_for_arch_variant = %s
-
-_aml_arches = %s
-
-_ndk_arches = %s
-
-arch_to_variants = _arch_to_variants
-arch_to_cpu_variants = _arch_to_cpu_variants
-arch_to_features = _arch_to_features
-android_arch_feature_for_arch_variants = _android_arch_feature_for_arch_variant
-aml_arches = _aml_arches
-ndk_arches = _ndk_arches
-`, printArchTypeStarlarkDict(archVariants),
-		printArchTypeStarlarkDict(cpuVariants),
-		printArchTypeStarlarkDict(archFeatures),
-		printArchTypeNestedStarlarkDict(androidArchFeatureMap),
-		printArchConfigList(getAmlAbisConfig()),
-		printArchConfigList(getNdkAbisConfig()),
-	)
-}
diff --git a/android/prebuilt.go b/android/prebuilt.go
index 91ba05b..2b7b55b 100644
--- a/android/prebuilt.go
+++ b/android/prebuilt.go
@@ -706,11 +706,6 @@
 		return true
 	}
 
-	// If the use_source_config_var property is set then it overrides the prefer property setting.
-	if configVar := p.properties.Use_source_config_var; configVar != nil {
-		return !ctx.Config().VendorConfig(proptools.String(configVar.Config_namespace)).Bool(proptools.String(configVar.Var_name))
-	}
-
 	// TODO: use p.Properties.Name and ctx.ModuleDir to override preference
 	return Bool(p.properties.Prefer)
 }
diff --git a/android/prebuilt_test.go b/android/prebuilt_test.go
index 2241b08..2574ed4 100644
--- a/android/prebuilt_test.go
+++ b/android/prebuilt_test.go
@@ -295,158 +295,6 @@
 				}`,
 			prebuilt: []OsType{Android, buildOS},
 		},
-		{
-			name: "prebuilt use_source_config_var={acme, use_source} - no var specified",
-			modules: `
-				source {
-					name: "bar",
-				}
-
-				prebuilt {
-					name: "bar",
-					use_source_config_var: {config_namespace: "acme", var_name: "use_source"},
-					srcs: ["prebuilt_file"],
-				}`,
-			// When use_source_env is specified then it will use the prebuilt by default if the environment
-			// variable is not set.
-			prebuilt: []OsType{Android, buildOS},
-		},
-		{
-			name: "prebuilt use_source_config_var={acme, use_source} - acme_use_source=false",
-			modules: `
-				source {
-					name: "bar",
-				}
-
-				prebuilt {
-					name: "bar",
-					use_source_config_var: {config_namespace: "acme", var_name: "use_source"},
-					srcs: ["prebuilt_file"],
-				}`,
-			preparer: FixtureModifyProductVariables(func(variables FixtureProductVariables) {
-				variables.VendorVars = map[string]map[string]string{
-					"acme": {
-						"use_source": "false",
-					},
-				}
-			}),
-			// Setting the environment variable named in use_source_env to false will cause the prebuilt to
-			// be used.
-			prebuilt: []OsType{Android, buildOS},
-		},
-		{
-			name: "apex_contributions supersedes any source preferred via use_source_config_var",
-			modules: `
-				source {
-					name: "bar",
-				}
-
-				prebuilt {
-					name: "bar",
-					use_source_config_var: {config_namespace: "acme", var_name: "use_source"},
-					srcs: ["prebuilt_file"],
-				}
-				apex_contributions {
-					name: "my_mainline_module_contribution",
-					api_domain: "apexfoo",
-					// this metadata module contains prebuilt
-					contents: ["prebuilt_bar"],
-				}
-				all_apex_contributions {
-					name: "all_apex_contributions",
-				}
-				`,
-			preparer: FixtureModifyProductVariables(func(variables FixtureProductVariables) {
-				variables.VendorVars = map[string]map[string]string{
-					"acme": {
-						"use_source": "true",
-					},
-				}
-				variables.BuildFlags = map[string]string{
-					"RELEASE_APEX_CONTRIBUTIONS_ADSERVICES": "my_mainline_module_contribution",
-				}
-			}),
-			// use_source_config_var indicates that source should be used
-			// but this is superseded by `my_mainline_module_contribution`
-			prebuilt: []OsType{Android, buildOS},
-		},
-		{
-			name: "apex_contributions supersedes any prebuilt preferred via use_source_config_var",
-			modules: `
-				source {
-					name: "bar",
-				}
-
-				prebuilt {
-					name: "bar",
-					use_source_config_var: {config_namespace: "acme", var_name: "use_source"},
-					srcs: ["prebuilt_file"],
-				}
-				apex_contributions {
-					name: "my_mainline_module_contribution",
-					api_domain: "apexfoo",
-					// this metadata module contains source
-					contents: ["bar"],
-				}
-				all_apex_contributions {
-					name: "all_apex_contributions",
-				}
-				`,
-			preparer: FixtureModifyProductVariables(func(variables FixtureProductVariables) {
-				variables.VendorVars = map[string]map[string]string{
-					"acme": {
-						"use_source": "false",
-					},
-				}
-				variables.BuildFlags = map[string]string{
-					"RELEASE_APEX_CONTRIBUTIONS_ADSERVICES": "my_mainline_module_contribution",
-				}
-			}),
-			// use_source_config_var indicates that prebuilt should be used
-			// but this is superseded by `my_mainline_module_contribution`
-			prebuilt: nil,
-		},
-		{
-			name: "prebuilt use_source_config_var={acme, use_source} - acme_use_source=true",
-			modules: `
-				source {
-					name: "bar",
-				}
-
-				prebuilt {
-					name: "bar",
-					use_source_config_var: {config_namespace: "acme", var_name: "use_source"},
-					srcs: ["prebuilt_file"],
-				}`,
-			preparer: FixtureModifyProductVariables(func(variables FixtureProductVariables) {
-				variables.VendorVars = map[string]map[string]string{
-					"acme": {
-						"use_source": "true",
-					},
-				}
-			}),
-			// Setting the environment variable named in use_source_env to true will cause the source to be
-			// used.
-			prebuilt: nil,
-		},
-		{
-			name: "prebuilt use_source_config_var={acme, use_source} - acme_use_source=true, no source",
-			modules: `
-				prebuilt {
-					name: "bar",
-					use_source_config_var: {config_namespace: "acme", var_name: "use_source"},
-					srcs: ["prebuilt_file"],
-				}`,
-			preparer: FixtureModifyProductVariables(func(variables FixtureProductVariables) {
-				variables.VendorVars = map[string]map[string]string{
-					"acme": {
-						"use_source": "true",
-					},
-				}
-			}),
-			// Although the environment variable says to use source there is no source available.
-			prebuilt: []OsType{Android, buildOS},
-		},
 	}
 
 	fs := MockFS{
diff --git a/android/soong_config_modules.go b/android/soong_config_modules.go
index 90b49eb..38db929 100644
--- a/android/soong_config_modules.go
+++ b/android/soong_config_modules.go
@@ -64,6 +64,7 @@
 // specified in `conditions_default` will only be used under the following conditions:
 //   bool variable: the variable is unspecified or not set to a true value
 //   value variable: the variable is unspecified
+//   list variable: the variable is unspecified
 //   string variable: the variable is unspecified or the variable is set to a string unused in the
 //                    given module. For example, string variable `test` takes values: "a" and "b",
 //                    if the module contains a property `a` and `conditions_default`, when test=b,
@@ -104,6 +105,12 @@
 //                     cflags: ["-DWIDTH=DEFAULT"],
 //                 },
 //             },
+//             impl: {
+//                 srcs: ["impl/%s"],
+//                 conditions_default: {
+//                     srcs: ["impl/default.cpp"],
+//                 },
+//             },
 //         },
 //     }
 //
@@ -122,6 +129,7 @@
 //         variables: ["board"],
 //         bool_variables: ["feature"],
 //         value_variables: ["width"],
+//         list_variables: ["impl"],
 //         properties: ["cflags", "srcs"],
 //     }
 //
@@ -135,8 +143,10 @@
 //     $(call add_soong_config_var_value, acme, board, soc_a)
 //     $(call add_soong_config_var_value, acme, feature, true)
 //     $(call add_soong_config_var_value, acme, width, 200)
+//     $(call add_soong_config_var_value, acme, impl, foo.cpp bar.cpp)
 //
-// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE -DWIDTH=200".
+// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE -DWIDTH=200" and srcs
+// ["*.cpp", "impl/foo.cpp", "impl/bar.cpp"].
 //
 // Alternatively, if acme BoardConfig.mk file contained:
 //
@@ -148,7 +158,9 @@
 //     SOONG_CONFIG_acme_feature := false
 //
 // Then libacme_foo would build with cflags:
-//   "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT".
+//   "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT"
+// and with srcs:
+//   ["*.cpp", "impl/default.cpp"].
 //
 // Similarly, if acme BoardConfig.mk file contained:
 //
@@ -158,9 +170,13 @@
 //         feature \
 //
 //     SOONG_CONFIG_acme_board := soc_c
+//     SOONG_CONFIG_acme_impl := foo.cpp bar.cpp
 //
 // Then libacme_foo would build with cflags:
-//   "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT".
+//   "-DGENERIC -DSOC_DEFAULT -DFEATURE_DEFAULT -DSIZE=DEFAULT"
+// and with srcs:
+//   ["*.cpp", "impl/foo.cpp", "impl/bar.cpp"].
+//
 
 func SoongConfigModuleTypeImportFactory() Module {
 	module := &soongConfigModuleTypeImport{}
@@ -201,6 +217,7 @@
 //
 //	bool variable: the variable is unspecified or not set to a true value
 //	value variable: the variable is unspecified
+//	list variable: the variable is unspecified
 //	string variable: the variable is unspecified or the variable is set to a string unused in the
 //	                 given module. For example, string variable `test` takes values: "a" and "b",
 //	                 if the module contains a property `a` and `conditions_default`, when test=b,
@@ -209,56 +226,63 @@
 //
 // For example, an Android.bp file could have:
 //
-//	    soong_config_module_type {
-//	        name: "acme_cc_defaults",
-//	        module_type: "cc_defaults",
-//	        config_namespace: "acme",
-//	        variables: ["board"],
-//	        bool_variables: ["feature"],
-//	        value_variables: ["width"],
-//	        properties: ["cflags", "srcs"],
-//	    }
+//	soong_config_module_type {
+//	    name: "acme_cc_defaults",
+//	    module_type: "cc_defaults",
+//	    config_namespace: "acme",
+//	    variables: ["board"],
+//	    bool_variables: ["feature"],
+//	    value_variables: ["width"],
+//	    list_variables: ["impl"],
+//	    properties: ["cflags", "srcs"],
+//	}
 //
-//	    soong_config_string_variable {
-//	        name: "board",
-//	        values: ["soc_a", "soc_b"],
-//	    }
+//	soong_config_string_variable {
+//	    name: "board",
+//	    values: ["soc_a", "soc_b"],
+//	}
 //
-//	    acme_cc_defaults {
-//	        name: "acme_defaults",
-//	        cflags: ["-DGENERIC"],
-//	        soong_config_variables: {
-//	            board: {
-//	                soc_a: {
-//	                    cflags: ["-DSOC_A"],
-//	                },
-//	                soc_b: {
-//	                    cflags: ["-DSOC_B"],
-//	                },
-//	                conditions_default: {
-//	                    cflags: ["-DSOC_DEFAULT"],
-//	                },
+//	acme_cc_defaults {
+//	    name: "acme_defaults",
+//	    cflags: ["-DGENERIC"],
+//	    soong_config_variables: {
+//	        board: {
+//	            soc_a: {
+//	                cflags: ["-DSOC_A"],
 //	            },
-//	            feature: {
-//	                cflags: ["-DFEATURE"],
-//	                conditions_default: {
-//	                    cflags: ["-DFEATURE_DEFAULT"],
-//	                },
+//	            soc_b: {
+//	                cflags: ["-DSOC_B"],
 //	            },
-//	            width: {
-//		               cflags: ["-DWIDTH=%s"],
-//	                conditions_default: {
-//	                    cflags: ["-DWIDTH=DEFAULT"],
-//	                },
+//	            conditions_default: {
+//	                cflags: ["-DSOC_DEFAULT"],
 //	            },
 //	        },
-//	    }
+//	        feature: {
+//	            cflags: ["-DFEATURE"],
+//	            conditions_default: {
+//	                cflags: ["-DFEATURE_DEFAULT"],
+//	            },
+//	        },
+//	        width: {
+//	            cflags: ["-DWIDTH=%s"],
+//	            conditions_default: {
+//	                cflags: ["-DWIDTH=DEFAULT"],
+//	            },
+//	        },
+//	        impl: {
+//	            srcs: ["impl/%s"],
+//	            conditions_default: {
+//	                srcs: ["impl/default.cpp"],
+//	            },
+//	        },
+//	    },
+//	}
 //
-//	    cc_library {
-//	        name: "libacme_foo",
-//	        defaults: ["acme_defaults"],
-//	        srcs: ["*.cpp"],
-//	    }
+//	cc_library {
+//	    name: "libacme_foo",
+//	    defaults: ["acme_defaults"],
+//	    srcs: ["*.cpp"],
+//	}
 //
 // If an acme BoardConfig.mk file contained:
 //
@@ -270,8 +294,10 @@
 //	SOONG_CONFIG_acme_board := soc_a
 //	SOONG_CONFIG_acme_feature := true
 //	SOONG_CONFIG_acme_width := 200
+//	SOONG_CONFIG_acme_impl := foo.cpp bar.cpp
 //
-// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE".
+// Then libacme_foo would build with cflags "-DGENERIC -DSOC_A -DFEATURE" and srcs
+// ["*.cpp", "impl/foo.cpp", "impl/bar.cpp"].
 func SoongConfigModuleTypeFactory() Module {
 	module := &soongConfigModuleTypeModule{}
 
diff --git a/android/soongconfig/modules.go b/android/soongconfig/modules.go
index c78b726..c910974 100644
--- a/android/soongconfig/modules.go
+++ b/android/soongconfig/modules.go
@@ -117,6 +117,10 @@
 	// inserted into the properties with %s substitution.
 	Value_variables []string
 
+	// the list of SOONG_CONFIG list variables that this module type will read. Each value will be
+	// inserted into the properties with %s substitution.
+	List_variables []string
+
 	// the list of properties that this module type will extend.
 	Properties []string
 }
@@ -468,6 +472,18 @@
 		})
 	}
 
+	for _, name := range props.List_variables {
+		if err := checkVariableName(name); err != nil {
+			return nil, []error{fmt.Errorf("list_variables %s", err)}
+		}
+
+		mt.Variables = append(mt.Variables, &listVariable{
+			baseVariable: baseVariable{
+				variable: name,
+			},
+		})
+	}
+
 	return mt, nil
 }
 
@@ -730,6 +746,90 @@
 	return nil
 }
 
+// Struct to allow conditions set based on a list variable, supporting string substitution.
+type listVariable struct {
+	baseVariable
+}
+
+func (s *listVariable) variableValuesType() reflect.Type {
+	return emptyInterfaceType
+}
+
+// initializeProperties initializes a property to zero value of typ with an additional conditions
+// default field.
+func (s *listVariable) initializeProperties(v reflect.Value, typ reflect.Type) {
+	initializePropertiesWithDefault(v, typ)
+}
+
+// PropertiesToApply returns an interface{} value based on initializeProperties to be applied to
+// the module. If the variable was not set, conditions_default interface will be returned;
+// otherwise, the interface in values, without conditions_default will be returned with all
+// appropriate string substitutions based on variable being set.
+func (s *listVariable) PropertiesToApply(config SoongConfig, values reflect.Value) (interface{}, error) {
+	// If this variable was not referenced in the module, there are no properties to apply.
+	if !values.IsValid() || values.Elem().IsZero() {
+		return nil, nil
+	}
+	if !config.IsSet(s.variable) {
+		return conditionsDefaultField(values.Elem().Elem()).Interface(), nil
+	}
+	configValues := strings.Split(config.String(s.variable), " ")
+
+	values = removeDefault(values)
+	propStruct := values.Elem()
+	if !propStruct.IsValid() {
+		return nil, nil
+	}
+	if err := s.printfIntoPropertyRecursive(nil, propStruct, configValues); err != nil {
+		return nil, err
+	}
+
+	return values.Interface(), nil
+}
+
+func (s *listVariable) printfIntoPropertyRecursive(fieldName []string, propStruct reflect.Value, configValues []string) error {
+	for i := 0; i < propStruct.NumField(); i++ {
+		field := propStruct.Field(i)
+		kind := field.Kind()
+		if kind == reflect.Ptr {
+			if field.IsNil() {
+				continue
+			}
+			field = field.Elem()
+			kind = field.Kind()
+		}
+		switch kind {
+		case reflect.Slice:
+			elemType := field.Type().Elem()
+			newLen := field.Len() * len(configValues)
+			newField := reflect.MakeSlice(field.Type(), 0, newLen)
+			for j := 0; j < field.Len(); j++ {
+				for _, configValue := range configValues {
+					res := reflect.Indirect(reflect.New(elemType))
+					res.Set(field.Index(j))
+					err := printfIntoProperty(res, configValue)
+					if 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)
+					}
+					newField = reflect.Append(newField, res)
+				}
+			}
+			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
+			}
+			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)
+		}
+	}
+	return nil
+}
+
 func printfIntoProperty(propertyValue reflect.Value, configValue string) error {
 	s := propertyValue.String()
 
@@ -739,7 +839,7 @@
 	}
 
 	if count > 1 {
-		return fmt.Errorf("value variable properties only support a single '%%'")
+		return fmt.Errorf("list/value variable properties only support a single '%%'")
 	}
 
 	if !strings.Contains(s, "%s") {
diff --git a/android/soongconfig/modules_test.go b/android/soongconfig/modules_test.go
index 1da0b49..d76794c 100644
--- a/android/soongconfig/modules_test.go
+++ b/android/soongconfig/modules_test.go
@@ -291,11 +291,13 @@
 type properties struct {
 	A *string
 	B bool
+	C []string
 }
 
-type boolVarProps struct {
+type varProps struct {
 	A                  *string
 	B                  bool
+	C                  []string
 	Conditions_default *properties
 }
 
@@ -311,6 +313,19 @@
 	My_value_var interface{}
 }
 
+type listProperties struct {
+	C []string
+}
+
+type listVarProps struct {
+	C                  []string
+	Conditions_default *listProperties
+}
+
+type listSoongConfigVars struct {
+	List_var interface{}
+}
+
 func Test_PropertiesToApply_Bool(t *testing.T) {
 	mt, _ := newModuleType(&ModuleTypeProperties{
 		Module_type:      "foo",
@@ -330,7 +345,7 @@
 		Soong_config_variables boolSoongConfigVars
 	}{
 		Soong_config_variables: boolSoongConfigVars{
-			Bool_var: &boolVarProps{
+			Bool_var: &varProps{
 				A:                  boolVarPositive.A,
 				B:                  boolVarPositive.B,
 				Conditions_default: conditionsDefault,
@@ -373,6 +388,59 @@
 	}
 }
 
+func Test_PropertiesToApply_List(t *testing.T) {
+	mt, _ := newModuleType(&ModuleTypeProperties{
+		Module_type:      "foo",
+		Config_namespace: "bar",
+		List_variables:   []string{"my_list_var"},
+		Properties:       []string{"c"},
+	})
+	conditionsDefault := &listProperties{
+		C: []string{"default"},
+	}
+	actualProps := &struct {
+		Soong_config_variables listSoongConfigVars
+	}{
+		Soong_config_variables: listSoongConfigVars{
+			List_var: &listVarProps{
+				C:                  []string{"A=%s", "B=%s"},
+				Conditions_default: conditionsDefault,
+			},
+		},
+	}
+	props := reflect.ValueOf(actualProps)
+
+	testCases := []struct {
+		name      string
+		config    SoongConfig
+		wantProps []interface{}
+	}{
+		{
+			name:      "no_vendor_config",
+			config:    Config(map[string]string{}),
+			wantProps: []interface{}{conditionsDefault},
+		},
+		{
+			name:   "value_var_set",
+			config: Config(map[string]string{"my_list_var": "hello there"}),
+			wantProps: []interface{}{&listProperties{
+				C: []string{"A=hello", "A=there", "B=hello", "B=there"},
+			}},
+		},
+	}
+
+	for _, tc := range testCases {
+		gotProps, err := PropertiesToApply(mt, props, tc.config)
+		if err != nil {
+			t.Errorf("%s: Unexpected error in PropertiesToApply: %s", tc.name, err)
+		}
+
+		if !reflect.DeepEqual(gotProps, tc.wantProps) {
+			t.Errorf("%s: Expected %s, got %s", tc.name, tc.wantProps, gotProps)
+		}
+	}
+}
+
 func Test_PropertiesToApply_Value(t *testing.T) {
 	mt, _ := newModuleType(&ModuleTypeProperties{
 		Module_type:      "foo",
@@ -388,7 +456,7 @@
 		Soong_config_variables valueSoongConfigVars
 	}{
 		Soong_config_variables: valueSoongConfigVars{
-			My_value_var: &boolVarProps{
+			My_value_var: &varProps{
 				A:                  proptools.StringPtr("A=%s"),
 				B:                  true,
 				Conditions_default: conditionsDefault,
@@ -524,7 +592,7 @@
 		Soong_config_variables stringSoongConfigVars
 	}{
 		Soong_config_variables: stringSoongConfigVars{
-			String_var: &boolVarProps{
+			String_var: &varProps{
 				A:                  stringVarPositive.A,
 				B:                  stringVarPositive.B,
 				Conditions_default: conditionsDefault,
diff --git a/android/test_suites.go b/android/test_suites.go
index adcc15a..ff75f26 100644
--- a/android/test_suites.go
+++ b/android/test_suites.go
@@ -14,6 +14,11 @@
 
 package android
 
+import (
+	"path/filepath"
+	"strings"
+)
+
 func init() {
 	RegisterParallelSingletonType("testsuites", testSuiteFilesFactory)
 }
@@ -23,8 +28,8 @@
 }
 
 type testSuiteFiles struct {
-	robolectric WritablePath
-	ravenwood   WritablePath
+	robolectric []Path
+	ravenwood   []Path
 }
 
 type TestSuiteModule interface {
@@ -48,53 +53,107 @@
 	})
 
 	t.robolectric = robolectricTestSuite(ctx, files["robolectric-tests"])
-	ctx.Phony("robolectric-tests", t.robolectric)
+	ctx.Phony("robolectric-tests", t.robolectric...)
 
 	t.ravenwood = ravenwoodTestSuite(ctx, files["ravenwood-tests"])
-	ctx.Phony("ravenwood-tests", t.ravenwood)
+	ctx.Phony("ravenwood-tests", t.ravenwood...)
 }
 
 func (t *testSuiteFiles) MakeVars(ctx MakeVarsContext) {
-	ctx.DistForGoal("robolectric-tests", t.robolectric)
-	ctx.DistForGoal("ravenwood-tests", t.ravenwood)
+	ctx.DistForGoal("robolectric-tests", t.robolectric...)
+	ctx.DistForGoal("ravenwood-tests", t.ravenwood...)
 }
 
-func robolectricTestSuite(ctx SingletonContext, files map[string]InstallPaths) WritablePath {
+func robolectricTestSuite(ctx SingletonContext, files map[string]InstallPaths) []Path {
 	var installedPaths InstallPaths
 	for _, module := range SortedKeys(files) {
 		installedPaths = append(installedPaths, files[module]...)
 	}
-	testCasesDir := pathForInstall(ctx, ctx.Config().BuildOS, X86, "testcases")
 
-	outputFile := PathForOutput(ctx, "packaging", "robolectric-tests.zip")
+	outputFile := pathForPackaging(ctx, "robolectric-tests.zip")
 	rule := NewRuleBuilder(pctx, ctx)
 	rule.Command().BuiltTool("soong_zip").
 		FlagWithOutput("-o ", outputFile).
 		FlagWithArg("-P ", "host/testcases").
-		FlagWithArg("-C ", testCasesDir.String()).
+		FlagWithArg("-C ", pathForTestCases(ctx).String()).
 		FlagWithRspFileInputList("-r ", outputFile.ReplaceExtension(ctx, "rsp"), installedPaths.Paths()).
+		Flag("-sha256") // necessary to save cas_uploader's time
+
+	testList := buildTestList(ctx, "robolectric-tests_list", installedPaths)
+	testListZipOutputFile := pathForPackaging(ctx, "robolectric-tests_list.zip")
+
+	rule.Command().BuiltTool("soong_zip").
+		FlagWithOutput("-o ", testListZipOutputFile).
+		FlagWithArg("-C ", pathForPackaging(ctx).String()).
+		FlagWithInput("-f ", testList).
 		Flag("-sha256")
+
 	rule.Build("robolectric_tests_zip", "robolectric-tests.zip")
 
-	return outputFile
+	return []Path{outputFile, testListZipOutputFile}
 }
 
-func ravenwoodTestSuite(ctx SingletonContext, files map[string]InstallPaths) WritablePath {
+func ravenwoodTestSuite(ctx SingletonContext, files map[string]InstallPaths) []Path {
 	var installedPaths InstallPaths
 	for _, module := range SortedKeys(files) {
 		installedPaths = append(installedPaths, files[module]...)
 	}
-	testCasesDir := pathForInstall(ctx, ctx.Config().BuildOS, X86, "testcases")
 
-	outputFile := PathForOutput(ctx, "packaging", "ravenwood-tests.zip")
+	outputFile := pathForPackaging(ctx, "ravenwood-tests.zip")
 	rule := NewRuleBuilder(pctx, ctx)
 	rule.Command().BuiltTool("soong_zip").
 		FlagWithOutput("-o ", outputFile).
 		FlagWithArg("-P ", "host/testcases").
-		FlagWithArg("-C ", testCasesDir.String()).
+		FlagWithArg("-C ", pathForTestCases(ctx).String()).
 		FlagWithRspFileInputList("-r ", outputFile.ReplaceExtension(ctx, "rsp"), installedPaths.Paths()).
+		Flag("-sha256") // necessary to save cas_uploader's time
+
+	testList := buildTestList(ctx, "ravenwood-tests_list", installedPaths)
+	testListZipOutputFile := pathForPackaging(ctx, "ravenwood-tests_list.zip")
+
+	rule.Command().BuiltTool("soong_zip").
+		FlagWithOutput("-o ", testListZipOutputFile).
+		FlagWithArg("-C ", pathForPackaging(ctx).String()).
+		FlagWithInput("-f ", testList).
 		Flag("-sha256")
+
 	rule.Build("ravenwood_tests_zip", "ravenwood-tests.zip")
 
+	return []Path{outputFile, testListZipOutputFile}
+}
+
+func buildTestList(ctx SingletonContext, listFile string, installedPaths InstallPaths) Path {
+	buf := &strings.Builder{}
+	for _, p := range installedPaths {
+		if p.Ext() != ".config" {
+			continue
+		}
+		pc, err := toTestListPath(p.String(), pathForTestCases(ctx).String(), "host/testcases")
+		if err != nil {
+			ctx.Errorf("Failed to convert path: %s, %v", p.String(), err)
+			continue
+		}
+		buf.WriteString(pc)
+		buf.WriteString("\n")
+	}
+	outputFile := pathForPackaging(ctx, listFile)
+	WriteFileRuleVerbatim(ctx, outputFile, buf.String())
 	return outputFile
 }
+
+func toTestListPath(path, relativeRoot, prefix string) (string, error) {
+	dest, err := filepath.Rel(relativeRoot, path)
+	if err != nil {
+		return "", err
+	}
+	return filepath.Join(prefix, dest), nil
+}
+
+func pathForPackaging(ctx PathContext, pathComponents ...string) OutputPath {
+	pathComponents = append([]string{"packaging"}, pathComponents...)
+	return PathForOutput(ctx, pathComponents...)
+}
+
+func pathForTestCases(ctx PathContext) InstallPath {
+	return pathForInstall(ctx, ctx.Config().BuildOS, X86, "testcases")
+}
diff --git a/android/test_suites_test.go b/android/test_suites_test.go
new file mode 100644
index 0000000..db9a34d
--- /dev/null
+++ b/android/test_suites_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 android
+
+import (
+	"path/filepath"
+	"testing"
+)
+
+func TestBuildTestList(t *testing.T) {
+	t.Parallel()
+	ctx := GroupFixturePreparers(
+		prepareForFakeTestSuite,
+		FixtureRegisterWithContext(func(ctx RegistrationContext) {
+			ctx.RegisterParallelSingletonType("testsuites", testSuiteFilesFactory)
+		}),
+	).RunTestWithBp(t, `
+		fake_module {
+			name: "module1",
+			outputs: [
+				"Test1/Test1.config",
+				"Test1/Test1.apk",
+			],
+			test_suites: ["ravenwood-tests"],
+		}
+		fake_module {
+			name: "module2",
+			outputs: [
+				"Test2/Test21/Test21.config",
+				"Test2/Test21/Test21.apk",
+			],
+			test_suites: ["ravenwood-tests", "robolectric-tests"],
+		}
+		fake_module {
+			name: "module_without_config",
+			outputs: [
+				"BadTest/BadTest.jar",
+			],
+			test_suites: ["robolectric-tests"],
+		}
+	`)
+
+	config := ctx.SingletonForTests("testsuites")
+	allOutputs := config.AllOutputs()
+
+	wantContents := map[string]string{
+		"robolectric-tests.zip":      "",
+		"robolectric-tests_list.zip": "",
+		"robolectric-tests_list": `host/testcases/Test2/Test21/Test21.config
+`,
+		"ravenwood-tests.zip":      "",
+		"ravenwood-tests_list.zip": "",
+		"ravenwood-tests_list": `host/testcases/Test1/Test1.config
+host/testcases/Test2/Test21/Test21.config
+`,
+	}
+	for _, output := range allOutputs {
+		want, ok := wantContents[filepath.Base(output)]
+		if !ok {
+			t.Errorf("unexpected output: %q", output)
+			continue
+		}
+
+		got := ""
+		if want != "" {
+			got = ContentFromFileRuleForTests(t, ctx.TestContext, config.MaybeOutput(output))
+		}
+
+		if want != got {
+			t.Errorf("want %q, got %q", want, got)
+		}
+	}
+}
+
+type fake_module struct {
+	ModuleBase
+	props struct {
+		Outputs     []string
+		Test_suites []string
+	}
+}
+
+func fakeTestSuiteFactory() Module {
+	module := &fake_module{}
+	base := module.base()
+	module.AddProperties(&base.nameProperties, &module.props)
+	InitAndroidModule(module)
+	return module
+}
+
+var prepareForFakeTestSuite = GroupFixturePreparers(
+	FixtureRegisterWithContext(func(ctx RegistrationContext) {
+		ctx.RegisterModuleType("fake_module", fakeTestSuiteFactory)
+	}),
+)
+
+func (f *fake_module) GenerateAndroidBuildActions(ctx ModuleContext) {
+	for _, output := range f.props.Outputs {
+		ctx.InstallFile(pathForTestCases(ctx), output, nil)
+	}
+}
+
+func (f *fake_module) TestSuites() []string {
+	return f.props.Test_suites
+}
diff --git a/android/variable.go b/android/variable.go
index 0040d83..599f88e 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -20,8 +20,6 @@
 	"runtime"
 	"strings"
 
-	"android/soong/bazel"
-
 	"github.com/google/blueprint/proptools"
 )
 
@@ -492,10 +490,6 @@
 
 	CheckVendorSeappViolations *bool `json:",omitempty"`
 
-	// PartitionVarsForBazelMigrationOnlyDoNotUse are extra variables that are used to define the
-	// partition images. They should not be read from soong modules.
-	PartitionVarsForBazelMigrationOnlyDoNotUse PartitionVariables `json:",omitempty"`
-
 	BuildFlags map[string]string `json:",omitempty"`
 
 	BuildFromSourceStub *bool `json:",omitempty"`
@@ -644,387 +638,6 @@
 	return val == "true"
 }
 
-// ProductConfigContext requires the access to the Module to get product config properties.
-type ProductConfigContext interface {
-	Module() Module
-}
-
-// ProductConfigOrSoongConfigProperty represents either a soong config variable + its value
-// or a product config variable. You can get both a ConfigurationAxis and a SelectKey from it
-// for use in bazel attributes. ProductVariableProperties() will return a map from properties ->
-// this interface -> property structs for use in bp2build converters
-type ProductConfigOrSoongConfigProperty interface {
-	// Name of the product variable or soong config variable
-	Name() string
-	// AlwaysEmit returns true for soong config variables but false for product variables. This
-	// is intended to indicate if we need to always emit empty lists in the select statements.
-	AlwaysEmit() bool
-	// ConfigurationAxis returns the bazel.ConfigurationAxis that represents this variable. The
-	// configuration axis will change depending on the variable and whether it's arch/os variant
-	// as well.
-	ConfigurationAxis() bazel.ConfigurationAxis
-	// SelectKey returns a string that represents the key of a select branch, however, it is not
-	// actually the real label written out to the build file.
-	// this.ConfigurationAxis().SelectKey(this.SelectKey()) will give the actual label.
-	SelectKey() string
-}
-
-// ProductConfigProperty represents a product config variable, and if it is arch-variant or not.
-type ProductConfigProperty struct {
-	// The name of the product variable, e.g. "safestack", "malloc_not_svelte",
-	// "board"
-	name string
-
-	arch string
-}
-
-func (p ProductConfigProperty) Name() string {
-	return p.name
-}
-
-func (p ProductConfigProperty) AlwaysEmit() bool {
-	return false
-}
-
-func (p ProductConfigProperty) ConfigurationAxis() bazel.ConfigurationAxis {
-	return bazel.ProductVariableConfigurationAxis(p.arch != "", p.name+"__"+p.arch)
-}
-
-func (p ProductConfigProperty) SelectKey() string {
-	if p.arch == "" {
-		return strings.ToLower(p.name)
-	} else {
-		return strings.ToLower(p.name + "-" + p.arch)
-	}
-}
-
-// SoongConfigProperty represents a soong config variable, its value if it's a string variable,
-// and if it's dependent on the OS or not
-type SoongConfigProperty struct {
-	name      string
-	namespace string
-	// Can be an empty string for bool/value soong config variables
-	value string
-	// If there is a target: field inside a soong config property struct, the os that it selects
-	// on will be represented here.
-	os string
-}
-
-func (p SoongConfigProperty) Name() string {
-	return p.name
-}
-
-func (p SoongConfigProperty) AlwaysEmit() bool {
-	return true
-}
-
-func (p SoongConfigProperty) ConfigurationAxis() bazel.ConfigurationAxis {
-	return bazel.ProductVariableConfigurationAxis(false, p.namespace+"__"+p.name+"__"+p.os)
-}
-
-// SelectKey returns the literal string that represents this variable in a BUILD
-// select statement.
-func (p SoongConfigProperty) SelectKey() string {
-	// p.value being conditions_default can happen with or without a desired os. When not using
-	// an os, we want to emit literally just //conditions:default in the select statement, but
-	// when using an os, we want to emit namespace__name__conditions_default__os, so that
-	// the branch is only taken if the variable is not set, and we're on the desired os.
-	// ConfigurationAxis#SelectKey will map the conditions_default result of this function to
-	// //conditions:default.
-	if p.value == bazel.ConditionsDefaultConfigKey && p.os == "" {
-		return bazel.ConditionsDefaultConfigKey
-	}
-
-	parts := []string{p.namespace, p.name}
-	if p.value != "" && p.value != bazel.ConditionsDefaultSelectKey {
-		parts = append(parts, p.value)
-	}
-	if p.os != "" {
-		parts = append(parts, p.os)
-	}
-
-	// e.g. acme__feature1, android__board__soc_a, my_namespace__my_variables__my_value__my_os
-	return strings.ToLower(strings.Join(parts, "__"))
-}
-
-// ProductConfigProperties is a map of maps to group property values according
-// their property name and the product config variable they're set under.
-//
-// The outer map key is the name of the property, like "cflags".
-//
-// The inner map key is a ProductConfigProperty, which is a struct of product
-// variable name, namespace, and the "full configuration" of the product
-// variable.
-//
-// e.g. product variable name: board, namespace: acme, full config: vendor_chip_foo
-//
-// The value of the map is the interface{} representing the value of the
-// property, like ["-DDEFINES"] for cflags.
-type ProductConfigProperties map[string]map[ProductConfigOrSoongConfigProperty]interface{}
-
-func (p *ProductConfigProperties) AddProductConfigProperty(
-	propertyName, productVariableName, arch string, propertyValue interface{}) {
-
-	productConfigProp := ProductConfigProperty{
-		name: productVariableName, // e.g. size, feature1, feature2, FEATURE3, board
-		arch: arch,                // e.g. "", x86, arm64
-	}
-
-	p.AddEitherProperty(propertyName, productConfigProp, propertyValue)
-}
-
-func (p *ProductConfigProperties) AddSoongConfigProperty(
-	propertyName, namespace, variableName, value, os string, propertyValue interface{}) {
-
-	soongConfigProp := SoongConfigProperty{
-		namespace: namespace,
-		name:      variableName, // e.g. size, feature1, feature2, FEATURE3, board
-		value:     value,
-		os:        os, // e.g. android, linux_x86
-	}
-
-	p.AddEitherProperty(propertyName, soongConfigProp, propertyValue)
-}
-
-func (p *ProductConfigProperties) AddEitherProperty(
-	propertyName string, key ProductConfigOrSoongConfigProperty, propertyValue interface{}) {
-	if (*p)[propertyName] == nil {
-		(*p)[propertyName] = make(map[ProductConfigOrSoongConfigProperty]interface{})
-	}
-
-	if existing, ok := (*p)[propertyName][key]; ok {
-		switch dst := existing.(type) {
-		case []string:
-			src, ok := propertyValue.([]string)
-			if !ok {
-				panic("Conflicting types")
-			}
-			dst = append(dst, src...)
-			(*p)[propertyName][key] = dst
-		default:
-			if existing != propertyValue {
-				panic(fmt.Errorf("TODO: handle merging value %#v", existing))
-			}
-		}
-	} else {
-		(*p)[propertyName][key] = propertyValue
-	}
-}
-
-// maybeExtractConfigVarProp attempts to read this value as a config var struct
-// wrapped by interfaces and ptrs. If it's not the right type, the second return
-// value is false.
-func maybeExtractConfigVarProp(v reflect.Value) (reflect.Value, bool) {
-	if v.Kind() == reflect.Interface {
-		// The conditions_default value can be either
-		// 1) an ptr to an interface of a struct (bool config variables and product variables)
-		// 2) an interface of 1) (config variables with nested structs, like string vars)
-		v = v.Elem()
-	}
-	if v.Kind() != reflect.Ptr {
-		return v, false
-	}
-	v = reflect.Indirect(v)
-	if v.Kind() == reflect.Interface {
-		// Extract the struct from the interface
-		v = v.Elem()
-	}
-
-	if !v.IsValid() {
-		return v, false
-	}
-
-	if v.Kind() != reflect.Struct {
-		return v, false
-	}
-	return v, true
-}
-
-func (productConfigProperties *ProductConfigProperties) AddProductConfigProperties(variableValues reflect.Value, arch string) {
-	// Example of product_variables:
-	//
-	// product_variables: {
-	//     malloc_not_svelte: {
-	//         shared_libs: ["malloc_not_svelte_shared_lib"],
-	//         whole_static_libs: ["malloc_not_svelte_whole_static_lib"],
-	//         exclude_static_libs: [
-	//             "malloc_not_svelte_static_lib_excludes",
-	//             "malloc_not_svelte_whole_static_lib_excludes",
-	//         ],
-	//     },
-	// },
-
-	for i := 0; i < variableValues.NumField(); i++ {
-		// e.g. Platform_sdk_version, Unbundled_build, Malloc_not_svelte, etc.
-		productVariableName := variableValues.Type().Field(i).Name
-
-		variableValue := variableValues.Field(i)
-		// Check if any properties were set for the module
-		if variableValue.IsZero() {
-			// e.g. feature1: {}, malloc_not_svelte: {}
-			continue
-		}
-
-		for j := 0; j < variableValue.NumField(); j++ {
-			property := variableValue.Field(j)
-			// e.g. Asflags, Cflags, Enabled, etc.
-			propertyName := variableValue.Type().Field(j).Name
-			if property.Kind() != reflect.Interface {
-				productConfigProperties.AddProductConfigProperty(propertyName, productVariableName, arch, property.Interface())
-			}
-		}
-	}
-
-}
-
-func (productConfigProperties *ProductConfigProperties) AddSoongConfigProperties(namespace string, soongConfigVariablesStruct reflect.Value) error {
-	//
-	// Example of soong_config_variables:
-	//
-	// soong_config_variables: {
-	//      feature1: {
-	//          conditions_default: {
-	//               ...
-	//          },
-	//          cflags: ...
-	//      },
-	//      feature2: {
-	//          cflags: ...
-	//          conditions_default: {
-	//               ...
-	//          },
-	//      },
-	//      board: {
-	//         soc_a: {
-	//             ...
-	//         },
-	//         soc_b: {
-	//             ...
-	//         },
-	//         soc_c: {},
-	//         conditions_default: {
-	//              ...
-	//         },
-	//      },
-	// }
-	for i := 0; i < soongConfigVariablesStruct.NumField(); i++ {
-		// e.g. feature1, feature2, board
-		variableName := soongConfigVariablesStruct.Type().Field(i).Name
-		variableStruct := soongConfigVariablesStruct.Field(i)
-		// Check if any properties were set for the module
-		if variableStruct.IsZero() {
-			// e.g. feature1: {}
-			continue
-		}
-
-		// Unlike product variables, config variables require a few more
-		// indirections to extract the struct from the reflect.Value.
-		if v, ok := maybeExtractConfigVarProp(variableStruct); ok {
-			variableStruct = v
-		} else if !v.IsValid() {
-			// Skip invalid variables which may not used, else leads to panic
-			continue
-		}
-
-		for j := 0; j < variableStruct.NumField(); j++ {
-			propertyOrStruct := variableStruct.Field(j)
-			// propertyOrValueName can either be:
-			//  - A property, like: Asflags, Cflags, Enabled, etc.
-			//  - A soong config string variable's value, like soc_a, soc_b, soc_c in the example above
-			//  - "conditions_default"
-			propertyOrValueName := variableStruct.Type().Field(j).Name
-
-			// If the property wasn't set, no need to pass it along
-			if propertyOrStruct.IsZero() {
-				continue
-			}
-
-			if v, ok := maybeExtractConfigVarProp(propertyOrStruct); ok {
-				// The field is a struct, which is used by:
-				// 1) soong_config_string_variables
-				//
-				// soc_a: {
-				//     cflags: ...,
-				// }
-				//
-				// soc_b: {
-				//     cflags: ...,
-				// }
-				//
-				// 2) conditions_default structs for all soong config variable types.
-				//
-				// conditions_default: {
-				//     cflags: ...,
-				//     static_libs: ...
-				// }
-				//
-				// This means that propertyOrValueName is either conditions_default, or a soong
-				// config string variable's value.
-				field := v
-				// Iterate over fields of this struct prop.
-				for k := 0; k < field.NumField(); k++ {
-					// For product variables, zero values are irrelevant; however, for soong config variables,
-					// empty values are relevant because there can also be a conditions default which is not
-					// applied for empty variables.
-					if field.Field(k).IsZero() && namespace == "" {
-						continue
-					}
-
-					propertyName := field.Type().Field(k).Name
-					if propertyName == "Target" {
-						productConfigProperties.AddSoongConfigPropertiesFromTargetStruct(namespace, variableName, proptools.PropertyNameForField(propertyOrValueName), field.Field(k))
-					} else if propertyName == "Arch" || propertyName == "Multilib" {
-						return fmt.Errorf("Arch/Multilib are not currently supported in soong config variable structs")
-					} else {
-						productConfigProperties.AddSoongConfigProperty(propertyName, namespace, variableName, proptools.PropertyNameForField(propertyOrValueName), "", field.Field(k).Interface())
-					}
-				}
-			} else if propertyOrStruct.Kind() != reflect.Interface {
-				// If not an interface, then this is not a conditions_default or
-				// a struct prop. That is, this is a bool/value config variable.
-				if propertyOrValueName == "Target" {
-					productConfigProperties.AddSoongConfigPropertiesFromTargetStruct(namespace, variableName, "", propertyOrStruct)
-				} else if propertyOrValueName == "Arch" || propertyOrValueName == "Multilib" {
-					return fmt.Errorf("Arch/Multilib are not currently supported in soong config variable structs")
-				} else {
-					productConfigProperties.AddSoongConfigProperty(propertyOrValueName, namespace, variableName, "", "", propertyOrStruct.Interface())
-				}
-			}
-		}
-	}
-	return nil
-}
-
-func (productConfigProperties *ProductConfigProperties) AddSoongConfigPropertiesFromTargetStruct(namespace, soongConfigVariableName string, soongConfigVariableValue string, targetStruct reflect.Value) {
-	// targetStruct will be a struct with fields like "android", "host", "arm", "x86",
-	// "android_arm", etc. The values of each of those fields will be a regular property struct.
-	for i := 0; i < targetStruct.NumField(); i++ {
-		targetFieldName := targetStruct.Type().Field(i).Name
-		archOrOsSpecificStruct := targetStruct.Field(i)
-		for j := 0; j < archOrOsSpecificStruct.NumField(); j++ {
-			property := archOrOsSpecificStruct.Field(j)
-			// e.g. Asflags, Cflags, Enabled, etc.
-			propertyName := archOrOsSpecificStruct.Type().Field(j).Name
-
-			if targetFieldName == "Android" {
-				productConfigProperties.AddSoongConfigProperty(propertyName, namespace, soongConfigVariableName, soongConfigVariableValue, "android", property.Interface())
-			} else if targetFieldName == "Host" {
-				for _, os := range osTypeList {
-					if os.Class == Host {
-						productConfigProperties.AddSoongConfigProperty(propertyName, namespace, soongConfigVariableName, soongConfigVariableValue, os.Name, property.Interface())
-					}
-				}
-			} else if !archOrOsSpecificStruct.IsZero() {
-				// One problem with supporting additional fields is that if multiple branches of
-				// "target" overlap, we don't want them to be in the same select statement (aka
-				// configuration axis). "android" and "host" are disjoint, so it's ok that we only
-				// have 2 axes right now. (soongConfigVariables and soongConfigVariablesPlusOs)
-				panic("TODO: support other target types in soong config variable structs: " + targetFieldName)
-			}
-		}
-	}
-}
-
 func VariableMutator(mctx BottomUpMutatorContext) {
 	var module Module
 	var ok bool
diff --git a/apex/aconfig_test.go b/apex/aconfig_test.go
index 3e44f0b..3de9286 100644
--- a/apex/aconfig_test.go
+++ b/apex/aconfig_test.go
@@ -162,6 +162,18 @@
 					name: "server_configurable_flags",
 					srcs: ["server_configurable_flags.cc"],
 				}
+				cc_library {
+					name: "libbase",
+					srcs: ["libbase.cc"],
+				}
+				cc_library {
+					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",
@@ -410,6 +422,18 @@
 					name: "server_configurable_flags",
 					srcs: ["server_configurable_flags.cc"],
 				}
+				cc_library {
+					name: "libbase",
+					srcs: ["libbase.cc"],
+				}
+				cc_library {
+					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",
@@ -460,6 +484,18 @@
 					name: "server_configurable_flags",
 					srcs: ["server_configurable_flags.cc"],
 				}
+				cc_library {
+					name: "libbase",
+					srcs: ["libbase.cc"],
+				}
+				cc_library {
+					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/apex.go b/apex/apex.go
index cb8449c..ef57d7e 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -73,7 +73,7 @@
 	// Run mark_platform_availability before the apexMutator as the apexMutator needs to know whether
 	// it should create a platform variant.
 	ctx.BottomUp("mark_platform_availability", markPlatformAvailability).Parallel()
-	ctx.BottomUp("apex", apexMutator).Parallel()
+	ctx.Transition("apex", &apexTransitionMutator{})
 	ctx.BottomUp("apex_directly_in_any", apexDirectlyInAnyMutator).Parallel()
 	ctx.BottomUp("apex_dcla_deps", apexDCLADepsMutator).Parallel()
 	// Register after apex_info mutator so that it can use ApexVariationName
@@ -1084,6 +1084,10 @@
 	if a, ok := mctx.Module().(ApexInfoMutator); ok {
 		a.ApexInfoMutator(mctx)
 	}
+
+	if am, ok := mctx.Module().(android.ApexModule); ok {
+		android.ApexInfoMutator(mctx, am)
+	}
 	enforceAppUpdatability(mctx)
 }
 
@@ -1284,40 +1288,41 @@
 	}
 }
 
-// apexMutator visits each module and creates apex variations if the module was marked in the
-// previous run of apexInfoMutator.
-func apexMutator(mctx android.BottomUpMutatorContext) {
-	if !mctx.Module().Enabled() {
-		return
-	}
+type apexTransitionMutator struct{}
 
-	// This is the usual path.
-	if am, ok := mctx.Module().(android.ApexModule); ok && am.CanHaveApexVariants() {
-		android.CreateApexVariations(mctx, am)
-		return
-	}
-
+func (a *apexTransitionMutator) Split(ctx android.BaseModuleContext) []string {
 	// apexBundle itself is mutated so that it and its dependencies have the same apex variant.
-	// Note that a default variation "" is also created as an alias, and the default dependency
-	// variation is set to the default variation. This is to allow an apex to depend on another
-	// module which is outside of the apex. This is because the dependent module is not mutated
-	// for this apex variant.
-	createApexVariation := func(apexBundleName string) {
-		defaultVariation := ""
-		mctx.SetDefaultDependencyVariation(&defaultVariation)
-		mctx.CreateVariations(apexBundleName)
-		mctx.CreateAliasVariation(defaultVariation, apexBundleName)
-	}
-
-	if ai, ok := mctx.Module().(ApexInfoMutator); ok && apexModuleTypeRequiresVariant(ai) {
-		createApexVariation(ai.ApexVariationName())
-	} else if o, ok := mctx.Module().(*OverrideApex); ok {
+	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 == "" {
-			mctx.ModuleErrorf("base property is not set")
-			return
+			ctx.ModuleErrorf("base property is not set")
 		}
-		createApexVariation(apexBundleName)
+		return []string{apexBundleName}
+	}
+	return []string{""}
+}
+
+func (a *apexTransitionMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+	return sourceVariation
+}
+
+func (a *apexTransitionMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
+	if am, ok := ctx.Module().(android.ApexModule); ok && am.CanHaveApexVariants() {
+		return android.IncomingApexTransition(ctx, incomingVariation)
+	} else if ai, ok := ctx.Module().(ApexInfoMutator); ok {
+		return ai.ApexVariationName()
+	} else if o, ok := ctx.Module().(*OverrideApex); ok {
+		return o.GetOverriddenModuleName()
+	}
+
+	return ""
+}
+
+func (a *apexTransitionMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
+	if am, ok := ctx.Module().(android.ApexModule); ok && am.CanHaveApexVariants() {
+		android.MutateApexTransition(ctx, variation)
 	}
 }
 
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 2441b02..8a3735c 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -10691,6 +10691,18 @@
 			name: "server_configurable_flags",
 			srcs: ["server_configurable_flags.cc"],
 		}
+		cc_library {
+			name: "libbase",
+			srcs: ["libbase.cc"],
+		}
+		cc_library {
+			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")
diff --git a/cc/Android.bp b/cc/Android.bp
index 5ba9427..9ce8933 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -96,7 +96,6 @@
         "gen_test.go",
         "genrule_test.go",
         "library_headers_test.go",
-        "library_stub_test.go",
         "library_test.go",
         "lto_test.go",
         "ndk_test.go",
diff --git a/cc/library.go b/cc/library.go
index 5b24809..44bbdfc 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -1261,6 +1261,18 @@
 		"34")
 }
 
+func (library *libraryDecorator) linkApexSAbiDumpFiles(ctx ModuleContext,
+	deps PathDeps, sAbiDumpFiles android.Paths, soFile android.Path, libFileName string,
+	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)
+}
+
 func getRefAbiDumpFile(ctx android.ModuleInstallPathContext,
 	versionedDumpDir, fileName string) android.OptionalPath {
 
@@ -1276,21 +1288,21 @@
 }
 
 // Return the previous and current SDK versions for cross-version ABI diff.
-func crossVersionAbiDiffSdkVersions(ctx ModuleContext, dumpDir string) (string, string) {
+func crossVersionAbiDiffSdkVersions(ctx ModuleContext, dumpDir string) (int, int) {
 	sdkVersionInt := ctx.Config().PlatformSdkVersion().FinalInt()
-	sdkVersionStr := ctx.Config().PlatformSdkVersion().String()
 
 	if ctx.Config().PlatformSdkFinal() {
-		return strconv.Itoa(sdkVersionInt - 1), sdkVersionStr
+		return sdkVersionInt - 1, sdkVersionInt
 	} else {
 		// The platform SDK version can be upgraded before finalization while the corresponding abi dumps hasn't
 		// been generated. Thus the Cross-Version Check chooses PLATFORM_SDK_VERION - 1 as previous version.
 		// This situation could be identified by checking the existence of the PLATFORM_SDK_VERION dump directory.
-		versionedDumpDir := android.ExistentPathForSource(ctx, dumpDir, sdkVersionStr)
+		versionedDumpDir := android.ExistentPathForSource(ctx,
+			dumpDir, ctx.Config().PlatformSdkVersion().String())
 		if versionedDumpDir.Valid() {
-			return sdkVersionStr, strconv.Itoa(sdkVersionInt + 1)
+			return sdkVersionInt, sdkVersionInt + 1
 		} else {
-			return strconv.Itoa(sdkVersionInt - 1), sdkVersionStr
+			return sdkVersionInt - 1, sdkVersionInt
 		}
 	}
 }
@@ -1387,7 +1399,7 @@
 			headerAbiChecker.Exclude_symbol_tags,
 			currSdkVersion)
 
-		var llndkDump android.Path
+		var llndkDump, apexVariantDump android.Path
 		tags := classifySourceAbiDump(ctx)
 		for _, tag := range tags {
 			if tag == llndkLsdumpTag {
@@ -1399,6 +1411,15 @@
 						headerAbiChecker.Exclude_symbol_tags)
 				}
 				addLsdumpPath(string(tag) + ":" + llndkDump.String())
+			} else if tag == apexLsdumpTag {
+				if apexVariantDump == nil {
+					apexVariantDump = library.linkApexSAbiDumpFiles(ctx,
+						deps, objs.sAbiDumpFiles, soFile, fileName,
+						headerAbiChecker.Exclude_symbol_versions,
+						headerAbiChecker.Exclude_symbol_tags,
+						currSdkVersion)
+				}
+				addLsdumpPath(string(tag) + ":" + apexVariantDump.String())
 			} else {
 				addLsdumpPath(string(tag) + ":" + implDump.String())
 			}
@@ -1412,11 +1433,14 @@
 			}
 			dumpDir := filepath.Join("prebuilts", "abi-dumps", dumpDirName)
 			isLlndk := (tag == llndkLsdumpTag)
+			isApex := (tag == apexLsdumpTag)
 			isNdk := (tag == ndkLsdumpTag)
 			binderBitness := ctx.DeviceConfig().BinderBitness()
 			nameExt := ""
 			if isLlndk {
 				nameExt = "llndk"
+			} else if isApex {
+				nameExt = "apex"
 			}
 			// Check against the previous version.
 			var prevVersion, currVersion string
@@ -1430,7 +1454,13 @@
 					sourceDump = llndkDump
 				}
 			} else {
-				prevVersion, currVersion = crossVersionAbiDiffSdkVersions(ctx, dumpDir)
+				prevVersionInt, currVersionInt := crossVersionAbiDiffSdkVersions(ctx, dumpDir)
+				prevVersion = strconv.Itoa(prevVersionInt)
+				currVersion = strconv.Itoa(currVersionInt)
+				// APEX dumps are generated by different rules after trunk stable.
+				if isApex && prevVersionInt > 34 {
+					sourceDump = apexVariantDump
+				}
 			}
 			prevDumpDir := filepath.Join(dumpDir, prevVersion, binderBitness)
 			prevDumpFile := getRefAbiDumpFile(ctx, prevDumpDir, fileName)
@@ -1447,6 +1477,10 @@
 				}
 			} else {
 				currVersion = currSdkVersion
+				if isApex && (!ctx.Config().PlatformSdkFinal() ||
+					ctx.Config().PlatformSdkVersion().FinalInt() > 34) {
+					sourceDump = apexVariantDump
+				}
 			}
 			currDumpDir := filepath.Join(dumpDir, currVersion, binderBitness)
 			currDumpFile := getRefAbiDumpFile(ctx, currDumpDir, fileName)
diff --git a/cc/library_stub_test.go b/cc/library_stub_test.go
deleted file mode 100644
index 4df0a41..0000000
--- a/cc/library_stub_test.go
+++ /dev/null
@@ -1,459 +0,0 @@
-// Copyright 2021 Google Inc. All rights reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package cc
-
-import (
-	_ "fmt"
-	_ "sort"
-
-	"testing"
-
-	"android/soong/android"
-
-	"github.com/google/blueprint"
-)
-
-func hasDirectDependency(t *testing.T, ctx *android.TestResult, from android.Module, to android.Module) bool {
-	t.Helper()
-	var found bool
-	ctx.VisitDirectDeps(from, func(dep blueprint.Module) {
-		if dep == to {
-			found = true
-		}
-	})
-	return found
-}
-
-func TestApiLibraryReplacesExistingModule(t *testing.T) {
-	bp := `
-		cc_library {
-			name: "libfoo",
-			shared_libs: ["libbar"],
-			vendor_available: true,
-		}
-
-		cc_library {
-			name: "libbar",
-		}
-
-		cc_api_library {
-			name: "libbar",
-			vendor_available: true,
-			src: "libbar.so",
-		}
-
-		api_imports {
-			name: "api_imports",
-			shared_libs: [
-				"libbar",
-			],
-		}
-	`
-
-	ctx := prepareForCcTest.RunTestWithBp(t, bp)
-
-	libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Module()
-	libbar := ctx.ModuleForTests("libbar", "android_arm64_armv8-a_shared").Module()
-	libbarApiImport := ctx.ModuleForTests("libbar.apiimport", "android_arm64_armv8-a_shared").Module()
-
-	android.AssertBoolEquals(t, "original library should be linked with non-stub variant", true, hasDirectDependency(t, ctx, libfoo, libbar))
-	android.AssertBoolEquals(t, "Stub library from API surface should be not linked with non-stub variant", false, hasDirectDependency(t, ctx, libfoo, libbarApiImport))
-
-	libfooVendor := ctx.ModuleForTests("libfoo", "android_vendor_arm64_armv8-a_shared").Module()
-	libbarApiImportVendor := ctx.ModuleForTests("libbar.apiimport", "android_vendor_arm64_armv8-a_shared").Module()
-
-	android.AssertBoolEquals(t, "original library should not be linked", false, hasDirectDependency(t, ctx, libfooVendor, libbar))
-	android.AssertBoolEquals(t, "Stub library from API surface should be linked", true, hasDirectDependency(t, ctx, libfooVendor, libbarApiImportVendor))
-}
-
-func TestApiLibraryDoNotRequireOriginalModule(t *testing.T) {
-	bp := `
-		cc_library {
-			name: "libfoo",
-			shared_libs: ["libbar"],
-			vendor: true,
-		}
-
-		cc_api_library {
-			name: "libbar",
-			src: "libbar.so",
-			vendor_available: true,
-		}
-
-		api_imports {
-			name: "api_imports",
-			shared_libs: [
-				"libbar",
-			],
-		}
-	`
-
-	ctx := prepareForCcTest.RunTestWithBp(t, bp)
-
-	libfoo := ctx.ModuleForTests("libfoo", "android_vendor_arm64_armv8-a_shared").Module()
-	libbarApiImport := ctx.ModuleForTests("libbar.apiimport", "android_vendor_arm64_armv8-a_shared").Module()
-
-	android.AssertBoolEquals(t, "Stub library from API surface should be linked", true, hasDirectDependency(t, ctx, libfoo, libbarApiImport))
-}
-
-func TestApiLibraryShouldNotReplaceWithoutApiImport(t *testing.T) {
-	bp := `
-		cc_library {
-			name: "libfoo",
-			shared_libs: ["libbar"],
-			vendor_available: true,
-		}
-
-		cc_library {
-			name: "libbar",
-			vendor_available: true,
-		}
-
-		cc_api_library {
-			name: "libbar",
-			src: "libbar.so",
-			vendor_available: true,
-		}
-
-		api_imports {
-			name: "api_imports",
-			shared_libs: [],
-		}
-	`
-
-	ctx := prepareForCcTest.RunTestWithBp(t, bp)
-
-	libfoo := ctx.ModuleForTests("libfoo", "android_vendor_arm64_armv8-a_shared").Module()
-	libbar := ctx.ModuleForTests("libbar", "android_vendor_arm64_armv8-a_shared").Module()
-	libbarApiImport := ctx.ModuleForTests("libbar.apiimport", "android_vendor_arm64_armv8-a_shared").Module()
-
-	android.AssertBoolEquals(t, "original library should be linked", true, hasDirectDependency(t, ctx, libfoo, libbar))
-	android.AssertBoolEquals(t, "Stub library from API surface should not be linked", false, hasDirectDependency(t, ctx, libfoo, libbarApiImport))
-}
-
-func TestExportDirFromStubLibrary(t *testing.T) {
-	bp := `
-		cc_library {
-			name: "libfoo",
-			export_include_dirs: ["source_include_dir"],
-			export_system_include_dirs: ["source_system_include_dir"],
-			vendor_available: true,
-		}
-		cc_api_library {
-			name: "libfoo",
-			export_include_dirs: ["stub_include_dir"],
-			export_system_include_dirs: ["stub_system_include_dir"],
-			vendor_available: true,
-			src: "libfoo.so",
-		}
-		api_imports {
-			name: "api_imports",
-			shared_libs: [
-				"libfoo",
-			],
-			header_libs: [],
-		}
-		// vendor binary
-		cc_binary {
-			name: "vendorbin",
-			vendor: true,
-			srcs: ["vendor.cc"],
-			shared_libs: ["libfoo"],
-		}
-	`
-	ctx := prepareForCcTest.RunTestWithBp(t, bp)
-	vendorCFlags := ctx.ModuleForTests("vendorbin", "android_vendor_arm64_armv8-a").Rule("cc").Args["cFlags"]
-	android.AssertStringDoesContain(t, "Vendor binary should compile using headers provided by stub", vendorCFlags, "-Istub_include_dir")
-	android.AssertStringDoesNotContain(t, "Vendor binary should not compile using headers of source", vendorCFlags, "-Isource_include_dir")
-	android.AssertStringDoesContain(t, "Vendor binary should compile using system headers provided by stub", vendorCFlags, "-isystem stub_system_include_dir")
-	android.AssertStringDoesNotContain(t, "Vendor binary should not compile using system headers of source", vendorCFlags, "-isystem source_system_include_dir")
-
-	vendorImplicits := ctx.ModuleForTests("vendorbin", "android_vendor_arm64_armv8-a").Rule("cc").OrderOnly.Strings()
-	// Building the stub.so file first assembles its .h files in multi-tree out.
-	// These header files are required for compiling the other API domain (vendor in this case)
-	android.AssertStringListContains(t, "Vendor binary compilation should have an implicit dep on the stub .so file", vendorImplicits, "libfoo.so")
-}
-
-func TestApiLibraryWithLlndkVariant(t *testing.T) {
-	bp := `
-		cc_binary {
-			name: "binfoo",
-			vendor: true,
-			srcs: ["binfoo.cc"],
-			shared_libs: ["libbar"],
-		}
-
-		cc_api_library {
-			name: "libbar",
-			// TODO(b/244244438) Remove src property once all variants are implemented.
-			src: "libbar.so",
-			vendor_available: true,
-			variants: [
-				"llndk",
-			],
-		}
-
-		cc_api_variant {
-			name: "libbar",
-			variant: "llndk",
-			src: "libbar_llndk.so",
-			export_include_dirs: ["libbar_llndk_include"]
-		}
-
-		api_imports {
-			name: "api_imports",
-			shared_libs: [
-				"libbar",
-			],
-			header_libs: [],
-		}
-	`
-
-	ctx := prepareForCcTest.RunTestWithBp(t, bp)
-
-	binfoo := ctx.ModuleForTests("binfoo", "android_vendor_arm64_armv8-a").Module()
-	libbarApiImport := ctx.ModuleForTests("libbar.apiimport", "android_vendor_arm64_armv8-a_shared").Module()
-	libbarApiVariant := ctx.ModuleForTests("libbar.llndk.apiimport", "android_vendor_arm64_armv8-a").Module()
-
-	android.AssertBoolEquals(t, "Stub library from API surface should be linked", true, hasDirectDependency(t, ctx, binfoo, libbarApiImport))
-	android.AssertBoolEquals(t, "Stub library variant from API surface should be linked", true, hasDirectDependency(t, ctx, libbarApiImport, libbarApiVariant))
-
-	binFooLibFlags := ctx.ModuleForTests("binfoo", "android_vendor_arm64_armv8-a").Rule("ld").Args["libFlags"]
-	android.AssertStringDoesContain(t, "Vendor binary should be linked with LLNDK variant source", binFooLibFlags, "libbar.llndk.apiimport.so")
-
-	binFooCFlags := ctx.ModuleForTests("binfoo", "android_vendor_arm64_armv8-a").Rule("cc").Args["cFlags"]
-	android.AssertStringDoesContain(t, "Vendor binary should include headers from the LLNDK variant source", binFooCFlags, "-Ilibbar_llndk_include")
-}
-
-func TestApiLibraryWithNdkVariant(t *testing.T) {
-	bp := `
-		cc_binary {
-			name: "binfoo",
-			sdk_version: "29",
-			srcs: ["binfoo.cc"],
-			shared_libs: ["libbar"],
-			stl: "c++_shared",
-		}
-
-		cc_binary {
-			name: "binbaz",
-			sdk_version: "30",
-			srcs: ["binbaz.cc"],
-			shared_libs: ["libbar"],
-			stl: "c++_shared",
-		}
-
-		cc_binary {
-			name: "binqux",
-			srcs: ["binfoo.cc"],
-			shared_libs: ["libbar"],
-		}
-
-		cc_library {
-			name: "libbar",
-			srcs: ["libbar.cc"],
-		}
-
-		cc_api_library {
-			name: "libbar",
-			// TODO(b/244244438) Remove src property once all variants are implemented.
-			src: "libbar.so",
-			variants: [
-				"ndk.29",
-				"ndk.30",
-				"ndk.current",
-			],
-		}
-
-		cc_api_variant {
-			name: "libbar",
-			variant: "ndk",
-			version: "29",
-			src: "libbar_ndk_29.so",
-			export_include_dirs: ["libbar_ndk_29_include"]
-		}
-
-		cc_api_variant {
-			name: "libbar",
-			variant: "ndk",
-			version: "30",
-			src: "libbar_ndk_30.so",
-			export_include_dirs: ["libbar_ndk_30_include"]
-		}
-
-		cc_api_variant {
-			name: "libbar",
-			variant: "ndk",
-			version: "current",
-			src: "libbar_ndk_current.so",
-			export_include_dirs: ["libbar_ndk_current_include"]
-		}
-
-		api_imports {
-			name: "api_imports",
-			shared_libs: [
-				"libbar",
-			],
-			header_libs: [],
-		}
-	`
-
-	ctx := prepareForCcTest.RunTestWithBp(t, bp)
-
-	binfoo := ctx.ModuleForTests("binfoo", "android_arm64_armv8-a_sdk").Module()
-	libbarApiImportv29 := ctx.ModuleForTests("libbar.apiimport", "android_arm64_armv8-a_sdk_shared_29").Module()
-	libbarApiVariantv29 := ctx.ModuleForTests("libbar.ndk.29.apiimport", "android_arm64_armv8-a_sdk").Module()
-	libbarApiImportv30 := ctx.ModuleForTests("libbar.apiimport", "android_arm64_armv8-a_sdk_shared_30").Module()
-	libbarApiVariantv30 := ctx.ModuleForTests("libbar.ndk.30.apiimport", "android_arm64_armv8-a_sdk").Module()
-
-	android.AssertBoolEquals(t, "Stub library from API surface should be linked with target version", true, hasDirectDependency(t, ctx, binfoo, libbarApiImportv29))
-	android.AssertBoolEquals(t, "Stub library variant from API surface should be linked with target version", true, hasDirectDependency(t, ctx, libbarApiImportv29, libbarApiVariantv29))
-	android.AssertBoolEquals(t, "Stub library from API surface should not be linked with different version", false, hasDirectDependency(t, ctx, binfoo, libbarApiImportv30))
-	android.AssertBoolEquals(t, "Stub library variant from API surface should not be linked with different version", false, hasDirectDependency(t, ctx, libbarApiImportv29, libbarApiVariantv30))
-
-	binbaz := ctx.ModuleForTests("binbaz", "android_arm64_armv8-a_sdk").Module()
-
-	android.AssertBoolEquals(t, "Stub library from API surface should be linked with target version", true, hasDirectDependency(t, ctx, binbaz, libbarApiImportv30))
-	android.AssertBoolEquals(t, "Stub library from API surface should not be linked with different version", false, hasDirectDependency(t, ctx, binbaz, libbarApiImportv29))
-
-	binFooLibFlags := ctx.ModuleForTests("binfoo", "android_arm64_armv8-a_sdk").Rule("ld").Args["libFlags"]
-	android.AssertStringDoesContain(t, "Binary using sdk should be linked with NDK variant source", binFooLibFlags, "libbar.ndk.29.apiimport.so")
-
-	binFooCFlags := ctx.ModuleForTests("binfoo", "android_arm64_armv8-a_sdk").Rule("cc").Args["cFlags"]
-	android.AssertStringDoesContain(t, "Binary using sdk should include headers from the NDK variant source", binFooCFlags, "-Ilibbar_ndk_29_include")
-
-	binQux := ctx.ModuleForTests("binqux", "android_arm64_armv8-a").Module()
-	android.AssertBoolEquals(t, "NDK Stub library from API surface should not be linked with nonSdk binary", false,
-		(hasDirectDependency(t, ctx, binQux, libbarApiImportv30) || hasDirectDependency(t, ctx, binQux, libbarApiImportv29)))
-}
-
-func TestApiLibraryWithMultipleVariants(t *testing.T) {
-	bp := `
-		cc_binary {
-			name: "binfoo",
-			sdk_version: "29",
-			srcs: ["binfoo.cc"],
-			shared_libs: ["libbar"],
-			stl: "c++_shared",
-		}
-
-		cc_binary {
-			name: "binbaz",
-			vendor: true,
-			srcs: ["binbaz.cc"],
-			shared_libs: ["libbar"],
-		}
-
-		cc_library {
-			name: "libbar",
-			srcs: ["libbar.cc"],
-		}
-
-		cc_api_library {
-			name: "libbar",
-			// TODO(b/244244438) Remove src property once all variants are implemented.
-			src: "libbar.so",
-			vendor_available: true,
-			variants: [
-				"llndk",
-				"ndk.29",
-				"ndk.30",
-				"ndk.current",
-				"apex.29",
-				"apex.30",
-				"apex.current",
-			],
-		}
-
-		cc_api_variant {
-			name: "libbar",
-			variant: "ndk",
-			version: "29",
-			src: "libbar_ndk_29.so",
-			export_include_dirs: ["libbar_ndk_29_include"]
-		}
-
-		cc_api_variant {
-			name: "libbar",
-			variant: "ndk",
-			version: "30",
-			src: "libbar_ndk_30.so",
-			export_include_dirs: ["libbar_ndk_30_include"]
-		}
-
-		cc_api_variant {
-			name: "libbar",
-			variant: "ndk",
-			version: "current",
-			src: "libbar_ndk_current.so",
-			export_include_dirs: ["libbar_ndk_current_include"]
-		}
-
-		cc_api_variant {
-			name: "libbar",
-			variant: "apex",
-			version: "29",
-			src: "libbar_apex_29.so",
-			export_include_dirs: ["libbar_apex_29_include"]
-		}
-
-		cc_api_variant {
-			name: "libbar",
-			variant: "apex",
-			version: "30",
-			src: "libbar_apex_30.so",
-			export_include_dirs: ["libbar_apex_30_include"]
-		}
-
-		cc_api_variant {
-			name: "libbar",
-			variant: "apex",
-			version: "current",
-			src: "libbar_apex_current.so",
-			export_include_dirs: ["libbar_apex_current_include"]
-		}
-
-		cc_api_variant {
-			name: "libbar",
-			variant: "llndk",
-			src: "libbar_llndk.so",
-			export_include_dirs: ["libbar_llndk_include"]
-		}
-
-		api_imports {
-			name: "api_imports",
-			shared_libs: [
-				"libbar",
-			],
-			apex_shared_libs: [
-				"libbar",
-			],
-		}
-	`
-	ctx := prepareForCcTest.RunTestWithBp(t, bp)
-
-	binfoo := ctx.ModuleForTests("binfoo", "android_arm64_armv8-a_sdk").Module()
-	libbarApiImportv29 := ctx.ModuleForTests("libbar.apiimport", "android_arm64_armv8-a_sdk_shared_29").Module()
-	libbarApiImportLlndk := ctx.ModuleForTests("libbar.apiimport", "android_vendor_arm64_armv8-a_shared").Module()
-
-	android.AssertBoolEquals(t, "Binary using SDK should be linked with API library from NDK variant", true, hasDirectDependency(t, ctx, binfoo, libbarApiImportv29))
-	android.AssertBoolEquals(t, "Binary using SDK should not be linked with API library from LLNDK variant", false, hasDirectDependency(t, ctx, binfoo, libbarApiImportLlndk))
-
-	binbaz := ctx.ModuleForTests("binbaz", "android_vendor_arm64_armv8-a").Module()
-
-	android.AssertBoolEquals(t, "Vendor binary should be linked with API library from LLNDK variant", true, hasDirectDependency(t, ctx, binbaz, libbarApiImportLlndk))
-	android.AssertBoolEquals(t, "Vendor binary should not be linked with API library from NDK variant", false, hasDirectDependency(t, ctx, binbaz, libbarApiImportv29))
-
-}
diff --git a/cc/sabi.go b/cc/sabi.go
index ef43c8d..cd9bf63 100644
--- a/cc/sabi.go
+++ b/cc/sabi.go
@@ -29,6 +29,7 @@
 type lsdumpTag string
 
 const (
+	apexLsdumpTag     lsdumpTag = "APEX"
 	llndkLsdumpTag    lsdumpTag = "LLNDK"
 	ndkLsdumpTag      lsdumpTag = "NDK"
 	platformLsdumpTag lsdumpTag = "PLATFORM"
@@ -39,6 +40,8 @@
 // Return the prebuilt ABI dump directory for a tag; an empty string for an opt-in dump.
 func (tag *lsdumpTag) dirName() string {
 	switch *tag {
+	case apexLsdumpTag:
+		return "platform"
 	case ndkLsdumpTag:
 		return "ndk"
 	case llndkLsdumpTag:
@@ -134,11 +137,13 @@
 		if m.isImplementationForLLNDKPublic() {
 			result = append(result, llndkLsdumpTag)
 		}
-		// Return NDK if the library is both NDK and APEX.
-		// TODO(b/309880485): Split NDK and APEX ABI.
 		if m.IsNdk(ctx.Config()) {
 			result = append(result, ndkLsdumpTag)
-		} else if m.library.hasStubsVariants() || headerAbiChecker.enabled() {
+		}
+		// APEX and opt-in platform dumps are placed in the same directory.
+		if m.library.hasStubsVariants() {
+			result = append(result, apexLsdumpTag)
+		} else if headerAbiChecker.enabled() {
 			result = append(result, platformLsdumpTag)
 		}
 	} else if headerAbiChecker.enabled() {
diff --git a/cc/sdk.go b/cc/sdk.go
index 736a958..ce0fdc2 100644
--- a/cc/sdk.go
+++ b/cc/sdk.go
@@ -59,35 +59,6 @@
 				modules[1].(*Module).Properties.PreventInstall = true
 			}
 			ctx.AliasVariation("")
-		} else if isCcModule && ccModule.isImportedApiLibrary() {
-			apiLibrary, _ := ccModule.linker.(*apiLibraryDecorator)
-			if apiLibrary.hasNDKStubs() && ccModule.canUseSdk() {
-				variations := []string{"sdk"}
-				if apiLibrary.hasApexStubs() {
-					variations = append(variations, "")
-				}
-				// Handle cc_api_library module with NDK stubs and variants only which can use SDK
-				modules := ctx.CreateVariations(variations...)
-				// Mark the SDK variant.
-				modules[0].(*Module).Properties.IsSdkVariant = true
-				if ctx.Config().UnbundledBuildApps() {
-					if apiLibrary.hasApexStubs() {
-						// For an unbundled apps build, hide the platform variant from Make.
-						modules[1].(*Module).Properties.HideFromMake = true
-					}
-					modules[1].(*Module).Properties.PreventInstall = true
-				} else {
-					// For a platform build, mark the SDK variant so that it gets a ".sdk" suffix when
-					// exposed to Make.
-					modules[0].(*Module).Properties.SdkAndPlatformVariantVisibleToMake = true
-					// SDK variant is not supposed to be installed
-					modules[0].(*Module).Properties.PreventInstall = true
-				}
-			} else {
-				ccModule.Properties.Sdk_version = nil
-				ctx.CreateVariations("")
-				ctx.AliasVariation("")
-			}
 		} else {
 			if isCcModule {
 				// Clear the sdk_version property for modules that don't have an SDK variant so
diff --git a/cmd/release_config/build_flag/Android.bp b/cmd/release_config/build_flag/Android.bp
new file mode 100644
index 0000000..0f10c91
--- /dev/null
+++ b/cmd/release_config/build_flag/Android.bp
@@ -0,0 +1,32 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+blueprint_go_binary {
+    name: "build-flag",
+    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",
+    pkgPath: "android/soong/cmd/release_config/build_flag",
+    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/main.go b/cmd/release_config/build_flag/main.go
new file mode 100644
index 0000000..6f909af
--- /dev/null
+++ b/cmd/release_config/build_flag/main.go
@@ -0,0 +1,229 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+
+	rc_lib "android/soong/cmd/release_config/release_config_lib"
+	rc_proto "android/soong/cmd/release_config/release_config_proto"
+
+	"google.golang.org/protobuf/proto"
+)
+
+type Flags struct {
+	// The path to the top of the workspace.  Default: ".".
+	top string
+
+	// Pathlist of release config map textproto files.
+	// If not specified, then the value is (if present):
+	// - build/release/release_config_map.textproto
+	// - vendor/google_shared/build/release/release_config_map.textproto
+	// - vendor/google/release/release_config_map.textproto
+	//
+	// Additionally, any maps specified in the environment variable
+	// `PRODUCT_RELEASE_CONFIG_MAPS` are used.
+	maps rc_lib.StringList
+
+	// Output directory (relative to `top`).
+	outDir string
+
+	// Which $TARGET_RELEASE(s) should we use.  Some commands will only
+	// accept one value, others also accept `--release --all`.
+	targetReleases rc_lib.StringList
+
+	// Disable warning messages
+	quiet bool
+}
+
+type CommandFunc func(*rc_lib.ReleaseConfigs, Flags, string, []string) error
+
+var commandMap map[string]CommandFunc = map[string]CommandFunc{
+	"get":   GetCommand,
+	"set":   SetCommand,
+	"trace": GetCommand, // Also handled by GetCommand
+}
+
+// Find the top of the release config contribution directory.
+// Returns the parent of the flag_declarations and flag_values directories.
+func GetMapDir(path string) (string, error) {
+	for p := path; p != "."; p = filepath.Dir(p) {
+		switch filepath.Base(p) {
+		case "flag_declarations":
+			return filepath.Dir(p), nil
+		case "flag_values":
+			return filepath.Dir(p), nil
+		}
+	}
+	return "", fmt.Errorf("Could not determine directory from %s", path)
+}
+
+func MarshalFlagValue(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.Value), nil
+}
+
+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.Parse(commonFlags.targetReleases)
+	var ret []*rc_lib.ReleaseConfig
+	if all {
+		for _, config := range configs.ReleaseConfigs {
+			ret = append(ret, config)
+		}
+		return ret, nil
+	}
+	for _, arg := range relFlags.Args() {
+		config, err := configs.GetReleaseConfig(arg)
+		if err != nil {
+			return nil, err
+		}
+		ret = append(ret, config)
+	}
+	return ret, nil
+}
+
+func GetCommand(configs *rc_lib.ReleaseConfigs, commonFlags Flags, cmd string, args []string) error {
+	isTrace := cmd == "trace"
+	var all bool
+	getFlags := flag.NewFlagSet("set", flag.ExitOnError)
+	getFlags.BoolVar(&all, "all", false, "Display all flags")
+	getFlags.Parse(args)
+	args = getFlags.Args()
+
+	releaseConfigList, err := GetReleaseArgs(configs, commonFlags)
+	if err != nil {
+		return err
+	}
+	if isTrace && len(releaseConfigList) > 1 {
+		return fmt.Errorf("trace command only allows one --release argument.  Got: %s", strings.Join(commonFlags.targetReleases, " "))
+	}
+
+	if all {
+		args = []string{}
+		for _, fa := range configs.FlagArtifacts {
+			args = append(args, *fa.FlagDeclaration.Name)
+		}
+	}
+
+	showName := len(releaseConfigList) > 1 || len(args) > 1
+	for _, config := range releaseConfigList {
+		var configName string
+		if len(releaseConfigList) > 1 {
+			configName = fmt.Sprintf("%s.", config.Name)
+		}
+		for _, arg := range args {
+			val, err := MarshalFlagValue(config, arg)
+			if err != nil {
+				return err
+			}
+			if showName {
+				fmt.Printf("%s%s=%s\n", configName, arg, val)
+			} else {
+				fmt.Printf("%s\n", val)
+			}
+			if isTrace {
+				for _, trace := range config.FlagArtifacts[arg].Traces {
+					fmt.Printf("  => \"%s\" in %s\n", rc_lib.MarshalValue(trace.Value), *trace.Source)
+				}
+			}
+		}
+	}
+	return nil
+}
+
+func SetCommand(configs *rc_lib.ReleaseConfigs, commonFlags Flags, cmd string, args []string) error {
+	var valueDir string
+	if len(commonFlags.targetReleases) > 1 {
+		return fmt.Errorf("set command only allows one --release argument.  Got: %s", strings.Join(commonFlags.targetReleases, " "))
+	}
+	targetRelease := commonFlags.targetReleases[0]
+
+	setFlags := flag.NewFlagSet("set", flag.ExitOnError)
+	setFlags.StringVar(&valueDir, "dir", "", "Directory in which to place the value")
+	setFlags.Parse(args)
+	setArgs := setFlags.Args()
+	if len(setArgs) != 2 {
+		return fmt.Errorf("set command expected flag and value, got: %s", strings.Join(setArgs, " "))
+	}
+	name := setArgs[0]
+	value := setArgs[1]
+	release, err := configs.GetReleaseConfig(targetRelease)
+	targetRelease = release.Name
+	if err != nil {
+		return err
+	}
+	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)
+		if err != nil {
+			return err
+		}
+		valueDir = mapDir
+	}
+
+	flagValue := &rc_proto.FlagValue{
+		Name:  proto.String(name),
+		Value: rc_lib.UnmarshalValue(value),
+	}
+	flagPath := filepath.Join(valueDir, "flag_values", targetRelease, fmt.Sprintf("%s.textproto", name))
+	return rc_lib.WriteMessage(flagPath, flagValue)
+}
+
+func main() {
+	var err error
+	var commonFlags Flags
+	var configs *rc_lib.ReleaseConfigs
+
+	outEnv := os.Getenv("OUT_DIR")
+	if outEnv == "" {
+		outEnv = "out"
+	}
+	// Handle the common arguments
+	flag.StringVar(&commonFlags.top, "top", ".", "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.Var(&commonFlags.targetReleases, "release", "TARGET_RELEASE for this build")
+	flag.Parse()
+
+	if commonFlags.quiet {
+		rc_lib.DisableWarnings()
+	}
+
+	if len(commonFlags.targetReleases) == 0 {
+		commonFlags.targetReleases = rc_lib.StringList{"trunk_staging"}
+	}
+
+	if err = os.Chdir(commonFlags.top); err != nil {
+		panic(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"
+	}
+	configs, err = rc_lib.ReadReleaseConfigMaps(commonFlags.maps, relName)
+	if err != nil {
+		panic(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)
+		}
+	}
+}
diff --git a/cmd/release_config/crunch_flags/Android.bp b/cmd/release_config/crunch_flags/Android.bp
new file mode 100644
index 0000000..89c9591
--- /dev/null
+++ b/cmd/release_config/crunch_flags/Android.bp
@@ -0,0 +1,32 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+blueprint_go_binary {
+    name: "crunch-flags",
+    deps: [
+        "golang-protobuf-encoding-prototext",
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+        "soong-cmd-release_config-lib",
+        "soong-cmd-release_config-proto",
+    ],
+    srcs: [
+        "main.go",
+    ],
+}
+
+bootstrap_go_package {
+    name: "soong-cmd-release_config-crunch_flags",
+    pkgPath: "android/soong/cmd/release_config/crunch_flags",
+    deps: [
+        "golang-protobuf-encoding-prototext",
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+        "soong-cmd-release_config-lib",
+        "soong-cmd-release_config-proto",
+    ],
+    srcs: [
+        "main.go",
+    ],
+}
diff --git a/cmd/release_config/crunch_flags/main.go b/cmd/release_config/crunch_flags/main.go
new file mode 100644
index 0000000..69abba2
--- /dev/null
+++ b/cmd/release_config/crunch_flags/main.go
@@ -0,0 +1,362 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"io/fs"
+	"os"
+	"path/filepath"
+	"regexp"
+	"strings"
+
+	rc_lib "android/soong/cmd/release_config/release_config_lib"
+	rc_proto "android/soong/cmd/release_config/release_config_proto"
+	"google.golang.org/protobuf/encoding/prototext"
+	"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 defaultFlagNamespace string = "android_UNKNOWN"
+
+func RenameNext(name string) string {
+	if name == "next" {
+		return "ap3a"
+	}
+	return name
+}
+
+func WriteFile(path string, message proto.Message) error {
+	data, err := prototext.MarshalOptions{Multiline: true}.Marshal(message)
+	if err != nil {
+		return err
+	}
+
+	err = os.MkdirAll(filepath.Dir(path), 0775)
+	if err != nil {
+		return err
+	}
+	return os.WriteFile(path, data, 0644)
+}
+
+func WalkValueFiles(dir string, Func fs.WalkDirFunc) error {
+	valPath := filepath.Join(dir, "build_config")
+	if _, err := os.Stat(valPath); err != nil {
+		fmt.Printf("%s not found, ignoring.\n", valPath)
+		return nil
+	}
+
+	return filepath.WalkDir(valPath, func(path string, d fs.DirEntry, err error) error {
+		if err != nil {
+			return err
+		}
+		if strings.HasSuffix(d.Name(), ".scl") && d.Type().IsRegular() {
+			return Func(path, d, err)
+		}
+		return nil
+	})
+}
+
+func ProcessBuildFlags(dir string, namespaceMap map[string]string) error {
+	var rootAconfigModule string
+
+	path := filepath.Join(dir, "build_flags.scl")
+	if _, err := os.Stat(path); err != nil {
+		fmt.Printf("%s not found, ignoring.\n", path)
+		return nil
+	} else {
+		fmt.Printf("Processing %s\n", path)
+	}
+	commentRegexp, err := regexp.Compile("^[[:space:]]*#(?<comment>.+)")
+	if err != nil {
+		return err
+	}
+	declRegexp, err := regexp.Compile("^[[:space:]]*flag.\"(?<name>[A-Z_0-9]+)\",[[:space:]]*(?<container>[_A-Z]*),[[:space:]]*(?<value>(\"[^\"]*\"|[^\",)]*))")
+	if err != nil {
+		return err
+	}
+	declIn, err := os.ReadFile(path)
+	if err != nil {
+		return err
+	}
+	lines := strings.Split(string(declIn), "\n")
+	var description string
+	for _, line := range lines {
+		if comment := commentRegexp.FindStringSubmatch(commentRegexp.FindString(line)); comment != nil {
+			// Description is the text from any contiguous series of lines before a `flag()` call.
+			descLine := strings.TrimSpace(comment[commentRegexp.SubexpIndex("comment")])
+			if !strings.HasPrefix(descLine, "keep-sorted") {
+				description += fmt.Sprintf(" %s", descLine)
+			}
+			continue
+		}
+		matches := declRegexp.FindStringSubmatch(declRegexp.FindString(line))
+		if matches == nil {
+			// The line is neither a comment nor a `flag()` call.
+			// Discard any description we have gathered and process the next line.
+			description = ""
+			continue
+		}
+		declValue := matches[declRegexp.SubexpIndex("value")]
+		declName := matches[declRegexp.SubexpIndex("name")]
+		container := rc_proto.Container(rc_proto.Container_value[matches[declRegexp.SubexpIndex("container")]])
+		description = strings.TrimSpace(description)
+		var namespace string
+		var ok bool
+		if namespace, ok = namespaceMap[declName]; !ok {
+			namespace = defaultFlagNamespace
+		}
+		flagDeclaration := &rc_proto.FlagDeclaration{
+			Name:        proto.String(declName),
+			Namespace:   proto.String(namespace),
+			Description: proto.String(description),
+			Container:   &container,
+		}
+		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]
+			continue
+		case strings.HasPrefix(declValue, "\""):
+			// String values mean that the flag workflow is (most likely) either MANUAL or PREBUILT.
+			declValue = declValue[1 : len(declValue)-1]
+			flagDeclaration.Value = &rc_proto.Value{Val: &rc_proto.Value_StringValue{declValue}}
+			for _, prefix := range manualFlagNamePrefixes {
+				if strings.HasPrefix(declName, prefix) {
+					workflow = rc_proto.Workflow(rc_proto.Workflow_MANUAL)
+					break
+				}
+			}
+		case declValue == "False" || declValue == "True":
+			// Boolean values are LAUNCH flags.
+			flagDeclaration.Value = &rc_proto.Value{Val: &rc_proto.Value_BoolValue{declValue == "True"}}
+			workflow = rc_proto.Workflow(rc_proto.Workflow_LAUNCH)
+		case declValue == "None":
+			// Use PREBUILT workflow with no initial value.
+		default:
+			fmt.Printf("%s: Unexpected value %s=%s\n", path, declName, declValue)
+		}
+		flagDeclaration.Workflow = &workflow
+		if flagDeclaration != nil {
+			declPath := filepath.Join(dir, "flag_declarations", fmt.Sprintf("%s.textproto", declName))
+			err := WriteFile(declPath, flagDeclaration)
+			if err != nil {
+				return err
+			}
+		}
+	}
+	if rootAconfigModule != "" {
+		rootProto := &rc_proto.ReleaseConfig{
+			Name:             proto.String("root"),
+			AconfigValueSets: []string{rootAconfigModule},
+		}
+		return WriteFile(filepath.Join(dir, "release_configs", "root.textproto"), rootProto)
+	}
+	return nil
+}
+
+func ProcessBuildConfigs(dir, name string, paths []string, releaseProto *rc_proto.ReleaseConfig) error {
+	valRegexp, err := regexp.Compile("[[:space:]]+value.\"(?<name>[A-Z_0-9]+)\",[[:space:]]*(?<value>[^,)]*)")
+	if err != nil {
+		return err
+	}
+	for _, path := range paths {
+		fmt.Printf("Processing %s\n", path)
+		valIn, err := os.ReadFile(path)
+		if err != nil {
+			fmt.Printf("%s: error: %v\n", path, err)
+			return err
+		}
+		vals := valRegexp.FindAllString(string(valIn), -1)
+		for _, val := range vals {
+			matches := valRegexp.FindStringSubmatch(val)
+			valValue := matches[valRegexp.SubexpIndex("value")]
+			valName := matches[valRegexp.SubexpIndex("name")]
+			flagValue := &rc_proto.FlagValue{
+				Name: proto.String(valName),
+			}
+			switch {
+			case valName == "RELEASE_ACONFIG_VALUE_SETS":
+				flagValue = nil
+				if releaseProto.AconfigValueSets == nil {
+					releaseProto.AconfigValueSets = []string{}
+				}
+				releaseProto.AconfigValueSets = append(releaseProto.AconfigValueSets, valValue[1:len(valValue)-1])
+			case strings.HasPrefix(valValue, "\""):
+				valValue = valValue[1 : len(valValue)-1]
+				flagValue.Value = &rc_proto.Value{Val: &rc_proto.Value_StringValue{valValue}}
+			case valValue == "None":
+				// nothing to do here.
+			case valValue == "True":
+				flagValue.Value = &rc_proto.Value{Val: &rc_proto.Value_BoolValue{true}}
+			case valValue == "False":
+				flagValue.Value = &rc_proto.Value{Val: &rc_proto.Value_BoolValue{false}}
+			default:
+				fmt.Printf("%s: Unexpected value %s=%s\n", path, valName, valValue)
+			}
+			if flagValue != nil {
+				valPath := filepath.Join(dir, "flag_values", RenameNext(name), fmt.Sprintf("%s.textproto", valName))
+				err := WriteFile(valPath, flagValue)
+				if err != nil {
+					return err
+				}
+			}
+		}
+	}
+	return err
+}
+
+func ProcessReleaseConfigMap(dir string, descriptionMap map[string]string) error {
+	path := filepath.Join(dir, "release_config_map.mk")
+	if _, err := os.Stat(path); err != nil {
+		fmt.Printf("%s not found, ignoring.\n", path)
+		return nil
+	} else {
+		fmt.Printf("Processing %s\n", path)
+	}
+	configRegexp, err := regexp.Compile("^..call[[:space:]]+declare-release-config,[[:space:]]+(?<name>[_a-z0-9A-Z]+),[[:space:]]+(?<files>[^,]*)(,[[:space:]]*(?<inherits>.*)|[[:space:]]*)[)]$")
+	if err != nil {
+		return err
+	}
+	aliasRegexp, err := regexp.Compile("^..call[[:space:]]+alias-release-config,[[:space:]]+(?<name>[_a-z0-9A-Z]+),[[:space:]]+(?<target>[_a-z0-9A-Z]+)")
+	if err != nil {
+		return err
+	}
+
+	mapIn, err := os.ReadFile(path)
+	if err != nil {
+		return err
+	}
+	cleanDir := strings.TrimLeft(dir, "../")
+	var defaultContainer rc_proto.Container
+	switch {
+	case strings.HasPrefix(cleanDir, "build/") || cleanDir == "vendor/google_shared/build":
+		defaultContainer = rc_proto.Container(rc_proto.Container_ALL)
+	case cleanDir == "vendor/google/release":
+		defaultContainer = rc_proto.Container(rc_proto.Container_ALL)
+	default:
+		defaultContainer = rc_proto.Container(rc_proto.Container_VENDOR)
+	}
+	releaseConfigMap := &rc_proto.ReleaseConfigMap{DefaultContainer: &defaultContainer}
+	// If we find a description for the directory, include it.
+	if description, ok := descriptionMap[cleanDir]; ok {
+		releaseConfigMap.Description = proto.String(description)
+	}
+	lines := strings.Split(string(mapIn), "\n")
+	for _, line := range lines {
+		alias := aliasRegexp.FindStringSubmatch(aliasRegexp.FindString(line))
+		if alias != nil {
+			fmt.Printf("processing alias %s\n", line)
+			name := alias[aliasRegexp.SubexpIndex("name")]
+			target := alias[aliasRegexp.SubexpIndex("target")]
+			if target == "next" {
+				if RenameNext(target) != name {
+					return fmt.Errorf("Unexpected name for next (%s)", RenameNext(target))
+				}
+				target, name = name, target
+			}
+			releaseConfigMap.Aliases = append(releaseConfigMap.Aliases,
+				&rc_proto.ReleaseAlias{
+					Name:   proto.String(name),
+					Target: proto.String(target),
+				})
+		}
+		config := configRegexp.FindStringSubmatch(configRegexp.FindString(line))
+		if config == nil {
+			continue
+		}
+		name := config[configRegexp.SubexpIndex("name")]
+		releaseConfig := &rc_proto.ReleaseConfig{
+			Name: proto.String(RenameNext(name)),
+		}
+		configFiles := config[configRegexp.SubexpIndex("files")]
+		files := strings.Split(strings.ReplaceAll(configFiles, "$(local_dir)", dir+"/"), " ")
+		configInherits := config[configRegexp.SubexpIndex("inherits")]
+		if len(configInherits) > 0 {
+			releaseConfig.Inherits = strings.Split(configInherits, " ")
+		}
+		err := ProcessBuildConfigs(dir, name, files, releaseConfig)
+		if err != nil {
+			return err
+		}
+
+		releasePath := filepath.Join(dir, "release_configs", fmt.Sprintf("%s.textproto", RenameNext(name)))
+		err = WriteFile(releasePath, releaseConfig)
+		if err != nil {
+			return err
+		}
+	}
+	return WriteFile(filepath.Join(dir, "release_config_map.textproto"), releaseConfigMap)
+}
+
+func main() {
+	var err error
+	var top string
+	var dirs rc_lib.StringList
+	var namespacesFile string
+	var descriptionsFile string
+
+	flag.StringVar(&top, "top", ".", "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.Parse()
+
+	if err = os.Chdir(top); err != nil {
+		panic(err)
+	}
+	if len(dirs) == 0 {
+		dirs = rc_lib.StringList{"build/release", "vendor/google_shared/build/release", "vendor/google/release"}
+	}
+
+	namespaceMap := make(map[string]string)
+	if namespacesFile != "" {
+		data, err := os.ReadFile(namespacesFile)
+		if err != nil {
+			panic(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))
+			}
+			namespaceMap[fields[0]] = fields[1]
+		}
+
+	}
+
+	descriptionMap := make(map[string]string)
+	descriptionMap["build/release"] = "Published open-source flags and declarations"
+	if descriptionsFile != "" {
+		data, err := os.ReadFile(descriptionsFile)
+		if err != nil {
+			panic(err)
+		}
+		for _, line := range strings.Split(string(data), "\n") {
+			if strings.TrimSpace(line) != "" {
+				fields := strings.SplitN(line, " ", 2)
+				descriptionMap[fields[0]] = fields[1]
+			}
+		}
+
+	}
+
+	for _, dir := range dirs {
+		err = ProcessBuildFlags(dir, namespaceMap)
+		if err != nil {
+			panic(err)
+		}
+
+		err = ProcessReleaseConfigMap(dir, descriptionMap)
+		if err != nil {
+			panic(err)
+		}
+	}
+}
diff --git a/cmd/release_config/release_config/main.go b/cmd/release_config/release_config/main.go
index 076abfa..a43fdcc 100644
--- a/cmd/release_config/release_config/main.go
+++ b/cmd/release_config/release_config/main.go
@@ -16,25 +16,44 @@
 
 import (
 	"flag"
+	"fmt"
 	"os"
+	"path/filepath"
 
 	rc_lib "android/soong/cmd/release_config/release_config_lib"
 )
 
 func main() {
 	var top string
+	var quiet bool
 	var releaseConfigMapPaths rc_lib.StringList
 	var targetRelease string
 	var outputDir string
 	var err error
 	var configs *rc_lib.ReleaseConfigs
+	var json, pb, textproto bool
+	var product string
+
+	defaultRelease := os.Getenv("TARGET_RELEASE")
+	if defaultRelease == "" {
+		defaultRelease = "trunk_staging"
+	}
 
 	flag.StringVar(&top, "top", ".", "path to top of workspace")
+	flag.StringVar(&product, "product", os.Getenv("TARGET_PRODUCT"), "TARGET_PRODUCT for the build")
+	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", "trunk_staging", "TARGET_RELEASE for this build")
+	flag.StringVar(&targetRelease, "release", defaultRelease, "TARGET_RELEASE for this build")
 	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.Parse()
 
+	if quiet {
+		rc_lib.DisableWarnings()
+	}
+
 	if err = os.Chdir(top); err != nil {
 		panic(err)
 	}
@@ -42,16 +61,36 @@
 	if err != nil {
 		panic(err)
 	}
+	config, err := configs.GetReleaseConfig(targetRelease)
+	if err != nil {
+		panic(err)
+	}
+	releaseName := config.Name
 	err = os.MkdirAll(outputDir, 0775)
 	if err != nil {
 		panic(err)
 	}
-	err = configs.DumpMakefile(outputDir, targetRelease)
+	makefilePath := filepath.Join(outputDir, fmt.Sprintf("release_config-%s-%s.mk", product, releaseName))
+	err = configs.WriteMakefile(makefilePath, targetRelease)
 	if err != nil {
 		panic(err)
 	}
-	err = configs.DumpArtifact(outputDir)
-	if err != nil {
-		panic(err)
+	if json {
+		err = configs.WriteArtifact(outputDir, product, "json")
+		if err != nil {
+			panic(err)
+		}
+	}
+	if pb {
+		err = configs.WriteArtifact(outputDir, product, "pb")
+		if err != nil {
+			panic(err)
+		}
+	}
+	if textproto {
+		err = configs.WriteArtifact(outputDir, product, "textproto")
+		if 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 601194c..0c67e11 100644
--- a/cmd/release_config/release_config_lib/Android.bp
+++ b/cmd/release_config/release_config_lib/Android.bp
@@ -18,7 +18,7 @@
 
 bootstrap_go_package {
     name: "soong-cmd-release_config-lib",
-    pkgPath: "android/soong/release_config/release_config_lib",
+    pkgPath: "android/soong/cmd/release_config/release_config_lib",
     deps: [
         "golang-protobuf-encoding-prototext",
         "golang-protobuf-reflect-protoreflect",
diff --git a/cmd/release_config/release_config_lib/flag_artifact.go b/cmd/release_config/release_config_lib/flag_artifact.go
index 51673a5..d6a629b 100644
--- a/cmd/release_config/release_config_lib/flag_artifact.go
+++ b/cmd/release_config/release_config_lib/flag_artifact.go
@@ -17,29 +17,41 @@
 import (
 	"fmt"
 
-	"android/soong/cmd/release_config/release_config_proto"
+	rc_proto "android/soong/cmd/release_config/release_config_proto"
 
 	"google.golang.org/protobuf/proto"
 )
 
+// A flag artifact, with its final value and declaration/override history.
 type FlagArtifact struct {
-	FlagDeclaration *release_config_proto.FlagDeclaration
+	// The flag_declaration message.
+	FlagDeclaration *rc_proto.FlagDeclaration
 
 	// The index of the config directory where this flag was declared.
 	// Flag values cannot be set in a location with a lower index.
 	DeclarationIndex int
 
-	Traces []*release_config_proto.Tracepoint
+	// A history of value assignments and overrides.
+	Traces []*rc_proto.Tracepoint
 
-	// Assigned value
-	Value *release_config_proto.Value
+	// The value of the flag.
+	Value *rc_proto.Value
+
+	// This flag is redacted.  Set by UpdateValue when the FlagValue proto
+	// says to redact it.
+	Redacted bool
 }
 
 // Key is flag name.
 type FlagArtifacts map[string]*FlagArtifact
 
+// Create a clone of the flag artifact.
+//
+// Returns:
+//
+//	*FlagArtifact: the copy of the artifact.
 func (src *FlagArtifact) Clone() *FlagArtifact {
-	value := &release_config_proto.Value{}
+	value := &rc_proto.Value{}
 	proto.Merge(value, src.Value)
 	return &FlagArtifact{
 		FlagDeclaration: src.FlagDeclaration,
@@ -48,6 +60,11 @@
 	}
 }
 
+// Clone FlagArtifacts.
+//
+// Returns:
+//
+//	FlagArtifacts: a copy of the source FlagArtifacts.
 func (src FlagArtifacts) Clone() (dst FlagArtifacts) {
 	if dst == nil {
 		dst = make(FlagArtifacts)
@@ -58,30 +75,55 @@
 	return
 }
 
+// Update the value of a flag.
+//
+// This appends to flagArtifact.Traces, and updates flagArtifact.Value.
+//
+// Args:
+//
+//	flagValue FlagValue: the value to assign
+//
+// Returns:
+//
+//	error: any error encountered
 func (fa *FlagArtifact) UpdateValue(flagValue FlagValue) error {
 	name := *flagValue.proto.Name
-	fa.Traces = append(fa.Traces, &release_config_proto.Tracepoint{Source: proto.String(flagValue.path), Value: flagValue.proto.Value})
+	fa.Traces = append(fa.Traces, &rc_proto.Tracepoint{Source: proto.String(flagValue.path), Value: flagValue.proto.Value})
+	if flagValue.proto.GetRedacted() {
+		fa.Redacted = true
+		fmt.Printf("Redacting flag %s in %s\n", name, flagValue.path)
+		return nil
+	}
 	if fa.Value.GetObsolete() {
 		return fmt.Errorf("Attempting to set obsolete flag %s. Trace=%v", name, fa.Traces)
 	}
+	var newValue *rc_proto.Value
 	switch val := flagValue.proto.Value.Val.(type) {
-	case *release_config_proto.Value_StringValue:
-		fa.Value = &release_config_proto.Value{Val: &release_config_proto.Value_StringValue{val.StringValue}}
-	case *release_config_proto.Value_BoolValue:
-		fa.Value = &release_config_proto.Value{Val: &release_config_proto.Value_BoolValue{val.BoolValue}}
-	case *release_config_proto.Value_Obsolete:
+	case *rc_proto.Value_StringValue:
+		newValue = &rc_proto.Value{Val: &rc_proto.Value_StringValue{val.StringValue}}
+	case *rc_proto.Value_BoolValue:
+		newValue = &rc_proto.Value{Val: &rc_proto.Value_BoolValue{val.BoolValue}}
+	case *rc_proto.Value_Obsolete:
 		if !val.Obsolete {
 			return fmt.Errorf("%s: Cannot set obsolete=false.  Trace=%v", name, fa.Traces)
 		}
-		fa.Value = &release_config_proto.Value{Val: &release_config_proto.Value_Obsolete{true}}
+		newValue = &rc_proto.Value{Val: &rc_proto.Value_Obsolete{true}}
 	default:
 		return fmt.Errorf("Invalid type for flag_value: %T.  Trace=%v", val, fa.Traces)
 	}
+	if proto.Equal(newValue, fa.Value) {
+		warnf("%s: redundant override (set in %s)\n", flagValue.path, *fa.Traces[len(fa.Traces)-2].Source)
+	}
+	fa.Value = newValue
 	return nil
 }
 
-func (fa *FlagArtifact) Marshal() (*release_config_proto.FlagArtifact, error) {
-	return &release_config_proto.FlagArtifact{
+// Marshal the FlagArtifact into a flag_artifact message.
+func (fa *FlagArtifact) Marshal() (*rc_proto.FlagArtifact, error) {
+	if fa.Redacted {
+		return nil, nil
+	}
+	return &rc_proto.FlagArtifact{
 		FlagDeclaration: fa.FlagDeclaration,
 		Value:           fa.Value,
 		Traces:          fa.Traces,
diff --git a/cmd/release_config/release_config_lib/flag_declaration.go b/cmd/release_config/release_config_lib/flag_declaration.go
index d5cc418..97d4d4c 100644
--- a/cmd/release_config/release_config_lib/flag_declaration.go
+++ b/cmd/release_config/release_config_lib/flag_declaration.go
@@ -15,13 +15,13 @@
 package release_config_lib
 
 import (
-	"android/soong/cmd/release_config/release_config_proto"
+	rc_proto "android/soong/cmd/release_config/release_config_proto"
 )
 
-func FlagDeclarationFactory(protoPath string) (fd *release_config_proto.FlagDeclaration) {
-	fd = &release_config_proto.FlagDeclaration{}
+func FlagDeclarationFactory(protoPath string) (fd *rc_proto.FlagDeclaration) {
+	fd = &rc_proto.FlagDeclaration{}
 	if protoPath != "" {
-		LoadTextproto(protoPath, fd)
+		LoadMessage(protoPath, fd)
 	}
 	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 138e8f8..e155e77 100644
--- a/cmd/release_config/release_config_lib/flag_value.go
+++ b/cmd/release_config/release_config_lib/flag_value.go
@@ -15,7 +15,9 @@
 package release_config_lib
 
 import (
-	"android/soong/cmd/release_config/release_config_proto"
+	"strings"
+
+	rc_proto "android/soong/cmd/release_config/release_config_proto"
 )
 
 type FlagValue struct {
@@ -23,31 +25,46 @@
 	path string
 
 	// Protobuf
-	proto release_config_proto.FlagValue
+	proto rc_proto.FlagValue
 }
 
 func FlagValueFactory(protoPath string) (fv *FlagValue) {
 	fv = &FlagValue{path: protoPath}
 	if protoPath != "" {
-		LoadTextproto(protoPath, &fv.proto)
+		LoadMessage(protoPath, &fv.proto)
 	}
 	return fv
 }
 
-func MarshalValue(value *release_config_proto.Value) string {
+func UnmarshalValue(str string) *rc_proto.Value {
+	ret := &rc_proto.Value{}
+	switch v := strings.ToLower(str); v {
+	case "true":
+		ret = &rc_proto.Value{Val: &rc_proto.Value_BoolValue{true}}
+	case "false":
+		ret = &rc_proto.Value{Val: &rc_proto.Value_BoolValue{false}}
+	case "##obsolete":
+		ret = &rc_proto.Value{Val: &rc_proto.Value_Obsolete{true}}
+	default:
+		ret = &rc_proto.Value{Val: &rc_proto.Value_StringValue{str}}
+	}
+	return ret
+}
+
+func MarshalValue(value *rc_proto.Value) string {
 	switch val := value.Val.(type) {
-	case *release_config_proto.Value_UnspecifiedValue:
+	case *rc_proto.Value_UnspecifiedValue:
 		// Value was never set.
 		return ""
-	case *release_config_proto.Value_StringValue:
+	case *rc_proto.Value_StringValue:
 		return val.StringValue
-	case *release_config_proto.Value_BoolValue:
+	case *rc_proto.Value_BoolValue:
 		if val.BoolValue {
 			return "true"
 		}
 		// False ==> empty string
 		return ""
-	case *release_config_proto.Value_Obsolete:
+	case *rc_proto.Value_Obsolete:
 		return " #OBSOLETE"
 	default:
 		// Flagged as error elsewhere, so return empty string here.
diff --git a/cmd/release_config/release_config_lib/release_config.go b/cmd/release_config/release_config_lib/release_config.go
index 3110dae..b08b6a3 100644
--- a/cmd/release_config/release_config_lib/release_config.go
+++ b/cmd/release_config/release_config_lib/release_config.go
@@ -16,8 +16,9 @@
 
 import (
 	"fmt"
+	"strings"
 
-	"android/soong/cmd/release_config/release_config_proto"
+	rc_proto "android/soong/cmd/release_config/release_config_proto"
 
 	"google.golang.org/protobuf/proto"
 )
@@ -33,7 +34,7 @@
 	DeclarationIndex int
 
 	// Protobufs relevant to the config.
-	proto release_config_proto.ReleaseConfig
+	proto rc_proto.ReleaseConfig
 
 	FlagValues []*FlagValue
 }
@@ -61,7 +62,7 @@
 	FlagArtifacts FlagArtifacts
 
 	// Generated release config
-	ReleaseConfigArtifact *release_config_proto.ReleaseConfigArtifact
+	ReleaseConfigArtifact *rc_proto.ReleaseConfigArtifact
 
 	// We have begun compiling this release config.
 	compileInProgress bool
@@ -79,12 +80,18 @@
 		return fmt.Errorf("Loop detected for release config %s", config.Name)
 	}
 	config.compileInProgress = true
+	isRoot := config.Name == "root"
 
 	// Generate any configs we need to inherit.  This will detect loops in
 	// the config.
 	contributionsToApply := []*ReleaseConfigContribution{}
 	myInherits := []string{}
 	myInheritsSet := make(map[string]bool)
+	// If there is a "root" release config, it is the start of every inheritance chain.
+	_, err := configs.GetReleaseConfig("root")
+	if err == nil && !isRoot {
+		config.InheritNames = append([]string{"root"}, config.InheritNames...)
+	}
 	for _, inherit := range config.InheritNames {
 		if _, ok := myInheritsSet[inherit]; ok {
 			continue
@@ -101,42 +108,87 @@
 	contributionsToApply = append(contributionsToApply, config.Contributions...)
 
 	myAconfigValueSets := []string{}
+	myAconfigValueSetsMap := map[string]bool{}
 	myFlags := configs.FlagArtifacts.Clone()
+	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{""}},
+			},
+		},
+	}
+	myFlags["RELEASE_ACONFIG_VALUE_SETS"] = &releaseAconfigValueSets
 	myDirsMap := make(map[int]bool)
 	for _, contrib := range contributionsToApply {
-		myAconfigValueSets = append(myAconfigValueSets, contrib.proto.AconfigValueSets...)
+		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
+				}
+			}
+			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, " ")}},
+				})
+		}
 		myDirsMap[contrib.DeclarationIndex] = true
 		for _, value := range contrib.FlagValues {
-			fa, ok := myFlags[*value.proto.Name]
+			name := *value.proto.Name
+			fa, ok := myFlags[name]
 			if !ok {
-				return fmt.Errorf("Setting value for undefined flag %s in %s\n", *value.proto.Name, value.path)
+				return fmt.Errorf("Setting value for undefined flag %s in %s\n", name, value.path)
 			}
 			myDirsMap[fa.DeclarationIndex] = true
 			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", *value.proto.Name, value.path)
+				return fmt.Errorf("Setting value for flag %s not allowed in %s\n", name, value.path)
+			}
+			if isRoot && *fa.FlagDeclaration.Workflow != workflowManual {
+				// The "root" release config can only contain workflow: MANUAL flags.
+				return fmt.Errorf("Setting value for non-MANUAL flag %s is not allowed in %s", name, value.path)
 			}
 			if err := fa.UpdateValue(*value); err != nil {
 				return err
 			}
+			if fa.Redacted {
+				delete(myFlags, name)
+			}
 		}
 	}
+	releaseAconfigValueSets.Value = &rc_proto.Value{Val: &rc_proto.Value_StringValue{strings.Join(myAconfigValueSets, " ")}}
 
 	directories := []string{}
-	for idx, confDir := range configs.ConfigDirs {
+	for idx, confDir := range configs.configDirs {
 		if _, ok := myDirsMap[idx]; ok {
 			directories = append(directories, confDir)
 		}
 	}
 
 	config.FlagArtifacts = myFlags
-	config.ReleaseConfigArtifact = &release_config_proto.ReleaseConfigArtifact{
+	config.ReleaseConfigArtifact = &rc_proto.ReleaseConfigArtifact{
 		Name:       proto.String(config.Name),
 		OtherNames: config.OtherNames,
-		FlagArtifacts: func() []*release_config_proto.FlagArtifact {
-			ret := []*release_config_proto.FlagArtifact{}
+		FlagArtifacts: func() []*rc_proto.FlagArtifact {
+			ret := []*rc_proto.FlagArtifact{}
 			for _, flag := range myFlags {
-				ret = append(ret, &release_config_proto.FlagArtifact{
+				ret = append(ret, &rc_proto.FlagArtifact{
 					FlagDeclaration: flag.FlagDeclaration,
 					Traces:          flag.Traces,
 					Value:           flag.Value,
diff --git a/cmd/release_config/release_config_lib/release_configs.go b/cmd/release_config/release_config_lib/release_configs.go
index 74fdc00..aba8cd2 100644
--- a/cmd/release_config/release_config_lib/release_configs.go
+++ b/cmd/release_config/release_config_lib/release_configs.go
@@ -16,7 +16,6 @@
 
 import (
 	"cmp"
-	"encoding/json"
 	"fmt"
 	"io/fs"
 	"os"
@@ -24,9 +23,8 @@
 	"slices"
 	"strings"
 
-	"android/soong/cmd/release_config/release_config_proto"
+	rc_proto "android/soong/cmd/release_config/release_config_proto"
 
-	"google.golang.org/protobuf/encoding/prototext"
 	"google.golang.org/protobuf/proto"
 )
 
@@ -37,10 +35,13 @@
 	path string
 
 	// Data received
-	proto release_config_proto.ReleaseConfigMap
+	proto rc_proto.ReleaseConfigMap
 
+	// Map of name:contribution for release config contributions.
 	ReleaseConfigContributions map[string]*ReleaseConfigContribution
-	FlagDeclarations           []release_config_proto.FlagDeclaration
+
+	// Flags declared this directory's flag_declarations/*.textproto
+	FlagDeclarations []rc_proto.FlagDeclaration
 }
 
 type ReleaseConfigDirMap map[string]int
@@ -56,50 +57,51 @@
 	// Dictionary of flag_name:FlagDeclaration, with no overrides applied.
 	FlagArtifacts FlagArtifacts
 
+	// Generated release configs artifact
+	Artifact rc_proto.ReleaseConfigsArtifact
+
 	// Dictionary of name:ReleaseConfig
+	// Use `GetReleaseConfigs(name)` to get a release config.
 	ReleaseConfigs map[string]*ReleaseConfig
 
-	// Generated release configs
-	Artifact release_config_proto.ReleaseConfigsArtifact
+	// Map of directory to *ReleaseConfigMap
+	releaseConfigMapsMap map[string]*ReleaseConfigMap
 
 	// The list of config directories used.
-	ConfigDirs []string
+	configDirs []string
 
 	// A map from the config directory to its order in the list of config
 	// directories.
-	ConfigDirIndexes ReleaseConfigDirMap
+	configDirIndexes ReleaseConfigDirMap
 }
 
-func (configs *ReleaseConfigs) DumpArtifact(outDir string) error {
-	message := &configs.Artifact
-	basePath := filepath.Join(outDir, "all_release_configs")
-	writer := func(suffix string, marshal func() ([]byte, error)) error {
-		data, err := marshal()
-		if err != nil {
-			return err
-		}
-		return os.WriteFile(fmt.Sprintf("%s.%s", basePath, suffix), data, 0644)
-	}
-	err := writer("textproto", func() ([]byte, error) { return prototext.MarshalOptions{Multiline: true}.Marshal(message) })
-	if err != nil {
-		return err
-	}
-
-	err = writer("pb", func() ([]byte, error) { return proto.Marshal(message) })
-	if err != nil {
-		return err
-	}
-
-	return writer("json", func() ([]byte, error) { return json.MarshalIndent(message, "", "  ") })
+// Write the "all_release_configs" artifact.
+//
+// The file will be in "{outDir}/all_release_configs-{product}.{format}"
+//
+// Args:
+//
+//	outDir string: directory path. Will be created if not present.
+//	product string: TARGET_PRODUCT for the release_configs.
+//	format string: one of "json", "pb", or "textproto"
+//
+// Returns:
+//
+//	error: Any error encountered.
+func (configs *ReleaseConfigs) WriteArtifact(outDir, product, format string) error {
+	return WriteMessage(
+		filepath.Join(outDir, fmt.Sprintf("all_release_configs-%s.%s", product, format)),
+		&configs.Artifact)
 }
 
 func ReleaseConfigsFactory() (c *ReleaseConfigs) {
 	return &ReleaseConfigs{
-		Aliases:          make(map[string]*string),
-		FlagArtifacts:    make(map[string]*FlagArtifact),
-		ReleaseConfigs:   make(map[string]*ReleaseConfig),
-		ConfigDirs:       []string{},
-		ConfigDirIndexes: make(ReleaseConfigDirMap),
+		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),
 	}
 }
 
@@ -109,7 +111,7 @@
 		ReleaseConfigContributions: make(map[string]*ReleaseConfigContribution),
 	}
 	if protoPath != "" {
-		LoadTextproto(protoPath, &m.proto)
+		LoadMessage(protoPath, &m.proto)
 	}
 	return m
 }
@@ -145,7 +147,7 @@
 		}
 		// If the input didn't specify a value, create one (== UnspecifiedValue).
 		if flagDeclaration.Value == nil {
-			flagDeclaration.Value = &release_config_proto.Value{Val: &release_config_proto.Value_UnspecifiedValue{false}}
+			flagDeclaration.Value = &rc_proto.Value{Val: &rc_proto.Value_UnspecifiedValue{false}}
 		}
 		m.FlagDeclarations = append(m.FlagDeclarations, *flagDeclaration)
 		name := *flagDeclaration.Name
@@ -156,8 +158,11 @@
 		}
 		// Set the initial value in the flag artifact.
 		configs.FlagArtifacts[name].UpdateValue(
-			FlagValue{path: path, proto: release_config_proto.FlagValue{
+			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 nil
 	})
 	if err != nil {
@@ -166,7 +171,7 @@
 
 	err = WalkTextprotoFiles(dir, "release_configs", func(path string, d fs.DirEntry, err error) error {
 		releaseConfigContribution := &ReleaseConfigContribution{path: path, DeclarationIndex: ConfigDirIndex}
-		LoadTextproto(path, &releaseConfigContribution.proto)
+		LoadMessage(path, &releaseConfigContribution.proto)
 		name := *releaseConfigContribution.proto.Name
 		if fmt.Sprintf("%s.textproto", name) != filepath.Base(path) {
 			return fmt.Errorf("%s incorrectly declares release config %s", path, name)
@@ -197,6 +202,7 @@
 		return err
 	}
 	configs.ReleaseConfigMaps = append(configs.ReleaseConfigMaps, m)
+	configs.releaseConfigMapsMap[dir] = m
 	return nil
 }
 
@@ -212,16 +218,23 @@
 	return nil, fmt.Errorf("Missing config %s.  Trace=%v", name, trace)
 }
 
-func (configs *ReleaseConfigs) DumpMakefile(outDir, targetRelease string) error {
-	outFile := filepath.Join(outDir, "release_config.mk")
+// Write the makefile for this targetRelease.
+func (configs *ReleaseConfigs) WriteMakefile(outFile, targetRelease string) error {
 	makeVars := make(map[string]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 config.FlagArtifacts {
+	for k, _ := range myFlagArtifacts {
 		names = append(names, k)
 	}
 	slices.SortFunc(names, func(a, b string) int {
@@ -237,12 +250,12 @@
 	}
 
 	for _, name := range names {
-		flag := config.FlagArtifacts[name]
+		flag := myFlagArtifacts[name]
 		decl := flag.FlagDeclaration
 
-		// cName := strings.ToLower(release_config_proto.Container_name[decl.GetContainer()])
+		// cName := strings.ToLower(rc_proto.Container_name[decl.GetContainer()])
 		cName := strings.ToLower(decl.Container.String())
-		if cName == strings.ToLower(release_config_proto.Container_ALL.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)
@@ -277,7 +290,13 @@
 	//   _ALL_RELEASE_FLAGS.PARTITIONS.*
 	//   all _ALL_RELEASE_FLAGS.*, sorted by name
 	//   Final flag values, sorted by name.
-	data := fmt.Sprintf("_ALL_RELEASE_FLAGS :=$= %s\n", strings.Join(names, " "))
+	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], " "))
 	}
@@ -285,8 +304,6 @@
 		data += fmt.Sprintf("%s :=$= %s\n", vName, makeVars[vName])
 	}
 	data += "\n\n# Values for all build flags\n"
-	data += fmt.Sprintf("RELEASE_ACONFIG_VALUE_SETS :=$= %s\n",
-		strings.Join(config.ReleaseConfigArtifact.AconfigValueSets, " "))
 	for _, name := range names {
 		data += fmt.Sprintf("%s :=$= %s\n", name, makeVars[name])
 	}
@@ -321,10 +338,10 @@
 	if err != nil {
 		return err
 	}
-	configs.Artifact = release_config_proto.ReleaseConfigsArtifact{
+	configs.Artifact = rc_proto.ReleaseConfigsArtifact{
 		ReleaseConfig: releaseConfig.ReleaseConfigArtifact,
-		OtherReleaseConfigs: func() []*release_config_proto.ReleaseConfigArtifact {
-			orc := []*release_config_proto.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)
@@ -332,6 +349,13 @@
 			}
 			return orc
 		}(),
+		ReleaseConfigMapsMap: func() map[string]*rc_proto.ReleaseConfigMap {
+			ret := make(map[string]*rc_proto.ReleaseConfigMap)
+			for k, v := range configs.releaseConfigMapsMap {
+				ret[k] = &v.proto
+			}
+			return ret
+		}(),
 	}
 	return nil
 }
@@ -351,8 +375,8 @@
 	for idx, releaseConfigMapPath := range releaseConfigMapPaths {
 		// Maintain an ordered list of release config directories.
 		configDir := filepath.Dir(releaseConfigMapPath)
-		configs.ConfigDirIndexes[configDir] = idx
-		configs.ConfigDirs = append(configs.ConfigDirs, configDir)
+		configs.configDirIndexes[configDir] = idx
+		configs.configDirs = append(configs.configDirs, configDir)
 		err = configs.LoadReleaseConfigMap(releaseConfigMapPath, idx)
 		if err != nil {
 			return nil, err
diff --git a/cmd/release_config/release_config_lib/util.go b/cmd/release_config/release_config_lib/util.go
index c59deb3..86940da 100644
--- a/cmd/release_config/release_config_lib/util.go
+++ b/cmd/release_config/release_config_lib/util.go
@@ -15,6 +15,7 @@
 package release_config_lib
 
 import (
+	"encoding/json"
 	"fmt"
 	"io/fs"
 	"os"
@@ -25,6 +26,8 @@
 	"google.golang.org/protobuf/proto"
 )
 
+var disableWarnings bool
+
 type StringList []string
 
 func (l *StringList) Set(v string) error {
@@ -36,15 +39,66 @@
 	return fmt.Sprintf("%v", *l)
 }
 
-func LoadTextproto(path string, message proto.Message) error {
+// Write a marshalled message to a file.
+//
+// Marshal the message based on the extension of the path we are writing it to.
+//
+// Args:
+//
+//	path string: the path of the file to write to.  Directories are not created.
+//	  Supported extensions are: ".json", ".pb", and ".textproto".
+//	message proto.Message: the message to write.
+//
+// Returns:
+//
+//	error: any error encountered.
+func WriteMessage(path string, message proto.Message) (err error) {
+	var data []byte
+	switch filepath.Ext(path) {
+	case ".json":
+		data, err = json.MarshalIndent(message, "", "  ")
+	case ".pb":
+		data, err = proto.Marshal(message)
+	case ".textproto":
+		data, err = prototext.MarshalOptions{Multiline: true}.Marshal(message)
+	default:
+		return fmt.Errorf("Unknown message format for %s", path)
+	}
+	if err != nil {
+		return err
+	}
+	return os.WriteFile(path, data, 0644)
+}
+
+// Read a message from a file.
+//
+// The message is unmarshalled based on the extension of the file read.
+//
+// Args:
+//
+//	path string: the path of the file to read.
+//	message proto.Message: the message to unmarshal the message into.
+//
+// Returns:
+//
+//	error: any error encountered.
+func LoadMessage(path string, message proto.Message) error {
 	data, err := os.ReadFile(path)
 	if err != nil {
 		return err
 	}
-	ret := prototext.Unmarshal(data, message)
-	return ret
+	switch filepath.Ext(path) {
+	case ".json":
+		return json.Unmarshal(data, message)
+	case ".pb":
+		return proto.Unmarshal(data, message)
+	case ".textproto":
+		return prototext.Unmarshal(data, message)
+	}
+	return fmt.Errorf("Unknown message format for %s", path)
 }
 
+// Call Func for any textproto files found in {root}/{subdir}.
 func WalkTextprotoFiles(root string, subdir string, Func fs.WalkDirFunc) error {
 	path := filepath.Join(root, subdir)
 	if _, err := os.Stat(path); err != nil {
@@ -62,6 +116,19 @@
 	})
 }
 
+// Turn off all warning output
+func DisableWarnings() {
+	disableWarnings = true
+}
+
+func warnf(format string, args ...any) (n int, err error) {
+	if !disableWarnings {
+		return fmt.Printf(format, args...)
+	}
+	return 0, nil
+}
+
+// Returns the default value for release config artifacts.
 func GetDefaultOutDir() string {
 	outEnv := os.Getenv("OUT_DIR")
 	if outEnv == "" {
@@ -70,6 +137,7 @@
 	return filepath.Join(outEnv, "soong", "release-config")
 }
 
+// Return the default list of map files to use.
 func GetDefaultMapPaths() StringList {
 	var defaultMapPaths StringList
 	defaultLocations := StringList{
diff --git a/cmd/release_config/release_config_proto/Android.bp b/cmd/release_config/release_config_proto/Android.bp
index 5a6aeab..8c47f2a 100644
--- a/cmd/release_config/release_config_proto/Android.bp
+++ b/cmd/release_config/release_config_proto/Android.bp
@@ -18,7 +18,7 @@
 
 bootstrap_go_package {
     name: "soong-cmd-release_config-proto",
-    pkgPath: "android/soong/release_config/release_config_proto",
+    pkgPath: "android/soong/cmd/release_config/release_config_proto",
     deps: [
         "golang-protobuf-reflect-protoreflect",
         "golang-protobuf-runtime-protoimpl",
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 adc1ea4..77e2069 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
@@ -11,7 +11,7 @@
 
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.33.0
+// 	protoc-gen-go v1.30.0
 // 	protoc        v3.21.12
 // source: build_flags_out.proto
 
@@ -259,6 +259,8 @@
 	ReleaseConfig *ReleaseConfigArtifact `protobuf:"bytes,1,opt,name=release_config,json=releaseConfig" json:"release_config,omitempty"`
 	// All other release configs defined for this TARGET_PRODUCT.
 	OtherReleaseConfigs []*ReleaseConfigArtifact `protobuf:"bytes,2,rep,name=other_release_configs,json=otherReleaseConfigs" json:"other_release_configs,omitempty"`
+	// Map of release_config_artifact.directories to release_config_map message.
+	ReleaseConfigMapsMap map[string]*ReleaseConfigMap `protobuf:"bytes,3,rep,name=release_config_maps_map,json=releaseConfigMapsMap" json:"release_config_maps_map,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
 }
 
 func (x *ReleaseConfigsArtifact) Reset() {
@@ -307,6 +309,13 @@
 	return nil
 }
 
+func (x *ReleaseConfigsArtifact) GetReleaseConfigMapsMap() map[string]*ReleaseConfigMap {
+	if x != nil {
+		return x.ReleaseConfigMapsMap
+	}
+	return nil
+}
+
 var File_build_flags_out_proto protoreflect.FileDescriptor
 
 var file_build_flags_out_proto_rawDesc = []byte{
@@ -352,7 +361,7 @@
 	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, 0xe3, 0x01, 0x0a, 0x18, 0x72,
+	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,
@@ -367,10 +376,26 @@
 	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,
-	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,
+	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 (
@@ -385,28 +410,32 @@
 	return file_build_flags_out_proto_rawDescData
 }
 
-var file_build_flags_out_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
+var file_build_flags_out_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
 var file_build_flags_out_proto_goTypes = []interface{}{
 	(*Tracepoint)(nil),             // 0: android.release_config_proto.tracepoint
 	(*FlagArtifact)(nil),           // 1: android.release_config_proto.flag_artifact
 	(*ReleaseConfigArtifact)(nil),  // 2: android.release_config_proto.release_config_artifact
 	(*ReleaseConfigsArtifact)(nil), // 3: android.release_config_proto.release_configs_artifact
-	(*Value)(nil),                  // 4: android.release_config_proto.value
-	(*FlagDeclaration)(nil),        // 5: android.release_config_proto.flag_declaration
+	nil,                            // 4: android.release_config_proto.release_configs_artifact.ReleaseConfigMapsMapEntry
+	(*Value)(nil),                  // 5: android.release_config_proto.value
+	(*FlagDeclaration)(nil),        // 6: android.release_config_proto.flag_declaration
+	(*ReleaseConfigMap)(nil),       // 7: android.release_config_proto.release_config_map
 }
 var file_build_flags_out_proto_depIdxs = []int32{
-	4, // 0: android.release_config_proto.tracepoint.value:type_name -> android.release_config_proto.value
-	5, // 1: android.release_config_proto.flag_artifact.flag_declaration:type_name -> android.release_config_proto.flag_declaration
-	4, // 2: android.release_config_proto.flag_artifact.value:type_name -> android.release_config_proto.value
+	5, // 0: android.release_config_proto.tracepoint.value:type_name -> android.release_config_proto.value
+	6, // 1: android.release_config_proto.flag_artifact.flag_declaration:type_name -> android.release_config_proto.flag_declaration
+	5, // 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.release_config_artifact.flag_artifacts:type_name -> android.release_config_proto.flag_artifact
 	2, // 5: android.release_config_proto.release_configs_artifact.release_config:type_name -> android.release_config_proto.release_config_artifact
 	2, // 6: android.release_config_proto.release_configs_artifact.other_release_configs:type_name -> android.release_config_proto.release_config_artifact
-	7, // [7:7] is the sub-list for method output_type
-	7, // [7:7] is the sub-list for method input_type
-	7, // [7:7] is the sub-list for extension type_name
-	7, // [7:7] is the sub-list for extension extendee
-	0, // [0:7] is the sub-list for field type_name
+	4, // 7: android.release_config_proto.release_configs_artifact.release_config_maps_map:type_name -> android.release_config_proto.release_configs_artifact.ReleaseConfigMapsMapEntry
+	7, // 8: android.release_config_proto.release_configs_artifact.ReleaseConfigMapsMapEntry.value:type_name -> android.release_config_proto.release_config_map
+	9, // [9:9] is the sub-list for method output_type
+	9, // [9:9] is the sub-list for method input_type
+	9, // [9:9] is the sub-list for extension type_name
+	9, // [9:9] is the sub-list for extension extendee
+	0, // [0:9] is the sub-list for field type_name
 }
 
 func init() { file_build_flags_out_proto_init() }
@@ -471,7 +500,7 @@
 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
 			RawDescriptor: file_build_flags_out_proto_rawDesc,
 			NumEnums:      0,
-			NumMessages:   4,
+			NumMessages:   5,
 			NumExtensions: 0,
 			NumServices:   0,
 		},
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 fd8487b..05e770f 100644
--- a/cmd/release_config/release_config_proto/build_flags_out.proto
+++ b/cmd/release_config/release_config_proto/build_flags_out.proto
@@ -82,5 +82,8 @@
 
   // All other release configs defined for this TARGET_PRODUCT.
   repeated release_config_artifact other_release_configs = 2;
+
+  // Map of release_config_artifact.directories to release_config_map message.
+  map<string, release_config_map> release_config_maps_map = 3;
 }
 
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 ccf3b3f..ca2005c 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
@@ -11,7 +11,7 @@
 
 // Code generated by protoc-gen-go. DO NOT EDIT.
 // versions:
-// 	protoc-gen-go v1.33.0
+// 	protoc-gen-go v1.30.0
 // 	protoc        v3.21.12
 // source: build_flags_src.proto
 
@@ -385,6 +385,9 @@
 	Name *string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"`
 	// Value for the flag
 	Value *Value `protobuf:"bytes,201,opt,name=value" json:"value,omitempty"`
+	// If true, the flag is completely removed from the release config as if
+	// never declared.
+	Redacted *bool `protobuf:"varint,202,opt,name=redacted" json:"redacted,omitempty"`
 }
 
 func (x *FlagValue) Reset() {
@@ -433,6 +436,13 @@
 	return nil
 }
 
+func (x *FlagValue) GetRedacted() bool {
+	if x != nil && x.Redacted != nil {
+		return *x.Redacted
+	}
+	return false
+}
+
 // This replaces $(call declare-release-config).
 type ReleaseConfig struct {
 	state         protoimpl.MessageState
@@ -568,6 +578,8 @@
 
 	// Any aliases.
 	Aliases []*ReleaseAlias `protobuf:"bytes,1,rep,name=aliases" json:"aliases,omitempty"`
+	// 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"`
 }
@@ -611,6 +623,13 @@
 	return nil
 }
 
+func (x *ReleaseConfigMap) GetDescription() string {
+	if x != nil && x.Description != nil {
+		return *x.Description
+	}
+	return ""
+}
+
 func (x *ReleaseConfigMap) GetDefaultContainer() Container {
 	if x != nil && x.DefaultContainer != nil {
 		return *x.DefaultContainer
@@ -654,50 +673,54 @@
 	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, 0x5c, 0x0a,
+	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, 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, 0xb1, 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,
+	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,
 	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, 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, 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,
+	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,
 }
 
 var (
diff --git a/cmd/release_config/release_config_proto/build_flags_src.proto b/cmd/release_config/release_config_proto/build_flags_src.proto
index 8501524..92edc2a 100644
--- a/cmd/release_config/release_config_proto/build_flags_src.proto
+++ b/cmd/release_config/release_config_proto/build_flags_src.proto
@@ -114,6 +114,10 @@
 
   // Value for the flag
   optional value value = 201;
+
+  // If true, the flag is completely removed from the release config as if
+  // never declared.
+  optional bool redacted = 202;
 }
 
 // This replaces $(call declare-release-config).
@@ -144,6 +148,9 @@
   // Any aliases.
   repeated release_alias aliases = 1;
 
+  // Description of this map and its intended use.
+  optional string description = 2;
+
   // The default container for flags declared here.
   optional container default_container = 3;
 
diff --git a/java/aar.go b/java/aar.go
index f8955ce..47c64bf 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -418,6 +418,9 @@
 	if a.isLibrary {
 		linkFlags = append(linkFlags, "--static-lib")
 	}
+	if opts.forceNonFinalResourceIDs {
+		linkFlags = append(linkFlags, "--non-final-ids")
+	}
 
 	linkFlags = append(linkFlags, "--no-static-lib-packages")
 	if a.isLibrary && a.useResourceProcessorBusyBox(ctx) {
@@ -968,6 +971,9 @@
 	// will be passed transitively through android_libraries to an android_app.
 	//TODO(b/241138093) evaluate whether we can have this flag default to true for Bazel conversion
 	Extract_jni *bool
+
+	// If set, overrides the manifest extracted from the AAR with the provided path.
+	Manifest *string `android:"path"`
 }
 
 type AARImport struct {
@@ -990,7 +996,7 @@
 	exportPackage                      android.WritablePath
 	transitiveAaptResourcePackagesFile android.Path
 	extraAaptPackagesFile              android.WritablePath
-	manifest                           android.WritablePath
+	manifest                           android.Path
 	assetsPackage                      android.WritablePath
 	rTxt                               android.WritablePath
 	rJar                               android.WritablePath
@@ -1166,7 +1172,15 @@
 	jarName := ctx.ModuleName() + ".jar"
 	extractedAARDir := android.PathForModuleOut(ctx, "aar")
 	classpathFile := extractedAARDir.Join(ctx, jarName)
-	a.manifest = extractedAARDir.Join(ctx, "AndroidManifest.xml")
+
+	extractedManifest := extractedAARDir.Join(ctx, "AndroidManifest.xml")
+	providedManifest := android.OptionalPathForModuleSrc(ctx, a.properties.Manifest)
+	if providedManifest.Valid() {
+		a.manifest = providedManifest.Path()
+	} else {
+		a.manifest = extractedManifest
+	}
+
 	a.rTxt = extractedAARDir.Join(ctx, "R.txt")
 	a.assetsPackage = android.PathForModuleOut(ctx, "assets.zip")
 	a.proguardFlags = extractedAARDir.Join(ctx, "proguard.txt")
@@ -1187,7 +1201,7 @@
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        unzipAAR,
 		Input:       a.aarPath,
-		Outputs:     android.WritablePaths{classpathFile, a.proguardFlags, a.manifest, a.assetsPackage, a.rTxt},
+		Outputs:     android.WritablePaths{classpathFile, a.proguardFlags, extractedManifest, a.assetsPackage, a.rTxt},
 		Description: "unzip AAR",
 		Args: map[string]string{
 			"outDir":             extractedAARDir.String(),
diff --git a/java/droidstubs.go b/java/droidstubs.go
index 02b81a4..ffd3caf 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -532,8 +532,8 @@
 		cmd.Flag(config.MetalavaAnnotationsFlags)
 
 		if params.migratingNullability {
-			previousApi := android.PathForModuleSrc(ctx, String(d.properties.Previous_api))
-			cmd.FlagWithInput("--migrate-nullness ", previousApi)
+			previousApiFiles := android.PathsForModuleSrc(ctx, []string{String(d.properties.Previous_api)})
+			cmd.FlagForEachInput("--migrate-nullness ", previousApiFiles)
 		}
 
 		if s := String(d.properties.Validate_nullability_from_list); s != "" {
@@ -692,11 +692,11 @@
 		ctx.PropertyErrorf("out", "out property may not be combined with check_api")
 	}
 
-	apiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Last_released.Api_file))
-	removedApiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Last_released.Removed_api_file))
+	apiFiles := android.PathsForModuleSrc(ctx, []string{String(d.properties.Check_api.Last_released.Api_file)})
+	removedApiFiles := android.PathsForModuleSrc(ctx, []string{String(d.properties.Check_api.Last_released.Removed_api_file)})
 
-	cmd.FlagWithInput("--check-compatibility:api:released ", apiFile)
-	cmd.FlagWithInput("--check-compatibility:removed:released ", removedApiFile)
+	cmd.FlagForEachInput("--check-compatibility:api:released ", apiFiles)
+	cmd.FlagForEachInput("--check-compatibility:removed:released ", removedApiFiles)
 
 	baselineFile := android.OptionalPathForModuleSrc(ctx, d.properties.Check_api.Last_released.Baseline_file)
 	if baselineFile.Valid() {
@@ -708,8 +708,8 @@
 	return ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_METALAVA")
 }
 
-func metalavaCmd(ctx android.ModuleContext, rule *android.RuleBuilder, javaVersion javaVersion, srcs android.Paths,
-	srcJarList android.Path, bootclasspath, classpath classpath, homeDir android.WritablePath) *android.RuleBuilderCommand {
+func metalavaCmd(ctx android.ModuleContext, rule *android.RuleBuilder, srcs android.Paths,
+	srcJarList android.Path, homeDir android.WritablePath, params stubsCommandConfigParams) *android.RuleBuilderCommand {
 	rule.Command().Text("rm -rf").Flag(homeDir.String())
 	rule.Command().Text("mkdir -p").Flag(homeDir.String())
 
@@ -739,14 +739,14 @@
 	cmd.BuiltTool("metalava").ImplicitTool(ctx.Config().HostJavaToolPath(ctx, "metalava.jar")).
 		Flag(config.JavacVmFlags).
 		Flag(config.MetalavaAddOpens).
-		FlagWithArg("--java-source ", javaVersion.String()).
-		FlagWithRspFileInputList("@", android.PathForModuleOut(ctx, "metalava.rsp"), srcs).
+		FlagWithArg("--java-source ", params.javaVersion.String()).
+		FlagWithRspFileInputList("@", android.PathForModuleOut(ctx, fmt.Sprintf("%s.metalava.rsp", params.stubsType.String())), srcs).
 		FlagWithInput("@", srcJarList)
 
 	// Metalava does not differentiate between bootclasspath and classpath and has not done so for
 	// years, so it is unlikely to change any time soon.
-	combinedPaths := append(([]android.Path)(nil), bootclasspath.Paths()...)
-	combinedPaths = append(combinedPaths, classpath.Paths()...)
+	combinedPaths := append(([]android.Path)(nil), params.deps.bootClasspath.Paths()...)
+	combinedPaths = append(combinedPaths, params.deps.classpath.Paths()...)
 	if len(combinedPaths) > 0 {
 		cmd.FlagWithInputList("--classpath ", combinedPaths, ":")
 	}
@@ -827,8 +827,7 @@
 	srcJarList := zipSyncCmd(ctx, rule, params.srcJarDir, d.Javadoc.srcJars)
 
 	homeDir := android.PathForModuleOut(ctx, params.stubConfig.stubsType.String(), "home")
-	cmd := metalavaCmd(ctx, rule, params.stubConfig.javaVersion, d.Javadoc.srcFiles, srcJarList,
-		params.stubConfig.deps.bootClasspath, params.stubConfig.deps.classpath, homeDir)
+	cmd := metalavaCmd(ctx, rule, d.Javadoc.srcFiles, srcJarList, homeDir, params.stubConfig)
 	cmd.Implicits(d.Javadoc.implicits)
 
 	d.stubsFlags(ctx, cmd, params.stubsDir, params.stubConfig.stubsType, params.stubConfig.checkApi)
@@ -950,12 +949,12 @@
 
 	// Add API lint options.
 	if doApiLint {
-		newSince := android.OptionalPathForModuleSrc(ctx, d.properties.Check_api.Api_lint.New_since)
-		if newSince.Valid() {
-			cmd.FlagWithInput("--api-lint ", newSince.Path())
-		} else {
-			cmd.Flag("--api-lint")
+		var newSince android.Paths
+		if d.properties.Check_api.Api_lint.New_since != nil {
+			newSince = android.PathsForModuleSrc(ctx, []string{proptools.String(d.properties.Check_api.Api_lint.New_since)})
 		}
+		cmd.Flag("--api-lint")
+		cmd.FlagForEachInput("--api-lint-previous-api ", newSince)
 		d.apiLintReport = android.PathForModuleOut(ctx, Everything.String(), "api_lint_report.txt")
 		cmd.FlagWithOutput("--report-even-if-suppressed ", d.apiLintReport) // TODO:  Change to ":api-lint"
 
diff --git a/java/java.go b/java/java.go
index 2834c5f..725e25a 100644
--- a/java/java.go
+++ b/java/java.go
@@ -587,6 +587,7 @@
 	JAVA_VERSION_9           = 9
 	JAVA_VERSION_11          = 11
 	JAVA_VERSION_17          = 17
+	JAVA_VERSION_21          = 21
 )
 
 func (v javaVersion) String() string {
@@ -605,6 +606,8 @@
 		return "11"
 	case JAVA_VERSION_17:
 		return "17"
+	case JAVA_VERSION_21:
+		return "21"
 	default:
 		return "unsupported"
 	}
@@ -647,6 +650,8 @@
 		return JAVA_VERSION_11
 	case "17":
 		return JAVA_VERSION_17
+	case "21":
+		return JAVA_VERSION_21
 	case "10", "12", "13", "14", "15", "16":
 		ctx.PropertyErrorf("java_version", "Java language level %s is not supported", javaVersion)
 		return JAVA_VERSION_UNSUPPORTED
diff --git a/java/prebuilt_apis.go b/java/prebuilt_apis.go
index 6a79e58..00613ee 100644
--- a/java/prebuilt_apis.go
+++ b/java/prebuilt_apis.go
@@ -367,10 +367,23 @@
 		info := latest[k]
 		name := PrebuiltApiCombinedModuleName(info.module, info.scope, "latest")
 
+		// Iterate until the currentApiScope does not extend any other api scopes
+		// i.e. is not a superset of any other api scopes
+		// the relationship between the api scopes is defined in java/sdk_library.go
 		var srcs []string
 		currentApiScope := scopeByName[info.scope]
-		srcs = append(srcs, PrebuiltApiModuleName(info.module, currentApiScope.name, "latest"))
+		for currentApiScope != nil {
+			if _, ok := latest[fmt.Sprintf("%s.%s", info.module, currentApiScope.name)]; ok {
+				srcs = append(srcs, PrebuiltApiModuleName(info.module, currentApiScope.name, "latest"))
+			}
+			currentApiScope = currentApiScope.extends
+		}
 
+		// srcs is currently listed in the order from the widest api scope to the narrowest api scopes
+		// e.g. module lib -> system -> public
+		// In order to pass the files in metalava from the narrowest api scope to the widest api scope,
+		// the list has to be reversed.
+		android.ReverseSliceInPlace(srcs)
 		createCombinedApiFilegroupModule(mctx, name, srcs)
 	}
 }
diff --git a/java/robolectric.go b/java/robolectric.go
index 9e8850c..18386c9 100644
--- a/java/robolectric.go
+++ b/java/robolectric.go
@@ -46,6 +46,7 @@
 var (
 	roboCoverageLibsTag = dependencyTag{name: "roboCoverageLibs"}
 	roboRuntimesTag     = dependencyTag{name: "roboRuntimes"}
+	roboRuntimeOnlyTag  = dependencyTag{name: "roboRuntimeOnlyTag"}
 )
 
 type robolectricProperties struct {
@@ -70,6 +71,9 @@
 	// Use /external/robolectric rather than /external/robolectric-shadows as the version of robolectric
 	// to use.  /external/robolectric closely tracks github's master, and will fully replace /external/robolectric-shadows
 	Upstream *bool
+
+	// Use strict mode to limit access of Robolectric API directly. See go/roboStrictMode
+	Strict_mode *bool
 }
 
 type robolectricTest struct {
@@ -112,7 +116,7 @@
 
 	if v := String(r.robolectricProperties.Robolectric_prebuilt_version); v != "" {
 		ctx.AddVariationDependencies(nil, libTag, fmt.Sprintf(robolectricPrebuiltLibPattern, v))
-	} else {
+	} else if !proptools.Bool(r.robolectricProperties.Strict_mode) {
 		if proptools.Bool(r.robolectricProperties.Upstream) {
 			ctx.AddVariationDependencies(nil, libTag, robolectricCurrentLib+"_upstream")
 		} else {
@@ -120,6 +124,10 @@
 		}
 	}
 
+	if proptools.Bool(r.robolectricProperties.Strict_mode) {
+		ctx.AddVariationDependencies(nil, roboRuntimeOnlyTag, robolectricCurrentLib+"_upstream")
+	}
+
 	ctx.AddVariationDependencies(nil, libTag, robolectricDefaultLibs...)
 
 	ctx.AddVariationDependencies(nil, roboCoverageLibsTag, r.robolectricProperties.Coverage_libs...)
@@ -192,19 +200,25 @@
 		combinedJarJars = append(combinedJarJars, instrumentedApp.implementationAndResourcesJar)
 	}
 
-	handleLibDeps := func(dep android.Module) {
+	handleLibDeps := func(dep android.Module, runtimeOnly bool) {
 		m, _ := android.OtherModuleProvider(ctx, dep, JavaInfoProvider)
-		r.libs = append(r.libs, ctx.OtherModuleName(dep))
+		if !runtimeOnly {
+			r.libs = append(r.libs, ctx.OtherModuleName(dep))
+		}
 		if !android.InList(ctx.OtherModuleName(dep), config.FrameworkLibraries) {
 			combinedJarJars = append(combinedJarJars, m.ImplementationAndResourcesJars...)
 		}
 	}
 
 	for _, dep := range ctx.GetDirectDepsWithTag(libTag) {
-		handleLibDeps(dep)
+		handleLibDeps(dep, false)
 	}
 	for _, dep := range ctx.GetDirectDepsWithTag(sdkLibTag) {
-		handleLibDeps(dep)
+		handleLibDeps(dep, false)
+	}
+	// handle the runtimeOnly tag for strict_mode
+	for _, dep := range ctx.GetDirectDepsWithTag(roboRuntimeOnlyTag) {
+		handleLibDeps(dep, true)
 	}
 
 	r.combinedJar = android.PathForModuleOut(ctx, "robolectric_combined", r.outputFile.Base())
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 56b1d39..113071f 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -705,10 +705,10 @@
 	annotationsZip android.OptionalPath
 
 	// The path to the latest API file.
-	latestApiPath android.OptionalPath
+	latestApiPaths android.Paths
 
 	// The path to the latest removed API file.
-	latestRemovedApiPath android.OptionalPath
+	latestRemovedApiPaths android.Paths
 }
 
 func (paths *scopePaths) extractStubsLibraryInfoFromDependency(ctx android.ModuleContext, dep android.Module) error {
@@ -829,28 +829,25 @@
 	})
 }
 
-func extractSingleOptionalOutputPath(dep android.Module) (android.OptionalPath, error) {
+func extractOutputPaths(dep android.Module) (android.Paths, error) {
 	var paths android.Paths
 	if sourceFileProducer, ok := dep.(android.SourceFileProducer); ok {
 		paths = sourceFileProducer.Srcs()
+		return paths, nil
 	} else {
-		return android.OptionalPath{}, fmt.Errorf("module %q does not produce source files", dep)
+		return nil, fmt.Errorf("module %q does not produce source files", dep)
 	}
-	if len(paths) != 1 {
-		return android.OptionalPath{}, fmt.Errorf("expected one path from %q, got %q", dep, paths)
-	}
-	return android.OptionalPathForPath(paths[0]), nil
 }
 
 func (paths *scopePaths) extractLatestApiPath(ctx android.ModuleContext, dep android.Module) error {
-	outputPath, err := extractSingleOptionalOutputPath(dep)
-	paths.latestApiPath = outputPath
+	outputPaths, err := extractOutputPaths(dep)
+	paths.latestApiPaths = outputPaths
 	return err
 }
 
 func (paths *scopePaths) extractLatestRemovedApiPath(ctx android.ModuleContext, dep android.Module) error {
-	outputPath, err := extractSingleOptionalOutputPath(dep)
-	paths.latestRemovedApiPath = outputPath
+	outputPaths, err := extractOutputPaths(dep)
+	paths.latestRemovedApiPaths = outputPaths
 	return err
 }
 
@@ -1625,11 +1622,15 @@
 		scopes[scope.name] = scopeInfo
 		scopeInfo["current_api"] = scope.snapshotRelativeCurrentApiTxtPath(baseModuleName)
 		scopeInfo["removed_api"] = scope.snapshotRelativeRemovedApiTxtPath(baseModuleName)
-		if p := scopePaths.latestApiPath; p.Valid() {
-			scopeInfo["latest_api"] = p.Path().String()
+		if p := scopePaths.latestApiPaths; len(p) > 0 {
+			// The last path in the list is the one that applies to this scope, the
+			// preceding ones, if any, are for the scope(s) that it extends.
+			scopeInfo["latest_api"] = p[len(p)-1].String()
 		}
-		if p := scopePaths.latestRemovedApiPath; p.Valid() {
-			scopeInfo["latest_removed_api"] = p.Path().String()
+		if p := scopePaths.latestRemovedApiPaths; len(p) > 0 {
+			// The last path in the list is the one that applies to this scope, the
+			// preceding ones, if any, are for the scope(s) that it extends.
+			scopeInfo["latest_removed_api"] = p[len(p)-1].String()
 		}
 	}
 	android.SetProvider(ctx, android.AdditionalSdkInfoProvider, android.AdditionalSdkInfo{additionalSdkInfo})
@@ -3315,6 +3316,7 @@
 	android.WriteFileRuleVerbatim(ctx, module.outputFilePath, xmlContent)
 
 	module.installDirPath = android.PathForModuleInstall(ctx, "etc", module.SubDir())
+	ctx.PackageFile(module.installDirPath, libName+".xml", module.outputFilePath)
 }
 
 func (module *sdkLibraryXml) AndroidMkEntries() []android.AndroidMkEntries {
diff --git a/java/sdk_library_test.go b/java/sdk_library_test.go
index 5fac255..0f163e6 100644
--- a/java/sdk_library_test.go
+++ b/java/sdk_library_test.go
@@ -1085,18 +1085,6 @@
 	t.Run("prefer", func(t *testing.T) {
 		testJavaSdkLibraryImport_Preferred(t, "prefer: true,", android.NullFixturePreparer)
 	})
-
-	t.Run("use_source_config_var", func(t *testing.T) {
-		testJavaSdkLibraryImport_Preferred(t,
-			"use_source_config_var: {config_namespace: \"acme\", var_name: \"use_source\"},",
-			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
-				variables.VendorVars = map[string]map[string]string{
-					"acme": {
-						"use_source": "false",
-					},
-				}
-			}))
-	})
 }
 
 // If a module is listed in `mainline_module_contributions, it should be used
diff --git a/java/testing.go b/java/testing.go
index 631d516..5ae326d 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -711,7 +711,7 @@
 
 func registerFakeApexMutator(ctx android.RegistrationContext) {
 	ctx.PostDepsMutators(func(ctx android.RegisterMutatorsContext) {
-		ctx.BottomUp("apex", fakeApexMutator).Parallel()
+		ctx.Transition("apex", &fakeApexMutator{})
 	})
 }
 
@@ -726,16 +726,30 @@
 // `apex_available`. It helps us avoid a dependency on the real mutator defined in "soong-apex",
 // which will cause a cyclic dependency, and it provides an easy way to create an APEX variant for
 // testing without dealing with all the complexities in the real mutator.
-func fakeApexMutator(mctx android.BottomUpMutatorContext) {
-	switch mctx.Module().(type) {
+type fakeApexMutator struct{}
+
+func (f *fakeApexMutator) Split(ctx android.BaseModuleContext) []string {
+	switch ctx.Module().(type) {
 	case *Library, *SdkLibrary:
-		if len(mctx.Module().(apexModuleBase).ApexAvailable()) > 0 {
-			modules := mctx.CreateVariations("", "apex1000")
-			apexInfo := android.ApexInfo{
-				ApexVariationName: "apex1000",
-			}
-			mctx.SetVariationProvider(modules[1], android.ApexInfoProvider, apexInfo)
+		return []string{"", "apex1000"}
+	}
+	return []string{""}
+}
+
+func (f *fakeApexMutator) OutgoingTransition(ctx android.OutgoingTransitionContext, sourceVariation string) string {
+	return sourceVariation
+}
+
+func (f *fakeApexMutator) IncomingTransition(ctx android.IncomingTransitionContext, incomingVariation string) string {
+	return incomingVariation
+}
+
+func (f *fakeApexMutator) Mutate(ctx android.BottomUpMutatorContext, variation string) {
+	if variation != "" {
+		apexInfo := android.ApexInfo{
+			ApexVariationName: "apex1000",
 		}
+		android.SetProvider(ctx, android.ApexInfoProvider, apexInfo)
 	}
 }
 
diff --git a/python/binary.go b/python/binary.go
index d6750c6..c84eeee 100644
--- a/python/binary.go
+++ b/python/binary.go
@@ -203,7 +203,7 @@
 }
 
 func (p *PythonBinaryModule) isEmbeddedLauncherEnabled() bool {
-	return Bool(p.properties.Embedded_launcher)
+	return BoolDefault(p.properties.Embedded_launcher, true)
 }
 
 func (b *PythonBinaryModule) autorun() bool {
diff --git a/python/python.go b/python/python.go
index 2b1974e..e14fdf3 100644
--- a/python/python.go
+++ b/python/python.go
@@ -59,7 +59,7 @@
 	// list of the Python libraries used only for this Python version.
 	Libs []string `android:"arch_variant"`
 
-	// whether the binary is required to be built with embedded launcher for this version, defaults to false.
+	// whether the binary is required to be built with embedded launcher for this version, defaults to true.
 	Embedded_launcher *bool // TODO(b/174041232): Remove this property
 }
 
@@ -151,6 +151,8 @@
 	// The zip file containing the current module's source/data files, with the
 	// source files precompiled.
 	precompiledSrcsZip android.Path
+
+	sourceProperties android.SourceProperties
 }
 
 // newModule generates new Python base module
@@ -203,7 +205,7 @@
 var _ pythonDependency = (*PythonLibraryModule)(nil)
 
 func (p *PythonLibraryModule) init() android.Module {
-	p.AddProperties(&p.properties, &p.protoProperties)
+	p.AddProperties(&p.properties, &p.protoProperties, &p.sourceProperties)
 	android.InitAndroidArchModule(p, p.hod, p.multilib)
 	android.InitDefaultableModule(p)
 	return p
@@ -421,6 +423,11 @@
 func (p *PythonLibraryModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	expandedSrcs := android.PathsForModuleSrcExcludes(ctx, p.properties.Srcs, p.properties.Exclude_srcs)
 	android.SetProvider(ctx, blueprint.SrcsFileProviderKey, blueprint.SrcsFileProviderData{SrcPaths: expandedSrcs.Strings()})
+	// Keep before any early returns.
+	android.SetProvider(ctx, android.TestOnlyProviderKey, android.TestModuleInformation{
+		TestOnly:       Bool(p.sourceProperties.Test_only),
+		TopLevelTarget: p.sourceProperties.Top_level_test_target,
+	})
 
 	// expand data files from "data" property.
 	expandedData := android.PathsForModuleSrc(ctx, p.properties.Data)
diff --git a/python/python_test.go b/python/python_test.go
index 75a6a89..c0b7295 100644
--- a/python/python_test.go
+++ b/python/python_test.go
@@ -18,10 +18,13 @@
 	"fmt"
 	"os"
 	"path/filepath"
+	"strings"
 	"testing"
 
 	"android/soong/android"
 	"android/soong/cc"
+
+	"github.com/google/blueprint"
 )
 
 type pyModule struct {
@@ -360,6 +363,76 @@
 	}
 }
 
+func TestTestOnlyProvider(t *testing.T) {
+	t.Parallel()
+	ctx := android.GroupFixturePreparers(
+		PrepareForTestWithPythonBuildComponents,
+		android.PrepareForTestWithAllowMissingDependencies,
+	).RunTestWithBp(t, `
+                // These should be test-only
+                python_library { name: "py-lib-test", test_only: true }
+                python_library { name: "py-lib-test-host", test_only: true, host_supported: true }
+                python_test {    name: "py-test", srcs: ["py-test.py"] }
+                python_test_host { name: "py-test-host", srcs: ["py-test-host.py"] }
+                python_binary_host { name: "py-bin-test", srcs: ["py-bin-test.py"] }
+
+                // These should not be.
+                python_library { name: "py-lib" }
+                python_binary_host { name: "py-bin", srcs: ["py-bin.py"] }
+	`)
+
+	// Visit all modules and ensure only the ones that should
+	// marked as test-only are marked as test-only.
+
+	actualTestOnly := []string{}
+	ctx.VisitAllModules(func(m blueprint.Module) {
+		if provider, ok := android.OtherModuleProvider(ctx.TestContext.OtherModuleProviderAdaptor(), m, android.TestOnlyProviderKey); ok {
+			if provider.TestOnly {
+				actualTestOnly = append(actualTestOnly, m.Name())
+			}
+		}
+	})
+	expectedTestOnlyModules := []string{
+		"py-lib-test",
+		"py-lib-test-host",
+		"py-test",
+		"py-test-host",
+	}
+
+	notEqual, left, right := android.ListSetDifference(expectedTestOnlyModules, actualTestOnly)
+	if notEqual {
+		t.Errorf("test-only: Expected but not found: %v, Found but not expected: %v", left, right)
+	}
+}
+
+// Don't allow setting test-only on things that are always tests or never tests.
+func TestInvalidTestOnlyTargets(t *testing.T) {
+	testCases := []string{
+		` python_test { name: "py-test", test_only: true, srcs: ["py-test.py"] } `,
+		` python_test_host { name: "py-test-host", test_only: true, srcs: ["py-test-host.py"] } `,
+		` python_defaults { name: "py-defaults", test_only: true, srcs: ["foo.py"] } `,
+	}
+
+	for i, bp := range testCases {
+		ctx := android.GroupFixturePreparers(
+			PrepareForTestWithPythonBuildComponents,
+			android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
+
+				ctx.RegisterModuleType("python_defaults", DefaultsFactory)
+			}),
+			android.PrepareForTestWithAllowMissingDependencies).
+			ExtendWithErrorHandler(android.FixtureIgnoreErrors).
+			RunTestWithBp(t, bp)
+		if len(ctx.Errs) != 1 {
+			t.Errorf("Expected err setting test_only in testcase #%d: %d errs", i, len(ctx.Errs))
+			continue
+		}
+		if !strings.Contains(ctx.Errs[0].Error(), "unrecognized property \"test_only\"") {
+			t.Errorf("ERR: %s bad bp: %s", ctx.Errs[0], bp)
+		}
+	}
+}
+
 func expectModule(t *testing.T, ctx *android.TestContext, name, variant, expectedSrcsZip string, expectedPyRunfiles []string) {
 	module := ctx.ModuleForTests(name, variant)
 
diff --git a/python/test.go b/python/test.go
index 826f353..2b939e7 100644
--- a/python/test.go
+++ b/python/test.go
@@ -36,7 +36,9 @@
 }
 
 func NewTest(hod android.HostOrDeviceSupported) *PythonTestModule {
-	return &PythonTestModule{PythonBinaryModule: *NewBinary(hod)}
+	p := &PythonTestModule{PythonBinaryModule: *NewBinary(hod)}
+	p.sourceProperties = android.SourceProperties{Test_only: proptools.BoolPtr(true), Top_level_test_target: true}
+	return p
 }
 
 func PythonTestHostFactory() android.Module {
diff --git a/rust/bindgen.go b/rust/bindgen.go
index 11ba74d..eaed1b9 100644
--- a/rust/bindgen.go
+++ b/rust/bindgen.go
@@ -101,6 +101,9 @@
 	//
 	// "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
 }
 
 type bindgenDecorator struct {
@@ -232,6 +235,9 @@
 
 	bindgenFlags := defaultBindgenFlags
 	bindgenFlags = append(bindgenFlags, esc(b.Properties.Bindgen_flags)...)
+	if b.Properties.Handle_static_inline {
+		bindgenFlags = append(bindgenFlags, "--experimental --wrap-static-fns")
+	}
 
 	// cat reads from stdin if its command line is empty,
 	// so we pass in /dev/null if there are no other flag files
diff --git a/rust/bindgen_test.go b/rust/bindgen_test.go
index 0c0a6da..11cfe4e 100644
--- a/rust/bindgen_test.go
+++ b/rust/bindgen_test.go
@@ -227,3 +227,22 @@
 	// TODO: The best we can do right now is check $flagfiles. Once bindgen.go switches to RuleBuilder,
 	// 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 {
+			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"])
+	}
+}
diff --git a/rust/protobuf.go b/rust/protobuf.go
index 0b26b80..fab5259 100644
--- a/rust/protobuf.go
+++ b/rust/protobuf.go
@@ -16,6 +16,7 @@
 
 import (
 	"fmt"
+	"strconv"
 	"strings"
 
 	"android/soong/android"
@@ -122,41 +123,65 @@
 	// stemFile must be first here as the first path in BaseSourceProvider.OutputFiles is the library entry-point.
 	var outputs android.WritablePaths
 
-	rule := android.NewRuleBuilder(pctx, ctx)
+	for i, shard := range android.ShardPaths(protoFiles, 50) {
+		rule := android.NewRuleBuilder(pctx, ctx)
 
-	for _, protoFile := range protoFiles {
-		// Since we're iterating over the protoFiles already, make sure they're not redeclared in grpcFiles
-		if android.InList(protoFile.String(), grpcFiles.Strings()) {
-			ctx.PropertyErrorf("protos",
-				"A proto can only be added once to either grpc_protos or protos. %q is declared in both properties",
-				protoFile.String())
+		for _, protoFile := range shard {
+			// Since we're iterating over the protoFiles already, make sure they're not redeclared in grpcFiles
+			if android.InList(protoFile.String(), grpcFiles.Strings()) {
+				ctx.PropertyErrorf("protos",
+					"A proto can only be added once to either grpc_protos or protos. %q is declared in both properties",
+					protoFile.String())
+			}
+
+			protoName := strings.TrimSuffix(protoFile.Base(), ".proto")
+			proto.protoNames = append(proto.protoNames, protoName)
+
+			protoOut := android.PathForModuleOut(ctx, protoName+".rs")
+			depFile := android.PathForModuleOut(ctx, protoName+".d")
+
+			ruleOutputs := android.WritablePaths{protoOut, depFile}
+
+			android.ProtoRule(rule, protoFile, protoFlags, protoFlags.Deps, outDir, depFile, ruleOutputs)
+			outputs = append(outputs, ruleOutputs...)
 		}
 
-		protoName := strings.TrimSuffix(protoFile.Base(), ".proto")
-		proto.protoNames = append(proto.protoNames, protoName)
+		ruleName := "protoc"
+		ruleDesc := "protoc"
+		if i > 0 {
+			ruleName += "_" + strconv.Itoa(i+1)
+			ruleDesc += " " + strconv.Itoa(i+1)
+		}
 
-		protoOut := android.PathForModuleOut(ctx, protoName+".rs")
-		depFile := android.PathForModuleOut(ctx, protoName+".d")
-
-		ruleOutputs := android.WritablePaths{protoOut, depFile}
-
-		android.ProtoRule(rule, protoFile, protoFlags, protoFlags.Deps, outDir, depFile, ruleOutputs)
-		outputs = append(outputs, ruleOutputs...)
+		rule.Build(ruleName, ruleDesc)
 	}
 
-	for _, grpcFile := range grpcFiles {
-		grpcName := strings.TrimSuffix(grpcFile.Base(), ".proto")
-		proto.grpcNames = append(proto.grpcNames, grpcName)
+	for i, shard := range android.ShardPaths(grpcFiles, 50) {
+		rule := android.NewRuleBuilder(pctx, ctx)
 
-		// GRPC protos produce two files, a proto.rs and a proto_grpc.rs
-		protoOut := android.WritablePath(android.PathForModuleOut(ctx, grpcName+".rs"))
-		grpcOut := android.WritablePath(android.PathForModuleOut(ctx, grpcName+grpcSuffix+".rs"))
-		depFile := android.PathForModuleOut(ctx, grpcName+".d")
+		for _, grpcFile := range shard {
+			grpcName := strings.TrimSuffix(grpcFile.Base(), ".proto")
+			proto.grpcNames = append(proto.grpcNames, grpcName)
 
-		ruleOutputs := android.WritablePaths{protoOut, grpcOut, depFile}
+			// GRPC protos produce two files, a proto.rs and a proto_grpc.rs
+			protoOut := android.WritablePath(android.PathForModuleOut(ctx, grpcName+".rs"))
+			grpcOut := android.WritablePath(android.PathForModuleOut(ctx, grpcName+grpcSuffix+".rs"))
+			depFile := android.PathForModuleOut(ctx, grpcName+".d")
 
-		android.ProtoRule(rule, grpcFile, grpcProtoFlags, grpcProtoFlags.Deps, outDir, depFile, ruleOutputs)
-		outputs = append(outputs, ruleOutputs...)
+			ruleOutputs := android.WritablePaths{protoOut, grpcOut, depFile}
+
+			android.ProtoRule(rule, grpcFile, grpcProtoFlags, grpcProtoFlags.Deps, outDir, depFile, ruleOutputs)
+			outputs = append(outputs, ruleOutputs...)
+		}
+
+		ruleName := "protoc_grpc"
+		ruleDesc := "protoc grpc"
+		if i > 0 {
+			ruleName += "_" + strconv.Itoa(i+1)
+			ruleDesc += " " + strconv.Itoa(i+1)
+		}
+
+		rule.Build(ruleName, ruleDesc)
 	}
 
 	// Check that all proto base filenames are unique as outputs are written to the same directory.
@@ -168,8 +193,6 @@
 
 	android.WriteFileRule(ctx, stemFile, proto.genModFileContents())
 
-	rule.Build("protoc_"+ctx.ModuleName(), "protoc "+ctx.ModuleName())
-
 	// stemFile must be first here as the first path in BaseSourceProvider.OutputFiles is the library entry-point.
 	proto.BaseSourceProvider.OutputFiles = append(android.Paths{stemFile}, outputs.Paths()...)
 
diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go
index 275860f..0a5483b 100644
--- a/sdk/java_sdk_test.go
+++ b/sdk/java_sdk_test.go
@@ -688,6 +688,12 @@
 			public: {
 				enabled: true,
 			},
+			system: {
+				enabled: true,
+			},
+			module_lib: {
+				enabled: true,
+			},
 		}
 
 		java_system_modules {
@@ -752,6 +758,20 @@
         removed_api: "sdk_library/public/myjavalib-removed.txt",
         sdk_version: "current",
     },
+    system: {
+        jars: ["sdk_library/system/myjavalib-stubs.jar"],
+        stub_srcs: ["sdk_library/system/myjavalib_stub_sources"],
+        current_api: "sdk_library/system/myjavalib.txt",
+        removed_api: "sdk_library/system/myjavalib-removed.txt",
+        sdk_version: "system_current",
+    },
+    module_lib: {
+        jars: ["sdk_library/module-lib/myjavalib-stubs.jar"],
+        stub_srcs: ["sdk_library/module-lib/myjavalib_stub_sources"],
+        current_api: "sdk_library/module-lib/myjavalib.txt",
+        removed_api: "sdk_library/module-lib/myjavalib-removed.txt",
+        sdk_version: "module_current",
+    },
 }
 
 java_system_modules_import {
@@ -771,6 +791,12 @@
 .intermediates/myjavalib.stubs.exportable/android_common/combined/myjavalib.stubs.exportable.jar -> sdk_library/public/myjavalib-stubs.jar
 .intermediates/myjavalib.stubs.source/android_common/exportable/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
 .intermediates/myjavalib.stubs.source/android_common/exportable/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
+.intermediates/myjavalib.stubs.exportable.system/android_common/combined/myjavalib.stubs.exportable.system.jar -> sdk_library/system/myjavalib-stubs.jar
+.intermediates/myjavalib.stubs.source.system/android_common/exportable/myjavalib.stubs.source.system_api.txt -> sdk_library/system/myjavalib.txt
+.intermediates/myjavalib.stubs.source.system/android_common/exportable/myjavalib.stubs.source.system_removed.txt -> sdk_library/system/myjavalib-removed.txt
+.intermediates/myjavalib.stubs.exportable.module_lib/android_common/combined/myjavalib.stubs.exportable.module_lib.jar -> sdk_library/module-lib/myjavalib-stubs.jar
+.intermediates/myjavalib.stubs.source.module_lib/android_common/exportable/myjavalib.stubs.source.module_lib_api.txt -> sdk_library/module-lib/myjavalib.txt
+.intermediates/myjavalib.stubs.source.module_lib/android_common/exportable/myjavalib.stubs.source.module_lib_removed.txt -> sdk_library/module-lib/myjavalib-removed.txt
 `),
 		checkInfoContents(result.Config, `
 [
@@ -805,11 +831,23 @@
     "@name": "myjavalib",
     "dist_stem": "myjavalib",
     "scopes": {
+      "module-lib": {
+        "current_api": "sdk_library/module-lib/myjavalib.txt",
+        "latest_api": "out/soong/.intermediates/prebuilts/sdk/myjavalib.api.module-lib.latest/gen/myjavalib.api.module-lib.latest",
+        "latest_removed_api": "out/soong/.intermediates/prebuilts/sdk/myjavalib-removed.api.module-lib.latest/gen/myjavalib-removed.api.module-lib.latest",
+        "removed_api": "sdk_library/module-lib/myjavalib-removed.txt"
+      },
       "public": {
         "current_api": "sdk_library/public/myjavalib.txt",
         "latest_api": "out/soong/.intermediates/prebuilts/sdk/myjavalib.api.public.latest/gen/myjavalib.api.public.latest",
         "latest_removed_api": "out/soong/.intermediates/prebuilts/sdk/myjavalib-removed.api.public.latest/gen/myjavalib-removed.api.public.latest",
         "removed_api": "sdk_library/public/myjavalib-removed.txt"
+      },
+      "system": {
+        "current_api": "sdk_library/system/myjavalib.txt",
+        "latest_api": "out/soong/.intermediates/prebuilts/sdk/myjavalib.api.system.latest/gen/myjavalib.api.system.latest",
+        "latest_removed_api": "out/soong/.intermediates/prebuilts/sdk/myjavalib-removed.api.system.latest/gen/myjavalib-removed.api.system.latest",
+        "removed_api": "sdk_library/system/myjavalib-removed.txt"
       }
     }
   },
diff --git a/soong_ui.bash b/soong_ui.bash
index 8e7cd19..7737880 100755
--- a/soong_ui.bash
+++ b/soong_ui.bash
@@ -35,6 +35,7 @@
 soong_build_go soong_ui android/soong/cmd/soong_ui
 soong_build_go mk2rbc android/soong/mk2rbc/mk2rbc
 soong_build_go rbcrun rbcrun/rbcrun
+soong_build_go release-config android/soong/cmd/release_config/release_config
 
 cd ${TOP}
 exec "$(getoutdir)/soong_ui" "$@"
diff --git a/tradefed_modules/test_module_config.go b/tradefed_modules/test_module_config.go
index 6867537..9127f67 100644
--- a/tradefed_modules/test_module_config.go
+++ b/tradefed_modules/test_module_config.go
@@ -297,10 +297,16 @@
 //  1. manifest file to testcases dir
 //  2. 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{
+		TestOnly:       true,
+		TopLevelTarget: true,
+	})
+
 	if !m.validateTestSuites(ctx) {
 		return
 	}
-	// Ensure the provider is accurate
+	// Ensure the base provider is accurate
 	if m.provider.TestConfig == nil {
 		return
 	}
diff --git a/tradefed_modules/test_module_config_test.go b/tradefed_modules/test_module_config_test.go
index 41dd3d4..6997228 100644
--- a/tradefed_modules/test_module_config_test.go
+++ b/tradefed_modules/test_module_config_test.go
@@ -19,6 +19,8 @@
 	"strconv"
 	"strings"
 	"testing"
+
+	"github.com/google/blueprint"
 )
 
 const bp = `
@@ -347,6 +349,67 @@
 		RunTestWithBp(t, badBp)
 }
 
+func TestTestOnlyProvider(t *testing.T) {
+	t.Parallel()
+	ctx := android.GroupFixturePreparers(
+		java.PrepareForTestWithJavaDefaultModules,
+		android.FixtureRegisterWithContext(RegisterTestModuleConfigBuildComponents),
+	).RunTestWithBp(t, `
+                // These should be test-only
+                test_module_config_host {
+                        name: "host-derived-test",
+                        base: "host-base",
+                        exclude_filters: ["android.test.example.devcodelab.DevCodelabTest#testHelloFail"],
+                        include_annotations: ["android.platform.test.annotations.LargeTest"],
+                        test_suites: ["general-tests"],
+                }
+
+                test_module_config {
+                        name: "derived-test",
+                        base: "base",
+                        exclude_filters: ["android.test.example.devcodelab.DevCodelabTest#testHelloFail"],
+                        include_annotations: ["android.platform.test.annotations.LargeTest"],
+                        test_suites: ["general-tests"],
+                }
+
+		android_test {
+			name: "base",
+			sdk_version: "current",
+                        data: ["data/testfile"],
+		}
+
+		java_test_host {
+			name: "host-base",
+                        srcs: ["a.java"],
+                        test_suites: ["general-tests"],
+		}`,
+	)
+
+	// Visit all modules and ensure only the ones that should
+	// marked as test-only are marked as test-only.
+
+	actualTestOnly := []string{}
+	ctx.VisitAllModules(func(m blueprint.Module) {
+		if provider, ok := android.OtherModuleProvider(ctx.TestContext.OtherModuleProviderAdaptor(), m, android.TestOnlyProviderKey); ok {
+			if provider.TestOnly {
+				actualTestOnly = append(actualTestOnly, m.Name())
+			}
+		}
+	})
+	expectedTestOnlyModules := []string{
+		"host-derived-test",
+		"derived-test",
+		// android_test and java_test_host are tests too.
+		"host-base",
+		"base",
+	}
+
+	notEqual, left, right := android.ListSetDifference(expectedTestOnlyModules, actualTestOnly)
+	if notEqual {
+		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) {