Merge changes from topics "snapshot_androidmk_suffix", "snapshot_list_module"

* changes:
  Remove global state on module suffixes from vendor and recovery snapshots
  Remove some global state from vendor and recovery snapshots
diff --git a/android/mutator.go b/android/mutator.go
index 2a2be6c..6b19dc5 100644
--- a/android/mutator.go
+++ b/android/mutator.go
@@ -48,6 +48,14 @@
 func RegisterMutatorsForBazelConversion(ctx *blueprint.Context, bp2buildMutators []RegisterMutatorFunc) {
 	mctx := &registerMutatorsContext{}
 
+	sharedMutators := []RegisterMutatorFunc{
+		RegisterDefaultsPreArchMutators,
+	}
+
+	for _, f := range sharedMutators {
+		f(mctx)
+	}
+
 	// Register bp2build mutators
 	for _, f := range bp2buildMutators {
 		f(mctx)
diff --git a/apex/apex.go b/apex/apex.go
index ade8fa9..c897042 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -89,6 +89,9 @@
 
 	Multilib apexMultilibProperties
 
+	// List of boot images that are embedded inside this APEX bundle.
+	Boot_images []string
+
 	// List of java libraries that are embedded inside this APEX bundle.
 	Java_libs []string
 
@@ -544,6 +547,7 @@
 	certificateTag = dependencyTag{name: "certificate"}
 	executableTag  = dependencyTag{name: "executable", payload: true}
 	fsTag          = dependencyTag{name: "filesystem", payload: true}
+	bootImageTag   = dependencyTag{name: "bootImage", payload: true}
 	javaLibTag     = dependencyTag{name: "javaLib", payload: true}
 	jniLibTag      = dependencyTag{name: "jniLib", payload: true}
 	keyTag         = dependencyTag{name: "key"}
@@ -721,6 +725,7 @@
 
 	// Common-arch dependencies come next
 	commonVariation := ctx.Config().AndroidCommonTarget.Variations()
+	ctx.AddFarVariationDependencies(commonVariation, bootImageTag, a.properties.Boot_images...)
 	ctx.AddFarVariationDependencies(commonVariation, javaLibTag, a.properties.Java_libs...)
 	ctx.AddFarVariationDependencies(commonVariation, bpfTag, a.properties.Bpfs...)
 	ctx.AddFarVariationDependencies(commonVariation, fsTag, a.properties.Filesystems...)
@@ -730,10 +735,6 @@
 		if ctx.Config().IsEnvTrue("EMMA_INSTRUMENT_FRAMEWORK") {
 			ctx.AddFarVariationDependencies(commonVariation, javaLibTag, "jacocoagent")
 		}
-		// The ART boot image depends on dex2oat to compile it.
-		if !java.SkipDexpreoptBootJars(ctx) {
-			dexpreopt.RegisterToolDeps(ctx)
-		}
 	}
 
 	// Dependencies for signing
@@ -1648,6 +1649,23 @@
 				} else {
 					ctx.PropertyErrorf("binaries", "%q is neither cc_binary, rust_binary, (embedded) py_binary, (host) blueprint_go_binary, (host) bootstrap_go_binary, nor sh_binary", depName)
 				}
+			case bootImageTag:
+				{
+					if _, ok := child.(*java.BootImageModule); !ok {
+						ctx.PropertyErrorf("boot_images", "%q is not a boot_image module", depName)
+						return false
+					}
+					bootImageInfo := ctx.OtherModuleProvider(child, java.BootImageInfoProvider).(java.BootImageInfo)
+					for arch, files := range bootImageInfo.AndroidBootImageFilesByArchType() {
+						dirInApex := filepath.Join("javalib", arch.String())
+						for _, f := range files {
+							androidMkModuleName := "javalib_" + arch.String() + "_" + filepath.Base(f.String())
+							// TODO(b/177892522) - consider passing in the boot image module here instead of nil
+							af := newApexFile(ctx, f, androidMkModuleName, dirInApex, etc, nil)
+							filesInfo = append(filesInfo, af)
+						}
+					}
+				}
 			case javaLibTag:
 				switch child.(type) {
 				case *java.Library, *java.SdkLibrary, *java.DexImport, *java.SdkLibraryImport, *java.Import:
@@ -1862,25 +1880,6 @@
 		return
 	}
 
-	if a.artApex {
-		// Specific to the ART apex: dexpreopt artifacts for libcore Java libraries. Build rules are
-		// generated by the dexpreopt singleton, and here we access build artifacts via the global
-		// boot image config.
-		for arch, files := range java.DexpreoptedArtApexJars(ctx) {
-			dirInApex := filepath.Join("javalib", arch.String())
-			for _, f := range files {
-				localModule := "javalib_" + arch.String() + "_" + filepath.Base(f.String())
-				af := newApexFile(ctx, f, localModule, dirInApex, etc, nil)
-				filesInfo = append(filesInfo, af)
-			}
-		}
-		// Call GetGlobalSoongConfig to initialize it, which may be necessary if dexpreopt is
-		// disabled for libraries/apps, but boot images are still needed.
-		if !java.SkipDexpreoptBootJars(ctx) {
-			dexpreopt.GetGlobalSoongConfig(ctx)
-		}
-	}
-
 	// Remove duplicates in filesInfo
 	removeDup := func(filesInfo []apexFile) []apexFile {
 		encountered := make(map[string]apexFile)
diff --git a/apex/boot_image_test.go b/apex/boot_image_test.go
index 07feb03..27a1562 100644
--- a/apex/boot_image_test.go
+++ b/apex/boot_image_test.go
@@ -15,6 +15,8 @@
 package apex
 
 import (
+	"reflect"
+	"strings"
 	"testing"
 
 	"android/soong/android"
@@ -69,6 +71,16 @@
 			],
 			srcs: ["b.java"],
 		}
+
+		boot_image {
+			name: "art-boot-image",
+			image_name: "art",
+		}
+
+		boot_image {
+			name: "framework-boot-image",
+			image_name: "boot",
+		}
 `,
 		// Configure some libraries in the art and framework boot images.
 		withArtBootImageJars("com.android.art:baz", "com.android.art:quuz"),
@@ -83,13 +95,39 @@
 	)
 
 	// Make sure that the framework-boot-image is using the correct configuration.
-	checkBootImage(t, ctx, "framework-boot-image", "platform:foo,platform:bar")
+	checkBootImage(t, ctx, "framework-boot-image", "platform:foo,platform:bar", `
+test_device/dex_bootjars/android/system/framework/arm/boot-foo.art
+test_device/dex_bootjars/android/system/framework/arm/boot-foo.oat
+test_device/dex_bootjars/android/system/framework/arm/boot-foo.vdex
+test_device/dex_bootjars/android/system/framework/arm/boot-bar.art
+test_device/dex_bootjars/android/system/framework/arm/boot-bar.oat
+test_device/dex_bootjars/android/system/framework/arm/boot-bar.vdex
+test_device/dex_bootjars/android/system/framework/arm64/boot-foo.art
+test_device/dex_bootjars/android/system/framework/arm64/boot-foo.oat
+test_device/dex_bootjars/android/system/framework/arm64/boot-foo.vdex
+test_device/dex_bootjars/android/system/framework/arm64/boot-bar.art
+test_device/dex_bootjars/android/system/framework/arm64/boot-bar.oat
+test_device/dex_bootjars/android/system/framework/arm64/boot-bar.vdex
+`)
 
 	// Make sure that the art-boot-image is using the correct configuration.
-	checkBootImage(t, ctx, "art-boot-image", "com.android.art:baz,com.android.art:quuz")
+	checkBootImage(t, ctx, "art-boot-image", "com.android.art:baz,com.android.art:quuz", `
+test_device/dex_artjars/android/apex/art_boot_images/javalib/arm/boot.art
+test_device/dex_artjars/android/apex/art_boot_images/javalib/arm/boot.oat
+test_device/dex_artjars/android/apex/art_boot_images/javalib/arm/boot.vdex
+test_device/dex_artjars/android/apex/art_boot_images/javalib/arm/boot-quuz.art
+test_device/dex_artjars/android/apex/art_boot_images/javalib/arm/boot-quuz.oat
+test_device/dex_artjars/android/apex/art_boot_images/javalib/arm/boot-quuz.vdex
+test_device/dex_artjars/android/apex/art_boot_images/javalib/arm64/boot.art
+test_device/dex_artjars/android/apex/art_boot_images/javalib/arm64/boot.oat
+test_device/dex_artjars/android/apex/art_boot_images/javalib/arm64/boot.vdex
+test_device/dex_artjars/android/apex/art_boot_images/javalib/arm64/boot-quuz.art
+test_device/dex_artjars/android/apex/art_boot_images/javalib/arm64/boot-quuz.oat
+test_device/dex_artjars/android/apex/art_boot_images/javalib/arm64/boot-quuz.vdex
+`)
 }
 
-func checkBootImage(t *testing.T, ctx *android.TestContext, moduleName string, expectedConfiguredModules string) {
+func checkBootImage(t *testing.T, ctx *android.TestContext, moduleName string, expectedConfiguredModules string, expectedBootImageFiles string) {
 	t.Helper()
 
 	bootImage := ctx.ModuleForTests(moduleName, "android_common").Module().(*java.BootImageModule)
@@ -99,6 +137,20 @@
 	if actual := modules.String(); actual != expectedConfiguredModules {
 		t.Errorf("invalid modules for %s: expected %q, actual %q", moduleName, expectedConfiguredModules, actual)
 	}
+
+	// Get a list of all the paths in the boot image sorted by arch type.
+	allPaths := []string{}
+	bootImageFilesByArchType := bootImageInfo.AndroidBootImageFilesByArchType()
+	for _, archType := range android.ArchTypeList() {
+		if paths, ok := bootImageFilesByArchType[archType]; ok {
+			for _, path := range paths {
+				allPaths = append(allPaths, android.NormalizePathForTesting(path))
+			}
+		}
+	}
+	if expected, actual := strings.TrimSpace(expectedBootImageFiles), strings.TrimSpace(strings.Join(allPaths, "\n")); !reflect.DeepEqual(expected, actual) {
+		t.Errorf("invalid paths for %s: expected \n%s, actual \n%s", moduleName, expected, actual)
+	}
 }
 
 func modifyDexpreoptConfig(configModifier func(dexpreoptConfig *dexpreopt.GlobalConfig)) func(fs map[string][]byte, config android.Config) {
@@ -126,3 +178,61 @@
 		dexpreoptConfig.BootJars = android.CreateTestConfiguredJarList(bootJars)
 	})
 }
+
+func TestBootImageInApex(t *testing.T) {
+	ctx, _ := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			boot_images: [
+				"mybootimage",
+			],
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		java_library {
+			name: "foo",
+			srcs: ["b.java"],
+			installable: true,
+		}
+
+		java_library {
+			name: "bar",
+			srcs: ["b.java"],
+			installable: true,
+		}
+
+		boot_image {
+			name: "mybootimage",
+			image_name: "boot",
+			apex_available: [
+				"myapex",
+			],
+		}
+`,
+		// Configure some libraries in the framework boot image.
+		withFrameworkBootImageJars("platform:foo", "platform:bar"),
+	)
+
+	ensureExactContents(t, ctx, "myapex", "android_common_myapex_image", []string{
+		"javalib/arm/boot-bar.art",
+		"javalib/arm/boot-bar.oat",
+		"javalib/arm/boot-bar.vdex",
+		"javalib/arm/boot-foo.art",
+		"javalib/arm/boot-foo.oat",
+		"javalib/arm/boot-foo.vdex",
+		"javalib/arm64/boot-bar.art",
+		"javalib/arm64/boot-bar.oat",
+		"javalib/arm64/boot-bar.vdex",
+		"javalib/arm64/boot-foo.art",
+		"javalib/arm64/boot-foo.oat",
+		"javalib/arm64/boot-foo.vdex",
+	})
+}
+
+// TODO(b/177892522) - add test for host apex.
diff --git a/bp2build/build_conversion_test.go b/bp2build/build_conversion_test.go
index 66ed42d..a01a7cd 100644
--- a/bp2build/build_conversion_test.go
+++ b/bp2build/build_conversion_test.go
@@ -633,3 +633,201 @@
 		}
 	}
 }
+
+type bp2buildMutator = func(android.TopDownMutatorContext)
+
+func TestBp2BuildInlinesDefaults(t *testing.T) {
+	testCases := []struct {
+		moduleTypesUnderTest      map[string]android.ModuleFactory
+		bp2buildMutatorsUnderTest map[string]bp2buildMutator
+		bp                        string
+		expectedBazelTarget       string
+		description               string
+	}{
+		{
+			moduleTypesUnderTest: map[string]android.ModuleFactory{
+				"genrule":          genrule.GenRuleFactory,
+				"genrule_defaults": func() android.Module { return genrule.DefaultsFactory() },
+			},
+			bp2buildMutatorsUnderTest: map[string]bp2buildMutator{
+				"genrule": genrule.GenruleBp2Build,
+			},
+			bp: `genrule_defaults {
+    name: "gen_defaults",
+    cmd: "do-something $(in) $(out)",
+}
+genrule {
+    name: "gen",
+    out: ["out"],
+    srcs: ["in1"],
+    defaults: ["gen_defaults"],
+}
+`,
+			expectedBazelTarget: `genrule(
+    name = "gen",
+    cmd = "do-something $(SRCS) $(OUTS)",
+    outs = [
+        "out",
+    ],
+    srcs = [
+        "in1",
+    ],
+)`,
+			description: "genrule applies properties from a genrule_defaults dependency if not specified",
+		},
+		{
+			moduleTypesUnderTest: map[string]android.ModuleFactory{
+				"genrule":          genrule.GenRuleFactory,
+				"genrule_defaults": func() android.Module { return genrule.DefaultsFactory() },
+			},
+			bp2buildMutatorsUnderTest: map[string]bp2buildMutator{
+				"genrule": genrule.GenruleBp2Build,
+			},
+			bp: `genrule_defaults {
+    name: "gen_defaults",
+    out: ["out-from-defaults"],
+    srcs: ["in-from-defaults"],
+    cmd: "cmd-from-defaults",
+}
+genrule {
+    name: "gen",
+    out: ["out"],
+    srcs: ["in1"],
+    defaults: ["gen_defaults"],
+    cmd: "do-something $(in) $(out)",
+}
+`,
+			expectedBazelTarget: `genrule(
+    name = "gen",
+    cmd = "do-something $(SRCS) $(OUTS)",
+    outs = [
+        "out-from-defaults",
+        "out",
+    ],
+    srcs = [
+        "in-from-defaults",
+        "in1",
+    ],
+)`,
+			description: "genrule does merges properties from a genrule_defaults dependency, latest-first",
+		},
+		{
+			moduleTypesUnderTest: map[string]android.ModuleFactory{
+				"genrule":          genrule.GenRuleFactory,
+				"genrule_defaults": func() android.Module { return genrule.DefaultsFactory() },
+			},
+			bp2buildMutatorsUnderTest: map[string]bp2buildMutator{
+				"genrule": genrule.GenruleBp2Build,
+			},
+			bp: `genrule_defaults {
+    name: "gen_defaults1",
+    cmd: "cp $(in) $(out)",
+}
+
+genrule_defaults {
+    name: "gen_defaults2",
+    srcs: ["in1"],
+}
+
+genrule {
+    name: "gen",
+    out: ["out"],
+    defaults: ["gen_defaults1", "gen_defaults2"],
+}
+`,
+			expectedBazelTarget: `genrule(
+    name = "gen",
+    cmd = "cp $(SRCS) $(OUTS)",
+    outs = [
+        "out",
+    ],
+    srcs = [
+        "in1",
+    ],
+)`,
+			description: "genrule applies properties from list of genrule_defaults",
+		},
+		{
+			moduleTypesUnderTest: map[string]android.ModuleFactory{
+				"genrule":          genrule.GenRuleFactory,
+				"genrule_defaults": func() android.Module { return genrule.DefaultsFactory() },
+			},
+			bp2buildMutatorsUnderTest: map[string]bp2buildMutator{
+				"genrule": genrule.GenruleBp2Build,
+			},
+			bp: `genrule_defaults {
+    name: "gen_defaults1",
+    defaults: ["gen_defaults2"],
+    cmd: "cmd1 $(in) $(out)", // overrides gen_defaults2's cmd property value.
+}
+
+genrule_defaults {
+    name: "gen_defaults2",
+    defaults: ["gen_defaults3"],
+    cmd: "cmd2 $(in) $(out)",
+    out: ["out-from-2"],
+    srcs: ["in1"],
+}
+
+genrule_defaults {
+    name: "gen_defaults3",
+    out: ["out-from-3"],
+    srcs: ["srcs-from-3"],
+}
+
+genrule {
+    name: "gen",
+    out: ["out"],
+    defaults: ["gen_defaults1"],
+}
+`,
+			expectedBazelTarget: `genrule(
+    name = "gen",
+    cmd = "cmd1 $(SRCS) $(OUTS)",
+    outs = [
+        "out-from-3",
+        "out-from-2",
+        "out",
+    ],
+    srcs = [
+        "srcs-from-3",
+        "in1",
+    ],
+)`,
+			description: "genrule applies properties from genrule_defaults transitively",
+		},
+	}
+
+	dir := "."
+	for _, testCase := range testCases {
+		config := android.TestConfig(buildDir, nil, testCase.bp, nil)
+		ctx := android.NewTestContext(config)
+		for m, factory := range testCase.moduleTypesUnderTest {
+			ctx.RegisterModuleType(m, factory)
+		}
+		for mutator, f := range testCase.bp2buildMutatorsUnderTest {
+			ctx.RegisterBp2BuildMutator(mutator, f)
+		}
+		ctx.RegisterForBazelConversion()
+
+		_, errs := ctx.ParseFileList(dir, []string{"Android.bp"})
+		android.FailIfErrored(t, errs)
+		_, errs = ctx.ResolveDependencies(config)
+		android.FailIfErrored(t, errs)
+
+		bazelTargets := GenerateSoongModuleTargets(ctx.Context.Context, Bp2Build)[dir]
+		if actualCount := len(bazelTargets); actualCount != 1 {
+			t.Fatalf("%s: Expected 1 bazel target, got %d", testCase.description, actualCount)
+		}
+
+		actualBazelTarget := bazelTargets[0]
+		if actualBazelTarget.content != testCase.expectedBazelTarget {
+			t.Errorf(
+				"%s: Expected generated Bazel target to be '%s', got '%s'",
+				testCase.description,
+				testCase.expectedBazelTarget,
+				actualBazelTarget.content,
+			)
+		}
+	}
+}
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index 8cd4f97..9dcb5f3 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -19,6 +19,7 @@
 	"fmt"
 	"os"
 	"path/filepath"
+	"strings"
 
 	"github.com/google/blueprint/bootstrap"
 
@@ -166,12 +167,43 @@
 	// conversion for Bazel conversion.
 	bp2buildCtx := android.NewContext(configuration)
 	bp2buildCtx.RegisterForBazelConversion()
+
+	// No need to generate Ninja build rules/statements from Modules and Singletons.
 	configuration.SetStopBefore(bootstrap.StopBeforePrepareBuildActions)
 	bp2buildCtx.SetNameInterface(newNameResolver(configuration))
+
+	// Run the loading and analysis pipeline.
 	bootstrap.Main(bp2buildCtx.Context, configuration, extraNinjaDeps...)
 
+	// Run the code-generation phase to convert BazelTargetModules to BUILD files.
 	codegenContext := bp2build.NewCodegenContext(configuration, *bp2buildCtx, bp2build.Bp2Build)
 	bp2build.Codegen(codegenContext)
+
+	// Workarounds to support running bp2build in a clean AOSP checkout with no
+	// prior builds, and exiting early as soon as the BUILD files get generated,
+	// therefore not creating build.ninja files that soong_ui and callers of
+	// soong_build expects.
+	//
+	// These files are: build.ninja and build.ninja.d. Since Kati hasn't been
+	// ran as well, and `nothing` is defined in a .mk file, there isn't a ninja
+	// target called `nothing`, so we manually create it here.
+	//
+	// Even though outFile (build.ninja) and depFile (build.ninja.d) are values
+	// passed into bootstrap.Main, they are package-private fields in bootstrap.
+	// Short of modifying Blueprint to add an exported getter, inlining them
+	// here is the next-best practical option.
+	ninjaFileName := "build.ninja"
+	ninjaFile := android.PathForOutput(codegenContext, ninjaFileName)
+	ninjaFileD := android.PathForOutput(codegenContext, ninjaFileName+".d")
+	extraNinjaDepsString := strings.Join(extraNinjaDeps, " \\\n ")
+	// A workaround to create the 'nothing' ninja target so `m nothing` works,
+	// since bp2build runs without Kati, and the 'nothing' target is declared in
+	// a Makefile.
+	android.WriteFileToOutputDir(ninjaFile, []byte("build nothing: phony\n  phony_output = true\n"), 0666)
+	android.WriteFileToOutputDir(
+		ninjaFileD,
+		[]byte(fmt.Sprintf("%s: \\\n %s\n", ninjaFileName, extraNinjaDepsString)),
+		0666)
 }
 
 // shouldPrepareBuildActions reads configuration and flags if build actions
diff --git a/dexpreopt/class_loader_context.go b/dexpreopt/class_loader_context.go
index bf610ef..ec62eb3 100644
--- a/dexpreopt/class_loader_context.go
+++ b/dexpreopt/class_loader_context.go
@@ -534,3 +534,26 @@
 	}
 	return clcs
 }
+
+// Convert Soong CLC map to JSON representation for Make.
+func toJsonClassLoaderContext(clcMap ClassLoaderContextMap) jsonClassLoaderContextMap {
+	jClcMap := make(jsonClassLoaderContextMap)
+	for sdkVer, clcs := range clcMap {
+		sdkVerStr := fmt.Sprintf("%d", sdkVer)
+		jClcMap[sdkVerStr] = toJsonClassLoaderContextRec(clcs)
+	}
+	return jClcMap
+}
+
+// Recursive helper for toJsonClassLoaderContext.
+func toJsonClassLoaderContextRec(clcs []*ClassLoaderContext) map[string]*jsonClassLoaderContext {
+	jClcs := make(map[string]*jsonClassLoaderContext, len(clcs))
+	for _, clc := range clcs {
+		jClcs[clc.Name] = &jsonClassLoaderContext{
+			Host:        clc.Host.String(),
+			Device:      clc.Device,
+			Subcontexts: toJsonClassLoaderContextRec(clc.Subcontexts),
+		}
+	}
+	return jClcs
+}
diff --git a/dexpreopt/config.go b/dexpreopt/config.go
index 867ece6..d55204b 100644
--- a/dexpreopt/config.go
+++ b/dexpreopt/config.go
@@ -114,6 +114,7 @@
 	ProfileBootListing   android.OptionalPath
 
 	EnforceUsesLibraries bool
+	ProvidesUsesLibrary  string // the name of the <uses-library> (usually the same as its module)
 	ClassLoaderContexts  ClassLoaderContextMap
 
 	Archs                   []android.ArchType
@@ -290,6 +291,42 @@
 	return config.ModuleConfig, nil
 }
 
+// WriteSlimModuleConfigForMake serializes a subset of ModuleConfig into a per-module
+// dexpreopt.config JSON file. It is a way to pass dexpreopt information about Soong modules to
+// Make, which is needed when a Make module has a <uses-library> dependency on a Soong module.
+func WriteSlimModuleConfigForMake(ctx android.ModuleContext, config *ModuleConfig, path android.WritablePath) {
+	if path == nil {
+		return
+	}
+
+	// JSON representation of the slim module dexpreopt.config.
+	type slimModuleJSONConfig struct {
+		Name                 string
+		DexLocation          string
+		BuildPath            string
+		EnforceUsesLibraries bool
+		ProvidesUsesLibrary  string
+		ClassLoaderContexts  jsonClassLoaderContextMap
+	}
+
+	jsonConfig := &slimModuleJSONConfig{
+		Name:                 config.Name,
+		DexLocation:          config.DexLocation,
+		BuildPath:            config.BuildPath.String(),
+		EnforceUsesLibraries: config.EnforceUsesLibraries,
+		ProvidesUsesLibrary:  config.ProvidesUsesLibrary,
+		ClassLoaderContexts:  toJsonClassLoaderContext(config.ClassLoaderContexts),
+	}
+
+	data, err := json.MarshalIndent(jsonConfig, "", "    ")
+	if err != nil {
+		ctx.ModuleErrorf("failed to JSON marshal module dexpreopt.config: %v", err)
+		return
+	}
+
+	android.WriteFileRule(ctx, path, string(data))
+}
+
 // dex2oatModuleName returns the name of the module to use for the dex2oat host
 // tool. It should be a binary module with public visibility that is compiled
 // and installed for host.
diff --git a/java/androidmk.go b/java/androidmk.go
index cc454b0..21f3012 100644
--- a/java/androidmk.go
+++ b/java/androidmk.go
@@ -125,6 +125,10 @@
 					entries.SetString("LOCAL_MODULE_STEM", library.Stem())
 
 					entries.SetOptionalPaths("LOCAL_SOONG_LINT_REPORTS", library.linter.reports)
+
+					if library.dexpreopter.configPath != nil {
+						entries.SetPath("LOCAL_SOONG_DEXPREOPT_CONFIG", library.dexpreopter.configPath)
+					}
 				},
 			},
 		})
diff --git a/java/app.go b/java/app.go
index e6c9a2d..ce89e9b 100755
--- a/java/app.go
+++ b/java/app.go
@@ -455,6 +455,7 @@
 
 func (a *AndroidApp) dexBuildActions(ctx android.ModuleContext) android.Path {
 	a.dexpreopter.installPath = a.installPath(ctx)
+	a.dexpreopter.isApp = true
 	if a.dexProperties.Uncompress_dex == nil {
 		// If the value was not force-set by the user, use reasonable default based on the module.
 		a.dexProperties.Uncompress_dex = proptools.BoolPtr(a.shouldUncompressDex(ctx))
diff --git a/java/app_import.go b/java/app_import.go
index df940f1..6f21bfb 100644
--- a/java/app_import.go
+++ b/java/app_import.go
@@ -255,6 +255,7 @@
 		installDir = android.PathForModuleInstall(ctx, "app", a.BaseModuleName())
 	}
 
+	a.dexpreopter.isApp = true
 	a.dexpreopter.installPath = installDir.Join(ctx, a.BaseModuleName()+".apk")
 	a.dexpreopter.isPresignedPrebuilt = Bool(a.properties.Presigned)
 	a.dexpreopter.uncompressedDex = a.shouldUncompressDex(ctx)
diff --git a/java/boot_image.go b/java/boot_image.go
index 07ef0d8..0a525b7 100644
--- a/java/boot_image.go
+++ b/java/boot_image.go
@@ -15,9 +15,11 @@
 package java
 
 import (
+	"fmt"
 	"strings"
 
 	"android/soong/android"
+	"android/soong/dexpreopt"
 	"github.com/google/blueprint"
 )
 
@@ -38,6 +40,7 @@
 
 type BootImageModule struct {
 	android.ModuleBase
+	android.ApexModuleBase
 
 	properties bootImageProperties
 }
@@ -45,7 +48,8 @@
 func bootImageFactory() android.Module {
 	m := &BootImageModule{}
 	m.AddProperties(&m.properties)
-	android.InitAndroidArchModule(m, android.HostAndDeviceDefault, android.MultilibCommon)
+	android.InitAndroidArchModule(m, android.HostAndDeviceSupported, android.MultilibCommon)
+	android.InitApexModule(m)
 	return m
 }
 
@@ -53,6 +57,9 @@
 
 type BootImageInfo struct {
 	// The image config, internal to this module (and the dex_bootjars singleton).
+	//
+	// Will be nil if the BootImageInfo has not been provided for a specific module. That can occur
+	// when SkipDexpreoptBootJars(ctx) returns true.
 	imageConfig *bootImageConfig
 }
 
@@ -60,12 +67,56 @@
 	return i.imageConfig.modules
 }
 
+// Get a map from ArchType to the associated boot image's contents for Android.
+//
+// Extension boot images only return their own files, not the files of the boot images they extend.
+func (i BootImageInfo) AndroidBootImageFilesByArchType() map[android.ArchType]android.OutputPaths {
+	files := map[android.ArchType]android.OutputPaths{}
+	if i.imageConfig != nil {
+		for _, variant := range i.imageConfig.variants {
+			// We also generate boot images for host (for testing), but we don't need those in the apex.
+			// TODO(b/177892522) - consider changing this to check Os.OsClass = android.Device
+			if variant.target.Os == android.Android {
+				files[variant.target.Arch.ArchType] = variant.imagesDeps
+			}
+		}
+	}
+	return files
+}
+
+func (b *BootImageModule) DepIsInSameApex(ctx android.BaseModuleContext, dep android.Module) bool {
+	tag := ctx.OtherModuleDependencyTag(dep)
+	if tag == dexpreopt.Dex2oatDepTag {
+		// The dex2oat tool is only needed for building and is not required in the apex.
+		return false
+	}
+	panic(fmt.Errorf("boot_image module %q should not have a dependency on %q via tag %s", b, dep, android.PrettyPrintTag(tag)))
+}
+
+func (b *BootImageModule) ShouldSupportSdkVersion(ctx android.BaseModuleContext, sdkVersion android.ApiLevel) error {
+	return nil
+}
+
+func (b *BootImageModule) DepsMutator(ctx android.BottomUpMutatorContext) {
+	if SkipDexpreoptBootJars(ctx) {
+		return
+	}
+
+	// Add a dependency onto the dex2oat tool which is needed for creating the boot image. The
+	// path is retrieved from the dependency by GetGlobalSoongConfig(ctx).
+	dexpreopt.RegisterToolDeps(ctx)
+}
+
 func (b *BootImageModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	// Nothing to do if skipping the dexpreopt of boot image jars.
 	if SkipDexpreoptBootJars(ctx) {
 		return
 	}
 
+	// Force the GlobalSoongConfig to be created and cached for use by the dex_bootjars
+	// GenerateSingletonBuildActions method as it cannot create it for itself.
+	dexpreopt.GetGlobalSoongConfig(ctx)
+
 	// Get a map of the image configs that are supported.
 	imageConfigs := genBootImageConfigs(ctx)
 
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index b5830c7..ac00592 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -30,6 +30,7 @@
 	installPath         android.InstallPath
 	uncompressedDex     bool
 	isSDKLibrary        bool
+	isApp               bool
 	isTest              bool
 	isPresignedPrebuilt bool
 
@@ -38,6 +39,11 @@
 	classLoaderContexts dexpreopt.ClassLoaderContextMap
 
 	builtInstalled string
+
+	// A path to a dexpreopt.config file generated by Soong for libraries that may be used as a
+	// <uses-library> by Make modules. The path is passed to Make via LOCAL_SOONG_DEXPREOPT_CONFIG
+	// variable. If the path is nil, no config is generated (which is the case for apps and tests).
+	configPath android.WritablePath
 }
 
 type DexpreoptProperties struct {
@@ -117,7 +123,40 @@
 	// the dexpreopter struct hasn't been fully initialized before we're called,
 	// e.g. in aar.go. This keeps the behaviour that dexpreopting is effectively
 	// disabled, even if installable is true.
-	if d.dexpreoptDisabled(ctx) || d.installPath.Base() == "." {
+	if d.installPath.Base() == "." {
+		return
+	}
+
+	dexLocation := android.InstallPathToOnDevicePath(ctx, d.installPath)
+
+	buildPath := android.PathForModuleOut(ctx, "dexpreopt", ctx.ModuleName()+".jar").OutputPath
+
+	providesUsesLib := ctx.ModuleName()
+	if ulib, ok := ctx.Module().(ProvidesUsesLib); ok {
+		name := ulib.ProvidesUsesLib()
+		if name != nil {
+			providesUsesLib = *name
+		}
+	}
+
+	if !d.isApp && !d.isTest {
+		// Slim dexpreopt config is serialized to dexpreopt.config files and used by
+		// dex_preopt_config_merger.py to get information about <uses-library> dependencies.
+		// Note that it might be needed even if dexpreopt is disabled for this module.
+		slimDexpreoptConfig := &dexpreopt.ModuleConfig{
+			Name:                 ctx.ModuleName(),
+			DexLocation:          dexLocation,
+			BuildPath:            buildPath,
+			EnforceUsesLibraries: d.enforceUsesLibs,
+			ProvidesUsesLibrary:  providesUsesLib,
+			ClassLoaderContexts:  d.classLoaderContexts,
+			// The rest of the fields are not needed.
+		}
+		d.configPath = android.PathForModuleOut(ctx, "dexpreopt", "dexpreopt.config")
+		dexpreopt.WriteSlimModuleConfigForMake(ctx, slimDexpreoptConfig, d.configPath)
+	}
+
+	if d.dexpreoptDisabled(ctx) {
 		return
 	}
 
@@ -157,8 +196,6 @@
 	// The image locations for all Android variants are identical.
 	imageLocations := bootImage.getAnyAndroidVariant().imageLocations()
 
-	dexLocation := android.InstallPathToOnDevicePath(ctx, d.installPath)
-
 	var profileClassListing android.OptionalPath
 	var profileBootListing android.OptionalPath
 	profileIsTextListing := false
@@ -177,10 +214,11 @@
 		}
 	}
 
+	// Full dexpreopt config, used to create dexpreopt build rules.
 	dexpreoptConfig := &dexpreopt.ModuleConfig{
 		Name:            ctx.ModuleName(),
 		DexLocation:     dexLocation,
-		BuildPath:       android.PathForModuleOut(ctx, "dexpreopt", ctx.ModuleName()+".jar").OutputPath,
+		BuildPath:       buildPath,
 		DexPath:         dexJarFile,
 		ManifestPath:    d.manifestFile,
 		UncompressedDex: d.uncompressedDex,
@@ -192,6 +230,7 @@
 		ProfileBootListing:   profileBootListing,
 
 		EnforceUsesLibraries: d.enforceUsesLibs,
+		ProvidesUsesLibrary:  providesUsesLib,
 		ClassLoaderContexts:  d.classLoaderContexts,
 
 		Archs:                   archs,
diff --git a/java/dexpreopt_bootjars.go b/java/dexpreopt_bootjars.go
index 36d0d30..2a7eb42 100644
--- a/java/dexpreopt_bootjars.go
+++ b/java/dexpreopt_bootjars.go
@@ -392,22 +392,6 @@
 	dexpreoptConfigForMake android.WritablePath
 }
 
-// Accessor function for the apex package. Returns nil if dexpreopt is disabled.
-func DexpreoptedArtApexJars(ctx android.BuilderContext) map[android.ArchType]android.OutputPaths {
-	if SkipDexpreoptBootJars(ctx) {
-		return nil
-	}
-	// Include dexpreopt files for the primary boot image.
-	files := map[android.ArchType]android.OutputPaths{}
-	for _, variant := range artBootImageConfig(ctx).variants {
-		// We also generate boot images for host (for testing), but we don't need those in the apex.
-		if variant.target.Os == android.Android {
-			files[variant.target.Arch.ArchType] = variant.imagesDeps
-		}
-	}
-	return files
-}
-
 // Provide paths to boot images for use by modules that depend upon them.
 //
 // The build rules are created in GenerateSingletonBuildActions().
diff --git a/java/dexpreopt_test.go b/java/dexpreopt_test.go
index 5550a4c..a9e0773 100644
--- a/java/dexpreopt_test.go
+++ b/java/dexpreopt_test.go
@@ -148,7 +148,7 @@
 		t.Run(test.name, func(t *testing.T) {
 			ctx, _ := testJava(t, test.bp)
 
-			dexpreopt := ctx.ModuleForTests("foo", "android_common").MaybeDescription("dexpreopt")
+			dexpreopt := ctx.ModuleForTests("foo", "android_common").MaybeRule("dexpreopt")
 			enabled := dexpreopt.Rule != nil
 
 			if enabled != test.enabled {
diff --git a/java/hiddenapi.go b/java/hiddenapi.go
index 71f1e57..2cd025e 100644
--- a/java/hiddenapi.go
+++ b/java/hiddenapi.go
@@ -28,10 +28,40 @@
 }, "outFlag", "stubAPIFlags")
 
 type hiddenAPI struct {
-	bootDexJarPath  android.Path
-	flagsCSVPath    android.Path
-	indexCSVPath    android.Path
+	// The path to the dex jar that is in the boot class path. If this is nil then the associated
+	// module is not a boot jar, but could be one of the <x>-hiddenapi modules that provide additional
+	// annotations for the <x> boot dex jar but which do not actually provide a boot dex jar
+	// themselves.
+	bootDexJarPath android.Path
+
+	// The path to the CSV file that contains mappings from Java signature to various flags derived
+	// from annotations in the source, e.g. whether it is public or the sdk version above which it
+	// can no longer be used.
+	//
+	// It is created by the Class2NonSdkList tool which processes the .class files in the class
+	// implementation jar looking for UnsupportedAppUsage and CovariantReturnType annotations. The
+	// tool also consumes the hiddenAPISingletonPathsStruct.stubFlags file in order to perform
+	// consistency checks on the information in the annotations and to filter out bridge methods
+	// that are already part of the public API.
+	flagsCSVPath android.Path
+
+	// The path to the CSV file that contains mappings from Java signature to the value of properties
+	// specified on UnsupportedAppUsage annotations in the source.
+	//
+	// Like the flagsCSVPath file this is also created by the Class2NonSdkList in the same way.
+	// Although the two files could potentially be created in a single invocation of the
+	// Class2NonSdkList at the moment they are created using their own invocation, with the behavior
+	// being determined by the property that is used.
 	metadataCSVPath android.Path
+
+	// The path to the CSV file that contains mappings from Java signature to source location
+	// information.
+	//
+	// It is created by the merge_csv tool which processes the class implementation jar, extracting
+	// all the files ending in .uau (which are CSV files) and merges them together. The .uau files are
+	// created by the unsupported app usage annotation processor during compilation of the class
+	// implementation jar.
+	indexCSVPath android.Path
 }
 
 func (h *hiddenAPI) flagsCSV() android.Path {
@@ -78,11 +108,9 @@
 		// not on the list then that will cause failures in the CtsHiddenApiBlacklist...
 		// tests.
 		if inList(bootJarName, ctx.Config().BootJars()) {
-			// Derive the greylist from classes jar.
-			flagsCSV := android.PathForModuleOut(ctx, "hiddenapi", "flags.csv")
-			metadataCSV := android.PathForModuleOut(ctx, "hiddenapi", "metadata.csv")
-			indexCSV := android.PathForModuleOut(ctx, "hiddenapi", "index.csv")
-			h.hiddenAPIGenerateCSV(ctx, flagsCSV, metadataCSV, indexCSV, implementationJar)
+			// Create ninja rules to generate various CSV files needed by hiddenapi and store the paths
+			// in the hiddenAPI structure.
+			h.hiddenAPIGenerateCSV(ctx, implementationJar)
 
 			// If this module is actually on the boot jars list and not providing
 			// hiddenapi information for a module on the boot jars list then encode
@@ -106,9 +134,10 @@
 	return dexJar
 }
 
-func (h *hiddenAPI) hiddenAPIGenerateCSV(ctx android.ModuleContext, flagsCSV, metadataCSV, indexCSV android.WritablePath, classesJar android.Path) {
+func (h *hiddenAPI) hiddenAPIGenerateCSV(ctx android.ModuleContext, classesJar android.Path) {
 	stubFlagsCSV := hiddenAPISingletonPaths(ctx).stubFlags
 
+	flagsCSV := android.PathForModuleOut(ctx, "hiddenapi", "flags.csv")
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        hiddenAPIGenerateCSVRule,
 		Description: "hiddenapi flags",
@@ -122,6 +151,7 @@
 	})
 	h.flagsCSVPath = flagsCSV
 
+	metadataCSV := android.PathForModuleOut(ctx, "hiddenapi", "metadata.csv")
 	ctx.Build(pctx, android.BuildParams{
 		Rule:        hiddenAPIGenerateCSVRule,
 		Description: "hiddenapi metadata",
@@ -135,6 +165,7 @@
 	})
 	h.metadataCSVPath = metadataCSV
 
+	indexCSV := android.PathForModuleOut(ctx, "hiddenapi", "index.csv")
 	rule := android.NewRuleBuilder(pctx, ctx)
 	rule.Command().
 		BuiltTool("merge_csv").
diff --git a/java/hiddenapi_singleton.go b/java/hiddenapi_singleton.go
index 4bd255c..ccb8745 100644
--- a/java/hiddenapi_singleton.go
+++ b/java/hiddenapi_singleton.go
@@ -28,9 +28,64 @@
 }
 
 type hiddenAPISingletonPathsStruct struct {
-	flags     android.OutputPath
-	index     android.OutputPath
-	metadata  android.OutputPath
+	// The path to the CSV file that contains the flags that will be encoded into the dex boot jars.
+	//
+	// It is created by the generate_hiddenapi_lists.py tool that is passed the stubFlags along with
+	// a number of additional files that are used to augment the information in the stubFlags with
+	// manually curated data.
+	flags android.OutputPath
+
+	// The path to the CSV index file that contains mappings from Java signature to source location
+	// information for all Java elements annotated with the UnsupportedAppUsage annotation in the
+	// source of all the boot jars.
+	//
+	// It is created by the merge_csv tool which merges all the hiddenAPI.indexCSVPath files that have
+	// been created by the rest of the build. That includes the index files generated for
+	// <x>-hiddenapi modules.
+	index android.OutputPath
+
+	// The path to the CSV metadata file that contains mappings from Java signature to the value of
+	// properties specified on UnsupportedAppUsage annotations in the source of all the boot jars.
+	//
+	// It is created by the merge_csv tool which merges all the hiddenAPI.metadataCSVPath files that
+	// have been created by the rest of the build. That includes the metadata files generated for
+	// <x>-hiddenapi modules.
+	metadata android.OutputPath
+
+	// The path to the CSV metadata file that contains mappings from Java signature to flags obtained
+	// from the public, system and test API stubs.
+	//
+	// This is created by the hiddenapi tool which is given dex files for the public, system and test
+	// API stubs (including product specific stubs) along with dex boot jars, so does not include
+	// <x>-hiddenapi modules. For each API surface (i.e. public, system, test) it records which
+	// members in the dex boot jars match a member in the dex stub jars for that API surface and then
+	// outputs a file containing the signatures of all members in the dex boot jars along with the
+	// flags that indicate which API surface it belongs, if any.
+	//
+	// e.g. a dex member that matches a member in the public dex stubs would have flags
+	// "public-api,system-api,test-api" set (as system and test are both supersets of public). A dex
+	// member that didn't match a member in any of the dex stubs is still output it just has an empty
+	// set of flags.
+	//
+	// The notion of matching is quite complex, it is not restricted to just exact matching but also
+	// follows the Java inheritance rules. e.g. if a method is public then all overriding/implementing
+	// methods are also public. If an interface method is public and a class inherits an
+	// implementation of that method from a super class then that super class method is also public.
+	// That ensures that any method that can be called directly by an App through a public method is
+	// visible to that App.
+	//
+	// Propagating the visibility of members across the inheritance hierarchy at build time will cause
+	// problems when modularizing and unbundling as it that propagation can cross module boundaries.
+	// e.g. Say that a private framework class implements a public interface and inherits an
+	// implementation of one of its methods from a core platform ART class. In that case the ART
+	// implementation method needs to be marked as public which requires the build to have access to
+	// the framework implementation classes at build time. The work to rectify this is being tracked
+	// at http://b/178693149.
+	//
+	// This file (or at least those items marked as being in the public-api) is used by hiddenapi when
+	// creating the metadata and flags for the individual modules in order to perform consistency
+	// checks and filter out bridge methods that are part of the public API. The latter relies on the
+	// propagation of visibility across the inheritance hierarchy.
 	stubFlags android.OutputPath
 }
 
diff --git a/java/testing.go b/java/testing.go
index 31ff47f..5fcf84c 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -219,16 +219,6 @@
 		dex_bootjars {
 			name: "dex_bootjars",
 		}
-
-		boot_image {
-			name: "art-boot-image",
-			image_name: "art",
-		}
-
-		boot_image {
-			name: "framework-boot-image",
-			image_name: "boot",
-		}
 `
 
 	return bp
diff --git a/ui/build/build.go b/ui/build/build.go
index 926da31..215a6c8 100644
--- a/ui/build/build.go
+++ b/ui/build/build.go
@@ -252,6 +252,11 @@
 	if what&BuildSoong != 0 {
 		// Run Soong
 		runSoong(ctx, config)
+
+		if config.Environment().IsEnvTrue("GENERATE_BAZEL_FILES") {
+			// Return early, if we're using Soong as the bp2build converter.
+			return
+		}
 	}
 
 	if what&BuildKati != 0 {
diff --git a/ui/build/soong.go b/ui/build/soong.go
index 6a93672..899ab5d 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -169,8 +169,11 @@
 	// This build generates <builddir>/build.ninja, which is used later by build/soong/ui/build/build.go#Build().
 	ninja("bootstrap", ".bootstrap/build.ninja")
 
-	soongBuildMetrics := loadSoongBuildMetrics(ctx, config)
-	logSoongBuildMetrics(ctx, soongBuildMetrics)
+	var soongBuildMetrics *soong_metrics_proto.SoongBuildMetrics
+	if shouldCollectBuildSoongMetrics(config) {
+		soongBuildMetrics := loadSoongBuildMetrics(ctx, config)
+		logSoongBuildMetrics(ctx, soongBuildMetrics)
+	}
 
 	distGzipFile(ctx, config, config.SoongNinjaFile(), "soong")
 
@@ -179,11 +182,16 @@
 		distGzipFile(ctx, config, config.SoongMakeVarsMk(), "soong")
 	}
 
-	if ctx.Metrics != nil {
+	if shouldCollectBuildSoongMetrics(config) && ctx.Metrics != nil {
 		ctx.Metrics.SetSoongBuildMetrics(soongBuildMetrics)
 	}
 }
 
+func shouldCollectBuildSoongMetrics(config Config) bool {
+	// Do not collect metrics protobuf if the soong_build binary ran as the bp2build converter.
+	return config.Environment().IsFalse("GENERATE_BAZEL_FILES")
+}
+
 func loadSoongBuildMetrics(ctx Context, config Config) *soong_metrics_proto.SoongBuildMetrics {
 	soongBuildMetricsFile := filepath.Join(config.OutDir(), "soong", "soong_build_metrics.pb")
 	buf, err := ioutil.ReadFile(soongBuildMetricsFile)