Merge "Use new --api-lint-previous-api" into main
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/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..3224c3a 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"
@@ -1899,428 +1894,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/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/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/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/java/aar.go b/java/aar.go
index f8955ce..0a91ff4 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) {
diff --git a/java/droidstubs.go b/java/droidstubs.go
index e2aa533..ffd3caf 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -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)
diff --git a/java/sdk_library.go b/java/sdk_library.go
index bb5730d..113071f 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -3316,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/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 621e429..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
 }