Merge "Store build targets in the soong metrics."
diff --git a/android/arch.go b/android/arch.go
index 273c02b..54242e5 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -2005,16 +2005,37 @@
 
 	osToProp := ArchVariantProperties{}
 	archOsToProp := ArchVariantProperties{}
+
+	var linuxStructs, bionicStructs []reflect.Value
+	var ok bool
+
+	linuxStructs, ok = getTargetStructs(ctx, archProperties, "Linux")
+	if !ok {
+		linuxStructs = make([]reflect.Value, 0)
+	}
+	bionicStructs, ok = getTargetStructs(ctx, archProperties, "Bionic")
+	if !ok {
+		bionicStructs = make([]reflect.Value, 0)
+	}
+
 	// For android, linux, ...
 	for _, os := range osTypeList {
 		if os == CommonOS {
 			// It looks like this OS value is not used in Blueprint files
 			continue
 		}
-		osStructs, ok := getTargetStructs(ctx, archProperties, os.Field)
+		osStructs := make([]reflect.Value, 0)
+		osSpecificStructs, ok := getTargetStructs(ctx, archProperties, os.Field)
 		if ok {
-			osToProp[os.Name] = mergeStructs(ctx, osStructs, propertySet)
+			osStructs = append(osStructs, osSpecificStructs...)
 		}
+		if os.Linux() {
+			osStructs = append(osStructs, linuxStructs...)
+		}
+		if os.Bionic() {
+			osStructs = append(osStructs, bionicStructs...)
+		}
+		osToProp[os.Name] = mergeStructs(ctx, osStructs, propertySet)
 
 		// For arm, x86, ...
 		for _, arch := range osArchTypeMap[os] {
@@ -2048,15 +2069,9 @@
 			archOsToProp[targetName] = mergeStructs(ctx, osArchStructs, propertySet)
 		}
 	}
+
 	axisToProps[bazel.OsConfigurationAxis] = osToProp
 	axisToProps[bazel.OsArchConfigurationAxis] = archOsToProp
-
-	bionicStructs, ok := getTargetStructs(ctx, archProperties, "Bionic")
-	if ok {
-		axisToProps[bazel.BionicConfigurationAxis] = map[string]interface{}{
-			"bionic": mergeStructs(ctx, bionicStructs, propertySet)}
-	}
-
 	return axisToProps
 }
 
diff --git a/android/bazel.go b/android/bazel.go
index f5ae453..6aba759 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -182,8 +182,11 @@
 		"external/boringssl":                                 Bp2BuildDefaultTrueRecursively,
 		"external/brotli":                                    Bp2BuildDefaultTrue,
 		"external/fmtlib":                                    Bp2BuildDefaultTrueRecursively,
+		"external/googletest/googletest":                     Bp2BuildDefaultTrueRecursively,
+		"external/google-benchmark":                          Bp2BuildDefaultTrueRecursively,
 		"external/gwp_asan":                                  Bp2BuildDefaultTrueRecursively,
 		"external/jemalloc_new":                              Bp2BuildDefaultTrueRecursively,
+		"external/jsoncpp":                                   Bp2BuildDefaultTrueRecursively,
 		"external/libcap":                                    Bp2BuildDefaultTrueRecursively,
 		"external/libcxx":                                    Bp2BuildDefaultTrueRecursively,
 		"external/libcxxabi":                                 Bp2BuildDefaultTrueRecursively,
@@ -206,8 +209,11 @@
 		"system/core/libcrypto_utils":                        Bp2BuildDefaultTrueRecursively,
 		"system/core/libcutils":                              Bp2BuildDefaultTrueRecursively,
 		"system/core/libprocessgroup":                        Bp2BuildDefaultTrue,
+		"system/core/libprocessgroup/cgrouprc":               Bp2BuildDefaultTrue,
+		"system/core/libprocessgroup/cgrouprc_format":        Bp2BuildDefaultTrue,
 		"system/core/property_service/libpropertyinfoparser": Bp2BuildDefaultTrueRecursively,
 		"system/libbase":                                     Bp2BuildDefaultTrueRecursively,
+		"system/libziparchive":                               Bp2BuildDefaultTrueRecursively,
 		"system/logging/liblog":                              Bp2BuildDefaultTrueRecursively,
 		"system/sepolicy/apex":                               Bp2BuildDefaultTrueRecursively,
 		"system/timezone/apex":                               Bp2BuildDefaultTrueRecursively,
@@ -216,33 +222,9 @@
 
 	// Per-module denylist to always opt modules out of both bp2build and mixed builds.
 	bp2buildModuleDoNotConvertList = []string{
-		// Things that transitively depend on unconverted libc_* modules.
-		"libbionic_spawn_benchmark", // http://b/186824595, cc_library_static, depends on //external/google-benchmark (http://b/186822740)
-		//                                                                also depends on //system/logging/liblog:liblog (http://b/186822772)
+		"libc_malloc_debug", // depends on libunwindstack, which depends on unsupported module art_cc_library_statics
 
-		"libc_malloc_debug",           // http://b/186824339, cc_library_static, depends on //system/libbase:libbase (http://b/186823646)
-		"libc_malloc_debug_backtrace", // http://b/186824112, cc_library_static, depends on //external/libcxxabi:libc++demangle (http://b/186823773)
-
-		//"libcutils",         // http://b/186827426, cc_library, depends on //system/core/libprocessgroup:libprocessgroup_headers (http://b/186826841)
-		//"libcutils_sockets", // http://b/186826853, cc_library, depends on //system/libbase:libbase (http://b/186826479)
-
-		"liblinker_debuggerd_stub", // http://b/186824327, cc_library_static, depends on //external/zlib:libz (http://b/186823782)
-		//                                                               also depends on //system/libziparchive:libziparchive (http://b/186823656)
-		//                                                               also depends on //system/logging/liblog:liblog (http://b/186822772)
-		"liblinker_main", // http://b/186825989, cc_library_static, depends on //external/zlib:libz (http://b/186823782)
-		//                                                     also depends on //system/libziparchive:libziparchive (http://b/186823656)
-		//                                                     also depends on//system/logging/liblog:liblog (http://b/186822772)
-		"liblinker_malloc", // http://b/186826466, cc_library_static, depends on //external/zlib:libz (http://b/186823782)
-		//                                                       also depends on //system/libziparchive:libziparchive (http://b/186823656)
-		//                                                       also depends on //system/logging/liblog:liblog (http://b/186822772)
-		"libc_ndk",          // http://b/187013218, cc_library_static, depends on //bionic/libm:libm (http://b/183064661)
-		"libc_malloc_hooks", // http://b/187016307, cc_library, ld.lld: error: undefined symbol: __malloc_hook
-
-		// http://b/186823769: Needs C++ STL support, includes from unconverted standard libraries in //external/libcxx
-		// c++_static
-		"libbase_ndk", // http://b/186826477, cc_library, no such target '//build/bazel/platforms/os:darwin' when --platforms //build/bazel/platforms:android_x86 is added
-		// libcxx
-		"libBionicBenchmarksUtils", // cc_library_static, fatal error: 'map' file not found, from libcxx
+		"libbase_ndk", // http://b/186826477, fails to link libctscamera2_jni for device (required for CtsCameraTestCases)
 
 		"libprotobuf-python",               // contains .proto sources
 		"libprotobuf-internal-protos",      // we don't handle path property for fileegroups
@@ -253,11 +235,7 @@
 
 		"gwp_asan_crash_handler", // cc_library, ld.lld: error: undefined symbol: memset
 
-		//system/core/libprocessgroup/...
-		"libprocessgroup", // depends on //system/core/libprocessgroup/cgrouprc:libcgrouprc
-
-		//external/brotli/...
-		"brotli-fuzzer-corpus", // "declared output 'external/brotli/c/fuzz/73231c6592f195ffd41100b8706d1138ff6893b9' was not created by genrule"
+		"brotli-fuzzer-corpus", // b/202015218: outputs are in location incompatible with bazel genrule handling.
 
 		// //external/libcap/...
 		"libcap",      // http://b/198595332, depends on _makenames, a cc_binary
@@ -270,7 +248,7 @@
 		"libjemalloc5_unittest",
 
 		// APEX support
-		"com.android.runtime", // http://b/194746715, apex, depends on 'libc_malloc_debug' and 'libc_malloc_hooks'
+		"com.android.runtime", // http://b/194746715, apex, depends on 'libc_malloc_debug'
 
 		"libadb_crypto",                    // Depends on libadb_protos
 		"libadb_crypto_static",             // Depends on libadb_protos_static
@@ -285,6 +263,9 @@
 		"libadb_protos_static",         // b/200601772: Requires cc_library proto support
 		"libadb_protos",                // b/200601772: Requires cc_library proto support
 		"libapp_processes_protos_lite", // b/200601772: Requires cc_library proto support
+
+		"libgtest_ndk_c++",      // b/201816222: Requires sdk_version support.
+		"libgtest_main_ndk_c++", // b/201816222: Requires sdk_version support.
 	}
 
 	// Per-module denylist of cc_library modules to only generate the static
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index 2241255..6648a2f 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -815,7 +815,7 @@
 		if len(buildStatement.OutputPaths) > 0 {
 			cmd.Text("rm -f")
 			for _, outputPath := range buildStatement.OutputPaths {
-				cmd.Text(PathForBazelOut(ctx, outputPath).String())
+				cmd.Text(outputPath)
 			}
 			cmd.Text("&&")
 		}
diff --git a/android/config.go b/android/config.go
index e7ebdef..c8d7cfd 100644
--- a/android/config.go
+++ b/android/config.go
@@ -854,11 +854,6 @@
 	return Bool(c.productVariables.Always_use_prebuilt_sdks)
 }
 
-// Returns true if the boot jars check should be skipped.
-func (c *config) SkipBootJarsCheck() bool {
-	return Bool(c.productVariables.Skip_boot_jars_check)
-}
-
 func (c *config) MinimizeJavaDebugInfo() bool {
 	return Bool(c.productVariables.MinimizeJavaDebugInfo) && !Bool(c.productVariables.Eng)
 }
diff --git a/android/filegroup.go b/android/filegroup.go
index 4db165f..2cf5567 100644
--- a/android/filegroup.go
+++ b/android/filegroup.go
@@ -72,7 +72,7 @@
 		Bzl_load_location: "//build/bazel/rules:filegroup.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(fg.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, CommonAttributes{Name: fg.Name()}, attrs)
 }
 
 type fileGroupProperties struct {
diff --git a/android/module.go b/android/module.go
index c9b01a0..327e6ae 100644
--- a/android/module.go
+++ b/android/module.go
@@ -852,6 +852,16 @@
 	UnconvertedBp2buildDeps []string `blueprint:"mutated"`
 }
 
+// CommonAttributes represents the common Bazel attributes from which properties
+// in `commonProperties` are translated/mapped; such properties are annotated in
+// a list their corresponding attribute. It is embedded within `bp2buildInfo`.
+type CommonAttributes struct {
+	// Soong nameProperties -> Bazel name
+	Name string
+	// Data mapped from: Required
+	Data bazel.LabelListAttribute
+}
+
 type distProperties struct {
 	// configuration to distribute output files from this module to the distribution
 	// directory (default: $OUT/dist, configurable with $DIST_DIR)
@@ -1072,6 +1082,34 @@
 	m.base().commonProperties.CreateCommonOSVariant = true
 }
 
+func (attrs *CommonAttributes) fillCommonBp2BuildModuleAttrs(ctx *topDownMutatorContext) {
+	// Assert passed-in attributes include Name
+	name := attrs.Name
+	if len(name) == 0 {
+		ctx.ModuleErrorf("CommonAttributes in fillCommonBp2BuildModuleAttrs expects a `.Name`!")
+	}
+
+	mod := ctx.Module().base()
+	props := &mod.commonProperties
+
+	depsToLabelList := func(deps []string) bazel.LabelListAttribute {
+		return bazel.MakeLabelListAttribute(BazelLabelForModuleDeps(ctx, deps))
+	}
+
+	data := &attrs.Data
+
+	required := depsToLabelList(props.Required)
+	archVariantProps := mod.GetArchVariantProperties(ctx, &commonProperties{})
+	for axis, configToProps := range archVariantProps {
+		for config, _props := range configToProps {
+			if archProps, ok := _props.(*commonProperties); ok {
+				required.SetSelectValue(axis, config, depsToLabelList(archProps.Required).Value)
+			}
+		}
+	}
+	data.Append(required)
+}
+
 // A ModuleBase object contains the properties that are common to all Android
 // modules.  It should be included as an anonymous field in every module
 // struct definition.  InitAndroidModule should then be called from the module's
@@ -1183,15 +1221,15 @@
 
 // A struct containing all relevant information about a Bazel target converted via bp2build.
 type bp2buildInfo struct {
-	Name       string
-	Dir        string
-	BazelProps bazel.BazelTargetModuleProperties
-	Attrs      interface{}
+	Dir         string
+	BazelProps  bazel.BazelTargetModuleProperties
+	CommonAttrs CommonAttributes
+	Attrs       interface{}
 }
 
 // TargetName returns the Bazel target name of a bp2build converted target.
 func (b bp2buildInfo) TargetName() string {
-	return b.Name
+	return b.CommonAttrs.Name
 }
 
 // TargetPackage returns the Bazel package of a bp2build converted target.
@@ -1211,8 +1249,8 @@
 }
 
 // BazelAttributes returns the Bazel attributes of a bp2build converted target.
-func (b bp2buildInfo) BazelAttributes() interface{} {
-	return b.Attrs
+func (b bp2buildInfo) BazelAttributes() []interface{} {
+	return []interface{}{&b.CommonAttrs, b.Attrs}
 }
 
 func (m *ModuleBase) addBp2buildInfo(info bp2buildInfo) {
diff --git a/android/mutator.go b/android/mutator.go
index b361c51..4b37377 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -15,10 +15,11 @@
 package android
 
 import (
-	"android/soong/bazel"
 	"reflect"
 	"sync"
 
+	"android/soong/bazel"
+
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
@@ -268,7 +269,7 @@
 	// factory method, just like in CreateModule, but also requires
 	// BazelTargetModuleProperties containing additional metadata for the
 	// bp2build codegenerator.
-	CreateBazelTargetModule(string, bazel.BazelTargetModuleProperties, interface{})
+	CreateBazelTargetModule(bazel.BazelTargetModuleProperties, CommonAttributes, interface{})
 }
 
 type topDownMutatorContext struct {
@@ -514,17 +515,18 @@
 }
 
 func (t *topDownMutatorContext) CreateBazelTargetModule(
-	name string,
 	bazelProps bazel.BazelTargetModuleProperties,
+	commonAttrs CommonAttributes,
 	attrs interface{}) {
-
+	commonAttrs.fillCommonBp2BuildModuleAttrs(t)
+	mod := t.Module()
 	info := bp2buildInfo{
-		Name:       name,
-		Dir:        t.OtherModuleDir(t.Module()),
-		BazelProps: bazelProps,
-		Attrs:      attrs,
+		Dir:         t.OtherModuleDir(mod),
+		BazelProps:  bazelProps,
+		CommonAttrs: commonAttrs,
+		Attrs:       attrs,
 	}
-	t.Module().base().addBp2buildInfo(info)
+	mod.base().addBp2buildInfo(info)
 }
 
 func (t *topDownMutatorContext) AppendProperties(props ...interface{}) {
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 6605869..c9a9ddd 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -769,16 +769,25 @@
 	paths Paths
 }
 
+func checkPathNotNil(path Path) {
+	if path == nil {
+		panic("rule_builder paths cannot be nil")
+	}
+}
+
 func (c *RuleBuilderCommand) addInput(path Path) string {
+	checkPathNotNil(path)
 	c.inputs = append(c.inputs, path)
 	return c.PathForInput(path)
 }
 
 func (c *RuleBuilderCommand) addImplicit(path Path) {
+	checkPathNotNil(path)
 	c.implicits = append(c.implicits, path)
 }
 
 func (c *RuleBuilderCommand) addOrderOnly(path Path) {
+	checkPathNotNil(path)
 	c.orderOnlys = append(c.orderOnlys, path)
 }
 
@@ -1004,19 +1013,23 @@
 // Tool adds the specified tool path to the command line.  The path will be also added to the dependencies returned by
 // RuleBuilder.Tools.
 func (c *RuleBuilderCommand) Tool(path Path) *RuleBuilderCommand {
+	checkPathNotNil(path)
 	c.tools = append(c.tools, path)
 	return c.Text(c.PathForTool(path))
 }
 
 // Tool adds the specified tool path to the dependencies returned by RuleBuilder.Tools.
 func (c *RuleBuilderCommand) ImplicitTool(path Path) *RuleBuilderCommand {
+	checkPathNotNil(path)
 	c.tools = append(c.tools, path)
 	return c
 }
 
 // Tool adds the specified tool path to the dependencies returned by RuleBuilder.Tools.
 func (c *RuleBuilderCommand) ImplicitTools(paths Paths) *RuleBuilderCommand {
-	c.tools = append(c.tools, paths...)
+	for _, path := range paths {
+		c.ImplicitTool(path)
+	}
 	return c
 }
 
@@ -1093,6 +1106,7 @@
 // Validation adds the specified input path to the validation dependencies by
 // RuleBuilder.Validations without modifying the command line.
 func (c *RuleBuilderCommand) Validation(path Path) *RuleBuilderCommand {
+	checkPathNotNil(path)
 	c.validations = append(c.validations, path)
 	return c
 }
@@ -1100,13 +1114,16 @@
 // Validations adds the specified input paths to the validation dependencies by
 // RuleBuilder.Validations without modifying the command line.
 func (c *RuleBuilderCommand) Validations(paths Paths) *RuleBuilderCommand {
-	c.validations = append(c.validations, paths...)
+	for _, path := range paths {
+		c.Validation(path)
+	}
 	return c
 }
 
 // Output adds the specified output path to the command line.  The path will also be added to the outputs returned by
 // RuleBuilder.Outputs.
 func (c *RuleBuilderCommand) Output(path WritablePath) *RuleBuilderCommand {
+	checkPathNotNil(path)
 	c.outputs = append(c.outputs, path)
 	return c.Text(c.PathForOutput(path))
 }
@@ -1133,6 +1150,7 @@
 // line, and causes RuleBuilder.Build file to set the depfile flag for ninja.  If multiple depfiles are added to
 // commands in a single RuleBuilder then RuleBuilder.Build will add an extra command to merge the depfiles together.
 func (c *RuleBuilderCommand) DepFile(path WritablePath) *RuleBuilderCommand {
+	checkPathNotNil(path)
 	c.depFiles = append(c.depFiles, path)
 	return c.Text(c.PathForOutput(path))
 }
@@ -1155,6 +1173,7 @@
 // will be a symlink instead of a regular file. Does not modify the command
 // line.
 func (c *RuleBuilderCommand) ImplicitSymlinkOutput(path WritablePath) *RuleBuilderCommand {
+	checkPathNotNil(path)
 	c.symlinkOutputs = append(c.symlinkOutputs, path)
 	return c.ImplicitOutput(path)
 }
@@ -1172,6 +1191,7 @@
 // SymlinkOutput declares the specified path as an output that will be a symlink
 // instead of a regular file. Modifies the command line.
 func (c *RuleBuilderCommand) SymlinkOutput(path WritablePath) *RuleBuilderCommand {
+	checkPathNotNil(path)
 	c.symlinkOutputs = append(c.symlinkOutputs, path)
 	return c.Output(path)
 }
diff --git a/apex/apex.go b/apex/apex.go
index 5294b6c..0a2e35b 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -3325,5 +3325,5 @@
 		Bzl_load_location: "//build/bazel/rules:apex.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(module.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: module.Name()}, attrs)
 }
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 420489e..5eb7acf 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -5058,8 +5058,9 @@
 				annotation_flags: "my-bootclasspath-fragment/annotation-flags.csv",
 				metadata: "my-bootclasspath-fragment/metadata.csv",
 				index: "my-bootclasspath-fragment/index.csv",
-				stub_flags: "my-bootclasspath-fragment/stub-flags.csv",
-				all_flags: "my-bootclasspath-fragment/all-flags.csv",
+				signature_patterns: "my-bootclasspath-fragment/signature-patterns.csv",
+				filtered_stub_flags: "my-bootclasspath-fragment/filtered-stub-flags.csv",
+				filtered_flags: "my-bootclasspath-fragment/filtered-flags.csv",
 			},
 		}
 
@@ -5109,8 +5110,9 @@
 				annotation_flags: "my-bootclasspath-fragment/annotation-flags.csv",
 				metadata: "my-bootclasspath-fragment/metadata.csv",
 				index: "my-bootclasspath-fragment/index.csv",
-				stub_flags: "my-bootclasspath-fragment/stub-flags.csv",
-				all_flags: "my-bootclasspath-fragment/all-flags.csv",
+				signature_patterns: "my-bootclasspath-fragment/signature-patterns.csv",
+				filtered_stub_flags: "my-bootclasspath-fragment/filtered-stub-flags.csv",
+				filtered_flags: "my-bootclasspath-fragment/filtered-flags.csv",
 			},
 		}
 
@@ -5238,8 +5240,9 @@
 				annotation_flags: "my-bootclasspath-fragment/annotation-flags.csv",
 				metadata: "my-bootclasspath-fragment/metadata.csv",
 				index: "my-bootclasspath-fragment/index.csv",
-				stub_flags: "my-bootclasspath-fragment/stub-flags.csv",
-				all_flags: "my-bootclasspath-fragment/all-flags.csv",
+				signature_patterns: "my-bootclasspath-fragment/signature-patterns.csv",
+				filtered_stub_flags: "my-bootclasspath-fragment/filtered-stub-flags.csv",
+				filtered_flags: "my-bootclasspath-fragment/filtered-flags.csv",
 			},
 		}
 
@@ -5324,8 +5327,9 @@
 				annotation_flags: "my-bootclasspath-fragment/annotation-flags.csv",
 				metadata: "my-bootclasspath-fragment/metadata.csv",
 				index: "my-bootclasspath-fragment/index.csv",
-				stub_flags: "my-bootclasspath-fragment/stub-flags.csv",
-				all_flags: "my-bootclasspath-fragment/all-flags.csv",
+				signature_patterns: "my-bootclasspath-fragment/signature-patterns.csv",
+				filtered_stub_flags: "my-bootclasspath-fragment/filtered-stub-flags.csv",
+				filtered_flags: "my-bootclasspath-fragment/filtered-flags.csv",
 			},
 		}
 
@@ -5408,8 +5412,9 @@
 				annotation_flags: "my-bootclasspath-fragment/annotation-flags.csv",
 				metadata: "my-bootclasspath-fragment/metadata.csv",
 				index: "my-bootclasspath-fragment/index.csv",
-				stub_flags: "my-bootclasspath-fragment/stub-flags.csv",
-				all_flags: "my-bootclasspath-fragment/all-flags.csv",
+				signature_patterns: "my-bootclasspath-fragment/signature-patterns.csv",
+				filtered_stub_flags: "my-bootclasspath-fragment/filtered-stub-flags.csv",
+				filtered_flags: "my-bootclasspath-fragment/filtered-flags.csv",
 			},
 		}
 
@@ -7441,8 +7446,9 @@
 					annotation_flags: "my-bootclasspath-fragment/annotation-flags.csv",
 					metadata: "my-bootclasspath-fragment/metadata.csv",
 					index: "my-bootclasspath-fragment/index.csv",
-					stub_flags: "my-bootclasspath-fragment/stub-flags.csv",
-					all_flags: "my-bootclasspath-fragment/all-flags.csv",
+					signature_patterns: "my-bootclasspath-fragment/signature-patterns.csv",
+					filtered_stub_flags: "my-bootclasspath-fragment/filtered-stub-flags.csv",
+					filtered_flags: "my-bootclasspath-fragment/filtered-flags.csv",
 				},
 			}
 
diff --git a/apex/key.go b/apex/key.go
index 468bb8a..e2695d7 100644
--- a/apex/key.go
+++ b/apex/key.go
@@ -240,5 +240,5 @@
 		Bzl_load_location: "//build/bazel/rules:apex_key.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(module.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: module.Name()}, attrs)
 }
diff --git a/apex/platform_bootclasspath_test.go b/apex/platform_bootclasspath_test.go
index d8a4a7a..06c39ee 100644
--- a/apex/platform_bootclasspath_test.go
+++ b/apex/platform_bootclasspath_test.go
@@ -168,6 +168,76 @@
 	android.AssertArrayString(t, "all flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/filtered-flags.csv:out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/signature-patterns.csv"}, info.FlagSubsets.RelativeToTop())
 }
 
+// TestPlatformBootclasspath_LegacyPrebuiltFragment verifies that the
+// prebuilt_bootclasspath_fragment falls back to using the complete stub-flags/all-flags if the
+// filtered files are not provided.
+//
+// TODO: Remove once all prebuilts use the filtered_... properties.
+func TestPlatformBootclasspath_LegacyPrebuiltFragment(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithPlatformBootclasspath,
+		java.FixtureConfigureApexBootJars("myapex:foo"),
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+	).RunTestWithBp(t, `
+		prebuilt_apex {
+			name: "myapex",
+			src: "myapex.apex",
+			exported_bootclasspath_fragments: ["mybootclasspath-fragment"],
+		}
+
+		// A prebuilt java_sdk_library_import that is not preferred by default but will be preferred
+		// because AlwaysUsePrebuiltSdks() is true.
+		java_sdk_library_import {
+			name: "foo",
+			prefer: false,
+			shared_library: false,
+			permitted_packages: ["foo"],
+			public: {
+				jars: ["sdk_library/public/foo-stubs.jar"],
+				stub_srcs: ["sdk_library/public/foo_stub_sources"],
+				current_api: "sdk_library/public/foo.txt",
+				removed_api: "sdk_library/public/foo-removed.txt",
+				sdk_version: "current",
+			},
+			apex_available: ["myapex"],
+		}
+
+		prebuilt_bootclasspath_fragment {
+			name: "mybootclasspath-fragment",
+			apex_available: [
+				"myapex",
+			],
+			contents: [
+				"foo",
+			],
+			hidden_api: {
+				stub_flags: "prebuilt-stub-flags.csv",
+				annotation_flags: "prebuilt-annotation-flags.csv",
+				metadata: "prebuilt-metadata.csv",
+				index: "prebuilt-index.csv",
+				all_flags: "prebuilt-all-flags.csv",
+			},
+		}
+
+		platform_bootclasspath {
+			name: "myplatform-bootclasspath",
+			fragments: [
+				{
+					apex: "myapex",
+					module:"mybootclasspath-fragment",
+				},
+			],
+		}
+`,
+	)
+
+	pbcp := result.Module("myplatform-bootclasspath", "android_common")
+	info := result.ModuleProvider(pbcp, java.MonolithicHiddenAPIInfoProvider).(java.MonolithicHiddenAPIInfo)
+
+	android.AssertArrayString(t, "stub flags", []string{"prebuilt-stub-flags.csv:out/soong/.intermediates/mybootclasspath-fragment/android_common_myapex/modular-hiddenapi/signature-patterns.csv"}, info.StubFlagSubsets.RelativeToTop())
+	android.AssertArrayString(t, "all flags", []string{"prebuilt-all-flags.csv:out/soong/.intermediates/mybootclasspath-fragment/android_common_myapex/modular-hiddenapi/signature-patterns.csv"}, info.FlagSubsets.RelativeToTop())
+}
+
 func TestPlatformBootclasspathDependencies(t *testing.T) {
 	result := android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
diff --git a/apex/systemserver_classpath_fragment_test.go b/apex/systemserver_classpath_fragment_test.go
index dc682fa..412fa0e 100644
--- a/apex/systemserver_classpath_fragment_test.go
+++ b/apex/systemserver_classpath_fragment_test.go
@@ -138,7 +138,7 @@
 		dexpreopt.FixtureSetApexSystemServerJars("myapex:foo"),
 	).
 		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
-			`in contents must also be declared in PRODUCT_UPDATABLE_SYSTEM_SERVER_JARS`)).
+			`in contents must also be declared in PRODUCT_APEX_SYSTEM_SERVER_JARS`)).
 		RunTestWithBp(t, `
 			apex {
 				name: "myapex",
diff --git a/bazel/configurability.go b/bazel/configurability.go
index 7aaff9a..e9641e7 100644
--- a/bazel/configurability.go
+++ b/bazel/configurability.go
@@ -91,11 +91,6 @@
 		ConditionsDefaultConfigKey: ConditionsDefaultSelectKey, // The default condition of an os select map.
 	}
 
-	platformBionicMap = map[string]string{
-		"bionic":                   "//build/bazel/platforms/os:bionic",
-		ConditionsDefaultConfigKey: ConditionsDefaultSelectKey, // The default condition of an os select map.
-	}
-
 	platformOsArchMap = map[string]string{
 		osArchAndroidArm:           "//build/bazel/platforms/os_arch:android_arm",
 		osArchAndroidArm64:         "//build/bazel/platforms/os_arch:android_arm64",
@@ -122,7 +117,6 @@
 	arch
 	os
 	osArch
-	bionic
 	productVariables
 )
 
@@ -132,7 +126,6 @@
 		arch:             "arch",
 		os:               "os",
 		osArch:           "arch_os",
-		bionic:           "bionic",
 		productVariables: "product_variables",
 	}[ct]
 }
@@ -155,10 +148,6 @@
 		if _, ok := platformOsArchMap[config]; !ok {
 			panic(fmt.Errorf("Unknown os+arch: %s", config))
 		}
-	case bionic:
-		if _, ok := platformBionicMap[config]; !ok {
-			panic(fmt.Errorf("Unknown for %s: %s", ct.String(), config))
-		}
 	case productVariables:
 		// do nothing
 	default:
@@ -178,8 +167,6 @@
 		return platformOsMap[config]
 	case osArch:
 		return platformOsArchMap[config]
-	case bionic:
-		return platformBionicMap[config]
 	case productVariables:
 		if config == ConditionsDefaultConfigKey {
 			return ConditionsDefaultSelectKey
@@ -199,8 +186,6 @@
 	OsConfigurationAxis = ConfigurationAxis{configurationType: os}
 	// An axis for arch+os-specific configurations
 	OsArchConfigurationAxis = ConfigurationAxis{configurationType: osArch}
-	// An axis for bionic os-specific configurations
-	BionicConfigurationAxis = ConfigurationAxis{configurationType: bionic}
 )
 
 // ProductVariableConfigurationAxis returns an axis for the given product variable
diff --git a/bazel/properties.go b/bazel/properties.go
index bd8ef0d..ee32e73 100644
--- a/bazel/properties.go
+++ b/bazel/properties.go
@@ -264,7 +264,7 @@
 	switch axis.configurationType {
 	case noConfig:
 		la.Value = &value
-	case arch, os, osArch, bionic, productVariables:
+	case arch, os, osArch, productVariables:
 		if la.ConfigurableValues == nil {
 			la.ConfigurableValues = make(configurableLabels)
 		}
@@ -280,7 +280,7 @@
 	switch axis.configurationType {
 	case noConfig:
 		return *la.Value
-	case arch, os, osArch, bionic, productVariables:
+	case arch, os, osArch, productVariables:
 		return *la.ConfigurableValues[axis][config]
 	default:
 		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
@@ -337,7 +337,7 @@
 	switch axis.configurationType {
 	case noConfig:
 		ba.Value = value
-	case arch, os, osArch, bionic, productVariables:
+	case arch, os, osArch, productVariables:
 		if ba.ConfigurableValues == nil {
 			ba.ConfigurableValues = make(configurableBools)
 		}
@@ -353,7 +353,7 @@
 	switch axis.configurationType {
 	case noConfig:
 		return ba.Value
-	case arch, os, osArch, bionic, productVariables:
+	case arch, os, osArch, productVariables:
 		if v, ok := ba.ConfigurableValues[axis][config]; ok {
 			return &v
 		} else {
@@ -459,7 +459,7 @@
 	switch axis.configurationType {
 	case noConfig:
 		lla.Value = list
-	case arch, os, osArch, bionic, productVariables:
+	case arch, os, osArch, productVariables:
 		if lla.ConfigurableValues == nil {
 			lla.ConfigurableValues = make(configurableLabelLists)
 		}
@@ -475,7 +475,7 @@
 	switch axis.configurationType {
 	case noConfig:
 		return lla.Value
-	case arch, os, osArch, bionic, productVariables:
+	case arch, os, osArch, productVariables:
 		return lla.ConfigurableValues[axis][config]
 	default:
 		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
@@ -773,7 +773,7 @@
 	switch axis.configurationType {
 	case noConfig:
 		sla.Value = list
-	case arch, os, osArch, bionic, productVariables:
+	case arch, os, osArch, productVariables:
 		if sla.ConfigurableValues == nil {
 			sla.ConfigurableValues = make(configurableStringLists)
 		}
@@ -789,7 +789,7 @@
 	switch axis.configurationType {
 	case noConfig:
 		return sla.Value
-	case arch, os, osArch, bionic, productVariables:
+	case arch, os, osArch, productVariables:
 		return sla.ConfigurableValues[axis][config]
 	default:
 		panic(fmt.Errorf("Unrecognized ConfigurationAxis %s", axis))
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
index 4a0eeea..c05a62b 100644
--- a/bp2build/build_conversion.go
+++ b/bp2build/build_conversion.go
@@ -411,7 +411,7 @@
 	TargetPackage() string
 	BazelRuleClass() string
 	BazelRuleLoadLocation() string
-	BazelAttributes() interface{}
+	BazelAttributes() []interface{}
 }
 
 func generateBazelTarget(ctx bpToBuildContext, m bp2buildModule) BazelTarget {
@@ -419,7 +419,8 @@
 	bzlLoadLocation := m.BazelRuleLoadLocation()
 
 	// extract the bazel attributes from the module.
-	props := extractModuleProperties([]interface{}{m.BazelAttributes()})
+	attrs := m.BazelAttributes()
+	props := extractModuleProperties(attrs, true)
 
 	delete(props.Attrs, "bp2build_available")
 
@@ -482,14 +483,14 @@
 	// TODO: this omits properties for blueprint modules (blueprint_go_binary,
 	// bootstrap_go_binary, bootstrap_go_package), which will have to be handled separately.
 	if aModule, ok := m.(android.Module); ok {
-		return extractModuleProperties(aModule.GetProperties())
+		return extractModuleProperties(aModule.GetProperties(), false)
 	}
 
 	return BazelAttributes{}
 }
 
 // Generically extract module properties and types into a map, keyed by the module property name.
-func extractModuleProperties(props []interface{}) BazelAttributes {
+func extractModuleProperties(props []interface{}, checkForDuplicateProperties bool) BazelAttributes {
 	ret := map[string]string{}
 
 	// Iterate over this android.Module's property structs.
@@ -503,6 +504,11 @@
 		if isStructPtr(propertiesValue.Type()) {
 			structValue := propertiesValue.Elem()
 			for k, v := range extractStructProperties(structValue, 0) {
+				if existing, exists := ret[k]; checkForDuplicateProperties && exists {
+					panic(fmt.Errorf(
+						"%s (%v) is present in properties whereas it should be consolidated into a commonAttributes",
+						k, existing))
+				}
 				ret[k] = v
 			}
 		} else {
diff --git a/bp2build/build_conversion_test.go b/bp2build/build_conversion_test.go
index e904627..f14574c 100644
--- a/bp2build/build_conversion_test.go
+++ b/bp2build/build_conversion_test.go
@@ -15,10 +15,12 @@
 package bp2build
 
 import (
-	"android/soong/android"
 	"fmt"
 	"strings"
 	"testing"
+
+	"android/soong/android"
+	"android/soong/python"
 )
 
 func TestGenerateSoongModuleTargets(t *testing.T) {
@@ -1215,3 +1217,133 @@
 		}
 	}
 }
+
+func TestCommonBp2BuildModuleAttrs(t *testing.T) {
+	testCases := []bp2buildTestCase{
+		{
+			description:                        "Required into data test",
+			moduleTypeUnderTest:                "filegroup",
+			moduleTypeUnderTestFactory:         android.FileGroupFactory,
+			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
+			blueprint: `filegroup {
+    name: "reqd",
+}
+
+filegroup {
+    name: "fg_foo",
+    required: ["reqd"],
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{`filegroup(
+    name = "fg_foo",
+    data = [":reqd"],
+)`,
+				`filegroup(
+    name = "reqd",
+)`,
+			},
+		},
+		{
+			description:                        "Required via arch into data test",
+			moduleTypeUnderTest:                "python_library",
+			moduleTypeUnderTestFactory:         python.PythonLibraryFactory,
+			moduleTypeUnderTestBp2BuildMutator: python.PythonLibraryBp2Build,
+			blueprint: `python_library {
+    name: "reqdx86",
+    bazel_module: { bp2build_available: false, },
+}
+
+python_library {
+    name: "reqdarm",
+    bazel_module: { bp2build_available: false, },
+}
+
+python_library {
+    name: "fg_foo",
+    arch: {
+			 arm: {
+				 required: ["reqdarm"],
+			 },
+			 x86: {
+				 required: ["reqdx86"],
+			 },
+    },
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{`py_library(
+    name = "fg_foo",
+    data = select({
+        "//build/bazel/platforms/arch:arm": [":reqdarm"],
+        "//build/bazel/platforms/arch:x86": [":reqdx86"],
+        "//conditions:default": [],
+    }),
+    srcs_version = "PY3",
+)`,
+			},
+		},
+		{
+			description:                        "Required appended to data test",
+			moduleTypeUnderTest:                "python_library",
+			moduleTypeUnderTestFactory:         python.PythonLibraryFactory,
+			moduleTypeUnderTestBp2BuildMutator: python.PythonLibraryBp2Build,
+			blueprint: `python_library {
+    name: "reqd",
+    srcs: ["src.py"],
+}
+
+python_library {
+    name: "fg_foo",
+    data: ["data.bin"],
+    required: ["reqd"],
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				`py_library(
+    name = "fg_foo",
+    data = [
+        "data.bin",
+        ":reqd",
+    ],
+    srcs_version = "PY3",
+)`,
+				`py_library(
+    name = "reqd",
+    srcs = ["src.py"],
+    srcs_version = "PY3",
+)`,
+			},
+			filesystem: map[string]string{
+				"data.bin": "",
+				"src.py":   "",
+			},
+		},
+		{
+			description:                        "All props-to-attrs at once together test",
+			moduleTypeUnderTest:                "filegroup",
+			moduleTypeUnderTestFactory:         android.FileGroupFactory,
+			moduleTypeUnderTestBp2BuildMutator: android.FilegroupBp2Build,
+			blueprint: `filegroup {
+    name: "reqd"
+}
+filegroup {
+    name: "fg_foo",
+    required: ["reqd"],
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{
+				`filegroup(
+    name = "fg_foo",
+    data = [":reqd"],
+)`,
+				`filegroup(
+    name = "reqd",
+)`,
+			},
+			filesystem: map[string]string{},
+		},
+	}
+
+	for _, test := range testCases {
+		runBp2BuildTestCaseSimple(t, test)
+	}
+}
diff --git a/bp2build/cc_library_conversion_test.go b/bp2build/cc_library_conversion_test.go
index 5952b15..0d65822 100644
--- a/bp2build/cc_library_conversion_test.go
+++ b/bp2build/cc_library_conversion_test.go
@@ -131,12 +131,13 @@
         "//build/bazel/platforms/arch:x86_64": ["x86_64.cpp"],
         "//conditions:default": [],
     }) + select({
-        "//build/bazel/platforms/os:android": ["android.cpp"],
+        "//build/bazel/platforms/os:android": [
+            "android.cpp",
+            "bionic.cpp",
+        ],
         "//build/bazel/platforms/os:darwin": ["darwin.cpp"],
         "//build/bazel/platforms/os:linux": ["linux.cpp"],
-        "//conditions:default": [],
-    }) + select({
-        "//build/bazel/platforms/os:bionic": ["bionic.cpp"],
+        "//build/bazel/platforms/os:linux_bionic": ["bionic.cpp"],
         "//conditions:default": [],
     }),
 )`}})
@@ -1571,3 +1572,68 @@
 )`},
 	})
 }
+
+func TestCcLibraryOsSelects(t *testing.T) {
+	runCcLibraryTestCase(t, bp2buildTestCase{
+		description:                        "cc_library - selects for all os targets",
+		moduleTypeUnderTest:                "cc_library",
+		moduleTypeUnderTestFactory:         cc.LibraryFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryBp2Build,
+		filesystem:                         map[string]string{},
+		blueprint: soongCcLibraryPreamble + `
+cc_library_headers { name: "some-headers" }
+cc_library {
+    name: "foo-lib",
+    srcs: ["base.cpp"],
+    target: {
+        android: {
+            srcs: ["android.cpp"],
+        },
+        linux: {
+            srcs: ["linux.cpp"],
+        },
+        linux_glibc: {
+            srcs: ["linux_glibc.cpp"],
+        },
+        darwin: {
+            srcs: ["darwin.cpp"],
+        },
+        bionic: {
+            srcs: ["bionic.cpp"],
+        },
+        linux_musl: {
+            srcs: ["linux_musl.cpp"],
+        },
+        windows: {
+            srcs: ["windows.cpp"],
+        },
+    },
+    include_build_directory: false,
+}
+`,
+		expectedBazelTargets: []string{`cc_library(
+    name = "foo-lib",
+    srcs = ["base.cpp"] + select({
+        "//build/bazel/platforms/os:android": [
+            "android.cpp",
+            "bionic.cpp",
+            "linux.cpp",
+        ],
+        "//build/bazel/platforms/os:darwin": ["darwin.cpp"],
+        "//build/bazel/platforms/os:linux": [
+            "linux.cpp",
+            "linux_glibc.cpp",
+        ],
+        "//build/bazel/platforms/os:linux_bionic": [
+            "bionic.cpp",
+            "linux.cpp",
+        ],
+        "//build/bazel/platforms/os:linux_musl": [
+            "linux.cpp",
+            "linux_musl.cpp",
+        ],
+        "//build/bazel/platforms/os:windows": ["windows.cpp"],
+        "//conditions:default": [],
+    }),
+)`}})
+}
diff --git a/bp2build/cc_library_static_conversion_test.go b/bp2build/cc_library_static_conversion_test.go
index 91b7478..f02ce4d 100644
--- a/bp2build/cc_library_static_conversion_test.go
+++ b/bp2build/cc_library_static_conversion_test.go
@@ -1382,7 +1382,8 @@
 		expectedBazelTargets: []string{`cc_library_static(
     name = "target_bionic",
     system_dynamic_deps = select({
-        "//build/bazel/platforms/os:bionic": [":libc"],
+        "//build/bazel/platforms/os:android": [":libc"],
+        "//build/bazel/platforms/os:linux_bionic": [":libc"],
         "//conditions:default": [],
     }),
 )`},
diff --git a/bp2build/configurability.go b/bp2build/configurability.go
index 005f13d..0bcf91d 100644
--- a/bp2build/configurability.go
+++ b/bp2build/configurability.go
@@ -48,6 +48,12 @@
 		}
 	}
 
+	// if there is a select, use the base value as the conditions default value
+	if len(ret) > 0 {
+		ret[bazel.ConditionsDefaultSelectKey] = value
+		value = reflect.Zero(value.Type())
+	}
+
 	return value, []selects{ret}
 }
 
diff --git a/bp2build/conversion.go b/bp2build/conversion.go
index 0a86a79..d34a4ba 100644
--- a/bp2build/conversion.go
+++ b/bp2build/conversion.go
@@ -1,12 +1,13 @@
 package bp2build
 
 import (
-	"android/soong/android"
-	"android/soong/cc/config"
 	"fmt"
 	"reflect"
 	"strings"
 
+	"android/soong/android"
+	"android/soong/cc/config"
+
 	"github.com/google/blueprint/proptools"
 )
 
diff --git a/bp2build/prebuilt_etc_conversion_test.go b/bp2build/prebuilt_etc_conversion_test.go
index 4e25d1b..62e407b 100644
--- a/bp2build/prebuilt_etc_conversion_test.go
+++ b/bp2build/prebuilt_etc_conversion_test.go
@@ -53,3 +53,40 @@
     sub_dir = "tz",
 )`}})
 }
+
+func TestPrebuiltEtcArchVariant(t *testing.T) {
+	runPrebuiltEtcTestCase(t, bp2buildTestCase{
+		description:                        "prebuilt_etc - simple example",
+		moduleTypeUnderTest:                "prebuilt_etc",
+		moduleTypeUnderTestFactory:         etc.PrebuiltEtcFactory,
+		moduleTypeUnderTestBp2BuildMutator: etc.PrebuiltEtcBp2Build,
+		filesystem:                         map[string]string{},
+		blueprint: `
+prebuilt_etc {
+    name: "apex_tz_version",
+    src: "version/tz_version",
+    filename: "tz_version",
+    sub_dir: "tz",
+    installable: false,
+    arch: {
+      arm: {
+        src: "arm",
+      },
+      arm64: {
+        src: "arm64",
+      },
+    }
+}
+`,
+		expectedBazelTargets: []string{`prebuilt_etc(
+    name = "apex_tz_version",
+    filename = "tz_version",
+    installable = False,
+    src = select({
+        "//build/bazel/platforms/arch:arm": "arm",
+        "//build/bazel/platforms/arch:arm64": "arm64",
+        "//conditions:default": "version/tz_version",
+    }),
+    sub_dir = "tz",
+)`}})
+}
diff --git a/bp2build/python_binary_conversion_test.go b/bp2build/python_binary_conversion_test.go
index 6f6fc11..5b4829e 100644
--- a/bp2build/python_binary_conversion_test.go
+++ b/bp2build/python_binary_conversion_test.go
@@ -116,3 +116,37 @@
 		},
 	})
 }
+
+func TestPythonBinaryHostArchVariance(t *testing.T) {
+	runBp2BuildTestCaseSimple(t, bp2buildTestCase{
+		description:                        "test arch variants",
+		moduleTypeUnderTest:                "python_binary_host",
+		moduleTypeUnderTestFactory:         python.PythonBinaryHostFactory,
+		moduleTypeUnderTestBp2BuildMutator: python.PythonBinaryBp2Build,
+		filesystem: map[string]string{
+			"dir/arm.py": "",
+			"dir/x86.py": "",
+		},
+		blueprint: `python_binary_host {
+					 name: "foo-arm",
+					 arch: {
+						 arm: {
+							 srcs: ["arm.py"],
+						 },
+						 x86: {
+							 srcs: ["x86.py"],
+						 },
+					},
+				 }`,
+		expectedBazelTargets: []string{
+			`py_binary(
+    name = "foo-arm",
+    srcs = select({
+        "//build/bazel/platforms/arch:arm": ["arm.py"],
+        "//build/bazel/platforms/arch:x86": ["x86.py"],
+        "//conditions:default": [],
+    }),
+)`,
+		},
+	})
+}
diff --git a/bp2build/python_library_conversion_test.go b/bp2build/python_library_conversion_test.go
index b6f45e5..7f983ad 100644
--- a/bp2build/python_library_conversion_test.go
+++ b/bp2build/python_library_conversion_test.go
@@ -154,3 +154,54 @@
 		},
 	})
 }
+
+func TestPythonLibraryArchVariance(t *testing.T) {
+	testPythonArchVariance(t, "python_library", "py_library",
+		python.PythonLibraryFactory, python.PythonLibraryBp2Build,
+		func(ctx android.RegistrationContext) {})
+}
+
+func TestPythonLibraryHostArchVariance(t *testing.T) {
+	testPythonArchVariance(t, "python_library_host", "py_library",
+		python.PythonLibraryHostFactory, python.PythonLibraryHostBp2Build,
+		func(ctx android.RegistrationContext) {})
+}
+
+// TODO: refactor python_binary_conversion_test to use this
+func testPythonArchVariance(t *testing.T, modType, bazelTarget string,
+	factory android.ModuleFactory, mutator PythonLibBp2Build,
+	registration func(ctx android.RegistrationContext)) {
+	t.Helper()
+	runBp2BuildTestCase(t, registration, bp2buildTestCase{
+		description:                        fmt.Sprintf("test %s arch variants", modType),
+		moduleTypeUnderTest:                modType,
+		moduleTypeUnderTestFactory:         factory,
+		moduleTypeUnderTestBp2BuildMutator: mutator,
+		filesystem: map[string]string{
+			"dir/arm.py": "",
+			"dir/x86.py": "",
+		},
+		blueprint: fmt.Sprintf(`%s {
+					 name: "foo",
+					 arch: {
+						 arm: {
+							 srcs: ["arm.py"],
+						 },
+						 x86: {
+							 srcs: ["x86.py"],
+						 },
+					},
+				 }`, modType),
+		expectedBazelTargets: []string{
+			fmt.Sprintf(`%s(
+    name = "foo",
+    srcs = select({
+        "//build/bazel/platforms/arch:arm": ["arm.py"],
+        "//build/bazel/platforms/arch:x86": ["x86.py"],
+        "//conditions:default": [],
+    }),
+    srcs_version = "PY3",
+)`, bazelTarget),
+		},
+	})
+}
diff --git a/bp2build/testing.go b/bp2build/testing.go
index 1e7e53c..6c322ee 100644
--- a/bp2build/testing.go
+++ b/bp2build/testing.go
@@ -135,17 +135,14 @@
 		android.FailIfErrored(t, errs)
 	}
 	if actualCount, expectedCount := len(bazelTargets), len(tc.expectedBazelTargets); actualCount != expectedCount {
-		t.Errorf("%s: Expected %d bazel target, got %d; %v",
-			tc.description, expectedCount, actualCount, bazelTargets)
+		t.Errorf("%s: Expected %d bazel target, got `%d``",
+			tc.description, expectedCount, actualCount)
 	} else {
 		for i, target := range bazelTargets {
 			if w, g := tc.expectedBazelTargets[i], target.content; w != g {
 				t.Errorf(
-					"%s: Expected generated Bazel target to be '%s', got '%s'",
-					tc.description,
-					w,
-					g,
-				)
+					"%s: Expected generated Bazel target to be `%s`, got `%s`",
+					tc.description, w, g)
 			}
 		}
 	}
@@ -312,7 +309,7 @@
 			Rule_class: "custom",
 		}
 
-		ctx.CreateBazelTargetModule(m.Name(), props, attrs)
+		ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs)
 	}
 }
 
@@ -331,19 +328,19 @@
 			Rule_class:        "my_library",
 			Bzl_load_location: "//build/bazel/rules:rules.bzl",
 		}
-		ctx.CreateBazelTargetModule(baseName, myLibraryProps, attrs)
+		ctx.CreateBazelTargetModule(myLibraryProps, android.CommonAttributes{Name: baseName}, attrs)
 
 		protoLibraryProps := bazel.BazelTargetModuleProperties{
 			Rule_class:        "proto_library",
 			Bzl_load_location: "//build/bazel/rules:proto.bzl",
 		}
-		ctx.CreateBazelTargetModule(baseName+"_proto_library_deps", protoLibraryProps, attrs)
+		ctx.CreateBazelTargetModule(protoLibraryProps, android.CommonAttributes{Name: baseName + "_proto_library_deps"}, attrs)
 
 		myProtoLibraryProps := bazel.BazelTargetModuleProperties{
 			Rule_class:        "my_proto_library",
 			Bzl_load_location: "//build/bazel/rules:proto.bzl",
 		}
-		ctx.CreateBazelTargetModule(baseName+"_my_proto_library_deps", myProtoLibraryProps, attrs)
+		ctx.CreateBazelTargetModule(myProtoLibraryProps, android.CommonAttributes{Name: baseName + "_my_proto_library_deps"}, attrs)
 	}
 }
 
diff --git a/cc/androidmk.go b/cc/androidmk.go
index cd52363..93283d0 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -294,9 +294,6 @@
 			if library.buildStubs() {
 				entries.SetBool("LOCAL_NO_NOTICE_FILE", true)
 			}
-			if library.apiListCoverageXmlPath.String() != "" {
-				entries.SetString("SOONG_CC_API_XML", "$(SOONG_CC_API_XML) "+library.apiListCoverageXmlPath.String())
-			}
 		})
 	}
 	// If a library providing a stub is included in an APEX, the private APIs of the library
@@ -394,6 +391,8 @@
 		if Bool(test.Properties.Test_options.Unit_test) {
 			entries.SetBool("LOCAL_IS_UNIT_TEST", true)
 		}
+
+		entries.SetBoolIfTrue("LOCAL_COMPATIBILITY_PER_TESTCASE_DIRECTORY", Bool(test.Properties.Per_testcase_directory))
 	})
 
 	AndroidMkWriteTestData(test.data, entries)
diff --git a/cc/cc.go b/cc/cc.go
index 57e7887..b410834 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -95,6 +95,7 @@
 
 	// Used for data dependencies adjacent to tests
 	DataLibs []string
+	DataBins []string
 
 	// Used by DepsMutator to pass system_shared_libs information to check_elf_file.py.
 	SystemSharedLibs []string
@@ -718,6 +719,7 @@
 	staticVariantTag      = dependencyTag{name: "static variant"}
 	vndkExtDepTag         = dependencyTag{name: "vndk extends"}
 	dataLibDepTag         = dependencyTag{name: "data lib"}
+	dataBinDepTag         = dependencyTag{name: "data bin"}
 	runtimeDepTag         = installDependencyTag{name: "runtime lib"}
 	testPerSrcDepTag      = dependencyTag{name: "test_per_src"}
 	stubImplDepTag        = dependencyTag{name: "stub_impl"}
@@ -2274,6 +2276,8 @@
 		{Mutator: "link", Variation: "shared"},
 	}, dataLibDepTag, deps.DataLibs...)
 
+	actx.AddVariationDependencies(nil, dataBinDepTag, deps.DataBins...)
+
 	actx.AddVariationDependencies([]blueprint.Variation{
 		{Mutator: "link", Variation: "shared"},
 	}, runtimeDepTag, deps.RuntimeLibs...)
diff --git a/cc/genrule.go b/cc/genrule.go
index 9df5228..239064f 100644
--- a/cc/genrule.go
+++ b/cc/genrule.go
@@ -15,6 +15,8 @@
 package cc
 
 import (
+	"fmt"
+
 	"android/soong/android"
 	"android/soong/genrule"
 	"android/soong/snapshot"
@@ -36,13 +38,23 @@
 
 // cc_genrule is a genrule that can depend on other cc_* objects.
 // The cmd may be run multiple times, once for each of the different arch/etc
-// variations.
+// variations.  The following environment variables will be set when the command
+// execute:
+//
+//   CC_ARCH           the name of the architecture the command is being executed for
+//
+//   CC_MULTILIB       "lib32" if the architecture the command is being executed for is 32-bit,
+//                     "lib64" if it is 64-bit.
+//
+//   CC_NATIVE_BRIDGE  the name of the subdirectory that native bridge libraries are stored in if
+//                     the architecture has native bridge enabled, empty if it is disabled.
 func GenRuleFactory() android.Module {
 	module := genrule.NewGenRule()
 
 	extra := &GenruleExtraProperties{}
 	module.Extra = extra
 	module.ImageInterface = extra
+	module.CmdModifier = genruleCmdModifier
 	module.AddProperties(module.Extra)
 
 	android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibBoth)
@@ -53,6 +65,13 @@
 	return module
 }
 
+func genruleCmdModifier(ctx android.ModuleContext, cmd string) string {
+	target := ctx.Target()
+	arch := target.Arch.ArchType
+	return fmt.Sprintf("CC_ARCH=%s CC_NATIVE_BRIDGE=%s CC_MULTILIB=%s && %s",
+		arch.Name, target.NativeBridgeRelativePath, arch.Multilib, cmd)
+}
+
 var _ android.ImageInterface = (*GenruleExtraProperties)(nil)
 
 func (g *GenruleExtraProperties) ImageMutatorBegin(ctx android.BaseModuleContext) {}
diff --git a/cc/genrule_test.go b/cc/genrule_test.go
index b6afb05..f25f704 100644
--- a/cc/genrule_test.go
+++ b/cc/genrule_test.go
@@ -115,3 +115,75 @@
 		t.Errorf(`want inputs %v, got %v`, expected, got)
 	}
 }
+
+func TestCmdPrefix(t *testing.T) {
+	bp := `
+		cc_genrule {
+			name: "gen",
+			cmd: "echo foo",
+			out: ["out"],
+			native_bridge_supported: true,
+		}
+		`
+
+	testCases := []struct {
+		name     string
+		variant  string
+		preparer android.FixturePreparer
+
+		arch         string
+		nativeBridge string
+		multilib     string
+	}{
+		{
+			name:     "arm",
+			variant:  "android_arm_armv7-a-neon",
+			arch:     "arm",
+			multilib: "lib32",
+		},
+		{
+			name:     "arm64",
+			variant:  "android_arm64_armv8-a",
+			arch:     "arm64",
+			multilib: "lib64",
+		},
+		{
+			name:    "nativebridge",
+			variant: "android_native_bridge_arm_armv7-a-neon",
+			preparer: android.FixtureModifyConfig(func(config android.Config) {
+				config.Targets[android.Android] = []android.Target{
+					{
+						Os:           android.Android,
+						Arch:         android.Arch{ArchType: android.X86, ArchVariant: "silvermont", Abi: []string{"armeabi-v7a"}},
+						NativeBridge: android.NativeBridgeDisabled,
+					},
+					{
+						Os:                       android.Android,
+						Arch:                     android.Arch{ArchType: android.Arm, ArchVariant: "armv7-a-neon", Abi: []string{"armeabi-v7a"}},
+						NativeBridge:             android.NativeBridgeEnabled,
+						NativeBridgeHostArchName: "x86",
+						NativeBridgeRelativePath: "arm",
+					},
+				}
+			}),
+			arch:         "arm",
+			multilib:     "lib32",
+			nativeBridge: "arm",
+		},
+	}
+
+	for _, tt := range testCases {
+		t.Run(tt.name, func(t *testing.T) {
+			result := android.GroupFixturePreparers(
+				PrepareForIntegrationTestWithCc,
+				android.OptionalFixturePreparer(tt.preparer),
+			).RunTestWithBp(t, bp)
+			gen := result.ModuleForTests("gen", tt.variant)
+			sboxProto := android.RuleBuilderSboxProtoForTests(t, gen.Output("genrule.sbox.textproto"))
+			cmd := *sboxProto.Commands[0].Command
+			android.AssertStringDoesContain(t, "incorrect CC_ARCH", cmd, "CC_ARCH="+tt.arch+" ")
+			android.AssertStringDoesContain(t, "incorrect CC_NATIVE_BRIDGE", cmd, "CC_NATIVE_BRIDGE="+tt.nativeBridge+" ")
+			android.AssertStringDoesContain(t, "incorrect CC_MULTILIB", cmd, "CC_MULTILIB="+tt.multilib+" ")
+		})
+	}
+}
diff --git a/cc/library.go b/cc/library.go
index de9d01e..77eddbf 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -345,7 +345,7 @@
 		Bzl_load_location: "//build/bazel/rules:full_cc_library.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(m.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs)
 }
 
 // cc_library creates both static and/or shared libraries for a device and/or
@@ -956,7 +956,7 @@
 			nativeAbiResult.versionScript)
 
 		// Parse symbol file to get API list for coverage
-		if library.stubsVersion() == "current" && ctx.PrimaryArch() {
+		if library.stubsVersion() == "current" && ctx.PrimaryArch() && !ctx.inRecovery() && !ctx.inProduct() && !ctx.inVendor() {
 			library.apiListCoverageXmlPath = parseSymbolFileForAPICoverage(ctx, symbolFile)
 		}
 
@@ -1035,6 +1035,8 @@
 	androidMkWriteAdditionalDependenciesForSourceAbiDiff(w io.Writer)
 
 	availableFor(string) bool
+
+	getAPIListCoverageXMLPath() android.ModuleOutPath
 }
 
 type versionedInterface interface {
@@ -1971,6 +1973,10 @@
 	mod.ModuleBase.MakeUninstallable()
 }
 
+func (library *libraryDecorator) getAPIListCoverageXMLPath() android.ModuleOutPath {
+	return library.apiListCoverageXmlPath
+}
+
 var versioningMacroNamesListKey = android.NewOnceKey("versioningMacroNamesList")
 
 // versioningMacroNamesList returns a singleton map, where keys are "version macro names",
@@ -2434,7 +2440,7 @@
 		Bzl_load_location: fmt.Sprintf("//build/bazel/rules:%s.bzl", modType),
 	}
 
-	ctx.CreateBazelTargetModule(module.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: module.Name()}, attrs)
 }
 
 // TODO(b/199902614): Can this be factored to share with the other Attributes?
diff --git a/cc/library_headers.go b/cc/library_headers.go
index cabeb01..51c1eb8 100644
--- a/cc/library_headers.go
+++ b/cc/library_headers.go
@@ -147,5 +147,5 @@
 		Bzl_load_location: "//build/bazel/rules:cc_library_headers.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(module.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: module.Name()}, attrs)
 }
diff --git a/cc/object.go b/cc/object.go
index 4ec2b19..d8bb08f 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -206,7 +206,7 @@
 		Bzl_load_location: "//build/bazel/rules:cc_object.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(m.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs)
 }
 
 func (object *objectLinker) appendLdflags(flags []string) {
diff --git a/cc/stub_library.go b/cc/stub_library.go
index 1722c80..76da782 100644
--- a/cc/stub_library.go
+++ b/cc/stub_library.go
@@ -15,6 +15,7 @@
 package cc
 
 import (
+	"sort"
 	"strings"
 
 	"android/soong/android"
@@ -27,6 +28,8 @@
 
 type stubLibraries struct {
 	stubLibraryMap map[string]bool
+
+	apiListCoverageXmlPaths []string
 }
 
 // Check if the module defines stub, or itself is stub
@@ -53,6 +56,11 @@
 					s.stubLibraryMap[name] = true
 				}
 			}
+			if m.library != nil {
+				if p := m.library.getAPIListCoverageXMLPath().String(); p != "" {
+					s.apiListCoverageXmlPaths = append(s.apiListCoverageXmlPaths, p)
+				}
+			}
 		}
 	})
 }
@@ -66,4 +74,8 @@
 func (s *stubLibraries) MakeVars(ctx android.MakeVarsContext) {
 	// Convert stub library file names into Makefile variable.
 	ctx.Strict("STUB_LIBRARIES", strings.Join(android.SortedStringKeys(s.stubLibraryMap), " "))
+
+	// Export the list of API XML files to Make.
+	sort.Strings(s.apiListCoverageXmlPaths)
+	ctx.Strict("SOONG_CC_API_XML", strings.Join(s.apiListCoverageXmlPaths, " "))
 }
diff --git a/cc/test.go b/cc/test.go
index 047a69e..c589165 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -80,6 +80,9 @@
 	// list of shared library modules that should be installed alongside the test
 	Data_libs []string `android:"arch_variant"`
 
+	// list of binary modules that should be installed alongside the test
+	Data_bins []string `android:"arch_variant"`
+
 	// list of compatibility suites (for example "cts", "vts") that the module should be
 	// installed into.
 	Test_suites []string `android:"arch_variant"`
@@ -115,6 +118,9 @@
 	// Add parameterized mainline modules to auto generated test config. The options will be
 	// handled by TradeFed to download and install the specified modules on the device.
 	Test_mainline_modules []string
+
+	// Install the test into a folder named for the module in all test suites.
+	Per_testcase_directory *bool
 }
 
 func init() {
@@ -347,6 +353,7 @@
 	deps = test.testDecorator.linkerDeps(ctx, deps)
 	deps = test.binaryDecorator.linkerDeps(ctx, deps)
 	deps.DataLibs = append(deps.DataLibs, test.Properties.Data_libs...)
+	deps.DataBins = append(deps.DataBins, test.Properties.Data_bins...)
 	return deps
 }
 
@@ -386,6 +393,18 @@
 					RelativeInstallPath: ccModule.installer.relativeInstallPath()})
 		}
 	})
+	ctx.VisitDirectDepsWithTag(dataBinDepTag, func(dep android.Module) {
+		depName := ctx.OtherModuleName(dep)
+		ccModule, ok := dep.(*Module)
+		if !ok {
+			ctx.ModuleErrorf("data_bin %q is not a cc module", depName)
+		}
+		if ccModule.OutputFile().Valid() {
+			test.data = append(test.data,
+				android.DataPath{SrcPath: ccModule.OutputFile().Path(),
+					RelativeInstallPath: ccModule.installer.relativeInstallPath()})
+		}
+	})
 
 	var configs []tradefed.Config
 	for _, module := range test.Properties.Test_mainline_modules {
diff --git a/etc/prebuilt_etc.go b/etc/prebuilt_etc.go
index 85abf59..a1ce8dc 100644
--- a/etc/prebuilt_etc.go
+++ b/etc/prebuilt_etc.go
@@ -673,8 +673,16 @@
 
 func prebuiltEtcBp2BuildInternal(ctx android.TopDownMutatorContext, module *PrebuiltEtc) {
 	var srcLabelAttribute bazel.LabelAttribute
-	if module.properties.Src != nil {
-		srcLabelAttribute.SetValue(android.BazelLabelForModuleSrcSingle(ctx, *module.properties.Src))
+	for axis, configToProps := range module.GetArchVariantProperties(ctx, &prebuiltEtcProperties{}) {
+		for config, p := range configToProps {
+			props, ok := p.(*prebuiltEtcProperties)
+			if !ok {
+				continue
+			}
+			if props.Src != nil {
+				srcLabelAttribute.SetSelectValue(axis, config, android.BazelLabelForModuleSrcSingle(ctx, *props.Src))
+			}
+		}
 	}
 
 	var filename string
@@ -704,5 +712,5 @@
 		Bzl_load_location: "//build/bazel/rules:prebuilt_etc.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(module.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: module.Name()}, attrs)
 }
diff --git a/genrule/genrule.go b/genrule/genrule.go
index f4bde70..4dd2135 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -156,6 +156,11 @@
 	// For other packages to make their own genrules with extra
 	// properties
 	Extra interface{}
+
+	// CmdModifier can be set by wrappers around genrule to modify the command, for example to
+	// prefix environment variables to it.
+	CmdModifier func(ctx android.ModuleContext, cmd string) string
+
 	android.ImageInterface
 
 	properties generatorProperties
@@ -398,8 +403,13 @@
 	var outputFiles android.WritablePaths
 	var zipArgs strings.Builder
 
+	cmd := String(g.properties.Cmd)
+	if g.CmdModifier != nil {
+		cmd = g.CmdModifier(ctx, cmd)
+	}
+
 	// Generate tasks, either from genrule or gensrcs.
-	for _, task := range g.taskGenerator(ctx, String(g.properties.Cmd), srcFiles) {
+	for _, task := range g.taskGenerator(ctx, cmd, srcFiles) {
 		if len(task.out) == 0 {
 			ctx.ModuleErrorf("must have at least one output file")
 			return
@@ -918,7 +928,7 @@
 	}
 
 	// Create the BazelTargetModule.
-	ctx.CreateBazelTargetModule(m.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs)
 }
 
 var Bool = proptools.Bool
diff --git a/java/androidmk.go b/java/androidmk.go
index 1914595..537159e 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -71,23 +71,7 @@
 		entriesList = append(entriesList, android.AndroidMkEntries{Disabled: true})
 	} else if !library.ApexModuleBase.AvailableFor(android.AvailableToPlatform) {
 		// Platform variant.  If not available for the platform, we don't need Make module.
-		// May still need to add some additional dependencies.
-		checkedModulePaths := library.additionalCheckedModules
-		if len(checkedModulePaths) != 0 {
-			entriesList = append(entriesList, android.AndroidMkEntries{
-				Class: "FAKE",
-				// Need at least one output file in order for this to take effect.
-				OutputFile: android.OptionalPathForPath(checkedModulePaths[0]),
-				Include:    "$(BUILD_PHONY_PACKAGE)",
-				ExtraEntries: []android.AndroidMkExtraEntriesFunc{
-					func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-						entries.AddStrings("LOCAL_ADDITIONAL_CHECKED_MODULE", checkedModulePaths.Strings()...)
-					},
-				},
-			})
-		} else {
-			entriesList = append(entriesList, android.AndroidMkEntries{Disabled: true})
-		}
+		entriesList = append(entriesList, android.AndroidMkEntries{Disabled: true})
 	} else {
 		entriesList = append(entriesList, android.AndroidMkEntries{
 			Class:      "JAVA_LIBRARIES",
@@ -123,10 +107,6 @@
 					requiredUsesLibs, optionalUsesLibs := library.classLoaderContexts.UsesLibs()
 					entries.AddStrings("LOCAL_EXPORT_SDK_LIBRARIES", append(requiredUsesLibs, optionalUsesLibs...)...)
 
-					if len(library.additionalCheckedModules) != 0 {
-						entries.AddStrings("LOCAL_ADDITIONAL_CHECKED_MODULE", library.additionalCheckedModules.Strings()...)
-					}
-
 					entries.SetOptionalPath("LOCAL_SOONG_PROGUARD_DICT", library.dexer.proguardDictionary)
 					entries.SetOptionalPath("LOCAL_SOONG_PROGUARD_USAGE_ZIP", library.dexer.proguardUsageZip)
 					entries.SetString("LOCAL_MODULE_STEM", library.Stem())
@@ -147,20 +127,21 @@
 }
 
 // Called for modules that are a component of a test suite.
-func testSuiteComponent(entries *android.AndroidMkEntries, test_suites []string) {
+func testSuiteComponent(entries *android.AndroidMkEntries, test_suites []string, perTestcaseDirectory bool) {
 	entries.SetString("LOCAL_MODULE_TAGS", "tests")
 	if len(test_suites) > 0 {
 		entries.AddCompatibilityTestSuites(test_suites...)
 	} else {
 		entries.AddCompatibilityTestSuites("null-suite")
 	}
+	entries.SetBoolIfTrue("LOCAL_COMPATIBILITY_PER_TESTCASE_DIRECTORY", perTestcaseDirectory)
 }
 
 func (j *Test) AndroidMkEntries() []android.AndroidMkEntries {
 	entriesList := j.Library.AndroidMkEntries()
 	entries := &entriesList[0]
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		testSuiteComponent(entries, j.testProperties.Test_suites)
+		testSuiteComponent(entries, j.testProperties.Test_suites, Bool(j.testProperties.Per_testcase_directory))
 		if j.testConfig != nil {
 			entries.SetPath("LOCAL_FULL_TEST_CONFIG", j.testConfig)
 		}
@@ -188,7 +169,7 @@
 	entriesList := j.Library.AndroidMkEntries()
 	entries := &entriesList[0]
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		testSuiteComponent(entries, j.testHelperLibraryProperties.Test_suites)
+		testSuiteComponent(entries, j.testHelperLibraryProperties.Test_suites, Bool(j.testHelperLibraryProperties.Per_testcase_directory))
 	})
 
 	return entriesList
@@ -450,7 +431,7 @@
 	entriesList := a.AndroidApp.AndroidMkEntries()
 	entries := &entriesList[0]
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		testSuiteComponent(entries, a.testProperties.Test_suites)
+		testSuiteComponent(entries, a.testProperties.Test_suites, Bool(a.testProperties.Per_testcase_directory))
 		if a.testConfig != nil {
 			entries.SetPath("LOCAL_FULL_TEST_CONFIG", a.testConfig)
 		}
@@ -466,7 +447,7 @@
 	entriesList := a.AndroidApp.AndroidMkEntries()
 	entries := &entriesList[0]
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		testSuiteComponent(entries, a.appTestHelperAppProperties.Test_suites)
+		testSuiteComponent(entries, a.appTestHelperAppProperties.Test_suites, Bool(a.appTestHelperAppProperties.Per_testcase_directory))
 		// introduce a flag variable to control the generation of the .config file
 		entries.SetString("LOCAL_DISABLE_TEST_CONFIG", "true")
 	})
@@ -677,7 +658,7 @@
 	entriesList := a.AndroidAppImport.AndroidMkEntries()
 	entries := &entriesList[0]
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-		testSuiteComponent(entries, a.testProperties.Test_suites)
+		testSuiteComponent(entries, a.testProperties.Test_suites, Bool(a.testProperties.Per_testcase_directory))
 		androidMkWriteTestData(a.data, entries)
 	})
 	return entriesList
diff --git a/java/app.go b/java/app.go
index 2fd6463..6554d66 100755
--- a/java/app.go
+++ b/java/app.go
@@ -1070,6 +1070,9 @@
 	// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
 	// explicitly.
 	Auto_gen_config *bool
+
+	// Install the test into a folder named for the module in all test suites.
+	Per_testcase_directory *bool
 }
 
 type AndroidTestHelperApp struct {
@@ -1428,5 +1431,5 @@
 		Bzl_load_location: "//build/bazel/rules:android_app_certificate.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(module.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: module.Name()}, attrs)
 }
diff --git a/java/base.go b/java/base.go
index 579085b..ca34f2e 100644
--- a/java/base.go
+++ b/java/base.go
@@ -433,9 +433,6 @@
 	// expanded Jarjar_rules
 	expandJarjarRules android.Path
 
-	// list of additional targets for checkbuild
-	additionalCheckedModules android.Paths
-
 	// Extra files generated by the module type to be added as java resources.
 	extraResources android.Paths
 
@@ -1265,10 +1262,25 @@
 
 	// Check package restrictions if necessary.
 	if len(j.properties.Permitted_packages) > 0 {
-		// Check packages and copy to package-checked file.
+		// Time stamp file created by the package check rule.
 		pkgckFile := android.PathForModuleOut(ctx, "package-check.stamp")
+
+		// Create a rule to copy the output jar to another path and add a validate dependency that
+		// will check that the jar only contains the permitted packages. The new location will become
+		// the output file of this module.
+		inputFile := outputFile
+		outputFile = android.PathForModuleOut(ctx, "package-check", jarName).OutputPath
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   android.Cp,
+			Input:  inputFile,
+			Output: outputFile,
+			// Make sure that any dependency on the output file will cause ninja to run the package check
+			// rule.
+			Validation: pkgckFile,
+		})
+
+		// Check packages and create a timestamp file when complete.
 		CheckJarPackages(ctx, pkgckFile, outputFile, j.properties.Permitted_packages)
-		j.additionalCheckedModules = append(j.additionalCheckedModules, pkgckFile)
 
 		if ctx.Failed() {
 			return
diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go
index 79c73ca..8f18790 100644
--- a/java/bootclasspath_fragment.go
+++ b/java/bootclasspath_fragment.go
@@ -139,6 +139,74 @@
 	BootclasspathFragmentsDepsProperties
 }
 
+type SourceOnlyBootclasspathProperties struct {
+	Hidden_api struct {
+		// Contains prefixes of a package hierarchy that is provided solely by this
+		// bootclasspath_fragment.
+		//
+		// This affects the signature patterns file that is used to select the subset of monolithic
+		// hidden API flags. See split_packages property for more details.
+		Package_prefixes []string
+
+		// The list of split packages provided by this bootclasspath_fragment.
+		//
+		// A split package is one that contains classes which are provided by multiple
+		// bootclasspath_fragment modules.
+		//
+		// This defaults to "*" - which treats all packages as being split. A module that has no split
+		// packages must specify an empty list.
+		//
+		// This affects the signature patterns file that is generated by a bootclasspath_fragment and
+		// used to select the subset of monolithic hidden API flags against which the flags generated
+		// by the bootclasspath_fragment are compared.
+		//
+		// The signature patterns file selects the subset of monolithic hidden API flags using a number
+		// of patterns, i.e.:
+		// * The qualified name (including package) of an outermost class, e.g. java/lang/Character.
+		//   This selects all the flags for all the members of this class and any nested classes.
+		// * A package wildcard, e.g. java/lang/*. This selects all the flags for all the members of all
+		//   the classes in this package (but not in sub-packages).
+		// * A recursive package wildcard, e.g. java/**. This selects all the flags for all the members
+		//   of all the classes in this package and sub-packages.
+		//
+		// The signature patterns file is constructed as follows:
+		// * All the signatures are retrieved from the all-flags.csv file.
+		// * The member and inner class names are removed.
+		// * If a class is in a split package then that is kept, otherwise the class part is removed
+		//   and replaced with a wildcard, i.e. *.
+		// * If a package matches a package prefix then the package is removed.
+		// * All the package prefixes are added with a recursive wildcard appended to each, i.e. **.
+		// * The resulting patterns are sorted.
+		//
+		// So, by default (i.e. without specifying any package_prefixes or split_packages) the signature
+		// patterns is a list of class names, because there are no package packages and all packages are
+		// assumed to be split.
+		//
+		// If any split packages are specified then only those packages are treated as split and all
+		// other packages are treated as belonging solely to the bootclasspath_fragment and so they use
+		// wildcard package patterns.
+		//
+		// So, if an empty list of split packages is specified then the signature patterns file just
+		// includes a wildcard package pattern for every package provided by the bootclasspath_fragment.
+		//
+		// If split_packages are specified and a package that is split is not listed then it could lead
+		// to build failures as it will select monolithic flags that are generated by another
+		// bootclasspath_fragment to compare against the flags provided by this fragment. The latter
+		// will obviously not contain those flags and that can cause the comparison and build to fail.
+		//
+		// If any package prefixes are specified then any matching packages are removed from the
+		// signature patterns and replaced with a single recursive package pattern.
+		//
+		// It is not strictly necessary to specify either package_prefixes or split_packages as the
+		// defaults will produce a valid set of signature patterns. However, those patterns may include
+		// implementation details, e.g. names of implementation classes or packages, which will be
+		// exported to the sdk snapshot in the signature patterns file. That is something that should be
+		// avoided where possible. Specifying package_prefixes and split_packages allows those
+		// implementation details to be excluded from the snapshot.
+		Split_packages []string
+	}
+}
+
 type BootclasspathFragmentModule struct {
 	android.ModuleBase
 	android.ApexModuleBase
@@ -147,6 +215,8 @@
 
 	properties bootclasspathFragmentProperties
 
+	sourceOnlyProperties SourceOnlyBootclasspathProperties
+
 	// Collect the module directory for IDE info in java/jdeps.go.
 	modulePaths []string
 }
@@ -180,7 +250,7 @@
 
 func bootclasspathFragmentFactory() android.Module {
 	m := &BootclasspathFragmentModule{}
-	m.AddProperties(&m.properties)
+	m.AddProperties(&m.properties, &m.sourceOnlyProperties)
 	android.InitApexModule(m)
 	android.InitSdkAwareModule(m)
 	initClasspathFragment(m, BOOTCLASSPATH)
@@ -590,7 +660,7 @@
 	// TODO(b/192868581): Remove once the source and prebuilts provide a signature patterns file of
 	//  their own.
 	if output.SignaturePatternsPath == nil {
-		output.SignaturePatternsPath = buildRuleSignaturePatternsFile(ctx, output.AllFlagsPath)
+		output.SignaturePatternsPath = buildRuleSignaturePatternsFile(ctx, output.AllFlagsPath, []string{"*"}, nil)
 	}
 
 	// Initialize a HiddenAPIInfo structure.
@@ -659,7 +729,20 @@
 func (b *BootclasspathFragmentModule) produceHiddenAPIOutput(ctx android.ModuleContext, contents []android.Module, input HiddenAPIFlagInput) *HiddenAPIOutput {
 	// Generate the rules to create the hidden API flags and update the supplied hiddenAPIInfo with the
 	// paths to the created files.
-	return hiddenAPIRulesForBootclasspathFragment(ctx, contents, input)
+	output := hiddenAPIRulesForBootclasspathFragment(ctx, contents, input)
+
+	// If the module specifies split_packages or package_prefixes then use those to generate the
+	// signature patterns.
+	splitPackages := b.sourceOnlyProperties.Hidden_api.Split_packages
+	packagePrefixes := b.sourceOnlyProperties.Hidden_api.Package_prefixes
+	if splitPackages != nil || packagePrefixes != nil {
+		if splitPackages == nil {
+			splitPackages = []string{"*"}
+		}
+		output.SignaturePatternsPath = buildRuleSignaturePatternsFile(ctx, output.AllFlagsPath, splitPackages, packagePrefixes)
+	}
+
+	return output
 }
 
 // produceBootImageFiles builds the boot image files from the source if it is required.
@@ -767,14 +850,20 @@
 	// The path to the generated index.csv file.
 	Index_path android.OptionalPath
 
-	// The path to the generated signature-patterns.csv file.
-	Signature_patterns_path android.OptionalPath
-
 	// The path to the generated stub-flags.csv file.
-	Stub_flags_path android.OptionalPath
+	Stub_flags_path android.OptionalPath `supported_build_releases:"S"`
 
 	// The path to the generated all-flags.csv file.
-	All_flags_path android.OptionalPath
+	All_flags_path android.OptionalPath `supported_build_releases:"S"`
+
+	// The path to the generated signature-patterns.csv file.
+	Signature_patterns_path android.OptionalPath `supported_build_releases:"T+"`
+
+	// The path to the generated filtered-stub-flags.csv file.
+	Filtered_stub_flags_path android.OptionalPath `supported_build_releases:"T+"`
+
+	// The path to the generated filtered-flags.csv file.
+	Filtered_flags_path android.OptionalPath `supported_build_releases:"T+"`
 }
 
 func (b *bootclasspathFragmentSdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) {
@@ -793,10 +882,13 @@
 	b.Metadata_path = android.OptionalPathForPath(hiddenAPIInfo.MetadataPath)
 	b.Index_path = android.OptionalPathForPath(hiddenAPIInfo.IndexPath)
 
-	b.Signature_patterns_path = android.OptionalPathForPath(hiddenAPIInfo.SignaturePatternsPath)
 	b.Stub_flags_path = android.OptionalPathForPath(hiddenAPIInfo.StubFlagsPath)
 	b.All_flags_path = android.OptionalPathForPath(hiddenAPIInfo.AllFlagsPath)
 
+	b.Signature_patterns_path = android.OptionalPathForPath(hiddenAPIInfo.SignaturePatternsPath)
+	b.Filtered_stub_flags_path = android.OptionalPathForPath(hiddenAPIInfo.FilteredStubFlagsPath)
+	b.Filtered_flags_path = android.OptionalPathForPath(hiddenAPIInfo.FilteredFlagsPath)
+
 	// Copy stub_libs properties.
 	b.Stub_libs = module.properties.Api.Stub_libs
 	b.Core_platform_stub_libs = module.properties.Core_platform_api.Stub_libs
@@ -861,9 +953,13 @@
 	copyOptionalPath(b.Annotation_flags_path, "annotation_flags")
 	copyOptionalPath(b.Metadata_path, "metadata")
 	copyOptionalPath(b.Index_path, "index")
-	copyOptionalPath(b.Signature_patterns_path, "signature_patterns")
+
 	copyOptionalPath(b.Stub_flags_path, "stub_flags")
 	copyOptionalPath(b.All_flags_path, "all_flags")
+
+	copyOptionalPath(b.Signature_patterns_path, "signature_patterns")
+	copyOptionalPath(b.Filtered_stub_flags_path, "filtered_stub_flags")
+	copyOptionalPath(b.Filtered_flags_path, "filtered_flags")
 }
 
 var _ android.SdkMemberType = (*bootclasspathFragmentMemberType)(nil)
@@ -889,6 +985,12 @@
 
 		// The path to the all-flags.csv file created by the bootclasspath_fragment.
 		All_flags *string `android:"path"`
+
+		// The path to the filtered-stub-flags.csv file created by the bootclasspath_fragment.
+		Filtered_stub_flags *string `android:"path"`
+
+		// The path to the filtered-flags.csv file created by the bootclasspath_fragment.
+		Filtered_flags *string `android:"path"`
 	}
 }
 
@@ -915,9 +1017,9 @@
 
 // produceHiddenAPIOutput returns a path to the prebuilt all-flags.csv or nil if none is specified.
 func (module *prebuiltBootclasspathFragmentModule) produceHiddenAPIOutput(ctx android.ModuleContext, contents []android.Module, input HiddenAPIFlagInput) *HiddenAPIOutput {
-	pathForOptionalSrc := func(src *string) android.Path {
+	pathForOptionalSrc := func(src *string, defaultPath android.Path) android.Path {
 		if src == nil {
-			return nil
+			return defaultPath
 		}
 		return android.PathForModuleSrc(ctx, *src)
 	}
@@ -938,13 +1040,19 @@
 			AnnotationFlagsPath:   pathForSrc("hidden_api.annotation_flags", module.prebuiltProperties.Hidden_api.Annotation_flags),
 			MetadataPath:          pathForSrc("hidden_api.metadata", module.prebuiltProperties.Hidden_api.Metadata),
 			IndexPath:             pathForSrc("hidden_api.index", module.prebuiltProperties.Hidden_api.Index),
-			SignaturePatternsPath: pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Signature_patterns),
-			StubFlagsPath:         pathForSrc("hidden_api.stub_flags", module.prebuiltProperties.Hidden_api.Stub_flags),
-			AllFlagsPath:          pathForSrc("hidden_api.all_flags", module.prebuiltProperties.Hidden_api.All_flags),
+			SignaturePatternsPath: pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Signature_patterns, nil),
+			// TODO: Temporarily handle stub_flags/all_flags properties until prebuilts have been updated.
+			StubFlagsPath: pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Stub_flags, nil),
+			AllFlagsPath:  pathForOptionalSrc(module.prebuiltProperties.Hidden_api.All_flags, nil),
 		},
+
 		EncodedBootDexFilesByModule: encodedBootDexJarsByModule,
 	}
 
+	// TODO: Temporarily fallback to stub_flags/all_flags properties until prebuilts have been updated.
+	output.FilteredStubFlagsPath = pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Filtered_stub_flags, output.StubFlagsPath)
+	output.FilteredFlagsPath = pathForOptionalSrc(module.prebuiltProperties.Hidden_api.Filtered_flags, output.AllFlagsPath)
+
 	return &output
 }
 
diff --git a/java/hiddenapi_modular.go b/java/hiddenapi_modular.go
index b9a1ca7..0cc960d 100644
--- a/java/hiddenapi_modular.go
+++ b/java/hiddenapi_modular.go
@@ -548,18 +548,18 @@
 	}
 }
 
-// StubFlagSubset returns a SignatureCsvSubset that contains a path to a stub-flags.csv file and a
-// path to a signature-patterns.csv file that defines a subset of the monolithic stub flags file,
-// i.e. out/soong/hiddenapi/hiddenapi-stub-flags.txt, against which it will be compared.
+// StubFlagSubset returns a SignatureCsvSubset that contains a path to a filtered-stub-flags.csv
+// file and a path to a signature-patterns.csv file that defines a subset of the monolithic stub
+// flags file, i.e. out/soong/hiddenapi/hiddenapi-stub-flags.txt, against which it will be compared.
 func (i *HiddenAPIInfo) StubFlagSubset() SignatureCsvSubset {
-	return SignatureCsvSubset{i.StubFlagsPath, i.SignaturePatternsPath}
+	return SignatureCsvSubset{i.FilteredStubFlagsPath, i.SignaturePatternsPath}
 }
 
-// FlagSubset returns a SignatureCsvSubset that contains a path to an all-flags.csv file and a
+// FlagSubset returns a SignatureCsvSubset that contains a path to a filtered-flags.csv file and a
 // path to a signature-patterns.csv file that defines a subset of the monolithic flags file, i.e.
 // out/soong/hiddenapi/hiddenapi-flags.csv, against which it will be compared.
 func (i *HiddenAPIInfo) FlagSubset() SignatureCsvSubset {
-	return SignatureCsvSubset{i.AllFlagsPath, i.SignaturePatternsPath}
+	return SignatureCsvSubset{i.FilteredFlagsPath, i.SignaturePatternsPath}
 }
 
 var HiddenAPIInfoProvider = blueprint.NewProvider(HiddenAPIInfo{})
@@ -784,9 +784,6 @@
 // HiddenAPIFlagOutput contains paths to output files from the hidden API flag generation for a
 // bootclasspath_fragment module.
 type HiddenAPIFlagOutput struct {
-	// The path to the generated stub-flags.csv file.
-	StubFlagsPath android.Path
-
 	// The path to the generated annotation-flags.csv file.
 	AnnotationFlagsPath android.Path
 
@@ -796,12 +793,21 @@
 	// The path to the generated index.csv file.
 	IndexPath android.Path
 
+	// The path to the generated stub-flags.csv file.
+	StubFlagsPath android.Path
+
 	// The path to the generated all-flags.csv file.
 	AllFlagsPath android.Path
 
 	// The path to the generated signature-patterns.txt file which defines the subset of the
 	// monolithic hidden API files provided in this.
 	SignaturePatternsPath android.Path
+
+	// The path to the generated filtered-stub-flags.csv file.
+	FilteredStubFlagsPath android.Path
+
+	// The path to the generated filtered-flags.csv file.
+	FilteredFlagsPath android.Path
 }
 
 // bootDexJarByModule is a map from base module name (without prebuilt_ prefix) to the boot dex
@@ -937,13 +943,22 @@
 
 // buildRuleSignaturePatternsFile creates a rule to generate a file containing the set of signature
 // patterns that will select a subset of the monolithic flags.
-func buildRuleSignaturePatternsFile(ctx android.ModuleContext, flagsPath android.Path) android.Path {
+func buildRuleSignaturePatternsFile(ctx android.ModuleContext, flagsPath android.Path, splitPackages []string, packagePrefixes []string) android.Path {
 	patternsFile := android.PathForModuleOut(ctx, "modular-hiddenapi", "signature-patterns.csv")
 	// Create a rule to validate the output from the following rule.
 	rule := android.NewRuleBuilder(pctx, ctx)
+
+	// Quote any * in the packages to avoid them being expanded by the shell.
+	quotedSplitPackages := []string{}
+	for _, pkg := range splitPackages {
+		quotedSplitPackages = append(quotedSplitPackages, strings.ReplaceAll(pkg, "*", "\\*"))
+	}
+
 	rule.Command().
 		BuiltTool("signature_patterns").
 		FlagWithInput("--flags ", flagsPath).
+		FlagForEachArg("--split-package ", quotedSplitPackages).
+		FlagForEachArg("--package-prefix ", packagePrefixes).
 		FlagWithOutput("--output ", patternsFile)
 	rule.Build("hiddenAPISignaturePatterns", "hidden API signature patterns")
 
@@ -1067,11 +1082,13 @@
 	// Store the paths in the info for use by other modules and sdk snapshot generation.
 	output := HiddenAPIOutput{
 		HiddenAPIFlagOutput: HiddenAPIFlagOutput{
-			StubFlagsPath:       filteredStubFlagsCSV,
-			AnnotationFlagsPath: annotationFlagsCSV,
-			MetadataPath:        metadataCSV,
-			IndexPath:           indexCSV,
-			AllFlagsPath:        filteredFlagsCSV,
+			AnnotationFlagsPath:   annotationFlagsCSV,
+			MetadataPath:          metadataCSV,
+			IndexPath:             indexCSV,
+			StubFlagsPath:         stubFlagsCSV,
+			AllFlagsPath:          allFlagsCSV,
+			FilteredStubFlagsPath: filteredStubFlagsCSV,
+			FilteredFlagsPath:     filteredFlagsCSV,
 		},
 		EncodedBootDexFilesByModule: encodedBootDexJarsByModule,
 	}
diff --git a/java/java.go b/java/java.go
index 23809df..287bcfa 100644
--- a/java/java.go
+++ b/java/java.go
@@ -781,6 +781,9 @@
 
 	// Names of modules containing JNI libraries that should be installed alongside the test.
 	Jni_libs []string
+
+	// Install the test into a folder named for the module in all test suites.
+	Per_testcase_directory *bool
 }
 
 type hostTestProperties struct {
@@ -792,6 +795,9 @@
 	// list of compatibility suites (for example "cts", "vts") that the module should be
 	// installed into.
 	Test_suites []string `android:"arch_variant"`
+
+	// Install the test into a folder named for the module in all test suites.
+	Per_testcase_directory *bool
 }
 
 type prebuiltTestProperties struct {
@@ -1392,10 +1398,19 @@
 			if dexOutputPath := di.PrebuiltExportPath(apexRootRelativePathToJavaLib(j.BaseModuleName())); dexOutputPath != nil {
 				dexJarFile := makeDexJarPathFromPath(dexOutputPath)
 				j.dexJarFile = dexJarFile
-				j.dexJarInstallFile = android.PathForModuleInPartitionInstall(ctx, "apex", ai.ApexVariationName, apexRootRelativePathToJavaLib(j.BaseModuleName()))
+				installPath := android.PathForModuleInPartitionInstall(ctx, "apex", ai.ApexVariationName, apexRootRelativePathToJavaLib(j.BaseModuleName()))
+				j.dexJarInstallFile = installPath
 
 				// Initialize the hiddenapi structure.
 				j.initHiddenAPI(ctx, dexJarFile, outputFile, nil)
+
+				j.dexpreopter.installPath = j.dexpreopter.getInstallPath(ctx, installPath)
+				if j.dexProperties.Uncompress_dex == nil {
+					// If the value was not force-set by the user, use reasonable default based on the module.
+					j.dexProperties.Uncompress_dex = proptools.BoolPtr(shouldUncompressDex(ctx, &j.dexpreopter))
+				}
+				j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex
+				j.dexpreopt(ctx, dexOutputPath)
 			} else {
 				// This should never happen as a variant for a prebuilt_apex is only created if the
 				// prebuilt_apex has been configured to export the java library dex file.
diff --git a/java/systemserver_classpath_fragment.go b/java/systemserver_classpath_fragment.go
index 1b4fda8..f209f4a 100644
--- a/java/systemserver_classpath_fragment.go
+++ b/java/systemserver_classpath_fragment.go
@@ -123,7 +123,7 @@
 
 	// For non test apexes, make sure that all contents are actually declared in make.
 	if global.ApexSystemServerJars.Len() > 0 && len(unknown) > 0 && !android.IsModuleInVersionedSdk(ctx.Module()) {
-		ctx.ModuleErrorf("%s in contents must also be declared in PRODUCT_UPDATABLE_SYSTEM_SERVER_JARS", unknown)
+		ctx.ModuleErrorf("%s in contents must also be declared in PRODUCT_APEX_SYSTEM_SERVER_JARS", unknown)
 	}
 
 	return jars
diff --git a/mk2rbc/cmd/mk2rbc.go b/mk2rbc/cmd/mk2rbc.go
index 7b5f298..d8e7018 100644
--- a/mk2rbc/cmd/mk2rbc.go
+++ b/mk2rbc/cmd/mk2rbc.go
@@ -154,47 +154,39 @@
 	}
 
 	// Convert!
-	ok := true
-	if *launcher != "" {
-		if len(flag.Args()) != 1 {
-			quit(fmt.Errorf("a launcher can be generated only for a single product"))
-		}
-		product := flag.Args()[0]
+	files := flag.Args()
+	if *allInSource {
 		productConfigMap := buildProductConfigMap()
-		path, found := productConfigMap[product]
-		if !found {
-			quit(fmt.Errorf("cannot generate configuration launcher for %s, it is not a known product",
-				product))
+		for _, path := range productConfigMap {
+			files = append(files, path)
+		}
+	}
+	ok := true
+	for _, mkFile := range files {
+		ok = convertOne(mkFile) && ok
+	}
+
+	if *launcher != "" {
+		if len(files) != 1 {
+			quit(fmt.Errorf("a launcher can be generated only for a single product"))
 		}
 		versionDefaults, err := generateVersionDefaults()
 		if err != nil {
 			quit(err)
 		}
-		ok = convertOne(path) && ok
 		versionDefaultsPath := outputFilePath(versionDefaultsMk)
 		err = writeGenerated(versionDefaultsPath, versionDefaults)
 		if err != nil {
-			fmt.Fprintf(os.Stderr, "%s:%s", path, err)
+			fmt.Fprintf(os.Stderr, "%s:%s", files[0], err)
 			ok = false
 		}
 
-		err = writeGenerated(*launcher, mk2rbc.Launcher(outputFilePath(path), versionDefaultsPath,
-			mk2rbc.MakePath2ModuleName(path)))
+		err = writeGenerated(*launcher, mk2rbc.Launcher(outputFilePath(files[0]), versionDefaultsPath,
+			mk2rbc.MakePath2ModuleName(files[0])))
 		if err != nil {
-			fmt.Fprintf(os.Stderr, "%s:%s", path, err)
+			fmt.Fprintf(os.Stderr, "%s:%s", files[0], err)
 			ok = false
 		}
-	} else {
-		files := flag.Args()
-		if *allInSource {
-			productConfigMap := buildProductConfigMap()
-			for _, path := range productConfigMap {
-				files = append(files, path)
-			}
-		}
-		for _, mkFile := range files {
-			ok = convertOne(mkFile) && ok
-		}
 	}
 
 	printStats()
diff --git a/python/binary.go b/python/binary.go
index bc2768c..bf6167c 100644
--- a/python/binary.go
+++ b/python/binary.go
@@ -37,7 +37,6 @@
 type bazelPythonBinaryAttributes struct {
 	Main           string
 	Srcs           bazel.LabelListAttribute
-	Data           bazel.LabelListAttribute
 	Deps           bazel.LabelListAttribute
 	Python_version string
 }
@@ -63,6 +62,7 @@
 			}
 		}
 	}
+
 	// TODO(b/182306917): this doesn't fully handle all nested props versioned
 	// by the python version, which would have been handled by the version split
 	// mutator. This is sufficient for very simple python_binary_host modules
@@ -80,15 +80,11 @@
 		// do nothing, since python_version defaults to PY3.
 	}
 
-	srcs := android.BazelLabelForModuleSrcExcludes(ctx, m.properties.Srcs, m.properties.Exclude_srcs)
-	data := android.BazelLabelForModuleSrc(ctx, m.properties.Data)
-	deps := android.BazelLabelForModuleDeps(ctx, m.properties.Libs)
-
+	baseAttrs := m.makeArchVariantBaseAttributes(ctx)
 	attrs := &bazelPythonBinaryAttributes{
 		Main:           main,
-		Srcs:           bazel.MakeLabelListAttribute(srcs),
-		Data:           bazel.MakeLabelListAttribute(data),
-		Deps:           bazel.MakeLabelListAttribute(deps),
+		Srcs:           baseAttrs.Srcs,
+		Deps:           baseAttrs.Deps,
 		Python_version: python_version,
 	}
 
@@ -97,7 +93,10 @@
 		Rule_class: "py_binary",
 	}
 
-	ctx.CreateBazelTargetModule(m.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{
+		Name: m.Name(),
+		Data: baseAttrs.Data,
+	}, attrs)
 }
 
 type BinaryProperties struct {
diff --git a/python/library.go b/python/library.go
index a132216..d136a4e 100644
--- a/python/library.go
+++ b/python/library.go
@@ -45,7 +45,6 @@
 
 type bazelPythonLibraryAttributes struct {
 	Srcs         bazel.LabelListAttribute
-	Data         bazel.LabelListAttribute
 	Deps         bazel.LabelListAttribute
 	Srcs_version string
 }
@@ -88,14 +87,10 @@
 		// do nothing, since python_version defaults to PY2ANDPY3
 	}
 
-	srcs := android.BazelLabelForModuleSrcExcludes(ctx, m.properties.Srcs, m.properties.Exclude_srcs)
-	data := android.BazelLabelForModuleSrc(ctx, m.properties.Data)
-	deps := android.BazelLabelForModuleDeps(ctx, m.properties.Libs)
-
+	baseAttrs := m.makeArchVariantBaseAttributes(ctx)
 	attrs := &bazelPythonLibraryAttributes{
-		Srcs:         bazel.MakeLabelListAttribute(srcs),
-		Data:         bazel.MakeLabelListAttribute(data),
-		Deps:         bazel.MakeLabelListAttribute(deps),
+		Srcs:         baseAttrs.Srcs,
+		Deps:         baseAttrs.Deps,
 		Srcs_version: python_version,
 	}
 
@@ -104,7 +99,10 @@
 		Rule_class: "py_library",
 	}
 
-	ctx.CreateBazelTargetModule(m.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{
+		Name: m.Name(),
+		Data: baseAttrs.Data,
+	}, attrs)
 }
 
 func PythonLibraryFactory() android.Module {
diff --git a/python/python.go b/python/python.go
index f900172..401d91f 100644
--- a/python/python.go
+++ b/python/python.go
@@ -22,6 +22,7 @@
 	"regexp"
 	"strings"
 
+	"android/soong/bazel"
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 
@@ -120,6 +121,18 @@
 	Embedded_launcher *bool `blueprint:"mutated"`
 }
 
+type baseAttributes struct {
+	// TODO(b/200311466): Probably not translate b/c Bazel has no good equiv
+	//Pkg_path    bazel.StringAttribute
+	// TODO: Related to Pkg_bath and similarLy gated
+	//Is_internal bazel.BoolAttribute
+	// Combines Srcs and Exclude_srcs
+	Srcs bazel.LabelListAttribute
+	Deps bazel.LabelListAttribute
+	// Combines Data and Java_data (invariant)
+	Data bazel.LabelListAttribute
+}
+
 // Used to store files of current module after expanding dependencies
 type pathMapping struct {
 	dest string
@@ -177,6 +190,25 @@
 	}
 }
 
+func (m *Module) makeArchVariantBaseAttributes(ctx android.TopDownMutatorContext) baseAttributes {
+	var attrs baseAttributes
+	archVariantBaseProps := m.GetArchVariantProperties(ctx, &BaseProperties{})
+	for axis, configToProps := range archVariantBaseProps {
+		for config, props := range configToProps {
+			if baseProps, ok := props.(*BaseProperties); ok {
+				attrs.Srcs.SetSelectValue(axis, config,
+					android.BazelLabelForModuleSrcExcludes(ctx, baseProps.Srcs, baseProps.Exclude_srcs))
+				attrs.Deps.SetSelectValue(axis, config,
+					android.BazelLabelForModuleDeps(ctx, baseProps.Libs))
+				data := android.BazelLabelForModuleSrc(ctx, baseProps.Data)
+				data.Append(android.BazelLabelForModuleSrc(ctx, baseProps.Java_data))
+				attrs.Data.SetSelectValue(axis, config, data)
+			}
+		}
+	}
+	return attrs
+}
+
 // bootstrapper interface should be implemented for runnable modules, e.g. binary and test
 type bootstrapper interface {
 	bootstrapperProps() []interface{}
diff --git a/rust/rust.go b/rust/rust.go
index 0a7d68d..b9afc7f 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -663,7 +663,7 @@
 }
 
 func (mod *Module) installable(apexInfo android.ApexInfo) bool {
-	if !mod.EverInstallable() {
+	if !proptools.BoolDefault(mod.Installable(), mod.EverInstallable()) {
 		return false
 	}
 
diff --git a/scripts/check_boot_jars/package_allowed_list.txt b/scripts/check_boot_jars/package_allowed_list.txt
index 9aae2a9..b1b1e7e 100644
--- a/scripts/check_boot_jars/package_allowed_list.txt
+++ b/scripts/check_boot_jars/package_allowed_list.txt
@@ -71,6 +71,7 @@
 javax\.xml\.xpath
 jdk\.internal\.math
 jdk\.internal\.misc
+jdk\.internal\.reflect
 jdk\.internal\.util
 jdk\.internal\.vm\.annotation
 jdk\.net
diff --git a/scripts/hiddenapi/signature_patterns.py b/scripts/hiddenapi/signature_patterns.py
index 0acb2a0..e75ee95 100755
--- a/scripts/hiddenapi/signature_patterns.py
+++ b/scripts/hiddenapi/signature_patterns.py
@@ -13,8 +13,10 @@
 # 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.
-"""Generate a set of signature patterns from the modular flags generated by a
-bootclasspath_fragment that can be used to select a subset of monolithic flags
+"""Generate a set of signature patterns for a bootclasspath_fragment.
+
+The patterns are generated from the modular flags produced by the
+bootclasspath_fragment and are used to select a subset of the monolithic flags
 against which the modular flags can be compared.
 """
 
@@ -22,33 +24,122 @@
 import csv
 import sys
 
+
 def dict_reader(csvfile):
     return csv.DictReader(
-        csvfile, delimiter=',', quotechar='|', fieldnames=['signature']
-    )
+        csvfile, delimiter=',', quotechar='|', fieldnames=['signature'])
 
 
-def produce_patterns_from_file(file):
+def dotPackageToSlashPackage(pkg):
+    return pkg.replace('.', '/')
+
+
+def slashPackageToDotPackage(pkg):
+    return pkg.replace('/', '.')
+
+
+def isSplitPackage(splitPackages, pkg):
+    return splitPackages and (pkg in splitPackages or '*' in splitPackages)
+
+
+def matchedByPackagePrefixPattern(packagePrefixes, prefix):
+    for packagePrefix in packagePrefixes:
+        if prefix == packagePrefix:
+            return packagePrefix
+        elif prefix.startswith(packagePrefix) and prefix[len(
+                packagePrefix)] == '/':
+            return packagePrefix
+    return False
+
+
+def validate_package_prefixes(splitPackages, packagePrefixes):
+    # If there are no package prefixes then there is no possible conflict
+    # between them and the split packages.
+    if len(packagePrefixes) == 0:
+        return
+
+    # Check to make sure that the split packages and package prefixes do not
+    # overlap.
+    errors = []
+    for splitPackage in splitPackages:
+        if splitPackage == '*':
+            # A package prefix matches a split package.
+            packagePrefixesForOutput = ', '.join(
+                map(slashPackageToDotPackage, packagePrefixes))
+            errors.append(
+                'split package "*" conflicts with all package prefixes %s\n'
+                '    add split_packages:[] to fix' % packagePrefixesForOutput)
+        else:
+            packagePrefix = matchedByPackagePrefixPattern(
+                packagePrefixes, splitPackage)
+            if packagePrefix:
+                # A package prefix matches a split package.
+                splitPackageForOutput = slashPackageToDotPackage(splitPackage)
+                packagePrefixForOutput = slashPackageToDotPackage(packagePrefix)
+                errors.append(
+                    'split package %s is matched by package prefix %s' %
+                    (splitPackageForOutput, packagePrefixForOutput))
+    return errors
+
+
+def validate_split_packages(splitPackages):
+    errors = []
+    if '*' in splitPackages and len(splitPackages) > 1:
+        errors.append('split packages are invalid as they contain both the'
+                      ' wildcard (*) and specific packages, use the wildcard or'
+                      ' specific packages, not a mixture')
+    return errors
+
+
+def produce_patterns_from_file(file, splitPackages=None, packagePrefixes=None):
     with open(file, 'r') as f:
-        return produce_patterns_from_stream(f)
+        return produce_patterns_from_stream(f, splitPackages, packagePrefixes)
 
 
-def produce_patterns_from_stream(stream):
-    # Read in all the signatures into a list and remove member names.
+def produce_patterns_from_stream(stream,
+                                 splitPackages=None,
+                                 packagePrefixes=None):
+    splitPackages = set(splitPackages or [])
+    packagePrefixes = list(packagePrefixes or [])
+    # Read in all the signatures into a list and remove any unnecessary class
+    # and member names.
     patterns = set()
     for row in dict_reader(stream):
         signature = row['signature']
-        text = signature.removeprefix("L")
+        text = signature.removeprefix('L')
         # Remove the class specific member signature
-        pieces = text.split(";->")
+        pieces = text.split(';->')
         qualifiedClassName = pieces[0]
-        # Remove inner class names as they cannot be separated
-        # from the containing outer class.
-        pieces = qualifiedClassName.split("$", maxsplit=1)
-        pattern = pieces[0]
+        pieces = qualifiedClassName.rsplit('/', maxsplit=1)
+        pkg = pieces[0]
+        # If the package is split across multiple modules then it cannot be used
+        # to select the subset of the monolithic flags that this module
+        # produces. In that case we need to keep the name of the class but can
+        # discard any nested class names as an outer class cannot be split
+        # across modules.
+        #
+        # If the package is not split then every class in the package must be
+        # provided by this module so there is no need to list the classes
+        # explicitly so just use the package name instead.
+        if isSplitPackage(splitPackages, pkg):
+            # Remove inner class names.
+            pieces = qualifiedClassName.split('$', maxsplit=1)
+            pattern = pieces[0]
+        else:
+            # Add a * to ensure that the pattern matches the classes in that
+            # package.
+            pattern = pkg + '/*'
         patterns.add(pattern)
 
-    patterns = list(patterns) #pylint: disable=redefined-variable-type
+    # Remove any patterns that would be matched by a package prefix pattern.
+    patterns = list(
+        filter(lambda p: not matchedByPackagePrefixPattern(packagePrefixes, p),
+               patterns))
+    # Add the package prefix patterns to the list. Add a ** to ensure that each
+    # package prefix pattern will match the classes in that package and all
+    # sub-packages.
+    patterns = patterns + list(map(lambda x: x + '/**', packagePrefixes))
+    # Sort the patterns.
     patterns.sort()
     return patterns
 
@@ -56,24 +147,47 @@
 def main(args):
     args_parser = argparse.ArgumentParser(
         description='Generate a set of signature patterns '
-        'that select a subset of monolithic hidden API files.'
-    )
+        'that select a subset of monolithic hidden API files.')
     args_parser.add_argument(
         '--flags',
         help='The stub flags file which contains an entry for every dex member',
     )
+    args_parser.add_argument(
+        '--split-package',
+        action='append',
+        help='A package that is split across multiple bootclasspath_fragment modules'
+    )
+    args_parser.add_argument(
+        '--package-prefix',
+        action='append',
+        help='A package prefix unique to this set of flags')
     args_parser.add_argument('--output', help='Generated signature prefixes')
     args = args_parser.parse_args(args)
 
+    splitPackages = set(map(dotPackageToSlashPackage, args.split_package or []))
+    errors = validate_split_packages(splitPackages)
+
+    packagePrefixes = list(
+        map(dotPackageToSlashPackage, args.package_prefix or []))
+
+    if not errors:
+        errors = validate_package_prefixes(splitPackages, packagePrefixes)
+
+    if errors:
+        for error in errors:
+            print(error)
+        sys.exit(1)
+
     # Read in all the patterns into a list.
-    patterns = produce_patterns_from_file(args.flags)
+    patterns = produce_patterns_from_file(args.flags, splitPackages,
+                                          packagePrefixes)
 
     # Write out all the patterns.
     with open(args.output, 'w') as outputFile:
         for pattern in patterns:
             outputFile.write(pattern)
-            outputFile.write("\n")
+            outputFile.write('\n')
 
 
-if __name__ == "__main__":
+if __name__ == '__main__':
     main(sys.argv[1:])
diff --git a/scripts/hiddenapi/signature_patterns_test.py b/scripts/hiddenapi/signature_patterns_test.py
index 3babe54..b59dfd7 100755
--- a/scripts/hiddenapi/signature_patterns_test.py
+++ b/scripts/hiddenapi/signature_patterns_test.py
@@ -13,37 +13,99 @@
 # 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.
-
 """Unit tests for signature_patterns.py."""
 import io
 import unittest
 
-from signature_patterns import * #pylint: disable=unused-wildcard-import,wildcard-import
+from signature_patterns import *  #pylint: disable=unused-wildcard-import,wildcard-import
 
 
 class TestGeneratedPatterns(unittest.TestCase):
-    def produce_patterns_from_string(self, csvdata):
-        with io.StringIO(csvdata) as f:
-            return produce_patterns_from_stream(f)
 
-    def test_generate(self):
-        #pylint: disable=line-too-long
-        patterns = self.produce_patterns_from_string(
-            '''
+    csvFlags = """
 Ljava/lang/ProcessBuilder$Redirect$1;-><init>()V,blocked
 Ljava/lang/Character$UnicodeScript;->of(I)Ljava/lang/Character$UnicodeScript;,public-api
 Ljava/lang/Object;->hashCode()I,public-api,system-api,test-api
 Ljava/lang/Object;->toString()Ljava/lang/String;,blocked
-'''
-        )
-        #pylint: enable=line-too-long
+"""
+
+    def produce_patterns_from_string(self,
+                                     csv,
+                                     splitPackages=None,
+                                     packagePrefixes=None):
+        with io.StringIO(csv) as f:
+            return produce_patterns_from_stream(f, splitPackages,
+                                                packagePrefixes)
+
+    def test_generate_default(self):
+        patterns = self.produce_patterns_from_string(
+            TestGeneratedPatterns.csvFlags)
         expected = [
-            "java/lang/Character",
-            "java/lang/Object",
-            "java/lang/ProcessBuilder",
+            'java/lang/*',
         ]
         self.assertEqual(expected, patterns)
 
+    def test_generate_split_package(self):
+        patterns = self.produce_patterns_from_string(
+            TestGeneratedPatterns.csvFlags, splitPackages={'java/lang'})
+        expected = [
+            'java/lang/Character',
+            'java/lang/Object',
+            'java/lang/ProcessBuilder',
+        ]
+        self.assertEqual(expected, patterns)
+
+    def test_generate_split_package_wildcard(self):
+        patterns = self.produce_patterns_from_string(
+            TestGeneratedPatterns.csvFlags, splitPackages={'*'})
+        expected = [
+            'java/lang/Character',
+            'java/lang/Object',
+            'java/lang/ProcessBuilder',
+        ]
+        self.assertEqual(expected, patterns)
+
+    def test_generate_package_prefix(self):
+        patterns = self.produce_patterns_from_string(
+            TestGeneratedPatterns.csvFlags, packagePrefixes={'java/lang'})
+        expected = [
+            'java/lang/**',
+        ]
+        self.assertEqual(expected, patterns)
+
+    def test_generate_package_prefix_top_package(self):
+        patterns = self.produce_patterns_from_string(
+            TestGeneratedPatterns.csvFlags, packagePrefixes={'java'})
+        expected = [
+            'java/**',
+        ]
+        self.assertEqual(expected, patterns)
+
+    def test_split_package_wildcard_conflicts_with_other_split_packages(self):
+        errors = validate_split_packages({'*', 'java'})
+        expected = [
+            'split packages are invalid as they contain both the wildcard (*)'
+            ' and specific packages, use the wildcard or specific packages,'
+            ' not a mixture'
+        ]
+        self.assertEqual(expected, errors)
+
+    def test_split_package_wildcard_conflicts_with_package_prefixes(self):
+        errors = validate_package_prefixes({'*'}, packagePrefixes={'java'})
+        expected = [
+            'split package "*" conflicts with all package prefixes java\n'
+            '    add split_packages:[] to fix',
+        ]
+        self.assertEqual(expected, errors)
+
+    def test_split_package_conflict(self):
+        errors = validate_package_prefixes({'java/split'},
+                                           packagePrefixes={'java'})
+        expected = [
+            'split package java.split is matched by package prefix java',
+        ]
+        self.assertEqual(expected, errors)
+
 
 if __name__ == '__main__':
     unittest.main(verbosity=2)
diff --git a/scripts/package-check.sh b/scripts/package-check.sh
index d7e602f..9f4a9da 100755
--- a/scripts/package-check.sh
+++ b/scripts/package-check.sh
@@ -42,7 +42,7 @@
   fi
   # Transform to a slash-separated path and add a trailing slash to enforce
   # package name boundary.
-  prefixes+=("${package//\./\/}/")
+  prefixes+=("${package//\.//}/")
   shift
 done
 
diff --git a/scripts/rbc-run b/scripts/rbc-run
index e2fa6d1..a0907cf 100755
--- a/scripts/rbc-run
+++ b/scripts/rbc-run
@@ -1,16 +1,15 @@
 #! /bin/bash
 # Convert and run one configuration
-# Args: <product>-<variant>
-[[ $# -eq 1 && "$1" =~ ^(.*)-(.*)$ ]] || { echo Usage: ${0##*/} PRODUCT-VARIANT >&2; exit 1; }
-declare -r product="${BASH_REMATCH[1]:-aosp_arm}"
-declare -r variant="${BASH_REMATCH[2]:-eng}"
+# Args: a product/board makefile optionally followed by additional arguments
+#       that will be passed to rbcrun.
+[[ $# -gt 0 && -f "$1" ]] || { echo "Usage: ${0##*/} product.mk [Additional rbcrun arguments]" >&2; exit 1; }
 set -eu
 declare -r output_root=${OUT_DIR:-out}
 declare -r runner="$output_root/soong/.bootstrap/bin/rbcrun"
 declare -r converter="$output_root/soong/.bootstrap/bin/mk2rbc"
 declare -r launcher=$output_root/launchers/run.rbc
-$converter -mode=write -r --outdir $output_root --launcher=$launcher $product
-printf "#TARGET_PRODUCT=$product TARGET_BUILD_VARIANT=$variant\n"
-env TARGET_PRODUCT=$product TARGET_BUILD_VARIANT=$variant \
-  $runner RBC_OUT="make,global" RBC_DEBUG="${RBC_DEBUG:-}" $launcher
+declare -r makefile=$1
+shift
+$converter -mode=write -r --outdir $output_root --launcher=$launcher $makefile
+$runner RBC_OUT="make,global" RBC_DEBUG="${RBC_DEBUG:-}" $@ $launcher
 
diff --git a/sdk/Android.bp b/sdk/Android.bp
index acb9dbb..f42b478 100644
--- a/sdk/Android.bp
+++ b/sdk/Android.bp
@@ -16,6 +16,7 @@
     ],
     srcs: [
         "bp.go",
+        "build_release.go",
         "exports.go",
         "member_trait.go",
         "member_type.go",
@@ -25,6 +26,7 @@
     testSrcs: [
         "bootclasspath_fragment_sdk_test.go",
         "bp_test.go",
+        "build_release_test.go",
         "cc_sdk_test.go",
         "compat_config_sdk_test.go",
         "exports_test.go",
diff --git a/sdk/bootclasspath_fragment_sdk_test.go b/sdk/bootclasspath_fragment_sdk_test.go
index e1ae474..ff2af43 100644
--- a/sdk/bootclasspath_fragment_sdk_test.go
+++ b/sdk/bootclasspath_fragment_sdk_test.go
@@ -138,8 +138,8 @@
         metadata: "hiddenapi/metadata.csv",
         index: "hiddenapi/index.csv",
         signature_patterns: "hiddenapi/signature-patterns.csv",
-        stub_flags: "hiddenapi/filtered-stub-flags.csv",
-        all_flags: "hiddenapi/filtered-flags.csv",
+        filtered_stub_flags: "hiddenapi/filtered-stub-flags.csv",
+        filtered_flags: "hiddenapi/filtered-flags.csv",
     },
 }
 
@@ -166,8 +166,8 @@
         metadata: "hiddenapi/metadata.csv",
         index: "hiddenapi/index.csv",
         signature_patterns: "hiddenapi/signature-patterns.csv",
-        stub_flags: "hiddenapi/filtered-stub-flags.csv",
-        all_flags: "hiddenapi/filtered-flags.csv",
+        filtered_stub_flags: "hiddenapi/filtered-stub-flags.csv",
+        filtered_flags: "hiddenapi/filtered-flags.csv",
     },
 }
 
@@ -339,8 +339,8 @@
         metadata: "hiddenapi/metadata.csv",
         index: "hiddenapi/index.csv",
         signature_patterns: "hiddenapi/signature-patterns.csv",
-        stub_flags: "hiddenapi/filtered-stub-flags.csv",
-        all_flags: "hiddenapi/filtered-flags.csv",
+        filtered_stub_flags: "hiddenapi/filtered-stub-flags.csv",
+        filtered_flags: "hiddenapi/filtered-flags.csv",
     },
 }
 
@@ -424,8 +424,8 @@
         metadata: "hiddenapi/metadata.csv",
         index: "hiddenapi/index.csv",
         signature_patterns: "hiddenapi/signature-patterns.csv",
-        stub_flags: "hiddenapi/filtered-stub-flags.csv",
-        all_flags: "hiddenapi/filtered-flags.csv",
+        filtered_stub_flags: "hiddenapi/filtered-stub-flags.csv",
+        filtered_flags: "hiddenapi/filtered-flags.csv",
     },
 }
 
@@ -649,8 +649,8 @@
         metadata: "hiddenapi/metadata.csv",
         index: "hiddenapi/index.csv",
         signature_patterns: "hiddenapi/signature-patterns.csv",
-        stub_flags: "hiddenapi/filtered-stub-flags.csv",
-        all_flags: "hiddenapi/filtered-flags.csv",
+        filtered_stub_flags: "hiddenapi/filtered-stub-flags.csv",
+        filtered_flags: "hiddenapi/filtered-flags.csv",
     },
 }
 
@@ -852,8 +852,8 @@
         metadata: "hiddenapi/metadata.csv",
         index: "hiddenapi/index.csv",
         signature_patterns: "hiddenapi/signature-patterns.csv",
-        stub_flags: "hiddenapi/filtered-stub-flags.csv",
-        all_flags: "hiddenapi/filtered-flags.csv",
+        filtered_stub_flags: "hiddenapi/filtered-stub-flags.csv",
+        filtered_flags: "hiddenapi/filtered-flags.csv",
     },
 }
 
diff --git a/sdk/build_release.go b/sdk/build_release.go
new file mode 100644
index 0000000..a3f0899
--- /dev/null
+++ b/sdk/build_release.go
@@ -0,0 +1,324 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package sdk
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+)
+
+// Supports customizing sdk snapshot output based on target build release.
+
+// buildRelease represents the version of a build system used to create a specific release.
+//
+// The name of the release, is the same as the code for the dessert release, e.g. S, T, etc.
+type buildRelease struct {
+	// The name of the release, e.g. S, T, etc.
+	name string
+
+	// The index of this structure within the buildReleases list.
+	ordinal int
+}
+
+// String returns the name of the build release.
+func (s *buildRelease) String() string {
+	return s.name
+}
+
+// buildReleaseSet represents a set of buildRelease objects.
+type buildReleaseSet struct {
+	// Set of *buildRelease represented as a map from *buildRelease to struct{}.
+	contents map[*buildRelease]struct{}
+}
+
+// addItem adds a build release to the set.
+func (s *buildReleaseSet) addItem(release *buildRelease) {
+	s.contents[release] = struct{}{}
+}
+
+// addRange adds all the build releases from start (inclusive) to end (inclusive).
+func (s *buildReleaseSet) addRange(start *buildRelease, end *buildRelease) {
+	for i := start.ordinal; i <= end.ordinal; i += 1 {
+		s.addItem(buildReleases[i])
+	}
+}
+
+// contains returns true if the set contains the specified build release.
+func (s *buildReleaseSet) contains(release *buildRelease) bool {
+	_, ok := s.contents[release]
+	return ok
+}
+
+// String returns a string representation of the set, sorted from earliest to latest release.
+func (s *buildReleaseSet) String() string {
+	list := []string{}
+	for _, release := range buildReleases {
+		if _, ok := s.contents[release]; ok {
+			list = append(list, release.name)
+		}
+	}
+	return fmt.Sprintf("[%s]", strings.Join(list, ","))
+}
+
+var (
+	// nameToBuildRelease contains a map from name to build release.
+	nameToBuildRelease = map[string]*buildRelease{}
+
+	// buildReleases lists all the available build releases.
+	buildReleases = []*buildRelease{}
+
+	// allBuildReleaseSet is the set of all build releases.
+	allBuildReleaseSet = &buildReleaseSet{contents: map[*buildRelease]struct{}{}}
+
+	// Add the build releases from oldest to newest.
+	buildReleaseS = initBuildRelease("S")
+	buildReleaseT = initBuildRelease("T")
+)
+
+// initBuildRelease creates a new build release with the specified name.
+func initBuildRelease(name string) *buildRelease {
+	ordinal := len(nameToBuildRelease)
+	release := &buildRelease{name: name, ordinal: ordinal}
+	nameToBuildRelease[name] = release
+	buildReleases = append(buildReleases, release)
+	allBuildReleaseSet.addItem(release)
+	return release
+}
+
+// latestBuildRelease returns the latest build release, i.e. the last one added.
+func latestBuildRelease() *buildRelease {
+	return buildReleases[len(buildReleases)-1]
+}
+
+// nameToRelease maps from build release name to the corresponding build release (if it exists) or
+// the error if it does not.
+func nameToRelease(name string) (*buildRelease, error) {
+	if r, ok := nameToBuildRelease[name]; ok {
+		return r, nil
+	}
+
+	return nil, fmt.Errorf("unknown release %q, expected one of %s", name, allBuildReleaseSet)
+}
+
+// parseBuildReleaseSet parses a build release set string specification into a build release set.
+//
+// The specification consists of one of the following:
+// * a single build release name, e.g. S, T, etc.
+// * a closed range (inclusive to inclusive), e.g. S-T
+// * an open range, e.g. T+.
+//
+// This returns the set if the specification was valid or an error.
+func parseBuildReleaseSet(specification string) (*buildReleaseSet, error) {
+	set := &buildReleaseSet{contents: map[*buildRelease]struct{}{}}
+
+	if strings.HasSuffix(specification, "+") {
+		rangeStart := strings.TrimSuffix(specification, "+")
+		start, err := nameToRelease(rangeStart)
+		if err != nil {
+			return nil, err
+		}
+		end := latestBuildRelease()
+		set.addRange(start, end)
+	} else if strings.Contains(specification, "-") {
+		limits := strings.SplitN(specification, "-", 2)
+		start, err := nameToRelease(limits[0])
+		if err != nil {
+			return nil, err
+		}
+
+		end, err := nameToRelease(limits[1])
+		if err != nil {
+			return nil, err
+		}
+
+		if start.ordinal > end.ordinal {
+			return nil, fmt.Errorf("invalid closed range, start release %q is later than end release %q", start.name, end.name)
+		}
+
+		set.addRange(start, end)
+	} else {
+		release, err := nameToRelease(specification)
+		if err != nil {
+			return nil, err
+		}
+		set.addItem(release)
+	}
+
+	return set, nil
+}
+
+// Given a set of properties (struct value), set the value of a field within that struct (or one of
+// its embedded structs) to its zero value.
+type fieldPrunerFunc func(structValue reflect.Value)
+
+// A property that can be cleared by a propertyPruner.
+type prunerProperty struct {
+	// The name of the field for this property. It is a "."-separated path for fields in non-anonymous
+	// sub-structs.
+	name string
+
+	// Sets the associated field to its zero value.
+	prunerFunc fieldPrunerFunc
+}
+
+// propertyPruner provides support for pruning (i.e. setting to their zero value) properties from
+// a properties structure.
+type propertyPruner struct {
+	// The properties that the pruner will clear.
+	properties []prunerProperty
+}
+
+// gatherFields recursively processes the supplied structure and a nested structures, selecting the
+// fields that require pruning and populates the propertyPruner.properties with the information
+// needed to prune those fields.
+//
+// containingStructAccessor is a func that if given an object will return a field whose value is
+// of the supplied structType. It is nil on initial entry to this method but when this method is
+// called recursively on a field that is a nested structure containingStructAccessor is set to a
+// func that provides access to the field's value.
+//
+// namePrefix is the prefix to the fields that are being visited. It is "" on initial entry to this
+// method but when this method is called recursively on a field that is a nested structure
+// namePrefix is the result of appending the field name (plus a ".") to the previous name prefix.
+// Unless the field is anonymous in which case it is passed through unchanged.
+//
+// selector is a func that will select whether the supplied field requires pruning or not. If it
+// returns true then the field will be added to those to be pruned, otherwise it will not.
+func (p *propertyPruner) gatherFields(structType reflect.Type, containingStructAccessor fieldAccessorFunc, namePrefix string, selector fieldSelectorFunc) {
+	for f := 0; f < structType.NumField(); f++ {
+		field := structType.Field(f)
+		if field.PkgPath != "" {
+			// Ignore unexported fields.
+			continue
+		}
+
+		// Save a copy of the field index for use in the function.
+		fieldIndex := f
+
+		name := namePrefix + field.Name
+
+		fieldGetter := func(container reflect.Value) reflect.Value {
+			if containingStructAccessor != nil {
+				// This is an embedded structure so first access the field for the embedded
+				// structure.
+				container = containingStructAccessor(container)
+			}
+
+			// Skip through interface and pointer values to find the structure.
+			container = getStructValue(container)
+
+			defer func() {
+				if r := recover(); r != nil {
+					panic(fmt.Errorf("%s for fieldIndex %d of field %s of container %#v", r, fieldIndex, name, container.Interface()))
+				}
+			}()
+
+			// Return the field.
+			return container.Field(fieldIndex)
+		}
+
+		zeroValue := reflect.Zero(field.Type)
+		fieldPruner := func(container reflect.Value) {
+			if containingStructAccessor != nil {
+				// This is an embedded structure so first access the field for the embedded
+				// structure.
+				container = containingStructAccessor(container)
+			}
+
+			// Skip through interface and pointer values to find the structure.
+			container = getStructValue(container)
+
+			defer func() {
+				if r := recover(); r != nil {
+					panic(fmt.Errorf("%s for fieldIndex %d of field %s of container %#v", r, fieldIndex, name, container.Interface()))
+				}
+			}()
+
+			// Set the field.
+			container.Field(fieldIndex).Set(zeroValue)
+		}
+
+		if selector(name, field) {
+			property := prunerProperty{
+				name,
+				fieldPruner,
+			}
+			p.properties = append(p.properties, property)
+		} else if field.Type.Kind() == reflect.Struct {
+			// Gather fields from the nested or embedded structure.
+			var subNamePrefix string
+			if field.Anonymous {
+				subNamePrefix = namePrefix
+			} else {
+				subNamePrefix = name + "."
+			}
+			p.gatherFields(field.Type, fieldGetter, subNamePrefix, selector)
+		}
+	}
+}
+
+// pruneProperties will prune (set to zero value) any properties in the supplied struct.
+//
+// The struct must be of the same type as was originally passed to newPropertyPruner to create this
+// propertyPruner.
+func (p *propertyPruner) pruneProperties(propertiesStruct interface{}) {
+	structValue := reflect.ValueOf(propertiesStruct)
+	for _, property := range p.properties {
+		property.prunerFunc(structValue)
+	}
+}
+
+// fieldSelectorFunc is called to select whether a specific field should be pruned or not.
+// name is the name of the field, including any prefixes from containing str
+type fieldSelectorFunc func(name string, field reflect.StructField) bool
+
+// newPropertyPruner creates a new property pruner for the structure type for the supplied
+// properties struct.
+//
+// The returned pruner can be used on any properties structure of the same type as the supplied set
+// of properties.
+func newPropertyPruner(propertiesStruct interface{}, selector fieldSelectorFunc) *propertyPruner {
+	structType := getStructValue(reflect.ValueOf(propertiesStruct)).Type()
+	pruner := &propertyPruner{}
+	pruner.gatherFields(structType, nil, "", selector)
+	return pruner
+}
+
+// newPropertyPrunerByBuildRelease creates a property pruner that will clear any properties in the
+// structure which are not supported by the specified target build release.
+//
+// A property is pruned if its field has a tag of the form:
+//     `supported_build_releases:"<build-release-set>"`
+// and the resulting build release set does not contain the target build release. Properties that
+// have no such tag are assumed to be supported by all releases.
+func newPropertyPrunerByBuildRelease(propertiesStruct interface{}, targetBuildRelease *buildRelease) *propertyPruner {
+	return newPropertyPruner(propertiesStruct, func(name string, field reflect.StructField) bool {
+		if supportedBuildReleases, ok := field.Tag.Lookup("supported_build_releases"); ok {
+			set, err := parseBuildReleaseSet(supportedBuildReleases)
+			if err != nil {
+				panic(fmt.Errorf("invalid `supported_build_releases` tag on %s of %T: %s", name, propertiesStruct, err))
+			}
+
+			// If the field does not support tha target release then prune it.
+			return !set.contains(targetBuildRelease)
+
+		} else {
+			// Any untagged fields are assumed to be supported by all build releases so should never be
+			// pruned.
+			return false
+		}
+	})
+}
diff --git a/sdk/build_release_test.go b/sdk/build_release_test.go
new file mode 100644
index 0000000..dff276d
--- /dev/null
+++ b/sdk/build_release_test.go
@@ -0,0 +1,185 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package sdk
+
+import (
+	"fmt"
+	"testing"
+
+	"android/soong/android"
+)
+
+// Tests for build_release.go
+
+var (
+	// Some additional test specific releases that are added after the currently supported ones and
+	// so are treated as being for future releases.
+	buildReleaseFuture1 = initBuildRelease("F1")
+	buildReleaseFuture2 = initBuildRelease("F2")
+)
+
+func TestNameToRelease(t *testing.T) {
+	t.Run("single release", func(t *testing.T) {
+		release, err := nameToRelease("S")
+		android.AssertDeepEquals(t, "errors", nil, err)
+		android.AssertDeepEquals(t, "release", buildReleaseS, release)
+	})
+	t.Run("invalid release", func(t *testing.T) {
+		release, err := nameToRelease("A")
+		android.AssertDeepEquals(t, "release", (*buildRelease)(nil), release)
+		// Uses a wildcard in the error message to allow for additional build releases to be added to
+		// the supported set without breaking this test.
+		android.FailIfNoMatchingErrors(t, `unknown release "A", expected one of \[S,T.*,F1,F2\]`, []error{err})
+	})
+}
+
+func TestParseBuildReleaseSet(t *testing.T) {
+	t.Run("single release", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("S")
+		android.AssertDeepEquals(t, "errors", nil, err)
+		android.AssertStringEquals(t, "set", "[S]", set.String())
+	})
+	t.Run("open range", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("F1+")
+		android.AssertDeepEquals(t, "errors", nil, err)
+		android.AssertStringEquals(t, "set", "[F1,F2]", set.String())
+	})
+	t.Run("closed range", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("S-F1")
+		android.AssertDeepEquals(t, "errors", nil, err)
+		android.AssertStringEquals(t, "set", "[S,T,F1]", set.String())
+	})
+	invalidAReleaseMessage := `unknown release "A", expected one of ` + allBuildReleaseSet.String()
+	t.Run("invalid release", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("A")
+		android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set)
+		android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), invalidAReleaseMessage)
+	})
+	t.Run("invalid release in open range", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("A+")
+		android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set)
+		android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), invalidAReleaseMessage)
+	})
+	t.Run("invalid release in closed range start", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("A-S")
+		android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set)
+		android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), invalidAReleaseMessage)
+	})
+	t.Run("invalid release in closed range end", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("T-A")
+		android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set)
+		android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), invalidAReleaseMessage)
+	})
+	t.Run("invalid closed range reversed", func(t *testing.T) {
+		set, err := parseBuildReleaseSet("F1-S")
+		android.AssertDeepEquals(t, "set", (*buildReleaseSet)(nil), set)
+		android.AssertStringDoesContain(t, "errors", fmt.Sprint(err), `invalid closed range, start release "F1" is later than end release "S"`)
+	})
+}
+
+func TestBuildReleaseSetContains(t *testing.T) {
+	t.Run("contains", func(t *testing.T) {
+		set, _ := parseBuildReleaseSet("F1-F2")
+		android.AssertBoolEquals(t, "set contains F1", true, set.contains(buildReleaseFuture1))
+		android.AssertBoolEquals(t, "set does not contain S", false, set.contains(buildReleaseS))
+		android.AssertBoolEquals(t, "set contains F2", true, set.contains(buildReleaseFuture2))
+		android.AssertBoolEquals(t, "set does not contain T", false, set.contains(buildReleaseT))
+	})
+}
+
+func TestPropertyPrunerInvalidTag(t *testing.T) {
+	type brokenStruct struct {
+		Broken string `supported_build_releases:"A"`
+	}
+	type containingStruct struct {
+		Nested brokenStruct
+	}
+
+	t.Run("broken struct", func(t *testing.T) {
+		android.AssertPanicMessageContains(t, "error", "invalid `supported_build_releases` tag on Broken of *sdk.brokenStruct: unknown release \"A\"", func() {
+			newPropertyPrunerByBuildRelease(&brokenStruct{}, buildReleaseS)
+		})
+	})
+
+	t.Run("nested broken struct", func(t *testing.T) {
+		android.AssertPanicMessageContains(t, "error", "invalid `supported_build_releases` tag on Nested.Broken of *sdk.containingStruct: unknown release \"A\"", func() {
+			newPropertyPrunerByBuildRelease(&containingStruct{}, buildReleaseS)
+		})
+	})
+}
+
+func TestPropertyPrunerByBuildRelease(t *testing.T) {
+	type nested struct {
+		F1_only string `supported_build_releases:"F1"`
+	}
+
+	type testBuildReleasePruner struct {
+		Default      string
+		S_and_T_only string `supported_build_releases:"S-T"`
+		T_later      string `supported_build_releases:"T+"`
+		Nested       nested
+	}
+
+	input := testBuildReleasePruner{
+		Default:      "Default",
+		S_and_T_only: "S_and_T_only",
+		T_later:      "T_later",
+		Nested: nested{
+			F1_only: "F1_only",
+		},
+	}
+
+	t.Run("target S", func(t *testing.T) {
+		testStruct := input
+		pruner := newPropertyPrunerByBuildRelease(&testStruct, buildReleaseS)
+		pruner.pruneProperties(&testStruct)
+
+		expected := input
+		expected.T_later = ""
+		expected.Nested.F1_only = ""
+		android.AssertDeepEquals(t, "test struct", expected, testStruct)
+	})
+
+	t.Run("target T", func(t *testing.T) {
+		testStruct := input
+		pruner := newPropertyPrunerByBuildRelease(&testStruct, buildReleaseT)
+		pruner.pruneProperties(&testStruct)
+
+		expected := input
+		expected.Nested.F1_only = ""
+		android.AssertDeepEquals(t, "test struct", expected, testStruct)
+	})
+
+	t.Run("target F1", func(t *testing.T) {
+		testStruct := input
+		pruner := newPropertyPrunerByBuildRelease(&testStruct, buildReleaseFuture1)
+		pruner.pruneProperties(&testStruct)
+
+		expected := input
+		expected.S_and_T_only = ""
+		android.AssertDeepEquals(t, "test struct", expected, testStruct)
+	})
+
+	t.Run("target F2", func(t *testing.T) {
+		testStruct := input
+		pruner := newPropertyPrunerByBuildRelease(&testStruct, buildReleaseFuture2)
+		pruner.pruneProperties(&testStruct)
+
+		expected := input
+		expected.S_and_T_only = ""
+		expected.Nested.F1_only = ""
+		android.AssertDeepEquals(t, "test struct", expected, testStruct)
+	})
+}
diff --git a/sdk/sdk_test.go b/sdk/sdk_test.go
index 85e3d87..83294f6 100644
--- a/sdk/sdk_test.go
+++ b/sdk/sdk_test.go
@@ -21,6 +21,7 @@
 	"testing"
 
 	"android/soong/android"
+	"android/soong/java"
 
 	"github.com/google/blueprint/proptools"
 )
@@ -706,4 +707,86 @@
 			snapshotTestPreparer(checkSnapshotWithoutSource, android.FixtureWithRootAndroidBp(bp)),
 		)
 	})
+
+	t.Run("SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE=S", func(t *testing.T) {
+		result := android.GroupFixturePreparers(
+			prepareForSdkTestWithJava,
+			java.PrepareForTestWithJavaDefaultModules,
+			java.PrepareForTestWithJavaSdkLibraryFiles,
+			java.FixtureWithLastReleaseApis("mysdklibrary"),
+			android.FixtureWithRootAndroidBp(`
+			sdk {
+				name: "mysdk",
+				bootclasspath_fragments: ["mybootclasspathfragment"],
+			}
+
+			bootclasspath_fragment {
+				name: "mybootclasspathfragment",
+				apex_available: ["myapex"],
+				contents: ["mysdklibrary"],
+			}
+
+			java_sdk_library {
+				name: "mysdklibrary",
+				srcs: ["Test.java"],
+				compile_dex: true,
+				public: {enabled: true},
+				permitted_packages: ["mysdklibrary"],
+			}
+		`),
+			android.FixtureMergeEnv(map[string]string{
+				"SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE": "S",
+			}),
+		).RunTest(t)
+
+		CheckSnapshot(t, result, "mysdk", "",
+			checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+prebuilt_bootclasspath_fragment {
+    name: "mybootclasspathfragment",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["myapex"],
+    contents: ["mysdklibrary"],
+    hidden_api: {
+        annotation_flags: "hiddenapi/annotation-flags.csv",
+        metadata: "hiddenapi/metadata.csv",
+        index: "hiddenapi/index.csv",
+        stub_flags: "hiddenapi/stub-flags.csv",
+        all_flags: "hiddenapi/all-flags.csv",
+    },
+}
+
+java_sdk_library_import {
+    name: "mysdklibrary",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    shared_library: true,
+    compile_dex: true,
+    permitted_packages: ["mysdklibrary"],
+    public: {
+        jars: ["sdk_library/public/mysdklibrary-stubs.jar"],
+        stub_srcs: ["sdk_library/public/mysdklibrary_stub_sources"],
+        current_api: "sdk_library/public/mysdklibrary.txt",
+        removed_api: "sdk_library/public/mysdklibrary-removed.txt",
+        sdk_version: "current",
+    },
+}
+`),
+
+			checkAllCopyRules(`
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/annotation-flags.csv -> hiddenapi/annotation-flags.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/metadata.csv -> hiddenapi/metadata.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/index.csv -> hiddenapi/index.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/stub-flags.csv -> hiddenapi/stub-flags.csv
+.intermediates/mybootclasspathfragment/android_common/modular-hiddenapi/all-flags.csv -> hiddenapi/all-flags.csv
+.intermediates/mysdklibrary.stubs/android_common/javac/mysdklibrary.stubs.jar -> sdk_library/public/mysdklibrary-stubs.jar
+.intermediates/mysdklibrary.stubs.source/android_common/metalava/mysdklibrary.stubs.source_api.txt -> sdk_library/public/mysdklibrary.txt
+.intermediates/mysdklibrary.stubs.source/android_common/metalava/mysdklibrary.stubs.source_removed.txt -> sdk_library/public/mysdklibrary-removed.txt
+`),
+		)
+	})
+
 }
diff --git a/sdk/testing.go b/sdk/testing.go
index 3254cf9..294f1a5 100644
--- a/sdk/testing.go
+++ b/sdk/testing.go
@@ -136,6 +136,7 @@
 		androidUnversionedBpContents: sdk.GetUnversionedAndroidBpContentsForTests(),
 		androidVersionedBpContents:   sdk.GetVersionedAndroidBpContentsForTests(),
 		snapshotTestCustomizations:   map[snapshotTest]*snapshotTestCustomization{},
+		targetBuildRelease:           sdk.builderForTests.targetBuildRelease,
 	}
 
 	buildParams := sdk.BuildParamsForTests()
@@ -253,6 +254,13 @@
 	}
 	fs[filepath.Join(snapshotSubDir, "Android.bp")] = []byte(snapshotBuildInfo.androidBpContents)
 
+	// If the generated snapshot builders not for the current release then it cannot be loaded by
+	// the current release.
+	currentBuildRelease := latestBuildRelease()
+	if snapshotBuildInfo.targetBuildRelease != currentBuildRelease {
+		return
+	}
+
 	// The preparers from the original source fixture.
 	sourcePreparers := result.Preparer()
 
@@ -476,6 +484,9 @@
 	// The final output zip.
 	outputZip string
 
+	// The target build release.
+	targetBuildRelease *buildRelease
+
 	// The test specific customizations for each snapshot test.
 	snapshotTestCustomizations map[snapshotTest]*snapshotTestCustomization
 }
diff --git a/sdk/update.go b/sdk/update.go
index 3246832..389e845 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -81,6 +81,19 @@
 //     snapshot module only. The zip file containing the generated snapshot will be
 //     <sdk-name>-<number>.zip.
 //
+// SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE
+//     This allows the target build release (i.e. the release version of the build within which
+//     the snapshot will be used) of the snapshot to be specified. If unspecified then it defaults
+//     to the current build release version. Otherwise, it must be the name of one of the build
+//     releases defined in nameToBuildRelease, e.g. S, T, etc..
+//
+//     The generated snapshot must only be used in the specified target release. If the target
+//     build release is not the current build release then the generated Android.bp file not be
+//     checked for compatibility.
+//
+//     e.g. if setting SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE=S will cause the generated snapshot
+//     to be compatible with S.
+//
 
 var pctx = android.NewPackageContext("android/soong/sdk")
 
@@ -358,6 +371,14 @@
 		snapshotZipFileSuffix = "-" + version
 	}
 
+	currentBuildRelease := latestBuildRelease()
+	targetBuildReleaseEnv := config.GetenvWithDefault("SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE", currentBuildRelease.name)
+	targetBuildRelease, err := nameToRelease(targetBuildReleaseEnv)
+	if err != nil {
+		ctx.ModuleErrorf("invalid SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE: %s", err)
+		targetBuildRelease = currentBuildRelease
+	}
+
 	builder := &snapshotBuilder{
 		ctx:                   ctx,
 		sdk:                   s,
@@ -369,6 +390,7 @@
 		prebuiltModules:       make(map[string]*bpModule),
 		allMembersByName:      allMembersByName,
 		exportedMembersByName: exportedMembersByName,
+		targetBuildRelease:    targetBuildRelease,
 	}
 	s.builderForTests = builder
 
@@ -449,7 +471,11 @@
 	generateBpContents(&bp.generatedContents, bpFile)
 
 	contents := bp.content.String()
-	syntaxCheckSnapshotBpFile(ctx, contents)
+	// If the snapshot is being generated for the current build release then check the syntax to make
+	// sure that it is compatible.
+	if targetBuildRelease == currentBuildRelease {
+		syntaxCheckSnapshotBpFile(ctx, contents)
+	}
 
 	bp.build(pctx, ctx, nil)
 
@@ -1051,6 +1077,9 @@
 
 	// The set of exported members by name.
 	exportedMembersByName map[string]struct{}
+
+	// The target build release for which the snapshot is to be generated.
+	targetBuildRelease *buildRelease
 }
 
 func (s *snapshotBuilder) CopyToSnapshot(src android.Path, dest string) {
@@ -1426,6 +1455,16 @@
 	return osInfo
 }
 
+func (osInfo *osTypeSpecificInfo) pruneUnsupportedProperties(pruner *propertyPruner) {
+	if len(osInfo.archInfos) == 0 {
+		pruner.pruneProperties(osInfo.Properties)
+	} else {
+		for _, archInfo := range osInfo.archInfos {
+			archInfo.pruneUnsupportedProperties(pruner)
+		}
+	}
+}
+
 // Optimize the properties by extracting common properties from arch type specific
 // properties into os type specific properties.
 func (osInfo *osTypeSpecificInfo) optimizeProperties(ctx *memberContext, commonValueExtractor *commonValueExtractor) {
@@ -1635,6 +1674,16 @@
 	return linkType
 }
 
+func (archInfo *archTypeSpecificInfo) pruneUnsupportedProperties(pruner *propertyPruner) {
+	if len(archInfo.imageVariantInfos) == 0 {
+		pruner.pruneProperties(archInfo.Properties)
+	} else {
+		for _, imageVariantInfo := range archInfo.imageVariantInfos {
+			imageVariantInfo.pruneUnsupportedProperties(pruner)
+		}
+	}
+}
+
 // Optimize the properties by extracting common properties from link type specific
 // properties into arch type specific properties.
 func (archInfo *archTypeSpecificInfo) optimizeProperties(ctx *memberContext, commonValueExtractor *commonValueExtractor) {
@@ -1732,6 +1781,16 @@
 	return imageInfo
 }
 
+func (imageInfo *imageVariantSpecificInfo) pruneUnsupportedProperties(pruner *propertyPruner) {
+	if len(imageInfo.linkInfos) == 0 {
+		pruner.pruneProperties(imageInfo.Properties)
+	} else {
+		for _, linkInfo := range imageInfo.linkInfos {
+			linkInfo.pruneUnsupportedProperties(pruner)
+		}
+	}
+}
+
 // Optimize the properties by extracting common properties from link type specific
 // properties into arch type specific properties.
 func (imageInfo *imageVariantSpecificInfo) optimizeProperties(ctx *memberContext, commonValueExtractor *commonValueExtractor) {
@@ -1798,6 +1857,10 @@
 	addSdkMemberPropertiesToSet(ctx, l.Properties, linkPropertySet)
 }
 
+func (l *linkTypeSpecificInfo) pruneUnsupportedProperties(pruner *propertyPruner) {
+	pruner.pruneProperties(l.Properties)
+}
+
 func (l *linkTypeSpecificInfo) String() string {
 	return fmt.Sprintf("LinkType{%s}", l.linkType)
 }
@@ -1837,12 +1900,12 @@
 	memberType := member.memberType
 
 	// Do not add the prefer property if the member snapshot module is a source module type.
+	config := ctx.sdkMemberContext.Config()
 	if !memberType.UsesSourceModuleTypeInSnapshot() {
 		// Set the prefer based on the environment variable. This is a temporary work around to allow a
 		// snapshot to be created that sets prefer: true.
 		// TODO(b/174997203): Remove once the ability to select the modules to prefer can be done
 		//  dynamically at build time not at snapshot generation time.
-		config := ctx.sdkMemberContext.Config()
 		prefer := config.IsEnvTrue("SOONG_SDK_SNAPSHOT_PREFER")
 
 		// Set prefer. Setting this to false is not strictly required as that is the default but it does
@@ -1884,6 +1947,11 @@
 	commonProperties := variantPropertiesFactory()
 	commonProperties.Base().Os = android.CommonOS
 
+	// Create a property pruner that will prune any properties unsupported by the target build
+	// release.
+	targetBuildRelease := ctx.builder.targetBuildRelease
+	unsupportedPropertyPruner := newPropertyPrunerByBuildRelease(commonProperties, targetBuildRelease)
+
 	// Create common value extractor that can be used to optimize the properties.
 	commonValueExtractor := newCommonValueExtractor(commonProperties)
 
@@ -1898,6 +1966,8 @@
 		// independent properties structs.
 		osSpecificPropertiesContainers = append(osSpecificPropertiesContainers, osInfo)
 
+		osInfo.pruneUnsupportedProperties(unsupportedPropertyPruner)
+
 		// Optimize the properties across all the variants for a specific os type.
 		osInfo.optimizeProperties(ctx, commonValueExtractor)
 	}
diff --git a/sh/sh_binary.go b/sh/sh_binary.go
index db66ae2..b22a5b7 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -548,7 +548,7 @@
 		Rule_class: "sh_binary",
 	}
 
-	ctx.CreateBazelTargetModule(m.Name(), props, attrs)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs)
 }
 
 var Bool = proptools.Bool
diff --git a/ui/build/dumpvars.go b/ui/build/dumpvars.go
index 3d16073..afec829 100644
--- a/ui/build/dumpvars.go
+++ b/ui/build/dumpvars.go
@@ -163,6 +163,7 @@
 	"AUX_OS_VARIANT_LIST",
 	"PRODUCT_SOONG_NAMESPACES",
 	"SOONG_SDK_SNAPSHOT_PREFER",
+	"SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE",
 	"SOONG_SDK_SNAPSHOT_USE_SOURCE_CONFIG_VAR",
 	"SOONG_SDK_SNAPSHOT_VERSION",
 }