Merge "Create Make flags to set source tree as ReadOnly in soong builds"
diff --git a/android/bazel.go b/android/bazel.go
index ef770bf..8c68055 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -220,15 +220,7 @@
 	// Per-module denylist to opt modules out of mixed builds. Such modules will
 	// still be generated via bp2build.
 	mixedBuildsDisabledList = []string{
-		"libc_common",                      // cparsons@ cc_library_static, depends on //bionic/libc:libc_nopthread
-		"libc_common_static",               // cparsons@ cc_library_static, depends on //bionic/libc:libc_common
-		"libc_common_shared",               // cparsons@ cc_library_static, depends on //bionic/libc:libc_common
-		"libc_netbsd",                      // lberki@, cc_library_static, version script assignment of 'LIBC_PRIVATE' to symbol 'SHA1Final' failed: symbol not defined
-		"libc_nopthread",                   // cparsons@ cc_library_static, version script assignment of 'LIBC' to symbol 'memcmp' failed: symbol not defined
-		"libc_openbsd",                     // ruperts@, cc_library_static, OK for bp2build but error: duplicate symbol: strcpy for mixed builds
-		"libarm-optimized-routines-string", // jingwen@, cc_library_static, OK for bp2build but b/186615213 (asflags not handled in  bp2build), version script assignment of 'LIBC' to symbol 'memcmp' failed: symbol not defined (also for memrchr, strnlen)
-		"fmtlib_ndk",                       // http://b/187040371, cc_library_static, OK for bp2build but format-inl.h:11:10: fatal error: 'cassert' file not found for mixed builds
-		"libc_nomalloc",                    // cc_library_static, OK for bp2build but ld.lld: error: undefined symbol: pthread_mutex_lock (and others)
+		// Currently empty, though should remain present to facilitate granular bp2build migration.
 	}
 
 	// Used for quicker lookups
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index a1206dc..f906c8a 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -351,6 +351,9 @@
 		"HOME="+paths.homeDir,
 		pwdPrefix(),
 		"BUILD_DIR="+absolutePath(paths.buildDir),
+		// Make OUT_DIR absolute here so tools/bazel.sh uses the correct
+		// OUT_DIR at <root>/out, instead of <root>/out/soong/workspace/out.
+		"OUT_DIR="+absolutePath(paths.outDir()),
 		// Disables local host detection of gcc; toolchain information is defined
 		// explicitly in BUILD files.
 		"BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1")
@@ -576,6 +579,11 @@
 	return filepath.Join(p.buildDir, "workspace")
 }
 
+// Returns the path to the top level out dir ($OUT_DIR).
+func (p *bazelPaths) outDir() string {
+	return filepath.Dir(p.buildDir)
+}
+
 // Issues commands to Bazel to receive results for all cquery requests
 // queued in the BazelContext.
 func (context *bazelContext) InvokeBazel() error {
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 4d00944..6a7c35c 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -709,6 +709,79 @@
 	}
 }
 
+func TestApexManifestMinSdkVersion(t *testing.T) {
+	ctx := testApex(t, `
+		apex_defaults {
+			name: "my_defaults",
+			key: "myapex.key",
+			product_specific: true,
+			file_contexts: ":my-file-contexts",
+			updatable: false,
+		}
+		apex {
+			name: "myapex_30",
+			min_sdk_version: "30",
+			defaults: ["my_defaults"],
+		}
+
+		apex {
+			name: "myapex_current",
+			min_sdk_version: "current",
+			defaults: ["my_defaults"],
+		}
+
+		apex {
+			name: "myapex_none",
+			defaults: ["my_defaults"],
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		filegroup {
+			name: "my-file-contexts",
+			srcs: ["product_specific_file_contexts"],
+		}
+	`, withFiles(map[string][]byte{
+		"product_specific_file_contexts": nil,
+	}), android.FixtureModifyProductVariables(
+		func(variables android.FixtureProductVariables) {
+			variables.Unbundled_build = proptools.BoolPtr(true)
+			variables.Always_use_prebuilt_sdks = proptools.BoolPtr(false)
+		}), android.FixtureMergeEnv(map[string]string{
+		"UNBUNDLED_BUILD_TARGET_SDK_WITH_API_FINGERPRINT": "true",
+	}))
+
+	testCases := []struct {
+		module        string
+		minSdkVersion string
+	}{
+		{
+			module:        "myapex_30",
+			minSdkVersion: "30",
+		},
+		{
+			module:        "myapex_current",
+			minSdkVersion: "Q.$$(cat out/soong/api_fingerprint.txt)",
+		},
+		{
+			module:        "myapex_none",
+			minSdkVersion: "Q.$$(cat out/soong/api_fingerprint.txt)",
+		},
+	}
+	for _, tc := range testCases {
+		module := ctx.ModuleForTests(tc.module, "android_common_"+tc.module+"_image")
+		args := module.Rule("apexRule").Args
+		optFlags := args["opt_flags"]
+		if !strings.Contains(optFlags, "--min_sdk_version "+tc.minSdkVersion) {
+			t.Errorf("%s: Expected min_sdk_version=%s, got: %s", tc.module, tc.minSdkVersion, optFlags)
+		}
+	}
+}
+
 func TestBasicZipApex(t *testing.T) {
 	ctx := testApex(t, `
 		apex {
diff --git a/apex/builder.go b/apex/builder.go
index da8841c..021e499 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -602,6 +602,11 @@
 		// codename
 		if moduleMinSdkVersion.IsCurrent() || moduleMinSdkVersion.IsNone() {
 			minSdkVersion = ctx.Config().DefaultAppTargetSdk(ctx).String()
+
+			if java.UseApiFingerprint(ctx) {
+				minSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", java.ApiFingerprintPath(ctx).String())
+				implicitInputs = append(implicitInputs, java.ApiFingerprintPath(ctx))
+			}
 		}
 		// apex module doesn't have a concept of target_sdk_version, hence for the time
 		// being targetSdkVersion == default targetSdkVersion of the branch.
@@ -611,10 +616,6 @@
 			targetSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", java.ApiFingerprintPath(ctx).String())
 			implicitInputs = append(implicitInputs, java.ApiFingerprintPath(ctx))
 		}
-		if java.UseApiFingerprint(ctx) {
-			minSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", java.ApiFingerprintPath(ctx).String())
-			implicitInputs = append(implicitInputs, java.ApiFingerprintPath(ctx))
-		}
 		optFlags = append(optFlags, "--target_sdk_version "+targetSdkVersion)
 		optFlags = append(optFlags, "--min_sdk_version "+minSdkVersion)
 
diff --git a/bazel/properties.go b/bazel/properties.go
index b9d6ead..b2d68da 100644
--- a/bazel/properties.go
+++ b/bazel/properties.go
@@ -307,19 +307,19 @@
 	}
 
 	PlatformTargetMap = map[string]string{
-		TARGET_ANDROID_ARM:         "//build/bazel/platforms:android_arm",
-		TARGET_ANDROID_ARM64:       "//build/bazel/platforms:android_arm64",
-		TARGET_ANDROID_X86:         "//build/bazel/platforms:android_x86",
-		TARGET_ANDROID_X86_64:      "//build/bazel/platforms:android_x86_64",
-		TARGET_DARWIN_X86_64:       "//build/bazel/platforms:darwin_x86_64",
-		TARGET_FUCHSIA_ARM64:       "//build/bazel/platforms:fuchsia_arm64",
-		TARGET_FUCHSIA_X86_64:      "//build/bazel/platforms:fuchsia_x86_64",
-		TARGET_LINUX_X86:           "//build/bazel/platforms:linux_glibc_x86",
-		TARGET_LINUX_x86_64:        "//build/bazel/platforms:linux_glibc_x86_64",
-		TARGET_LINUX_BIONIC_ARM64:  "//build/bazel/platforms:linux_bionic_arm64",
-		TARGET_LINUX_BIONIC_X86_64: "//build/bazel/platforms:linux_bionic_x86_64",
-		TARGET_WINDOWS_X86:         "//build/bazel/platforms:windows_x86",
-		TARGET_WINDOWS_X86_64:      "//build/bazel/platforms:windows_x86_64",
+		TARGET_ANDROID_ARM:         "//build/bazel/platforms/os_arch:android_arm",
+		TARGET_ANDROID_ARM64:       "//build/bazel/platforms/os_arch:android_arm64",
+		TARGET_ANDROID_X86:         "//build/bazel/platforms/os_arch:android_x86",
+		TARGET_ANDROID_X86_64:      "//build/bazel/platforms/os_arch:android_x86_64",
+		TARGET_DARWIN_X86_64:       "//build/bazel/platforms/os_arch:darwin_x86_64",
+		TARGET_FUCHSIA_ARM64:       "//build/bazel/platforms/os_arch:fuchsia_arm64",
+		TARGET_FUCHSIA_X86_64:      "//build/bazel/platforms/os_arch:fuchsia_x86_64",
+		TARGET_LINUX_X86:           "//build/bazel/platforms/os_arch:linux_glibc_x86",
+		TARGET_LINUX_x86_64:        "//build/bazel/platforms/os_arch:linux_glibc_x86_64",
+		TARGET_LINUX_BIONIC_ARM64:  "//build/bazel/platforms/os_arch:linux_bionic_arm64",
+		TARGET_LINUX_BIONIC_X86_64: "//build/bazel/platforms/os_arch:linux_bionic_x86_64",
+		TARGET_WINDOWS_X86:         "//build/bazel/platforms/os_arch:windows_x86",
+		TARGET_WINDOWS_X86_64:      "//build/bazel/platforms/os_arch:windows_x86_64",
 		CONDITIONS_DEFAULT:         ConditionsDefaultSelectKey, // The default condition of an os select map.
 	}
 
@@ -993,6 +993,6 @@
 // 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+"}")
+	sub := productVariableSubstitutionPattern.ReplaceAllString(s, "$("+productVariable+")")
 	return sub, s != sub
 }
diff --git a/bp2build/cc_library_conversion_test.go b/bp2build/cc_library_conversion_test.go
index 49f1f42..b87d713 100644
--- a/bp2build/cc_library_conversion_test.go
+++ b/bp2build/cc_library_conversion_test.go
@@ -497,7 +497,7 @@
         "//build/bazel/platforms/os:android": ["-DANDROID_SHARED"],
         "//conditions:default": [],
     }) + select({
-        "//build/bazel/platforms:android_arm": ["-DANDROID_ARM_SHARED"],
+        "//build/bazel/platforms/os_arch:android_arm": ["-DANDROID_ARM_SHARED"],
         "//conditions:default": [],
     }),
     shared_srcs = ["sharedonly.cpp"] + select({
@@ -844,8 +844,8 @@
     ],
     srcs = ["a.cpp"],
     version_script = select({
-        "//build/bazel/platforms:android_arm": "android_arm.map",
-        "//build/bazel/platforms:linux_bionic_arm64": "linux_bionic_arm64.map",
+        "//build/bazel/platforms/os_arch:android_arm": "android_arm.map",
+        "//build/bazel/platforms/os_arch:linux_bionic_arm64": "linux_bionic_arm64.map",
         "//conditions:default": None,
     }),
 )`},
diff --git a/bp2build/cc_library_static_conversion_test.go b/bp2build/cc_library_static_conversion_test.go
index 7709254..da38adb 100644
--- a/bp2build/cc_library_static_conversion_test.go
+++ b/bp2build/cc_library_static_conversion_test.go
@@ -1282,14 +1282,14 @@
         "//build/bazel/platforms/os:android": ["android_src.c"],
         "//conditions:default": [],
     }) + select({
-        "//build/bazel/platforms:android_arm": ["android_arm_src.c"],
-        "//build/bazel/platforms:android_arm64": ["android_arm64_src.c"],
-        "//build/bazel/platforms:android_x86": ["android_x86_src.c"],
-        "//build/bazel/platforms:android_x86_64": ["android_x86_64_src.c"],
+        "//build/bazel/platforms/os_arch:android_arm": ["android_arm_src.c"],
+        "//build/bazel/platforms/os_arch:android_arm64": ["android_arm64_src.c"],
+        "//build/bazel/platforms/os_arch:android_x86": ["android_x86_src.c"],
+        "//build/bazel/platforms/os_arch:android_x86_64": ["android_x86_64_src.c"],
         "//conditions:default": [],
     }) + select({
-        "//build/bazel/platforms:linux_bionic_arm64": ["linux_bionic_arm64_src.c"],
-        "//build/bazel/platforms:linux_bionic_x86_64": ["linux_bionic_x86_64_src.c"],
+        "//build/bazel/platforms/os_arch:linux_bionic_arm64": ["linux_bionic_arm64_src.c"],
+        "//build/bazel/platforms/os_arch:linux_bionic_x86_64": ["linux_bionic_x86_64_src.c"],
         "//conditions:default": [],
     }),
 )`},
@@ -1412,3 +1412,37 @@
 )`},
 	})
 }
+
+func TestCcLibraryStaticProductVariableStringReplacement(t *testing.T) {
+	runCcLibraryStaticTestCase(t, bp2buildTestCase{
+		description:                        "cc_library_static product variable selects",
+		moduleTypeUnderTest:                "cc_library_static",
+		moduleTypeUnderTestFactory:         cc.LibraryStaticFactory,
+		moduleTypeUnderTestBp2BuildMutator: cc.CcLibraryStaticBp2Build,
+		depsMutators:                       []android.RegisterMutatorFunc{cc.RegisterDepsBp2Build},
+		filesystem:                         map[string]string{},
+		blueprint: soongCcLibraryStaticPreamble + `
+cc_library_static {
+    name: "foo_static",
+    srcs: ["common.c"],
+    product_variables: {
+      platform_sdk_version: {
+          asflags: ["-DPLATFORM_SDK_VERSION=%d"],
+      },
+    },
+} `,
+		expectedBazelTargets: []string{`cc_library_static(
+    name = "foo_static",
+    asflags = select({
+        "//build/bazel/product_variables:platform_sdk_version": ["-DPLATFORM_SDK_VERSION=$(Platform_sdk_version)"],
+        "//conditions:default": [],
+    }),
+    copts = [
+        "-I.",
+        "-I$(BINDIR)/.",
+    ],
+    linkstatic = True,
+    srcs_c = ["common.c"],
+)`},
+	})
+}
diff --git a/bp2build/cc_object_conversion_test.go b/bp2build/cc_object_conversion_test.go
index f7e94c2..57f75ea 100644
--- a/bp2build/cc_object_conversion_test.go
+++ b/bp2build/cc_object_conversion_test.go
@@ -210,7 +210,7 @@
 		expectedBazelTargets: []string{`cc_object(
     name = "foo",
     asflags = select({
-        "//build/bazel/product_variables:platform_sdk_version": ["-DPLATFORM_SDK_VERSION={Platform_sdk_version}"],
+        "//build/bazel/product_variables:platform_sdk_version": ["-DPLATFORM_SDK_VERSION=$(Platform_sdk_version)"],
         "//conditions:default": [],
     }),
     copts = ["-fno-addrsig"],
diff --git a/build_test.bash b/build_test.bash
index 296a79c..b039285 100755
--- a/build_test.bash
+++ b/build_test.bash
@@ -54,7 +54,7 @@
 
 echo
 echo "Running Bazel smoke test..."
-"${TOP}/tools/bazel" --batch --max_idle_secs=1 info
+STANDALONE_BAZEL=true "${TOP}/tools/bazel" --batch --max_idle_secs=1 info
 
 echo
 echo "Running Soong test..."
diff --git a/cc/bp2build.go b/cc/bp2build.go
index 5d024f8..fed9936 100644
--- a/cc/bp2build.go
+++ b/cc/bp2build.go
@@ -19,6 +19,8 @@
 
 	"android/soong/android"
 	"android/soong/bazel"
+
+	"github.com/google/blueprint/proptools"
 )
 
 // bp2build functions and helpers for converting cc_* modules to Bazel.
@@ -266,6 +268,66 @@
 	return attrs
 }
 
+// Convenience struct to hold all attributes parsed from prebuilt properties.
+type prebuiltAttributes struct {
+	Src bazel.LabelAttribute
+}
+
+func Bp2BuildParsePrebuiltLibraryProps(ctx android.TopDownMutatorContext, module *Module) prebuiltAttributes {
+	prebuiltLibraryLinker := module.linker.(*prebuiltLibraryLinker)
+	prebuiltLinker := prebuiltLibraryLinker.prebuiltLinker
+
+	var srcLabelAttribute bazel.LabelAttribute
+
+	if len(prebuiltLinker.properties.Srcs) > 1 {
+		ctx.ModuleErrorf("Bp2BuildParsePrebuiltLibraryProps: Expected at most once source file\n")
+	}
+
+	if len(prebuiltLinker.properties.Srcs) == 1 {
+		srcLabelAttribute.Value = android.BazelLabelForModuleSrcSingle(ctx, prebuiltLinker.properties.Srcs[0])
+		for arch, props := range module.GetArchProperties(ctx, &prebuiltLinkerProperties{}) {
+			if prebuiltLinkerProperties, ok := props.(*prebuiltLinkerProperties); ok {
+				if len(prebuiltLinkerProperties.Srcs) > 1 {
+					ctx.ModuleErrorf("Bp2BuildParsePrebuiltLibraryProps: Expected at most once source file for arch %s\n", arch.Name)
+				}
+				if len(prebuiltLinkerProperties.Srcs) == 1 {
+					srcLabelAttribute.SetValueForArch(arch.Name, android.BazelLabelForModuleSrcSingle(ctx, prebuiltLinkerProperties.Srcs[0]))
+				}
+			}
+		}
+	}
+
+	for os, targetProperties := range module.GetTargetProperties(ctx, &prebuiltLinkerProperties{}) {
+		if prebuiltLinkerProperties, ok := targetProperties.Properties.(*prebuiltLinkerProperties); ok {
+			if len(prebuiltLinkerProperties.Srcs) > 1 {
+				ctx.ModuleErrorf("Bp2BuildParsePrebuiltLibraryProps: Expected at most once source file for os %s\n", os.Name)
+
+			}
+
+			if len(prebuiltLinkerProperties.Srcs) == 1 {
+				srcLabelAttribute.SetOsValueForTarget(os.Name, android.BazelLabelForModuleSrcSingle(ctx, prebuiltLinkerProperties.Srcs[0]))
+			}
+		}
+		for arch, archProperties := range targetProperties.ArchProperties {
+			if prebuiltLinkerProperties, ok := archProperties.(*prebuiltLinkerProperties); ok {
+				if len(prebuiltLinkerProperties.Srcs) > 1 {
+					ctx.ModuleErrorf("Bp2BuildParsePrebuiltLibraryProps: Expected at most once source file for os_arch %s_%s\n", os.Name, arch.Name)
+
+				}
+
+				if len(prebuiltLinkerProperties.Srcs) == 1 {
+					srcLabelAttribute.SetOsArchValueForTarget(os.Name, arch.Name, android.BazelLabelForModuleSrcSingle(ctx, prebuiltLinkerProperties.Srcs[0]))
+				}
+			}
+
+		}
+	}
+
+	return prebuiltAttributes{
+		Src: srcLabelAttribute,
+	}
+}
+
 // Convenience struct to hold all attributes parsed from compiler properties.
 type compilerAttributes struct {
 	// Options for all languages
@@ -444,18 +506,25 @@
 		}
 	}
 
+	productVarPropNameToAttribute := map[string]*bazel.StringListAttribute{
+		"Cflags":   &copts,
+		"Asflags":  &asFlags,
+		"CppFlags": &cppFlags,
+	}
 	productVariableProps := android.ProductVariableProperties(ctx)
-	if props, exists := productVariableProps["Cflags"]; exists {
-		for _, prop := range props {
-			flags, ok := prop.Property.([]string)
-			if !ok {
-				ctx.ModuleErrorf("Could not convert product variable cflag property")
+	for propName, attr := range productVarPropNameToAttribute {
+		if props, exists := productVariableProps[propName]; exists {
+			for _, prop := range props {
+				flags, ok := prop.Property.([]string)
+				if !ok {
+					ctx.ModuleErrorf("Could not convert product variable %s property", proptools.PropertyNameForField(propName))
+				}
+				newFlags, _ := bazel.TryVariableSubstitutions(flags, prop.ProductConfigVariable)
+				attr.ProductValues = append(attr.ProductValues, bazel.ProductVariableValues{
+					ProductVariable: prop.ProductConfigVariable,
+					Values:          newFlags,
+				})
 			}
-			newFlags, _ := bazel.TryVariableSubstitutions(flags, prop.ProductConfigVariable)
-			copts.ProductValues = append(copts.ProductValues, bazel.ProductVariableValues{
-				ProductVariable: prop.ProductConfigVariable,
-				Values:          newFlags,
-			})
 		}
 	}
 
@@ -633,11 +702,20 @@
 	return relativePaths
 }
 
-// bp2BuildParseExportedIncludes creates a string list attribute contains the
-// exported included directories of a module.
 func bp2BuildParseExportedIncludes(ctx android.TopDownMutatorContext, module *Module) bazel.StringListAttribute {
 	libraryDecorator := module.linker.(*libraryDecorator)
+	return bp2BuildParseExportedIncludesHelper(ctx, module, libraryDecorator)
+}
 
+func Bp2BuildParseExportedIncludesForPrebuiltLibrary(ctx android.TopDownMutatorContext, module *Module) bazel.StringListAttribute {
+	prebuiltLibraryLinker := module.linker.(*prebuiltLibraryLinker)
+	libraryDecorator := prebuiltLibraryLinker.libraryDecorator
+	return bp2BuildParseExportedIncludesHelper(ctx, module, libraryDecorator)
+}
+
+// bp2BuildParseExportedIncludes creates a string list attribute contains the
+// exported included directories of a module.
+func bp2BuildParseExportedIncludesHelper(ctx android.TopDownMutatorContext, module *Module, libraryDecorator *libraryDecorator) bazel.StringListAttribute {
 	// Export_system_include_dirs and export_include_dirs are already module dir
 	// relative, so they don't need to be relativized like include_dirs, which
 	// are root-relative.
diff --git a/cc/builder.go b/cc/builder.go
index 51c8a0b..fae9522 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -534,7 +534,7 @@
 				Implicits:   cFlagsDeps,
 				OrderOnly:   pathDeps,
 				Args: map[string]string{
-					"windresCmd": gccCmd(flags.toolchain, "windres"),
+					"windresCmd": mingwCmd(flags.toolchain, "windres"),
 					"flags":      flags.toolchain.WindresFlags(),
 				},
 			})
@@ -1069,6 +1069,6 @@
 	})
 }
 
-func gccCmd(toolchain config.Toolchain, cmd string) string {
+func mingwCmd(toolchain config.Toolchain, cmd string) string {
 	return filepath.Join(toolchain.GccRoot(), "bin", toolchain.GccTriple()+"-"+cmd)
 }
diff --git a/cc/cc.go b/cc/cc.go
index aa218bf..91c4417 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -726,7 +726,6 @@
 	runtimeDepTag         = installDependencyTag{name: "runtime lib"}
 	testPerSrcDepTag      = dependencyTag{name: "test_per_src"}
 	stubImplDepTag        = dependencyTag{name: "stub_impl"}
-	llndkStubDepTag       = dependencyTag{name: "llndk stub"}
 )
 
 func IsSharedDepTag(depTag blueprint.DependencyTag) bool {
@@ -3238,8 +3237,8 @@
 			return false
 		}
 	}
-	if depTag == stubImplDepTag || depTag == llndkStubDepTag {
-		// We don't track beyond LLNDK or from an implementation library to its stubs.
+	if depTag == stubImplDepTag {
+		// We don't track from an implementation library to its stubs.
 		return false
 	}
 	if depTag == staticVariantTag {
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 5acafbe..7fc044d 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -2799,12 +2799,8 @@
 		}
 	}
 	expected := []string{
-		"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",
 	}
@@ -2813,9 +2809,6 @@
 	params := result.ModuleForTests("libllndk", "android_vendor.29_arm_armv7-a-neon_shared").Description("generate stub")
 	android.AssertSame(t, "use VNDK version for default stubs", "current", params.Args["apiLevel"])
 
-	params = result.ModuleForTests("libllndk", "android_vendor.29_arm_armv7-a-neon_shared_1").Description("generate stub")
-	android.AssertSame(t, "override apiLevel for versioned stubs", "1", params.Args["apiLevel"])
-
 	checkExportedIncludeDirs := func(module, variant string, expectedDirs ...string) {
 		t.Helper()
 		m := result.ModuleForTests(module, variant).Module()
diff --git a/cc/gen.go b/cc/gen.go
index b152e02..3a1a0e2 100644
--- a/cc/gen.go
+++ b/cc/gen.go
@@ -204,7 +204,7 @@
 	headerFile := android.GenPathWithExt(ctx, "windmc", srcFile, "h")
 	rcFile := android.GenPathWithExt(ctx, "windmc", srcFile, "rc")
 
-	windmcCmd := gccCmd(flags.toolchain, "windmc")
+	windmcCmd := mingwCmd(flags.toolchain, "windmc")
 
 	ctx.Build(pctx, android.BuildParams{
 		Rule:           windmc,
diff --git a/cc/library.go b/cc/library.go
index 9fb7a24..5e70c51 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -1813,6 +1813,11 @@
 		return nil
 	}
 
+	if library.hasLLNDKStubs() && ctx.Module().(*Module).UseVndk() {
+		// LLNDK libraries only need a single stubs variant.
+		return []string{android.FutureApiLevel.String()}
+	}
+
 	// Future API level is implicitly added if there isn't
 	vers := library.Properties.Stubs.Versions
 	if inList(android.FutureApiLevel.String(), vers) {
@@ -2154,8 +2159,7 @@
 	return nil
 }
 
-// versionSelector normalizes the versions in the Stubs.Versions property into MutatedProperties.AllStubsVersions,
-// and propagates the value from implementation libraries to llndk libraries with the same name.
+// versionSelector normalizes the versions in the Stubs.Versions property into MutatedProperties.AllStubsVersions.
 func versionSelectorMutator(mctx android.BottomUpMutatorContext) {
 	if library := moduleLibraryInterface(mctx.Module()); library != nil && CanBeVersionVariant(mctx.Module().(*Module)) {
 		if library.buildShared() {
@@ -2169,15 +2173,6 @@
 				// depend on the implementation library and haven't been mutated yet.
 				library.setAllStubsVersions(versions)
 			}
-
-			if mctx.Module().(*Module).UseVndk() && library.hasLLNDKStubs() {
-				// Propagate the version to the llndk stubs module.
-				mctx.VisitDirectDepsWithTag(llndkStubDepTag, func(stubs android.Module) {
-					if stubsLib := moduleLibraryInterface(stubs); stubsLib != nil {
-						stubsLib.setAllStubsVersions(library.allStubsVersions())
-					}
-				})
-			}
 		}
 	}
 }
diff --git a/cc/object.go b/cc/object.go
index cd71161..39fc43d 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -157,8 +157,6 @@
 
 	// Set arch-specific configurable attributes
 	compilerAttrs := bp2BuildParseCompilerProps(ctx, m)
-	var asFlags bazel.StringListAttribute
-
 	var deps bazel.LabelListAttribute
 	for _, props := range m.linker.linkerProps() {
 		if objectLinkerProps, ok := props.(*ObjectLinkerProperties); ok {
@@ -167,24 +165,6 @@
 		}
 	}
 
-	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
-			}
-			newFlags, _ := bazel.TryVariableSubstitutions(flags, prop.ProductConfigVariable)
-			asFlags.ProductValues = append(asFlags.ProductValues, bazel.ProductVariableValues{
-				ProductVariable: prop.ProductConfigVariable,
-				Values:          newFlags,
-			})
-		}
-	}
-	// TODO(b/183595872) warn/error if we're not handling product variables
-
 	// Don't split cc_object srcs across languages. Doing so would add complexity,
 	// and this isn't typically done for cc_object.
 	srcs := compilerAttrs.srcs
@@ -195,7 +175,7 @@
 		Srcs:    srcs,
 		Deps:    deps,
 		Copts:   compilerAttrs.copts,
-		Asflags: asFlags,
+		Asflags: compilerAttrs.asFlags,
 	}
 
 	props := bazel.BazelTargetModuleProperties{
diff --git a/cc/snapshot_prebuilt.go b/cc/snapshot_prebuilt.go
index a7351a9..bf11b11 100644
--- a/cc/snapshot_prebuilt.go
+++ b/cc/snapshot_prebuilt.go
@@ -453,30 +453,49 @@
 	return p.baseProperties.Androidmk_suffix
 }
 
-func (p *baseSnapshotDecorator) setSnapshotAndroidMkSuffix(ctx android.ModuleContext) {
-	coreVariations := append(ctx.Target().Variations(), blueprint.Variation{
+func (p *baseSnapshotDecorator) setSnapshotAndroidMkSuffix(ctx android.ModuleContext, variant string) {
+	// If there are any 2 or more variations among {core, product, vendor, recovery}
+	// we have to add the androidmk suffix to avoid duplicate modules with the same
+	// name.
+	variations := append(ctx.Target().Variations(), blueprint.Variation{
 		Mutator:   "image",
 		Variation: android.CoreVariation})
 
-	if ctx.OtherModuleFarDependencyVariantExists(coreVariations, ctx.Module().(*Module).BaseModuleName()) {
+	if ctx.OtherModuleFarDependencyVariantExists(variations, ctx.Module().(*Module).BaseModuleName()) {
 		p.baseProperties.Androidmk_suffix = p.image.moduleNameSuffix()
 		return
 	}
 
-	// If there is no matching core variation, there could still be a
-	// product variation, for example if a module is product specific and
-	// vendor available. In that case, we also want to add the androidmk
-	// suffix.
-
-	productVariations := append(ctx.Target().Variations(), blueprint.Variation{
+	variations = append(ctx.Target().Variations(), blueprint.Variation{
 		Mutator:   "image",
 		Variation: ProductVariationPrefix + ctx.DeviceConfig().PlatformVndkVersion()})
 
-	if ctx.OtherModuleFarDependencyVariantExists(productVariations, ctx.Module().(*Module).BaseModuleName()) {
+	if ctx.OtherModuleFarDependencyVariantExists(variations, ctx.Module().(*Module).BaseModuleName()) {
 		p.baseProperties.Androidmk_suffix = p.image.moduleNameSuffix()
 		return
 	}
 
+	images := []snapshotImage{vendorSnapshotImageSingleton, recoverySnapshotImageSingleton}
+
+	for _, image := range images {
+		if p.image == image {
+			continue
+		}
+		variations = append(ctx.Target().Variations(), blueprint.Variation{
+			Mutator:   "image",
+			Variation: image.imageVariantName(ctx.DeviceConfig())})
+
+		if ctx.OtherModuleFarDependencyVariantExists(variations,
+			ctx.Module().(*Module).BaseModuleName()+
+				getSnapshotNameSuffix(
+					image.moduleNameSuffix()+variant,
+					p.version(),
+					ctx.DeviceConfig().Arches()[0].ArchType.String())) {
+			p.baseProperties.Androidmk_suffix = p.image.moduleNameSuffix()
+			return
+		}
+	}
+
 	p.baseProperties.Androidmk_suffix = ""
 }
 
@@ -566,7 +585,16 @@
 // 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)
+	var variant string
+	if p.shared() {
+		variant = snapshotSharedSuffix
+	} else if p.static() {
+		variant = snapshotStaticSuffix
+	} else {
+		variant = snapshotHeaderSuffix
+	}
+
+	p.setSnapshotAndroidMkSuffix(ctx, variant)
 
 	if p.header() {
 		return p.libraryDecorator.link(ctx, flags, deps, objs)
@@ -784,7 +812,7 @@
 // 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)
+	p.setSnapshotAndroidMkSuffix(ctx, snapshotBinarySuffix)
 
 	if !p.matchesWithDevice(ctx.DeviceConfig()) {
 		return nil
@@ -879,7 +907,7 @@
 // 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)
+	p.setSnapshotAndroidMkSuffix(ctx, snapshotObjectSuffix)
 
 	if !p.matchesWithDevice(ctx.DeviceConfig()) {
 		return nil
diff --git a/cmd/multiproduct_kati/main.go b/cmd/multiproduct_kati/main.go
index e0f8caa..20f146a 100644
--- a/cmd/multiproduct_kati/main.go
+++ b/cmd/multiproduct_kati/main.go
@@ -433,7 +433,7 @@
 	config := build.NewConfig(ctx, args...)
 	config.Environment().Set("OUT_DIR", outDir)
 	if !*keepArtifacts {
-		config.Environment().Set("EMPTY_NINJA_FILE", "true")
+		config.SetEmptyNinjaFile(true)
 	}
 	build.FindSources(ctx, config, mpctx.Finder)
 	config.Lunch(ctx, product, *buildVariant)
diff --git a/java/androidmk.go b/java/androidmk.go
index 0154544..04357e0 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -460,6 +460,8 @@
 	entries := &entriesList[0]
 	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 		testSuiteComponent(entries, a.appTestHelperAppProperties.Test_suites)
+		// introduce a flag variable to control the generation of the .config file
+		entries.SetString("LOCAL_DISABLE_TEST_CONFIG", "true")
 	})
 
 	return entriesList
diff --git a/java/androidmk_test.go b/java/androidmk_test.go
index 5eaa77b..246c0eb 100644
--- a/java/androidmk_test.go
+++ b/java/androidmk_test.go
@@ -188,3 +188,21 @@
 
 	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_SOONG_DEX_JAR", result.Config, []string{expectedSoongDexJar}, actualSoongDexJar)
 }
+
+func TestAndroidTestHelperApp_LocalDisableTestConfig(t *testing.T) {
+	ctx, _ := testJava(t, `
+		android_test_helper_app {
+			name: "foo",
+			srcs: ["a.java"],
+		}
+	`)
+
+	mod := ctx.ModuleForTests("foo", "android_common").Module()
+	entries := android.AndroidMkEntriesForTest(t, ctx, mod)[0]
+
+	expected := []string{"true"}
+	actual := entries.EntryMap["LOCAL_DISABLE_TEST_CONFIG"]
+	if !reflect.DeepEqual(expected, actual) {
+		t.Errorf("Unexpected flag value - expected: %q, actual: %q", expected, actual)
+	}
+}
diff --git a/java/droidstubs.go b/java/droidstubs.go
index 9531056..17c7a7b 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -51,7 +51,6 @@
 	lastReleasedApiXmlFile  android.WritablePath
 	privateApiFile          android.WritablePath
 	removedApiFile          android.WritablePath
-	removedDexApiFile       android.WritablePath
 	nullabilityWarningsFile android.WritablePath
 
 	checkCurrentApiTimestamp      android.WritablePath
@@ -79,9 +78,6 @@
 	// 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
 
@@ -274,11 +270,6 @@
 		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())
diff --git a/java/java_test.go b/java/java_test.go
index 1b8aec2..bd373c1 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -19,7 +19,6 @@
 	"os"
 	"path/filepath"
 	"reflect"
-	"regexp"
 	"runtime"
 	"strconv"
 	"strings"
@@ -655,306 +654,6 @@
 	})
 }
 
-func TestJavaSdkLibraryImport(t *testing.T) {
-	result := prepareForJavaTest.RunTestWithBp(t, `
-		java_library {
-			name: "foo",
-			srcs: ["a.java"],
-			libs: ["sdklib"],
-			sdk_version: "current",
-		}
-
-		java_library {
-			name: "foo.system",
-			srcs: ["a.java"],
-			libs: ["sdklib"],
-			sdk_version: "system_current",
-		}
-
-		java_library {
-			name: "foo.test",
-			srcs: ["a.java"],
-			libs: ["sdklib"],
-			sdk_version: "test_current",
-		}
-
-		java_sdk_library_import {
-			name: "sdklib",
-			public: {
-				jars: ["a.jar"],
-			},
-			system: {
-				jars: ["b.jar"],
-			},
-			test: {
-				jars: ["c.jar"],
-				stub_srcs: ["c.java"],
-			},
-		}
-		`)
-
-	for _, scope := range []string{"", ".system", ".test"} {
-		fooModule := result.ModuleForTests("foo"+scope, "android_common")
-		javac := fooModule.Rule("javac")
-
-		sdklibStubsJar := result.ModuleForTests("sdklib.stubs"+scope, "android_common").Rule("combineJar").Output
-		android.AssertStringDoesContain(t, "foo classpath", javac.Args["classpath"], sdklibStubsJar.String())
-	}
-
-	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
-		`prebuilt_sdklib.stubs`,
-		`prebuilt_sdklib.stubs.source.test`,
-		`prebuilt_sdklib.stubs.system`,
-		`prebuilt_sdklib.stubs.test`,
-	})
-}
-
-func TestJavaSdkLibraryImport_WithSource(t *testing.T) {
-	result := android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("sdklib"),
-	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "sdklib",
-			srcs: ["a.java"],
-			sdk_version: "none",
-			system_modules: "none",
-			public: {
-				enabled: true,
-			},
-		}
-
-		java_sdk_library_import {
-			name: "sdklib",
-			public: {
-				jars: ["a.jar"],
-			},
-		}
-		`)
-
-	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
-		`dex2oatd`,
-		`prebuilt_sdklib`,
-		`sdklib.impl`,
-		`sdklib.stubs`,
-		`sdklib.stubs.source`,
-		`sdklib.xml`,
-	})
-
-	CheckModuleDependencies(t, result.TestContext, "prebuilt_sdklib", "android_common", []string{
-		`prebuilt_sdklib.stubs`,
-		`sdklib.impl`,
-		// This should be prebuilt_sdklib.stubs but is set to sdklib.stubs because the
-		// dependency is added after prebuilts may have been renamed and so has to use
-		// the renamed name.
-		`sdklib.xml`,
-	})
-}
-
-func TestJavaSdkLibraryImport_Preferred(t *testing.T) {
-	result := android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("sdklib"),
-	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "sdklib",
-			srcs: ["a.java"],
-			sdk_version: "none",
-			system_modules: "none",
-			public: {
-				enabled: true,
-			},
-		}
-
-		java_sdk_library_import {
-			name: "sdklib",
-			prefer: true,
-			public: {
-				jars: ["a.jar"],
-			},
-		}
-		`)
-
-	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
-		`dex2oatd`,
-		`prebuilt_sdklib`,
-		`sdklib.impl`,
-		`sdklib.stubs`,
-		`sdklib.stubs.source`,
-		`sdklib.xml`,
-	})
-
-	CheckModuleDependencies(t, result.TestContext, "prebuilt_sdklib", "android_common", []string{
-		`prebuilt_sdklib.stubs`,
-		`sdklib.impl`,
-		`sdklib.xml`,
-	})
-}
-
-func TestJavaSdkLibraryEnforce(t *testing.T) {
-	partitionToBpOption := func(partition string) string {
-		switch partition {
-		case "system":
-			return ""
-		case "vendor":
-			return "soc_specific: true,"
-		case "product":
-			return "product_specific: true,"
-		default:
-			panic("Invalid partition group name: " + partition)
-		}
-	}
-
-	type testConfigInfo struct {
-		libraryType                string
-		fromPartition              string
-		toPartition                string
-		enforceVendorInterface     bool
-		enforceProductInterface    bool
-		enforceJavaSdkLibraryCheck bool
-		allowList                  []string
-	}
-
-	createPreparer := func(info testConfigInfo) android.FixturePreparer {
-		bpFileTemplate := `
-			java_library {
-				name: "foo",
-				srcs: ["foo.java"],
-				libs: ["bar"],
-				sdk_version: "current",
-				%s
-			}
-
-			%s {
-				name: "bar",
-				srcs: ["bar.java"],
-				sdk_version: "current",
-				%s
-			}
-		`
-
-		bpFile := fmt.Sprintf(bpFileTemplate,
-			partitionToBpOption(info.fromPartition),
-			info.libraryType,
-			partitionToBpOption(info.toPartition))
-
-		return android.GroupFixturePreparers(
-			PrepareForTestWithJavaSdkLibraryFiles,
-			FixtureWithLastReleaseApis("bar"),
-			android.FixtureWithRootAndroidBp(bpFile),
-			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
-				variables.EnforceProductPartitionInterface = proptools.BoolPtr(info.enforceProductInterface)
-				if info.enforceVendorInterface {
-					variables.DeviceVndkVersion = proptools.StringPtr("current")
-				}
-				variables.EnforceInterPartitionJavaSdkLibrary = proptools.BoolPtr(info.enforceJavaSdkLibraryCheck)
-				variables.InterPartitionJavaLibraryAllowList = info.allowList
-			}),
-		)
-	}
-
-	runTest := func(t *testing.T, info testConfigInfo, expectedErrorPattern string) {
-		t.Run(fmt.Sprintf("%v", info), func(t *testing.T) {
-			errorHandler := android.FixtureExpectsNoErrors
-			if expectedErrorPattern != "" {
-				errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(expectedErrorPattern)
-			}
-			android.GroupFixturePreparers(
-				prepareForJavaTest,
-				createPreparer(info),
-			).
-				ExtendWithErrorHandler(errorHandler).
-				RunTest(t)
-		})
-	}
-
-	errorMessage := "is not allowed across the partitions"
-
-	runTest(t, testConfigInfo{
-		libraryType:                "java_library",
-		fromPartition:              "product",
-		toPartition:                "system",
-		enforceVendorInterface:     true,
-		enforceProductInterface:    true,
-		enforceJavaSdkLibraryCheck: false,
-	}, "")
-
-	runTest(t, testConfigInfo{
-		libraryType:                "java_library",
-		fromPartition:              "product",
-		toPartition:                "system",
-		enforceVendorInterface:     true,
-		enforceProductInterface:    false,
-		enforceJavaSdkLibraryCheck: true,
-	}, "")
-
-	runTest(t, testConfigInfo{
-		libraryType:                "java_library",
-		fromPartition:              "product",
-		toPartition:                "system",
-		enforceVendorInterface:     true,
-		enforceProductInterface:    true,
-		enforceJavaSdkLibraryCheck: true,
-	}, errorMessage)
-
-	runTest(t, testConfigInfo{
-		libraryType:                "java_library",
-		fromPartition:              "vendor",
-		toPartition:                "system",
-		enforceVendorInterface:     true,
-		enforceProductInterface:    true,
-		enforceJavaSdkLibraryCheck: true,
-	}, errorMessage)
-
-	runTest(t, testConfigInfo{
-		libraryType:                "java_library",
-		fromPartition:              "vendor",
-		toPartition:                "system",
-		enforceVendorInterface:     true,
-		enforceProductInterface:    true,
-		enforceJavaSdkLibraryCheck: true,
-		allowList:                  []string{"bar"},
-	}, "")
-
-	runTest(t, testConfigInfo{
-		libraryType:                "java_library",
-		fromPartition:              "vendor",
-		toPartition:                "product",
-		enforceVendorInterface:     true,
-		enforceProductInterface:    true,
-		enforceJavaSdkLibraryCheck: true,
-	}, errorMessage)
-
-	runTest(t, testConfigInfo{
-		libraryType:                "java_sdk_library",
-		fromPartition:              "product",
-		toPartition:                "system",
-		enforceVendorInterface:     true,
-		enforceProductInterface:    true,
-		enforceJavaSdkLibraryCheck: true,
-	}, "")
-
-	runTest(t, testConfigInfo{
-		libraryType:                "java_sdk_library",
-		fromPartition:              "vendor",
-		toPartition:                "system",
-		enforceVendorInterface:     true,
-		enforceProductInterface:    true,
-		enforceJavaSdkLibraryCheck: true,
-	}, "")
-
-	runTest(t, testConfigInfo{
-		libraryType:                "java_sdk_library",
-		fromPartition:              "vendor",
-		toPartition:                "product",
-		enforceVendorInterface:     true,
-		enforceProductInterface:    true,
-		enforceJavaSdkLibraryCheck: true,
-	}, "")
-}
-
 func TestDefaults(t *testing.T) {
 	ctx, _ := testJava(t, `
 		java_defaults {
@@ -1411,521 +1110,6 @@
 	})
 }
 
-func TestJavaSdkLibrary(t *testing.T) {
-	result := android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithPrebuiltApis(map[string][]string{
-			"28": {"foo"},
-			"29": {"foo"},
-			"30": {"bar", "barney", "baz", "betty", "foo", "fred", "quuz", "wilma"},
-		}),
-	).RunTestWithBp(t, `
-		droiddoc_exported_dir {
-			name: "droiddoc-templates-sdk",
-			path: ".",
-		}
-		java_sdk_library {
-			name: "foo",
-			srcs: ["a.java", "b.java"],
-			api_packages: ["foo"],
-		}
-		java_sdk_library {
-			name: "bar",
-			srcs: ["a.java", "b.java"],
-			api_packages: ["bar"],
-		}
-		java_library {
-			name: "baz",
-			srcs: ["c.java"],
-			libs: ["foo", "bar.stubs"],
-			sdk_version: "system_current",
-		}
-		java_sdk_library {
-			name: "barney",
-			srcs: ["c.java"],
-			api_only: true,
-		}
-		java_sdk_library {
-			name: "betty",
-			srcs: ["c.java"],
-			shared_library: false,
-		}
-		java_sdk_library_import {
-		    name: "quuz",
-				public: {
-					jars: ["c.jar"],
-				},
-		}
-		java_sdk_library_import {
-		    name: "fred",
-				public: {
-					jars: ["b.jar"],
-				},
-		}
-		java_sdk_library_import {
-		    name: "wilma",
-				public: {
-					jars: ["b.jar"],
-				},
-				shared_library: false,
-		}
-		java_library {
-		    name: "qux",
-		    srcs: ["c.java"],
-		    libs: ["baz", "fred", "quuz.stubs", "wilma", "barney", "betty"],
-		    sdk_version: "system_current",
-		}
-		java_library {
-			name: "baz-test",
-			srcs: ["c.java"],
-			libs: ["foo"],
-			sdk_version: "test_current",
-		}
-		java_library {
-			name: "baz-29",
-			srcs: ["c.java"],
-			libs: ["foo"],
-			sdk_version: "system_29",
-		}
-		java_library {
-			name: "baz-module-30",
-			srcs: ["c.java"],
-			libs: ["foo"],
-			sdk_version: "module_30",
-		}
-		`)
-
-	// check the existence of the internal modules
-	result.ModuleForTests("foo", "android_common")
-	result.ModuleForTests(apiScopePublic.stubsLibraryModuleName("foo"), "android_common")
-	result.ModuleForTests(apiScopeSystem.stubsLibraryModuleName("foo"), "android_common")
-	result.ModuleForTests(apiScopeTest.stubsLibraryModuleName("foo"), "android_common")
-	result.ModuleForTests(apiScopePublic.stubsSourceModuleName("foo"), "android_common")
-	result.ModuleForTests(apiScopeSystem.stubsSourceModuleName("foo"), "android_common")
-	result.ModuleForTests(apiScopeTest.stubsSourceModuleName("foo"), "android_common")
-	result.ModuleForTests("foo"+sdkXmlFileSuffix, "android_common")
-	result.ModuleForTests("foo.api.public.28", "")
-	result.ModuleForTests("foo.api.system.28", "")
-	result.ModuleForTests("foo.api.test.28", "")
-
-	bazJavac := result.ModuleForTests("baz", "android_common").Rule("javac")
-	// tests if baz is actually linked to the stubs lib
-	android.AssertStringDoesContain(t, "baz javac classpath", bazJavac.Args["classpath"], "foo.stubs.system.jar")
-	// ... and not to the impl lib
-	android.AssertStringDoesNotContain(t, "baz javac classpath", bazJavac.Args["classpath"], "foo.jar")
-	// test if baz is not linked to the system variant of foo
-	android.AssertStringDoesNotContain(t, "baz javac classpath", bazJavac.Args["classpath"], "foo.stubs.jar")
-
-	bazTestJavac := result.ModuleForTests("baz-test", "android_common").Rule("javac")
-	// tests if baz-test is actually linked to the test stubs lib
-	android.AssertStringDoesContain(t, "baz-test javac classpath", bazTestJavac.Args["classpath"], "foo.stubs.test.jar")
-
-	baz29Javac := result.ModuleForTests("baz-29", "android_common").Rule("javac")
-	// tests if baz-29 is actually linked to the system 29 stubs lib
-	android.AssertStringDoesContain(t, "baz-29 javac classpath", baz29Javac.Args["classpath"], "prebuilts/sdk/29/system/foo.jar")
-
-	bazModule30Javac := result.ModuleForTests("baz-module-30", "android_common").Rule("javac")
-	// tests if "baz-module-30" is actually linked to the module 30 stubs lib
-	android.AssertStringDoesContain(t, "baz-module-30 javac classpath", bazModule30Javac.Args["classpath"], "prebuilts/sdk/30/module-lib/foo.jar")
-
-	// test if baz has exported SDK lib names foo and bar to qux
-	qux := result.ModuleForTests("qux", "android_common")
-	if quxLib, ok := qux.Module().(*Library); ok {
-		sdkLibs := quxLib.ClassLoaderContexts().UsesLibs()
-		android.AssertDeepEquals(t, "qux exports", []string{"foo", "bar", "fred", "quuz"}, sdkLibs)
-	}
-}
-
-func TestJavaSdkLibrary_StubOrImplOnlyLibs(t *testing.T) {
-	result := android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("sdklib"),
-	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "sdklib",
-			srcs: ["a.java"],
-			libs: ["lib"],
-			static_libs: ["static-lib"],
-			impl_only_libs: ["impl-only-lib"],
-			stub_only_libs: ["stub-only-lib"],
-			stub_only_static_libs: ["stub-only-static-lib"],
-		}
-		java_defaults {
-			name: "defaults",
-			srcs: ["a.java"],
-			sdk_version: "current",
-		}
-		java_library { name: "lib", defaults: ["defaults"] }
-		java_library { name: "static-lib", defaults: ["defaults"] }
-		java_library { name: "impl-only-lib", defaults: ["defaults"] }
-		java_library { name: "stub-only-lib", defaults: ["defaults"] }
-		java_library { name: "stub-only-static-lib", defaults: ["defaults"] }
-		`)
-	var expectations = []struct {
-		lib               string
-		on_impl_classpath bool
-		on_stub_classpath bool
-		in_impl_combined  bool
-		in_stub_combined  bool
-	}{
-		{lib: "lib", on_impl_classpath: true},
-		{lib: "static-lib", in_impl_combined: true},
-		{lib: "impl-only-lib", on_impl_classpath: true},
-		{lib: "stub-only-lib", on_stub_classpath: true},
-		{lib: "stub-only-static-lib", in_stub_combined: true},
-	}
-	verify := func(sdklib, dep string, cp, combined bool) {
-		sdklibCp := result.ModuleForTests(sdklib, "android_common").Rule("javac").Args["classpath"]
-		expected := cp || combined // Every combined jar is also on the classpath.
-		android.AssertStringContainsEquals(t, "bad classpath for "+sdklib, sdklibCp, "/"+dep+".jar", expected)
-
-		combineJarInputs := result.ModuleForTests(sdklib, "android_common").Rule("combineJar").Inputs.Strings()
-		depPath := filepath.Join("out", "soong", ".intermediates", dep, "android_common", "turbine-combined", dep+".jar")
-		android.AssertStringListContainsEquals(t, "bad combined inputs for "+sdklib, combineJarInputs, depPath, combined)
-	}
-	for _, expectation := range expectations {
-		verify("sdklib", expectation.lib, expectation.on_impl_classpath, expectation.in_impl_combined)
-		verify("sdklib.impl", expectation.lib, expectation.on_impl_classpath, expectation.in_impl_combined)
-
-		stubName := apiScopePublic.stubsLibraryModuleName("sdklib")
-		verify(stubName, expectation.lib, expectation.on_stub_classpath, expectation.in_stub_combined)
-	}
-}
-
-func TestJavaSdkLibrary_DoNotAccessImplWhenItIsNotBuilt(t *testing.T) {
-	result := android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("foo"),
-	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "foo",
-			srcs: ["a.java"],
-			api_only: true,
-			public: {
-				enabled: true,
-			},
-		}
-
-		java_library {
-			name: "bar",
-			srcs: ["b.java"],
-			libs: ["foo"],
-		}
-		`)
-
-	// The bar library should depend on the stubs jar.
-	barLibrary := result.ModuleForTests("bar", "android_common").Rule("javac")
-	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)
-	}
-}
-
-func TestJavaSdkLibrary_UseSourcesFromAnotherSdkLibrary(t *testing.T) {
-	android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("foo"),
-	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "foo",
-			srcs: ["a.java"],
-			api_packages: ["foo"],
-			public: {
-				enabled: true,
-			},
-		}
-
-		java_library {
-			name: "bar",
-			srcs: ["b.java", ":foo{.public.stubs.source}"],
-		}
-		`)
-}
-
-func TestJavaSdkLibrary_AccessOutputFiles_MissingScope(t *testing.T) {
-	android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("foo"),
-	).
-		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`"foo" does not provide api scope system`)).
-		RunTestWithBp(t, `
-		java_sdk_library {
-			name: "foo",
-			srcs: ["a.java"],
-			api_packages: ["foo"],
-			public: {
-				enabled: true,
-			},
-		}
-
-		java_library {
-			name: "bar",
-			srcs: ["b.java", ":foo{.system.stubs.source}"],
-		}
-		`)
-}
-
-func TestJavaSdkLibrary_Deps(t *testing.T) {
-	result := android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("sdklib"),
-	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "sdklib",
-			srcs: ["a.java"],
-			sdk_version: "none",
-			system_modules: "none",
-			public: {
-				enabled: true,
-			},
-		}
-		`)
-
-	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
-		`dex2oatd`,
-		`sdklib.impl`,
-		`sdklib.stubs`,
-		`sdklib.stubs.source`,
-		`sdklib.xml`,
-	})
-}
-
-func TestJavaSdkLibraryImport_AccessOutputFiles(t *testing.T) {
-	prepareForJavaTest.RunTestWithBp(t, `
-		java_sdk_library_import {
-			name: "foo",
-			public: {
-				jars: ["a.jar"],
-				stub_srcs: ["a.java"],
-				current_api: "api/current.txt",
-				removed_api: "api/removed.txt",
-			},
-		}
-
-		java_library {
-			name: "bar",
-			srcs: [":foo{.public.stubs.source}"],
-			java_resources: [
-				":foo{.public.api.txt}",
-				":foo{.public.removed-api.txt}",
-			],
-		}
-		`)
-}
-
-func TestJavaSdkLibraryImport_AccessOutputFiles_Invalid(t *testing.T) {
-	bp := `
-		java_sdk_library_import {
-			name: "foo",
-			public: {
-				jars: ["a.jar"],
-			},
-		}
-		`
-
-	t.Run("stubs.source", func(t *testing.T) {
-		prepareForJavaTest.
-			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`stubs.source not available for api scope public`)).
-			RunTestWithBp(t, bp+`
-				java_library {
-					name: "bar",
-					srcs: [":foo{.public.stubs.source}"],
-					java_resources: [
-						":foo{.public.api.txt}",
-						":foo{.public.removed-api.txt}",
-					],
-				}
-			`)
-	})
-
-	t.Run("api.txt", func(t *testing.T) {
-		prepareForJavaTest.
-			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`api.txt not available for api scope public`)).
-			RunTestWithBp(t, bp+`
-				java_library {
-					name: "bar",
-					srcs: ["a.java"],
-					java_resources: [
-						":foo{.public.api.txt}",
-					],
-				}
-			`)
-	})
-
-	t.Run("removed-api.txt", func(t *testing.T) {
-		prepareForJavaTest.
-			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`removed-api.txt not available for api scope public`)).
-			RunTestWithBp(t, bp+`
-				java_library {
-					name: "bar",
-					srcs: ["a.java"],
-					java_resources: [
-						":foo{.public.removed-api.txt}",
-					],
-				}
-			`)
-	})
-}
-
-func TestJavaSdkLibrary_InvalidScopes(t *testing.T) {
-	prepareForJavaTest.
-		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "foo": enabled api scope "system" depends on disabled scope "public"`)).
-		RunTestWithBp(t, `
-			java_sdk_library {
-				name: "foo",
-				srcs: ["a.java", "b.java"],
-				api_packages: ["foo"],
-				// Explicitly disable public to test the check that ensures the set of enabled
-				// scopes is consistent.
-				public: {
-					enabled: false,
-				},
-				system: {
-					enabled: true,
-				},
-			}
-		`)
-}
-
-func TestJavaSdkLibrary_SdkVersion_ForScope(t *testing.T) {
-	android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("foo"),
-	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "foo",
-			srcs: ["a.java", "b.java"],
-			api_packages: ["foo"],
-			system: {
-				enabled: true,
-				sdk_version: "module_current",
-			},
-		}
-		`)
-}
-
-func TestJavaSdkLibrary_ModuleLib(t *testing.T) {
-	android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("foo"),
-	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "foo",
-			srcs: ["a.java", "b.java"],
-			api_packages: ["foo"],
-			system: {
-				enabled: true,
-			},
-			module_lib: {
-				enabled: true,
-			},
-		}
-		`)
-}
-
-func TestJavaSdkLibrary_SystemServer(t *testing.T) {
-	android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("foo"),
-	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "foo",
-			srcs: ["a.java", "b.java"],
-			api_packages: ["foo"],
-			system: {
-				enabled: true,
-			},
-			system_server: {
-				enabled: true,
-			},
-		}
-		`)
-}
-
-func TestJavaSdkLibrary_MissingScope(t *testing.T) {
-	prepareForJavaTest.
-		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`requires api scope module-lib from foo but it only has \[\] available`)).
-		RunTestWithBp(t, `
-			java_sdk_library {
-				name: "foo",
-				srcs: ["a.java"],
-				public: {
-					enabled: false,
-				},
-			}
-
-			java_library {
-				name: "baz",
-				srcs: ["a.java"],
-				libs: ["foo"],
-				sdk_version: "module_current",
-			}
-		`)
-}
-
-func TestJavaSdkLibrary_FallbackScope(t *testing.T) {
-	android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("foo"),
-	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "foo",
-			srcs: ["a.java"],
-			system: {
-				enabled: true,
-			},
-		}
-
-		java_library {
-			name: "baz",
-			srcs: ["a.java"],
-			libs: ["foo"],
-			// foo does not have module-lib scope so it should fallback to system
-			sdk_version: "module_current",
-		}
-		`)
-}
-
-func TestJavaSdkLibrary_DefaultToStubs(t *testing.T) {
-	result := android.GroupFixturePreparers(
-		prepareForJavaTest,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("foo"),
-	).RunTestWithBp(t, `
-		java_sdk_library {
-			name: "foo",
-			srcs: ["a.java"],
-			system: {
-				enabled: true,
-			},
-			default_to_stubs: true,
-		}
-
-		java_library {
-			name: "baz",
-			srcs: ["a.java"],
-			libs: ["foo"],
-			// does not have sdk_version set, should fallback to module,
-			// which will then fallback to system because the module scope
-			// is not enabled.
-		}
-		`)
-	// The baz library should depend on the system stubs jar.
-	bazLibrary := result.ModuleForTests("baz", "android_common").Rule("javac")
-	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)
-	}
-}
-
 var compilerFlagsTestCases = []struct {
 	in  string
 	out bool
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 8f36758..ecf2b1a 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -457,6 +457,11 @@
 	// * API incompatibilities baseline filegroup -> <dist-stem>-incompatibilities.api.<scope>.latest
 	Dist_stem *string
 
+	// The subdirectory for the artifacts that are copied to the dist directory.  If not specified
+	// then defaults to "android".  Should be set to "android" for anything that should be published
+	// in the public Android SDK.
+	Dist_group *string
+
 	// A compatibility mode that allows historical API-tracking files to not exist.
 	// Do not use.
 	Unsafe_ignore_missing_latest_api bool
@@ -1198,12 +1203,10 @@
 
 // The dist path of the stub artifacts
 func (module *SdkLibrary) apiDistPath(apiScope *apiScope) string {
-	if module.ModuleBase.Owner() != "" {
-		return path.Join("apistubs", module.ModuleBase.Owner(), apiScope.name)
-	} else if Bool(module.sdkLibraryProperties.Core_lib) {
+	if Bool(module.sdkLibraryProperties.Core_lib) {
 		return path.Join("apistubs", "core", apiScope.name)
 	} else {
-		return path.Join("apistubs", "android", apiScope.name)
+		return path.Join("apistubs", module.distGroup(), apiScope.name)
 	}
 }
 
@@ -1228,6 +1231,19 @@
 	return proptools.StringDefault(module.sdkLibraryProperties.Dist_stem, module.BaseModuleName())
 }
 
+// distGroup returns the subdirectory of the dist path of the stub artifacts.
+func (module *SdkLibrary) distGroup() string {
+	if group := proptools.String(module.sdkLibraryProperties.Dist_group); group != "" {
+		return group
+	}
+	// TODO(b/186723288): Remove this once everything uses dist_group.
+	if owner := module.ModuleBase.Owner(); owner != "" {
+		return owner
+	}
+	// TODO(b/186723288): Make this "unknown".
+	return "android"
+}
+
 func (module *SdkLibrary) latestApiFilegroupName(apiScope *apiScope) string {
 	return ":" + module.distStem() + ".api." + apiScope.name + ".latest"
 }
diff --git a/java/sdk_library_test.go b/java/sdk_library_test.go
new file mode 100644
index 0000000..0fe6e72
--- /dev/null
+++ b/java/sdk_library_test.go
@@ -0,0 +1,931 @@
+// 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"
+	"fmt"
+	"path/filepath"
+	"regexp"
+	"testing"
+
+	"github.com/google/blueprint/proptools"
+)
+
+func TestJavaSdkLibrary(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithPrebuiltApis(map[string][]string{
+			"28": {"foo"},
+			"29": {"foo"},
+			"30": {"bar", "barney", "baz", "betty", "foo", "fred", "quuz", "wilma"},
+		}),
+	).RunTestWithBp(t, `
+		droiddoc_exported_dir {
+			name: "droiddoc-templates-sdk",
+			path: ".",
+		}
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java", "b.java"],
+			api_packages: ["foo"],
+		}
+		java_sdk_library {
+			name: "bar",
+			srcs: ["a.java", "b.java"],
+			api_packages: ["bar"],
+		}
+		java_library {
+			name: "baz",
+			srcs: ["c.java"],
+			libs: ["foo", "bar.stubs"],
+			sdk_version: "system_current",
+		}
+		java_sdk_library {
+			name: "barney",
+			srcs: ["c.java"],
+			api_only: true,
+		}
+		java_sdk_library {
+			name: "betty",
+			srcs: ["c.java"],
+			shared_library: false,
+		}
+		java_sdk_library_import {
+		    name: "quuz",
+				public: {
+					jars: ["c.jar"],
+				},
+		}
+		java_sdk_library_import {
+		    name: "fred",
+				public: {
+					jars: ["b.jar"],
+				},
+		}
+		java_sdk_library_import {
+		    name: "wilma",
+				public: {
+					jars: ["b.jar"],
+				},
+				shared_library: false,
+		}
+		java_library {
+		    name: "qux",
+		    srcs: ["c.java"],
+		    libs: ["baz", "fred", "quuz.stubs", "wilma", "barney", "betty"],
+		    sdk_version: "system_current",
+		}
+		java_library {
+			name: "baz-test",
+			srcs: ["c.java"],
+			libs: ["foo"],
+			sdk_version: "test_current",
+		}
+		java_library {
+			name: "baz-29",
+			srcs: ["c.java"],
+			libs: ["foo"],
+			sdk_version: "system_29",
+		}
+		java_library {
+			name: "baz-module-30",
+			srcs: ["c.java"],
+			libs: ["foo"],
+			sdk_version: "module_30",
+		}
+		`)
+
+	// check the existence of the internal modules
+	result.ModuleForTests("foo", "android_common")
+	result.ModuleForTests(apiScopePublic.stubsLibraryModuleName("foo"), "android_common")
+	result.ModuleForTests(apiScopeSystem.stubsLibraryModuleName("foo"), "android_common")
+	result.ModuleForTests(apiScopeTest.stubsLibraryModuleName("foo"), "android_common")
+	result.ModuleForTests(apiScopePublic.stubsSourceModuleName("foo"), "android_common")
+	result.ModuleForTests(apiScopeSystem.stubsSourceModuleName("foo"), "android_common")
+	result.ModuleForTests(apiScopeTest.stubsSourceModuleName("foo"), "android_common")
+	result.ModuleForTests("foo"+sdkXmlFileSuffix, "android_common")
+	result.ModuleForTests("foo.api.public.28", "")
+	result.ModuleForTests("foo.api.system.28", "")
+	result.ModuleForTests("foo.api.test.28", "")
+
+	bazJavac := result.ModuleForTests("baz", "android_common").Rule("javac")
+	// tests if baz is actually linked to the stubs lib
+	android.AssertStringDoesContain(t, "baz javac classpath", bazJavac.Args["classpath"], "foo.stubs.system.jar")
+	// ... and not to the impl lib
+	android.AssertStringDoesNotContain(t, "baz javac classpath", bazJavac.Args["classpath"], "foo.jar")
+	// test if baz is not linked to the system variant of foo
+	android.AssertStringDoesNotContain(t, "baz javac classpath", bazJavac.Args["classpath"], "foo.stubs.jar")
+
+	bazTestJavac := result.ModuleForTests("baz-test", "android_common").Rule("javac")
+	// tests if baz-test is actually linked to the test stubs lib
+	android.AssertStringDoesContain(t, "baz-test javac classpath", bazTestJavac.Args["classpath"], "foo.stubs.test.jar")
+
+	baz29Javac := result.ModuleForTests("baz-29", "android_common").Rule("javac")
+	// tests if baz-29 is actually linked to the system 29 stubs lib
+	android.AssertStringDoesContain(t, "baz-29 javac classpath", baz29Javac.Args["classpath"], "prebuilts/sdk/29/system/foo.jar")
+
+	bazModule30Javac := result.ModuleForTests("baz-module-30", "android_common").Rule("javac")
+	// tests if "baz-module-30" is actually linked to the module 30 stubs lib
+	android.AssertStringDoesContain(t, "baz-module-30 javac classpath", bazModule30Javac.Args["classpath"], "prebuilts/sdk/30/module-lib/foo.jar")
+
+	// test if baz has exported SDK lib names foo and bar to qux
+	qux := result.ModuleForTests("qux", "android_common")
+	if quxLib, ok := qux.Module().(*Library); ok {
+		sdkLibs := quxLib.ClassLoaderContexts().UsesLibs()
+		android.AssertDeepEquals(t, "qux exports", []string{"foo", "bar", "fred", "quuz"}, sdkLibs)
+	}
+}
+
+func TestJavaSdkLibrary_StubOrImplOnlyLibs(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("sdklib"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "sdklib",
+			srcs: ["a.java"],
+			libs: ["lib"],
+			static_libs: ["static-lib"],
+			impl_only_libs: ["impl-only-lib"],
+			stub_only_libs: ["stub-only-lib"],
+			stub_only_static_libs: ["stub-only-static-lib"],
+		}
+		java_defaults {
+			name: "defaults",
+			srcs: ["a.java"],
+			sdk_version: "current",
+		}
+		java_library { name: "lib", defaults: ["defaults"] }
+		java_library { name: "static-lib", defaults: ["defaults"] }
+		java_library { name: "impl-only-lib", defaults: ["defaults"] }
+		java_library { name: "stub-only-lib", defaults: ["defaults"] }
+		java_library { name: "stub-only-static-lib", defaults: ["defaults"] }
+		`)
+	var expectations = []struct {
+		lib               string
+		on_impl_classpath bool
+		on_stub_classpath bool
+		in_impl_combined  bool
+		in_stub_combined  bool
+	}{
+		{lib: "lib", on_impl_classpath: true},
+		{lib: "static-lib", in_impl_combined: true},
+		{lib: "impl-only-lib", on_impl_classpath: true},
+		{lib: "stub-only-lib", on_stub_classpath: true},
+		{lib: "stub-only-static-lib", in_stub_combined: true},
+	}
+	verify := func(sdklib, dep string, cp, combined bool) {
+		sdklibCp := result.ModuleForTests(sdklib, "android_common").Rule("javac").Args["classpath"]
+		expected := cp || combined // Every combined jar is also on the classpath.
+		android.AssertStringContainsEquals(t, "bad classpath for "+sdklib, sdklibCp, "/"+dep+".jar", expected)
+
+		combineJarInputs := result.ModuleForTests(sdklib, "android_common").Rule("combineJar").Inputs.Strings()
+		depPath := filepath.Join("out", "soong", ".intermediates", dep, "android_common", "turbine-combined", dep+".jar")
+		android.AssertStringListContainsEquals(t, "bad combined inputs for "+sdklib, combineJarInputs, depPath, combined)
+	}
+	for _, expectation := range expectations {
+		verify("sdklib", expectation.lib, expectation.on_impl_classpath, expectation.in_impl_combined)
+		verify("sdklib.impl", expectation.lib, expectation.on_impl_classpath, expectation.in_impl_combined)
+
+		stubName := apiScopePublic.stubsLibraryModuleName("sdklib")
+		verify(stubName, expectation.lib, expectation.on_stub_classpath, expectation.in_stub_combined)
+	}
+}
+
+func TestJavaSdkLibrary_DoNotAccessImplWhenItIsNotBuilt(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java"],
+			api_only: true,
+			public: {
+				enabled: true,
+			},
+		}
+
+		java_library {
+			name: "bar",
+			srcs: ["b.java"],
+			libs: ["foo"],
+		}
+		`)
+
+	// The bar library should depend on the stubs jar.
+	barLibrary := result.ModuleForTests("bar", "android_common").Rule("javac")
+	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)
+	}
+}
+
+func TestJavaSdkLibrary_UseSourcesFromAnotherSdkLibrary(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java"],
+			api_packages: ["foo"],
+			public: {
+				enabled: true,
+			},
+		}
+
+		java_library {
+			name: "bar",
+			srcs: ["b.java", ":foo{.public.stubs.source}"],
+		}
+		`)
+}
+
+func TestJavaSdkLibrary_AccessOutputFiles_MissingScope(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`"foo" does not provide api scope system`)).
+		RunTestWithBp(t, `
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java"],
+			api_packages: ["foo"],
+			public: {
+				enabled: true,
+			},
+		}
+
+		java_library {
+			name: "bar",
+			srcs: ["b.java", ":foo{.system.stubs.source}"],
+		}
+		`)
+}
+
+func TestJavaSdkLibrary_Deps(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("sdklib"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "sdklib",
+			srcs: ["a.java"],
+			sdk_version: "none",
+			system_modules: "none",
+			public: {
+				enabled: true,
+			},
+		}
+		`)
+
+	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
+		`dex2oatd`,
+		`sdklib.impl`,
+		`sdklib.stubs`,
+		`sdklib.stubs.source`,
+		`sdklib.xml`,
+	})
+}
+
+func TestJavaSdkLibraryImport_AccessOutputFiles(t *testing.T) {
+	prepareForJavaTest.RunTestWithBp(t, `
+		java_sdk_library_import {
+			name: "foo",
+			public: {
+				jars: ["a.jar"],
+				stub_srcs: ["a.java"],
+				current_api: "api/current.txt",
+				removed_api: "api/removed.txt",
+			},
+		}
+
+		java_library {
+			name: "bar",
+			srcs: [":foo{.public.stubs.source}"],
+			java_resources: [
+				":foo{.public.api.txt}",
+				":foo{.public.removed-api.txt}",
+			],
+		}
+		`)
+}
+
+func TestJavaSdkLibraryImport_AccessOutputFiles_Invalid(t *testing.T) {
+	bp := `
+		java_sdk_library_import {
+			name: "foo",
+			public: {
+				jars: ["a.jar"],
+			},
+		}
+		`
+
+	t.Run("stubs.source", func(t *testing.T) {
+		prepareForJavaTest.
+			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`stubs.source not available for api scope public`)).
+			RunTestWithBp(t, bp+`
+				java_library {
+					name: "bar",
+					srcs: [":foo{.public.stubs.source}"],
+					java_resources: [
+						":foo{.public.api.txt}",
+						":foo{.public.removed-api.txt}",
+					],
+				}
+			`)
+	})
+
+	t.Run("api.txt", func(t *testing.T) {
+		prepareForJavaTest.
+			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`api.txt not available for api scope public`)).
+			RunTestWithBp(t, bp+`
+				java_library {
+					name: "bar",
+					srcs: ["a.java"],
+					java_resources: [
+						":foo{.public.api.txt}",
+					],
+				}
+			`)
+	})
+
+	t.Run("removed-api.txt", func(t *testing.T) {
+		prepareForJavaTest.
+			ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`removed-api.txt not available for api scope public`)).
+			RunTestWithBp(t, bp+`
+				java_library {
+					name: "bar",
+					srcs: ["a.java"],
+					java_resources: [
+						":foo{.public.removed-api.txt}",
+					],
+				}
+			`)
+	})
+}
+
+func TestJavaSdkLibrary_InvalidScopes(t *testing.T) {
+	prepareForJavaTest.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`module "foo": enabled api scope "system" depends on disabled scope "public"`)).
+		RunTestWithBp(t, `
+			java_sdk_library {
+				name: "foo",
+				srcs: ["a.java", "b.java"],
+				api_packages: ["foo"],
+				// Explicitly disable public to test the check that ensures the set of enabled
+				// scopes is consistent.
+				public: {
+					enabled: false,
+				},
+				system: {
+					enabled: true,
+				},
+			}
+		`)
+}
+
+func TestJavaSdkLibrary_SdkVersion_ForScope(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java", "b.java"],
+			api_packages: ["foo"],
+			system: {
+				enabled: true,
+				sdk_version: "module_current",
+			},
+		}
+		`)
+}
+
+func TestJavaSdkLibrary_ModuleLib(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java", "b.java"],
+			api_packages: ["foo"],
+			system: {
+				enabled: true,
+			},
+			module_lib: {
+				enabled: true,
+			},
+		}
+		`)
+}
+
+func TestJavaSdkLibrary_SystemServer(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java", "b.java"],
+			api_packages: ["foo"],
+			system: {
+				enabled: true,
+			},
+			system_server: {
+				enabled: true,
+			},
+		}
+		`)
+}
+
+func TestJavaSdkLibrary_MissingScope(t *testing.T) {
+	prepareForJavaTest.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(`requires api scope module-lib from foo but it only has \[\] available`)).
+		RunTestWithBp(t, `
+			java_sdk_library {
+				name: "foo",
+				srcs: ["a.java"],
+				public: {
+					enabled: false,
+				},
+			}
+
+			java_library {
+				name: "baz",
+				srcs: ["a.java"],
+				libs: ["foo"],
+				sdk_version: "module_current",
+			}
+		`)
+}
+
+func TestJavaSdkLibrary_FallbackScope(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java"],
+			system: {
+				enabled: true,
+			},
+		}
+
+		java_library {
+			name: "baz",
+			srcs: ["a.java"],
+			libs: ["foo"],
+			// foo does not have module-lib scope so it should fallback to system
+			sdk_version: "module_current",
+		}
+		`)
+}
+
+func TestJavaSdkLibrary_DefaultToStubs(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("foo"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "foo",
+			srcs: ["a.java"],
+			system: {
+				enabled: true,
+			},
+			default_to_stubs: true,
+		}
+
+		java_library {
+			name: "baz",
+			srcs: ["a.java"],
+			libs: ["foo"],
+			// does not have sdk_version set, should fallback to module,
+			// which will then fallback to system because the module scope
+			// is not enabled.
+		}
+		`)
+	// The baz library should depend on the system stubs jar.
+	bazLibrary := result.ModuleForTests("baz", "android_common").Rule("javac")
+	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)
+	}
+}
+
+func TestJavaSdkLibraryImport(t *testing.T) {
+	result := prepareForJavaTest.RunTestWithBp(t, `
+		java_library {
+			name: "foo",
+			srcs: ["a.java"],
+			libs: ["sdklib"],
+			sdk_version: "current",
+		}
+
+		java_library {
+			name: "foo.system",
+			srcs: ["a.java"],
+			libs: ["sdklib"],
+			sdk_version: "system_current",
+		}
+
+		java_library {
+			name: "foo.test",
+			srcs: ["a.java"],
+			libs: ["sdklib"],
+			sdk_version: "test_current",
+		}
+
+		java_sdk_library_import {
+			name: "sdklib",
+			public: {
+				jars: ["a.jar"],
+			},
+			system: {
+				jars: ["b.jar"],
+			},
+			test: {
+				jars: ["c.jar"],
+				stub_srcs: ["c.java"],
+			},
+		}
+		`)
+
+	for _, scope := range []string{"", ".system", ".test"} {
+		fooModule := result.ModuleForTests("foo"+scope, "android_common")
+		javac := fooModule.Rule("javac")
+
+		sdklibStubsJar := result.ModuleForTests("sdklib.stubs"+scope, "android_common").Rule("combineJar").Output
+		android.AssertStringDoesContain(t, "foo classpath", javac.Args["classpath"], sdklibStubsJar.String())
+	}
+
+	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
+		`prebuilt_sdklib.stubs`,
+		`prebuilt_sdklib.stubs.source.test`,
+		`prebuilt_sdklib.stubs.system`,
+		`prebuilt_sdklib.stubs.test`,
+	})
+}
+
+func TestJavaSdkLibraryImport_WithSource(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("sdklib"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "sdklib",
+			srcs: ["a.java"],
+			sdk_version: "none",
+			system_modules: "none",
+			public: {
+				enabled: true,
+			},
+		}
+
+		java_sdk_library_import {
+			name: "sdklib",
+			public: {
+				jars: ["a.jar"],
+			},
+		}
+		`)
+
+	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
+		`dex2oatd`,
+		`prebuilt_sdklib`,
+		`sdklib.impl`,
+		`sdklib.stubs`,
+		`sdklib.stubs.source`,
+		`sdklib.xml`,
+	})
+
+	CheckModuleDependencies(t, result.TestContext, "prebuilt_sdklib", "android_common", []string{
+		`prebuilt_sdklib.stubs`,
+		`sdklib.impl`,
+		// This should be prebuilt_sdklib.stubs but is set to sdklib.stubs because the
+		// dependency is added after prebuilts may have been renamed and so has to use
+		// the renamed name.
+		`sdklib.xml`,
+	})
+}
+
+func TestJavaSdkLibraryImport_Preferred(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		PrepareForTestWithJavaSdkLibraryFiles,
+		FixtureWithLastReleaseApis("sdklib"),
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "sdklib",
+			srcs: ["a.java"],
+			sdk_version: "none",
+			system_modules: "none",
+			public: {
+				enabled: true,
+			},
+		}
+
+		java_sdk_library_import {
+			name: "sdklib",
+			prefer: true,
+			public: {
+				jars: ["a.jar"],
+			},
+		}
+		`)
+
+	CheckModuleDependencies(t, result.TestContext, "sdklib", "android_common", []string{
+		`dex2oatd`,
+		`prebuilt_sdklib`,
+		`sdklib.impl`,
+		`sdklib.stubs`,
+		`sdklib.stubs.source`,
+		`sdklib.xml`,
+	})
+
+	CheckModuleDependencies(t, result.TestContext, "prebuilt_sdklib", "android_common", []string{
+		`prebuilt_sdklib.stubs`,
+		`sdklib.impl`,
+		`sdklib.xml`,
+	})
+}
+
+func TestJavaSdkLibraryEnforce(t *testing.T) {
+	partitionToBpOption := func(partition string) string {
+		switch partition {
+		case "system":
+			return ""
+		case "vendor":
+			return "soc_specific: true,"
+		case "product":
+			return "product_specific: true,"
+		default:
+			panic("Invalid partition group name: " + partition)
+		}
+	}
+
+	type testConfigInfo struct {
+		libraryType                string
+		fromPartition              string
+		toPartition                string
+		enforceVendorInterface     bool
+		enforceProductInterface    bool
+		enforceJavaSdkLibraryCheck bool
+		allowList                  []string
+	}
+
+	createPreparer := func(info testConfigInfo) android.FixturePreparer {
+		bpFileTemplate := `
+			java_library {
+				name: "foo",
+				srcs: ["foo.java"],
+				libs: ["bar"],
+				sdk_version: "current",
+				%s
+			}
+
+			%s {
+				name: "bar",
+				srcs: ["bar.java"],
+				sdk_version: "current",
+				%s
+			}
+		`
+
+		bpFile := fmt.Sprintf(bpFileTemplate,
+			partitionToBpOption(info.fromPartition),
+			info.libraryType,
+			partitionToBpOption(info.toPartition))
+
+		return android.GroupFixturePreparers(
+			PrepareForTestWithJavaSdkLibraryFiles,
+			FixtureWithLastReleaseApis("bar"),
+			android.FixtureWithRootAndroidBp(bpFile),
+			android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
+				variables.EnforceProductPartitionInterface = proptools.BoolPtr(info.enforceProductInterface)
+				if info.enforceVendorInterface {
+					variables.DeviceVndkVersion = proptools.StringPtr("current")
+				}
+				variables.EnforceInterPartitionJavaSdkLibrary = proptools.BoolPtr(info.enforceJavaSdkLibraryCheck)
+				variables.InterPartitionJavaLibraryAllowList = info.allowList
+			}),
+		)
+	}
+
+	runTest := func(t *testing.T, info testConfigInfo, expectedErrorPattern string) {
+		t.Run(fmt.Sprintf("%v", info), func(t *testing.T) {
+			errorHandler := android.FixtureExpectsNoErrors
+			if expectedErrorPattern != "" {
+				errorHandler = android.FixtureExpectsAtLeastOneErrorMatchingPattern(expectedErrorPattern)
+			}
+			android.GroupFixturePreparers(
+				prepareForJavaTest,
+				createPreparer(info),
+			).
+				ExtendWithErrorHandler(errorHandler).
+				RunTest(t)
+		})
+	}
+
+	errorMessage := "is not allowed across the partitions"
+
+	runTest(t, testConfigInfo{
+		libraryType:                "java_library",
+		fromPartition:              "product",
+		toPartition:                "system",
+		enforceVendorInterface:     true,
+		enforceProductInterface:    true,
+		enforceJavaSdkLibraryCheck: false,
+	}, "")
+
+	runTest(t, testConfigInfo{
+		libraryType:                "java_library",
+		fromPartition:              "product",
+		toPartition:                "system",
+		enforceVendorInterface:     true,
+		enforceProductInterface:    false,
+		enforceJavaSdkLibraryCheck: true,
+	}, "")
+
+	runTest(t, testConfigInfo{
+		libraryType:                "java_library",
+		fromPartition:              "product",
+		toPartition:                "system",
+		enforceVendorInterface:     true,
+		enforceProductInterface:    true,
+		enforceJavaSdkLibraryCheck: true,
+	}, errorMessage)
+
+	runTest(t, testConfigInfo{
+		libraryType:                "java_library",
+		fromPartition:              "vendor",
+		toPartition:                "system",
+		enforceVendorInterface:     true,
+		enforceProductInterface:    true,
+		enforceJavaSdkLibraryCheck: true,
+	}, errorMessage)
+
+	runTest(t, testConfigInfo{
+		libraryType:                "java_library",
+		fromPartition:              "vendor",
+		toPartition:                "system",
+		enforceVendorInterface:     true,
+		enforceProductInterface:    true,
+		enforceJavaSdkLibraryCheck: true,
+		allowList:                  []string{"bar"},
+	}, "")
+
+	runTest(t, testConfigInfo{
+		libraryType:                "java_library",
+		fromPartition:              "vendor",
+		toPartition:                "product",
+		enforceVendorInterface:     true,
+		enforceProductInterface:    true,
+		enforceJavaSdkLibraryCheck: true,
+	}, errorMessage)
+
+	runTest(t, testConfigInfo{
+		libraryType:                "java_sdk_library",
+		fromPartition:              "product",
+		toPartition:                "system",
+		enforceVendorInterface:     true,
+		enforceProductInterface:    true,
+		enforceJavaSdkLibraryCheck: true,
+	}, "")
+
+	runTest(t, testConfigInfo{
+		libraryType:                "java_sdk_library",
+		fromPartition:              "vendor",
+		toPartition:                "system",
+		enforceVendorInterface:     true,
+		enforceProductInterface:    true,
+		enforceJavaSdkLibraryCheck: true,
+	}, "")
+
+	runTest(t, testConfigInfo{
+		libraryType:                "java_sdk_library",
+		fromPartition:              "vendor",
+		toPartition:                "product",
+		enforceVendorInterface:     true,
+		enforceProductInterface:    true,
+		enforceJavaSdkLibraryCheck: true,
+	}, "")
+}
+
+func TestJavaSdkLibraryDist(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		PrepareForTestWithJavaBuildComponents,
+		PrepareForTestWithJavaDefaultModules,
+		PrepareForTestWithJavaSdkLibraryFiles,
+	).RunTestWithBp(t, `
+		java_sdk_library {
+			name: "sdklib_no_owner",
+			unsafe_ignore_missing_latest_api: true,
+			srcs: ["foo.java"],
+		}
+
+		java_sdk_library {
+			name: "sdklib_group_foo",
+			unsafe_ignore_missing_latest_api: true,
+			srcs: ["foo.java"],
+			dist_group: "foo",
+		}
+
+		java_sdk_library {
+			name: "sdklib_owner_foo",
+			unsafe_ignore_missing_latest_api: true,
+			srcs: ["foo.java"],
+			owner: "foo",
+		}
+
+		java_sdk_library {
+			name: "sdklib_stem_foo",
+			unsafe_ignore_missing_latest_api: true,
+			srcs: ["foo.java"],
+			dist_stem: "foo",
+		}
+
+		java_sdk_library {
+			name: "sdklib_core_lib",
+			unsafe_ignore_missing_latest_api: true,
+			srcs: ["foo.java"],
+			core_lib: true,
+		}
+	`)
+
+	type testCase struct {
+		module   string
+		distDir  string
+		distStem string
+	}
+	testCases := []testCase{
+		{
+			module:   "sdklib_no_owner",
+			distDir:  "apistubs/android/public",
+			distStem: "sdklib_no_owner.jar",
+		},
+		{
+			module:   "sdklib_group_foo",
+			distDir:  "apistubs/foo/public",
+			distStem: "sdklib_group_foo.jar",
+		},
+		{
+			module:   "sdklib_owner_foo",
+			distDir:  "apistubs/foo/public",
+			distStem: "sdklib_owner_foo.jar",
+		},
+		{
+			module:   "sdklib_stem_foo",
+			distDir:  "apistubs/android/public",
+			distStem: "foo.jar",
+		},
+		{
+			module:   "sdklib_core_lib",
+			distDir:  "apistubs/core/public",
+			distStem: "sdklib_core_lib.jar",
+		},
+	}
+
+	for _, tt := range testCases {
+		t.Run(tt.module, func(t *testing.T) {
+			m := result.ModuleForTests(tt.module+".stubs", "android_common").Module().(*Library)
+			dists := m.Dists()
+			if len(dists) != 1 {
+				t.Fatalf("expected exactly 1 dist entry, got %d", len(dists))
+			}
+			if g, w := String(dists[0].Dir), tt.distDir; g != w {
+				t.Errorf("expected dist dir %q, got %q", w, g)
+			}
+			if g, w := String(dists[0].Dest), tt.distStem; g != w {
+				t.Errorf("expected dist stem %q, got %q", w, g)
+			}
+		})
+	}
+}
diff --git a/python/test.go b/python/test.go
index 6713189..7413782 100644
--- a/python/test.go
+++ b/python/test.go
@@ -15,6 +15,8 @@
 package python
 
 import (
+	"github.com/google/blueprint/proptools"
+
 	"android/soong/android"
 	"android/soong/tradefed"
 )
@@ -102,6 +104,9 @@
 	binary.pythonInstaller = NewPythonInstaller("nativetest", "nativetest64")
 
 	test := &testDecorator{binaryDecorator: binary}
+	if hod == android.HostSupportedNoCross && test.testProperties.Test_options.Unit_test == nil {
+		test.testProperties.Test_options.Unit_test = proptools.BoolPtr(true)
+	}
 
 	module.bootstrapper = test
 	module.installer = test
diff --git a/rust/snapshot_utils.go b/rust/snapshot_utils.go
index e0ed1f7..943c790 100644
--- a/rust/snapshot_utils.go
+++ b/rust/snapshot_utils.go
@@ -20,12 +20,12 @@
 
 func (mod *Module) ExcludeFromVendorSnapshot() bool {
 	// TODO Rust does not yet support snapshotting
-	return true
+	return false
 }
 
 func (mod *Module) ExcludeFromRecoverySnapshot() bool {
 	// TODO Rust does not yet support snapshotting
-	return true
+	return false
 }
 
 func (mod *Module) IsSnapshotLibrary() bool {
diff --git a/scripts/build-mainline-modules.sh b/scripts/build-mainline-modules.sh
index 30cb937..39c8fba 100755
--- a/scripts/build-mainline-modules.sh
+++ b/scripts/build-mainline-modules.sh
@@ -68,6 +68,9 @@
 # Make sure this build builds from source, regardless of the default.
 export SOONG_CONFIG_art_module_source_build=true
 
+# This script does not intend to handle compressed APEX
+export OVERRIDE_PRODUCT_COMPRESSED_APEX=false
+
 OUT_DIR=$(source build/envsetup.sh > /dev/null; TARGET_PRODUCT= get_build_var OUT_DIR)
 DIST_DIR=$(source build/envsetup.sh > /dev/null; TARGET_PRODUCT= get_build_var DIST_DIR)
 
diff --git a/tests/mixed_mode_test.sh b/tests/mixed_mode_test.sh
index 80774bf..b408fd3 100755
--- a/tests/mixed_mode_test.sh
+++ b/tests/mixed_mode_test.sh
@@ -14,7 +14,7 @@
   setup
   create_mock_bazel
 
-  run_bazel info
+  STANDALONE_BAZEL=true run_bazel info
 }
 
 test_bazel_smoke
diff --git a/ui/build/config.go b/ui/build/config.go
index f9f638c..f1f5989 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -73,6 +73,9 @@
 	// During Bazel execution, Bazel cannot write outside OUT_DIR.
 	// So if DIST_DIR is set to an external dir (outside of OUT_DIR), we need to rig it temporarily and then migrate files at the end of the build.
 	riggedDistDirForBazel string
+
+	// Set by multiproduct_kati
+	emptyNinjaFile bool
 }
 
 const srcDirFileCheck = "build/soong/root.bp"
@@ -205,9 +208,6 @@
 		"ANDROID_DEV_SCRIPTS",
 		"ANDROID_EMULATOR_PREBUILTS",
 		"ANDROID_PRE_BUILD_PATHS",
-
-		// Only set in multiproduct_kati after config generation
-		"EMPTY_NINJA_FILE",
 	)
 
 	if ret.UseGoma() || ret.ForceUseGoma() {
@@ -1191,3 +1191,11 @@
 func (c *configImpl) BazelMetricsDir() string {
 	return filepath.Join(c.LogsDir(), "bazel_metrics")
 }
+
+func (c *configImpl) SetEmptyNinjaFile(v bool) {
+	c.emptyNinjaFile = v
+}
+
+func (c *configImpl) EmptyNinjaFile() bool {
+	return c.emptyNinjaFile
+}
diff --git a/ui/build/kati.go b/ui/build/kati.go
index 06ec646..dad68fa 100644
--- a/ui/build/kati.go
+++ b/ui/build/kati.go
@@ -136,7 +136,7 @@
 	// information out with --empty_ninja_file.
 	//
 	// From https://github.com/google/kati/commit/87b8da7af2c8bea28b1d8ab17679453d859f96e5
-	if config.Environment().IsEnvTrue("EMPTY_NINJA_FILE") {
+	if config.EmptyNinjaFile() {
 		args = append(args, "--empty_ninja_file")
 	}
 
diff --git a/ui/build/soong.go b/ui/build/soong.go
index cd645eb..7128414 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -119,6 +119,7 @@
 	args.OutFile = shared.JoinPath(config.SoongOutDir(), ".bootstrap/build.ninja")
 	args.GlobFile = globFile
 	args.GeneratingPrimaryBuilder = true
+	args.EmptyNinjaFile = config.EmptyNinjaFile()
 
 	args.DelveListen = os.Getenv("SOONG_DELVE")
 	if args.DelveListen != "" {