Merge "Dedup apex/Prebuilt and apex/ApexSet" into sc-dev
diff --git a/android/config.go b/android/config.go
index 6dc8efe..24fc522 100644
--- a/android/config.go
+++ b/android/config.go
@@ -1661,6 +1661,16 @@
 	return paths
 }
 
+// BuildPathsByModule returns a map from module name to build paths based on the given directory
+// prefix.
+func (l *ConfiguredJarList) BuildPathsByModule(ctx PathContext, dir OutputPath) map[string]WritablePath {
+	paths := map[string]WritablePath{}
+	for _, jar := range l.jars {
+		paths[jar] = dir.Join(ctx, ModuleStem(jar)+".jar")
+	}
+	return paths
+}
+
 // UnmarshalJSON converts JSON configuration from raw bytes into a
 // ConfiguredJarList structure.
 func (l *ConfiguredJarList) UnmarshalJSON(b []byte) error {
diff --git a/android/testing.go b/android/testing.go
index 191cb8d..b36f62c 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -713,9 +713,11 @@
 
 func (b baseTestingComponent) maybeBuildParamsFromRule(rule string) (TestingBuildParams, []string) {
 	var searchedRules []string
-	for _, p := range b.provider.BuildParamsForTests() {
-		searchedRules = append(searchedRules, p.Rule.String())
-		if strings.Contains(p.Rule.String(), rule) {
+	buildParams := b.provider.BuildParamsForTests()
+	for _, p := range buildParams {
+		ruleAsString := p.Rule.String()
+		searchedRules = append(searchedRules, ruleAsString)
+		if strings.Contains(ruleAsString, rule) {
 			return b.newTestingBuildParams(p), searchedRules
 		}
 	}
@@ -725,7 +727,7 @@
 func (b baseTestingComponent) buildParamsFromRule(rule string) TestingBuildParams {
 	p, searchRules := b.maybeBuildParamsFromRule(rule)
 	if p.Rule == nil {
-		panic(fmt.Errorf("couldn't find rule %q.\nall rules: %v", rule, searchRules))
+		panic(fmt.Errorf("couldn't find rule %q.\nall rules:\n%s", rule, strings.Join(searchRules, "\n")))
 	}
 	return p
 }
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 8a71d4f..92e098c 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -4546,7 +4546,7 @@
 		// prebuilt_apex module always depends on the prebuilt, and so it doesn't
 		// find the dex boot jar in it. We either need to disable the source libfoo
 		// or make the prebuilt libfoo preferred.
-		testDexpreoptWithApexes(t, bp, "failed to find a dex jar path for module 'libfoo'", preparer)
+		testDexpreoptWithApexes(t, bp, "module libfoo does not provide a dex boot jar", preparer)
 	})
 
 	t.Run("prebuilt library preferred with source", func(t *testing.T) {
diff --git a/apex/platform_bootclasspath_test.go b/apex/platform_bootclasspath_test.go
index ce12f46..7297926 100644
--- a/apex/platform_bootclasspath_test.go
+++ b/apex/platform_bootclasspath_test.go
@@ -15,6 +15,8 @@
 package apex
 
 import (
+	"fmt"
+	"strings"
 	"testing"
 
 	"android/soong/android"
@@ -31,6 +33,139 @@
 	PrepareForTestWithApexBuildComponents,
 )
 
+func TestPlatformBootclasspath_Fragments(t *testing.T) {
+	result := android.GroupFixturePreparers(
+		prepareForTestWithPlatformBootclasspath,
+		prepareForTestWithMyapex,
+		java.PrepareForTestWithJavaSdkLibraryFiles,
+		java.FixtureWithLastReleaseApis("foo"),
+		java.FixtureConfigureBootJars("myapex:bar"),
+		android.FixtureWithRootAndroidBp(`
+			platform_bootclasspath {
+				name: "platform-bootclasspath",
+				fragments: [
+					{
+						apex: "myapex",
+						module:"bar-fragment",
+					},
+				],
+				hidden_api: {
+					unsupported: [
+							"unsupported.txt",
+					],
+					removed: [
+							"removed.txt",
+					],
+					max_target_r_low_priority: [
+							"max-target-r-low-priority.txt",
+					],
+					max_target_q: [
+							"max-target-q.txt",
+					],
+					max_target_p: [
+							"max-target-p.txt",
+					],
+					max_target_o_low_priority: [
+							"max-target-o-low-priority.txt",
+					],
+					blocked: [
+							"blocked.txt",
+					],
+					unsupported_packages: [
+							"unsupported-packages.txt",
+					],
+				},
+			}
+
+			apex {
+				name: "myapex",
+				key: "myapex.key",
+				bootclasspath_fragments: [
+					"bar-fragment",
+				],
+				updatable: false,
+			}
+
+			apex_key {
+				name: "myapex.key",
+				public_key: "testkey.avbpubkey",
+				private_key: "testkey.pem",
+			}
+
+			bootclasspath_fragment {
+				name: "bar-fragment",
+				contents: ["bar"],
+				apex_available: ["myapex"],
+				api: {
+					stub_libs: ["foo"],
+				},
+				hidden_api: {
+					unsupported: [
+							"bar-unsupported.txt",
+					],
+					removed: [
+							"bar-removed.txt",
+					],
+					max_target_r_low_priority: [
+							"bar-max-target-r-low-priority.txt",
+					],
+					max_target_q: [
+							"bar-max-target-q.txt",
+					],
+					max_target_p: [
+							"bar-max-target-p.txt",
+					],
+					max_target_o_low_priority: [
+							"bar-max-target-o-low-priority.txt",
+					],
+					blocked: [
+							"bar-blocked.txt",
+					],
+					unsupported_packages: [
+							"bar-unsupported-packages.txt",
+					],
+				},
+			}
+
+			java_library {
+				name: "bar",
+				apex_available: ["myapex"],
+				srcs: ["a.java"],
+				system_modules: "none",
+				sdk_version: "none",
+				compile_dex: true,
+				permitted_packages: ["bar"],
+			}
+
+			java_sdk_library {
+				name: "foo",
+				srcs: ["a.java"],
+				public: {
+					enabled: true,
+				},
+				compile_dex: true,
+			}
+		`),
+	).RunTest(t)
+
+	pbcp := result.Module("platform-bootclasspath", "android_common")
+	info := result.ModuleProvider(pbcp, java.MonolithicHiddenAPIInfoProvider).(java.MonolithicHiddenAPIInfo)
+
+	for _, category := range java.HiddenAPIFlagFileCategories {
+		name := category.PropertyName
+		message := fmt.Sprintf("category %s", name)
+		filename := strings.ReplaceAll(name, "_", "-")
+		expected := []string{fmt.Sprintf("%s.txt", filename), fmt.Sprintf("bar-%s.txt", filename)}
+		android.AssertPathsRelativeToTopEquals(t, message, expected, info.FlagsFilesByCategory[category])
+	}
+
+	android.AssertPathsRelativeToTopEquals(t, "stub flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/stub-flags.csv"}, info.StubFlagsPaths)
+	android.AssertPathsRelativeToTopEquals(t, "annotation flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/annotation-flags.csv"}, info.AnnotationFlagsPaths)
+	android.AssertPathsRelativeToTopEquals(t, "metadata flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/metadata.csv"}, info.MetadataPaths)
+	android.AssertPathsRelativeToTopEquals(t, "index flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/index.csv"}, info.IndexPaths)
+	android.AssertPathsRelativeToTopEquals(t, "all flags", []string{"out/soong/.intermediates/bar-fragment/android_common_apex10000/modular-hiddenapi/all-flags.csv"}, info.AllFlagsPaths)
+}
+
 func TestPlatformBootclasspathDependencies(t *testing.T) {
 	result := android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
diff --git a/java/bootclasspath_fragment.go b/java/bootclasspath_fragment.go
index 1c7ad78..10d2fe2 100644
--- a/java/bootclasspath_fragment.go
+++ b/java/bootclasspath_fragment.go
@@ -648,7 +648,8 @@
 	}
 
 	// Copy the dex jars of this fragment's content modules to their predefined locations.
-	copyBootJarsToPredefinedLocations(ctx, contents, imageConfig.modules, imageConfig.dexPaths)
+	bootDexJarByModule := extractEncodedDexJarsFromModules(ctx, contents)
+	copyBootJarsToPredefinedLocations(ctx, bootDexJarByModule, imageConfig.dexPathsByModule)
 
 	// Build a profile for the image config and then use that to build the boot image.
 	profile := bootImageProfileRule(ctx, imageConfig)
@@ -763,7 +764,7 @@
 
 	// Copy manually curated flag files specified on the bootclasspath_fragment.
 	if b.Flag_files_by_category != nil {
-		for _, category := range hiddenAPIFlagFileCategories {
+		for _, category := range HiddenAPIFlagFileCategories {
 			paths := b.Flag_files_by_category[category]
 			if len(paths) > 0 {
 				dests := []string{}
@@ -772,7 +773,7 @@
 					builder.CopyToSnapshot(p, dest)
 					dests = append(dests, dest)
 				}
-				hiddenAPISet.AddProperty(category.propertyName, dests)
+				hiddenAPISet.AddProperty(category.PropertyName, dests)
 			}
 		}
 	}
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index e1a3650..dc8df5e 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -15,7 +15,6 @@
 package java
 
 import (
-	"fmt"
 	"path/filepath"
 	"sort"
 	"strings"
@@ -254,6 +253,9 @@
 	dexPaths     android.WritablePaths // for this image
 	dexPathsDeps android.WritablePaths // for the dependency images and in this image
 
+	// Map from module name (without prebuilt_ prefix) to the predefined build path.
+	dexPathsByModule map[string]android.WritablePath
+
 	// File path to a zip archive with all image files (or nil, if not needed).
 	zip android.WritablePath
 
@@ -276,13 +278,24 @@
 	dexLocationsDeps []string // for the dependency images and in this image
 
 	// Paths to image files.
-	imagePathOnHost   android.OutputPath  // first image file path on host
-	imagePathOnDevice string              // first image file path on device
-	imagesDeps        android.OutputPaths // all files
+	imagePathOnHost   android.OutputPath // first image file path on host
+	imagePathOnDevice string             // first image file path on device
 
-	// Only for extensions, paths to the primary boot images.
+	// All the files that constitute this image variant, i.e. .art, .oat and .vdex files.
+	imagesDeps android.OutputPaths
+
+	// The path to the primary image variant's imagePathOnHost field, where primary image variant
+	// means the image variant that this extends.
+	//
+	// This is only set for a variant of an image that extends another image.
 	primaryImages android.OutputPath
 
+	// The paths to the primary image variant's imagesDeps field, where primary image variant
+	// means the image variant that this extends.
+	//
+	// This is only set for a variant of an image that extends another image.
+	primaryImagesDeps android.Paths
+
 	// Rules which should be used in make to install the outputs.
 	installs           android.RuleBuilderInstalls
 	vdexInstalls       android.RuleBuilderInstalls
@@ -450,53 +463,27 @@
 	return true
 }
 
-// copyBootJarsToPredefinedLocations generates commands that will copy boot jars to
-// predefined paths in the global config.
-func copyBootJarsToPredefinedLocations(ctx android.ModuleContext, bootModules []android.Module, bootjars android.ConfiguredJarList, jarPathsPredefined android.WritablePaths) {
-	jarPaths := make(android.Paths, bootjars.Len())
-	for i, module := range bootModules {
-		if module != nil {
-			bootDexJar := module.(interface{ DexJarBuildPath() android.Path }).DexJarBuildPath()
-			jarPaths[i] = bootDexJar
+// copyBootJarsToPredefinedLocations generates commands that will copy boot jars to predefined
+// paths in the global config.
+func copyBootJarsToPredefinedLocations(ctx android.ModuleContext, srcBootDexJarsByModule bootDexJarByModule, dstBootJarsByModule map[string]android.WritablePath) {
+	// Create the super set of module names.
+	names := []string{}
+	names = append(names, android.SortedStringKeys(srcBootDexJarsByModule)...)
+	names = append(names, android.SortedStringKeys(dstBootJarsByModule)...)
+	names = android.SortedUniqueStrings(names)
+	for _, name := range names {
+		src := srcBootDexJarsByModule[name]
+		dst := dstBootJarsByModule[name]
 
-			name := android.RemoveOptionalPrebuiltPrefix(ctx.OtherModuleName(module))
-			if bootjars.Jar(i) != name {
-				ctx.ModuleErrorf("expected module %s at position %d but found %s", bootjars.Jar(i), i, name)
-			}
-		}
-	}
-
-	// The paths to bootclasspath DEX files need to be known at module GenerateAndroidBuildAction
-	// time, before the boot images are built (these paths are used in dexpreopt rule generation for
-	// Java libraries and apps). Generate rules that copy bootclasspath DEX jars to the predefined
-	// paths.
-	for i := range jarPaths {
-		input := jarPaths[i]
-		output := jarPathsPredefined[i]
-		module := bootjars.Jar(i)
-		if input == nil {
-			if ctx.Config().AllowMissingDependencies() {
-				apex := bootjars.Apex(i)
-
-				// Create an error rule that pretends to create the output file but will actually fail if it
-				// is run.
-				ctx.Build(pctx, android.BuildParams{
-					Rule:   android.ErrorRule,
-					Output: output,
-					Args: map[string]string{
-						"error": fmt.Sprintf("missing dependencies: dex jar for %s:%s", module, apex),
-					},
-				})
-			} else {
-				ctx.ModuleErrorf("failed to find a dex jar path for module '%s'"+
-					", note that some jars may be filtered out by module constraints", module)
-			}
-
+		if src == nil {
+			ctx.ModuleErrorf("module %s does not provide a dex boot jar", name)
+		} else if dst == nil {
+			ctx.ModuleErrorf("module %s is not part of the boot configuration", name)
 		} else {
 			ctx.Build(pctx, android.BuildParams{
 				Rule:   android.Cp,
-				Input:  input,
-				Output: output,
+				Input:  src,
+				Output: dst,
 			})
 		}
 	}
@@ -588,7 +575,15 @@
 		cmd.
 			Flag("--runtime-arg").FlagWithInputList("-Xbootclasspath:", image.dexPathsDeps.Paths(), ":").
 			Flag("--runtime-arg").FlagWithList("-Xbootclasspath-locations:", image.dexLocationsDeps, ":").
-			FlagWithArg("--boot-image=", dexpreopt.PathToLocation(artImage, arch)).Implicit(artImage)
+			// Add the path to the first file in the boot image with the arch specific directory removed,
+			// dex2oat will reconstruct the path to the actual file when it needs it. As the actual path
+			// to the file cannot be passed to the command make sure to add the actual path as an Implicit
+			// dependency to ensure that it is built before the command runs.
+			FlagWithArg("--boot-image=", dexpreopt.PathToLocation(artImage, arch)).Implicit(artImage).
+			// Similarly, the dex2oat tool will automatically find the paths to other files in the base
+			// boot image so make sure to add them as implicit dependencies to ensure that they are built
+			// before this command is run.
+			Implicits(image.primaryImagesDeps)
 	} else {
 		// It is a primary image, so it needs a base address.
 		cmd.FlagWithArg("--base=", ctx.Config().LibartImgDeviceBaseAddress())
diff --git a/java/dexpreopt_config.go b/java/dexpreopt_config.go
index 39a3e11..b13955f 100644
--- a/java/dexpreopt_config.go
+++ b/java/dexpreopt_config.go
@@ -100,6 +100,7 @@
 			// TODO(b/143682396): use module dependencies instead
 			inputDir := deviceDir.Join(ctx, "dex_"+c.name+"jars_input")
 			c.dexPaths = c.modules.BuildPaths(ctx, inputDir)
+			c.dexPathsByModule = c.modules.BuildPathsByModule(ctx, inputDir)
 			c.dexPathsDeps = c.dexPaths
 
 			// Create target-specific variants.
@@ -125,6 +126,7 @@
 		frameworkCfg.dexPathsDeps = append(artCfg.dexPathsDeps, frameworkCfg.dexPathsDeps...)
 		for i := range targets {
 			frameworkCfg.variants[i].primaryImages = artCfg.variants[i].imagePathOnHost
+			frameworkCfg.variants[i].primaryImagesDeps = artCfg.variants[i].imagesDeps.Paths()
 			frameworkCfg.variants[i].dexLocationsDeps = append(artCfg.variants[i].dexLocations, frameworkCfg.variants[i].dexLocationsDeps...)
 		}
 
@@ -152,6 +154,9 @@
 	// later on a singleton adds commands to copy actual jars to the predefined paths.
 	dexPaths android.WritablePaths
 
+	// Map from module name (without prebuilt_ prefix) to the predefined build path.
+	dexPathsByModule map[string]android.WritablePath
+
 	// A list of dex locations (a.k.a. on-device paths) to the boot jars.
 	dexLocations []string
 }
@@ -165,10 +170,11 @@
 
 		dir := android.PathForOutput(ctx, ctx.Config().DeviceName(), "updatable_bootjars")
 		dexPaths := updatableBootJars.BuildPaths(ctx, dir)
+		dexPathsByModuleName := updatableBootJars.BuildPathsByModule(ctx, dir)
 
 		dexLocations := updatableBootJars.DevicePaths(ctx.Config(), android.Android)
 
-		return updatableBootConfig{updatableBootJars, dexPaths, dexLocations}
+		return updatableBootConfig{updatableBootJars, dexPaths, dexPathsByModuleName, dexLocations}
 	}).(updatableBootConfig)
 }
 
diff --git a/java/hiddenapi.go b/java/hiddenapi.go
index e9693c6..f901434 100644
--- a/java/hiddenapi.go
+++ b/java/hiddenapi.go
@@ -219,7 +219,6 @@
 		BuiltTool("merge_csv").
 		Flag("--zip_input").
 		Flag("--key_field signature").
-		FlagWithArg("--header=", "signature,file,startline,startcol,endline,endcol,properties").
 		FlagWithOutput("--output=", indexCSV).
 		Inputs(classesJars)
 	rule.Build(desc, desc)
diff --git a/java/hiddenapi_modular.go b/java/hiddenapi_modular.go
index f2649d3..5600645 100644
--- a/java/hiddenapi_modular.go
+++ b/java/hiddenapi_modular.go
@@ -248,8 +248,8 @@
 }
 
 type hiddenAPIFlagFileCategory struct {
-	// propertyName is the name of the property for this category.
-	propertyName string
+	// PropertyName is the name of the property for this category.
+	PropertyName string
 
 	// propertyValueReader retrieves the value of the property for this category from the set of
 	// properties.
@@ -262,12 +262,12 @@
 
 // The flag file category for removed members of the API.
 //
-// This is extracted from hiddenAPIFlagFileCategories as it is needed to add the dex signatures
+// This is extracted from HiddenAPIFlagFileCategories as it is needed to add the dex signatures
 // list of removed API members that are generated automatically from the removed.txt files provided
 // by API stubs.
 var hiddenAPIRemovedFlagFileCategory = &hiddenAPIFlagFileCategory{
 	// See HiddenAPIFlagFileProperties.Removed
-	propertyName: "removed",
+	PropertyName: "removed",
 	propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
 		return properties.Removed
 	},
@@ -276,10 +276,10 @@
 	},
 }
 
-var hiddenAPIFlagFileCategories = []*hiddenAPIFlagFileCategory{
+var HiddenAPIFlagFileCategories = []*hiddenAPIFlagFileCategory{
 	// See HiddenAPIFlagFileProperties.Unsupported
 	{
-		propertyName: "unsupported",
+		PropertyName: "unsupported",
 		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
 			return properties.Unsupported
 		},
@@ -290,7 +290,7 @@
 	hiddenAPIRemovedFlagFileCategory,
 	// See HiddenAPIFlagFileProperties.Max_target_r_low_priority
 	{
-		propertyName: "max_target_r_low_priority",
+		PropertyName: "max_target_r_low_priority",
 		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
 			return properties.Max_target_r_low_priority
 		},
@@ -300,7 +300,7 @@
 	},
 	// See HiddenAPIFlagFileProperties.Max_target_q
 	{
-		propertyName: "max_target_q",
+		PropertyName: "max_target_q",
 		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
 			return properties.Max_target_q
 		},
@@ -310,7 +310,7 @@
 	},
 	// See HiddenAPIFlagFileProperties.Max_target_p
 	{
-		propertyName: "max_target_p",
+		PropertyName: "max_target_p",
 		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
 			return properties.Max_target_p
 		},
@@ -320,7 +320,7 @@
 	},
 	// See HiddenAPIFlagFileProperties.Max_target_o_low_priority
 	{
-		propertyName: "max_target_o_low_priority",
+		PropertyName: "max_target_o_low_priority",
 		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
 			return properties.Max_target_o_low_priority
 		},
@@ -330,7 +330,7 @@
 	},
 	// See HiddenAPIFlagFileProperties.Blocked
 	{
-		propertyName: "blocked",
+		PropertyName: "blocked",
 		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
 			return properties.Blocked
 		},
@@ -340,7 +340,7 @@
 	},
 	// See HiddenAPIFlagFileProperties.Unsupported_packages
 	{
-		propertyName: "unsupported_packages",
+		PropertyName: "unsupported_packages",
 		propertyValueReader: func(properties *HiddenAPIFlagFileProperties) []string {
 			return properties.Unsupported_packages
 		},
@@ -355,7 +355,7 @@
 
 // append appends the supplied flags files to the corresponding category in this map.
 func (s FlagFilesByCategory) append(other FlagFilesByCategory) {
-	for _, category := range hiddenAPIFlagFileCategories {
+	for _, category := range HiddenAPIFlagFileCategories {
 		s[category] = append(s[category], other[category]...)
 	}
 }
@@ -540,7 +540,7 @@
 // extractFlagFilesFromProperties extracts the paths to flag files that are specified in the
 // supplied properties and stores them in this struct.
 func (i *HiddenAPIFlagInput) extractFlagFilesFromProperties(ctx android.ModuleContext, p *HiddenAPIFlagFileProperties) {
-	for _, category := range hiddenAPIFlagFileCategories {
+	for _, category := range HiddenAPIFlagFileCategories {
 		paths := android.PathsForModuleSrc(ctx, category.propertyValueReader(p))
 		i.FlagFilesByCategory[category] = paths
 	}
@@ -571,6 +571,15 @@
 	AllFlagsPath android.Path
 }
 
+// bootDexJarByModule is a map from base module name (without prebuilt_ prefix) to the boot dex
+// path.
+type bootDexJarByModule map[string]android.Path
+
+// addPath adds the path for a module to the map.
+func (b bootDexJarByModule) addPath(module android.Module, path android.Path) {
+	b[android.RemoveOptionalPrebuiltPrefix(module.Name())] = path
+}
+
 // pathForValidation creates a path of the same type as the supplied type but with a name of
 // <path>.valid.
 //
@@ -630,7 +639,7 @@
 		FlagWithOutput("--output ", tempPath)
 
 	// Add the options for the different categories of flag files.
-	for _, category := range hiddenAPIFlagFileCategories {
+	for _, category := range HiddenAPIFlagFileCategories {
 		paths := flagFilesByCategory[category]
 		for _, path := range paths {
 			category.commandMutator(command, path)
@@ -759,7 +768,7 @@
 		bootDexJar := module.bootDexJar()
 		if bootDexJar == nil {
 			if ctx.Config().AlwaysUsePrebuiltSdks() {
-				// TODO(b/179354495): Remove this work around when it is unnecessary.
+				// TODO(b/179354495): Remove this workaround when it is unnecessary.
 				// Prebuilt modules like framework-wifi do not yet provide dex implementation jars. So,
 				// create a fake one that will cause a build error only if it is used.
 				fake := android.PathForModuleOut(ctx, "fake/boot-dex/%s.jar", module.Name())
@@ -792,3 +801,113 @@
 	}
 	return classesJars
 }
+
+// deferReportingMissingBootDexJar returns true if a missing boot dex jar should not be reported by
+// Soong but should instead only be reported in ninja if the file is actually built.
+func deferReportingMissingBootDexJar(ctx android.ModuleContext, module android.Module) bool {
+	// TODO(b/179354495): Remove this workaround when it is unnecessary.
+	// Prebuilt modules like framework-wifi do not yet provide dex implementation jars. So,
+	// create a fake one that will cause a build error only if it is used.
+	if ctx.Config().AlwaysUsePrebuiltSdks() {
+		return true
+	}
+
+	// This is called for both platform_bootclasspath and bootclasspath_fragment modules.
+	//
+	// A bootclasspath_fragment module should only use the APEX variant of source or prebuilt modules.
+	// Ideally, a bootclasspath_fragment module should never have a platform variant created for it
+	// but unfortunately, due to b/187910671 it does.
+	//
+	// That causes issues when obtaining a boot dex jar for a prebuilt module as a prebuilt module
+	// used by a bootclasspath_fragment can only provide a boot dex jar when it is part of APEX, i.e.
+	// has an APEX variant not a platform variant.
+	//
+	// There are some other situations when a prebuilt module used by a bootclasspath_fragment cannot
+	// provide a boot dex jar:
+	// 1. If the bootclasspath_fragment is not exported by the prebuilt_apex/apex_set module then it
+	//    does not have an APEX variant and only has a platform variant and neither do its content
+	//    modules.
+	// 2. Some build configurations, e.g. setting TARGET_BUILD_USE_PREBUILT_SDKS causes all
+	//    java_sdk_library_import modules to be treated as preferred and as many of them are not part
+	//    of an apex they cannot provide a boot dex jar.
+	//
+	// The first case causes problems when the affected prebuilt modules are preferred but that is an
+	// invalid configuration and it is ok for it to fail as the work to enable that is not yet
+	// complete. The second case is used for building targets that do not use boot dex jars and so
+	// deferring error reporting to ninja is fine as the affected ninja targets should never be built.
+	// That is handled above.
+	//
+	// A platform_bootclasspath module can use libraries from both platform and APEX variants. Unlike
+	// the bootclasspath_fragment it supports dex_import modules which provides the dex file. So, it
+	// can obtain a boot dex jar from a prebuilt that is not part of an APEX. However, it is assumed
+	// that if the library can be part of an APEX then it is the APEX variant that is used.
+	//
+	// This check handles the slightly different requirements of the bootclasspath_fragment and
+	// platform_bootclasspath modules by only deferring error reporting for the platform variant of
+	// a prebuilt modules that has other variants which are part of an APEX.
+	//
+	// TODO(b/187910671): Remove this once platform variants are no longer created unnecessarily.
+	if android.IsModulePrebuilt(module) {
+		if am, ok := module.(android.ApexModule); ok && am.InAnyApex() {
+			apexInfo := ctx.OtherModuleProvider(module, android.ApexInfoProvider).(android.ApexInfo)
+			if apexInfo.IsForPlatform() {
+				return true
+			}
+		}
+	}
+
+	// A bootclasspath module that is part of a versioned sdk never provides a boot dex jar as there
+	// is no equivalently versioned prebuilt APEX file from which it can be obtained. However,
+	// versioned bootclasspath modules are processed by Soong so in order to avoid them causing build
+	// failures missing boot dex jars need to be deferred.
+	if android.IsModuleInVersionedSdk(ctx.Module()) {
+		return true
+	}
+
+	return false
+}
+
+// handleMissingDexBootFile will either log a warning or create an error rule to create the fake
+// file depending on the value returned from deferReportingMissingBootDexJar.
+func handleMissingDexBootFile(ctx android.ModuleContext, module android.Module, fake android.WritablePath) {
+	if deferReportingMissingBootDexJar(ctx, module) {
+		// Create an error rule that pretends to create the output file but will actually fail if it
+		// is run.
+		ctx.Build(pctx, android.BuildParams{
+			Rule:   android.ErrorRule,
+			Output: fake,
+			Args: map[string]string{
+				"error": fmt.Sprintf("missing dependencies: boot dex jar for %s", module),
+			},
+		})
+	} else {
+		ctx.ModuleErrorf("module %s does not provide a dex jar", module)
+	}
+}
+
+// retrieveEncodedBootDexJarFromModule returns a path to the boot dex jar from the supplied module's
+// DexJarBuildPath() method.
+//
+// The returned path will usually be to a dex jar file that has been encoded with hidden API flags.
+// However, under certain conditions, e.g. errors, or special build configurations it will return
+// a path to a fake file.
+func retrieveEncodedBootDexJarFromModule(ctx android.ModuleContext, module android.Module) android.Path {
+	bootDexJar := module.(interface{ DexJarBuildPath() android.Path }).DexJarBuildPath()
+	if bootDexJar == nil {
+		fake := android.PathForModuleOut(ctx, fmt.Sprintf("fake/encoded-dex/%s.jar", module.Name()))
+		bootDexJar = fake
+
+		handleMissingDexBootFile(ctx, module, fake)
+	}
+	return bootDexJar
+}
+
+// extractEncodedDexJarsFromModules extracts the encoded dex jars from the supplied modules.
+func extractEncodedDexJarsFromModules(ctx android.ModuleContext, contents []android.Module) bootDexJarByModule {
+	encodedDexJarsByModuleName := bootDexJarByModule{}
+	for _, module := range contents {
+		path := retrieveEncodedBootDexJarFromModule(ctx, module)
+		encodedDexJarsByModuleName.addPath(module, path)
+	}
+	return encodedDexJarsByModuleName
+}
diff --git a/java/hiddenapi_monolithic.go b/java/hiddenapi_monolithic.go
index a6bf8c7..edf4235 100644
--- a/java/hiddenapi_monolithic.go
+++ b/java/hiddenapi_monolithic.go
@@ -99,4 +99,4 @@
 	i.AllFlagsPaths = android.FirstUniquePaths(i.AllFlagsPaths)
 }
 
-var monolithicHiddenAPIInfoProvider = blueprint.NewProvider(MonolithicHiddenAPIInfo{})
+var MonolithicHiddenAPIInfoProvider = blueprint.NewProvider(MonolithicHiddenAPIInfo{})
diff --git a/java/platform_bootclasspath.go b/java/platform_bootclasspath.go
index 87c695c..a4beb89 100644
--- a/java/platform_bootclasspath.go
+++ b/java/platform_bootclasspath.go
@@ -280,7 +280,6 @@
 	}
 
 	monolithicInfo := b.createAndProvideMonolithicHiddenAPIInfo(ctx, fragments)
-
 	// Create the input to pass to ruleToGenerateHiddenAPIStubFlagsFile
 	input := newHiddenAPIFlagInput()
 
@@ -342,7 +341,7 @@
 	monolithicInfo := newMonolithicHiddenAPIInfo(ctx, temporaryInput.FlagFilesByCategory, fragments)
 
 	// Store the information for testing.
-	ctx.SetProvider(monolithicHiddenAPIInfoProvider, monolithicInfo)
+	ctx.SetProvider(MonolithicHiddenAPIInfoProvider, monolithicInfo)
 	return monolithicInfo
 }
 
@@ -390,11 +389,13 @@
 	generateUpdatableBcpPackagesRule(ctx, imageConfig, updatableModules)
 
 	// Copy non-updatable module dex jars to their predefined locations.
-	copyBootJarsToPredefinedLocations(ctx, nonUpdatableModules, imageConfig.modules, imageConfig.dexPaths)
+	nonUpdatableBootDexJarsByModule := extractEncodedDexJarsFromModules(ctx, nonUpdatableModules)
+	copyBootJarsToPredefinedLocations(ctx, nonUpdatableBootDexJarsByModule, imageConfig.dexPathsByModule)
 
 	// Copy updatable module dex jars to their predefined locations.
 	config := GetUpdatableBootConfig(ctx)
-	copyBootJarsToPredefinedLocations(ctx, updatableModules, config.modules, config.dexPaths)
+	updatableBootDexJarsByModule := extractEncodedDexJarsFromModules(ctx, updatableModules)
+	copyBootJarsToPredefinedLocations(ctx, updatableBootDexJarsByModule, config.dexPathsByModule)
 
 	// Build a profile for the image config and then use that to build the boot image.
 	profile := bootImageProfileRule(ctx, imageConfig)
diff --git a/java/platform_bootclasspath_test.go b/java/platform_bootclasspath_test.go
index ed5549d..0318a07 100644
--- a/java/platform_bootclasspath_test.go
+++ b/java/platform_bootclasspath_test.go
@@ -15,8 +15,6 @@
 package java
 
 import (
-	"fmt"
-	"strings"
 	"testing"
 
 	"android/soong/android"
@@ -152,116 +150,6 @@
 	})
 }
 
-func TestPlatformBootclasspath_Fragments(t *testing.T) {
-	result := android.GroupFixturePreparers(
-		prepareForTestWithPlatformBootclasspath,
-		PrepareForTestWithJavaSdkLibraryFiles,
-		FixtureWithLastReleaseApis("foo"),
-		android.FixtureWithRootAndroidBp(`
-			platform_bootclasspath {
-				name: "platform-bootclasspath",
-				fragments: [
-					{module:"bar-fragment"},
-				],
-				hidden_api: {
-					unsupported: [
-							"unsupported.txt",
-					],
-					removed: [
-							"removed.txt",
-					],
-					max_target_r_low_priority: [
-							"max-target-r-low-priority.txt",
-					],
-					max_target_q: [
-							"max-target-q.txt",
-					],
-					max_target_p: [
-							"max-target-p.txt",
-					],
-					max_target_o_low_priority: [
-							"max-target-o-low-priority.txt",
-					],
-					blocked: [
-							"blocked.txt",
-					],
-					unsupported_packages: [
-							"unsupported-packages.txt",
-					],
-				},
-			}
-
-			bootclasspath_fragment {
-				name: "bar-fragment",
-				contents: ["bar"],
-				api: {
-					stub_libs: ["foo"],
-				},
-				hidden_api: {
-					unsupported: [
-							"bar-unsupported.txt",
-					],
-					removed: [
-							"bar-removed.txt",
-					],
-					max_target_r_low_priority: [
-							"bar-max-target-r-low-priority.txt",
-					],
-					max_target_q: [
-							"bar-max-target-q.txt",
-					],
-					max_target_p: [
-							"bar-max-target-p.txt",
-					],
-					max_target_o_low_priority: [
-							"bar-max-target-o-low-priority.txt",
-					],
-					blocked: [
-							"bar-blocked.txt",
-					],
-					unsupported_packages: [
-							"bar-unsupported-packages.txt",
-					],
-				},
-			}
-
-			java_library {
-				name: "bar",
-				srcs: ["a.java"],
-				system_modules: "none",
-				sdk_version: "none",
-				compile_dex: true,
-			}
-
-			java_sdk_library {
-				name: "foo",
-				srcs: ["a.java"],
-				public: {
-					enabled: true,
-				},
-				compile_dex: true,
-			}
-		`),
-	).RunTest(t)
-
-	pbcp := result.Module("platform-bootclasspath", "android_common")
-	info := result.ModuleProvider(pbcp, monolithicHiddenAPIInfoProvider).(MonolithicHiddenAPIInfo)
-
-	for _, category := range hiddenAPIFlagFileCategories {
-		name := category.propertyName
-		message := fmt.Sprintf("category %s", name)
-		filename := strings.ReplaceAll(name, "_", "-")
-		expected := []string{fmt.Sprintf("%s.txt", filename), fmt.Sprintf("bar-%s.txt", filename)}
-		android.AssertPathsRelativeToTopEquals(t, message, expected, info.FlagsFilesByCategory[category])
-	}
-
-	android.AssertPathsRelativeToTopEquals(t, "stub flags", []string{"out/soong/.intermediates/bar-fragment/android_common/modular-hiddenapi/stub-flags.csv"}, info.StubFlagsPaths)
-	android.AssertPathsRelativeToTopEquals(t, "annotation flags", []string{"out/soong/.intermediates/bar-fragment/android_common/modular-hiddenapi/annotation-flags.csv"}, info.AnnotationFlagsPaths)
-	android.AssertPathsRelativeToTopEquals(t, "metadata flags", []string{"out/soong/.intermediates/bar-fragment/android_common/modular-hiddenapi/metadata.csv"}, info.MetadataPaths)
-	android.AssertPathsRelativeToTopEquals(t, "index flags", []string{"out/soong/.intermediates/bar-fragment/android_common/modular-hiddenapi/index.csv"}, info.IndexPaths)
-	android.AssertPathsRelativeToTopEquals(t, "all flags", []string{"out/soong/.intermediates/bar-fragment/android_common/modular-hiddenapi/all-flags.csv"}, info.AllFlagsPaths)
-}
-
 func TestPlatformBootclasspathVariant(t *testing.T) {
 	result := android.GroupFixturePreparers(
 		prepareForTestWithPlatformBootclasspath,
diff --git a/scripts/hiddenapi/merge_csv.py b/scripts/hiddenapi/merge_csv.py
index b047aab..a65326c 100755
--- a/scripts/hiddenapi/merge_csv.py
+++ b/scripts/hiddenapi/merge_csv.py
@@ -55,14 +55,15 @@
                 if entry.endswith('.uau'):
                     csv_readers.append(dict_reader(io.TextIOWrapper(zip.open(entry, 'r'))))
 
-headers = set()
 if args.header:
     fieldnames = args.header.split(',')
 else:
+    headers = {}
     # Build union of all columns from source files:
     for reader in csv_readers:
-        headers = headers.union(reader.fieldnames)
-    fieldnames = sorted(headers)
+        for fieldname in reader.fieldnames:
+            headers[fieldname] = ""
+    fieldnames = list(headers.keys())
 
 # By default chain the csv readers together so that the resulting output is
 # the concatenation of the rows from each of them:
diff --git a/sdk/bootclasspath_fragment_sdk_test.go b/sdk/bootclasspath_fragment_sdk_test.go
index d9fe281..f2ab6a1 100644
--- a/sdk/bootclasspath_fragment_sdk_test.go
+++ b/sdk/bootclasspath_fragment_sdk_test.go
@@ -15,12 +15,53 @@
 package sdk
 
 import (
+	"fmt"
+	"path/filepath"
 	"testing"
 
 	"android/soong/android"
 	"android/soong/java"
 )
 
+// fixtureAddPlatformBootclasspathForBootclasspathFragment adds a platform_bootclasspath module that
+// references the bootclasspath fragment.
+func fixtureAddPlatformBootclasspathForBootclasspathFragment(apex, fragment string) android.FixturePreparer {
+	return android.GroupFixturePreparers(
+		// Add a platform_bootclasspath module.
+		android.FixtureAddTextFile("frameworks/base/boot/Android.bp", fmt.Sprintf(`
+			platform_bootclasspath {
+				name: "platform-bootclasspath",
+				fragments: [
+					{
+						apex: "%s",
+						module: "%s",
+					},
+				],
+			}
+		`, apex, fragment)),
+		android.FixtureAddFile("frameworks/base/config/boot-profile.txt", nil),
+	)
+}
+
+// fixtureAddPrebuiltApexForBootclasspathFragment adds a prebuilt_apex that exports the fragment.
+func fixtureAddPrebuiltApexForBootclasspathFragment(apex, fragment string) android.FixturePreparer {
+	apexFile := fmt.Sprintf("%s.apex", apex)
+	dir := "prebuilts/apex"
+	return android.GroupFixturePreparers(
+		// A preparer to add a prebuilt apex to the test fixture.
+		android.FixtureAddTextFile(filepath.Join(dir, "Android.bp"), fmt.Sprintf(`
+			prebuilt_apex {
+				name: "%s",
+				src: "%s",
+				exported_bootclasspath_fragments: [
+					"%s",
+				],
+			}
+		`, apex, apexFile, fragment)),
+		android.FixtureAddFile(filepath.Join(dir, apexFile), nil),
+	)
+}
+
 func TestSnapshotWithBootclasspathFragment_ImageName(t *testing.T) {
 	result := android.GroupFixturePreparers(
 		prepareForSdkTestWithJava,
@@ -34,20 +75,8 @@
 			"system/sepolicy/apex/com.android.art-file_contexts": nil,
 		}),
 
-		// platform_bootclasspath that depends on the fragment.
-		android.FixtureAddTextFile("frameworks/base/boot/Android.bp", `
-			platform_bootclasspath {
-				name: "platform-bootclasspath",
-				fragments: [
-					{
-						apex: "com.android.art",
-						module: "mybootclasspathfragment",
-					},
-				],
-			}
-		`),
-		// Needed for platform_bootclasspath
-		android.FixtureAddFile("frameworks/base/config/boot-profile.txt", nil),
+		// Add a platform_bootclasspath that depends on the fragment.
+		fixtureAddPlatformBootclasspathForBootclasspathFragment("com.android.art", "mybootclasspathfragment"),
 
 		java.FixtureConfigureBootJars("com.android.art:mybootlib"),
 		android.FixtureWithRootAndroidBp(`
@@ -89,19 +118,8 @@
 		`),
 	).RunTest(t)
 
-	// A preparer to add a prebuilt apex to the test fixture.
-	prepareWithPrebuiltApex := android.GroupFixturePreparers(
-		android.FixtureAddTextFile("prebuilts/apex/Android.bp", `
-				prebuilt_apex {
-					name: "com.android.art",
-					src: "art.apex",
-					exported_bootclasspath_fragments: [
-						"mybootclasspathfragment",
-					],
-				}
-			`),
-		android.FixtureAddFile("prebuilts/apex/art.apex", nil),
-	)
+	// A preparer to update the test fixture used when processing an unpackage snapshot.
+	preparerForSnapshot := fixtureAddPrebuiltApexForBootclasspathFragment("com.android.art", "mybootclasspathfragment")
 
 	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
@@ -154,9 +172,9 @@
 		checkAllCopyRules(`
 .intermediates/mybootlib/android_common/javac/mybootlib.jar -> java/mybootlib.jar
 `),
-		snapshotTestPreparer(checkSnapshotWithoutSource, prepareWithPrebuiltApex),
-		snapshotTestPreparer(checkSnapshotWithSourcePreferred, prepareWithPrebuiltApex),
-		snapshotTestPreparer(checkSnapshotPreferredWithSource, prepareWithPrebuiltApex),
+		snapshotTestPreparer(checkSnapshotWithoutSource, preparerForSnapshot),
+		snapshotTestPreparer(checkSnapshotWithSourcePreferred, preparerForSnapshot),
+		snapshotTestPreparer(checkSnapshotPreferredWithSource, preparerForSnapshot),
 	)
 }
 
@@ -166,6 +184,12 @@
 		java.PrepareForTestWithJavaDefaultModules,
 		java.PrepareForTestWithJavaSdkLibraryFiles,
 		java.FixtureWithLastReleaseApis("mysdklibrary", "myothersdklibrary", "mycoreplatform"),
+		java.FixtureConfigureUpdatableBootJars("myapex:mybootlib", "myapex:myothersdklibrary"),
+		prepareForSdkTestWithApex,
+
+		// Add a platform_bootclasspath that depends on the fragment.
+		fixtureAddPlatformBootclasspathForBootclasspathFragment("myapex", "mybootclasspathfragment"),
+
 		android.FixtureWithRootAndroidBp(`
 			sdk {
 				name: "mysdk",
@@ -179,8 +203,16 @@
 				],
 			}
 
+			apex {
+				name: "myapex",
+				key: "myapex.key",
+				min_sdk_version: "2",
+				bootclasspath_fragments: ["mybootclasspathfragment"],
+			}
+
 			bootclasspath_fragment {
 				name: "mybootclasspathfragment",
+				apex_available: ["myapex"],
 				contents: [
 					// This should be automatically added to the sdk_snapshot as a java_boot_libs module.
 					"mybootlib",
@@ -198,35 +230,48 @@
 
 			java_library {
 				name: "mybootlib",
+				apex_available: ["myapex"],
 				srcs: ["Test.java"],
 				system_modules: "none",
 				sdk_version: "none",
+				min_sdk_version: "2",
 				compile_dex: true,
+				permitted_packages: ["mybootlib"],
 			}
 
 			java_sdk_library {
 				name: "mysdklibrary",
+				apex_available: ["myapex"],
 				srcs: ["Test.java"],
 				shared_library: false,
 				public: {enabled: true},
+				min_sdk_version: "2",
 			}
 
 			java_sdk_library {
 				name: "myothersdklibrary",
+				apex_available: ["myapex"],
 				srcs: ["Test.java"],
 				shared_library: false,
 				public: {enabled: true},
+				min_sdk_version: "2",
+				permitted_packages: ["myothersdklibrary"],
 			}
 
 			java_sdk_library {
 				name: "mycoreplatform",
+				apex_available: ["myapex"],
 				srcs: ["Test.java"],
 				shared_library: false,
 				public: {enabled: true},
+				min_sdk_version: "2",
 			}
 		`),
 	).RunTest(t)
 
+	// A preparer to update the test fixture used when processing an unpackage snapshot.
+	preparerForSnapshot := fixtureAddPrebuiltApexForBootclasspathFragment("myapex", "mybootclasspathfragment")
+
 	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
@@ -235,7 +280,7 @@
     name: "mybootclasspathfragment",
     prefer: false,
     visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
+    apex_available: ["myapex"],
     contents: [
         "mybootlib",
         "myothersdklibrary",
@@ -259,7 +304,7 @@
     name: "mybootlib",
     prefer: false,
     visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
+    apex_available: ["myapex"],
     jars: ["java/mybootlib.jar"],
 }
 
@@ -267,7 +312,7 @@
     name: "myothersdklibrary",
     prefer: false,
     visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
+    apex_available: ["myapex"],
     shared_library: false,
     public: {
         jars: ["sdk_library/public/myothersdklibrary-stubs.jar"],
@@ -282,7 +327,7 @@
     name: "mysdklibrary",
     prefer: false,
     visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
+    apex_available: ["myapex"],
     shared_library: false,
     public: {
         jars: ["sdk_library/public/mysdklibrary-stubs.jar"],
@@ -297,7 +342,7 @@
     name: "mycoreplatform",
     prefer: false,
     visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
+    apex_available: ["myapex"],
     shared_library: false,
     public: {
         jars: ["sdk_library/public/mycoreplatform-stubs.jar"],
@@ -315,7 +360,7 @@
     name: "mysdk_mybootclasspathfragment@current",
     sdk_member_name: "mybootclasspathfragment",
     visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
+    apex_available: ["myapex"],
     contents: [
         "mysdk_mybootlib@current",
         "mysdk_myothersdklibrary@current",
@@ -339,7 +384,7 @@
     name: "mysdk_mybootlib@current",
     sdk_member_name: "mybootlib",
     visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
+    apex_available: ["myapex"],
     jars: ["java/mybootlib.jar"],
 }
 
@@ -347,7 +392,7 @@
     name: "mysdk_myothersdklibrary@current",
     sdk_member_name: "myothersdklibrary",
     visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
+    apex_available: ["myapex"],
     shared_library: false,
     public: {
         jars: ["sdk_library/public/myothersdklibrary-stubs.jar"],
@@ -362,7 +407,7 @@
     name: "mysdk_mysdklibrary@current",
     sdk_member_name: "mysdklibrary",
     visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
+    apex_available: ["myapex"],
     shared_library: false,
     public: {
         jars: ["sdk_library/public/mysdklibrary-stubs.jar"],
@@ -377,7 +422,7 @@
     name: "mysdk_mycoreplatform@current",
     sdk_member_name: "mycoreplatform",
     visibility: ["//visibility:public"],
-    apex_available: ["//apex_available:platform"],
+    apex_available: ["myapex"],
     shared_library: false,
     public: {
         jars: ["sdk_library/public/mycoreplatform-stubs.jar"],
@@ -416,7 +461,11 @@
 .intermediates/mycoreplatform.stubs/android_common/javac/mycoreplatform.stubs.jar -> sdk_library/public/mycoreplatform-stubs.jar
 .intermediates/mycoreplatform.stubs.source/android_common/metalava/mycoreplatform.stubs.source_api.txt -> sdk_library/public/mycoreplatform.txt
 .intermediates/mycoreplatform.stubs.source/android_common/metalava/mycoreplatform.stubs.source_removed.txt -> sdk_library/public/mycoreplatform-removed.txt
-`))
+`),
+		snapshotTestPreparer(checkSnapshotWithoutSource, preparerForSnapshot),
+		snapshotTestPreparer(checkSnapshotWithSourcePreferred, preparerForSnapshot),
+		snapshotTestPreparer(checkSnapshotPreferredWithSource, preparerForSnapshot),
+	)
 }
 
 // Test that bootclasspath_fragment works with sdk.
@@ -482,7 +531,12 @@
 		java.PrepareForTestWithJavaDefaultModules,
 		java.PrepareForTestWithJavaSdkLibraryFiles,
 		java.FixtureWithLastReleaseApis("mysdklibrary"),
+		java.FixtureConfigureUpdatableBootJars("myapex:mybootlib"),
 		prepareForSdkTestWithApex,
+
+		// Add a platform_bootclasspath that depends on the fragment.
+		fixtureAddPlatformBootclasspathForBootclasspathFragment("myapex", "mybootclasspathfragment"),
+
 		android.MockFS{
 			"my-blocked.txt":                   nil,
 			"my-max-target-o-low-priority.txt": nil,
@@ -549,6 +603,7 @@
 				sdk_version: "none",
 				min_sdk_version: "1",
 				compile_dex: true,
+				permitted_packages: ["mybootlib"],
 			}
 
 			java_sdk_library {
@@ -560,6 +615,9 @@
 		`),
 	).RunTest(t)
 
+	// A preparer to update the test fixture used when processing an unpackage snapshot.
+	preparerForSnapshot := fixtureAddPrebuiltApexForBootclasspathFragment("myapex", "mybootclasspathfragment")
+
 	CheckSnapshot(t, result, "mysdk", "",
 		checkUnversionedAndroidBpContents(`
 // This is auto-generated. DO NOT EDIT.
@@ -633,5 +691,8 @@
 .intermediates/mysdklibrary.stubs.source/android_common/metalava/mysdklibrary.stubs.source_api.txt -> sdk_library/public/mysdklibrary.txt
 .intermediates/mysdklibrary.stubs.source/android_common/metalava/mysdklibrary.stubs.source_removed.txt -> sdk_library/public/mysdklibrary-removed.txt
 `),
+		snapshotTestPreparer(checkSnapshotWithoutSource, preparerForSnapshot),
+		snapshotTestPreparer(checkSnapshotWithSourcePreferred, preparerForSnapshot),
+		snapshotTestPreparer(checkSnapshotPreferredWithSource, preparerForSnapshot),
 	)
 }