Merge "Allow Rust code in system/logging/rust"
diff --git a/Android.bp b/Android.bp
index 9d5b07d..8f7f3e2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -47,101 +47,6 @@
 //
 
 toolchain_library {
-    name: "libatomic",
-    defaults: ["linux_bionic_supported"],
-    vendor_available: true,
-    product_available: true,
-    ramdisk_available: true,
-    vendor_ramdisk_available: true,
-    recovery_available: true,
-    native_bridge_supported: true,
-
-    arch: {
-        arm: {
-            src: "prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9/arm-linux-androideabi/lib/libatomic.a",
-        },
-        arm64: {
-            src: "prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/aarch64-linux-android/lib64/libatomic.a",
-        },
-        x86: {
-            src: "prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9/x86_64-linux-android/lib/libatomic.a",
-        },
-        x86_64: {
-            src: "prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9/x86_64-linux-android/lib64/libatomic.a",
-        },
-    },
-}
-
-toolchain_library {
-    name: "libgcc",
-    defaults: ["linux_bionic_supported"],
-    vendor_available: true,
-    product_available: true,
-    recovery_available: true,
-    native_bridge_supported: true,
-    apex_available: [
-        "//apex_available:platform",
-        "//apex_available:anyapex",
-    ],
-
-    arch: {
-        arm: {
-            src: "prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9/lib/gcc/arm-linux-androideabi/4.9.x/libgcc.a",
-        },
-        arm64: {
-            src: "prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/lib/gcc/aarch64-linux-android/4.9.x/libgcc.a",
-        },
-        x86: {
-            src: "prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9/lib/gcc/x86_64-linux-android/4.9.x/32/libgcc.a",
-        },
-        x86_64: {
-            src: "prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9/lib/gcc/x86_64-linux-android/4.9.x/libgcc.a",
-        },
-    },
-}
-
-toolchain_library {
-    name: "libgcc_stripped",
-    defaults: ["linux_bionic_supported"],
-    vendor_available: true,
-    product_available: true,
-    ramdisk_available: true,
-    vendor_ramdisk_available: true,
-    recovery_available: true,
-    native_bridge_supported: true,
-    sdk_version: "current",
-
-    arch: {
-        arm: {
-            src: "prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9/lib/gcc/arm-linux-androideabi/4.9.x/libgcc.a",
-            repack_objects_to_keep: [],
-            enabled: false,
-        },
-        arm64: {
-            src: "prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/lib/gcc/aarch64-linux-android/4.9.x/libgcc.a",
-            repack_objects_to_keep: [
-                "unwind-dw2.o",
-                "unwind-dw2-fde-dip.o",
-            ],
-        },
-        x86: {
-            src: "prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9/lib/gcc/x86_64-linux-android/4.9.x/32/libgcc.a",
-            repack_objects_to_keep: [
-                "unwind-dw2.o",
-                "unwind-dw2-fde-dip.o",
-            ],
-        },
-        x86_64: {
-            src: "prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9/lib/gcc/x86_64-linux-android/4.9.x/libgcc.a",
-            repack_objects_to_keep: [
-                "unwind-dw2.o",
-                "unwind-dw2-fde-dip.o",
-            ],
-        },
-    },
-}
-
-toolchain_library {
     name: "libwinpthread",
     host_supported: true,
     enabled: false,
@@ -159,26 +64,6 @@
     notice: ":mingw-libwinpthread-notice",
 }
 
-toolchain_library {
-    name: "libgcov",
-    defaults: ["linux_bionic_supported"],
-
-    arch: {
-        arm: {
-            src: "prebuilts/gcc/linux-x86/arm/arm-linux-androideabi-4.9/lib/gcc/arm-linux-androideabi/4.9.x/libgcov.a",
-        },
-        arm64: {
-            src: "prebuilts/gcc/linux-x86/aarch64/aarch64-linux-android-4.9/lib/gcc/aarch64-linux-android/4.9.x/libgcov.a",
-        },
-        x86: {
-            src: "prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9/lib/gcc/x86_64-linux-android/4.9.x/32/libgcov.a",
-        },
-        x86_64: {
-            src: "prebuilts/gcc/linux-x86/x86/x86_64-linux-android-4.9/lib/gcc/x86_64-linux-android/4.9.x/libgcov.a",
-        },
-    },
-}
-
 kernel_headers {
     name: "device_kernel_headers",
     vendor: true,
diff --git a/android/Android.bp b/android/Android.bp
index 4da0f4e..a32e8f2 100644
--- a/android/Android.bp
+++ b/android/Android.bp
@@ -14,6 +14,7 @@
         "soong-bazel",
         "soong-cquery",
         "soong-remoteexec",
+        "soong-response",
         "soong-shared",
         "soong-ui-metrics_proto",
     ],
@@ -66,6 +67,7 @@
         "rule_builder.go",
         "sandbox.go",
         "sdk.go",
+        "sdk_version.go",
         "singleton.go",
         "singleton_module.go",
         "soong_config_modules.go",
diff --git a/android/apex.go b/android/apex.go
index a5ff442..7f9f0f5 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -453,6 +453,23 @@
 	}
 }
 
+// AvailableToSameApexes returns true if the two modules are apex_available to
+// exactly the same set of APEXes (and platform), i.e. if their apex_available
+// properties have the same elements.
+func AvailableToSameApexes(mod1, mod2 ApexModule) bool {
+	mod1ApexAvail := SortedUniqueStrings(mod1.apexModuleBase().ApexProperties.Apex_available)
+	mod2ApexAvail := SortedUniqueStrings(mod2.apexModuleBase().ApexProperties.Apex_available)
+	if len(mod1ApexAvail) != len(mod2ApexAvail) {
+		return false
+	}
+	for i, v := range mod1ApexAvail {
+		if v != mod2ApexAvail[i] {
+			return false
+		}
+	}
+	return true
+}
+
 type byApexName []ApexInfo
 
 func (a byApexName) Len() int           { return len(a) }
@@ -786,60 +803,73 @@
 	}
 	return list
 }(map[string]int{
-	"adbd":                           30,
-	"android.net.ipsec.ike":          30,
-	"apache-commons-compress":        29,
-	"bouncycastle_ike_digests":       30,
-	"brotli-java":                    29,
-	"captiveportal-lib":              28,
-	"flatbuffer_headers":             30,
-	"framework-permission":           30,
-	"gemmlowp_headers":               30,
-	"ike-internals":                  30,
-	"kotlinx-coroutines-android":     28,
-	"kotlinx-coroutines-core":        28,
-	"libadb_crypto":                  30,
-	"libadb_pairing_auth":            30,
-	"libadb_pairing_connection":      30,
-	"libadb_pairing_server":          30,
-	"libadb_protos":                  30,
-	"libadb_tls_connection":          30,
-	"libadbconnection_client":        30,
-	"libadbconnection_server":        30,
-	"libadbd_core":                   30,
-	"libadbd_services":               30,
-	"libadbd":                        30,
-	"libapp_processes_protos_lite":   30,
-	"libasyncio":                     30,
-	"libbrotli":                      30,
-	"libbuildversion":                30,
-	"libcrypto_static":               30,
-	"libcrypto_utils":                30,
-	"libdiagnose_usb":                30,
-	"libeigen":                       30,
-	"liblz4":                         30,
-	"libmdnssd":                      30,
-	"libneuralnetworks_common":       30,
-	"libneuralnetworks_headers":      30,
-	"libneuralnetworks":              30,
-	"libprocpartition":               30,
-	"libprotobuf-java-lite":          30,
-	"libprotoutil":                   30,
-	"libqemu_pipe":                   30,
-	"libsync":                        30,
-	"libtextclassifier_hash_headers": 30,
-	"libtextclassifier_hash_static":  30,
-	"libtflite_kernel_utils":         30,
-	"libwatchdog":                    29,
-	"libzstd":                        30,
-	"metrics-constants-protos":       28,
-	"net-utils-framework-common":     29,
-	"permissioncontroller-statsd":    28,
-	"philox_random_headers":          30,
-	"philox_random":                  30,
-	"service-permission":             30,
-	"tensorflow_headers":             30,
-	"xz-java":                        29,
+	"adbd":                                                     30,
+	"android.net.ipsec.ike":                                    30,
+	"androidx.annotation_annotation-nodeps":                    29,
+	"androidx.arch.core_core-common-nodeps":                    29,
+	"androidx.collection_collection-nodeps":                    29,
+	"androidx.collection_collection-ktx-nodeps":                30,
+	"androidx.concurrent_concurrent-futures-nodeps":            30,
+	"androidx.lifecycle_lifecycle-common-java8-nodeps":         30,
+	"androidx.lifecycle_lifecycle-common-nodeps":               29,
+	"androidx.room_room-common-nodeps":                         30,
+	"androidx-constraintlayout_constraintlayout-solver-nodeps": 29,
+	"apache-commons-compress":                                  29,
+	"bouncycastle_ike_digests":                                 30,
+	"brotli-java":                                              29,
+	"captiveportal-lib":                                        28,
+	"error_prone_annotations":                                  30,
+	"flatbuffer_headers":                                       30,
+	"framework-permission":                                     30,
+	"gemmlowp_headers":                                         30,
+	"guava-listenablefuture-prebuilt-jar":                      30,
+	"ike-internals":                                            30,
+	"kotlinx-coroutines-android":                               28,
+	"kotlinx-coroutines-android-nodeps":                        30,
+	"kotlinx-coroutines-core":                                  28,
+	"kotlinx-coroutines-core-nodeps":                           30,
+	"libadb_crypto":                                            30,
+	"libadb_pairing_auth":                                      30,
+	"libadb_pairing_connection":                                30,
+	"libadb_pairing_server":                                    30,
+	"libadb_protos":                                            30,
+	"libadb_tls_connection":                                    30,
+	"libadbconnection_client":                                  30,
+	"libadbconnection_server":                                  30,
+	"libadbd_core":                                             30,
+	"libadbd_services":                                         30,
+	"libadbd":                                                  30,
+	"libapp_processes_protos_lite":                             30,
+	"libasyncio":                                               30,
+	"libbrotli":                                                30,
+	"libbuildversion":                                          30,
+	"libcrypto_static":                                         30,
+	"libcrypto_utils":                                          30,
+	"libdiagnose_usb":                                          30,
+	"libeigen":                                                 30,
+	"liblz4":                                                   30,
+	"libmdnssd":                                                30,
+	"libneuralnetworks_common":                                 30,
+	"libneuralnetworks_headers":                                30,
+	"libneuralnetworks":                                        30,
+	"libprocpartition":                                         30,
+	"libprotobuf-java-lite":                                    30,
+	"libprotoutil":                                             30,
+	"libqemu_pipe":                                             30,
+	"libsync":                                                  30,
+	"libtextclassifier_hash_headers":                           30,
+	"libtextclassifier_hash_static":                            30,
+	"libtflite_kernel_utils":                                   30,
+	"libwatchdog":                                              29,
+	"libzstd":                                                  30,
+	"metrics-constants-protos":                                 28,
+	"net-utils-framework-common":                               29,
+	"permissioncontroller-statsd":                              28,
+	"philox_random_headers":                                    30,
+	"philox_random":                                            30,
+	"service-permission":                                       30,
+	"tensorflow_headers":                                       30,
+	"xz-java":                                                  29,
 })
 
 // Function called while walking an APEX's payload dependencies.
diff --git a/android/api_levels.go b/android/api_levels.go
index 2f6a9d2..9bc7e83 100644
--- a/android/api_levels.go
+++ b/android/api_levels.go
@@ -31,9 +31,9 @@
 // ApiLevelFromUser or ApiLevelOrPanic.
 //
 // The different *types* of API levels are handled separately. Currently only
-// Java has these, and they're managed with the sdkKind enum of the sdkSpec. A
-// future cleanup should be to migrate sdkSpec to using ApiLevel instead of its
-// sdkVersion int, and to move sdkSpec into this package.
+// Java has these, and they're managed with the SdkKind enum of the SdkSpec. A
+// future cleanup should be to migrate SdkSpec to using ApiLevel instead of its
+// SdkVersion int, and to move SdkSpec into this package.
 type ApiLevel struct {
 	// The string representation of the API level.
 	value string
diff --git a/android/arch.go b/android/arch.go
index 20b4ab0..6826f3b 100644
--- a/android/arch.go
+++ b/android/arch.go
@@ -167,7 +167,8 @@
 	return archType
 }
 
-// ArchTypeList returns the 4 supported ArchTypes for arm, arm64, x86 and x86_64.
+// ArchTypeList returns the a slice copy of the 4 supported ArchTypes for arm,
+// arm64, x86 and x86_64.
 func ArchTypeList() []ArchType {
 	return append([]ArchType(nil), archTypeList...)
 }
@@ -266,7 +267,7 @@
 
 		DefaultDisabled: defDisabled,
 	}
-	OsTypeList = append(OsTypeList, os)
+	osTypeList = append(osTypeList, os)
 
 	if _, found := commonTargetMap[name]; found {
 		panic(fmt.Errorf("Found Os type duplicate during OsType registration: %q", name))
@@ -280,7 +281,7 @@
 
 // osByName returns the OsType that has the given name, or NoOsType if none match.
 func osByName(name string) OsType {
-	for _, os := range OsTypeList {
+	for _, os := range osTypeList {
 		if os.Name == name {
 			return os
 		}
@@ -312,9 +313,9 @@
 }()
 
 var (
-	// OsTypeList contains a list of all the supported OsTypes, including ones not supported
+	// osTypeList contains a list of all the supported OsTypes, including ones not supported
 	// by the current build host or the target device.
-	OsTypeList []OsType
+	osTypeList []OsType
 	// commonTargetMap maps names of OsTypes to the corresponding common Target, i.e. the
 	// Target with the same OsType and the common ArchType.
 	commonTargetMap = make(map[string]Target)
@@ -347,6 +348,11 @@
 	CommonArch = Arch{ArchType: Common}
 )
 
+// OsTypeList returns a slice copy of the supported OsTypes.
+func OsTypeList() []OsType {
+	return append([]OsType(nil), osTypeList...)
+}
+
 // Target specifies the OS and architecture that a module is being compiled for.
 type Target struct {
 	// Os the OS that the module is being compiled for (e.g. "linux_glibc", "android").
@@ -448,7 +454,7 @@
 	// Collect a list of OSTypes supported by this module based on the HostOrDevice value
 	// passed to InitAndroidArchModule and the device_supported and host_supported properties.
 	var moduleOSList []OsType
-	for _, os := range OsTypeList {
+	for _, os := range osTypeList {
 		for _, t := range mctx.Config().Targets[os] {
 			if base.supportsTarget(t) {
 				moduleOSList = append(moduleOSList, os)
@@ -838,7 +844,7 @@
 			"Arm_on_x86_64",
 			"Native_bridge",
 		}
-		for _, os := range OsTypeList {
+		for _, os := range osTypeList {
 			// Add all the OSes.
 			targets = append(targets, os.Field)
 
@@ -1709,3 +1715,90 @@
 	}
 	return archToProp
 }
+
+// GetTargetProperties returns a map of OS target (e.g. android, windows) to the
+// values of the properties of the 'dst' struct that are specific to that OS
+// target.
+//
+// For example, passing a struct { Foo bool, Bar string } will return an
+// interface{} that can be type asserted back into the same struct, containing
+// the os-specific property value specified by the module if defined.
+//
+// While this looks similar to GetArchProperties, the internal representation of
+// the properties have a slightly different layout to warrant a standalone
+// lookup function.
+func (m *ModuleBase) GetTargetProperties(dst interface{}) map[OsType]interface{} {
+	// Return value of the arch types to the prop values for that arch.
+	osToProp := map[OsType]interface{}{}
+
+	// Nothing to do for non-OS/arch-specific modules.
+	if !m.ArchSpecific() {
+		return osToProp
+	}
+
+	// archProperties has the type of [][]interface{}. Looks complicated, so
+	// let's explain this step by step.
+	//
+	// Loop over the outer index, which determines the property struct that
+	// contains a matching set of properties in dst that we're interested in.
+	// For example, BaseCompilerProperties or BaseLinkerProperties.
+	for i := range m.archProperties {
+		if m.archProperties[i] == nil {
+			continue
+		}
+
+		// Iterate over the supported OS types
+		for _, os := range osTypeList {
+			// e.g android, linux_bionic
+			field := os.Field
+
+			// If it's not nil, loop over the inner index, which determines the arch variant
+			// of the prop type. In an Android.bp file, this is like looping over:
+			//
+			// target: { android: { key: value, ... }, linux_bionic: { key: value, ... } }
+			for _, archProperties := range m.archProperties[i] {
+				archPropValues := reflect.ValueOf(archProperties).Elem()
+
+				// This is the archPropRoot struct. Traverse into the Targetnested struct.
+				src := archPropValues.FieldByName("Target").Elem()
+
+				// Step into non-nil pointers to structs in the src value.
+				if src.Kind() == reflect.Ptr {
+					if src.IsNil() {
+						continue
+					}
+					src = src.Elem()
+				}
+
+				// Find the requested field (e.g. android, linux_bionic) in the src struct.
+				src = src.FieldByName(field)
+
+				// Validation steps. We want valid non-nil pointers to structs.
+				if !src.IsValid() || src.IsNil() {
+					continue
+				}
+
+				if src.Kind() != reflect.Ptr || src.Elem().Kind() != reflect.Struct {
+					continue
+				}
+
+				// Clone the destination prop, since we want a unique prop struct per arch.
+				dstClone := reflect.New(reflect.ValueOf(dst).Elem().Type()).Interface()
+
+				// Copy the located property struct into the cloned destination property struct.
+				err := proptools.ExtendMatchingProperties([]interface{}{dstClone}, src.Interface(), nil, proptools.OrderReplace)
+				if err != nil {
+					// This is fine, it just means the src struct doesn't match.
+					continue
+				}
+
+				// Found the prop for the os, you have.
+				osToProp[os] = dstClone
+
+				// Go to the next prop.
+				break
+			}
+		}
+	}
+	return osToProp
+}
diff --git a/android/bazel.go b/android/bazel.go
index 9b06426..b2170be 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -108,6 +108,11 @@
 type BazelConversionConfigEntry int
 
 const (
+	// A sentinel value to be used as a key in Bp2BuildConfig for modules with
+	// no package path. This is also the module dir for top level Android.bp
+	// modules.
+	BP2BUILD_TOPLEVEL = "."
+
 	// iota + 1 ensures that the int value is not 0 when used in the Bp2buildAllowlist map,
 	// which can also mean that the key doesn't exist in a lookup.
 
@@ -127,10 +132,66 @@
 		"system/core/libcutils": Bp2BuildDefaultTrueRecursively,
 		"system/logging/liblog": Bp2BuildDefaultTrueRecursively,
 	}
+
+	// Per-module denylist to always opt modules out.
+	bp2buildModuleDoNotConvertList = []string{
+		"libBionicBenchmarksUtils",      // ruperts@, cc_library_static
+		"libbionic_spawn_benchmark",     // ruperts@, cc_library_static, depends on //system/libbase
+		"libc_jemalloc_wrapper",         // ruperts@, cc_library_static, depends on //external/jemalloc_new
+		"libc_bootstrap",                // ruperts@, cc_library_static
+		"libc_init_static",              // ruperts@, cc_library_static
+		"libc_init_dynamic",             // ruperts@, cc_library_static
+		"libc_tzcode",                   // ruperts@, cc_library_static
+		"libc_freebsd",                  // ruperts@, cc_library_static
+		"libc_freebsd_large_stack",      // ruperts@, cc_library_static
+		"libc_netbsd",                   // ruperts@, cc_library_static
+		"libc_openbsd_ndk",              // ruperts@, cc_library_static
+		"libc_openbsd_large_stack",      // ruperts@, cc_library_static
+		"libc_openbsd",                  // ruperts@, cc_library_static
+		"libc_gdtoa",                    // ruperts@, cc_library_static
+		"libc_fortify",                  // ruperts@, cc_library_static
+		"libc_bionic",                   // ruperts@, cc_library_static
+		"libc_bionic_ndk",               // ruperts@, cc_library_static, depends on //bionic/libc/system_properties
+		"libc_bionic_systrace",          // ruperts@, cc_library_static
+		"libc_pthread",                  // ruperts@, cc_library_static
+		"libc_syscalls",                 // ruperts@, cc_library_static
+		"libc_aeabi",                    // ruperts@, cc_library_static
+		"libc_ndk",                      // ruperts@, cc_library_static, depends on //bionic/libm:libm
+		"libc_nopthread",                // ruperts@, cc_library_static, depends on //external/arm-optimized-routines
+		"libc_common",                   // ruperts@, cc_library_static, depends on //bionic/libc:libc_nopthread
+		"libc_static_dispatch",          // ruperts@, cc_library_static
+		"libc_dynamic_dispatch",         // ruperts@, cc_library_static
+		"libc_common_static",            // ruperts@, cc_library_static, depends on //bionic/libc:libc_common
+		"libc_common_shared",            // ruperts@, cc_library_static, depends on //bionic/libc:libc_common
+		"libc_unwind_static",            // ruperts@, cc_library_static
+		"libc_nomalloc",                 // ruperts@, cc_library_static, depends on //bionic/libc:libc_common
+		"libasync_safe",                 // ruperts@, cc_library_static
+		"libc_malloc_debug_backtrace",   // ruperts@, cc_library_static, depends on //system/libbase
+		"libsystemproperties",           // ruperts@, cc_library_static, depends on //system/core/property_service/libpropertyinfoparser
+		"libdl_static",                  // ruperts@, cc_library_static
+		"liblinker_main",                // ruperts@, cc_library_static, depends on //system/libbase
+		"liblinker_malloc",              // ruperts@, cc_library_static, depends on //system/logging/liblog:liblog
+		"liblinker_debuggerd_stub",      // ruperts@, cc_library_static, depends on //system/libbase
+		"libbionic_tests_headers_posix", // ruperts@, cc_library_static
+		"libc_dns",                      // ruperts@, cc_library_static
+	}
+
+	// Used for quicker lookups
+	bp2buildModuleDoNotConvert = map[string]bool{}
 )
 
+func init() {
+	for _, moduleName := range bp2buildModuleDoNotConvertList {
+		bp2buildModuleDoNotConvert[moduleName] = true
+	}
+}
+
 // ConvertWithBp2build returns whether the given BazelModuleBase should be converted with bp2build.
 func (b *BazelModuleBase) ConvertWithBp2build(ctx BazelConversionPathContext) bool {
+	if bp2buildModuleDoNotConvert[ctx.Module().Name()] {
+		return false
+	}
+
 	// Ensure that the module type of this module has a bp2build converter. This
 	// prevents mixed builds from using auto-converted modules just by matching
 	// the package dir; it also has to have a bp2build mutator as well.
@@ -165,10 +226,15 @@
 func bp2buildDefaultTrueRecursively(packagePath string, config Bp2BuildConfig) bool {
 	ret := false
 
+	// Return exact matches in the config.
+	if config[packagePath] == Bp2BuildDefaultTrueRecursively {
+		return true
+	}
 	if config[packagePath] == Bp2BuildDefaultFalse {
 		return false
 	}
 
+	// If not, check for the config recursively.
 	packagePrefix := ""
 	// e.g. for x/y/z, iterate over x, x/y, then x/y/z, taking the final value from the allowlist.
 	for _, part := range strings.Split(packagePath, "/") {
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index 0595d68..5b9d3bf 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -27,20 +27,13 @@
 	"sync"
 
 	"android/soong/bazel/cquery"
+
 	"github.com/google/blueprint/bootstrap"
 
 	"android/soong/bazel"
 	"android/soong/shared"
 )
 
-type CqueryRequestType int
-
-const (
-	getAllFiles CqueryRequestType = iota
-	getCcObjectFiles
-	getAllFilesAndCcObjectFiles
-)
-
 // Map key to describe bazel cquery requests.
 type cqueryKey struct {
 	label       string
@@ -56,10 +49,6 @@
 	// Returns result files built by building the given bazel target label.
 	GetOutputFiles(label string, archType ArchType) ([]string, bool)
 
-	// Returns object files produced by compiling the given cc-related target.
-	// Retrieves these files from Bazel's CcInfo provider.
-	GetCcObjectFiles(label string, archType ArchType) ([]string, bool)
-
 	// TODO(cparsons): Other cquery-related methods should be added here.
 	// Returns the results of GetOutputFiles and GetCcObjectFiles in a single query (in that order).
 	GetOutputFilesAndCcObjectFiles(label string, archType ArchType) ([]string, []string, bool)
@@ -116,11 +105,6 @@
 	return result, ok
 }
 
-func (m MockBazelContext) GetCcObjectFiles(label string, archType ArchType) ([]string, bool) {
-	result, ok := m.AllFiles[label]
-	return result, ok
-}
-
 func (m MockBazelContext) GetOutputFilesAndCcObjectFiles(label string, archType ArchType) ([]string, []string, bool) {
 	result, ok := m.AllFiles[label]
 	return result, result, ok
@@ -154,16 +138,6 @@
 	return ret, ok
 }
 
-func (bazelCtx *bazelContext) GetCcObjectFiles(label string, archType ArchType) ([]string, bool) {
-	rawString, ok := bazelCtx.cquery(label, cquery.GetCcObjectFiles, archType)
-	var returnResult []string
-	if ok {
-		bazelOutput := strings.TrimSpace(rawString)
-		returnResult = cquery.GetCcObjectFiles.ParseResult(bazelOutput).([]string)
-	}
-	return returnResult, ok
-}
-
 func (bazelCtx *bazelContext) GetOutputFilesAndCcObjectFiles(label string, archType ArchType) ([]string, []string, bool) {
 	var outputFiles []string
 	var ccObjects []string
@@ -183,10 +157,6 @@
 	panic("unimplemented")
 }
 
-func (n noopBazelContext) GetCcObjectFiles(label string, archType ArchType) ([]string, bool) {
-	panic("unimplemented")
-}
-
 func (n noopBazelContext) GetOutputFilesAndCcObjectFiles(label string, archType ArchType) ([]string, []string, bool) {
 	panic("unimplemented")
 }
@@ -293,13 +263,23 @@
 	cmdFlags = append(cmdFlags, labels...)
 	cmdFlags = append(cmdFlags, "--package_path=%workspace%/"+context.intermediatesDir())
 	cmdFlags = append(cmdFlags, "--profile="+shared.BazelMetricsFilename(context, runName))
-	// Set default platforms to canonicalized values for mixed builds requests. If these are set
-	// in the bazelrc, they will have values that are non-canonicalized, and thus be invalid.
-	// The actual platform values here may be overridden by configuration transitions from the buildroot.
+
+	// Set default platforms to canonicalized values for mixed builds requests.
+	// If these are set in the bazelrc, they will have values that are
+	// non-canonicalized to @sourceroot labels, and thus be invalid when
+	// referenced from the buildroot.
+	//
+	// The actual platform values here may be overridden by configuration
+	// transitions from the buildroot.
 	cmdFlags = append(cmdFlags,
-		fmt.Sprintf("--platforms=%s", canonicalizeLabel("//build/bazel/platforms:generic_x86_64")))
+		fmt.Sprintf("--platforms=%s", canonicalizeLabel("//build/bazel/platforms:android_x86_64")))
 	cmdFlags = append(cmdFlags,
 		fmt.Sprintf("--extra_toolchains=%s", canonicalizeLabel("//prebuilts/clang/host/linux-x86:all")))
+	// This should be parameterized on the host OS, but let's restrict to linux
+	// to keep things simple for now.
+	cmdFlags = append(cmdFlags,
+		fmt.Sprintf("--host_platform=%s", canonicalizeLabel("//build/bazel/platforms:linux_x86_64")))
+
 	// Explicitly disable downloading rules (such as canonical C++ and Java rules) from the network.
 	cmdFlags = append(cmdFlags, "--experimental_repository_disable_download")
 	cmdFlags = append(cmdFlags, extraFlags...)
@@ -332,8 +312,13 @@
     name = "sourceroot",
     path = "%s",
 )
+
+local_repository(
+    name = "rules_cc",
+    path = "%s/build/bazel/rules_cc",
+)
 `
-	return []byte(fmt.Sprintf(formatString, context.workspaceDir))
+	return []byte(fmt.Sprintf(formatString, context.workspaceDir, context.workspaceDir))
 }
 
 func (context *bazelContext) mainBzlFileContents() []byte {
@@ -344,73 +329,39 @@
 # This file is generated by soong_build. Do not edit.
 #####################################################
 
-def _x86_64_transition_impl(settings, attr):
+def _config_node_transition_impl(settings, attr):
     return {
-        "//command_line_option:platforms": "@sourceroot//build/bazel/platforms:generic_x86_64",
+        "//command_line_option:platforms": "@sourceroot//build/bazel/platforms:android_%s" % attr.arch,
     }
 
-def _x86_transition_impl(settings, attr):
-    return {
-        "//command_line_option:platforms": "@sourceroot//build/bazel/platforms:generic_x86",
-    }
-
-def _arm64_transition_impl(settings, attr):
-    return {
-        "//command_line_option:platforms": "@sourceroot//build/bazel/platforms:generic_arm64",
-    }
-
-def _arm_transition_impl(settings, attr):
-    return {
-        "//command_line_option:platforms": "@sourceroot//build/bazel/platforms:generic_arm",
-    }
-
-x86_64_transition = transition(
-    implementation = _x86_64_transition_impl,
+_config_node_transition = transition(
+    implementation = _config_node_transition_impl,
     inputs = [],
     outputs = [
         "//command_line_option:platforms",
     ],
 )
 
-x86_transition = transition(
-    implementation = _x86_transition_impl,
-    inputs = [],
-    outputs = [
-        "//command_line_option:platforms",
-    ],
+def _passthrough_rule_impl(ctx):
+    return [DefaultInfo(files = depset(ctx.files.deps))]
+
+config_node = rule(
+    implementation = _passthrough_rule_impl,
+    attrs = {
+        "arch" : attr.string(mandatory = True),
+        "deps" : attr.label_list(cfg = _config_node_transition),
+        "_allowlist_function_transition": attr.label(default = "@bazel_tools//tools/allowlists/function_transition_allowlist"),
+    },
 )
 
-arm64_transition = transition(
-    implementation = _arm64_transition_impl,
-    inputs = [],
-    outputs = [
-        "//command_line_option:platforms",
-    ],
-)
-
-arm_transition = transition(
-    implementation = _arm_transition_impl,
-    inputs = [],
-    outputs = [
-        "//command_line_option:platforms",
-    ],
-)
-
-def _mixed_build_root_impl(ctx):
-    all_files = ctx.files.deps_x86_64 + ctx.files.deps_x86 + ctx.files.deps_arm64 + ctx.files.deps_arm
-    return [DefaultInfo(files = depset(all_files))]
 
 # Rule representing the root of the build, to depend on all Bazel targets that
 # are required for the build. Building this target will build the entire Bazel
 # build tree.
 mixed_build_root = rule(
-    implementation = _mixed_build_root_impl,
+    implementation = _passthrough_rule_impl,
     attrs = {
-        "deps_x86_64" : attr.label_list(cfg = x86_64_transition),
-        "deps_x86" : attr.label_list(cfg = x86_transition),
-        "deps_arm64" : attr.label_list(cfg = arm64_transition),
-        "deps_arm" : attr.label_list(cfg = arm_transition),
-        "_allowlist_function_transition": attr.label(default = "@bazel_tools//tools/allowlists/function_transition_allowlist"),
+        "deps" : attr.label_list(),
     },
 )
 
@@ -446,44 +397,42 @@
 	// architecture mapping.
 	formatString := `
 # This file is generated by soong_build. Do not edit.
-load(":main.bzl", "mixed_build_root", "phony_root")
+load(":main.bzl", "config_node", "mixed_build_root", "phony_root")
+
+%s
 
 mixed_build_root(name = "buildroot",
-    deps_x86_64 = [%s],
-    deps_x86 = [%s],
-    deps_arm64 = [%s],
-    deps_arm = [%s],
+    deps = [%s],
 )
 
 phony_root(name = "phonyroot",
     deps = [":buildroot"],
 )
 `
-	var deps_x86_64 []string = nil
-	var deps_x86 []string = nil
-	var deps_arm64 []string = nil
-	var deps_arm []string = nil
+	configNodeFormatString := `
+config_node(name = "%s",
+    arch = "%s",
+    deps = [%s],
+)
+`
+
+	configNodesSection := ""
+
+	labelsByArch := map[string][]string{}
 	for val, _ := range context.requests {
 		labelString := fmt.Sprintf("\"%s\"", canonicalizeLabel(val.label))
-		switch getArchString(val) {
-		case "x86_64":
-			deps_x86_64 = append(deps_x86_64, labelString)
-		case "x86":
-			deps_x86 = append(deps_x86, labelString)
-		case "arm64":
-			deps_arm64 = append(deps_arm64, labelString)
-		case "arm":
-			deps_arm = append(deps_arm, labelString)
-		default:
-			panic(fmt.Sprintf("unhandled architecture %s for %v", getArchString(val), val))
-		}
+		archString := getArchString(val)
+		labelsByArch[archString] = append(labelsByArch[archString], labelString)
 	}
 
-	return []byte(fmt.Sprintf(formatString,
-		strings.Join(deps_x86_64, ",\n            "),
-		strings.Join(deps_x86, ",\n            "),
-		strings.Join(deps_arm64, ",\n            "),
-		strings.Join(deps_arm, ",\n            ")))
+	configNodeLabels := []string{}
+	for archString, labels := range labelsByArch {
+		configNodeLabels = append(configNodeLabels, fmt.Sprintf("\":%s\"", archString))
+		labelsString := strings.Join(labels, ",\n            ")
+		configNodesSection += fmt.Sprintf(configNodeFormatString, archString, archString, labelsString)
+	}
+
+	return []byte(fmt.Sprintf(formatString, configNodesSection, strings.Join(configNodeLabels, ",\n            ")))
 }
 
 func indent(original string) string {
@@ -525,7 +474,7 @@
     return id_string + ">>" + %s(target)
 `
 
-	for _, requestType := range cquery.RequestTypes {
+	for requestType, _ := range requestTypeToCqueryIdEntries {
 		labelMapName := requestType.Name() + "_Labels"
 		functionName := requestType.Name() + "_Fn"
 		labelRegistrationMapSection += fmt.Sprintf(mapDeclarationFormatString,
@@ -558,10 +507,10 @@
   platform_name = build_options(target)["//command_line_option:platforms"][0].name
   if platform_name == "host":
     return "HOST"
-  elif not platform_name.startswith("generic_"):
-    fail("expected platform name of the form 'generic_<arch>', but was " + str(platforms))
+  elif not platform_name.startswith("android_"):
+    fail("expected platform name of the form 'android_<arch>', but was " + str(platforms))
     return "UNKNOWN"
-  return platform_name[len("generic_"):]
+  return platform_name[len("android_"):]
 
 def format(target):
   id_string = str(target.label) + "|" + get_arch(target)
@@ -732,7 +681,7 @@
 	// Register bazel-owned build statements (obtained from the aquery invocation).
 	for index, buildStatement := range ctx.Config().BazelContext.BuildStatementsToRegister() {
 		if len(buildStatement.Command) < 1 {
-			panic(fmt.Sprintf("unhandled build statement: %s", buildStatement))
+			panic(fmt.Sprintf("unhandled build statement: %v", buildStatement))
 		}
 		rule := NewRuleBuilder(pctx, ctx)
 		cmd := rule.Command()
@@ -746,6 +695,10 @@
 			cmd.Implicit(PathForBazelOut(ctx, inputPath))
 		}
 
+		if depfile := buildStatement.Depfile; depfile != nil {
+			cmd.ImplicitDepFile(PathForBazelOut(ctx, *depfile))
+		}
+
 		// This is required to silence warnings pertaining to unexpected timestamps. Particularly,
 		// some Bazel builtins (such as files in the bazel_tools directory) have far-future
 		// timestamps. Without restat, Ninja would emit warnings that the input files of a
diff --git a/android/filegroup.go b/android/filegroup.go
index abbb4d4..2f13ab8 100644
--- a/android/filegroup.go
+++ b/android/filegroup.go
@@ -30,7 +30,7 @@
 
 // https://docs.bazel.build/versions/master/be/general.html#filegroup
 type bazelFilegroupAttributes struct {
-	Srcs bazel.LabelList
+	Srcs bazel.LabelListAttribute
 }
 
 type bazelFilegroup struct {
@@ -57,8 +57,10 @@
 		return
 	}
 
+	srcs := bazel.MakeLabelListAttribute(
+		BazelLabelForModuleSrcExcludes(ctx, fg.properties.Srcs, fg.properties.Exclude_srcs))
 	attrs := &bazelFilegroupAttributes{
-		Srcs: BazelLabelForModuleSrcExcludes(ctx, fg.properties.Srcs, fg.properties.Exclude_srcs),
+		Srcs: srcs,
 	}
 
 	props := bazel.BazelTargetModuleProperties{Rule_class: "filegroup"}
diff --git a/android/fixture.go b/android/fixture.go
index 8d62958..5fc668a 100644
--- a/android/fixture.go
+++ b/android/fixture.go
@@ -26,16 +26,9 @@
 // Fixture
 // =======
 // These determine the environment within which a test can be run. Fixtures are mutable and are
-// created by FixtureFactory instances and mutated by FixturePreparer instances. They are created by
-// first creating a base Fixture (which is essentially empty) and then applying FixturePreparer
-// instances to it to modify the environment.
-//
-// FixtureFactory (deprecated)
-// ===========================
-// These are responsible for creating fixtures. Factories are immutable and are intended to be
-// initialized once and reused to create multiple fixtures. Each factory has a list of fixture
-// preparers that prepare a fixture for running a test. Factories can also be used to create other
-// factories by extending them with additional fixture preparers.
+// created and mutated by FixturePreparer instances. They are created by first creating a base
+// Fixture (which is essentially empty) and then applying FixturePreparer instances to it to modify
+// the environment.
 //
 // FixturePreparer
 // ===============
@@ -169,77 +162,6 @@
 //    PrepareForApex,
 // )
 //
-// // FixtureFactory instances have been deprecated, this remains for informational purposes to
-// // help explain some of the existing code but will be removed along with FixtureFactory.
-//
-// var javaFixtureFactory = android.NewFixtureFactory(
-//    PrepareForIntegrationTestWithJava,
-//    FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
-//      ctx.RegisterModuleType("test_module", testModule)
-//    }),
-//    javaMockFS.AddToFixture(),
-//    ...
-// }
-//
-// func TestJavaStuff(t *testing.T) {
-//   result := javaFixtureFactory.RunTest(t,
-//       android.FixtureWithRootAndroidBp(`java_library {....}`),
-//       android.MockFS{...}.AddToFixture(),
-//   )
-//   ... test result ...
-// }
-//
-// package cc
-// var PrepareForTestWithCC = GroupFixturePreparers(
-//    android.PrepareForArchMutator,
-//    android.prepareForPrebuilts,
-//    FixtureRegisterWithContext(RegisterRequiredBuildComponentsForTest),
-//    ...
-// )
-//
-// package apex
-//
-// var PrepareForApex = GroupFixturePreparers(
-//    ...
-// )
-//
-// Use modules and mutators from java, cc and apex. Any duplicate preparers (like
-// android.PrepareForArchMutator) will be automatically deduped.
-//
-// var apexFixtureFactory = android.NewFixtureFactory(
-//    PrepareForJava,
-//    PrepareForCC,
-//    PrepareForApex,
-// )
-
-// Factory for Fixture objects.
-//
-// This is configured with a set of FixturePreparer objects that are used to
-// initialize each Fixture instance this creates.
-//
-// deprecated: Use FixturePreparer instead.
-type FixtureFactory interface {
-	FixturePreparer
-}
-
-// Create a new FixtureFactory that will apply the supplied preparers.
-//
-// The buildDirSupplier is a pointer to the package level buildDir variable that is initialized by
-// the package level setUp method. It has to be a pointer to the variable as the variable will not
-// have been initialized at the time the factory is created. If it is nil then a test specific
-// temporary directory will be created instead.
-//
-// deprecated: The functionality provided by FixtureFactory will be merged into FixturePreparer
-func NewFixtureFactory(buildDirSupplier *string, preparers ...FixturePreparer) FixtureFactory {
-	f := &fixtureFactory{
-		buildDirSupplier: buildDirSupplier,
-		compositeFixturePreparer: compositeFixturePreparer{
-			preparers: dedupAndFlattenPreparers(nil, preparers),
-		},
-	}
-	f.initBaseFixturePreparer(f)
-	return f
-}
 
 // A set of mock files to add to the mock file system.
 type MockFS map[string][]byte
@@ -445,17 +367,8 @@
 	// Return the flattened and deduped list of simpleFixturePreparer pointers.
 	list() []*simpleFixturePreparer
 
-	// Creates a copy of this instance and adds some additional preparers.
-	//
-	// Before the preparers are used they are combined with the preparers provided when the factory
-	// was created, any groups of preparers are flattened, and the list is deduped so that each
-	// preparer is only used once. See the file documentation in android/fixture.go for more details.
-	//
-	// deprecated: Use GroupFixturePreparers() instead.
-	Extend(preparers ...FixturePreparer) FixturePreparer
-
 	// Create a Fixture.
-	Fixture(t *testing.T, preparers ...FixturePreparer) Fixture
+	Fixture(t *testing.T) Fixture
 
 	// ExtendWithErrorHandler creates a new FixturePreparer that will use the supplied error handler
 	// to check the errors (may be 0) reported by the test.
@@ -466,12 +379,13 @@
 
 	// Run the test, checking any errors reported and returning a TestResult instance.
 	//
-	// Shorthand for Fixture(t, preparers...).RunTest()
-	RunTest(t *testing.T, preparers ...FixturePreparer) *TestResult
+	// Shorthand for Fixture(t).RunTest()
+	RunTest(t *testing.T) *TestResult
 
 	// Run the test with the supplied Android.bp file.
 	//
-	// Shorthand for RunTest(t, android.FixtureWithRootAndroidBp(bp))
+	// preparer.RunTestWithBp(t, bp) is shorthand for
+	// android.GroupFixturePreparers(preparer, android.FixtureWithRootAndroidBp(bp)).RunTest(t)
 	RunTestWithBp(t *testing.T, bp string) *TestResult
 
 	// RunTestWithConfig is a temporary method added to help ease the migration of existing tests to
@@ -705,13 +619,11 @@
 	NinjaDeps []string
 }
 
-func createFixture(t *testing.T, buildDir string, base []*simpleFixturePreparer, extra []FixturePreparer) Fixture {
-	all := dedupAndFlattenPreparers(base, extra)
-
+func createFixture(t *testing.T, buildDir string, preparers []*simpleFixturePreparer) Fixture {
 	config := TestConfig(buildDir, nil, "", nil)
 	ctx := NewTestContext(config)
 	fixture := &fixture{
-		preparers: all,
+		preparers: preparers,
 		t:         t,
 		config:    config,
 		ctx:       ctx,
@@ -720,7 +632,7 @@
 		errorHandler: FixtureExpectsNoErrors,
 	}
 
-	for _, preparer := range all {
+	for _, preparer := range preparers {
 		preparer.function(fixture)
 	}
 
@@ -735,30 +647,25 @@
 	b.self = self
 }
 
-func (b *baseFixturePreparer) Extend(preparers ...FixturePreparer) FixturePreparer {
-	all := dedupAndFlattenPreparers(b.self.list(), preparers)
-	return newFixturePreparer(all)
-}
-
-func (b *baseFixturePreparer) Fixture(t *testing.T, preparers ...FixturePreparer) Fixture {
-	return createFixture(t, t.TempDir(), b.self.list(), preparers)
+func (b *baseFixturePreparer) Fixture(t *testing.T) Fixture {
+	return createFixture(t, t.TempDir(), b.self.list())
 }
 
 func (b *baseFixturePreparer) ExtendWithErrorHandler(errorHandler FixtureErrorHandler) FixturePreparer {
-	return b.self.Extend(newSimpleFixturePreparer(func(fixture *fixture) {
+	return GroupFixturePreparers(b.self, newSimpleFixturePreparer(func(fixture *fixture) {
 		fixture.errorHandler = errorHandler
 	}))
 }
 
-func (b *baseFixturePreparer) RunTest(t *testing.T, preparers ...FixturePreparer) *TestResult {
+func (b *baseFixturePreparer) RunTest(t *testing.T) *TestResult {
 	t.Helper()
-	fixture := b.self.Fixture(t, preparers...)
+	fixture := b.self.Fixture(t)
 	return fixture.RunTest()
 }
 
 func (b *baseFixturePreparer) RunTestWithBp(t *testing.T, bp string) *TestResult {
 	t.Helper()
-	return b.RunTest(t, FixtureWithRootAndroidBp(bp))
+	return GroupFixturePreparers(b.self, FixtureWithRootAndroidBp(bp)).RunTest(t)
 }
 
 func (b *baseFixturePreparer) RunTestWithConfig(t *testing.T, config Config) *TestResult {
@@ -783,46 +690,6 @@
 	return fixture.RunTest()
 }
 
-var _ FixtureFactory = (*fixtureFactory)(nil)
-
-type fixtureFactory struct {
-	compositeFixturePreparer
-
-	buildDirSupplier *string
-}
-
-// Override to preserve the buildDirSupplier.
-func (f *fixtureFactory) Extend(preparers ...FixturePreparer) FixturePreparer {
-	// If there is no buildDirSupplier then just use the default implementation.
-	if f.buildDirSupplier == nil {
-		return f.baseFixturePreparer.Extend(preparers...)
-	}
-
-	all := dedupAndFlattenPreparers(f.preparers, preparers)
-
-	// Create a new factory which uses the same buildDirSupplier as the previous one.
-	extendedFactory := &fixtureFactory{
-		buildDirSupplier: f.buildDirSupplier,
-		compositeFixturePreparer: compositeFixturePreparer{
-			preparers: all,
-		},
-	}
-	extendedFactory.initBaseFixturePreparer(extendedFactory)
-	return extendedFactory
-}
-
-func (f *fixtureFactory) Fixture(t *testing.T, preparers ...FixturePreparer) Fixture {
-	// If there is no buildDirSupplier then just use the default implementation.
-	if f.buildDirSupplier == nil {
-		return f.baseFixturePreparer.Fixture(t, preparers...)
-	}
-
-	// Retrieve the buildDir from the supplier.
-	buildDir := *f.buildDirSupplier
-
-	return createFixture(t, buildDir, f.preparers, preparers)
-}
-
 type fixture struct {
 	// The preparers used to create this fixture.
 	preparers []*simpleFixturePreparer
@@ -937,10 +804,10 @@
 // that produced this result.
 //
 // e.g. assuming that this result was created by running:
-//     factory.Extend(preparer1, preparer2).RunTest(t, preparer3, preparer4)
+//     GroupFixturePreparers(preparer1, preparer2, preparer3).RunTest(t)
 //
 // Then this method will be equivalent to running:
-//     GroupFixturePreparers(preparer1, preparer2, preparer3, preparer4)
+//     GroupFixturePreparers(preparer1, preparer2, preparer3)
 //
 // This is intended for use by tests whose output is Android.bp files to verify that those files
 // are valid, e.g. tests of the snapshots produced by the sdk module type.
diff --git a/android/fixture_test.go b/android/fixture_test.go
index 681a034..5b810e0 100644
--- a/android/fixture_test.go
+++ b/android/fixture_test.go
@@ -41,45 +41,42 @@
 
 	group := GroupFixturePreparers(preparer1, preparer2, preparer1, preparer1Then2)
 
-	extension := group.Extend(preparer4, preparer2)
+	extension := GroupFixturePreparers(group, preparer4, preparer2)
 
-	extension.Fixture(t, preparer1, preparer2, preparer2Then1, preparer3)
+	GroupFixturePreparers(extension, preparer1, preparer2, preparer2Then1, preparer3).Fixture(t)
 
 	AssertDeepEquals(t, "preparers called in wrong order",
 		[]string{"preparer1", "preparer2", "preparer4", "preparer3"}, list)
 }
 
 func TestFixtureValidateMockFS(t *testing.T) {
-	buildDir := "<unused>"
-	factory := NewFixtureFactory(&buildDir)
-
 	t.Run("absolute path", func(t *testing.T) {
 		AssertPanicMessageContains(t, "source path validation failed", "Path is outside directory: /abs/path/Android.bp", func() {
-			factory.Fixture(t, FixtureAddFile("/abs/path/Android.bp", nil))
+			FixtureAddFile("/abs/path/Android.bp", nil).Fixture(t)
 		})
 	})
 	t.Run("not canonical", func(t *testing.T) {
 		AssertPanicMessageContains(t, "source path validation failed", `path "path/with/../in/it/Android.bp" is not a canonical path, use "path/in/it/Android.bp" instead`, func() {
-			factory.Fixture(t, FixtureAddFile("path/with/../in/it/Android.bp", nil))
+			FixtureAddFile("path/with/../in/it/Android.bp", nil).Fixture(t)
 		})
 	})
 	t.Run("FixtureAddFile", func(t *testing.T) {
 		AssertPanicMessageContains(t, "source path validation failed", `cannot add output path "out/Android.bp" to the mock file system`, func() {
-			factory.Fixture(t, FixtureAddFile("out/Android.bp", nil))
+			FixtureAddFile("out/Android.bp", nil).Fixture(t)
 		})
 	})
 	t.Run("FixtureMergeMockFs", func(t *testing.T) {
 		AssertPanicMessageContains(t, "source path validation failed", `cannot add output path "out/Android.bp" to the mock file system`, func() {
-			factory.Fixture(t, FixtureMergeMockFs(MockFS{
+			FixtureMergeMockFs(MockFS{
 				"out/Android.bp": nil,
-			}))
+			}).Fixture(t)
 		})
 	})
 	t.Run("FixtureModifyMockFS", func(t *testing.T) {
 		AssertPanicMessageContains(t, "source path validation failed", `cannot add output path "out/Android.bp" to the mock file system`, func() {
-			factory.Fixture(t, FixtureModifyMockFS(func(fs MockFS) {
+			FixtureModifyMockFS(func(fs MockFS) {
 				fs["out/Android.bp"] = nil
-			}))
+			}).Fixture(t)
 		})
 	})
 }
diff --git a/android/mutator.go b/android/mutator.go
index 9e99bee..e25e2e8 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -202,7 +202,7 @@
 	RegisterPrebuiltsPostDepsMutators,
 	RegisterVisibilityRuleEnforcer,
 	RegisterLicensesDependencyChecker,
-	RegisterNeverallowMutator,
+	registerNeverallowMutator,
 	RegisterOverridePostDepsMutators,
 }
 
@@ -539,7 +539,7 @@
 		Name: &name,
 	}
 
-	b := t.CreateModule(factory, &nameProp, attrs).(BazelTargetModule)
+	b := t.createModuleWithoutInheritance(factory, &nameProp, attrs).(BazelTargetModule)
 	b.SetBazelTargetModuleProperties(bazelProps)
 	return b
 }
@@ -608,6 +608,11 @@
 	return module
 }
 
+func (t *topDownMutatorContext) createModuleWithoutInheritance(factory ModuleFactory, props ...interface{}) Module {
+	module := t.bp.CreateModule(ModuleFactoryAdaptor(factory), props...).(Module)
+	return module
+}
+
 func (b *bottomUpMutatorContext) MutatorName() string {
 	return b.bp.MutatorName()
 }
diff --git a/android/neverallow.go b/android/neverallow.go
index 7455e6a..a385bbc 100644
--- a/android/neverallow.go
+++ b/android/neverallow.go
@@ -42,7 +42,7 @@
 //     counts as a match
 // - it has none of the "Without" properties matched (same rules as above)
 
-func RegisterNeverallowMutator(ctx RegisterMutatorsContext) {
+func registerNeverallowMutator(ctx RegisterMutatorsContext) {
 	ctx.BottomUp("neverallow", neverallowMutator).Parallel()
 }
 
@@ -661,6 +661,22 @@
 // Overrides the default neverallow rules for the supplied config.
 //
 // For testing only.
-func SetTestNeverallowRules(config Config, testRules []Rule) {
+func setTestNeverallowRules(config Config, testRules []Rule) {
 	config.Once(neverallowRulesKey, func() interface{} { return testRules })
 }
+
+// Prepares for a test by setting neverallow rules and enabling the mutator.
+//
+// If the supplied rules are nil then the default rules are used.
+func PrepareForTestWithNeverallowRules(testRules []Rule) FixturePreparer {
+	return GroupFixturePreparers(
+		FixtureModifyConfig(func(config Config) {
+			if testRules != nil {
+				setTestNeverallowRules(config, testRules)
+			}
+		}),
+		FixtureRegisterWithContext(func(ctx RegistrationContext) {
+			ctx.PostDepsMutators(registerNeverallowMutator)
+		}),
+	)
+}
diff --git a/android/neverallow_test.go b/android/neverallow_test.go
index de0197a..268346a 100644
--- a/android/neverallow_test.go
+++ b/android/neverallow_test.go
@@ -292,7 +292,6 @@
 		ctx.RegisterModuleType("java_library_host", newMockJavaLibraryModule)
 		ctx.RegisterModuleType("java_device_for_host", newMockJavaLibraryModule)
 		ctx.RegisterModuleType("makefile_goal", newMockMakefileGoalModule)
-		ctx.PostDepsMutators(RegisterNeverallowMutator)
 	}),
 )
 
@@ -301,12 +300,7 @@
 		t.Run(test.name, func(t *testing.T) {
 			GroupFixturePreparers(
 				prepareForNeverAllowTest,
-				FixtureModifyConfig(func(config Config) {
-					// If the test has its own rules then use them instead of the default ones.
-					if test.rules != nil {
-						SetTestNeverallowRules(config, test.rules)
-					}
-				}),
+				PrepareForTestWithNeverallowRules(test.rules),
 				test.fs.AddToFixture(),
 			).
 				ExtendWithErrorHandler(FixtureExpectsAllErrorsToMatchAPattern(test.expectedErrors)).
diff --git a/android/paths.go b/android/paths.go
index 0ed988a..ba1ab11 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -174,14 +174,41 @@
 	// example, Rel on a PathsForModuleSrc would return the path relative to the module source
 	// directory, and OutputPath.Join("foo").Rel() would return "foo".
 	Rel() string
+
+	// RelativeToTop returns a new path relative to the top, it is provided solely for use in tests.
+	//
+	// It is guaranteed to always return the same type as it is called on, e.g. if called on an
+	// InstallPath then the returned value can be converted to an InstallPath.
+	//
+	// A standard build has the following structure:
+	//   ../top/
+	//          out/ - make install files go here.
+	//          out/soong - this is the buildDir passed to NewTestConfig()
+	//          ... - the source files
+	//
+	// This function converts a path so that it appears relative to the ../top/ directory, i.e.
+	// * Make install paths, which have the pattern "buildDir/../<path>" are converted into the top
+	//   relative path "out/<path>"
+	// * Soong install paths and other writable paths, which have the pattern "buildDir/<path>" are
+	//   converted into the top relative path "out/soong/<path>".
+	// * Source paths are already relative to the top.
+	// * Phony paths are not relative to anything.
+	// * toolDepPath have an absolute but known value in so don't need making relative to anything in
+	//   order to test.
+	RelativeToTop() Path
 }
 
+const (
+	OutDir      = "out"
+	OutSoongDir = OutDir + "/soong"
+)
+
 // WritablePath is a type of path that can be used as an output for build rules.
 type WritablePath interface {
 	Path
 
 	// return the path to the build directory.
-	buildDir() string
+	getBuildDir() string
 
 	// the writablePath method doesn't directly do anything,
 	// but it allows a struct to distinguish between whether or not it implements the WritablePath interface
@@ -259,6 +286,16 @@
 	return p.path
 }
 
+// RelativeToTop returns an OptionalPath with the path that was embedded having been replaced by the
+// result of calling Path.RelativeToTop on it.
+func (p OptionalPath) RelativeToTop() OptionalPath {
+	if !p.valid {
+		return p
+	}
+	p.path = p.path.RelativeToTop()
+	return p
+}
+
 // String returns the string version of the Path, or "" if it isn't valid.
 func (p OptionalPath) String() string {
 	if p.valid {
@@ -271,6 +308,20 @@
 // Paths is a slice of Path objects, with helpers to operate on the collection.
 type Paths []Path
 
+// RelativeToTop creates a new Paths containing the result of calling Path.RelativeToTop on each
+// item in this slice.
+func (p Paths) RelativeToTop() Paths {
+	ensureTestOnly()
+	if p == nil {
+		return p
+	}
+	ret := make(Paths, len(p))
+	for i, path := range p {
+		ret[i] = path.RelativeToTop()
+	}
+	return ret
+}
+
 func (paths Paths) containsPath(path Path) bool {
 	for _, p := range paths {
 		if p == path {
@@ -436,6 +487,9 @@
 // already be resolved by either deps mutator or path deps mutator.
 func getOtherModuleLabel(ctx BazelConversionPathContext, dep, tag string) bazel.Label {
 	m, _ := ctx.GetDirectDep(dep)
+	if m == nil {
+		panic(fmt.Errorf("cannot get direct dep %s of %s", dep, ctx.Module().Name()))
+	}
 	otherLabel := bazelModuleLabel(ctx, m, tag)
 	label := bazelModuleLabel(ctx, ctx.Module(), "")
 	if samePackage(label, otherLabel) {
@@ -910,6 +964,20 @@
 // WritablePaths is a slice of WritablePath, used for multiple outputs.
 type WritablePaths []WritablePath
 
+// RelativeToTop creates a new WritablePaths containing the result of calling Path.RelativeToTop on
+// each item in this slice.
+func (p WritablePaths) RelativeToTop() WritablePaths {
+	ensureTestOnly()
+	if p == nil {
+		return p
+	}
+	ret := make(WritablePaths, len(p))
+	for i, path := range p {
+		ret[i] = path.RelativeToTop().(WritablePath)
+	}
+	return ret
+}
+
 // Strings returns the string forms of the writable paths.
 func (p WritablePaths) Strings() []string {
 	if p == nil {
@@ -935,9 +1003,8 @@
 }
 
 type basePath struct {
-	path   string
-	config Config
-	rel    string
+	path string
+	rel  string
 }
 
 func (p basePath) Ext() string {
@@ -968,6 +1035,14 @@
 // SourcePath is a Path representing a file path rooted from SrcDir
 type SourcePath struct {
 	basePath
+
+	// The sources root, i.e. Config.SrcDir()
+	srcDir string
+}
+
+func (p SourcePath) RelativeToTop() Path {
+	ensureTestOnly()
+	return p
 }
 
 var _ Path = SourcePath{}
@@ -981,7 +1056,7 @@
 // code that is embedding ninja variables in paths
 func safePathForSource(ctx PathContext, pathComponents ...string) (SourcePath, error) {
 	p, err := validateSafePath(pathComponents...)
-	ret := SourcePath{basePath{p, ctx.Config(), ""}}
+	ret := SourcePath{basePath{p, ""}, ctx.Config().srcDir}
 	if err != nil {
 		return ret, err
 	}
@@ -997,7 +1072,7 @@
 // pathForSource creates a SourcePath from pathComponents, but does not check that it exists.
 func pathForSource(ctx PathContext, pathComponents ...string) (SourcePath, error) {
 	p, err := validatePath(pathComponents...)
-	ret := SourcePath{basePath{p, ctx.Config(), ""}}
+	ret := SourcePath{basePath{p, ""}, ctx.Config().srcDir}
 	if err != nil {
 		return ret, err
 	}
@@ -1091,7 +1166,7 @@
 }
 
 func (p SourcePath) String() string {
-	return filepath.Join(p.config.srcDir, p.path)
+	return filepath.Join(p.srcDir, p.path)
 }
 
 // Join creates a new SourcePath with paths... joined with the current path. The
@@ -1123,7 +1198,7 @@
 		ReportPathErrorf(ctx, "Cannot find relative path for %s(%s)", reflect.TypeOf(path).Name(), path)
 		return OptionalPath{}
 	}
-	dir := filepath.Join(p.config.srcDir, p.path, relDir)
+	dir := filepath.Join(p.srcDir, p.path, relDir)
 	// Use Glob so that we are run again if the directory is added.
 	if pathtools.IsGlob(dir) {
 		ReportPathErrorf(ctx, "Path may not contain a glob: %s", dir)
@@ -1136,13 +1211,17 @@
 	if len(paths) == 0 {
 		return OptionalPath{}
 	}
-	relPath := Rel(ctx, p.config.srcDir, paths[0])
+	relPath := Rel(ctx, p.srcDir, paths[0])
 	return OptionalPathForPath(PathForSource(ctx, relPath))
 }
 
 // OutputPath is a Path representing an intermediates file path rooted from the build directory
 type OutputPath struct {
 	basePath
+
+	// The soong build directory, i.e. Config.BuildDir()
+	buildDir string
+
 	fullPath string
 }
 
@@ -1157,8 +1236,18 @@
 	return p
 }
 
-func (p OutputPath) buildDir() string {
-	return p.config.buildDir
+func (p OutputPath) getBuildDir() string {
+	return p.buildDir
+}
+
+func (p OutputPath) RelativeToTop() Path {
+	return p.outputPathRelativeToTop()
+}
+
+func (p OutputPath) outputPathRelativeToTop() OutputPath {
+	p.fullPath = StringPathRelativeToTop(p.buildDir, p.fullPath)
+	p.buildDir = OutSoongDir
+	return p
 }
 
 func (p OutputPath) objPathWithExt(ctx ModuleOutPathContext, subdir, ext string) ModuleObjPath {
@@ -1174,6 +1263,11 @@
 	basePath
 }
 
+func (t toolDepPath) RelativeToTop() Path {
+	ensureTestOnly()
+	return t
+}
+
 var _ Path = toolDepPath{}
 
 // pathForBuildToolDep returns a toolDepPath representing the given path string.
@@ -1182,7 +1276,7 @@
 // Only use this function to construct paths for dependencies of the build
 // tool invocation.
 func pathForBuildToolDep(ctx PathContext, path string) toolDepPath {
-	return toolDepPath{basePath{path, ctx.Config(), ""}}
+	return toolDepPath{basePath{path, ""}}
 }
 
 // PathForOutput joins the provided paths and returns an OutputPath that is
@@ -1195,7 +1289,7 @@
 	}
 	fullPath := filepath.Join(ctx.Config().buildDir, path)
 	path = fullPath[len(fullPath)-len(path):]
-	return OutputPath{basePath{path, ctx.Config(), ""}, fullPath}
+	return OutputPath{basePath{path, ""}, ctx.Config().buildDir, fullPath}
 }
 
 // PathsForOutput returns Paths rooted from buildDir
@@ -1351,7 +1445,13 @@
 	OutputPath
 }
 
+func (p ModuleOutPath) RelativeToTop() Path {
+	p.OutputPath = p.outputPathRelativeToTop()
+	return p
+}
+
 var _ Path = ModuleOutPath{}
+var _ WritablePath = ModuleOutPath{}
 
 func (p ModuleOutPath) objPathWithExt(ctx ModuleOutPathContext, subdir, ext string) ModuleObjPath {
 	return PathForModuleObj(ctx, subdir, pathtools.ReplaceExtension(p.path, ext))
@@ -1429,7 +1529,8 @@
 		reportPathError(ctx, err)
 	}
 
-	outputPath := OutputPath{basePath{"", ctx.Config(), ""},
+	outputPath := OutputPath{basePath{"", ""},
+		ctx.Config().buildDir,
 		ctx.Config().BazelContext.OutputBase()}
 
 	return BazelOutPath{
@@ -1455,7 +1556,13 @@
 	ModuleOutPath
 }
 
+func (p ModuleGenPath) RelativeToTop() Path {
+	p.OutputPath = p.outputPathRelativeToTop()
+	return p
+}
+
 var _ Path = ModuleGenPath{}
+var _ WritablePath = ModuleGenPath{}
 var _ genPathProvider = ModuleGenPath{}
 var _ objPathProvider = ModuleGenPath{}
 
@@ -1488,7 +1595,13 @@
 	ModuleOutPath
 }
 
+func (p ModuleObjPath) RelativeToTop() Path {
+	p.OutputPath = p.outputPathRelativeToTop()
+	return p
+}
+
 var _ Path = ModuleObjPath{}
+var _ WritablePath = ModuleObjPath{}
 
 // PathForModuleObj returns a Path representing the paths... under the module's
 // 'obj' directory.
@@ -1506,7 +1619,13 @@
 	ModuleOutPath
 }
 
+func (p ModuleResPath) RelativeToTop() Path {
+	p.OutputPath = p.outputPathRelativeToTop()
+	return p
+}
+
 var _ Path = ModuleResPath{}
+var _ WritablePath = ModuleResPath{}
 
 // PathForModuleRes returns a Path representing the paths... under the module's
 // 'res' directory.
@@ -1523,6 +1642,9 @@
 type InstallPath struct {
 	basePath
 
+	// The soong build directory, i.e. Config.BuildDir()
+	buildDir string
+
 	// partitionDir is the part of the InstallPath that is automatically determined according to the context.
 	// For example, it is host/<os>-<arch> for host modules, and target/product/<device>/<partition> for device modules.
 	partitionDir string
@@ -1531,8 +1653,22 @@
 	makePath bool
 }
 
-func (p InstallPath) buildDir() string {
-	return p.config.buildDir
+// Will panic if called from outside a test environment.
+func ensureTestOnly() {
+	if PrefixInList(os.Args, "-test.") {
+		return
+	}
+	panic(fmt.Errorf("Not in test. Command line:\n  %s", strings.Join(os.Args, "\n  ")))
+}
+
+func (p InstallPath) RelativeToTop() Path {
+	ensureTestOnly()
+	p.buildDir = OutSoongDir
+	return p
+}
+
+func (p InstallPath) getBuildDir() string {
+	return p.buildDir
 }
 
 func (p InstallPath) ReplaceExtension(ctx PathContext, ext string) OutputPath {
@@ -1547,9 +1683,9 @@
 func (p InstallPath) String() string {
 	if p.makePath {
 		// Make path starts with out/ instead of out/soong.
-		return filepath.Join(p.config.buildDir, "../", p.path)
+		return filepath.Join(p.buildDir, "../", p.path)
 	} else {
-		return filepath.Join(p.config.buildDir, p.path)
+		return filepath.Join(p.buildDir, p.path)
 	}
 }
 
@@ -1558,9 +1694,9 @@
 // The ./soong is dropped if the install path is for Make.
 func (p InstallPath) PartitionDir() string {
 	if p.makePath {
-		return filepath.Join(p.config.buildDir, "../", p.partitionDir)
+		return filepath.Join(p.buildDir, "../", p.partitionDir)
 	} else {
-		return filepath.Join(p.config.buildDir, p.partitionDir)
+		return filepath.Join(p.buildDir, p.partitionDir)
 	}
 }
 
@@ -1643,7 +1779,8 @@
 	}
 
 	base := InstallPath{
-		basePath:     basePath{partionPath, ctx.Config(), ""},
+		basePath:     basePath{partionPath, ""},
+		buildDir:     ctx.Config().buildDir,
 		partitionDir: partionPath,
 		makePath:     false,
 	}
@@ -1653,7 +1790,8 @@
 
 func pathForNdkOrSdkInstall(ctx PathContext, prefix string, paths []string) InstallPath {
 	base := InstallPath{
-		basePath:     basePath{prefix, ctx.Config(), ""},
+		basePath:     basePath{prefix, ""},
+		buildDir:     ctx.Config().buildDir,
 		partitionDir: prefix,
 		makePath:     false,
 	}
@@ -1788,7 +1926,7 @@
 	if strings.ContainsAny(phony, "$/") {
 		ReportPathErrorf(ctx, "Phony target contains invalid character ($ or /): %s", phony)
 	}
-	return PhonyPath{basePath{phony, ctx.Config(), ""}}
+	return PhonyPath{basePath{phony, ""}}
 }
 
 type PhonyPath struct {
@@ -1797,8 +1935,16 @@
 
 func (p PhonyPath) writablePath() {}
 
-func (p PhonyPath) buildDir() string {
-	return p.config.buildDir
+func (p PhonyPath) getBuildDir() string {
+	// A phone path cannot contain any / so cannot be relative to the build directory.
+	return ""
+}
+
+func (p PhonyPath) RelativeToTop() Path {
+	ensureTestOnly()
+	// A phony path cannot contain any / so does not have a build directory so switching to a new
+	// build directory has no effect so just return this path.
+	return p
 }
 
 func (p PhonyPath) ReplaceExtension(ctx PathContext, ext string) OutputPath {
@@ -1812,10 +1958,17 @@
 	basePath
 }
 
+func (p testPath) RelativeToTop() Path {
+	ensureTestOnly()
+	return p
+}
+
 func (p testPath) String() string {
 	return p.path
 }
 
+var _ Path = testPath{}
+
 // PathForTesting returns a Path constructed from joining the elements of paths with '/'.  It should only be used from
 // within tests.
 func PathForTesting(paths ...string) Path {
diff --git a/android/prebuilt.go b/android/prebuilt.go
index 04864a1..2fc4782 100644
--- a/android/prebuilt.go
+++ b/android/prebuilt.go
@@ -82,6 +82,12 @@
 }
 
 func (p *Prebuilt) Name(name string) string {
+	return PrebuiltNameFromSource(name)
+}
+
+// PrebuiltNameFromSource returns the result of prepending the "prebuilt_" prefix to the supplied
+// name.
+func PrebuiltNameFromSource(name string) string {
 	return "prebuilt_" + name
 }
 
@@ -93,22 +99,24 @@
 	return proptools.Bool(p.properties.Prefer)
 }
 
-// The below source-related functions and the srcs, src fields are based on an assumption that
-// prebuilt modules have a static source property at the moment. Currently there is only one
-// exception, android_app_import, which chooses a source file depending on the product's DPI
-// preference configs. We'll want to add native support for dynamic source cases if we end up having
-// more modules like this.
-func (p *Prebuilt) SingleSourcePath(ctx ModuleContext) Path {
-	if p.srcsSupplier != nil {
-		srcs := p.srcsSupplier(ctx, ctx.Module())
+// SingleSourcePathFromSupplier invokes the supplied supplier for the current module in the
+// supplied context to retrieve a list of file paths, ensures that the returned list of file paths
+// contains a single value and then assumes that is a module relative file path and converts it to
+// a Path accordingly.
+//
+// Any issues, such as nil supplier or not exactly one file path will be reported as errors on the
+// supplied context and this will return nil.
+func SingleSourcePathFromSupplier(ctx ModuleContext, srcsSupplier PrebuiltSrcsSupplier, srcsPropertyName string) Path {
+	if srcsSupplier != nil {
+		srcs := srcsSupplier(ctx, ctx.Module())
 
 		if len(srcs) == 0 {
-			ctx.PropertyErrorf(p.srcsPropertyName, "missing prebuilt source file")
+			ctx.PropertyErrorf(srcsPropertyName, "missing prebuilt source file")
 			return nil
 		}
 
 		if len(srcs) > 1 {
-			ctx.PropertyErrorf(p.srcsPropertyName, "multiple prebuilt source files")
+			ctx.PropertyErrorf(srcsPropertyName, "multiple prebuilt source files")
 			return nil
 		}
 
@@ -122,6 +130,15 @@
 	}
 }
 
+// The below source-related functions and the srcs, src fields are based on an assumption that
+// prebuilt modules have a static source property at the moment. Currently there is only one
+// exception, android_app_import, which chooses a source file depending on the product's DPI
+// preference configs. We'll want to add native support for dynamic source cases if we end up having
+// more modules like this.
+func (p *Prebuilt) SingleSourcePath(ctx ModuleContext) Path {
+	return SingleSourcePathFromSupplier(ctx, p.srcsSupplier, p.srcsPropertyName)
+}
+
 func (p *Prebuilt) UsePrebuilt() bool {
 	return p.properties.UsePrebuilt
 }
@@ -213,6 +230,26 @@
 	Prebuilt() *Prebuilt
 }
 
+// IsModulePreferred returns true if the given module is preferred.
+//
+// A source module is preferred if there is no corresponding prebuilt module or the prebuilt module
+// does not have "prefer: true".
+//
+// A prebuilt module is preferred if there is no corresponding source module or the prebuilt module
+// has "prefer: true".
+func IsModulePreferred(module Module) bool {
+	if module.IsReplacedByPrebuilt() {
+		// A source module that has been replaced by a prebuilt counterpart.
+		return false
+	}
+	if prebuilt, ok := module.(PrebuiltInterface); ok {
+		if p := prebuilt.Prebuilt(); p != nil {
+			return p.UsePrebuilt()
+		}
+	}
+	return true
+}
+
 func RegisterPrebuiltsPreArchMutators(ctx RegisterMutatorsContext) {
 	ctx.BottomUp("prebuilt_rename", PrebuiltRenameMutator).Parallel()
 }
@@ -272,11 +309,9 @@
 	}
 }
 
-// PrebuiltPostDepsMutator does two operations.  It replace dependencies on the
-// source module with dependencies on the prebuilt when both modules exist and
-// the prebuilt should be used.  When the prebuilt should not be used, disable
-// installing it.  Secondly, it also adds a sourcegroup to any filegroups found
-// in the prebuilt's 'Srcs' property.
+// PrebuiltPostDepsMutator replaces dependencies on the source module with dependencies on the
+// prebuilt when both modules exist and the prebuilt should be used.  When the prebuilt should not
+// be used, disable installing it.
 func PrebuiltPostDepsMutator(ctx BottomUpMutatorContext) {
 	if m, ok := ctx.Module().(PrebuiltInterface); ok && m.Prebuilt() != nil {
 		p := m.Prebuilt()
diff --git a/android/queryview.go b/android/queryview.go
index 12d14df..224652e 100644
--- a/android/queryview.go
+++ b/android/queryview.go
@@ -68,7 +68,7 @@
 			Command: fmt.Sprintf(
 				`rm -rf "${outDir}/"* && `+
 					`mkdir -p "${outDir}" && `+
-					`echo WORKSPACE: cat "%s" > "${outDir}/.queryview-depfile.d" && `+
+					`echo WORKSPACE: $$(cat "%s") > "${outDir}/.queryview-depfile.d" && `+
 					`BUILDER="%s" && `+
 					`echo BUILDER=$$BUILDER && `+
 					`cd "$$(dirname "$$BUILDER")" && `+
diff --git a/android/register.go b/android/register.go
index 35469d4..4c8088d 100644
--- a/android/register.go
+++ b/android/register.go
@@ -192,6 +192,15 @@
 		t.register(ctx)
 	}
 
+	if ctx.config.BazelContext.BazelEnabled() {
+		// Hydrate the configuration of bp2build-enabled module types. This is
+		// required as a signal to identify which modules should be deferred to
+		// Bazel in mixed builds, if it is enabled.
+		for t, _ := range bp2buildMutators {
+			ctx.config.bp2buildModuleTypeConfig[t] = true
+		}
+	}
+
 	mutators := collateGloballyRegisteredMutators()
 	mutators.registerAll(ctx)
 
diff --git a/android/rule_builder.go b/android/rule_builder.go
index 0d8e2b7..06e82c8 100644
--- a/android/rule_builder.go
+++ b/android/rule_builder.go
@@ -28,6 +28,7 @@
 
 	"android/soong/cmd/sbox/sbox_proto"
 	"android/soong/remoteexec"
+	"android/soong/response"
 	"android/soong/shared"
 )
 
@@ -408,30 +409,21 @@
 func (r *RuleBuilder) RspFileInputs() Paths {
 	var rspFileInputs Paths
 	for _, c := range r.commands {
-		if c.rspFileInputs != nil {
-			if rspFileInputs != nil {
-				panic("Multiple commands in a rule may not have rsp file inputs")
-			}
-			rspFileInputs = c.rspFileInputs
+		for _, rspFile := range c.rspFiles {
+			rspFileInputs = append(rspFileInputs, rspFile.paths...)
 		}
 	}
 
 	return rspFileInputs
 }
 
-// RspFile returns the path to the rspfile that was passed to the RuleBuilderCommand.FlagWithRspFileInputList method.
-func (r *RuleBuilder) RspFile() WritablePath {
-	var rspFile WritablePath
+func (r *RuleBuilder) rspFiles() []rspFileAndPaths {
+	var rspFiles []rspFileAndPaths
 	for _, c := range r.commands {
-		if c.rspFile != nil {
-			if rspFile != nil {
-				panic("Multiple commands in a rule may not have rsp file inputs")
-			}
-			rspFile = c.rspFile
-		}
+		rspFiles = append(rspFiles, c.rspFiles...)
 	}
 
-	return rspFile
+	return rspFiles
 }
 
 // Commands returns a slice containing the built command line for each call to RuleBuilder.Command.
@@ -459,26 +451,6 @@
 		Inputs(depFiles.Paths())
 }
 
-// composeRspFileContent returns a string that will serve as the contents of the rsp file to pass
-// the listed input files to the command running in the sandbox.
-func (r *RuleBuilder) composeRspFileContent(rspFileInputs Paths) string {
-	if r.sboxInputs {
-		if len(rspFileInputs) > 0 {
-			// When SandboxInputs is used the paths need to be rewritten to be relative to the sandbox
-			// directory so that they are valid after sbox chdirs into the sandbox directory.
-			return proptools.NinjaEscape(strings.Join(r.sboxPathsForInputsRel(rspFileInputs), " "))
-		} else {
-			// If the list of inputs is empty fall back to "$in" so that the rspfilecontent Ninja
-			// variable is set to something non-empty, otherwise ninja will complain.  The inputs
-			// will be empty (all the non-rspfile inputs are implicits), so $in will evaluate to
-			// an empty string.
-			return "$in"
-		}
-	} else {
-		return "$in"
-	}
-}
-
 // Build adds the built command line to the build graph, with dependencies on Inputs and Tools, and output files for
 // Outputs.
 func (r *RuleBuilder) Build(name string, desc string) {
@@ -520,8 +492,7 @@
 	commands := r.Commands()
 	outputs := r.Outputs()
 	inputs := r.Inputs()
-	rspFileInputs := r.RspFileInputs()
-	rspFilePath := r.RspFile()
+	rspFiles := r.rspFiles()
 
 	if len(commands) == 0 {
 		return
@@ -575,11 +546,22 @@
 				})
 			}
 
-			// If using an rsp file copy it into the sbox directory.
-			if rspFilePath != nil {
-				command.CopyBefore = append(command.CopyBefore, &sbox_proto.Copy{
-					From: proto.String(rspFilePath.String()),
-					To:   proto.String(r.sboxPathForInputRel(rspFilePath)),
+			// If using rsp files copy them and their contents into the sbox directory with
+			// the appropriate path mappings.
+			for _, rspFile := range rspFiles {
+				command.RspFiles = append(command.RspFiles, &sbox_proto.RspFile{
+					File: proto.String(rspFile.file.String()),
+					// These have to match the logic in sboxPathForInputRel
+					PathMappings: []*sbox_proto.PathMapping{
+						{
+							From: proto.String(r.outDir.String()),
+							To:   proto.String(sboxOutSubDir),
+						},
+						{
+							From: proto.String(PathForOutput(r.ctx).String()),
+							To:   proto.String(sboxOutSubDir),
+						},
+					},
 				})
 			}
 
@@ -641,20 +623,30 @@
 		inputs = append(inputs, sboxCmd.inputs...)
 
 		if r.rbeParams != nil {
-			var remoteInputs []string
-			remoteInputs = append(remoteInputs, inputs.Strings()...)
-			remoteInputs = append(remoteInputs, tools.Strings()...)
-			remoteInputs = append(remoteInputs, rspFileInputs.Strings()...)
-			if rspFilePath != nil {
-				remoteInputs = append(remoteInputs, rspFilePath.String())
+			// RBE needs a list of input files to copy to the remote builder.  For inputs already
+			// listed in an rsp file, pass the rsp file directly to rewrapper.  For the rest,
+			// create a new rsp file to pass to rewrapper.
+			var remoteRspFiles Paths
+			var remoteInputs Paths
+
+			remoteInputs = append(remoteInputs, inputs...)
+			remoteInputs = append(remoteInputs, tools...)
+
+			for _, rspFile := range rspFiles {
+				remoteInputs = append(remoteInputs, rspFile.file)
+				remoteRspFiles = append(remoteRspFiles, rspFile.file)
 			}
-			inputsListFile := r.sboxManifestPath.ReplaceExtension(r.ctx, "rbe_inputs.list")
-			inputsListContents := rspFileForInputs(remoteInputs)
-			WriteFileRule(r.ctx, inputsListFile, inputsListContents)
-			inputs = append(inputs, inputsListFile)
+
+			if len(remoteInputs) > 0 {
+				inputsListFile := r.sboxManifestPath.ReplaceExtension(r.ctx, "rbe_inputs.list")
+				writeRspFileRule(r.ctx, inputsListFile, remoteInputs)
+				remoteRspFiles = append(remoteRspFiles, inputsListFile)
+				// Add the new rsp file as an extra input to the rule.
+				inputs = append(inputs, inputsListFile)
+			}
 
 			r.rbeParams.OutputFiles = outputs.Strings()
-			r.rbeParams.RSPFile = inputsListFile.String()
+			r.rbeParams.RSPFiles = remoteRspFiles.Strings()
 			rewrapperCommand := r.rbeParams.NoVarTemplate(r.ctx.Config().RBEWrapper())
 			commandString = rewrapperCommand + " bash -c '" + strings.ReplaceAll(commandString, `'`, `'\''`) + "'"
 		}
@@ -672,9 +664,24 @@
 	implicitOutputs := outputs[1:]
 
 	var rspFile, rspFileContent string
-	if rspFilePath != nil {
-		rspFile = rspFilePath.String()
-		rspFileContent = r.composeRspFileContent(rspFileInputs)
+	var rspFileInputs Paths
+	if len(rspFiles) > 0 {
+		// The first rsp files uses Ninja's rsp file support for the rule
+		rspFile = rspFiles[0].file.String()
+		// Use "$in" for rspFileContent to avoid duplicating the list of files in the dependency
+		// list and in the contents of the rsp file.  Inputs to the rule that are not in the
+		// rsp file will be listed in Implicits instead of Inputs so they don't show up in "$in".
+		rspFileContent = "$in"
+		rspFileInputs = append(rspFileInputs, rspFiles[0].paths...)
+
+		for _, rspFile := range rspFiles[1:] {
+			// Any additional rsp files need an extra rule to write the file.
+			writeRspFileRule(r.ctx, rspFile.file, rspFile.paths)
+			// The main rule needs to depend on the inputs listed in the extra rsp file.
+			inputs = append(inputs, rspFile.paths...)
+			// The main rule needs to depend on the extra rsp file.
+			inputs = append(inputs, rspFile.file)
+		}
 	}
 
 	var pool blueprint.Pool
@@ -725,8 +732,12 @@
 	depFiles       WritablePaths
 	tools          Paths
 	packagedTools  []PackagingSpec
-	rspFileInputs  Paths
-	rspFile        WritablePath
+	rspFiles       []rspFileAndPaths
+}
+
+type rspFileAndPaths struct {
+	file  WritablePath
+	paths Paths
 }
 
 func (c *RuleBuilderCommand) addInput(path Path) string {
@@ -782,13 +793,6 @@
 	return path.String()
 }
 
-// SboxPathForTool takes a path to a tool, which may be an output file or a source file, and returns
-// the corresponding path for the tool in the sbox sandbox.  It assumes that sandboxing and tool
-// sandboxing are enabled.
-func SboxPathForTool(ctx BuilderContext, path Path) string {
-	return filepath.Join(sboxSandboxBaseDir, sboxPathForToolRel(ctx, path))
-}
-
 func sboxPathForToolRel(ctx BuilderContext, path Path) string {
 	// Errors will be handled in RuleBuilder.Build where we have a context to report them
 	relOut, isRelOut, _ := maybeRelErr(PathForOutput(ctx, "host", ctx.Config().PrebuiltOS()).String(), path.String())
@@ -831,17 +835,21 @@
 	return ret
 }
 
-// SboxPathForPackagedTool takes a PackageSpec for a tool and returns the corresponding path for the
-// tool after copying it into the sandbox.  This can be used  on the RuleBuilder command line to
-// reference the tool.
-func SboxPathForPackagedTool(spec PackagingSpec) string {
-	return filepath.Join(sboxSandboxBaseDir, sboxPathForPackagedToolRel(spec))
-}
-
 func sboxPathForPackagedToolRel(spec PackagingSpec) string {
 	return filepath.Join(sboxToolsSubDir, "out", spec.relPathInPackage)
 }
 
+// PathForPackagedTool takes a PackageSpec for a tool and returns the corresponding path for the
+// tool after copying it into the sandbox.  This can be used  on the RuleBuilder command line to
+// reference the tool.
+func (c *RuleBuilderCommand) PathForPackagedTool(spec PackagingSpec) string {
+	if !c.rule.sboxTools {
+		panic("PathForPackagedTool() requires SandboxTools()")
+	}
+
+	return filepath.Join(sboxSandboxBaseDir, sboxPathForPackagedToolRel(spec))
+}
+
 // PathForTool takes a path to a tool, which may be an output file or a source file, and returns
 // the corresponding path for the tool in the sbox sandbox if sbox is enabled, or the original path
 // if it is not.  This can be used  on the RuleBuilder command line to reference the tool.
@@ -852,6 +860,20 @@
 	return path.String()
 }
 
+// PathsForTools takes a list of paths to tools, which may be output files or source files, and
+// returns the corresponding paths for the tools in the sbox sandbox if sbox is enabled, or the
+// original paths if it is not.  This can be used  on the RuleBuilder command line to reference the tool.
+func (c *RuleBuilderCommand) PathsForTools(paths Paths) []string {
+	if c.rule.sbox && c.rule.sboxTools {
+		var ret []string
+		for _, path := range paths {
+			ret = append(ret, filepath.Join(sboxSandboxBaseDir, sboxPathForToolRel(c.rule.ctx, path)))
+		}
+		return ret
+	}
+	return paths.Strings()
+}
+
 // PackagedTool adds the specified tool path to the command line.  It can only be used with tool
 // sandboxing enabled by SandboxTools(), and will copy the tool into the sandbox.
 func (c *RuleBuilderCommand) PackagedTool(spec PackagingSpec) *RuleBuilderCommand {
@@ -1170,22 +1192,19 @@
 	return c.Text(flag + c.PathForOutput(path))
 }
 
-// FlagWithRspFileInputList adds the specified flag and path to an rspfile to the command line, with no separator
-// between them.  The paths will be written to the rspfile.  If sbox is enabled, the rspfile must
-// be outside the sbox directory.
+// FlagWithRspFileInputList adds the specified flag and path to an rspfile to the command line, with
+// no separator between them.  The paths will be written to the rspfile.  If sbox is enabled, the
+// rspfile must be outside the sbox directory.  The first use of FlagWithRspFileInputList in any
+// RuleBuilderCommand of a RuleBuilder will use Ninja's rsp file support for the rule, additional
+// uses will result in an auxiliary rules to write the rspFile contents.
 func (c *RuleBuilderCommand) FlagWithRspFileInputList(flag string, rspFile WritablePath, paths Paths) *RuleBuilderCommand {
-	if c.rspFileInputs != nil {
-		panic("FlagWithRspFileInputList cannot be called if rsp file inputs have already been provided")
-	}
-
 	// Use an empty slice if paths is nil, the non-nil slice is used as an indicator that the rsp file must be
 	// generated.
 	if paths == nil {
 		paths = Paths{}
 	}
 
-	c.rspFileInputs = paths
-	c.rspFile = rspFile
+	c.rspFiles = append(c.rspFiles, rspFileAndPaths{rspFile, paths})
 
 	if c.rule.sbox {
 		if _, isRel, _ := maybeRelErr(c.rule.outDir.String(), rspFile.String()); isRel {
@@ -1264,13 +1283,12 @@
 }
 func (builderContextForTests) Build(PackageContext, BuildParams) {}
 
-func rspFileForInputs(paths []string) string {
-	s := strings.Builder{}
-	for i, path := range paths {
-		if i != 0 {
-			s.WriteByte(' ')
-		}
-		s.WriteString(proptools.ShellEscape(path))
+func writeRspFileRule(ctx BuilderContext, rspFile WritablePath, paths Paths) {
+	buf := &strings.Builder{}
+	err := response.WriteRspFile(buf, paths.Strings())
+	if err != nil {
+		// There should never be I/O errors writing to a bytes.Buffer.
+		panic(err)
 	}
-	return s.String()
+	WriteFileRule(ctx, rspFile, buf.String())
 }
diff --git a/android/rule_builder_test.go b/android/rule_builder_test.go
index 9cd60a2..d2a7d8d 100644
--- a/android/rule_builder_test.go
+++ b/android/rule_builder_test.go
@@ -376,8 +376,6 @@
 		wantDepMergerCommand := "out_local/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer " +
 			"out_local/module/DepFile out_local/module/depfile out_local/module/ImplicitDepFile out_local/module/depfile2"
 
-		wantRspFileContent := "$in"
-
 		AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
 
 		AssertDeepEquals(t, "rule.Inputs()", wantInputs, rule.Inputs())
@@ -389,8 +387,6 @@
 		AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys())
 
 		AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String())
-
-		AssertSame(t, "rule.composeRspFileContent()", wantRspFileContent, rule.composeRspFileContent(rule.RspFileInputs()))
 	})
 
 	t.Run("sbox", func(t *testing.T) {
@@ -409,8 +405,6 @@
 
 		wantDepMergerCommand := "out_local/host/" + ctx.Config().PrebuiltOS() + "/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2"
 
-		wantRspFileContent := "$in"
-
 		AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
 
 		AssertDeepEquals(t, "rule.Inputs()", wantInputs, rule.Inputs())
@@ -422,8 +416,6 @@
 		AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys())
 
 		AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String())
-
-		AssertSame(t, "rule.composeRspFileContent()", wantRspFileContent, rule.composeRspFileContent(rule.RspFileInputs()))
 	})
 
 	t.Run("sbox tools", func(t *testing.T) {
@@ -442,7 +434,34 @@
 
 		wantDepMergerCommand := "__SBOX_SANDBOX_DIR__/tools/out/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2"
 
-		wantRspFileContent := "$in"
+		AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
+
+		AssertDeepEquals(t, "rule.Inputs()", wantInputs, rule.Inputs())
+		AssertDeepEquals(t, "rule.RspfileInputs()", wantRspFileInputs, rule.RspFileInputs())
+		AssertDeepEquals(t, "rule.Outputs()", wantOutputs, rule.Outputs())
+		AssertDeepEquals(t, "rule.SymlinkOutputs()", wantSymlinkOutputs, rule.SymlinkOutputs())
+		AssertDeepEquals(t, "rule.DepFiles()", wantDepFiles, rule.DepFiles())
+		AssertDeepEquals(t, "rule.Tools()", wantTools, rule.Tools())
+		AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys())
+
+		AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String())
+	})
+
+	t.Run("sbox inputs", func(t *testing.T) {
+		rule := NewRuleBuilder(pctx, ctx).Sbox(PathForOutput(ctx, "module"),
+			PathForOutput(ctx, "sbox.textproto")).SandboxInputs()
+		addCommands(rule)
+
+		wantCommands := []string{
+			"__SBOX_SANDBOX_DIR__/out/DepFile Flag FlagWithArg=arg FlagWithDepFile=__SBOX_SANDBOX_DIR__/out/depfile " +
+				"FlagWithInput=input FlagWithOutput=__SBOX_SANDBOX_DIR__/out/output " +
+				"FlagWithRspFileInputList=__SBOX_SANDBOX_DIR__/out/rsp Input __SBOX_SANDBOX_DIR__/out/Output " +
+				"__SBOX_SANDBOX_DIR__/out/SymlinkOutput Text __SBOX_SANDBOX_DIR__/tools/src/Tool after command2 old cmd",
+			"command2 __SBOX_SANDBOX_DIR__/out/depfile2 input2 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/tools/src/tool2",
+			"command3 input3 __SBOX_SANDBOX_DIR__/out/output2 __SBOX_SANDBOX_DIR__/out/output3 input3 __SBOX_SANDBOX_DIR__/out/output2",
+		}
+
+		wantDepMergerCommand := "__SBOX_SANDBOX_DIR__/tools/out/bin/dep_fixer __SBOX_SANDBOX_DIR__/out/DepFile __SBOX_SANDBOX_DIR__/out/depfile __SBOX_SANDBOX_DIR__/out/ImplicitDepFile __SBOX_SANDBOX_DIR__/out/depfile2"
 
 		AssertDeepEquals(t, "rule.Commands()", wantCommands, rule.Commands())
 
@@ -455,8 +474,6 @@
 		AssertDeepEquals(t, "rule.OrderOnlys()", wantOrderOnlys, rule.OrderOnlys())
 
 		AssertSame(t, "rule.depFileMergerCmd()", wantDepMergerCommand, rule.depFileMergerCmd(rule.DepFiles()).String())
-
-		AssertSame(t, "rule.composeRspFileContent()", wantRspFileContent, rule.composeRspFileContent(rule.RspFileInputs()))
 	})
 }
 
@@ -472,8 +489,9 @@
 	properties struct {
 		Srcs []string
 
-		Restat bool
-		Sbox   bool
+		Restat      bool
+		Sbox        bool
+		Sbox_inputs bool
 	}
 }
 
@@ -482,9 +500,15 @@
 	out := PathForModuleOut(ctx, "gen", ctx.ModuleName())
 	outDep := PathForModuleOut(ctx, "gen", ctx.ModuleName()+".d")
 	outDir := PathForModuleOut(ctx, "gen")
+	rspFile := PathForModuleOut(ctx, "rsp")
+	rspFile2 := PathForModuleOut(ctx, "rsp2")
+	rspFileContents := PathsForSource(ctx, []string{"rsp_in"})
+	rspFileContents2 := PathsForSource(ctx, []string{"rsp_in2"})
 	manifestPath := PathForModuleOut(ctx, "sbox.textproto")
 
-	testRuleBuilder_Build(ctx, in, out, outDep, outDir, manifestPath, t.properties.Restat, t.properties.Sbox)
+	testRuleBuilder_Build(ctx, in, out, outDep, outDir, manifestPath, t.properties.Restat,
+		t.properties.Sbox, t.properties.Sbox_inputs, rspFile, rspFileContents,
+		rspFile2, rspFileContents2)
 }
 
 type testRuleBuilderSingleton struct{}
@@ -498,18 +522,35 @@
 	out := PathForOutput(ctx, "singleton/gen/baz")
 	outDep := PathForOutput(ctx, "singleton/gen/baz.d")
 	outDir := PathForOutput(ctx, "singleton/gen")
+	rspFile := PathForOutput(ctx, "singleton/rsp")
+	rspFile2 := PathForOutput(ctx, "singleton/rsp2")
+	rspFileContents := PathsForSource(ctx, []string{"rsp_in"})
+	rspFileContents2 := PathsForSource(ctx, []string{"rsp_in2"})
 	manifestPath := PathForOutput(ctx, "singleton/sbox.textproto")
-	testRuleBuilder_Build(ctx, Paths{in}, out, outDep, outDir, manifestPath, true, false)
+	testRuleBuilder_Build(ctx, Paths{in}, out, outDep, outDir, manifestPath, true, false, false,
+		rspFile, rspFileContents, rspFile2, rspFileContents2)
 }
 
-func testRuleBuilder_Build(ctx BuilderContext, in Paths, out, outDep, outDir, manifestPath WritablePath, restat, sbox bool) {
+func testRuleBuilder_Build(ctx BuilderContext, in Paths, out, outDep, outDir, manifestPath WritablePath,
+	restat, sbox, sboxInputs bool,
+	rspFile WritablePath, rspFileContents Paths, rspFile2 WritablePath, rspFileContents2 Paths) {
+
 	rule := NewRuleBuilder(pctx, ctx)
 
 	if sbox {
 		rule.Sbox(outDir, manifestPath)
+		if sboxInputs {
+			rule.SandboxInputs()
+		}
 	}
 
-	rule.Command().Tool(PathForSource(ctx, "cp")).Inputs(in).Output(out).ImplicitDepFile(outDep)
+	rule.Command().
+		Tool(PathForSource(ctx, "cp")).
+		Inputs(in).
+		Output(out).
+		ImplicitDepFile(outDep).
+		FlagWithRspFileInputList("@", rspFile, rspFileContents).
+		FlagWithRspFileInputList("@", rspFile2, rspFileContents2)
 
 	if restat {
 		rule.Restat()
@@ -540,6 +581,12 @@
 			srcs: ["bar"],
 			sbox: true,
 		}
+		rule_builder_test {
+			name: "foo_sbox_inputs",
+			srcs: ["bar"],
+			sbox: true,
+			sbox_inputs: true,
+		}
 	`
 
 	result := GroupFixturePreparers(
@@ -548,7 +595,10 @@
 		fs.AddToFixture(),
 	).RunTest(t)
 
-	check := func(t *testing.T, params TestingBuildParams, wantCommand, wantOutput, wantDepfile string, wantRestat bool, extraImplicits, extraCmdDeps []string) {
+	check := func(t *testing.T, params TestingBuildParams, rspFile2Params TestingBuildParams,
+		wantCommand, wantOutput, wantDepfile, wantRspFile, wantRspFile2 string,
+		wantRestat bool, extraImplicits, extraCmdDeps []string) {
+
 		t.Helper()
 		command := params.RuleParams.Command
 		re := regexp.MustCompile(" # hash of input list: [a-z0-9]*$")
@@ -561,9 +611,19 @@
 
 		AssertBoolEquals(t, "RuleParams.Restat", wantRestat, params.RuleParams.Restat)
 
+		wantInputs := []string{"rsp_in"}
+		AssertArrayString(t, "Inputs", wantInputs, params.Inputs.Strings())
+
 		wantImplicits := append([]string{"bar"}, extraImplicits...)
+		// The second rsp file and the files listed in it should be in implicits
+		wantImplicits = append(wantImplicits, "rsp_in2", wantRspFile2)
 		AssertPathsRelativeToTopEquals(t, "Implicits", wantImplicits, params.Implicits)
 
+		wantRspFileContent := "$in"
+		AssertStringEquals(t, "RspfileContent", wantRspFileContent, params.RuleParams.RspfileContent)
+
+		AssertStringEquals(t, "Rspfile", wantRspFile, params.RuleParams.Rspfile)
+
 		AssertPathRelativeToTopEquals(t, "Output", wantOutput, params.Output)
 
 		if len(params.ImplicitOutputs) != 0 {
@@ -575,18 +635,42 @@
 		if params.Deps != blueprint.DepsGCC {
 			t.Errorf("want Deps = %q, got %q", blueprint.DepsGCC, params.Deps)
 		}
+
+		rspFile2Content := ContentFromFileRuleForTests(t, rspFile2Params)
+		AssertStringEquals(t, "rspFile2 content", "rsp_in2\n", rspFile2Content)
 	}
 
 	t.Run("module", func(t *testing.T) {
 		outFile := "out/soong/.intermediates/foo/gen/foo"
-		check(t, result.ModuleForTests("foo", "").Rule("rule").RelativeToTop(),
-			"cp bar "+outFile,
-			outFile, outFile+".d", true, nil, nil)
+		rspFile := "out/soong/.intermediates/foo/rsp"
+		rspFile2 := "out/soong/.intermediates/foo/rsp2"
+		module := result.ModuleForTests("foo", "")
+		check(t, module.Rule("rule"), module.Output(rspFile2),
+			"cp bar "+outFile+" @"+rspFile+" @"+rspFile2,
+			outFile, outFile+".d", rspFile, rspFile2, true, nil, nil)
 	})
 	t.Run("sbox", func(t *testing.T) {
 		outDir := "out/soong/.intermediates/foo_sbox"
 		outFile := filepath.Join(outDir, "gen/foo_sbox")
 		depFile := filepath.Join(outDir, "gen/foo_sbox.d")
+		rspFile := filepath.Join(outDir, "rsp")
+		rspFile2 := filepath.Join(outDir, "rsp2")
+		manifest := filepath.Join(outDir, "sbox.textproto")
+		sbox := filepath.Join("out", "soong", "host", result.Config.PrebuiltOS(), "bin/sbox")
+		sandboxPath := shared.TempDirForOutDir("out/soong")
+
+		cmd := `rm -rf ` + outDir + `/gen && ` +
+			sbox + ` --sandbox-path ` + sandboxPath + ` --manifest ` + manifest
+		module := result.ModuleForTests("foo_sbox", "")
+		check(t, module.Output("gen/foo_sbox"), module.Output(rspFile2),
+			cmd, outFile, depFile, rspFile, rspFile2, false, []string{manifest}, []string{sbox})
+	})
+	t.Run("sbox_inputs", func(t *testing.T) {
+		outDir := "out/soong/.intermediates/foo_sbox_inputs"
+		outFile := filepath.Join(outDir, "gen/foo_sbox_inputs")
+		depFile := filepath.Join(outDir, "gen/foo_sbox_inputs.d")
+		rspFile := filepath.Join(outDir, "rsp")
+		rspFile2 := filepath.Join(outDir, "rsp2")
 		manifest := filepath.Join(outDir, "sbox.textproto")
 		sbox := filepath.Join("out", "soong", "host", result.Config.PrebuiltOS(), "bin/sbox")
 		sandboxPath := shared.TempDirForOutDir("out/soong")
@@ -594,13 +678,18 @@
 		cmd := `rm -rf ` + outDir + `/gen && ` +
 			sbox + ` --sandbox-path ` + sandboxPath + ` --manifest ` + manifest
 
-		check(t, result.ModuleForTests("foo_sbox", "").Output("gen/foo_sbox").RelativeToTop(),
-			cmd, outFile, depFile, false, []string{manifest}, []string{sbox})
+		module := result.ModuleForTests("foo_sbox_inputs", "")
+		check(t, module.Output("gen/foo_sbox_inputs"), module.Output(rspFile2),
+			cmd, outFile, depFile, rspFile, rspFile2, false, []string{manifest}, []string{sbox})
 	})
 	t.Run("singleton", func(t *testing.T) {
 		outFile := filepath.Join("out/soong/singleton/gen/baz")
-		check(t, result.SingletonForTests("rule_builder_test").Rule("rule").RelativeToTop(),
-			"cp bar "+outFile, outFile, outFile+".d", true, nil, nil)
+		rspFile := filepath.Join("out/soong/singleton/rsp")
+		rspFile2 := filepath.Join("out/soong/singleton/rsp2")
+		singleton := result.SingletonForTests("rule_builder_test")
+		check(t, singleton.Rule("rule"), singleton.Output(rspFile2),
+			"cp bar "+outFile+" @"+rspFile+" @"+rspFile2,
+			outFile, outFile+".d", rspFile, rspFile2, true, nil, nil)
 	})
 }
 
diff --git a/android/sdk_version.go b/android/sdk_version.go
new file mode 100644
index 0000000..5fdaa91
--- /dev/null
+++ b/android/sdk_version.go
@@ -0,0 +1,278 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package android
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+)
+
+type SdkContext interface {
+	// SdkVersion returns SdkSpec that corresponds to the sdk_version property of the current module
+	SdkVersion() SdkSpec
+	// SystemModules returns the system_modules property of the current module, or an empty string if it is not set.
+	SystemModules() string
+	// MinSdkVersion returns SdkSpec that corresponds to the min_sdk_version property of the current module,
+	// or from sdk_version if it is not set.
+	MinSdkVersion() SdkSpec
+	// TargetSdkVersion returns the SdkSpec that corresponds to the target_sdk_version property of the current module,
+	// or from sdk_version if it is not set.
+	TargetSdkVersion() SdkSpec
+}
+
+// SdkKind represents a particular category of an SDK spec like public, system, test, etc.
+type SdkKind int
+
+const (
+	SdkInvalid SdkKind = iota
+	SdkNone
+	SdkCore
+	SdkCorePlatform
+	SdkPublic
+	SdkSystem
+	SdkTest
+	SdkModule
+	SdkSystemServer
+	SdkPrivate
+)
+
+// String returns the string representation of this SdkKind
+func (k SdkKind) String() string {
+	switch k {
+	case SdkPrivate:
+		return "private"
+	case SdkNone:
+		return "none"
+	case SdkPublic:
+		return "public"
+	case SdkSystem:
+		return "system"
+	case SdkTest:
+		return "test"
+	case SdkCore:
+		return "core"
+	case SdkCorePlatform:
+		return "core_platform"
+	case SdkModule:
+		return "module-lib"
+	case SdkSystemServer:
+		return "system-server"
+	default:
+		return "invalid"
+	}
+}
+
+// SdkSpec represents the kind and the version of an SDK for a module to build against
+type SdkSpec struct {
+	Kind     SdkKind
+	ApiLevel ApiLevel
+	Raw      string
+}
+
+func (s SdkSpec) String() string {
+	return fmt.Sprintf("%s_%s", s.Kind, s.ApiLevel)
+}
+
+// Valid checks if this SdkSpec is well-formed. Note however that true doesn't mean that the
+// specified SDK actually exists.
+func (s SdkSpec) Valid() bool {
+	return s.Kind != SdkInvalid
+}
+
+// Specified checks if this SdkSpec is well-formed and is not "".
+func (s SdkSpec) Specified() bool {
+	return s.Valid() && s.Kind != SdkPrivate
+}
+
+// whether the API surface is managed and versioned, i.e. has .txt file that
+// get frozen on SDK freeze and changes get reviewed by API council.
+func (s SdkSpec) Stable() bool {
+	if !s.Specified() {
+		return false
+	}
+	switch s.Kind {
+	case SdkNone:
+		// there is nothing to manage and version in this case; de facto stable API.
+		return true
+	case SdkCore, SdkPublic, SdkSystem, SdkModule, SdkSystemServer:
+		return true
+	case SdkCorePlatform, SdkTest, SdkPrivate:
+		return false
+	default:
+		panic(fmt.Errorf("unknown SdkKind=%v", s.Kind))
+	}
+	return false
+}
+
+// PrebuiltSdkAvailableForUnbundledBuilt tells whether this SdkSpec can have a prebuilt SDK
+// that can be used for unbundled builds.
+func (s SdkSpec) PrebuiltSdkAvailableForUnbundledBuild() bool {
+	// "", "none", and "core_platform" are not available for unbundled build
+	// as we don't/can't have prebuilt stub for the versions
+	return s.Kind != SdkPrivate && s.Kind != SdkNone && s.Kind != SdkCorePlatform
+}
+
+func (s SdkSpec) ForVendorPartition(ctx EarlyModuleContext) SdkSpec {
+	// If BOARD_CURRENT_API_LEVEL_FOR_VENDOR_MODULES has a numeric value,
+	// use it instead of "current" for the vendor partition.
+	currentSdkVersion := ctx.DeviceConfig().CurrentApiLevelForVendorModules()
+	if currentSdkVersion == "current" {
+		return s
+	}
+
+	if s.Kind == SdkPublic || s.Kind == SdkSystem {
+		if s.ApiLevel.IsCurrent() {
+			if i, err := strconv.Atoi(currentSdkVersion); err == nil {
+				apiLevel := uncheckedFinalApiLevel(i)
+				return SdkSpec{s.Kind, apiLevel, s.Raw}
+			}
+			panic(fmt.Errorf("BOARD_CURRENT_API_LEVEL_FOR_VENDOR_MODULES must be either \"current\" or a number, but was %q", currentSdkVersion))
+		}
+	}
+	return s
+}
+
+// UsePrebuilt determines whether prebuilt SDK should be used for this SdkSpec with the given context.
+func (s SdkSpec) UsePrebuilt(ctx EarlyModuleContext) bool {
+	if s.ApiLevel.IsCurrent() {
+		// "current" can be built from source and be from prebuilt SDK
+		return ctx.Config().AlwaysUsePrebuiltSdks()
+	} else if !s.ApiLevel.IsPreview() {
+		// validation check
+		if s.Kind != SdkPublic && s.Kind != SdkSystem && s.Kind != SdkTest && s.Kind != SdkModule {
+			panic(fmt.Errorf("prebuilt SDK is not not available for SdkKind=%q", s.Kind))
+			return false
+		}
+		// numbered SDKs are always from prebuilt
+		return true
+	}
+	// "", "none", "core_platform" fall here
+	return false
+}
+
+// EffectiveVersion converts an SdkSpec into the concrete ApiLevel that the module should use. For
+// modules targeting an unreleased SDK (meaning it does not yet have a number) it returns
+// FutureApiLevel(10000).
+func (s SdkSpec) EffectiveVersion(ctx EarlyModuleContext) (ApiLevel, error) {
+	if !s.Valid() {
+		return s.ApiLevel, fmt.Errorf("invalid sdk version %q", s.Raw)
+	}
+
+	if ctx.DeviceSpecific() || ctx.SocSpecific() {
+		s = s.ForVendorPartition(ctx)
+	}
+	if !s.ApiLevel.IsPreview() {
+		return s.ApiLevel, nil
+	}
+	ret := ctx.Config().DefaultAppTargetSdk(ctx)
+	if ret.IsPreview() {
+		return FutureApiLevel, nil
+	}
+	return ret, nil
+}
+
+// EffectiveVersionString converts an SdkSpec into the concrete version string that the module
+// should use. For modules targeting an unreleased SDK (meaning it does not yet have a number)
+// it returns the codename (P, Q, R, etc.)
+func (s SdkSpec) EffectiveVersionString(ctx EarlyModuleContext) (string, error) {
+	if !s.Valid() {
+		return s.ApiLevel.String(), fmt.Errorf("invalid sdk version %q", s.Raw)
+	}
+
+	if ctx.DeviceSpecific() || ctx.SocSpecific() {
+		s = s.ForVendorPartition(ctx)
+	}
+	if !s.ApiLevel.IsPreview() {
+		return s.ApiLevel.String(), nil
+	}
+	return ctx.Config().DefaultAppTargetSdk(ctx).String(), nil
+}
+
+func SdkSpecFrom(str string) SdkSpec {
+	switch str {
+	// special cases first
+	case "":
+		return SdkSpec{SdkPrivate, NoneApiLevel, str}
+	case "none":
+		return SdkSpec{SdkNone, NoneApiLevel, str}
+	case "core_platform":
+		return SdkSpec{SdkCorePlatform, NoneApiLevel, str}
+	default:
+		// the syntax is [kind_]version
+		sep := strings.LastIndex(str, "_")
+
+		var kindString string
+		if sep == 0 {
+			return SdkSpec{SdkInvalid, NoneApiLevel, str}
+		} else if sep == -1 {
+			kindString = ""
+		} else {
+			kindString = str[0:sep]
+		}
+		versionString := str[sep+1 : len(str)]
+
+		var kind SdkKind
+		switch kindString {
+		case "":
+			kind = SdkPublic
+		case "core":
+			kind = SdkCore
+		case "system":
+			kind = SdkSystem
+		case "test":
+			kind = SdkTest
+		case "module":
+			kind = SdkModule
+		case "system_server":
+			kind = SdkSystemServer
+		default:
+			return SdkSpec{SdkInvalid, NoneApiLevel, str}
+		}
+
+		var apiLevel ApiLevel
+		if versionString == "current" {
+			apiLevel = FutureApiLevel
+		} else if i, err := strconv.Atoi(versionString); err == nil {
+			apiLevel = uncheckedFinalApiLevel(i)
+		} else {
+			return SdkSpec{SdkInvalid, apiLevel, str}
+		}
+
+		return SdkSpec{kind, apiLevel, str}
+	}
+}
+
+func (s SdkSpec) ValidateSystemSdk(ctx EarlyModuleContext) bool {
+	// Ensures that the specified system SDK version is one of BOARD_SYSTEMSDK_VERSIONS (for vendor/product Java module)
+	// Assuming that BOARD_SYSTEMSDK_VERSIONS := 28 29,
+	// sdk_version of the modules in vendor/product that use system sdk must be either system_28, system_29 or system_current
+	if s.Kind != SdkSystem || s.ApiLevel.IsPreview() {
+		return true
+	}
+	allowedVersions := ctx.DeviceConfig().PlatformSystemSdkVersions()
+	if ctx.DeviceSpecific() || ctx.SocSpecific() || (ctx.ProductSpecific() && ctx.Config().EnforceProductPartitionInterface()) {
+		systemSdkVersions := ctx.DeviceConfig().SystemSdkVersions()
+		if len(systemSdkVersions) > 0 {
+			allowedVersions = systemSdkVersions
+		}
+	}
+	if len(allowedVersions) > 0 && !InList(s.ApiLevel.String(), allowedVersions) {
+		ctx.PropertyErrorf("sdk_version", "incompatible sdk version %q. System SDK version should be one of %q",
+			s.Raw, allowedVersions)
+		return false
+	}
+	return true
+}
diff --git a/android/testing.go b/android/testing.go
index 1436f0b..ce27fca 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -539,8 +539,7 @@
 // The parts of this structure which are changed are:
 // * BuildParams
 //   * Args
-//   * Path instances are intentionally not modified, use AssertPathRelativeToTopEquals or
-//     AssertPathsRelativeToTopEquals instead which do something similar.
+//   * All Path, Paths, WritablePath and WritablePaths fields.
 //
 // * RuleParams
 //   * Command
@@ -552,6 +551,8 @@
 //   * CommandOrderOnly
 //
 // See PathRelativeToTop for more details.
+//
+// deprecated: this is no longer needed as TestingBuildParams are created in this form.
 func (p TestingBuildParams) RelativeToTop() TestingBuildParams {
 	// If this is not a valid params then just return it back. That will make it easy to use with the
 	// Maybe...() methods.
@@ -559,11 +560,25 @@
 		return p
 	}
 	if p.config.config == nil {
-		panic("cannot call RelativeToTop() on a TestingBuildParams previously returned by RelativeToTop()")
+		return p
 	}
 	// Take a copy of the build params and replace any args that contains test specific temporary
 	// paths with paths relative to the top.
 	bparams := p.BuildParams
+	bparams.Depfile = normalizeWritablePathRelativeToTop(bparams.Depfile)
+	bparams.Output = normalizeWritablePathRelativeToTop(bparams.Output)
+	bparams.Outputs = bparams.Outputs.RelativeToTop()
+	bparams.SymlinkOutput = normalizeWritablePathRelativeToTop(bparams.SymlinkOutput)
+	bparams.SymlinkOutputs = bparams.SymlinkOutputs.RelativeToTop()
+	bparams.ImplicitOutput = normalizeWritablePathRelativeToTop(bparams.ImplicitOutput)
+	bparams.ImplicitOutputs = bparams.ImplicitOutputs.RelativeToTop()
+	bparams.Input = normalizePathRelativeToTop(bparams.Input)
+	bparams.Inputs = bparams.Inputs.RelativeToTop()
+	bparams.Implicit = normalizePathRelativeToTop(bparams.Implicit)
+	bparams.Implicits = bparams.Implicits.RelativeToTop()
+	bparams.OrderOnly = bparams.OrderOnly.RelativeToTop()
+	bparams.Validation = normalizePathRelativeToTop(bparams.Validation)
+	bparams.Validations = bparams.Validations.RelativeToTop()
 	bparams.Args = normalizeStringMapRelativeToTop(p.config, bparams.Args)
 
 	// Ditto for any fields in the RuleParams.
@@ -582,6 +597,20 @@
 	}
 }
 
+func normalizeWritablePathRelativeToTop(path WritablePath) WritablePath {
+	if path == nil {
+		return nil
+	}
+	return path.RelativeToTop().(WritablePath)
+}
+
+func normalizePathRelativeToTop(path Path) Path {
+	if path == nil {
+		return nil
+	}
+	return path.RelativeToTop()
+}
+
 // baseTestingComponent provides functionality common to both TestingModule and TestingSingleton.
 type baseTestingComponent struct {
 	config   Config
@@ -643,7 +672,7 @@
 		config:      b.config,
 		BuildParams: bparams,
 		RuleParams:  b.provider.RuleParamsForTests()[bparams.Rule],
-	}
+	}.RelativeToTop()
 }
 
 func (b baseTestingComponent) maybeBuildParamsFromRule(rule string) (TestingBuildParams, []string) {
@@ -789,6 +818,22 @@
 	return normalizeStringMapRelativeToTop(m.config, m.module.VariablesForTests())
 }
 
+// OutputFiles calls OutputFileProducer.OutputFiles on the encapsulated module, exits the test
+// immediately if there is an error and otherwise returns the result of calling Paths.RelativeToTop
+// on the returned Paths.
+func (m TestingModule) OutputFiles(t *testing.T, tag string) Paths {
+	producer, ok := m.module.(OutputFileProducer)
+	if !ok {
+		t.Fatalf("%q must implement OutputFileProducer\n", m.module.Name())
+	}
+	paths, err := producer.OutputFiles(tag)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	return paths.RelativeToTop()
+}
+
 // TestingSingleton is wrapper around an android.Singleton that provides methods to find information about individual
 // ctx.Build parameters for verification in tests.
 type TestingSingleton struct {
@@ -909,7 +954,7 @@
 	}
 	p := path.String()
 	if w, ok := path.(WritablePath); ok {
-		rel, err := filepath.Rel(w.buildDir(), p)
+		rel, err := filepath.Rel(w.getBuildDir(), p)
 		if err != nil {
 			panic(err)
 		}
@@ -935,19 +980,14 @@
 // PathRelativeToTop returns a string representation of the path relative to a notional top
 // directory.
 //
-// For a WritablePath it applies StringPathRelativeToTop to it, using the buildDir returned from the
-// WritablePath's buildDir() method. For all other paths, i.e. source paths, that are already
-// relative to the top it just returns their string representation.
+// It return "<nil path>" if the supplied path is nil, otherwise it returns the result of calling
+// Path.RelativeToTop to obtain a relative Path and then calling Path.String on that to get the
+// string representation.
 func PathRelativeToTop(path Path) string {
 	if path == nil {
 		return "<nil path>"
 	}
-	p := path.String()
-	if w, ok := path.(WritablePath); ok {
-		buildDir := w.buildDir()
-		return StringPathRelativeToTop(buildDir, p)
-	}
-	return p
+	return path.RelativeToTop().String()
 }
 
 // PathsRelativeToTop creates a slice of strings where each string is the result of applying
@@ -964,23 +1004,13 @@
 // StringPathRelativeToTop returns a string representation of the path relative to a notional top
 // directory.
 //
-// A standard build has the following structure:
-//   ../top/
-//          out/ - make install files go here.
-//          out/soong - this is the buildDir passed to NewTestConfig()
-//          ... - the source files
-//
-// This function converts a path so that it appears relative to the ../top/ directory, i.e.
-// * Make install paths, which have the pattern "buildDir/../<path>" are converted into the top
-//   relative path "out/<path>"
-// * Soong install paths and other writable paths, which have the pattern "buildDir/<path>" are
-//   converted into the top relative path "out/soong/<path>".
-// * Source paths are already relative to the top.
+// See Path.RelativeToTop for more details as to what `relative to top` means.
 //
 // This is provided for processing paths that have already been converted into a string, e.g. paths
 // in AndroidMkEntries structures. As a result it needs to be supplied the soong output dir against
 // which it can try and relativize paths. PathRelativeToTop must be used for process Path objects.
 func StringPathRelativeToTop(soongOutDir string, path string) string {
+	ensureTestOnly()
 
 	// A relative path must be a source path so leave it as it is.
 	if !filepath.IsAbs(path) {
@@ -1028,3 +1058,20 @@
 	}
 	return result
 }
+
+// StringRelativeToTop will normalize a string containing paths, e.g. ninja command, by replacing
+// any references to the test specific temporary build directory that changes with each run to a
+// fixed path relative to a notional top directory.
+//
+// This is similar to StringPathRelativeToTop except that assumes the string is a single path
+// containing at most one instance of the temporary build directory at the start of the path while
+// this assumes that there can be any number at any position.
+func StringRelativeToTop(config Config, command string) string {
+	return normalizeStringRelativeToTop(config, command)
+}
+
+// StringsRelativeToTop will return a new slice such that each item in the new slice is the result
+// of calling StringRelativeToTop on the corresponding item in the input slice.
+func StringsRelativeToTop(config Config, command []string) []string {
+	return normalizeStringArrayRelativeToTop(config, command)
+}
diff --git a/android/util.go b/android/util.go
index 506f8f7..a0394f6 100644
--- a/android/util.go
+++ b/android/util.go
@@ -193,6 +193,17 @@
 	return
 }
 
+// FilterListPred returns the elements of the given list for which the predicate
+// returns true. Order is kept.
+func FilterListPred(list []string, pred func(s string) bool) (filtered []string) {
+	for _, l := range list {
+		if pred(l) {
+			filtered = append(filtered, l)
+		}
+	}
+	return
+}
+
 // RemoveListFromList removes the strings belonging to the filter list from the
 // given list and returns the result
 func RemoveListFromList(list []string, filter_out []string) (result []string) {
diff --git a/android/util_test.go b/android/util_test.go
index fa26c77..09bec01 100644
--- a/android/util_test.go
+++ b/android/util_test.go
@@ -18,6 +18,7 @@
 	"fmt"
 	"reflect"
 	"strconv"
+	"strings"
 	"testing"
 )
 
@@ -299,6 +300,14 @@
 	}
 }
 
+func TestFilterListPred(t *testing.T) {
+	pred := func(s string) bool { return strings.HasPrefix(s, "a/") }
+	AssertArrayString(t, "filter", FilterListPred([]string{"a/c", "b/a", "a/b"}, pred), []string{"a/c", "a/b"})
+	AssertArrayString(t, "filter", FilterListPred([]string{"b/c", "a/a", "b/b"}, pred), []string{"a/a"})
+	AssertArrayString(t, "filter", FilterListPred([]string{"c/c", "b/a", "c/b"}, pred), []string{})
+	AssertArrayString(t, "filter", FilterListPred([]string{"a/c", "a/a", "a/b"}, pred), []string{"a/c", "a/a", "a/b"})
+}
+
 func TestRemoveListFromList(t *testing.T) {
 	input := []string{"a", "b", "c", "d", "a", "c", "d"}
 	filter := []string{"a", "c"}
diff --git a/android/variable.go b/android/variable.go
index 2ab51c7..dff48c2 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -448,6 +448,63 @@
 	}
 }
 
+// ProductConfigContext requires the access to the Module to get product config properties.
+type ProductConfigContext interface {
+	Module() Module
+}
+
+// ProductConfigProperty contains the information for a single property (may be a struct) paired
+// with the appropriate ProductConfigVariable.
+type ProductConfigProperty struct {
+	ProductConfigVariable string
+	Property              interface{}
+}
+
+// ProductConfigProperties is a map of property name to a slice of ProductConfigProperty such that
+// all it all product variable-specific versions of a property are easily accessed together
+type ProductConfigProperties map[string][]ProductConfigProperty
+
+// ProductVariableProperties returns a ProductConfigProperties containing only the properties which
+// have been set for the module in the given context.
+func ProductVariableProperties(ctx ProductConfigContext) ProductConfigProperties {
+	module := ctx.Module()
+	moduleBase := module.base()
+
+	productConfigProperties := ProductConfigProperties{}
+
+	if moduleBase.variableProperties == nil {
+		return productConfigProperties
+	}
+
+	variableValues := reflect.ValueOf(moduleBase.variableProperties).Elem().FieldByName("Product_variables")
+	for i := 0; i < variableValues.NumField(); i++ {
+		variableValue := variableValues.Field(i)
+		// Check if any properties were set for the module
+		if variableValue.IsZero() {
+			continue
+		}
+		// e.g. Platform_sdk_version, Unbundled_build, Malloc_not_svelte, etc.
+		productVariableName := variableValues.Type().Field(i).Name
+		for j := 0; j < variableValue.NumField(); j++ {
+			property := variableValue.Field(j)
+			// If the property wasn't set, no need to pass it along
+			if property.IsZero() {
+				continue
+			}
+
+			// e.g. Asflags, Cflags, Enabled, etc.
+			propertyName := variableValue.Type().Field(j).Name
+			productConfigProperties[propertyName] = append(productConfigProperties[propertyName],
+				ProductConfigProperty{
+					ProductConfigVariable: productVariableName,
+					Property:              property.Interface(),
+				})
+		}
+	}
+
+	return productConfigProperties
+}
+
 func VariableMutator(mctx BottomUpMutatorContext) {
 	var module Module
 	var ok bool
diff --git a/apex/apex.go b/apex/apex.go
index dca5595..bad382a 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -93,8 +93,14 @@
 	Multilib apexMultilibProperties
 
 	// List of boot images that are embedded inside this APEX bundle.
+	//
+	// deprecated: Use Bootclasspath_fragments
+	// TODO(b/177892522): Remove after has been replaced by Bootclasspath_fragments
 	Boot_images []string
 
+	// List of bootclasspath fragments that are embedded inside this APEX bundle.
+	Bootclasspath_fragments []string
+
 	// List of java libraries that are embedded inside this APEX bundle.
 	Java_libs []string
 
@@ -567,7 +573,7 @@
 	certificateTag  = dependencyTag{name: "certificate"}
 	executableTag   = dependencyTag{name: "executable", payload: true}
 	fsTag           = dependencyTag{name: "filesystem", payload: true}
-	bootImageTag    = dependencyTag{name: "bootImage", payload: true}
+	bootImageTag    = dependencyTag{name: "bootImage", payload: true, sourceOnly: true}
 	compatConfigTag = dependencyTag{name: "compatConfig", payload: true, sourceOnly: true}
 	javaLibTag      = dependencyTag{name: "javaLib", payload: true}
 	jniLibTag       = dependencyTag{name: "jniLib", payload: true}
@@ -748,6 +754,7 @@
 	// Common-arch dependencies come next
 	commonVariation := ctx.Config().AndroidCommonTarget.Variations()
 	ctx.AddFarVariationDependencies(commonVariation, bootImageTag, a.properties.Boot_images...)
+	ctx.AddFarVariationDependencies(commonVariation, bootImageTag, a.properties.Bootclasspath_fragments...)
 	ctx.AddFarVariationDependencies(commonVariation, javaLibTag, a.properties.Java_libs...)
 	ctx.AddFarVariationDependencies(commonVariation, bpfTag, a.properties.Bpfs...)
 	ctx.AddFarVariationDependencies(commonVariation, fsTag, a.properties.Filesystems...)
@@ -1042,6 +1049,16 @@
 	if a, ok := mctx.Module().(*apexBundle); ok && !a.vndkApex {
 		apexBundleName := mctx.ModuleName()
 		mctx.CreateVariations(apexBundleName)
+		if strings.HasPrefix(apexBundleName, "com.android.art") {
+			// Create an alias from the platform variant. This is done to make
+			// test_for dependencies work for modules that are split by the APEX
+			// mutator, since test_for dependencies always go to the platform variant.
+			// This doesn't happen for normal APEXes that are disjunct, so only do
+			// this for the overlapping ART APEXes.
+			// TODO(b/183882457): Remove this if the test_for functionality is
+			// refactored to depend on the proper APEX variants instead of platform.
+			mctx.CreateAliasVariation("", apexBundleName)
+		}
 	} else if o, ok := mctx.Module().(*OverrideApex); ok {
 		apexBundleName := o.GetOverriddenModuleName()
 		if apexBundleName == "" {
@@ -1049,6 +1066,10 @@
 			return
 		}
 		mctx.CreateVariations(apexBundleName)
+		if strings.HasPrefix(apexBundleName, "com.android.art") {
+			// TODO(b/183882457): See note for CreateAliasVariation above.
+			mctx.CreateAliasVariation("", apexBundleName)
+		}
 	}
 }
 
@@ -1695,6 +1716,9 @@
 							filesInfo = append(filesInfo, af)
 						}
 					}
+
+					// Track transitive dependencies.
+					return true
 				}
 			case javaLibTag:
 				switch child.(type) {
@@ -1903,6 +1927,21 @@
 						filesInfo = append(filesInfo, af)
 						return true // track transitive dependencies
 					}
+				} else if java.IsbootImageContentDepTag(depTag) {
+					// Add the contents of the boot image to the apex.
+					switch child.(type) {
+					case *java.Library, *java.SdkLibrary:
+						af := apexFileForJavaModule(ctx, child.(javaModule))
+						if !af.ok() {
+							ctx.PropertyErrorf("boot_images", "boot image content %q is not configured to be compiled into dex", depName)
+							return false
+						}
+						filesInfo = append(filesInfo, af)
+						return true // track transitive dependencies
+					default:
+						ctx.PropertyErrorf("boot_images", "boot image content %q of type %q is not supported", depName, ctx.OtherModuleType(child))
+					}
+
 				} else if _, ok := depTag.(android.CopyDirectlyInAnyApexTag); ok {
 					// nothing
 				} else if am.CanHaveApexVariants() && am.IsInstallableToApex() {
@@ -2900,9 +2939,7 @@
 		"com.google.android.material_material",
 		"com.google.android.material_material-nodeps",
 
-		"libatomic",
 		"libclang_rt",
-		"libgcc_stripped",
 		"libprofile-clang-extras",
 		"libprofile-clang-extras_ndk",
 		"libprofile-extras",
diff --git a/apex/apex_test.go b/apex/apex_test.go
index e0cefa1..bdff41e 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -16,13 +16,13 @@
 
 import (
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path"
 	"path/filepath"
 	"reflect"
 	"regexp"
 	"sort"
+	"strconv"
 	"strings"
 	"testing"
 
@@ -38,8 +38,6 @@
 	"android/soong/sh"
 )
 
-var buildDir string
-
 // names returns name list from white space separated string
 func names(s string) (ns []string) {
 	for _, n := range strings.Split(s, " ") {
@@ -52,18 +50,28 @@
 
 func testApexError(t *testing.T, pattern, bp string, preparers ...android.FixturePreparer) {
 	t.Helper()
-	apexFixtureFactory.Extend(preparers...).
+	android.GroupFixturePreparers(
+		prepareForApexTest,
+		android.GroupFixturePreparers(preparers...),
+	).
 		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(pattern)).
 		RunTestWithBp(t, bp)
 }
 
 func testApex(t *testing.T, bp string, preparers ...android.FixturePreparer) *android.TestContext {
 	t.Helper()
-	factory := apexFixtureFactory.Extend(preparers...)
+
+	optionalBpPreparer := android.NullFixturePreparer
 	if bp != "" {
-		factory = factory.Extend(android.FixtureWithRootAndroidBp(bp))
+		optionalBpPreparer = android.FixtureWithRootAndroidBp(bp)
 	}
-	result := factory.RunTest(t)
+
+	result := android.GroupFixturePreparers(
+		prepareForApexTest,
+		android.GroupFixturePreparers(preparers...),
+		optionalBpPreparer,
+	).RunTest(t)
+
 	return result.TestContext
 }
 
@@ -117,8 +125,16 @@
 	},
 )
 
-var apexFixtureFactory = android.NewFixtureFactory(
-	&buildDir,
+// Legacy preparer used for running tests within the apex package.
+//
+// This includes everything that was needed to run any test in the apex package prior to the
+// introduction of the test fixtures. Tests that are being converted to use fixtures directly
+// rather than through the testApex...() methods should avoid using this and instead use the
+// various preparers directly, using android.GroupFixturePreparers(...) to group them when
+// necessary.
+//
+// deprecated
+var prepareForApexTest = android.GroupFixturePreparers(
 	// General preparers in alphabetical order as test infrastructure will enforce correct
 	// registration order.
 	android.PrepareForTestWithAndroidBuildComponents,
@@ -140,13 +156,13 @@
 			],
 		}
 	`),
+	prepareForTestWithMyapex,
 	android.FixtureMergeMockFs(android.MockFS{
-		"a.java":                                    nil,
-		"PrebuiltAppFoo.apk":                        nil,
-		"PrebuiltAppFooPriv.apk":                    nil,
-		"apex_manifest.json":                        nil,
-		"AndroidManifest.xml":                       nil,
-		"system/sepolicy/apex/myapex-file_contexts": nil,
+		"a.java":                 nil,
+		"PrebuiltAppFoo.apk":     nil,
+		"PrebuiltAppFooPriv.apk": nil,
+		"apex_manifest.json":     nil,
+		"AndroidManifest.xml":    nil,
 		"system/sepolicy/apex/myapex.updatable-file_contexts":         nil,
 		"system/sepolicy/apex/myapex2-file_contexts":                  nil,
 		"system/sepolicy/apex/otherapex-file_contexts":                nil,
@@ -200,21 +216,13 @@
 		variables.Platform_sdk_codename = proptools.StringPtr("Q")
 		variables.Platform_sdk_final = proptools.BoolPtr(false)
 		variables.Platform_version_active_codenames = []string{"Q"}
-		variables.Platform_vndk_version = proptools.StringPtr("VER")
+		variables.Platform_vndk_version = proptools.StringPtr("29")
 	}),
 )
 
-func setUp() {
-	var err error
-	buildDir, err = ioutil.TempDir("", "soong_apex_test")
-	if err != nil {
-		panic(err)
-	}
-}
-
-func tearDown() {
-	_ = os.RemoveAll(buildDir)
-}
+var prepareForTestWithMyapex = android.FixtureMergeMockFs(android.MockFS{
+	"system/sepolicy/apex/myapex-file_contexts": nil,
+})
 
 // ensure that 'result' equals 'expected'
 func ensureEquals(t *testing.T, result string, expected string) {
@@ -511,7 +519,7 @@
 	optFlags := apexRule.Args["opt_flags"]
 	ensureContains(t, optFlags, "--pubkey vendor/foo/devkeys/testkey.avbpubkey")
 	// Ensure that the NOTICE output is being packaged as an asset.
-	ensureContains(t, optFlags, "--assets_dir "+buildDir+"/.intermediates/myapex/android_common_myapex_image/NOTICE")
+	ensureContains(t, optFlags, "--assets_dir out/soong/.intermediates/myapex/android_common_myapex_image/NOTICE")
 
 	copyCmds := apexRule.Args["copy_commands"]
 
@@ -812,7 +820,7 @@
 	mylibLdFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_shared_apex10000").Rule("ld").Args["libFlags"]
 
 	// Ensure that mylib is linking with the latest version of stubs for mylib2
-	ensureContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared_3/mylib2.so")
+	ensureContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared_current/mylib2.so")
 	// ... and not linking to the non-stub (impl) variant of mylib2
 	ensureNotContains(t, mylibLdFlags, "mylib2/android_arm64_armv8-a_shared/mylib2.so")
 
@@ -1290,15 +1298,15 @@
 			name:          "unspecified version links to the latest",
 			minSdkVersion: "",
 			apexVariant:   "apex10000",
-			shouldLink:    "30",
-			shouldNotLink: []string{"29"},
+			shouldLink:    "current",
+			shouldNotLink: []string{"29", "30"},
 		},
 		{
 			name:          "always use the latest",
 			minSdkVersion: "min_sdk_version: \"29\",",
 			apexVariant:   "apex29",
-			shouldLink:    "30",
-			shouldNotLink: []string{"29"},
+			shouldLink:    "current",
+			shouldNotLink: []string{"29", "30"},
 		},
 	}
 	for _, tc := range testcases {
@@ -1358,14 +1366,18 @@
 			ensureListEmpty(t, names(apexManifestRule.Args["provideNativeLibs"]))
 			ensureListContains(t, names(apexManifestRule.Args["requireNativeLibs"]), "libbar.so")
 
-			mylibLdFlags := ctx.ModuleForTests("mylib", "android_vendor.VER_arm64_armv8-a_shared_"+tc.apexVariant).Rule("ld").Args["libFlags"]
-			ensureContains(t, mylibLdFlags, "libbar/android_vendor.VER_arm64_armv8-a_shared_"+tc.shouldLink+"/libbar.so")
+			mylibLdFlags := ctx.ModuleForTests("mylib", "android_vendor.29_arm64_armv8-a_shared_"+tc.apexVariant).Rule("ld").Args["libFlags"]
+			ensureContains(t, mylibLdFlags, "libbar/android_vendor.29_arm64_armv8-a_shared_"+tc.shouldLink+"/libbar.so")
 			for _, ver := range tc.shouldNotLink {
-				ensureNotContains(t, mylibLdFlags, "libbar/android_vendor.VER_arm64_armv8-a_shared_"+ver+"/libbar.so")
+				ensureNotContains(t, mylibLdFlags, "libbar/android_vendor.29_arm64_armv8-a_shared_"+ver+"/libbar.so")
 			}
 
-			mylibCFlags := ctx.ModuleForTests("mylib", "android_vendor.VER_arm64_armv8-a_static_"+tc.apexVariant).Rule("cc").Args["cFlags"]
-			ensureContains(t, mylibCFlags, "__LIBBAR_API__="+tc.shouldLink)
+			mylibCFlags := ctx.ModuleForTests("mylib", "android_vendor.29_arm64_armv8-a_static_"+tc.apexVariant).Rule("cc").Args["cFlags"]
+			ver := tc.shouldLink
+			if tc.shouldLink == "current" {
+				ver = strconv.Itoa(android.FutureApiLevelInt)
+			}
+			ensureContains(t, mylibCFlags, "__LIBBAR_API__="+ver)
 		})
 	}
 }
@@ -1427,12 +1439,12 @@
 
 	// For dependency to libc
 	// Ensure that mylib is linking with the latest version of stubs
-	ensureContains(t, mylibLdFlags, "libc/android_arm64_armv8-a_shared_29/libc.so")
+	ensureContains(t, mylibLdFlags, "libc/android_arm64_armv8-a_shared_current/libc.so")
 	// ... and not linking to the non-stub (impl) variant
 	ensureNotContains(t, mylibLdFlags, "libc/android_arm64_armv8-a_shared/libc.so")
 	// ... Cflags from stub is correctly exported to mylib
-	ensureContains(t, mylibCFlags, "__LIBC_API__=29")
-	ensureContains(t, mylibSharedCFlags, "__LIBC_API__=29")
+	ensureContains(t, mylibCFlags, "__LIBC_API__=10000")
+	ensureContains(t, mylibSharedCFlags, "__LIBC_API__=10000")
 
 	// For dependency to libm
 	// Ensure that mylib is linking with the non-stub (impl) variant
@@ -1538,12 +1550,14 @@
 	}
 	// platform liba is linked to non-stub version
 	expectLink("liba", "shared", "libz", "shared")
-	// liba in myapex is linked to #30
-	expectLink("liba", "shared_apex29", "libz", "shared_30")
+	// liba in myapex is linked to current
+	expectLink("liba", "shared_apex29", "libz", "shared_current")
+	expectNoLink("liba", "shared_apex29", "libz", "shared_30")
 	expectNoLink("liba", "shared_apex29", "libz", "shared_28")
 	expectNoLink("liba", "shared_apex29", "libz", "shared")
-	// liba in otherapex is linked to #30
-	expectLink("liba", "shared_apex30", "libz", "shared_30")
+	// liba in otherapex is linked to current
+	expectLink("liba", "shared_apex30", "libz", "shared_current")
+	expectNoLink("liba", "shared_apex30", "libz", "shared_30")
 	expectNoLink("liba", "shared_apex30", "libz", "shared_28")
 	expectNoLink("liba", "shared_apex30", "libz", "shared")
 }
@@ -1594,7 +1608,8 @@
 		ldArgs := ctx.ModuleForTests(from, "android_arm64_armv8-a_"+from_variant).Rule("ld").Args["libFlags"]
 		ensureNotContains(t, ldArgs, "android_arm64_armv8-a_"+to_variant+"/"+to+".so")
 	}
-	expectLink("libx", "shared_apex10000", "libz", "shared_R")
+	expectLink("libx", "shared_apex10000", "libz", "shared_current")
+	expectNoLink("libx", "shared_apex10000", "libz", "shared_R")
 	expectNoLink("libx", "shared_apex10000", "libz", "shared_29")
 	expectNoLink("libx", "shared_apex10000", "libz", "shared")
 }
@@ -1640,8 +1655,9 @@
 		ldArgs := ctx.ModuleForTests(from, "android_arm64_armv8-a_"+from_variant).Rule("ld").Args["libFlags"]
 		ensureNotContains(t, ldArgs, "android_arm64_armv8-a_"+to_variant+"/"+to+".so")
 	}
-	expectLink("libx", "shared_apex10000", "libz", "shared_2")
+	expectLink("libx", "shared_apex10000", "libz", "shared_current")
 	expectNoLink("libx", "shared_apex10000", "libz", "shared_1")
+	expectNoLink("libx", "shared_apex10000", "libz", "shared_2")
 	expectNoLink("libx", "shared_apex10000", "libz", "shared")
 }
 
@@ -1688,7 +1704,8 @@
 		ldArgs := ctx.ModuleForTests(from, "android_arm64_armv8-a_"+from_variant).Rule("ld").Args["libFlags"]
 		ensureNotContains(t, ldArgs, "android_arm64_armv8-a_"+to_variant+"/"+to+".so")
 	}
-	expectLink("libz", "shared", "libx", "shared_2")
+	expectLink("libz", "shared", "libx", "shared_current")
+	expectNoLink("libz", "shared", "libx", "shared_2")
 	expectNoLink("libz", "shared", "libz", "shared_1")
 	expectNoLink("libz", "shared", "libz", "shared")
 }
@@ -1735,7 +1752,7 @@
 		libFlags := ld.Args["libFlags"]
 		ensureContains(t, libFlags, "android_arm64_armv8-a_"+to_variant+"/"+to+".so")
 	}
-	expectLink("libx", "shared_hwasan_apex29", "libbar", "shared_30")
+	expectLink("libx", "shared_hwasan_apex29", "libbar", "shared_current")
 }
 
 func TestQTargetApexUsesStaticUnwinder(t *testing.T) {
@@ -1819,6 +1836,30 @@
 			min_sdk_version: "30",
 		}
 	`)
+
+	testApexError(t, `module "libfoo".*: should support min_sdk_version\(29\)`, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			java_libs: ["libfoo"],
+			min_sdk_version: "29",
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_import {
+			name: "libfoo",
+			jars: ["libfoo.jar"],
+			apex_available: [
+				"myapex",
+			],
+			min_sdk_version: "30",
+		}
+	`)
 }
 
 func TestApexMinSdkVersion_Okay(t *testing.T) {
@@ -1856,7 +1897,10 @@
 			name: "libbar",
 			sdk_version: "current",
 			srcs: ["a.java"],
-			static_libs: ["libbar_dep"],
+			static_libs: [
+				"libbar_dep",
+				"libbar_import_dep",
+			],
 			apex_available: ["myapex"],
 			min_sdk_version: "29",
 		}
@@ -1868,6 +1912,13 @@
 			apex_available: ["myapex"],
 			min_sdk_version: "29",
 		}
+
+		java_import {
+			name: "libbar_import_dep",
+			jars: ["libbar.jar"],
+			apex_available: ["myapex"],
+			min_sdk_version: "29",
+		}
 	`)
 }
 
@@ -2083,7 +2134,7 @@
 			private_key: "testkey.pem",
 		}
 
-		// mylib in myapex will link to mylib2#30
+		// mylib in myapex will link to mylib2#current
 		// mylib in otherapex will link to mylib2(non-stub) in otherapex as well
 		cc_library {
 			name: "mylib",
@@ -2117,7 +2168,7 @@
 		libFlags := ld.Args["libFlags"]
 		ensureContains(t, libFlags, "android_arm64_armv8-a_"+to_variant+"/"+to+".so")
 	}
-	expectLink("mylib", "shared_apex29", "mylib2", "shared_30")
+	expectLink("mylib", "shared_apex29", "mylib2", "shared_current")
 	expectLink("mylib", "shared_apex30", "mylib2", "shared_apex30")
 }
 
@@ -2185,10 +2236,10 @@
 		}
 	`, withSAsActiveCodeNames)
 
-	// ensure libfoo is linked with "S" version of libbar stub
+	// ensure libfoo is linked with current version of libbar stub
 	libfoo := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared_apex10000")
 	libFlags := libfoo.Rule("ld").Args["libFlags"]
-	ensureContains(t, libFlags, "android_arm64_armv8-a_shared_T/libbar.so")
+	ensureContains(t, libFlags, "android_arm64_armv8-a_shared_current/libbar.so")
 }
 
 func TestFilesInSubDir(t *testing.T) {
@@ -2360,8 +2411,8 @@
 	inputsString := strings.Join(inputsList, " ")
 
 	// ensure that the apex includes vendor variants of the direct and indirect deps
-	ensureContains(t, inputsString, "android_vendor.VER_arm64_armv8-a_shared_apex10000/mylib.so")
-	ensureContains(t, inputsString, "android_vendor.VER_arm64_armv8-a_shared_apex10000/mylib2.so")
+	ensureContains(t, inputsString, "android_vendor.29_arm64_armv8-a_shared_apex10000/mylib.so")
+	ensureContains(t, inputsString, "android_vendor.29_arm64_armv8-a_shared_apex10000/mylib2.so")
 
 	// ensure that the apex does not include core variants
 	ensureNotContains(t, inputsString, "android_arm64_armv8-a_shared_apex10000/mylib.so")
@@ -2464,8 +2515,8 @@
 	prefix := "TARGET_"
 	var builder strings.Builder
 	data.Custom(&builder, name, prefix, "", data)
-	androidMk := builder.String()
-	installPath := path.Join(buildDir, "../target/product/test_device/vendor/apex")
+	androidMk := android.StringRelativeToTop(ctx.Config(), builder.String())
+	installPath := "out/target/product/test_device/vendor/apex"
 	ensureContains(t, androidMk, "LOCAL_MODULE_PATH := "+installPath)
 
 	apexManifestRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Rule("apexManifestRule")
@@ -2507,15 +2558,15 @@
 		}
 	`)
 
-	vendorVariant := "android_vendor.VER_arm64_armv8-a"
+	vendorVariant := "android_vendor.29_arm64_armv8-a"
 
 	ldRule := ctx.ModuleForTests("mybin", vendorVariant+"_apex10000").Rule("ld")
 	libs := names(ldRule.Args["libFlags"])
 	// VNDK libs(libvndk/libc++) as they are
-	ensureListContains(t, libs, buildDir+"/.intermediates/libvndk/"+vendorVariant+"_shared/libvndk.so")
-	ensureListContains(t, libs, buildDir+"/.intermediates/"+cc.DefaultCcCommonTestModulesDir+"libc++/"+vendorVariant+"_shared/libc++.so")
+	ensureListContains(t, libs, "out/soong/.intermediates/libvndk/"+vendorVariant+"_shared/libvndk.so")
+	ensureListContains(t, libs, "out/soong/.intermediates/"+cc.DefaultCcCommonTestModulesDir+"libc++/"+vendorVariant+"_shared/libc++.so")
 	// non-stable Vendor libs as APEX variants
-	ensureListContains(t, libs, buildDir+"/.intermediates/libvendor/"+vendorVariant+"_shared_apex10000/libvendor.so")
+	ensureListContains(t, libs, "out/soong/.intermediates/libvendor/"+vendorVariant+"_shared_apex10000/libvendor.so")
 
 	// VNDK libs are not included when use_vndk_as_stable: true
 	ensureExactContents(t, ctx, "myapex", "android_common_myapex_image", []string{
@@ -2556,7 +2607,7 @@
 	)
 
 	cflags := strings.Fields(
-		ctx.ModuleForTests("foo", "android_product.VER_arm64_armv8-a_apex10000").Rule("cc").Args["cFlags"])
+		ctx.ModuleForTests("foo", "android_product.29_arm64_armv8-a_apex10000").Rule("cc").Args["cFlags"])
 	ensureListContains(t, cflags, "-D__ANDROID_VNDK__")
 	ensureListContains(t, cflags, "-D__ANDROID_APEX__")
 	ensureListContains(t, cflags, "-D__ANDROID_PRODUCT__")
@@ -3254,11 +3305,11 @@
 		"lib64/libvndk.so",
 		"lib64/libvndksp.so",
 		"lib64/libc++.so",
-		"etc/llndk.libraries.VER.txt",
-		"etc/vndkcore.libraries.VER.txt",
-		"etc/vndksp.libraries.VER.txt",
-		"etc/vndkprivate.libraries.VER.txt",
-		"etc/vndkproduct.libraries.VER.txt",
+		"etc/llndk.libraries.29.txt",
+		"etc/vndkcore.libraries.29.txt",
+		"etc/vndksp.libraries.29.txt",
+		"etc/vndkprivate.libraries.29.txt",
+		"etc/vndkproduct.libraries.29.txt",
 	})
 }
 
@@ -3444,7 +3495,7 @@
 		}
 	}
 
-	assertApexName("com.android.vndk.vVER", "com.android.vndk.current")
+	assertApexName("com.android.vndk.v29", "com.android.vndk.current")
 	assertApexName("com.android.vndk.v28", "com.android.vndk.v28")
 }
 
@@ -4088,15 +4139,15 @@
 			`)
 
 			apex := ctx.ModuleForTests("myapex", "android_common_myapex_image").Module().(*apexBundle)
-			expected := buildDir + "/target/product/test_device/" + tc.parition + "/apex"
-			actual := apex.installDir.String()
+			expected := "out/soong/target/product/test_device/" + tc.parition + "/apex"
+			actual := apex.installDir.RelativeToTop().String()
 			if actual != expected {
 				t.Errorf("wrong install path. expected %q. actual %q", expected, actual)
 			}
 
 			flattened := ctx.ModuleForTests("myapex", "android_common_myapex_flattened").Module().(*apexBundle)
-			expected = buildDir + "/target/product/test_device/" + tc.flattenedPartition + "/apex"
-			actual = flattened.installDir.String()
+			expected = "out/soong/target/product/test_device/" + tc.flattenedPartition + "/apex"
+			actual = flattened.installDir.RelativeToTop().String()
 			if actual != expected {
 				t.Errorf("wrong install path. expected %q. actual %q", expected, actual)
 			}
@@ -4265,6 +4316,14 @@
 	}
 }
 
+func TestPrebuiltMissingSrc(t *testing.T) {
+	testApexError(t, `module "myapex" variant "android_common".*: prebuilt_apex does not support "arm64_armv8-a"`, `
+		prebuilt_apex {
+			name: "myapex",
+		}
+	`)
+}
+
 func TestPrebuiltFilenameOverride(t *testing.T) {
 	ctx := testApex(t, `
 		prebuilt_apex {
@@ -4490,14 +4549,11 @@
 			if filepath.Base(output) == base {
 				foundLibfooJar = true
 				buildRule := s.Output(output)
-				actual := android.NormalizePathForTesting(buildRule.Input)
-				if actual != bootDexJarPath {
-					t.Errorf("Incorrect boot dex jar path '%s', expected '%s'", actual, bootDexJarPath)
-				}
+				android.AssertStringEquals(t, "boot dex jar path", bootDexJarPath, buildRule.Input.String())
 			}
 		}
 		if !foundLibfooJar {
-			t.Errorf("Rule for libfoo.jar missing in dex_bootjars singleton outputs")
+			t.Errorf("Rule for libfoo.jar missing in dex_bootjars singleton outputs %q", android.StringPathsRelativeToTop(ctx.Config().BuildDir(), s.AllOutputs()))
 		}
 	}
 
@@ -4539,8 +4595,8 @@
 	`
 
 		ctx := testDexpreoptWithApexes(t, bp, "", transform)
-		checkBootDexJarPath(t, ctx, "libfoo", ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libfoo.jar")
-		checkBootDexJarPath(t, ctx, "libbar", ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libbar.jar")
+		checkBootDexJarPath(t, ctx, "libfoo", "out/soong/.intermediates/myapex.deapexer/android_common/deapexer/javalib/libfoo.jar")
+		checkBootDexJarPath(t, ctx, "libbar", "out/soong/.intermediates/myapex.deapexer/android_common/deapexer/javalib/libbar.jar")
 
 		// Make sure that the dex file from the prebuilt_apex contributes to the hiddenapi index file.
 		checkHiddenAPIIndexInputs(t, ctx, `
@@ -4646,8 +4702,8 @@
 	`
 
 		ctx := testDexpreoptWithApexes(t, bp, "", transform)
-		checkBootDexJarPath(t, ctx, "libfoo", ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libfoo.jar")
-		checkBootDexJarPath(t, ctx, "libbar", ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libbar.jar")
+		checkBootDexJarPath(t, ctx, "libfoo", "out/soong/.intermediates/myapex.deapexer/android_common/deapexer/javalib/libfoo.jar")
+		checkBootDexJarPath(t, ctx, "libbar", "out/soong/.intermediates/myapex.deapexer/android_common/deapexer/javalib/libbar.jar")
 
 		// Make sure that the dex file from the prebuilt_apex contributes to the hiddenapi index file.
 		checkHiddenAPIIndexInputs(t, ctx, `
@@ -4713,8 +4769,8 @@
 	`
 
 		ctx := testDexpreoptWithApexes(t, bp, "", transform)
-		checkBootDexJarPath(t, ctx, "libfoo", ".intermediates/libfoo/android_common_apex10000/hiddenapi/libfoo.jar")
-		checkBootDexJarPath(t, ctx, "libbar", ".intermediates/libbar/android_common_myapex/hiddenapi/libbar.jar")
+		checkBootDexJarPath(t, ctx, "libfoo", "out/soong/.intermediates/libfoo/android_common_apex10000/hiddenapi/libfoo.jar")
+		checkBootDexJarPath(t, ctx, "libbar", "out/soong/.intermediates/libbar/android_common_myapex/hiddenapi/libbar.jar")
 
 		// Make sure that the dex file from the prebuilt_apex contributes to the hiddenapi index file.
 		checkHiddenAPIIndexInputs(t, ctx, `
@@ -4782,8 +4838,8 @@
 	`
 
 		ctx := testDexpreoptWithApexes(t, bp, "", transform)
-		checkBootDexJarPath(t, ctx, "libfoo", ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libfoo.jar")
-		checkBootDexJarPath(t, ctx, "libbar", ".intermediates/myapex.deapexer/android_common/deapexer/javalib/libbar.jar")
+		checkBootDexJarPath(t, ctx, "libfoo", "out/soong/.intermediates/myapex.deapexer/android_common/deapexer/javalib/libfoo.jar")
+		checkBootDexJarPath(t, ctx, "libbar", "out/soong/.intermediates/myapex.deapexer/android_common/deapexer/javalib/libbar.jar")
 
 		// Make sure that the dex file from the prebuilt_apex contributes to the hiddenapi index file.
 		checkHiddenAPIIndexInputs(t, ctx, `
@@ -5077,7 +5133,7 @@
 	}
 	// JNI libraries including transitive deps are
 	for _, jni := range []string{"libjni", "libfoo"} {
-		jniOutput := ctx.ModuleForTests(jni, "android_arm64_armv8-a_sdk_shared_apex10000").Module().(*cc.Module).OutputFile()
+		jniOutput := ctx.ModuleForTests(jni, "android_arm64_armv8-a_sdk_shared_apex10000").Module().(*cc.Module).OutputFile().RelativeToTop()
 		// ... embedded inside APK (jnilibs.zip)
 		ensureListContains(t, appZipRule.Implicits.Strings(), jniOutput.String())
 		// ... and not directly inside the APEX
@@ -5715,7 +5771,7 @@
 
 	// The bar library should depend on the implementation jar.
 	barLibrary := ctx.ModuleForTests("bar", "android_common_myapex").Rule("javac")
-	if expected, actual := `^-classpath /[^:]*/turbine-combined/foo\.jar$`, barLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
+	if expected, actual := `^-classpath [^:]*/turbine-combined/foo\.jar$`, barLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
 		t.Errorf("expected %q, found %#q", expected, actual)
 	}
 }
@@ -5766,7 +5822,7 @@
 
 	// The bar library should depend on the stubs jar.
 	barLibrary := ctx.ModuleForTests("bar", "android_common").Rule("javac")
-	if expected, actual := `^-classpath /[^:]*/turbine-combined/foo\.stubs\.jar$`, barLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
+	if expected, actual := `^-classpath [^:]*/turbine-combined/foo\.stubs\.jar$`, barLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
 		t.Errorf("expected %q, found %#q", expected, actual)
 	}
 }
@@ -5856,7 +5912,7 @@
 
 	// The bar library should depend on the implementation jar.
 	barLibrary := ctx.ModuleForTests("bar", "android_common_myapex").Rule("javac")
-	if expected, actual := `^-classpath /[^:]*/turbine-combined/foo\.impl\.jar$`, barLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
+	if expected, actual := `^-classpath [^:]*/turbine-combined/foo\.impl\.jar$`, barLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
 		t.Errorf("expected %q, found %#q", expected, actual)
 	}
 }
@@ -5889,9 +5945,10 @@
 }
 
 func TestCompatConfig(t *testing.T) {
-	result := apexFixtureFactory.
-		Extend(java.PrepareForTestWithPlatformCompatConfig).
-		RunTestWithBp(t, `
+	result := android.GroupFixturePreparers(
+		prepareForApexTest,
+		java.PrepareForTestWithPlatformCompatConfig,
+	).RunTestWithBp(t, `
 		apex {
 			name: "myapex",
 			key: "myapex.key",
@@ -6327,7 +6384,7 @@
 	)
 
 	m := ctx.ModuleForTests("myapex", "android_common")
-	extractedApex := m.Output(buildDir + "/.intermediates/myapex/android_common/foo_v2.apex")
+	extractedApex := m.Output("out/soong/.intermediates/myapex/android_common/foo_v2.apex")
 
 	actual := extractedApex.Inputs
 	if len(actual) != 1 {
@@ -6434,68 +6491,46 @@
 func testDexpreoptWithApexes(t *testing.T, bp, errmsg string, transformDexpreoptConfig func(*dexpreopt.GlobalConfig)) *android.TestContext {
 	t.Helper()
 
-	bp += cc.GatherRequiredDepsForTest(android.Android)
-	bp += java.GatherRequiredDepsForTest()
-
-	fs := map[string][]byte{
-		"a.java":                             nil,
-		"a.jar":                              nil,
-		"build/make/target/product/security": nil,
-		"apex_manifest.json":                 nil,
-		"AndroidManifest.xml":                nil,
+	fs := android.MockFS{
+		"a.java":              nil,
+		"a.jar":               nil,
+		"apex_manifest.json":  nil,
+		"AndroidManifest.xml": nil,
 		"system/sepolicy/apex/myapex-file_contexts":                  nil,
 		"system/sepolicy/apex/some-updatable-apex-file_contexts":     nil,
 		"system/sepolicy/apex/some-non-updatable-apex-file_contexts": nil,
 		"system/sepolicy/apex/com.android.art.debug-file_contexts":   nil,
 		"framework/aidl/a.aidl":                                      nil,
 	}
-	cc.GatherRequiredFilesForTest(fs)
 
-	for k, v := range filesForSdkLibrary {
-		fs[k] = v
-	}
-	config := android.TestArchConfig(buildDir, nil, bp, fs)
-
-	ctx := android.NewTestArchContext(config)
-	ctx.RegisterModuleType("apex", BundleFactory)
-	ctx.RegisterModuleType("apex_key", ApexKeyFactory)
-	ctx.RegisterModuleType("prebuilt_apex", PrebuiltFactory)
-	ctx.RegisterModuleType("filegroup", android.FileGroupFactory)
-	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
-	ctx.PreArchMutators(android.RegisterComponentsMutator)
-	android.RegisterPrebuiltMutators(ctx)
-	cc.RegisterRequiredBuildComponentsForTest(ctx)
-	java.RegisterRequiredBuildComponentsForTest(ctx)
-	java.RegisterHiddenApiSingletonComponents(ctx)
-	ctx.PostDepsMutators(android.RegisterOverridePostDepsMutators)
-	ctx.PreDepsMutators(RegisterPreDepsMutators)
-	ctx.PostDepsMutators(RegisterPostDepsMutators)
-
-	ctx.Register()
-
-	pathCtx := android.PathContextForTesting(config)
-	dexpreoptConfig := dexpreopt.GlobalConfigForTests(pathCtx)
-	transformDexpreoptConfig(dexpreoptConfig)
-	dexpreopt.SetTestGlobalConfig(config, dexpreoptConfig)
-
-	// Make sure that any changes to these dexpreopt properties are mirrored in the corresponding
-	// product variables.
-	config.TestProductVariables.BootJars = dexpreoptConfig.BootJars
-	config.TestProductVariables.UpdatableBootJars = dexpreoptConfig.UpdatableBootJars
-
-	_, errs := ctx.ParseBlueprintsFiles("Android.bp")
-	android.FailIfErrored(t, errs)
-
-	_, errs = ctx.PrepareBuildActions(config)
-	if errmsg == "" {
-		android.FailIfErrored(t, errs)
-	} else if len(errs) > 0 {
-		android.FailIfNoMatchingErrors(t, errmsg, errs)
-	} else {
-		t.Fatalf("missing expected error %q (0 errors are returned)", errmsg)
+	errorHandler := android.FixtureExpectsNoErrors
+	if errmsg != "" {
+		errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(errmsg)
 	}
 
-	return ctx
+	result := android.GroupFixturePreparers(
+		cc.PrepareForTestWithCcDefaultModules,
+		java.PrepareForTestWithHiddenApiBuildComponents,
+		java.PrepareForTestWithJavaDefaultModules,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		PrepareForTestWithApexBuildComponents,
+		android.FixtureModifyConfig(func(config android.Config) {
+			pathCtx := android.PathContextForTesting(config)
+			dexpreoptConfig := dexpreopt.GlobalConfigForTests(pathCtx)
+			transformDexpreoptConfig(dexpreoptConfig)
+			dexpreopt.SetTestGlobalConfig(config, dexpreoptConfig)
+
+			// Make sure that any changes to these dexpreopt properties are mirrored in the corresponding
+			// product variables.
+			config.TestProductVariables.BootJars = dexpreoptConfig.BootJars
+			config.TestProductVariables.UpdatableBootJars = dexpreoptConfig.UpdatableBootJars
+		}),
+		fs.AddToFixture(),
+	).
+		ExtendWithErrorHandler(errorHandler).
+		RunTestWithBp(t, bp)
+
+	return result.TestContext
 }
 
 func TestUpdatable_should_set_min_sdk_version(t *testing.T) {
@@ -6669,45 +6704,33 @@
 		public_key: "testkey.avbpubkey",
 		private_key: "testkey.pem",
 	}`
-	fs := map[string][]byte{
+	fs := android.MockFS{
 		"lib1/src/A.java": nil,
 		"lib2/src/B.java": nil,
 		"system/sepolicy/apex/myapex-file_contexts": nil,
 	}
 
-	config := android.TestArchConfig(buildDir, nil, bp, fs)
-	android.SetTestNeverallowRules(config, rules)
-	updatableBootJars := make([]string, 0, len(apexBootJars))
-	for _, apexBootJar := range apexBootJars {
-		updatableBootJars = append(updatableBootJars, "myapex:"+apexBootJar)
+	errorHandler := android.FixtureExpectsNoErrors
+	if errmsg != "" {
+		errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(errmsg)
 	}
-	config.TestProductVariables.UpdatableBootJars = android.CreateTestConfiguredJarList(updatableBootJars)
 
-	ctx := android.NewTestArchContext(config)
-	ctx.RegisterModuleType("apex", BundleFactory)
-	ctx.RegisterModuleType("apex_key", ApexKeyFactory)
-	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
-	cc.RegisterRequiredBuildComponentsForTest(ctx)
-	java.RegisterRequiredBuildComponentsForTest(ctx)
-	ctx.PostDepsMutators(android.RegisterOverridePostDepsMutators)
-	ctx.PreDepsMutators(RegisterPreDepsMutators)
-	ctx.PostDepsMutators(RegisterPostDepsMutators)
-	ctx.PostDepsMutators(android.RegisterNeverallowMutator)
-
-	ctx.Register()
-
-	_, errs := ctx.ParseBlueprintsFiles("Android.bp")
-	android.FailIfErrored(t, errs)
-
-	_, errs = ctx.PrepareBuildActions(config)
-	if errmsg == "" {
-		android.FailIfErrored(t, errs)
-	} else if len(errs) > 0 {
-		android.FailIfNoMatchingErrors(t, errmsg, errs)
-		return
-	} else {
-		t.Fatalf("missing expected error %q (0 errors are returned)", errmsg)
-	}
+	android.GroupFixturePreparers(
+		android.PrepareForTestWithAndroidBuildComponents,
+		java.PrepareForTestWithJavaBuildComponents,
+		PrepareForTestWithApexBuildComponents,
+		android.PrepareForTestWithNeverallowRules(rules),
+		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+			updatableBootJars := make([]string, 0, len(apexBootJars))
+			for _, apexBootJar := range apexBootJars {
+				updatableBootJars = append(updatableBootJars, "myapex:"+apexBootJar)
+			}
+			variables.UpdatableBootJars = android.CreateTestConfiguredJarList(updatableBootJars)
+		}),
+		fs.AddToFixture(),
+	).
+		ExtendWithErrorHandler(errorHandler).
+		RunTestWithBp(t, bp)
 }
 
 func TestApexPermittedPackagesRules(t *testing.T) {
@@ -6859,21 +6882,127 @@
 		}
 	`)
 
-	// the test 'mytest' is a test for the apex, therefore is linked to the
+	ensureLinkedLibIs := func(mod, variant, linkedLib, expectedVariant string) {
+		ldFlags := strings.Split(ctx.ModuleForTests(mod, variant).Rule("ld").Args["libFlags"], " ")
+		mylibLdFlags := android.FilterListPred(ldFlags, func(s string) bool { return strings.HasPrefix(s, linkedLib) })
+		android.AssertArrayString(t, "unexpected "+linkedLib+" link library for "+mod, []string{linkedLib + expectedVariant}, mylibLdFlags)
+	}
+
+	// These modules are tests for the apex, therefore are linked to the
 	// actual implementation of mylib instead of its stub.
-	ldFlags := ctx.ModuleForTests("mytest", "android_arm64_armv8-a").Rule("ld").Args["libFlags"]
-	ensureContains(t, ldFlags, "mylib/android_arm64_armv8-a_shared/mylib.so")
-	ensureNotContains(t, ldFlags, "mylib/android_arm64_armv8-a_shared_1/mylib.so")
+	ensureLinkedLibIs("mytest", "android_arm64_armv8-a", "out/soong/.intermediates/mylib/", "android_arm64_armv8-a_shared/mylib.so")
+	ensureLinkedLibIs("mytestlib", "android_arm64_armv8-a_shared", "out/soong/.intermediates/mylib/", "android_arm64_armv8-a_shared/mylib.so")
+	ensureLinkedLibIs("mybench", "android_arm64_armv8-a", "out/soong/.intermediates/mylib/", "android_arm64_armv8-a_shared/mylib.so")
+}
 
-	// The same should be true for cc_library
-	ldFlags = ctx.ModuleForTests("mytestlib", "android_arm64_armv8-a_shared").Rule("ld").Args["libFlags"]
-	ensureContains(t, ldFlags, "mylib/android_arm64_armv8-a_shared/mylib.so")
-	ensureNotContains(t, ldFlags, "mylib/android_arm64_armv8-a_shared_1/mylib.so")
+func TestIndirectTestFor(t *testing.T) {
+	ctx := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			native_shared_libs: ["mylib", "myprivlib"],
+			updatable: false,
+		}
 
-	// ... and for cc_benchmark
-	ldFlags = ctx.ModuleForTests("mybench", "android_arm64_armv8-a").Rule("ld").Args["libFlags"]
-	ensureContains(t, ldFlags, "mylib/android_arm64_armv8-a_shared/mylib.so")
-	ensureNotContains(t, ldFlags, "mylib/android_arm64_armv8-a_shared_1/mylib.so")
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_library {
+			name: "mylib",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			stl: "none",
+			stubs: {
+				versions: ["1"],
+			},
+			apex_available: ["myapex"],
+		}
+
+		cc_library {
+			name: "myprivlib",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			stl: "none",
+			shared_libs: ["mylib"],
+			apex_available: ["myapex"],
+		}
+
+		cc_library {
+			name: "mytestlib",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			shared_libs: ["myprivlib"],
+			stl: "none",
+			test_for: ["myapex"],
+		}
+	`)
+
+	ensureLinkedLibIs := func(mod, variant, linkedLib, expectedVariant string) {
+		ldFlags := strings.Split(ctx.ModuleForTests(mod, variant).Rule("ld").Args["libFlags"], " ")
+		mylibLdFlags := android.FilterListPred(ldFlags, func(s string) bool { return strings.HasPrefix(s, linkedLib) })
+		android.AssertArrayString(t, "unexpected "+linkedLib+" link library for "+mod, []string{linkedLib + expectedVariant}, mylibLdFlags)
+	}
+
+	// The platform variant of mytestlib links to the platform variant of the
+	// internal myprivlib.
+	ensureLinkedLibIs("mytestlib", "android_arm64_armv8-a_shared", "out/soong/.intermediates/myprivlib/", "android_arm64_armv8-a_shared/myprivlib.so")
+
+	// The platform variant of myprivlib links to the platform variant of mylib
+	// and bypasses its stubs.
+	ensureLinkedLibIs("myprivlib", "android_arm64_armv8-a_shared", "out/soong/.intermediates/mylib/", "android_arm64_armv8-a_shared/mylib.so")
+}
+
+func TestTestForForLibInOtherApex(t *testing.T) {
+	// This case is only allowed for known overlapping APEXes, i.e. the ART APEXes.
+	_ = testApex(t, `
+		apex {
+			name: "com.android.art",
+			key: "myapex.key",
+			native_shared_libs: ["mylib"],
+			updatable: false,
+		}
+
+		apex {
+			name: "com.android.art.debug",
+			key: "myapex.key",
+			native_shared_libs: ["mylib", "mytestlib"],
+			updatable: false,
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_library {
+			name: "mylib",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			stl: "none",
+			stubs: {
+				versions: ["1"],
+			},
+			apex_available: ["com.android.art", "com.android.art.debug"],
+		}
+
+		cc_library {
+			name: "mytestlib",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			shared_libs: ["mylib"],
+			stl: "none",
+			apex_available: ["com.android.art.debug"],
+			test_for: ["com.android.art"],
+		}
+	`,
+		android.MockFS{
+			"system/sepolicy/apex/com.android.art-file_contexts":       nil,
+			"system/sepolicy/apex/com.android.art.debug-file_contexts": nil,
+		}.AddToFixture())
 }
 
 // TODO(jungjw): Move this to proptools
@@ -6904,7 +7033,7 @@
 	m := ctx.ModuleForTests("myapex", "android_common")
 
 	// Check extract_apks tool parameters.
-	extractedApex := m.Output(buildDir + "/.intermediates/myapex/android_common/foo_v2.apex")
+	extractedApex := m.Output("out/soong/.intermediates/myapex/android_common/foo_v2.apex")
 	actual := extractedApex.Args["abis"]
 	expected := "ARMEABI_V7A,ARM64_V8A"
 	if actual != expected {
@@ -7383,7 +7512,7 @@
 							t.Errorf("AndroidMk entry for \"stublib\" has LOCAL_NOT_AVAILABLE_FOR_PLATFORM set: %+v", entry.mkEntries)
 						}
 						cflags := entry.mkEntries.EntryMap["LOCAL_EXPORT_CFLAGS"]
-						expected := "-D__STUBLIB_API__=1"
+						expected := "-D__STUBLIB_API__=10000"
 						if !android.InList(expected, cflags) {
 							t.Errorf("LOCAL_EXPORT_CFLAGS expected to have %q, but got %q", expected, cflags)
 						}
@@ -7395,12 +7524,5 @@
 }
 
 func TestMain(m *testing.M) {
-	run := func() int {
-		setUp()
-		defer tearDown()
-
-		return m.Run()
-	}
-
-	os.Exit(run())
+	os.Exit(m.Run())
 }
diff --git a/apex/boot_image_test.go b/apex/boot_image_test.go
index 7e37e42..aa0d9c4 100644
--- a/apex/boot_image_test.go
+++ b/apex/boot_image_test.go
@@ -26,23 +26,32 @@
 // Contains tests for boot_image logic from java/boot_image.go as the ART boot image requires
 // modules from the ART apex.
 
+var prepareForTestWithBootImage = android.GroupFixturePreparers(
+	java.PrepareForTestWithDexpreopt,
+	PrepareForTestWithApexBuildComponents,
+)
+
+// Some additional files needed for the art apex.
+var prepareForTestWithArtApex = android.FixtureMergeMockFs(android.MockFS{
+	"com.android.art.avbpubkey":                          nil,
+	"com.android.art.pem":                                nil,
+	"system/sepolicy/apex/com.android.art-file_contexts": nil,
+})
+
 func TestBootImages(t *testing.T) {
-	result := apexFixtureFactory.Extend(
+	result := android.GroupFixturePreparers(
+		prepareForTestWithBootImage,
 		// Configure some libraries in the art and framework boot images.
 		dexpreopt.FixtureSetArtBootJars("com.android.art:baz", "com.android.art:quuz"),
 		dexpreopt.FixtureSetBootJars("platform:foo", "platform:bar"),
-		filesForSdkLibrary.AddToFixture(),
-		// Some additional files needed for the art apex.
-		android.FixtureMergeMockFs(android.MockFS{
-			"com.android.art.avbpubkey":                          nil,
-			"com.android.art.pem":                                nil,
-			"system/sepolicy/apex/com.android.art-file_contexts": nil,
-		}),
+		prepareForTestWithArtApex,
+
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.FixtureWithLastReleaseApis("foo"),
 	).RunTestWithBp(t, `
 		java_sdk_library {
 			name: "foo",
 			srcs: ["b.java"],
-			unsafe_ignore_missing_latest_api: true,
 		}
 
 		java_library {
@@ -86,6 +95,9 @@
 		boot_image {
 			name: "art-boot-image",
 			image_name: "art",
+			apex_available: [
+				"com.android.art",
+			],
 		}
 
 		boot_image {
@@ -151,10 +163,164 @@
 	android.AssertTrimmedStringEquals(t, "invalid paths for "+moduleName, expectedBootImageFiles, strings.Join(allPaths, "\n"))
 }
 
-func TestBootImageInApex(t *testing.T) {
-	result := apexFixtureFactory.Extend(
-		// Configure some libraries in the framework boot image.
-		dexpreopt.FixtureSetBootJars("platform:foo", "platform:bar"),
+func TestBootImageInArtApex(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithBootImage,
+		prepareForTestWithArtApex,
+
+		// Configure some libraries in the art boot image.
+		dexpreopt.FixtureSetArtBootJars("com.android.art:foo", "com.android.art:bar"),
+	).RunTestWithBp(t, `
+		apex {
+			name: "com.android.art",
+			key: "com.android.art.key",
+			boot_images: [
+				"mybootimage",
+			],
+			// bar (like foo) should be transitively included in this apex because it is part of the
+			// mybootimage boot_image. However, it is kept here to ensure that the apex dedups the files
+			// correctly.
+			java_libs: [
+				"bar",
+			],
+			updatable: false,
+		}
+
+		apex_key {
+			name: "com.android.art.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_library {
+			name: "foo",
+			srcs: ["b.java"],
+			installable: true,
+			apex_available: [
+				"com.android.art",
+			],
+		}
+
+		java_library {
+			name: "bar",
+			srcs: ["b.java"],
+			installable: true,
+			apex_available: [
+				"com.android.art",
+			],
+		}
+
+		boot_image {
+			name: "mybootimage",
+			image_name: "art",
+			apex_available: [
+				"com.android.art",
+			],
+		}
+
+		// Make sure that a preferred prebuilt doesn't affect the apex.
+		prebuilt_boot_image {
+			name: "mybootimage",
+			image_name: "art",
+			prefer: true,
+			apex_available: [
+				"com.android.art",
+			],
+		}
+	`)
+
+	ensureExactContents(t, result.TestContext, "com.android.art", "android_common_com.android.art_image", []string{
+		"javalib/arm/boot.art",
+		"javalib/arm/boot.oat",
+		"javalib/arm/boot.vdex",
+		"javalib/arm/boot-bar.art",
+		"javalib/arm/boot-bar.oat",
+		"javalib/arm/boot-bar.vdex",
+		"javalib/arm64/boot.art",
+		"javalib/arm64/boot.oat",
+		"javalib/arm64/boot.vdex",
+		"javalib/arm64/boot-bar.art",
+		"javalib/arm64/boot-bar.oat",
+		"javalib/arm64/boot-bar.vdex",
+		"javalib/bar.jar",
+		"javalib/foo.jar",
+	})
+
+	java.CheckModuleDependencies(t, result.TestContext, "com.android.art", "android_common_com.android.art_image", []string{
+		`bar`,
+		`com.android.art.key`,
+		`mybootimage`,
+	})
+}
+
+func TestBootImageInPrebuiltArtApex(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithBootImage,
+		prepareForTestWithArtApex,
+
+		android.FixtureMergeMockFs(android.MockFS{
+			"com.android.art-arm64.apex": nil,
+			"com.android.art-arm.apex":   nil,
+		}),
+
+		// Configure some libraries in the art boot image.
+		dexpreopt.FixtureSetArtBootJars("com.android.art:foo", "com.android.art:bar"),
+	).RunTestWithBp(t, `
+		prebuilt_apex {
+			name: "com.android.art",
+			arch: {
+				arm64: {
+					src: "com.android.art-arm64.apex",
+				},
+				arm: {
+					src: "com.android.art-arm.apex",
+				},
+			},
+			exported_java_libs: ["foo", "bar"],
+		}
+
+		java_import {
+			name: "foo",
+			jars: ["foo.jar"],
+			apex_available: [
+				"com.android.art",
+			],
+		}
+
+		java_import {
+			name: "bar",
+			jars: ["bar.jar"],
+			apex_available: [
+				"com.android.art",
+			],
+		}
+
+		prebuilt_boot_image {
+			name: "mybootimage",
+			image_name: "art",
+			apex_available: [
+				"com.android.art",
+			],
+		}
+	`)
+
+	java.CheckModuleDependencies(t, result.TestContext, "com.android.art", "android_common", []string{
+		`com.android.art.apex.selector`,
+		`prebuilt_bar`,
+		`prebuilt_foo`,
+	})
+
+	java.CheckModuleDependencies(t, result.TestContext, "mybootimage", "android_common", []string{
+		`dex2oatd`,
+		`prebuilt_bar`,
+		`prebuilt_foo`,
+	})
+}
+
+func TestBootImageContentsNoName(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithBootImage,
+		prepareForTestWithMyapex,
 	).RunTestWithBp(t, `
 		apex {
 			name: "myapex",
@@ -175,17 +341,26 @@
 			name: "foo",
 			srcs: ["b.java"],
 			installable: true,
+			apex_available: [
+				"myapex",
+			],
 		}
 
 		java_library {
 			name: "bar",
 			srcs: ["b.java"],
 			installable: true,
+			apex_available: [
+				"myapex",
+			],
 		}
 
 		boot_image {
 			name: "mybootimage",
-			image_name: "boot",
+			contents: [
+				"foo",
+				"bar",
+			],
 			apex_available: [
 				"myapex",
 			],
@@ -193,18 +368,15 @@
 	`)
 
 	ensureExactContents(t, result.TestContext, "myapex", "android_common_myapex_image", []string{
-		"javalib/arm/boot-bar.art",
-		"javalib/arm/boot-bar.oat",
-		"javalib/arm/boot-bar.vdex",
-		"javalib/arm/boot-foo.art",
-		"javalib/arm/boot-foo.oat",
-		"javalib/arm/boot-foo.vdex",
-		"javalib/arm64/boot-bar.art",
-		"javalib/arm64/boot-bar.oat",
-		"javalib/arm64/boot-bar.vdex",
-		"javalib/arm64/boot-foo.art",
-		"javalib/arm64/boot-foo.oat",
-		"javalib/arm64/boot-foo.vdex",
+		// This does not include art, oat or vdex files as they are only included for the art boot
+		// image.
+		"javalib/bar.jar",
+		"javalib/foo.jar",
+	})
+
+	java.CheckModuleDependencies(t, result.TestContext, "myapex", "android_common_myapex_image", []string{
+		`myapex.key`,
+		`mybootimage`,
 	})
 }
 
diff --git a/apex/builder.go b/apex/builder.go
index da800d4..2df380b 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -950,6 +950,10 @@
 				if v := m.MinSdkVersion(); v != "" {
 					toMinSdkVersion = v
 				}
+			} else if m, ok := to.(interface{ MinSdkVersionString() string }); ok {
+				if v := m.MinSdkVersionString(); v != "" {
+					toMinSdkVersion = v
+				}
 			}
 
 			depInfos[to.Name()] = android.ApexModuleDepInfo{
diff --git a/apex/deapexer.go b/apex/deapexer.go
index 46ce41f..1db13f9 100644
--- a/apex/deapexer.go
+++ b/apex/deapexer.go
@@ -49,35 +49,31 @@
 	Exported_java_libs []string
 }
 
+type SelectedApexProperties struct {
+	// The path to the apex selected for use by this module.
+	//
+	// Is tagged as `android:"path"` because it will usually contain a string of the form ":<module>"
+	// and is tagged as "`blueprint:"mutate"` because it is only initialized in a LoadHook not an
+	// Android.bp file.
+	Selected_apex *string `android:"path" blueprint:"mutated"`
+}
+
 type Deapexer struct {
 	android.ModuleBase
-	prebuilt android.Prebuilt
 
-	properties         DeapexerProperties
-	apexFileProperties ApexFileProperties
+	properties             DeapexerProperties
+	selectedApexProperties SelectedApexProperties
 
 	inputApex android.Path
 }
 
 func privateDeapexerFactory() android.Module {
 	module := &Deapexer{}
-	module.AddProperties(
-		&module.properties,
-		&module.apexFileProperties,
-	)
-	android.InitPrebuiltModuleWithSrcSupplier(module, module.apexFileProperties.prebuiltApexSelector, "src")
+	module.AddProperties(&module.properties, &module.selectedApexProperties)
 	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
 	return module
 }
 
-func (p *Deapexer) Prebuilt() *android.Prebuilt {
-	return &p.prebuilt
-}
-
-func (p *Deapexer) Name() string {
-	return p.prebuilt.Name(p.ModuleBase.Name())
-}
-
 func (p *Deapexer) DepsMutator(ctx android.BottomUpMutatorContext) {
 	// Add dependencies from the java modules to which this exports files from the `.apex` file onto
 	// this module so that they can access the `DeapexerInfo` object that this provides.
@@ -88,7 +84,7 @@
 }
 
 func (p *Deapexer) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	p.inputApex = p.Prebuilt().SingleSourcePath(ctx)
+	p.inputApex = android.OptionalPathForModuleSrc(ctx, p.selectedApexProperties.Selected_apex).Path()
 
 	// Create and remember the directory into which the .apex file's contents will be unpacked.
 	deapexerOutput := android.PathForModuleOut(ctx, "deapexer")
diff --git a/apex/prebuilt.go b/apex/prebuilt.go
index 3280cd8..9d84281 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -91,11 +91,40 @@
 	return false
 }
 
+// prebuiltApexSelectorModule is a private module type that is only created by the prebuilt_apex
+// module. It selects the apex to use and makes it available for use by prebuilt_apex and the
+// deapexer.
+type prebuiltApexSelectorModule struct {
+	android.ModuleBase
+
+	apexFileProperties ApexFileProperties
+
+	inputApex android.Path
+}
+
+func privateApexSelectorModuleFactory() android.Module {
+	module := &prebuiltApexSelectorModule{}
+	module.AddProperties(
+		&module.apexFileProperties,
+	)
+	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
+	return module
+}
+
+func (p *prebuiltApexSelectorModule) Srcs() android.Paths {
+	return android.Paths{p.inputApex}
+}
+
+func (p *prebuiltApexSelectorModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	p.inputApex = android.SingleSourcePathFromSupplier(ctx, p.apexFileProperties.prebuiltApexSelector, "src")
+}
+
 type Prebuilt struct {
 	android.ModuleBase
 	prebuiltCommon
 
-	properties PrebuiltProperties
+	properties             PrebuiltProperties
+	selectedApexProperties SelectedApexProperties
 
 	inputApex       android.Path
 	installDir      android.InstallPath
@@ -113,19 +142,19 @@
 	// This cannot be marked as `android:"arch_variant"` because the `prebuilt_apex` is only mutated
 	// for android_common. That is so that it will have the same arch variant as, and so be compatible
 	// with, the source `apex` module type that it replaces.
-	Src  *string
+	Src  *string `android:"path"`
 	Arch struct {
 		Arm struct {
-			Src *string
+			Src *string `android:"path"`
 		}
 		Arm64 struct {
-			Src *string
+			Src *string `android:"path"`
 		}
 		X86 struct {
-			Src *string
+			Src *string `android:"path"`
 		}
 		X86_64 struct {
-			Src *string
+			Src *string `android:"path"`
 		}
 	}
 }
@@ -152,14 +181,17 @@
 		src = String(p.Arch.X86.Src)
 	case android.X86_64:
 		src = String(p.Arch.X86_64.Src)
-	default:
-		ctx.OtherModuleErrorf(prebuilt, "prebuilt_apex does not support %q", multiTargets[0].Arch.String())
-		return nil
 	}
 	if src == "" {
 		src = String(p.Src)
 	}
 
+	if src == "" {
+		ctx.OtherModuleErrorf(prebuilt, "prebuilt_apex does not support %q", multiTargets[0].Arch.String())
+		// Drop through to return an empty string as the src (instead of nil) to avoid the prebuilt
+		// logic from reporting a more general, less useful message.
+	}
+
 	return []string{src}
 }
 
@@ -221,28 +253,77 @@
 // 3. The `deapexer` module adds a dependency from the modules that require the exported files onto
 //    itself so that they can retrieve the file paths to those files.
 //
+// It also creates a child module `selector` that is responsible for selecting the appropriate
+// input apex for both the prebuilt_apex and the deapexer. That is needed for a couple of reasons:
+// 1. To dedup the selection logic so it only runs in one module.
+// 2. To allow the deapexer to be wired up to a different source for the input apex, e.g. an
+//    `apex_set`.
+//
+//                     prebuilt_apex
+//                    /      |      \
+//                 /         |         \
+//              V            |            V
+//       selector  <---  deapexer  <---  exported java lib
+//
 func PrebuiltFactory() android.Module {
 	module := &Prebuilt{}
-	module.AddProperties(&module.properties)
-	android.InitPrebuiltModuleWithSrcSupplier(module, module.properties.prebuiltApexSelector, "src")
+	module.AddProperties(&module.properties, &module.selectedApexProperties)
+	android.InitSingleSourcePrebuiltModule(module, &module.selectedApexProperties, "Selected_apex")
 	android.InitAndroidMultiTargetsArchModule(module, android.DeviceSupported, android.MultilibCommon)
 
 	android.AddLoadHook(module, func(ctx android.LoadHookContext) {
-		props := struct {
-			Name *string
-		}{
-			Name: proptools.StringPtr(module.BaseModuleName() + ".deapexer"),
+		baseModuleName := module.BaseModuleName()
+
+		apexSelectorModuleName := apexSelectorModuleName(baseModuleName)
+		createApexSelectorModule(ctx, apexSelectorModuleName, &module.properties.ApexFileProperties)
+
+		apexFileSource := ":" + apexSelectorModuleName
+		if len(module.properties.Exported_java_libs) != 0 {
+			createDeapexerModule(ctx, deapexerModuleName(baseModuleName), apexFileSource, &module.properties.DeapexerProperties)
 		}
-		ctx.CreateModule(privateDeapexerFactory,
-			&props,
-			&module.properties.ApexFileProperties,
-			&module.properties.DeapexerProperties,
-		)
+
+		// Add a source reference to retrieve the selected apex from the selector module.
+		module.selectedApexProperties.Selected_apex = proptools.StringPtr(apexFileSource)
 	})
 
 	return module
 }
 
+func createApexSelectorModule(ctx android.LoadHookContext, name string, apexFileProperties *ApexFileProperties) {
+	props := struct {
+		Name *string
+	}{
+		Name: proptools.StringPtr(name),
+	}
+
+	ctx.CreateModule(privateApexSelectorModuleFactory,
+		&props,
+		apexFileProperties,
+	)
+}
+
+func createDeapexerModule(ctx android.LoadHookContext, deapexerName string, apexFileSource string, deapexerProperties *DeapexerProperties) {
+	props := struct {
+		Name          *string
+		Selected_apex *string
+	}{
+		Name:          proptools.StringPtr(deapexerName),
+		Selected_apex: proptools.StringPtr(apexFileSource),
+	}
+	ctx.CreateModule(privateDeapexerFactory,
+		&props,
+		deapexerProperties,
+	)
+}
+
+func deapexerModuleName(baseModuleName string) string {
+	return baseModuleName + ".deapexer"
+}
+
+func apexSelectorModuleName(baseModuleName string) string {
+	return baseModuleName + ".apex.selector"
+}
+
 func prebuiltApexExportedModuleName(ctx android.BottomUpMutatorContext, name string) string {
 	// The prebuilt_apex should be depending on prebuilt modules but as this runs after
 	// prebuilt_rename the prebuilt module may or may not be using the prebuilt_ prefixed named. So,
@@ -250,7 +331,7 @@
 	// the unprefixed name is the one to use. If the unprefixed one turns out to be a source module
 	// and not a renamed prebuilt module then that will be detected and reported as an error when
 	// processing the dependency in ApexInfoMutator().
-	prebuiltName := "prebuilt_" + name
+	prebuiltName := android.PrebuiltNameFromSource(name)
 	if ctx.OtherModuleExists(prebuiltName) {
 		name = prebuiltName
 	}
@@ -375,7 +456,7 @@
 
 func (p *Prebuilt) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	// TODO(jungjw): Check the key validity.
-	p.inputApex = p.Prebuilt().SingleSourcePath(ctx)
+	p.inputApex = android.OptionalPathForModuleSrc(ctx, p.selectedApexProperties.Selected_apex).Path()
 	p.installDir = android.PathForModuleInstall(ctx, "apex")
 	p.installFilename = p.InstallFilename()
 	if !strings.HasSuffix(p.installFilename, imageApexSuffix) {
diff --git a/apex/testing.go b/apex/testing.go
index e662cad..926125f 100644
--- a/apex/testing.go
+++ b/apex/testing.go
@@ -19,4 +19,11 @@
 var PrepareForTestWithApexBuildComponents = android.GroupFixturePreparers(
 	android.FixtureRegisterWithContext(registerApexBuildComponents),
 	android.FixtureRegisterWithContext(registerApexKeyBuildComponents),
+	// Additional files needed in tests that disallow non-existent source files.
+	// This includes files that are needed by all, or at least most, instances of an apex module type.
+	android.MockFS{
+		// Needed by apex.
+		"system/core/rootdir/etc/public.libraries.android.txt": nil,
+		"build/soong/scripts/gen_ndk_backedby_apex.sh":         nil,
+	}.AddToFixture(),
 )
diff --git a/apex/vndk_test.go b/apex/vndk_test.go
index 015283d..d580e5a 100644
--- a/apex/vndk_test.go
+++ b/apex/vndk_test.go
@@ -59,11 +59,11 @@
 		"lib/libc++.so",
 		"lib64/libvndksp.so",
 		"lib64/libc++.so",
-		"etc/llndk.libraries.VER.txt",
-		"etc/vndkcore.libraries.VER.txt",
-		"etc/vndksp.libraries.VER.txt",
-		"etc/vndkprivate.libraries.VER.txt",
-		"etc/vndkproduct.libraries.VER.txt",
+		"etc/llndk.libraries.29.txt",
+		"etc/vndkcore.libraries.29.txt",
+		"etc/vndksp.libraries.29.txt",
+		"etc/vndkprivate.libraries.29.txt",
+		"etc/vndkproduct.libraries.29.txt",
 	})
 }
 
@@ -111,7 +111,7 @@
 
 		// VNDK APEX doesn't create apex variant
 		files := getFiles(t, ctx, "com.android.vndk.current", "android_common_image")
-		ensureFileSrc(t, files, "lib/libfoo.so", "libfoo/android_vendor.VER_arm_armv7-a-neon_shared/libfoo.so")
+		ensureFileSrc(t, files, "lib/libfoo.so", "libfoo/android_vendor.29_arm_armv7-a-neon_shared/libfoo.so")
 	})
 
 	t.Run("VNDK APEX gathers only vendor variants even if product variants are available", func(t *testing.T) {
@@ -123,7 +123,7 @@
 		)
 
 		files := getFiles(t, ctx, "com.android.vndk.current", "android_common_image")
-		ensureFileSrc(t, files, "lib/libfoo.so", "libfoo/android_vendor.VER_arm_armv7-a-neon_shared/libfoo.so")
+		ensureFileSrc(t, files, "lib/libfoo.so", "libfoo/android_vendor.29_arm_armv7-a-neon_shared/libfoo.so")
 	})
 
 	t.Run("VNDK APEX supports coverage variants", func(t *testing.T) {
@@ -135,9 +135,9 @@
 		)
 
 		files := getFiles(t, ctx, "com.android.vndk.current", "android_common_image")
-		ensureFileSrc(t, files, "lib/libfoo.so", "libfoo/android_vendor.VER_arm_armv7-a-neon_shared/libfoo.so")
+		ensureFileSrc(t, files, "lib/libfoo.so", "libfoo/android_vendor.29_arm_armv7-a-neon_shared/libfoo.so")
 
 		files = getFiles(t, ctx, "com.android.vndk.current", "android_common_cov_image")
-		ensureFileSrc(t, files, "lib/libfoo.so", "libfoo/android_vendor.VER_arm_armv7-a-neon_shared_cov/libfoo.so")
+		ensureFileSrc(t, files, "lib/libfoo.so", "libfoo/android_vendor.29_arm_armv7-a-neon_shared_cov/libfoo.so")
 	})
 }
diff --git a/bazel/aquery.go b/bazel/aquery.go
index c82b464..555f1dc 100644
--- a/bazel/aquery.go
+++ b/bazel/aquery.go
@@ -74,6 +74,7 @@
 // with a Bazel action from Bazel's action graph.
 type BuildStatement struct {
 	Command     string
+	Depfile     *string
 	OutputPaths []string
 	InputPaths  []string
 	Env         []KeyValuePair
@@ -133,12 +134,22 @@
 			continue
 		}
 		outputPaths := []string{}
+		var depfile *string
 		for _, outputId := range actionEntry.OutputIds {
 			outputPath, exists := artifactIdToPath[outputId]
 			if !exists {
 				return nil, fmt.Errorf("undefined outputId %d", outputId)
 			}
-			outputPaths = append(outputPaths, outputPath)
+			ext := filepath.Ext(outputPath)
+			if ext == ".d" {
+				if depfile != nil {
+					return nil, fmt.Errorf("found multiple potential depfiles %q, %q", *depfile, outputPath)
+				} else {
+					depfile = &outputPath
+				}
+			} else {
+				outputPaths = append(outputPaths, outputPath)
+			}
 		}
 		inputPaths := []string{}
 		for _, inputDepSetId := range actionEntry.InputDepSetIds {
@@ -161,12 +172,13 @@
 		}
 		buildStatement := BuildStatement{
 			Command:     strings.Join(proptools.ShellEscapeList(actionEntry.Arguments), " "),
+			Depfile:     depfile,
 			OutputPaths: outputPaths,
 			InputPaths:  inputPaths,
 			Env:         actionEntry.EnvironmentVariables,
 			Mnemonic:    actionEntry.Mnemonic}
 		if len(actionEntry.Arguments) < 1 {
-			return nil, fmt.Errorf("received action with no command: [%s]", buildStatement)
+			return nil, fmt.Errorf("received action with no command: [%v]", buildStatement)
 			continue
 		}
 		buildStatements = append(buildStatements, buildStatement)
diff --git a/bazel/aquery_test.go b/bazel/aquery_test.go
index a48e083..fa8810f 100644
--- a/bazel/aquery_test.go
+++ b/bazel/aquery_test.go
@@ -393,6 +393,109 @@
 	assertError(t, err, "undefined path fragment id 3")
 }
 
+func TestDepfiles(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [{
+    "id": 1,
+    "pathFragmentId": 1
+  }, {
+    "id": 2,
+    "pathFragmentId": 2
+  }, {
+    "id": 3,
+    "pathFragmentId": 3
+  }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "x",
+    "arguments": ["touch", "foo"],
+    "inputDepSetIds": [1],
+    "outputIds": [2, 3],
+    "primaryOutputId": 2
+  }],
+  "depSetOfFiles": [{
+    "id": 1,
+    "directArtifactIds": [1, 2, 3]
+  }],
+  "pathFragments": [{
+    "id": 1,
+    "label": "one"
+  }, {
+    "id": 2,
+    "label": "two"
+  }, {
+    "id": 3,
+    "label": "two.d"
+  }]
+}`
+
+	actual, err := AqueryBuildStatements([]byte(inputString))
+	if err != nil {
+		t.Errorf("Unexpected error %q", err)
+	}
+	if expected := 1; len(actual) != expected {
+		t.Fatalf("Expected %d build statements, got %d", expected, len(actual))
+	}
+
+	bs := actual[0]
+	expectedDepfile := "two.d"
+	if bs.Depfile == nil {
+		t.Errorf("Expected depfile %q, but there was none found", expectedDepfile)
+	} else if *bs.Depfile != expectedDepfile {
+		t.Errorf("Expected depfile %q, but got %q", expectedDepfile, *bs.Depfile)
+	}
+}
+
+func TestMultipleDepfiles(t *testing.T) {
+	const inputString = `
+{
+  "artifacts": [{
+    "id": 1,
+    "pathFragmentId": 1
+  }, {
+    "id": 2,
+    "pathFragmentId": 2
+  }, {
+    "id": 3,
+    "pathFragmentId": 3
+  }, {
+    "id": 4,
+    "pathFragmentId": 4
+  }],
+  "actions": [{
+    "targetId": 1,
+    "actionKey": "x",
+    "mnemonic": "x",
+    "arguments": ["touch", "foo"],
+    "inputDepSetIds": [1],
+    "outputIds": [2,3,4],
+    "primaryOutputId": 2
+  }],
+  "depSetOfFiles": [{
+    "id": 1,
+    "directArtifactIds": [1, 2, 3, 4]
+  }],
+  "pathFragments": [{
+    "id": 1,
+    "label": "one"
+  }, {
+    "id": 2,
+    "label": "two"
+  }, {
+    "id": 3,
+    "label": "two.d"
+  }, {
+    "id": 4,
+    "label": "other.d"
+  }]
+}`
+
+	_, err := AqueryBuildStatements([]byte(inputString))
+	assertError(t, err, `found multiple potential depfiles "two.d", "other.d"`)
+}
+
 func TestTransitiveInputDepsets(t *testing.T) {
 	// The input aquery for this test comes from a proof-of-concept starlark rule which registers
 	// a single action with many inputs given via a deep depset.
@@ -627,7 +730,7 @@
 // Build statement equivalence is determined using buildStatementEquals.
 func assertBuildStatements(t *testing.T, expected []BuildStatement, actual []BuildStatement) {
 	if len(expected) != len(actual) {
-		t.Errorf("expected %d build statements, but got %d,\n expected: %s,\n actual: %s",
+		t.Errorf("expected %d build statements, but got %d,\n expected: %v,\n actual: %v",
 			len(expected), len(actual), expected, actual)
 		return
 	}
@@ -638,7 +741,7 @@
 				continue ACTUAL_LOOP
 			}
 		}
-		t.Errorf("unexpected build statement %s.\n expected: %s",
+		t.Errorf("unexpected build statement %v.\n expected: %v",
 			actualStatement, expected)
 		return
 	}
diff --git a/bazel/cquery/request_type.go b/bazel/cquery/request_type.go
index 864db3d..affb5ce 100644
--- a/bazel/cquery/request_type.go
+++ b/bazel/cquery/request_type.go
@@ -6,7 +6,6 @@
 
 var (
 	GetOutputFiles                 RequestType = &getOutputFilesRequestType{}
-	GetCcObjectFiles               RequestType = &getCcObjectFilesRequestType{}
 	GetOutputFilesAndCcObjectFiles RequestType = &getOutputFilesAndCcObjectFilesType{}
 )
 
@@ -15,9 +14,6 @@
 	CcObjectFiles []string
 }
 
-var RequestTypes []RequestType = []RequestType{
-	GetOutputFiles, GetCcObjectFiles, GetOutputFilesAndCcObjectFiles}
-
 type RequestType interface {
 	// Name returns a string name for this request type. Such request type names must be unique,
 	// and must only consist of alphanumeric characters.
@@ -55,28 +51,6 @@
 	return strings.Split(rawString, ", ")
 }
 
-type getCcObjectFilesRequestType struct{}
-
-func (g getCcObjectFilesRequestType) Name() string {
-	return "getCcObjectFiles"
-}
-
-func (g getCcObjectFilesRequestType) StarlarkFunctionBody() string {
-	return `
-result = []
-linker_inputs = providers(target)["CcInfo"].linking_context.linker_inputs.to_list()
-
-for linker_input in linker_inputs:
-  for library in linker_input.libraries:
-    for object in library.objects:
-      result += [object.path]
-return ', '.join(result)`
-}
-
-func (g getCcObjectFilesRequestType) ParseResult(rawString string) interface{} {
-	return strings.Split(rawString, ", ")
-}
-
 type getOutputFilesAndCcObjectFilesType struct{}
 
 func (g getOutputFilesAndCcObjectFilesType) Name() string {
@@ -104,7 +78,17 @@
 	splitString := strings.Split(rawString, "|")
 	outputFilesString := splitString[0]
 	ccObjectsString := splitString[1]
-	outputFiles = strings.Split(outputFilesString, ", ")
-	ccObjects = strings.Split(ccObjectsString, ", ")
+	outputFiles = splitOrEmpty(outputFilesString, ", ")
+	ccObjects = splitOrEmpty(ccObjectsString, ", ")
 	return GetOutputFilesAndCcObjectFiles_Result{outputFiles, ccObjects}
 }
+
+// splitOrEmpty is a modification of strings.Split() that returns an empty list
+// if the given string is empty.
+func splitOrEmpty(s string, sep string) []string {
+	if len(s) < 1 {
+		return []string{}
+	} else {
+		return strings.Split(s, sep)
+	}
+}
diff --git a/bazel/properties.go b/bazel/properties.go
index abdc107..148386f 100644
--- a/bazel/properties.go
+++ b/bazel/properties.go
@@ -16,6 +16,7 @@
 
 import (
 	"fmt"
+	"regexp"
 	"sort"
 )
 
@@ -31,6 +32,8 @@
 
 const BazelTargetModuleNamePrefix = "__bp2build__"
 
+var productVariableSubstitutionPattern = regexp.MustCompile("%(d|s)")
+
 // Label is used to represent a Bazel compatible Label. Also stores the original bp text to support
 // string replacement.
 type Label struct {
@@ -76,71 +79,355 @@
 	return uniqueLabelList
 }
 
-// StringListAttribute corresponds to the string_list Bazel attribute type with
-// support for additional metadata, like configurations.
-type StringListAttribute struct {
-	// The base value of the string list attribute.
-	Value []string
+// Subtract needle from haystack
+func SubtractStrings(haystack []string, needle []string) []string {
+	// This is really a set
+	remainder := make(map[string]bool)
 
-	// Optional additive set of list values to the base value.
-	ArchValues stringListArchValues
+	for _, s := range haystack {
+		remainder[s] = true
+	}
+	for _, s := range needle {
+		delete(remainder, s)
+	}
+
+	var strings []string
+	for s, _ := range remainder {
+		strings = append(strings, s)
+	}
+
+	sort.SliceStable(strings, func(i, j int) bool {
+		return strings[i] < strings[j]
+	})
+
+	return strings
 }
 
-// Arch-specific string_list typed Bazel attribute values. This should correspond
+// Subtract needle from haystack
+func SubtractBazelLabels(haystack []Label, needle []Label) []Label {
+	// This is really a set
+	remainder := make(map[Label]bool)
+
+	for _, label := range haystack {
+		remainder[label] = true
+	}
+	for _, label := range needle {
+		delete(remainder, label)
+	}
+
+	var labels []Label
+	for label, _ := range remainder {
+		labels = append(labels, label)
+	}
+
+	sort.SliceStable(labels, func(i, j int) bool {
+		return labels[i].Label < labels[j].Label
+	})
+
+	return labels
+}
+
+// Subtract needle from haystack
+func SubtractBazelLabelList(haystack LabelList, needle LabelList) LabelList {
+	var result LabelList
+	result.Includes = SubtractBazelLabels(haystack.Includes, needle.Includes)
+	// NOTE: Excludes are intentionally not subtracted
+	result.Excludes = haystack.Excludes
+	return result
+}
+
+const (
+	// ArchType names in arch.go
+	ARCH_ARM    = "arm"
+	ARCH_ARM64  = "arm64"
+	ARCH_X86    = "x86"
+	ARCH_X86_64 = "x86_64"
+
+	// OsType names in arch.go
+	OS_ANDROID      = "android"
+	OS_DARWIN       = "darwin"
+	OS_FUCHSIA      = "fuchsia"
+	OS_LINUX        = "linux_glibc"
+	OS_LINUX_BIONIC = "linux_bionic"
+	OS_WINDOWS      = "windows"
+)
+
+var (
+	// These are the list of OSes and architectures with a Bazel config_setting
+	// and constraint value equivalent. These exist in arch.go, but the android
+	// package depends on the bazel package, so a cyclic dependency prevents
+	// using those variables here.
+
+	// A map of architectures to the Bazel label of the constraint_value
+	// for the @platforms//cpu:cpu constraint_setting
+	PlatformArchMap = map[string]string{
+		ARCH_ARM:    "//build/bazel/platforms/arch:arm",
+		ARCH_ARM64:  "//build/bazel/platforms/arch:arm64",
+		ARCH_X86:    "//build/bazel/platforms/arch:x86",
+		ARCH_X86_64: "//build/bazel/platforms/arch:x86_64",
+	}
+
+	// A map of target operating systems to the Bazel label of the
+	// constraint_value for the @platforms//os:os constraint_setting
+	PlatformOsMap = map[string]string{
+		OS_ANDROID:      "//build/bazel/platforms/os:android",
+		OS_DARWIN:       "//build/bazel/platforms/os:darwin",
+		OS_FUCHSIA:      "//build/bazel/platforms/os:fuchsia",
+		OS_LINUX:        "//build/bazel/platforms/os:linux",
+		OS_LINUX_BIONIC: "//build/bazel/platforms/os:linux_bionic",
+		OS_WINDOWS:      "//build/bazel/platforms/os:windows",
+	}
+)
+
+type Attribute interface {
+	HasConfigurableValues() bool
+}
+
+// Arch-specific label_list typed Bazel attribute values. This should correspond
 // to the types of architectures supported for compilation in arch.go.
-type stringListArchValues struct {
-	X86     []string
-	X86_64  []string
-	Arm     []string
-	Arm64   []string
-	Default []string
-	// TODO(b/181299724): this is currently missing the "common" arch, which
-	// doesn't have an equivalent platform() definition yet.
+type labelListArchValues struct {
+	X86    LabelList
+	X86_64 LabelList
+	Arm    LabelList
+	Arm64  LabelList
+	Common LabelList
+}
+
+type labelListOsValues struct {
+	Android     LabelList
+	Darwin      LabelList
+	Fuchsia     LabelList
+	Linux       LabelList
+	LinuxBionic LabelList
+	Windows     LabelList
+}
+
+// LabelListAttribute is used to represent a list of Bazel labels as an
+// attribute.
+type LabelListAttribute struct {
+	// The non-arch specific attribute label list Value. Required.
+	Value LabelList
+
+	// The arch-specific attribute label list values. Optional. If used, these
+	// are generated in a select statement and appended to the non-arch specific
+	// label list Value.
+	ArchValues labelListArchValues
+
+	// The os-specific attribute label list values. Optional. If used, these
+	// are generated in a select statement and appended to the non-os specific
+	// label list Value.
+	OsValues labelListOsValues
+}
+
+// MakeLabelListAttribute initializes a LabelListAttribute with the non-arch specific value.
+func MakeLabelListAttribute(value LabelList) LabelListAttribute {
+	return LabelListAttribute{Value: UniqueBazelLabelList(value)}
 }
 
 // HasArchSpecificValues returns true if the attribute contains
-// architecture-specific string_list values.
-func (attrs *StringListAttribute) HasArchSpecificValues() bool {
-	for _, arch := range []string{"x86", "x86_64", "arm", "arm64", "default"} {
-		if len(attrs.GetValueForArch(arch)) > 0 {
+// architecture-specific label_list values.
+func (attrs LabelListAttribute) HasConfigurableValues() bool {
+	for arch := range PlatformArchMap {
+		if len(attrs.GetValueForArch(arch).Includes) > 0 {
+			return true
+		}
+	}
+
+	for os := range PlatformOsMap {
+		if len(attrs.GetValueForOS(os).Includes) > 0 {
 			return true
 		}
 	}
 	return false
 }
 
-// GetValueForArch returns the string_list attribute value for an architecture.
-func (attrs *StringListAttribute) GetValueForArch(arch string) []string {
-	switch arch {
-	case "x86":
-		return attrs.ArchValues.X86
-	case "x86_64":
-		return attrs.ArchValues.X86_64
-	case "arm":
-		return attrs.ArchValues.Arm
-	case "arm64":
-		return attrs.ArchValues.Arm64
-	case "default":
-		return attrs.ArchValues.Default
-	default:
+func (attrs *LabelListAttribute) archValuePtrs() map[string]*LabelList {
+	return map[string]*LabelList{
+		ARCH_X86:    &attrs.ArchValues.X86,
+		ARCH_X86_64: &attrs.ArchValues.X86_64,
+		ARCH_ARM:    &attrs.ArchValues.Arm,
+		ARCH_ARM64:  &attrs.ArchValues.Arm64,
+	}
+}
+
+// GetValueForArch returns the label_list attribute value for an architecture.
+func (attrs *LabelListAttribute) GetValueForArch(arch string) LabelList {
+	var v *LabelList
+	if v = attrs.archValuePtrs()[arch]; v == nil {
 		panic(fmt.Errorf("Unknown arch: %s", arch))
 	}
+	return *v
+}
+
+// SetValueForArch sets the label_list attribute value for an architecture.
+func (attrs *LabelListAttribute) SetValueForArch(arch string, value LabelList) {
+	var v *LabelList
+	if v = attrs.archValuePtrs()[arch]; v == nil {
+		panic(fmt.Errorf("Unknown arch: %s", arch))
+	}
+	*v = value
+}
+
+func (attrs *LabelListAttribute) osValuePtrs() map[string]*LabelList {
+	return map[string]*LabelList{
+		OS_ANDROID:      &attrs.OsValues.Android,
+		OS_DARWIN:       &attrs.OsValues.Darwin,
+		OS_FUCHSIA:      &attrs.OsValues.Fuchsia,
+		OS_LINUX:        &attrs.OsValues.Linux,
+		OS_LINUX_BIONIC: &attrs.OsValues.LinuxBionic,
+		OS_WINDOWS:      &attrs.OsValues.Windows,
+	}
+}
+
+// GetValueForOS returns the label_list attribute value for an OS target.
+func (attrs *LabelListAttribute) GetValueForOS(os string) LabelList {
+	var v *LabelList
+	if v = attrs.osValuePtrs()[os]; v == nil {
+		panic(fmt.Errorf("Unknown os: %s", os))
+	}
+	return *v
+}
+
+// SetValueForArch sets the label_list attribute value for an OS target.
+func (attrs *LabelListAttribute) SetValueForOS(os string, value LabelList) {
+	var v *LabelList
+	if v = attrs.osValuePtrs()[os]; v == nil {
+		panic(fmt.Errorf("Unknown os: %s", os))
+	}
+	*v = value
+}
+
+// StringListAttribute corresponds to the string_list Bazel attribute type with
+// support for additional metadata, like configurations.
+type StringListAttribute struct {
+	// The base value of the string list attribute.
+	Value []string
+
+	// The arch-specific attribute string list values. Optional. If used, these
+	// are generated in a select statement and appended to the non-arch specific
+	// label list Value.
+	ArchValues stringListArchValues
+
+	// The os-specific attribute string list values. Optional. If used, these
+	// are generated in a select statement and appended to the non-os specific
+	// label list Value.
+	OsValues stringListOsValues
+}
+
+// MakeStringListAttribute initializes a StringListAttribute with the non-arch specific value.
+func MakeStringListAttribute(value []string) StringListAttribute {
+	// NOTE: These strings are not necessarily unique or sorted.
+	return StringListAttribute{Value: value}
+}
+
+// Arch-specific string_list typed Bazel attribute values. This should correspond
+// to the types of architectures supported for compilation in arch.go.
+type stringListArchValues struct {
+	X86    []string
+	X86_64 []string
+	Arm    []string
+	Arm64  []string
+	Common []string
+}
+
+type stringListOsValues struct {
+	Android     []string
+	Darwin      []string
+	Fuchsia     []string
+	Linux       []string
+	LinuxBionic []string
+	Windows     []string
+}
+
+// HasConfigurableValues returns true if the attribute contains
+// architecture-specific string_list values.
+func (attrs StringListAttribute) HasConfigurableValues() bool {
+	for arch := range PlatformArchMap {
+		if len(attrs.GetValueForArch(arch)) > 0 {
+			return true
+		}
+	}
+
+	for os := range PlatformOsMap {
+		if len(attrs.GetValueForOS(os)) > 0 {
+			return true
+		}
+	}
+	return false
+}
+
+func (attrs *StringListAttribute) archValuePtrs() map[string]*[]string {
+	return map[string]*[]string{
+		ARCH_X86:    &attrs.ArchValues.X86,
+		ARCH_X86_64: &attrs.ArchValues.X86_64,
+		ARCH_ARM:    &attrs.ArchValues.Arm,
+		ARCH_ARM64:  &attrs.ArchValues.Arm64,
+	}
+}
+
+// GetValueForArch returns the string_list attribute value for an architecture.
+func (attrs *StringListAttribute) GetValueForArch(arch string) []string {
+	var v *[]string
+	if v = attrs.archValuePtrs()[arch]; v == nil {
+		panic(fmt.Errorf("Unknown arch: %s", arch))
+	}
+	return *v
 }
 
 // SetValueForArch sets the string_list attribute value for an architecture.
 func (attrs *StringListAttribute) SetValueForArch(arch string, value []string) {
-	switch arch {
-	case "x86":
-		attrs.ArchValues.X86 = value
-	case "x86_64":
-		attrs.ArchValues.X86_64 = value
-	case "arm":
-		attrs.ArchValues.Arm = value
-	case "arm64":
-		attrs.ArchValues.Arm64 = value
-	case "default":
-		attrs.ArchValues.Default = value
-	default:
+	var v *[]string
+	if v = attrs.archValuePtrs()[arch]; v == nil {
 		panic(fmt.Errorf("Unknown arch: %s", arch))
 	}
+	*v = value
+}
+
+func (attrs *StringListAttribute) osValuePtrs() map[string]*[]string {
+	return map[string]*[]string{
+		OS_ANDROID:      &attrs.OsValues.Android,
+		OS_DARWIN:       &attrs.OsValues.Darwin,
+		OS_FUCHSIA:      &attrs.OsValues.Fuchsia,
+		OS_LINUX:        &attrs.OsValues.Linux,
+		OS_LINUX_BIONIC: &attrs.OsValues.LinuxBionic,
+		OS_WINDOWS:      &attrs.OsValues.Windows,
+	}
+}
+
+// GetValueForOS returns the string_list attribute value for an OS target.
+func (attrs *StringListAttribute) GetValueForOS(os string) []string {
+	var v *[]string
+	if v = attrs.osValuePtrs()[os]; v == nil {
+		panic(fmt.Errorf("Unknown os: %s", os))
+	}
+	return *v
+}
+
+// SetValueForArch sets the string_list attribute value for an OS target.
+func (attrs *StringListAttribute) SetValueForOS(os string, value []string) {
+	var v *[]string
+	if v = attrs.osValuePtrs()[os]; v == nil {
+		panic(fmt.Errorf("Unknown os: %s", os))
+	}
+	*v = value
+}
+
+// TryVariableSubstitution, replace string substitution formatting within each string in slice with
+// Starlark string.format compatible tag for productVariable.
+func TryVariableSubstitutions(slice []string, productVariable string) ([]string, bool) {
+	ret := make([]string, 0, len(slice))
+	changesMade := false
+	for _, s := range slice {
+		newS, changed := TryVariableSubstitution(s, productVariable)
+		ret = append(ret, newS)
+		changesMade = changesMade || changed
+	}
+	return ret, changesMade
+}
+
+// TryVariableSubstitution, replace string substitution formatting within s with Starlark
+// string.format compatible tag for productVariable.
+func TryVariableSubstitution(s string, productVariable string) (string, bool) {
+	sub := productVariableSubstitutionPattern.ReplaceAllString(s, "{"+productVariable+"}")
+	return sub, s != sub
 }
diff --git a/bazel/properties_test.go b/bazel/properties_test.go
index 0fcb904..56840ef 100644
--- a/bazel/properties_test.go
+++ b/bazel/properties_test.go
@@ -46,6 +46,82 @@
 	}
 }
 
+func TestSubtractStrings(t *testing.T) {
+	testCases := []struct {
+		haystack       []string
+		needle         []string
+		expectedResult []string
+	}{
+		{
+			haystack: []string{
+				"a",
+				"b",
+				"c",
+			},
+			needle: []string{
+				"a",
+			},
+			expectedResult: []string{
+				"b", "c",
+			},
+		},
+	}
+	for _, tc := range testCases {
+		actualResult := SubtractStrings(tc.haystack, tc.needle)
+		if !reflect.DeepEqual(tc.expectedResult, actualResult) {
+			t.Fatalf("Expected %v, got %v", tc.expectedResult, actualResult)
+		}
+	}
+}
+
+func TestSubtractBazelLabelList(t *testing.T) {
+	testCases := []struct {
+		haystack       LabelList
+		needle         LabelList
+		expectedResult LabelList
+	}{
+		{
+			haystack: LabelList{
+				Includes: []Label{
+					{Label: "a"},
+					{Label: "b"},
+					{Label: "c"},
+				},
+				Excludes: []Label{
+					{Label: "x"},
+					{Label: "y"},
+					{Label: "z"},
+				},
+			},
+			needle: LabelList{
+				Includes: []Label{
+					{Label: "a"},
+				},
+				Excludes: []Label{
+					{Label: "z"},
+				},
+			},
+			// NOTE: Excludes are intentionally not subtracted
+			expectedResult: LabelList{
+				Includes: []Label{
+					{Label: "b"},
+					{Label: "c"},
+				},
+				Excludes: []Label{
+					{Label: "x"},
+					{Label: "y"},
+					{Label: "z"},
+				},
+			},
+		},
+	}
+	for _, tc := range testCases {
+		actualResult := SubtractBazelLabelList(tc.haystack, tc.needle)
+		if !reflect.DeepEqual(tc.expectedResult, actualResult) {
+			t.Fatalf("Expected %v, got %v", tc.expectedResult, actualResult)
+		}
+	}
+}
 func TestUniqueBazelLabelList(t *testing.T) {
 	testCases := []struct {
 		originalLabelList       LabelList
diff --git a/bloaty/Android.bp b/bloaty/Android.bp
index b1f1e39..96cc1a5 100644
--- a/bloaty/Android.bp
+++ b/bloaty/Android.bp
@@ -1,3 +1,7 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 bootstrap_go_package {
     name: "soong-bloaty",
     pkgPath: "android/soong/bloaty",
diff --git a/bloaty/bloaty.go b/bloaty/bloaty.go
index 21bf4ac..653c489 100644
--- a/bloaty/bloaty.go
+++ b/bloaty/bloaty.go
@@ -22,7 +22,7 @@
 	"github.com/google/blueprint"
 )
 
-const bloatyDescriptorExt = "bloaty.csv"
+const bloatyDescriptorExt = ".bloaty.csv"
 const protoFilename = "binary_sizes.pb"
 
 var (
@@ -75,7 +75,7 @@
 			return
 		}
 		filePath := ctx.ModuleProvider(m, fileSizeMeasurerKey).(android.ModuleOutPath)
-		sizeFile := filePath.ReplaceExtension(ctx, bloatyDescriptorExt)
+		sizeFile := filePath.InSameDir(ctx, filePath.Base()+bloatyDescriptorExt)
 		ctx.Build(pctx, android.BuildParams{
 			Rule:        bloaty,
 			Description: "bloaty " + filePath.Rel(),
diff --git a/bootstrap_test.sh b/bootstrap_test.sh
index 6c5338a..9d87697 100755
--- a/bootstrap_test.sh
+++ b/bootstrap_test.sh
@@ -402,6 +402,14 @@
   fi
 }
 
+function test_dump_json_module_graph() {
+  setup
+  SOONG_DUMP_JSON_MODULE_GRAPH="$MOCK_TOP/modules.json" run_soong
+  if [[ ! -r "$MOCK_TOP/modules.json" ]]; then
+    fail "JSON file was not created"
+  fi
+}
+
 test_bazel_smoke
 test_smoke
 test_null_build
@@ -413,3 +421,4 @@
 test_delete_android_bp
 test_add_file_to_soong_build
 test_soong_build_rerun_iff_environment_changes
+test_dump_json_module_graph
diff --git a/bp2build/Android.bp b/bp2build/Android.bp
index c74f902..cc616f2 100644
--- a/bp2build/Android.bp
+++ b/bp2build/Android.bp
@@ -27,6 +27,7 @@
         "build_conversion_test.go",
         "bzl_conversion_test.go",
         "cc_library_headers_conversion_test.go",
+        "cc_library_static_conversion_test.go",
         "cc_object_conversion_test.go",
         "conversion_test.go",
         "python_binary_conversion_test.go",
diff --git a/bp2build/build_conversion.go b/bp2build/build_conversion.go
index 9c98c76..dd14c7d 100644
--- a/bp2build/build_conversion.go
+++ b/bp2build/build_conversion.go
@@ -370,9 +370,20 @@
 		// values for unset properties, like system_shared_libs = ["libc", "libm", "libdl"] at
 		// https://cs.android.com/android/platform/superproject/+/master:build/soong/cc/linker.go;l=281-287;drc=f70926eef0b9b57faf04c17a1062ce50d209e480
 		//
-		// In Bazel-parlance, we would use "attr.<type>(default = <default value>)" to set the default
-		// value of unset attributes.
-		return "", nil
+		// In Bazel-parlance, we would use "attr.<type>(default = <default
+		// value>)" to set the default value of unset attributes. In the cases
+		// where the bp2build converter didn't set the default value within the
+		// mutator when creating the BazelTargetModule, this would be a zero
+		// value. For those cases, we return a non-surprising default value so
+		// generated BUILD files are syntactically correct.
+		switch propertyValue.Kind() {
+		case reflect.Slice:
+			return "[]", nil
+		case reflect.Map:
+			return "{}", nil
+		default:
+			return "", nil
+		}
 	}
 
 	var ret string
@@ -404,40 +415,10 @@
 	case reflect.Struct:
 		// Special cases where the bp2build sends additional information to the codegenerator
 		// by wrapping the attributes in a custom struct type.
-		if labels, ok := propertyValue.Interface().(bazel.LabelList); ok {
-			// TODO(b/165114590): convert glob syntax
-			return prettyPrint(reflect.ValueOf(labels.Includes), indent)
+		if attr, ok := propertyValue.Interface().(bazel.Attribute); ok {
+			return prettyPrintAttribute(attr, indent)
 		} else if label, ok := propertyValue.Interface().(bazel.Label); ok {
 			return fmt.Sprintf("%q", label.Label), nil
-		} else if stringList, ok := propertyValue.Interface().(bazel.StringListAttribute); ok {
-			// A Bazel string_list attribute that may contain a select statement.
-			ret, err := prettyPrint(reflect.ValueOf(stringList.Value), indent)
-			if err != nil {
-				return ret, err
-			}
-
-			if !stringList.HasArchSpecificValues() {
-				// Select statement not needed.
-				return ret, nil
-			}
-
-			ret += " + " + "select({\n"
-			for _, arch := range android.ArchTypeList() {
-				value := stringList.GetValueForArch(arch.Name)
-				if len(value) > 0 {
-					ret += makeIndent(indent + 1)
-					list, _ := prettyPrint(reflect.ValueOf(value), indent+1)
-					ret += fmt.Sprintf("\"%s\": %s,\n", platformArchMap[arch], list)
-				}
-			}
-
-			ret += makeIndent(indent + 1)
-			list, _ := prettyPrint(reflect.ValueOf(stringList.GetValueForArch("default")), indent+1)
-			ret += fmt.Sprintf("\"%s\": %s,\n", "//conditions:default", list)
-
-			ret += makeIndent(indent)
-			ret += "})"
-			return ret, err
 		}
 
 		ret = "{\n"
@@ -533,6 +514,13 @@
 
 func escapeString(s string) string {
 	s = strings.ReplaceAll(s, "\\", "\\\\")
+
+	// b/184026959: Reverse the application of some common control sequences.
+	// These must be generated literally in the BUILD file.
+	s = strings.ReplaceAll(s, "\t", "\\t")
+	s = strings.ReplaceAll(s, "\n", "\\n")
+	s = strings.ReplaceAll(s, "\r", "\\r")
+
 	return strings.ReplaceAll(s, "\"", "\\\"")
 }
 
diff --git a/bp2build/build_conversion_test.go b/bp2build/build_conversion_test.go
index b9b250a..49897b3 100644
--- a/bp2build/build_conversion_test.go
+++ b/bp2build/build_conversion_test.go
@@ -241,6 +241,22 @@
     string_prop = "a",
 )`,
 		},
+		{
+			bp: `custom {
+	name: "control_characters",
+    string_list_prop: ["\t", "\n"],
+    string_prop: "a\t\n\r",
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTarget: `custom(
+    name = "control_characters",
+    string_list_prop = [
+        "\t",
+        "\n",
+    ],
+    string_prop = "a\t\n\r",
+)`,
+		},
 	}
 
 	dir := "."
@@ -502,8 +518,6 @@
 			expectedBazelTargets: []string{
 				`filegroup(
     name = "fg_foo",
-    srcs = [
-    ],
 )`,
 			},
 		},
@@ -1101,8 +1115,8 @@
         "out",
     ],
     srcs = [
-        "srcs-from-3",
         "in1",
+        "srcs-from-3",
     ],
 )`,
 			description: "genrule applies properties from genrule_defaults transitively",
diff --git a/bp2build/cc_library_headers_conversion_test.go b/bp2build/cc_library_headers_conversion_test.go
index 5bf5c80..74226ae 100644
--- a/bp2build/cc_library_headers_conversion_test.go
+++ b/bp2build/cc_library_headers_conversion_test.go
@@ -37,17 +37,6 @@
 	recovery_available: true,
 	native_bridge_supported: true,
 	src: "",
-}
-
-toolchain_library {
-	name: "libatomic",
-	defaults: ["linux_bionic_supported"],
-	vendor_available: true,
-	vendor_ramdisk_available: true,
-	product_available: true,
-	recovery_available: true,
-	native_bridge_supported: true,
-	src: "",
 }`
 )
 
@@ -97,34 +86,48 @@
 			moduleTypeUnderTestFactory:         cc.LibraryHeaderFactory,
 			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryHeadersBp2Build,
 			filesystem: map[string]string{
-				"lib-1/lib1a.h": "",
-				"lib-1/lib1b.h": "",
-				"lib-2/lib2a.h": "",
-				"lib-2/lib2b.h": "",
-				"dir-1/dir1a.h": "",
-				"dir-1/dir1b.h": "",
-				"dir-2/dir2a.h": "",
-				"dir-2/dir2b.h": "",
+				"lib-1/lib1a.h":                        "",
+				"lib-1/lib1b.h":                        "",
+				"lib-2/lib2a.h":                        "",
+				"lib-2/lib2b.h":                        "",
+				"dir-1/dir1a.h":                        "",
+				"dir-1/dir1b.h":                        "",
+				"dir-2/dir2a.h":                        "",
+				"dir-2/dir2b.h":                        "",
+				"arch_arm64_exported_include_dir/a.h":  "",
+				"arch_x86_exported_include_dir/b.h":    "",
+				"arch_x86_64_exported_include_dir/c.h": "",
 			},
 			bp: soongCcLibraryPreamble + `
 cc_library_headers {
     name: "lib-1",
     export_include_dirs: ["lib-1"],
-    bazel_module: { bp2build_available: true },
 }
 
 cc_library_headers {
     name: "lib-2",
     export_include_dirs: ["lib-2"],
-    bazel_module: { bp2build_available: true },
 }
 
 cc_library_headers {
     name: "foo_headers",
     export_include_dirs: ["dir-1", "dir-2"],
     header_libs: ["lib-1", "lib-2"],
-    export_header_lib_headers: ["lib-1", "lib-2"],
-    bazel_module: { bp2build_available: true },
+
+    arch: {
+        arm64: {
+	    // We expect dir-1 headers to be dropped, because dir-1 is already in export_include_dirs
+            export_include_dirs: ["arch_arm64_exported_include_dir", "dir-1"],
+        },
+        x86: {
+            export_include_dirs: ["arch_x86_exported_include_dir"],
+        },
+        x86_64: {
+            export_include_dirs: ["arch_x86_64_exported_include_dir"],
+        },
+    },
+
+    // TODO: Also support export_header_lib_headers
 }`,
 			expectedBazelTargets: []string{`cc_library_headers(
     name = "foo_headers",
@@ -137,11 +140,33 @@
         "dir-1/dir1b.h",
         "dir-2/dir2a.h",
         "dir-2/dir2b.h",
-    ],
+    ] + select({
+        "//build/bazel/platforms/arch:arm64": [
+            "arch_arm64_exported_include_dir/a.h",
+        ],
+        "//build/bazel/platforms/arch:x86": [
+            "arch_x86_exported_include_dir/b.h",
+        ],
+        "//build/bazel/platforms/arch:x86_64": [
+            "arch_x86_64_exported_include_dir/c.h",
+        ],
+        "//conditions:default": [],
+    }),
     includes = [
         "dir-1",
         "dir-2",
-    ],
+    ] + select({
+        "//build/bazel/platforms/arch:arm64": [
+            "arch_arm64_exported_include_dir",
+        ],
+        "//build/bazel/platforms/arch:x86": [
+            "arch_x86_exported_include_dir",
+        ],
+        "//build/bazel/platforms/arch:x86_64": [
+            "arch_x86_64_exported_include_dir",
+        ],
+        "//conditions:default": [],
+    }),
 )`, `cc_library_headers(
     name = "lib-1",
     hdrs = [
@@ -162,6 +187,106 @@
     ],
 )`},
 		},
+		{
+			description:                        "cc_library_headers test with os-specific header_libs props",
+			moduleTypeUnderTest:                "cc_library_headers",
+			moduleTypeUnderTestFactory:         cc.LibraryHeaderFactory,
+			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryHeadersBp2Build,
+			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+			filesystem:                         map[string]string{},
+			bp: soongCcLibraryPreamble + `
+cc_library_headers { name: "android-lib" }
+cc_library_headers { name: "base-lib" }
+cc_library_headers { name: "darwin-lib" }
+cc_library_headers { name: "fuchsia-lib" }
+cc_library_headers { name: "linux-lib" }
+cc_library_headers { name: "linux_bionic-lib" }
+cc_library_headers { name: "windows-lib" }
+cc_library_headers {
+    name: "foo_headers",
+    header_libs: ["base-lib"],
+    target: {
+        android: { header_libs: ["android-lib"] },
+        darwin: { header_libs: ["darwin-lib"] },
+        fuchsia: { header_libs: ["fuchsia-lib"] },
+        linux_bionic: { header_libs: ["linux_bionic-lib"] },
+        linux_glibc: { header_libs: ["linux-lib"] },
+        windows: { header_libs: ["windows-lib"] },
+    },
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{`cc_library_headers(
+    name = "android-lib",
+)`, `cc_library_headers(
+    name = "base-lib",
+)`, `cc_library_headers(
+    name = "darwin-lib",
+)`, `cc_library_headers(
+    name = "foo_headers",
+    deps = [
+        ":base-lib",
+    ] + select({
+        "//build/bazel/platforms/os:android": [
+            ":android-lib",
+        ],
+        "//build/bazel/platforms/os:darwin": [
+            ":darwin-lib",
+        ],
+        "//build/bazel/platforms/os:fuchsia": [
+            ":fuchsia-lib",
+        ],
+        "//build/bazel/platforms/os:linux": [
+            ":linux-lib",
+        ],
+        "//build/bazel/platforms/os:linux_bionic": [
+            ":linux_bionic-lib",
+        ],
+        "//build/bazel/platforms/os:windows": [
+            ":windows-lib",
+        ],
+        "//conditions:default": [],
+    }),
+)`, `cc_library_headers(
+    name = "fuchsia-lib",
+)`, `cc_library_headers(
+    name = "linux-lib",
+)`, `cc_library_headers(
+    name = "linux_bionic-lib",
+)`, `cc_library_headers(
+    name = "windows-lib",
+)`},
+		},
+		{
+			description:                        "cc_library_headers test with os-specific header_libs and export_header_lib_headers props",
+			moduleTypeUnderTest:                "cc_library_headers",
+			moduleTypeUnderTestFactory:         cc.LibraryHeaderFactory,
+			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryHeadersBp2Build,
+			depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+			filesystem:                         map[string]string{},
+			bp: soongCcLibraryPreamble + `
+cc_library_headers { name: "android-lib" }
+cc_library_headers { name: "exported-lib" }
+cc_library_headers {
+    name: "foo_headers",
+    target: {
+        android: { header_libs: ["android-lib"], export_header_lib_headers: ["exported-lib"] },
+    },
+}`,
+			expectedBazelTargets: []string{`cc_library_headers(
+    name = "android-lib",
+)`, `cc_library_headers(
+    name = "exported-lib",
+)`, `cc_library_headers(
+    name = "foo_headers",
+    deps = [] + select({
+        "//build/bazel/platforms/os:android": [
+            ":android-lib",
+            ":exported-lib",
+        ],
+        "//conditions:default": [],
+    }),
+)`},
+		},
 	}
 
 	dir := "."
@@ -179,6 +304,9 @@
 		config := android.TestConfig(buildDir, nil, testCase.bp, filesystem)
 		ctx := android.NewTestContext(config)
 
+		// TODO(jingwen): make this default for all bp2build tests
+		ctx.RegisterBp2BuildConfig(bp2buildConfig)
+
 		cc.RegisterCCBuildComponents(ctx)
 		ctx.RegisterModuleType("toolchain_library", cc.ToolchainLibraryFactory)
 
diff --git a/bp2build/cc_library_static_conversion_test.go b/bp2build/cc_library_static_conversion_test.go
new file mode 100644
index 0000000..467b0b2
--- /dev/null
+++ b/bp2build/cc_library_static_conversion_test.go
@@ -0,0 +1,296 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package bp2build
+
+import (
+	"android/soong/android"
+	"android/soong/cc"
+	"strings"
+	"testing"
+)
+
+const (
+	// See cc/testing.go for more context
+	soongCcLibraryStaticPreamble = `
+cc_defaults {
+	name: "linux_bionic_supported",
+}
+
+toolchain_library {
+	name: "libclang_rt.builtins-x86_64-android",
+	defaults: ["linux_bionic_supported"],
+	vendor_available: true,
+	vendor_ramdisk_available: true,
+	product_available: true,
+	recovery_available: true,
+	native_bridge_supported: true,
+	src: "",
+}`
+)
+
+func TestCcLibraryStaticLoadStatement(t *testing.T) {
+	testCases := []struct {
+		bazelTargets           BazelTargets
+		expectedLoadStatements string
+	}{
+		{
+			bazelTargets: BazelTargets{
+				BazelTarget{
+					name:      "cc_library_static_target",
+					ruleClass: "cc_library_static",
+					// NOTE: No bzlLoadLocation for native rules
+				},
+			},
+			expectedLoadStatements: ``,
+		},
+	}
+
+	for _, testCase := range testCases {
+		actual := testCase.bazelTargets.LoadStatements()
+		expected := testCase.expectedLoadStatements
+		if actual != expected {
+			t.Fatalf("Expected load statements to be %s, got %s", expected, actual)
+		}
+	}
+
+}
+
+func TestCcLibraryStaticBp2Build(t *testing.T) {
+	testCases := []struct {
+		description                        string
+		moduleTypeUnderTest                string
+		moduleTypeUnderTestFactory         android.ModuleFactory
+		moduleTypeUnderTestBp2BuildMutator func(android.TopDownMutatorContext)
+		preArchMutators                    []android.RegisterMutatorFunc
+		depsMutators                       []android.RegisterMutatorFunc
+		bp                                 string
+		expectedBazelTargets               []string
+		filesystem                         map[string]string
+		dir                                string
+	}{
+		{
+			description:                        "cc_library_static test",
+			moduleTypeUnderTest:                "cc_library_static",
+			moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+			moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+			filesystem: map[string]string{
+				// NOTE: include_dir headers *should not* appear in Bazel hdrs later (?)
+				"include_dir_1/include_dir_1_a.h": "",
+				"include_dir_1/include_dir_1_b.h": "",
+				"include_dir_2/include_dir_2_a.h": "",
+				"include_dir_2/include_dir_2_b.h": "",
+				// NOTE: local_include_dir headers *should not* appear in Bazel hdrs later (?)
+				"local_include_dir_1/local_include_dir_1_a.h": "",
+				"local_include_dir_1/local_include_dir_1_b.h": "",
+				"local_include_dir_2/local_include_dir_2_a.h": "",
+				"local_include_dir_2/local_include_dir_2_b.h": "",
+				// NOTE: export_include_dir headers *should* appear in Bazel hdrs later
+				"export_include_dir_1/export_include_dir_1_a.h": "",
+				"export_include_dir_1/export_include_dir_1_b.h": "",
+				"export_include_dir_2/export_include_dir_2_a.h": "",
+				"export_include_dir_2/export_include_dir_2_b.h": "",
+			},
+			bp: soongCcLibraryStaticPreamble + `
+cc_library_headers {
+    name: "header_lib_1",
+    export_include_dirs: ["header_lib_1"],
+}
+
+cc_library_headers {
+    name: "header_lib_2",
+    export_include_dirs: ["header_lib_2"],
+}
+
+cc_library_static {
+    name: "static_lib_1",
+    srcs: ["static_lib_1.cc"],
+    bazel_module: { bp2build_available: true },
+}
+
+cc_library_static {
+    name: "static_lib_2",
+    srcs: ["static_lib_2.cc"],
+    bazel_module: { bp2build_available: true },
+}
+
+cc_library_static {
+    name: "whole_static_lib_1",
+    srcs: ["whole_static_lib_1.cc"],
+    bazel_module: { bp2build_available: true },
+}
+
+cc_library_static {
+    name: "whole_static_lib_2",
+    srcs: ["whole_static_lib_2.cc"],
+    bazel_module: { bp2build_available: true },
+}
+
+cc_library_static {
+    name: "foo_static",
+    srcs: [
+        "foo_static1.cc",
+	"foo_static2.cc",
+    ],
+    cflags: [
+        "-Dflag1",
+	"-Dflag2"
+    ],
+    static_libs: [
+        "static_lib_1",
+	"static_lib_2"
+    ],
+    whole_static_libs: [
+        "whole_static_lib_1",
+	"whole_static_lib_2"
+    ],
+    include_dirs: [
+	"include_dir_1",
+	"include_dir_2",
+    ],
+    local_include_dirs: [
+        "local_include_dir_1",
+	"local_include_dir_2",
+    ],
+    export_include_dirs: [
+	"export_include_dir_1",
+	"export_include_dir_2"
+    ],
+    header_libs: [
+        "header_lib_1",
+	"header_lib_2"
+    ],
+
+    // TODO: Also support export_header_lib_headers
+
+    bazel_module: { bp2build_available: true },
+}`,
+			expectedBazelTargets: []string{`cc_library_static(
+    name = "foo_static",
+    copts = [
+        "-Dflag1",
+        "-Dflag2",
+    ],
+    deps = [
+        ":header_lib_1",
+        ":header_lib_2",
+        ":static_lib_1",
+        ":static_lib_2",
+        ":whole_static_lib_1",
+        ":whole_static_lib_2",
+    ],
+    hdrs = [
+        "export_include_dir_1/export_include_dir_1_a.h",
+        "export_include_dir_1/export_include_dir_1_b.h",
+        "export_include_dir_2/export_include_dir_2_a.h",
+        "export_include_dir_2/export_include_dir_2_b.h",
+    ],
+    includes = [
+        "export_include_dir_1",
+        "export_include_dir_2",
+        "include_dir_1",
+        "include_dir_2",
+        "local_include_dir_1",
+        "local_include_dir_2",
+    ],
+    linkstatic = True,
+    srcs = [
+        "foo_static1.cc",
+        "foo_static2.cc",
+    ],
+)`, `cc_library_static(
+    name = "static_lib_1",
+    linkstatic = True,
+    srcs = [
+        "static_lib_1.cc",
+    ],
+)`, `cc_library_static(
+    name = "static_lib_2",
+    linkstatic = True,
+    srcs = [
+        "static_lib_2.cc",
+    ],
+)`, `cc_library_static(
+    name = "whole_static_lib_1",
+    linkstatic = True,
+    srcs = [
+        "whole_static_lib_1.cc",
+    ],
+)`, `cc_library_static(
+    name = "whole_static_lib_2",
+    linkstatic = True,
+    srcs = [
+        "whole_static_lib_2.cc",
+    ],
+)`},
+		},
+	}
+
+	dir := "."
+	for _, testCase := range testCases {
+		filesystem := make(map[string][]byte)
+		toParse := []string{
+			"Android.bp",
+		}
+		for f, content := range testCase.filesystem {
+			if strings.HasSuffix(f, "Android.bp") {
+				toParse = append(toParse, f)
+			}
+			filesystem[f] = []byte(content)
+		}
+		config := android.TestConfig(buildDir, nil, testCase.bp, filesystem)
+		ctx := android.NewTestContext(config)
+
+		cc.RegisterCCBuildComponents(ctx)
+		ctx.RegisterModuleType("toolchain_library", cc.ToolchainLibraryFactory)
+		ctx.RegisterModuleType("cc_library_headers", cc.LibraryHeaderFactory)
+
+		ctx.RegisterModuleType(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestFactory)
+		for _, m := range testCase.depsMutators {
+			ctx.DepsBp2BuildMutators(m)
+		}
+		ctx.RegisterBp2BuildMutator(testCase.moduleTypeUnderTest, testCase.moduleTypeUnderTestBp2BuildMutator)
+		ctx.RegisterForBazelConversion()
+
+		_, errs := ctx.ParseFileList(dir, toParse)
+		if Errored(t, testCase.description, errs) {
+			continue
+		}
+		_, errs = ctx.ResolveDependencies(config)
+		if Errored(t, testCase.description, errs) {
+			continue
+		}
+
+		checkDir := dir
+		if testCase.dir != "" {
+			checkDir = testCase.dir
+		}
+		codegenCtx := NewCodegenContext(config, *ctx.Context, Bp2Build)
+		bazelTargets := generateBazelTargetsForDir(codegenCtx, checkDir)
+		if actualCount, expectedCount := len(bazelTargets), len(testCase.expectedBazelTargets); actualCount != expectedCount {
+			t.Errorf("%s: Expected %d bazel target, got %d", testCase.description, expectedCount, actualCount)
+		} else {
+			for i, target := range bazelTargets {
+				if w, g := testCase.expectedBazelTargets[i], target.content; w != g {
+					t.Errorf(
+						"%s: Expected generated Bazel target to be '%s', got '%s'",
+						testCase.description,
+						w,
+						g,
+					)
+				}
+			}
+		}
+	}
+}
diff --git a/bp2build/cc_object_conversion_test.go b/bp2build/cc_object_conversion_test.go
index 1c058ba..4f3babe 100644
--- a/bp2build/cc_object_conversion_test.go
+++ b/bp2build/cc_object_conversion_test.go
@@ -74,8 +74,8 @@
     ],
     srcs = [
         "a/b/bar.h",
-        "a/b/foo.h",
         "a/b/c.c",
+        "a/b/foo.h",
     ],
 )`,
 			},
@@ -209,6 +209,34 @@
 )`,
 			},
 		},
+		{
+			description:                        "cc_object with product variable",
+			moduleTypeUnderTest:                "cc_object",
+			moduleTypeUnderTestFactory:         cc.ObjectFactory,
+			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
+			blueprint: `cc_object {
+    name: "foo",
+    include_build_directory: false,
+    product_variables: {
+        platform_sdk_version: {
+            asflags: ["-DPLATFORM_SDK_VERSION=%d"],
+        },
+    },
+
+    bazel_module: { bp2build_available: true },
+}
+`,
+			expectedBazelTargets: []string{`cc_object(
+    name = "foo",
+    asflags = [
+        "-DPLATFORM_SDK_VERSION={Platform_sdk_version}",
+    ],
+    copts = [
+        "-fno-addrsig",
+    ],
+)`,
+			},
+		},
 	}
 
 	dir := "."
@@ -278,9 +306,13 @@
 			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
 			blueprint: `cc_object {
     name: "foo",
+    srcs: ["a.cpp"],
     arch: {
         x86: {
-            cflags: ["-fPIC"],
+            cflags: ["-fPIC"], // string list
+        },
+        arm: {
+            srcs: ["arch/arm/file.S"], // label list
         },
     },
     bazel_module: { bp2build_available: true },
@@ -292,15 +324,22 @@
     copts = [
         "-fno-addrsig",
     ] + select({
-        "@bazel_tools//platforms:x86_32": [
+        "//build/bazel/platforms/arch:x86": [
             "-fPIC",
         ],
-        "//conditions:default": [
-        ],
+        "//conditions:default": [],
     }),
     local_include_dirs = [
         ".",
     ],
+    srcs = [
+        "a.cpp",
+    ] + select({
+        "//build/bazel/platforms/arch:arm": [
+            "arch/arm/file.S",
+        ],
+        "//conditions:default": [],
+    }),
 )`,
 			},
 		},
@@ -311,17 +350,22 @@
 			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
 			blueprint: `cc_object {
     name: "foo",
+    srcs: ["base.cpp"],
     arch: {
         x86: {
+            srcs: ["x86.cpp"],
             cflags: ["-fPIC"],
         },
         x86_64: {
+            srcs: ["x86_64.cpp"],
             cflags: ["-fPIC"],
         },
         arm: {
+            srcs: ["arm.cpp"],
             cflags: ["-Wall"],
         },
         arm64: {
+            srcs: ["arm64.cpp"],
             cflags: ["-Wall"],
         },
     },
@@ -334,24 +378,88 @@
     copts = [
         "-fno-addrsig",
     ] + select({
-        "@bazel_tools//platforms:arm": [
+        "//build/bazel/platforms/arch:arm": [
             "-Wall",
         ],
-        "@bazel_tools//platforms:aarch64": [
+        "//build/bazel/platforms/arch:arm64": [
             "-Wall",
         ],
-        "@bazel_tools//platforms:x86_32": [
+        "//build/bazel/platforms/arch:x86": [
             "-fPIC",
         ],
-        "@bazel_tools//platforms:x86_64": [
+        "//build/bazel/platforms/arch:x86_64": [
             "-fPIC",
         ],
-        "//conditions:default": [
-        ],
+        "//conditions:default": [],
     }),
     local_include_dirs = [
         ".",
     ],
+    srcs = [
+        "base.cpp",
+    ] + select({
+        "//build/bazel/platforms/arch:arm": [
+            "arm.cpp",
+        ],
+        "//build/bazel/platforms/arch:arm64": [
+            "arm64.cpp",
+        ],
+        "//build/bazel/platforms/arch:x86": [
+            "x86.cpp",
+        ],
+        "//build/bazel/platforms/arch:x86_64": [
+            "x86_64.cpp",
+        ],
+        "//conditions:default": [],
+    }),
+)`,
+			},
+		},
+		{
+			description:                        "cc_object setting cflags for multiple OSes",
+			moduleTypeUnderTest:                "cc_object",
+			moduleTypeUnderTestFactory:         cc.ObjectFactory,
+			moduleTypeUnderTestBp2BuildMutator: cc.ObjectBp2Build,
+			blueprint: `cc_object {
+    name: "foo",
+    srcs: ["base.cpp"],
+    target: {
+        android: {
+            cflags: ["-fPIC"],
+        },
+        windows: {
+            cflags: ["-fPIC"],
+        },
+        darwin: {
+            cflags: ["-Wall"],
+        },
+    },
+    bazel_module: { bp2build_available: true },
+}
+`,
+			expectedBazelTargets: []string{
+				`cc_object(
+    name = "foo",
+    copts = [
+        "-fno-addrsig",
+    ] + select({
+        "//build/bazel/platforms/os:android": [
+            "-fPIC",
+        ],
+        "//build/bazel/platforms/os:darwin": [
+            "-Wall",
+        ],
+        "//build/bazel/platforms/os:windows": [
+            "-fPIC",
+        ],
+        "//conditions:default": [],
+    }),
+    local_include_dirs = [
+        ".",
+    ],
+    srcs = [
+        "base.cpp",
+    ],
 )`,
 			},
 		},
diff --git a/bp2build/configurability.go b/bp2build/configurability.go
index 47cf3c6..b2b3379 100644
--- a/bp2build/configurability.go
+++ b/bp2build/configurability.go
@@ -1,15 +1,136 @@
 package bp2build
 
-import "android/soong/android"
+import (
+	"android/soong/android"
+	"android/soong/bazel"
+	"fmt"
+	"reflect"
+)
 
 // Configurability support for bp2build.
 
-var (
-	// A map of architectures to the Bazel label of the constraint_value.
-	platformArchMap = map[android.ArchType]string{
-		android.Arm:    "@bazel_tools//platforms:arm",
-		android.Arm64:  "@bazel_tools//platforms:aarch64",
-		android.X86:    "@bazel_tools//platforms:x86_32",
-		android.X86_64: "@bazel_tools//platforms:x86_64",
+type selects map[string]reflect.Value
+
+func getStringListValues(list bazel.StringListAttribute) (reflect.Value, selects, selects) {
+	value := reflect.ValueOf(list.Value)
+	if !list.HasConfigurableValues() {
+		return value, nil, nil
 	}
-)
+
+	archSelects := map[string]reflect.Value{}
+	for arch, selectKey := range bazel.PlatformArchMap {
+		archSelects[selectKey] = reflect.ValueOf(list.GetValueForArch(arch))
+	}
+
+	osSelects := map[string]reflect.Value{}
+	for os, selectKey := range bazel.PlatformOsMap {
+		osSelects[selectKey] = reflect.ValueOf(list.GetValueForOS(os))
+	}
+
+	return value, archSelects, osSelects
+}
+
+func getLabelListValues(list bazel.LabelListAttribute) (reflect.Value, selects, selects) {
+	value := reflect.ValueOf(list.Value.Includes)
+	if !list.HasConfigurableValues() {
+		return value, nil, nil
+	}
+
+	archSelects := map[string]reflect.Value{}
+	for arch, selectKey := range bazel.PlatformArchMap {
+		archSelects[selectKey] = reflect.ValueOf(list.GetValueForArch(arch).Includes)
+	}
+
+	osSelects := map[string]reflect.Value{}
+	for os, selectKey := range bazel.PlatformOsMap {
+		osSelects[selectKey] = reflect.ValueOf(list.GetValueForOS(os).Includes)
+	}
+
+	return value, archSelects, osSelects
+}
+
+// prettyPrintAttribute converts an Attribute to its Bazel syntax. May contain
+// select statements.
+func prettyPrintAttribute(v bazel.Attribute, indent int) (string, error) {
+	var value reflect.Value
+	var archSelects, osSelects selects
+
+	switch list := v.(type) {
+	case bazel.StringListAttribute:
+		value, archSelects, osSelects = getStringListValues(list)
+	case bazel.LabelListAttribute:
+		value, archSelects, osSelects = getLabelListValues(list)
+	default:
+		return "", fmt.Errorf("Not a supported Bazel attribute type: %s", v)
+	}
+
+	ret, err := prettyPrint(value, indent)
+	if err != nil {
+		return ret, err
+	}
+
+	// Create the selects for arch specific values.
+	selectMap, err := prettyPrintSelectMap(archSelects, "[]", indent)
+	if err != nil {
+		return "", err
+	}
+	ret += selectMap
+
+	// Create the selects for target os specific values.
+	selectMap, err = prettyPrintSelectMap(osSelects, "[]", indent)
+	if err != nil {
+		return "", err
+	}
+	ret += selectMap
+
+	return ret, err
+}
+
+// prettyPrintSelectMap converts a map of select keys to reflected Values as a generic way
+// to construct a select map for any kind of attribute type.
+func prettyPrintSelectMap(selectMap map[string]reflect.Value, defaultValue string, indent int) (string, error) {
+	if selectMap == nil {
+		return "", nil
+	}
+
+	var selects string
+	for _, selectKey := range android.SortedStringKeys(selectMap) {
+		value := selectMap[selectKey]
+		if isZero(value) {
+			// Ignore zero values to not generate empty lists.
+			continue
+		}
+		s, err := prettyPrintSelectEntry(value, selectKey, indent)
+		if err != nil {
+			return "", err
+		}
+		selects += s + ",\n"
+	}
+
+	if len(selects) == 0 {
+		// No conditions (or all values are empty lists), so no need for a map.
+		return "", nil
+	}
+
+	// Create the map.
+	ret := " + select({\n"
+	ret += selects
+	// default condition comes last.
+	ret += fmt.Sprintf("%s\"%s\": %s,\n", makeIndent(indent+1), "//conditions:default", defaultValue)
+	ret += makeIndent(indent)
+	ret += "})"
+
+	return ret, nil
+}
+
+// prettyPrintSelectEntry converts a reflect.Value into an entry in a select map
+// with a provided key.
+func prettyPrintSelectEntry(value reflect.Value, key string, indent int) (string, error) {
+	s := makeIndent(indent + 1)
+	v, err := prettyPrint(value, indent+1)
+	if err != nil {
+		return "", err
+	}
+	s += fmt.Sprintf("\"%s\": %s", key, v)
+	return s, nil
+}
diff --git a/bp2build/constants.go b/bp2build/constants.go
index 23bca83..70f320e 100644
--- a/bp2build/constants.go
+++ b/bp2build/constants.go
@@ -18,7 +18,7 @@
 	// When both a BUILD and BUILD.bazel file are exist in the same package, the BUILD.bazel file will
 	// be preferred for use within a Bazel build.
 
-	// The file name used for automatically generated files. Files with this name are ignored by git.
+	// The file name used for automatically generated files.
 	GeneratedBuildFileName = "BUILD"
 	// The file name used for hand-crafted build targets.
 	HandcraftedBuildFileName = "BUILD.bazel"
diff --git a/bp2build/conversion.go b/bp2build/conversion.go
index 787222d..6b47cd1 100644
--- a/bp2build/conversion.go
+++ b/bp2build/conversion.go
@@ -19,12 +19,13 @@
 	ruleShims map[string]RuleShim,
 	buildToTargets map[string]BazelTargets,
 	mode CodegenMode) []BazelFile {
-	files := make([]BazelFile, 0, len(ruleShims)+len(buildToTargets)+numAdditionalFiles)
 
-	// Write top level files: WORKSPACE. These files are empty.
-	files = append(files, newFile("", "WORKSPACE", ""))
+	var files []BazelFile
 
 	if mode == QueryView {
+		// Write top level WORKSPACE.
+		files = append(files, newFile("", "WORKSPACE", ""))
+
 		// Used to denote that the top level directory is a package.
 		files = append(files, newFile("", GeneratedBuildFileName, ""))
 
diff --git a/bp2build/conversion_test.go b/bp2build/conversion_test.go
index a115ddc..9fd6817 100644
--- a/bp2build/conversion_test.go
+++ b/bp2build/conversion_test.go
@@ -24,36 +24,6 @@
 	basename string
 }
 
-func assertFilecountsAreEqual(t *testing.T, actual []BazelFile, expected []filepath) {
-	if a, e := len(actual), len(expected); a != e {
-		t.Errorf("Expected %d files, got %d", e, a)
-	}
-}
-
-func assertFileContent(t *testing.T, actual []BazelFile, expected []filepath) {
-	for i := range actual {
-		if g, w := actual[i], expected[i]; g.Dir != w.dir || g.Basename != w.basename {
-			t.Errorf("Did not find expected file %s/%s", g.Dir, g.Basename)
-		} else if g.Basename == "BUILD" || g.Basename == "WORKSPACE" {
-			if g.Contents != "" {
-				t.Errorf("Expected %s to have no content.", g)
-			}
-		} else if g.Contents == "" {
-			t.Errorf("Contents of %s unexpected empty.", g)
-		}
-	}
-}
-
-func sortFiles(files []BazelFile) {
-	sort.Slice(files, func(i, j int) bool {
-		if dir1, dir2 := files[i].Dir, files[j].Dir; dir1 == dir2 {
-			return files[i].Basename < files[j].Basename
-		} else {
-			return dir1 < dir2
-		}
-	})
-}
-
 func TestCreateBazelFiles_QueryView_AddsTopLevelFiles(t *testing.T) {
 	files := CreateBazelFiles(map[string]RuleShim{}, map[string]BazelTargets{}, QueryView)
 	expectedFilePaths := []filepath{
@@ -79,21 +49,39 @@
 		},
 	}
 
-	assertFilecountsAreEqual(t, files, expectedFilePaths)
-	sortFiles(files)
-	assertFileContent(t, files, expectedFilePaths)
-}
-
-func TestCreateBazelFiles_Bp2Build_AddsTopLevelFiles(t *testing.T) {
-	files := CreateBazelFiles(map[string]RuleShim{}, map[string]BazelTargets{}, Bp2Build)
-	expectedFilePaths := []filepath{
-		{
-			dir:      "",
-			basename: "WORKSPACE",
-		},
+	// Compare number of files
+	if a, e := len(files), len(expectedFilePaths); a != e {
+		t.Errorf("Expected %d files, got %d", e, a)
 	}
 
-	assertFilecountsAreEqual(t, files, expectedFilePaths)
-	sortFiles(files)
-	assertFileContent(t, files, expectedFilePaths)
+	// Sort the files to be deterministic
+	sort.Slice(files, func(i, j int) bool {
+		if dir1, dir2 := files[i].Dir, files[j].Dir; dir1 == dir2 {
+			return files[i].Basename < files[j].Basename
+		} else {
+			return dir1 < dir2
+		}
+	})
+
+	// Compare the file contents
+	for i := range files {
+		actualFile, expectedFile := files[i], expectedFilePaths[i]
+
+		if actualFile.Dir != expectedFile.dir || actualFile.Basename != expectedFile.basename {
+			t.Errorf("Did not find expected file %s/%s", actualFile.Dir, actualFile.Basename)
+		} else if actualFile.Basename == "BUILD" || actualFile.Basename == "WORKSPACE" {
+			if actualFile.Contents != "" {
+				t.Errorf("Expected %s to have no content.", actualFile)
+			}
+		} else if actualFile.Contents == "" {
+			t.Errorf("Contents of %s unexpected empty.", actualFile)
+		}
+	}
+}
+
+func TestCreateBazelFiles_Bp2Build_CreatesNoFilesWithNoTargets(t *testing.T) {
+	files := CreateBazelFiles(map[string]RuleShim{}, map[string]BazelTargets{}, Bp2Build)
+	if len(files) != 0 {
+		t.Errorf("Expected no files, got %d", len(files))
+	}
 }
diff --git a/bp2build/testing.go b/bp2build/testing.go
index ede8044..ef3a78f 100644
--- a/bp2build/testing.go
+++ b/bp2build/testing.go
@@ -5,6 +5,13 @@
 	"android/soong/bazel"
 )
 
+var (
+	// A default configuration for tests to not have to specify bp2build_available on top level targets.
+	bp2buildConfig = android.Bp2BuildConfig{
+		android.BP2BUILD_TOPLEVEL: android.Bp2BuildDefaultTrueRecursively,
+	}
+)
+
 type nestedProps struct {
 	Nested_prop string
 }
diff --git a/cc/Android.bp b/cc/Android.bp
index bdbb3c0..cc4d9bc 100644
--- a/cc/Android.bp
+++ b/cc/Android.bp
@@ -10,6 +10,7 @@
         "blueprint-pathtools",
         "soong",
         "soong-android",
+        "soong-bazel",
         "soong-cc-config",
         "soong-etc",
         "soong-genrule",
@@ -19,6 +20,7 @@
         "androidmk.go",
         "api_level.go",
         "builder.go",
+        "bp2build.go",
         "cc.go",
         "ccdeps.go",
         "check.go",
diff --git a/cc/bp2build.go b/cc/bp2build.go
new file mode 100644
index 0000000..cffeb24
--- /dev/null
+++ b/cc/bp2build.go
@@ -0,0 +1,187 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package cc
+
+import (
+	"android/soong/android"
+	"android/soong/bazel"
+	"strings"
+)
+
+// bp2build functions and helpers for converting cc_* modules to Bazel.
+
+func init() {
+	android.DepsBp2BuildMutators(RegisterDepsBp2Build)
+}
+
+func RegisterDepsBp2Build(ctx android.RegisterMutatorsContext) {
+	ctx.BottomUp("cc_bp2build_deps", depsBp2BuildMutator)
+}
+
+// A naive deps mutator to add deps on all modules across all combinations of
+// target props for cc modules. This is needed to make module -> bazel label
+// resolution work in the bp2build mutator later. This is probably
+// the wrong way to do it, but it works.
+//
+// TODO(jingwen): can we create a custom os mutator in depsBp2BuildMutator to do this?
+func depsBp2BuildMutator(ctx android.BottomUpMutatorContext) {
+	module, ok := ctx.Module().(*Module)
+	if !ok {
+		// Not a cc module
+		return
+	}
+
+	if !module.ConvertWithBp2build(ctx) {
+		return
+	}
+
+	var allDeps []string
+
+	for _, p := range module.GetTargetProperties(&BaseLinkerProperties{}) {
+		// arch specific linker props
+		if baseLinkerProps, ok := p.(*BaseLinkerProperties); ok {
+			allDeps = append(allDeps, baseLinkerProps.Header_libs...)
+			allDeps = append(allDeps, baseLinkerProps.Export_header_lib_headers...)
+		}
+	}
+
+	ctx.AddDependency(module, nil, android.SortedUniqueStrings(allDeps)...)
+}
+
+// bp2buildParseCflags creates a label list attribute containing the cflags of a module, including
+func bp2BuildParseCflags(ctx android.TopDownMutatorContext, module *Module) bazel.StringListAttribute {
+	var ret bazel.StringListAttribute
+	for _, props := range module.compiler.compilerProps() {
+		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
+			ret.Value = baseCompilerProps.Cflags
+			break
+		}
+	}
+
+	for arch, props := range module.GetArchProperties(&BaseCompilerProperties{}) {
+		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
+			ret.SetValueForArch(arch.Name, baseCompilerProps.Cflags)
+		}
+	}
+
+	for os, props := range module.GetTargetProperties(&BaseCompilerProperties{}) {
+		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
+			ret.SetValueForOS(os.Name, baseCompilerProps.Cflags)
+		}
+	}
+
+	return ret
+}
+
+// bp2BuildParseHeaderLibs creates a label list attribute containing the header library deps of a module, including
+// configurable attribute values.
+func bp2BuildParseHeaderLibs(ctx android.TopDownMutatorContext, module *Module) bazel.LabelListAttribute {
+	var ret bazel.LabelListAttribute
+	for _, linkerProps := range module.linker.linkerProps() {
+		if baseLinkerProps, ok := linkerProps.(*BaseLinkerProperties); ok {
+			libs := baseLinkerProps.Header_libs
+			libs = append(libs, baseLinkerProps.Export_header_lib_headers...)
+			ret = bazel.MakeLabelListAttribute(
+				android.BazelLabelForModuleDeps(ctx, android.SortedUniqueStrings(libs)))
+			break
+		}
+	}
+
+	for os, p := range module.GetTargetProperties(&BaseLinkerProperties{}) {
+		if baseLinkerProps, ok := p.(*BaseLinkerProperties); ok {
+			libs := baseLinkerProps.Header_libs
+			libs = append(libs, baseLinkerProps.Export_header_lib_headers...)
+			libs = android.SortedUniqueStrings(libs)
+			ret.SetValueForOS(os.Name, android.BazelLabelForModuleDeps(ctx, libs))
+		}
+	}
+
+	return ret
+}
+
+func bp2BuildListHeadersInDir(ctx android.TopDownMutatorContext, includeDir string) bazel.LabelList {
+	var globInfix string
+
+	if includeDir == "." {
+		globInfix = ""
+	} else {
+		globInfix = "/**"
+	}
+
+	var includeDirGlobs []string
+	includeDirGlobs = append(includeDirGlobs, includeDir+globInfix+"/*.h")
+	includeDirGlobs = append(includeDirGlobs, includeDir+globInfix+"/*.inc")
+	includeDirGlobs = append(includeDirGlobs, includeDir+globInfix+"/*.hpp")
+
+	return android.BazelLabelForModuleSrc(ctx, includeDirGlobs)
+}
+
+// Bazel wants include paths to be relative to the module
+func bp2BuildMakePathsRelativeToModule(ctx android.TopDownMutatorContext, paths []string) []string {
+	var relativePaths []string
+	for _, path := range paths {
+		relativePath := strings.TrimPrefix(path, ctx.ModuleDir()+"/")
+		relativePaths = append(relativePaths, relativePath)
+	}
+	return relativePaths
+}
+
+// bp2BuildParseExportedIncludes creates a label list attribute contains the
+// exported included directories of a module.
+func bp2BuildParseExportedIncludes(ctx android.TopDownMutatorContext, module *Module) (bazel.StringListAttribute, bazel.LabelListAttribute) {
+	libraryDecorator := module.linker.(*libraryDecorator)
+
+	includeDirs := libraryDecorator.flagExporter.Properties.Export_system_include_dirs
+	includeDirs = append(includeDirs, libraryDecorator.flagExporter.Properties.Export_include_dirs...)
+	includeDirs = bp2BuildMakePathsRelativeToModule(ctx, includeDirs)
+	includeDirsAttribute := bazel.MakeStringListAttribute(includeDirs)
+
+	var headersAttribute bazel.LabelListAttribute
+	var headers bazel.LabelList
+	for _, includeDir := range includeDirs {
+		headers.Append(bp2BuildListHeadersInDir(ctx, includeDir))
+	}
+	headers = bazel.UniqueBazelLabelList(headers)
+	headersAttribute.Value = headers
+
+	for arch, props := range module.GetArchProperties(&FlagExporterProperties{}) {
+		if flagExporterProperties, ok := props.(*FlagExporterProperties); ok {
+			archIncludeDirs := flagExporterProperties.Export_system_include_dirs
+			archIncludeDirs = append(archIncludeDirs, flagExporterProperties.Export_include_dirs...)
+			archIncludeDirs = bp2BuildMakePathsRelativeToModule(ctx, archIncludeDirs)
+
+			// To avoid duplicate includes when base includes + arch includes are combined
+			archIncludeDirs = bazel.SubtractStrings(archIncludeDirs, includeDirs)
+
+			if len(archIncludeDirs) > 0 {
+				includeDirsAttribute.SetValueForArch(arch.Name, archIncludeDirs)
+			}
+
+			var archHeaders bazel.LabelList
+			for _, archIncludeDir := range archIncludeDirs {
+				archHeaders.Append(bp2BuildListHeadersInDir(ctx, archIncludeDir))
+			}
+			archHeaders = bazel.UniqueBazelLabelList(archHeaders)
+
+			// To avoid duplicate headers when base headers + arch headers are combined
+			archHeaders = bazel.SubtractBazelLabelList(archHeaders, headers)
+
+			if len(archHeaders.Includes) > 0 || len(archHeaders.Excludes) > 0 {
+				headersAttribute.SetValueForArch(arch.Name, archHeaders)
+			}
+		}
+	}
+
+	return includeDirsAttribute, headersAttribute
+}
diff --git a/cc/builder.go b/cc/builder.go
index 273cdd3..8c9743f 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -73,7 +73,7 @@
 			Labels:          map[string]string{"type": "link", "tool": "clang"},
 			ExecStrategy:    "${config.RECXXLinksExecStrategy}",
 			Inputs:          []string{"${out}.rsp", "$implicitInputs"},
-			RSPFile:         "${out}.rsp",
+			RSPFiles:        []string{"${out}.rsp"},
 			OutputFiles:     []string{"${out}", "$implicitOutputs"},
 			ToolchainInputs: []string{"$ldCmd"},
 			Platform:        map[string]string{remoteexec.PoolKey: "${config.RECXXLinksPool}"},
@@ -182,11 +182,11 @@
 		blueprint.RuleParams{
 			Depfile:     "${out}.d",
 			Deps:        blueprint.DepsGCC,
-			Command:     "CROSS_COMPILE=$crossCompile $tocPath $format -i ${in} -o ${out} -d ${out}.d",
+			Command:     "CLANG_BIN=$clangBin $tocPath $format -i ${in} -o ${out} -d ${out}.d",
 			CommandDeps: []string{"$tocPath"},
 			Restat:      true,
 		},
-		"crossCompile", "format")
+		"clangBin", "format")
 
 	// Rule for invoking clang-tidy (a clang-based linter).
 	clangTidy, clangTidyRE = pctx.RemoteStaticRules("clangTidy",
@@ -256,7 +256,7 @@
 			Labels:          map[string]string{"type": "tool", "name": "abi-linker"},
 			ExecStrategy:    "${config.REAbiLinkerExecStrategy}",
 			Inputs:          []string{"$sAbiLinkerLibs", "${out}.rsp", "$implicitInputs"},
-			RSPFile:         "${out}.rsp",
+			RSPFiles:        []string{"${out}.rsp"},
 			OutputFiles:     []string{"$out"},
 			ToolchainInputs: []string{"$sAbiLinker"},
 			Platform:        map[string]string{remoteexec.PoolKey: "${config.RECXXPool}"},
@@ -918,16 +918,12 @@
 	outputFile android.WritablePath, flags builderFlags) {
 
 	var format string
-	var crossCompile string
 	if ctx.Darwin() {
 		format = "--macho"
-		crossCompile = "${config.MacToolPath}"
 	} else if ctx.Windows() {
 		format = "--pe"
-		crossCompile = gccCmd(flags.toolchain, "")
 	} else {
 		format = "--elf"
-		crossCompile = gccCmd(flags.toolchain, "")
 	}
 
 	ctx.Build(pctx, android.BuildParams{
@@ -936,8 +932,8 @@
 		Output:      outputFile,
 		Input:       inputFile,
 		Args: map[string]string{
-			"crossCompile": crossCompile,
-			"format":       format,
+			"clangBin": "${config.ClangBin}",
+			"format":   format,
 		},
 	})
 }
diff --git a/cc/cc.go b/cc/cc.go
index cab7459..1ce83a9 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -363,7 +363,7 @@
 	// List of APEXes that this module has private access to for testing purpose. The module
 	// can depend on libraries that are not exported by the APEXes and use private symbols
 	// from the exported libraries.
-	Test_for []string
+	Test_for []string `android:"arch_variant"`
 }
 
 type VendorProperties struct {
@@ -1363,6 +1363,19 @@
 	if ver == "apex_inherit" || ver == "" {
 		ver = ctx.sdkVersion()
 	}
+	// For crt objects, the meaning of min_sdk_version is very different from other types of
+	// module. For them, min_sdk_version defines the oldest version that the build system will
+	// create versioned variants for. For example, if min_sdk_version is 16, then sdk variant of
+	// the crt object has local variants of 16, 17, ..., up to the latest version. sdk_version
+	// and min_sdk_version properties of the variants are set to the corresponding version
+	// numbers. However, the platform (non-sdk) variant of the crt object is left untouched.
+	// min_sdk_version: 16 doesn't actually mean that the platform variant has to support such
+	// an old version. Since the variant is for the platform, it's preferred to target the
+	// latest version.
+	if ctx.mod.SplitPerApiLevel() && !ctx.isSdkVariant() {
+		ver = strconv.Itoa(android.FutureApiLevelInt)
+	}
+
 	// Also make sure that minSdkVersion is not greater than sdkVersion, if they are both numbers
 	sdkVersionInt, err := strconv.Atoi(ctx.sdkVersion())
 	minSdkVersionInt, err2 := strconv.Atoi(ver)
@@ -1633,12 +1646,12 @@
 		c.hideApexVariantFromMake = true
 	}
 
+	c.makeLinkType = GetMakeLinkType(actx, c)
+
 	if c.maybeGenerateBazelActions(actx) {
 		return
 	}
 
-	c.makeLinkType = GetMakeLinkType(actx, c)
-
 	ctx := &moduleContext{
 		ModuleContext: actx,
 		moduleContextImpl: moduleContextImpl{
@@ -1833,12 +1846,6 @@
 	if c.compiler != nil {
 		deps = c.compiler.compilerDeps(ctx, deps)
 	}
-	// Add the PGO dependency (the clang_rt.profile runtime library), which
-	// sometimes depends on symbols from libgcc, before libgcc gets added
-	// in linkerDeps().
-	if c.pgo != nil {
-		deps = c.pgo.deps(ctx, deps)
-	}
 	if c.linker != nil {
 		deps = c.linker.linkerDeps(ctx, deps)
 	}
@@ -1885,8 +1892,8 @@
 	}
 
 	for _, lib := range deps.ReexportStaticLibHeaders {
-		if !inList(lib, deps.StaticLibs) {
-			ctx.PropertyErrorf("export_static_lib_headers", "Static library not in static_libs: '%s'", lib)
+		if !inList(lib, deps.StaticLibs) && !inList(lib, deps.WholeStaticLibs) {
+			ctx.PropertyErrorf("export_static_lib_headers", "Static library not in static_libs or whole_static_libs: '%s'", lib)
 		}
 	}
 
@@ -1933,9 +1940,14 @@
 		return nil
 	}
 	if m.UseSdk() {
+		// Choose the CRT that best satisfies the min_sdk_version requirement of this module
+		minSdkVersion := m.MinSdkVersion()
+		if minSdkVersion == "" || minSdkVersion == "apex_inherit" {
+			minSdkVersion = m.SdkVersion()
+		}
 		return []blueprint.Variation{
 			{Mutator: "sdk", Variation: "sdk"},
-			{Mutator: "version", Variation: m.SdkVersion()},
+			{Mutator: "version", Variation: minSdkVersion},
 		}
 	}
 	return []blueprint.Variation{
@@ -2619,14 +2631,31 @@
 						// However, for host, ramdisk, vendor_ramdisk, recovery or bootstrap modules,
 						// always link to non-stub variant
 						useStubs = dep.(android.ApexModule).NotInPlatform() && !c.bootstrap()
-						// Another exception: if this module is bundled with an APEX, then
-						// it is linked with the non-stub variant of a module in the APEX
-						// as if this is part of the APEX.
-						testFor := ctx.Provider(android.ApexTestForInfoProvider).(android.ApexTestForInfo)
-						for _, apexContents := range testFor.ApexContents {
-							if apexContents.DirectlyInApex(depName) {
+						if useStubs {
+							// Another exception: if this module is a test for an APEX, then
+							// it is linked with the non-stub variant of a module in the APEX
+							// as if this is part of the APEX.
+							testFor := ctx.Provider(android.ApexTestForInfoProvider).(android.ApexTestForInfo)
+							for _, apexContents := range testFor.ApexContents {
+								if apexContents.DirectlyInApex(depName) {
+									useStubs = false
+									break
+								}
+							}
+						}
+						if useStubs {
+							// Yet another exception: If this module and the dependency are
+							// available to the same APEXes then skip stubs between their
+							// platform variants. This complements the test_for case above,
+							// which avoids the stubs on a direct APEX library dependency, by
+							// avoiding stubs for indirect test dependencies as well.
+							//
+							// TODO(b/183882457): This doesn't work if the two libraries have
+							// only partially overlapping apex_available. For that test_for
+							// modules would need to be split into APEX variants and resolved
+							// separately for each APEX they have access to.
+							if android.AvailableToSameApexes(c, dep.(android.ApexModule)) {
 								useStubs = false
-								break
 							}
 						}
 					} else {
@@ -3255,6 +3284,10 @@
 type Defaults struct {
 	android.ModuleBase
 	android.DefaultsModuleBase
+	// Included to support setting bazel_module.label for multiple Soong modules to the same Bazel
+	// target. This is primarily useful for modules that were architecture specific and instead are
+	// handled in Bazel as a select().
+	android.BazelModuleBase
 	android.ApexModuleBase
 }
 
@@ -3301,6 +3334,8 @@
 		&RustBindgenClangProperties{},
 	)
 
+	// Bazel module must be initialized _before_ Defaults to be included in cc_defaults module.
+	android.InitBazelModule(module)
 	android.InitDefaultsModule(module)
 
 	return module
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 7196615..465283d 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -16,7 +16,6 @@
 
 import (
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"reflect"
@@ -27,97 +26,75 @@
 	"android/soong/android"
 )
 
-var buildDir string
-
-func setUp() {
-	var err error
-	buildDir, err = ioutil.TempDir("", "soong_cc_test")
-	if err != nil {
-		panic(err)
-	}
-}
-
-func tearDown() {
-	os.RemoveAll(buildDir)
-}
-
 func TestMain(m *testing.M) {
-	run := func() int {
-		setUp()
-		defer tearDown()
-
-		return m.Run()
-	}
-
-	os.Exit(run())
+	os.Exit(m.Run())
 }
 
-var ccFixtureFactory = android.NewFixtureFactory(
-	&buildDir,
+var prepareForCcTest = android.GroupFixturePreparers(
 	PrepareForTestWithCcIncludeVndk,
 	android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
 		variables.DeviceVndkVersion = StringPtr("current")
 		variables.ProductVndkVersion = StringPtr("current")
-		variables.Platform_vndk_version = StringPtr("VER")
+		variables.Platform_vndk_version = StringPtr("29")
 	}),
 )
 
-// testCcWithConfig runs tests using the ccFixtureFactory
+// testCcWithConfig runs tests using the prepareForCcTest
 //
 // See testCc for an explanation as to how to stop using this deprecated method.
 //
 // deprecated
 func testCcWithConfig(t *testing.T, config android.Config) *android.TestContext {
 	t.Helper()
-	result := ccFixtureFactory.RunTestWithConfig(t, config)
+	result := prepareForCcTest.RunTestWithConfig(t, config)
 	return result.TestContext
 }
 
-// testCc runs tests using the ccFixtureFactory
+// testCc runs tests using the prepareForCcTest
 //
-// Do not add any new usages of this, instead use the ccFixtureFactory directly as it makes it much
+// Do not add any new usages of this, instead use the prepareForCcTest directly as it makes it much
 // easier to customize the test behavior.
 //
 // If it is necessary to customize the behavior of an existing test that uses this then please first
-// convert the test to using ccFixtureFactory first and then in a following change add the
+// convert the test to using prepareForCcTest first and then in a following change add the
 // appropriate fixture preparers. Keeping the conversion change separate makes it easy to verify
 // that it did not change the test behavior unexpectedly.
 //
 // deprecated
 func testCc(t *testing.T, bp string) *android.TestContext {
 	t.Helper()
-	result := ccFixtureFactory.RunTestWithBp(t, bp)
+	result := prepareForCcTest.RunTestWithBp(t, bp)
 	return result.TestContext
 }
 
-// testCcNoVndk runs tests using the ccFixtureFactory
+// testCcNoVndk runs tests using the prepareForCcTest
 //
 // See testCc for an explanation as to how to stop using this deprecated method.
 //
 // deprecated
 func testCcNoVndk(t *testing.T, bp string) *android.TestContext {
 	t.Helper()
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
-	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
+	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 
 	return testCcWithConfig(t, config)
 }
 
-// testCcNoProductVndk runs tests using the ccFixtureFactory
+// testCcNoProductVndk runs tests using the prepareForCcTest
 //
 // See testCc for an explanation as to how to stop using this deprecated method.
 //
 // deprecated
 func testCcNoProductVndk(t *testing.T, bp string) *android.TestContext {
 	t.Helper()
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 
 	return testCcWithConfig(t, config)
 }
 
-// testCcErrorWithConfig runs tests using the ccFixtureFactory
+// testCcErrorWithConfig runs tests using the prepareForCcTest
 //
 // See testCc for an explanation as to how to stop using this deprecated method.
 //
@@ -125,47 +102,56 @@
 func testCcErrorWithConfig(t *testing.T, pattern string, config android.Config) {
 	t.Helper()
 
-	ccFixtureFactory.Extend().
+	prepareForCcTest.
 		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(pattern)).
 		RunTestWithConfig(t, config)
 }
 
-// testCcError runs tests using the ccFixtureFactory
+// testCcError runs tests using the prepareForCcTest
 //
 // See testCc for an explanation as to how to stop using this deprecated method.
 //
 // deprecated
 func testCcError(t *testing.T, pattern string, bp string) {
 	t.Helper()
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 	testCcErrorWithConfig(t, pattern, config)
 	return
 }
 
-// testCcErrorProductVndk runs tests using the ccFixtureFactory
+// testCcErrorProductVndk runs tests using the prepareForCcTest
 //
 // See testCc for an explanation as to how to stop using this deprecated method.
 //
 // deprecated
 func testCcErrorProductVndk(t *testing.T, pattern string, bp string) {
 	t.Helper()
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
 	config.TestProductVariables.ProductVndkVersion = StringPtr("current")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 	testCcErrorWithConfig(t, pattern, config)
 	return
 }
 
 const (
 	coreVariant     = "android_arm64_armv8-a_shared"
-	vendorVariant   = "android_vendor.VER_arm64_armv8-a_shared"
-	productVariant  = "android_product.VER_arm64_armv8-a_shared"
+	vendorVariant   = "android_vendor.29_arm64_armv8-a_shared"
+	productVariant  = "android_product.29_arm64_armv8-a_shared"
 	recoveryVariant = "android_recovery_arm64_armv8-a_shared"
 )
 
+// Test that the PrepareForTestWithCcDefaultModules provides all the files that it uses by
+// running it in a fixture that requires all source files to exist.
+func TestPrepareForTestWithCcDefaultModules(t *testing.T) {
+	android.GroupFixturePreparers(
+		PrepareForTestWithCcDefaultModules,
+		android.PrepareForTestDisallowNonExistentPaths,
+	).RunTest(t)
+}
+
 func TestFuchsiaDeps(t *testing.T) {
 	t.Helper()
 
@@ -180,7 +166,10 @@
 			},
 		}`
 
-	result := ccFixtureFactory.Extend(PrepareForTestOnFuchsia).RunTestWithBp(t, bp)
+	result := android.GroupFixturePreparers(
+		prepareForCcTest,
+		PrepareForTestOnFuchsia,
+	).RunTestWithBp(t, bp)
 
 	rt := false
 	fb := false
@@ -216,7 +205,10 @@
 			},
 		}`
 
-	result := ccFixtureFactory.Extend(PrepareForTestOnFuchsia).RunTestWithBp(t, bp)
+	result := android.GroupFixturePreparers(
+		prepareForCcTest,
+		PrepareForTestOnFuchsia,
+	).RunTestWithBp(t, bp)
 	ld := result.ModuleForTests("libTest", "fuchsia_arm64_shared").Rule("ld")
 	var objs []string
 	for _, o := range ld.Inputs {
@@ -292,13 +284,9 @@
 
 func checkSnapshotIncludeExclude(t *testing.T, ctx *android.TestContext, singleton android.TestingSingleton, moduleName, snapshotFilename, subDir, variant string, include bool, fake bool) {
 	t.Helper()
-	mod, ok := ctx.ModuleForTests(moduleName, variant).Module().(android.OutputFileProducer)
-	if !ok {
-		t.Errorf("%q must have output\n", moduleName)
-		return
-	}
-	outputFiles, err := mod.OutputFiles("")
-	if err != nil || len(outputFiles) != 1 {
+	mod := ctx.ModuleForTests(moduleName, variant)
+	outputFiles := mod.OutputFiles(t, "")
+	if len(outputFiles) != 1 {
 		t.Errorf("%q must have single output\n", moduleName)
 		return
 	}
@@ -324,14 +312,17 @@
 }
 
 func checkSnapshot(t *testing.T, ctx *android.TestContext, singleton android.TestingSingleton, moduleName, snapshotFilename, subDir, variant string) {
+	t.Helper()
 	checkSnapshotIncludeExclude(t, ctx, singleton, moduleName, snapshotFilename, subDir, variant, true, false)
 }
 
 func checkSnapshotExclude(t *testing.T, ctx *android.TestContext, singleton android.TestingSingleton, moduleName, snapshotFilename, subDir, variant string) {
+	t.Helper()
 	checkSnapshotIncludeExclude(t, ctx, singleton, moduleName, snapshotFilename, subDir, variant, false, false)
 }
 
 func checkSnapshotRule(t *testing.T, ctx *android.TestContext, singleton android.TestingSingleton, moduleName, snapshotFilename, subDir, variant string) {
+	t.Helper()
 	checkSnapshotIncludeExclude(t, ctx, singleton, moduleName, snapshotFilename, subDir, variant, true, true)
 }
 
@@ -462,10 +453,10 @@
 		}
 	`
 
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
 	config.TestProductVariables.ProductVndkVersion = StringPtr("current")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 
 	ctx := testCcWithConfig(t, config)
 
@@ -483,7 +474,7 @@
 
 	// Check VNDK snapshot output.
 	snapshotDir := "vndk-snapshot"
-	snapshotVariantPath := filepath.Join(buildDir, snapshotDir, "arm64")
+	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
 
 	vndkLibPath := filepath.Join(snapshotVariantPath, fmt.Sprintf("arch-%s-%s",
 		"arm64", "armv8-a"))
@@ -495,8 +486,8 @@
 	vndkCoreLib2ndPath := filepath.Join(vndkLib2ndPath, "shared", "vndk-core")
 	vndkSpLib2ndPath := filepath.Join(vndkLib2ndPath, "shared", "vndk-sp")
 
-	variant := "android_vendor.VER_arm64_armv8-a_shared"
-	variant2nd := "android_vendor.VER_arm_armv7-a-neon_shared"
+	variant := "android_vendor.29_arm64_armv8-a_shared"
+	variant2nd := "android_vendor.29_arm_armv7-a-neon_shared"
 
 	snapshotSingleton := ctx.SingletonForTests("vndk-snapshot")
 
@@ -584,14 +575,14 @@
 			name: "llndk.libraries.txt",
 			insert_vndk_version: true,
 		}`
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 	ctx := testCcWithConfig(t, config)
 
 	module := ctx.ModuleForTests("llndk.libraries.txt", "")
 	entries := android.AndroidMkEntriesForTest(t, ctx, module.Module())[0]
-	assertArrayString(t, entries.EntryMap["LOCAL_MODULE_STEM"], []string{"llndk.libraries.VER.txt"})
+	assertArrayString(t, entries.EntryMap["LOCAL_MODULE_STEM"], []string{"llndk.libraries.29.txt"})
 }
 
 func TestVndkUsingCoreVariant(t *testing.T) {
@@ -634,9 +625,9 @@
 		}
 	`
 
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 	config.TestProductVariables.VndkUseCoreVariant = BoolPtr(true)
 
 	setVndkMustUseVendorVariantListForTest(config, []string{"libvndk"})
@@ -661,9 +652,9 @@
 		}
  `
 
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 	config.TestProductVariables.VndkUseCoreVariant = BoolPtr(true)
 
 	ctx := testCcWithConfig(t, config)
@@ -712,9 +703,9 @@
 		}
  `
 
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 	config.TestProductVariables.VndkUseCoreVariant = BoolPtr(true)
 
 	ctx := testCcWithConfig(t, config)
@@ -1337,10 +1328,10 @@
 			nocrt: true,
 		}
 	`
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
 	config.TestProductVariables.ProductVndkVersion = StringPtr("current")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 
 	ctx := testCcWithConfig(t, config)
 
@@ -1782,10 +1773,10 @@
 			nocrt: true,
 		}
 	`
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
 	config.TestProductVariables.ProductVndkVersion = StringPtr("current")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 
 	testCcWithConfig(t, config)
 }
@@ -2109,7 +2100,7 @@
 		}
 	`
 
-	ctx := ccFixtureFactory.RunTestWithBp(t, bp).TestContext
+	ctx := prepareForCcTest.RunTestWithBp(t, bp).TestContext
 
 	checkVndkModule(t, ctx, "libvndk", "", false, "", productVariant)
 	checkVndkModule(t, ctx, "libvndk_sp", "", true, "", productVariant)
@@ -2149,7 +2140,7 @@
 }
 
 func TestEnforceProductVndkVersionErrors(t *testing.T) {
-	testCcErrorProductVndk(t, "dependency \".*\" of \".*\" missing variant:\n.*image:product.VER", `
+	testCcErrorProductVndk(t, "dependency \".*\" of \".*\" missing variant:\n.*image:product.29", `
 		cc_library {
 			name: "libprod",
 			product_specific: true,
@@ -2164,7 +2155,7 @@
 			nocrt: true,
 		}
 	`)
-	testCcErrorProductVndk(t, "dependency \".*\" of \".*\" missing variant:\n.*image:product.VER", `
+	testCcErrorProductVndk(t, "dependency \".*\" of \".*\" missing variant:\n.*image:product.29", `
 		cc_library {
 			name: "libprod",
 			product_specific: true,
@@ -2178,7 +2169,7 @@
 			nocrt: true,
 		}
 	`)
-	testCcErrorProductVndk(t, "dependency \".*\" of \".*\" missing variant:\n.*image:product.VER", `
+	testCcErrorProductVndk(t, "dependency \".*\" of \".*\" missing variant:\n.*image:product.29", `
 		cc_library {
 			name: "libprod",
 			product_specific: true,
@@ -2213,7 +2204,7 @@
 			nocrt: true,
 		}
 	`)
-	testCcErrorProductVndk(t, "dependency \".*\" of \".*\" missing variant:\n.*image:product.VER", `
+	testCcErrorProductVndk(t, "dependency \".*\" of \".*\" missing variant:\n.*image:product.29", `
 		cc_library {
 			name: "libprod",
 			product_specific: true,
@@ -2337,9 +2328,9 @@
 		}
 	`
 
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 	// native:vndk
 	ctx := testCcWithConfig(t, config)
 
@@ -2543,7 +2534,7 @@
 func getOutputPaths(ctx *android.TestContext, variant string, moduleNames []string) (paths android.Paths) {
 	for _, moduleName := range moduleNames {
 		module := ctx.ModuleForTests(moduleName, variant).Module().(*Module)
-		output := module.outputFile.Path()
+		output := module.outputFile.Path().RelativeToTop()
 		paths = append(paths, output)
 	}
 	return paths
@@ -2574,7 +2565,8 @@
 
 	variant := "android_arm64_armv8-a_static"
 	moduleA := ctx.ModuleForTests("a", variant).Module().(*Module)
-	actual := ctx.ModuleProvider(moduleA, StaticLibraryInfoProvider).(StaticLibraryInfo).TransitiveStaticLibrariesForOrdering.ToList()
+	actual := ctx.ModuleProvider(moduleA, StaticLibraryInfoProvider).(StaticLibraryInfo).
+		TransitiveStaticLibrariesForOrdering.ToList().RelativeToTop()
 	expected := getOutputPaths(ctx, variant, []string{"a", "c", "b", "d"})
 
 	if !reflect.DeepEqual(actual, expected) {
@@ -2608,7 +2600,8 @@
 
 	variant := "android_arm64_armv8-a_static"
 	moduleA := ctx.ModuleForTests("a", variant).Module().(*Module)
-	actual := ctx.ModuleProvider(moduleA, StaticLibraryInfoProvider).(StaticLibraryInfo).TransitiveStaticLibrariesForOrdering.ToList()
+	actual := ctx.ModuleProvider(moduleA, StaticLibraryInfoProvider).(StaticLibraryInfo).
+		TransitiveStaticLibrariesForOrdering.ToList().RelativeToTop()
 	expected := getOutputPaths(ctx, variant, []string{"a", "c", "b"})
 
 	if !reflect.DeepEqual(actual, expected) {
@@ -2671,25 +2664,27 @@
 	`)
 	actual := ctx.ModuleVariantsForTests("libllndk")
 	for i := 0; i < len(actual); i++ {
-		if !strings.HasPrefix(actual[i], "android_vendor.VER_") {
+		if !strings.HasPrefix(actual[i], "android_vendor.29_") {
 			actual = append(actual[:i], actual[i+1:]...)
 			i--
 		}
 	}
 	expected := []string{
-		"android_vendor.VER_arm64_armv8-a_shared_1",
-		"android_vendor.VER_arm64_armv8-a_shared_2",
-		"android_vendor.VER_arm64_armv8-a_shared",
-		"android_vendor.VER_arm_armv7-a-neon_shared_1",
-		"android_vendor.VER_arm_armv7-a-neon_shared_2",
-		"android_vendor.VER_arm_armv7-a-neon_shared",
+		"android_vendor.29_arm64_armv8-a_shared_1",
+		"android_vendor.29_arm64_armv8-a_shared_2",
+		"android_vendor.29_arm64_armv8-a_shared_current",
+		"android_vendor.29_arm64_armv8-a_shared",
+		"android_vendor.29_arm_armv7-a-neon_shared_1",
+		"android_vendor.29_arm_armv7-a-neon_shared_2",
+		"android_vendor.29_arm_armv7-a-neon_shared_current",
+		"android_vendor.29_arm_armv7-a-neon_shared",
 	}
 	checkEquals(t, "variants for llndk stubs", expected, actual)
 
-	params := ctx.ModuleForTests("libllndk", "android_vendor.VER_arm_armv7-a-neon_shared").Description("generate stub")
+	params := ctx.ModuleForTests("libllndk", "android_vendor.29_arm_armv7-a-neon_shared").Description("generate stub")
 	checkEquals(t, "use VNDK version for default stubs", "current", params.Args["apiLevel"])
 
-	params = ctx.ModuleForTests("libllndk", "android_vendor.VER_arm_armv7-a-neon_shared_1").Description("generate stub")
+	params = ctx.ModuleForTests("libllndk", "android_vendor.29_arm_armv7-a-neon_shared_1").Description("generate stub")
 	checkEquals(t, "override apiLevel for versioned stubs", "1", params.Args["apiLevel"])
 }
 
@@ -2719,7 +2714,7 @@
 	`)
 
 	// _static variant is used since _shared reuses *.o from the static variant
-	cc := ctx.ModuleForTests("libvendor", "android_vendor.VER_arm_armv7-a-neon_static").Rule("cc")
+	cc := ctx.ModuleForTests("libvendor", "android_vendor.29_arm_armv7-a-neon_static").Rule("cc")
 	cflags := cc.Args["cFlags"]
 	if !strings.Contains(cflags, "-Imy_include") {
 		t.Errorf("cflags for libvendor must contain -Imy_include, but was %#v.", cflags)
@@ -2840,7 +2835,7 @@
 
 	// runtime_libs for vendor variants have '.vendor' suffixes if the modules have both core
 	// and vendor variants.
-	variant = "android_vendor.VER_arm64_armv8-a_shared"
+	variant = "android_vendor.29_arm64_armv8-a_shared"
 
 	module = ctx.ModuleForTests("libvendor_available1", variant).Module().(*Module)
 	checkRuntimeLibs(t, []string{"liball_available.vendor"}, module)
@@ -2850,7 +2845,7 @@
 
 	// runtime_libs for product variants have '.product' suffixes if the modules have both core
 	// and product variants.
-	variant = "android_product.VER_arm64_armv8-a_shared"
+	variant = "android_product.29_arm64_armv8-a_shared"
 
 	module = ctx.ModuleForTests("libproduct_available1", variant).Module().(*Module)
 	checkRuntimeLibs(t, []string{"liball_available.product"}, module)
@@ -2866,7 +2861,7 @@
 	module := ctx.ModuleForTests("libvendor_available2", variant).Module().(*Module)
 	checkRuntimeLibs(t, []string{"liball_available"}, module)
 
-	variant = "android_vendor.VER_arm64_armv8-a_shared"
+	variant = "android_vendor.29_arm64_armv8-a_shared"
 	module = ctx.ModuleForTests("libvendor_available2", variant).Module().(*Module)
 	checkRuntimeLibs(t, nil, module)
 }
@@ -2917,13 +2912,13 @@
 	// Check the shared version of lib2.
 	variant := "android_arm64_armv8-a_shared"
 	module := ctx.ModuleForTests("lib2", variant).Module().(*Module)
-	checkStaticLibs(t, []string{"lib1", "libc++demangle", "libclang_rt.builtins-aarch64-android", "libatomic"}, module)
+	checkStaticLibs(t, []string{"lib1", "libc++demangle", "libclang_rt.builtins-aarch64-android"}, module)
 
 	// Check the static version of lib2.
 	variant = "android_arm64_armv8-a_static"
 	module = ctx.ModuleForTests("lib2", variant).Module().(*Module)
 	// libc++_static is linked additionally.
-	checkStaticLibs(t, []string{"lib1", "libc++_static", "libc++demangle", "libclang_rt.builtins-aarch64-android", "libatomic"}, module)
+	checkStaticLibs(t, []string{"lib1", "libc++_static", "libc++demangle", "libclang_rt.builtins-aarch64-android"}, module)
 }
 
 var compilerFlagsTestCases = []struct {
@@ -3049,7 +3044,7 @@
 	`)
 
 	coreVariant := "android_arm64_armv8-a_shared"
-	vendorVariant := "android_vendor.VER_arm64_armv8-a_shared"
+	vendorVariant := "android_vendor.29_arm64_armv8-a_shared"
 
 	// test if header search paths are correctly added
 	// _static variant is used since _shared reuses *.o from the static variant
@@ -3127,9 +3122,9 @@
 		}
  `
 
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 	config.TestProductVariables.VndkUseCoreVariant = BoolPtr(true)
 
 	ctx := testCcWithConfig(t, config)
@@ -3181,10 +3176,12 @@
 		"android_arm64_armv8-a_shared_1",
 		"android_arm64_armv8-a_shared_2",
 		"android_arm64_armv8-a_shared_3",
+		"android_arm64_armv8-a_shared_current",
 		"android_arm_armv7-a-neon_shared",
 		"android_arm_armv7-a-neon_shared_1",
 		"android_arm_armv7-a-neon_shared_2",
 		"android_arm_armv7-a-neon_shared_3",
+		"android_arm_armv7-a-neon_shared_current",
 	}
 	variantsMismatch := false
 	if len(variants) != len(expectedVariants) {
@@ -3371,6 +3368,9 @@
 			shared: {
 				srcs: ["baz.c"],
 			},
+			bazel_module: {
+				bp2build_available: true,
+			},
 		}
 
 		cc_library_static {
@@ -3443,7 +3443,8 @@
 		}
 	`
 
-	result := ccFixtureFactory.Extend(
+	result := android.GroupFixturePreparers(
+		prepareForCcTest,
 		android.PrepareForTestWithVariables,
 
 		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
@@ -3470,7 +3471,8 @@
 		}
 	`
 
-	result := ccFixtureFactory.Extend(
+	result := android.GroupFixturePreparers(
+		prepareForCcTest,
 		android.PrepareForTestWithAllowMissingDependencies,
 	).RunTestWithBp(t, bp)
 
@@ -3522,7 +3524,7 @@
 		}
 	`
 
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	ctx := testCcWithConfig(t, config)
 
 	hostBin := ctx.ModuleForTests("bin", config.BuildOSTarget.String()).Description("install")
@@ -3628,6 +3630,71 @@
 	}
 }
 
+func TestMinSdkVersionInClangTriple(t *testing.T) {
+	ctx := testCc(t, `
+		cc_library_shared {
+			name: "libfoo",
+			srcs: ["foo.c"],
+			min_sdk_version: "29",
+		}`)
+
+	cFlags := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Rule("cc").Args["cFlags"]
+	android.AssertStringDoesContain(t, "min sdk version", cFlags, "-target aarch64-linux-android29")
+}
+
+func TestMinSdkVersionsOfCrtObjects(t *testing.T) {
+	ctx := testCc(t, `
+		cc_object {
+			name: "crt_foo",
+			srcs: ["foo.c"],
+			crt: true,
+			stl: "none",
+			min_sdk_version: "28",
+
+		}`)
+
+	arch := "android_arm64_armv8-a"
+	for _, v := range []string{"", "28", "29", "30", "current"} {
+		var variant string
+		if v == "" {
+			variant = arch
+		} else {
+			variant = arch + "_sdk_" + v
+		}
+		cflags := ctx.ModuleForTests("crt_foo", variant).Rule("cc").Args["cFlags"]
+		vNum := v
+		if v == "current" || v == "" {
+			vNum = "10000"
+		}
+		expected := "-target aarch64-linux-android" + vNum + " "
+		android.AssertStringDoesContain(t, "cflag", cflags, expected)
+	}
+}
+
+func TestUseCrtObjectOfCorrectVersion(t *testing.T) {
+	ctx := testCc(t, `
+		cc_binary {
+			name: "bin",
+			srcs: ["foo.c"],
+			stl: "none",
+			min_sdk_version: "29",
+			sdk_version: "current",
+		}
+		`)
+
+	// Sdk variant uses the crt object of the matching min_sdk_version
+	variant := "android_arm64_armv8-a_sdk"
+	crt := ctx.ModuleForTests("bin", variant).Rule("ld").Args["crtBegin"]
+	android.AssertStringDoesContain(t, "crt dep of sdk variant", crt,
+		variant+"_29/crtbegin_dynamic.o")
+
+	// platform variant uses the crt object built for platform
+	variant = "android_arm64_armv8-a"
+	crt = ctx.ModuleForTests("bin", variant).Rule("ld").Args["crtBegin"]
+	android.AssertStringDoesContain(t, "crt dep of platform variant", crt,
+		variant+"/crtbegin_dynamic.o")
+}
+
 type MemtagNoteType int
 
 const (
@@ -3748,7 +3815,10 @@
 func TestSanitizeMemtagHeap(t *testing.T) {
 	variant := "android_arm64_armv8-a"
 
-	result := ccFixtureFactory.Extend(prepareForTestWithMemtagHeap).RunTest(t)
+	result := android.GroupFixturePreparers(
+		prepareForCcTest,
+		prepareForTestWithMemtagHeap,
+	).RunTest(t)
 	ctx := result.TestContext
 
 	checkHasMemtagNote(t, ctx.ModuleForTests("default_test", variant), Sync)
@@ -3803,7 +3873,8 @@
 func TestSanitizeMemtagHeapWithSanitizeDevice(t *testing.T) {
 	variant := "android_arm64_armv8-a"
 
-	result := ccFixtureFactory.Extend(
+	result := android.GroupFixturePreparers(
+		prepareForCcTest,
 		prepareForTestWithMemtagHeap,
 		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
 			variables.SanitizeDevice = []string{"memtag_heap"}
@@ -3863,7 +3934,8 @@
 func TestSanitizeMemtagHeapWithSanitizeDeviceDiag(t *testing.T) {
 	variant := "android_arm64_armv8-a"
 
-	result := ccFixtureFactory.Extend(
+	result := android.GroupFixturePreparers(
+		prepareForCcTest,
 		prepareForTestWithMemtagHeap,
 		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
 			variables.SanitizeDevice = []string{"memtag_heap"}
diff --git a/cc/compiler.go b/cc/compiler.go
index b09b58e..78a5a5d 100644
--- a/cc/compiler.go
+++ b/cc/compiler.go
@@ -414,12 +414,7 @@
 
 	target := "-target " + tc.ClangTriple()
 	if ctx.Os().Class == android.Device {
-		// When built for the non-updateble part of platform, minSdkVersion doesn't matter.
-		// It matters only when building we are building for modules that can be unbundled.
-		version := "current"
-		if !ctx.isForPlatform() || ctx.isSdkVariant() {
-			version = ctx.minSdkVersion()
-		}
+		version := ctx.minSdkVersion()
 		if version == "" || version == "current" {
 			target += strconv.Itoa(android.FutureApiLevelInt)
 		} else {
diff --git a/cc/config/toolchain.go b/cc/config/toolchain.go
index 59c0422..fce28c1 100644
--- a/cc/config/toolchain.go
+++ b/cc/config/toolchain.go
@@ -245,10 +245,6 @@
 	return LibclangRuntimeLibrary(t, "tsan")
 }
 
-func ProfileRuntimeLibrary(t Toolchain) string {
-	return LibclangRuntimeLibrary(t, "profile")
-}
-
 func ScudoRuntimeLibrary(t Toolchain) string {
 	return LibclangRuntimeLibrary(t, "scudo")
 }
diff --git a/cc/gen_test.go b/cc/gen_test.go
index 41ef95c..40a5716 100644
--- a/cc/gen_test.go
+++ b/cc/gen_test.go
@@ -36,8 +36,10 @@
 		aidl := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Rule("aidl")
 		libfoo := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Module().(*Module)
 
-		if !inList("-I"+filepath.Dir(aidl.Output.String()), libfoo.flags.Local.CommonFlags) {
-			t.Errorf("missing aidl includes in global flags")
+		expected := "-I" + filepath.Dir(aidl.Output.String())
+		actual := android.StringsRelativeToTop(ctx.Config(), libfoo.flags.Local.CommonFlags)
+		if !inList(expected, actual) {
+			t.Errorf("missing aidl includes in global flags, expected %q, actual %q", expected, actual)
 		}
 	})
 
@@ -61,7 +63,7 @@
 		aidlManifest := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Output("aidl.sbox.textproto")
 		libfoo := ctx.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Module().(*Module)
 
-		if !inList("-I"+filepath.Dir(aidl.Output.String()), libfoo.flags.Local.CommonFlags) {
+		if !inList("-I"+filepath.Dir(aidl.Output.String()), android.StringsRelativeToTop(ctx.Config(), libfoo.flags.Local.CommonFlags)) {
 			t.Errorf("missing aidl includes in global flags")
 		}
 
diff --git a/cc/genrule_test.go b/cc/genrule_test.go
index fa0c6f2..45b343b 100644
--- a/cc/genrule_test.go
+++ b/cc/genrule_test.go
@@ -52,7 +52,7 @@
 					},
 				}
 			`
-	config := android.TestArchConfig(buildDir, nil, bp, fs)
+	config := android.TestArchConfig(t.TempDir(), nil, bp, fs)
 
 	ctx := testGenruleContext(config)
 
diff --git a/cc/library.go b/cc/library.go
index 22a36c6..b49f1e5 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -27,6 +27,7 @@
 	"github.com/google/blueprint/pathtools"
 
 	"android/soong/android"
+	"android/soong/bazel"
 	"android/soong/cc/config"
 )
 
@@ -64,7 +65,8 @@
 		// symbols that are exported for stubs variant of this library.
 		Symbol_file *string `android:"path"`
 
-		// List versions to generate stubs libs for.
+		// List versions to generate stubs libs for. The version name "current" is always
+		// implicitly added.
 		Versions []string
 	}
 
@@ -170,6 +172,8 @@
 
 	// This variant is a stubs lib
 	BuildStubs bool `blueprint:"mutated"`
+	// This variant is the latest version
+	IsLatestVersion bool `blueprint:"mutated"`
 	// Version of the stubs lib
 	StubsVersion string `blueprint:"mutated"`
 	// List of all stubs versions associated with an implementation lib
@@ -200,6 +204,8 @@
 
 func init() {
 	RegisterLibraryBuildComponents(android.InitRegistrationContext)
+
+	android.RegisterBp2BuildMutator("cc_library_static", CcLibraryStaticBp2Build)
 }
 
 func RegisterLibraryBuildComponents(ctx android.RegistrationContext) {
@@ -419,11 +425,14 @@
 	if !ok {
 		return ok
 	}
-	if len(outputPaths) != 1 {
+	if len(outputPaths) > 1 {
 		// TODO(cparsons): This is actually expected behavior for static libraries with no srcs.
 		// We should support this.
-		ctx.ModuleErrorf("expected exactly one output file for '%s', but got %s", label, objPaths)
+		ctx.ModuleErrorf("expected at most one output file for '%s', but got %s", label, objPaths)
 		return false
+	} else if len(outputPaths) == 0 {
+		handler.module.outputFile = android.OptionalPath{}
+		return true
 	}
 	outputFilePath := android.PathForBazelOut(ctx, outputPaths[0])
 	handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
@@ -447,7 +456,15 @@
 			Direct(outputFilePath).
 			Build(),
 	})
-	handler.module.outputFile = android.OptionalPathForPath(android.PathForBazelOut(ctx, objPaths[0]))
+	if i, ok := handler.module.linker.(snapshotLibraryInterface); ok {
+		// Dependencies on this library will expect collectedSnapshotHeaders to
+		// be set, otherwise validation will fail. For now, set this to an empty
+		// list.
+		// TODO(cparsons): More closely mirror the collectHeadersForSnapshot
+		// implementation.
+		i.(*libraryDecorator).collectedSnapshotHeaders = android.Paths{}
+	}
+
 	return ok
 }
 
@@ -501,12 +518,11 @@
 			return
 		}
 		isLibcxx := strings.HasPrefix(dir, "external/libcxx/include")
-		j := 0
-		for i, header := range glob {
+		for _, header := range glob {
 			if isLibcxx {
 				// Glob all files under this special directory, because of C++ headers with no
 				// extension.
-				if !strings.HasSuffix(header, "/") {
+				if strings.HasSuffix(header, "/") {
 					continue
 				}
 			} else {
@@ -522,12 +538,8 @@
 					continue
 				}
 			}
-			if i != j {
-				glob[j] = glob[i]
-			}
-			j++
+			ret = append(ret, android.PathForSource(ctx, header))
 		}
-		glob = glob[:j]
 	}
 
 	// Collect generated headers
@@ -777,7 +789,7 @@
 
 type versionedInterface interface {
 	buildStubs() bool
-	setBuildStubs()
+	setBuildStubs(isLatest bool)
 	hasStubsVariants() bool
 	setStubsVersion(string)
 	stubsVersion() string
@@ -1495,7 +1507,7 @@
 			if ctx.isVndk() && !ctx.IsVndkExt() {
 				return
 			}
-		} else if len(library.Properties.Stubs.Versions) > 0 && !ctx.Host() && ctx.directlyInAnyApex() {
+		} else if library.hasStubsVariants() && !ctx.Host() && ctx.directlyInAnyApex() {
 			// Bionic libraries (e.g. libc.so) is installed to the bootstrap subdirectory.
 			// The original path becomes a symlink to the corresponding file in the
 			// runtime APEX.
@@ -1611,11 +1623,29 @@
 }
 
 func (library *libraryDecorator) hasStubsVariants() bool {
-	return len(library.Properties.Stubs.Versions) > 0
+	// Just having stubs.symbol_file is enough to create a stub variant. In that case
+	// the stub for the future API level is created.
+	return library.Properties.Stubs.Symbol_file != nil ||
+		len(library.Properties.Stubs.Versions) > 0
 }
 
 func (library *libraryDecorator) stubsVersions(ctx android.BaseMutatorContext) []string {
-	return library.Properties.Stubs.Versions
+	if !library.hasStubsVariants() {
+		return nil
+	}
+
+	// Future API level is implicitly added if there isn't
+	vers := library.Properties.Stubs.Versions
+	if inList(android.FutureApiLevel.String(), vers) {
+		return vers
+	}
+	// In some cases, people use the raw value "10000" in the versions property.
+	// We shouldn't add the future API level in that case, otherwise there will
+	// be two identical versions.
+	if inList(strconv.Itoa(android.FutureApiLevel.FinalOrFutureInt()), vers) {
+		return vers
+	}
+	return append(vers, android.FutureApiLevel.String())
 }
 
 func (library *libraryDecorator) setStubsVersion(version string) {
@@ -1626,8 +1656,9 @@
 	return library.MutatedProperties.StubsVersion
 }
 
-func (library *libraryDecorator) setBuildStubs() {
+func (library *libraryDecorator) setBuildStubs(isLatest bool) {
 	library.MutatedProperties.BuildStubs = true
+	library.MutatedProperties.IsLatestVersion = isLatest
 }
 
 func (library *libraryDecorator) setAllStubsVersions(versions []string) {
@@ -1639,8 +1670,7 @@
 }
 
 func (library *libraryDecorator) isLatestStubVersion() bool {
-	versions := library.Properties.Stubs.Versions
-	return versions[len(versions)-1] == library.stubsVersion()
+	return library.MutatedProperties.IsLatestVersion
 }
 
 func (library *libraryDecorator) availableFor(what string) bool {
@@ -1883,7 +1913,8 @@
 			c.stl = nil
 			c.Properties.PreventInstall = true
 			lib := moduleLibraryInterface(m)
-			lib.setBuildStubs()
+			isLatest := i == (len(versions) - 1)
+			lib.setBuildStubs(isLatest)
 
 			if variants[i] != "" {
 				// A non-LLNDK stubs module is hidden from make and has a dependency from the
@@ -1914,6 +1945,7 @@
 
 	for i, module := range modules {
 		module.(*Module).Properties.Sdk_version = StringPtr(versionStrs[i])
+		module.(*Module).Properties.Min_sdk_version = StringPtr(versionStrs[i])
 	}
 }
 
@@ -2024,3 +2056,102 @@
 
 	return outputFile
 }
+
+type bazelCcLibraryStaticAttributes struct {
+	Copts      []string
+	Srcs       bazel.LabelListAttribute
+	Deps       bazel.LabelListAttribute
+	Linkstatic bool
+	Includes   bazel.StringListAttribute
+	Hdrs       bazel.LabelListAttribute
+}
+
+type bazelCcLibraryStatic struct {
+	android.BazelTargetModuleBase
+	bazelCcLibraryStaticAttributes
+}
+
+func BazelCcLibraryStaticFactory() android.Module {
+	module := &bazelCcLibraryStatic{}
+	module.AddProperties(&module.bazelCcLibraryStaticAttributes)
+	android.InitBazelTargetModule(module)
+	return module
+}
+
+func CcLibraryStaticBp2Build(ctx android.TopDownMutatorContext) {
+	module, ok := ctx.Module().(*Module)
+	if !ok {
+		// Not a cc module
+		return
+	}
+	if !module.ConvertWithBp2build(ctx) {
+		return
+	}
+	if ctx.ModuleType() != "cc_library_static" {
+		return
+	}
+
+	var copts []string
+	var srcs []string
+	var includeDirs []string
+	var localIncludeDirs []string
+	for _, props := range module.compiler.compilerProps() {
+		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
+			copts = baseCompilerProps.Cflags
+			srcs = baseCompilerProps.Srcs
+			includeDirs = bp2BuildMakePathsRelativeToModule(ctx, baseCompilerProps.Include_dirs)
+			localIncludeDirs = bp2BuildMakePathsRelativeToModule(ctx, baseCompilerProps.Local_include_dirs)
+			break
+		}
+	}
+	srcsLabels := bazel.MakeLabelListAttribute(android.BazelLabelForModuleSrc(ctx, srcs))
+
+	var staticLibs []string
+	var wholeStaticLibs []string
+	for _, props := range module.linker.linkerProps() {
+		if baseLinkerProperties, ok := props.(*BaseLinkerProperties); ok {
+			staticLibs = baseLinkerProperties.Static_libs
+			wholeStaticLibs = baseLinkerProperties.Whole_static_libs
+			break
+		}
+	}
+
+	// FIXME: Treat Static_libs and Whole_static_libs differently?
+	allDeps := staticLibs
+	allDeps = append(allDeps, wholeStaticLibs...)
+
+	depsLabels := android.BazelLabelForModuleDeps(ctx, allDeps)
+
+	exportedIncludes, exportedIncludesHeaders := bp2BuildParseExportedIncludes(ctx, module)
+
+	// FIXME: Unify absolute vs relative paths
+	// FIXME: Use -I copts instead of setting includes= ?
+	allIncludes := exportedIncludes
+	allIncludes.Value = append(allIncludes.Value, includeDirs...)
+	allIncludes.Value = append(allIncludes.Value, localIncludeDirs...)
+
+	headerLibsLabels := bp2BuildParseHeaderLibs(ctx, module)
+	depsLabels.Append(headerLibsLabels.Value)
+
+	attrs := &bazelCcLibraryStaticAttributes{
+		Copts:      copts,
+		Srcs:       srcsLabels,
+		Deps:       bazel.MakeLabelListAttribute(depsLabels),
+		Linkstatic: true,
+		Includes:   allIncludes,
+		Hdrs:       exportedIncludesHeaders,
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class:        "cc_library_static",
+		Bzl_load_location: "//build/bazel/rules:cc_library_static.bzl",
+	}
+
+	ctx.CreateBazelTargetModule(BazelCcLibraryStaticFactory, module.Name(), props, attrs)
+}
+
+func (m *bazelCcLibraryStatic) Name() string {
+	return m.BaseModuleName()
+}
+
+func (m *bazelCcLibraryStatic) GenerateAndroidBuildActions(ctx android.ModuleContext) {}
diff --git a/cc/library_headers.go b/cc/library_headers.go
index 0f4d8a6..d35748b 100644
--- a/cc/library_headers.go
+++ b/cc/library_headers.go
@@ -62,9 +62,10 @@
 }
 
 type bazelCcLibraryHeadersAttributes struct {
-	Hdrs     bazel.LabelList
-	Includes bazel.LabelList
-	Deps     bazel.LabelList
+	Copts    bazel.StringListAttribute
+	Hdrs     bazel.LabelListAttribute
+	Includes bazel.StringListAttribute
+	Deps     bazel.LabelListAttribute
 }
 
 type bazelCcLibraryHeaders struct {
@@ -94,35 +95,15 @@
 		return
 	}
 
-	lib, _ := module.linker.(*libraryDecorator)
+	exportedIncludes, exportedIncludesHeaders := bp2BuildParseExportedIncludes(ctx, module)
 
-	// list of directories that will be added to the include path (using -I) for this
-	// module and any module that links against this module.
-	includeDirs := lib.flagExporter.Properties.Export_system_include_dirs
-	includeDirs = append(includeDirs, lib.flagExporter.Properties.Export_include_dirs...)
-	includeDirLabels := android.BazelLabelForModuleSrc(ctx, includeDirs)
-
-	var includeDirGlobs []string
-	for _, includeDir := range includeDirs {
-		includeDirGlobs = append(includeDirGlobs, includeDir+"/**/*.h")
-	}
-
-	headerLabels := android.BazelLabelForModuleSrc(ctx, includeDirGlobs)
-
-	// list of modules that should only provide headers for this module.
-	var headerLibs []string
-	for _, linkerProps := range lib.linkerProps() {
-		if baseLinkerProps, ok := linkerProps.(*BaseLinkerProperties); ok {
-			headerLibs = baseLinkerProps.Export_header_lib_headers
-			break
-		}
-	}
-	headerLibLabels := android.BazelLabelForModuleDeps(ctx, headerLibs)
+	headerLibs := bp2BuildParseHeaderLibs(ctx, module)
 
 	attrs := &bazelCcLibraryHeadersAttributes{
-		Includes: includeDirLabels,
-		Hdrs:     headerLabels,
-		Deps:     headerLibLabels,
+		Copts:    bp2BuildParseCflags(ctx, module),
+		Includes: exportedIncludes,
+		Hdrs:     exportedIncludesHeaders,
+		Deps:     headerLibs,
 	}
 
 	props := bazel.BazelTargetModuleProperties{
diff --git a/cc/library_test.go b/cc/library_test.go
index 49838b4..7975275 100644
--- a/cc/library_test.go
+++ b/cc/library_test.go
@@ -199,7 +199,7 @@
 			},
 		}
 	`
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	config.TestProductVariables.Platform_version_active_codenames = []string{"R"}
 	ctx := testCcWithConfig(t, config)
 
@@ -222,7 +222,7 @@
 			},
 		}
 	`
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	config.TestProductVariables.Platform_version_active_codenames = []string{"R"}
 	testCcErrorWithConfig(t, `"libfoo" .*: versions: not sorted`, config)
 }
diff --git a/cc/linkable.go b/cc/linkable.go
index 58919a0..6aa238b 100644
--- a/cc/linkable.go
+++ b/cc/linkable.go
@@ -111,6 +111,7 @@
 	InProduct() bool
 
 	SdkVersion() string
+	MinSdkVersion() string
 	AlwaysSdk() bool
 	IsSdkVariant() bool
 
@@ -157,8 +158,16 @@
 }
 
 // StaticDepTag returns the dependency tag for any C++ static libraries.
-func StaticDepTag() blueprint.DependencyTag {
-	return libraryDependencyTag{Kind: staticLibraryDependency}
+func StaticDepTag(wholeStatic bool) blueprint.DependencyTag {
+	return libraryDependencyTag{Kind: staticLibraryDependency, wholeStatic: wholeStatic}
+}
+
+// IsWholeStaticLib whether a dependency tag is a whole static library dependency.
+func IsWholeStaticLib(depTag blueprint.DependencyTag) bool {
+	if tag, ok := depTag.(libraryDependencyTag); ok {
+		return tag.wholeStatic
+	}
+	return false
 }
 
 // HeaderDepTag returns the dependency tag for any C++ "header-only" libraries.
diff --git a/cc/linker.go b/cc/linker.go
index 6d0d416..ae33356 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -321,10 +321,9 @@
 	}
 
 	if ctx.toolchain().Bionic() {
-		// libclang_rt.builtins and libatomic have to be last on the command line
+		// libclang_rt.builtins has to be last on the command line
 		if !Bool(linker.Properties.No_libcrt) {
 			deps.LateStaticLibs = append(deps.LateStaticLibs, config.BuiltinsRuntimeLibrary(ctx.toolchain()))
-			deps.LateStaticLibs = append(deps.LateStaticLibs, "libatomic")
 		}
 
 		deps.SystemSharedLibs = linker.Properties.System_shared_libs
@@ -599,21 +598,20 @@
 	_                   = pctx.SourcePathVariable("genSortedBssSymbolsPath", "build/soong/scripts/gen_sorted_bss_symbols.sh")
 	genSortedBssSymbols = pctx.AndroidStaticRule("gen_sorted_bss_symbols",
 		blueprint.RuleParams{
-			Command:     "CROSS_COMPILE=$crossCompile $genSortedBssSymbolsPath ${in} ${out}",
-			CommandDeps: []string{"$genSortedBssSymbolsPath", "${crossCompile}nm"},
+			Command:     "CLANG_BIN=${clangBin} $genSortedBssSymbolsPath ${in} ${out}",
+			CommandDeps: []string{"$genSortedBssSymbolsPath", "${clangBin}/llvm-nm"},
 		},
-		"crossCompile")
+		"clangBin")
 )
 
 func (linker *baseLinker) sortBssSymbolsBySize(ctx ModuleContext, in android.Path, symbolOrderingFile android.ModuleOutPath, flags builderFlags) string {
-	crossCompile := gccCmd(flags.toolchain, "")
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        genSortedBssSymbols,
 		Description: "generate bss symbol order " + symbolOrderingFile.Base(),
 		Output:      symbolOrderingFile,
 		Input:       in,
 		Args: map[string]string{
-			"crossCompile": crossCompile,
+			"clangBin": "${config.ClangBin}",
 		},
 	})
 	return "-Wl,--symbol-ordering-file," + symbolOrderingFile.String()
diff --git a/cc/object.go b/cc/object.go
index 664be8d..4f8797d 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -53,8 +53,17 @@
 }
 
 func (handler *objectBazelHandler) generateBazelBuildActions(ctx android.ModuleContext, label string) bool {
-	// TODO(b/181794963): restore mixed builds once cc_object incompatibility resolved
-	return false
+	bazelCtx := ctx.Config().BazelContext
+	objPaths, ok := bazelCtx.GetOutputFiles(label, ctx.Arch().ArchType)
+	if ok {
+		if len(objPaths) != 1 {
+			ctx.ModuleErrorf("expected exactly one object file for '%s', but got %s", label, objPaths)
+			return false
+		}
+
+		handler.module.outputFile = android.OptionalPathForPath(android.PathForBazelOut(ctx, objPaths[0]))
+	}
+	return ok
 }
 
 type ObjectLinkerProperties struct {
@@ -103,9 +112,10 @@
 
 // For bp2build conversion.
 type bazelObjectAttributes struct {
-	Srcs               bazel.LabelList
-	Deps               bazel.LabelList
+	Srcs               bazel.LabelListAttribute
+	Deps               bazel.LabelListAttribute
 	Copts              bazel.StringListAttribute
+	Asflags            []string
 	Local_include_dirs []string
 }
 
@@ -146,15 +156,16 @@
 	}
 
 	// Set arch-specific configurable attributes
-	var copts bazel.StringListAttribute
-	var srcs []string
-	var excludeSrcs []string
+	var srcs bazel.LabelListAttribute
 	var localIncludeDirs []string
+	var asFlags []string
 	for _, props := range m.compiler.compilerProps() {
 		if baseCompilerProps, ok := props.(*BaseCompilerProperties); ok {
-			copts.Value = baseCompilerProps.Cflags
-			srcs = baseCompilerProps.Srcs
-			excludeSrcs = baseCompilerProps.Exclude_srcs
+			srcs = bazel.MakeLabelListAttribute(
+				android.BazelLabelForModuleSrcExcludes(
+					ctx,
+					baseCompilerProps.Srcs,
+					baseCompilerProps.Exclude_srcs))
 			localIncludeDirs = baseCompilerProps.Local_include_dirs
 			break
 		}
@@ -164,24 +175,42 @@
 		localIncludeDirs = append(localIncludeDirs, ".")
 	}
 
-	var deps bazel.LabelList
+	var deps bazel.LabelListAttribute
 	for _, props := range m.linker.linkerProps() {
 		if objectLinkerProps, ok := props.(*ObjectLinkerProperties); ok {
-			deps = android.BazelLabelForModuleDeps(ctx, objectLinkerProps.Objs)
+			deps = bazel.MakeLabelListAttribute(
+				android.BazelLabelForModuleDeps(ctx, objectLinkerProps.Objs))
 		}
 	}
 
+	productVariableProps := android.ProductVariableProperties(ctx)
+	if props, exists := productVariableProps["Asflags"]; exists {
+		// TODO(b/183595873): consider deduplicating handling of product variable properties
+		for _, prop := range props {
+			flags, ok := prop.Property.([]string)
+			if !ok {
+				ctx.ModuleErrorf("Could not convert product variable asflag property")
+				return
+			}
+			// TODO(b/183595873) handle other product variable usages -- as selects?
+			if newFlags, subbed := bazel.TryVariableSubstitutions(flags, prop.ProductConfigVariable); subbed {
+				asFlags = append(asFlags, newFlags...)
+			}
+		}
+	}
+	// TODO(b/183595872) warn/error if we're not handling product variables
+
 	for arch, p := range m.GetArchProperties(&BaseCompilerProperties{}) {
 		if cProps, ok := p.(*BaseCompilerProperties); ok {
-			copts.SetValueForArch(arch.Name, cProps.Cflags)
+			srcs.SetValueForArch(arch.Name, android.BazelLabelForModuleSrcExcludes(ctx, cProps.Srcs, cProps.Exclude_srcs))
 		}
 	}
-	copts.SetValueForArch("default", []string{})
 
 	attrs := &bazelObjectAttributes{
-		Srcs:               android.BazelLabelForModuleSrcExcludes(ctx, srcs, excludeSrcs),
+		Srcs:               srcs,
 		Deps:               deps,
-		Copts:              copts,
+		Copts:              bp2BuildParseCflags(ctx, m),
+		Asflags:            asFlags,
 		Local_include_dirs: localIncludeDirs,
 	}
 
diff --git a/cc/pgo.go b/cc/pgo.go
index ada694b..95c9c2e 100644
--- a/cc/pgo.go
+++ b/cc/pgo.go
@@ -22,7 +22,6 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
-	"android/soong/cc/config"
 )
 
 var (
@@ -271,14 +270,6 @@
 	}
 }
 
-func (pgo *pgo) deps(ctx BaseModuleContext, deps Deps) Deps {
-	if pgo.Properties.ShouldProfileModule {
-		runtimeLibrary := config.ProfileRuntimeLibrary(ctx.toolchain())
-		deps.LateStaticLibs = append(deps.LateStaticLibs, runtimeLibrary)
-	}
-	return deps
-}
-
 func (pgo *pgo) flags(ctx ModuleContext, flags Flags) Flags {
 	if ctx.Host() {
 		return flags
diff --git a/cc/prebuilt_test.go b/cc/prebuilt_test.go
index 20274b2..fa6dd87 100644
--- a/cc/prebuilt_test.go
+++ b/cc/prebuilt_test.go
@@ -15,7 +15,6 @@
 package cc
 
 import (
-	"path/filepath"
 	"testing"
 
 	"android/soong/android"
@@ -23,14 +22,17 @@
 	"github.com/google/blueprint"
 )
 
-var prebuiltFixtureFactory = ccFixtureFactory.Extend(
+var prepareForPrebuiltTest = android.GroupFixturePreparers(
+	prepareForCcTest,
 	android.PrepareForTestWithAndroidMk,
 )
 
 func testPrebuilt(t *testing.T, bp string, fs android.MockFS, handlers ...android.FixturePreparer) *android.TestContext {
-	result := prebuiltFixtureFactory.Extend(
+	result := android.GroupFixturePreparers(
+		prepareForPrebuiltTest,
 		fs.AddToFixture(),
-	).Extend(handlers...).RunTestWithBp(t, bp)
+		android.GroupFixturePreparers(handlers...),
+	).RunTestWithBp(t, bp)
 
 	return result.TestContext
 }
@@ -302,8 +304,7 @@
 	})
 
 	fooRule := ctx.ModuleForTests("foo", "linux_glibc_x86_64").Rule("Symlink")
-	assertString(t, fooRule.Output.String(),
-		filepath.Join(buildDir, ".intermediates/foo/linux_glibc_x86_64/foo"))
+	assertString(t, fooRule.Output.String(), "out/soong/.intermediates/foo/linux_glibc_x86_64/foo")
 	assertString(t, fooRule.Args["fromPath"], "$$PWD/linux_glibc_x86_64/bin/foo")
 
 	var libfooDep android.Path
@@ -313,8 +314,7 @@
 			break
 		}
 	}
-	assertString(t, libfooDep.String(),
-		filepath.Join(buildDir, ".intermediates/libfoo/linux_glibc_x86_64_shared/libfoo.so"))
+	assertString(t, libfooDep.String(), "out/soong/.intermediates/libfoo/linux_glibc_x86_64_shared/libfoo.so")
 }
 
 func TestPrebuiltLibrarySanitized(t *testing.T) {
diff --git a/cc/proto_test.go b/cc/proto_test.go
index f8bbd26..b9c89c7 100644
--- a/cc/proto_test.go
+++ b/cc/proto_test.go
@@ -61,7 +61,7 @@
 			t.Errorf("expected %q in %q", w, cmd)
 		}
 
-		foobarPath := foobar.Module().(android.HostToolProvider).HostToolPath().String()
+		foobarPath := foobar.Module().(android.HostToolProvider).HostToolPath().RelativeToTop().String()
 
 		if w := "--plugin=protoc-gen-foobar=" + foobarPath; !strings.Contains(cmd, w) {
 			t.Errorf("expected %q in %q", w, cmd)
diff --git a/cc/sanitize.go b/cc/sanitize.go
index cd09e6e..e1ac9f0 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -1129,6 +1129,9 @@
 			Bool(c.sanitize.Properties.Sanitize.Undefined) ||
 			Bool(c.sanitize.Properties.Sanitize.All_undefined) {
 			runtimeLibrary = config.UndefinedBehaviorSanitizerRuntimeLibrary(toolchain)
+			if c.staticBinary() {
+				runtimeLibrary += ".static"
+			}
 		}
 
 		if runtimeLibrary != "" && (toolchain.Bionic() || c.sanitize.Properties.UbsanRuntimeDep) {
diff --git a/cc/sdk_test.go b/cc/sdk_test.go
index 5a3c181..61925e3 100644
--- a/cc/sdk_test.go
+++ b/cc/sdk_test.go
@@ -66,6 +66,7 @@
 		} else {
 			toFile = m.outputFile.Path()
 		}
+		toFile = toFile.RelativeToTop()
 
 		rule := from.Description("link")
 		for _, dep := range rule.Implicits {
diff --git a/cc/snapshot_prebuilt.go b/cc/snapshot_prebuilt.go
index bbb8896..af05102 100644
--- a/cc/snapshot_prebuilt.go
+++ b/cc/snapshot_prebuilt.go
@@ -337,7 +337,8 @@
 		for _, name := range names {
 			snapshotMap[name] = name +
 				getSnapshotNameSuffix(snapshotSuffix+moduleSuffix,
-					s.baseSnapshot.version(), ctx.Arch().ArchType.Name)
+					s.baseSnapshot.version(),
+					ctx.DeviceConfig().Arches()[0].ArchType.String())
 		}
 		return snapshotMap
 	}
@@ -396,7 +397,7 @@
 	Target_arch string
 
 	// Suffix to be added to the module name when exporting to Android.mk, e.g. ".vendor".
-	Androidmk_suffix string
+	Androidmk_suffix string `blueprint:"mutated"`
 
 	// Suffix to be added to the module name, e.g., vendor_shared,
 	// recovery_shared, etc.
@@ -417,6 +418,7 @@
 // will be seen as "libbase.vendor_static.30.arm64" by Soong.
 type baseSnapshotDecorator struct {
 	baseProperties baseSnapshotDecoratorProperties
+	image          snapshotImage
 }
 
 func (p *baseSnapshotDecorator) Name(name string) string {
@@ -447,10 +449,21 @@
 	return p.baseProperties.Androidmk_suffix
 }
 
+func (p *baseSnapshotDecorator) setSnapshotAndroidMkSuffix(ctx android.ModuleContext) {
+	if ctx.OtherModuleDependencyVariantExists([]blueprint.Variation{
+		{Mutator: "image", Variation: android.CoreVariation},
+	}, ctx.Module().(*Module).BaseModuleName()) {
+		p.baseProperties.Androidmk_suffix = p.image.moduleNameSuffix()
+	} else {
+		p.baseProperties.Androidmk_suffix = ""
+	}
+}
+
 // Call this with a module suffix after creating a snapshot module, such as
 // vendorSnapshotSharedSuffix, recoverySnapshotBinarySuffix, etc.
-func (p *baseSnapshotDecorator) init(m *Module, snapshotSuffix, moduleSuffix string) {
-	p.baseProperties.ModuleSuffix = snapshotSuffix + moduleSuffix
+func (p *baseSnapshotDecorator) init(m *Module, image snapshotImage, moduleSuffix string) {
+	p.image = image
+	p.baseProperties.ModuleSuffix = image.moduleNameSuffix() + moduleSuffix
 	m.AddProperties(&p.baseProperties)
 	android.AddLoadHook(m, func(ctx android.LoadHookContext) {
 		vendorSnapshotLoadHook(ctx, p)
@@ -532,6 +545,8 @@
 // As snapshots are prebuilts, this just returns the prebuilt binary after doing things which are
 // done by normal library decorator, e.g. exporting flags.
 func (p *snapshotLibraryDecorator) link(ctx ModuleContext, flags Flags, deps PathDeps, objs Objects) android.Path {
+	p.setSnapshotAndroidMkSuffix(ctx)
+
 	if p.header() {
 		return p.libraryDecorator.link(ctx, flags, deps, objs)
 	}
@@ -614,7 +629,7 @@
 	}
 }
 
-func snapshotLibraryFactory(snapshotSuffix, moduleSuffix string) (*Module, *snapshotLibraryDecorator) {
+func snapshotLibraryFactory(image snapshotImage, moduleSuffix string) (*Module, *snapshotLibraryDecorator) {
 	module, library := NewLibrary(android.DeviceSupported)
 
 	module.stl = nil
@@ -637,7 +652,7 @@
 	module.linker = prebuilt
 	module.installer = prebuilt
 
-	prebuilt.init(module, snapshotSuffix, moduleSuffix)
+	prebuilt.init(module, image, moduleSuffix)
 	module.AddProperties(
 		&prebuilt.properties,
 		&prebuilt.sanitizerProperties,
@@ -651,7 +666,7 @@
 // overrides the vendor variant of the cc shared library with the same name, if BOARD_VNDK_VERSION
 // is set.
 func VendorSnapshotSharedFactory() android.Module {
-	module, prebuilt := snapshotLibraryFactory(vendorSnapshotImageSingleton.moduleNameSuffix(), snapshotSharedSuffix)
+	module, prebuilt := snapshotLibraryFactory(vendorSnapshotImageSingleton, snapshotSharedSuffix)
 	prebuilt.libraryDecorator.BuildOnlyShared()
 	return module.Init()
 }
@@ -661,7 +676,7 @@
 // overrides the recovery variant of the cc shared library with the same name, if BOARD_VNDK_VERSION
 // is set.
 func RecoverySnapshotSharedFactory() android.Module {
-	module, prebuilt := snapshotLibraryFactory(recoverySnapshotImageSingleton.moduleNameSuffix(), snapshotSharedSuffix)
+	module, prebuilt := snapshotLibraryFactory(recoverySnapshotImageSingleton, snapshotSharedSuffix)
 	prebuilt.libraryDecorator.BuildOnlyShared()
 	return module.Init()
 }
@@ -671,7 +686,7 @@
 // overrides the vendor variant of the cc static library with the same name, if BOARD_VNDK_VERSION
 // is set.
 func VendorSnapshotStaticFactory() android.Module {
-	module, prebuilt := snapshotLibraryFactory(vendorSnapshotImageSingleton.moduleNameSuffix(), snapshotStaticSuffix)
+	module, prebuilt := snapshotLibraryFactory(vendorSnapshotImageSingleton, snapshotStaticSuffix)
 	prebuilt.libraryDecorator.BuildOnlyStatic()
 	return module.Init()
 }
@@ -681,7 +696,7 @@
 // overrides the recovery variant of the cc static library with the same name, if BOARD_VNDK_VERSION
 // is set.
 func RecoverySnapshotStaticFactory() android.Module {
-	module, prebuilt := snapshotLibraryFactory(recoverySnapshotImageSingleton.moduleNameSuffix(), snapshotStaticSuffix)
+	module, prebuilt := snapshotLibraryFactory(recoverySnapshotImageSingleton, snapshotStaticSuffix)
 	prebuilt.libraryDecorator.BuildOnlyStatic()
 	return module.Init()
 }
@@ -691,7 +706,7 @@
 // overrides the vendor variant of the cc header library with the same name, if BOARD_VNDK_VERSION
 // is set.
 func VendorSnapshotHeaderFactory() android.Module {
-	module, prebuilt := snapshotLibraryFactory(vendorSnapshotImageSingleton.moduleNameSuffix(), snapshotHeaderSuffix)
+	module, prebuilt := snapshotLibraryFactory(vendorSnapshotImageSingleton, snapshotHeaderSuffix)
 	prebuilt.libraryDecorator.HeaderOnly()
 	return module.Init()
 }
@@ -701,7 +716,7 @@
 // overrides the recovery variant of the cc header library with the same name, if BOARD_VNDK_VERSION
 // is set.
 func RecoverySnapshotHeaderFactory() android.Module {
-	module, prebuilt := snapshotLibraryFactory(recoverySnapshotImageSingleton.moduleNameSuffix(), snapshotHeaderSuffix)
+	module, prebuilt := snapshotLibraryFactory(recoverySnapshotImageSingleton, snapshotHeaderSuffix)
 	prebuilt.libraryDecorator.HeaderOnly()
 	return module.Init()
 }
@@ -739,6 +754,8 @@
 // cc modules' link functions are to link compiled objects into final binaries.
 // As snapshots are prebuilts, this just returns the prebuilt binary
 func (p *snapshotBinaryDecorator) link(ctx ModuleContext, flags Flags, deps PathDeps, objs Objects) android.Path {
+	p.setSnapshotAndroidMkSuffix(ctx)
+
 	if !p.matchesWithDevice(ctx.DeviceConfig()) {
 		return nil
 	}
@@ -767,17 +784,17 @@
 // development/vendor_snapshot/update.py. As a part of vendor snapshot, vendor_snapshot_binary
 // overrides the vendor variant of the cc binary with the same name, if BOARD_VNDK_VERSION is set.
 func VendorSnapshotBinaryFactory() android.Module {
-	return snapshotBinaryFactory(vendorSnapshotImageSingleton.moduleNameSuffix(), snapshotBinarySuffix)
+	return snapshotBinaryFactory(vendorSnapshotImageSingleton, snapshotBinarySuffix)
 }
 
 // recovery_snapshot_binary is a special prebuilt executable binary which is auto-generated by
 // development/vendor_snapshot/update.py. As a part of recovery snapshot, recovery_snapshot_binary
 // overrides the recovery variant of the cc binary with the same name, if BOARD_VNDK_VERSION is set.
 func RecoverySnapshotBinaryFactory() android.Module {
-	return snapshotBinaryFactory(recoverySnapshotImageSingleton.moduleNameSuffix(), snapshotBinarySuffix)
+	return snapshotBinaryFactory(recoverySnapshotImageSingleton, snapshotBinarySuffix)
 }
 
-func snapshotBinaryFactory(snapshotSuffix, moduleSuffix string) android.Module {
+func snapshotBinaryFactory(image snapshotImage, moduleSuffix string) android.Module {
 	module, binary := NewBinary(android.DeviceSupported)
 	binary.baseLinker.Properties.No_libcrt = BoolPtr(true)
 	binary.baseLinker.Properties.Nocrt = BoolPtr(true)
@@ -796,7 +813,7 @@
 	module.stl = nil
 	module.linker = prebuilt
 
-	prebuilt.init(module, snapshotSuffix, moduleSuffix)
+	prebuilt.init(module, image, moduleSuffix)
 	module.AddProperties(&prebuilt.properties)
 	return module.Init()
 }
@@ -832,6 +849,8 @@
 // cc modules' link functions are to link compiled objects into final binaries.
 // As snapshots are prebuilts, this just returns the prebuilt binary
 func (p *snapshotObjectLinker) link(ctx ModuleContext, flags Flags, deps PathDeps, objs Objects) android.Path {
+	p.setSnapshotAndroidMkSuffix(ctx)
+
 	if !p.matchesWithDevice(ctx.DeviceConfig()) {
 		return nil
 	}
@@ -856,7 +875,7 @@
 	}
 	module.linker = prebuilt
 
-	prebuilt.init(module, vendorSnapshotImageSingleton.moduleNameSuffix(), snapshotObjectSuffix)
+	prebuilt.init(module, vendorSnapshotImageSingleton, snapshotObjectSuffix)
 	module.AddProperties(&prebuilt.properties)
 	return module.Init()
 }
@@ -874,7 +893,7 @@
 	}
 	module.linker = prebuilt
 
-	prebuilt.init(module, recoverySnapshotImageSingleton.moduleNameSuffix(), snapshotObjectSuffix)
+	prebuilt.init(module, recoverySnapshotImageSingleton, snapshotObjectSuffix)
 	module.AddProperties(&prebuilt.properties)
 	return module.Init()
 }
diff --git a/cc/stl.go b/cc/stl.go
index 75fab17..594231d 100644
--- a/cc/stl.go
+++ b/cc/stl.go
@@ -188,12 +188,7 @@
 		if needsLibAndroidSupport(ctx) {
 			deps.StaticLibs = append(deps.StaticLibs, "ndk_libandroid_support")
 		}
-		// TODO: Switch the NDK over to the LLVM unwinder for non-arm32 architectures.
-		if ctx.Arch().ArchType == android.Arm {
-			deps.StaticLibs = append(deps.StaticLibs, "ndk_libunwind")
-		} else {
-			deps.StaticLibs = append(deps.StaticLibs, "libgcc_stripped")
-		}
+		deps.StaticLibs = append(deps.StaticLibs, "ndk_libunwind")
 	default:
 		panic(fmt.Errorf("Unknown stl: %q", stl.Properties.SelectedStl))
 	}
diff --git a/cc/testing.go b/cc/testing.go
index d8adc61..ff32bff 100644
--- a/cc/testing.go
+++ b/cc/testing.go
@@ -63,17 +63,6 @@
 func commonDefaultModules() string {
 	return `
 		toolchain_library {
-			name: "libatomic",
-			defaults: ["linux_bionic_supported"],
-			vendor_available: true,
-			vendor_ramdisk_available: true,
-			product_available: true,
-			recovery_available: true,
-			native_bridge_supported: true,
-			src: "",
-		}
-
-		toolchain_library {
 			name: "libcompiler_rt-extras",
 			vendor_available: true,
 			vendor_ramdisk_available: true,
@@ -200,29 +189,6 @@
 			srcs: [""],
 		}
 
-		toolchain_library {
-			name: "libgcc",
-			defaults: ["linux_bionic_supported"],
-			vendor_available: true,
-			product_available: true,
-			recovery_available: true,
-			src: "",
-			apex_available: [
-				"//apex_available:platform",
-				"//apex_available:anyapex",
-			],
-		}
-
-		toolchain_library {
-			name: "libgcc_stripped",
-			defaults: ["linux_bionic_supported"],
-			vendor_available: true,
-			product_available: true,
-			recovery_available: true,
-			sdk_version: "current",
-			src: "",
-		}
-
 		cc_library {
 			name: "libc",
 			defaults: ["linux_bionic_supported"],
@@ -398,16 +364,6 @@
 				"//apex_available:anyapex",
 			],
 		}
-		cc_library {
-			name: "libunwind_llvm",
-			no_libcrt: true,
-			nocrt: true,
-			system_shared_libs: [],
-			stl: "none",
-			vendor_available: true,
-			product_available: true,
-			recovery_available: true,
-		}
 
 		cc_defaults {
 			name: "crt_defaults",
@@ -619,11 +575,26 @@
 
 		RegisterVndkLibraryTxtTypes(ctx)
 	}),
+
+	// Additional files needed in tests that disallow non-existent source files.
+	// This includes files that are needed by all, or at least most, instances of a cc module type.
+	android.MockFS{
+		// Needed for ndk_prebuilt_(shared|static)_stl.
+		"prebuilts/ndk/current/sources/cxx-stl/llvm-libc++/libs": nil,
+	}.AddToFixture(),
 )
 
 // Preparer that will define default cc modules, e.g. standard prebuilt modules.
 var PrepareForTestWithCcDefaultModules = android.GroupFixturePreparers(
 	PrepareForTestWithCcBuildComponents,
+
+	// Additional files needed in tests that disallow non-existent source.
+	android.MockFS{
+		"defaults/cc/common/libc.map.txt":  nil,
+		"defaults/cc/common/libdl.map.txt": nil,
+		"defaults/cc/common/libm.map.txt":  nil,
+	}.AddToFixture(),
+
 	// Place the default cc test modules that are common to all platforms in a location that will not
 	// conflict with default test modules defined by other packages.
 	android.FixtureAddTextFile(DefaultCcCommonTestModulesDir+"Android.bp", commonDefaultModules()),
diff --git a/cc/vendor_snapshot.go b/cc/vendor_snapshot.go
index 4014fe0..3d31be4 100644
--- a/cc/vendor_snapshot.go
+++ b/cc/vendor_snapshot.go
@@ -238,7 +238,6 @@
 type snapshotJsonFlags struct {
 	ModuleName          string `json:",omitempty"`
 	RelativeInstallPath string `json:",omitempty"`
-	AndroidMkSuffix     string `json:",omitempty"`
 
 	// library flags
 	ExportedDirs       []string `json:",omitempty"`
@@ -352,7 +351,6 @@
 		} else {
 			prop.RelativeInstallPath = m.RelativeInstallPath()
 		}
-		prop.AndroidMkSuffix = m.Properties.SubName
 		prop.RuntimeLibs = m.Properties.SnapshotRuntimeLibs
 		prop.Required = m.RequiredModuleNames()
 		for _, path := range m.InitRc() {
diff --git a/cc/vendor_snapshot_test.go b/cc/vendor_snapshot_test.go
index 0833277..8f77c28 100644
--- a/cc/vendor_snapshot_test.go
+++ b/cc/vendor_snapshot_test.go
@@ -86,15 +86,15 @@
 		symbol_file: "",
 	}
 `
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 	ctx := testCcWithConfig(t, config)
 
 	// Check Vendor snapshot output.
 
 	snapshotDir := "vendor-snapshot"
-	snapshotVariantPath := filepath.Join(buildDir, snapshotDir, "arm64")
+	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
 	snapshotSingleton := ctx.SingletonForTests("vendor-snapshot")
 
 	var jsonFiles []string
@@ -108,7 +108,7 @@
 		archDir := fmt.Sprintf("arch-%s-%s", archType, archVariant)
 
 		// For shared libraries, only non-VNDK vendor_available modules are captured
-		sharedVariant := fmt.Sprintf("android_vendor.VER_%s_%s_shared", archType, archVariant)
+		sharedVariant := fmt.Sprintf("android_vendor.29_%s_%s_shared", archType, archVariant)
 		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
 		checkSnapshot(t, ctx, snapshotSingleton, "libvendor", "libvendor.so", sharedDir, sharedVariant)
 		checkSnapshot(t, ctx, snapshotSingleton, "libvendor_available", "libvendor_available.so", sharedDir, sharedVariant)
@@ -121,8 +121,8 @@
 
 		// For static libraries, all vendor:true and vendor_available modules (including VNDK) are captured.
 		// Also cfi variants are captured, except for prebuilts like toolchain_library
-		staticVariant := fmt.Sprintf("android_vendor.VER_%s_%s_static", archType, archVariant)
-		staticCfiVariant := fmt.Sprintf("android_vendor.VER_%s_%s_static_cfi", archType, archVariant)
+		staticVariant := fmt.Sprintf("android_vendor.29_%s_%s_static", archType, archVariant)
+		staticCfiVariant := fmt.Sprintf("android_vendor.29_%s_%s_static_cfi", archType, archVariant)
 		staticDir := filepath.Join(snapshotVariantPath, archDir, "static")
 		checkSnapshot(t, ctx, snapshotSingleton, "libb", "libb.a", staticDir, staticVariant)
 		checkSnapshot(t, ctx, snapshotSingleton, "libvndk", "libvndk.a", staticDir, staticVariant)
@@ -142,7 +142,7 @@
 
 		// For binary executables, all vendor:true and vendor_available modules are captured.
 		if archType == "arm64" {
-			binaryVariant := fmt.Sprintf("android_vendor.VER_%s_%s", archType, archVariant)
+			binaryVariant := fmt.Sprintf("android_vendor.29_%s_%s", archType, archVariant)
 			binaryDir := filepath.Join(snapshotVariantPath, archDir, "binary")
 			checkSnapshot(t, ctx, snapshotSingleton, "vendor_bin", "vendor_bin", binaryDir, binaryVariant)
 			checkSnapshot(t, ctx, snapshotSingleton, "vendor_available_bin", "vendor_available_bin", binaryDir, binaryVariant)
@@ -156,7 +156,7 @@
 		jsonFiles = append(jsonFiles, filepath.Join(headerDir, "libvendor_headers.json"))
 
 		// For object modules, all vendor:true and vendor_available modules are captured.
-		objectVariant := fmt.Sprintf("android_vendor.VER_%s_%s", archType, archVariant)
+		objectVariant := fmt.Sprintf("android_vendor.29_%s_%s", archType, archVariant)
 		objectDir := filepath.Join(snapshotVariantPath, archDir, "object")
 		checkSnapshot(t, ctx, snapshotSingleton, "obj", "obj.o", objectDir, objectVariant)
 		jsonFiles = append(jsonFiles, filepath.Join(objectDir, "obj.o.json"))
@@ -212,9 +212,9 @@
 		nocrt: true,
 	}
 `
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 	config.TestProductVariables.DirectedVendorSnapshot = true
 	config.TestProductVariables.VendorSnapshotModules = make(map[string]bool)
 	config.TestProductVariables.VendorSnapshotModules["libvendor"] = true
@@ -224,7 +224,7 @@
 	// Check Vendor snapshot output.
 
 	snapshotDir := "vendor-snapshot"
-	snapshotVariantPath := filepath.Join(buildDir, snapshotDir, "arm64")
+	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
 	snapshotSingleton := ctx.SingletonForTests("vendor-snapshot")
 
 	var includeJsonFiles []string
@@ -237,7 +237,7 @@
 		archVariant := arch[1]
 		archDir := fmt.Sprintf("arch-%s-%s", archType, archVariant)
 
-		sharedVariant := fmt.Sprintf("android_vendor.VER_%s_%s_shared", archType, archVariant)
+		sharedVariant := fmt.Sprintf("android_vendor.29_%s_%s_shared", archType, archVariant)
 		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
 
 		// Included modules
@@ -271,7 +271,6 @@
 			enabled: true,
 		},
 		nocrt: true,
-		compile_multilib: "64",
 	}
 
 	cc_library {
@@ -281,7 +280,6 @@
 		no_libcrt: true,
 		stl: "none",
 		system_shared_libs: [],
-		compile_multilib: "64",
 	}
 
 	cc_library {
@@ -291,6 +289,25 @@
 		no_libcrt: true,
 		stl: "none",
 		system_shared_libs: [],
+	}
+
+	cc_library {
+		name: "lib32",
+		vendor: true,
+		nocrt: true,
+		no_libcrt: true,
+		stl: "none",
+		system_shared_libs: [],
+		compile_multilib: "32",
+	}
+
+	cc_library {
+		name: "lib64",
+		vendor: true,
+		nocrt: true,
+		no_libcrt: true,
+		stl: "none",
+		system_shared_libs: [],
 		compile_multilib: "64",
 	}
 
@@ -301,14 +318,23 @@
 		no_libcrt: true,
 		stl: "none",
 		system_shared_libs: [],
-		compile_multilib: "64",
+	}
+
+	cc_binary {
+		name: "bin32",
+		vendor: true,
+		nocrt: true,
+		no_libcrt: true,
+		stl: "none",
+		system_shared_libs: [],
+		compile_multilib: "32",
 	}
 `
 
 	vndkBp := `
 	vndk_prebuilt_shared {
 		name: "libvndk",
-		version: "BOARD",
+		version: "28",
 		target_arch: "arm64",
 		vendor_available: true,
 		product_available: true,
@@ -320,13 +346,17 @@
 				srcs: ["libvndk.so"],
 				export_include_dirs: ["include/libvndk"],
 			},
+			arm: {
+				srcs: ["libvndk.so"],
+				export_include_dirs: ["include/libvndk"],
+			},
 		},
 	}
 
 	// old snapshot module which has to be ignored
 	vndk_prebuilt_shared {
 		name: "libvndk",
-		version: "OLD",
+		version: "26",
 		target_arch: "arm64",
 		vendor_available: true,
 		product_available: true,
@@ -338,6 +368,28 @@
 				srcs: ["libvndk.so"],
 				export_include_dirs: ["include/libvndk"],
 			},
+			arm: {
+				srcs: ["libvndk.so"],
+				export_include_dirs: ["include/libvndk"],
+			},
+		},
+	}
+
+	// different arch snapshot which has to be ignored
+	vndk_prebuilt_shared {
+		name: "libvndk",
+		version: "28",
+		target_arch: "arm",
+		vendor_available: true,
+		product_available: true,
+		vndk: {
+			enabled: true,
+		},
+		arch: {
+			arm: {
+				srcs: ["libvndk.so"],
+				export_include_dirs: ["include/libvndk"],
+			},
 		},
 	}
 `
@@ -350,7 +402,6 @@
 		no_libcrt: true,
 		stl: "none",
 		system_shared_libs: [],
-		compile_multilib: "64",
 	}
 
 	cc_library_shared {
@@ -362,7 +413,14 @@
 		system_shared_libs: [],
 		shared_libs: ["libvndk", "libvendor_available"],
 		static_libs: ["libvendor", "libvendor_without_snapshot"],
-		compile_multilib: "64",
+		arch: {
+			arm64: {
+				shared_libs: ["lib64"],
+			},
+			arm: {
+				shared_libs: ["lib32"],
+			},
+		},
 		srcs: ["client.cpp"],
 	}
 
@@ -374,49 +432,77 @@
 		stl: "none",
 		system_shared_libs: [],
 		static_libs: ["libvndk"],
-		compile_multilib: "64",
 		srcs: ["bin.cpp"],
 	}
 
 	vendor_snapshot {
 		name: "vendor_snapshot",
-		compile_multilib: "first",
-		version: "BOARD",
-		vndk_libs: [
-			"libvndk",
-		],
-		static_libs: [
-			"libvendor",
-			"libvendor_available",
-			"libvndk",
-		],
-		shared_libs: [
-			"libvendor",
-			"libvendor_available",
-		],
-		binaries: [
-			"bin",
-		],
+		version: "28",
+		arch: {
+			arm64: {
+				vndk_libs: [
+					"libvndk",
+				],
+				static_libs: [
+					"libvendor",
+					"libvendor_available",
+					"libvndk",
+					"lib64",
+				],
+				shared_libs: [
+					"libvendor",
+					"libvendor_available",
+					"lib64",
+				],
+				binaries: [
+					"bin",
+				],
+			},
+			arm: {
+				vndk_libs: [
+					"libvndk",
+				],
+				static_libs: [
+					"libvendor",
+					"libvendor_available",
+					"libvndk",
+					"lib32",
+				],
+				shared_libs: [
+					"libvendor",
+					"libvendor_available",
+					"lib32",
+				],
+				binaries: [
+					"bin32",
+				],
+			},
+		}
 	}
 
 	vendor_snapshot_static {
 		name: "libvndk",
-		version: "BOARD",
+		version: "28",
 		target_arch: "arm64",
+		compile_multilib: "both",
 		vendor: true,
 		arch: {
 			arm64: {
 				src: "libvndk.a",
 				export_include_dirs: ["include/libvndk"],
 			},
+			arm: {
+				src: "libvndk.a",
+				export_include_dirs: ["include/libvndk"],
+			},
 		},
 	}
 
 	vendor_snapshot_shared {
 		name: "libvendor",
-		version: "BOARD",
+		version: "28",
 		target_arch: "arm64",
-		compile_multilib: "64",
+		compile_multilib: "both",
 		vendor: true,
 		shared_libs: [
 			"libvendor_without_snapshot",
@@ -428,54 +514,124 @@
 				src: "libvendor.so",
 				export_include_dirs: ["include/libvendor"],
 			},
+			arm: {
+				src: "libvendor.so",
+				export_include_dirs: ["include/libvendor"],
+			},
+		},
+	}
+
+	vendor_snapshot_static {
+		name: "lib32",
+		version: "28",
+		target_arch: "arm64",
+		compile_multilib: "32",
+		vendor: true,
+		arch: {
+			arm: {
+				src: "lib32.a",
+			},
+		},
+	}
+
+	vendor_snapshot_shared {
+		name: "lib32",
+		version: "28",
+		target_arch: "arm64",
+		compile_multilib: "32",
+		vendor: true,
+		arch: {
+			arm: {
+				src: "lib32.so",
+			},
+		},
+	}
+
+	vendor_snapshot_static {
+		name: "lib64",
+		version: "28",
+		target_arch: "arm64",
+		compile_multilib: "64",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "lib64.a",
+			},
+		},
+	}
+
+	vendor_snapshot_shared {
+		name: "lib64",
+		version: "28",
+		target_arch: "arm64",
+		compile_multilib: "64",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "lib64.so",
+			},
 		},
 	}
 
 	vendor_snapshot_static {
 		name: "libvendor",
-		version: "BOARD",
+		version: "28",
 		target_arch: "arm64",
+		compile_multilib: "both",
 		vendor: true,
 		arch: {
 			arm64: {
 				src: "libvendor.a",
 				export_include_dirs: ["include/libvendor"],
 			},
+			arm: {
+				src: "libvendor.a",
+				export_include_dirs: ["include/libvendor"],
+			},
 		},
 	}
 
 	vendor_snapshot_shared {
 		name: "libvendor_available",
-		androidmk_suffix: ".vendor",
-		version: "BOARD",
+		version: "28",
 		target_arch: "arm64",
+		compile_multilib: "both",
 		vendor: true,
 		arch: {
 			arm64: {
 				src: "libvendor_available.so",
 				export_include_dirs: ["include/libvendor"],
 			},
+			arm: {
+				src: "libvendor_available.so",
+				export_include_dirs: ["include/libvendor"],
+			},
 		},
 	}
 
 	vendor_snapshot_static {
 		name: "libvendor_available",
-		androidmk_suffix: ".vendor",
-		version: "BOARD",
+		version: "28",
 		target_arch: "arm64",
+		compile_multilib: "both",
 		vendor: true,
 		arch: {
 			arm64: {
 				src: "libvendor_available.a",
 				export_include_dirs: ["include/libvendor"],
 			},
+			arm: {
+				src: "libvendor_available.so",
+				export_include_dirs: ["include/libvendor"],
+			},
 		},
 	}
 
 	vendor_snapshot_binary {
 		name: "bin",
-		version: "BOARD",
+		version: "28",
 		target_arch: "arm64",
+		compile_multilib: "64",
 		vendor: true,
 		arch: {
 			arm64: {
@@ -484,11 +640,39 @@
 		},
 	}
 
+	vendor_snapshot_binary {
+		name: "bin32",
+		version: "28",
+		target_arch: "arm64",
+		compile_multilib: "32",
+		vendor: true,
+		arch: {
+			arm: {
+				src: "bin32",
+			},
+		},
+	}
+
 	// old snapshot module which has to be ignored
 	vendor_snapshot_binary {
 		name: "bin",
-		version: "OLD",
+		version: "26",
 		target_arch: "arm64",
+		compile_multilib: "first",
+		vendor: true,
+		arch: {
+			arm64: {
+				src: "bin",
+			},
+		},
+	}
+
+	// different arch snapshot which has to be ignored
+	vendor_snapshot_binary {
+		name: "bin",
+		version: "28",
+		target_arch: "arm",
+		compile_multilib: "first",
 		vendor: true,
 		arch: {
 			arm64: {
@@ -504,6 +688,7 @@
 		"framework/Android.bp":         []byte(frameworkBp),
 		"vendor/Android.bp":            []byte(vendorProprietaryBp),
 		"vendor/bin":                   nil,
+		"vendor/bin32":                 nil,
 		"vendor/bin.cpp":               nil,
 		"vendor/client.cpp":            nil,
 		"vendor/include/libvndk/a.h":   nil,
@@ -511,14 +696,18 @@
 		"vendor/libvndk.a":             nil,
 		"vendor/libvendor.a":           nil,
 		"vendor/libvendor.so":          nil,
+		"vendor/lib32.a":               nil,
+		"vendor/lib32.so":              nil,
+		"vendor/lib64.a":               nil,
+		"vendor/lib64.so":              nil,
 		"vndk/Android.bp":              []byte(vndkBp),
 		"vndk/include/libvndk/a.h":     nil,
 		"vndk/libvndk.so":              nil,
 	}
 
-	config := TestConfig(buildDir, android.Android, nil, "", mockFS)
-	config.TestProductVariables.DeviceVndkVersion = StringPtr("BOARD")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	config := TestConfig(t.TempDir(), android.Android, nil, "", mockFS)
+	config.TestProductVariables.DeviceVndkVersion = StringPtr("28")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 	ctx := CreateTestContext(config)
 	ctx.Register()
 
@@ -527,11 +716,14 @@
 	_, errs = ctx.PrepareBuildActions(config)
 	android.FailIfErrored(t, errs)
 
-	sharedVariant := "android_vendor.BOARD_arm64_armv8-a_shared"
-	staticVariant := "android_vendor.BOARD_arm64_armv8-a_static"
-	binaryVariant := "android_vendor.BOARD_arm64_armv8-a"
+	sharedVariant := "android_vendor.28_arm64_armv8-a_shared"
+	staticVariant := "android_vendor.28_arm64_armv8-a_static"
+	binaryVariant := "android_vendor.28_arm64_armv8-a"
 
-	// libclient uses libvndk.vndk.BOARD.arm64, libvendor.vendor_static.BOARD.arm64, libvendor_without_snapshot
+	shared32Variant := "android_vendor.28_arm_armv7-a-neon_shared"
+	binary32Variant := "android_vendor.28_arm_armv7-a-neon"
+
+	// libclient uses libvndk.vndk.28.arm64, libvendor.vendor_static.28.arm64, libvendor_without_snapshot
 	libclientCcFlags := ctx.ModuleForTests("libclient", sharedVariant).Rule("cc").Args["cFlags"]
 	for _, includeFlags := range []string{
 		"-Ivndk/include/libvndk",     // libvndk
@@ -545,8 +737,8 @@
 
 	libclientLdFlags := ctx.ModuleForTests("libclient", sharedVariant).Rule("ld").Args["libFlags"]
 	for _, input := range [][]string{
-		[]string{sharedVariant, "libvndk.vndk.BOARD.arm64"},
-		[]string{staticVariant, "libvendor.vendor_static.BOARD.arm64"},
+		[]string{sharedVariant, "libvndk.vndk.28.arm64"},
+		[]string{staticVariant, "libvendor.vendor_static.28.arm64"},
 		[]string{staticVariant, "libvendor_without_snapshot"},
 	} {
 		outputPaths := getOutputPaths(ctx, input[0] /* variant */, []string{input[1]} /* module name */)
@@ -556,7 +748,7 @@
 	}
 
 	libclientAndroidMkSharedLibs := ctx.ModuleForTests("libclient", sharedVariant).Module().(*Module).Properties.AndroidMkSharedLibs
-	if g, w := libclientAndroidMkSharedLibs, []string{"libvndk.vendor", "libvendor_available.vendor"}; !reflect.DeepEqual(g, w) {
+	if g, w := libclientAndroidMkSharedLibs, []string{"libvndk.vendor", "libvendor_available.vendor", "lib64"}; !reflect.DeepEqual(g, w) {
 		t.Errorf("wanted libclient AndroidMkSharedLibs %q, got %q", w, g)
 	}
 
@@ -565,7 +757,12 @@
 		t.Errorf("wanted libclient AndroidMkStaticLibs %q, got %q", w, g)
 	}
 
-	// bin_without_snapshot uses libvndk.vendor_static.BOARD.arm64
+	libclient32AndroidMkSharedLibs := ctx.ModuleForTests("libclient", shared32Variant).Module().(*Module).Properties.AndroidMkSharedLibs
+	if g, w := libclient32AndroidMkSharedLibs, []string{"libvndk.vendor", "libvendor_available.vendor", "lib32"}; !reflect.DeepEqual(g, w) {
+		t.Errorf("wanted libclient32 AndroidMkSharedLibs %q, got %q", w, g)
+	}
+
+	// bin_without_snapshot uses libvndk.vendor_static.28.arm64
 	binWithoutSnapshotCcFlags := ctx.ModuleForTests("bin_without_snapshot", binaryVariant).Rule("cc").Args["cFlags"]
 	if !strings.Contains(binWithoutSnapshotCcFlags, "-Ivendor/include/libvndk") {
 		t.Errorf("flags for bin_without_snapshot must contain %#v, but was %#v.",
@@ -573,28 +770,37 @@
 	}
 
 	binWithoutSnapshotLdFlags := ctx.ModuleForTests("bin_without_snapshot", binaryVariant).Rule("ld").Args["libFlags"]
-	libVndkStaticOutputPaths := getOutputPaths(ctx, staticVariant, []string{"libvndk.vendor_static.BOARD.arm64"})
+	libVndkStaticOutputPaths := getOutputPaths(ctx, staticVariant, []string{"libvndk.vendor_static.28.arm64"})
 	if !strings.Contains(binWithoutSnapshotLdFlags, libVndkStaticOutputPaths[0].String()) {
 		t.Errorf("libflags for bin_without_snapshot must contain %#v, but was %#v",
 			libVndkStaticOutputPaths[0], binWithoutSnapshotLdFlags)
 	}
 
-	// libvendor.so is installed by libvendor.vendor_shared.BOARD.arm64
-	ctx.ModuleForTests("libvendor.vendor_shared.BOARD.arm64", sharedVariant).Output("libvendor.so")
+	// libvendor.so is installed by libvendor.vendor_shared.28.arm64
+	ctx.ModuleForTests("libvendor.vendor_shared.28.arm64", sharedVariant).Output("libvendor.so")
 
-	// libvendor_available.so is installed by libvendor_available.vendor_shared.BOARD.arm64
-	ctx.ModuleForTests("libvendor_available.vendor_shared.BOARD.arm64", sharedVariant).Output("libvendor_available.so")
+	// lib64.so is installed by lib64.vendor_shared.28.arm64
+	ctx.ModuleForTests("lib64.vendor_shared.28.arm64", sharedVariant).Output("lib64.so")
+
+	// lib32.so is installed by lib32.vendor_shared.28.arm64
+	ctx.ModuleForTests("lib32.vendor_shared.28.arm64", shared32Variant).Output("lib32.so")
+
+	// libvendor_available.so is installed by libvendor_available.vendor_shared.28.arm64
+	ctx.ModuleForTests("libvendor_available.vendor_shared.28.arm64", sharedVariant).Output("libvendor_available.so")
 
 	// libvendor_without_snapshot.so is installed by libvendor_without_snapshot
 	ctx.ModuleForTests("libvendor_without_snapshot", sharedVariant).Output("libvendor_without_snapshot.so")
 
-	// bin is installed by bin.vendor_binary.BOARD.arm64
-	ctx.ModuleForTests("bin.vendor_binary.BOARD.arm64", binaryVariant).Output("bin")
+	// bin is installed by bin.vendor_binary.28.arm64
+	ctx.ModuleForTests("bin.vendor_binary.28.arm64", binaryVariant).Output("bin")
+
+	// bin32 is installed by bin32.vendor_binary.28.arm64
+	ctx.ModuleForTests("bin32.vendor_binary.28.arm64", binary32Variant).Output("bin32")
 
 	// bin_without_snapshot is installed by bin_without_snapshot
 	ctx.ModuleForTests("bin_without_snapshot", binaryVariant).Output("bin_without_snapshot")
 
-	// libvendor, libvendor_available and bin don't have vendor.BOARD variant
+	// libvendor, libvendor_available and bin don't have vendor.28 variant
 	libvendorVariants := ctx.ModuleVariantsForTests("libvendor")
 	if inList(sharedVariant, libvendorVariants) {
 		t.Errorf("libvendor must not have variant %#v, but it does", sharedVariant)
@@ -617,7 +823,7 @@
 		name: "libsnapshot",
 		vendor: true,
 		target_arch: "arm64",
-		version: "BOARD",
+		version: "28",
 		arch: {
 			arm64: {
 				src: "libsnapshot.a",
@@ -628,19 +834,19 @@
 		},
 	}
 `
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
-	config.TestProductVariables.DeviceVndkVersion = StringPtr("BOARD")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
+	config.TestProductVariables.DeviceVndkVersion = StringPtr("28")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 	ctx := testCcWithConfig(t, config)
 
 	// Check non-cfi and cfi variant.
-	staticVariant := "android_vendor.BOARD_arm64_armv8-a_static"
-	staticCfiVariant := "android_vendor.BOARD_arm64_armv8-a_static_cfi"
+	staticVariant := "android_vendor.28_arm64_armv8-a_static"
+	staticCfiVariant := "android_vendor.28_arm64_armv8-a_static_cfi"
 
-	staticModule := ctx.ModuleForTests("libsnapshot.vendor_static.BOARD.arm64", staticVariant).Module().(*Module)
+	staticModule := ctx.ModuleForTests("libsnapshot.vendor_static.28.arm64", staticVariant).Module().(*Module)
 	assertString(t, staticModule.outputFile.Path().Base(), "libsnapshot.a")
 
-	staticCfiModule := ctx.ModuleForTests("libsnapshot.vendor_static.BOARD.arm64", staticCfiVariant).Module().(*Module)
+	staticCfiModule := ctx.ModuleForTests("libsnapshot.vendor_static.28.arm64", staticCfiVariant).Module().(*Module)
 	assertString(t, staticCfiModule.outputFile.Path().Base(), "libsnapshot.cfi.a")
 }
 
@@ -707,9 +913,9 @@
 		"device/vendor.cpp":     nil,
 	}
 
-	config := TestConfig(buildDir, android.Android, nil, "", mockFS)
+	config := TestConfig(t.TempDir(), android.Android, nil, "", mockFS)
 	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 	ctx := CreateTestContext(config)
 	ctx.Register()
 
@@ -730,7 +936,7 @@
 	// Verify the content of the vendor snapshot.
 
 	snapshotDir := "vendor-snapshot"
-	snapshotVariantPath := filepath.Join(buildDir, snapshotDir, "arm64")
+	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
 	snapshotSingleton := ctx.SingletonForTests("vendor-snapshot")
 
 	var includeJsonFiles []string
@@ -744,7 +950,7 @@
 		archVariant := arch[1]
 		archDir := fmt.Sprintf("arch-%s-%s", archType, archVariant)
 
-		sharedVariant := fmt.Sprintf("android_vendor.VER_%s_%s_shared", archType, archVariant)
+		sharedVariant := fmt.Sprintf("android_vendor.29_%s_%s_shared", archType, archVariant)
 		sharedDir := filepath.Join(snapshotVariantPath, archDir, "shared")
 
 		// Included modules
@@ -799,9 +1005,9 @@
 		"device/vendor.cpp": nil,
 	}
 
-	config := TestConfig(buildDir, android.Android, nil, "", mockFS)
+	config := TestConfig(t.TempDir(), android.Android, nil, "", mockFS)
 	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 	ctx := CreateTestContext(config)
 	ctx.Register()
 
@@ -873,15 +1079,15 @@
 		recovery_available: true,
 	}
 `
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	config.TestProductVariables.RecoverySnapshotVersion = StringPtr("current")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 	ctx := testCcWithConfig(t, config)
 
 	// Check Recovery snapshot output.
 
 	snapshotDir := "recovery-snapshot"
-	snapshotVariantPath := filepath.Join(buildDir, snapshotDir, "arm64")
+	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
 	snapshotSingleton := ctx.SingletonForTests("recovery-snapshot")
 
 	var jsonFiles []string
@@ -991,9 +1197,9 @@
 		"device/recovery.cpp":   nil,
 	}
 
-	config := TestConfig(buildDir, android.Android, nil, "", mockFS)
+	config := TestConfig(t.TempDir(), android.Android, nil, "", mockFS)
 	config.TestProductVariables.RecoverySnapshotVersion = StringPtr("current")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 	ctx := CreateTestContext(config)
 	ctx.Register()
 
@@ -1014,7 +1220,7 @@
 	// Verify the content of the recovery snapshot.
 
 	snapshotDir := "recovery-snapshot"
-	snapshotVariantPath := filepath.Join(buildDir, snapshotDir, "arm64")
+	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
 	snapshotSingleton := ctx.SingletonForTests("recovery-snapshot")
 
 	var includeJsonFiles []string
@@ -1091,10 +1297,10 @@
 		nocrt: true,
 	}
 `
-	config := TestConfig(buildDir, android.Android, nil, bp, nil)
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
 	config.TestProductVariables.DeviceVndkVersion = StringPtr("current")
 	config.TestProductVariables.RecoverySnapshotVersion = StringPtr("current")
-	config.TestProductVariables.Platform_vndk_version = StringPtr("VER")
+	config.TestProductVariables.Platform_vndk_version = StringPtr("29")
 	config.TestProductVariables.DirectedRecoverySnapshot = true
 	config.TestProductVariables.RecoverySnapshotModules = make(map[string]bool)
 	config.TestProductVariables.RecoverySnapshotModules["librecovery"] = true
@@ -1104,7 +1310,7 @@
 	// Check recovery snapshot output.
 
 	snapshotDir := "recovery-snapshot"
-	snapshotVariantPath := filepath.Join(buildDir, snapshotDir, "arm64")
+	snapshotVariantPath := filepath.Join("out/soong", snapshotDir, "arm64")
 	snapshotSingleton := ctx.SingletonForTests("recovery-snapshot")
 
 	var includeJsonFiles []string
diff --git a/cmd/merge_zips/Android.bp b/cmd/merge_zips/Android.bp
index 930d040..c516f99 100644
--- a/cmd/merge_zips/Android.bp
+++ b/cmd/merge_zips/Android.bp
@@ -22,7 +22,7 @@
         "android-archive-zip",
         "blueprint-pathtools",
         "soong-jar",
-        "soong-zip",
+        "soong-response",
     ],
     srcs: [
         "merge_zips.go",
diff --git a/cmd/merge_zips/merge_zips.go b/cmd/merge_zips/merge_zips.go
index 274c8ee..712c7fc 100644
--- a/cmd/merge_zips/merge_zips.go
+++ b/cmd/merge_zips/merge_zips.go
@@ -25,12 +25,14 @@
 	"os"
 	"path/filepath"
 	"sort"
+	"strings"
+
+	"android/soong/response"
 
 	"github.com/google/blueprint/pathtools"
 
 	"android/soong/jar"
 	"android/soong/third_party/zip"
-	soongZip "android/soong/zip"
 )
 
 // Input zip: we can open it, close it, and obtain an array of entries
@@ -690,15 +692,20 @@
 	inputs := make([]string, 0)
 	for _, input := range args[1:] {
 		if input[0] == '@' {
-			bytes, err := ioutil.ReadFile(input[1:])
+			f, err := os.Open(strings.TrimPrefix(input[1:], "@"))
 			if err != nil {
 				log.Fatal(err)
 			}
-			inputs = append(inputs, soongZip.ReadRespFile(bytes)...)
-			continue
+
+			rspInputs, err := response.ReadRspFile(f)
+			f.Close()
+			if err != nil {
+				log.Fatal(err)
+			}
+			inputs = append(inputs, rspInputs...)
+		} else {
+			inputs = append(inputs, input)
 		}
-		inputs = append(inputs, input)
-		continue
 	}
 
 	log.SetFlags(log.Lshortfile)
diff --git a/cmd/pom2bp/pom2bp.go b/cmd/pom2bp/pom2bp.go
index d341b8c..d31489e 100644
--- a/cmd/pom2bp/pom2bp.go
+++ b/cmd/pom2bp/pom2bp.go
@@ -144,6 +144,7 @@
 var hostAndDeviceModuleNames = HostAndDeviceModuleNames{}
 
 var sdkVersion string
+var defaultMinSdkVersion string
 var useVersion string
 var staticDeps bool
 var jetifier bool
@@ -286,6 +287,10 @@
 	return sdkVersion
 }
 
+func (p Pom) DefaultMinSdkVersion() string {
+	return defaultMinSdkVersion
+}
+
 func (p Pom) Jetifier() bool {
 	return jetifier
 }
@@ -396,6 +401,8 @@
         {{- end}}
     ],
     {{- end}}
+    {{- else if not .IsHostOnly}}
+    min_sdk_version: "{{.DefaultMinSdkVersion}}",
     {{- end}}
 }
 `))
@@ -437,6 +444,8 @@
         {{- end}}
     ],
     {{- end}}
+    {{- else if not .IsHostOnly}}
+    min_sdk_version: "{{.DefaultMinSdkVersion}}",
     {{- end}}
 }
 
@@ -457,7 +466,7 @@
     min_sdk_version: "{{.MinSdkVersion}}",
     manifest: "manifests/{{.BpName}}/AndroidManifest.xml",
     {{- else if not .IsHostOnly}}
-    min_sdk_version: "24",
+    min_sdk_version: "{{.DefaultMinSdkVersion}}",
     {{- end}}
     {{- end}}
     static_libs: [
@@ -598,6 +607,8 @@
      This may be specified multiple times to declare these dependencies.
   -sdk-version <version>
      Sets sdk_version: "<version>" for all modules.
+  -default-min-sdk-version
+     The default min_sdk_version to use for a module if one cannot be mined from AndroidManifest.xml
   -use-version <version>
      If the maven directory contains multiple versions of artifacts and their pom files,
      -use-version can be used to only write Android.bp files for a specific version of those artifacts.
@@ -622,6 +633,7 @@
 	flag.Var(&hostModuleNames, "host", "Specifies that the corresponding module (specified in the form 'module.group:module.artifact') is a host module")
 	flag.Var(&hostAndDeviceModuleNames, "host-and-device", "Specifies that the corresponding module (specified in the form 'module.group:module.artifact') is both a host and device module.")
 	flag.StringVar(&sdkVersion, "sdk-version", "", "What to write to sdk_version")
+	flag.StringVar(&defaultMinSdkVersion, "default-min-sdk-version", "24", "Default min_sdk_version to use, if one is not available from AndroidManifest.xml. Default: 24")
 	flag.StringVar(&useVersion, "use-version", "", "Only read artifacts of a specific version")
 	flag.BoolVar(&staticDeps, "static-deps", false, "Statically include direct dependencies")
 	flag.BoolVar(&jetifier, "jetifier", false, "Sets jetifier: true on all modules")
diff --git a/cmd/sbox/Android.bp b/cmd/sbox/Android.bp
index d88505f..b8d75ed 100644
--- a/cmd/sbox/Android.bp
+++ b/cmd/sbox/Android.bp
@@ -21,6 +21,7 @@
     deps: [
         "sbox_proto",
         "soong-makedeps",
+        "soong-response",
     ],
     srcs: [
         "sbox.go",
diff --git a/cmd/sbox/sbox.go b/cmd/sbox/sbox.go
index f47c601..7bd0868 100644
--- a/cmd/sbox/sbox.go
+++ b/cmd/sbox/sbox.go
@@ -32,6 +32,7 @@
 
 	"android/soong/cmd/sbox/sbox_proto"
 	"android/soong/makedeps"
+	"android/soong/response"
 
 	"github.com/golang/protobuf/proto"
 )
@@ -218,6 +219,11 @@
 		return "", fmt.Errorf("command is required")
 	}
 
+	pathToTempDirInSbox := tempDir
+	if command.GetChdir() {
+		pathToTempDirInSbox = "."
+	}
+
 	err = os.MkdirAll(tempDir, 0777)
 	if err != nil {
 		return "", fmt.Errorf("failed to create %q: %w", tempDir, err)
@@ -228,10 +234,9 @@
 	if err != nil {
 		return "", err
 	}
-
-	pathToTempDirInSbox := tempDir
-	if command.GetChdir() {
-		pathToTempDirInSbox = "."
+	err = copyRspFiles(command.RspFiles, tempDir, pathToTempDirInSbox)
+	if err != nil {
+		return "", err
 	}
 
 	if strings.Contains(rawCommand, depFilePlaceholder) {
@@ -382,6 +387,14 @@
 	}
 	defer in.Close()
 
+	// Remove the target before copying.  In most cases the file won't exist, but if there are
+	// duplicate copy rules for a file and the source file was read-only the second copy could
+	// fail.
+	err = os.Remove(to)
+	if err != nil && !os.IsNotExist(err) {
+		return err
+	}
+
 	out, err := os.Create(to)
 	if err != nil {
 		return err
@@ -409,6 +422,83 @@
 	return nil
 }
 
+// copyRspFiles copies rsp files into the sandbox with path mappings, and also copies the files
+// listed into the sandbox.
+func copyRspFiles(rspFiles []*sbox_proto.RspFile, toDir, toDirInSandbox string) error {
+	for _, rspFile := range rspFiles {
+		err := copyOneRspFile(rspFile, toDir, toDirInSandbox)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// copyOneRspFiles copies an rsp file into the sandbox with path mappings, and also copies the files
+// listed into the sandbox.
+func copyOneRspFile(rspFile *sbox_proto.RspFile, toDir, toDirInSandbox string) error {
+	in, err := os.Open(rspFile.GetFile())
+	if err != nil {
+		return err
+	}
+	defer in.Close()
+
+	files, err := response.ReadRspFile(in)
+	if err != nil {
+		return err
+	}
+
+	for i, from := range files {
+		// Convert the real path of the input file into the path inside the sandbox using the
+		// path mappings.
+		to := applyPathMappings(rspFile.PathMappings, from)
+
+		// Copy the file into the sandbox.
+		err := copyOneFile(from, joinPath(toDir, to), false)
+		if err != nil {
+			return err
+		}
+
+		// Rewrite the name in the list of files to be relative to the sandbox directory.
+		files[i] = joinPath(toDirInSandbox, to)
+	}
+
+	// Convert the real path of the rsp file into the path inside the sandbox using the path
+	// mappings.
+	outRspFile := joinPath(toDir, applyPathMappings(rspFile.PathMappings, rspFile.GetFile()))
+
+	err = os.MkdirAll(filepath.Dir(outRspFile), 0777)
+	if err != nil {
+		return err
+	}
+
+	out, err := os.Create(outRspFile)
+	if err != nil {
+		return err
+	}
+	defer out.Close()
+
+	// Write the rsp file with converted paths into the sandbox.
+	err = response.WriteRspFile(out, files)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// applyPathMappings takes a list of path mappings and a path, and returns the path with the first
+// matching path mapping applied.  If the path does not match any of the path mappings then it is
+// returned unmodified.
+func applyPathMappings(pathMappings []*sbox_proto.PathMapping, path string) string {
+	for _, mapping := range pathMappings {
+		if strings.HasPrefix(path, mapping.GetFrom()+"/") {
+			return joinPath(mapping.GetTo()+"/", strings.TrimPrefix(path, mapping.GetFrom()+"/"))
+		}
+	}
+	return path
+}
+
 // moveFiles moves files specified by a set of copy rules.  It uses os.Rename, so it is restricted
 // to moving files where the source and destination are in the same filesystem.  This is OK for
 // sbox because the temporary directory is inside the out directory.  It updates the timestamp
diff --git a/cmd/sbox/sbox_proto/sbox.pb.go b/cmd/sbox/sbox_proto/sbox.pb.go
index 79bb90c..b996481 100644
--- a/cmd/sbox/sbox_proto/sbox.pb.go
+++ b/cmd/sbox/sbox_proto/sbox.pb.go
@@ -86,10 +86,13 @@
 	CopyAfter []*Copy `protobuf:"bytes,4,rep,name=copy_after,json=copyAfter" json:"copy_after,omitempty"`
 	// An optional hash of the input files to ensure the textproto files and the sbox rule reruns
 	// when the lists of inputs changes, even if the inputs are not on the command line.
-	InputHash            *string  `protobuf:"bytes,5,opt,name=input_hash,json=inputHash" json:"input_hash,omitempty"`
-	XXX_NoUnkeyedLiteral struct{} `json:"-"`
-	XXX_unrecognized     []byte   `json:"-"`
-	XXX_sizecache        int32    `json:"-"`
+	InputHash *string `protobuf:"bytes,5,opt,name=input_hash,json=inputHash" json:"input_hash,omitempty"`
+	// A list of files that will be copied before the sandboxed command, and whose contents should be
+	// copied as if they were listed in copy_before.
+	RspFiles             []*RspFile `protobuf:"bytes,6,rep,name=rsp_files,json=rspFiles" json:"rsp_files,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}   `json:"-"`
+	XXX_unrecognized     []byte     `json:"-"`
+	XXX_sizecache        int32      `json:"-"`
 }
 
 func (m *Command) Reset()         { *m = Command{} }
@@ -152,6 +155,13 @@
 	return ""
 }
 
+func (m *Command) GetRspFiles() []*RspFile {
+	if m != nil {
+		return m.RspFiles
+	}
+	return nil
+}
+
 // Copy describes a from-to pair of files to copy.  The paths may be relative, the root that they
 // are relative to is specific to the context the Copy is used in and will be different for
 // from and to.
@@ -211,10 +221,110 @@
 	return false
 }
 
+// RspFile describes an rspfile that should be copied into the sandbox directory.
+type RspFile struct {
+	// The path to the rsp file.
+	File *string `protobuf:"bytes,1,req,name=file" json:"file,omitempty"`
+	// A list of path mappings that should be applied to each file listed in the rsp file.
+	PathMappings         []*PathMapping `protobuf:"bytes,2,rep,name=path_mappings,json=pathMappings" json:"path_mappings,omitempty"`
+	XXX_NoUnkeyedLiteral struct{}       `json:"-"`
+	XXX_unrecognized     []byte         `json:"-"`
+	XXX_sizecache        int32          `json:"-"`
+}
+
+func (m *RspFile) Reset()         { *m = RspFile{} }
+func (m *RspFile) String() string { return proto.CompactTextString(m) }
+func (*RspFile) ProtoMessage()    {}
+func (*RspFile) Descriptor() ([]byte, []int) {
+	return fileDescriptor_9d0425bf0de86ed1, []int{3}
+}
+
+func (m *RspFile) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_RspFile.Unmarshal(m, b)
+}
+func (m *RspFile) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_RspFile.Marshal(b, m, deterministic)
+}
+func (m *RspFile) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_RspFile.Merge(m, src)
+}
+func (m *RspFile) XXX_Size() int {
+	return xxx_messageInfo_RspFile.Size(m)
+}
+func (m *RspFile) XXX_DiscardUnknown() {
+	xxx_messageInfo_RspFile.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_RspFile proto.InternalMessageInfo
+
+func (m *RspFile) GetFile() string {
+	if m != nil && m.File != nil {
+		return *m.File
+	}
+	return ""
+}
+
+func (m *RspFile) GetPathMappings() []*PathMapping {
+	if m != nil {
+		return m.PathMappings
+	}
+	return nil
+}
+
+// PathMapping describes a mapping from a path outside the sandbox to the path inside the sandbox.
+type PathMapping struct {
+	From                 *string  `protobuf:"bytes,1,req,name=from" json:"from,omitempty"`
+	To                   *string  `protobuf:"bytes,2,req,name=to" json:"to,omitempty"`
+	XXX_NoUnkeyedLiteral struct{} `json:"-"`
+	XXX_unrecognized     []byte   `json:"-"`
+	XXX_sizecache        int32    `json:"-"`
+}
+
+func (m *PathMapping) Reset()         { *m = PathMapping{} }
+func (m *PathMapping) String() string { return proto.CompactTextString(m) }
+func (*PathMapping) ProtoMessage()    {}
+func (*PathMapping) Descriptor() ([]byte, []int) {
+	return fileDescriptor_9d0425bf0de86ed1, []int{4}
+}
+
+func (m *PathMapping) XXX_Unmarshal(b []byte) error {
+	return xxx_messageInfo_PathMapping.Unmarshal(m, b)
+}
+func (m *PathMapping) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	return xxx_messageInfo_PathMapping.Marshal(b, m, deterministic)
+}
+func (m *PathMapping) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_PathMapping.Merge(m, src)
+}
+func (m *PathMapping) XXX_Size() int {
+	return xxx_messageInfo_PathMapping.Size(m)
+}
+func (m *PathMapping) XXX_DiscardUnknown() {
+	xxx_messageInfo_PathMapping.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_PathMapping proto.InternalMessageInfo
+
+func (m *PathMapping) GetFrom() string {
+	if m != nil && m.From != nil {
+		return *m.From
+	}
+	return ""
+}
+
+func (m *PathMapping) GetTo() string {
+	if m != nil && m.To != nil {
+		return *m.To
+	}
+	return ""
+}
+
 func init() {
 	proto.RegisterType((*Manifest)(nil), "sbox.Manifest")
 	proto.RegisterType((*Command)(nil), "sbox.Command")
 	proto.RegisterType((*Copy)(nil), "sbox.Copy")
+	proto.RegisterType((*RspFile)(nil), "sbox.RspFile")
+	proto.RegisterType((*PathMapping)(nil), "sbox.PathMapping")
 }
 
 func init() {
@@ -222,22 +332,27 @@
 }
 
 var fileDescriptor_9d0425bf0de86ed1 = []byte{
-	// 268 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x64, 0x90, 0x4f, 0x4b, 0xc3, 0x40,
-	0x10, 0xc5, 0xc9, 0x9f, 0xd2, 0x64, 0x6a, 0x7b, 0x18, 0x3c, 0xec, 0x45, 0x09, 0x01, 0x21, 0x45,
-	0xe8, 0xc1, 0x6f, 0x60, 0xf5, 0x20, 0x82, 0x97, 0x1c, 0x45, 0x08, 0x9b, 0x64, 0x43, 0x02, 0x4d,
-	0x26, 0xec, 0x6e, 0xa0, 0xfd, 0x56, 0x7e, 0x44, 0xd9, 0x49, 0x2a, 0x82, 0xb7, 0x99, 0xdf, 0xe3,
-	0xcd, 0x7b, 0x0c, 0x80, 0x29, 0xe9, 0x7c, 0x18, 0x35, 0x59, 0xc2, 0xd0, 0xcd, 0xe9, 0x17, 0x44,
-	0x1f, 0x72, 0xe8, 0x1a, 0x65, 0x2c, 0xee, 0x21, 0xaa, 0xa8, 0xef, 0xe5, 0x50, 0x1b, 0xe1, 0x25,
-	0x41, 0xb6, 0x79, 0xda, 0x1e, 0xd8, 0xf0, 0x32, 0xd3, 0xfc, 0x57, 0xc6, 0x07, 0xd8, 0xd1, 0x64,
-	0xc7, 0xc9, 0x16, 0xb5, 0x1a, 0x9b, 0xee, 0xa4, 0x84, 0x9f, 0x78, 0x59, 0x9c, 0x6f, 0x67, 0xfa,
-	0x3a, 0xc3, 0xf4, 0xdb, 0x83, 0xf5, 0x62, 0xc6, 0x47, 0xd8, 0x54, 0x34, 0x5e, 0x8a, 0x52, 0x35,
-	0xa4, 0xd5, 0x12, 0x00, 0xd7, 0x80, 0xf1, 0x92, 0x83, 0x93, 0x8f, 0xac, 0xe2, 0x2d, 0xac, 0xaa,
-	0xb6, 0xee, 0x34, 0x9f, 0x8d, 0xf2, 0x79, 0x41, 0x01, 0xeb, 0xa5, 0x81, 0x08, 0x12, 0x3f, 0x8b,
-	0xf3, 0xeb, 0x8a, 0x7b, 0x60, 0x77, 0x21, 0x1b, 0xab, 0xb4, 0x08, 0xff, 0xdd, 0x8e, 0x9d, 0xfa,
-	0xec, 0x44, 0xbc, 0x03, 0xe8, 0x06, 0xd7, 0xbc, 0x95, 0xa6, 0x15, 0x2b, 0xae, 0x1d, 0x33, 0x79,
-	0x93, 0xa6, 0x4d, 0xdf, 0x21, 0x74, 0x0e, 0x44, 0x08, 0x1b, 0x4d, 0xbd, 0xf0, 0x38, 0x88, 0x67,
-	0xdc, 0x81, 0x6f, 0x49, 0xf8, 0x4c, 0x7c, 0x4b, 0x78, 0x0f, 0xa0, 0xce, 0xaa, 0x9a, 0xac, 0x2c,
-	0x4f, 0x4a, 0x04, 0x5c, 0xf5, 0x0f, 0x39, 0xde, 0x7c, 0xf2, 0xc3, 0x0b, 0x7e, 0xf8, 0x4f, 0x00,
-	0x00, 0x00, 0xff, 0xff, 0x78, 0x37, 0x3e, 0x6a, 0x7d, 0x01, 0x00, 0x00,
+	// 342 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0x5f, 0x4b, 0xc3, 0x30,
+	0x14, 0xc5, 0x69, 0xd7, 0xb9, 0xf6, 0xee, 0x0f, 0x18, 0x7c, 0xc8, 0x8b, 0x32, 0x0a, 0xc2, 0xa6,
+	0x30, 0xd0, 0x07, 0xdf, 0x9d, 0x22, 0x22, 0x0c, 0x24, 0xe0, 0x8b, 0x08, 0x25, 0xeb, 0x52, 0x5b,
+	0x58, 0x9b, 0x90, 0x64, 0xb0, 0x7d, 0x57, 0x3f, 0x8c, 0xe4, 0xa6, 0xd3, 0x82, 0x2f, 0xbe, 0xdd,
+	0x7b, 0x0e, 0xf7, 0xdc, 0x5f, 0xc2, 0x05, 0x30, 0x6b, 0xb9, 0x5f, 0x28, 0x2d, 0xad, 0x24, 0x91,
+	0xab, 0xd3, 0x0f, 0x88, 0x57, 0xbc, 0xa9, 0x0a, 0x61, 0x2c, 0x99, 0x43, 0x9c, 0xcb, 0xba, 0xe6,
+	0xcd, 0xc6, 0xd0, 0x60, 0xda, 0x9b, 0x0d, 0x6f, 0xc7, 0x0b, 0x1c, 0x78, 0xf0, 0x2a, 0xfb, 0xb1,
+	0xc9, 0x25, 0x4c, 0xe4, 0xce, 0xaa, 0x9d, 0xcd, 0x36, 0x42, 0x15, 0xd5, 0x56, 0xd0, 0x70, 0x1a,
+	0xcc, 0x12, 0x36, 0xf6, 0xea, 0xa3, 0x17, 0xd3, 0xaf, 0x00, 0x06, 0xed, 0x30, 0xb9, 0x86, 0x61,
+	0x2e, 0xd5, 0x21, 0x5b, 0x8b, 0x42, 0x6a, 0xd1, 0x2e, 0x80, 0xe3, 0x02, 0x75, 0x60, 0xe0, 0xec,
+	0x25, 0xba, 0xe4, 0x0c, 0xfa, 0x79, 0xb9, 0xa9, 0x34, 0xc6, 0xc6, 0xcc, 0x37, 0x84, 0xc2, 0xa0,
+	0x25, 0xa0, 0xbd, 0x69, 0x38, 0x4b, 0xd8, 0xb1, 0x25, 0x73, 0xc0, 0xe9, 0x8c, 0x17, 0x56, 0x68,
+	0x1a, 0xfd, 0xc9, 0x4e, 0x9c, 0x7b, 0xef, 0x4c, 0x72, 0x0e, 0x50, 0x35, 0x8e, 0xbc, 0xe4, 0xa6,
+	0xa4, 0x7d, 0xc4, 0x4e, 0x50, 0x79, 0xe6, 0xa6, 0x24, 0x57, 0x90, 0x68, 0xa3, 0x32, 0x87, 0x6f,
+	0xe8, 0x49, 0xf7, 0x17, 0x98, 0x51, 0x4f, 0xd5, 0x56, 0xb0, 0x58, 0xfb, 0xc2, 0xa4, 0x2f, 0x10,
+	0xb9, 0x74, 0x42, 0x20, 0x2a, 0xb4, 0xac, 0x69, 0x80, 0x50, 0x58, 0x93, 0x09, 0x84, 0x56, 0xd2,
+	0x10, 0x95, 0xd0, 0x4a, 0x72, 0x01, 0x20, 0xf6, 0x22, 0xdf, 0x59, 0xbe, 0xde, 0x0a, 0xda, 0xc3,
+	0x67, 0x75, 0x94, 0xf4, 0x0d, 0x06, 0xed, 0x02, 0x8c, 0x73, 0x5f, 0x7a, 0x8c, 0x73, 0xda, 0x1d,
+	0x8c, 0x15, 0xb7, 0x65, 0x56, 0x73, 0xa5, 0xaa, 0xe6, 0xd3, 0xd0, 0x10, 0xd1, 0x4e, 0x3d, 0xda,
+	0x2b, 0xb7, 0xe5, 0xca, 0x3b, 0x6c, 0xa4, 0x7e, 0x1b, 0x93, 0xde, 0xc0, 0xb0, 0x63, 0xfe, 0x87,
+	0x74, 0x39, 0x7a, 0xc7, 0x33, 0xc9, 0xf0, 0x4c, 0xbe, 0x03, 0x00, 0x00, 0xff, 0xff, 0x83, 0x82,
+	0xb0, 0xc3, 0x33, 0x02, 0x00, 0x00,
 }
diff --git a/cmd/sbox/sbox_proto/sbox.proto b/cmd/sbox/sbox_proto/sbox.proto
index 695b0e8..bdf92c6 100644
--- a/cmd/sbox/sbox_proto/sbox.proto
+++ b/cmd/sbox/sbox_proto/sbox.proto
@@ -47,6 +47,10 @@
   // An optional hash of the input files to ensure the textproto files and the sbox rule reruns
   // when the lists of inputs changes, even if the inputs are not on the command line.
   optional string input_hash = 5;
+
+  // A list of files that will be copied before the sandboxed command, and whose contents should be
+  // copied as if they were listed in copy_before.
+  repeated RspFile rsp_files = 6;
 }
 
 // Copy describes a from-to pair of files to copy.  The paths may be relative, the root that they
@@ -58,4 +62,19 @@
 
   // If true, make the file executable after copying it.
   optional bool executable = 3;
-}
\ No newline at end of file
+}
+
+// RspFile describes an rspfile that should be copied into the sandbox directory.
+message RspFile {
+  // The path to the rsp file.
+  required string file = 1;
+
+  // A list of path mappings that should be applied to each file listed in the rsp file.
+  repeated PathMapping path_mappings = 2;
+}
+
+// PathMapping describes a mapping from a path outside the sandbox to the path inside the sandbox.
+message PathMapping {
+  required string from = 1;
+  required string to = 2;
+}
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index e2fc78c..1863ece 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -24,7 +24,6 @@
 	"time"
 
 	"android/soong/shared"
-
 	"github.com/google/blueprint/bootstrap"
 
 	"android/soong/android"
@@ -65,16 +64,10 @@
 	return android.NewNameResolver(exportFilter)
 }
 
-// bazelConversionRequested checks that the user is intending to convert
-// Blueprint to Bazel BUILD files.
-func bazelConversionRequested(configuration android.Config) bool {
-	return configuration.IsEnvTrue("GENERATE_BAZEL_FILES")
-}
-
-func newContext(configuration android.Config) *android.Context {
+func newContext(configuration android.Config, prepareBuildActions bool) *android.Context {
 	ctx := android.NewContext(configuration)
 	ctx.Register()
-	if !shouldPrepareBuildActions(configuration) {
+	if !prepareBuildActions {
 		configuration.SetStopBefore(bootstrap.StopBeforePrepareBuildActions)
 	}
 	ctx.SetNameInterface(newNameResolver(configuration))
@@ -91,6 +84,105 @@
 	return configuration
 }
 
+// Bazel-enabled mode. Soong runs in two passes.
+// First pass: Analyze the build tree, but only store all bazel commands
+// needed to correctly evaluate the tree in the second pass.
+// TODO(cparsons): Don't output any ninja file, as the second pass will overwrite
+// the incorrect results from the first pass, and file I/O is expensive.
+func runMixedModeBuild(configuration android.Config, firstCtx *android.Context, extraNinjaDeps []string) {
+	configuration.SetStopBefore(bootstrap.StopBeforeWriteNinja)
+	bootstrap.Main(firstCtx.Context, configuration, false, extraNinjaDeps...)
+	// Invoke bazel commands and save results for second pass.
+	if err := configuration.BazelContext.InvokeBazel(); err != nil {
+		fmt.Fprintf(os.Stderr, "%s", err)
+		os.Exit(1)
+	}
+	// Second pass: Full analysis, using the bazel command results. Output ninja file.
+	secondPassConfig, err := android.ConfigForAdditionalRun(configuration)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "%s", err)
+		os.Exit(1)
+	}
+	secondCtx := newContext(secondPassConfig, true)
+	bootstrap.Main(secondCtx.Context, secondPassConfig, false, extraNinjaDeps...)
+}
+
+// Run the code-generation phase to convert BazelTargetModules to BUILD files.
+func runQueryView(configuration android.Config, ctx *android.Context) {
+	codegenContext := bp2build.NewCodegenContext(configuration, *ctx, bp2build.QueryView)
+	absoluteQueryViewDir := shared.JoinPath(topDir, bazelQueryViewDir)
+	if err := createBazelQueryView(codegenContext, absoluteQueryViewDir); err != nil {
+		fmt.Fprintf(os.Stderr, "%s", err)
+		os.Exit(1)
+	}
+}
+
+func runSoongDocs(configuration android.Config, extraNinjaDeps []string) {
+	ctx := newContext(configuration, false)
+	bootstrap.Main(ctx.Context, configuration, false, extraNinjaDeps...)
+	if err := writeDocs(ctx, configuration, docFile); err != nil {
+		fmt.Fprintf(os.Stderr, "%s", err)
+		os.Exit(1)
+	}
+}
+
+func writeMetrics(configuration android.Config) {
+	metricsFile := filepath.Join(bootstrap.CmdlineBuildDir(), "soong_build_metrics.pb")
+	err := android.WriteMetrics(configuration, metricsFile)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "error writing soong_build metrics %s: %s", metricsFile, err)
+		os.Exit(1)
+	}
+}
+
+func writeJsonModuleGraph(configuration android.Config, ctx *android.Context, path string, extraNinjaDeps []string) {
+	f, err := os.Create(path)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "%s", err)
+		os.Exit(1)
+	}
+
+	defer f.Close()
+	ctx.Context.PrintJSONGraph(f)
+	writeFakeNinjaFile(extraNinjaDeps, configuration.BuildDir())
+}
+
+func doChosenActivity(configuration android.Config, extraNinjaDeps []string) {
+	bazelConversionRequested := configuration.IsEnvTrue("GENERATE_BAZEL_FILES")
+	mixedModeBuild := configuration.BazelContext.BazelEnabled()
+	generateQueryView := bazelQueryViewDir != ""
+	jsonModuleFile := configuration.Getenv("SOONG_DUMP_JSON_MODULE_GRAPH")
+
+	prepareBuildActions := !generateQueryView && jsonModuleFile == ""
+
+	if bazelConversionRequested {
+		// Run the alternate pipeline of bp2build mutators and singleton to convert
+		// Blueprint to BUILD files before everything else.
+		runBp2Build(configuration, extraNinjaDeps)
+		return
+	}
+
+	ctx := newContext(configuration, prepareBuildActions)
+	if mixedModeBuild {
+		runMixedModeBuild(configuration, ctx, extraNinjaDeps)
+	} else {
+		bootstrap.Main(ctx.Context, configuration, false, extraNinjaDeps...)
+	}
+
+	// Convert the Soong module graph into Bazel BUILD files.
+	if generateQueryView {
+		runQueryView(configuration, ctx)
+		return
+	}
+
+	if jsonModuleFile != "" {
+		writeJsonModuleGraph(configuration, ctx, jsonModuleFile, extraNinjaDeps)
+		return
+	}
+
+	writeMetrics(configuration)
+}
+
 func main() {
 	flag.Parse()
 
@@ -101,7 +193,6 @@
 	usedVariablesFile := shared.JoinPath(outDir, "soong.environment.used")
 	// The top-level Blueprints file is passed as the first argument.
 	srcDir := filepath.Dir(flag.Arg(0))
-	var ctx *android.Context
 	configuration := newConfig(srcDir)
 	extraNinjaDeps := []string{
 		configuration.ProductVariablesFileName,
@@ -122,72 +213,17 @@
 		extraNinjaDeps = append(extraNinjaDeps, filepath.Join(configuration.BuildDir(), "always_rerun_for_delve"))
 	}
 
-	bazelConversionRequested := bazelConversionRequested(configuration)
-	if bazelConversionRequested {
-		// Run the alternate pipeline of bp2build mutators and singleton to convert Blueprint to BUILD files
-		// before everything else.
-		runBp2Build(srcDir, configuration, extraNinjaDeps)
-	} else if configuration.BazelContext.BazelEnabled() {
-		// Bazel-enabled mode. Soong runs in two passes.
-		// First pass: Analyze the build tree, but only store all bazel commands
-		// needed to correctly evaluate the tree in the second pass.
-		// TODO(cparsons): Don't output any ninja file, as the second pass will overwrite
-		// the incorrect results from the first pass, and file I/O is expensive.
-		firstCtx := newContext(configuration)
-		configuration.SetStopBefore(bootstrap.StopBeforeWriteNinja)
-		bootstrap.Main(firstCtx.Context, configuration, false, extraNinjaDeps...)
-		// Invoke bazel commands and save results for second pass.
-		if err := configuration.BazelContext.InvokeBazel(); err != nil {
-			fmt.Fprintf(os.Stderr, "%s", err)
-			os.Exit(1)
-		}
-		// Second pass: Full analysis, using the bazel command results. Output ninja file.
-		secondPassConfig, err := android.ConfigForAdditionalRun(configuration)
-		if err != nil {
-			fmt.Fprintf(os.Stderr, "%s", err)
-			os.Exit(1)
-		}
-		ctx = newContext(secondPassConfig)
-		bootstrap.Main(ctx.Context, secondPassConfig, false, extraNinjaDeps...)
-	} else {
-		ctx = newContext(configuration)
-		bootstrap.Main(ctx.Context, configuration, false, extraNinjaDeps...)
+	if docFile != "" {
+		// We don't write an used variables file when generating documentation
+		// because that is done from within the actual builds as a Ninja action and
+		// thus it would overwrite the actual used variables file so this is
+		// special-cased.
+		runSoongDocs(configuration, extraNinjaDeps)
+		return
 	}
 
-	// Convert the Soong module graph into Bazel BUILD files.
-	if !bazelConversionRequested && bazelQueryViewDir != "" {
-		// Run the code-generation phase to convert BazelTargetModules to BUILD files.
-		codegenContext := bp2build.NewCodegenContext(configuration, *ctx, bp2build.QueryView)
-		absoluteQueryViewDir := shared.JoinPath(topDir, bazelQueryViewDir)
-		if err := createBazelQueryView(codegenContext, absoluteQueryViewDir); err != nil {
-			fmt.Fprintf(os.Stderr, "%s", err)
-			os.Exit(1)
-		}
-	}
-
-	if !bazelConversionRequested && docFile != "" {
-		if err := writeDocs(ctx, configuration, docFile); err != nil {
-			fmt.Fprintf(os.Stderr, "%s", err)
-			os.Exit(1)
-		}
-	}
-
-	// TODO(ccross): make this a command line argument.  Requires plumbing through blueprint
-	//  to affect the command line of the primary builder.
-	if !bazelConversionRequested && shouldPrepareBuildActions(configuration) {
-		metricsFile := filepath.Join(bootstrap.CmdlineBuildDir(), "soong_build_metrics.pb")
-		err := android.WriteMetrics(configuration, metricsFile)
-		if err != nil {
-			fmt.Fprintf(os.Stderr, "error writing soong_build metrics %s: %s", metricsFile, err)
-			os.Exit(1)
-		}
-	}
-
-	if docFile == "" {
-		// Let's not overwrite the used variables file when generating
-		// documentation
-		writeUsedVariablesFile(shared.JoinPath(topDir, usedVariablesFile), configuration)
-	}
+	doChosenActivity(configuration, extraNinjaDeps)
+	writeUsedVariablesFile(shared.JoinPath(topDir, usedVariablesFile), configuration)
 }
 
 func writeUsedVariablesFile(path string, configuration android.Config) {
@@ -215,10 +251,33 @@
 	}
 }
 
+// Workarounds to support running bp2build in a clean AOSP checkout with no
+// prior builds, and exiting early as soon as the BUILD files get generated,
+// therefore not creating build.ninja files that soong_ui and callers of
+// soong_build expects.
+//
+// These files are: build.ninja and build.ninja.d. Since Kati hasn't been
+// ran as well, and `nothing` is defined in a .mk file, there isn't a ninja
+// target called `nothing`, so we manually create it here.
+func writeFakeNinjaFile(extraNinjaDeps []string, buildDir string) {
+	extraNinjaDepsString := strings.Join(extraNinjaDeps, " \\\n ")
+
+	ninjaFileName := "build.ninja"
+	ninjaFile := shared.JoinPath(topDir, buildDir, ninjaFileName)
+	ninjaFileD := shared.JoinPath(topDir, buildDir, ninjaFileName)
+	// A workaround to create the 'nothing' ninja target so `m nothing` works,
+	// since bp2build runs without Kati, and the 'nothing' target is declared in
+	// a Makefile.
+	ioutil.WriteFile(ninjaFile, []byte("build nothing: phony\n  phony_output = true\n"), 0666)
+	ioutil.WriteFile(ninjaFileD,
+		[]byte(fmt.Sprintf("%s: \\\n %s\n", ninjaFileName, extraNinjaDepsString)),
+		0666)
+}
+
 // Run Soong in the bp2build mode. This creates a standalone context that registers
 // an alternate pipeline of mutators and singletons specifically for generating
 // Bazel BUILD files instead of Ninja files.
-func runBp2Build(srcDir string, configuration android.Config, extraNinjaDeps []string) {
+func runBp2Build(configuration android.Config, extraNinjaDeps []string) {
 	// Register an alternate set of singletons and mutators for bazel
 	// conversion for Bazel conversion.
 	bp2buildCtx := android.NewContext(configuration)
@@ -233,7 +292,7 @@
 	// configurations or variables, since those will generate different BUILD
 	// files based on how the user has configured their tree.
 	bp2buildCtx.SetModuleListFile(bootstrap.CmdlineModuleListFile())
-	modulePaths, err := bp2buildCtx.ListModulePaths(srcDir)
+	modulePaths, err := bp2buildCtx.ListModulePaths(configuration.SrcDir())
 	if err != nil {
 		panic(err)
 	}
@@ -256,47 +315,5 @@
 	metrics.Print()
 
 	extraNinjaDeps = append(extraNinjaDeps, codegenContext.AdditionalNinjaDeps()...)
-	extraNinjaDepsString := strings.Join(extraNinjaDeps, " \\\n ")
-
-	// Workarounds to support running bp2build in a clean AOSP checkout with no
-	// prior builds, and exiting early as soon as the BUILD files get generated,
-	// therefore not creating build.ninja files that soong_ui and callers of
-	// soong_build expects.
-	//
-	// These files are: build.ninja and build.ninja.d. Since Kati hasn't been
-	// ran as well, and `nothing` is defined in a .mk file, there isn't a ninja
-	// target called `nothing`, so we manually create it here.
-	//
-	// Even though outFile (build.ninja) and depFile (build.ninja.d) are values
-	// passed into bootstrap.Main, they are package-private fields in bootstrap.
-	// Short of modifying Blueprint to add an exported getter, inlining them
-	// here is the next-best practical option.
-	ninjaFileName := "build.ninja"
-	ninjaFile := android.PathForOutput(codegenContext, ninjaFileName)
-	ninjaFileD := android.PathForOutput(codegenContext, ninjaFileName+".d")
-	// A workaround to create the 'nothing' ninja target so `m nothing` works,
-	// since bp2build runs without Kati, and the 'nothing' target is declared in
-	// a Makefile.
-	android.WriteFileToOutputDir(ninjaFile, []byte("build nothing: phony\n  phony_output = true\n"), 0666)
-	android.WriteFileToOutputDir(
-		ninjaFileD,
-		[]byte(fmt.Sprintf("%s: \\\n %s\n", ninjaFileName, extraNinjaDepsString)),
-		0666)
-}
-
-// shouldPrepareBuildActions reads configuration and flags if build actions
-// should be generated.
-func shouldPrepareBuildActions(configuration android.Config) bool {
-	// Generating Soong docs
-	if docFile != "" {
-		return false
-	}
-
-	// Generating a directory for Soong query (queryview)
-	if bazelQueryViewDir != "" {
-		return false
-	}
-
-	// Generating a directory for converted Bazel BUILD files
-	return !bazelConversionRequested(configuration)
+	writeFakeNinjaFile(extraNinjaDeps, codegenContext.Config().BuildDir())
 }
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index 4999bc7..81a63b0 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -259,35 +259,53 @@
 			Implicits(clcHost).
 			Text("stored_class_loader_context_arg=--stored-class-loader-context=PCL[" + strings.Join(clcTarget, ":") + "]")
 
-	} else if module.EnforceUsesLibraries {
-		// Generate command that saves target SDK version in a shell variable.
-		manifestOrApk := module.ManifestPath
-		if manifestOrApk == nil {
-			// No manifest to extract targetSdkVersion from, hope that dexjar is an APK.
+	} else {
+		// There are three categories of Java modules handled here:
+		//
+		// - Modules that have passed verify_uses_libraries check. They are AOT-compiled and
+		//   expected to be loaded on device without CLC mismatch errors.
+		//
+		// - Modules that have failed the check in relaxed mode, so it didn't cause a build error.
+		//   They are dexpreopted with "verify" filter and not AOT-compiled.
+		//   TODO(b/132357300): ensure that CLC mismatch errors are ignored with "verify" filter.
+		//
+		// - Modules that didn't run the check. They are AOT-compiled, but it's unknown if they
+		//   will have CLC mismatch errors on device (the check is disabled by default).
+		//
+		// TODO(b/132357300): enable the check by default and eliminate the last category, so that
+		// no time/space is wasted on AOT-compiling modules that will fail CLC check on device.
+
+		var manifestOrApk android.Path
+		if module.ManifestPath != nil {
+			// Ok, there is an XML manifest.
+			manifestOrApk = module.ManifestPath
+		} else if filepath.Ext(base) == ".apk" {
+			// Ok, there is is an APK with the manifest inside.
 			manifestOrApk = module.DexPath
 		}
-		rule.Command().Text(`target_sdk_version="$(`).
-			Tool(globalSoong.ManifestCheck).
-			Flag("--extract-target-sdk-version").
-			Input(manifestOrApk).
-			FlagWithInput("--aapt ", ctx.Config().HostToolPath(ctx, "aapt")).
-			Text(`)"`)
+
+		// Generate command that saves target SDK version in a shell variable.
+		if manifestOrApk == nil {
+			// There is neither an XML manifest nor APK => nowhere to extract targetSdkVersion from.
+			// Set the latest ("any") version: then construct_context will not add any compatibility
+			// libraries (if this is incorrect, there will be a CLC mismatch and dexopt on device).
+			rule.Command().Textf(`target_sdk_version=%d`, AnySdkVersion)
+		} else {
+			rule.Command().Text(`target_sdk_version="$(`).
+				Tool(globalSoong.ManifestCheck).
+				Flag("--extract-target-sdk-version").
+				Input(manifestOrApk).
+				FlagWithInput("--aapt ", globalSoong.Aapt).
+				Text(`)"`)
+		}
 
 		// Generate command that saves host and target class loader context in shell variables.
 		clc, paths := ComputeClassLoaderContext(module.ClassLoaderContexts)
 		rule.Command().
-			Text("if ! test -s ").Input(module.EnforceUsesLibrariesStatusFile).
-			Text(` ; then eval "$(`).Tool(globalSoong.ConstructContext).
+			Text(`eval "$(`).Tool(globalSoong.ConstructContext).
 			Text(` --target-sdk-version ${target_sdk_version}`).
 			Text(clc).Implicits(paths).
-			Text(`)" ; fi`)
-
-	} else {
-		// Other libraries or APKs for which the exact <uses-library> list is unknown.
-		// We assume the class loader context is empty.
-		rule.Command().
-			Text(`class_loader_context_arg=--class-loader-context=PCL[]`).
-			Text(`stored_class_loader_context_arg=""`)
+			Text(`)"`)
 	}
 
 	// Devices that do not have a product partition use a symlink from /product to /system/product.
diff --git a/filesystem/logical_partition.go b/filesystem/logical_partition.go
index 20d9622..739e609 100644
--- a/filesystem/logical_partition.go
+++ b/filesystem/logical_partition.go
@@ -40,9 +40,14 @@
 	// Set the name of the output. Defaults to <module_name>.img.
 	Stem *string
 
-	// Total size of the logical partition
+	// Total size of the logical partition. If set to "auto", total size is automatically
+	// calcaulted as minimum.
 	Size *string
 
+	// List of partitions for default group. Default group has no size limit and automatically
+	// minimized when creating an image.
+	Default_group []partitionProperties
+
 	// List of groups. A group defines a fixed sized region. It can host one or more logical
 	// partitions and their total size is limited by the size of the group they are in.
 	Groups []groupProperties
@@ -52,7 +57,7 @@
 }
 
 type groupProperties struct {
-	// Name of the partition group
+	// Name of the partition group. Can't be "default"; use default_group instead.
 	Name *string
 
 	// Size of the partition group
@@ -92,8 +97,9 @@
 	// Sparse the filesystem images and calculate their sizes
 	sparseImages := make(map[string]android.OutputPath)
 	sparseImageSizes := make(map[string]android.OutputPath)
-	for _, group := range l.properties.Groups {
-		for _, part := range group.Partitions {
+
+	sparsePartitions := func(partitions []partitionProperties) {
+		for _, part := range partitions {
 			sparseImg, sizeTxt := sparseFilesystem(ctx, part, builder)
 			pName := proptools.String(part.Name)
 			sparseImages[pName] = sparseImg
@@ -101,14 +107,19 @@
 		}
 	}
 
+	for _, group := range l.properties.Groups {
+		sparsePartitions(group.Partitions)
+	}
+
+	sparsePartitions(l.properties.Default_group)
+
 	cmd := builder.Command().BuiltTool("lpmake")
 
 	size := proptools.String(l.properties.Size)
 	if size == "" {
 		ctx.PropertyErrorf("size", "must be set")
-	}
-	if _, err := strconv.Atoi(size); err != nil {
-		ctx.PropertyErrorf("size", "must be a number")
+	} else if _, err := strconv.Atoi(size); err != nil && size != "auto" {
+		ctx.PropertyErrorf("size", `must be a number or "auto"`)
 	}
 	cmd.FlagWithArg("--device-size=", size)
 
@@ -123,10 +134,32 @@
 	groupNames := make(map[string]bool)
 	partitionNames := make(map[string]bool)
 
+	addPartitionsToGroup := func(partitions []partitionProperties, gName string) {
+		for _, part := range partitions {
+			pName := proptools.String(part.Name)
+			if pName == "" {
+				ctx.PropertyErrorf("groups.partitions.name", "must be set")
+			}
+			if _, ok := partitionNames[pName]; ok {
+				ctx.PropertyErrorf("groups.partitions.name", "already exists")
+			} else {
+				partitionNames[pName] = true
+			}
+			// Get size of the partition by reading the -size.txt file
+			pSize := fmt.Sprintf("$(cat %s)", sparseImageSizes[pName])
+			cmd.FlagWithArg("--partition=", fmt.Sprintf("%s:readonly:%s:%s", pName, pSize, gName))
+			cmd.FlagWithInput("--image="+pName+"=", sparseImages[pName])
+		}
+	}
+
+	addPartitionsToGroup(l.properties.Default_group, "default")
+
 	for _, group := range l.properties.Groups {
 		gName := proptools.String(group.Name)
 		if gName == "" {
 			ctx.PropertyErrorf("groups.name", "must be set")
+		} else if gName == "default" {
+			ctx.PropertyErrorf("groups.name", `can't use "default" as a group name. Use default_group instead`)
 		}
 		if _, ok := groupNames[gName]; ok {
 			ctx.PropertyErrorf("group.name", "already exists")
@@ -142,21 +175,7 @@
 		}
 		cmd.FlagWithArg("--group=", gName+":"+gSize)
 
-		for _, part := range group.Partitions {
-			pName := proptools.String(part.Name)
-			if pName == "" {
-				ctx.PropertyErrorf("groups.partitions.name", "must be set")
-			}
-			if _, ok := partitionNames[pName]; ok {
-				ctx.PropertyErrorf("groups.partitions.name", "already exists")
-			} else {
-				partitionNames[pName] = true
-			}
-			// Get size of the partition by reading the -size.txt file
-			pSize := fmt.Sprintf("$(cat %s)", sparseImageSizes[pName])
-			cmd.FlagWithArg("--partition=", fmt.Sprintf("%s:readonly:%s:%s", pName, pSize, gName))
-			cmd.FlagWithInput("--image="+pName+"=", sparseImages[pName])
-		}
+		addPartitionsToGroup(group.Partitions, gName)
 	}
 
 	l.output = android.PathForModuleOut(ctx, l.installFileName()).OutputPath
diff --git a/genrule/Android.bp b/genrule/Android.bp
index 214940d..8fb5c40 100644
--- a/genrule/Android.bp
+++ b/genrule/Android.bp
@@ -16,6 +16,7 @@
     ],
     srcs: [
         "genrule.go",
+        "locations.go",
     ],
     testSrcs: [
         "genrule_test.go",
diff --git a/genrule/genrule.go b/genrule/genrule.go
index cb841ed..d07b002 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -91,7 +91,6 @@
 
 func init() {
 	pctx.Import("android/soong/android")
-	pctx.HostBinToolVariable("sboxCmd", "sbox")
 
 	pctx.HostBinToolVariable("soongZip", "soong_zip")
 	pctx.HostBinToolVariable("zipSync", "zipsync")
@@ -230,11 +229,16 @@
 	filePaths, ok := bazelCtx.GetOutputFiles(label, ctx.Arch().ArchType)
 	if ok {
 		var bazelOutputFiles android.Paths
+		exportIncludeDirs := map[string]bool{}
 		for _, bazelOutputFile := range filePaths {
 			bazelOutputFiles = append(bazelOutputFiles, android.PathForBazelOut(ctx, bazelOutputFile))
+			exportIncludeDirs[filepath.Dir(bazelOutputFile)] = true
 		}
 		c.outputFiles = bazelOutputFiles
 		c.outputDeps = bazelOutputFiles
+		for includePath, _ := range exportIncludeDirs {
+			c.exportedIncludeDirs = append(c.exportedIncludeDirs, android.PathForBazelOut(ctx, includePath))
+		}
 	}
 	return ok
 }
@@ -254,18 +258,18 @@
 		g.exportedIncludeDirs = append(g.exportedIncludeDirs, android.PathForModuleGen(ctx, g.subDir))
 	}
 
-	locationLabels := map[string][]string{}
+	locationLabels := map[string]location{}
 	firstLabel := ""
 
-	addLocationLabel := func(label string, paths []string) {
+	addLocationLabel := func(label string, loc location) {
 		if firstLabel == "" {
 			firstLabel = label
 		}
 		if _, exists := locationLabels[label]; !exists {
-			locationLabels[label] = paths
+			locationLabels[label] = loc
 		} else {
 			ctx.ModuleErrorf("multiple labels for %q, %q and %q",
-				label, strings.Join(locationLabels[label], " "), strings.Join(paths, " "))
+				label, locationLabels[label], loc)
 		}
 	}
 
@@ -303,17 +307,17 @@
 						// sandbox.
 						packagedTools = append(packagedTools, specs...)
 						// Assume that the first PackagingSpec of the module is the tool.
-						addLocationLabel(tag.label, []string{android.SboxPathForPackagedTool(specs[0])})
+						addLocationLabel(tag.label, packagedToolLocation{specs[0]})
 					} else {
 						tools = append(tools, path.Path())
-						addLocationLabel(tag.label, []string{android.SboxPathForTool(ctx, path.Path())})
+						addLocationLabel(tag.label, toolLocation{android.Paths{path.Path()}})
 					}
 				case bootstrap.GoBinaryTool:
 					// A GoBinaryTool provides the install path to a tool, which will be copied.
 					if s, err := filepath.Rel(android.PathForOutput(ctx).String(), t.InstallPath()); err == nil {
 						toolPath := android.PathForOutput(ctx, s)
 						tools = append(tools, toolPath)
-						addLocationLabel(tag.label, []string{android.SboxPathForTool(ctx, toolPath)})
+						addLocationLabel(tag.label, toolLocation{android.Paths{toolPath}})
 					} else {
 						ctx.ModuleErrorf("cannot find path for %q: %v", tool, err)
 						return
@@ -335,7 +339,7 @@
 		if ctx.Config().AllowMissingDependencies() {
 			for _, tool := range g.properties.Tools {
 				if !seenTools[tool] {
-					addLocationLabel(tool, []string{"***missing tool " + tool + "***"})
+					addLocationLabel(tool, errorLocation{"***missing tool " + tool + "***"})
 				}
 			}
 		}
@@ -348,11 +352,7 @@
 	for _, toolFile := range g.properties.Tool_files {
 		paths := android.PathsForModuleSrc(ctx, []string{toolFile})
 		tools = append(tools, paths...)
-		var sandboxPaths []string
-		for _, path := range paths {
-			sandboxPaths = append(sandboxPaths, android.SboxPathForTool(ctx, path))
-		}
-		addLocationLabel(toolFile, sandboxPaths)
+		addLocationLabel(toolFile, toolLocation{paths})
 	}
 
 	var srcFiles android.Paths
@@ -370,10 +370,10 @@
 			// The command that uses this placeholder file will never be executed because the rule will be
 			// replaced with an android.Error rule reporting the missing dependencies.
 			ctx.AddMissingDependencies(missingDeps)
-			addLocationLabel(in, []string{"***missing srcs " + in + "***"})
+			addLocationLabel(in, errorLocation{"***missing srcs " + in + "***"})
 		} else {
 			srcFiles = append(srcFiles, paths...)
-			addLocationLabel(in, paths.Strings())
+			addLocationLabel(in, inputLocation{paths})
 		}
 	}
 
@@ -408,7 +408,7 @@
 		cmd := rule.Command()
 
 		for _, out := range task.out {
-			addLocationLabel(out.Rel(), []string{cmd.PathForOutput(out)})
+			addLocationLabel(out.Rel(), outputLocation{out})
 		}
 
 		referencedDepfile := false
@@ -426,16 +426,17 @@
 				if len(g.properties.Tools) == 0 && len(g.properties.Tool_files) == 0 {
 					return reportError("at least one `tools` or `tool_files` is required if $(location) is used")
 				}
-				paths := locationLabels[firstLabel]
+				loc := locationLabels[firstLabel]
+				paths := loc.Paths(cmd)
 				if len(paths) == 0 {
 					return reportError("default label %q has no files", firstLabel)
 				} else if len(paths) > 1 {
 					return reportError("default label %q has multiple files, use $(locations %s) to reference it",
 						firstLabel, firstLabel)
 				}
-				return locationLabels[firstLabel][0], nil
+				return paths[0], nil
 			case "in":
-				return strings.Join(srcFiles.Strings(), " "), nil
+				return strings.Join(cmd.PathsForInputs(srcFiles), " "), nil
 			case "out":
 				var sandboxOuts []string
 				for _, out := range task.out {
@@ -453,7 +454,8 @@
 			default:
 				if strings.HasPrefix(name, "location ") {
 					label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
-					if paths, ok := locationLabels[label]; ok {
+					if loc, ok := locationLabels[label]; ok {
+						paths := loc.Paths(cmd)
 						if len(paths) == 0 {
 							return reportError("label %q has no files", label)
 						} else if len(paths) > 1 {
@@ -466,7 +468,8 @@
 					}
 				} else if strings.HasPrefix(name, "locations ") {
 					label := strings.TrimSpace(strings.TrimPrefix(name, "locations "))
-					if paths, ok := locationLabels[label]; ok {
+					if loc, ok := locationLabels[label]; ok {
+						paths := loc.Paths(cmd)
 						if len(paths) == 0 {
 							return reportError("label %q has no files", label)
 						}
@@ -719,7 +722,7 @@
 				outputDepfile = android.PathForModuleGen(ctx, genSubDir, "gensrcs.d")
 				depFixerTool := ctx.Config().HostToolPath(ctx, "dep_fixer")
 				fullCommand += fmt.Sprintf(" && %s -o $(depfile) %s",
-					android.SboxPathForTool(ctx, depFixerTool),
+					rule.Command().PathForTool(depFixerTool),
 					strings.Join(commandDepFiles, " "))
 				extraTools = append(extraTools, depFixerTool)
 			}
@@ -800,9 +803,9 @@
 }
 
 type bazelGenruleAttributes struct {
-	Srcs  bazel.LabelList
+	Srcs  bazel.LabelListAttribute
 	Outs  []string
-	Tools bazel.LabelList
+	Tools bazel.LabelListAttribute
 	Cmd   string
 }
 
@@ -830,15 +833,16 @@
 	}
 
 	// Bazel only has the "tools" attribute.
-	tools := android.BazelLabelForModuleDeps(ctx, m.properties.Tools)
-	tool_files := android.BazelLabelForModuleSrc(ctx, m.properties.Tool_files)
-	tools.Append(tool_files)
+	tools_prop := android.BazelLabelForModuleDeps(ctx, m.properties.Tools)
+	tool_files_prop := android.BazelLabelForModuleSrc(ctx, m.properties.Tool_files)
+	tools_prop.Append(tool_files_prop)
 
-	srcs := android.BazelLabelForModuleSrc(ctx, m.properties.Srcs)
+	tools := bazel.MakeLabelListAttribute(tools_prop)
+	srcs := bazel.MakeLabelListAttribute(android.BazelLabelForModuleSrc(ctx, m.properties.Srcs))
 
 	var allReplacements bazel.LabelList
-	allReplacements.Append(tools)
-	allReplacements.Append(srcs)
+	allReplacements.Append(tools.Value)
+	allReplacements.Append(srcs.Value)
 
 	// Replace in and out variables with $< and $@
 	var cmd string
@@ -846,9 +850,9 @@
 		cmd = strings.Replace(*m.properties.Cmd, "$(in)", "$(SRCS)", -1)
 		cmd = strings.Replace(cmd, "$(out)", "$(OUTS)", -1)
 		cmd = strings.Replace(cmd, "$(genDir)", "$(GENDIR)", -1)
-		if len(tools.Includes) > 0 {
-			cmd = strings.Replace(cmd, "$(location)", fmt.Sprintf("$(location %s)", tools.Includes[0].Label), -1)
-			cmd = strings.Replace(cmd, "$(locations)", fmt.Sprintf("$(locations %s)", tools.Includes[0].Label), -1)
+		if len(tools.Value.Includes) > 0 {
+			cmd = strings.Replace(cmd, "$(location)", fmt.Sprintf("$(location %s)", tools.Value.Includes[0].Label), -1)
+			cmd = strings.Replace(cmd, "$(locations)", fmt.Sprintf("$(locations %s)", tools.Value.Includes[0].Label), -1)
 		}
 		for _, l := range allReplacements.Includes {
 			bpLoc := fmt.Sprintf("$(location %s)", l.Bp_text)
diff --git a/genrule/genrule_test.go b/genrule/genrule_test.go
index 2ee456d..3f1e9f3 100644
--- a/genrule/genrule_test.go
+++ b/genrule/genrule_test.go
@@ -670,7 +670,8 @@
 			cmd: "cat $(in) > $(out)",
 		}
        `
-	result := prepareForGenRuleTest.Extend(
+	result := android.GroupFixturePreparers(
+		prepareForGenRuleTest,
 		android.FixtureModifyConfigAndContext(
 			func(config android.Config, ctx *android.TestContext) {
 				config.TestProductVariables.Allow_missing_dependencies = proptools.BoolPtr(true)
diff --git a/genrule/locations.go b/genrule/locations.go
new file mode 100644
index 0000000..2978b91
--- /dev/null
+++ b/genrule/locations.go
@@ -0,0 +1,104 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package genrule
+
+import (
+	"strings"
+
+	"android/soong/android"
+)
+
+// location is used to service $(location) and $(locations) entries in genrule commands.
+type location interface {
+	Paths(cmd *android.RuleBuilderCommand) []string
+	String() string
+}
+
+// inputLocation is a $(location) result for an entry in the srcs property.
+type inputLocation struct {
+	paths android.Paths
+}
+
+func (l inputLocation) String() string {
+	return strings.Join(l.paths.Strings(), " ")
+}
+
+func (l inputLocation) Paths(cmd *android.RuleBuilderCommand) []string {
+	return cmd.PathsForInputs(l.paths)
+}
+
+var _ location = inputLocation{}
+
+// outputLocation is a $(location) result for an entry in the out property.
+type outputLocation struct {
+	path android.WritablePath
+}
+
+func (l outputLocation) String() string {
+	return l.path.String()
+}
+
+func (l outputLocation) Paths(cmd *android.RuleBuilderCommand) []string {
+	return []string{cmd.PathForOutput(l.path)}
+}
+
+var _ location = outputLocation{}
+
+// toolLocation is a $(location) result for an entry in the tools or tool_files property.
+type toolLocation struct {
+	paths android.Paths
+}
+
+func (l toolLocation) String() string {
+	return strings.Join(l.paths.Strings(), " ")
+}
+
+func (l toolLocation) Paths(cmd *android.RuleBuilderCommand) []string {
+	return cmd.PathsForTools(l.paths)
+}
+
+var _ location = toolLocation{}
+
+// packagedToolLocation is a $(location) result for an entry in the tools or tool_files property
+// that has PackagingSpecs.
+type packagedToolLocation struct {
+	spec android.PackagingSpec
+}
+
+func (l packagedToolLocation) String() string {
+	return l.spec.FileName()
+}
+
+func (l packagedToolLocation) Paths(cmd *android.RuleBuilderCommand) []string {
+	return []string{cmd.PathForPackagedTool(l.spec)}
+}
+
+var _ location = packagedToolLocation{}
+
+// errorLocation is a placeholder for a $(location) result that returns garbage to break the command
+// when error reporting is delayed by ALLOW_MISSING_DEPENDENCIES=true.
+type errorLocation struct {
+	err string
+}
+
+func (l errorLocation) String() string {
+	return l.err
+}
+
+func (l errorLocation) Paths(cmd *android.RuleBuilderCommand) []string {
+	return []string{l.err}
+}
+
+var _ location = errorLocation{}
diff --git a/java/Android.bp b/java/Android.bp
index c67379c..8334b85 100644
--- a/java/Android.bp
+++ b/java/Android.bp
@@ -38,6 +38,7 @@
         "dexpreopt_bootjars.go",
         "dexpreopt_config.go",
         "droiddoc.go",
+        "droidstubs.go",
         "gen.go",
         "genrule.go",
         "hiddenapi.go",
@@ -49,6 +50,7 @@
         "kotlin.go",
         "lint.go",
         "legacy_core_platform_api_usage.go",
+        "platform_bootclasspath.go",
         "platform_compat_config.go",
         "plugin.go",
         "prebuilt_apis.go",
@@ -72,10 +74,13 @@
         "device_host_converter_test.go",
         "dexpreopt_test.go",
         "dexpreopt_bootjars_test.go",
+        "droiddoc_test.go",
+        "droidstubs_test.go",
         "hiddenapi_singleton_test.go",
         "java_test.go",
         "jdeps_test.go",
         "kotlin_test.go",
+        "platform_bootclasspath_test.go",
         "platform_compat_config_test.go",
         "plugin_test.go",
         "rro_test.go",
diff --git a/java/aar.go b/java/aar.go
index 67b9ef0..a122a94 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -163,7 +163,7 @@
 		a.aaptProperties.RROEnforcedForDependent
 }
 
-func (a *aapt) aapt2Flags(ctx android.ModuleContext, sdkContext sdkContext,
+func (a *aapt) aapt2Flags(ctx android.ModuleContext, sdkContext android.SdkContext,
 	manifestPath android.Path) (compileFlags, linkFlags []string, linkDeps android.Paths,
 	resDirs, overlayDirs []globbedResourceDir, rroDirs []rroDir, resZips android.Paths) {
 
@@ -218,7 +218,7 @@
 	linkDeps = append(linkDeps, assetDeps...)
 
 	// SDK version flags
-	minSdkVersion, err := sdkContext.minSdkVersion().effectiveVersionString(ctx)
+	minSdkVersion, err := sdkContext.MinSdkVersion().EffectiveVersionString(ctx)
 	if err != nil {
 		ctx.ModuleErrorf("invalid minSdkVersion: %s", err)
 	}
@@ -266,7 +266,7 @@
 		CommandDeps: []string{"${config.Zip2ZipCmd}"},
 	})
 
-func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext sdkContext,
+func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext android.SdkContext,
 	classLoaderContexts dexpreopt.ClassLoaderContextMap, extraLinkFlags ...string) {
 
 	transitiveStaticLibs, transitiveStaticLibManifests, staticRRODirs, assetPackages, libDeps, libFlags :=
@@ -397,7 +397,7 @@
 }
 
 // aaptLibs collects libraries from dependencies and sdk_version and converts them into paths
-func aaptLibs(ctx android.ModuleContext, sdkContext sdkContext, classLoaderContexts dexpreopt.ClassLoaderContextMap) (
+func aaptLibs(ctx android.ModuleContext, sdkContext android.SdkContext, classLoaderContexts dexpreopt.ClassLoaderContextMap) (
 	transitiveStaticLibs, transitiveStaticLibManifests android.Paths, staticRRODirs []rroDir, assets, deps android.Paths, flags []string) {
 
 	var sharedLibs android.Paths
@@ -498,7 +498,7 @@
 
 func (a *AndroidLibrary) DepsMutator(ctx android.BottomUpMutatorContext) {
 	a.Module.deps(ctx)
-	sdkDep := decodeSdkDep(ctx, sdkContext(a))
+	sdkDep := decodeSdkDep(ctx, android.SdkContext(a))
 	if sdkDep.hasFrameworkLibs() {
 		a.aapt.deps(ctx, sdkDep)
 	}
@@ -507,7 +507,7 @@
 func (a *AndroidLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	a.aapt.isLibrary = true
 	a.classLoaderContexts = make(dexpreopt.ClassLoaderContextMap)
-	a.aapt.buildActions(ctx, sdkContext(a), a.classLoaderContexts)
+	a.aapt.buildActions(ctx, android.SdkContext(a), a.classLoaderContexts)
 
 	a.hideApexVariantFromMake = !ctx.Provider(android.ApexInfoProvider).(android.ApexInfo).IsForPlatform()
 
@@ -625,23 +625,23 @@
 	}
 }
 
-func (a *AARImport) sdkVersion() sdkSpec {
-	return sdkSpecFrom(String(a.properties.Sdk_version))
+func (a *AARImport) SdkVersion() android.SdkSpec {
+	return android.SdkSpecFrom(String(a.properties.Sdk_version))
 }
 
-func (a *AARImport) systemModules() string {
+func (a *AARImport) SystemModules() string {
 	return ""
 }
 
-func (a *AARImport) minSdkVersion() sdkSpec {
+func (a *AARImport) MinSdkVersion() android.SdkSpec {
 	if a.properties.Min_sdk_version != nil {
-		return sdkSpecFrom(*a.properties.Min_sdk_version)
+		return android.SdkSpecFrom(*a.properties.Min_sdk_version)
 	}
-	return a.sdkVersion()
+	return a.SdkVersion()
 }
 
-func (a *AARImport) targetSdkVersion() sdkSpec {
-	return a.sdkVersion()
+func (a *AARImport) TargetSdkVersion() android.SdkSpec {
+	return a.SdkVersion()
 }
 
 func (a *AARImport) javaVersion() string {
@@ -700,7 +700,7 @@
 
 func (a *AARImport) DepsMutator(ctx android.BottomUpMutatorContext) {
 	if !ctx.Config().AlwaysUsePrebuiltSdks() {
-		sdkDep := decodeSdkDep(ctx, sdkContext(a))
+		sdkDep := decodeSdkDep(ctx, android.SdkContext(a))
 		if sdkDep.useModule && sdkDep.frameworkResModule != "" {
 			ctx.AddVariationDependencies(nil, frameworkResTag, sdkDep.frameworkResModule)
 		}
@@ -780,7 +780,7 @@
 	linkDeps = append(linkDeps, a.manifest)
 
 	transitiveStaticLibs, staticLibManifests, staticRRODirs, transitiveAssets, libDeps, libFlags :=
-		aaptLibs(ctx, sdkContext(a), nil)
+		aaptLibs(ctx, android.SdkContext(a), nil)
 
 	_ = staticLibManifests
 	_ = staticRRODirs
diff --git a/java/android_manifest.go b/java/android_manifest.go
index b30f3d2..8510f30 100644
--- a/java/android_manifest.go
+++ b/java/android_manifest.go
@@ -43,7 +43,7 @@
 	"args", "libs")
 
 // Uses manifest_fixer.py to inject minSdkVersion, etc. into an AndroidManifest.xml
-func manifestFixer(ctx android.ModuleContext, manifest android.Path, sdkContext sdkContext,
+func manifestFixer(ctx android.ModuleContext, manifest android.Path, sdkContext android.SdkContext,
 	classLoaderContexts dexpreopt.ClassLoaderContextMap, isLibrary, useEmbeddedNativeLibs, usesNonSdkApis,
 	useEmbeddedDex, hasNoCode bool, loggingParent string) android.Path {
 
@@ -51,11 +51,11 @@
 	if isLibrary {
 		args = append(args, "--library")
 	} else {
-		minSdkVersion, err := sdkContext.minSdkVersion().effectiveVersion(ctx)
+		minSdkVersion, err := sdkContext.MinSdkVersion().EffectiveVersion(ctx)
 		if err != nil {
 			ctx.ModuleErrorf("invalid minSdkVersion: %s", err)
 		}
-		if minSdkVersion >= 23 {
+		if minSdkVersion.FinalOrFutureInt() >= 23 {
 			args = append(args, fmt.Sprintf("--extract-native-libs=%v", !useEmbeddedNativeLibs))
 		} else if useEmbeddedNativeLibs {
 			ctx.ModuleErrorf("module attempted to store uncompressed native libraries, but minSdkVersion=%d doesn't support it",
@@ -87,7 +87,7 @@
 		args = append(args, "--logging-parent", loggingParent)
 	}
 	var deps android.Paths
-	targetSdkVersion, err := sdkContext.targetSdkVersion().effectiveVersionString(ctx)
+	targetSdkVersion, err := sdkContext.TargetSdkVersion().EffectiveVersionString(ctx)
 	if err != nil {
 		ctx.ModuleErrorf("invalid targetSdkVersion: %s", err)
 	}
@@ -96,7 +96,7 @@
 		deps = append(deps, ApiFingerprintPath(ctx))
 	}
 
-	minSdkVersion, err := sdkContext.minSdkVersion().effectiveVersionString(ctx)
+	minSdkVersion, err := sdkContext.MinSdkVersion().EffectiveVersionString(ctx)
 	if err != nil {
 		ctx.ModuleErrorf("invalid minSdkVersion: %s", err)
 	}
diff --git a/java/androidmk.go b/java/androidmk.go
index 3d3eae5..75661a7 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -106,7 +106,7 @@
 					if len(library.dexpreopter.builtInstalled) > 0 {
 						entries.SetString("LOCAL_SOONG_BUILT_INSTALLED", library.dexpreopter.builtInstalled)
 					}
-					entries.SetString("LOCAL_SDK_VERSION", library.sdkVersion().raw)
+					entries.SetString("LOCAL_SDK_VERSION", library.SdkVersion().Raw)
 					entries.SetPath("LOCAL_SOONG_CLASSES_JAR", library.implementationAndResourcesJar)
 					entries.SetPath("LOCAL_SOONG_HEADER_JAR", library.headerJarFile)
 
@@ -255,7 +255,7 @@
 				entries.SetPath("LOCAL_SOONG_EXPORT_PROGUARD_FLAGS", prebuilt.proguardFlags)
 				entries.SetPath("LOCAL_SOONG_STATIC_LIBRARY_EXTRA_PACKAGES", prebuilt.extraAaptPackagesFile)
 				entries.SetPath("LOCAL_FULL_MANIFEST_FILE", prebuilt.manifest)
-				entries.SetString("LOCAL_SDK_VERSION", prebuilt.sdkVersion().raw)
+				entries.SetString("LOCAL_SDK_VERSION", prebuilt.SdkVersion().Raw)
 			},
 		},
 	}}
diff --git a/java/app.go b/java/app.go
index 1b6e0e3..04406e7 100755
--- a/java/app.go
+++ b/java/app.go
@@ -213,16 +213,16 @@
 func (a *AndroidApp) DepsMutator(ctx android.BottomUpMutatorContext) {
 	a.Module.deps(ctx)
 
-	if String(a.appProperties.Stl) == "c++_shared" && !a.sdkVersion().specified() {
+	if String(a.appProperties.Stl) == "c++_shared" && !a.SdkVersion().Specified() {
 		ctx.PropertyErrorf("stl", "sdk_version must be set in order to use c++_shared")
 	}
 
-	sdkDep := decodeSdkDep(ctx, sdkContext(a))
+	sdkDep := decodeSdkDep(ctx, android.SdkContext(a))
 	if sdkDep.hasFrameworkLibs() {
 		a.aapt.deps(ctx, sdkDep)
 	}
 
-	usesSDK := a.sdkVersion().specified() && a.sdkVersion().kind != sdkCorePlatform
+	usesSDK := a.SdkVersion().Specified() && a.SdkVersion().Kind != android.SdkCorePlatform
 
 	if usesSDK && Bool(a.appProperties.Jni_uses_sdk_apis) {
 		ctx.PropertyErrorf("jni_uses_sdk_apis",
@@ -279,16 +279,16 @@
 
 func (a *AndroidApp) checkAppSdkVersions(ctx android.ModuleContext) {
 	if a.Updatable() {
-		if !a.sdkVersion().stable() {
-			ctx.PropertyErrorf("sdk_version", "Updatable apps must use stable SDKs, found %v", a.sdkVersion())
+		if !a.SdkVersion().Stable() {
+			ctx.PropertyErrorf("sdk_version", "Updatable apps must use stable SDKs, found %v", a.SdkVersion())
 		}
 		if String(a.deviceProperties.Min_sdk_version) == "" {
 			ctx.PropertyErrorf("updatable", "updatable apps must set min_sdk_version.")
 		}
 
-		if minSdkVersion, err := a.minSdkVersion().effectiveVersion(ctx); err == nil {
+		if minSdkVersion, err := a.MinSdkVersion().EffectiveVersion(ctx); err == nil {
 			a.checkJniLibsSdkVersion(ctx, minSdkVersion)
-			android.CheckMinSdkVersion(a, ctx, minSdkVersion.ApiLevel(ctx))
+			android.CheckMinSdkVersion(a, ctx, minSdkVersion)
 		} else {
 			ctx.PropertyErrorf("min_sdk_version", "%s", err.Error())
 		}
@@ -304,7 +304,7 @@
 // because, sdk_version is overridden by min_sdk_version (if set as smaller)
 // and sdkLinkType is checked with dependencies so we can be sure that the whole dependency tree
 // will meet the requirements.
-func (a *AndroidApp) checkJniLibsSdkVersion(ctx android.ModuleContext, minSdkVersion sdkVersion) {
+func (a *AndroidApp) checkJniLibsSdkVersion(ctx android.ModuleContext, minSdkVersion android.ApiLevel) {
 	// It's enough to check direct JNI deps' sdk_version because all transitive deps from JNI deps are checked in cc.checkLinkType()
 	ctx.VisitDirectDeps(func(m android.Module) {
 		if !IsJniDepTag(ctx.OtherModuleDependencyTag(m)) {
@@ -312,10 +312,10 @@
 		}
 		dep, _ := m.(*cc.Module)
 		// The domain of cc.sdk_version is "current" and <number>
-		// We can rely on sdkSpec to convert it to <number> so that "current" is handled
-		// properly regardless of sdk finalization.
-		jniSdkVersion, err := sdkSpecFrom(dep.SdkVersion()).effectiveVersion(ctx)
-		if err != nil || minSdkVersion < jniSdkVersion {
+		// We can rely on android.SdkSpec to convert it to <number> so that "current" is
+		// handled properly regardless of sdk finalization.
+		jniSdkVersion, err := android.SdkSpecFrom(dep.SdkVersion()).EffectiveVersion(ctx)
+		if err != nil || minSdkVersion.LessThan(jniSdkVersion) {
 			ctx.OtherModuleErrorf(dep, "sdk_version(%v) is higher than min_sdk_version(%v) of the containing android_app(%v)",
 				dep.SdkVersion(), minSdkVersion, ctx.ModuleName())
 			return
@@ -327,13 +327,13 @@
 // Returns true if the native libraries should be stored in the APK uncompressed and the
 // extractNativeLibs application flag should be set to false in the manifest.
 func (a *AndroidApp) useEmbeddedNativeLibs(ctx android.ModuleContext) bool {
-	minSdkVersion, err := a.minSdkVersion().effectiveVersion(ctx)
+	minSdkVersion, err := a.MinSdkVersion().EffectiveVersion(ctx)
 	if err != nil {
-		ctx.PropertyErrorf("min_sdk_version", "invalid value %q: %s", a.minSdkVersion(), err)
+		ctx.PropertyErrorf("min_sdk_version", "invalid value %q: %s", a.MinSdkVersion(), err)
 	}
 
 	apexInfo := ctx.Provider(android.ApexInfoProvider).(android.ApexInfo)
-	return (minSdkVersion >= 23 && Bool(a.appProperties.Use_embedded_native_libs)) ||
+	return (minSdkVersion.FinalOrFutureInt() >= 23 && Bool(a.appProperties.Use_embedded_native_libs)) ||
 		!apexInfo.IsForPlatform()
 }
 
@@ -380,7 +380,11 @@
 }
 
 func (a *AndroidApp) aaptBuildActions(ctx android.ModuleContext) {
-	a.aapt.usesNonSdkApis = Bool(a.Module.deviceProperties.Platform_apis)
+	usePlatformAPI := proptools.Bool(a.Module.deviceProperties.Platform_apis)
+	if ctx.Module().(android.SdkContext).SdkVersion().Kind == android.SdkModule {
+		usePlatformAPI = true
+	}
+	a.aapt.usesNonSdkApis = usePlatformAPI
 
 	// Ask manifest_fixer to add or update the application element indicating this app has no code.
 	a.aapt.hasNoCode = !a.hasCode(ctx)
@@ -419,7 +423,7 @@
 
 	a.aapt.splitNames = a.appProperties.Package_splits
 	a.aapt.LoggingParent = String(a.overridableAppProperties.Logging_parent)
-	a.aapt.buildActions(ctx, sdkContext(a), a.classLoaderContexts, aaptLinkFlags...)
+	a.aapt.buildActions(ctx, android.SdkContext(a), a.classLoaderContexts, aaptLinkFlags...)
 
 	// apps manifests are handled by aapt, don't let Module see them
 	a.properties.Manifest = nil
@@ -720,8 +724,8 @@
 }
 
 type appDepsInterface interface {
-	sdkVersion() sdkSpec
-	minSdkVersion() sdkSpec
+	SdkVersion() android.SdkSpec
+	MinSdkVersion() android.SdkSpec
 	RequiresStableAPIs(ctx android.BaseModuleContext) bool
 }
 
@@ -734,8 +738,8 @@
 	seenModulePaths := make(map[string]bool)
 
 	if checkNativeSdkVersion {
-		checkNativeSdkVersion = app.sdkVersion().specified() &&
-			app.sdkVersion().kind != sdkCorePlatform && !app.RequiresStableAPIs(ctx)
+		checkNativeSdkVersion = app.SdkVersion().Specified() &&
+			app.SdkVersion().Kind != android.SdkCorePlatform && !app.RequiresStableAPIs(ctx)
 	}
 
 	ctx.WalkDeps(func(module android.Module, parent android.Module) bool {
@@ -829,6 +833,10 @@
 				if v := m.MinSdkVersion(); v != "" {
 					toMinSdkVersion = v
 				}
+			} else if m, ok := to.(interface{ MinSdkVersionString() string }); ok {
+				if v := m.MinSdkVersionString(); v != "" {
+					toMinSdkVersion = v
+				}
 			}
 			depsInfo[depName] = android.ApexModuleDepInfo{
 				To:            depName,
@@ -840,7 +848,7 @@
 		return true
 	})
 
-	a.ApexBundleDepsInfo.BuildDepsInfoLists(ctx, a.MinSdkVersion(), depsInfo)
+	a.ApexBundleDepsInfo.BuildDepsInfoLists(ctx, a.MinSdkVersionString(), depsInfo)
 }
 
 func (a *AndroidApp) Updatable() bool {
@@ -1294,7 +1302,8 @@
 	// check is not necessary, and although it is good to have, it is difficult to maintain on
 	// non-linux build platforms where dexpreopt is generally disabled (the check may fail due to
 	// various unrelated reasons, such as a failure to get manifest from an APK).
-	if dexpreopt.GetGlobalConfig(ctx).DisablePreopt {
+	global := dexpreopt.GetGlobalConfig(ctx)
+	if global.DisablePreopt || global.OnlyPreoptBootImageAndSystemServer {
 		return inputFile
 	}
 
diff --git a/java/app_import.go b/java/app_import.go
index d4da64d..32cec23 100644
--- a/java/app_import.go
+++ b/java/app_import.go
@@ -394,12 +394,12 @@
 	return false
 }
 
-func (a *AndroidAppImport) sdkVersion() sdkSpec {
-	return sdkSpecFrom("")
+func (a *AndroidAppImport) SdkVersion() android.SdkSpec {
+	return android.SdkSpecFrom("")
 }
 
-func (a *AndroidAppImport) minSdkVersion() sdkSpec {
-	return sdkSpecFrom("")
+func (a *AndroidAppImport) MinSdkVersion() android.SdkSpec {
+	return android.SdkSpecFrom("")
 }
 
 var _ android.ApexModule = (*AndroidAppImport)(nil)
diff --git a/java/app_test.go b/java/app_test.go
index 825ad20..a99ac62 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -652,7 +652,7 @@
 			} else {
 				aapt2link = m.Output("package-res.apk")
 			}
-			aapt2link = aapt2link.RelativeToTop()
+			aapt2link = aapt2link
 			aapt2Flags := aapt2link.Args["flags"]
 			if test.assetFlag != "" {
 				android.AssertStringDoesContain(t, "asset flag", aapt2Flags, test.assetFlag)
@@ -1993,14 +1993,14 @@
 		`)
 
 	// Verify baz, which depends on the overridden module foo, has the correct classpath javac arg.
-	javac := ctx.ModuleForTests("baz", "android_common").Rule("javac").RelativeToTop()
+	javac := ctx.ModuleForTests("baz", "android_common").Rule("javac")
 	fooTurbine := "out/soong/.intermediates/foo/android_common/turbine-combined/foo.jar"
 	if !strings.Contains(javac.Args["classpath"], fooTurbine) {
 		t.Errorf("baz classpath %v does not contain %q", javac.Args["classpath"], fooTurbine)
 	}
 
 	// Verify qux, which depends on the overriding module bar, has the correct classpath javac arg.
-	javac = ctx.ModuleForTests("qux", "android_common").Rule("javac").RelativeToTop()
+	javac = ctx.ModuleForTests("qux", "android_common").Rule("javac")
 	barTurbine := "out/soong/.intermediates/foo/android_common_bar/turbine-combined/foo.jar"
 	if !strings.Contains(javac.Args["classpath"], barTurbine) {
 		t.Errorf("qux classpath %v does not contain %q", javac.Args["classpath"], barTurbine)
@@ -2077,7 +2077,7 @@
 		}
 
 		// Check if javac classpath has the correct jar file path. This checks instrumentation_for overrides.
-		javac := variant.Rule("javac").RelativeToTop()
+		javac := variant.Rule("javac")
 		turbine := filepath.Join("out", "soong", ".intermediates", "foo", expected.targetVariant, "turbine-combined", "foo.jar")
 		if !strings.Contains(javac.Args["classpath"], turbine) {
 			t.Errorf("classpath %q does not contain %q", javac.Args["classpath"], turbine)
@@ -2151,7 +2151,7 @@
 
 	for _, test := range testCases {
 		variant := ctx.ModuleForTests(test.moduleName, test.variantName)
-		params := variant.MaybeOutput("test_config_fixer/AndroidTest.xml").RelativeToTop()
+		params := variant.MaybeOutput("test_config_fixer/AndroidTest.xml")
 
 		if len(test.expectedFlags) > 0 {
 			if params.Rule == nil {
@@ -2647,14 +2647,14 @@
 		t.Errorf("GENRULE_NOTICE is missing from notice files, %q", noticeInputs)
 	}
 	// aapt2 flags should include -A <NOTICE dir> so that its contents are put in the APK's /assets.
-	res := foo.Output("package-res.apk").RelativeToTop()
+	res := foo.Output("package-res.apk")
 	aapt2Flags := res.Args["flags"]
 	e := "-A out/soong/.intermediates/foo/android_common/NOTICE"
 	android.AssertStringDoesContain(t, "expected.apkPath", aapt2Flags, e)
 
 	// bar has NOTICE files to process, but embed_notices is not set.
 	bar := result.ModuleForTests("bar", "android_common")
-	res = bar.Output("package-res.apk").RelativeToTop()
+	res = bar.Output("package-res.apk")
 	aapt2Flags = res.Args["flags"]
 	e = "-A out/soong/.intermediates/bar/android_common/NOTICE"
 	android.AssertStringDoesNotContain(t, "bar shouldn't have the asset dir flag for NOTICE", aapt2Flags, e)
diff --git a/java/base.go b/java/base.go
index bd394af..9bc0738 100644
--- a/java/base.go
+++ b/java/base.go
@@ -373,11 +373,11 @@
 }
 
 func (j *Module) CheckStableSdkVersion() error {
-	sdkVersion := j.sdkVersion()
-	if sdkVersion.stable() {
+	sdkVersion := j.SdkVersion()
+	if sdkVersion.Stable() {
 		return nil
 	}
-	if sdkVersion.kind == sdkCorePlatform {
+	if sdkVersion.Kind == android.SdkCorePlatform {
 		if useLegacyCorePlatformApiByName(j.BaseModuleName()) {
 			return fmt.Errorf("non stable SDK %v - uses legacy core platform", sdkVersion)
 		} else {
@@ -392,8 +392,8 @@
 // checkSdkVersions enforces restrictions around SDK dependencies.
 func (j *Module) checkSdkVersions(ctx android.ModuleContext) {
 	if j.RequiresStableAPIs(ctx) {
-		if sc, ok := ctx.Module().(sdkContext); ok {
-			if !sc.sdkVersion().specified() {
+		if sc, ok := ctx.Module().(android.SdkContext); ok {
+			if !sc.SdkVersion().Specified() {
 				ctx.PropertyErrorf("sdk_version",
 					"sdk_version must have a value when the module is located at vendor or product(only if PRODUCT_ENFORCE_PRODUCT_PARTITION_INTERFACE is set).")
 			}
@@ -416,9 +416,9 @@
 }
 
 func (j *Module) checkPlatformAPI(ctx android.ModuleContext) {
-	if sc, ok := ctx.Module().(sdkContext); ok {
+	if sc, ok := ctx.Module().(android.SdkContext); ok {
 		usePlatformAPI := proptools.Bool(j.deviceProperties.Platform_apis)
-		sdkVersionSpecified := sc.sdkVersion().specified()
+		sdkVersionSpecified := sc.SdkVersion().Specified()
 		if usePlatformAPI && sdkVersionSpecified {
 			ctx.PropertyErrorf("platform_apis", "platform_apis must be false when sdk_version is not empty.")
 		} else if !usePlatformAPI && !sdkVersionSpecified {
@@ -512,30 +512,30 @@
 	return false
 }
 
-func (j *Module) sdkVersion() sdkSpec {
-	return sdkSpecFrom(String(j.deviceProperties.Sdk_version))
+func (j *Module) SdkVersion() android.SdkSpec {
+	return android.SdkSpecFrom(String(j.deviceProperties.Sdk_version))
 }
 
-func (j *Module) systemModules() string {
+func (j *Module) SystemModules() string {
 	return proptools.String(j.deviceProperties.System_modules)
 }
 
-func (j *Module) minSdkVersion() sdkSpec {
+func (j *Module) MinSdkVersion() android.SdkSpec {
 	if j.deviceProperties.Min_sdk_version != nil {
-		return sdkSpecFrom(*j.deviceProperties.Min_sdk_version)
+		return android.SdkSpecFrom(*j.deviceProperties.Min_sdk_version)
 	}
-	return j.sdkVersion()
+	return j.SdkVersion()
 }
 
-func (j *Module) targetSdkVersion() sdkSpec {
+func (j *Module) TargetSdkVersion() android.SdkSpec {
 	if j.deviceProperties.Target_sdk_version != nil {
-		return sdkSpecFrom(*j.deviceProperties.Target_sdk_version)
+		return android.SdkSpecFrom(*j.deviceProperties.Target_sdk_version)
 	}
-	return j.sdkVersion()
+	return j.SdkVersion()
 }
 
-func (j *Module) MinSdkVersion() string {
-	return j.minSdkVersion().version.String()
+func (j *Module) MinSdkVersionString() string {
+	return j.MinSdkVersion().ApiLevel.String()
 }
 
 func (j *Module) AvailableFor(what string) bool {
@@ -552,7 +552,7 @@
 	if ctx.Device() {
 		j.linter.deps(ctx)
 
-		sdkDeps(ctx, sdkContext(j), j.dexer)
+		sdkDeps(ctx, android.SdkContext(j), j.dexer)
 
 		if j.deviceProperties.SyspropPublicStub != "" {
 			// This is a sysprop implementation library that has a corresponding sysprop public
@@ -702,7 +702,7 @@
 	var flags javaBuilderFlags
 
 	// javaVersion flag.
-	flags.javaVersion = getJavaVersion(ctx, String(j.properties.Java_version), sdkContext(j))
+	flags.javaVersion = getJavaVersion(ctx, String(j.properties.Java_version), android.SdkContext(j))
 
 	if ctx.Config().RunErrorProne() {
 		if config.ErrorProneClasspath == nil && ctx.Config().TestProductVariables == nil {
@@ -731,7 +731,7 @@
 	flags.processors = android.FirstUniqueStrings(flags.processors)
 
 	if len(flags.bootClasspath) == 0 && ctx.Host() && !flags.javaVersion.usesJavaModules() &&
-		decodeSdkDep(ctx, sdkContext(j)).hasStandardLibs() {
+		decodeSdkDep(ctx, android.SdkContext(j)).hasStandardLibs() {
 		// Give host-side tools a version of OpenJDK's standard libraries
 		// close to what they're targeting. As of Dec 2017, AOSP is only
 		// bundling OpenJDK 8 and 9, so nothing < 8 is available.
@@ -1209,7 +1209,7 @@
 			}
 			// Dex compilation
 			var dexOutputFile android.OutputPath
-			dexOutputFile = j.dexer.compileDex(ctx, flags, j.minSdkVersion(), outputFile, jarName)
+			dexOutputFile = j.dexer.compileDex(ctx, flags, j.MinSdkVersion(), outputFile, jarName)
 			if ctx.Failed() {
 				return
 			}
@@ -1254,8 +1254,8 @@
 	}
 
 	if ctx.Device() {
-		lintSDKVersionString := func(sdkSpec sdkSpec) string {
-			if v := sdkSpec.version; v.isNumbered() {
+		lintSDKVersionString := func(sdkSpec android.SdkSpec) string {
+			if v := sdkSpec.ApiLevel; !v.IsPreview() {
 				return v.String()
 			} else {
 				return ctx.Config().DefaultAppTargetSdk(ctx).String()
@@ -1267,9 +1267,9 @@
 		j.linter.srcJars = srcJars
 		j.linter.classpath = append(append(android.Paths(nil), flags.bootClasspath...), flags.classpath...)
 		j.linter.classes = j.implementationJarFile
-		j.linter.minSdkVersion = lintSDKVersionString(j.minSdkVersion())
-		j.linter.targetSdkVersion = lintSDKVersionString(j.targetSdkVersion())
-		j.linter.compileSdkVersion = lintSDKVersionString(j.sdkVersion())
+		j.linter.minSdkVersion = lintSDKVersionString(j.MinSdkVersion())
+		j.linter.targetSdkVersion = lintSDKVersionString(j.TargetSdkVersion())
+		j.linter.compileSdkVersion = lintSDKVersionString(j.SdkVersion())
 		j.linter.javaLanguageLevel = flags.javaVersion.String()
 		j.linter.kotlinLanguageLevel = "1.3"
 		if !apexInfo.IsForPlatform() && ctx.Config().UnbundledBuildApps() {
@@ -1471,18 +1471,18 @@
 // Implements android.ApexModule
 func (j *Module) ShouldSupportSdkVersion(ctx android.BaseModuleContext,
 	sdkVersion android.ApiLevel) error {
-	sdkSpec := j.minSdkVersion()
-	if !sdkSpec.specified() {
+	sdkSpec := j.MinSdkVersion()
+	if !sdkSpec.Specified() {
 		return fmt.Errorf("min_sdk_version is not specified")
 	}
-	if sdkSpec.kind == sdkCore {
+	if sdkSpec.Kind == android.SdkCore {
 		return nil
 	}
-	ver, err := sdkSpec.effectiveVersion(ctx)
+	ver, err := sdkSpec.EffectiveVersion(ctx)
 	if err != nil {
 		return err
 	}
-	if ver.ApiLevel(ctx).GreaterThan(sdkVersion) {
+	if ver.GreaterThan(sdkVersion) {
 		return fmt.Errorf("newer SDK(%v)", ver)
 	}
 	return nil
@@ -1576,24 +1576,24 @@
 		return linkType, true
 	}
 
-	ver := m.sdkVersion()
-	switch ver.kind {
-	case sdkCore:
+	ver := m.SdkVersion()
+	switch ver.Kind {
+	case android.SdkCore:
 		return javaCore, false
-	case sdkSystem:
+	case android.SdkSystem:
 		return javaSystem, false
-	case sdkPublic:
+	case android.SdkPublic:
 		return javaSdk, false
-	case sdkModule:
+	case android.SdkModule:
 		return javaModule, false
-	case sdkSystemServer:
+	case android.SdkSystemServer:
 		return javaSystemServer, false
-	case sdkPrivate, sdkNone, sdkCorePlatform, sdkTest:
+	case android.SdkPrivate, android.SdkNone, android.SdkCorePlatform, android.SdkTest:
 		return javaPlatform, false
 	}
 
-	if !ver.valid() {
-		panic(fmt.Errorf("sdk_version is invalid. got %q", ver.raw))
+	if !ver.Valid() {
+		panic(fmt.Errorf("sdk_version is invalid. got %q", ver.Raw))
 	}
 	return javaSdk, false
 }
@@ -1625,7 +1625,7 @@
 	var deps deps
 
 	if ctx.Device() {
-		sdkDep := decodeSdkDep(ctx, sdkContext(j))
+		sdkDep := decodeSdkDep(ctx, android.SdkContext(j))
 		if sdkDep.invalidVersion {
 			ctx.AddMissingDependencies(sdkDep.bootclasspath)
 			ctx.AddMissingDependencies(sdkDep.java9Classpath)
@@ -1656,7 +1656,7 @@
 		if dep, ok := module.(SdkLibraryDependency); ok {
 			switch tag {
 			case libTag:
-				deps.classpath = append(deps.classpath, dep.SdkHeaderJars(ctx, j.sdkVersion())...)
+				deps.classpath = append(deps.classpath, dep.SdkHeaderJars(ctx, j.SdkVersion())...)
 			case staticLibTag:
 				ctx.ModuleErrorf("dependency on java_sdk_library %q can only be in libs", otherName)
 			}
diff --git a/java/boot_image.go b/java/boot_image.go
index a14940d..0c47976 100644
--- a/java/boot_image.go
+++ b/java/boot_image.go
@@ -20,6 +20,7 @@
 
 	"android/soong/android"
 	"android/soong/dexpreopt"
+	"github.com/google/blueprint/proptools"
 
 	"github.com/google/blueprint"
 )
@@ -27,24 +28,62 @@
 func init() {
 	RegisterBootImageBuildComponents(android.InitRegistrationContext)
 
+	// TODO(b/177892522): Remove after has been replaced by bootclasspath_fragments
 	android.RegisterSdkMemberType(&bootImageMemberType{
 		SdkMemberTypeBase: android.SdkMemberTypeBase{
 			PropertyName: "boot_images",
 			SupportsSdk:  true,
 		},
 	})
+
+	android.RegisterSdkMemberType(&bootImageMemberType{
+		SdkMemberTypeBase: android.SdkMemberTypeBase{
+			PropertyName: "bootclasspath_fragments",
+			SupportsSdk:  true,
+		},
+	})
 }
 
 func RegisterBootImageBuildComponents(ctx android.RegistrationContext) {
+	// TODO(b/177892522): Remove after has been replaced by bootclasspath_fragment
 	ctx.RegisterModuleType("boot_image", bootImageFactory)
 	ctx.RegisterModuleType("prebuilt_boot_image", prebuiltBootImageFactory)
+
+	ctx.RegisterModuleType("bootclasspath_fragment", bootImageFactory)
+	ctx.RegisterModuleType("prebuilt_bootclasspath_fragment", prebuiltBootImageFactory)
+}
+
+type bootImageContentDependencyTag struct {
+	blueprint.BaseDependencyTag
+}
+
+// Avoid having to make boot image content visible to the boot image.
+//
+// This is a temporary workaround to make it easier to migrate to boot image modules with proper
+// dependencies.
+// TODO(b/177892522): Remove this and add needed visibility.
+func (b bootImageContentDependencyTag) ExcludeFromVisibilityEnforcement() {
+}
+
+// The tag used for the dependency between the boot image module and its contents.
+var bootImageContentDepTag = bootImageContentDependencyTag{}
+
+var _ android.ExcludeFromVisibilityEnforcementTag = bootImageContentDepTag
+
+func IsbootImageContentDepTag(tag blueprint.DependencyTag) bool {
+	return tag == bootImageContentDepTag
 }
 
 type bootImageProperties struct {
 	// The name of the image this represents.
 	//
-	// Must be one of "art" or "boot".
-	Image_name string
+	// If specified then it must be one of "art" or "boot".
+	Image_name *string
+
+	// The contents of this boot image, could be either java_library, java_sdk_library, or boot_image.
+	//
+	// The order of this list matters as it is the order that is used in the bootclasspath.
+	Contents []string
 }
 
 type BootImageModule struct {
@@ -60,9 +99,59 @@
 	android.InitApexModule(m)
 	android.InitSdkAwareModule(m)
 	android.InitAndroidArchModule(m, android.HostAndDeviceSupported, android.MultilibCommon)
+
+	// Perform some consistency checking to ensure that the configuration is correct.
+	android.AddLoadHook(m, func(ctx android.LoadHookContext) {
+		bootImageConsistencyCheck(ctx, m)
+	})
 	return m
 }
 
+func bootImageConsistencyCheck(ctx android.EarlyModuleContext, m *BootImageModule) {
+	contents := m.properties.Contents
+	if m.properties.Image_name == nil && len(contents) == 0 {
+		ctx.ModuleErrorf(`neither of the "image_name" and "contents" properties have been supplied, please supply exactly one`)
+	}
+	if m.properties.Image_name != nil && len(contents) != 0 {
+		ctx.ModuleErrorf(`both of the "image_name" and "contents" properties have been supplied, please supply exactly one`)
+	}
+	imageName := proptools.String(m.properties.Image_name)
+	if imageName == "art" {
+		// Get the configuration for the art apex jars. Do not use getImageConfig(ctx) here as this is
+		// too early in the Soong processing for that to work.
+		global := dexpreopt.GetGlobalConfig(ctx)
+		modules := global.ArtApexJars
+
+		// Make sure that the apex specified in the configuration is consistent and is one for which
+		// this boot image is available.
+		jars := []string{}
+		commonApex := ""
+		for i := 0; i < modules.Len(); i++ {
+			apex := modules.Apex(i)
+			jar := modules.Jar(i)
+			if apex == "platform" {
+				ctx.ModuleErrorf("ArtApexJars is invalid as it requests a platform variant of %q", jar)
+				continue
+			}
+			if !m.AvailableFor(apex) {
+				ctx.ModuleErrorf("incompatible with ArtApexJars which expects this to be in apex %q but this is only in apexes %q",
+					apex, m.ApexAvailable())
+				continue
+			}
+			if commonApex == "" {
+				commonApex = apex
+			} else if commonApex != apex {
+				ctx.ModuleErrorf("ArtApexJars configuration is inconsistent, expected all jars to be in the same apex but it specifies apex %q and %q",
+					commonApex, apex)
+			}
+			jars = append(jars, jar)
+		}
+
+		// Store the jars in the Contents property so that they can be used to add dependencies.
+		m.properties.Contents = jars
+	}
+}
+
 var BootImageInfoProvider = blueprint.NewProvider(BootImageInfo{})
 
 type BootImageInfo struct {
@@ -96,6 +185,10 @@
 
 func (b *BootImageModule) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
 	tag := ctx.OtherModuleDependencyTag(dep)
+	if tag == bootImageContentDepTag {
+		// Boot image contents are automatically added to apex.
+		return true
+	}
 	if android.IsMetaDependencyTag(tag) {
 		// Cross-cutting metadata dependencies are metadata.
 		return false
@@ -108,6 +201,8 @@
 }
 
 func (b *BootImageModule) DepsMutator(ctx android.BottomUpMutatorContext) {
+	ctx.AddDependency(ctx.Module(), bootImageContentDepTag, b.properties.Contents...)
+
 	if SkipDexpreoptBootJars(ctx) {
 		return
 	}
@@ -127,14 +222,8 @@
 	// GenerateSingletonBuildActions method as it cannot create it for itself.
 	dexpreopt.GetGlobalSoongConfig(ctx)
 
-	// Get a map of the image configs that are supported.
-	imageConfigs := genBootImageConfigs(ctx)
-
-	// Retrieve the config for this image.
-	imageName := b.properties.Image_name
-	imageConfig := imageConfigs[imageName]
+	imageConfig := b.getImageConfig(ctx)
 	if imageConfig == nil {
-		ctx.PropertyErrorf("image_name", "Unknown image name %q, expected one of %s", imageName, strings.Join(android.SortedStringKeys(imageConfigs), ", "))
 		return
 	}
 
@@ -145,6 +234,25 @@
 	ctx.SetProvider(BootImageInfoProvider, info)
 }
 
+func (b *BootImageModule) getImageConfig(ctx android.EarlyModuleContext) *bootImageConfig {
+	// Get a map of the image configs that are supported.
+	imageConfigs := genBootImageConfigs(ctx)
+
+	// Retrieve the config for this image.
+	imageNamePtr := b.properties.Image_name
+	if imageNamePtr == nil {
+		return nil
+	}
+
+	imageName := *imageNamePtr
+	imageConfig := imageConfigs[imageName]
+	if imageConfig == nil {
+		ctx.PropertyErrorf("image_name", "Unknown image name %q, expected one of %s", imageName, strings.Join(android.SortedStringKeys(imageConfigs), ", "))
+		return nil
+	}
+	return imageConfig
+}
+
 type bootImageMemberType struct {
 	android.SdkMemberTypeBase
 }
@@ -159,7 +267,11 @@
 }
 
 func (b *bootImageMemberType) AddPrebuiltModule(ctx android.SdkMemberContext, member android.SdkMember) android.BpModule {
-	return ctx.SnapshotBuilder().AddPrebuiltModule(member, "prebuilt_boot_image")
+	if b.PropertyName == "boot_images" {
+		return ctx.SnapshotBuilder().AddPrebuiltModule(member, "prebuilt_boot_image")
+	} else {
+		return ctx.SnapshotBuilder().AddPrebuiltModule(member, "prebuilt_bootclasspath_fragment")
+	}
 }
 
 func (b *bootImageMemberType) CreateVariantPropertiesStruct() android.SdkMemberProperties {
@@ -169,7 +281,7 @@
 type bootImageSdkMemberProperties struct {
 	android.SdkMemberPropertiesBase
 
-	Image_name string
+	Image_name *string
 }
 
 func (b *bootImageSdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) {
@@ -179,8 +291,8 @@
 }
 
 func (b *bootImageSdkMemberProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) {
-	if b.Image_name != "" {
-		propertySet.AddProperty("image_name", b.Image_name)
+	if b.Image_name != nil {
+		propertySet.AddProperty("image_name", *b.Image_name)
 	}
 }
 
@@ -213,5 +325,10 @@
 	android.InitApexModule(m)
 	android.InitSdkAwareModule(m)
 	android.InitAndroidArchModule(m, android.HostAndDeviceSupported, android.MultilibCommon)
+
+	// Perform some consistency checking to ensure that the configuration is correct.
+	android.AddLoadHook(m, func(ctx android.LoadHookContext) {
+		bootImageConsistencyCheck(ctx, &m.BootImageModule)
+	})
 	return m
 }
diff --git a/java/boot_image_test.go b/java/boot_image_test.go
index 65e590d..e1866de 100644
--- a/java/boot_image_test.go
+++ b/java/boot_image_test.go
@@ -16,25 +16,112 @@
 
 import (
 	"testing"
+
+	"android/soong/android"
+	"android/soong/dexpreopt"
 )
 
 // Contains some simple tests for boot_image logic, additional tests can be found in
 // apex/boot_image_test.go as the ART boot image requires modules from the ART apex.
 
+var prepareForTestWithBootImage = android.GroupFixturePreparers(
+	PrepareForTestWithJavaDefaultModules,
+	dexpreopt.PrepareForTestByEnablingDexpreopt,
+)
+
 func TestUnknownBootImage(t *testing.T) {
-	testJavaError(t, "image_name: Unknown image name \\\"unknown\\\", expected one of art, boot", `
-		boot_image {
-			name: "unknown-boot-image",
-			image_name: "unknown",
-		}
-`)
+	prepareForTestWithBootImage.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+			`\Qimage_name: Unknown image name "unknown", expected one of art, boot\E`)).
+		RunTestWithBp(t, `
+			boot_image {
+				name: "unknown-boot-image",
+				image_name: "unknown",
+			}
+		`)
+}
+
+func TestUnknownBootclasspathFragmentImageName(t *testing.T) {
+	prepareForTestWithBootImage.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+			`\Qimage_name: Unknown image name "unknown", expected one of art, boot\E`)).
+		RunTestWithBp(t, `
+			bootclasspath_fragment {
+				name: "unknown-boot-image",
+				image_name: "unknown",
+			}
+		`)
 }
 
 func TestUnknownPrebuiltBootImage(t *testing.T) {
-	testJavaError(t, "image_name: Unknown image name \\\"unknown\\\", expected one of art, boot", `
-		prebuilt_boot_image {
-			name: "unknown-boot-image",
-			image_name: "unknown",
-		}
-`)
+	prepareForTestWithBootImage.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+			`\Qimage_name: Unknown image name "unknown", expected one of art, boot\E`)).
+		RunTestWithBp(t, `
+			prebuilt_boot_image {
+				name: "unknown-boot-image",
+				image_name: "unknown",
+			}
+		`)
+}
+
+func TestBootImageInconsistentArtConfiguration_Platform(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForTestWithBootImage,
+		dexpreopt.FixtureSetArtBootJars("platform:foo", "apex:bar"),
+	).
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+			`\QArtApexJars is invalid as it requests a platform variant of "foo"\E`)).
+		RunTestWithBp(t, `
+			boot_image {
+				name: "boot-image",
+				image_name: "art",
+				apex_available: [
+					"apex",
+				],
+			}
+		`)
+}
+
+func TestBootImageInconsistentArtConfiguration_ApexMixture(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForTestWithBootImage,
+		dexpreopt.FixtureSetArtBootJars("apex1:foo", "apex2:bar"),
+	).
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+			`\QArtApexJars configuration is inconsistent, expected all jars to be in the same apex but it specifies apex "apex1" and "apex2"\E`)).
+		RunTestWithBp(t, `
+			boot_image {
+				name: "boot-image",
+				image_name: "art",
+				apex_available: [
+					"apex1",
+					"apex2",
+				],
+			}
+		`)
+}
+
+func TestBootImageWithoutImageNameOrContents(t *testing.T) {
+	prepareForTestWithBootImage.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+			`\Qneither of the "image_name" and "contents" properties\E`)).
+		RunTestWithBp(t, `
+			boot_image {
+				name: "boot-image",
+			}
+		`)
+}
+
+func TestBootImageWithImageNameAndContents(t *testing.T) {
+	prepareForTestWithBootImage.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+			`\Qboth of the "image_name" and "contents" properties\E`)).
+		RunTestWithBp(t, `
+			boot_image {
+				name: "boot-image",
+				image_name: "boot",
+				contents: ["other"],
+			}
+		`)
 }
diff --git a/java/boot_jars.go b/java/boot_jars.go
index ac8107b..1fb3deb 100644
--- a/java/boot_jars.go
+++ b/java/boot_jars.go
@@ -56,16 +56,7 @@
 	if !module.Enabled() {
 		return false
 	}
-	if module.IsReplacedByPrebuilt() {
-		// A source module that has been replaced by a prebuilt counterpart.
-		return false
-	}
-	if prebuilt, ok := module.(android.PrebuiltInterface); ok {
-		if p := prebuilt.Prebuilt(); p != nil {
-			return p.UsePrebuilt()
-		}
-	}
-	return true
+	return android.IsModulePreferred(module)
 }
 
 func (b *bootJarsSingleton) GenerateBuildActions(ctx android.SingletonContext) {
diff --git a/java/builder.go b/java/builder.go
index fc740a8..cde8731 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -150,7 +150,7 @@
 		&remoteexec.REParams{Labels: map[string]string{"type": "tool", "name": "turbine"},
 			ExecStrategy:      "${config.RETurbineExecStrategy}",
 			Inputs:            []string{"${config.TurbineJar}", "${out}.rsp", "$implicits"},
-			RSPFile:           "${out}.rsp",
+			RSPFiles:          []string{"${out}.rsp"},
 			OutputFiles:       []string{"$out.tmp"},
 			OutputDirectories: []string{"$outDir"},
 			ToolchainInputs:   []string{"${config.JavaCmd}"},
@@ -167,7 +167,7 @@
 		&remoteexec.REParams{
 			ExecStrategy: "${config.REJarExecStrategy}",
 			Inputs:       []string{"${config.SoongZipCmd}", "${out}.rsp"},
-			RSPFile:      "${out}.rsp",
+			RSPFiles:     []string{"${out}.rsp"},
 			OutputFiles:  []string{"$out"},
 			Platform:     map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"},
 		}, []string{"jarArgs"}, nil)
@@ -182,7 +182,7 @@
 		&remoteexec.REParams{
 			ExecStrategy: "${config.REZipExecStrategy}",
 			Inputs:       []string{"${config.SoongZipCmd}", "${out}.rsp", "$implicits"},
-			RSPFile:      "${out}.rsp",
+			RSPFiles:     []string{"${out}.rsp"},
 			OutputFiles:  []string{"$out"},
 			Platform:     map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"},
 		}, []string{"jarArgs"}, []string{"implicits"})
diff --git a/java/config/kotlin.go b/java/config/kotlin.go
index fd8e3db..6cb61f3 100644
--- a/java/config/kotlin.go
+++ b/java/config/kotlin.go
@@ -35,11 +35,16 @@
 	pctx.SourcePathVariable("KotlinAnnotationJar", "external/kotlinc/lib/annotations-13.0.jar")
 	pctx.SourcePathVariable("KotlinStdlibJar", KotlinStdlibJar)
 
-	// These flags silence "Illegal reflective access" warnings when running kotlinc in OpenJDK9
-	pctx.StaticVariable("KotlincSuppressJDK9Warnings", strings.Join([]string{
+	// These flags silence "Illegal reflective access" warnings when running kapt in OpenJDK9+
+	pctx.StaticVariable("KaptSuppressJDK9Warnings", strings.Join([]string{
 		"-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
 		"-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
 		"-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED",
 		"-J--add-opens=java.base/sun.net.www.protocol.jar=ALL-UNNAMED",
 	}, " "))
+
+	// These flags silence "Illegal reflective access" warnings when running kotlinc in OpenJDK9+
+	pctx.StaticVariable("KotlincSuppressJDK9Warnings", strings.Join([]string{
+		"-J--add-opens=java.base/java.util=ALL-UNNAMED", // https://youtrack.jetbrains.com/issue/KT-43704
+	}, " "))
 }
diff --git a/java/dex.go b/java/dex.go
index b042f13..7898e9d 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -15,6 +15,7 @@
 package java
 
 import (
+	"strconv"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -157,7 +158,7 @@
 	}, []string{"outDir", "outDict", "outUsage", "outUsageZip", "outUsageDir",
 		"r8Flags", "zipFlags"}, []string{"implicits"})
 
-func (d *dexer) dexCommonFlags(ctx android.ModuleContext, minSdkVersion sdkSpec) []string {
+func (d *dexer) dexCommonFlags(ctx android.ModuleContext, minSdkVersion android.SdkSpec) []string {
 	flags := d.dexProperties.Dxflags
 	// Translate all the DX flags to D8 ones until all the build files have been migrated
 	// to D8 flags. See: b/69377755
@@ -174,12 +175,12 @@
 			"--verbose")
 	}
 
-	effectiveVersion, err := minSdkVersion.effectiveVersion(ctx)
+	effectiveVersion, err := minSdkVersion.EffectiveVersion(ctx)
 	if err != nil {
 		ctx.PropertyErrorf("min_sdk_version", "%s", err)
 	}
 
-	flags = append(flags, "--min-api "+effectiveVersion.asNumberString())
+	flags = append(flags, "--min-api "+strconv.Itoa(effectiveVersion.FinalOrFutureInt()))
 	return flags
 }
 
@@ -266,7 +267,7 @@
 	return r8Flags, r8Deps
 }
 
-func (d *dexer) compileDex(ctx android.ModuleContext, flags javaBuilderFlags, minSdkVersion sdkSpec,
+func (d *dexer) compileDex(ctx android.ModuleContext, flags javaBuilderFlags, minSdkVersion android.SdkSpec,
 	classesJar android.Path, jarName string) android.OutputPath {
 
 	// Compile classes.jar into classes.dex and then javalib.jar
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 6dc408b..7137f33 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -214,7 +214,7 @@
 var artApexNames = []string{
 	"com.android.art",
 	"com.android.art.debug",
-	"com.android.art,testing",
+	"com.android.art.testing",
 	"com.google.android.art",
 	"com.google.android.art.debug",
 	"com.google.android.art.testing",
diff --git a/java/droiddoc.go b/java/droiddoc.go
index a892b36..e527d59 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -23,12 +23,10 @@
 
 	"android/soong/android"
 	"android/soong/java/config"
-	"android/soong/remoteexec"
 )
 
 func init() {
 	RegisterDocsBuildComponents(android.InitRegistrationContext)
-	RegisterStubsBuildComponents(android.InitRegistrationContext)
 }
 
 func RegisterDocsBuildComponents(ctx android.RegistrationContext) {
@@ -41,19 +39,6 @@
 	ctx.RegisterModuleType("javadoc_host", JavadocHostFactory)
 }
 
-func RegisterStubsBuildComponents(ctx android.RegistrationContext) {
-	ctx.RegisterModuleType("stubs_defaults", StubsDefaultsFactory)
-
-	ctx.RegisterModuleType("droidstubs", DroidstubsFactory)
-	ctx.RegisterModuleType("droidstubs_host", DroidstubsHostFactory)
-
-	ctx.RegisterModuleType("prebuilt_stubs_sources", PrebuiltStubsSourcesFactory)
-}
-
-var (
-	srcsLibTag = dependencyTag{name: "sources from javalib"}
-)
-
 type JavadocProperties struct {
 	// list of source files used to compile the Java module.  May be .java, .logtags, .proto,
 	// or .aidl files.
@@ -113,10 +98,6 @@
 
 	// names of the output files used in args that will be generated
 	Out []string
-
-	// If set, metalava is sandboxed to only read files explicitly specified on the command
-	// line. Defaults to false.
-	Sandbox *bool
 }
 
 type ApiToCheck struct {
@@ -177,80 +158,6 @@
 	Compat_config *string `android:"path"`
 }
 
-type DroidstubsProperties struct {
-	// The generated public API filename by Metalava, defaults to <module>_api.txt
-	Api_filename *string
-
-	// the generated removed API filename by Metalava, defaults to <module>_removed.txt
-	Removed_api_filename *string
-
-	// the generated removed Dex API filename by Metalava.
-	Removed_dex_api_filename *string
-
-	Check_api struct {
-		Last_released ApiToCheck
-
-		Current ApiToCheck
-
-		Api_lint struct {
-			Enabled *bool
-
-			// If set, performs api_lint on any new APIs not found in the given signature file
-			New_since *string `android:"path"`
-
-			// If not blank, path to the baseline txt file for approved API lint violations.
-			Baseline_file *string `android:"path"`
-		}
-	}
-
-	// user can specify the version of previous released API file in order to do compatibility check.
-	Previous_api *string `android:"path"`
-
-	// is set to true, Metalava will allow framework SDK to contain annotations.
-	Annotations_enabled *bool
-
-	// a list of top-level directories containing files to merge qualifier annotations (i.e. those intended to be included in the stubs written) from.
-	Merge_annotations_dirs []string
-
-	// a list of top-level directories containing Java stub files to merge show/hide annotations from.
-	Merge_inclusion_annotations_dirs []string
-
-	// a file containing a list of classes to do nullability validation for.
-	Validate_nullability_from_list *string
-
-	// a file containing expected warnings produced by validation of nullability annotations.
-	Check_nullability_warnings *string
-
-	// if set to true, allow Metalava to generate doc_stubs source files. Defaults to false.
-	Create_doc_stubs *bool
-
-	// if set to true, cause Metalava to output Javadoc comments in the stubs source files. Defaults to false.
-	// Has no effect if create_doc_stubs: true.
-	Output_javadoc_comments *bool
-
-	// if set to false then do not write out stubs. Defaults to true.
-	//
-	// TODO(b/146727827): Remove capability when we do not need to generate stubs and API separately.
-	Generate_stubs *bool
-
-	// if set to true, provides a hint to the build system that this rule uses a lot of memory,
-	// whicih can be used for scheduling purposes
-	High_mem *bool
-
-	// is set to true, Metalava will allow framework SDK to contain API levels annotations.
-	Api_levels_annotations_enabled *bool
-
-	// the dirs which Metalava extracts API levels annotations from.
-	Api_levels_annotations_dirs []string
-
-	// the filename which Metalava extracts API levels annotations from. Defaults to android.jar.
-	Api_levels_jar_filename *string
-
-	// if set to true, collect the values used by the Dev tools and
-	// write them in files packaged with the SDK. Defaults to false.
-	Write_sdk_values *bool
-}
-
 //
 // Common flags passed down to build rule
 //
@@ -315,11 +222,8 @@
 	srcJars     android.Paths
 	srcFiles    android.Paths
 	sourcepaths android.Paths
-	argFiles    android.Paths
 	implicits   android.Paths
 
-	args []string
-
 	docZip      android.WritablePath
 	stubsSrcJar android.WritablePath
 }
@@ -357,25 +261,25 @@
 
 var _ android.OutputFileProducer = (*Javadoc)(nil)
 
-func (j *Javadoc) sdkVersion() sdkSpec {
-	return sdkSpecFrom(String(j.properties.Sdk_version))
+func (j *Javadoc) SdkVersion() android.SdkSpec {
+	return android.SdkSpecFrom(String(j.properties.Sdk_version))
 }
 
-func (j *Javadoc) systemModules() string {
+func (j *Javadoc) SystemModules() string {
 	return proptools.String(j.properties.System_modules)
 }
 
-func (j *Javadoc) minSdkVersion() sdkSpec {
-	return j.sdkVersion()
+func (j *Javadoc) MinSdkVersion() android.SdkSpec {
+	return j.SdkVersion()
 }
 
-func (j *Javadoc) targetSdkVersion() sdkSpec {
-	return j.sdkVersion()
+func (j *Javadoc) TargetSdkVersion() android.SdkSpec {
+	return j.SdkVersion()
 }
 
 func (j *Javadoc) addDeps(ctx android.BottomUpMutatorContext) {
 	if ctx.Device() {
-		sdkDep := decodeSdkDep(ctx, sdkContext(j))
+		sdkDep := decodeSdkDep(ctx, android.SdkContext(j))
 		if sdkDep.useModule {
 			ctx.AddVariationDependencies(nil, bootClasspathTag, sdkDep.bootclasspath...)
 			ctx.AddVariationDependencies(nil, systemModulesTag, sdkDep.systemModules)
@@ -453,7 +357,7 @@
 func (j *Javadoc) collectDeps(ctx android.ModuleContext) deps {
 	var deps deps
 
-	sdkDep := decodeSdkDep(ctx, sdkContext(j))
+	sdkDep := decodeSdkDep(ctx, android.SdkContext(j))
 	if sdkDep.invalidVersion {
 		ctx.AddMissingDependencies(sdkDep.bootclasspath)
 		ctx.AddMissingDependencies(sdkDep.java9Classpath)
@@ -482,7 +386,7 @@
 			}
 		case libTag:
 			if dep, ok := module.(SdkLibraryDependency); ok {
-				deps.classpath = append(deps.classpath, dep.SdkHeaderJars(ctx, j.sdkVersion())...)
+				deps.classpath = append(deps.classpath, dep.SdkHeaderJars(ctx, j.SdkVersion())...)
 			} else if ctx.OtherModuleHasProvider(module, JavaInfoProvider) {
 				dep := ctx.OtherModuleProvider(module, JavaInfoProvider).(JavaInfo)
 				deps.classpath = append(deps.classpath, dep.HeaderJars...)
@@ -569,15 +473,20 @@
 		j.sourcepaths = android.PathsForModuleSrc(ctx, []string{"."})
 	}
 
-	j.argFiles = android.PathsForModuleSrc(ctx, j.properties.Arg_files)
+	return deps
+}
+
+func (j *Javadoc) expandArgs(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
+	var argFiles android.Paths
 	argFilesMap := map[string]string{}
 	argFileLabels := []string{}
 
 	for _, label := range j.properties.Arg_files {
 		var paths = android.PathsForModuleSrc(ctx, []string{label})
 		if _, exists := argFilesMap[label]; !exists {
-			argFilesMap[label] = strings.Join(paths.Strings(), " ")
+			argFilesMap[label] = strings.Join(cmd.PathsForInputs(paths), " ")
 			argFileLabels = append(argFileLabels, label)
+			argFiles = append(argFiles, paths...)
 		} else {
 			ctx.ModuleErrorf("multiple arg_files for %q, %q and %q",
 				label, argFilesMap[label], paths)
@@ -597,7 +506,7 @@
 	}
 
 	for _, flag := range flags {
-		args, err := android.Expand(flag, func(name string) (string, error) {
+		expanded, err := android.Expand(flag, func(name string) (string, error) {
 			if strings.HasPrefix(name, "location ") {
 				label := strings.TrimSpace(strings.TrimPrefix(name, "location "))
 				if paths, ok := argFilesMap[label]; ok {
@@ -615,10 +524,10 @@
 		if err != nil {
 			ctx.PropertyErrorf(argsPropertyName, "%s", err.Error())
 		}
-		j.args = append(j.args, args)
+		cmd.Flag(expanded)
 	}
 
-	return deps
+	cmd.Implicits(argFiles)
 }
 
 func (j *Javadoc) DepsMutator(ctx android.BottomUpMutatorContext) {
@@ -642,7 +551,7 @@
 
 	srcJarList := zipSyncCmd(ctx, rule, srcJarDir, j.srcJars)
 
-	javaVersion := getJavaVersion(ctx, String(j.properties.Java_version), sdkContext(j))
+	javaVersion := getJavaVersion(ctx, String(j.properties.Java_version), android.SdkContext(j))
 
 	cmd := javadocSystemModulesCmd(ctx, rule, j.srcFiles, outDir, srcJarDir, srcJarList,
 		deps.systemModules, deps.classpath, j.sourcepaths)
@@ -652,6 +561,8 @@
 		Flag("-XDignore.symbol.file").
 		Flag("-Xdoclint:none")
 
+	j.expandArgs(ctx, cmd)
+
 	rule.Command().
 		BuiltTool("soong_zip").
 		Flag("-write_if_changed").
@@ -910,7 +821,7 @@
 			deps.bootClasspath, deps.classpath, d.Javadoc.sourcepaths)
 	}
 
-	cmd.Flag(strings.Join(d.Javadoc.args, " ")).Implicits(d.Javadoc.argFiles)
+	d.expandArgs(ctx, cmd)
 
 	if d.properties.Compat_config != nil {
 		compatConfig := android.PathForModuleSrc(ctx, String(d.properties.Compat_config))
@@ -947,669 +858,9 @@
 }
 
 //
-// Droidstubs
-//
-type Droidstubs struct {
-	Javadoc
-	android.SdkBase
-
-	properties              DroidstubsProperties
-	apiFile                 android.WritablePath
-	apiXmlFile              android.WritablePath
-	lastReleasedApiXmlFile  android.WritablePath
-	privateApiFile          android.WritablePath
-	removedApiFile          android.WritablePath
-	removedDexApiFile       android.WritablePath
-	nullabilityWarningsFile android.WritablePath
-
-	checkCurrentApiTimestamp      android.WritablePath
-	updateCurrentApiTimestamp     android.WritablePath
-	checkLastReleasedApiTimestamp android.WritablePath
-	apiLintTimestamp              android.WritablePath
-	apiLintReport                 android.WritablePath
-
-	checkNullabilityWarningsTimestamp android.WritablePath
-
-	annotationsZip android.WritablePath
-	apiVersionsXml android.WritablePath
-
-	apiFilePath        android.Path
-	removedApiFilePath android.Path
-
-	metadataZip android.WritablePath
-	metadataDir android.WritablePath
-}
-
-// droidstubs passes sources files through Metalava to generate stub .java files that only contain the API to be
-// documented, filtering out hidden classes and methods.  The resulting .java files are intended to be passed to
-// a droiddoc module to generate documentation.
-func DroidstubsFactory() android.Module {
-	module := &Droidstubs{}
-
-	module.AddProperties(&module.properties,
-		&module.Javadoc.properties)
-
-	InitDroiddocModule(module, android.HostAndDeviceSupported)
-	android.InitSdkAwareModule(module)
-	return module
-}
-
-// droidstubs_host passes sources files through Metalava to generate stub .java files that only contain the API
-// to be documented, filtering out hidden classes and methods.  The resulting .java files are intended to be
-// passed to a droiddoc_host module to generate documentation.  Use a droidstubs_host instead of a droidstubs
-// module when symbols needed by the source files are provided by java_library_host modules.
-func DroidstubsHostFactory() android.Module {
-	module := &Droidstubs{}
-
-	module.AddProperties(&module.properties,
-		&module.Javadoc.properties)
-
-	InitDroiddocModule(module, android.HostSupported)
-	return module
-}
-
-func (d *Droidstubs) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		return android.Paths{d.stubsSrcJar}, nil
-	case ".docs.zip":
-		return android.Paths{d.docZip}, nil
-	case ".api.txt", android.DefaultDistTag:
-		// This is the default dist path for dist properties that have no tag property.
-		return android.Paths{d.apiFilePath}, nil
-	case ".removed-api.txt":
-		return android.Paths{d.removedApiFilePath}, nil
-	case ".annotations.zip":
-		return android.Paths{d.annotationsZip}, nil
-	case ".api_versions.xml":
-		return android.Paths{d.apiVersionsXml}, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
-func (d *Droidstubs) ApiFilePath() android.Path {
-	return d.apiFilePath
-}
-
-func (d *Droidstubs) RemovedApiFilePath() android.Path {
-	return d.removedApiFilePath
-}
-
-func (d *Droidstubs) StubsSrcJar() android.Path {
-	return d.stubsSrcJar
-}
-
-func (d *Droidstubs) DepsMutator(ctx android.BottomUpMutatorContext) {
-	d.Javadoc.addDeps(ctx)
-
-	if len(d.properties.Merge_annotations_dirs) != 0 {
-		for _, mergeAnnotationsDir := range d.properties.Merge_annotations_dirs {
-			ctx.AddDependency(ctx.Module(), metalavaMergeAnnotationsDirTag, mergeAnnotationsDir)
-		}
-	}
-
-	if len(d.properties.Merge_inclusion_annotations_dirs) != 0 {
-		for _, mergeInclusionAnnotationsDir := range d.properties.Merge_inclusion_annotations_dirs {
-			ctx.AddDependency(ctx.Module(), metalavaMergeInclusionAnnotationsDirTag, mergeInclusionAnnotationsDir)
-		}
-	}
-
-	if len(d.properties.Api_levels_annotations_dirs) != 0 {
-		for _, apiLevelsAnnotationsDir := range d.properties.Api_levels_annotations_dirs {
-			ctx.AddDependency(ctx.Module(), metalavaAPILevelsAnnotationsDirTag, apiLevelsAnnotationsDir)
-		}
-	}
-}
-
-func (d *Droidstubs) stubsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, stubsDir android.OptionalPath) {
-	if apiCheckEnabled(ctx, d.properties.Check_api.Current, "current") ||
-		apiCheckEnabled(ctx, d.properties.Check_api.Last_released, "last_released") ||
-		String(d.properties.Api_filename) != "" {
-		filename := proptools.StringDefault(d.properties.Api_filename, ctx.ModuleName()+"_api.txt")
-		d.apiFile = android.PathForModuleOut(ctx, filename)
-		cmd.FlagWithOutput("--api ", d.apiFile)
-		d.apiFilePath = d.apiFile
-	} else if sourceApiFile := proptools.String(d.properties.Check_api.Current.Api_file); sourceApiFile != "" {
-		// If check api is disabled then make the source file available for export.
-		d.apiFilePath = android.PathForModuleSrc(ctx, sourceApiFile)
-	}
-
-	if apiCheckEnabled(ctx, d.properties.Check_api.Current, "current") ||
-		apiCheckEnabled(ctx, d.properties.Check_api.Last_released, "last_released") ||
-		String(d.properties.Removed_api_filename) != "" {
-		filename := proptools.StringDefault(d.properties.Removed_api_filename, ctx.ModuleName()+"_removed.txt")
-		d.removedApiFile = android.PathForModuleOut(ctx, filename)
-		cmd.FlagWithOutput("--removed-api ", d.removedApiFile)
-		d.removedApiFilePath = d.removedApiFile
-	} else if sourceRemovedApiFile := proptools.String(d.properties.Check_api.Current.Removed_api_file); sourceRemovedApiFile != "" {
-		// If check api is disabled then make the source removed api file available for export.
-		d.removedApiFilePath = android.PathForModuleSrc(ctx, sourceRemovedApiFile)
-	}
-
-	if String(d.properties.Removed_dex_api_filename) != "" {
-		d.removedDexApiFile = android.PathForModuleOut(ctx, String(d.properties.Removed_dex_api_filename))
-		cmd.FlagWithOutput("--removed-dex-api ", d.removedDexApiFile)
-	}
-
-	if Bool(d.properties.Write_sdk_values) {
-		d.metadataDir = android.PathForModuleOut(ctx, "metadata")
-		cmd.FlagWithArg("--sdk-values ", d.metadataDir.String())
-	}
-
-	if stubsDir.Valid() {
-		if Bool(d.properties.Create_doc_stubs) {
-			cmd.FlagWithArg("--doc-stubs ", stubsDir.String())
-		} else {
-			cmd.FlagWithArg("--stubs ", stubsDir.String())
-			if !Bool(d.properties.Output_javadoc_comments) {
-				cmd.Flag("--exclude-documentation-from-stubs")
-			}
-		}
-	}
-}
-
-func (d *Droidstubs) annotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
-	if Bool(d.properties.Annotations_enabled) {
-		cmd.Flag("--include-annotations")
-
-		validatingNullability :=
-			android.InList("--validate-nullability-from-merged-stubs", d.Javadoc.args) ||
-				String(d.properties.Validate_nullability_from_list) != ""
-
-		migratingNullability := String(d.properties.Previous_api) != ""
-		if migratingNullability {
-			previousApi := android.PathForModuleSrc(ctx, String(d.properties.Previous_api))
-			cmd.FlagWithInput("--migrate-nullness ", previousApi)
-		}
-
-		if s := String(d.properties.Validate_nullability_from_list); s != "" {
-			cmd.FlagWithInput("--validate-nullability-from-list ", android.PathForModuleSrc(ctx, s))
-		}
-
-		if validatingNullability {
-			d.nullabilityWarningsFile = android.PathForModuleOut(ctx, ctx.ModuleName()+"_nullability_warnings.txt")
-			cmd.FlagWithOutput("--nullability-warnings-txt ", d.nullabilityWarningsFile)
-		}
-
-		d.annotationsZip = android.PathForModuleOut(ctx, ctx.ModuleName()+"_annotations.zip")
-		cmd.FlagWithOutput("--extract-annotations ", d.annotationsZip)
-
-		if len(d.properties.Merge_annotations_dirs) != 0 {
-			d.mergeAnnoDirFlags(ctx, cmd)
-		}
-
-		// TODO(tnorbye): find owners to fix these warnings when annotation was enabled.
-		cmd.FlagWithArg("--hide ", "HiddenTypedefConstant").
-			FlagWithArg("--hide ", "SuperfluousPrefix").
-			FlagWithArg("--hide ", "AnnotationExtraction")
-	}
-}
-
-func (d *Droidstubs) mergeAnnoDirFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
-	ctx.VisitDirectDepsWithTag(metalavaMergeAnnotationsDirTag, func(m android.Module) {
-		if t, ok := m.(*ExportedDroiddocDir); ok {
-			cmd.FlagWithArg("--merge-qualifier-annotations ", t.dir.String()).Implicits(t.deps)
-		} else {
-			ctx.PropertyErrorf("merge_annotations_dirs",
-				"module %q is not a metalava merge-annotations dir", ctx.OtherModuleName(m))
-		}
-	})
-}
-
-func (d *Droidstubs) inclusionAnnotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
-	ctx.VisitDirectDepsWithTag(metalavaMergeInclusionAnnotationsDirTag, func(m android.Module) {
-		if t, ok := m.(*ExportedDroiddocDir); ok {
-			cmd.FlagWithArg("--merge-inclusion-annotations ", t.dir.String()).Implicits(t.deps)
-		} else {
-			ctx.PropertyErrorf("merge_inclusion_annotations_dirs",
-				"module %q is not a metalava merge-annotations dir", ctx.OtherModuleName(m))
-		}
-	})
-}
-
-func (d *Droidstubs) apiLevelsAnnotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
-	if !Bool(d.properties.Api_levels_annotations_enabled) {
-		return
-	}
-
-	d.apiVersionsXml = android.PathForModuleOut(ctx, "api-versions.xml")
-
-	if len(d.properties.Api_levels_annotations_dirs) == 0 {
-		ctx.PropertyErrorf("api_levels_annotations_dirs",
-			"has to be non-empty if api levels annotations was enabled!")
-	}
-
-	cmd.FlagWithOutput("--generate-api-levels ", d.apiVersionsXml)
-	cmd.FlagWithInput("--apply-api-levels ", d.apiVersionsXml)
-	cmd.FlagWithArg("--current-version ", ctx.Config().PlatformSdkVersion().String())
-	cmd.FlagWithArg("--current-codename ", ctx.Config().PlatformSdkCodename())
-
-	filename := proptools.StringDefault(d.properties.Api_levels_jar_filename, "android.jar")
-
-	ctx.VisitDirectDepsWithTag(metalavaAPILevelsAnnotationsDirTag, func(m android.Module) {
-		if t, ok := m.(*ExportedDroiddocDir); ok {
-			for _, dep := range t.deps {
-				if strings.HasSuffix(dep.String(), filename) {
-					cmd.Implicit(dep)
-				}
-			}
-			cmd.FlagWithArg("--android-jar-pattern ", t.dir.String()+"/%/public/"+filename)
-		} else {
-			ctx.PropertyErrorf("api_levels_annotations_dirs",
-				"module %q is not a metalava api-levels-annotations dir", ctx.OtherModuleName(m))
-		}
-	})
-}
-
-func metalavaCmd(ctx android.ModuleContext, rule *android.RuleBuilder, javaVersion javaVersion, srcs android.Paths,
-	srcJarList android.Path, bootclasspath, classpath classpath, sourcepaths android.Paths,
-	implicitsRsp, homeDir android.WritablePath, sandbox bool) *android.RuleBuilderCommand {
-	rule.Command().Text("rm -rf").Flag(homeDir.String())
-	rule.Command().Text("mkdir -p").Flag(homeDir.String())
-
-	cmd := rule.Command()
-	cmd.FlagWithArg("ANDROID_SDK_HOME=", homeDir.String())
-
-	if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_METALAVA") {
-		rule.Remoteable(android.RemoteRuleSupports{RBE: true})
-		pool := ctx.Config().GetenvWithDefault("RBE_METALAVA_POOL", "metalava")
-		execStrategy := ctx.Config().GetenvWithDefault("RBE_METALAVA_EXEC_STRATEGY", remoteexec.LocalExecStrategy)
-		labels := map[string]string{"type": "compile", "lang": "java", "compiler": "metalava"}
-		if !sandbox {
-			execStrategy = remoteexec.LocalExecStrategy
-			labels["shallow"] = "true"
-		}
-		inputs := []string{
-			ctx.Config().HostJavaToolPath(ctx, "metalava").String(),
-			homeDir.String(),
-		}
-		if v := ctx.Config().Getenv("RBE_METALAVA_INPUTS"); v != "" {
-			inputs = append(inputs, strings.Split(v, ",")...)
-		}
-		cmd.Text((&remoteexec.REParams{
-			Labels:               labels,
-			ExecStrategy:         execStrategy,
-			Inputs:               inputs,
-			RSPFile:              implicitsRsp.String(),
-			ToolchainInputs:      []string{config.JavaCmd(ctx).String()},
-			Platform:             map[string]string{remoteexec.PoolKey: pool},
-			EnvironmentVariables: []string{"ANDROID_SDK_HOME"},
-		}).NoVarTemplate(ctx.Config().RBEWrapper()))
-	}
-
-	cmd.BuiltTool("metalava").
-		Flag(config.JavacVmFlags).
-		Flag("-J--add-opens=java.base/java.util=ALL-UNNAMED").
-		FlagWithArg("-encoding ", "UTF-8").
-		FlagWithArg("-source ", javaVersion.String()).
-		FlagWithRspFileInputList("@", android.PathForModuleOut(ctx, "metalava.rsp"), srcs).
-		FlagWithInput("@", srcJarList)
-
-	if javaHome := ctx.Config().Getenv("ANDROID_JAVA_HOME"); javaHome != "" {
-		cmd.Implicit(android.PathForSource(ctx, javaHome))
-	}
-
-	if sandbox {
-		cmd.FlagWithOutput("--strict-input-files ", android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"violations.txt"))
-	} else {
-		cmd.FlagWithOutput("--strict-input-files:warn ", android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"violations.txt"))
-	}
-
-	if implicitsRsp != nil {
-		cmd.FlagWithArg("--strict-input-files-exempt ", "@"+implicitsRsp.String())
-	}
-
-	if len(bootclasspath) > 0 {
-		cmd.FlagWithInputList("-bootclasspath ", bootclasspath.Paths(), ":")
-	}
-
-	if len(classpath) > 0 {
-		cmd.FlagWithInputList("-classpath ", classpath.Paths(), ":")
-	}
-
-	if len(sourcepaths) > 0 {
-		cmd.FlagWithList("-sourcepath ", sourcepaths.Strings(), ":")
-	} else {
-		cmd.FlagWithArg("-sourcepath ", `""`)
-	}
-
-	cmd.Flag("--no-banner").
-		Flag("--color").
-		Flag("--quiet").
-		Flag("--format=v2").
-		FlagWithArg("--repeat-errors-max ", "10").
-		FlagWithArg("--hide ", "UnresolvedImport")
-
-	return cmd
-}
-
-func (d *Droidstubs) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	deps := d.Javadoc.collectDeps(ctx)
-
-	javaVersion := getJavaVersion(ctx, String(d.Javadoc.properties.Java_version), sdkContext(d))
-
-	// Create rule for metalava
-
-	srcJarDir := android.PathForModuleOut(ctx, "srcjars")
-
-	rule := android.NewRuleBuilder(pctx, ctx)
-
-	if BoolDefault(d.properties.High_mem, false) {
-		// This metalava run uses lots of memory, restrict the number of metalava jobs that can run in parallel.
-		rule.HighMem()
-	}
-
-	generateStubs := BoolDefault(d.properties.Generate_stubs, true)
-	var stubsDir android.OptionalPath
-	if generateStubs {
-		d.Javadoc.stubsSrcJar = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"stubs.srcjar")
-		stubsDir = android.OptionalPathForPath(android.PathForModuleOut(ctx, "stubsDir"))
-		rule.Command().Text("rm -rf").Text(stubsDir.String())
-		rule.Command().Text("mkdir -p").Text(stubsDir.String())
-	}
-
-	srcJarList := zipSyncCmd(ctx, rule, srcJarDir, d.Javadoc.srcJars)
-
-	implicitsRsp := android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"implicits.rsp")
-	homeDir := android.PathForModuleOut(ctx, "metalava-home")
-	cmd := metalavaCmd(ctx, rule, javaVersion, d.Javadoc.srcFiles, srcJarList,
-		deps.bootClasspath, deps.classpath, d.Javadoc.sourcepaths, implicitsRsp, homeDir,
-		Bool(d.Javadoc.properties.Sandbox))
-	cmd.Implicits(d.Javadoc.implicits)
-
-	d.stubsFlags(ctx, cmd, stubsDir)
-
-	d.annotationsFlags(ctx, cmd)
-	d.inclusionAnnotationsFlags(ctx, cmd)
-	d.apiLevelsAnnotationsFlags(ctx, cmd)
-
-	if android.InList("--generate-documentation", d.Javadoc.args) {
-		// Currently Metalava have the ability to invoke Javadoc in a seperate process.
-		// Pass "-nodocs" to suppress the Javadoc invocation when Metalava receives
-		// "--generate-documentation" arg. This is not needed when Metalava removes this feature.
-		d.Javadoc.args = append(d.Javadoc.args, "-nodocs")
-	}
-
-	cmd.Flag(strings.Join(d.Javadoc.args, " ")).Implicits(d.Javadoc.argFiles)
-	for _, o := range d.Javadoc.properties.Out {
-		cmd.ImplicitOutput(android.PathForModuleGen(ctx, o))
-	}
-
-	// Add options for the other optional tasks: API-lint and check-released.
-	// We generate separate timestamp files for them.
-
-	doApiLint := false
-	doCheckReleased := false
-
-	// Add API lint options.
-
-	if BoolDefault(d.properties.Check_api.Api_lint.Enabled, false) {
-		doApiLint = true
-
-		newSince := android.OptionalPathForModuleSrc(ctx, d.properties.Check_api.Api_lint.New_since)
-		if newSince.Valid() {
-			cmd.FlagWithInput("--api-lint ", newSince.Path())
-		} else {
-			cmd.Flag("--api-lint")
-		}
-		d.apiLintReport = android.PathForModuleOut(ctx, "api_lint_report.txt")
-		cmd.FlagWithOutput("--report-even-if-suppressed ", d.apiLintReport) // TODO:  Change to ":api-lint"
-
-		// TODO(b/154317059): Clean up this whitelist by baselining and/or checking in last-released.
-		if d.Name() != "android.car-system-stubs-docs" &&
-			d.Name() != "android.car-stubs-docs" {
-			cmd.Flag("--lints-as-errors")
-			cmd.Flag("--warnings-as-errors") // Most lints are actually warnings.
-		}
-
-		baselineFile := android.OptionalPathForModuleSrc(ctx, d.properties.Check_api.Api_lint.Baseline_file)
-		updatedBaselineOutput := android.PathForModuleOut(ctx, "api_lint_baseline.txt")
-		d.apiLintTimestamp = android.PathForModuleOut(ctx, "api_lint.timestamp")
-
-		// Note this string includes a special shell quote $' ... ', which decodes the "\n"s.
-		// However, because $' ... ' doesn't expand environmental variables, we can't just embed
-		// $PWD, so we have to terminate $'...', use "$PWD", then start $' ... ' again,
-		// which is why we have '"$PWD"$' in it.
-		//
-		// TODO: metalava also has a slightly different message hardcoded. Should we unify this
-		// message and metalava's one?
-		msg := `$'` + // Enclose with $' ... '
-			`************************************************************\n` +
-			`Your API changes are triggering API Lint warnings or errors.\n` +
-			`To make these errors go away, fix the code according to the\n` +
-			`error and/or warning messages above.\n` +
-			`\n` +
-			`If it is not possible to do so, there are workarounds:\n` +
-			`\n` +
-			`1. You can suppress the errors with @SuppressLint("<id>")\n`
-
-		if baselineFile.Valid() {
-			cmd.FlagWithInput("--baseline:api-lint ", baselineFile.Path())
-			cmd.FlagWithOutput("--update-baseline:api-lint ", updatedBaselineOutput)
-
-			msg += fmt.Sprintf(``+
-				`2. You can update the baseline by executing the following\n`+
-				`   command:\n`+
-				`       cp \\\n`+
-				`       "'"$PWD"$'/%s" \\\n`+
-				`       "'"$PWD"$'/%s"\n`+
-				`   To submit the revised baseline.txt to the main Android\n`+
-				`   repository, you will need approval.\n`, updatedBaselineOutput, baselineFile.Path())
-		} else {
-			msg += fmt.Sprintf(``+
-				`2. You can add a baseline file of existing lint failures\n`+
-				`   to the build rule of %s.\n`, d.Name())
-		}
-		// Note the message ends with a ' (single quote), to close the $' ... ' .
-		msg += `************************************************************\n'`
-
-		cmd.FlagWithArg("--error-message:api-lint ", msg)
-	}
-
-	// Add "check released" options. (Detect incompatible API changes from the last public release)
-
-	if apiCheckEnabled(ctx, d.properties.Check_api.Last_released, "last_released") {
-		doCheckReleased = true
-
-		if len(d.Javadoc.properties.Out) > 0 {
-			ctx.PropertyErrorf("out", "out property may not be combined with check_api")
-		}
-
-		apiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Last_released.Api_file))
-		removedApiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Last_released.Removed_api_file))
-		baselineFile := android.OptionalPathForModuleSrc(ctx, d.properties.Check_api.Last_released.Baseline_file)
-		updatedBaselineOutput := android.PathForModuleOut(ctx, "last_released_baseline.txt")
-
-		d.checkLastReleasedApiTimestamp = android.PathForModuleOut(ctx, "check_last_released_api.timestamp")
-
-		cmd.FlagWithInput("--check-compatibility:api:released ", apiFile)
-		cmd.FlagWithInput("--check-compatibility:removed:released ", removedApiFile)
-
-		if baselineFile.Valid() {
-			cmd.FlagWithInput("--baseline:compatibility:released ", baselineFile.Path())
-			cmd.FlagWithOutput("--update-baseline:compatibility:released ", updatedBaselineOutput)
-		}
-
-		// Note this string includes quote ($' ... '), which decodes the "\n"s.
-		msg := `$'\n******************************\n` +
-			`You have tried to change the API from what has been previously released in\n` +
-			`an SDK.  Please fix the errors listed above.\n` +
-			`******************************\n'`
-
-		cmd.FlagWithArg("--error-message:compatibility:released ", msg)
-	}
-
-	impRule := android.NewRuleBuilder(pctx, ctx)
-	impCmd := impRule.Command()
-	// An action that copies the ninja generated rsp file to a new location. This allows us to
-	// add a large number of inputs to a file without exceeding bash command length limits (which
-	// would happen if we use the WriteFile rule). The cp is needed because RuleBuilder sets the
-	// rsp file to be ${output}.rsp.
-	impCmd.Text("cp").
-		FlagWithRspFileInputList("", android.PathForModuleOut(ctx, "metalava-implicits.rsp"), cmd.GetImplicits()).
-		Output(implicitsRsp)
-	impRule.Build("implicitsGen", "implicits generation")
-	cmd.Implicit(implicitsRsp)
-
-	if generateStubs {
-		rule.Command().
-			BuiltTool("soong_zip").
-			Flag("-write_if_changed").
-			Flag("-jar").
-			FlagWithOutput("-o ", d.Javadoc.stubsSrcJar).
-			FlagWithArg("-C ", stubsDir.String()).
-			FlagWithArg("-D ", stubsDir.String())
-	}
-
-	if Bool(d.properties.Write_sdk_values) {
-		d.metadataZip = android.PathForModuleOut(ctx, ctx.ModuleName()+"-metadata.zip")
-		rule.Command().
-			BuiltTool("soong_zip").
-			Flag("-write_if_changed").
-			Flag("-d").
-			FlagWithOutput("-o ", d.metadataZip).
-			FlagWithArg("-C ", d.metadataDir.String()).
-			FlagWithArg("-D ", d.metadataDir.String())
-	}
-
-	// TODO: We don't really need two separate API files, but this is a reminiscence of how
-	// we used to run metalava separately for API lint and the "last_released" check. Unify them.
-	if doApiLint {
-		rule.Command().Text("touch").Output(d.apiLintTimestamp)
-	}
-	if doCheckReleased {
-		rule.Command().Text("touch").Output(d.checkLastReleasedApiTimestamp)
-	}
-
-	rule.Restat()
-
-	zipSyncCleanupCmd(rule, srcJarDir)
-
-	rule.Build("metalava", "metalava merged")
-
-	if apiCheckEnabled(ctx, d.properties.Check_api.Current, "current") {
-
-		if len(d.Javadoc.properties.Out) > 0 {
-			ctx.PropertyErrorf("out", "out property may not be combined with check_api")
-		}
-
-		apiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Current.Api_file))
-		removedApiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Current.Removed_api_file))
-		baselineFile := android.OptionalPathForModuleSrc(ctx, d.properties.Check_api.Current.Baseline_file)
-
-		if baselineFile.Valid() {
-			ctx.PropertyErrorf("baseline_file", "current API check can't have a baseline file. (module %s)", ctx.ModuleName())
-		}
-
-		d.checkCurrentApiTimestamp = android.PathForModuleOut(ctx, "check_current_api.timestamp")
-
-		rule := android.NewRuleBuilder(pctx, ctx)
-
-		// Diff command line.
-		// -F matches the closest "opening" line, such as "package android {"
-		// and "  public class Intent {".
-		diff := `diff -u -F '{ *$'`
-
-		rule.Command().Text("( true")
-		rule.Command().
-			Text(diff).
-			Input(apiFile).Input(d.apiFile)
-
-		rule.Command().
-			Text(diff).
-			Input(removedApiFile).Input(d.removedApiFile)
-
-		msg := fmt.Sprintf(`\n******************************\n`+
-			`You have tried to change the API from what has been previously approved.\n\n`+
-			`To make these errors go away, you have two choices:\n`+
-			`   1. You can add '@hide' javadoc comments (and remove @SystemApi/@TestApi/etc)\n`+
-			`      to the new methods, etc. shown in the above diff.\n\n`+
-			`   2. You can update current.txt and/or removed.txt by executing the following command:\n`+
-			`         m %s-update-current-api\n\n`+
-			`      To submit the revised current.txt to the main Android repository,\n`+
-			`      you will need approval.\n`+
-			`******************************\n`, ctx.ModuleName())
-
-		rule.Command().
-			Text("touch").Output(d.checkCurrentApiTimestamp).
-			Text(") || (").
-			Text("echo").Flag("-e").Flag(`"` + msg + `"`).
-			Text("; exit 38").
-			Text(")")
-
-		rule.Build("metalavaCurrentApiCheck", "check current API")
-
-		d.updateCurrentApiTimestamp = android.PathForModuleOut(ctx, "update_current_api.timestamp")
-
-		// update API rule
-		rule = android.NewRuleBuilder(pctx, ctx)
-
-		rule.Command().Text("( true")
-
-		rule.Command().
-			Text("cp").Flag("-f").
-			Input(d.apiFile).Flag(apiFile.String())
-
-		rule.Command().
-			Text("cp").Flag("-f").
-			Input(d.removedApiFile).Flag(removedApiFile.String())
-
-		msg = "failed to update public API"
-
-		rule.Command().
-			Text("touch").Output(d.updateCurrentApiTimestamp).
-			Text(") || (").
-			Text("echo").Flag("-e").Flag(`"` + msg + `"`).
-			Text("; exit 38").
-			Text(")")
-
-		rule.Build("metalavaCurrentApiUpdate", "update current API")
-	}
-
-	if String(d.properties.Check_nullability_warnings) != "" {
-		if d.nullabilityWarningsFile == nil {
-			ctx.PropertyErrorf("check_nullability_warnings",
-				"Cannot specify check_nullability_warnings unless validating nullability")
-		}
-
-		checkNullabilityWarnings := android.PathForModuleSrc(ctx, String(d.properties.Check_nullability_warnings))
-
-		d.checkNullabilityWarningsTimestamp = android.PathForModuleOut(ctx, "check_nullability_warnings.timestamp")
-
-		msg := fmt.Sprintf(`\n******************************\n`+
-			`The warnings encountered during nullability annotation validation did\n`+
-			`not match the checked in file of expected warnings. The diffs are shown\n`+
-			`above. You have two options:\n`+
-			`   1. Resolve the differences by editing the nullability annotations.\n`+
-			`   2. Update the file of expected warnings by running:\n`+
-			`         cp %s %s\n`+
-			`       and submitting the updated file as part of your change.`,
-			d.nullabilityWarningsFile, checkNullabilityWarnings)
-
-		rule := android.NewRuleBuilder(pctx, ctx)
-
-		rule.Command().
-			Text("(").
-			Text("diff").Input(checkNullabilityWarnings).Input(d.nullabilityWarningsFile).
-			Text("&&").
-			Text("touch").Output(d.checkNullabilityWarningsTimestamp).
-			Text(") || (").
-			Text("echo").Flag("-e").Flag(`"` + msg + `"`).
-			Text("; exit 38").
-			Text(")")
-
-		rule.Build("nullabilityWarningsCheck", "nullability warnings check")
-	}
-}
-
-//
 // Exported Droiddoc Directory
 //
 var droiddocTemplateTag = dependencyTag{name: "droiddoc-template"}
-var metalavaMergeAnnotationsDirTag = dependencyTag{name: "metalava-merge-annotations-dir"}
-var metalavaMergeInclusionAnnotationsDirTag = dependencyTag{name: "metalava-merge-inclusion-annotations-dir"}
-var metalavaAPILevelsAnnotationsDirTag = dependencyTag{name: "metalava-api-levels-annotations-dir"}
 
 type ExportedDroiddocDirProperties struct {
 	// path to the directory containing Droiddoc related files.
@@ -1662,19 +913,6 @@
 	return module
 }
 
-func StubsDefaultsFactory() android.Module {
-	module := &DocDefaults{}
-
-	module.AddProperties(
-		&JavadocProperties{},
-		&DroidstubsProperties{},
-	)
-
-	android.InitDefaultsModule(module)
-
-	return module
-}
-
 func zipSyncCmd(ctx android.ModuleContext, rule *android.RuleBuilder,
 	srcJarDir android.ModuleOutPath, srcJars android.Paths) android.OutputPath {
 
@@ -1699,94 +937,3 @@
 func zipSyncCleanupCmd(rule *android.RuleBuilder, srcJarDir android.ModuleOutPath) {
 	rule.Command().Text("rm -rf").Text(srcJarDir.String())
 }
-
-var _ android.PrebuiltInterface = (*PrebuiltStubsSources)(nil)
-
-type PrebuiltStubsSourcesProperties struct {
-	Srcs []string `android:"path"`
-}
-
-type PrebuiltStubsSources struct {
-	android.ModuleBase
-	android.DefaultableModuleBase
-	prebuilt android.Prebuilt
-	android.SdkBase
-
-	properties PrebuiltStubsSourcesProperties
-
-	stubsSrcJar android.ModuleOutPath
-}
-
-func (p *PrebuiltStubsSources) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		return android.Paths{p.stubsSrcJar}, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
-func (d *PrebuiltStubsSources) StubsSrcJar() android.Path {
-	return d.stubsSrcJar
-}
-
-func (p *PrebuiltStubsSources) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	p.stubsSrcJar = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"stubs.srcjar")
-
-	if len(p.properties.Srcs) != 1 {
-		ctx.PropertyErrorf("srcs", "must only specify one directory path, contains %d paths", len(p.properties.Srcs))
-		return
-	}
-
-	localSrcDir := p.properties.Srcs[0]
-	// Although PathForModuleSrc can return nil if either the path doesn't exist or
-	// the path components are invalid it won't in this case because no components
-	// are specified and the module directory must exist in order to get this far.
-	srcDir := android.PathForModuleSrc(ctx).(android.SourcePath).Join(ctx, localSrcDir)
-
-	// Glob the contents of the directory just in case the directory does not exist.
-	srcGlob := localSrcDir + "/**/*"
-	srcPaths := android.PathsForModuleSrc(ctx, []string{srcGlob})
-
-	rule := android.NewRuleBuilder(pctx, ctx)
-	rule.Command().
-		BuiltTool("soong_zip").
-		Flag("-write_if_changed").
-		Flag("-jar").
-		FlagWithOutput("-o ", p.stubsSrcJar).
-		FlagWithArg("-C ", srcDir.String()).
-		FlagWithRspFileInputList("-r ", p.stubsSrcJar.ReplaceExtension(ctx, "rsp"), srcPaths)
-
-	rule.Restat()
-
-	rule.Build("zip src", "Create srcjar from prebuilt source")
-}
-
-func (p *PrebuiltStubsSources) Prebuilt() *android.Prebuilt {
-	return &p.prebuilt
-}
-
-func (p *PrebuiltStubsSources) Name() string {
-	return p.prebuilt.Name(p.ModuleBase.Name())
-}
-
-// prebuilt_stubs_sources imports a set of java source files as if they were
-// generated by droidstubs.
-//
-// By default, a prebuilt_stubs_sources has a single variant that expects a
-// set of `.java` files generated by droidstubs.
-//
-// Specifying `host_supported: true` will produce two variants, one for use as a dependency of device modules and one
-// for host modules.
-//
-// Intended only for use by sdk snapshots.
-func PrebuiltStubsSourcesFactory() android.Module {
-	module := &PrebuiltStubsSources{}
-
-	module.AddProperties(&module.properties)
-
-	android.InitPrebuiltModule(module, &module.properties.Srcs)
-	android.InitSdkAwareModule(module)
-	InitDroiddocModule(module, android.HostAndDeviceSupported)
-	return module
-}
diff --git a/java/droiddoc_test.go b/java/droiddoc_test.go
new file mode 100644
index 0000000..8d1f591
--- /dev/null
+++ b/java/droiddoc_test.go
@@ -0,0 +1,147 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package java
+
+import (
+	"reflect"
+	"strings"
+	"testing"
+
+	"android/soong/android"
+)
+
+func TestDroiddoc(t *testing.T) {
+	ctx, _ := testJavaWithFS(t, `
+		droiddoc_exported_dir {
+		    name: "droiddoc-templates-sdk",
+		    path: ".",
+		}
+		filegroup {
+		    name: "bar-doc-aidl-srcs",
+		    srcs: ["bar-doc/IBar.aidl"],
+		    path: "bar-doc",
+		}
+		droidstubs {
+		    name: "bar-stubs",
+		    srcs: [
+		        "bar-doc/a.java",
+		    ],
+		    exclude_srcs: [
+		        "bar-doc/b.java"
+		    ],
+		    api_levels_annotations_dirs: [
+		      "droiddoc-templates-sdk",
+		    ],
+		    api_levels_annotations_enabled: true,
+		}
+		droiddoc {
+		    name: "bar-doc",
+		    srcs: [
+		        ":bar-stubs",
+		        "bar-doc/IFoo.aidl",
+		        ":bar-doc-aidl-srcs",
+		    ],
+		    custom_template: "droiddoc-templates-sdk",
+		    hdf: [
+		        "android.whichdoc offline",
+		    ],
+		    knowntags: [
+		        "bar-doc/known_oj_tags.txt",
+		    ],
+		    proofread_file: "libcore-proofread.txt",
+		    todo_file: "libcore-docs-todo.html",
+		    flags: ["-offlinemode -title \"libcore\""],
+		}
+		`,
+		map[string][]byte{
+			"bar-doc/a.java": nil,
+			"bar-doc/b.java": nil,
+		})
+	barStubs := ctx.ModuleForTests("bar-stubs", "android_common")
+	barStubsOutputs, err := barStubs.Module().(*Droidstubs).OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error %q retrieving \"bar-stubs\" output file", err)
+	}
+	if len(barStubsOutputs) != 1 {
+		t.Errorf("Expected one output from \"bar-stubs\" got %s", barStubsOutputs)
+	}
+
+	barStubsOutput := barStubsOutputs[0]
+	barDoc := ctx.ModuleForTests("bar-doc", "android_common")
+	javaDoc := barDoc.Rule("javadoc")
+	if g, w := android.PathsRelativeToTop(javaDoc.Implicits), android.PathRelativeToTop(barStubsOutput); !inList(w, g) {
+		t.Errorf("implicits of bar-doc must contain %q, but was %q.", w, g)
+	}
+
+	expected := "-sourcepath out/soong/.intermediates/bar-doc/android_common/srcjars "
+	if !strings.Contains(javaDoc.RuleParams.Command, expected) {
+		t.Errorf("bar-doc command does not contain flag %q, but should\n%q", expected, javaDoc.RuleParams.Command)
+	}
+
+	aidl := barDoc.Rule("aidl")
+	if g, w := android.PathsRelativeToTop(javaDoc.Implicits), android.PathRelativeToTop(aidl.Output); !inList(w, g) {
+		t.Errorf("implicits of bar-doc must contain %q, but was %q.", w, g)
+	}
+
+	if g, w := aidl.Implicits.Strings(), []string{"bar-doc/IBar.aidl", "bar-doc/IFoo.aidl"}; !reflect.DeepEqual(w, g) {
+		t.Errorf("aidl inputs must be %q, but was %q", w, g)
+	}
+}
+
+func TestDroiddocArgsAndFlagsCausesError(t *testing.T) {
+	testJavaError(t, "flags is set. Cannot set args", `
+		droiddoc_exported_dir {
+		    name: "droiddoc-templates-sdk",
+		    path: ".",
+		}
+		filegroup {
+		    name: "bar-doc-aidl-srcs",
+		    srcs: ["bar-doc/IBar.aidl"],
+		    path: "bar-doc",
+		}
+		droidstubs {
+		    name: "bar-stubs",
+		    srcs: [
+		        "bar-doc/a.java",
+		    ],
+		    exclude_srcs: [
+		        "bar-doc/b.java"
+		    ],
+		    api_levels_annotations_dirs: [
+		      "droiddoc-templates-sdk",
+		    ],
+		    api_levels_annotations_enabled: true,
+		}
+		droiddoc {
+		    name: "bar-doc",
+		    srcs: [
+		        ":bar-stubs",
+		        "bar-doc/IFoo.aidl",
+		        ":bar-doc-aidl-srcs",
+		    ],
+		    custom_template: "droiddoc-templates-sdk",
+		    hdf: [
+		        "android.whichdoc offline",
+		    ],
+		    knowntags: [
+		        "bar-doc/known_oj_tags.txt",
+		    ],
+		    proofread_file: "libcore-proofread.txt",
+		    todo_file: "libcore-docs-todo.html",
+		    flags: ["-offlinemode -title \"libcore\""],
+		    args: "-offlinemode -title \"libcore\"",
+		}
+		`)
+}
diff --git a/java/droidstubs.go b/java/droidstubs.go
new file mode 100644
index 0000000..a9e2749
--- /dev/null
+++ b/java/droidstubs.go
@@ -0,0 +1,845 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package java
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/google/blueprint/proptools"
+
+	"android/soong/android"
+	"android/soong/java/config"
+	"android/soong/remoteexec"
+)
+
+func init() {
+	RegisterStubsBuildComponents(android.InitRegistrationContext)
+}
+
+func RegisterStubsBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("stubs_defaults", StubsDefaultsFactory)
+
+	ctx.RegisterModuleType("droidstubs", DroidstubsFactory)
+	ctx.RegisterModuleType("droidstubs_host", DroidstubsHostFactory)
+
+	ctx.RegisterModuleType("prebuilt_stubs_sources", PrebuiltStubsSourcesFactory)
+}
+
+//
+// Droidstubs
+//
+type Droidstubs struct {
+	Javadoc
+	android.SdkBase
+
+	properties              DroidstubsProperties
+	apiFile                 android.WritablePath
+	apiXmlFile              android.WritablePath
+	lastReleasedApiXmlFile  android.WritablePath
+	privateApiFile          android.WritablePath
+	removedApiFile          android.WritablePath
+	removedDexApiFile       android.WritablePath
+	nullabilityWarningsFile android.WritablePath
+
+	checkCurrentApiTimestamp      android.WritablePath
+	updateCurrentApiTimestamp     android.WritablePath
+	checkLastReleasedApiTimestamp android.WritablePath
+	apiLintTimestamp              android.WritablePath
+	apiLintReport                 android.WritablePath
+
+	checkNullabilityWarningsTimestamp android.WritablePath
+
+	annotationsZip android.WritablePath
+	apiVersionsXml android.WritablePath
+
+	apiFilePath        android.Path
+	removedApiFilePath android.Path
+
+	metadataZip android.WritablePath
+	metadataDir android.WritablePath
+}
+
+type DroidstubsProperties struct {
+	// The generated public API filename by Metalava, defaults to <module>_api.txt
+	Api_filename *string
+
+	// the generated removed API filename by Metalava, defaults to <module>_removed.txt
+	Removed_api_filename *string
+
+	// the generated removed Dex API filename by Metalava.
+	Removed_dex_api_filename *string
+
+	Check_api struct {
+		Last_released ApiToCheck
+
+		Current ApiToCheck
+
+		Api_lint struct {
+			Enabled *bool
+
+			// If set, performs api_lint on any new APIs not found in the given signature file
+			New_since *string `android:"path"`
+
+			// If not blank, path to the baseline txt file for approved API lint violations.
+			Baseline_file *string `android:"path"`
+		}
+	}
+
+	// user can specify the version of previous released API file in order to do compatibility check.
+	Previous_api *string `android:"path"`
+
+	// is set to true, Metalava will allow framework SDK to contain annotations.
+	Annotations_enabled *bool
+
+	// a list of top-level directories containing files to merge qualifier annotations (i.e. those intended to be included in the stubs written) from.
+	Merge_annotations_dirs []string
+
+	// a list of top-level directories containing Java stub files to merge show/hide annotations from.
+	Merge_inclusion_annotations_dirs []string
+
+	// a file containing a list of classes to do nullability validation for.
+	Validate_nullability_from_list *string
+
+	// a file containing expected warnings produced by validation of nullability annotations.
+	Check_nullability_warnings *string
+
+	// if set to true, allow Metalava to generate doc_stubs source files. Defaults to false.
+	Create_doc_stubs *bool
+
+	// if set to true, cause Metalava to output Javadoc comments in the stubs source files. Defaults to false.
+	// Has no effect if create_doc_stubs: true.
+	Output_javadoc_comments *bool
+
+	// if set to false then do not write out stubs. Defaults to true.
+	//
+	// TODO(b/146727827): Remove capability when we do not need to generate stubs and API separately.
+	Generate_stubs *bool
+
+	// if set to true, provides a hint to the build system that this rule uses a lot of memory,
+	// whicih can be used for scheduling purposes
+	High_mem *bool
+
+	// is set to true, Metalava will allow framework SDK to contain API levels annotations.
+	Api_levels_annotations_enabled *bool
+
+	// the dirs which Metalava extracts API levels annotations from.
+	Api_levels_annotations_dirs []string
+
+	// the filename which Metalava extracts API levels annotations from. Defaults to android.jar.
+	Api_levels_jar_filename *string
+
+	// if set to true, collect the values used by the Dev tools and
+	// write them in files packaged with the SDK. Defaults to false.
+	Write_sdk_values *bool
+}
+
+// droidstubs passes sources files through Metalava to generate stub .java files that only contain the API to be
+// documented, filtering out hidden classes and methods.  The resulting .java files are intended to be passed to
+// a droiddoc module to generate documentation.
+func DroidstubsFactory() android.Module {
+	module := &Droidstubs{}
+
+	module.AddProperties(&module.properties,
+		&module.Javadoc.properties)
+
+	InitDroiddocModule(module, android.HostAndDeviceSupported)
+	android.InitSdkAwareModule(module)
+	return module
+}
+
+// droidstubs_host passes sources files through Metalava to generate stub .java files that only contain the API
+// to be documented, filtering out hidden classes and methods.  The resulting .java files are intended to be
+// passed to a droiddoc_host module to generate documentation.  Use a droidstubs_host instead of a droidstubs
+// module when symbols needed by the source files are provided by java_library_host modules.
+func DroidstubsHostFactory() android.Module {
+	module := &Droidstubs{}
+
+	module.AddProperties(&module.properties,
+		&module.Javadoc.properties)
+
+	InitDroiddocModule(module, android.HostSupported)
+	return module
+}
+
+func (d *Droidstubs) OutputFiles(tag string) (android.Paths, error) {
+	switch tag {
+	case "":
+		return android.Paths{d.stubsSrcJar}, nil
+	case ".docs.zip":
+		return android.Paths{d.docZip}, nil
+	case ".api.txt", android.DefaultDistTag:
+		// This is the default dist path for dist properties that have no tag property.
+		return android.Paths{d.apiFilePath}, nil
+	case ".removed-api.txt":
+		return android.Paths{d.removedApiFilePath}, nil
+	case ".annotations.zip":
+		return android.Paths{d.annotationsZip}, nil
+	case ".api_versions.xml":
+		return android.Paths{d.apiVersionsXml}, nil
+	default:
+		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+	}
+}
+
+func (d *Droidstubs) ApiFilePath() android.Path {
+	return d.apiFilePath
+}
+
+func (d *Droidstubs) RemovedApiFilePath() android.Path {
+	return d.removedApiFilePath
+}
+
+func (d *Droidstubs) StubsSrcJar() android.Path {
+	return d.stubsSrcJar
+}
+
+var metalavaMergeAnnotationsDirTag = dependencyTag{name: "metalava-merge-annotations-dir"}
+var metalavaMergeInclusionAnnotationsDirTag = dependencyTag{name: "metalava-merge-inclusion-annotations-dir"}
+var metalavaAPILevelsAnnotationsDirTag = dependencyTag{name: "metalava-api-levels-annotations-dir"}
+
+func (d *Droidstubs) DepsMutator(ctx android.BottomUpMutatorContext) {
+	d.Javadoc.addDeps(ctx)
+
+	if len(d.properties.Merge_annotations_dirs) != 0 {
+		for _, mergeAnnotationsDir := range d.properties.Merge_annotations_dirs {
+			ctx.AddDependency(ctx.Module(), metalavaMergeAnnotationsDirTag, mergeAnnotationsDir)
+		}
+	}
+
+	if len(d.properties.Merge_inclusion_annotations_dirs) != 0 {
+		for _, mergeInclusionAnnotationsDir := range d.properties.Merge_inclusion_annotations_dirs {
+			ctx.AddDependency(ctx.Module(), metalavaMergeInclusionAnnotationsDirTag, mergeInclusionAnnotationsDir)
+		}
+	}
+
+	if len(d.properties.Api_levels_annotations_dirs) != 0 {
+		for _, apiLevelsAnnotationsDir := range d.properties.Api_levels_annotations_dirs {
+			ctx.AddDependency(ctx.Module(), metalavaAPILevelsAnnotationsDirTag, apiLevelsAnnotationsDir)
+		}
+	}
+}
+
+func (d *Droidstubs) stubsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, stubsDir android.OptionalPath) {
+	if apiCheckEnabled(ctx, d.properties.Check_api.Current, "current") ||
+		apiCheckEnabled(ctx, d.properties.Check_api.Last_released, "last_released") ||
+		String(d.properties.Api_filename) != "" {
+		filename := proptools.StringDefault(d.properties.Api_filename, ctx.ModuleName()+"_api.txt")
+		d.apiFile = android.PathForModuleOut(ctx, "metalava", filename)
+		cmd.FlagWithOutput("--api ", d.apiFile)
+		d.apiFilePath = d.apiFile
+	} else if sourceApiFile := proptools.String(d.properties.Check_api.Current.Api_file); sourceApiFile != "" {
+		// If check api is disabled then make the source file available for export.
+		d.apiFilePath = android.PathForModuleSrc(ctx, sourceApiFile)
+	}
+
+	if apiCheckEnabled(ctx, d.properties.Check_api.Current, "current") ||
+		apiCheckEnabled(ctx, d.properties.Check_api.Last_released, "last_released") ||
+		String(d.properties.Removed_api_filename) != "" {
+		filename := proptools.StringDefault(d.properties.Removed_api_filename, ctx.ModuleName()+"_removed.txt")
+		d.removedApiFile = android.PathForModuleOut(ctx, "metalava", filename)
+		cmd.FlagWithOutput("--removed-api ", d.removedApiFile)
+		d.removedApiFilePath = d.removedApiFile
+	} else if sourceRemovedApiFile := proptools.String(d.properties.Check_api.Current.Removed_api_file); sourceRemovedApiFile != "" {
+		// If check api is disabled then make the source removed api file available for export.
+		d.removedApiFilePath = android.PathForModuleSrc(ctx, sourceRemovedApiFile)
+	}
+
+	if String(d.properties.Removed_dex_api_filename) != "" {
+		d.removedDexApiFile = android.PathForModuleOut(ctx, "metalava", String(d.properties.Removed_dex_api_filename))
+		cmd.FlagWithOutput("--removed-dex-api ", d.removedDexApiFile)
+	}
+
+	if Bool(d.properties.Write_sdk_values) {
+		d.metadataDir = android.PathForModuleOut(ctx, "metalava", "metadata")
+		cmd.FlagWithArg("--sdk-values ", d.metadataDir.String())
+	}
+
+	if stubsDir.Valid() {
+		if Bool(d.properties.Create_doc_stubs) {
+			cmd.FlagWithArg("--doc-stubs ", stubsDir.String())
+		} else {
+			cmd.FlagWithArg("--stubs ", stubsDir.String())
+			if !Bool(d.properties.Output_javadoc_comments) {
+				cmd.Flag("--exclude-documentation-from-stubs")
+			}
+		}
+	}
+}
+
+func (d *Droidstubs) annotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
+	if Bool(d.properties.Annotations_enabled) {
+		cmd.Flag("--include-annotations")
+
+		validatingNullability :=
+			strings.Contains(String(d.Javadoc.properties.Args), "--validate-nullability-from-merged-stubs") ||
+				String(d.properties.Validate_nullability_from_list) != ""
+
+		migratingNullability := String(d.properties.Previous_api) != ""
+		if migratingNullability {
+			previousApi := android.PathForModuleSrc(ctx, String(d.properties.Previous_api))
+			cmd.FlagWithInput("--migrate-nullness ", previousApi)
+		}
+
+		if s := String(d.properties.Validate_nullability_from_list); s != "" {
+			cmd.FlagWithInput("--validate-nullability-from-list ", android.PathForModuleSrc(ctx, s))
+		}
+
+		if validatingNullability {
+			d.nullabilityWarningsFile = android.PathForModuleOut(ctx, "metalava", ctx.ModuleName()+"_nullability_warnings.txt")
+			cmd.FlagWithOutput("--nullability-warnings-txt ", d.nullabilityWarningsFile)
+		}
+
+		d.annotationsZip = android.PathForModuleOut(ctx, "metalava", ctx.ModuleName()+"_annotations.zip")
+		cmd.FlagWithOutput("--extract-annotations ", d.annotationsZip)
+
+		if len(d.properties.Merge_annotations_dirs) != 0 {
+			d.mergeAnnoDirFlags(ctx, cmd)
+		}
+
+		// TODO(tnorbye): find owners to fix these warnings when annotation was enabled.
+		cmd.FlagWithArg("--hide ", "HiddenTypedefConstant").
+			FlagWithArg("--hide ", "SuperfluousPrefix").
+			FlagWithArg("--hide ", "AnnotationExtraction")
+	}
+}
+
+func (d *Droidstubs) mergeAnnoDirFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
+	ctx.VisitDirectDepsWithTag(metalavaMergeAnnotationsDirTag, func(m android.Module) {
+		if t, ok := m.(*ExportedDroiddocDir); ok {
+			cmd.FlagWithArg("--merge-qualifier-annotations ", t.dir.String()).Implicits(t.deps)
+		} else {
+			ctx.PropertyErrorf("merge_annotations_dirs",
+				"module %q is not a metalava merge-annotations dir", ctx.OtherModuleName(m))
+		}
+	})
+}
+
+func (d *Droidstubs) inclusionAnnotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
+	ctx.VisitDirectDepsWithTag(metalavaMergeInclusionAnnotationsDirTag, func(m android.Module) {
+		if t, ok := m.(*ExportedDroiddocDir); ok {
+			cmd.FlagWithArg("--merge-inclusion-annotations ", t.dir.String()).Implicits(t.deps)
+		} else {
+			ctx.PropertyErrorf("merge_inclusion_annotations_dirs",
+				"module %q is not a metalava merge-annotations dir", ctx.OtherModuleName(m))
+		}
+	})
+}
+
+func (d *Droidstubs) apiLevelsAnnotationsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand) {
+	if !Bool(d.properties.Api_levels_annotations_enabled) {
+		return
+	}
+
+	d.apiVersionsXml = android.PathForModuleOut(ctx, "metalava", "api-versions.xml")
+
+	if len(d.properties.Api_levels_annotations_dirs) == 0 {
+		ctx.PropertyErrorf("api_levels_annotations_dirs",
+			"has to be non-empty if api levels annotations was enabled!")
+	}
+
+	cmd.FlagWithOutput("--generate-api-levels ", d.apiVersionsXml)
+	cmd.FlagWithInput("--apply-api-levels ", d.apiVersionsXml)
+	cmd.FlagWithArg("--current-version ", ctx.Config().PlatformSdkVersion().String())
+	cmd.FlagWithArg("--current-codename ", ctx.Config().PlatformSdkCodename())
+
+	filename := proptools.StringDefault(d.properties.Api_levels_jar_filename, "android.jar")
+
+	ctx.VisitDirectDepsWithTag(metalavaAPILevelsAnnotationsDirTag, func(m android.Module) {
+		if t, ok := m.(*ExportedDroiddocDir); ok {
+			for _, dep := range t.deps {
+				if dep.Base() == filename {
+					cmd.Implicit(dep)
+				}
+				if filename != "android.jar" && dep.Base() == "android.jar" {
+					// Metalava implicitly searches these patterns:
+					//  prebuilts/tools/common/api-versions/android-%/android.jar
+					//  prebuilts/sdk/%/public/android.jar
+					// Add android.jar files from the api_levels_annotations_dirs directories to try
+					// to satisfy these patterns.  If Metalava can't find a match for an API level
+					// between 1 and 28 in at least one pattern it will fail.
+					cmd.Implicit(dep)
+				}
+			}
+			cmd.FlagWithArg("--android-jar-pattern ", t.dir.String()+"/%/public/"+filename)
+		} else {
+			ctx.PropertyErrorf("api_levels_annotations_dirs",
+				"module %q is not a metalava api-levels-annotations dir", ctx.OtherModuleName(m))
+		}
+	})
+}
+
+func metalavaCmd(ctx android.ModuleContext, rule *android.RuleBuilder, javaVersion javaVersion, srcs android.Paths,
+	srcJarList android.Path, bootclasspath, classpath classpath, sourcepaths android.Paths,
+	homeDir android.WritablePath) *android.RuleBuilderCommand {
+	rule.Command().Text("rm -rf").Flag(homeDir.String())
+	rule.Command().Text("mkdir -p").Flag(homeDir.String())
+
+	cmd := rule.Command()
+	cmd.FlagWithArg("ANDROID_PREFS_ROOT=", homeDir.String())
+
+	if ctx.Config().UseRBE() && ctx.Config().IsEnvTrue("RBE_METALAVA") {
+		rule.Remoteable(android.RemoteRuleSupports{RBE: true})
+		execStrategy := ctx.Config().GetenvWithDefault("RBE_METALAVA_EXEC_STRATEGY", remoteexec.LocalExecStrategy)
+		labels := map[string]string{"type": "tool", "name": "metalava"}
+		// TODO: metalava pool rejects these jobs
+		pool := ctx.Config().GetenvWithDefault("RBE_METALAVA_POOL", "java16")
+		rule.Rewrapper(&remoteexec.REParams{
+			Labels:          labels,
+			ExecStrategy:    execStrategy,
+			ToolchainInputs: []string{config.JavaCmd(ctx).String()},
+			Platform:        map[string]string{remoteexec.PoolKey: pool},
+		})
+	}
+
+	cmd.BuiltTool("metalava").ImplicitTool(ctx.Config().HostJavaToolPath(ctx, "metalava.jar")).
+		Flag(config.JavacVmFlags).
+		Flag("-J--add-opens=java.base/java.util=ALL-UNNAMED").
+		FlagWithArg("-encoding ", "UTF-8").
+		FlagWithArg("-source ", javaVersion.String()).
+		FlagWithRspFileInputList("@", android.PathForModuleOut(ctx, "metalava.rsp"), srcs).
+		FlagWithInput("@", srcJarList)
+
+	if len(bootclasspath) > 0 {
+		cmd.FlagWithInputList("-bootclasspath ", bootclasspath.Paths(), ":")
+	}
+
+	if len(classpath) > 0 {
+		cmd.FlagWithInputList("-classpath ", classpath.Paths(), ":")
+	}
+
+	if len(sourcepaths) > 0 {
+		cmd.FlagWithList("-sourcepath ", sourcepaths.Strings(), ":")
+	} else {
+		cmd.FlagWithArg("-sourcepath ", `""`)
+	}
+
+	cmd.Flag("--no-banner").
+		Flag("--color").
+		Flag("--quiet").
+		Flag("--format=v2").
+		FlagWithArg("--repeat-errors-max ", "10").
+		FlagWithArg("--hide ", "UnresolvedImport")
+
+	return cmd
+}
+
+func (d *Droidstubs) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	deps := d.Javadoc.collectDeps(ctx)
+
+	javaVersion := getJavaVersion(ctx, String(d.Javadoc.properties.Java_version), android.SdkContext(d))
+
+	// Create rule for metalava
+
+	srcJarDir := android.PathForModuleOut(ctx, "metalava", "srcjars")
+
+	rule := android.NewRuleBuilder(pctx, ctx)
+
+	rule.Sbox(android.PathForModuleOut(ctx, "metalava"),
+		android.PathForModuleOut(ctx, "metalava.sbox.textproto")).
+		SandboxInputs()
+
+	if BoolDefault(d.properties.High_mem, false) {
+		// This metalava run uses lots of memory, restrict the number of metalava jobs that can run in parallel.
+		rule.HighMem()
+	}
+
+	generateStubs := BoolDefault(d.properties.Generate_stubs, true)
+	var stubsDir android.OptionalPath
+	if generateStubs {
+		d.Javadoc.stubsSrcJar = android.PathForModuleOut(ctx, "metalava", ctx.ModuleName()+"-"+"stubs.srcjar")
+		stubsDir = android.OptionalPathForPath(android.PathForModuleOut(ctx, "metalava", "stubsDir"))
+		rule.Command().Text("rm -rf").Text(stubsDir.String())
+		rule.Command().Text("mkdir -p").Text(stubsDir.String())
+	}
+
+	srcJarList := zipSyncCmd(ctx, rule, srcJarDir, d.Javadoc.srcJars)
+
+	homeDir := android.PathForModuleOut(ctx, "metalava", "home")
+	cmd := metalavaCmd(ctx, rule, javaVersion, d.Javadoc.srcFiles, srcJarList,
+		deps.bootClasspath, deps.classpath, d.Javadoc.sourcepaths, homeDir)
+	cmd.Implicits(d.Javadoc.implicits)
+
+	d.stubsFlags(ctx, cmd, stubsDir)
+
+	d.annotationsFlags(ctx, cmd)
+	d.inclusionAnnotationsFlags(ctx, cmd)
+	d.apiLevelsAnnotationsFlags(ctx, cmd)
+
+	d.expandArgs(ctx, cmd)
+
+	for _, o := range d.Javadoc.properties.Out {
+		cmd.ImplicitOutput(android.PathForModuleGen(ctx, o))
+	}
+
+	// Add options for the other optional tasks: API-lint and check-released.
+	// We generate separate timestamp files for them.
+
+	doApiLint := false
+	doCheckReleased := false
+
+	// Add API lint options.
+
+	if BoolDefault(d.properties.Check_api.Api_lint.Enabled, false) {
+		doApiLint = true
+
+		newSince := android.OptionalPathForModuleSrc(ctx, d.properties.Check_api.Api_lint.New_since)
+		if newSince.Valid() {
+			cmd.FlagWithInput("--api-lint ", newSince.Path())
+		} else {
+			cmd.Flag("--api-lint")
+		}
+		d.apiLintReport = android.PathForModuleOut(ctx, "metalava", "api_lint_report.txt")
+		cmd.FlagWithOutput("--report-even-if-suppressed ", d.apiLintReport) // TODO:  Change to ":api-lint"
+
+		// TODO(b/154317059): Clean up this allowlist by baselining and/or checking in last-released.
+		if d.Name() != "android.car-system-stubs-docs" &&
+			d.Name() != "android.car-stubs-docs" {
+			cmd.Flag("--lints-as-errors")
+			cmd.Flag("--warnings-as-errors") // Most lints are actually warnings.
+		}
+
+		baselineFile := android.OptionalPathForModuleSrc(ctx, d.properties.Check_api.Api_lint.Baseline_file)
+		updatedBaselineOutput := android.PathForModuleOut(ctx, "metalava", "api_lint_baseline.txt")
+		d.apiLintTimestamp = android.PathForModuleOut(ctx, "metalava", "api_lint.timestamp")
+
+		// Note this string includes a special shell quote $' ... ', which decodes the "\n"s.
+		// However, because $' ... ' doesn't expand environmental variables, we can't just embed
+		// $PWD, so we have to terminate $'...', use "$PWD", then start $' ... ' again,
+		// which is why we have '"$PWD"$' in it.
+		//
+		// TODO: metalava also has a slightly different message hardcoded. Should we unify this
+		// message and metalava's one?
+		msg := `$'` + // Enclose with $' ... '
+			`************************************************************\n` +
+			`Your API changes are triggering API Lint warnings or errors.\n` +
+			`To make these errors go away, fix the code according to the\n` +
+			`error and/or warning messages above.\n` +
+			`\n` +
+			`If it is not possible to do so, there are workarounds:\n` +
+			`\n` +
+			`1. You can suppress the errors with @SuppressLint("<id>")\n`
+
+		if baselineFile.Valid() {
+			cmd.FlagWithInput("--baseline:api-lint ", baselineFile.Path())
+			cmd.FlagWithOutput("--update-baseline:api-lint ", updatedBaselineOutput)
+
+			msg += fmt.Sprintf(``+
+				`2. You can update the baseline by executing the following\n`+
+				`   command:\n`+
+				`       cp \\\n`+
+				`       "'"$PWD"$'/%s" \\\n`+
+				`       "'"$PWD"$'/%s"\n`+
+				`   To submit the revised baseline.txt to the main Android\n`+
+				`   repository, you will need approval.\n`, updatedBaselineOutput, baselineFile.Path())
+		} else {
+			msg += fmt.Sprintf(``+
+				`2. You can add a baseline file of existing lint failures\n`+
+				`   to the build rule of %s.\n`, d.Name())
+		}
+		// Note the message ends with a ' (single quote), to close the $' ... ' .
+		msg += `************************************************************\n'`
+
+		cmd.FlagWithArg("--error-message:api-lint ", msg)
+	}
+
+	// Add "check released" options. (Detect incompatible API changes from the last public release)
+
+	if apiCheckEnabled(ctx, d.properties.Check_api.Last_released, "last_released") {
+		doCheckReleased = true
+
+		if len(d.Javadoc.properties.Out) > 0 {
+			ctx.PropertyErrorf("out", "out property may not be combined with check_api")
+		}
+
+		apiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Last_released.Api_file))
+		removedApiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Last_released.Removed_api_file))
+		baselineFile := android.OptionalPathForModuleSrc(ctx, d.properties.Check_api.Last_released.Baseline_file)
+		updatedBaselineOutput := android.PathForModuleOut(ctx, "metalava", "last_released_baseline.txt")
+
+		d.checkLastReleasedApiTimestamp = android.PathForModuleOut(ctx, "metalava", "check_last_released_api.timestamp")
+
+		cmd.FlagWithInput("--check-compatibility:api:released ", apiFile)
+		cmd.FlagWithInput("--check-compatibility:removed:released ", removedApiFile)
+
+		if baselineFile.Valid() {
+			cmd.FlagWithInput("--baseline:compatibility:released ", baselineFile.Path())
+			cmd.FlagWithOutput("--update-baseline:compatibility:released ", updatedBaselineOutput)
+		}
+
+		// Note this string includes quote ($' ... '), which decodes the "\n"s.
+		msg := `$'\n******************************\n` +
+			`You have tried to change the API from what has been previously released in\n` +
+			`an SDK.  Please fix the errors listed above.\n` +
+			`******************************\n'`
+
+		cmd.FlagWithArg("--error-message:compatibility:released ", msg)
+	}
+
+	if generateStubs {
+		rule.Command().
+			BuiltTool("soong_zip").
+			Flag("-write_if_changed").
+			Flag("-jar").
+			FlagWithOutput("-o ", d.Javadoc.stubsSrcJar).
+			FlagWithArg("-C ", stubsDir.String()).
+			FlagWithArg("-D ", stubsDir.String())
+	}
+
+	if Bool(d.properties.Write_sdk_values) {
+		d.metadataZip = android.PathForModuleOut(ctx, "metalava", ctx.ModuleName()+"-metadata.zip")
+		rule.Command().
+			BuiltTool("soong_zip").
+			Flag("-write_if_changed").
+			Flag("-d").
+			FlagWithOutput("-o ", d.metadataZip).
+			FlagWithArg("-C ", d.metadataDir.String()).
+			FlagWithArg("-D ", d.metadataDir.String())
+	}
+
+	// TODO: We don't really need two separate API files, but this is a reminiscence of how
+	// we used to run metalava separately for API lint and the "last_released" check. Unify them.
+	if doApiLint {
+		rule.Command().Text("touch").Output(d.apiLintTimestamp)
+	}
+	if doCheckReleased {
+		rule.Command().Text("touch").Output(d.checkLastReleasedApiTimestamp)
+	}
+
+	// TODO(b/183630617): rewrapper doesn't support restat rules
+	// rule.Restat()
+
+	zipSyncCleanupCmd(rule, srcJarDir)
+
+	rule.Build("metalava", "metalava merged")
+
+	if apiCheckEnabled(ctx, d.properties.Check_api.Current, "current") {
+
+		if len(d.Javadoc.properties.Out) > 0 {
+			ctx.PropertyErrorf("out", "out property may not be combined with check_api")
+		}
+
+		apiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Current.Api_file))
+		removedApiFile := android.PathForModuleSrc(ctx, String(d.properties.Check_api.Current.Removed_api_file))
+		baselineFile := android.OptionalPathForModuleSrc(ctx, d.properties.Check_api.Current.Baseline_file)
+
+		if baselineFile.Valid() {
+			ctx.PropertyErrorf("baseline_file", "current API check can't have a baseline file. (module %s)", ctx.ModuleName())
+		}
+
+		d.checkCurrentApiTimestamp = android.PathForModuleOut(ctx, "metalava", "check_current_api.timestamp")
+
+		rule := android.NewRuleBuilder(pctx, ctx)
+
+		// Diff command line.
+		// -F matches the closest "opening" line, such as "package android {"
+		// and "  public class Intent {".
+		diff := `diff -u -F '{ *$'`
+
+		rule.Command().Text("( true")
+		rule.Command().
+			Text(diff).
+			Input(apiFile).Input(d.apiFile)
+
+		rule.Command().
+			Text(diff).
+			Input(removedApiFile).Input(d.removedApiFile)
+
+		msg := fmt.Sprintf(`\n******************************\n`+
+			`You have tried to change the API from what has been previously approved.\n\n`+
+			`To make these errors go away, you have two choices:\n`+
+			`   1. You can add '@hide' javadoc comments (and remove @SystemApi/@TestApi/etc)\n`+
+			`      to the new methods, etc. shown in the above diff.\n\n`+
+			`   2. You can update current.txt and/or removed.txt by executing the following command:\n`+
+			`         m %s-update-current-api\n\n`+
+			`      To submit the revised current.txt to the main Android repository,\n`+
+			`      you will need approval.\n`+
+			`******************************\n`, ctx.ModuleName())
+
+		rule.Command().
+			Text("touch").Output(d.checkCurrentApiTimestamp).
+			Text(") || (").
+			Text("echo").Flag("-e").Flag(`"` + msg + `"`).
+			Text("; exit 38").
+			Text(")")
+
+		rule.Build("metalavaCurrentApiCheck", "check current API")
+
+		d.updateCurrentApiTimestamp = android.PathForModuleOut(ctx, "metalava", "update_current_api.timestamp")
+
+		// update API rule
+		rule = android.NewRuleBuilder(pctx, ctx)
+
+		rule.Command().Text("( true")
+
+		rule.Command().
+			Text("cp").Flag("-f").
+			Input(d.apiFile).Flag(apiFile.String())
+
+		rule.Command().
+			Text("cp").Flag("-f").
+			Input(d.removedApiFile).Flag(removedApiFile.String())
+
+		msg = "failed to update public API"
+
+		rule.Command().
+			Text("touch").Output(d.updateCurrentApiTimestamp).
+			Text(") || (").
+			Text("echo").Flag("-e").Flag(`"` + msg + `"`).
+			Text("; exit 38").
+			Text(")")
+
+		rule.Build("metalavaCurrentApiUpdate", "update current API")
+	}
+
+	if String(d.properties.Check_nullability_warnings) != "" {
+		if d.nullabilityWarningsFile == nil {
+			ctx.PropertyErrorf("check_nullability_warnings",
+				"Cannot specify check_nullability_warnings unless validating nullability")
+		}
+
+		checkNullabilityWarnings := android.PathForModuleSrc(ctx, String(d.properties.Check_nullability_warnings))
+
+		d.checkNullabilityWarningsTimestamp = android.PathForModuleOut(ctx, "metalava", "check_nullability_warnings.timestamp")
+
+		msg := fmt.Sprintf(`\n******************************\n`+
+			`The warnings encountered during nullability annotation validation did\n`+
+			`not match the checked in file of expected warnings. The diffs are shown\n`+
+			`above. You have two options:\n`+
+			`   1. Resolve the differences by editing the nullability annotations.\n`+
+			`   2. Update the file of expected warnings by running:\n`+
+			`         cp %s %s\n`+
+			`       and submitting the updated file as part of your change.`,
+			d.nullabilityWarningsFile, checkNullabilityWarnings)
+
+		rule := android.NewRuleBuilder(pctx, ctx)
+
+		rule.Command().
+			Text("(").
+			Text("diff").Input(checkNullabilityWarnings).Input(d.nullabilityWarningsFile).
+			Text("&&").
+			Text("touch").Output(d.checkNullabilityWarningsTimestamp).
+			Text(") || (").
+			Text("echo").Flag("-e").Flag(`"` + msg + `"`).
+			Text("; exit 38").
+			Text(")")
+
+		rule.Build("nullabilityWarningsCheck", "nullability warnings check")
+	}
+}
+
+func StubsDefaultsFactory() android.Module {
+	module := &DocDefaults{}
+
+	module.AddProperties(
+		&JavadocProperties{},
+		&DroidstubsProperties{},
+	)
+
+	android.InitDefaultsModule(module)
+
+	return module
+}
+
+var _ android.PrebuiltInterface = (*PrebuiltStubsSources)(nil)
+
+type PrebuiltStubsSourcesProperties struct {
+	Srcs []string `android:"path"`
+}
+
+type PrebuiltStubsSources struct {
+	android.ModuleBase
+	android.DefaultableModuleBase
+	prebuilt android.Prebuilt
+	android.SdkBase
+
+	properties PrebuiltStubsSourcesProperties
+
+	stubsSrcJar android.ModuleOutPath
+}
+
+func (p *PrebuiltStubsSources) OutputFiles(tag string) (android.Paths, error) {
+	switch tag {
+	case "":
+		return android.Paths{p.stubsSrcJar}, nil
+	default:
+		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+	}
+}
+
+func (d *PrebuiltStubsSources) StubsSrcJar() android.Path {
+	return d.stubsSrcJar
+}
+
+func (p *PrebuiltStubsSources) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	p.stubsSrcJar = android.PathForModuleOut(ctx, ctx.ModuleName()+"-"+"stubs.srcjar")
+
+	if len(p.properties.Srcs) != 1 {
+		ctx.PropertyErrorf("srcs", "must only specify one directory path, contains %d paths", len(p.properties.Srcs))
+		return
+	}
+
+	localSrcDir := p.properties.Srcs[0]
+	// Although PathForModuleSrc can return nil if either the path doesn't exist or
+	// the path components are invalid it won't in this case because no components
+	// are specified and the module directory must exist in order to get this far.
+	srcDir := android.PathForModuleSrc(ctx).(android.SourcePath).Join(ctx, localSrcDir)
+
+	// Glob the contents of the directory just in case the directory does not exist.
+	srcGlob := localSrcDir + "/**/*"
+	srcPaths := android.PathsForModuleSrc(ctx, []string{srcGlob})
+
+	rule := android.NewRuleBuilder(pctx, ctx)
+	rule.Command().
+		BuiltTool("soong_zip").
+		Flag("-write_if_changed").
+		Flag("-jar").
+		FlagWithOutput("-o ", p.stubsSrcJar).
+		FlagWithArg("-C ", srcDir.String()).
+		FlagWithRspFileInputList("-r ", p.stubsSrcJar.ReplaceExtension(ctx, "rsp"), srcPaths)
+
+	rule.Restat()
+
+	rule.Build("zip src", "Create srcjar from prebuilt source")
+}
+
+func (p *PrebuiltStubsSources) Prebuilt() *android.Prebuilt {
+	return &p.prebuilt
+}
+
+func (p *PrebuiltStubsSources) Name() string {
+	return p.prebuilt.Name(p.ModuleBase.Name())
+}
+
+// prebuilt_stubs_sources imports a set of java source files as if they were
+// generated by droidstubs.
+//
+// By default, a prebuilt_stubs_sources has a single variant that expects a
+// set of `.java` files generated by droidstubs.
+//
+// Specifying `host_supported: true` will produce two variants, one for use as a dependency of device modules and one
+// for host modules.
+//
+// Intended only for use by sdk snapshots.
+func PrebuiltStubsSourcesFactory() android.Module {
+	module := &PrebuiltStubsSources{}
+
+	module.AddProperties(&module.properties)
+
+	android.InitPrebuiltModule(module, &module.properties.Srcs)
+	android.InitSdkAwareModule(module)
+	InitDroiddocModule(module, android.HostAndDeviceSupported)
+	return module
+}
diff --git a/java/droidstubs_test.go b/java/droidstubs_test.go
new file mode 100644
index 0000000..db664c1
--- /dev/null
+++ b/java/droidstubs_test.go
@@ -0,0 +1,173 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package java
+
+import (
+	"reflect"
+	"strings"
+	"testing"
+
+	"android/soong/android"
+)
+
+func TestDroidstubs(t *testing.T) {
+	ctx, _ := testJavaWithFS(t, `
+		droiddoc_exported_dir {
+			name: "droiddoc-templates-sdk",
+			path: ".",
+		}
+
+		droidstubs {
+			name: "bar-stubs",
+			srcs: ["bar-doc/a.java"],
+			api_levels_annotations_dirs: ["droiddoc-templates-sdk"],
+			api_levels_annotations_enabled: true,
+		}
+
+		droidstubs {
+			name: "bar-stubs-other",
+			srcs: ["bar-doc/a.java"],
+			high_mem: true,
+			api_levels_annotations_dirs: ["droiddoc-templates-sdk"],
+			api_levels_annotations_enabled: true,
+			api_levels_jar_filename: "android.other.jar",
+		}
+		`,
+		map[string][]byte{
+			"bar-doc/a.java": nil,
+		})
+	testcases := []struct {
+		moduleName          string
+		expectedJarFilename string
+		high_mem            bool
+	}{
+		{
+			moduleName:          "bar-stubs",
+			expectedJarFilename: "android.jar",
+			high_mem:            false,
+		},
+		{
+			moduleName:          "bar-stubs-other",
+			expectedJarFilename: "android.other.jar",
+			high_mem:            true,
+		},
+	}
+	for _, c := range testcases {
+		m := ctx.ModuleForTests(c.moduleName, "android_common")
+		manifest := m.Output("metalava.sbox.textproto")
+		sboxProto := android.RuleBuilderSboxProtoForTests(t, manifest)
+		expected := "--android-jar-pattern ./%/public/" + c.expectedJarFilename
+		if actual := String(sboxProto.Commands[0].Command); !strings.Contains(actual, expected) {
+			t.Errorf("For %q, expected metalava argument %q, but was not found %q", c.moduleName, expected, actual)
+		}
+
+		metalava := m.Rule("metalava")
+		rp := metalava.RuleParams
+		if actual := rp.Pool != nil && strings.Contains(rp.Pool.String(), "highmem"); actual != c.high_mem {
+			t.Errorf("Expected %q high_mem to be %v, was %v", c.moduleName, c.high_mem, actual)
+		}
+	}
+}
+
+func TestDroidstubsSandbox(t *testing.T) {
+	ctx, _ := testJavaWithFS(t, `
+		genrule {
+			name: "foo",
+			out: ["foo.txt"],
+			cmd: "touch $(out)",
+		}
+
+		droidstubs {
+			name: "bar-stubs",
+			srcs: ["bar-doc/a.java"],
+
+			args: "--reference $(location :foo)",
+			arg_files: [":foo"],
+		}
+		`,
+		map[string][]byte{
+			"bar-doc/a.java": nil,
+		})
+
+	m := ctx.ModuleForTests("bar-stubs", "android_common")
+	metalava := m.Rule("metalava")
+	if g, w := metalava.Inputs.Strings(), []string{"bar-doc/a.java"}; !reflect.DeepEqual(w, g) {
+		t.Errorf("Expected inputs %q, got %q", w, g)
+	}
+
+	manifest := android.RuleBuilderSboxProtoForTests(t, m.Output("metalava.sbox.textproto"))
+	if g, w := manifest.Commands[0].GetCommand(), "reference __SBOX_SANDBOX_DIR__/out/.intermediates/foo/gen/foo.txt"; !strings.Contains(g, w) {
+		t.Errorf("Expected command to contain %q, got %q", w, g)
+	}
+}
+
+func TestDroidstubsWithSystemModules(t *testing.T) {
+	ctx, _ := testJava(t, `
+		droidstubs {
+		    name: "stubs-source-system-modules",
+		    srcs: [
+		        "bar-doc/a.java",
+		    ],
+				sdk_version: "none",
+				system_modules: "source-system-modules",
+		}
+
+		java_library {
+				name: "source-jar",
+		    srcs: [
+		        "a.java",
+		    ],
+		}
+
+		java_system_modules {
+				name: "source-system-modules",
+				libs: ["source-jar"],
+		}
+
+		droidstubs {
+		    name: "stubs-prebuilt-system-modules",
+		    srcs: [
+		        "bar-doc/a.java",
+		    ],
+				sdk_version: "none",
+				system_modules: "prebuilt-system-modules",
+		}
+
+		java_import {
+				name: "prebuilt-jar",
+				jars: ["a.jar"],
+		}
+
+		java_system_modules_import {
+				name: "prebuilt-system-modules",
+				libs: ["prebuilt-jar"],
+		}
+		`)
+
+	checkSystemModulesUseByDroidstubs(t, ctx, "stubs-source-system-modules", "source-jar.jar")
+
+	checkSystemModulesUseByDroidstubs(t, ctx, "stubs-prebuilt-system-modules", "prebuilt-jar.jar")
+}
+
+func checkSystemModulesUseByDroidstubs(t *testing.T, ctx *android.TestContext, moduleName string, systemJar string) {
+	metalavaRule := ctx.ModuleForTests(moduleName, "android_common").Rule("metalava")
+	var systemJars []string
+	for _, i := range metalavaRule.Implicits {
+		systemJars = append(systemJars, i.Base())
+	}
+	if len(systemJars) < 1 || systemJars[0] != systemJar {
+		t.Errorf("inputs of %q must be []string{%q}, but was %#v.", moduleName, systemJar, systemJars)
+	}
+}
diff --git a/java/hiddenapi_singleton.go b/java/hiddenapi_singleton.go
index 6ad4ff3..7e9477b 100644
--- a/java/hiddenapi_singleton.go
+++ b/java/hiddenapi_singleton.go
@@ -186,17 +186,6 @@
 	// We do not have prebuilts of the core platform api yet
 	corePlatformStubModules = append(corePlatformStubModules, "legacy.core.platform.api.stubs")
 
-	// Add the android.test.base to the set of stubs only if the android.test.base module is on
-	// the boot jars list as the runtime will only enforce hiddenapi access against modules on
-	// that list.
-	if inList("android.test.base", ctx.Config().BootJars()) {
-		if ctx.Config().AlwaysUsePrebuiltSdks() {
-			publicStubModules = append(publicStubModules, "sdk_public_current_android.test.base")
-		} else {
-			publicStubModules = append(publicStubModules, "android.test.base.stubs")
-		}
-	}
-
 	// Allow products to define their own stubs for custom product jars that apps can use.
 	publicStubModules = append(publicStubModules, ctx.Config().ProductHiddenAPIStubs()...)
 	systemStubModules = append(systemStubModules, ctx.Config().ProductHiddenAPIStubsSystem()...)
@@ -369,20 +358,20 @@
 		FlagWithInput("--csv ", stubFlags).
 		Inputs(flagsCSV).
 		FlagWithInput("--unsupported ",
-			android.PathForSource(ctx, "frameworks/base/config/hiddenapi-unsupported.txt")).
+			android.PathForSource(ctx, "frameworks/base/boot/hiddenapi/hiddenapi-unsupported.txt")).
 		FlagWithInput("--unsupported ", combinedRemovedApis).Flag("--ignore-conflicts ").FlagWithArg("--tag ", "removed").
 		FlagWithInput("--max-target-r ",
-			android.PathForSource(ctx, "frameworks/base/config/hiddenapi-max-target-r-loprio.txt")).FlagWithArg("--tag ", "lo-prio").
+			android.PathForSource(ctx, "frameworks/base/boot/hiddenapi/hiddenapi-max-target-r-loprio.txt")).FlagWithArg("--tag ", "lo-prio").
 		FlagWithInput("--max-target-q ",
-			android.PathForSource(ctx, "frameworks/base/config/hiddenapi-max-target-q.txt")).
+			android.PathForSource(ctx, "frameworks/base/boot/hiddenapi/hiddenapi-max-target-q.txt")).
 		FlagWithInput("--max-target-p ",
-			android.PathForSource(ctx, "frameworks/base/config/hiddenapi-max-target-p.txt")).
+			android.PathForSource(ctx, "frameworks/base/boot/hiddenapi/hiddenapi-max-target-p.txt")).
 		FlagWithInput("--max-target-o ", android.PathForSource(
-			ctx, "frameworks/base/config/hiddenapi-max-target-o.txt")).Flag("--ignore-conflicts ").FlagWithArg("--tag ", "lo-prio").
+			ctx, "frameworks/base/boot/hiddenapi/hiddenapi-max-target-o.txt")).Flag("--ignore-conflicts ").FlagWithArg("--tag ", "lo-prio").
 		FlagWithInput("--blocked ",
-			android.PathForSource(ctx, "frameworks/base/config/hiddenapi-force-blocked.txt")).
+			android.PathForSource(ctx, "frameworks/base/boot/hiddenapi/hiddenapi-force-blocked.txt")).
 		FlagWithInput("--unsupported ", android.PathForSource(
-			ctx, "frameworks/base/config/hiddenapi-unsupported-packages.txt")).Flag("--packages ").
+			ctx, "frameworks/base/boot/hiddenapi/hiddenapi-unsupported-packages.txt")).Flag("--packages ").
 		FlagWithOutput("--output ", tempPath)
 
 	commitChangeForRestat(rule, tempPath, outputPath)
diff --git a/java/hiddenapi_singleton_test.go b/java/hiddenapi_singleton_test.go
index d6c6a2d..5c449e5 100644
--- a/java/hiddenapi_singleton_test.go
+++ b/java/hiddenapi_singleton_test.go
@@ -39,7 +39,8 @@
 	prepareForJavaTest, PrepareForTestWithHiddenApiBuildComponents)
 
 func TestHiddenAPISingleton(t *testing.T) {
-	result := hiddenApiFixtureFactory.Extend(
+	result := android.GroupFixturePreparers(
+		hiddenApiFixtureFactory,
 		fixtureSetBootJarsProductVariable("platform:foo"),
 	).RunTestWithBp(t, `
 		java_library {
@@ -50,13 +51,14 @@
 	`)
 
 	hiddenAPI := result.SingletonForTests("hiddenapi")
-	hiddenapiRule := hiddenAPI.Rule("hiddenapi").RelativeToTop()
+	hiddenapiRule := hiddenAPI.Rule("hiddenapi")
 	want := "--boot-dex=out/soong/.intermediates/foo/android_common/aligned/foo.jar"
 	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, want)
 }
 
 func TestHiddenAPIIndexSingleton(t *testing.T) {
-	result := hiddenApiFixtureFactory.Extend(
+	result := android.GroupFixturePreparers(
+		hiddenApiFixtureFactory,
 		PrepareForTestWithJavaSdkLibraryFiles,
 		FixtureWithLastReleaseApis("bar"),
 		fixtureSetBootJarsProductVariable("platform:foo", "platform:bar"),
@@ -115,7 +117,8 @@
 			" replaced by the prebuilt module \"prebuilt_foo\" but unfortunately it does not provide a" +
 			" suitable boot dex jar"
 
-	hiddenApiFixtureFactory.Extend(
+	android.GroupFixturePreparers(
+		hiddenApiFixtureFactory,
 		fixtureSetBootJarsProductVariable("platform:foo"),
 	).ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(expectedErrorMessage)).
 		RunTestWithBp(t, `
@@ -134,7 +137,8 @@
 }
 
 func TestHiddenAPISingletonWithPrebuilt(t *testing.T) {
-	result := hiddenApiFixtureFactory.Extend(
+	result := android.GroupFixturePreparers(
+		hiddenApiFixtureFactory,
 		fixtureSetBootJarsProductVariable("platform:foo"),
 	).RunTestWithBp(t, `
 		java_import {
@@ -145,13 +149,14 @@
 	`)
 
 	hiddenAPI := result.SingletonForTests("hiddenapi")
-	hiddenapiRule := hiddenAPI.Rule("hiddenapi").RelativeToTop()
+	hiddenapiRule := hiddenAPI.Rule("hiddenapi")
 	want := "--boot-dex=out/soong/.intermediates/foo/android_common/aligned/foo.jar"
 	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, want)
 }
 
 func TestHiddenAPISingletonWithPrebuiltUseSource(t *testing.T) {
-	result := hiddenApiFixtureFactory.Extend(
+	result := android.GroupFixturePreparers(
+		hiddenApiFixtureFactory,
 		fixtureSetBootJarsProductVariable("platform:foo"),
 	).RunTestWithBp(t, `
 		java_library {
@@ -169,7 +174,7 @@
 	`)
 
 	hiddenAPI := result.SingletonForTests("hiddenapi")
-	hiddenapiRule := hiddenAPI.Rule("hiddenapi").RelativeToTop()
+	hiddenapiRule := hiddenAPI.Rule("hiddenapi")
 	fromSourceJarArg := "--boot-dex=out/soong/.intermediates/foo/android_common/aligned/foo.jar"
 	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, fromSourceJarArg)
 
@@ -178,7 +183,8 @@
 }
 
 func TestHiddenAPISingletonWithPrebuiltOverrideSource(t *testing.T) {
-	result := hiddenApiFixtureFactory.Extend(
+	result := android.GroupFixturePreparers(
+		hiddenApiFixtureFactory,
 		fixtureSetBootJarsProductVariable("platform:foo"),
 	).RunTestWithBp(t, `
 		java_library {
@@ -196,7 +202,7 @@
 	`)
 
 	hiddenAPI := result.SingletonForTests("hiddenapi")
-	hiddenapiRule := hiddenAPI.Rule("hiddenapi").RelativeToTop()
+	hiddenapiRule := hiddenAPI.Rule("hiddenapi")
 	prebuiltJarArg := "--boot-dex=out/soong/.intermediates/prebuilt_foo/android_common/dex/foo.jar"
 	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, prebuiltJarArg)
 
@@ -236,7 +242,8 @@
 	}
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
-			result := hiddenApiFixtureFactory.Extend(
+			result := android.GroupFixturePreparers(
+				hiddenApiFixtureFactory,
 				tc.preparer,
 				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
 					variables.Always_use_prebuilt_sdks = proptools.BoolPtr(tc.unbundledBuild)
@@ -244,7 +251,7 @@
 			).RunTest(t)
 
 			hiddenAPI := result.SingletonForTests("hiddenapi")
-			hiddenapiRule := hiddenAPI.Rule("hiddenapi").RelativeToTop()
+			hiddenapiRule := hiddenAPI.Rule("hiddenapi")
 			wantPublicStubs := "--public-stub-classpath=" + generateSdkDexPath(tc.publicStub, tc.unbundledBuild)
 			android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, wantPublicStubs)
 
@@ -286,7 +293,8 @@
 	// Where to find the prebuilt hiddenapi files:
 	prebuiltHiddenApiDir := "path/to/prebuilt/hiddenapi"
 
-	result := hiddenApiFixtureFactory.Extend(
+	result := android.GroupFixturePreparers(
+		hiddenApiFixtureFactory,
 		fixtureSetBootJarsProductVariable("platform:foo"),
 		fixtureSetPrebuiltHiddenApiDirProductVariable(&prebuiltHiddenApiDir),
 	).RunTestWithBp(t, `
@@ -307,7 +315,7 @@
 	cpRule := hiddenAPI.Rule("Cp")
 	actualCpInput := cpRule.BuildParams.Input
 	actualCpOutput := cpRule.BuildParams.Output
-	encodeDexRule := foo.Rule("hiddenAPIEncodeDex").RelativeToTop()
+	encodeDexRule := foo.Rule("hiddenAPIEncodeDex")
 	actualFlagsCsv := encodeDexRule.BuildParams.Args["flagsCsv"]
 
 	android.AssertPathRelativeToTopEquals(t, "hiddenapi cp rule input", expectedCpInput, actualCpInput)
diff --git a/java/java.go b/java/java.go
index 9786947..fa7c96e 100644
--- a/java/java.go
+++ b/java/java.go
@@ -33,12 +33,12 @@
 )
 
 func init() {
-	RegisterJavaBuildComponents(android.InitRegistrationContext)
+	registerJavaBuildComponents(android.InitRegistrationContext)
 
 	RegisterJavaSdkMemberTypes()
 }
 
-func RegisterJavaBuildComponents(ctx android.RegistrationContext) {
+func registerJavaBuildComponents(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("java_defaults", DefaultsFactory)
 
 	ctx.RegisterModuleType("java_library", LibraryFactory)
@@ -304,7 +304,7 @@
 	unstrippedFile android.Path
 }
 
-func sdkDeps(ctx android.BottomUpMutatorContext, sdkContext sdkContext, d dexer) {
+func sdkDeps(ctx android.BottomUpMutatorContext, sdkContext android.SdkContext, d dexer) {
 	sdkDep := decodeSdkDep(ctx, sdkContext)
 	if sdkDep.useModule {
 		ctx.AddVariationDependencies(nil, bootClasspathTag, sdkDep.bootclasspath...)
@@ -352,11 +352,11 @@
 	}
 }
 
-func getJavaVersion(ctx android.ModuleContext, javaVersion string, sdkContext sdkContext) javaVersion {
+func getJavaVersion(ctx android.ModuleContext, javaVersion string, sdkContext android.SdkContext) javaVersion {
 	if javaVersion != "" {
 		return normalizeJavaVersion(ctx, javaVersion)
 	} else if ctx.Device() {
-		return sdkContext.sdkVersion().defaultJavaLanguageVersion(ctx)
+		return defaultJavaLanguageVersion(ctx, sdkContext.SdkVersion())
 	} else {
 		return JAVA_VERSION_9
 	}
@@ -776,7 +776,7 @@
 func (j *Test) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	if j.testProperties.Test_options.Unit_test == nil && ctx.Host() {
 		// TODO(b/): Clean temporary heuristic to avoid unexpected onboarding.
-		defaultUnitTest := !inList("tradefed", j.properties.Static_libs) && !inList("tradefed", j.properties.Libs) && !inList("cts", j.testProperties.Test_suites)
+		defaultUnitTest := !inList("tradefed", j.properties.Libs) && !inList("cts", j.testProperties.Test_suites)
 		j.testProperties.Test_options.Unit_test = proptools.BoolPtr(defaultUnitTest)
 	}
 	j.testConfig = tradefed.AutoGenJavaTestConfig(ctx, j.testProperties.Test_config, j.testProperties.Test_config_template,
@@ -1074,8 +1074,14 @@
 type ImportProperties struct {
 	Jars []string `android:"path,arch_variant"`
 
+	// The version of the SDK that the source prebuilt file was built against. Defaults to the
+	// current version if not specified.
 	Sdk_version *string
 
+	// The minimum version of the SDK that this module supports. Defaults to sdk_version if not
+	// specified.
+	Min_sdk_version *string
+
 	Installable *bool
 
 	// List of shared java libs that this module has dependencies to
@@ -1126,28 +1132,31 @@
 	hideApexVariantFromMake bool
 }
 
-func (j *Import) sdkVersion() sdkSpec {
-	return sdkSpecFrom(String(j.properties.Sdk_version))
+func (j *Import) SdkVersion() android.SdkSpec {
+	return android.SdkSpecFrom(String(j.properties.Sdk_version))
 }
 
 func (j *Import) makeSdkVersion() string {
-	return j.sdkVersion().raw
+	return j.SdkVersion().Raw
 }
 
-func (j *Import) systemModules() string {
+func (j *Import) SystemModules() string {
 	return "none"
 }
 
-func (j *Import) minSdkVersion() sdkSpec {
-	return j.sdkVersion()
+func (j *Import) MinSdkVersion() android.SdkSpec {
+	if j.properties.Min_sdk_version != nil {
+		return android.SdkSpecFrom(*j.properties.Min_sdk_version)
+	}
+	return j.SdkVersion()
 }
 
-func (j *Import) targetSdkVersion() sdkSpec {
-	return j.sdkVersion()
+func (j *Import) TargetSdkVersion() android.SdkSpec {
+	return j.SdkVersion()
 }
 
-func (j *Import) MinSdkVersion() string {
-	return j.minSdkVersion().version.String()
+func (j *Import) MinSdkVersionString() string {
+	return j.MinSdkVersion().ApiLevel.String()
 }
 
 func (j *Import) Prebuilt() *android.Prebuilt {
@@ -1178,7 +1187,7 @@
 	ctx.AddVariationDependencies(nil, libTag, j.properties.Libs...)
 
 	if ctx.Device() && Bool(j.dexProperties.Compile_dex) {
-		sdkDeps(ctx, sdkContext(j), j.dexer)
+		sdkDeps(ctx, android.SdkContext(j), j.dexer)
 	}
 }
 
@@ -1221,7 +1230,7 @@
 		} else if dep, ok := module.(SdkLibraryDependency); ok {
 			switch tag {
 			case libTag:
-				flags.classpath = append(flags.classpath, dep.SdkHeaderJars(ctx, j.sdkVersion())...)
+				flags.classpath = append(flags.classpath, dep.SdkHeaderJars(ctx, j.SdkVersion())...)
 			}
 		}
 
@@ -1263,7 +1272,7 @@
 				ctx.ModuleErrorf("internal error: no dex implementation jar available from prebuilt_apex %q", deapexerModule.Name())
 			}
 		} else if Bool(j.dexProperties.Compile_dex) {
-			sdkDep := decodeSdkDep(ctx, sdkContext(j))
+			sdkDep := decodeSdkDep(ctx, android.SdkContext(j))
 			if sdkDep.invalidVersion {
 				ctx.AddMissingDependencies(sdkDep.bootclasspath)
 				ctx.AddMissingDependencies(sdkDep.java9Classpath)
@@ -1282,7 +1291,7 @@
 			j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex
 
 			var dexOutputFile android.OutputPath
-			dexOutputFile = j.dexer.compileDex(ctx, flags, j.minSdkVersion(), outputFile, jarName)
+			dexOutputFile = j.dexer.compileDex(ctx, flags, j.MinSdkVersion(), outputFile, jarName)
 			if ctx.Failed() {
 				return
 			}
@@ -1350,7 +1359,20 @@
 // Implements android.ApexModule
 func (j *Import) ShouldSupportSdkVersion(ctx android.BaseModuleContext,
 	sdkVersion android.ApiLevel) error {
-	// Do not check for prebuilts against the min_sdk_version of enclosing APEX
+	sdkSpec := j.MinSdkVersion()
+	if !sdkSpec.Specified() {
+		return fmt.Errorf("min_sdk_version is not specified")
+	}
+	if sdkSpec.Kind == android.SdkCore {
+		return nil
+	}
+	ver, err := sdkSpec.EffectiveVersion(ctx)
+	if err != nil {
+		return err
+	}
+	if ver.GreaterThan(sdkVersion) {
+		return fmt.Errorf("newer SDK(%v)", ver)
+	}
 	return nil
 }
 
diff --git a/java/java_test.go b/java/java_test.go
index 9924be7..fdf7579 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -74,23 +74,6 @@
 	return result.TestContext, result.Config
 }
 
-// testJavaErrorWithConfig is a legacy way of running tests of java modules that expect errors.
-//
-// See testJava for an explanation as to how to stop using this deprecated method.
-//
-// deprecated
-func testJavaErrorWithConfig(t *testing.T, pattern string, config android.Config) (*android.TestContext, android.Config) {
-	t.Helper()
-	// This must be done on the supplied config and not as part of the fixture because any changes to
-	// the fixture's config will be ignored when RunTestWithConfig replaces it.
-	pathCtx := android.PathContextForTesting(config)
-	dexpreopt.SetTestGlobalConfig(config, dexpreopt.GlobalConfigForTests(pathCtx))
-	result := prepareForJavaTest.
-		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(pattern)).
-		RunTestWithConfig(t, config)
-	return result.TestContext, result.Config
-}
-
 // testJavaWithFS runs tests using the prepareForJavaTest
 //
 // See testJava for an explanation as to how to stop using this deprecated method.
@@ -250,7 +233,7 @@
 		}
 	`)
 
-	javac := ctx.ModuleForTests("foo", "android_common").Rule("javac").RelativeToTop()
+	javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
 	combineJar := ctx.ModuleForTests("foo", "android_common").Description("for javac")
 
 	if len(javac.Inputs) != 1 || javac.Inputs[0].String() != "a.java" {
@@ -628,7 +611,7 @@
 	}
 
 	t.Run("empty/missing directory", func(t *testing.T) {
-		test(t, "empty-directory", []string{})
+		test(t, "empty-directory", nil)
 	})
 
 	t.Run("non-empty set of sources", func(t *testing.T) {
@@ -845,7 +828,12 @@
 			if expectedErrorPattern != "" {
 				errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(expectedErrorPattern)
 			}
-			prepareForJavaTest.ExtendWithErrorHandler(errorHandler).RunTest(t, createPreparer(info))
+			android.GroupFixturePreparers(
+				prepareForJavaTest,
+				createPreparer(info),
+			).
+				ExtendWithErrorHandler(errorHandler).
+				RunTest(t)
 		})
 	}
 
@@ -976,7 +964,7 @@
 		}
 		`)
 
-	javac := ctx.ModuleForTests("foo", "android_common").Rule("javac").RelativeToTop()
+	javac := ctx.ModuleForTests("foo", "android_common").Rule("javac")
 	combineJar := ctx.ModuleForTests("foo", "android_common").Description("for javac")
 
 	if len(javac.Inputs) != 1 || javac.Inputs[0].String() != "a.java" {
@@ -1336,11 +1324,11 @@
 		}
 		`)
 
-	fooTurbine := result.ModuleForTests("foo", "android_common").Rule("turbine").RelativeToTop()
-	barTurbine := result.ModuleForTests("bar", "android_common").Rule("turbine").RelativeToTop()
-	barJavac := result.ModuleForTests("bar", "android_common").Rule("javac").RelativeToTop()
-	barTurbineCombined := result.ModuleForTests("bar", "android_common").Description("for turbine").RelativeToTop()
-	bazJavac := result.ModuleForTests("baz", "android_common").Rule("javac").RelativeToTop()
+	fooTurbine := result.ModuleForTests("foo", "android_common").Rule("turbine")
+	barTurbine := result.ModuleForTests("bar", "android_common").Rule("turbine")
+	barJavac := result.ModuleForTests("bar", "android_common").Rule("javac")
+	barTurbineCombined := result.ModuleForTests("bar", "android_common").Description("for turbine")
+	bazJavac := result.ModuleForTests("baz", "android_common").Rule("javac")
 
 	android.AssertPathsRelativeToTopEquals(t, "foo inputs", []string{"a.java"}, fooTurbine.Inputs)
 
@@ -1363,253 +1351,13 @@
 
 	barHeaderJar := filepath.Join("out", "soong", ".intermediates", "bar", "android_common", "turbine-combined", "bar.jar")
 	for i := 0; i < 3; i++ {
-		barJavac := ctx.ModuleForTests("bar", "android_common").Description("javac" + strconv.Itoa(i)).RelativeToTop()
+		barJavac := ctx.ModuleForTests("bar", "android_common").Description("javac" + strconv.Itoa(i))
 		if !strings.Contains(barJavac.Args["classpath"], barHeaderJar) {
 			t.Errorf("bar javac classpath %v does not contain %q", barJavac.Args["classpath"], barHeaderJar)
 		}
 	}
 }
 
-func TestDroiddoc(t *testing.T) {
-	ctx, _ := testJavaWithFS(t, `
-		droiddoc_exported_dir {
-		    name: "droiddoc-templates-sdk",
-		    path: ".",
-		}
-		filegroup {
-		    name: "bar-doc-aidl-srcs",
-		    srcs: ["bar-doc/IBar.aidl"],
-		    path: "bar-doc",
-		}
-		droidstubs {
-		    name: "bar-stubs",
-		    srcs: [
-		        "bar-doc/a.java",
-		    ],
-		    exclude_srcs: [
-		        "bar-doc/b.java"
-		    ],
-		    api_levels_annotations_dirs: [
-		      "droiddoc-templates-sdk",
-		    ],
-		    api_levels_annotations_enabled: true,
-		}
-		droiddoc {
-		    name: "bar-doc",
-		    srcs: [
-		        ":bar-stubs",
-		        "bar-doc/IFoo.aidl",
-		        ":bar-doc-aidl-srcs",
-		    ],
-		    custom_template: "droiddoc-templates-sdk",
-		    hdf: [
-		        "android.whichdoc offline",
-		    ],
-		    knowntags: [
-		        "bar-doc/known_oj_tags.txt",
-		    ],
-		    proofread_file: "libcore-proofread.txt",
-		    todo_file: "libcore-docs-todo.html",
-		    flags: ["-offlinemode -title \"libcore\""],
-		}
-		`,
-		map[string][]byte{
-			"bar-doc/a.java": nil,
-			"bar-doc/b.java": nil,
-		})
-	barStubs := ctx.ModuleForTests("bar-stubs", "android_common")
-	barStubsOutputs, err := barStubs.Module().(*Droidstubs).OutputFiles("")
-	if err != nil {
-		t.Errorf("Unexpected error %q retrieving \"bar-stubs\" output file", err)
-	}
-	if len(barStubsOutputs) != 1 {
-		t.Errorf("Expected one output from \"bar-stubs\" got %s", barStubsOutputs)
-	}
-
-	barStubsOutput := barStubsOutputs[0]
-	barDoc := ctx.ModuleForTests("bar-doc", "android_common")
-	javaDoc := barDoc.Rule("javadoc").RelativeToTop()
-	if g, w := javaDoc.Implicits.Strings(), barStubsOutput.String(); !inList(w, g) {
-		t.Errorf("implicits of bar-doc must contain %q, but was %q.", w, g)
-	}
-
-	expected := "-sourcepath out/soong/.intermediates/bar-doc/android_common/srcjars "
-	if !strings.Contains(javaDoc.RuleParams.Command, expected) {
-		t.Errorf("bar-doc command does not contain flag %q, but should\n%q", expected, javaDoc.RuleParams.Command)
-	}
-
-	aidl := barDoc.Rule("aidl")
-	if g, w := javaDoc.Implicits.Strings(), aidl.Output.String(); !inList(w, g) {
-		t.Errorf("implicits of bar-doc must contain %q, but was %q.", w, g)
-	}
-
-	if g, w := aidl.Implicits.Strings(), []string{"bar-doc/IBar.aidl", "bar-doc/IFoo.aidl"}; !reflect.DeepEqual(w, g) {
-		t.Errorf("aidl inputs must be %q, but was %q", w, g)
-	}
-}
-
-func TestDroiddocArgsAndFlagsCausesError(t *testing.T) {
-	testJavaError(t, "flags is set. Cannot set args", `
-		droiddoc_exported_dir {
-		    name: "droiddoc-templates-sdk",
-		    path: ".",
-		}
-		filegroup {
-		    name: "bar-doc-aidl-srcs",
-		    srcs: ["bar-doc/IBar.aidl"],
-		    path: "bar-doc",
-		}
-		droidstubs {
-		    name: "bar-stubs",
-		    srcs: [
-		        "bar-doc/a.java",
-		    ],
-		    exclude_srcs: [
-		        "bar-doc/b.java"
-		    ],
-		    api_levels_annotations_dirs: [
-		      "droiddoc-templates-sdk",
-		    ],
-		    api_levels_annotations_enabled: true,
-		}
-		droiddoc {
-		    name: "bar-doc",
-		    srcs: [
-		        ":bar-stubs",
-		        "bar-doc/IFoo.aidl",
-		        ":bar-doc-aidl-srcs",
-		    ],
-		    custom_template: "droiddoc-templates-sdk",
-		    hdf: [
-		        "android.whichdoc offline",
-		    ],
-		    knowntags: [
-		        "bar-doc/known_oj_tags.txt",
-		    ],
-		    proofread_file: "libcore-proofread.txt",
-		    todo_file: "libcore-docs-todo.html",
-		    flags: ["-offlinemode -title \"libcore\""],
-		    args: "-offlinemode -title \"libcore\"",
-		}
-		`)
-}
-
-func TestDroidstubs(t *testing.T) {
-	ctx, _ := testJavaWithFS(t, `
-		droiddoc_exported_dir {
-			name: "droiddoc-templates-sdk",
-			path: ".",
-		}
-
-		droidstubs {
-			name: "bar-stubs",
-			srcs: ["bar-doc/a.java"],
-			api_levels_annotations_dirs: ["droiddoc-templates-sdk"],
-			api_levels_annotations_enabled: true,
-		}
-
-		droidstubs {
-			name: "bar-stubs-other",
-			srcs: ["bar-doc/a.java"],
-			high_mem: true,
-			api_levels_annotations_dirs: ["droiddoc-templates-sdk"],
-			api_levels_annotations_enabled: true,
-			api_levels_jar_filename: "android.other.jar",
-		}
-		`,
-		map[string][]byte{
-			"bar-doc/a.java": nil,
-		})
-	testcases := []struct {
-		moduleName          string
-		expectedJarFilename string
-		high_mem            bool
-	}{
-		{
-			moduleName:          "bar-stubs",
-			expectedJarFilename: "android.jar",
-			high_mem:            false,
-		},
-		{
-			moduleName:          "bar-stubs-other",
-			expectedJarFilename: "android.other.jar",
-			high_mem:            true,
-		},
-	}
-	for _, c := range testcases {
-		m := ctx.ModuleForTests(c.moduleName, "android_common")
-		metalava := m.Rule("metalava")
-		rp := metalava.RuleParams
-		expected := "--android-jar-pattern ./%/public/" + c.expectedJarFilename
-		if actual := rp.Command; !strings.Contains(actual, expected) {
-			t.Errorf("For %q, expected metalava argument %q, but was not found %q", c.moduleName, expected, actual)
-		}
-
-		if actual := rp.Pool != nil && strings.Contains(rp.Pool.String(), "highmem"); actual != c.high_mem {
-			t.Errorf("Expected %q high_mem to be %v, was %v", c.moduleName, c.high_mem, actual)
-		}
-	}
-}
-
-func TestDroidstubsWithSystemModules(t *testing.T) {
-	ctx, _ := testJava(t, `
-		droidstubs {
-		    name: "stubs-source-system-modules",
-		    srcs: [
-		        "bar-doc/a.java",
-		    ],
-				sdk_version: "none",
-				system_modules: "source-system-modules",
-		}
-
-		java_library {
-				name: "source-jar",
-		    srcs: [
-		        "a.java",
-		    ],
-		}
-
-		java_system_modules {
-				name: "source-system-modules",
-				libs: ["source-jar"],
-		}
-
-		droidstubs {
-		    name: "stubs-prebuilt-system-modules",
-		    srcs: [
-		        "bar-doc/a.java",
-		    ],
-				sdk_version: "none",
-				system_modules: "prebuilt-system-modules",
-		}
-
-		java_import {
-				name: "prebuilt-jar",
-				jars: ["a.jar"],
-		}
-
-		java_system_modules_import {
-				name: "prebuilt-system-modules",
-				libs: ["prebuilt-jar"],
-		}
-		`)
-
-	checkSystemModulesUseByDroidstubs(t, ctx, "stubs-source-system-modules", "source-jar.jar")
-
-	checkSystemModulesUseByDroidstubs(t, ctx, "stubs-prebuilt-system-modules", "prebuilt-jar.jar")
-}
-
-func checkSystemModulesUseByDroidstubs(t *testing.T, ctx *android.TestContext, moduleName string, systemJar string) {
-	metalavaRule := ctx.ModuleForTests(moduleName, "android_common").Rule("metalava")
-	var systemJars []string
-	for _, i := range metalavaRule.Implicits {
-		systemJars = append(systemJars, i.Base())
-	}
-	if len(systemJars) < 1 || systemJars[0] != systemJar {
-		t.Errorf("inputs of %q must be []string{%q}, but was %#v.", moduleName, systemJar, systemJars)
-	}
-}
-
 func TestJarGenrules(t *testing.T) {
 	ctx, _ := testJava(t, `
 		java_library {
@@ -1910,7 +1658,7 @@
 
 	// The bar library should depend on the stubs jar.
 	barLibrary := result.ModuleForTests("bar", "android_common").Rule("javac")
-	if expected, actual := `^-classpath .*:/[^:]*/turbine-combined/foo\.stubs\.jar$`, barLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
+	if expected, actual := `^-classpath .*:out/soong/[^:]*/turbine-combined/foo\.stubs\.jar$`, barLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
 		t.Errorf("expected %q, found %#q", expected, actual)
 	}
 }
@@ -2213,7 +1961,7 @@
 		`)
 	// The baz library should depend on the system stubs jar.
 	bazLibrary := result.ModuleForTests("baz", "android_common").Rule("javac")
-	if expected, actual := `^-classpath .*:/[^:]*/turbine-combined/foo\.stubs.system\.jar$`, bazLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
+	if expected, actual := `^-classpath .*:out/soong/[^:]*/turbine-combined/foo\.stubs.system\.jar$`, bazLibrary.Args["classpath"]; !regexp.MustCompile(expected).MatchString(actual) {
 		t.Errorf("expected %q, found %#q", expected, actual)
 	}
 }
diff --git a/java/kotlin.go b/java/kotlin.go
index 2960f81..3a6fc0f 100644
--- a/java/kotlin.go
+++ b/java/kotlin.go
@@ -34,8 +34,9 @@
 			`${config.GenKotlinBuildFileCmd} --classpath "$classpath" --name "$name"` +
 			` --out_dir "$classesDir" --srcs "$out.rsp" --srcs "$srcJarDir/list"` +
 			` $commonSrcFilesArg --out "$kotlinBuildFile" && ` +
-			`${config.KotlincCmd} ${config.JavacHeapFlags} $kotlincFlags ` +
-			`-jvm-target $kotlinJvmTarget -Xbuild-file=$kotlinBuildFile -kotlin-home $emptyDir && ` +
+			`${config.KotlincCmd} ${config.KotlincSuppressJDK9Warnings} ${config.JavacHeapFlags} ` +
+			`$kotlincFlags -jvm-target $kotlinJvmTarget -Xbuild-file=$kotlinBuildFile ` +
+			`-kotlin-home $emptyDir && ` +
 			`${config.SoongZipCmd} -jar -o $out -C $classesDir -D $classesDir && ` +
 			`rm -rf "$srcJarDir"`,
 		CommandDeps: []string{
@@ -123,8 +124,8 @@
 			`${config.GenKotlinBuildFileCmd} --classpath "$classpath" --name "$name"` +
 			` --srcs "$out.rsp" --srcs "$srcJarDir/list"` +
 			` $commonSrcFilesArg --out "$kotlinBuildFile" && ` +
-			`${config.KotlincCmd} ${config.KotlincSuppressJDK9Warnings} ${config.JavacHeapFlags} $kotlincFlags ` +
-			`-Xplugin=${config.KotlinKaptJar} ` +
+			`${config.KotlincCmd} ${config.KaptSuppressJDK9Warnings} ${config.KotlincSuppressJDK9Warnings} ` +
+			`${config.JavacHeapFlags} $kotlincFlags -Xplugin=${config.KotlinKaptJar} ` +
 			`-P plugin:org.jetbrains.kotlin.kapt3:sources=$kaptDir/sources ` +
 			`-P plugin:org.jetbrains.kotlin.kapt3:classes=$kaptDir/classes ` +
 			`-P plugin:org.jetbrains.kotlin.kapt3:stubs=$kaptDir/stubs ` +
diff --git a/java/lint.go b/java/lint.go
index 938e2b0..30843dc 100644
--- a/java/lint.go
+++ b/java/lint.go
@@ -182,11 +182,6 @@
 	cacheDir   android.WritablePath
 	homeDir    android.WritablePath
 	srcjarDir  android.WritablePath
-
-	deps android.Paths
-
-	remoteInputs    android.Paths
-	remoteRSPInputs android.Paths
 }
 
 func lintRBEExecStrategy(ctx android.ModuleContext) string {
@@ -194,39 +189,6 @@
 }
 
 func (l *linter) writeLintProjectXML(ctx android.ModuleContext, rule *android.RuleBuilder) lintPaths {
-	var deps android.Paths
-	var remoteInputs android.Paths
-	var remoteRSPInputs android.Paths
-
-	// Paths passed to trackInputDependency will be added as dependencies of the rule that runs
-	// lint and passed as inputs to the remote execution proxy.
-	trackInputDependency := func(paths ...android.Path) {
-		deps = append(deps, paths...)
-		remoteInputs = append(remoteInputs, paths...)
-	}
-
-	// Paths passed to trackRSPDependency will be added as dependencies of the rule that runs
-	// lint, but the RSP file will be used by the remote execution proxy to find the files so that
-	// it doesn't overflow command line limits.
-	trackRSPDependency := func(paths android.Paths, rsp android.Path) {
-		deps = append(deps, paths...)
-		remoteRSPInputs = append(remoteRSPInputs, rsp)
-	}
-
-	var resourcesList android.WritablePath
-	if len(l.resources) > 0 {
-		// The list of resources may be too long to put on the command line, but
-		// we can't use the rsp file because it is already being used for srcs.
-		// Insert a second rule to write out the list of resources to a file.
-		resourcesList = android.PathForModuleOut(ctx, "resources.list")
-		resListRule := android.NewRuleBuilder(pctx, ctx)
-		resListRule.Command().Text("cp").
-			FlagWithRspFileInputList("", resourcesList.ReplaceExtension(ctx, "rsp"), l.resources).
-			Output(resourcesList)
-		resListRule.Build("lint_resources_list", "lint resources list")
-		trackRSPDependency(l.resources, resourcesList)
-	}
-
 	projectXMLPath := android.PathForModuleOut(ctx, "lint", "project.xml")
 	// Lint looks for a lint.xml file next to the project.xml file, give it one.
 	configXMLPath := android.PathForModuleOut(ctx, "lint", "lint.xml")
@@ -235,20 +197,6 @@
 
 	srcJarDir := android.PathForModuleOut(ctx, "lint", "srcjars")
 	srcJarList := zipSyncCmd(ctx, rule, srcJarDir, l.srcJars)
-	// TODO(ccross): this is a little fishy.  The files extracted from the srcjars are referenced
-	// by the project.xml and used by the later lint rule, but the lint rule depends on the srcjars,
-	// not the extracted files.
-	trackRSPDependency(l.srcJars, srcJarList)
-
-	// TODO(ccross): some of the files in l.srcs are generated sources and should be passed to
-	// lint separately.
-	srcsList := android.PathForModuleOut(ctx, "lint", "srcs.list")
-	srcsListRsp := android.PathForModuleOut(ctx, "lint-srcs.list.rsp")
-	rule.Command().Text("cp").
-		FlagWithRspFileInputList("", srcsListRsp, l.srcs).
-		Output(srcsList)
-	trackRSPDependency(l.srcs, srcsList)
-	rule.Temporary(srcsList)
 
 	cmd := rule.Command().
 		BuiltTool("lint-project-xml").
@@ -263,32 +211,31 @@
 		cmd.Flag("--test")
 	}
 	if l.manifest != nil {
-		cmd.FlagWithArg("--manifest ", cmd.PathForInput(l.manifest))
-		trackInputDependency(l.manifest)
+		cmd.FlagWithInput("--manifest ", l.manifest)
 	}
 	if l.mergedManifest != nil {
-		cmd.FlagWithArg("--merged_manifest ", cmd.PathForInput(l.mergedManifest))
-		trackInputDependency(l.mergedManifest)
+		cmd.FlagWithInput("--merged_manifest ", l.mergedManifest)
 	}
 
-	cmd.FlagWithInput("--srcs ", srcsList)
+	// TODO(ccross): some of the files in l.srcs are generated sources and should be passed to
+	// lint separately.
+	srcsList := android.PathForModuleOut(ctx, "lint-srcs.list")
+	cmd.FlagWithRspFileInputList("--srcs ", srcsList, l.srcs)
 
 	cmd.FlagWithInput("--generated_srcs ", srcJarList)
 
-	if resourcesList != nil {
-		cmd.FlagWithInput("--resources ", resourcesList)
+	if len(l.resources) > 0 {
+		resourcesList := android.PathForModuleOut(ctx, "lint-resources.list")
+		cmd.FlagWithRspFileInputList("--resources ", resourcesList, l.resources)
 	}
 
 	if l.classes != nil {
-		cmd.FlagWithArg("--classes ", cmd.PathForInput(l.classes))
-		trackInputDependency(l.classes)
+		cmd.FlagWithInput("--classes ", l.classes)
 	}
 
-	cmd.FlagForEachArg("--classpath ", cmd.PathsForInputs(l.classpath))
-	trackInputDependency(l.classpath...)
+	cmd.FlagForEachInput("--classpath ", l.classpath)
 
-	cmd.FlagForEachArg("--extra_checks_jar ", cmd.PathsForInputs(l.extraLintCheckJars))
-	trackInputDependency(l.extraLintCheckJars...)
+	cmd.FlagForEachInput("--extra_checks_jar ", l.extraLintCheckJars)
 
 	cmd.FlagWithArg("--root_dir ", "$PWD")
 
@@ -309,11 +256,6 @@
 		configXML:  configXMLPath,
 		cacheDir:   cacheDir,
 		homeDir:    homeDir,
-
-		deps: deps,
-
-		remoteInputs:    remoteInputs,
-		remoteRSPInputs: remoteRSPInputs,
 	}
 
 }
@@ -405,7 +347,7 @@
 
 	cmd := rule.Command()
 
-	cmd.Flag("JAVA_OPTS=-Xmx3072m").
+	cmd.Flag(`JAVA_OPTS="-Xmx3072m --add-opens java.base/java.util=ALL-UNNAMED"`).
 		FlagWithArg("ANDROID_SDK_HOME=", lintPaths.homeDir.String()).
 		FlagWithInput("SDK_ANNOTATIONS=", annotationsZipPath).
 		FlagWithInput("LINT_OPTS=-DLINT_API_DATABASE=", apiVersionsXMLPath)
@@ -424,8 +366,7 @@
 		Flag("--exitcode").
 		Flags(l.properties.Lint.Flags).
 		Implicit(annotationsZipPath).
-		Implicit(apiVersionsXMLPath).
-		Implicits(lintPaths.deps)
+		Implicit(apiVersionsXMLPath)
 
 	rule.Temporary(lintPaths.projectXML)
 	rule.Temporary(lintPaths.configXML)
@@ -451,6 +392,9 @@
 
 	rule.Command().Text("rm -rf").Flag(lintPaths.cacheDir.String()).Flag(lintPaths.homeDir.String())
 
+	// The HTML output contains a date, remove it to make the output deterministic.
+	rule.Command().Text(`sed -i.tmp -e 's|Check performed at .*\(</nav>\)|\1|'`).Output(html)
+
 	rule.Build("lint", "lint")
 
 	l.outputs = lintOutputs{
diff --git a/java/lint_defaults.txt b/java/lint_defaults.txt
index 0786b7c..2de05b0 100644
--- a/java/lint_defaults.txt
+++ b/java/lint_defaults.txt
@@ -76,3 +76,5 @@
 
 # TODO(b/158390965): remove this when lint doesn't crash
 --disable_check HardcodedDebugMode
+
+--warning_check QueryAllPackagesPermission
diff --git a/java/platform_bootclasspath.go b/java/platform_bootclasspath.go
new file mode 100644
index 0000000..5507077
--- /dev/null
+++ b/java/platform_bootclasspath.go
@@ -0,0 +1,74 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package java
+
+import (
+	"android/soong/android"
+	"android/soong/dexpreopt"
+)
+
+func init() {
+	registerPlatformBootclasspathBuildComponents(android.InitRegistrationContext)
+}
+
+func registerPlatformBootclasspathBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("platform_bootclasspath", platformBootclasspathFactory)
+}
+
+type platformBootclasspathModule struct {
+	android.ModuleBase
+}
+
+func platformBootclasspathFactory() android.Module {
+	m := &platformBootclasspathModule{}
+	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
+	return m
+}
+
+func (b *platformBootclasspathModule) DepsMutator(ctx android.BottomUpMutatorContext) {
+	if SkipDexpreoptBootJars(ctx) {
+		return
+	}
+
+	// Add a dependency onto the dex2oat tool which is needed for creating the boot image. The
+	// path is retrieved from the dependency by GetGlobalSoongConfig(ctx).
+	dexpreopt.RegisterToolDeps(ctx)
+}
+
+func (b *platformBootclasspathModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	// Nothing to do if skipping the dexpreopt of boot image jars.
+	if SkipDexpreoptBootJars(ctx) {
+		return
+	}
+
+	// Force the GlobalSoongConfig to be created and cached for use by the dex_bootjars
+	// GenerateSingletonBuildActions method as it cannot create it for itself.
+	dexpreopt.GetGlobalSoongConfig(ctx)
+
+	imageConfig := b.getImageConfig(ctx)
+	if imageConfig == nil {
+		return
+	}
+
+	// Construct the boot image info from the config.
+	info := BootImageInfo{imageConfig: imageConfig}
+
+	// Make it available for other modules.
+	ctx.SetProvider(BootImageInfoProvider, info)
+}
+
+func (b *platformBootclasspathModule) getImageConfig(ctx android.EarlyModuleContext) *bootImageConfig {
+	return defaultBootImageConfig(ctx)
+}
diff --git a/java/platform_bootclasspath_test.go b/java/platform_bootclasspath_test.go
new file mode 100644
index 0000000..1c81cfd
--- /dev/null
+++ b/java/platform_bootclasspath_test.go
@@ -0,0 +1,38 @@
+// 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 java
+
+import (
+	"testing"
+
+	"android/soong/android"
+	"android/soong/dexpreopt"
+)
+
+// Contains some simple tests for platform_bootclasspath.
+
+var prepareForTestWithPlatformBootclasspath = android.GroupFixturePreparers(
+	PrepareForTestWithJavaDefaultModules,
+	dexpreopt.PrepareForTestByEnablingDexpreopt,
+)
+
+func TestPlatformBootclasspath(t *testing.T) {
+	prepareForTestWithPlatformBootclasspath.
+		RunTestWithBp(t, `
+			platform_bootclasspath {
+				name: "platform-bootclasspath",
+			}
+		`)
+}
diff --git a/java/platform_compat_config.go b/java/platform_compat_config.go
index 03e82c2..edfa146 100644
--- a/java/platform_compat_config.go
+++ b/java/platform_compat_config.go
@@ -221,12 +221,32 @@
 	metadata android.Path
 }
 
+// isModulePreferredByCompatConfig checks to see whether the module is preferred for use by
+// platform compat config.
+func isModulePreferredByCompatConfig(module android.Module) bool {
+	// A versioned prebuilt_platform_compat_config, i.e. foo-platform-compat-config@current should be
+	// ignored.
+	if s, ok := module.(android.SdkAware); ok {
+		if !s.ContainingSdk().Unversioned() {
+			return false
+		}
+	}
+
+	return android.IsModulePreferred(module)
+}
+
 func (p *platformCompatConfigSingleton) GenerateBuildActions(ctx android.SingletonContext) {
 
 	var compatConfigMetadata android.Paths
 
 	ctx.VisitAllModules(func(module android.Module) {
+		if !module.Enabled() {
+			return
+		}
 		if c, ok := module.(platformCompatConfigMetadataProvider); ok {
+			if !isModulePreferredByCompatConfig(module) {
+				return
+			}
 			metadata := c.compatConfigMetadata()
 			compatConfigMetadata = append(compatConfigMetadata, metadata)
 		}
diff --git a/java/rro.go b/java/rro.go
index aafa88e..4ae0014 100644
--- a/java/rro.go
+++ b/java/rro.go
@@ -91,7 +91,7 @@
 }
 
 func (r *RuntimeResourceOverlay) DepsMutator(ctx android.BottomUpMutatorContext) {
-	sdkDep := decodeSdkDep(ctx, sdkContext(r))
+	sdkDep := decodeSdkDep(ctx, android.SdkContext(r))
 	if sdkDep.hasFrameworkLibs() {
 		r.aapt.deps(ctx, sdkDep)
 	}
@@ -141,23 +141,23 @@
 	ctx.InstallFile(r.installDir, r.outputFile.Base(), r.outputFile)
 }
 
-func (r *RuntimeResourceOverlay) sdkVersion() sdkSpec {
-	return sdkSpecFrom(String(r.properties.Sdk_version))
+func (r *RuntimeResourceOverlay) SdkVersion() android.SdkSpec {
+	return android.SdkSpecFrom(String(r.properties.Sdk_version))
 }
 
-func (r *RuntimeResourceOverlay) systemModules() string {
+func (r *RuntimeResourceOverlay) SystemModules() string {
 	return ""
 }
 
-func (r *RuntimeResourceOverlay) minSdkVersion() sdkSpec {
+func (r *RuntimeResourceOverlay) MinSdkVersion() android.SdkSpec {
 	if r.properties.Min_sdk_version != nil {
-		return sdkSpecFrom(*r.properties.Min_sdk_version)
+		return android.SdkSpecFrom(*r.properties.Min_sdk_version)
 	}
-	return r.sdkVersion()
+	return r.SdkVersion()
 }
 
-func (r *RuntimeResourceOverlay) targetSdkVersion() sdkSpec {
-	return r.sdkVersion()
+func (r *RuntimeResourceOverlay) TargetSdkVersion() android.SdkSpec {
+	return r.SdkVersion()
 }
 
 func (r *RuntimeResourceOverlay) Certificate() Certificate {
diff --git a/java/rro_test.go b/java/rro_test.go
index 0a10d93..bad60bc 100644
--- a/java/rro_test.go
+++ b/java/rro_test.go
@@ -68,7 +68,7 @@
 	m := result.ModuleForTests("foo", "android_common")
 
 	// Check AAPT2 link flags.
-	aapt2Flags := m.Output("package-res.apk").RelativeToTop().Args["flags"]
+	aapt2Flags := m.Output("package-res.apk").Args["flags"]
 	expectedFlags := []string{"--keep-raw-values", "--no-resource-deduping", "--no-resource-removal"}
 	absentFlags := android.RemoveListFromList(expectedFlags, strings.Split(aapt2Flags, " "))
 	if len(absentFlags) > 0 {
diff --git a/java/sdk.go b/java/sdk.go
index 74d5a81..f324b76 100644
--- a/java/sdk.go
+++ b/java/sdk.go
@@ -19,7 +19,6 @@
 	"path/filepath"
 	"sort"
 	"strconv"
-	"strings"
 
 	"android/soong/android"
 	"android/soong/java/config"
@@ -38,19 +37,6 @@
 var nonUpdatableFrameworkAidlPathKey = android.NewOnceKey("nonUpdatableFrameworkAidlPathKey")
 var apiFingerprintPathKey = android.NewOnceKey("apiFingerprintPathKey")
 
-type sdkContext interface {
-	// sdkVersion returns sdkSpec that corresponds to the sdk_version property of the current module
-	sdkVersion() sdkSpec
-	// systemModules returns the system_modules property of the current module, or an empty string if it is not set.
-	systemModules() string
-	// minSdkVersion returns sdkSpec that corresponds to the min_sdk_version property of the current module,
-	// or from sdk_version if it is not set.
-	minSdkVersion() sdkSpec
-	// targetSdkVersion returns the sdkSpec that corresponds to the target_sdk_version property of the current module,
-	// or from sdk_version if it is not set.
-	targetSdkVersion() sdkSpec
-}
-
 func UseApiFingerprint(ctx android.BaseModuleContext) bool {
 	if ctx.Config().UnbundledBuild() &&
 		!ctx.Config().AlwaysUsePrebuiltSdks() &&
@@ -60,318 +46,41 @@
 	return false
 }
 
-// sdkKind represents a particular category of an SDK spec like public, system, test, etc.
-type sdkKind int
-
-const (
-	sdkInvalid sdkKind = iota
-	sdkNone
-	sdkCore
-	sdkCorePlatform
-	sdkPublic
-	sdkSystem
-	sdkTest
-	sdkModule
-	sdkSystemServer
-	sdkPrivate
-)
-
-// String returns the string representation of this sdkKind
-func (k sdkKind) String() string {
-	switch k {
-	case sdkPrivate:
-		return "private"
-	case sdkNone:
-		return "none"
-	case sdkPublic:
-		return "public"
-	case sdkSystem:
-		return "system"
-	case sdkTest:
-		return "test"
-	case sdkCore:
-		return "core"
-	case sdkCorePlatform:
-		return "core_platform"
-	case sdkModule:
-		return "module-lib"
-	case sdkSystemServer:
-		return "system-server"
-	default:
-		return "invalid"
-	}
-}
-
-// sdkVersion represents a specific version number of an SDK spec of a particular kind
-type sdkVersion int
-
-const (
-	// special version number for a not-yet-frozen SDK
-	sdkVersionCurrent sdkVersion = sdkVersion(android.FutureApiLevelInt)
-	// special version number to be used for SDK specs where version number doesn't
-	// make sense, e.g. "none", "", etc.
-	sdkVersionNone sdkVersion = sdkVersion(0)
-)
-
-// isCurrent checks if the sdkVersion refers to the not-yet-published version of an sdkKind
-func (v sdkVersion) isCurrent() bool {
-	return v == sdkVersionCurrent
-}
-
-// isNumbered checks if the sdkVersion refers to the published (a.k.a numbered) version of an sdkKind
-func (v sdkVersion) isNumbered() bool {
-	return !v.isCurrent() && v != sdkVersionNone
-}
-
-// String returns the string representation of this sdkVersion.
-func (v sdkVersion) String() string {
-	if v.isCurrent() {
-		return "current"
-	} else if v.isNumbered() {
-		return strconv.Itoa(int(v))
-	}
-	return "(no version)"
-}
-
-func (v sdkVersion) ApiLevel(ctx android.EarlyModuleContext) android.ApiLevel {
-	return android.ApiLevelOrPanic(ctx, v.String())
-}
-
-// asNumberString directly converts the numeric value of this sdk version as a string.
-// When isNumbered() is true, this method is the same as String(). However, for sdkVersionCurrent
-// and sdkVersionNone, this returns 10000 and 0 while String() returns "current" and "(no version"),
-// respectively.
-func (v sdkVersion) asNumberString() string {
-	return strconv.Itoa(int(v))
-}
-
-// sdkSpec represents the kind and the version of an SDK for a module to build against
-type sdkSpec struct {
-	kind    sdkKind
-	version sdkVersion
-	raw     string
-}
-
-func (s sdkSpec) String() string {
-	return fmt.Sprintf("%s_%s", s.kind, s.version)
-}
-
-// valid checks if this sdkSpec is well-formed. Note however that true doesn't mean that the
-// specified SDK actually exists.
-func (s sdkSpec) valid() bool {
-	return s.kind != sdkInvalid
-}
-
-// specified checks if this sdkSpec is well-formed and is not "".
-func (s sdkSpec) specified() bool {
-	return s.valid() && s.kind != sdkPrivate
-}
-
-// whether the API surface is managed and versioned, i.e. has .txt file that
-// get frozen on SDK freeze and changes get reviewed by API council.
-func (s sdkSpec) stable() bool {
-	if !s.specified() {
-		return false
-	}
-	switch s.kind {
-	case sdkNone:
-		// there is nothing to manage and version in this case; de facto stable API.
-		return true
-	case sdkCore, sdkPublic, sdkSystem, sdkModule, sdkSystemServer:
-		return true
-	case sdkCorePlatform, sdkTest, sdkPrivate:
-		return false
-	default:
-		panic(fmt.Errorf("unknown sdkKind=%v", s.kind))
-	}
-	return false
-}
-
-// prebuiltSdkAvailableForUnbundledBuilt tells whether this sdkSpec can have a prebuilt SDK
-// that can be used for unbundled builds.
-func (s sdkSpec) prebuiltSdkAvailableForUnbundledBuild() bool {
-	// "", "none", and "core_platform" are not available for unbundled build
-	// as we don't/can't have prebuilt stub for the versions
-	return s.kind != sdkPrivate && s.kind != sdkNone && s.kind != sdkCorePlatform
-}
-
-func (s sdkSpec) forVendorPartition(ctx android.EarlyModuleContext) sdkSpec {
-	// If BOARD_CURRENT_API_LEVEL_FOR_VENDOR_MODULES has a numeric value,
-	// use it instead of "current" for the vendor partition.
-	currentSdkVersion := ctx.DeviceConfig().CurrentApiLevelForVendorModules()
-	if currentSdkVersion == "current" {
-		return s
-	}
-
-	if s.kind == sdkPublic || s.kind == sdkSystem {
-		if s.version.isCurrent() {
-			if i, err := strconv.Atoi(currentSdkVersion); err == nil {
-				version := sdkVersion(i)
-				return sdkSpec{s.kind, version, s.raw}
-			}
-			panic(fmt.Errorf("BOARD_CURRENT_API_LEVEL_FOR_VENDOR_MODULES must be either \"current\" or a number, but was %q", currentSdkVersion))
-		}
-	}
-	return s
-}
-
-// usePrebuilt determines whether prebuilt SDK should be used for this sdkSpec with the given context.
-func (s sdkSpec) usePrebuilt(ctx android.EarlyModuleContext) bool {
-	if s.version.isCurrent() {
-		// "current" can be built from source and be from prebuilt SDK
-		return ctx.Config().AlwaysUsePrebuiltSdks()
-	} else if s.version.isNumbered() {
-		// validation check
-		if s.kind != sdkPublic && s.kind != sdkSystem && s.kind != sdkTest && s.kind != sdkModule {
-			panic(fmt.Errorf("prebuilt SDK is not not available for sdkKind=%q", s.kind))
-			return false
-		}
-		// numbered SDKs are always from prebuilt
-		return true
-	}
-	// "", "none", "core_platform" fall here
-	return false
-}
-
-// effectiveVersion converts an sdkSpec into the concrete sdkVersion that the module
-// should use. For modules targeting an unreleased SDK (meaning it does not yet have a number)
-// it returns android.FutureApiLevel(10000).
-func (s sdkSpec) effectiveVersion(ctx android.EarlyModuleContext) (sdkVersion, error) {
-	if !s.valid() {
-		return s.version, fmt.Errorf("invalid sdk version %q", s.raw)
-	}
-
-	if ctx.DeviceSpecific() || ctx.SocSpecific() {
-		s = s.forVendorPartition(ctx)
-	}
-	if s.version.isNumbered() {
-		return s.version, nil
-	}
-	return sdkVersion(ctx.Config().DefaultAppTargetSdk(ctx).FinalOrFutureInt()), nil
-}
-
-// effectiveVersionString converts an sdkSpec into the concrete version string that the module
-// should use. For modules targeting an unreleased SDK (meaning it does not yet have a number)
-// it returns the codename (P, Q, R, etc.)
-func (s sdkSpec) effectiveVersionString(ctx android.EarlyModuleContext) (string, error) {
-	ver, err := s.effectiveVersion(ctx)
-	if err == nil && int(ver) == ctx.Config().DefaultAppTargetSdk(ctx).FinalOrFutureInt() {
-		return ctx.Config().DefaultAppTargetSdk(ctx).String(), nil
-	}
-	return ver.String(), err
-}
-
-func (s sdkSpec) defaultJavaLanguageVersion(ctx android.EarlyModuleContext) javaVersion {
-	sdk, err := s.effectiveVersion(ctx)
+func defaultJavaLanguageVersion(ctx android.EarlyModuleContext, s android.SdkSpec) javaVersion {
+	sdk, err := s.EffectiveVersion(ctx)
 	if err != nil {
 		ctx.PropertyErrorf("sdk_version", "%s", err)
 	}
-	if sdk <= 23 {
+	if sdk.FinalOrFutureInt() <= 23 {
 		return JAVA_VERSION_7
-	} else if sdk <= 29 {
+	} else if sdk.FinalOrFutureInt() <= 29 {
 		return JAVA_VERSION_8
 	} else {
 		return JAVA_VERSION_9
 	}
 }
 
-func sdkSpecFrom(str string) sdkSpec {
-	switch str {
-	// special cases first
-	case "":
-		return sdkSpec{sdkPrivate, sdkVersionNone, str}
-	case "none":
-		return sdkSpec{sdkNone, sdkVersionNone, str}
-	case "core_platform":
-		return sdkSpec{sdkCorePlatform, sdkVersionNone, str}
-	default:
-		// the syntax is [kind_]version
-		sep := strings.LastIndex(str, "_")
-
-		var kindString string
-		if sep == 0 {
-			return sdkSpec{sdkInvalid, sdkVersionNone, str}
-		} else if sep == -1 {
-			kindString = ""
-		} else {
-			kindString = str[0:sep]
-		}
-		versionString := str[sep+1 : len(str)]
-
-		var kind sdkKind
-		switch kindString {
-		case "":
-			kind = sdkPublic
-		case "core":
-			kind = sdkCore
-		case "system":
-			kind = sdkSystem
-		case "test":
-			kind = sdkTest
-		case "module":
-			kind = sdkModule
-		case "system_server":
-			kind = sdkSystemServer
-		default:
-			return sdkSpec{sdkInvalid, sdkVersionNone, str}
-		}
-
-		var version sdkVersion
-		if versionString == "current" {
-			version = sdkVersionCurrent
-		} else if i, err := strconv.Atoi(versionString); err == nil {
-			version = sdkVersion(i)
-		} else {
-			return sdkSpec{sdkInvalid, sdkVersionNone, str}
-		}
-
-		return sdkSpec{kind, version, str}
-	}
-}
-
-func (s sdkSpec) validateSystemSdk(ctx android.EarlyModuleContext) bool {
-	// Ensures that the specified system SDK version is one of BOARD_SYSTEMSDK_VERSIONS (for vendor/product Java module)
-	// Assuming that BOARD_SYSTEMSDK_VERSIONS := 28 29,
-	// sdk_version of the modules in vendor/product that use system sdk must be either system_28, system_29 or system_current
-	if s.kind != sdkSystem || !s.version.isNumbered() {
-		return true
-	}
-	allowedVersions := ctx.DeviceConfig().PlatformSystemSdkVersions()
-	if ctx.DeviceSpecific() || ctx.SocSpecific() || (ctx.ProductSpecific() && ctx.Config().EnforceProductPartitionInterface()) {
-		systemSdkVersions := ctx.DeviceConfig().SystemSdkVersions()
-		if len(systemSdkVersions) > 0 {
-			allowedVersions = systemSdkVersions
-		}
-	}
-	if len(allowedVersions) > 0 && !android.InList(s.version.String(), allowedVersions) {
-		ctx.PropertyErrorf("sdk_version", "incompatible sdk version %q. System SDK version should be one of %q",
-			s.raw, allowedVersions)
-		return false
-	}
-	return true
-}
-
-func decodeSdkDep(ctx android.EarlyModuleContext, sdkContext sdkContext) sdkDep {
-	sdkVersion := sdkContext.sdkVersion()
-	if !sdkVersion.valid() {
-		ctx.PropertyErrorf("sdk_version", "invalid version %q", sdkVersion.raw)
+func decodeSdkDep(ctx android.EarlyModuleContext, sdkContext android.SdkContext) sdkDep {
+	sdkVersion := sdkContext.SdkVersion()
+	if !sdkVersion.Valid() {
+		ctx.PropertyErrorf("sdk_version", "invalid version %q", sdkVersion.Raw)
 		return sdkDep{}
 	}
 
 	if ctx.DeviceSpecific() || ctx.SocSpecific() {
-		sdkVersion = sdkVersion.forVendorPartition(ctx)
+		sdkVersion = sdkVersion.ForVendorPartition(ctx)
 	}
 
-	if !sdkVersion.validateSystemSdk(ctx) {
+	if !sdkVersion.ValidateSystemSdk(ctx) {
 		return sdkDep{}
 	}
 
-	if sdkVersion.usePrebuilt(ctx) {
-		dir := filepath.Join("prebuilts", "sdk", sdkVersion.version.String(), sdkVersion.kind.String())
+	if sdkVersion.UsePrebuilt(ctx) {
+		dir := filepath.Join("prebuilts", "sdk", sdkVersion.ApiLevel.String(), sdkVersion.Kind.String())
 		jar := filepath.Join(dir, "android.jar")
 		// There's no aidl for other SDKs yet.
 		// TODO(77525052): Add aidl files for other SDKs too.
-		publicDir := filepath.Join("prebuilts", "sdk", sdkVersion.version.String(), "public")
+		publicDir := filepath.Join("prebuilts", "sdk", sdkVersion.ApiLevel.String(), "public")
 		aidl := filepath.Join(publicDir, "framework.aidl")
 		jarPath := android.ExistentPathForSource(ctx, jar)
 		aidlPath := android.ExistentPathForSource(ctx, aidl)
@@ -380,23 +89,23 @@
 		if (!jarPath.Valid() || !aidlPath.Valid()) && ctx.Config().AllowMissingDependencies() {
 			return sdkDep{
 				invalidVersion: true,
-				bootclasspath:  []string{fmt.Sprintf("sdk_%s_%s_android", sdkVersion.kind, sdkVersion.version.String())},
+				bootclasspath:  []string{fmt.Sprintf("sdk_%s_%s_android", sdkVersion.Kind, sdkVersion.ApiLevel.String())},
 			}
 		}
 
 		if !jarPath.Valid() {
-			ctx.PropertyErrorf("sdk_version", "invalid sdk version %q, %q does not exist", sdkVersion.raw, jar)
+			ctx.PropertyErrorf("sdk_version", "invalid sdk version %q, %q does not exist", sdkVersion.Raw, jar)
 			return sdkDep{}
 		}
 
 		if !aidlPath.Valid() {
-			ctx.PropertyErrorf("sdk_version", "invalid sdk version %q, %q does not exist", sdkVersion.raw, aidl)
+			ctx.PropertyErrorf("sdk_version", "invalid sdk version %q, %q does not exist", sdkVersion.Raw, aidl)
 			return sdkDep{}
 		}
 
 		var systemModules string
-		if sdkVersion.defaultJavaLanguageVersion(ctx).usesJavaModules() {
-			systemModules = "sdk_public_" + sdkVersion.version.String() + "_system_modules"
+		if defaultJavaLanguageVersion(ctx, sdkVersion).usesJavaModules() {
+			systemModules = "sdk_public_" + sdkVersion.ApiLevel.String() + "_system_modules"
 		}
 
 		return sdkDep{
@@ -418,8 +127,8 @@
 		}
 	}
 
-	switch sdkVersion.kind {
-	case sdkPrivate:
+	switch sdkVersion.Kind {
+	case android.SdkPrivate:
 		return sdkDep{
 			useModule:          true,
 			systemModules:      corePlatformSystemModules(ctx),
@@ -427,8 +136,8 @@
 			classpath:          config.FrameworkLibraries,
 			frameworkResModule: "framework-res",
 		}
-	case sdkNone:
-		systemModules := sdkContext.systemModules()
+	case android.SdkNone:
+		systemModules := sdkContext.SystemModules()
 		if systemModules == "" {
 			ctx.PropertyErrorf("sdk_version",
 				`system_modules is required to be set to a non-empty value when sdk_version is "none", did you mean sdk_version: "core_platform"?`)
@@ -444,34 +153,34 @@
 			systemModules:  systemModules,
 			bootclasspath:  []string{systemModules},
 		}
-	case sdkCorePlatform:
+	case android.SdkCorePlatform:
 		return sdkDep{
 			useModule:        true,
 			systemModules:    corePlatformSystemModules(ctx),
 			bootclasspath:    corePlatformBootclasspathLibraries(ctx),
 			noFrameworksLibs: true,
 		}
-	case sdkPublic:
+	case android.SdkPublic:
 		return toModule([]string{"android_stubs_current"}, "framework-res", sdkFrameworkAidlPath(ctx))
-	case sdkSystem:
+	case android.SdkSystem:
 		return toModule([]string{"android_system_stubs_current"}, "framework-res", sdkFrameworkAidlPath(ctx))
-	case sdkTest:
+	case android.SdkTest:
 		return toModule([]string{"android_test_stubs_current"}, "framework-res", sdkFrameworkAidlPath(ctx))
-	case sdkCore:
+	case android.SdkCore:
 		return sdkDep{
 			useModule:        true,
 			bootclasspath:    []string{"core.current.stubs", config.DefaultLambdaStubsLibrary},
 			systemModules:    "core-current-stubs-system-modules",
 			noFrameworksLibs: true,
 		}
-	case sdkModule:
+	case android.SdkModule:
 		// TODO(146757305): provide .apk and .aidl that have more APIs for modules
 		return toModule([]string{"android_module_lib_stubs_current"}, "framework-res", nonUpdatableFrameworkAidlPath(ctx))
-	case sdkSystemServer:
+	case android.SdkSystemServer:
 		// TODO(146757305): provide .apk and .aidl that have more APIs for modules
 		return toModule([]string{"android_system_server_stubs_current"}, "framework-res", sdkFrameworkAidlPath(ctx))
 	default:
-		panic(fmt.Errorf("invalid sdk %q", sdkVersion.raw))
+		panic(fmt.Errorf("invalid sdk %q", sdkVersion.Raw))
 	}
 }
 
diff --git a/java/sdk_library.go b/java/sdk_library.go
index e1ca77d..eb9ba9b 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -814,22 +814,22 @@
 	return nil
 }
 
-func (c *commonToSdkLibraryAndImport) selectHeaderJarsForSdkVersion(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths {
+func (c *commonToSdkLibraryAndImport) selectHeaderJarsForSdkVersion(ctx android.BaseModuleContext, sdkVersion android.SdkSpec) android.Paths {
 
 	// If a specific numeric version has been requested then use prebuilt versions of the sdk.
-	if sdkVersion.version.isNumbered() {
+	if !sdkVersion.ApiLevel.IsPreview() {
 		return PrebuiltJars(ctx, c.moduleBase.BaseModuleName(), sdkVersion)
 	}
 
 	var apiScope *apiScope
-	switch sdkVersion.kind {
-	case sdkSystem:
+	switch sdkVersion.Kind {
+	case android.SdkSystem:
 		apiScope = apiScopeSystem
-	case sdkModule:
+	case android.SdkModule:
 		apiScope = apiScopeModuleLib
-	case sdkTest:
+	case android.SdkTest:
 		apiScope = apiScopeTest
-	case sdkSystemServer:
+	case android.SdkSystemServer:
 		apiScope = apiScopeSystemServer
 	default:
 		apiScope = apiScopePublic
@@ -932,14 +932,14 @@
 	//
 	// These are turbine generated jars so they only change if the externals of the
 	// class changes but it does not contain and implementation or JavaDoc.
-	SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths
+	SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion android.SdkSpec) android.Paths
 
 	// Get the implementation jars appropriate for the supplied sdk version.
 	//
 	// These are either the implementation jar for the whole sdk library or the implementation
 	// jars for the stubs. The latter should only be needed when generating JavaDoc as otherwise
 	// they are identical to the corresponding header jars.
-	SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths
+	SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion android.SdkSpec) android.Paths
 }
 
 type SdkLibrary struct {
@@ -1147,7 +1147,7 @@
 		return proptools.String(scopeProperties.Sdk_version)
 	}
 
-	sdkDep := decodeSdkDep(mctx, sdkContext(&module.Library))
+	sdkDep := decodeSdkDep(mctx, android.SdkContext(&module.Library))
 	if sdkDep.hasStandardLibs() {
 		// If building against a standard sdk then use the sdk version appropriate for the scope.
 		return apiScope.sdkVersion
@@ -1465,17 +1465,17 @@
 	mctx.CreateModule(sdkLibraryXmlFactory, &props)
 }
 
-func PrebuiltJars(ctx android.BaseModuleContext, baseName string, s sdkSpec) android.Paths {
-	var ver sdkVersion
-	var kind sdkKind
-	if s.usePrebuilt(ctx) {
-		ver = s.version
-		kind = s.kind
+func PrebuiltJars(ctx android.BaseModuleContext, baseName string, s android.SdkSpec) android.Paths {
+	var ver android.ApiLevel
+	var kind android.SdkKind
+	if s.UsePrebuilt(ctx) {
+		ver = s.ApiLevel
+		kind = s.Kind
 	} else {
 		// We don't have prebuilt SDK for the specific sdkVersion.
 		// Instead of breaking the build, fallback to use "system_current"
-		ver = sdkVersionCurrent
-		kind = sdkSystem
+		ver = android.FutureApiLevel
+		kind = android.SdkSystem
 	}
 
 	dir := filepath.Join("prebuilts", "sdk", ver.String(), kind.String())
@@ -1485,7 +1485,7 @@
 		if ctx.Config().AllowMissingDependencies() {
 			return android.Paths{android.PathForSource(ctx, jar)}
 		} else {
-			ctx.PropertyErrorf("sdk_library", "invalid sdk version %q, %q does not exist", s.raw, jar)
+			ctx.PropertyErrorf("sdk_library", "invalid sdk version %q, %q does not exist", s.Raw, jar)
 		}
 		return nil
 	}
@@ -1502,13 +1502,13 @@
 	return len(otherApexInfo.InApexes) > 0 && reflect.DeepEqual(apexInfo.InApexes, otherApexInfo.InApexes)
 }
 
-func (module *SdkLibrary) sdkJars(ctx android.BaseModuleContext, sdkVersion sdkSpec, headerJars bool) android.Paths {
+func (module *SdkLibrary) sdkJars(ctx android.BaseModuleContext, sdkVersion android.SdkSpec, headerJars bool) android.Paths {
 	// If the client doesn't set sdk_version, but if this library prefers stubs over
 	// the impl library, let's provide the widest API surface possible. To do so,
 	// force override sdk_version to module_current so that the closest possible API
 	// surface could be found in selectHeaderJarsForSdkVersion
-	if module.defaultsToStubs() && !sdkVersion.specified() {
-		sdkVersion = sdkSpecFrom("module_current")
+	if module.defaultsToStubs() && !sdkVersion.Specified() {
+		sdkVersion = android.SdkSpecFrom("module_current")
 	}
 
 	// Only provide access to the implementation library if it is actually built.
@@ -1518,7 +1518,7 @@
 		// Only allow access to the implementation library in the following condition:
 		// * No sdk_version specified on the referencing module.
 		// * The referencing module is in the same apex as this.
-		if sdkVersion.kind == sdkPrivate || withinSameApexesAs(ctx, module) {
+		if sdkVersion.Kind == android.SdkPrivate || withinSameApexesAs(ctx, module) {
 			if headerJars {
 				return module.HeaderJars()
 			} else {
@@ -1531,12 +1531,12 @@
 }
 
 // to satisfy SdkLibraryDependency interface
-func (module *SdkLibrary) SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths {
+func (module *SdkLibrary) SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion android.SdkSpec) android.Paths {
 	return module.sdkJars(ctx, sdkVersion, true /*headerJars*/)
 }
 
 // to satisfy SdkLibraryDependency interface
-func (module *SdkLibrary) SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths {
+func (module *SdkLibrary) SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion android.SdkSpec) android.Paths {
 	return module.sdkJars(ctx, sdkVersion, false /*headerJars*/)
 }
 
@@ -1568,7 +1568,7 @@
 
 	// If this builds against standard libraries (i.e. is not part of the core libraries)
 	// then assume it provides both system and test apis.
-	sdkDep := decodeSdkDep(mctx, sdkContext(&module.Library))
+	sdkDep := decodeSdkDep(mctx, android.SdkContext(&module.Library))
 	hasSystemAndTestApis := sdkDep.hasStandardLibs()
 	module.sdkLibraryProperties.Generate_system_and_test_apis = hasSystemAndTestApis
 
@@ -1945,11 +1945,11 @@
 		}
 
 		// Add dependencies to the prebuilt stubs library
-		ctx.AddVariationDependencies(nil, apiScope.stubsTag, "prebuilt_"+module.stubsLibraryModuleName(apiScope))
+		ctx.AddVariationDependencies(nil, apiScope.stubsTag, android.PrebuiltNameFromSource(module.stubsLibraryModuleName(apiScope)))
 
 		if len(scopeProperties.Stub_srcs) > 0 {
 			// Add dependencies to the prebuilt stubs source library
-			ctx.AddVariationDependencies(nil, apiScope.stubsSourceTag, "prebuilt_"+module.stubsSourceModuleName(apiScope))
+			ctx.AddVariationDependencies(nil, apiScope.stubsSourceTag, android.PrebuiltNameFromSource(module.stubsSourceModuleName(apiScope)))
 		}
 	}
 }
@@ -2069,7 +2069,7 @@
 	}
 }
 
-func (module *SdkLibraryImport) sdkJars(ctx android.BaseModuleContext, sdkVersion sdkSpec, headerJars bool) android.Paths {
+func (module *SdkLibraryImport) sdkJars(ctx android.BaseModuleContext, sdkVersion android.SdkSpec, headerJars bool) android.Paths {
 
 	// For consistency with SdkLibrary make the implementation jar available to libraries that
 	// are within the same APEX.
@@ -2086,13 +2086,13 @@
 }
 
 // to satisfy SdkLibraryDependency interface
-func (module *SdkLibraryImport) SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths {
+func (module *SdkLibraryImport) SdkHeaderJars(ctx android.BaseModuleContext, sdkVersion android.SdkSpec) android.Paths {
 	// This module is just a wrapper for the prebuilt stubs.
 	return module.sdkJars(ctx, sdkVersion, true)
 }
 
 // to satisfy SdkLibraryDependency interface
-func (module *SdkLibraryImport) SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion sdkSpec) android.Paths {
+func (module *SdkLibraryImport) SdkImplementationJars(ctx android.BaseModuleContext, sdkVersion android.SdkSpec) android.Paths {
 	// This module is just a wrapper for the stubs.
 	return module.sdkJars(ctx, sdkVersion, false)
 }
diff --git a/java/sdk_test.go b/java/sdk_test.go
index 028c4fe..2b18465 100644
--- a/java/sdk_test.go
+++ b/java/sdk_test.go
@@ -313,10 +313,10 @@
 
 			checkClasspath := func(t *testing.T, result *android.TestResult, isJava8 bool) {
 				foo := result.ModuleForTests("foo", variant)
-				javac := foo.Rule("javac").RelativeToTop()
+				javac := foo.Rule("javac")
 				var deps []string
 
-				aidl := foo.MaybeRule("aidl").RelativeToTop()
+				aidl := foo.MaybeRule("aidl")
 				if aidl.Rule != nil {
 					deps = append(deps, android.PathRelativeToTop(aidl.Output))
 				}
@@ -376,7 +376,7 @@
 				checkClasspath(t, result, true /* isJava8 */)
 
 				if testcase.host != android.Host {
-					aidl := result.ModuleForTests("foo", variant).Rule("aidl").RelativeToTop()
+					aidl := result.ModuleForTests("foo", variant).Rule("aidl")
 
 					android.AssertStringDoesContain(t, "aidl command", aidl.RuleParams.Command, testcase.aidl+" -I.")
 				}
@@ -389,7 +389,7 @@
 				checkClasspath(t, result, false /* isJava8 */)
 
 				if testcase.host != android.Host {
-					aidl := result.ModuleForTests("foo", variant).Rule("aidl").RelativeToTop()
+					aidl := result.ModuleForTests("foo", variant).Rule("aidl")
 
 					android.AssertStringDoesContain(t, "aidl command", aidl.RuleParams.Command, testcase.aidl+" -I.")
 				}
@@ -402,14 +402,16 @@
 
 			// Test again with PLATFORM_VERSION_CODENAME=REL, javac -source 8 -target 8
 			t.Run("REL + Java language level 8", func(t *testing.T) {
-				result := fixtureFactory.Extend(prepareWithPlatformVersionRel).RunTestWithBp(t, bpJava8)
+				result := android.GroupFixturePreparers(
+					fixtureFactory, prepareWithPlatformVersionRel).RunTestWithBp(t, bpJava8)
 
 				checkClasspath(t, result, true /* isJava8 */)
 			})
 
 			// Test again with PLATFORM_VERSION_CODENAME=REL, javac -source 9 -target 9
 			t.Run("REL + Java language level 9", func(t *testing.T) {
-				result := fixtureFactory.Extend(prepareWithPlatformVersionRel).RunTestWithBp(t, bp)
+				result := android.GroupFixturePreparers(
+					fixtureFactory, prepareWithPlatformVersionRel).RunTestWithBp(t, bp)
 
 				checkClasspath(t, result, false /* isJava8 */)
 			})
diff --git a/java/system_modules.go b/java/system_modules.go
index 8c69051..320a2bb 100644
--- a/java/system_modules.go
+++ b/java/system_modules.go
@@ -236,7 +236,7 @@
 // modules.
 func (system *systemModulesImport) ComponentDepsMutator(ctx android.BottomUpMutatorContext) {
 	for _, lib := range system.properties.Libs {
-		ctx.AddVariationDependencies(nil, systemModulesLibsTag, "prebuilt_"+lib)
+		ctx.AddVariationDependencies(nil, systemModulesLibsTag, android.PrebuiltNameFromSource(lib))
 	}
 }
 
diff --git a/java/system_modules_test.go b/java/system_modules_test.go
index 7d8935a..7b5a386 100644
--- a/java/system_modules_test.go
+++ b/java/system_modules_test.go
@@ -20,12 +20,12 @@
 	"android/soong/android"
 )
 
-func getModuleHeaderJarsAsNormalizedPaths(result *android.TestResult, moduleNames ...string) []string {
+func getModuleHeaderJarsAsRelativeToTopPaths(result *android.TestResult, moduleNames ...string) []string {
 	paths := []string{}
 	for _, moduleName := range moduleNames {
 		module := result.Module(moduleName, "android_common")
 		info := result.ModuleProvider(module, JavaInfoProvider).(JavaInfo)
-		paths = append(paths, result.NormalizePathsForTesting(info.HeaderJars)...)
+		paths = append(paths, info.HeaderJars.RelativeToTop().Strings()...)
 	}
 	return paths
 }
@@ -50,15 +50,15 @@
 `)
 
 func TestJavaSystemModules(t *testing.T) {
-	result := prepareForJavaTest.RunTest(t, addSourceSystemModules)
+	result := android.GroupFixturePreparers(prepareForJavaTest, addSourceSystemModules).RunTest(t)
 
 	// check the existence of the source module
 	sourceSystemModules := result.ModuleForTests("system-modules", "android_common")
 	sourceInputs := sourceSystemModules.Rule("jarsTosystemModules").Inputs
 
 	// The expected paths are the header jars from the source input modules.
-	expectedSourcePaths := getModuleHeaderJarsAsNormalizedPaths(result, "system-module1", "system-module2")
-	android.AssertArrayString(t, "source system modules inputs", expectedSourcePaths, result.NormalizePathsForTesting(sourceInputs))
+	expectedSourcePaths := getModuleHeaderJarsAsRelativeToTopPaths(result, "system-module1", "system-module2")
+	android.AssertArrayString(t, "source system modules inputs", expectedSourcePaths, sourceInputs.RelativeToTop().Strings())
 }
 
 var addPrebuiltSystemModules = android.FixtureAddTextFile("prebuilts/Android.bp", `
@@ -77,36 +77,37 @@
 `)
 
 func TestJavaSystemModulesImport(t *testing.T) {
-	result := prepareForJavaTest.RunTest(t, addPrebuiltSystemModules)
+	result := android.GroupFixturePreparers(prepareForJavaTest, addPrebuiltSystemModules).RunTest(t)
 
 	// check the existence of the renamed prebuilt module
 	prebuiltSystemModules := result.ModuleForTests("system-modules", "android_common")
 	prebuiltInputs := prebuiltSystemModules.Rule("jarsTosystemModules").Inputs
 
 	// The expected paths are the header jars from the renamed prebuilt input modules.
-	expectedPrebuiltPaths := getModuleHeaderJarsAsNormalizedPaths(result, "system-module1", "system-module2")
-	android.AssertArrayString(t, "renamed prebuilt system modules inputs", expectedPrebuiltPaths, result.NormalizePathsForTesting(prebuiltInputs))
+	expectedPrebuiltPaths := getModuleHeaderJarsAsRelativeToTopPaths(result, "system-module1", "system-module2")
+	android.AssertArrayString(t, "renamed prebuilt system modules inputs", expectedPrebuiltPaths, prebuiltInputs.RelativeToTop().Strings())
 }
 
 func TestJavaSystemModulesMixSourceAndPrebuilt(t *testing.T) {
-	result := prepareForJavaTest.RunTest(t,
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
 		addSourceSystemModules,
 		addPrebuiltSystemModules,
-	)
+	).RunTest(t)
 
 	// check the existence of the source module
 	sourceSystemModules := result.ModuleForTests("system-modules", "android_common")
 	sourceInputs := sourceSystemModules.Rule("jarsTosystemModules").Inputs
 
 	// The expected paths are the header jars from the source input modules.
-	expectedSourcePaths := getModuleHeaderJarsAsNormalizedPaths(result, "system-module1", "system-module2")
-	android.AssertArrayString(t, "source system modules inputs", expectedSourcePaths, result.NormalizePathsForTesting(sourceInputs))
+	expectedSourcePaths := getModuleHeaderJarsAsRelativeToTopPaths(result, "system-module1", "system-module2")
+	android.AssertArrayString(t, "source system modules inputs", expectedSourcePaths, sourceInputs.RelativeToTop().Strings())
 
 	// check the existence of the renamed prebuilt module
 	prebuiltSystemModules := result.ModuleForTests("prebuilt_system-modules", "android_common")
 	prebuiltInputs := prebuiltSystemModules.Rule("jarsTosystemModules").Inputs
 
 	// The expected paths are the header jars from the renamed prebuilt input modules.
-	expectedPrebuiltPaths := getModuleHeaderJarsAsNormalizedPaths(result, "prebuilt_system-module1", "prebuilt_system-module2")
-	android.AssertArrayString(t, "prebuilt system modules inputs", expectedPrebuiltPaths, result.NormalizePathsForTesting(prebuiltInputs))
+	expectedPrebuiltPaths := getModuleHeaderJarsAsRelativeToTopPaths(result, "prebuilt_system-module1", "prebuilt_system-module2")
+	android.AssertArrayString(t, "prebuilt system modules inputs", expectedPrebuiltPaths, prebuiltInputs.RelativeToTop().Strings())
 }
diff --git a/java/testing.go b/java/testing.go
index 221ceb1..80c107d 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -160,28 +160,6 @@
 	)
 }
 
-func TestConfig(buildDir string, env map[string]string, bp string, fs map[string][]byte) android.Config {
-	bp += GatherRequiredDepsForTest()
-
-	mockFS := android.MockFS{}
-
-	cc.GatherRequiredFilesForTest(mockFS)
-
-	for k, v := range fs {
-		mockFS[k] = v
-	}
-
-	if env == nil {
-		env = make(map[string]string)
-	}
-	if env["ANDROID_JAVA8_HOME"] == "" {
-		env["ANDROID_JAVA8_HOME"] = "jdk8"
-	}
-	config := android.TestArchConfig(buildDir, env, bp, mockFS)
-
-	return config
-}
-
 func prebuiltApisFilesForLibs(apiLevels []string, sdkLibs []string) map[string][]byte {
 	fs := make(map[string][]byte)
 	for _, level := range apiLevels {
@@ -200,19 +178,6 @@
 	return fs
 }
 
-// Register build components provided by this package that are needed by tests.
-//
-// In particular this must register all the components that are used in the `Android.bp` snippet
-// returned by GatherRequiredDepsForTest()
-//
-// deprecated: Use test fixtures instead, e.g. PrepareForTestWithJavaBuildComponents
-func RegisterRequiredBuildComponentsForTest(ctx android.RegistrationContext) {
-	registerRequiredBuildComponentsForTest(ctx)
-
-	// Make sure that any tool related module types needed by dexpreopt have been registered.
-	dexpreopt.RegisterToolModulesForTest(ctx)
-}
-
 // registerRequiredBuildComponentsForTest registers the build components used by
 // PrepareForTestWithJavaDefaultModules.
 //
@@ -228,7 +193,8 @@
 	RegisterDexpreoptBootJarsComponents(ctx)
 	RegisterDocsBuildComponents(ctx)
 	RegisterGenRuleBuildComponents(ctx)
-	RegisterJavaBuildComponents(ctx)
+	registerJavaBuildComponents(ctx)
+	registerPlatformBootclasspathBuildComponents(ctx)
 	RegisterPrebuiltApisBuildComponents(ctx)
 	RegisterRuntimeResourceOverlayBuildComponents(ctx)
 	RegisterSdkLibraryBuildComponents(ctx)
@@ -236,23 +202,6 @@
 	RegisterSystemModulesBuildComponents(ctx)
 }
 
-// Gather the module definitions needed by tests that depend upon code from this package.
-//
-// Returns an `Android.bp` snippet that defines the modules that are needed by this package.
-//
-// deprecated: Use test fixtures instead, e.g. PrepareForTestWithJavaDefaultModules
-func GatherRequiredDepsForTest() string {
-	bp := gatherRequiredDepsForTest()
-
-	// For class loader context and <uses-library> tests.
-	bp += dexpreopt.CompatLibDefinitionsForTest()
-
-	// Make sure that any tools needed for dexpreopting are defined.
-	bp += dexpreopt.BpToolModulesForTest()
-
-	return bp
-}
-
 // gatherRequiredDepsForTest gathers the module definitions used by
 // PrepareForTestWithJavaDefaultModules.
 //
diff --git a/kernel/prebuilt_kernel_modules.go b/kernel/prebuilt_kernel_modules.go
index 14ac021..5bcca04 100644
--- a/kernel/prebuilt_kernel_modules.go
+++ b/kernel/prebuilt_kernel_modules.go
@@ -27,8 +27,12 @@
 )
 
 func init() {
-	android.RegisterModuleType("prebuilt_kernel_modules", prebuiltKernelModulesFactory)
 	pctx.Import("android/soong/cc/config")
+	registerKernelBuildComponents(android.InitRegistrationContext)
+}
+
+func registerKernelBuildComponents(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("prebuilt_kernel_modules", prebuiltKernelModulesFactory)
 }
 
 type prebuiltKernelModules struct {
diff --git a/kernel/prebuilt_kernel_modules_test.go b/kernel/prebuilt_kernel_modules_test.go
index 433548b..90b9886 100644
--- a/kernel/prebuilt_kernel_modules_test.go
+++ b/kernel/prebuilt_kernel_modules_test.go
@@ -15,73 +15,29 @@
 package kernel
 
 import (
-	"io/ioutil"
 	"os"
-	"reflect"
-	"strings"
 	"testing"
 
 	"android/soong/android"
 	"android/soong/cc"
 )
 
-func testKernelModules(t *testing.T, bp string, fs map[string][]byte) (*android.TestContext, android.Config) {
-	bp = bp + `
-		cc_binary_host {
-			name: "depmod",
-			srcs: ["depmod.cpp"],
-			stl: "none",
-			static_executable: true,
-			system_shared_libs: [],
-		}
-	`
-	bp = bp + cc.GatherRequiredDepsForTest(android.Android)
-
-	fs["depmod.cpp"] = nil
-	cc.GatherRequiredFilesForTest(fs)
-
-	config := android.TestArchConfig(buildDir, nil, bp, fs)
-
-	ctx := android.NewTestArchContext(config)
-	ctx.RegisterModuleType("prebuilt_kernel_modules", prebuiltKernelModulesFactory)
-	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
-	cc.RegisterRequiredBuildComponentsForTest(ctx)
-
-	ctx.Register()
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	android.FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	android.FailIfErrored(t, errs)
-
-	return ctx, config
-}
-
-func ensureListContains(t *testing.T, result []string, expected string) {
-	t.Helper()
-	if !android.InList(expected, result) {
-		t.Errorf("%q is not found in %v", expected, result)
-	}
-}
-
-func ensureContains(t *testing.T, result string, expected string) {
-	t.Helper()
-	if !strings.Contains(result, expected) {
-		t.Errorf("%q is not found in %q", expected, result)
-	}
-}
-
 func TestKernelModulesFilelist(t *testing.T) {
-	ctx, _ := testKernelModules(t, `
+	ctx := android.GroupFixturePreparers(
+		cc.PrepareForTestWithCcDefaultModules,
+		android.FixtureRegisterWithContext(registerKernelBuildComponents),
+		android.MockFS{
+			"depmod.cpp": nil,
+			"mod1.ko":    nil,
+			"mod2.ko":    nil,
+		}.AddToFixture(),
+	).RunTestWithBp(t, `
 		prebuilt_kernel_modules {
 			name: "foo",
 			srcs: ["*.ko"],
 			kernel_version: "5.10",
 		}
-	`,
-		map[string][]byte{
-			"mod1.ko": nil,
-			"mod2.ko": nil,
-		})
+	`)
 
 	expected := []string{
 		"lib/modules/5.10/mod1.ko",
@@ -98,32 +54,9 @@
 	}
 	actual = android.SortedUniqueStrings(actual)
 	expected = android.SortedUniqueStrings(expected)
-	if !reflect.DeepEqual(actual, expected) {
-		t.Errorf("\ngot: %v\nexpected: %v\n", actual, expected)
-	}
-}
-
-var buildDir string
-
-func setUp() {
-	var err error
-	buildDir, err = ioutil.TempDir("", "soong_kernel_test")
-	if err != nil {
-		panic(err)
-	}
-}
-
-func tearDown() {
-	os.RemoveAll(buildDir)
+	android.AssertDeepEquals(t, "foo packaging specs", expected, actual)
 }
 
 func TestMain(m *testing.M) {
-	run := func() int {
-		setUp()
-		defer tearDown()
-
-		return m.Run()
-	}
-
-	os.Exit(run())
+	os.Exit(m.Run())
 }
diff --git a/linkerconfig/linkerconfig.go b/linkerconfig/linkerconfig.go
index ff548e5..da80a47 100644
--- a/linkerconfig/linkerconfig.go
+++ b/linkerconfig/linkerconfig.go
@@ -27,7 +27,11 @@
 
 func init() {
 	pctx.HostBinToolVariable("conv_linker_config", "conv_linker_config")
-	android.RegisterModuleType("linker_config", linkerConfigFactory)
+	registerLinkerConfigBuildComponent(android.InitRegistrationContext)
+}
+
+func registerLinkerConfigBuildComponent(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("linker_config", linkerConfigFactory)
 }
 
 type linkerConfigProperties struct {
diff --git a/linkerconfig/linkerconfig_test.go b/linkerconfig/linkerconfig_test.go
index 8eed4b5..939e4bb 100644
--- a/linkerconfig/linkerconfig_test.go
+++ b/linkerconfig/linkerconfig_test.go
@@ -15,65 +15,29 @@
 package linkerconfig
 
 import (
-	"android/soong/android"
-	"io/ioutil"
 	"os"
 	"reflect"
 	"testing"
+
+	"android/soong/android"
 )
 
-var buildDir string
-
-func setUp() {
-	var err error
-	buildDir, err = ioutil.TempDir("", "soong_etc_test")
-	if err != nil {
-		panic(err)
-	}
-}
-
-func tearDown() {
-	os.RemoveAll(buildDir)
-}
-
 func TestMain(m *testing.M) {
-	run := func() int {
-		setUp()
-		defer tearDown()
-
-		return m.Run()
-	}
-
-	os.Exit(run())
+	os.Exit(m.Run())
 }
 
-func testContext(t *testing.T, bp string) *android.TestContext {
-	t.Helper()
-
-	fs := map[string][]byte{
-		"linker.config.json": nil,
-	}
-
-	config := android.TestArchConfig(buildDir, nil, bp, fs)
-
-	ctx := android.NewTestArchContext(config)
-	ctx.RegisterModuleType("linker_config", linkerConfigFactory)
-	ctx.Register()
-
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	android.FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	android.FailIfErrored(t, errs)
-
-	return ctx
-}
+var prepareForLinkerConfigTest = android.GroupFixturePreparers(
+	android.PrepareForTestWithAndroidBuildComponents,
+	android.FixtureRegisterWithContext(registerLinkerConfigBuildComponent),
+	android.FixtureAddFile("linker.config.json", nil),
+)
 
 func TestBaseLinkerConfig(t *testing.T) {
-	ctx := testContext(t, `
-	linker_config {
-		name: "linker-config-base",
-		src: "linker.config.json",
-	}
+	result := prepareForLinkerConfigTest.RunTestWithBp(t, `
+		linker_config {
+			name: "linker-config-base",
+			src: "linker.config.json",
+		}
 	`)
 
 	expected := map[string][]string{
@@ -82,13 +46,13 @@
 		"LOCAL_INSTALLED_MODULE_STEM": {"linker.config.pb"},
 	}
 
-	p := ctx.ModuleForTests("linker-config-base", "android_arm64_armv8-a").Module().(*linkerConfig)
+	p := result.ModuleForTests("linker-config-base", "android_arm64_armv8-a").Module().(*linkerConfig)
 
 	if p.outputFilePath.Base() != "linker.config.pb" {
 		t.Errorf("expected linker.config.pb, got %q", p.outputFilePath.Base())
 	}
 
-	entries := android.AndroidMkEntriesForTest(t, ctx, p)[0]
+	entries := android.AndroidMkEntriesForTest(t, result.TestContext, p)[0]
 	for k, expectedValue := range expected {
 		if value, ok := entries.EntryMap[k]; ok {
 			if !reflect.DeepEqual(value, expectedValue) {
@@ -105,18 +69,18 @@
 }
 
 func TestUninstallableLinkerConfig(t *testing.T) {
-	ctx := testContext(t, `
-	linker_config {
-		name: "linker-config-base",
-		src: "linker.config.json",
-		installable: false,
-	}
+	result := prepareForLinkerConfigTest.RunTestWithBp(t, `
+		linker_config {
+			name: "linker-config-base",
+			src: "linker.config.json",
+			installable: false,
+		}
 	`)
 
 	expected := []string{"true"}
 
-	p := ctx.ModuleForTests("linker-config-base", "android_arm64_armv8-a").Module().(*linkerConfig)
-	entries := android.AndroidMkEntriesForTest(t, ctx, p)[0]
+	p := result.ModuleForTests("linker-config-base", "android_arm64_armv8-a").Module().(*linkerConfig)
+	entries := android.AndroidMkEntriesForTest(t, result.TestContext, p)[0]
 	if value, ok := entries.EntryMap["LOCAL_UNINSTALLABLE_MODULE"]; ok {
 		if !reflect.DeepEqual(value, expected) {
 			t.Errorf("LOCAL_UNINSTALLABLE_MODULE is expected to be true but %s", value)
diff --git a/python/binary.go b/python/binary.go
index 5b0f080..e955492 100644
--- a/python/binary.go
+++ b/python/binary.go
@@ -36,8 +36,8 @@
 
 type bazelPythonBinaryAttributes struct {
 	Main           string
-	Srcs           bazel.LabelList
-	Data           bazel.LabelList
+	Srcs           bazel.LabelListAttribute
+	Data           bazel.LabelListAttribute
 	Python_version string
 }
 
@@ -97,10 +97,13 @@
 		// 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)
+
 	attrs := &bazelPythonBinaryAttributes{
 		Main:           main,
-		Srcs:           android.BazelLabelForModuleSrcExcludes(ctx, m.properties.Srcs, m.properties.Exclude_srcs),
-		Data:           android.BazelLabelForModuleSrc(ctx, m.properties.Data),
+		Srcs:           bazel.MakeLabelListAttribute(srcs),
+		Data:           bazel.MakeLabelListAttribute(data),
 		Python_version: python_version,
 	}
 
diff --git a/remoteexec/remoteexec.go b/remoteexec/remoteexec.go
index 166f68c..ef4672a 100644
--- a/remoteexec/remoteexec.go
+++ b/remoteexec/remoteexec.go
@@ -64,9 +64,8 @@
 	ExecStrategy string
 	// Inputs is a list of input paths or ninja variables.
 	Inputs []string
-	// RSPFile is the name of the ninja variable used by the rule as a placeholder for an rsp
-	// input.
-	RSPFile string
+	// RSPFiles is the name of the files used by the rule as a placeholder for an rsp input.
+	RSPFiles []string
 	// OutputFiles is a list of output file paths or ninja variables as placeholders for rule
 	// outputs.
 	OutputFiles []string
@@ -134,8 +133,8 @@
 		args += " --inputs=" + strings.Join(r.Inputs, ",")
 	}
 
-	if r.RSPFile != "" {
-		args += " --input_list_paths=" + r.RSPFile
+	if len(r.RSPFiles) > 0 {
+		args += " --input_list_paths=" + strings.Join(r.RSPFiles, ",")
 	}
 
 	if len(r.OutputFiles) > 0 {
diff --git a/remoteexec/remoteexec_test.go b/remoteexec/remoteexec_test.go
index 875aa6a..b117b89 100644
--- a/remoteexec/remoteexec_test.go
+++ b/remoteexec/remoteexec_test.go
@@ -45,14 +45,14 @@
 				Inputs:          []string{"$in"},
 				OutputFiles:     []string{"$out"},
 				ExecStrategy:    "remote",
-				RSPFile:         "$out.rsp",
+				RSPFiles:        []string{"$out.rsp", "out2.rsp"},
 				ToolchainInputs: []string{"clang++"},
 				Platform: map[string]string{
 					ContainerImageKey: DefaultImage,
 					PoolKey:           "default",
 				},
 			},
-			want: fmt.Sprintf("${android.RBEWrapper} --labels=compiler=clang,lang=cpp,type=compile --platform=\"Pool=default,container-image=%s\" --exec_strategy=remote --inputs=$in --input_list_paths=$out.rsp --output_files=$out --toolchain_inputs=clang++ -- ", DefaultImage),
+			want: fmt.Sprintf("${android.RBEWrapper} --labels=compiler=clang,lang=cpp,type=compile --platform=\"Pool=default,container-image=%s\" --exec_strategy=remote --inputs=$in --input_list_paths=$out.rsp,out2.rsp --output_files=$out --toolchain_inputs=clang++ -- ", DefaultImage),
 		},
 	}
 	for _, test := range tests {
diff --git a/response/Android.bp b/response/Android.bp
new file mode 100644
index 0000000..e19981f
--- /dev/null
+++ b/response/Android.bp
@@ -0,0 +1,16 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+bootstrap_go_package {
+    name: "soong-response",
+    pkgPath: "android/soong/response",
+    deps: [
+    ],
+    srcs: [
+        "response.go",
+    ],
+    testSrcs: [
+        "response_test.go",
+    ],
+}
diff --git a/response/response.go b/response/response.go
new file mode 100644
index 0000000..b65503e
--- /dev/null
+++ b/response/response.go
@@ -0,0 +1,112 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package response
+
+import (
+	"io"
+	"io/ioutil"
+	"strings"
+	"unicode"
+)
+
+const noQuote = '\x00'
+
+// ReadRspFile reads a file in Ninja's response file format and returns its contents.
+func ReadRspFile(r io.Reader) ([]string, error) {
+	var files []string
+	var file []byte
+
+	buf, err := ioutil.ReadAll(r)
+	if err != nil {
+		return nil, err
+	}
+
+	isEscaping := false
+	quotingStart := byte(noQuote)
+	for _, c := range buf {
+		switch {
+		case isEscaping:
+			if quotingStart == '"' {
+				if !(c == '"' || c == '\\') {
+					// '\"' or '\\' will be escaped under double quoting.
+					file = append(file, '\\')
+				}
+			}
+			file = append(file, c)
+			isEscaping = false
+		case c == '\\' && quotingStart != '\'':
+			isEscaping = true
+		case quotingStart == noQuote && (c == '\'' || c == '"'):
+			quotingStart = c
+		case quotingStart != noQuote && c == quotingStart:
+			quotingStart = noQuote
+		case quotingStart == noQuote && unicode.IsSpace(rune(c)):
+			// Current character is a space outside quotes
+			if len(file) != 0 {
+				files = append(files, string(file))
+			}
+			file = file[:0]
+		default:
+			file = append(file, c)
+		}
+	}
+
+	if len(file) != 0 {
+		files = append(files, string(file))
+	}
+
+	return files, nil
+}
+
+func rspUnsafeChar(r rune) bool {
+	switch {
+	case 'A' <= r && r <= 'Z',
+		'a' <= r && r <= 'z',
+		'0' <= r && r <= '9',
+		r == '_',
+		r == '+',
+		r == '-',
+		r == '.',
+		r == '/':
+		return false
+	default:
+		return true
+	}
+}
+
+var rspEscaper = strings.NewReplacer(`'`, `'\''`)
+
+// WriteRspFile writes a list of files to a file in Ninja's response file format.
+func WriteRspFile(w io.Writer, files []string) error {
+	for i, f := range files {
+		if i != 0 {
+			_, err := io.WriteString(w, " ")
+			if err != nil {
+				return err
+			}
+		}
+
+		if strings.IndexFunc(f, rspUnsafeChar) != -1 {
+			f = `'` + rspEscaper.Replace(f) + `'`
+		}
+
+		_, err := io.WriteString(w, f)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
diff --git a/response/response_test.go b/response/response_test.go
new file mode 100644
index 0000000..4d1fb41
--- /dev/null
+++ b/response/response_test.go
@@ -0,0 +1,123 @@
+// Copyright 2021 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package response
+
+import (
+	"bytes"
+	"reflect"
+	"testing"
+)
+
+func TestReadRspFile(t *testing.T) {
+	testCases := []struct {
+		name, in string
+		out      []string
+	}{
+		{
+			name: "single quoting test case 1",
+			in:   `./cmd '"'-C`,
+			out:  []string{"./cmd", `"-C`},
+		},
+		{
+			name: "single quoting test case 2",
+			in:   `./cmd '-C`,
+			out:  []string{"./cmd", `-C`},
+		},
+		{
+			name: "single quoting test case 3",
+			in:   `./cmd '\"'-C`,
+			out:  []string{"./cmd", `\"-C`},
+		},
+		{
+			name: "single quoting test case 4",
+			in:   `./cmd '\\'-C`,
+			out:  []string{"./cmd", `\\-C`},
+		},
+		{
+			name: "none quoting test case 1",
+			in:   `./cmd \'-C`,
+			out:  []string{"./cmd", `'-C`},
+		},
+		{
+			name: "none quoting test case 2",
+			in:   `./cmd \\-C`,
+			out:  []string{"./cmd", `\-C`},
+		},
+		{
+			name: "none quoting test case 3",
+			in:   `./cmd \"-C`,
+			out:  []string{"./cmd", `"-C`},
+		},
+		{
+			name: "double quoting test case 1",
+			in:   `./cmd "'"-C`,
+			out:  []string{"./cmd", `'-C`},
+		},
+		{
+			name: "double quoting test case 2",
+			in:   `./cmd "\\"-C`,
+			out:  []string{"./cmd", `\-C`},
+		},
+		{
+			name: "double quoting test case 3",
+			in:   `./cmd "\""-C`,
+			out:  []string{"./cmd", `"-C`},
+		},
+		{
+			name: "ninja rsp file",
+			in:   "'a'\nb\n'@'\n'foo'\\''bar'\n'foo\"bar'",
+			out:  []string{"a", "b", "@", "foo'bar", `foo"bar`},
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			got, err := ReadRspFile(bytes.NewBuffer([]byte(testCase.in)))
+			if err != nil {
+				t.Errorf("unexpected error: %q", err)
+			}
+			if !reflect.DeepEqual(got, testCase.out) {
+				t.Errorf("expected %q got %q", testCase.out, got)
+			}
+		})
+	}
+}
+
+func TestWriteRspFile(t *testing.T) {
+	testCases := []struct {
+		name string
+		in   []string
+		out  string
+	}{
+		{
+			name: "ninja rsp file",
+			in:   []string{"a", "b", "@", "foo'bar", `foo"bar`},
+			out:  "a b '@' 'foo'\\''bar' 'foo\"bar'",
+		},
+	}
+
+	for _, testCase := range testCases {
+		t.Run(testCase.name, func(t *testing.T) {
+			buf := &bytes.Buffer{}
+			err := WriteRspFile(buf, testCase.in)
+			if err != nil {
+				t.Errorf("unexpected error: %q", err)
+			}
+			if buf.String() != testCase.out {
+				t.Errorf("expected %q got %q", testCase.out, buf.String())
+			}
+		})
+	}
+}
diff --git a/rust/androidmk.go b/rust/androidmk.go
index 0f9a17d..b0e6967 100644
--- a/rust/androidmk.go
+++ b/rust/androidmk.go
@@ -50,7 +50,7 @@
 	}
 
 	ret := android.AndroidMkEntries{
-		OutputFile: mod.outputFile,
+		OutputFile: mod.unstrippedOutputFile,
 		Include:    "$(BUILD_SYSTEM)/soong_rust_prebuilt.mk",
 		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
 			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
diff --git a/rust/clippy_test.go b/rust/clippy_test.go
index e90564f..bd3bfb1 100644
--- a/rust/clippy_test.go
+++ b/rust/clippy_test.go
@@ -18,7 +18,6 @@
 	"testing"
 
 	"android/soong/android"
-	"android/soong/cc"
 )
 
 func TestClippy(t *testing.T) {
@@ -45,15 +44,6 @@
 			clippy_lints: "none",
 		}`
 
-	bp = bp + GatherRequiredDepsForTest()
-	bp = bp + cc.GatherRequiredDepsForTest(android.NoOsType)
-
-	fs := map[string][]byte{
-		// Reuse the same blueprint file for subdirectories.
-		"external/Android.bp": []byte(bp),
-		"hardware/Android.bp": []byte(bp),
-	}
-
 	var clippyLintTests = []struct {
 		modulePath string
 		fooFlags   string
@@ -66,29 +56,22 @@
 	for _, tc := range clippyLintTests {
 		t.Run("path="+tc.modulePath, func(t *testing.T) {
 
-			config := android.TestArchConfig(t.TempDir(), nil, bp, fs)
-			ctx := CreateTestContext(config)
-			ctx.Register()
-			_, errs := ctx.ParseFileList(".", []string{tc.modulePath + "Android.bp"})
-			android.FailIfErrored(t, errs)
-			_, errs = ctx.PrepareBuildActions(config)
-			android.FailIfErrored(t, errs)
+			result := android.GroupFixturePreparers(
+				prepareForRustTest,
+				// Test with the blueprint file in different directories.
+				android.FixtureAddTextFile(tc.modulePath+"Android.bp", bp),
+			).RunTest(t)
 
-			r := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_dylib").MaybeRule("clippy")
-			if r.Args["clippyFlags"] != tc.fooFlags {
-				t.Errorf("Incorrect flags for libfoo: %q, want %q", r.Args["clippyFlags"], tc.fooFlags)
-			}
+			r := result.ModuleForTests("libfoo", "android_arm64_armv8-a_dylib").MaybeRule("clippy")
+			android.AssertStringEquals(t, "libfoo flags", tc.fooFlags, r.Args["clippyFlags"])
 
-			r = ctx.ModuleForTests("libbar", "android_arm64_armv8-a_dylib").MaybeRule("clippy")
-			if r.Args["clippyFlags"] != "${config.ClippyDefaultLints}" {
-				t.Errorf("Incorrect flags for libbar: %q, want %q", r.Args["clippyFlags"], "${config.ClippyDefaultLints}")
-			}
+			r = result.ModuleForTests("libbar", "android_arm64_armv8-a_dylib").MaybeRule("clippy")
+			android.AssertStringEquals(t, "libbar flags", "${config.ClippyDefaultLints}", r.Args["clippyFlags"])
 
-			r = ctx.ModuleForTests("libfoobar", "android_arm64_armv8-a_dylib").MaybeRule("clippy")
+			r = result.ModuleForTests("libfoobar", "android_arm64_armv8-a_dylib").MaybeRule("clippy")
 			if r.Rule != nil {
 				t.Errorf("libfoobar is setup to use clippy when explicitly disabled: clippyFlags=%q", r.Args["clippyFlags"])
 			}
-
 		})
 	}
 }
diff --git a/rust/compiler.go b/rust/compiler.go
index 200af90..2e85f5a 100644
--- a/rust/compiler.go
+++ b/rust/compiler.go
@@ -97,13 +97,25 @@
 	// list of C shared library dependencies
 	Shared_libs []string `android:"arch_variant"`
 
-	// list of C static library dependencies. Note, static libraries prefixed by "lib" will be passed to rustc
-	// along with "-lstatic=<name>". This will bundle the static library into rlib/static libraries so dependents do
-	// not need to also declare the static library as a dependency. Static libraries which are not prefixed by "lib"
-	// cannot be passed to rustc with this flag and will not be bundled into rlib/static libraries, and thus must
-	// be redeclared in dependents.
+	// list of C static library dependencies. These dependencies do not normally propagate to dependents
+	// and may need to be redeclared. See whole_static_libs for bundling static dependencies into a library.
 	Static_libs []string `android:"arch_variant"`
 
+	// Similar to static_libs, but will bundle the static library dependency into a library. This is helpful
+	// to avoid having to redeclare the dependency for dependents of this library, but in some cases may also
+	// result in bloat if multiple dependencies all include the same static library whole.
+	//
+	// The common use case for this is when the static library is unlikely to be a dependency of other modules to avoid
+	// having to redeclare the static library dependency for every dependent module.
+	// If you are not sure what to, for rust_library modules most static dependencies should go in static_libraries,
+	// and for rust_ffi modules most static dependencies should go into whole_static_libraries.
+	//
+	// For rust_ffi static variants, these libraries will be included in the resulting static library archive.
+	//
+	// For rust_library rlib variants, these libraries will be bundled into the resulting rlib library. This will
+	// include all of the static libraries symbols in any dylibs or binaries which use this rlib as well.
+	Whole_static_libs []string `android:"arch_variant"`
+
 	// crate name, required for modules which produce Rust libraries: rust_library, rust_ffi and SourceProvider
 	// modules which create library variants (rust_bindgen). This must be the expected extern crate name used in
 	// source, and is required to conform to an enforced format matching library output files (if the output file is
@@ -260,12 +272,17 @@
 	return false
 }
 
+func (compiler *baseCompiler) strippedOutputFilePath() android.OptionalPath {
+	return compiler.strippedOutputFile
+}
+
 func (compiler *baseCompiler) compilerDeps(ctx DepsContext, deps Deps) Deps {
 	deps.Rlibs = append(deps.Rlibs, compiler.Properties.Rlibs...)
 	deps.Dylibs = append(deps.Dylibs, compiler.Properties.Dylibs...)
 	deps.Rustlibs = append(deps.Rustlibs, compiler.Properties.Rustlibs...)
 	deps.ProcMacros = append(deps.ProcMacros, compiler.Properties.Proc_macros...)
 	deps.StaticLibs = append(deps.StaticLibs, compiler.Properties.Static_libs...)
+	deps.WholeStaticLibs = append(deps.WholeStaticLibs, compiler.Properties.Whole_static_libs...)
 	deps.SharedLibs = append(deps.SharedLibs, compiler.Properties.Shared_libs...)
 
 	if !Bool(compiler.Properties.No_stdlibs) {
@@ -324,10 +341,7 @@
 }
 
 func (compiler *baseCompiler) install(ctx ModuleContext) {
-	path := ctx.RustModule().outputFile
-	if compiler.strippedOutputFile.Valid() {
-		path = compiler.strippedOutputFile
-	}
+	path := ctx.RustModule().OutputFile()
 	compiler.path = ctx.InstallFile(compiler.installDir(ctx), path.Path().Base(), path.Path())
 }
 
diff --git a/rust/compiler_test.go b/rust/compiler_test.go
index 3ed086f..c752762 100644
--- a/rust/compiler_test.go
+++ b/rust/compiler_test.go
@@ -19,7 +19,6 @@
 	"testing"
 
 	"android/soong/android"
-	"android/soong/cc"
 )
 
 // Test that feature flags are being correctly generated.
@@ -132,15 +131,6 @@
 			lints: "none",
 		}`
 
-	bp = bp + GatherRequiredDepsForTest()
-	bp = bp + cc.GatherRequiredDepsForTest(android.NoOsType)
-
-	fs := map[string][]byte{
-		// Reuse the same blueprint file for subdirectories.
-		"external/Android.bp": []byte(bp),
-		"hardware/Android.bp": []byte(bp),
-	}
-
 	var lintTests = []struct {
 		modulePath string
 		fooFlags   string
@@ -153,29 +143,20 @@
 	for _, tc := range lintTests {
 		t.Run("path="+tc.modulePath, func(t *testing.T) {
 
-			config := android.TestArchConfig(t.TempDir(), nil, bp, fs)
-			ctx := CreateTestContext(config)
-			ctx.Register()
-			_, errs := ctx.ParseFileList(".", []string{tc.modulePath + "Android.bp"})
-			android.FailIfErrored(t, errs)
-			_, errs = ctx.PrepareBuildActions(config)
-			android.FailIfErrored(t, errs)
+			result := android.GroupFixturePreparers(
+				prepareForRustTest,
+				// Test with the blueprint file in different directories.
+				android.FixtureAddTextFile(tc.modulePath+"Android.bp", bp),
+			).RunTest(t)
 
-			r := ctx.ModuleForTests("libfoo", "android_arm64_armv8-a_dylib").MaybeRule("rustc")
-			if !strings.Contains(r.Args["rustcFlags"], tc.fooFlags) {
-				t.Errorf("Incorrect flags for libfoo: %q, want %q", r.Args["rustcFlags"], tc.fooFlags)
-			}
+			r := result.ModuleForTests("libfoo", "android_arm64_armv8-a_dylib").MaybeRule("rustc")
+			android.AssertStringDoesContain(t, "libfoo flags", r.Args["rustcFlags"], tc.fooFlags)
 
-			r = ctx.ModuleForTests("libbar", "android_arm64_armv8-a_dylib").MaybeRule("rustc")
-			if !strings.Contains(r.Args["rustcFlags"], "${config.RustDefaultLints}") {
-				t.Errorf("Incorrect flags for libbar: %q, want %q", r.Args["rustcFlags"], "${config.RustDefaultLints}")
-			}
+			r = result.ModuleForTests("libbar", "android_arm64_armv8-a_dylib").MaybeRule("rustc")
+			android.AssertStringDoesContain(t, "libbar flags", r.Args["rustcFlags"], "${config.RustDefaultLints}")
 
-			r = ctx.ModuleForTests("libfoobar", "android_arm64_armv8-a_dylib").MaybeRule("rustc")
-			if !strings.Contains(r.Args["rustcFlags"], "${config.RustAllowAllLints}") {
-				t.Errorf("Incorrect flags for libfoobar: %q, want %q", r.Args["rustcFlags"], "${config.RustAllowAllLints}")
-			}
-
+			r = result.ModuleForTests("libfoobar", "android_arm64_armv8-a_dylib").MaybeRule("rustc")
+			android.AssertStringDoesContain(t, "libfoobar flags", r.Args["rustcFlags"], "${config.RustAllowAllLints}")
 		})
 	}
 }
diff --git a/rust/config/allowed_list.go b/rust/config/allowed_list.go
index b9732aa..394fcc5 100644
--- a/rust/config/allowed_list.go
+++ b/rust/config/allowed_list.go
@@ -19,6 +19,7 @@
 		"packages/modules/Virtualization",
 		"prebuilts/rust",
 		"system/bt",
+		"system/core/libstats/pull_rust",
 		"system/extras/profcollectd",
 		"system/extras/simpleperf",
 		"system/hardware/interfaces/keystore2",
diff --git a/rust/image_test.go b/rust/image_test.go
index e40599c..7677cce 100644
--- a/rust/image_test.go
+++ b/rust/image_test.go
@@ -38,7 +38,7 @@
 			}
 		`)
 
-	vendorBinary := ctx.ModuleForTests("fizz_vendor", "android_vendor.VER_arm64_armv8-a").Module().(*cc.Module)
+	vendorBinary := ctx.ModuleForTests("fizz_vendor", "android_vendor.29_arm64_armv8-a").Module().(*cc.Module)
 
 	if !android.InList("libfoo_vendor", vendorBinary.Properties.AndroidMkStaticLibs) {
 		t.Errorf("vendorBinary should have a dependency on libfoo_vendor")
@@ -56,7 +56,7 @@
 			}
 		`)
 
-	vendor := ctx.ModuleForTests("libfoo", "android_vendor.VER_arm64_armv8-a_static").Rule("rustc")
+	vendor := ctx.ModuleForTests("libfoo", "android_vendor.29_arm64_armv8-a_static").Rule("rustc")
 
 	if !strings.Contains(vendor.Args["rustcFlags"], "--cfg 'android_vndk'") {
 		t.Errorf("missing \"--cfg 'android_vndk'\" for libfoo vendor variant, rustcFlags: %#v", vendor.Args["rustcFlags"])
diff --git a/rust/project_json.go b/rust/project_json.go
index 8d3d250..c28bc7b 100644
--- a/rust/project_json.go
+++ b/rust/project_json.go
@@ -49,7 +49,7 @@
 	RootModule  string            `json:"root_module"`
 	Edition     string            `json:"edition,omitempty"`
 	Deps        []rustProjectDep  `json:"deps"`
-	Cfgs        []string          `json:"cfgs"`
+	Cfg         []string          `json:"cfg"`
 	Env         map[string]string `json:"env"`
 }
 
@@ -230,7 +230,7 @@
 		RootModule:  rootModule,
 		Edition:     comp.edition(),
 		Deps:        make([]rustProjectDep, 0),
-		Cfgs:        make([]string, 0),
+		Cfg:         make([]string, 0),
 		Env:         make(map[string]string),
 	}
 
@@ -238,6 +238,10 @@
 		crate.Env["OUT_DIR"] = comp.CargoOutDir().String()
 	}
 
+	for _, feature := range comp.Properties.Features {
+		crate.Cfg = append(crate.Cfg, "feature=\""+feature+"\"")
+	}
+
 	deps := make(map[string]int)
 	singleton.mergeDependencies(ctx, rModule, &crate, deps)
 
diff --git a/rust/project_json_test.go b/rust/project_json_test.go
index 8f64f56..09d30db 100644
--- a/rust/project_json_test.go
+++ b/rust/project_json_test.go
@@ -18,6 +18,7 @@
 	"encoding/json"
 	"io/ioutil"
 	"path/filepath"
+	"sort"
 	"strings"
 	"testing"
 
@@ -27,9 +28,10 @@
 // testProjectJson run the generation of rust-project.json. It returns the raw
 // content of the generated file.
 func testProjectJson(t *testing.T, bp string) []byte {
-	result := prepareForRustTest.
-		Extend(android.FixtureMergeEnv(map[string]string{"SOONG_GEN_RUST_PROJECT": "1"})).
-		RunTestWithBp(t, bp)
+	result := android.GroupFixturePreparers(
+		prepareForRustTest,
+		android.FixtureMergeEnv(map[string]string{"SOONG_GEN_RUST_PROJECT": "1"}),
+	).RunTestWithBp(t, bp)
 
 	// The JSON file is generated via WriteFileToOutputDir. Therefore, it
 	// won't appear in the Output of the TestingSingleton. Manually verify
@@ -115,6 +117,41 @@
 	validateJsonCrates(t, jsonContent)
 }
 
+func TestProjectJsonFeature(t *testing.T) {
+	bp := `
+	rust_library {
+		name: "liba",
+		srcs: ["a/src/lib.rs"],
+		crate_name: "a",
+		features: ["f1", "f2"]
+	}
+	`
+	jsonContent := testProjectJson(t, bp)
+	crates := validateJsonCrates(t, jsonContent)
+	for _, c := range crates {
+		crate := validateCrate(t, c)
+		cfgs, ok := crate["cfg"].([]interface{})
+		if !ok {
+			t.Fatalf("Unexpected type for cfgs: %v", crate)
+		}
+		expectedCfgs := []string{"feature=\"f1\"", "feature=\"f2\""}
+		foundCfgs := []string{}
+		for _, cfg := range cfgs {
+			cfg, ok := cfg.(string)
+			if !ok {
+				t.Fatalf("Unexpected type for cfg: %v", cfg)
+			}
+			foundCfgs = append(foundCfgs, cfg)
+		}
+		sort.Strings(foundCfgs)
+		for i, foundCfg := range foundCfgs {
+			if foundCfg != expectedCfgs[i] {
+				t.Errorf("Incorrect features: got %v; want %v", foundCfg, expectedCfgs[i])
+			}
+		}
+	}
+}
+
 func TestProjectJsonBinary(t *testing.T) {
 	bp := `
 	rust_binary {
diff --git a/rust/rust.go b/rust/rust.go
index 8ebdb72..566ad37 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -114,7 +114,10 @@
 	sourceProvider   SourceProvider
 	subAndroidMkOnce map[SubAndroidMkProvider]bool
 
-	outputFile android.OptionalPath
+	// Unstripped output. This is usually used when this module is linked to another module
+	// as a library. The stripped output which is used for installation can be found via
+	// compiler.strippedOutputFile if it exists.
+	unstrippedOutputFile android.OptionalPath
 
 	hideApexVariantFromMake bool
 }
@@ -163,8 +166,8 @@
 		if mod.sourceProvider != nil && (mod.compiler == nil || mod.compiler.Disabled()) {
 			return mod.sourceProvider.Srcs(), nil
 		} else {
-			if mod.outputFile.Valid() {
-				return android.Paths{mod.outputFile.Path()}, nil
+			if mod.OutputFile().Valid() {
+				return android.Paths{mod.OutputFile().Path()}, nil
 			}
 			return android.Paths{}, nil
 		}
@@ -256,6 +259,10 @@
 	return ""
 }
 
+func (mod *Module) MinSdkVersion() string {
+	return ""
+}
+
 func (mod *Module) AlwaysSdk() bool {
 	return false
 }
@@ -269,14 +276,15 @@
 }
 
 type Deps struct {
-	Dylibs     []string
-	Rlibs      []string
-	Rustlibs   []string
-	Stdlibs    []string
-	ProcMacros []string
-	SharedLibs []string
-	StaticLibs []string
-	HeaderLibs []string
+	Dylibs          []string
+	Rlibs           []string
+	Rustlibs        []string
+	Stdlibs         []string
+	ProcMacros      []string
+	SharedLibs      []string
+	StaticLibs      []string
+	WholeStaticLibs []string
+	HeaderLibs      []string
 
 	CrtBegin, CrtEnd string
 }
@@ -341,6 +349,8 @@
 
 	stdLinkage(ctx *depsContext) RustLinkage
 	isDependencyRoot() bool
+
+	strippedOutputFilePath() android.OptionalPath
 }
 
 type exportedFlagsProducer interface {
@@ -518,7 +528,10 @@
 }
 
 func (mod *Module) OutputFile() android.OptionalPath {
-	return mod.outputFile
+	if mod.compiler != nil && mod.compiler.strippedOutputFilePath().Valid() {
+		return mod.compiler.strippedOutputFilePath()
+	}
+	return mod.unstrippedOutputFile
 }
 
 func (mod *Module) CoverageFiles() android.Paths {
@@ -535,7 +548,7 @@
 		return false
 	}
 
-	return mod.outputFile.Valid() && !mod.Properties.PreventInstall
+	return mod.OutputFile().Valid() && !mod.Properties.PreventInstall
 }
 
 var _ cc.LinkableInterface = (*Module)(nil)
@@ -716,9 +729,9 @@
 
 	if mod.compiler != nil && !mod.compiler.Disabled() {
 		mod.compiler.initialize(ctx)
-		outputFile := mod.compiler.compile(ctx, flags, deps)
+		unstrippedOutputFile := mod.compiler.compile(ctx, flags, deps)
 
-		mod.outputFile = android.OptionalPathForPath(outputFile)
+		mod.unstrippedOutputFile = android.OptionalPathForPath(unstrippedOutputFile)
 
 		apexInfo := actx.Provider(android.ApexInfoProvider).(android.ApexInfo)
 		if mod.installable(apexInfo) {
@@ -751,7 +764,7 @@
 	deps.ProcMacros = android.LastUniqueStrings(deps.ProcMacros)
 	deps.SharedLibs = android.LastUniqueStrings(deps.SharedLibs)
 	deps.StaticLibs = android.LastUniqueStrings(deps.StaticLibs)
-
+	deps.WholeStaticLibs = android.LastUniqueStrings(deps.WholeStaticLibs)
 	return deps
 
 }
@@ -877,7 +890,7 @@
 			}
 
 			if depTag == dylibDepTag || depTag == rlibDepTag || depTag == procMacroDepTag {
-				linkFile := rustDep.outputFile
+				linkFile := rustDep.unstrippedOutputFile
 				if !linkFile.Valid() {
 					ctx.ModuleErrorf("Invalid output file when adding dep %q to %q",
 						depName, ctx.ModuleName())
@@ -911,16 +924,13 @@
 			exportDep := false
 			switch {
 			case cc.IsStaticDepTag(depTag):
-				// Only pass -lstatic for rlibs as it results in dylib bloat.
-				if lib, ok := ctx.Module().(*Module).compiler.(libraryInterface); ok && lib.rlib() {
-					// Link cc static libraries using "-lstatic" so rustc can reason about how to handle these
-					// (for example, bundling them into rlibs).
-					//
-					// rustc does not support linking libraries with the "-l" flag unless they are prefixed by "lib".
-					// If we need to link a library that isn't prefixed by "lib", we'll just link to it directly through
-					// linkObjects; such a library may need to be redeclared by static dependents.
+				if cc.IsWholeStaticLib(depTag) {
+					// rustc will bundle static libraries when they're passed with "-lstatic=<lib>". This will fail
+					// if the library is not prefixed by "lib".
 					if libName, ok := libNameFromFilePath(linkObject.Path()); ok {
 						depPaths.depFlags = append(depPaths.depFlags, "-lstatic="+libName)
+					} else {
+						ctx.ModuleErrorf("'%q' cannot be listed as a whole_static_library in Rust modules unless the output is prefixed by 'lib'", depName, ctx.ModuleName())
 					}
 				}
 
@@ -976,15 +986,15 @@
 
 	var rlibDepFiles RustLibraries
 	for _, dep := range directRlibDeps {
-		rlibDepFiles = append(rlibDepFiles, RustLibrary{Path: dep.outputFile.Path(), CrateName: dep.CrateName()})
+		rlibDepFiles = append(rlibDepFiles, RustLibrary{Path: dep.unstrippedOutputFile.Path(), CrateName: dep.CrateName()})
 	}
 	var dylibDepFiles RustLibraries
 	for _, dep := range directDylibDeps {
-		dylibDepFiles = append(dylibDepFiles, RustLibrary{Path: dep.outputFile.Path(), CrateName: dep.CrateName()})
+		dylibDepFiles = append(dylibDepFiles, RustLibrary{Path: dep.unstrippedOutputFile.Path(), CrateName: dep.CrateName()})
 	}
 	var procMacroDepFiles RustLibraries
 	for _, dep := range directProcMacroDeps {
-		procMacroDepFiles = append(procMacroDepFiles, RustLibrary{Path: dep.outputFile.Path(), CrateName: dep.CrateName()})
+		procMacroDepFiles = append(procMacroDepFiles, RustLibrary{Path: dep.unstrippedOutputFile.Path(), CrateName: dep.CrateName()})
 	}
 
 	var staticLibDepFiles android.Paths
@@ -1099,7 +1109,10 @@
 		cc.SharedDepTag(), deps.SharedLibs...)
 	actx.AddVariationDependencies(append(commonDepVariations,
 		blueprint.Variation{Mutator: "link", Variation: "static"}),
-		cc.StaticDepTag(), deps.StaticLibs...)
+		cc.StaticDepTag(false), deps.StaticLibs...)
+	actx.AddVariationDependencies(append(commonDepVariations,
+		blueprint.Variation{Mutator: "link", Variation: "static"}),
+		cc.StaticDepTag(true), deps.WholeStaticLibs...)
 
 	actx.AddVariationDependencies(nil, cc.HeaderDepTag(), deps.HeaderLibs...)
 
diff --git a/rust/rust_test.go b/rust/rust_test.go
index bed28ec..890fb26 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -75,7 +75,7 @@
 			func(variables android.FixtureProductVariables) {
 				variables.DeviceVndkVersion = StringPtr("current")
 				variables.ProductVndkVersion = StringPtr("current")
-				variables.Platform_vndk_version = StringPtr("VER")
+				variables.Platform_vndk_version = StringPtr("29")
 			},
 		),
 	).RunTestWithBp(t, bp)
@@ -149,6 +149,11 @@
 			srcs: ["foo.rs"],
 			crate_name: "static",
 		}
+		rust_ffi_host_static {
+			name: "libwholestatic",
+			srcs: ["foo.rs"],
+			crate_name: "wholestatic",
+		}
 		rust_ffi_host_shared {
 			name: "libshared",
 			srcs: ["foo.rs"],
@@ -164,6 +169,7 @@
 			srcs: ["foo.rs"],
 			crate_name: "rlib",
 			static_libs: ["libstatic"],
+			whole_static_libs: ["libwholestatic"],
 		}
 		rust_proc_macro {
 			name: "libpm",
@@ -204,8 +210,8 @@
 		t.Errorf("Static library dependency not detected (dependency missing from AndroidMkStaticLibs)")
 	}
 
-	if !strings.Contains(rustc.Args["rustcFlags"], "-lstatic=static") {
-		t.Errorf("-lstatic flag not being passed to rustc for static library")
+	if !strings.Contains(rustc.Args["rustcFlags"], "-lstatic=wholestatic") {
+		t.Errorf("-lstatic flag not being passed to rustc for static library %#v", rustc.Args["rustcFlags"])
 	}
 
 }
diff --git a/rust/sanitize.go b/rust/sanitize.go
index 2f44b20..2498aa1 100644
--- a/rust/sanitize.go
+++ b/rust/sanitize.go
@@ -40,12 +40,12 @@
 	"-C passes='sancov'",
 
 	"--cfg fuzzing",
-	"-C llvm-args=-sanitizer-coverage-level=4",
+	"-C llvm-args=-sanitizer-coverage-level=3",
 	"-C llvm-args=-sanitizer-coverage-trace-compares",
 	"-C llvm-args=-sanitizer-coverage-inline-8bit-counters",
+	"-C llvm-args=-sanitizer-coverage-stack-depth",
 	"-C llvm-args=-sanitizer-coverage-trace-geps",
 	"-C llvm-args=-sanitizer-coverage-prune-blocks=0",
-	"-C llvm-args=-sanitizer-coverage-pc-table",
 	"-Z sanitizer=address",
 
 	// Sancov breaks with lto
diff --git a/rust/testing.go b/rust/testing.go
index 5be71c9..75adcfc 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -17,13 +17,12 @@
 import (
 	"android/soong/android"
 	"android/soong/cc"
-	"android/soong/genrule"
 )
 
 // Preparer that will define all cc module types and a limited set of mutators and singletons that
 // make those module types usable.
 var PrepareForTestWithRustBuildComponents = android.GroupFixturePreparers(
-	android.FixtureRegisterWithContext(RegisterRequiredBuildComponentsForTest),
+	android.FixtureRegisterWithContext(registerRequiredBuildComponentsForTest),
 )
 
 // The directory in which rust test default modules will be defined.
@@ -197,7 +196,7 @@
 	return bp
 }
 
-func RegisterRequiredBuildComponentsForTest(ctx android.RegistrationContext) {
+func registerRequiredBuildComponentsForTest(ctx android.RegistrationContext) {
 	ctx.RegisterModuleType("rust_binary", RustBinaryFactory)
 	ctx.RegisterModuleType("rust_binary_host", RustBinaryHostFactory)
 	ctx.RegisterModuleType("rust_bindgen", RustBindgenFactory)
@@ -231,14 +230,3 @@
 	})
 	ctx.RegisterSingletonType("rust_project_generator", rustProjectGeneratorSingleton)
 }
-
-func CreateTestContext(config android.Config) *android.TestContext {
-	ctx := android.NewTestArchContext(config)
-	android.RegisterPrebuiltMutators(ctx)
-	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
-	genrule.RegisterGenruleBuildComponents(ctx)
-	cc.RegisterRequiredBuildComponentsForTest(ctx)
-	RegisterRequiredBuildComponentsForTest(ctx)
-
-	return ctx
-}
diff --git a/scripts/Android.bp b/scripts/Android.bp
index 9e8a602..c54b2bc 100644
--- a/scripts/Android.bp
+++ b/scripts/Android.bp
@@ -254,3 +254,22 @@
         "linker_config_proto",
     ],
 }
+
+python_binary_host {
+    name: "conv_classpaths_proto",
+    srcs: [
+        "conv_classpaths_proto.py",
+    ],
+    version: {
+        py2: {
+            enabled: false,
+        },
+        py3: {
+            enabled: true,
+            embedded_launcher: true,
+        },
+    },
+    libs: [
+        "classpaths_proto_python",
+    ],
+}
diff --git a/scripts/OWNERS b/scripts/OWNERS
index 8198083..2b9c2de 100644
--- a/scripts/OWNERS
+++ b/scripts/OWNERS
@@ -3,3 +3,4 @@
 per-file build-aml-prebuilts.sh = ngeoffray@google.com,paulduffin@google.com,mast@google.com
 per-file construct_context.py = ngeoffray@google.com,calin@google.com,mathieuc@google.com,skvadrik@google.com
 per-file conv_linker_config.py = kiyoungkim@google.com, jiyong@google.com, jooyung@google.com
+per-file gen_ndk*.sh = sophiez@google.com, allenhair@google.com
diff --git a/scripts/build-mainline-modules.sh b/scripts/build-mainline-modules.sh
index 18174a4..30cb937 100755
--- a/scripts/build-mainline-modules.sh
+++ b/scripts/build-mainline-modules.sh
@@ -27,7 +27,6 @@
   platform-mainline-test-exports
   runtime-module-host-exports
   runtime-module-sdk
-  stats-log-api-gen-exports
   statsd-module-sdk
   statsd-module-sdk-for-art
   tzdata-module-test-exports
diff --git a/scripts/construct_context.py b/scripts/construct_context.py
index 6f9edc4..f0658ba 100755
--- a/scripts/construct_context.py
+++ b/scripts/construct_context.py
@@ -66,9 +66,9 @@
     if not args.sdk:
       raise SystemExit('target sdk version is not set')
     if not args.host_contexts:
-      raise SystemExit('host context is not set')
+      args.host_contexts = []
     if not args.target_contexts:
-      raise SystemExit('target context is not set')
+      args.target_contexts = []
 
     print(construct_contexts(args))
 
diff --git a/scripts/conv_classpaths_proto.py b/scripts/conv_classpaths_proto.py
new file mode 100644
index 0000000..f49fbbb
--- /dev/null
+++ b/scripts/conv_classpaths_proto.py
@@ -0,0 +1,76 @@
+#  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.
+
+import argparse
+
+import classpaths_pb2
+
+import google.protobuf.json_format as json_format
+import google.protobuf.text_format as text_format
+
+
+def encode(args):
+    pb = classpaths_pb2.ExportedClasspathsJars()
+    if args.format == 'json':
+        json_format.Parse(args.input.read(), pb)
+    else:
+        text_format.Parse(args.input.read(), pb)
+    args.output.write(pb.SerializeToString())
+    args.input.close()
+    args.output.close()
+
+
+def decode(args):
+    pb = classpaths_pb2.ExportedClasspathsJars()
+    pb.ParseFromString(args.input.read())
+    if args.format == 'json':
+        args.output.write(json_format.MessageToJson(pb))
+    else:
+        args.output.write(text_format.MessageToString(pb).encode('utf_8'))
+    args.input.close()
+    args.output.close()
+
+
+def main():
+    parser = argparse.ArgumentParser('Convert classpaths.proto messages between binary and '
+                                     'human-readable formats.')
+    parser.add_argument('-f', '--format', default='textproto',
+                        help='human-readable format, either json or text(proto), '
+                             'defaults to textproto')
+    parser.add_argument('-i', '--input',
+                        nargs='?', type=argparse.FileType('rb'), default=sys.stdin.buffer)
+    parser.add_argument('-o', '--output',
+                        nargs='?', type=argparse.FileType('wb'),
+                        default=sys.stdout.buffer)
+
+    subparsers = parser.add_subparsers()
+
+    parser_encode = subparsers.add_parser('encode',
+                                          help='convert classpaths protobuf message from '
+                                               'JSON to binary format',
+                                          parents=[parser], add_help=False)
+
+    parser_encode.set_defaults(func=encode)
+
+    parser_decode = subparsers.add_parser('decode',
+                                          help='print classpaths config in JSON format',
+                                          parents=[parser], add_help=False)
+    parser_decode.set_defaults(func=decode)
+
+    args = parser.parse_args()
+    args.func(args)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/scripts/gen_ndk_usedby_apex.sh b/scripts/gen_ndk_usedby_apex.sh
index f143161..0d3ed5a 100755
--- a/scripts/gen_ndk_usedby_apex.sh
+++ b/scripts/gen_ndk_usedby_apex.sh
@@ -33,7 +33,7 @@
   do
       if [[ $line = *FUNC*GLOBAL*UND*@* ]] ;
       then
-          echo "$line" | sed -r 's/.*UND (.*)@.*/\1/g' >> "$2"
+          echo "$line" | sed -r 's/.*UND (.*@.*)/\1/g' >> "$2"
       fi
   done < "$1"
   echo "" >> "$2"
diff --git a/scripts/gen_sorted_bss_symbols.sh b/scripts/gen_sorted_bss_symbols.sh
index 244ed0d..a9b61a1 100755
--- a/scripts/gen_sorted_bss_symbols.sh
+++ b/scripts/gen_sorted_bss_symbols.sh
@@ -18,11 +18,11 @@
 # their sizes.
 # Inputs:
 #  Environment:
-#   CROSS_COMPILE: prefix added to nm tools
+#   CLANG_BIN: path to the clang bin directory
 #  Arguments:
 #   $1: Input ELF file
 #   $2: Output symbol ordering file
 
 set -o pipefail
 
-${CROSS_COMPILE}nm --size-sort $1 | awk '{if ($2 == "b" || $2 == "B") print $3}' > $2
+${CLANG_BIN}/llvm-nm --size-sort $1 | awk '{if ($2 == "b" || $2 == "B") print $3}' > $2
diff --git a/scripts/manifest_check.py b/scripts/manifest_check.py
index 1343f35..8168fbf 100755
--- a/scripts/manifest_check.py
+++ b/scripts/manifest_check.py
@@ -74,7 +74,7 @@
   return parser.parse_args()
 
 
-def enforce_uses_libraries(manifest, required, optional, relax, is_apk = False):
+def enforce_uses_libraries(manifest, required, optional, relax, is_apk, path):
   """Verify that the <uses-library> tags in the manifest match those provided
   by the build system.
 
@@ -86,26 +86,36 @@
     is_apk:   if the manifest comes from an APK or an XML file
   """
   if is_apk:
-    manifest_required, manifest_optional = extract_uses_libs_apk(manifest)
+    manifest_required, manifest_optional, tags = extract_uses_libs_apk(manifest)
   else:
-    manifest_required, manifest_optional = extract_uses_libs_xml(manifest)
+    manifest_required, manifest_optional, tags = extract_uses_libs_xml(manifest)
 
-  err = []
-  if manifest_required != required:
-    err.append('Expected required <uses-library> tags "%s", got "%s"' %
-               (', '.join(required), ', '.join(manifest_required)))
+  if manifest_required == required and manifest_optional == optional:
+    return None
 
-  if manifest_optional != optional:
-    err.append('Expected optional <uses-library> tags "%s", got "%s"' %
-               (', '.join(optional), ', '.join(manifest_optional)))
+  errmsg = ''.join([
+    'mismatch in the <uses-library> tags between the build system and the '
+      'manifest:\n',
+    '\t- required libraries in build system: [%s]\n' % ', '.join(required),
+    '\t                 vs. in the manifest: [%s]\n' % ', '.join(manifest_required),
+    '\t- optional libraries in build system: [%s]\n' % ', '.join(optional),
+    '\t                 vs. in the manifest: [%s]\n' % ', '.join(manifest_optional),
+    '\t- tags in the manifest (%s):\n' % path,
+    '\t\t%s\n' % '\t\t'.join(tags),
+      'note: the following options are available:\n',
+    '\t- to temporarily disable the check on command line, rebuild with ',
+      'RELAX_USES_LIBRARY_CHECK=true (this will set compiler filter "verify" ',
+      'and disable AOT-compilation in dexpreopt)\n',
+    '\t- to temporarily disable the check for the whole product, set ',
+      'PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true in the product makefiles\n',
+    '\t- to fix the check, make build system properties coherent with the '
+      'manifest\n',
+    '\t- see build/make/Changes.md for details\n'])
 
-  if err:
-    errmsg = '\n'.join(err)
-    if not relax:
-      raise ManifestMismatchError(errmsg)
-    return errmsg
+  if not relax:
+    raise ManifestMismatchError(errmsg)
 
-  return None
+  return errmsg
 
 
 def extract_uses_libs_apk(badging):
@@ -115,14 +125,19 @@
 
   required = []
   optional = []
+  lines = []
   for match in re.finditer(pattern, badging):
+    lines.append(match.group(0))
     libname = match.group(2)
     if match.group(1) == None:
       required.append(libname)
     else:
       optional.append(libname)
 
-  return first_unique_elements(required), first_unique_elements(optional)
+  required = first_unique_elements(required)
+  optional = first_unique_elements(optional)
+  tags = first_unique_elements(lines)
+  return required, optional, tags
 
 
 def extract_uses_libs_xml(xml):
@@ -143,7 +158,15 @@
   required = [uses_library_name(x) for x in libs if uses_library_required(x)]
   optional = [uses_library_name(x) for x in libs if not uses_library_required(x)]
 
-  return first_unique_elements(required), first_unique_elements(optional)
+  # render <uses-library> tags as XML for a pretty error message
+  tags = []
+  for lib in libs:
+    tags.append(lib.toprettyxml())
+
+  required = first_unique_elements(required)
+  optional = first_unique_elements(optional)
+  tags = first_unique_elements(tags)
+  return required, optional, tags
 
 
 def first_unique_elements(l):
@@ -278,7 +301,7 @@
       # in the manifest. Raise an exception on mismatch, unless the script was
       # passed a special parameter to suppress exceptions.
       errmsg = enforce_uses_libraries(manifest, required, optional,
-        args.enforce_uses_libraries_relax, is_apk)
+        args.enforce_uses_libraries_relax, is_apk, args.input)
 
       # Create a status file that is empty on success, or contains an error
       # message on failure. When exceptions are suppressed, dexpreopt command
@@ -289,7 +312,12 @@
             f.write("%s\n" % errmsg)
 
     if args.extract_target_sdk_version:
-      print(extract_target_sdk_version(manifest, is_apk))
+      try:
+        print(extract_target_sdk_version(manifest, is_apk))
+      except:
+        # Failed; don't crash, return "any" SDK version. This will result in
+        # dexpreopt not adding any compatibility libraries.
+        print(10000)
 
     if args.output:
       # XML output is supposed to be written only when this script is invoked
diff --git a/scripts/manifest_check_test.py b/scripts/manifest_check_test.py
index 635ba9d..7159bdd 100755
--- a/scripts/manifest_check_test.py
+++ b/scripts/manifest_check_test.py
@@ -49,9 +49,9 @@
     try:
       relax = False
       manifest_check.enforce_uses_libraries(doc, uses_libraries,
-        optional_uses_libraries, relax, is_apk=False)
+        optional_uses_libraries, relax, False, 'path/to/X/AndroidManifest.xml')
       manifest_check.enforce_uses_libraries(apk, uses_libraries,
-        optional_uses_libraries, relax, is_apk=True)
+        optional_uses_libraries, relax, True, 'path/to/X/X.apk')
       return True
     except manifest_check.ManifestMismatchError:
       return False
diff --git a/scripts/toc.sh b/scripts/toc.sh
index 8b1d25f..c6b7866 100755
--- a/scripts/toc.sh
+++ b/scripts/toc.sh
@@ -17,7 +17,7 @@
 # Script to handle generating a .toc file from a .so file
 # Inputs:
 #  Environment:
-#   CROSS_COMPILE: prefix added to readelf tool
+#   CLANG_BIN: path to the clang bin directory
 #  Arguments:
 #   -i ${file}: input file (required)
 #   -o ${file}: output file (required)
@@ -35,34 +35,34 @@
 }
 
 do_elf() {
-    ("${CROSS_COMPILE}readelf" -d "${infile}" | grep SONAME || echo "No SONAME for ${infile}") > "${outfile}.tmp"
-    "${CROSS_COMPILE}readelf" --dyn-syms "${infile}" | awk '{$2=""; $3=""; print}' >> "${outfile}.tmp"
+    ("${CLANG_BIN}/llvm-readelf" -d "${infile}" | grep SONAME || echo "No SONAME for ${infile}") > "${outfile}.tmp"
+    "${CLANG_BIN}/llvm-readelf" --dyn-syms "${infile}" | awk '{$2=""; $3=""; print}' >> "${outfile}.tmp"
 
     cat <<EOF > "${depsfile}"
 ${outfile}: \\
-  ${CROSS_COMPILE}readelf \\
+  ${CLANG_BIN}/llvm-readelf \\
 EOF
 }
 
 do_macho() {
-    "${CROSS_COMPILE}/otool" -l "${infile}" | grep LC_ID_DYLIB -A 5 > "${outfile}.tmp"
-    "${CROSS_COMPILE}/nm" -gP "${infile}" | cut -f1-2 -d" " | (grep -v 'U$' >> "${outfile}.tmp" || true)
+    "${CLANG_BIN}/llvm-objdump" -p "${infile}" | grep LC_ID_DYLIB -A 5 > "${outfile}.tmp"
+    "${CLANG_BIN}/llvm-nm" -gP "${infile}" | cut -f1-2 -d" " | (grep -v 'U$' >> "${outfile}.tmp" || true)
 
     cat <<EOF > "${depsfile}"
 ${outfile}: \\
-  ${CROSS_COMPILE}/otool \\
-  ${CROSS_COMPILE}/nm \\
+  ${CLANG_BIN}/llvm-objdump \\
+  ${CLANG_BIN}/llvm-nm \\
 EOF
 }
 
 do_pe() {
-    "${CROSS_COMPILE}objdump" -x "${infile}" | grep "^Name" | cut -f3 -d" " > "${outfile}.tmp"
-    "${CROSS_COMPILE}nm" -g -f p "${infile}" | cut -f1-2 -d" " >> "${outfile}.tmp"
+    "${CLANG_BIN}/llvm-objdump" -x "${infile}" | grep "^Name" | cut -f3 -d" " > "${outfile}.tmp"
+    "${CLANG_BIN}/llvm-nm" -gP "${infile}" | cut -f1-2 -d" " >> "${outfile}.tmp"
 
     cat <<EOF > "${depsfile}"
 ${outfile}: \\
-  ${CROSS_COMPILE}objdump \\
-  ${CROSS_COMPILE}nm \\
+  ${CLANG_BIN}/llvm-objdump \\
+  ${CLANG_BIN}/llvm-nm \\
 EOF
 }
 
@@ -98,8 +98,8 @@
     usage
 fi
 
-if [ -z "${CROSS_COMPILE:-}" ]; then
-    echo "CROSS_COMPILE environment variable must be set"
+if [ -z "${CLANG_BIN:-}" ]; then
+    echo "CLANG_BIN environment variable must be set"
     usage
 fi
 
@@ -107,7 +107,7 @@
 
 cat <<EOF > "${depsfile}"
 ${outfile}: \\
-  ${CROSS_COMPILE}readelf \\
+  ${CLANG_BIN}/llvm-readelf \\
 EOF
 
 if [ -n "${elf:-}" ]; then
diff --git a/sdk/cc_sdk_test.go b/sdk/cc_sdk_test.go
index 9626a04..b19fcc5 100644
--- a/sdk/cc_sdk_test.go
+++ b/sdk/cc_sdk_test.go
@@ -486,6 +486,9 @@
 		}
 	`)
 
+	// TODO(b/183322862): Remove this and fix the issue.
+	errorHandler := android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module source path "snapshot/include_gen/generated_foo/gen/protos" does not exist`)
+
 	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
@@ -518,6 +521,9 @@
 .intermediates/mynativelib/android_arm64_armv8-a_shared/mynativelib.so -> arm64/lib/mynativelib.so
 .intermediates/mynativelib/android_arm_armv7-a-neon_shared/mynativelib.so -> arm/lib/mynativelib.so
 `),
+		snapshotTestErrorHandler(checkSnapshotWithoutSource, errorHandler),
+		snapshotTestErrorHandler(checkSnapshotWithSourcePreferred, errorHandler),
+		snapshotTestErrorHandler(checkSnapshotPreferredWithSource, errorHandler),
 	)
 }
 
@@ -1816,7 +1822,10 @@
 .intermediates/mynativelib/android_arm64_armv8-a_static/mynativelib.a -> arm64/lib/mynativelib.a
 .intermediates/mynativelib/android_arm64_armv8-a_shared/mynativelib.so -> arm64/lib/mynativelib.so
 .intermediates/mynativelib/android_arm_armv7-a-neon_static/mynativelib.a -> arm/lib/mynativelib.a
-.intermediates/mynativelib/android_arm_armv7-a-neon_shared/mynativelib.so -> arm/lib/mynativelib.so`),
+.intermediates/mynativelib/android_arm_armv7-a-neon_shared/mynativelib.so -> arm/lib/mynativelib.so
+`),
+		// TODO(b/183315522): Remove this and fix the issue.
+		snapshotTestErrorHandler(checkSnapshotPreferredWithSource, android.FixtureExpectsAtLeastOneErrorMatchingPattern(`\Qunrecognized property "arch.arm.shared.export_include_dirs"\E`)),
 	)
 }
 
@@ -2398,6 +2407,7 @@
             "1",
             "2",
             "3",
+            "current",
         ],
     },
     arch: {
@@ -2452,6 +2462,7 @@
             "1",
             "2",
             "3",
+            "current",
         ],
     },
     target: {
@@ -2491,6 +2502,7 @@
             "1",
             "2",
             "3",
+            "current",
         ],
     },
     target: {
@@ -2664,6 +2676,11 @@
 		}
 	`)
 
+	// Mixing the snapshot with the source (irrespective of which one is preferred) causes a problem
+	// due to missing variants.
+	// TODO(b/183204176): Remove this and fix the cause.
+	snapshotWithSourceErrorHandler := android.FixtureExpectsAtLeastOneErrorMatchingPattern(`\QReplaceDependencies could not find identical variant {os:android,image:,arch:arm64_armv8-a,sdk:,link:shared,version:} for module mynativelib\E`)
+
 	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
@@ -2688,6 +2705,9 @@
 		checkAllCopyRules(`
 myinclude/Test.h -> include/myinclude/Test.h
 arm64/include/Arm64Test.h -> arm64/include/arm64/include/Arm64Test.h
-.intermediates/mynativelib/android_arm_armv7-a-neon_shared/mynativelib.so -> arm/lib/mynativelib.so`),
+.intermediates/mynativelib/android_arm_armv7-a-neon_shared/mynativelib.so -> arm/lib/mynativelib.so
+`),
+		snapshotTestErrorHandler(checkSnapshotWithSourcePreferred, snapshotWithSourceErrorHandler),
+		snapshotTestErrorHandler(checkSnapshotPreferredWithSource, snapshotWithSourceErrorHandler),
 	)
 }
diff --git a/sdk/compat_config_sdk_test.go b/sdk/compat_config_sdk_test.go
index dffc02a..00073c2 100644
--- a/sdk/compat_config_sdk_test.go
+++ b/sdk/compat_config_sdk_test.go
@@ -66,5 +66,26 @@
 		checkAllCopyRules(`
 .intermediates/myconfig/android_common/myconfig_meta.xml -> compat_configs/myconfig/myconfig_meta.xml
 `),
+		snapshotTestChecker(checkSnapshotWithoutSource,
+			func(t *testing.T, result *android.TestResult) {
+				// Make sure that the snapshot metadata is collated by the platform compat config singleton.
+				java.CheckMergedCompatConfigInputs(t, result, "snapshot module", "snapshot/compat_configs/myconfig/myconfig_meta.xml")
+			}),
+
+		snapshotTestChecker(checkSnapshotWithSourcePreferred,
+			func(t *testing.T, result *android.TestResult) {
+				// Make sure that the snapshot metadata is collated by the platform compat config singleton.
+				java.CheckMergedCompatConfigInputs(t, result, "snapshot module",
+					"out/soong/.intermediates/myconfig/android_common/myconfig_meta.xml",
+				)
+			}),
+
+		snapshotTestChecker(checkSnapshotPreferredWithSource,
+			func(t *testing.T, result *android.TestResult) {
+				// Make sure that the snapshot metadata is collated by the platform compat config singleton.
+				java.CheckMergedCompatConfigInputs(t, result, "snapshot module",
+					"snapshot/compat_configs/myconfig/myconfig_meta.xml",
+				)
+			}),
 	)
 }
diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go
index fa0eb3f..208cd58 100644
--- a/sdk/java_sdk_test.go
+++ b/sdk/java_sdk_test.go
@@ -24,6 +24,16 @@
 var prepareForSdkTestWithJava = android.GroupFixturePreparers(
 	java.PrepareForTestWithJavaBuildComponents,
 	PrepareForTestWithSdkBuildComponents,
+
+	// Ensure that all source paths are provided. This helps ensure that the snapshot generation is
+	// consistent and all files referenced from the snapshot's Android.bp file have actually been
+	// copied into the snapshot.
+	android.PrepareForTestDisallowNonExistentPaths,
+
+	// Files needs by most of the tests.
+	android.MockFS{
+		"Test.java": nil,
+	}.AddToFixture(),
 )
 
 var prepareForSdkTestWithJavaSdkLibrary = android.GroupFixturePreparers(
@@ -48,42 +58,17 @@
 			system_modules: "none",
 			sdk_version: "none",
 		}
-
-		java_import {
-			name: "sdkmember",
-			prefer: true,
-			jars: ["prebuilt.jar"],
-		}
 	`)
 
 	// Make sure that the mysdk module depends on "sdkmember" and not "prebuilt_sdkmember".
-	java.CheckModuleDependencies(t, result.TestContext, "mysdk", "android_common", []string{"sdkmember"})
+	sdkChecker := func(t *testing.T, result *android.TestResult) {
+		java.CheckModuleDependencies(t, result.TestContext, "mysdk", "android_common", []string{"sdkmember"})
+	}
 
 	CheckSnapshot(t, result, "mysdk", "",
-		checkAndroidBpContents(`// This is auto-generated. DO NOT EDIT.
-
-java_import {
-    name: "mysdk_sdkmember@current",
-    sdk_member_name: "sdkmember",
-    visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
-    jars: ["java/sdkmember.jar"],
-}
-
-java_import {
-    name: "sdkmember",
-    prefer: false,
-    visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
-    jars: ["java/sdkmember.jar"],
-}
-
-sdk_snapshot {
-    name: "mysdk@current",
-    visibility: ["//visibility:public"],
-    java_header_libs: ["mysdk_sdkmember@current"],
-}
-`))
+		snapshotTestChecker(checkSnapshotWithSourcePreferred, sdkChecker),
+		snapshotTestChecker(checkSnapshotPreferredWithSource, sdkChecker),
+	)
 }
 
 func TestBasicSdkWithJavaLibrary(t *testing.T) {
@@ -364,6 +349,7 @@
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
 		android.FixtureAddFile("aidl/foo/bar/Test.aidl", nil),
+		android.FixtureAddFile("resource.txt", nil),
 	).RunTestWithBp(t, `
 		module_exports {
 			name: "myexports",
@@ -419,7 +405,11 @@
 }
 
 func TestSnapshotWithJavaBootLibrary(t *testing.T) {
-	result := android.GroupFixturePreparers(prepareForSdkTestWithJava).RunTestWithBp(t, `
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJava,
+		android.FixtureAddFile("aidl", nil),
+		android.FixtureAddFile("resource.txt", nil),
+	).RunTestWithBp(t, `
 		module_exports {
 			name: "myexports",
 			java_boot_libs: ["myjavalib"],
@@ -1049,14 +1039,14 @@
 `),
 		checkAllCopyRules(`
 .intermediates/myjavalib.stubs/android_common/javac/myjavalib.stubs.jar -> sdk_library/public/myjavalib-stubs.jar
-.intermediates/myjavalib.stubs.source/android_common/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
-.intermediates/myjavalib.stubs.source/android_common/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
 .intermediates/myjavalib.stubs.system/android_common/javac/myjavalib.stubs.system.jar -> sdk_library/system/myjavalib-stubs.jar
-.intermediates/myjavalib.stubs.source.system/android_common/myjavalib.stubs.source.system_api.txt -> sdk_library/system/myjavalib.txt
-.intermediates/myjavalib.stubs.source.system/android_common/myjavalib.stubs.source.system_removed.txt -> sdk_library/system/myjavalib-removed.txt
+.intermediates/myjavalib.stubs.source.system/android_common/metalava/myjavalib.stubs.source.system_api.txt -> sdk_library/system/myjavalib.txt
+.intermediates/myjavalib.stubs.source.system/android_common/metalava/myjavalib.stubs.source.system_removed.txt -> sdk_library/system/myjavalib-removed.txt
 .intermediates/myjavalib.stubs.test/android_common/javac/myjavalib.stubs.test.jar -> sdk_library/test/myjavalib-stubs.jar
-.intermediates/myjavalib.stubs.source.test/android_common/myjavalib.stubs.source.test_api.txt -> sdk_library/test/myjavalib.txt
-.intermediates/myjavalib.stubs.source.test/android_common/myjavalib.stubs.source.test_removed.txt -> sdk_library/test/myjavalib-removed.txt
+.intermediates/myjavalib.stubs.source.test/android_common/metalava/myjavalib.stubs.source.test_api.txt -> sdk_library/test/myjavalib.txt
+.intermediates/myjavalib.stubs.source.test/android_common/metalava/myjavalib.stubs.source.test_removed.txt -> sdk_library/test/myjavalib-removed.txt
 `),
 		checkMergeZips(
 			".intermediates/mysdk/common_os/tmp/sdk_library/public/myjavalib_stub_sources.zip",
@@ -1122,8 +1112,8 @@
 `),
 		checkAllCopyRules(`
 .intermediates/myjavalib.stubs/android_common/javac/myjavalib.stubs.jar -> sdk_library/public/myjavalib-stubs.jar
-.intermediates/myjavalib.stubs.source/android_common/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
-.intermediates/myjavalib.stubs.source/android_common/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
 `),
 		checkMergeZips(
 			".intermediates/mysdk/common_os/tmp/sdk_library/public/myjavalib_stub_sources.zip",
@@ -1191,8 +1181,8 @@
 `),
 		checkAllCopyRules(`
 .intermediates/myjavalib.stubs/android_common/javac/myjavalib.stubs.jar -> sdk_library/public/myjavalib-stubs.jar
-.intermediates/myjavalib.stubs.source/android_common/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
-.intermediates/myjavalib.stubs.source/android_common/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
 `),
 		checkMergeZips(
 			".intermediates/mysdk/common_os/tmp/sdk_library/public/myjavalib_stub_sources.zip",
@@ -1277,11 +1267,11 @@
 `),
 		checkAllCopyRules(`
 .intermediates/myjavalib.stubs/android_common/javac/myjavalib.stubs.jar -> sdk_library/public/myjavalib-stubs.jar
-.intermediates/myjavalib.stubs.source/android_common/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
-.intermediates/myjavalib.stubs.source/android_common/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
 .intermediates/myjavalib.stubs.system/android_common/javac/myjavalib.stubs.system.jar -> sdk_library/system/myjavalib-stubs.jar
-.intermediates/myjavalib.stubs.source.system/android_common/myjavalib.stubs.source.system_api.txt -> sdk_library/system/myjavalib.txt
-.intermediates/myjavalib.stubs.source.system/android_common/myjavalib.stubs.source.system_removed.txt -> sdk_library/system/myjavalib-removed.txt
+.intermediates/myjavalib.stubs.source.system/android_common/metalava/myjavalib.stubs.source.system_api.txt -> sdk_library/system/myjavalib.txt
+.intermediates/myjavalib.stubs.source.system/android_common/metalava/myjavalib.stubs.source.system_removed.txt -> sdk_library/system/myjavalib-removed.txt
 `),
 		checkMergeZips(
 			".intermediates/mysdk/common_os/tmp/sdk_library/public/myjavalib_stub_sources.zip",
@@ -1384,14 +1374,14 @@
 `),
 		checkAllCopyRules(`
 .intermediates/myjavalib.stubs/android_common/javac/myjavalib.stubs.jar -> sdk_library/public/myjavalib-stubs.jar
-.intermediates/myjavalib.stubs.source/android_common/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
-.intermediates/myjavalib.stubs.source/android_common/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
 .intermediates/myjavalib.stubs.system/android_common/javac/myjavalib.stubs.system.jar -> sdk_library/system/myjavalib-stubs.jar
-.intermediates/myjavalib.stubs.source.system/android_common/myjavalib.stubs.source.system_api.txt -> sdk_library/system/myjavalib.txt
-.intermediates/myjavalib.stubs.source.system/android_common/myjavalib.stubs.source.system_removed.txt -> sdk_library/system/myjavalib-removed.txt
+.intermediates/myjavalib.stubs.source.system/android_common/metalava/myjavalib.stubs.source.system_api.txt -> sdk_library/system/myjavalib.txt
+.intermediates/myjavalib.stubs.source.system/android_common/metalava/myjavalib.stubs.source.system_removed.txt -> sdk_library/system/myjavalib-removed.txt
 .intermediates/myjavalib.stubs.module_lib/android_common/javac/myjavalib.stubs.module_lib.jar -> sdk_library/module-lib/myjavalib-stubs.jar
-.intermediates/myjavalib.stubs.source.module_lib/android_common/myjavalib.stubs.source.module_lib_api.txt -> sdk_library/module-lib/myjavalib.txt
-.intermediates/myjavalib.stubs.source.module_lib/android_common/myjavalib.stubs.source.module_lib_removed.txt -> sdk_library/module-lib/myjavalib-removed.txt
+.intermediates/myjavalib.stubs.source.module_lib/android_common/metalava/myjavalib.stubs.source.module_lib_api.txt -> sdk_library/module-lib/myjavalib.txt
+.intermediates/myjavalib.stubs.source.module_lib/android_common/metalava/myjavalib.stubs.source.module_lib_removed.txt -> sdk_library/module-lib/myjavalib-removed.txt
 `),
 		checkMergeZips(
 			".intermediates/mysdk/common_os/tmp/sdk_library/public/myjavalib_stub_sources.zip",
@@ -1478,11 +1468,11 @@
 `),
 		checkAllCopyRules(`
 .intermediates/myjavalib.stubs/android_common/javac/myjavalib.stubs.jar -> sdk_library/public/myjavalib-stubs.jar
-.intermediates/myjavalib.stubs.source/android_common/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
-.intermediates/myjavalib.stubs.source/android_common/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
 .intermediates/myjavalib.stubs.system_server/android_common/javac/myjavalib.stubs.system_server.jar -> sdk_library/system-server/myjavalib-stubs.jar
-.intermediates/myjavalib.stubs.source.system_server/android_common/myjavalib.stubs.source.system_server_api.txt -> sdk_library/system-server/myjavalib.txt
-.intermediates/myjavalib.stubs.source.system_server/android_common/myjavalib.stubs.source.system_server_removed.txt -> sdk_library/system-server/myjavalib-removed.txt
+.intermediates/myjavalib.stubs.source.system_server/android_common/metalava/myjavalib.stubs.source.system_server_api.txt -> sdk_library/system-server/myjavalib.txt
+.intermediates/myjavalib.stubs.source.system_server/android_common/metalava/myjavalib.stubs.source.system_server_removed.txt -> sdk_library/system-server/myjavalib-removed.txt
 `),
 		checkMergeZips(
 			".intermediates/mysdk/common_os/tmp/sdk_library/public/myjavalib_stub_sources.zip",
@@ -1554,8 +1544,8 @@
 `),
 		checkAllCopyRules(`
 .intermediates/myjavalib.stubs/android_common/javac/myjavalib.stubs.jar -> sdk_library/public/myjavalib-stubs.jar
-.intermediates/myjavalib.stubs.source/android_common/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
-.intermediates/myjavalib.stubs.source/android_common/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
 `),
 		checkMergeZips(
 			".intermediates/mysdk/common_os/tmp/sdk_library/public/myjavalib_stub_sources.zip",
@@ -1564,7 +1554,10 @@
 }
 
 func TestSnapshotWithJavaSdkLibrary_DoctagFiles(t *testing.T) {
-	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJavaSdkLibrary,
+		android.FixtureAddFile("docs/known_doctags", nil),
+	).RunTestWithBp(t, `
 		sdk {
 			name: "mysdk",
 			java_sdk_libs: ["myjavalib"],
@@ -1630,8 +1623,8 @@
 `),
 		checkAllCopyRules(`
 .intermediates/myjavalib.stubs/android_common/javac/myjavalib.stubs.jar -> sdk_library/public/myjavalib-stubs.jar
-.intermediates/myjavalib.stubs.source/android_common/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
-.intermediates/myjavalib.stubs.source/android_common/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_api.txt -> sdk_library/public/myjavalib.txt
+.intermediates/myjavalib.stubs.source/android_common/metalava/myjavalib.stubs.source_removed.txt -> sdk_library/public/myjavalib-removed.txt
 docs/known_doctags -> doctags/docs/known_doctags
 `),
 	)
diff --git a/sdk/sdk.go b/sdk/sdk.go
index e561529..b60fb18 100644
--- a/sdk/sdk.go
+++ b/sdk/sdk.go
@@ -486,6 +486,15 @@
 			// sdk containing sdkmember.
 			memberName := versionedSdkMember.MemberName()
 
+			// Convert a panic into a normal error to allow it to be more easily tested for. This is a
+			// temporary workaround, once http://b/183204176 has been fixed this can be removed.
+			// TODO(b/183204176): Remove this after fixing.
+			defer func() {
+				if r := recover(); r != nil {
+					mctx.ModuleErrorf("%s", r)
+				}
+			}()
+
 			// Replace dependencies on sdkmember with a dependency on the current module which
 			// is a versioned prebuilt of the sdkmember if required.
 			mctx.ReplaceDependenciesIf(memberName, func(from blueprint.Module, tag blueprint.DependencyTag, to blueprint.Module) bool {
diff --git a/sdk/testing.go b/sdk/testing.go
index ba40f67..9465e13 100644
--- a/sdk/testing.go
+++ b/sdk/testing.go
@@ -48,10 +48,10 @@
 		"system/sepolicy/apex/myapex-file_contexts":    nil,
 		"system/sepolicy/apex/myapex2-file_contexts":   nil,
 		"system/sepolicy/apex/mysdkapex-file_contexts": nil,
-		"myapex.avbpubkey":                             nil,
-		"myapex.pem":                                   nil,
-		"myapex.x509.pem":                              nil,
-		"myapex.pk8":                                   nil,
+		"sdk/tests/myapex.avbpubkey":                   nil,
+		"sdk/tests/myapex.pem":                         nil,
+		"sdk/tests/myapex.x509.pem":                    nil,
+		"sdk/tests/myapex.pk8":                         nil,
 	}),
 )
 
@@ -80,6 +80,12 @@
 			{android.Windows, android.Arch{ArchType: android.X86_64}, android.NativeBridgeDisabled, "", "", true},
 		}
 	}),
+
+	// Make sure that every test provides all the source files.
+	android.PrepareForTestDisallowNonExistentPaths,
+	android.MockFS{
+		"Test.java": nil,
+	}.AddToFixture(),
 )
 
 var PrepareForTestWithSdkBuildComponents = android.GroupFixturePreparers(
@@ -89,7 +95,10 @@
 
 func testSdkWithFs(t *testing.T, bp string, fs android.MockFS) *android.TestResult {
 	t.Helper()
-	return prepareForSdkTest.RunTest(t, fs.AddToFixture(), android.FixtureWithRootAndroidBp(bp))
+	return android.GroupFixturePreparers(
+		prepareForSdkTest,
+		fs.AddToFixture(),
+	).RunTestWithBp(t, bp)
 }
 
 func testSdkError(t *testing.T, pattern, bp string) {
@@ -125,6 +134,7 @@
 		androidBpContents:            sdk.GetAndroidBpContentsForTests(),
 		androidUnversionedBpContents: sdk.GetUnversionedAndroidBpContentsForTests(),
 		androidVersionedBpContents:   sdk.GetVersionedAndroidBpContentsForTests(),
+		snapshotTestCustomizations:   map[snapshotTest]*snapshotTestCustomization{},
 	}
 
 	buildParams := sdk.BuildParamsForTests()
@@ -183,6 +193,24 @@
 	return info
 }
 
+// The enum of different sdk snapshot tests performed by CheckSnapshot.
+type snapshotTest int
+
+const (
+	// The enumeration of the different test configurations.
+	// A test with the snapshot/Android.bp file but without the original Android.bp file.
+	checkSnapshotWithoutSource snapshotTest = iota
+
+	// A test with both the original source and the snapshot, with the source preferred.
+	checkSnapshotWithSourcePreferred
+
+	// A test with both the original source and the snapshot, with the snapshot preferred.
+	checkSnapshotPreferredWithSource
+
+	// The directory into which the snapshot will be 'unpacked'.
+	snapshotSubDir = "snapshot"
+)
+
 // Check the snapshot build rules.
 //
 // Takes a list of functions which check different facets of the snapshot build rules.
@@ -214,31 +242,58 @@
 	// Populate a mock filesystem with the files that would have been copied by
 	// the rules.
 	fs := android.MockFS{}
-	snapshotSubDir := "snapshot"
 	for _, dest := range snapshotBuildInfo.snapshotContents {
 		fs[filepath.Join(snapshotSubDir, dest)] = nil
 	}
 	fs[filepath.Join(snapshotSubDir, "Android.bp")] = []byte(snapshotBuildInfo.androidBpContents)
 
-	preparer := result.Preparer()
+	// The preparers from the original source fixture.
+	sourcePreparers := result.Preparer()
 
-	// Process the generated bp file to make sure it is valid. Use the same preparer as was used to
-	// produce this result.
+	// Preparer to combine the snapshot and the source.
+	snapshotPreparer := android.GroupFixturePreparers(sourcePreparers, fs.AddToFixture())
+
+	var runSnapshotTestWithCheckers = func(t *testing.T, testConfig snapshotTest, extraPreparer android.FixturePreparer) {
+		customization := snapshotBuildInfo.snapshotTestCustomization(testConfig)
+
+		// TODO(b/183184375): Set Config.TestAllowNonExistentPaths = false to verify that all the
+		//  files the snapshot needs are actually copied into the snapshot.
+
+		// Run the snapshot with the snapshot preparer and the extra preparer, which must come after as
+		// it may need to modify parts of the MockFS populated by the snapshot preparer.
+		result := android.GroupFixturePreparers(snapshotPreparer, extraPreparer).
+			ExtendWithErrorHandler(customization.errorHandler).
+			RunTest(t)
+
+		// Perform any additional checks the test need on the result of processing the snapshot.
+		for _, checker := range customization.checkers {
+			checker(t, result)
+		}
+	}
+
 	t.Run("snapshot without source", func(t *testing.T) {
-		android.GroupFixturePreparers(
-			preparer,
-			// TODO(b/183184375): Set Config.TestAllowNonExistentPaths = false to verify that all the
-			//  files the snapshot needs are actually copied into the snapshot.
+		// Remove the source Android.bp file to make sure it works without.
+		removeSourceAndroidBp := android.FixtureModifyMockFS(func(fs android.MockFS) {
+			delete(fs, "Android.bp")
+		})
 
-			// Add the files (including bp) created for this snapshot to the test fixture.
-			fs.AddToFixture(),
+		runSnapshotTestWithCheckers(t, checkSnapshotWithoutSource, removeSourceAndroidBp)
+	})
 
-			// Remove the source Android.bp file to make sure it works without.
-			// TODO(b/183184375): Add a test with the source.
-			android.FixtureModifyMockFS(func(fs android.MockFS) {
-				delete(fs, "Android.bp")
-			}),
-		).RunTest(t)
+	t.Run("snapshot with source preferred", func(t *testing.T) {
+		runSnapshotTestWithCheckers(t, checkSnapshotWithSourcePreferred, android.NullFixturePreparer)
+	})
+
+	t.Run("snapshot preferred with source", func(t *testing.T) {
+		// Replace the snapshot/Android.bp file with one where "prefer: false," has been replaced with
+		// "prefer: true,"
+		preferPrebuilts := android.FixtureModifyMockFS(func(fs android.MockFS) {
+			snapshotBpFile := filepath.Join(snapshotSubDir, "Android.bp")
+			unpreferred := string(fs[snapshotBpFile])
+			fs[snapshotBpFile] = []byte(strings.ReplaceAll(unpreferred, "prefer: false,", "prefer: true,"))
+		})
+
+		runSnapshotTestWithCheckers(t, checkSnapshotPreferredWithSource, preferPrebuilts)
 	})
 }
 
@@ -312,6 +367,46 @@
 	}
 }
 
+type resultChecker func(t *testing.T, result *android.TestResult)
+
+// snapshotTestChecker registers a checker that will be run against the result of processing the
+// generated snapshot for the specified snapshotTest.
+func snapshotTestChecker(snapshotTest snapshotTest, checker resultChecker) snapshotBuildInfoChecker {
+	return func(info *snapshotBuildInfo) {
+		customization := info.snapshotTestCustomization(snapshotTest)
+		customization.checkers = append(customization.checkers, checker)
+	}
+}
+
+// snapshotTestErrorHandler registers an error handler to use when processing the snapshot
+// in the specific test case.
+//
+// Generally, the snapshot should work with all the test cases but some do not and just in case
+// there are a lot of issues to resolve, or it will take a lot of time this is a
+// get-out-of-jail-free card that allows progress to be made.
+//
+// deprecated: should only be used as a temporary workaround with an attached to do and bug.
+func snapshotTestErrorHandler(snapshotTest snapshotTest, handler android.FixtureErrorHandler) snapshotBuildInfoChecker {
+	return func(info *snapshotBuildInfo) {
+		customization := info.snapshotTestCustomization(snapshotTest)
+		customization.errorHandler = handler
+	}
+}
+
+// Encapsulates information provided by each test to customize a specific snapshotTest.
+type snapshotTestCustomization struct {
+	// Checkers that are run on the result of processing the preferred snapshot in a specific test
+	// case.
+	checkers []resultChecker
+
+	// Specify an error handler for when processing a specific test case.
+	//
+	// In some cases the generated snapshot cannot be used in a test configuration. Those cases are
+	// invariably bugs that need to be resolved but sometimes that can take a while. This provides a
+	// mechanism to temporarily ignore that error.
+	errorHandler android.FixtureErrorHandler
+}
+
 // Encapsulates information about the snapshot build structure in order to insulate tests from
 // knowing too much about internal structures.
 //
@@ -355,4 +450,21 @@
 
 	// The final output zip.
 	outputZip string
+
+	// The test specific customizations for each snapshot test.
+	snapshotTestCustomizations map[snapshotTest]*snapshotTestCustomization
+}
+
+// snapshotTestCustomization gets the test specific customization for the specified snapshotTest.
+//
+// If no customization was created previously then it creates a default customization.
+func (i *snapshotBuildInfo) snapshotTestCustomization(snapshotTest snapshotTest) *snapshotTestCustomization {
+	customization := i.snapshotTestCustomizations[snapshotTest]
+	if customization == nil {
+		customization = &snapshotTestCustomization{
+			errorHandler: android.FixtureExpectsNoErrors,
+		}
+		i.snapshotTestCustomizations[snapshotTest] = customization
+	}
+	return customization
 }
diff --git a/sdk/update.go b/sdk/update.go
index 828c7b6..522a888 100644
--- a/sdk/update.go
+++ b/sdk/update.go
@@ -1350,7 +1350,7 @@
 // Compute the list of possible os types that this sdk could support.
 func (s *sdk) getPossibleOsTypes() []android.OsType {
 	var osTypes []android.OsType
-	for _, osType := range android.OsTypeList {
+	for _, osType := range android.OsTypeList() {
 		if s.DeviceSupported() {
 			if osType.Class == android.Device && osType != android.Fuchsia {
 				osTypes = append(osTypes, osType)
diff --git a/sh/sh_binary.go b/sh/sh_binary.go
index 1ae557a..6623381 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -485,7 +485,7 @@
 }
 
 type bazelShBinaryAttributes struct {
-	Srcs bazel.LabelList
+	Srcs bazel.LabelListAttribute
 	// Bazel also supports the attributes below, but (so far) these are not required for Bionic
 	// deps
 	// data
@@ -525,7 +525,8 @@
 		return
 	}
 
-	srcs := android.BazelLabelForModuleSrc(ctx, []string{*m.properties.Src})
+	srcs := bazel.MakeLabelListAttribute(
+		android.BazelLabelForModuleSrc(ctx, []string{*m.properties.Src}))
 
 	attrs := &bazelShBinaryAttributes{
 		Srcs: srcs,
diff --git a/sysprop/sysprop_test.go b/sysprop/sysprop_test.go
index e9d9051..6d35f9c 100644
--- a/sysprop/sysprop_test.go
+++ b/sysprop/sysprop_test.go
@@ -129,7 +129,7 @@
 		android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
 			variables.DeviceSystemSdkVersions = []string{"28"}
 			variables.DeviceVndkVersion = proptools.StringPtr("current")
-			variables.Platform_vndk_version = proptools.StringPtr("VER")
+			variables.Platform_vndk_version = proptools.StringPtr("29")
 		}),
 		mockFS.AddToFixture(),
 		android.FixtureWithRootAndroidBp(bp),
@@ -246,10 +246,10 @@
 
 	// Check for generated cc_library
 	for _, variant := range []string{
-		"android_vendor.VER_arm_armv7-a-neon_shared",
-		"android_vendor.VER_arm_armv7-a-neon_static",
-		"android_vendor.VER_arm64_armv8-a_shared",
-		"android_vendor.VER_arm64_armv8-a_static",
+		"android_vendor.29_arm_armv7-a-neon_shared",
+		"android_vendor.29_arm_armv7-a-neon_static",
+		"android_vendor.29_arm64_armv8-a_shared",
+		"android_vendor.29_arm64_armv8-a_static",
 	} {
 		result.ModuleForTests("libsysprop-platform", variant)
 		result.ModuleForTests("libsysprop-vendor", variant)
@@ -277,15 +277,15 @@
 
 	// Check for exported includes
 	coreVariant := "android_arm64_armv8-a_static"
-	vendorVariant := "android_vendor.VER_arm64_armv8-a_static"
+	vendorVariant := "android_vendor.29_arm64_armv8-a_static"
 
 	platformInternalPath := "libsysprop-platform/android_arm64_armv8-a_static/gen/sysprop/include"
 	platformPublicCorePath := "libsysprop-platform/android_arm64_armv8-a_static/gen/sysprop/public/include"
-	platformPublicVendorPath := "libsysprop-platform/android_vendor.VER_arm64_armv8-a_static/gen/sysprop/public/include"
+	platformPublicVendorPath := "libsysprop-platform/android_vendor.29_arm64_armv8-a_static/gen/sysprop/public/include"
 
 	platformOnProductPath := "libsysprop-platform-on-product/android_arm64_armv8-a_static/gen/sysprop/public/include"
 
-	vendorInternalPath := "libsysprop-vendor/android_vendor.VER_arm64_armv8-a_static/gen/sysprop/include"
+	vendorInternalPath := "libsysprop-vendor/android_vendor.29_arm64_armv8-a_static/gen/sysprop/include"
 	vendorPublicPath := "libsysprop-vendor-on-product/android_arm64_armv8-a_static/gen/sysprop/public/include"
 
 	platformClient := result.ModuleForTests("cc-client-platform", coreVariant)
@@ -371,6 +371,6 @@
 	android.AssertStringEquals(t, "min_sdk_version forwarding to cc module", "29", propFromCc)
 
 	javaModule := result.ModuleForTests("sysprop-platform", "android_common").Module().(*java.Library)
-	propFromJava := javaModule.MinSdkVersion()
+	propFromJava := javaModule.MinSdkVersionString()
 	android.AssertStringEquals(t, "min_sdk_version forwarding to java module", "30", propFromJava)
 }
diff --git a/ui/build/bazel.go b/ui/build/bazel.go
index ec561d5..0ebfcd8 100644
--- a/ui/build/bazel.go
+++ b/ui/build/bazel.go
@@ -149,6 +149,9 @@
 		cmd.Args = append(cmd.Args, "--action_env=PATH="+pathEnvValue)
 	}
 
+	// Allow Bazel actions to see the SHELL variable (passed to Bazel above)
+	cmd.Args = append(cmd.Args, "--action_env=SHELL")
+
 	// Append custom build flags to the Bazel command. Changes to these flags
 	// may invalidate Bazel's analysis cache.
 	// These should be appended as the final args, so that they take precedence.
diff --git a/zip/Android.bp b/zip/Android.bp
index b28adbd..14541eb 100644
--- a/zip/Android.bp
+++ b/zip/Android.bp
@@ -25,6 +25,7 @@
         "android-archive-zip",
         "blueprint-pathtools",
         "soong-jar",
+        "soong-response",
     ],
     srcs: [
         "zip.go",
diff --git a/zip/cmd/main.go b/zip/cmd/main.go
index fc976f6..cbc73ed 100644
--- a/zip/cmd/main.go
+++ b/zip/cmd/main.go
@@ -24,7 +24,6 @@
 import (
 	"flag"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"runtime"
 	"runtime/pprof"
@@ -32,6 +31,7 @@
 	"strconv"
 	"strings"
 
+	"android/soong/response"
 	"android/soong/zip"
 )
 
@@ -125,12 +125,18 @@
 	var expandedArgs []string
 	for _, arg := range os.Args {
 		if strings.HasPrefix(arg, "@") {
-			bytes, err := ioutil.ReadFile(strings.TrimPrefix(arg, "@"))
+			f, err := os.Open(strings.TrimPrefix(arg, "@"))
 			if err != nil {
 				fmt.Fprintln(os.Stderr, err.Error())
 				os.Exit(1)
 			}
-			respArgs := zip.ReadRespFile(bytes)
+
+			respArgs, err := response.ReadRspFile(f)
+			f.Close()
+			if err != nil {
+				fmt.Fprintln(os.Stderr, err.Error())
+				os.Exit(1)
+			}
 			expandedArgs = append(expandedArgs, respArgs...)
 		} else {
 			expandedArgs = append(expandedArgs, arg)
diff --git a/zip/zip.go b/zip/zip.go
index 088ed0d..a6490d4 100644
--- a/zip/zip.go
+++ b/zip/zip.go
@@ -29,7 +29,8 @@
 	"sync"
 	"syscall"
 	"time"
-	"unicode"
+
+	"android/soong/response"
 
 	"github.com/google/blueprint/pathtools"
 
@@ -164,14 +165,12 @@
 	}
 	defer f.Close()
 
-	list, err := ioutil.ReadAll(f)
+	arg := b.state
+	arg.SourceFiles, err = response.ReadRspFile(f)
 	if err != nil {
 		b.err = err
 		return b
 	}
-
-	arg := b.state
-	arg.SourceFiles = ReadRespFile(list)
 	for i := range arg.SourceFiles {
 		arg.SourceFiles[i] = pathtools.MatchEscape(arg.SourceFiles[i])
 	}
@@ -253,49 +252,6 @@
 	Filesystem pathtools.FileSystem
 }
 
-const NOQUOTE = '\x00'
-
-func ReadRespFile(bytes []byte) []string {
-	var args []string
-	var arg []rune
-
-	isEscaping := false
-	quotingStart := NOQUOTE
-	for _, c := range string(bytes) {
-		switch {
-		case isEscaping:
-			if quotingStart == '"' {
-				if !(c == '"' || c == '\\') {
-					// '\"' or '\\' will be escaped under double quoting.
-					arg = append(arg, '\\')
-				}
-			}
-			arg = append(arg, c)
-			isEscaping = false
-		case c == '\\' && quotingStart != '\'':
-			isEscaping = true
-		case quotingStart == NOQUOTE && (c == '\'' || c == '"'):
-			quotingStart = c
-		case quotingStart != NOQUOTE && c == quotingStart:
-			quotingStart = NOQUOTE
-		case quotingStart == NOQUOTE && unicode.IsSpace(c):
-			// Current character is a space outside quotes
-			if len(arg) != 0 {
-				args = append(args, string(arg))
-			}
-			arg = arg[:0]
-		default:
-			arg = append(arg, c)
-		}
-	}
-
-	if len(arg) != 0 {
-		args = append(args, string(arg))
-	}
-
-	return args
-}
-
 func zipTo(args ZipArgs, w io.Writer) error {
 	if args.EmulateJar {
 		args.AddDirectoryEntriesToZip = true
diff --git a/zip/zip_test.go b/zip/zip_test.go
index b456ef8..a37ae41 100644
--- a/zip/zip_test.go
+++ b/zip/zip_test.go
@@ -535,78 +535,6 @@
 	}
 }
 
-func TestReadRespFile(t *testing.T) {
-	testCases := []struct {
-		name, in string
-		out      []string
-	}{
-		{
-			name: "single quoting test case 1",
-			in:   `./cmd '"'-C`,
-			out:  []string{"./cmd", `"-C`},
-		},
-		{
-			name: "single quoting test case 2",
-			in:   `./cmd '-C`,
-			out:  []string{"./cmd", `-C`},
-		},
-		{
-			name: "single quoting test case 3",
-			in:   `./cmd '\"'-C`,
-			out:  []string{"./cmd", `\"-C`},
-		},
-		{
-			name: "single quoting test case 4",
-			in:   `./cmd '\\'-C`,
-			out:  []string{"./cmd", `\\-C`},
-		},
-		{
-			name: "none quoting test case 1",
-			in:   `./cmd \'-C`,
-			out:  []string{"./cmd", `'-C`},
-		},
-		{
-			name: "none quoting test case 2",
-			in:   `./cmd \\-C`,
-			out:  []string{"./cmd", `\-C`},
-		},
-		{
-			name: "none quoting test case 3",
-			in:   `./cmd \"-C`,
-			out:  []string{"./cmd", `"-C`},
-		},
-		{
-			name: "double quoting test case 1",
-			in:   `./cmd "'"-C`,
-			out:  []string{"./cmd", `'-C`},
-		},
-		{
-			name: "double quoting test case 2",
-			in:   `./cmd "\\"-C`,
-			out:  []string{"./cmd", `\-C`},
-		},
-		{
-			name: "double quoting test case 3",
-			in:   `./cmd "\""-C`,
-			out:  []string{"./cmd", `"-C`},
-		},
-		{
-			name: "ninja rsp file",
-			in:   "'a'\nb\n'@'\n'foo'\\''bar'\n'foo\"bar'",
-			out:  []string{"a", "b", "@", "foo'bar", `foo"bar`},
-		},
-	}
-
-	for _, testCase := range testCases {
-		t.Run(testCase.name, func(t *testing.T) {
-			got := ReadRespFile([]byte(testCase.in))
-			if !reflect.DeepEqual(got, testCase.out) {
-				t.Errorf("expected %q got %q", testCase.out, got)
-			}
-		})
-	}
-}
-
 func TestSrcJar(t *testing.T) {
 	mockFs := pathtools.MockFs(map[string][]byte{
 		"wrong_package.java":       []byte("package foo;"),