Merge "Disable BTI for now."
diff --git a/android/bazel.go b/android/bazel.go
index 0940205..d5fdfd7 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -235,6 +235,7 @@
 
 	// Configure modules in these directories to enable bp2build_available: true or false by default.
 	bp2buildDefaultConfig = Bp2BuildConfig{
+		"art/libartpalette":                     Bp2BuildDefaultTrueRecursively,
 		"art/libdexfile":                        Bp2BuildDefaultTrueRecursively,
 		"art/runtime":                           Bp2BuildDefaultTrueRecursively,
 		"art/tools":                             Bp2BuildDefaultTrue,
@@ -291,11 +292,14 @@
 		"external/bouncycastle":                              Bp2BuildDefaultTrue,
 		"external/brotli":                                    Bp2BuildDefaultTrue,
 		"external/conscrypt":                                 Bp2BuildDefaultTrue,
-		"external/error_prone":                               Bp2BuildDefaultTrue,
+		"external/error_prone":                               Bp2BuildDefaultTrueRecursively,
 		"external/fmtlib":                                    Bp2BuildDefaultTrueRecursively,
 		"external/google-benchmark":                          Bp2BuildDefaultTrueRecursively,
 		"external/googletest":                                Bp2BuildDefaultTrueRecursively,
 		"external/gwp_asan":                                  Bp2BuildDefaultTrueRecursively,
+		"external/icu":                                       Bp2BuildDefaultTrueRecursively,
+		"external/icu/android_icu4j":                         Bp2BuildDefaultFalse, // java rules incomplete
+		"external/icu/icu4j":                                 Bp2BuildDefaultFalse, // java rules incomplete
 		"external/jemalloc_new":                              Bp2BuildDefaultTrueRecursively,
 		"external/jsoncpp":                                   Bp2BuildDefaultTrueRecursively,
 		"external/libcap":                                    Bp2BuildDefaultTrueRecursively,
@@ -431,8 +435,19 @@
 		"libprotobuf-internal-protos",      // b/210751803, we don't handle path property for filegroups
 		"libprotobuf-internal-python-srcs", // b/210751803, we don't handle path property for filegroups
 		"libprotobuf-java-full",            // b/210751803, we don't handle path property for filegroups
+		"host-libprotobuf-java-full",       // b/210751803, we don't handle path property for filegroups
 		"libprotobuf-java-util-full",       // b/210751803, we don't handle path property for filegroups
-		"conscrypt",                        // b/210751803, we don't handle path property for filegroups
+
+		"conscrypt",          // b/210751803, we don't handle path property for filegroups
+		"conscrypt-for-host", // b/210751803, we don't handle path property for filegroups
+
+		"host-libprotobuf-java-lite",  // b/217236083, java_library cannot have deps without srcs
+		"host-libprotobuf-java-micro", // b/217236083, java_library cannot have deps without srcs
+		"host-libprotobuf-java-nano",  // b/217236083, java_library cannot have deps without srcs
+		"error_prone_core",            // b/217236083, java_library cannot have deps without srcs
+		"bouncycastle-host",           // b/217236083, java_library cannot have deps without srcs
+
+		"apex_manifest_proto_java", // b/215230097, we don't handle .proto files in java_library srcs attribute
 
 		// python protos
 		"libprotobuf-python",                           // contains .proto sources
@@ -470,14 +485,30 @@
 		"libdexfiled", // depends on unconverted modules: dexfile_operator_srcs, libartbased, libartpalette
 
 		// go deps:
-		"apex-protos",                    // depends on soong_zip, a go binary
-		"robolectric_tzdata",             // depends on soong_zip, a go binary
-		"robolectric-sqlite4java-native", // depends on soong_zip, a go binary
+		"apex-protos",                                                                                // depends on soong_zip, a go binary
+		"generated_android_icu4j_src_files", "generated_android_icu4j_test_files", "icu4c_test_data", // depends on unconverted modules: soong_zip
 		"host_bionic_linker_asm",         // depends on extract_linker, a go binary.
 		"host_bionic_linker_script",      // depends on extract_linker, a go binary.
+		"robolectric-sqlite4java-native", // depends on soong_zip, a go binary
+		"robolectric_tzdata",             // depends on soong_zip, a go binary
+
+		"android_icu4j_srcgen_binary", // Bazel build error: deps not allowed without srcs; move to runtime_deps
+		"core-icu4j-for-host",         // Bazel build error: deps not allowed without srcs; move to runtime_deps
 
 		// java deps
-		"bin2c_fastdeployagent", // depends on deployagent, a java binary
+		"android_icu4j_srcgen",          // depends on unconverted modules: currysrc
+		"bin2c_fastdeployagent",         // depends on deployagent, a java binary
+		"currysrc",                      // depends on unconverted modules: currysrc_org.eclipse, guavalib, jopt-simple-4.9
+		"robolectric-sqlite4java-0.282", // depends on unconverted modules: robolectric-sqlite4java-import, robolectric-sqlite4java-native
+		"timezone-host",                 // depends on unconverted modules: art.module.api.annotations
+		"truth-host-prebuilt",           // depends on unconverted modules: truth-prebuilt
+		"truth-prebuilt",                // depends on unconverted modules: asm-7.0, guava
+
+		"generated_android_icu4j_resources",      // depends on unconverted modules: android_icu4j_srcgen_binary, soong_zip
+		"generated_android_icu4j_test_resources", // depends on unconverted modules: android_icu4j_srcgen_binary, soong_zip
+
+		"art-script",     // depends on unconverted modules: dalvikvm, dex2oat
+		"dex2oat-script", // depends on unconverted modules: dex2oat
 	}
 
 	// Per-module denylist of cc_library modules to only generate the static
diff --git a/android/module.go b/android/module.go
index 00aed95..d0807c3 100644
--- a/android/module.go
+++ b/android/module.go
@@ -1165,6 +1165,11 @@
 		productConfigEnabledLabels, nil,
 	})
 
+	moduleSupportsDevice := mod.commonProperties.HostOrDeviceSupported&deviceSupported == deviceSupported
+	if mod.commonProperties.HostOrDeviceSupported != NeitherHostNorDeviceSupported && !moduleSupportsDevice {
+		enabledProperty.SetSelectValue(bazel.OsConfigurationAxis, Android.Name, proptools.BoolPtr(false))
+	}
+
 	platformEnabledAttribute, err := enabledProperty.ToLabelListAttribute(
 		bazel.LabelList{[]bazel.Label{bazel.Label{Label: "@platforms//:incompatible"}}, nil},
 		bazel.LabelList{[]bazel.Label{}, nil})
diff --git a/android/testing.go b/android/testing.go
index 8daf6b7..39864e1 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -781,19 +781,21 @@
 	return p
 }
 
-func (b baseTestingComponent) maybeBuildParamsFromDescription(desc string) TestingBuildParams {
+func (b baseTestingComponent) maybeBuildParamsFromDescription(desc string) (TestingBuildParams, []string) {
+	var searchedDescriptions []string
 	for _, p := range b.provider.BuildParamsForTests() {
+		searchedDescriptions = append(searchedDescriptions, p.Description)
 		if strings.Contains(p.Description, desc) {
-			return b.newTestingBuildParams(p)
+			return b.newTestingBuildParams(p), searchedDescriptions
 		}
 	}
-	return TestingBuildParams{}
+	return TestingBuildParams{}, searchedDescriptions
 }
 
 func (b baseTestingComponent) buildParamsFromDescription(desc string) TestingBuildParams {
-	p := b.maybeBuildParamsFromDescription(desc)
+	p, searchedDescriptions := b.maybeBuildParamsFromDescription(desc)
 	if p.Rule == nil {
-		panic(fmt.Errorf("couldn't find description %q", desc))
+		panic(fmt.Errorf("couldn't find description %q\nall descriptions:\n%s", desc, strings.Join(searchedDescriptions, "\n")))
 	}
 	return p
 }
@@ -860,7 +862,8 @@
 // MaybeDescription finds a call to ctx.Build with BuildParams.Description set to a the given string.  Returns an empty
 // BuildParams if no rule is found.
 func (b baseTestingComponent) MaybeDescription(desc string) TestingBuildParams {
-	return b.maybeBuildParamsFromDescription(desc)
+	p, _ := b.maybeBuildParamsFromDescription(desc)
+	return p
 }
 
 // Description finds a call to ctx.Build with BuildParams.Description set to a the given string.  Panics if no rule is
diff --git a/apex/bootclasspath_fragment_test.go b/apex/bootclasspath_fragment_test.go
index 8f44fc5..ce6b7f7 100644
--- a/apex/bootclasspath_fragment_test.go
+++ b/apex/bootclasspath_fragment_test.go
@@ -553,12 +553,66 @@
 			`prebuilt_com.android.art`,
 		})
 
+		// The boot images are installed in the APEX by Soong, so there shouldn't be any dexpreopt-related Make modules.
+		ensureDoesNotContainRequiredDeps(t, result.TestContext, "com.android.art", "android_common_com.android.art_image", []string{
+			"mybootclasspathfragment-dexpreopt-arm64-boot.art",
+			"mybootclasspathfragment-dexpreopt-arm64-boot.oat",
+			"mybootclasspathfragment-dexpreopt-arm64-boot.vdex",
+			"mybootclasspathfragment-dexpreopt-arm64-boot-bar.art",
+			"mybootclasspathfragment-dexpreopt-arm64-boot-bar.oat",
+			"mybootclasspathfragment-dexpreopt-arm64-boot-bar.vdex",
+			"mybootclasspathfragment-dexpreopt-arm-boot.art",
+			"mybootclasspathfragment-dexpreopt-arm-boot.oat",
+			"mybootclasspathfragment-dexpreopt-arm-boot.vdex",
+			"mybootclasspathfragment-dexpreopt-arm-boot-bar.art",
+			"mybootclasspathfragment-dexpreopt-arm-boot-bar.oat",
+			"mybootclasspathfragment-dexpreopt-arm-boot-bar.vdex",
+		})
+
 		// Make sure that the prebuilt bootclasspath_fragment copies its dex files to the predefined
 		// locations for the art image.
 		module := result.ModuleForTests("prebuilt_mybootclasspathfragment", "android_common_com.android.art")
 		checkCopiesToPredefinedLocationForArt(t, result.Config, module, "bar", "foo")
 	})
 
+	t.Run("boot image files from preferred prebuilt no boot image in apex", func(t *testing.T) {
+		result := android.GroupFixturePreparers(
+			commonPreparer,
+
+			// Configure some libraries in the art bootclasspath_fragment that match the source
+			// bootclasspath_fragment's contents property.
+			java.FixtureConfigureBootJars("com.android.art:foo", "com.android.art:bar"),
+			addSource("foo", "bar"),
+
+			// Make sure that a preferred prebuilt with consistent contents doesn't affect the apex.
+			addPrebuilt(true, "foo", "bar"),
+
+			java.FixtureSetBootImageInstallDirOnDevice("art", "system/framework"),
+		).RunTest(t)
+
+		ensureExactContents(t, result.TestContext, "com.android.art", "android_common_com.android.art_image", []string{
+			"etc/boot-image.prof",
+			"etc/classpaths/bootclasspath.pb",
+			"javalib/bar.jar",
+			"javalib/foo.jar",
+		})
+
+		ensureContainsRequiredDeps(t, result.TestContext, "com.android.art", "android_common_com.android.art_image", []string{
+			"mybootclasspathfragment-dexpreopt-arm64-boot.art",
+			"mybootclasspathfragment-dexpreopt-arm64-boot.oat",
+			"mybootclasspathfragment-dexpreopt-arm64-boot.vdex",
+			"mybootclasspathfragment-dexpreopt-arm64-boot-bar.art",
+			"mybootclasspathfragment-dexpreopt-arm64-boot-bar.oat",
+			"mybootclasspathfragment-dexpreopt-arm64-boot-bar.vdex",
+			"mybootclasspathfragment-dexpreopt-arm-boot.art",
+			"mybootclasspathfragment-dexpreopt-arm-boot.oat",
+			"mybootclasspathfragment-dexpreopt-arm-boot.vdex",
+			"mybootclasspathfragment-dexpreopt-arm-boot-bar.art",
+			"mybootclasspathfragment-dexpreopt-arm-boot-bar.oat",
+			"mybootclasspathfragment-dexpreopt-arm-boot-bar.vdex",
+		})
+	})
+
 	t.Run("source with inconsistency between config and contents", func(t *testing.T) {
 		android.GroupFixturePreparers(
 			commonPreparer,
@@ -631,6 +685,7 @@
 
 		// Configure some libraries in the art bootclasspath_fragment.
 		java.FixtureConfigureBootJars("com.android.art:foo", "com.android.art:bar"),
+		java.FixtureSetBootImageInstallDirOnDevice("art", "apex/com.android.art/javalib"),
 	)
 
 	bp := `
diff --git a/apex/builder.go b/apex/builder.go
index a66e1e0..1a1f22b 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -397,6 +397,22 @@
 	return output.OutputPath
 }
 
+func markManifestTestOnly(ctx android.ModuleContext, androidManifestFile android.Path) android.Path {
+	return java.ManifestFixer(java.ManifestFixerParams{
+		Ctx:                   ctx,
+		Manifest:              androidManifestFile,
+		SdkContext:            nil,
+		ClassLoaderContexts:   nil,
+		IsLibrary:             false,
+		UseEmbeddedNativeLibs: false,
+		UsesNonSdkApis:        false,
+		UseEmbeddedDex:        false,
+		HasNoCode:             false,
+		TestOnly:              true,
+		LoggingParent:         "",
+	})
+}
+
 // buildUnflattendApex creates build rules to build an APEX using apexer.
 func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) {
 	apexType := a.properties.ApexType
@@ -595,6 +611,11 @@
 
 		if a.properties.AndroidManifest != nil {
 			androidManifestFile := android.PathForModuleSrc(ctx, proptools.String(a.properties.AndroidManifest))
+
+			if a.testApex {
+				androidManifestFile = markManifestTestOnly(ctx, androidManifestFile)
+			}
+
 			implicitInputs = append(implicitInputs, androidManifestFile)
 			optFlags = append(optFlags, "--android_manifest "+androidManifestFile.String())
 		}
diff --git a/apex/prebuilt.go b/apex/prebuilt.go
index 02d8075..158c804 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -65,7 +65,8 @@
 	// Installed locations of symlinks for backward compatibility.
 	compatSymlinks android.InstallPaths
 
-	hostRequired []string
+	hostRequired        []string
+	requiredModuleNames []string
 }
 
 type sanitizedPrebuilt interface {
@@ -195,9 +196,19 @@
 				}
 				p.apexFilesForAndroidMk = append(p.apexFilesForAndroidMk, af)
 			}
-		} else if tag == exportedBootclasspathFragmentTag ||
-			tag == exportedSystemserverclasspathFragmentTag {
-			// Visit the children of the bootclasspath_fragment and systemserver_fragment.
+		} else if tag == exportedBootclasspathFragmentTag {
+			bcpfModule, ok := child.(*java.PrebuiltBootclasspathFragmentModule)
+			if !ok {
+				ctx.PropertyErrorf("exported_bootclasspath_fragments", "%q is not a prebuilt_bootclasspath_fragment module", name)
+				return false
+			}
+			for _, makeModuleName := range bcpfModule.BootImageDeviceInstallMakeModules() {
+				p.requiredModuleNames = append(p.requiredModuleNames, makeModuleName)
+			}
+			// Visit the children of the bootclasspath_fragment.
+			return true
+		} else if tag == exportedSystemserverclasspathFragmentTag {
+			// Visit the children of the systemserver_fragment.
 			return true
 		}
 
@@ -211,6 +222,7 @@
 		entries.AddStrings("LOCAL_TARGET_REQUIRED_MODULES", fi.targetRequiredModuleNames...)
 		entries.AddStrings("LOCAL_HOST_REQUIRED_MODULES", fi.hostRequiredModuleNames...)
 	}
+	entries.AddStrings("LOCAL_REQUIRED_MODULES", p.requiredModuleNames...)
 }
 
 func (p *prebuiltCommon) AndroidMkEntries() []android.AndroidMkEntries {
diff --git a/bp2build/cc_binary_conversion_test.go b/bp2build/cc_binary_conversion_test.go
index a156480..f9adc78 100644
--- a/bp2build/cc_binary_conversion_test.go
+++ b/bp2build/cc_binary_conversion_test.go
@@ -84,13 +84,13 @@
 	t.Helper()
 	testCase := tc
 	for i, tar := range testCase.targets {
-		if tar.typ != "cc_binary" {
-			continue
-		}
-		tar.attrs["target_compatible_with"] = `select({
+		switch tar.typ {
+		case "cc_binary", "proto_library", "cc_lite_proto_library":
+			tar.attrs["target_compatible_with"] = `select({
         "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
         "//conditions:default": [],
     })`
+		}
 		testCase.targets[i] = tar
 	}
 	moduleTypeUnderTest := "cc_binary_host"
diff --git a/bp2build/genrule_conversion_test.go b/bp2build/genrule_conversion_test.go
index 0666da7..9244b99 100644
--- a/bp2build/genrule_conversion_test.go
+++ b/bp2build/genrule_conversion_test.go
@@ -97,13 +97,22 @@
 }`
 
 	for _, tc := range testCases {
+		moduleAttrs := attrNameToString{
+			"cmd":   fmt.Sprintf(`"$(location :foo.tool) --genDir=%s arg $(SRCS) $(OUTS)"`, tc.genDir),
+			"outs":  `["foo.out"]`,
+			"srcs":  `["foo.in"]`,
+			"tools": `[":foo.tool"]`,
+		}
+
+		if tc.moduleType == "java_genrule_host" {
+			moduleAttrs["target_compatible_with"] = `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`
+		}
+
 		expectedBazelTargets := []string{
-			makeBazelTarget("genrule", "foo", attrNameToString{
-				"cmd":   fmt.Sprintf(`"$(location :foo.tool) --genDir=%s arg $(SRCS) $(OUTS)"`, tc.genDir),
-				"outs":  `["foo.out"]`,
-				"srcs":  `["foo.in"]`,
-				"tools": `[":foo.tool"]`,
-			}),
+			makeBazelTarget("genrule", "foo", moduleAttrs),
 		}
 
 		t.Run(tc.moduleType, func(t *testing.T) {
@@ -158,25 +167,36 @@
     bazel_module: { bp2build_available: true },
 }`
 
-	expectedBazelTargets :=
-		[]string{
-			makeBazelTarget("genrule", "foo", attrNameToString{
-				"cmd":   `"$(locations :foo.tools) -s $(OUTS) $(SRCS)"`,
-				"outs":  `["foo.out"]`,
-				"srcs":  `["foo.in"]`,
-				"tools": `[":foo.tools"]`,
-			}),
-			makeBazelTarget("genrule", "foo.tools", attrNameToString{
-				"cmd": `"cp $(SRCS) $(OUTS)"`,
-				"outs": `[
+	for _, tc := range testCases {
+		fooAttrs := attrNameToString{
+			"cmd":   `"$(locations :foo.tools) -s $(OUTS) $(SRCS)"`,
+			"outs":  `["foo.out"]`,
+			"srcs":  `["foo.in"]`,
+			"tools": `[":foo.tools"]`,
+		}
+		fooToolsAttrs := attrNameToString{
+			"cmd": `"cp $(SRCS) $(OUTS)"`,
+			"outs": `[
         "foo_tool.out",
         "foo_tool2.out",
     ]`,
-				"srcs": `["foo_tool.in"]`,
-			}),
+			"srcs": `["foo_tool.in"]`,
 		}
 
-	for _, tc := range testCases {
+		if tc.moduleType == "java_genrule_host" {
+			compatibilityAttrs := `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`
+			fooAttrs["target_compatible_with"] = compatibilityAttrs
+			fooToolsAttrs["target_compatible_with"] = compatibilityAttrs
+		}
+
+		expectedBazelTargets := []string{
+			makeBazelTarget("genrule", "foo", fooAttrs),
+			makeBazelTarget("genrule", "foo.tools", fooToolsAttrs),
+		}
+
 		t.Run(tc.moduleType, func(t *testing.T) {
 			runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {},
 				bp2buildTestCase{
@@ -221,16 +241,25 @@
     bazel_module: { bp2build_available: true },
 }`
 
-	expectedBazelTargets := []string{
-		makeBazelTarget("genrule", "foo", attrNameToString{
+	for _, tc := range testCases {
+		moduleAttrs := attrNameToString{
 			"cmd":   `"$(locations //other:foo.tool) -s $(OUTS) $(SRCS)"`,
 			"outs":  `["foo.out"]`,
 			"srcs":  `["foo.in"]`,
 			"tools": `["//other:foo.tool"]`,
-		}),
-	}
+		}
 
-	for _, tc := range testCases {
+		if tc.moduleType == "java_genrule_host" {
+			moduleAttrs["target_compatible_with"] = `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`
+		}
+
+		expectedBazelTargets := []string{
+			makeBazelTarget("genrule", "foo", moduleAttrs),
+		}
+
 		t.Run(tc.moduleType, func(t *testing.T) {
 			runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {},
 				bp2buildTestCase{
@@ -276,16 +305,25 @@
     bazel_module: { bp2build_available: true },
 }`
 
-	expectedBazelTargets := []string{
-		makeBazelTarget("genrule", "foo", attrNameToString{
+	for _, tc := range testCases {
+		moduleAttrs := attrNameToString{
 			"cmd":   `"$(locations //other:foo.tool) -s $(OUTS) $(location //other:other.tool)"`,
 			"outs":  `["foo.out"]`,
 			"srcs":  `["//other:other.tool"]`,
 			"tools": `["//other:foo.tool"]`,
-		}),
-	}
+		}
 
-	for _, tc := range testCases {
+		if tc.moduleType == "java_genrule_host" {
+			moduleAttrs["target_compatible_with"] = `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`
+		}
+
+		expectedBazelTargets := []string{
+			makeBazelTarget("genrule", "foo", moduleAttrs),
+		}
+
 		t.Run(tc.moduleType, func(t *testing.T) {
 			runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {},
 				bp2buildTestCase{
@@ -331,8 +369,8 @@
     bazel_module: { bp2build_available: true },
 }`
 
-	expectedBazelTargets := []string{
-		makeBazelTarget("genrule", "foo", attrNameToString{
+	for _, tc := range testCases {
+		moduleAttrs := attrNameToString{
 			"cmd":  `"$(location //other:foo.tool) -s $(OUTS) $(SRCS)"`,
 			"outs": `["foo.out"]`,
 			"srcs": `["foo.in"]`,
@@ -340,9 +378,19 @@
         "//other:foo.tool",
         "//other:other.tool",
     ]`,
-		})}
+		}
 
-	for _, tc := range testCases {
+		if tc.moduleType == "java_genrule_host" {
+			moduleAttrs["target_compatible_with"] = `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`
+		}
+
+		expectedBazelTargets := []string{
+			makeBazelTarget("genrule", "foo", moduleAttrs),
+		}
+
 		t.Run(tc.moduleType, func(t *testing.T) {
 			runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {},
 				bp2buildTestCase{
@@ -388,8 +436,8 @@
     bazel_module: { bp2build_available: true },
 }`
 
-	expectedBazelTargets := []string{
-		makeBazelTarget("genrule", "foo", attrNameToString{
+	for _, tc := range testCases {
+		moduleAttrs := attrNameToString{
 			"cmd":  `"$(locations //other:foo.tool) -s $(OUTS) $(SRCS)"`,
 			"outs": `["foo.out"]`,
 			"srcs": `["foo.in"]`,
@@ -397,9 +445,19 @@
         "//other:foo.tool",
         "//other:other.tool",
     ]`,
-		})}
+		}
 
-	for _, tc := range testCases {
+		if tc.moduleType == "java_genrule_host" {
+			moduleAttrs["target_compatible_with"] = `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`
+		}
+
+		expectedBazelTargets := []string{
+			makeBazelTarget("genrule", "foo", moduleAttrs),
+		}
+
 		t.Run(tc.moduleType, func(t *testing.T) {
 			runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {},
 				bp2buildTestCase{
@@ -444,14 +502,24 @@
     bazel_module: { bp2build_available: true },
 }`
 
-	expectedBazelTargets := []string{
-		makeBazelTarget("genrule", "foo", attrNameToString{
+	for _, tc := range testCases {
+		moduleAttrs := attrNameToString{
 			"cmd":  `"cp $(SRCS) $(OUTS)"`,
 			"outs": `["foo.out"]`,
 			"srcs": `["foo.in"]`,
-		})}
+		}
 
-	for _, tc := range testCases {
+		if tc.moduleType == "java_genrule_host" {
+			moduleAttrs["target_compatible_with"] = `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`
+		}
+
+		expectedBazelTargets := []string{
+			makeBazelTarget("genrule", "foo", moduleAttrs),
+		}
+
 		t.Run(tc.moduleType, func(t *testing.T) {
 			runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {},
 				bp2buildTestCase{
diff --git a/bp2build/java_binary_host_conversion_test.go b/bp2build/java_binary_host_conversion_test.go
index c683b25..65136d9 100644
--- a/bp2build/java_binary_host_conversion_test.go
+++ b/bp2build/java_binary_host_conversion_test.go
@@ -59,6 +59,10 @@
 				"deps":       `["//other:jni-lib-1"]`,
 				"jvm_flags":  `["-Djava.library.path=$${RUNPATH}other"]`,
 				"javacopts":  `["-Xdoclint:all/protected"]`,
+				"target_compatible_with": `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`,
 			}),
 		},
 	})
diff --git a/bp2build/java_library_host_conversion_test.go b/bp2build/java_library_host_conversion_test.go
index 6ac82db..73abdd2 100644
--- a/bp2build/java_library_host_conversion_test.go
+++ b/bp2build/java_library_host_conversion_test.go
@@ -48,9 +48,17 @@
 			makeBazelTarget("java_library", "java-lib-host-1", attrNameToString{
 				"srcs": `["a.java"]`,
 				"deps": `[":java-lib-host-2"]`,
+				"target_compatible_with": `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`,
 			}),
 			makeBazelTarget("java_library", "java-lib-host-2", attrNameToString{
 				"srcs": `["c.java"]`,
+				"target_compatible_with": `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`,
 			}),
 		},
 	})
diff --git a/bp2build/java_plugin_conversion_test.go b/bp2build/java_plugin_conversion_test.go
new file mode 100644
index 0000000..ff13bb0
--- /dev/null
+++ b/bp2build/java_plugin_conversion_test.go
@@ -0,0 +1,72 @@
+// 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 (
+	"testing"
+
+	"android/soong/android"
+	"android/soong/java"
+)
+
+func runJavaPluginTestCase(t *testing.T, tc bp2buildTestCase) {
+	t.Helper()
+	(&tc).moduleTypeUnderTest = "java_plugin"
+	(&tc).moduleTypeUnderTestFactory = java.PluginFactory
+	runBp2BuildTestCase(t, func(ctx android.RegistrationContext) {
+		ctx.RegisterModuleType("java_library", java.LibraryFactory)
+	}, tc)
+}
+
+func TestJavaPlugin(t *testing.T) {
+	runJavaPluginTestCase(t, bp2buildTestCase{
+		description: "java_plugin with srcs, libs, static_libs",
+		blueprint: `java_plugin {
+    name: "java-plug-1",
+    srcs: ["a.java", "b.java"],
+    libs: ["java-lib-1"],
+    static_libs: ["java-lib-2"],
+    bazel_module: { bp2build_available: true },
+}
+
+java_library {
+    name: "java-lib-1",
+    srcs: ["b.java"],
+    bazel_module: { bp2build_available: false },
+}
+
+java_library {
+    name: "java-lib-2",
+    srcs: ["c.java"],
+    bazel_module: { bp2build_available: false },
+}`,
+		expectedBazelTargets: []string{
+			makeBazelTarget("java_plugin", "java-plug-1", attrNameToString{
+				"target_compatible_with": `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`,
+				"deps": `[
+        ":java-lib-1",
+        ":java-lib-2",
+    ]`,
+				"srcs": `[
+        "a.java",
+        "b.java",
+    ]`,
+			}),
+		},
+	})
+}
diff --git a/bp2build/python_binary_conversion_test.go b/bp2build/python_binary_conversion_test.go
index 40c8ba1..dfa11d1 100644
--- a/bp2build/python_binary_conversion_test.go
+++ b/bp2build/python_binary_conversion_test.go
@@ -51,6 +51,10 @@
         "b/c.py",
         "b/d.py",
     ]`,
+				"target_compatible_with": `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`,
 			}),
 		},
 	})
@@ -80,6 +84,10 @@
 			makeBazelTarget("py_binary", "foo", attrNameToString{
 				"python_version": `"PY2"`,
 				"srcs":           `["a.py"]`,
+				"target_compatible_with": `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`,
 			}),
 		},
 	})
@@ -109,6 +117,10 @@
 			// python_version is PY3 by default.
 			makeBazelTarget("py_binary", "foo", attrNameToString{
 				"srcs": `["a.py"]`,
+				"target_compatible_with": `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`,
 			}),
 		},
 	})
@@ -141,6 +153,10 @@
         "//build/bazel/platforms/arch:x86": ["x86.py"],
         "//conditions:default": [],
     })`,
+				"target_compatible_with": `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`,
 			}),
 		},
 	})
diff --git a/bp2build/python_library_conversion_test.go b/bp2build/python_library_conversion_test.go
index 6b26105..356d52e 100644
--- a/bp2build/python_library_conversion_test.go
+++ b/bp2build/python_library_conversion_test.go
@@ -11,19 +11,51 @@
 // TODO(alexmarquez): Should be lifted into a generic Bp2Build file
 type PythonLibBp2Build func(ctx android.TopDownMutatorContext)
 
-func runPythonLibraryTestCase(t *testing.T, tc bp2buildTestCase) {
+type pythonLibBp2BuildTestCase struct {
+	description          string
+	filesystem           map[string]string
+	blueprint            string
+	expectedBazelTargets []testBazelTarget
+}
+
+func convertPythonLibTestCaseToBp2build_Host(tc pythonLibBp2BuildTestCase) bp2buildTestCase {
+	for i := range tc.expectedBazelTargets {
+		tc.expectedBazelTargets[i].attrs["target_compatible_with"] = `select({
+        "//build/bazel/platforms/os:android": ["@platforms//:incompatible"],
+        "//conditions:default": [],
+    })`
+	}
+
+	return convertPythonLibTestCaseToBp2build(tc)
+}
+
+func convertPythonLibTestCaseToBp2build(tc pythonLibBp2BuildTestCase) bp2buildTestCase {
+	var bp2BuildTargets []string
+	for _, t := range tc.expectedBazelTargets {
+		bp2BuildTargets = append(bp2BuildTargets, makeBazelTarget(t.typ, t.name, t.attrs))
+	}
+	return bp2buildTestCase{
+		description:          tc.description,
+		filesystem:           tc.filesystem,
+		blueprint:            tc.blueprint,
+		expectedBazelTargets: bp2BuildTargets,
+	}
+}
+
+func runPythonLibraryTestCase(t *testing.T, tc pythonLibBp2BuildTestCase) {
 	t.Helper()
-	testCase := tc
+	testCase := convertPythonLibTestCaseToBp2build(tc)
 	testCase.description = fmt.Sprintf(testCase.description, "python_library")
 	testCase.blueprint = fmt.Sprintf(testCase.blueprint, "python_library")
 	testCase.moduleTypeUnderTest = "python_library"
 	testCase.moduleTypeUnderTestFactory = python.PythonLibraryFactory
+
 	runBp2BuildTestCaseSimple(t, testCase)
 }
 
-func runPythonLibraryHostTestCase(t *testing.T, tc bp2buildTestCase) {
+func runPythonLibraryHostTestCase(t *testing.T, tc pythonLibBp2BuildTestCase) {
 	t.Helper()
-	testCase := tc
+	testCase := convertPythonLibTestCaseToBp2build_Host(tc)
 	testCase.description = fmt.Sprintf(testCase.description, "python_library_host")
 	testCase.blueprint = fmt.Sprintf(testCase.blueprint, "python_library_host")
 	testCase.moduleTypeUnderTest = "python_library_host"
@@ -34,14 +66,14 @@
 		testCase)
 }
 
-func runPythonLibraryTestCases(t *testing.T, tc bp2buildTestCase) {
+func runPythonLibraryTestCases(t *testing.T, tc pythonLibBp2BuildTestCase) {
 	t.Helper()
 	runPythonLibraryTestCase(t, tc)
 	runPythonLibraryHostTestCase(t, tc)
 }
 
 func TestSimplePythonLib(t *testing.T) {
-	testCases := []bp2buildTestCase{
+	testCases := []pythonLibBp2BuildTestCase{
 		{
 			description: "simple %s converts to a native py_library",
 			filesystem: map[string]string{
@@ -64,17 +96,21 @@
       srcs: ["b/e.py"],
       bazel_module: { bp2build_available: false },
     }`,
-			expectedBazelTargets: []string{
-				makeBazelTarget("py_library", "foo", attrNameToString{
-					"data": `["files/data.txt"]`,
-					"deps": `[":bar"]`,
-					"srcs": `[
+			expectedBazelTargets: []testBazelTarget{
+				{
+					typ:  "py_library",
+					name: "foo",
+					attrs: attrNameToString{
+						"data": `["files/data.txt"]`,
+						"deps": `[":bar"]`,
+						"srcs": `[
         "a.py",
         "b/c.py",
         "b/d.py",
     ]`,
-					"srcs_version": `"PY3"`,
-				}),
+						"srcs_version": `"PY3"`,
+					},
+				},
 			},
 		},
 		{
@@ -93,11 +129,15 @@
 
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{
-				makeBazelTarget("py_library", "foo", attrNameToString{
-					"srcs":         `["a.py"]`,
-					"srcs_version": `"PY2"`,
-				}),
+			expectedBazelTargets: []testBazelTarget{
+				{
+					typ:  "py_library",
+					name: "foo",
+					attrs: attrNameToString{
+						"srcs":         `["a.py"]`,
+						"srcs_version": `"PY2"`,
+					},
+				},
 			},
 		},
 		{
@@ -116,11 +156,15 @@
 
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{
-				makeBazelTarget("py_library", "foo", attrNameToString{
-					"srcs":         `["a.py"]`,
-					"srcs_version": `"PY3"`,
-				}),
+			expectedBazelTargets: []testBazelTarget{
+				{
+					typ:  "py_library",
+					name: "foo",
+					attrs: attrNameToString{
+						"srcs":         `["a.py"]`,
+						"srcs_version": `"PY3"`,
+					},
+				},
 			},
 		},
 		{
@@ -139,11 +183,15 @@
 
     bazel_module: { bp2build_available: true },
 }`,
-			expectedBazelTargets: []string{
-				// srcs_version is PY2ANDPY3 by default.
-				makeBazelTarget("py_library", "foo", attrNameToString{
-					"srcs": `["a.py"]`,
-				}),
+			expectedBazelTargets: []testBazelTarget{
+				{
+					// srcs_version is PY2ANDPY3 by default.
+					typ:  "py_library",
+					name: "foo",
+					attrs: attrNameToString{
+						"srcs": `["a.py"]`,
+					},
+				},
 			},
 		},
 	}
@@ -156,7 +204,7 @@
 }
 
 func TestPythonArchVariance(t *testing.T) {
-	runPythonLibraryTestCases(t, bp2buildTestCase{
+	runPythonLibraryTestCases(t, pythonLibBp2BuildTestCase{
 		description: "test %s arch variants",
 		filesystem: map[string]string{
 			"dir/arm.py": "",
@@ -173,15 +221,19 @@
 						 },
 					},
 				 }`,
-		expectedBazelTargets: []string{
-			makeBazelTarget("py_library", "foo", attrNameToString{
-				"srcs": `select({
+		expectedBazelTargets: []testBazelTarget{
+			{
+				typ:  "py_library",
+				name: "foo",
+				attrs: attrNameToString{
+					"srcs": `select({
         "//build/bazel/platforms/arch:arm": ["arm.py"],
         "//build/bazel/platforms/arch:x86": ["x86.py"],
         "//conditions:default": [],
     })`,
-				"srcs_version": `"PY3"`,
-			}),
+					"srcs_version": `"PY3"`,
+				},
+			},
 		},
 	})
 }
diff --git a/cc/binary.go b/cc/binary.go
index ee3de3f..05923b1 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -76,7 +76,6 @@
 // cc_binary_host produces a binary that is runnable on a host.
 func BinaryHostFactory() android.Module {
 	module, _ := newBinary(android.HostSupported, true)
-	module.bazelable = true
 	return module.Init()
 }
 
@@ -606,19 +605,12 @@
 		Features: baseAttrs.features,
 	}
 
-	var enabledProperty bazel.BoolAttribute
-	if typ == "cc_binary_host" {
-		falseVal := false
-		enabledProperty.SetSelectValue(bazel.OsConfigurationAxis, android.Android.Name, &falseVal)
-	}
-
-	ctx.CreateBazelTargetModuleWithRestrictions(bazel.BazelTargetModuleProperties{
+	ctx.CreateBazelTargetModule(bazel.BazelTargetModuleProperties{
 		Rule_class:        "cc_binary",
 		Bzl_load_location: "//build/bazel/rules:cc_binary.bzl",
 	},
 		android.CommonAttributes{Name: m.Name()},
-		attrs,
-		enabledProperty)
+		attrs)
 }
 
 // binaryAttributes contains Bazel attributes corresponding to a cc binary
diff --git a/cc/bp2build.go b/cc/bp2build.go
index c5eab06..30c3c50 100644
--- a/cc/bp2build.go
+++ b/cc/bp2build.go
@@ -844,10 +844,8 @@
 
 func bazelLabelForStaticModule(ctx android.BazelConversionPathContext, m blueprint.Module) string {
 	label := android.BazelModuleLabel(ctx, m)
-	if aModule, ok := m.(android.Module); ok {
-		if ctx.OtherModuleType(aModule) == "cc_library" && !android.GenerateCcLibraryStaticOnly(m.Name()) {
-			label += "_bp2build_cc_library_static"
-		}
+	if ccModule, ok := m.(*Module); ok && ccModule.typ() == fullLibrary && !android.GenerateCcLibraryStaticOnly(m.Name()) {
+		label += "_bp2build_cc_library_static"
 	}
 	return label
 }
diff --git a/cc/cc.go b/cc/cc.go
index 31babc2..46e3b91 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -1751,7 +1751,7 @@
 // Returns true if Bazel was successfully used for the analysis of this module.
 func (c *Module) maybeGenerateBazelActions(actx android.ModuleContext) bool {
 	var bazelModuleLabel string
-	if actx.ModuleType() == "cc_library" && c.static() {
+	if c.typ() == fullLibrary && c.static() {
 		// cc_library is a special case in bp2build; two targets are generated -- one for each
 		// of the shared and static variants. The shared variant keeps the module name, but the
 		// static variant uses a different suffixed name.
@@ -1759,6 +1759,7 @@
 	} else {
 		bazelModuleLabel = c.GetBazelLabel(actx, c)
 	}
+
 	bazelActionsUsed := false
 	// Mixed builds mode is disabled for modules outside of device OS.
 	// TODO(b/200841190): Support non-device OS in mixed builds.
@@ -3466,17 +3467,23 @@
 
 var _ snapshot.RelativeInstallPath = (*Module)(nil)
 
-// ConvertWithBp2build converts Module to Bazel for bp2build.
-func (c *Module) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
-	prebuilt := c.IsPrebuilt()
+type moduleType int
+
+const (
+	unknownType moduleType = iota
+	binary
+	object
+	fullLibrary
+	staticLibrary
+	sharedLibrary
+	headerLibrary
+)
+
+func (c *Module) typ() moduleType {
 	if c.Binary() {
-		if !prebuilt {
-			binaryBp2build(ctx, c, ctx.ModuleType())
-		}
+		return binary
 	} else if c.Object() {
-		if !prebuilt {
-			objectBp2Build(ctx, c)
-		}
+		return object
 	} else if c.CcLibrary() {
 		static := false
 		shared := false
@@ -3487,25 +3494,48 @@
 			static = library.MutatedProperties.BuildStatic
 			shared = library.MutatedProperties.BuildShared
 		}
-
 		if static && shared {
-			if !prebuilt {
-				libraryBp2Build(ctx, c)
-			}
+			return fullLibrary
 		} else if !static && !shared {
-			libraryHeadersBp2Build(ctx, c)
+			return headerLibrary
 		} else if static {
-			if prebuilt {
-				prebuiltLibraryStaticBp2Build(ctx, c)
-			} else {
-				sharedOrStaticLibraryBp2Build(ctx, c, true)
-			}
-		} else if shared {
-			if prebuilt {
-				prebuiltLibrarySharedBp2Build(ctx, c)
-			} else {
-				sharedOrStaticLibraryBp2Build(ctx, c, false)
-			}
+			return staticLibrary
+		}
+		return sharedLibrary
+	}
+	return unknownType
+}
+
+// ConvertWithBp2build converts Module to Bazel for bp2build.
+func (c *Module) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	prebuilt := c.IsPrebuilt()
+	switch c.typ() {
+	case binary:
+		if !prebuilt {
+			binaryBp2build(ctx, c, ctx.ModuleType())
+		}
+	case object:
+		if !prebuilt {
+			objectBp2Build(ctx, c)
+		}
+	case fullLibrary:
+		if !prebuilt {
+			libraryBp2Build(ctx, c)
+		}
+	case headerLibrary:
+		libraryHeadersBp2Build(ctx, c)
+	case staticLibrary:
+
+		if prebuilt {
+			prebuiltLibraryStaticBp2Build(ctx, c)
+		} else {
+			sharedOrStaticLibraryBp2Build(ctx, c, true)
+		}
+	case sharedLibrary:
+		if prebuilt {
+			prebuiltLibrarySharedBp2Build(ctx, c)
+		} else {
+			sharedOrStaticLibraryBp2Build(ctx, c, false)
 		}
 	}
 }
diff --git a/cc/config/arm64_linux_host.go b/cc/config/arm64_linux_host.go
index 853d818..5c7f926 100644
--- a/cc/config/arm64_linux_host.go
+++ b/cc/config/arm64_linux_host.go
@@ -41,7 +41,6 @@
 		"-Wl,-z,relro",
 		"-Wl,-z,now",
 		"-Wl,--build-id=md5",
-		"-Wl,--warn-shared-textrel",
 		"-Wl,--fatal-warnings",
 		"-Wl,--hash-style=gnu",
 		"-Wl,--no-undefined-version",
diff --git a/cc/config/global.go b/cc/config/global.go
index 5acc7f5..48a8b48 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -95,6 +95,9 @@
 		// Nested and array designated initialization is nice to have.
 		"-Wno-c99-designator",
 
+		// Many old files still have GNU designator syntax.
+		"-Wno-gnu-designator",
+
 		// Warnings from clang-12
 		"-Wno-gnu-folding-constant",
 
@@ -139,7 +142,6 @@
 		"-Wl,-z,relro",
 		"-Wl,-z,now",
 		"-Wl,--build-id=md5",
-		"-Wl,--warn-shared-textrel",
 		"-Wl,--fatal-warnings",
 		"-Wl,--no-undefined-version",
 		// TODO: Eventually we should link against a libunwind.a with hidden symbols, and then these
diff --git a/cc/config/x86_linux_bionic_host.go b/cc/config/x86_linux_bionic_host.go
index 4b7ba6a..976cc25 100644
--- a/cc/config/x86_linux_bionic_host.go
+++ b/cc/config/x86_linux_bionic_host.go
@@ -47,7 +47,6 @@
 		"-Wl,-z,relro",
 		"-Wl,-z,now",
 		"-Wl,--build-id=md5",
-		"-Wl,--warn-shared-textrel",
 		"-Wl,--fatal-warnings",
 		"-Wl,--hash-style=gnu",
 		"-Wl,--no-undefined-version",
diff --git a/java/aar.go b/java/aar.go
index aabbec6..4687424 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -276,9 +276,19 @@
 	manifestFile := proptools.StringDefault(a.aaptProperties.Manifest, "AndroidManifest.xml")
 	manifestSrcPath := android.PathForModuleSrc(ctx, manifestFile)
 
-	manifestPath := manifestFixer(ctx, manifestSrcPath, sdkContext, classLoaderContexts,
-		a.isLibrary, a.useEmbeddedNativeLibs, a.usesNonSdkApis, a.useEmbeddedDex, a.hasNoCode,
-		a.LoggingParent)
+	manifestPath := ManifestFixer(ManifestFixerParams{
+		Ctx:                   ctx,
+		Manifest:              manifestSrcPath,
+		SdkContext:            sdkContext,
+		ClassLoaderContexts:   classLoaderContexts,
+		IsLibrary:             a.isLibrary,
+		UseEmbeddedNativeLibs: a.useEmbeddedNativeLibs,
+		UsesNonSdkApis:        a.usesNonSdkApis,
+		UseEmbeddedDex:        a.useEmbeddedDex,
+		HasNoCode:             a.hasNoCode,
+		TestOnly:              false,
+		LoggingParent:         a.LoggingParent,
+	})
 
 	// Add additional manifest files to transitive manifests.
 	additionalManifests := android.PathsForModuleSrc(ctx, a.aaptProperties.Additional_manifests)
diff --git a/java/android_manifest.go b/java/android_manifest.go
index f29d8ad..a5d5b97 100644
--- a/java/android_manifest.go
+++ b/java/android_manifest.go
@@ -28,13 +28,10 @@
 var manifestFixerRule = pctx.AndroidStaticRule("manifestFixer",
 	blueprint.RuleParams{
 		Command: `${config.ManifestFixerCmd} ` +
-			`--minSdkVersion ${minSdkVersion} ` +
-			`--targetSdkVersion ${targetSdkVersion} ` +
-			`--raise-min-sdk-version ` +
 			`$args $in $out`,
 		CommandDeps: []string{"${config.ManifestFixerCmd}"},
 	},
-	"minSdkVersion", "targetSdkVersion", "args")
+	"args")
 
 var manifestMergerRule = pctx.AndroidStaticRule("manifestMerger",
 	blueprint.RuleParams{
@@ -58,84 +55,110 @@
 	return targetSdkVersion
 }
 
-// Uses manifest_fixer.py to inject minSdkVersion, etc. into an AndroidManifest.xml
-func manifestFixer(ctx android.ModuleContext, manifest android.Path, sdkContext android.SdkContext,
-	classLoaderContexts dexpreopt.ClassLoaderContextMap, isLibrary, useEmbeddedNativeLibs, usesNonSdkApis,
-	useEmbeddedDex, hasNoCode bool, loggingParent string) android.Path {
+type ManifestFixerParams struct {
+	Ctx                   android.ModuleContext
+	Manifest              android.Path
+	SdkContext            android.SdkContext
+	ClassLoaderContexts   dexpreopt.ClassLoaderContextMap
+	IsLibrary             bool
+	UseEmbeddedNativeLibs bool
+	UsesNonSdkApis        bool
+	UseEmbeddedDex        bool
+	HasNoCode             bool
+	TestOnly              bool
+	LoggingParent         string
+}
 
+// Uses manifest_fixer.py to inject minSdkVersion, etc. into an AndroidManifest.xml
+func ManifestFixer(params ManifestFixerParams) android.Path {
 	var args []string
-	if isLibrary {
+
+	if params.IsLibrary {
 		args = append(args, "--library")
-	} else {
-		minSdkVersion, err := sdkContext.MinSdkVersion(ctx).EffectiveVersion(ctx)
+	} else if params.SdkContext != nil {
+		minSdkVersion, err := params.SdkContext.MinSdkVersion(params.Ctx).EffectiveVersion(params.Ctx)
 		if err != nil {
-			ctx.ModuleErrorf("invalid minSdkVersion: %s", err)
+			params.Ctx.ModuleErrorf("invalid minSdkVersion: %s", err)
 		}
 		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",
+			args = append(args, fmt.Sprintf("--extract-native-libs=%v", !params.UseEmbeddedNativeLibs))
+		} else if params.UseEmbeddedNativeLibs {
+			params.Ctx.ModuleErrorf("module attempted to store uncompressed native libraries, but minSdkVersion=%d doesn't support it",
 				minSdkVersion)
 		}
 	}
 
-	if usesNonSdkApis {
+	if params.UsesNonSdkApis {
 		args = append(args, "--uses-non-sdk-api")
 	}
 
-	if useEmbeddedDex {
+	if params.UseEmbeddedDex {
 		args = append(args, "--use-embedded-dex")
 	}
 
-	// manifest_fixer should add only the implicit SDK libraries inferred by Soong, not those added
-	// explicitly via `uses_libs`/`optional_uses_libs`.
-	requiredUsesLibs, optionalUsesLibs := classLoaderContexts.ImplicitUsesLibs()
-	for _, usesLib := range requiredUsesLibs {
-		args = append(args, "--uses-library", usesLib)
-	}
-	for _, usesLib := range optionalUsesLibs {
-		args = append(args, "--optional-uses-library", usesLib)
+	if params.ClassLoaderContexts != nil {
+		// manifest_fixer should add only the implicit SDK libraries inferred by Soong, not those added
+		// explicitly via `uses_libs`/`optional_uses_libs`.
+		requiredUsesLibs, optionalUsesLibs := params.ClassLoaderContexts.ImplicitUsesLibs()
+
+		for _, usesLib := range requiredUsesLibs {
+			args = append(args, "--uses-library", usesLib)
+		}
+		for _, usesLib := range optionalUsesLibs {
+			args = append(args, "--optional-uses-library", usesLib)
+		}
 	}
 
-	if hasNoCode {
+	if params.HasNoCode {
 		args = append(args, "--has-no-code")
 	}
 
-	if loggingParent != "" {
-		args = append(args, "--logging-parent", loggingParent)
+	if params.TestOnly {
+		args = append(args, "--test-only")
+	}
+
+	if params.LoggingParent != "" {
+		args = append(args, "--logging-parent", params.LoggingParent)
 	}
 	var deps android.Paths
-	targetSdkVersion := targetSdkVersionForManifestFixer(ctx, sdkContext)
+	var argsMapper = make(map[string]string)
 
-	if UseApiFingerprint(ctx) && ctx.ModuleName() != "framework-res" {
-		targetSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", ApiFingerprintPath(ctx).String())
-		deps = append(deps, ApiFingerprintPath(ctx))
+	if params.SdkContext != nil {
+		targetSdkVersion := targetSdkVersionForManifestFixer(params.Ctx, params.SdkContext)
+		args = append(args, "--targetSdkVersion ", targetSdkVersion)
+
+		if UseApiFingerprint(params.Ctx) && params.Ctx.ModuleName() != "framework-res" {
+			targetSdkVersion = params.Ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", ApiFingerprintPath(params.Ctx).String())
+			deps = append(deps, ApiFingerprintPath(params.Ctx))
+		}
+
+		minSdkVersion, err := params.SdkContext.MinSdkVersion(params.Ctx).EffectiveVersionString(params.Ctx)
+		if err != nil {
+			params.Ctx.ModuleErrorf("invalid minSdkVersion: %s", err)
+		}
+
+		if UseApiFingerprint(params.Ctx) && params.Ctx.ModuleName() != "framework-res" {
+			minSdkVersion = params.Ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", ApiFingerprintPath(params.Ctx).String())
+			deps = append(deps, ApiFingerprintPath(params.Ctx))
+		}
+
+		if err != nil {
+			params.Ctx.ModuleErrorf("invalid minSdkVersion: %s", err)
+		}
+		args = append(args, "--minSdkVersion ", minSdkVersion)
+		args = append(args, "--raise-min-sdk-version")
 	}
 
-	minSdkVersion, err := sdkContext.MinSdkVersion(ctx).EffectiveVersionString(ctx)
-	if err != nil {
-		ctx.ModuleErrorf("invalid minSdkVersion: %s", err)
-	}
-	if UseApiFingerprint(ctx) && ctx.ModuleName() != "framework-res" {
-		minSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", ApiFingerprintPath(ctx).String())
-		deps = append(deps, ApiFingerprintPath(ctx))
-	}
+	fixedManifest := android.PathForModuleOut(params.Ctx, "manifest_fixer", "AndroidManifest.xml")
+	argsMapper["args"] = strings.Join(args, " ")
 
-	fixedManifest := android.PathForModuleOut(ctx, "manifest_fixer", "AndroidManifest.xml")
-	if err != nil {
-		ctx.ModuleErrorf("invalid minSdkVersion: %s", err)
-	}
-	ctx.Build(pctx, android.BuildParams{
+	params.Ctx.Build(pctx, android.BuildParams{
 		Rule:        manifestFixerRule,
 		Description: "fix manifest",
-		Input:       manifest,
+		Input:       params.Manifest,
 		Implicits:   deps,
 		Output:      fixedManifest,
-		Args: map[string]string{
-			"minSdkVersion":    minSdkVersion,
-			"targetSdkVersion": targetSdkVersion,
-			"args":             strings.Join(args, " "),
-		},
+		Args:        argsMapper,
 	})
 
 	return fixedManifest.WithoutRel()
diff --git a/java/app.go b/java/app.go
index 7ae73f7..9f2f99a 100755
--- a/java/app.go
+++ b/java/app.go
@@ -1432,17 +1432,15 @@
 }
 
 type bazelAndroidAppAttributes struct {
-	Srcs           bazel.LabelListAttribute
+	*javaLibraryAttributes
 	Manifest       bazel.Label
 	Custom_package *string
 	Resource_files bazel.LabelListAttribute
-	Deps           bazel.LabelListAttribute
 }
 
 // ConvertWithBp2build is used to convert android_app to Bazel.
 func (a *AndroidApp) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
-	//TODO(b/209577426): Support multiple arch variants
-	srcs := bazel.MakeLabelListAttribute(android.BazelLabelForModuleSrcExcludes(ctx, a.properties.Srcs, a.properties.Exclude_srcs))
+	libAttrs := a.convertLibraryAttrsBp2Build(ctx)
 
 	manifest := proptools.StringDefault(a.aaptProperties.Manifest, "AndroidManifest.xml")
 
@@ -1454,15 +1452,12 @@
 		resourceFiles.Includes = append(resourceFiles.Includes, files...)
 	}
 
-	deps := bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, a.properties.Static_libs))
-
 	attrs := &bazelAndroidAppAttributes{
-		Srcs:     srcs,
-		Manifest: android.BazelLabelForModuleSrcSingle(ctx, manifest),
+		libAttrs,
+		android.BazelLabelForModuleSrcSingle(ctx, manifest),
 		// TODO(b/209576404): handle package name override by product variable PRODUCT_MANIFEST_PACKAGE_NAME_OVERRIDES
-		Custom_package: a.overridableAppProperties.Package_name,
-		Resource_files: bazel.MakeLabelListAttribute(resourceFiles),
-		Deps:           deps,
+		a.overridableAppProperties.Package_name,
+		bazel.MakeLabelListAttribute(resourceFiles),
 	}
 	props := bazel.BazelTargetModuleProperties{Rule_class: "android_binary",
 		Bzl_load_location: "@rules_android//rules:rules.bzl"}
diff --git a/java/app_test.go b/java/app_test.go
index 2322ef4..16bbec1 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -2512,7 +2512,7 @@
 		`--uses-library qux ` +
 		`--uses-library quuz ` +
 		`--uses-library runtime-library`
-	android.AssertStringEquals(t, "manifest_fixer args", expectManifestFixerArgs, actualManifestFixerArgs)
+	android.AssertStringDoesContain(t, "manifest_fixer args", actualManifestFixerArgs, expectManifestFixerArgs)
 
 	// Test that all libraries are verified (library order matters).
 	verifyCmd := app.Rule("verify_uses_libraries").RuleParams.Command
@@ -3055,7 +3055,7 @@
 		result := fixture.RunTestWithBp(t, bp)
 		foo := result.ModuleForTests("foo", "android_common")
 
-		manifestFixerArgs := foo.Output("manifest_fixer/AndroidManifest.xml").Args
-		android.AssertStringEquals(t, testCase.name, testCase.targetSdkVersionExpected, manifestFixerArgs["targetSdkVersion"])
+		manifestFixerArgs := foo.Output("manifest_fixer/AndroidManifest.xml").Args["args"]
+		android.AssertStringDoesContain(t, testCase.name, manifestFixerArgs, "--targetSdkVersion  "+testCase.targetSdkVersionExpected)
 	}
 }
diff --git a/java/base.go b/java/base.go
index a3eb8de..42d7733 100644
--- a/java/base.go
+++ b/java/base.go
@@ -1969,7 +1969,7 @@
 
 func (j *Module) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
 	switch ctx.ModuleType() {
-	case "java_library", "java_library_host":
+	case "java_library", "java_library_host", "java_library_static":
 		if lib, ok := ctx.Module().(*Library); ok {
 			javaLibraryBp2Build(ctx, lib)
 		}
@@ -1978,5 +1978,4 @@
 			javaBinaryHostBp2Build(ctx, binary)
 		}
 	}
-
 }
diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go
index 4794180..a36bd6a 100644
--- a/java/bootclasspath_fragment.go
+++ b/java/bootclasspath_fragment.go
@@ -1073,7 +1073,7 @@
 // At the moment this is basically just a bootclasspath_fragment module that can be used as a
 // prebuilt. Eventually as more functionality is migrated into the bootclasspath_fragment module
 // type from the various singletons then this will diverge.
-type prebuiltBootclasspathFragmentModule struct {
+type PrebuiltBootclasspathFragmentModule struct {
 	BootclasspathFragmentModule
 	prebuilt android.Prebuilt
 
@@ -1081,16 +1081,16 @@
 	prebuiltProperties prebuiltBootclasspathFragmentProperties
 }
 
-func (module *prebuiltBootclasspathFragmentModule) Prebuilt() *android.Prebuilt {
+func (module *PrebuiltBootclasspathFragmentModule) Prebuilt() *android.Prebuilt {
 	return &module.prebuilt
 }
 
-func (module *prebuiltBootclasspathFragmentModule) Name() string {
+func (module *PrebuiltBootclasspathFragmentModule) Name() string {
 	return module.prebuilt.Name(module.ModuleBase.Name())
 }
 
 // produceHiddenAPIOutput returns a path to the prebuilt all-flags.csv or nil if none is specified.
-func (module *prebuiltBootclasspathFragmentModule) produceHiddenAPIOutput(ctx android.ModuleContext, contents []android.Module, input HiddenAPIFlagInput) *HiddenAPIOutput {
+func (module *PrebuiltBootclasspathFragmentModule) produceHiddenAPIOutput(ctx android.ModuleContext, contents []android.Module, input HiddenAPIFlagInput) *HiddenAPIOutput {
 	pathForOptionalSrc := func(src *string, defaultPath android.Path) android.Path {
 		if src == nil {
 			return defaultPath
@@ -1131,7 +1131,7 @@
 }
 
 // produceBootImageFiles extracts the boot image files from the APEX if available.
-func (module *prebuiltBootclasspathFragmentModule) produceBootImageFiles(ctx android.ModuleContext, imageConfig *bootImageConfig) bootImageFilesByArch {
+func (module *PrebuiltBootclasspathFragmentModule) produceBootImageFiles(ctx android.ModuleContext, imageConfig *bootImageConfig) bootImageFilesByArch {
 	if !shouldCopyBootFilesToPredefinedLocations(ctx, imageConfig) {
 		return nil
 	}
@@ -1141,37 +1141,53 @@
 		return nil // An error has been reported by FindDeapexerProviderForModule.
 	}
 
-	files := bootImageFilesByArch{}
-	for _, variant := range imageConfig.apexVariants() {
-		arch := variant.target.Arch.ArchType
-		for _, toPath := range variant.imagesDeps {
-			apexRelativePath := apexRootRelativePathToBootImageFile(arch, toPath.Base())
-			// Get the path to the file that the deapexer extracted from the prebuilt apex file.
-			fromPath := di.PrebuiltExportPath(apexRelativePath)
-
-			// Return the toPath as the calling code expects the paths in the returned map to be the
-			// paths predefined in the bootImageConfig.
-			files[arch] = append(files[arch], toPath)
-
-			// Copy the file to the predefined location.
-			ctx.Build(pctx, android.BuildParams{
-				Rule:   android.Cp,
-				Input:  fromPath,
-				Output: toPath,
-			})
-		}
+	profile := (android.WritablePath)(nil)
+	if imageConfig.profileInstallPathInApex != "" {
+		profile = di.PrebuiltExportPath(imageConfig.profileInstallPathInApex)
 	}
 
-	// Build the boot image files for the host variants. These are built from the dex files provided
-	// by the contents of this module as prebuilt versions of the host boot image files are not
-	// available, i.e. there is no host specific prebuilt apex containing them. This has to be built
-	// without a profile as the prebuilt modules do not provide a profile.
-	buildBootImageVariantsForBuildOs(ctx, imageConfig, nil)
+	// Build the boot image files for the host variants. These are always built from the dex files
+	// provided by the contents of this module as prebuilt versions of the host boot image files are
+	// not available, i.e. there is no host specific prebuilt apex containing them. This has to be
+	// built without a profile as the prebuilt modules do not provide a profile.
+	buildBootImageVariantsForBuildOs(ctx, imageConfig, profile)
 
-	return files
+	if imageConfig.shouldInstallInApex() {
+		// If the boot image files for the android variants are in the prebuilt apex, we must use those
+		// rather than building new ones because those boot image files are going to be used on device.
+		files := bootImageFilesByArch{}
+		for _, variant := range imageConfig.apexVariants() {
+			arch := variant.target.Arch.ArchType
+			for _, toPath := range variant.imagesDeps {
+				apexRelativePath := apexRootRelativePathToBootImageFile(arch, toPath.Base())
+				// Get the path to the file that the deapexer extracted from the prebuilt apex file.
+				fromPath := di.PrebuiltExportPath(apexRelativePath)
+
+				// Return the toPath as the calling code expects the paths in the returned map to be the
+				// paths predefined in the bootImageConfig.
+				files[arch] = append(files[arch], toPath)
+
+				// Copy the file to the predefined location.
+				ctx.Build(pctx, android.BuildParams{
+					Rule:   android.Cp,
+					Input:  fromPath,
+					Output: toPath,
+				})
+			}
+		}
+		return files
+	} else {
+		if profile == nil {
+			ctx.ModuleErrorf("Unable to produce boot image files: neither boot image files nor profiles exists in the prebuilt apex")
+			return nil
+		}
+		// Build boot image files for the android variants from the dex files provided by the contents
+		// of this module.
+		return buildBootImageVariantsForAndroidOs(ctx, imageConfig, profile)
+	}
 }
 
-var _ commonBootclasspathFragment = (*prebuiltBootclasspathFragmentModule)(nil)
+var _ commonBootclasspathFragment = (*PrebuiltBootclasspathFragmentModule)(nil)
 
 // createBootImageTag creates the tag to uniquely identify the boot image file among all of the
 // files that a module requires from the prebuilt .apex file.
@@ -1185,16 +1201,22 @@
 //
 // If there is no image config associated with this fragment then it returns nil. Otherwise, it
 // returns the files that are listed in the image config.
-func (module *prebuiltBootclasspathFragmentModule) RequiredFilesFromPrebuiltApex(ctx android.BaseModuleContext) []string {
+func (module *PrebuiltBootclasspathFragmentModule) RequiredFilesFromPrebuiltApex(ctx android.BaseModuleContext) []string {
 	imageConfig := module.getImageConfig(ctx)
 	if imageConfig != nil {
-		// Add the boot image files, e.g. .art, .oat and .vdex files.
 		files := []string{}
-		for _, variant := range imageConfig.apexVariants() {
-			arch := variant.target.Arch.ArchType
-			for _, path := range variant.imagesDeps.Paths() {
-				base := path.Base()
-				files = append(files, apexRootRelativePathToBootImageFile(arch, base))
+		if imageConfig.profileInstallPathInApex != "" {
+			// Add the boot image profile.
+			files = append(files, imageConfig.profileInstallPathInApex)
+		}
+		if imageConfig.shouldInstallInApex() {
+			// Add the boot image files, e.g. .art, .oat and .vdex files.
+			for _, variant := range imageConfig.apexVariants() {
+				arch := variant.target.Arch.ArchType
+				for _, path := range variant.imagesDeps.Paths() {
+					base := path.Base()
+					files = append(files, apexRootRelativePathToBootImageFile(arch, base))
+				}
 			}
 		}
 		return files
@@ -1206,10 +1228,10 @@
 	return filepath.Join("javalib", arch.String(), base)
 }
 
-var _ android.RequiredFilesFromPrebuiltApex = (*prebuiltBootclasspathFragmentModule)(nil)
+var _ android.RequiredFilesFromPrebuiltApex = (*PrebuiltBootclasspathFragmentModule)(nil)
 
 func prebuiltBootclasspathFragmentFactory() android.Module {
-	m := &prebuiltBootclasspathFragmentModule{}
+	m := &PrebuiltBootclasspathFragmentModule{}
 	m.AddProperties(&m.properties, &m.prebuiltProperties)
 	// This doesn't actually have any prebuilt files of its own so pass a placeholder for the srcs
 	// array.
diff --git a/java/droidstubs.go b/java/droidstubs.go
index 7ad316f..f9dcfd6 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -334,7 +334,11 @@
 		// TODO(tnorbye): find owners to fix these warnings when annotation was enabled.
 		cmd.FlagWithArg("--hide ", "HiddenTypedefConstant").
 			FlagWithArg("--hide ", "SuperfluousPrefix").
-			FlagWithArg("--hide ", "AnnotationExtraction")
+			FlagWithArg("--hide ", "AnnotationExtraction").
+			// (b/217545629)
+			FlagWithArg("--hide ", "ChangedThrows").
+			// (b/217552813)
+			FlagWithArg("--hide ", "ChangedAbstract")
 	}
 }
 
diff --git a/java/java.go b/java/java.go
index fef9912..e55f045 100644
--- a/java/java.go
+++ b/java/java.go
@@ -2010,7 +2010,8 @@
 	Javacopts bazel.StringListAttribute
 }
 
-func javaLibraryBp2Build(ctx android.TopDownMutatorContext, m *Library) {
+func (m *Library) convertLibraryAttrsBp2Build(ctx android.TopDownMutatorContext) *javaLibraryAttributes {
+	//TODO(b/209577426): Support multiple arch variants
 	srcs := bazel.MakeLabelListAttribute(android.BazelLabelForModuleSrcExcludes(ctx, m.properties.Srcs, m.properties.Exclude_srcs))
 	attrs := &javaLibraryAttributes{
 		Srcs: srcs,
@@ -2020,9 +2021,21 @@
 		attrs.Javacopts = bazel.MakeStringListAttribute(m.properties.Javacflags)
 	}
 
+	var deps bazel.LabelList
 	if m.properties.Libs != nil {
-		attrs.Deps = bazel.MakeLabelListAttribute(android.BazelLabelForModuleDeps(ctx, m.properties.Libs))
+		deps.Append(android.BazelLabelForModuleDeps(ctx, m.properties.Libs))
 	}
+	if m.properties.Static_libs != nil {
+		//TODO(b/217236083) handle static libs similarly to Soong
+		deps.Append(android.BazelLabelForModuleDeps(ctx, m.properties.Static_libs))
+	}
+	attrs.Deps = bazel.MakeLabelListAttribute(deps)
+
+	return attrs
+}
+
+func javaLibraryBp2Build(ctx android.TopDownMutatorContext, m *Library) {
+	attrs := m.convertLibraryAttrsBp2Build(ctx)
 
 	props := bazel.BazelTargetModuleProperties{
 		Rule_class:        "java_library",
diff --git a/java/plugin.go b/java/plugin.go
index 297ac2c..4b174b9 100644
--- a/java/plugin.go
+++ b/java/plugin.go
@@ -14,7 +14,10 @@
 
 package java
 
-import "android/soong/android"
+import (
+	"android/soong/android"
+	"android/soong/bazel"
+)
 
 func init() {
 	registerJavaPluginBuildComponents(android.InitRegistrationContext)
@@ -24,7 +27,6 @@
 	ctx.RegisterModuleType("java_plugin", PluginFactory)
 }
 
-// A java_plugin module describes a host java library that will be used by javac as an annotation processor.
 func PluginFactory() android.Module {
 	module := &Plugin{}
 
@@ -32,9 +34,13 @@
 	module.AddProperties(&module.pluginProperties)
 
 	InitJavaModule(module, android.HostSupported)
+
+	android.InitBazelModule(module)
+
 	return module
 }
 
+// Plugin describes a java_plugin module, a host java library that will be used by javac as an annotation processor.
 type Plugin struct {
 	Library
 
@@ -50,3 +56,29 @@
 	// parallelism and cause more recompilation for modules that depend on modules that use this plugin.
 	Generates_api *bool
 }
+
+type pluginAttributes struct {
+	*javaLibraryAttributes
+	Processor_class        *string
+	Target_compatible_with bazel.LabelListAttribute
+}
+
+// ConvertWithBp2build is used to convert android_app to Bazel.
+func (p *Plugin) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	libAttrs := p.convertLibraryAttrsBp2Build(ctx)
+	attrs := &pluginAttributes{
+		libAttrs,
+		nil,
+		bazel.LabelListAttribute{},
+	}
+
+	if p.pluginProperties.Processor_class != nil {
+		attrs.Processor_class = p.pluginProperties.Processor_class
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class: "java_plugin",
+	}
+
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: p.Name()}, attrs)
+}
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 57ab268..6a2a7a8 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -2755,7 +2755,7 @@
 	android.SdkMemberPropertiesBase
 
 	// Scope to per scope properties.
-	Scopes map[*apiScope]scopeProperties
+	Scopes map[*apiScope]*scopeProperties
 
 	// The Java stubs source files.
 	Stub_srcs []string
@@ -2808,14 +2808,14 @@
 	StubsSrcJar    android.Path
 	CurrentApiFile android.Path
 	RemovedApiFile android.Path
-	AnnotationsZip android.Path
+	AnnotationsZip android.Path `supported_build_releases:"T+"`
 	SdkVersion     string
 }
 
 func (s *sdkLibrarySdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) {
 	sdk := variant.(*SdkLibrary)
 
-	s.Scopes = make(map[*apiScope]scopeProperties)
+	s.Scopes = make(map[*apiScope]*scopeProperties)
 	for _, apiScope := range allApiScopes {
 		paths := sdk.findScopePaths(apiScope)
 		if paths == nil {
@@ -2838,7 +2838,7 @@
 			if paths.annotationsZip.Valid() {
 				properties.AnnotationsZip = paths.annotationsZip.Path()
 			}
-			s.Scopes[apiScope] = properties
+			s.Scopes[apiScope] = &properties
 		}
 	}
 
diff --git a/mk2rbc/expr.go b/mk2rbc/expr.go
index 3f355ac..07f7ca1 100644
--- a/mk2rbc/expr.go
+++ b/mk2rbc/expr.go
@@ -378,32 +378,6 @@
 	}
 }
 
-// variableDefinedExpr corresponds to Make's ifdef VAR
-type variableDefinedExpr struct {
-	v variable
-}
-
-func (v *variableDefinedExpr) emit(gctx *generationContext) {
-	if v.v != nil {
-		v.v.emitDefined(gctx)
-		return
-	}
-	gctx.writef("%s(%q)", cfnWarning, "TODO(VAR)")
-}
-
-func (_ *variableDefinedExpr) typ() starlarkType {
-	return starlarkTypeBool
-}
-
-func (v *variableDefinedExpr) emitListVarCopy(gctx *generationContext) {
-	v.emit(gctx)
-}
-
-func (v *variableDefinedExpr) transform(transformer func(expr starlarkExpr) starlarkExpr) starlarkExpr {
-	// TODO: VariableDefinedExpr isn't really an expression?
-	return v
-}
-
 type listExpr struct {
 	items []starlarkExpr
 }
diff --git a/mk2rbc/mk2rbc.go b/mk2rbc/mk2rbc.go
index e317cad..03cf21e 100644
--- a/mk2rbc/mk2rbc.go
+++ b/mk2rbc/mk2rbc.go
@@ -370,10 +370,6 @@
 	}
 }
 
-type nodeReceiver interface {
-	newNode(node starlarkNode)
-}
-
 // Information about the generated Starlark script.
 type StarlarkScript struct {
 	mkFile         string
@@ -389,10 +385,6 @@
 	nodeLocator    func(pos mkparser.Pos) int
 }
 
-func (ss *StarlarkScript) newNode(node starlarkNode) {
-	ss.nodes = append(ss.nodes, node)
-}
-
 // varAssignmentScope points to the last assignment for each variable
 // in the current block. It is used during the parsing to chain
 // the assignments to a variable together.
@@ -415,8 +407,6 @@
 	tracedVariables  map[string]bool // variables to be traced in the generated script
 	variables        map[string]variable
 	varAssignments   *varAssignmentScope
-	receiver         nodeReceiver // receptacle for the generated starlarkNode's
-	receiverStack    []nodeReceiver
 	outputDir        string
 	dependentModules map[string]*moduleInfo
 	soongNamespaces  map[string]map[string]bool
@@ -503,20 +493,6 @@
 	ctx.varAssignments = ctx.varAssignments.outer
 }
 
-func (ctx *parseContext) pushReceiver(rcv nodeReceiver) {
-	ctx.receiverStack = append(ctx.receiverStack, ctx.receiver)
-	ctx.receiver = rcv
-}
-
-func (ctx *parseContext) popReceiver() {
-	last := len(ctx.receiverStack) - 1
-	if last < 0 {
-		panic(fmt.Errorf("popReceiver: receiver stack empty"))
-	}
-	ctx.receiver = ctx.receiverStack[last]
-	ctx.receiverStack = ctx.receiverStack[0:last]
-}
-
 func (ctx *parseContext) hasNodes() bool {
 	return ctx.currentNodeIndex < len(ctx.nodes)
 }
@@ -537,11 +513,10 @@
 	ctx.currentNodeIndex--
 }
 
-func (ctx *parseContext) handleAssignment(a *mkparser.Assignment) {
+func (ctx *parseContext) handleAssignment(a *mkparser.Assignment) []starlarkNode {
 	// Handle only simple variables
 	if !a.Name.Const() {
-		ctx.errorf(a, "Only simple variables are handled")
-		return
+		return []starlarkNode{ctx.newBadNode(a, "Only simple variables are handled")}
 	}
 	name := a.Name.Strings[0]
 	// The `override` directive
@@ -549,18 +524,16 @@
 	// is parsed as an assignment to a variable named `override FOO`.
 	// There are very few places where `override` is used, just flag it.
 	if strings.HasPrefix(name, "override ") {
-		ctx.errorf(a, "cannot handle override directive")
+		return []starlarkNode{ctx.newBadNode(a, "cannot handle override directive")}
 	}
 
 	// Soong configuration
 	if strings.HasPrefix(name, soongNsPrefix) {
-		ctx.handleSoongNsAssignment(strings.TrimPrefix(name, soongNsPrefix), a)
-		return
+		return ctx.handleSoongNsAssignment(strings.TrimPrefix(name, soongNsPrefix), a)
 	}
 	lhs := ctx.addVariable(name)
 	if lhs == nil {
-		ctx.errorf(a, "unknown variable %s", name)
-		return
+		return []starlarkNode{ctx.newBadNode(a, "unknown variable %s", name)}
 	}
 	_, isTraced := ctx.tracedVariables[name]
 	asgn := &assignmentNode{lhs: lhs, mkValue: a.Value, isTraced: isTraced, location: ctx.errorLocation(a)}
@@ -568,8 +541,7 @@
 		// Try to divine variable type from the RHS
 		asgn.value = ctx.parseMakeString(a, a.Value)
 		if xBad, ok := asgn.value.(*badExpr); ok {
-			ctx.wrapBadExpr(xBad)
-			return
+			return []starlarkNode{&exprNode{xBad}}
 		}
 		inferred_type := asgn.value.typ()
 		if inferred_type != starlarkTypeUnknown {
@@ -577,9 +549,9 @@
 		}
 	}
 	if lhs.valueType() == starlarkTypeList {
-		xConcat := ctx.buildConcatExpr(a)
-		if xConcat == nil {
-			return
+		xConcat, xBad := ctx.buildConcatExpr(a)
+		if xBad != nil {
+			return []starlarkNode{&exprNode{expr: xBad}}
 		}
 		switch len(xConcat.items) {
 		case 0:
@@ -592,8 +564,7 @@
 	} else {
 		asgn.value = ctx.parseMakeString(a, a.Value)
 		if xBad, ok := asgn.value.(*badExpr); ok {
-			ctx.wrapBadExpr(xBad)
-			return
+			return []starlarkNode{&exprNode{expr: xBad}}
 		}
 	}
 
@@ -614,14 +585,13 @@
 		panic(fmt.Errorf("unexpected assignment type %s", a.Type))
 	}
 
-	ctx.receiver.newNode(asgn)
+	return []starlarkNode{asgn}
 }
 
-func (ctx *parseContext) handleSoongNsAssignment(name string, asgn *mkparser.Assignment) {
+func (ctx *parseContext) handleSoongNsAssignment(name string, asgn *mkparser.Assignment) []starlarkNode {
 	val := ctx.parseMakeString(asgn, asgn.Value)
 	if xBad, ok := val.(*badExpr); ok {
-		ctx.wrapBadExpr(xBad)
-		return
+		return []starlarkNode{&exprNode{expr: xBad}}
 	}
 
 	// Unfortunately, Soong namespaces can be set up by directly setting corresponding Make
@@ -634,17 +604,18 @@
 		//      $(call add_soong_config_namespace,foo)
 		s, ok := maybeString(val)
 		if !ok {
-			ctx.errorf(asgn, "cannot handle variables in SOONG_CONFIG_NAMESPACES assignment, please use add_soong_config_namespace instead")
-			return
+			return []starlarkNode{ctx.newBadNode(asgn, "cannot handle variables in SOONG_CONFIG_NAMESPACES assignment, please use add_soong_config_namespace instead")}
 		}
+		result := make([]starlarkNode, 0)
 		for _, ns := range strings.Fields(s) {
 			ctx.addSoongNamespace(ns)
-			ctx.receiver.newNode(&exprNode{&callExpr{
+			result = append(result, &exprNode{&callExpr{
 				name:       baseName + ".soong_config_namespace",
 				args:       []starlarkExpr{&globalsExpr{}, &stringLiteralExpr{ns}},
 				returnType: starlarkTypeVoid,
 			}})
 		}
+		return result
 	} else {
 		// Upon seeing
 		//      SOONG_CONFIG_x_y = v
@@ -664,45 +635,41 @@
 				continue
 			}
 			if namespaceName != "" {
-				ctx.errorf(asgn, "ambiguous soong namespace (may be either `%s` or  `%s`)", namespaceName, name[0:pos])
-				return
+				return []starlarkNode{ctx.newBadNode(asgn, "ambiguous soong namespace (may be either `%s` or  `%s`)", namespaceName, name[0:pos])}
 			}
 			namespaceName = name[0:pos]
 			varName = name[pos+1:]
 		}
 		if namespaceName == "" {
-			ctx.errorf(asgn, "cannot figure out Soong namespace, please use add_soong_config_var_value macro instead")
-			return
+			return []starlarkNode{ctx.newBadNode(asgn, "cannot figure out Soong namespace, please use add_soong_config_var_value macro instead")}
 		}
 		if varName == "" {
 			// Remember variables in this namespace
 			s, ok := maybeString(val)
 			if !ok {
-				ctx.errorf(asgn, "cannot handle variables in SOONG_CONFIG_ assignment, please use add_soong_config_var_value instead")
-				return
+				return []starlarkNode{ctx.newBadNode(asgn, "cannot handle variables in SOONG_CONFIG_ assignment, please use add_soong_config_var_value instead")}
 			}
 			ctx.updateSoongNamespace(asgn.Type != "+=", namespaceName, strings.Fields(s))
-			return
+			return []starlarkNode{}
 		}
 
 		// Finally, handle assignment to a namespace variable
 		if !ctx.hasNamespaceVar(namespaceName, varName) {
-			ctx.errorf(asgn, "no %s variable in %s namespace, please use add_soong_config_var_value instead", varName, namespaceName)
-			return
+			return []starlarkNode{ctx.newBadNode(asgn, "no %s variable in %s namespace, please use add_soong_config_var_value instead", varName, namespaceName)}
 		}
 		fname := baseName + "." + soongConfigAssign
 		if asgn.Type == "+=" {
 			fname = baseName + "." + soongConfigAppend
 		}
-		ctx.receiver.newNode(&exprNode{&callExpr{
+		return []starlarkNode{&exprNode{&callExpr{
 			name:       fname,
 			args:       []starlarkExpr{&globalsExpr{}, &stringLiteralExpr{namespaceName}, &stringLiteralExpr{varName}, val},
 			returnType: starlarkTypeVoid,
-		}})
+		}}}
 	}
 }
 
-func (ctx *parseContext) buildConcatExpr(a *mkparser.Assignment) *concatExpr {
+func (ctx *parseContext) buildConcatExpr(a *mkparser.Assignment) (*concatExpr, *badExpr) {
 	xConcat := &concatExpr{}
 	var xItemList *listExpr
 	addToItemList := func(x ...starlarkExpr) {
@@ -724,8 +691,7 @@
 		// expressions return individual elements.
 		switch x := ctx.parseMakeString(a, item).(type) {
 		case *badExpr:
-			ctx.wrapBadExpr(x)
-			return nil
+			return nil, x
 		case *stringLiteralExpr:
 			addToItemList(maybeConvertToStringList(x).(*listExpr).items...)
 		default:
@@ -749,7 +715,7 @@
 	if xItemList != nil {
 		xConcat.items = append(xConcat.items, xItemList)
 	}
-	return xConcat
+	return xConcat, nil
 }
 
 func (ctx *parseContext) newDependentModule(path string, optional bool) *moduleInfo {
@@ -779,7 +745,7 @@
 }
 
 func (ctx *parseContext) handleSubConfig(
-	v mkparser.Node, pathExpr starlarkExpr, loadAlways bool, processModule func(inheritedModule)) {
+	v mkparser.Node, pathExpr starlarkExpr, loadAlways bool, processModule func(inheritedModule) starlarkNode) []starlarkNode {
 
 	// In a simple case, the name of a module to inherit/include is known statically.
 	if path, ok := maybeString(pathExpr); ok {
@@ -788,18 +754,19 @@
 		moduleShouldExist := loadAlways && ctx.ifNestLevel == 0
 		if strings.Contains(path, "*") {
 			if paths, err := fs.Glob(ctx.script.sourceFS, path); err == nil {
+				result := make([]starlarkNode, 0)
 				for _, p := range paths {
 					mi := ctx.newDependentModule(p, !moduleShouldExist)
-					processModule(inheritedStaticModule{mi, loadAlways})
+					result = append(result, processModule(inheritedStaticModule{mi, loadAlways}))
 				}
+				return result
 			} else {
-				ctx.errorf(v, "cannot glob wildcard argument")
+				return []starlarkNode{ctx.newBadNode(v, "cannot glob wildcard argument")}
 			}
 		} else {
 			mi := ctx.newDependentModule(path, !moduleShouldExist)
-			processModule(inheritedStaticModule{mi, loadAlways})
+			return []starlarkNode{processModule(inheritedStaticModule{mi, loadAlways})}
 		}
-		return
 	}
 
 	// If module path references variables (e.g., $(v1)/foo/$(v2)/device-config.mk), find all the paths in the
@@ -819,8 +786,7 @@
 	var matchingPaths []string
 	varPath, ok := pathExpr.(*interpolateExpr)
 	if !ok {
-		ctx.errorf(v, "inherit-product/include argument is too complex")
-		return
+		return []starlarkNode{ctx.newBadNode(v, "inherit-product/include argument is too complex")}
 	}
 
 	pathPattern := []string{varPath.chunks[0]}
@@ -829,11 +795,7 @@
 			pathPattern = append(pathPattern, chunk)
 		}
 	}
-	if pathPattern[0] == "" {
-		if len(ctx.includeTops) == 0 {
-			ctx.errorf(v, "inherit-product/include statements must not be prefixed with a variable, or must include a #RBC# include_top comment beforehand giving a root directory to search.")
-			return
-		}
+	if pathPattern[0] == "" && len(ctx.includeTops) > 0 {
 		// If pattern starts from the top. restrict it to the directories where
 		// we know inherit-product uses dynamically calculated path.
 		for _, p := range ctx.includeTops {
@@ -846,17 +808,22 @@
 	// Safeguard against $(call inherit-product,$(PRODUCT_PATH))
 	const maxMatchingFiles = 150
 	if len(matchingPaths) > maxMatchingFiles {
-		ctx.errorf(v, "there are >%d files matching the pattern, please rewrite it", maxMatchingFiles)
-		return
+		return []starlarkNode{ctx.newBadNode(v, "there are >%d files matching the pattern, please rewrite it", maxMatchingFiles)}
 	}
-	res := inheritedDynamicModule{*varPath, []*moduleInfo{}, loadAlways}
-	for _, p := range matchingPaths {
-		// A product configuration files discovered dynamically may attempt to inherit
-		// from another one which does not exist in this source tree. Prevent load errors
-		// by always loading the dynamic files as optional.
-		res.candidateModules = append(res.candidateModules, ctx.newDependentModule(p, true))
+	if len(matchingPaths) == 1 {
+		res := inheritedStaticModule{ctx.newDependentModule(matchingPaths[0], loadAlways && ctx.ifNestLevel == 0), loadAlways}
+		return []starlarkNode{processModule(res)}
+	} else {
+		needsWarning := pathPattern[0] == "" && len(ctx.includeTops) == 0
+		res := inheritedDynamicModule{*varPath, []*moduleInfo{}, loadAlways, ctx.errorLocation(v), needsWarning}
+		for _, p := range matchingPaths {
+			// A product configuration files discovered dynamically may attempt to inherit
+			// from another one which does not exist in this source tree. Prevent load errors
+			// by always loading the dynamic files as optional.
+			res.candidateModules = append(res.candidateModules, ctx.newDependentModule(p, true))
+		}
+		return []starlarkNode{processModule(res)}
 	}
-	processModule(res)
 }
 
 func (ctx *parseContext) findMatchingPaths(pattern []string) []string {
@@ -883,25 +850,25 @@
 	return res
 }
 
-func (ctx *parseContext) handleInheritModule(v mkparser.Node, args *mkparser.MakeString, loadAlways bool) {
+func (ctx *parseContext) handleInheritModule(v mkparser.Node, args *mkparser.MakeString, loadAlways bool) []starlarkNode {
 	args.TrimLeftSpaces()
 	args.TrimRightSpaces()
 	pathExpr := ctx.parseMakeString(v, args)
 	if _, ok := pathExpr.(*badExpr); ok {
-		ctx.errorf(v, "Unable to parse argument to inherit")
+		return []starlarkNode{ctx.newBadNode(v, "Unable to parse argument to inherit")}
 	}
-	ctx.handleSubConfig(v, pathExpr, loadAlways, func(im inheritedModule) {
-		ctx.receiver.newNode(&inheritNode{im, loadAlways})
+	return ctx.handleSubConfig(v, pathExpr, loadAlways, func(im inheritedModule) starlarkNode {
+		return &inheritNode{im, loadAlways}
 	})
 }
 
-func (ctx *parseContext) handleInclude(v mkparser.Node, pathExpr starlarkExpr, loadAlways bool) {
-	ctx.handleSubConfig(v, pathExpr, loadAlways, func(im inheritedModule) {
-		ctx.receiver.newNode(&includeNode{im, loadAlways})
+func (ctx *parseContext) handleInclude(v mkparser.Node, pathExpr starlarkExpr, loadAlways bool) []starlarkNode {
+	return ctx.handleSubConfig(v, pathExpr, loadAlways, func(im inheritedModule) starlarkNode {
+		return &includeNode{im, loadAlways}
 	})
 }
 
-func (ctx *parseContext) handleVariable(v *mkparser.Variable) {
+func (ctx *parseContext) handleVariable(v *mkparser.Variable) []starlarkNode {
 	// Handle:
 	//   $(call inherit-product,...)
 	//   $(call inherit-product-if-exists,...)
@@ -916,67 +883,57 @@
 	if strings.HasPrefix(v.Name.Dump(), "call inherit-product,") {
 		args := v.Name.Clone()
 		args.ReplaceLiteral("call inherit-product,", "")
-		ctx.handleInheritModule(v, args, true)
-		return
+		return ctx.handleInheritModule(v, args, true)
 	}
 	if strings.HasPrefix(v.Name.Dump(), "call inherit-product-if-exists,") {
 		args := v.Name.Clone()
 		args.ReplaceLiteral("call inherit-product-if-exists,", "")
-		ctx.handleInheritModule(v, args, false)
-		return
+		return ctx.handleInheritModule(v, args, false)
 	}
-	expr := ctx.parseReference(v, v.Name)
-	switch x := expr.(type) {
-	case *callExpr:
-		ctx.receiver.newNode(&exprNode{expr})
-	case *badExpr:
-		ctx.wrapBadExpr(x)
-	default:
-		ctx.errorf(v, "cannot handle %s", v.Dump())
-	}
+	return []starlarkNode{&exprNode{expr: ctx.parseReference(v, v.Name)}}
 }
 
-func (ctx *parseContext) handleDefine(directive *mkparser.Directive) {
+func (ctx *parseContext) maybeHandleDefine(directive *mkparser.Directive) starlarkNode {
 	macro_name := strings.Fields(directive.Args.Strings[0])[0]
 	// Ignore the macros that we handle
 	_, ignored := ignoredDefines[macro_name]
 	_, known := knownFunctions[macro_name]
 	if !ignored && !known {
-		ctx.errorf(directive, "define is not supported: %s", macro_name)
+		return ctx.newBadNode(directive, "define is not supported: %s", macro_name)
 	}
+	return nil
 }
 
-func (ctx *parseContext) handleIfBlock(ifDirective *mkparser.Directive) {
-	ssSwitch := &switchNode{}
-	ctx.pushReceiver(ssSwitch)
-	for ctx.processBranch(ifDirective); ctx.hasNodes() && ctx.fatalError == nil; {
+func (ctx *parseContext) handleIfBlock(ifDirective *mkparser.Directive) starlarkNode {
+	ssSwitch := &switchNode{
+		ssCases: []*switchCase{ctx.processBranch(ifDirective)},
+	}
+	for ctx.hasNodes() && ctx.fatalError == nil {
 		node := ctx.getNode()
 		switch x := node.(type) {
 		case *mkparser.Directive:
 			switch x.Name {
 			case "else", "elifdef", "elifndef", "elifeq", "elifneq":
-				ctx.processBranch(x)
+				ssSwitch.ssCases = append(ssSwitch.ssCases, ctx.processBranch(x))
 			case "endif":
-				ctx.popReceiver()
-				ctx.receiver.newNode(ssSwitch)
-				return
+				return ssSwitch
 			default:
-				ctx.errorf(node, "unexpected directive %s", x.Name)
+				return ctx.newBadNode(node, "unexpected directive %s", x.Name)
 			}
 		default:
-			ctx.errorf(ifDirective, "unexpected statement")
+			return ctx.newBadNode(ifDirective, "unexpected statement")
 		}
 	}
 	if ctx.fatalError == nil {
 		ctx.fatalError = fmt.Errorf("no matching endif for %s", ifDirective.Dump())
 	}
-	ctx.popReceiver()
+	return ctx.newBadNode(ifDirective, "no matching endif for %s", ifDirective.Dump())
 }
 
 // processBranch processes a single branch (if/elseif/else) until the next directive
 // on the same level.
-func (ctx *parseContext) processBranch(check *mkparser.Directive) {
-	block := switchCase{gate: ctx.parseCondition(check)}
+func (ctx *parseContext) processBranch(check *mkparser.Directive) *switchCase {
+	block := &switchCase{gate: ctx.parseCondition(check)}
 	defer func() {
 		ctx.popVarAssignments()
 		ctx.ifNestLevel--
@@ -985,37 +942,29 @@
 	ctx.pushVarAssignments()
 	ctx.ifNestLevel++
 
-	ctx.pushReceiver(&block)
 	for ctx.hasNodes() {
 		node := ctx.getNode()
 		if d, ok := node.(*mkparser.Directive); ok {
 			switch d.Name {
 			case "else", "elifdef", "elifndef", "elifeq", "elifneq", "endif":
-				ctx.popReceiver()
-				ctx.receiver.newNode(&block)
 				ctx.backNode()
-				return
+				return block
 			}
 		}
-		ctx.handleSimpleStatement(node)
+		block.nodes = append(block.nodes, ctx.handleSimpleStatement(node)...)
 	}
 	ctx.fatalError = fmt.Errorf("no matching endif for %s", check.Dump())
-	ctx.popReceiver()
-}
-
-func (ctx *parseContext) newIfDefinedNode(check *mkparser.Directive) (starlarkExpr, bool) {
-	if !check.Args.Const() {
-		return ctx.newBadExpr(check, "ifdef variable ref too complex: %s", check.Args.Dump()), false
-	}
-	v := ctx.addVariable(check.Args.Strings[0])
-	return &variableDefinedExpr{v}, true
+	return block
 }
 
 func (ctx *parseContext) parseCondition(check *mkparser.Directive) starlarkNode {
 	switch check.Name {
 	case "ifdef", "ifndef", "elifdef", "elifndef":
-		v, ok := ctx.newIfDefinedNode(check)
-		if ok && strings.HasSuffix(check.Name, "ndef") {
+		if !check.Args.Const() {
+			return ctx.newBadNode(check, "ifdef variable ref too complex: %s", check.Args.Dump())
+		}
+		v := NewVariableRefExpr(ctx.addVariable(check.Args.Strings[0]), false)
+		if strings.HasSuffix(check.Name, "ndef") {
 			v = &notExpr{v}
 		}
 		return &ifNode{
@@ -1035,12 +984,16 @@
 }
 
 func (ctx *parseContext) newBadExpr(node mkparser.Node, text string, args ...interface{}) starlarkExpr {
-	message := fmt.Sprintf(text, args...)
 	if ctx.errorLogger != nil {
 		ctx.errorLogger.NewError(ctx.errorLocation(node), node, text, args...)
 	}
 	ctx.script.hasErrors = true
-	return &badExpr{errorLocation: ctx.errorLocation(node), message: message}
+	return &badExpr{errorLocation: ctx.errorLocation(node), message: fmt.Sprintf(text, args...)}
+}
+
+// records that the given node failed to be converted and includes an explanatory message
+func (ctx *parseContext) newBadNode(failedNode mkparser.Node, message string, args ...interface{}) starlarkNode {
+	return &exprNode{ctx.newBadExpr(failedNode, message, args...)}
 }
 
 func (ctx *parseContext) parseCompare(cond *mkparser.Directive) starlarkExpr {
@@ -1733,28 +1686,34 @@
 // Handles the statements whose treatment is the same in all contexts: comment,
 // assignment, variable (which is a macro call in reality) and all constructs that
 // do not handle in any context ('define directive and any unrecognized stuff).
-func (ctx *parseContext) handleSimpleStatement(node mkparser.Node) {
+func (ctx *parseContext) handleSimpleStatement(node mkparser.Node) []starlarkNode {
+	var result []starlarkNode
 	switch x := node.(type) {
 	case *mkparser.Comment:
-		ctx.maybeHandleAnnotation(x)
-		ctx.insertComment("#" + x.Comment)
+		if n, handled := ctx.maybeHandleAnnotation(x); handled && n != nil {
+			result = []starlarkNode{n}
+		} else if !handled {
+			result = []starlarkNode{&commentNode{strings.TrimSpace("#" + x.Comment)}}
+		}
 	case *mkparser.Assignment:
-		ctx.handleAssignment(x)
+		result = ctx.handleAssignment(x)
 	case *mkparser.Variable:
-		ctx.handleVariable(x)
+		result = ctx.handleVariable(x)
 	case *mkparser.Directive:
 		switch x.Name {
 		case "define":
-			ctx.handleDefine(x)
+			if res := ctx.maybeHandleDefine(x); res != nil {
+				result = []starlarkNode{res}
+			}
 		case "include", "-include":
-			ctx.handleInclude(node, ctx.parseMakeString(node, x.Args), x.Name[0] != '-')
+			result = ctx.handleInclude(node, ctx.parseMakeString(node, x.Args), x.Name[0] != '-')
 		case "ifeq", "ifneq", "ifdef", "ifndef":
-			ctx.handleIfBlock(x)
+			result = []starlarkNode{ctx.handleIfBlock(x)}
 		default:
-			ctx.errorf(x, "unexpected directive %s", x.Name)
+			result = []starlarkNode{ctx.newBadNode(x, "unexpected directive %s", x.Name)}
 		}
 	default:
-		ctx.errorf(x, "unsupported line %s", strings.ReplaceAll(x.Dump(), "\n", "\n#"))
+		result = []starlarkNode{ctx.newBadNode(x, "unsupported line %s", strings.ReplaceAll(x.Dump(), "\n", "\n#"))}
 	}
 
 	// Clear the includeTops after each non-comment statement
@@ -1763,12 +1722,17 @@
 	if _, wasComment := node.(*mkparser.Comment); !wasComment && len(ctx.includeTops) > 0 {
 		ctx.includeTops = []string{}
 	}
+
+	if result == nil {
+		result = []starlarkNode{}
+	}
+	return result
 }
 
 // Processes annotation. An annotation is a comment that starts with #RBC# and provides
 // a conversion hint -- say, where to look for the dynamically calculated inherit/include
-// paths.
-func (ctx *parseContext) maybeHandleAnnotation(cnode *mkparser.Comment) {
+// paths. Returns true if the comment was a successfully-handled annotation.
+func (ctx *parseContext) maybeHandleAnnotation(cnode *mkparser.Comment) (starlarkNode, bool) {
 	maybeTrim := func(s, prefix string) (string, bool) {
 		if strings.HasPrefix(s, prefix) {
 			return strings.TrimSpace(strings.TrimPrefix(s, prefix)), true
@@ -1777,44 +1741,20 @@
 	}
 	annotation, ok := maybeTrim(cnode.Comment, annotationCommentPrefix)
 	if !ok {
-		return
+		return nil, false
 	}
 	if p, ok := maybeTrim(annotation, "include_top"); ok {
 		// Don't allow duplicate include tops, because then we will generate
 		// invalid starlark code. (duplicate keys in the _entry dictionary)
 		for _, top := range ctx.includeTops {
 			if top == p {
-				return
+				return nil, true
 			}
 		}
 		ctx.includeTops = append(ctx.includeTops, p)
-		return
+		return nil, true
 	}
-	ctx.errorf(cnode, "unsupported annotation %s", cnode.Comment)
-
-}
-
-func (ctx *parseContext) insertComment(s string) {
-	ctx.receiver.newNode(&commentNode{strings.TrimSpace(s)})
-}
-
-func (ctx *parseContext) carryAsComment(failedNode mkparser.Node) {
-	for _, line := range strings.Split(failedNode.Dump(), "\n") {
-		ctx.insertComment("# " + line)
-	}
-}
-
-// records that the given node failed to be converted and includes an explanatory message
-func (ctx *parseContext) errorf(failedNode mkparser.Node, message string, args ...interface{}) {
-	if ctx.errorLogger != nil {
-		ctx.errorLogger.NewError(ctx.errorLocation(failedNode), failedNode, message, args...)
-	}
-	ctx.receiver.newNode(&exprNode{ctx.newBadExpr(failedNode, message, args...)})
-	ctx.script.hasErrors = true
-}
-
-func (ctx *parseContext) wrapBadExpr(xBad *badExpr) {
-	ctx.receiver.newNode(&exprNode{xBad})
+	return ctx.newBadNode(cnode, "unsupported annotation %s", cnode.Comment), true
 }
 
 func (ctx *parseContext) loadedModulePath(path string) string {
@@ -1929,6 +1869,7 @@
 		sourceFS:       req.SourceFS,
 		makefileFinder: req.MakefileFinder,
 		nodeLocator:    func(pos mkparser.Pos) int { return parser.Unpack(pos).Line },
+		nodes:          make([]starlarkNode, 0),
 	}
 	ctx := newParseContext(starScript, nodes)
 	ctx.outputSuffix = req.OutputSuffix
@@ -1940,9 +1881,8 @@
 			ctx.tracedVariables[v] = true
 		}
 	}
-	ctx.pushReceiver(starScript)
 	for ctx.hasNodes() && ctx.fatalError == nil {
-		ctx.handleSimpleStatement(ctx.getNode())
+		starScript.nodes = append(starScript.nodes, ctx.handleSimpleStatement(ctx.getNode())...)
 	}
 	if ctx.fatalError != nil {
 		return nil, ctx.fatalError
diff --git a/mk2rbc/mk2rbc_test.go b/mk2rbc/mk2rbc_test.go
index c4f7da3..c499398 100644
--- a/mk2rbc/mk2rbc_test.go
+++ b/mk2rbc/mk2rbc_test.go
@@ -131,7 +131,7 @@
 def init(g, handle):
   cfg = rblf.cfg(handle)
   rblf.inherit(handle, "part", _part_init)
-  if g.get("PRODUCT_NAME") != None:
+  if cfg.get("PRODUCT_NAME", ""):
     if not _part1_init:
       rblf.mkerror("product.mk", "Cannot find %s" % (":part1.star"))
     rblf.inherit(handle, "part1", _part1_init)
@@ -174,7 +174,7 @@
 def init(g, handle):
   cfg = rblf.cfg(handle)
   _part_init(g, handle)
-  if g.get("PRODUCT_NAME") != None:
+  if cfg.get("PRODUCT_NAME", ""):
     if not _part1_init:
       rblf.mkerror("product.mk", "Cannot find %s" % (":part1.star"))
     _part1_init(g, handle)
@@ -231,7 +231,7 @@
 
 def init(g, handle):
   cfg = rblf.cfg(handle)
-  if g.get("PRODUCT_NAME") != None:
+  if cfg.get("PRODUCT_NAME", ""):
     cfg["PRODUCT_NAME"] = "gizmo"
   else:
     pass
@@ -275,7 +275,7 @@
 
 def init(g, handle):
   cfg = rblf.cfg(handle)
-  if g.get("PRODUCT_NAME") != None:
+  if cfg.get("PRODUCT_NAME", ""):
     # Comment
     pass
   else:
@@ -296,7 +296,7 @@
 
 def init(g, handle):
   cfg = rblf.cfg(handle)
-  if not g.get("PRODUCT_NAME") != None:
+  if not cfg.get("PRODUCT_NAME", ""):
     cfg["PRODUCT_NAME"] = "gizmo1"
   else:
     cfg["PRODUCT_NAME"] = "gizmo2"
@@ -315,9 +315,9 @@
 
 def init(g, handle):
   cfg = rblf.cfg(handle)
-  if g.get("PRODUCT_NAME") != None:
+  if cfg.get("PRODUCT_NAME", ""):
     cfg["PRODUCT_NAME"] = "gizmo"
-  elif not g.get("PRODUCT_PACKAGES") != None:
+  elif not cfg.get("PRODUCT_PACKAGES", []):
     # Comment
     pass
 `,
@@ -509,11 +509,11 @@
 
 def init(g, handle):
   cfg = rblf.cfg(handle)
-  if g.get("PRODUCT_NAME") != None:
+  if cfg.get("PRODUCT_NAME", ""):
     cfg["PRODUCT_PACKAGES"] = ["pack-if0"]
-    if g.get("PRODUCT_MODEL") != None:
+    if cfg.get("PRODUCT_MODEL", ""):
       cfg["PRODUCT_PACKAGES"] = ["pack-if-if"]
-    elif g.get("PRODUCT_NAME") != None:
+    elif cfg.get("PRODUCT_NAME", ""):
       cfg["PRODUCT_PACKAGES"] = ["pack-if-elif"]
     else:
       cfg["PRODUCT_PACKAGES"] = ["pack-if-else"]
@@ -1071,14 +1071,7 @@
 def init(g, handle):
   cfg = rblf.cfg(handle)
   g["MY_PATH"] = "foo"
-  #RBC# include_top vendor/foo1
-  _entry = {
-    "vendor/foo1/cfg.mk": ("vendor/foo1/cfg", _cfg_init),
-  }.get("%s/cfg.mk" % g["MY_PATH"])
-  (_varmod, _varmod_init) = _entry if _entry else (None, None)
-  if not _varmod_init:
-    rblf.mkerror("product.mk", "Cannot find %s" % ("%s/cfg.mk" % g["MY_PATH"]))
-  rblf.inherit(handle, _varmod, _varmod_init)
+  rblf.inherit(handle, "vendor/foo1/cfg", _cfg_init)
 `,
 	},
 	{
@@ -1089,6 +1082,7 @@
 #RBC# include_top vendor/foo1
 $(call inherit-product,$(MY_PATH)/cfg.mk)
 #RBC# include_top vendor/foo1
+#RBC# include_top vendor/foo1
 $(call inherit-product,$(MY_PATH)/cfg.mk)
 `,
 		expected: `load("//build/make/core:product_config.rbc", "rblf")
@@ -1097,26 +1091,12 @@
 def init(g, handle):
   cfg = rblf.cfg(handle)
   g["MY_PATH"] = "foo"
-  #RBC# include_top vendor/foo1
-  _entry = {
-    "vendor/foo1/cfg.mk": ("vendor/foo1/cfg", _cfg_init),
-  }.get("%s/cfg.mk" % g["MY_PATH"])
-  (_varmod, _varmod_init) = _entry if _entry else (None, None)
-  if not _varmod_init:
-    rblf.mkerror("product.mk", "Cannot find %s" % ("%s/cfg.mk" % g["MY_PATH"]))
-  rblf.inherit(handle, _varmod, _varmod_init)
-  #RBC# include_top vendor/foo1
-  _entry = {
-    "vendor/foo1/cfg.mk": ("vendor/foo1/cfg", _cfg_init),
-  }.get("%s/cfg.mk" % g["MY_PATH"])
-  (_varmod, _varmod_init) = _entry if _entry else (None, None)
-  if not _varmod_init:
-    rblf.mkerror("product.mk", "Cannot find %s" % ("%s/cfg.mk" % g["MY_PATH"]))
-  rblf.inherit(handle, _varmod, _varmod_init)
+  rblf.inherit(handle, "vendor/foo1/cfg", _cfg_init)
+  rblf.inherit(handle, "vendor/foo1/cfg", _cfg_init)
 `,
 	},
 	{
-		desc:   "Dynamic inherit path that lacks necessary hint",
+		desc:   "Dynamic inherit path that lacks hint",
 		mkname: "product.mk",
 		in: `
 #RBC# include_top foo
@@ -1130,29 +1110,24 @@
 
 $(call inherit-product,$(MY_VAR)/font.mk)
 `,
-		expected: `#RBC# include_top foo
-load("//build/make/core:product_config.rbc", "rblf")
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
 load("//foo:font.star|init", _font_init = "init")
+load("//bar:font.star|init", _font1_init = "init")
 
 def init(g, handle):
   cfg = rblf.cfg(handle)
-  _entry = {
-    "foo/font.mk": ("foo/font", _font_init),
-  }.get("%s/font.mk" % g.get("MY_VAR", ""))
-  (_varmod, _varmod_init) = _entry if _entry else (None, None)
-  if not _varmod_init:
-    rblf.mkerror("product.mk", "Cannot find %s" % ("%s/font.mk" % g.get("MY_VAR", "")))
-  rblf.inherit(handle, _varmod, _varmod_init)
-  #RBC# include_top foo
+  rblf.inherit(handle, "foo/font", _font_init)
   # There's some space and even this comment between the include_top and the inherit-product
+  rblf.inherit(handle, "foo/font", _font_init)
+  rblf.mkwarning("product.mk:11", "Including a path with a non-constant prefix, please convert this to a simple literal to generate cleaner starlark.")
   _entry = {
     "foo/font.mk": ("foo/font", _font_init),
+    "bar/font.mk": ("bar/font", _font1_init),
   }.get("%s/font.mk" % g.get("MY_VAR", ""))
   (_varmod, _varmod_init) = _entry if _entry else (None, None)
   if not _varmod_init:
     rblf.mkerror("product.mk", "Cannot find %s" % ("%s/font.mk" % g.get("MY_VAR", "")))
   rblf.inherit(handle, _varmod, _varmod_init)
-  rblf.mk2rbc_error("product.mk:11", "inherit-product/include statements must not be prefixed with a variable, or must include a #RBC# include_top comment beforehand giving a root directory to search.")
 `,
 	},
 	{
@@ -1178,7 +1153,6 @@
 def init(g, handle):
   cfg = rblf.cfg(handle)
   rblf.mk2rbc_error("product.mk:2", "cannot handle override directive")
-  g["override FOO"] = ""
 `,
 	},
 	{
diff --git a/mk2rbc/node.go b/mk2rbc/node.go
index 4f7c4f0..61aaf91 100644
--- a/mk2rbc/node.go
+++ b/mk2rbc/node.go
@@ -86,6 +86,8 @@
 	path             interpolateExpr
 	candidateModules []*moduleInfo
 	loadAlways       bool
+	location         ErrorLocation
+	needsWarning     bool
 }
 
 func (i inheritedDynamicModule) name() string {
@@ -97,6 +99,10 @@
 }
 
 func (i inheritedDynamicModule) emitSelect(gctx *generationContext) {
+	if i.needsWarning {
+		gctx.newLine()
+		gctx.writef("%s.mkwarning(%q, %q)", baseName, i.location, "Including a path with a non-constant prefix, please convert this to a simple literal to generate cleaner starlark.")
+	}
 	gctx.newLine()
 	gctx.writef("_entry = {")
 	gctx.indentLevel++
@@ -249,29 +255,17 @@
 	nodes []starlarkNode
 }
 
-func (cb *switchCase) newNode(node starlarkNode) {
-	cb.nodes = append(cb.nodes, node)
-}
-
 func (cb *switchCase) emit(gctx *generationContext) {
 	cb.gate.emit(gctx)
 	gctx.indentLevel++
 	hasStatements := false
-	emitNode := func(node starlarkNode) {
+	for _, node := range cb.nodes {
 		if _, ok := node.(*commentNode); !ok {
 			hasStatements = true
 		}
 		node.emit(gctx)
 	}
-	if len(cb.nodes) > 0 {
-		emitNode(cb.nodes[0])
-		for _, node := range cb.nodes[1:] {
-			emitNode(node)
-		}
-		if !hasStatements {
-			gctx.emitPass()
-		}
-	} else {
+	if !hasStatements {
 		gctx.emitPass()
 	}
 	gctx.indentLevel--
@@ -282,22 +276,8 @@
 	ssCases []*switchCase
 }
 
-func (ssw *switchNode) newNode(node starlarkNode) {
-	switch br := node.(type) {
-	case *switchCase:
-		ssw.ssCases = append(ssw.ssCases, br)
-	default:
-		panic(fmt.Errorf("expected switchCase node, got %t", br))
-	}
-}
-
 func (ssw *switchNode) emit(gctx *generationContext) {
-	if len(ssw.ssCases) == 0 {
-		gctx.emitPass()
-	} else {
-		ssw.ssCases[0].emit(gctx)
-		for _, ssCase := range ssw.ssCases[1:] {
-			ssCase.emit(gctx)
-		}
+	for _, ssCase := range ssw.ssCases {
+		ssCase.emit(gctx)
 	}
 }
diff --git a/scripts/manifest_fixer.py b/scripts/manifest_fixer.py
index d80a617..2d3103b 100755
--- a/scripts/manifest_fixer.py
+++ b/scripts/manifest_fixer.py
@@ -65,6 +65,9 @@
   parser.add_argument('--has-no-code', dest='has_no_code', action='store_true',
                       help=('adds hasCode="false" attribute to application. Ignored if application elem '
                             'already has a hasCode attribute.'))
+  parser.add_argument('--test-only', dest='test_only', action='store_true',
+                      help=('adds testOnly="true" attribute to application. Assign true value if application elem '
+                            'already has a testOnly attribute.'))
   parser.add_argument('input', help='input AndroidManifest.xml file')
   parser.add_argument('output', help='output AndroidManifest.xml file')
   return parser.parse_args()
@@ -318,6 +321,26 @@
   attr.value = 'false'
   application.setAttributeNode(attr)
 
+def set_test_only_flag_to_true(doc):
+  manifest = parse_manifest(doc)
+  elems = get_children_with_tag(manifest, 'application')
+  application = elems[0] if len(elems) == 1 else None
+  if len(elems) > 1:
+    raise RuntimeError('found multiple <application> tags')
+  elif not elems:
+    application = doc.createElement('application')
+    indent = get_indent(manifest.firstChild, 1)
+    first = manifest.firstChild
+    manifest.insertBefore(doc.createTextNode(indent), first)
+    manifest.insertBefore(application, first)
+
+  attr = application.getAttributeNodeNS(android_ns, 'testOnly')
+  if attr is not None:
+    # Do nothing If the application already has a testOnly attribute.
+    return
+  attr = doc.createAttributeNS(android_ns, 'android:testOnly')
+  attr.value = 'true'
+  application.setAttributeNode(attr)
 
 def main():
   """Program entry point."""
@@ -349,6 +372,9 @@
     if args.has_no_code:
       set_has_code_to_false(doc)
 
+    if args.test_only:
+      set_test_only_flag_to_true(doc)
+
     if args.extract_native_libs is not None:
       add_extract_native_libs(doc, args.extract_native_libs)
 
diff --git a/scripts/manifest_fixer_test.py b/scripts/manifest_fixer_test.py
index f6fcaaf..199b279 100755
--- a/scripts/manifest_fixer_test.py
+++ b/scripts/manifest_fixer_test.py
@@ -521,12 +521,55 @@
     self.assert_xml_equal(output, manifest_input)
 
   def test_has_application_has_code_true(self):
-    """ Do nothing if there's already an application elemeent even if its
+    """ Do nothing if there's already an application element even if its
      hasCode attribute is true. """
     manifest_input = self.manifest_tmpl % '    <application android:hasCode="true"/>\n'
     output = self.run_test(manifest_input)
     self.assert_xml_equal(output, manifest_input)
 
 
+class AddTestOnlyApplicationTest(unittest.TestCase):
+  """Unit tests for set_test_only_flag_to_true function."""
+
+  def assert_xml_equal(self, output, expected):
+    self.assertEqual(ET.canonicalize(output), ET.canonicalize(expected))
+
+  def run_test(self, input_manifest):
+    doc = minidom.parseString(input_manifest)
+    manifest_fixer.set_test_only_flag_to_true(doc)
+    output = io.StringIO()
+    manifest_fixer.write_xml(output, doc)
+    return output.getvalue()
+
+  manifest_tmpl = (
+      '<?xml version="1.0" encoding="utf-8"?>\n'
+      '<manifest xmlns:android="http://schemas.android.com/apk/res/android">\n'
+      '%s'
+      '</manifest>\n')
+
+  def test_no_application(self):
+    manifest_input = self.manifest_tmpl % ''
+    expected = self.manifest_tmpl % '    <application android:testOnly="true"/>\n'
+    output = self.run_test(manifest_input)
+    self.assert_xml_equal(output, expected)
+
+  def test_has_application_no_test_only(self):
+    manifest_input = self.manifest_tmpl % '    <application/>\n'
+    expected = self.manifest_tmpl % '    <application android:testOnly="true"/>\n'
+    output = self.run_test(manifest_input)
+    self.assert_xml_equal(output, expected)
+
+  def test_has_application_test_only_true(self):
+    """ If there's already an application element."""
+    manifest_input = self.manifest_tmpl % '    <application android:testOnly="true"/>\n'
+    output = self.run_test(manifest_input)
+    self.assert_xml_equal(output, manifest_input)
+
+  def test_has_application_test_only_false(self):
+    """ If there's already an application element with the testOnly attribute as false."""
+    manifest_input = self.manifest_tmpl % '    <application android:testOnly="false"/>\n'
+    output = self.run_test(manifest_input)
+    self.assert_xml_equal(output, manifest_input)
+
 if __name__ == '__main__':
   unittest.main(verbosity=2)
diff --git a/sdk/build_release.go b/sdk/build_release.go
index a3f0899..2bcdc6f 100644
--- a/sdk/build_release.go
+++ b/sdk/build_release.go
@@ -230,51 +230,108 @@
 			return container.Field(fieldIndex)
 		}
 
-		zeroValue := reflect.Zero(field.Type)
-		fieldPruner := func(container reflect.Value) {
-			if containingStructAccessor != nil {
-				// This is an embedded structure so first access the field for the embedded
-				// structure.
-				container = containingStructAccessor(container)
+		fieldType := field.Type
+		if selector(name, field) {
+			zeroValue := reflect.Zero(fieldType)
+			fieldPruner := func(container reflect.Value) {
+				if containingStructAccessor != nil {
+					// This is an embedded structure so first access the field for the embedded
+					// structure.
+					container = containingStructAccessor(container)
+				}
+
+				// Skip through interface and pointer values to find the structure.
+				container = getStructValue(container)
+
+				defer func() {
+					if r := recover(); r != nil {
+						panic(fmt.Errorf("%s\n\tfor field (index %d, name %s)", r, fieldIndex, name))
+					}
+				}()
+
+				// Set the field.
+				container.Field(fieldIndex).Set(zeroValue)
 			}
 
-			// Skip through interface and pointer values to find the structure.
-			container = getStructValue(container)
-
-			defer func() {
-				if r := recover(); r != nil {
-					panic(fmt.Errorf("%s for fieldIndex %d of field %s of container %#v", r, fieldIndex, name, container.Interface()))
-				}
-			}()
-
-			// Set the field.
-			container.Field(fieldIndex).Set(zeroValue)
-		}
-
-		if selector(name, field) {
 			property := prunerProperty{
 				name,
 				fieldPruner,
 			}
 			p.properties = append(p.properties, property)
-		} else if field.Type.Kind() == reflect.Struct {
-			// Gather fields from the nested or embedded structure.
-			var subNamePrefix string
-			if field.Anonymous {
-				subNamePrefix = namePrefix
-			} else {
-				subNamePrefix = name + "."
+		} else {
+			switch fieldType.Kind() {
+			case reflect.Struct:
+				// Gather fields from the nested or embedded structure.
+				var subNamePrefix string
+				if field.Anonymous {
+					subNamePrefix = namePrefix
+				} else {
+					subNamePrefix = name + "."
+				}
+				p.gatherFields(fieldType, fieldGetter, subNamePrefix, selector)
+
+			case reflect.Map:
+				// Get the type of the values stored in the map.
+				valueType := fieldType.Elem()
+				// Skip over * types.
+				if valueType.Kind() == reflect.Ptr {
+					valueType = valueType.Elem()
+				}
+				if valueType.Kind() == reflect.Struct {
+					// If this is not referenced by a pointer then it is an error as it is impossible to
+					// modify a struct that is stored directly as a value in a map.
+					if fieldType.Elem().Kind() != reflect.Ptr {
+						panic(fmt.Errorf("Cannot prune struct %s stored by value in map %s, map values must"+
+							" be pointers to structs",
+							fieldType.Elem(), name))
+					}
+
+					// Create a new pruner for the values of the map.
+					valuePruner := newPropertyPrunerForStructType(valueType, selector)
+
+					// Create a new fieldPruner that will iterate over all the items in the map and call the
+					// pruner on them.
+					fieldPruner := func(container reflect.Value) {
+						mapValue := fieldGetter(container)
+
+						for _, keyValue := range mapValue.MapKeys() {
+							itemValue := mapValue.MapIndex(keyValue)
+
+							defer func() {
+								if r := recover(); r != nil {
+									panic(fmt.Errorf("%s\n\tfor key %q", r, keyValue))
+								}
+							}()
+
+							valuePruner.pruneProperties(itemValue.Interface())
+						}
+					}
+
+					// Add the map field pruner to the list of property pruners.
+					property := prunerProperty{
+						name + "[*]",
+						fieldPruner,
+					}
+					p.properties = append(p.properties, property)
+				}
 			}
-			p.gatherFields(field.Type, fieldGetter, subNamePrefix, selector)
 		}
 	}
 }
 
-// pruneProperties will prune (set to zero value) any properties in the supplied struct.
+// pruneProperties will prune (set to zero value) any properties in the struct referenced by the
+// supplied struct pointer.
 //
 // The struct must be of the same type as was originally passed to newPropertyPruner to create this
 // propertyPruner.
 func (p *propertyPruner) pruneProperties(propertiesStruct interface{}) {
+
+	defer func() {
+		if r := recover(); r != nil {
+			panic(fmt.Errorf("%s\n\tof container %#v", r, propertiesStruct))
+		}
+	}()
+
 	structValue := reflect.ValueOf(propertiesStruct)
 	for _, property := range p.properties {
 		property.prunerFunc(structValue)
@@ -292,6 +349,13 @@
 // of properties.
 func newPropertyPruner(propertiesStruct interface{}, selector fieldSelectorFunc) *propertyPruner {
 	structType := getStructValue(reflect.ValueOf(propertiesStruct)).Type()
+	return newPropertyPrunerForStructType(structType, selector)
+}
+
+// newPropertyPruner creates a new property pruner for the supplied properties struct type.
+//
+// The returned pruner can be used on any properties structure of the supplied type.
+func newPropertyPrunerForStructType(structType reflect.Type, selector fieldSelectorFunc) *propertyPruner {
 	pruner := &propertyPruner{}
 	pruner.gatherFields(structType, nil, "", selector)
 	return pruner
diff --git a/sdk/build_release_test.go b/sdk/build_release_test.go
index dff276d..6608be4 100644
--- a/sdk/build_release_test.go
+++ b/sdk/build_release_test.go
@@ -15,6 +15,7 @@
 package sdk
 
 import (
+	"encoding/json"
 	"fmt"
 	"testing"
 
@@ -125,61 +126,102 @@
 		F1_only string `supported_build_releases:"F1"`
 	}
 
+	type mapped struct {
+		Default string
+		T_only  string `supported_build_releases:"T"`
+	}
+
 	type testBuildReleasePruner struct {
 		Default      string
 		S_and_T_only string `supported_build_releases:"S-T"`
 		T_later      string `supported_build_releases:"T+"`
 		Nested       nested
+		Mapped       map[string]*mapped
 	}
 
-	input := testBuildReleasePruner{
-		Default:      "Default",
-		S_and_T_only: "S_and_T_only",
-		T_later:      "T_later",
-		Nested: nested{
-			F1_only: "F1_only",
-		},
+	inputFactory := func() testBuildReleasePruner {
+		return testBuildReleasePruner{
+			Default:      "Default",
+			S_and_T_only: "S_and_T_only",
+			T_later:      "T_later",
+			Nested: nested{
+				F1_only: "F1_only",
+			},
+			Mapped: map[string]*mapped{
+				"one": {
+					Default: "one-default",
+					T_only:  "one-t-only",
+				},
+				"two": {
+					Default: "two-default",
+					T_only:  "two-t-only",
+				},
+			},
+		}
+	}
+
+	marshal := func(t interface{}) string {
+		bytes, err := json.MarshalIndent(t, "", "  ")
+		if err != nil {
+			panic(err)
+		}
+		return string(bytes)
+	}
+
+	assertJsonEquals := func(t *testing.T, expected, actual interface{}) {
+		t.Helper()
+		expectedJson := marshal(expected)
+		actualJson := marshal(actual)
+		if actualJson != expectedJson {
+			t.Errorf("test struct: expected:\n%s\n got:\n%s", expectedJson, actualJson)
+		}
 	}
 
 	t.Run("target S", func(t *testing.T) {
-		testStruct := input
+		testStruct := inputFactory()
 		pruner := newPropertyPrunerByBuildRelease(&testStruct, buildReleaseS)
 		pruner.pruneProperties(&testStruct)
 
-		expected := input
+		expected := inputFactory()
 		expected.T_later = ""
 		expected.Nested.F1_only = ""
-		android.AssertDeepEquals(t, "test struct", expected, testStruct)
+		expected.Mapped["one"].T_only = ""
+		expected.Mapped["two"].T_only = ""
+		assertJsonEquals(t, expected, testStruct)
 	})
 
 	t.Run("target T", func(t *testing.T) {
-		testStruct := input
+		testStruct := inputFactory()
 		pruner := newPropertyPrunerByBuildRelease(&testStruct, buildReleaseT)
 		pruner.pruneProperties(&testStruct)
 
-		expected := input
+		expected := inputFactory()
 		expected.Nested.F1_only = ""
-		android.AssertDeepEquals(t, "test struct", expected, testStruct)
+		assertJsonEquals(t, expected, testStruct)
 	})
 
 	t.Run("target F1", func(t *testing.T) {
-		testStruct := input
+		testStruct := inputFactory()
 		pruner := newPropertyPrunerByBuildRelease(&testStruct, buildReleaseFuture1)
 		pruner.pruneProperties(&testStruct)
 
-		expected := input
+		expected := inputFactory()
 		expected.S_and_T_only = ""
-		android.AssertDeepEquals(t, "test struct", expected, testStruct)
+		expected.Mapped["one"].T_only = ""
+		expected.Mapped["two"].T_only = ""
+		assertJsonEquals(t, expected, testStruct)
 	})
 
 	t.Run("target F2", func(t *testing.T) {
-		testStruct := input
+		testStruct := inputFactory()
 		pruner := newPropertyPrunerByBuildRelease(&testStruct, buildReleaseFuture2)
 		pruner.pruneProperties(&testStruct)
 
-		expected := input
+		expected := inputFactory()
 		expected.S_and_T_only = ""
 		expected.Nested.F1_only = ""
-		android.AssertDeepEquals(t, "test struct", expected, testStruct)
+		expected.Mapped["one"].T_only = ""
+		expected.Mapped["two"].T_only = ""
+		assertJsonEquals(t, expected, testStruct)
 	})
 }
diff --git a/sdk/java_sdk_test.go b/sdk/java_sdk_test.go
index 0d9b4a0..f0d3b35 100644
--- a/sdk/java_sdk_test.go
+++ b/sdk/java_sdk_test.go
@@ -1319,6 +1319,58 @@
 	)
 }
 
+func TestSnapshotWithJavaSdkLibrary_AnnotationsZip_PreT(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForSdkTestWithJavaSdkLibrary,
+		android.FixtureMergeEnv(map[string]string{
+			"SOONG_SDK_SNAPSHOT_TARGET_BUILD_RELEASE": "S",
+		}),
+	).RunTestWithBp(t, `
+		sdk {
+			name: "mysdk",
+			java_sdk_libs: ["myjavalib"],
+		}
+
+		java_sdk_library {
+			name: "myjavalib",
+			srcs: ["Test.java"],
+			sdk_version: "current",
+			shared_library: false,
+			annotations_enabled: true,
+			public: {
+				enabled: true,
+			},
+		}
+	`)
+
+	CheckSnapshot(t, result, "mysdk", "",
+		checkUnversionedAndroidBpContents(`
+// This is auto-generated. DO NOT EDIT.
+
+java_sdk_library_import {
+    name: "myjavalib",
+    prefer: false,
+    visibility: ["//visibility:public"],
+    apex_available: ["//apex_available:platform"],
+    shared_library: false,
+    public: {
+        jars: ["sdk_library/public/myjavalib-stubs.jar"],
+        stub_srcs: ["sdk_library/public/myjavalib_stub_sources"],
+        current_api: "sdk_library/public/myjavalib.txt",
+        removed_api: "sdk_library/public/myjavalib-removed.txt",
+        sdk_version: "current",
+    },
+}
+		`),
+		checkAllCopyRules(`
+.intermediates/myjavalib.stubs/android_common/javac/myjavalib.stubs.jar -> sdk_library/public/myjavalib-stubs.jar
+.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"),
+	)
+}
+
 func TestSnapshotWithJavaSdkLibrary_CompileDex(t *testing.T) {
 	result := android.GroupFixturePreparers(prepareForSdkTestWithJavaSdkLibrary).RunTestWithBp(t, `
 		sdk {
diff --git a/ui/build/cleanbuild.go b/ui/build/cleanbuild.go
index a3a1aaf..1c80cff 100644
--- a/ui/build/cleanbuild.go
+++ b/ui/build/cleanbuild.go
@@ -171,6 +171,7 @@
 		productOut("recovery"),
 		productOut("root"),
 		productOut("system"),
+		productOut("system_dlkm"),
 		productOut("system_other"),
 		productOut("vendor"),
 		productOut("vendor_dlkm"),