Merge "Don't fail the build if manifest_check cannot extract targetSdkVersion."
diff --git a/android/apex.go b/android/apex.go
index a5ff442..257bdad 100644
--- a/android/apex.go
+++ b/android/apex.go
@@ -453,6 +453,23 @@
 	}
 }
 
+// AvailableToSameApexes returns true if the two modules are apex_available to
+// exactly the same set of APEXes (and platform), i.e. if their apex_available
+// properties have the same elements.
+func AvailableToSameApexes(mod1, mod2 ApexModule) bool {
+	mod1ApexAvail := SortedUniqueStrings(mod1.apexModuleBase().ApexProperties.Apex_available)
+	mod2ApexAvail := SortedUniqueStrings(mod2.apexModuleBase().ApexProperties.Apex_available)
+	if len(mod1ApexAvail) != len(mod2ApexAvail) {
+		return false
+	}
+	for i, v := range mod1ApexAvail {
+		if v != mod2ApexAvail[i] {
+			return false
+		}
+	}
+	return true
+}
+
 type byApexName []ApexInfo
 
 func (a byApexName) Len() int           { return len(a) }
diff --git a/android/paths.go b/android/paths.go
index 1278961..b457372 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -1642,16 +1642,10 @@
 
 // Will panic if called from outside a test environment.
 func ensureTestOnly() {
-	// Normal soong test environment
-	if InList("-test.short", os.Args) {
+	if PrefixInList(os.Args, "-test.") {
 		return
 	}
-	// IntelliJ test environment
-	if InList("-test.v", os.Args) {
-		return
-	}
-
-	panic(fmt.Errorf("Not in test\n%s", strings.Join(os.Args, "\n")))
+	panic(fmt.Errorf("Not in test. Command line:\n  %s", strings.Join(os.Args, "\n  ")))
 }
 
 func (p InstallPath) RelativeToTop() Path {
diff --git a/android/util.go b/android/util.go
index 506f8f7..a0394f6 100644
--- a/android/util.go
+++ b/android/util.go
@@ -193,6 +193,17 @@
 	return
 }
 
+// FilterListPred returns the elements of the given list for which the predicate
+// returns true. Order is kept.
+func FilterListPred(list []string, pred func(s string) bool) (filtered []string) {
+	for _, l := range list {
+		if pred(l) {
+			filtered = append(filtered, l)
+		}
+	}
+	return
+}
+
 // RemoveListFromList removes the strings belonging to the filter list from the
 // given list and returns the result
 func RemoveListFromList(list []string, filter_out []string) (result []string) {
diff --git a/android/util_test.go b/android/util_test.go
index fa26c77..09bec01 100644
--- a/android/util_test.go
+++ b/android/util_test.go
@@ -18,6 +18,7 @@
 	"fmt"
 	"reflect"
 	"strconv"
+	"strings"
 	"testing"
 )
 
@@ -299,6 +300,14 @@
 	}
 }
 
+func TestFilterListPred(t *testing.T) {
+	pred := func(s string) bool { return strings.HasPrefix(s, "a/") }
+	AssertArrayString(t, "filter", FilterListPred([]string{"a/c", "b/a", "a/b"}, pred), []string{"a/c", "a/b"})
+	AssertArrayString(t, "filter", FilterListPred([]string{"b/c", "a/a", "b/b"}, pred), []string{"a/a"})
+	AssertArrayString(t, "filter", FilterListPred([]string{"c/c", "b/a", "c/b"}, pred), []string{})
+	AssertArrayString(t, "filter", FilterListPred([]string{"a/c", "a/a", "a/b"}, pred), []string{"a/c", "a/a", "a/b"})
+}
+
 func TestRemoveListFromList(t *testing.T) {
 	input := []string{"a", "b", "c", "d", "a", "c", "d"}
 	filter := []string{"a", "c"}
diff --git a/apex/apex.go b/apex/apex.go
index 66c598c..80d9615 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -93,8 +93,14 @@
 	Multilib apexMultilibProperties
 
 	// List of boot images that are embedded inside this APEX bundle.
+	//
+	// deprecated: Use Bootclasspath_fragments
+	// TODO(b/177892522): Remove after has been replaced by Bootclasspath_fragments
 	Boot_images []string
 
+	// List of bootclasspath fragments that are embedded inside this APEX bundle.
+	Bootclasspath_fragments []string
+
 	// List of java libraries that are embedded inside this APEX bundle.
 	Java_libs []string
 
@@ -748,6 +754,7 @@
 	// Common-arch dependencies come next
 	commonVariation := ctx.Config().AndroidCommonTarget.Variations()
 	ctx.AddFarVariationDependencies(commonVariation, bootImageTag, a.properties.Boot_images...)
+	ctx.AddFarVariationDependencies(commonVariation, bootImageTag, a.properties.Bootclasspath_fragments...)
 	ctx.AddFarVariationDependencies(commonVariation, javaLibTag, a.properties.Java_libs...)
 	ctx.AddFarVariationDependencies(commonVariation, bpfTag, a.properties.Bpfs...)
 	ctx.AddFarVariationDependencies(commonVariation, fsTag, a.properties.Filesystems...)
diff --git a/apex/apex_test.go b/apex/apex_test.go
index bcbbb28..37db5d8 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -6863,21 +6863,77 @@
 		}
 	`)
 
-	// the test 'mytest' is a test for the apex, therefore is linked to the
+	ensureLinkedLibIs := func(mod, variant, linkedLib, expectedVariant string) {
+		ldFlags := strings.Split(ctx.ModuleForTests(mod, variant).Rule("ld").RelativeToTop().Args["libFlags"], " ")
+		mylibLdFlags := android.FilterListPred(ldFlags, func(s string) bool { return strings.HasPrefix(s, linkedLib) })
+		android.AssertArrayString(t, "unexpected "+linkedLib+" link library for "+mod, []string{linkedLib + expectedVariant}, mylibLdFlags)
+	}
+
+	// These modules are tests for the apex, therefore are linked to the
 	// actual implementation of mylib instead of its stub.
-	ldFlags := ctx.ModuleForTests("mytest", "android_arm64_armv8-a").Rule("ld").Args["libFlags"]
-	ensureContains(t, ldFlags, "mylib/android_arm64_armv8-a_shared/mylib.so")
-	ensureNotContains(t, ldFlags, "mylib/android_arm64_armv8-a_shared_1/mylib.so")
+	ensureLinkedLibIs("mytest", "android_arm64_armv8-a", "out/soong/.intermediates/mylib/", "android_arm64_armv8-a_shared/mylib.so")
+	ensureLinkedLibIs("mytestlib", "android_arm64_armv8-a_shared", "out/soong/.intermediates/mylib/", "android_arm64_armv8-a_shared/mylib.so")
+	ensureLinkedLibIs("mybench", "android_arm64_armv8-a", "out/soong/.intermediates/mylib/", "android_arm64_armv8-a_shared/mylib.so")
+}
 
-	// The same should be true for cc_library
-	ldFlags = ctx.ModuleForTests("mytestlib", "android_arm64_armv8-a_shared").Rule("ld").Args["libFlags"]
-	ensureContains(t, ldFlags, "mylib/android_arm64_armv8-a_shared/mylib.so")
-	ensureNotContains(t, ldFlags, "mylib/android_arm64_armv8-a_shared_1/mylib.so")
+func TestIndirectTestFor(t *testing.T) {
+	ctx := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			native_shared_libs: ["mylib", "myprivlib"],
+			updatable: false,
+		}
 
-	// ... and for cc_benchmark
-	ldFlags = ctx.ModuleForTests("mybench", "android_arm64_armv8-a").Rule("ld").Args["libFlags"]
-	ensureContains(t, ldFlags, "mylib/android_arm64_armv8-a_shared/mylib.so")
-	ensureNotContains(t, ldFlags, "mylib/android_arm64_armv8-a_shared_1/mylib.so")
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_library {
+			name: "mylib",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			stl: "none",
+			stubs: {
+				versions: ["1"],
+			},
+			apex_available: ["myapex"],
+		}
+
+		cc_library {
+			name: "myprivlib",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			stl: "none",
+			shared_libs: ["mylib"],
+			apex_available: ["myapex"],
+		}
+
+		cc_library {
+			name: "mytestlib",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			shared_libs: ["myprivlib"],
+			stl: "none",
+			test_for: ["myapex"],
+		}
+	`)
+
+	ensureLinkedLibIs := func(mod, variant, linkedLib, expectedVariant string) {
+		ldFlags := strings.Split(ctx.ModuleForTests(mod, variant).Rule("ld").RelativeToTop().Args["libFlags"], " ")
+		mylibLdFlags := android.FilterListPred(ldFlags, func(s string) bool { return strings.HasPrefix(s, linkedLib) })
+		android.AssertArrayString(t, "unexpected "+linkedLib+" link library for "+mod, []string{linkedLib + expectedVariant}, mylibLdFlags)
+	}
+
+	// The platform variant of mytestlib links to the platform variant of the
+	// internal myprivlib.
+	ensureLinkedLibIs("mytestlib", "android_arm64_armv8-a_shared", "out/soong/.intermediates/myprivlib/", "android_arm64_armv8-a_shared/myprivlib.so")
+
+	// The platform variant of myprivlib links to the platform variant of mylib
+	// and bypasses its stubs.
+	ensureLinkedLibIs("myprivlib", "android_arm64_armv8-a_shared", "out/soong/.intermediates/mylib/", "android_arm64_armv8-a_shared/mylib.so")
 }
 
 // TODO(jungjw): Move this to proptools
diff --git a/apex/boot_image_test.go b/apex/boot_image_test.go
index 91b45fc..678a4cd 100644
--- a/apex/boot_image_test.go
+++ b/apex/boot_image_test.go
@@ -95,6 +95,9 @@
 		boot_image {
 			name: "art-boot-image",
 			image_name: "art",
+			apex_available: [
+				"com.android.art",
+			],
 		}
 
 		boot_image {
@@ -160,24 +163,29 @@
 	android.AssertTrimmedStringEquals(t, "invalid paths for "+moduleName, expectedBootImageFiles, strings.Join(allPaths, "\n"))
 }
 
-func TestBootImageInApex(t *testing.T) {
+func TestBootImageInArtApex(t *testing.T) {
 	result := android.GroupFixturePreparers(
 		prepareForTestWithBootImage,
-		prepareForTestWithMyapex,
-		// Configure some libraries in the framework boot image.
-		dexpreopt.FixtureSetBootJars("platform:foo", "platform:bar"),
+		prepareForTestWithArtApex,
+
+		// Configure some libraries in the art boot image.
+		dexpreopt.FixtureSetArtBootJars("com.android.art:foo", "com.android.art:bar"),
 	).RunTestWithBp(t, `
 		apex {
-			name: "myapex",
-			key: "myapex.key",
+			name: "com.android.art",
+			key: "com.android.art.key",
 			boot_images: [
 				"mybootimage",
 			],
+			java_libs: [
+				"foo",
+				"bar",
+			],
 			updatable: false,
 		}
 
 		apex_key {
-			name: "myapex.key",
+			name: "com.android.art.key",
 			public_key: "testkey.avbpubkey",
 			private_key: "testkey.pem",
 		}
@@ -186,52 +194,125 @@
 			name: "foo",
 			srcs: ["b.java"],
 			installable: true,
+			apex_available: [
+				"com.android.art",
+			],
 		}
 
 		java_library {
 			name: "bar",
 			srcs: ["b.java"],
 			installable: true,
+			apex_available: [
+				"com.android.art",
+			],
 		}
 
 		boot_image {
 			name: "mybootimage",
-			image_name: "boot",
+			image_name: "art",
 			apex_available: [
-				"myapex",
+				"com.android.art",
 			],
 		}
 
 		// Make sure that a preferred prebuilt doesn't affect the apex.
 		prebuilt_boot_image {
 			name: "mybootimage",
-			image_name: "boot",
+			image_name: "art",
 			prefer: true,
 			apex_available: [
-				"myapex",
+				"com.android.art",
 			],
 		}
 	`)
 
-	ensureExactContents(t, result.TestContext, "myapex", "android_common_myapex_image", []string{
+	ensureExactContents(t, result.TestContext, "com.android.art", "android_common_com.android.art_image", []string{
+		"javalib/arm/boot.art",
+		"javalib/arm/boot.oat",
+		"javalib/arm/boot.vdex",
 		"javalib/arm/boot-bar.art",
 		"javalib/arm/boot-bar.oat",
 		"javalib/arm/boot-bar.vdex",
-		"javalib/arm/boot-foo.art",
-		"javalib/arm/boot-foo.oat",
-		"javalib/arm/boot-foo.vdex",
+		"javalib/arm64/boot.art",
+		"javalib/arm64/boot.oat",
+		"javalib/arm64/boot.vdex",
 		"javalib/arm64/boot-bar.art",
 		"javalib/arm64/boot-bar.oat",
 		"javalib/arm64/boot-bar.vdex",
-		"javalib/arm64/boot-foo.art",
-		"javalib/arm64/boot-foo.oat",
-		"javalib/arm64/boot-foo.vdex",
+		"javalib/bar.jar",
+		"javalib/foo.jar",
 	})
 
-	java.CheckModuleDependencies(t, result.TestContext, "myapex", "android_common_myapex_image", []string{
-		`myapex.key`,
+	java.CheckModuleDependencies(t, result.TestContext, "com.android.art", "android_common_com.android.art_image", []string{
+		`bar`,
+		`com.android.art.key`,
+		`foo`,
 		`mybootimage`,
 	})
 }
 
+func TestBootImageInPrebuiltArtApex(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithBootImage,
+		prepareForTestWithArtApex,
+
+		android.FixtureMergeMockFs(android.MockFS{
+			"com.android.art-arm64.apex": nil,
+			"com.android.art-arm.apex":   nil,
+		}),
+
+		// Configure some libraries in the art boot image.
+		dexpreopt.FixtureSetArtBootJars("com.android.art:foo", "com.android.art:bar"),
+	).RunTestWithBp(t, `
+		prebuilt_apex {
+			name: "com.android.art",
+			arch: {
+				arm64: {
+					src: "com.android.art-arm64.apex",
+				},
+				arm: {
+					src: "com.android.art-arm.apex",
+				},
+			},
+			exported_java_libs: ["foo", "bar"],
+		}
+
+		java_import {
+			name: "foo",
+			jars: ["foo.jar"],
+			apex_available: [
+				"com.android.art",
+			],
+		}
+
+		java_import {
+			name: "bar",
+			jars: ["bar.jar"],
+			apex_available: [
+				"com.android.art",
+			],
+		}
+
+		prebuilt_boot_image {
+			name: "mybootimage",
+			image_name: "art",
+			apex_available: [
+				"com.android.art",
+			],
+		}
+	`)
+
+	java.CheckModuleDependencies(t, result.TestContext, "com.android.art", "android_common", []string{
+		`prebuilt_bar`,
+		`prebuilt_foo`,
+	})
+
+	java.CheckModuleDependencies(t, result.TestContext, "mybootimage", "android_common", []string{
+		`dex2oatd`,
+		`prebuilt_bar`,
+		`prebuilt_foo`,
+	})
+}
+
 // TODO(b/177892522) - add test for host apex.
diff --git a/bloaty/bloaty.go b/bloaty/bloaty.go
index 21bf4ac..653c489 100644
--- a/bloaty/bloaty.go
+++ b/bloaty/bloaty.go
@@ -22,7 +22,7 @@
 	"github.com/google/blueprint"
 )
 
-const bloatyDescriptorExt = "bloaty.csv"
+const bloatyDescriptorExt = ".bloaty.csv"
 const protoFilename = "binary_sizes.pb"
 
 var (
@@ -75,7 +75,7 @@
 			return
 		}
 		filePath := ctx.ModuleProvider(m, fileSizeMeasurerKey).(android.ModuleOutPath)
-		sizeFile := filePath.ReplaceExtension(ctx, bloatyDescriptorExt)
+		sizeFile := filePath.InSameDir(ctx, filePath.Base()+bloatyDescriptorExt)
 		ctx.Build(pctx, android.BuildParams{
 			Rule:        bloaty,
 			Description: "bloaty " + filePath.Rel(),
diff --git a/cc/cc.go b/cc/cc.go
index f843b41..f074597 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -363,7 +363,7 @@
 	// List of APEXes that this module has private access to for testing purpose. The module
 	// can depend on libraries that are not exported by the APEXes and use private symbols
 	// from the exported libraries.
-	Test_for []string
+	Test_for []string `android:"arch_variant"`
 }
 
 type VendorProperties struct {
@@ -2631,14 +2631,31 @@
 						// However, for host, ramdisk, vendor_ramdisk, recovery or bootstrap modules,
 						// always link to non-stub variant
 						useStubs = dep.(android.ApexModule).NotInPlatform() && !c.bootstrap()
-						// Another exception: if this module is bundled with an APEX, then
-						// it is linked with the non-stub variant of a module in the APEX
-						// as if this is part of the APEX.
-						testFor := ctx.Provider(android.ApexTestForInfoProvider).(android.ApexTestForInfo)
-						for _, apexContents := range testFor.ApexContents {
-							if apexContents.DirectlyInApex(depName) {
+						if useStubs {
+							// Another exception: if this module is a test for an APEX, then
+							// it is linked with the non-stub variant of a module in the APEX
+							// as if this is part of the APEX.
+							testFor := ctx.Provider(android.ApexTestForInfoProvider).(android.ApexTestForInfo)
+							for _, apexContents := range testFor.ApexContents {
+								if apexContents.DirectlyInApex(depName) {
+									useStubs = false
+									break
+								}
+							}
+						}
+						if useStubs {
+							// Yet another exception: If this module and the dependency are
+							// available to the same APEXes then skip stubs between their
+							// platform variants. This complements the test_for case above,
+							// which avoids the stubs on a direct APEX library dependency, by
+							// avoiding stubs for indirect test dependencies as well.
+							//
+							// TODO(b/183882457): This doesn't work if the two libraries have
+							// only partially overlapping apex_available. For that test_for
+							// modules would need to be split into APEX variants and resolved
+							// separately for each APEX they have access to.
+							if android.AvailableToSameApexes(c, dep.(android.ApexModule)) {
 								useStubs = false
-								break
 							}
 						}
 					} else {
diff --git a/cc/sanitize.go b/cc/sanitize.go
index cd09e6e..e1ac9f0 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -1129,6 +1129,9 @@
 			Bool(c.sanitize.Properties.Sanitize.Undefined) ||
 			Bool(c.sanitize.Properties.Sanitize.All_undefined) {
 			runtimeLibrary = config.UndefinedBehaviorSanitizerRuntimeLibrary(toolchain)
+			if c.staticBinary() {
+				runtimeLibrary += ".static"
+			}
 		}
 
 		if runtimeLibrary != "" && (toolchain.Bionic() || c.sanitize.Properties.UbsanRuntimeDep) {
diff --git a/cmd/pom2bp/pom2bp.go b/cmd/pom2bp/pom2bp.go
index d341b8c..d9116b0 100644
--- a/cmd/pom2bp/pom2bp.go
+++ b/cmd/pom2bp/pom2bp.go
@@ -144,6 +144,7 @@
 var hostAndDeviceModuleNames = HostAndDeviceModuleNames{}
 
 var sdkVersion string
+var defaultMinSdkVersion string
 var useVersion string
 var staticDeps bool
 var jetifier bool
@@ -286,6 +287,10 @@
 	return sdkVersion
 }
 
+func (p Pom) DefaultMinSdkVersion() string {
+	return defaultMinSdkVersion
+}
+
 func (p Pom) Jetifier() bool {
 	return jetifier
 }
@@ -457,7 +462,7 @@
     min_sdk_version: "{{.MinSdkVersion}}",
     manifest: "manifests/{{.BpName}}/AndroidManifest.xml",
     {{- else if not .IsHostOnly}}
-    min_sdk_version: "24",
+    min_sdk_version: "{{.DefaultMinSdkVersion}}",
     {{- end}}
     {{- end}}
     static_libs: [
@@ -598,6 +603,8 @@
      This may be specified multiple times to declare these dependencies.
   -sdk-version <version>
      Sets sdk_version: "<version>" for all modules.
+  -default-min-sdk-version
+     The default min_sdk_version to use for a module if one cannot be mined from AndroidManifest.xml
   -use-version <version>
      If the maven directory contains multiple versions of artifacts and their pom files,
      -use-version can be used to only write Android.bp files for a specific version of those artifacts.
@@ -622,6 +629,7 @@
 	flag.Var(&hostModuleNames, "host", "Specifies that the corresponding module (specified in the form 'module.group:module.artifact') is a host module")
 	flag.Var(&hostAndDeviceModuleNames, "host-and-device", "Specifies that the corresponding module (specified in the form 'module.group:module.artifact') is both a host and device module.")
 	flag.StringVar(&sdkVersion, "sdk-version", "", "What to write to sdk_version")
+	flag.StringVar(&defaultMinSdkVersion, "default-min-sdk-version", "24", "Default min_sdk_version to use, if one is not available from AndroidManifest.xml. Default: 24")
 	flag.StringVar(&useVersion, "use-version", "", "Only read artifacts of a specific version")
 	flag.BoolVar(&staticDeps, "static-deps", false, "Statically include direct dependencies")
 	flag.BoolVar(&jetifier, "jetifier", false, "Sets jetifier: true on all modules")
diff --git a/java/app.go b/java/app.go
index 1b6e0e3..b849b98 100755
--- a/java/app.go
+++ b/java/app.go
@@ -1294,7 +1294,8 @@
 	// check is not necessary, and although it is good to have, it is difficult to maintain on
 	// non-linux build platforms where dexpreopt is generally disabled (the check may fail due to
 	// various unrelated reasons, such as a failure to get manifest from an APK).
-	if dexpreopt.GetGlobalConfig(ctx).DisablePreopt {
+	global := dexpreopt.GetGlobalConfig(ctx)
+	if global.DisablePreopt || global.OnlyPreoptBootImageAndSystemServer {
 		return inputFile
 	}
 
diff --git a/java/boot_image.go b/java/boot_image.go
index a14940d..8594792 100644
--- a/java/boot_image.go
+++ b/java/boot_image.go
@@ -20,6 +20,7 @@
 
 	"android/soong/android"
 	"android/soong/dexpreopt"
+	"github.com/google/blueprint/proptools"
 
 	"github.com/google/blueprint"
 )
@@ -27,24 +28,58 @@
 func init() {
 	RegisterBootImageBuildComponents(android.InitRegistrationContext)
 
+	// TODO(b/177892522): Remove after has been replaced by bootclasspath_fragments
 	android.RegisterSdkMemberType(&bootImageMemberType{
 		SdkMemberTypeBase: android.SdkMemberTypeBase{
 			PropertyName: "boot_images",
 			SupportsSdk:  true,
 		},
 	})
+
+	android.RegisterSdkMemberType(&bootImageMemberType{
+		SdkMemberTypeBase: android.SdkMemberTypeBase{
+			PropertyName: "bootclasspath_fragments",
+			SupportsSdk:  true,
+		},
+	})
 }
 
 func RegisterBootImageBuildComponents(ctx android.RegistrationContext) {
+	// TODO(b/177892522): Remove after has been replaced by bootclasspath_fragment
 	ctx.RegisterModuleType("boot_image", bootImageFactory)
 	ctx.RegisterModuleType("prebuilt_boot_image", prebuiltBootImageFactory)
+
+	ctx.RegisterModuleType("bootclasspath_fragment", bootImageFactory)
+	ctx.RegisterModuleType("prebuilt_bootclasspath_fragment", prebuiltBootImageFactory)
 }
 
+type bootImageContentDependencyTag struct {
+	blueprint.BaseDependencyTag
+}
+
+// Avoid having to make boot image content visible to the boot image.
+//
+// This is a temporary workaround to make it easier to migrate to boot image modules with proper
+// dependencies.
+// TODO(b/177892522): Remove this and add needed visibility.
+func (b bootImageContentDependencyTag) ExcludeFromVisibilityEnforcement() {
+}
+
+// The tag used for the dependency between the boot image module and its contents.
+var bootImageContentDepTag = bootImageContentDependencyTag{}
+
+var _ android.ExcludeFromVisibilityEnforcementTag = bootImageContentDepTag
+
 type bootImageProperties struct {
 	// The name of the image this represents.
 	//
 	// Must be one of "art" or "boot".
-	Image_name string
+	Image_name *string
+
+	// The contents of this boot image, could be either java_library, java_sdk_library, or boot_image.
+	//
+	// The order of this list matters as it is the order that is used in the bootclasspath.
+	Contents []string `blueprint:"mutated"`
 }
 
 type BootImageModule struct {
@@ -60,9 +95,52 @@
 	android.InitApexModule(m)
 	android.InitSdkAwareModule(m)
 	android.InitAndroidArchModule(m, android.HostAndDeviceSupported, android.MultilibCommon)
+
+	// Perform some consistency checking to ensure that the configuration is correct.
+	android.AddLoadHook(m, func(ctx android.LoadHookContext) {
+		bootImageConsistencyCheck(ctx, m)
+	})
 	return m
 }
 
+func bootImageConsistencyCheck(ctx android.EarlyModuleContext, m *BootImageModule) {
+	imageName := proptools.String(m.properties.Image_name)
+	if imageName == "art" {
+		// Get the configuration for the art apex jars. Do not use getImageConfig(ctx) here as this is
+		// too early in the Soong processing for that to work.
+		global := dexpreopt.GetGlobalConfig(ctx)
+		modules := global.ArtApexJars
+
+		// Make sure that the apex specified in the configuration is consistent and is one for which
+		// this boot image is available.
+		jars := []string{}
+		commonApex := ""
+		for i := 0; i < modules.Len(); i++ {
+			apex := modules.Apex(i)
+			jar := modules.Jar(i)
+			if apex == "platform" {
+				ctx.ModuleErrorf("ArtApexJars is invalid as it requests a platform variant of %q", jar)
+				continue
+			}
+			if !m.AvailableFor(apex) {
+				ctx.ModuleErrorf("incompatible with ArtApexJars which expects this to be in apex %q but this is only in apexes %q",
+					apex, m.ApexAvailable())
+				continue
+			}
+			if commonApex == "" {
+				commonApex = apex
+			} else if commonApex != apex {
+				ctx.ModuleErrorf("ArtApexJars configuration is inconsistent, expected all jars to be in the same apex but it specifies apex %q and %q",
+					commonApex, apex)
+			}
+			jars = append(jars, jar)
+		}
+
+		// Store the jars in the Contents property so that they can be used to add dependencies.
+		m.properties.Contents = jars
+	}
+}
+
 var BootImageInfoProvider = blueprint.NewProvider(BootImageInfo{})
 
 type BootImageInfo struct {
@@ -96,6 +174,10 @@
 
 func (b *BootImageModule) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
 	tag := ctx.OtherModuleDependencyTag(dep)
+	if tag == bootImageContentDepTag {
+		// Boot image contents are not automatically added to apex, yet.
+		return false
+	}
 	if android.IsMetaDependencyTag(tag) {
 		// Cross-cutting metadata dependencies are metadata.
 		return false
@@ -108,6 +190,8 @@
 }
 
 func (b *BootImageModule) DepsMutator(ctx android.BottomUpMutatorContext) {
+	ctx.AddDependency(ctx.Module(), bootImageContentDepTag, b.properties.Contents...)
+
 	if SkipDexpreoptBootJars(ctx) {
 		return
 	}
@@ -127,14 +211,8 @@
 	// GenerateSingletonBuildActions method as it cannot create it for itself.
 	dexpreopt.GetGlobalSoongConfig(ctx)
 
-	// Get a map of the image configs that are supported.
-	imageConfigs := genBootImageConfigs(ctx)
-
-	// Retrieve the config for this image.
-	imageName := b.properties.Image_name
-	imageConfig := imageConfigs[imageName]
+	imageConfig := b.getImageConfig(ctx)
 	if imageConfig == nil {
-		ctx.PropertyErrorf("image_name", "Unknown image name %q, expected one of %s", imageName, strings.Join(android.SortedStringKeys(imageConfigs), ", "))
 		return
 	}
 
@@ -145,6 +223,25 @@
 	ctx.SetProvider(BootImageInfoProvider, info)
 }
 
+func (b *BootImageModule) getImageConfig(ctx android.EarlyModuleContext) *bootImageConfig {
+	// Get a map of the image configs that are supported.
+	imageConfigs := genBootImageConfigs(ctx)
+
+	// Retrieve the config for this image.
+	imageNamePtr := b.properties.Image_name
+	if imageNamePtr == nil {
+		return nil
+	}
+
+	imageName := *imageNamePtr
+	imageConfig := imageConfigs[imageName]
+	if imageConfig == nil {
+		ctx.PropertyErrorf("image_name", "Unknown image name %q, expected one of %s", imageName, strings.Join(android.SortedStringKeys(imageConfigs), ", "))
+		return nil
+	}
+	return imageConfig
+}
+
 type bootImageMemberType struct {
 	android.SdkMemberTypeBase
 }
@@ -159,7 +256,11 @@
 }
 
 func (b *bootImageMemberType) AddPrebuiltModule(ctx android.SdkMemberContext, member android.SdkMember) android.BpModule {
-	return ctx.SnapshotBuilder().AddPrebuiltModule(member, "prebuilt_boot_image")
+	if b.PropertyName == "boot_images" {
+		return ctx.SnapshotBuilder().AddPrebuiltModule(member, "prebuilt_boot_image")
+	} else {
+		return ctx.SnapshotBuilder().AddPrebuiltModule(member, "prebuilt_bootclasspath_fragment")
+	}
 }
 
 func (b *bootImageMemberType) CreateVariantPropertiesStruct() android.SdkMemberProperties {
@@ -169,7 +270,7 @@
 type bootImageSdkMemberProperties struct {
 	android.SdkMemberPropertiesBase
 
-	Image_name string
+	Image_name *string
 }
 
 func (b *bootImageSdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.Module) {
@@ -179,8 +280,8 @@
 }
 
 func (b *bootImageSdkMemberProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) {
-	if b.Image_name != "" {
-		propertySet.AddProperty("image_name", b.Image_name)
+	if b.Image_name != nil {
+		propertySet.AddProperty("image_name", *b.Image_name)
 	}
 }
 
@@ -213,5 +314,10 @@
 	android.InitApexModule(m)
 	android.InitSdkAwareModule(m)
 	android.InitAndroidArchModule(m, android.HostAndDeviceSupported, android.MultilibCommon)
+
+	// Perform some consistency checking to ensure that the configuration is correct.
+	android.AddLoadHook(m, func(ctx android.LoadHookContext) {
+		bootImageConsistencyCheck(ctx, &m.BootImageModule)
+	})
 	return m
 }
diff --git a/java/boot_image_test.go b/java/boot_image_test.go
index 65e590d..a289df8 100644
--- a/java/boot_image_test.go
+++ b/java/boot_image_test.go
@@ -16,25 +16,88 @@
 
 import (
 	"testing"
+
+	"android/soong/android"
+	"android/soong/dexpreopt"
 )
 
 // Contains some simple tests for boot_image logic, additional tests can be found in
 // apex/boot_image_test.go as the ART boot image requires modules from the ART apex.
 
+var prepareForTestWithBootImage = android.GroupFixturePreparers(
+	PrepareForTestWithJavaDefaultModules,
+	dexpreopt.PrepareForTestByEnablingDexpreopt,
+)
+
 func TestUnknownBootImage(t *testing.T) {
-	testJavaError(t, "image_name: Unknown image name \\\"unknown\\\", expected one of art, boot", `
-		boot_image {
-			name: "unknown-boot-image",
-			image_name: "unknown",
-		}
-`)
+	prepareForTestWithBootImage.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+			`\Qimage_name: Unknown image name "unknown", expected one of art, boot\E`)).
+		RunTestWithBp(t, `
+			boot_image {
+				name: "unknown-boot-image",
+				image_name: "unknown",
+			}
+		`)
+}
+
+func TestUnknownBootclasspathFragmentImageName(t *testing.T) {
+	prepareForTestWithBootImage.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+			`\Qimage_name: Unknown image name "unknown", expected one of art, boot\E`)).
+		RunTestWithBp(t, `
+			bootclasspath_fragment {
+				name: "unknown-boot-image",
+				image_name: "unknown",
+			}
+		`)
 }
 
 func TestUnknownPrebuiltBootImage(t *testing.T) {
-	testJavaError(t, "image_name: Unknown image name \\\"unknown\\\", expected one of art, boot", `
-		prebuilt_boot_image {
-			name: "unknown-boot-image",
-			image_name: "unknown",
-		}
-`)
+	prepareForTestWithBootImage.
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+			`\Qimage_name: Unknown image name "unknown", expected one of art, boot\E`)).
+		RunTestWithBp(t, `
+			prebuilt_boot_image {
+				name: "unknown-boot-image",
+				image_name: "unknown",
+			}
+		`)
+}
+
+func TestBootImageInconsistentArtConfiguration_Platform(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForTestWithBootImage,
+		dexpreopt.FixtureSetArtBootJars("platform:foo", "apex:bar"),
+	).
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+			`\QArtApexJars is invalid as it requests a platform variant of "foo"\E`)).
+		RunTestWithBp(t, `
+			boot_image {
+				name: "boot-image",
+				image_name: "art",
+				apex_available: [
+					"apex",
+				],
+			}
+		`)
+}
+
+func TestBootImageInconsistentArtConfiguration_ApexMixture(t *testing.T) {
+	android.GroupFixturePreparers(
+		prepareForTestWithBootImage,
+		dexpreopt.FixtureSetArtBootJars("apex1:foo", "apex2:bar"),
+	).
+		ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
+			`\QArtApexJars configuration is inconsistent, expected all jars to be in the same apex but it specifies apex "apex1" and "apex2"\E`)).
+		RunTestWithBp(t, `
+			boot_image {
+				name: "boot-image",
+				image_name: "art",
+				apex_available: [
+					"apex1",
+					"apex2",
+				],
+			}
+		`)
 }
diff --git a/linkerconfig/linkerconfig.go b/linkerconfig/linkerconfig.go
index ff548e5..da80a47 100644
--- a/linkerconfig/linkerconfig.go
+++ b/linkerconfig/linkerconfig.go
@@ -27,7 +27,11 @@
 
 func init() {
 	pctx.HostBinToolVariable("conv_linker_config", "conv_linker_config")
-	android.RegisterModuleType("linker_config", linkerConfigFactory)
+	registerLinkerConfigBuildComponent(android.InitRegistrationContext)
+}
+
+func registerLinkerConfigBuildComponent(ctx android.RegistrationContext) {
+	ctx.RegisterModuleType("linker_config", linkerConfigFactory)
 }
 
 type linkerConfigProperties struct {
diff --git a/linkerconfig/linkerconfig_test.go b/linkerconfig/linkerconfig_test.go
index 8eed4b5..939e4bb 100644
--- a/linkerconfig/linkerconfig_test.go
+++ b/linkerconfig/linkerconfig_test.go
@@ -15,65 +15,29 @@
 package linkerconfig
 
 import (
-	"android/soong/android"
-	"io/ioutil"
 	"os"
 	"reflect"
 	"testing"
+
+	"android/soong/android"
 )
 
-var buildDir string
-
-func setUp() {
-	var err error
-	buildDir, err = ioutil.TempDir("", "soong_etc_test")
-	if err != nil {
-		panic(err)
-	}
-}
-
-func tearDown() {
-	os.RemoveAll(buildDir)
-}
-
 func TestMain(m *testing.M) {
-	run := func() int {
-		setUp()
-		defer tearDown()
-
-		return m.Run()
-	}
-
-	os.Exit(run())
+	os.Exit(m.Run())
 }
 
-func testContext(t *testing.T, bp string) *android.TestContext {
-	t.Helper()
-
-	fs := map[string][]byte{
-		"linker.config.json": nil,
-	}
-
-	config := android.TestArchConfig(buildDir, nil, bp, fs)
-
-	ctx := android.NewTestArchContext(config)
-	ctx.RegisterModuleType("linker_config", linkerConfigFactory)
-	ctx.Register()
-
-	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
-	android.FailIfErrored(t, errs)
-	_, errs = ctx.PrepareBuildActions(config)
-	android.FailIfErrored(t, errs)
-
-	return ctx
-}
+var prepareForLinkerConfigTest = android.GroupFixturePreparers(
+	android.PrepareForTestWithAndroidBuildComponents,
+	android.FixtureRegisterWithContext(registerLinkerConfigBuildComponent),
+	android.FixtureAddFile("linker.config.json", nil),
+)
 
 func TestBaseLinkerConfig(t *testing.T) {
-	ctx := testContext(t, `
-	linker_config {
-		name: "linker-config-base",
-		src: "linker.config.json",
-	}
+	result := prepareForLinkerConfigTest.RunTestWithBp(t, `
+		linker_config {
+			name: "linker-config-base",
+			src: "linker.config.json",
+		}
 	`)
 
 	expected := map[string][]string{
@@ -82,13 +46,13 @@
 		"LOCAL_INSTALLED_MODULE_STEM": {"linker.config.pb"},
 	}
 
-	p := ctx.ModuleForTests("linker-config-base", "android_arm64_armv8-a").Module().(*linkerConfig)
+	p := result.ModuleForTests("linker-config-base", "android_arm64_armv8-a").Module().(*linkerConfig)
 
 	if p.outputFilePath.Base() != "linker.config.pb" {
 		t.Errorf("expected linker.config.pb, got %q", p.outputFilePath.Base())
 	}
 
-	entries := android.AndroidMkEntriesForTest(t, ctx, p)[0]
+	entries := android.AndroidMkEntriesForTest(t, result.TestContext, p)[0]
 	for k, expectedValue := range expected {
 		if value, ok := entries.EntryMap[k]; ok {
 			if !reflect.DeepEqual(value, expectedValue) {
@@ -105,18 +69,18 @@
 }
 
 func TestUninstallableLinkerConfig(t *testing.T) {
-	ctx := testContext(t, `
-	linker_config {
-		name: "linker-config-base",
-		src: "linker.config.json",
-		installable: false,
-	}
+	result := prepareForLinkerConfigTest.RunTestWithBp(t, `
+		linker_config {
+			name: "linker-config-base",
+			src: "linker.config.json",
+			installable: false,
+		}
 	`)
 
 	expected := []string{"true"}
 
-	p := ctx.ModuleForTests("linker-config-base", "android_arm64_armv8-a").Module().(*linkerConfig)
-	entries := android.AndroidMkEntriesForTest(t, ctx, p)[0]
+	p := result.ModuleForTests("linker-config-base", "android_arm64_armv8-a").Module().(*linkerConfig)
+	entries := android.AndroidMkEntriesForTest(t, result.TestContext, p)[0]
 	if value, ok := entries.EntryMap["LOCAL_UNINSTALLABLE_MODULE"]; ok {
 		if !reflect.DeepEqual(value, expected) {
 			t.Errorf("LOCAL_UNINSTALLABLE_MODULE is expected to be true but %s", value)