Merge "Move creation of paths into hiddenAPIGenerateCSV"
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/cc/androidmk.go b/cc/androidmk.go
index 040aa0b..9345728 100644
--- a/cc/androidmk.go
+++ b/cc/androidmk.go
@@ -168,7 +168,7 @@
 	}
 }
 
-func androidMkWriteTestData(data []android.DataPath, ctx AndroidMkContext, entries *android.AndroidMkEntries) {
+func AndroidMkWriteTestData(data []android.DataPath, entries *android.AndroidMkEntries) {
 	testFiles := android.AndroidMkDataPaths(data)
 	if len(testFiles) > 0 {
 		entries.ExtraEntries = append(entries.ExtraEntries, func(entries *android.AndroidMkEntries) {
@@ -353,7 +353,7 @@
 	for _, srcPath := range benchmark.data {
 		dataPaths = append(dataPaths, android.DataPath{SrcPath: srcPath})
 	}
-	androidMkWriteTestData(dataPaths, ctx, entries)
+	AndroidMkWriteTestData(dataPaths, entries)
 }
 
 func (test *testBinary) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
@@ -378,7 +378,7 @@
 		}
 	})
 
-	androidMkWriteTestData(test.data, ctx, entries)
+	AndroidMkWriteTestData(test.data, entries)
 	androidMkWriteExtraTestConfigs(test.extraTestConfigs, entries)
 }
 
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/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/rust/androidmk.go b/rust/androidmk.go
index 0307727..1a286f7 100644
--- a/rust/androidmk.go
+++ b/rust/androidmk.go
@@ -18,6 +18,7 @@
 	"path/filepath"
 
 	"android/soong/android"
+	"android/soong/cc"
 )
 
 type AndroidMkContext interface {
@@ -85,7 +86,8 @@
 }
 
 func (test *testDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
-	test.binaryDecorator.AndroidMk(ctx, ret)
+	ctx.SubAndroidMk(ret, test.binaryDecorator)
+
 	ret.Class = "NATIVE_TESTS"
 	ret.ExtraEntries = append(ret.ExtraEntries, func(entries *android.AndroidMkEntries) {
 		entries.AddCompatibilityTestSuites(test.Properties.Test_suites...)
@@ -95,7 +97,8 @@
 		entries.SetBoolIfTrue("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", !BoolDefault(test.Properties.Auto_gen_config, true))
 		entries.SetBoolIfTrue("LOCAL_IS_UNIT_TEST", Bool(test.Properties.Test_options.Unit_test))
 	})
-	// TODO(chh): add test data with androidMkWriteTestData(test.data, ctx, ret)
+
+	cc.AndroidMkWriteTestData(test.data, ret)
 }
 
 func (library *libraryDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
diff --git a/rust/rust_test.go b/rust/rust_test.go
index fc7f47e..abc9af9 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -116,6 +116,7 @@
 		"foo.proto":       nil,
 		"liby.so":         nil,
 		"libz.so":         nil,
+		"data.txt":        nil,
 	}
 }
 
diff --git a/rust/test.go b/rust/test.go
index 35e04ff..92b4860 100644
--- a/rust/test.go
+++ b/rust/test.go
@@ -43,6 +43,10 @@
 	// installed into.
 	Test_suites []string `android:"arch_variant"`
 
+	// list of files or filegroup modules that provide data that should be installed alongside
+	// the test
+	Data []string `android:"path,arch_variant"`
+
 	// Flag to indicate whether or not to create test config automatically. If AndroidTest.xml
 	// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
 	// explicitly.
@@ -62,6 +66,12 @@
 	*binaryDecorator
 	Properties TestProperties
 	testConfig android.Path
+
+	data []android.DataPath
+}
+
+func (test *testDecorator) dataPaths() []android.DataPath {
+	return test.data
 }
 
 func (test *testDecorator) nativeCoverage() bool {
@@ -89,7 +99,6 @@
 	}
 
 	module.compiler = test
-	module.AddProperties(&test.Properties)
 	return module, test
 }
 
@@ -105,6 +114,12 @@
 		nil,
 		test.Properties.Auto_gen_config)
 
+	dataSrcPaths := android.PathsForModuleSrc(ctx, test.Properties.Data)
+
+	for _, dataSrcPath := range dataSrcPaths {
+		test.data = append(test.data, android.DataPath{SrcPath: dataSrcPath})
+	}
+
 	// default relative install path is module name
 	if !Bool(test.Properties.No_named_install_directory) {
 		test.baseCompiler.relative = ctx.ModuleName()
diff --git a/rust/test_test.go b/rust/test_test.go
index fea2ad0..892761a 100644
--- a/rust/test_test.go
+++ b/rust/test_test.go
@@ -26,6 +26,7 @@
 		rust_test_host {
 			name: "my_test",
 			srcs: ["foo.rs"],
+			data: ["data.txt"],
 		}`)
 
 	testingModule := ctx.ModuleForTests("my_test", "linux_glibc_x86_64")
@@ -34,6 +35,12 @@
 	if !strings.Contains(outPath, expectedOut) {
 		t.Errorf("wrong output path: %v;  expected: %v", outPath, expectedOut)
 	}
+
+	dataPaths := testingModule.Module().(*Module).compiler.(*testDecorator).dataPaths()
+	if len(dataPaths) != 1 {
+		t.Errorf("expected exactly one test data file. test data files: [%s]", dataPaths)
+		return
+	}
 }
 
 func TestRustTestLinkage(t *testing.T) {
diff --git a/scripts/OWNERS b/scripts/OWNERS
index c4c39d5..8198083 100644
--- a/scripts/OWNERS
+++ b/scripts/OWNERS
@@ -1,4 +1,5 @@
 per-file system-clang-format,system-clang-format-2 = enh@google.com,smoreland@google.com
 per-file build-mainline-modules.sh = ngeoffray@google.com,paulduffin@google.com,mast@google.com
+per-file build-aml-prebuilts.sh = ngeoffray@google.com,paulduffin@google.com,mast@google.com
 per-file construct_context.py = ngeoffray@google.com,calin@google.com,mathieuc@google.com,skvadrik@google.com
 per-file conv_linker_config.py = kiyoungkim@google.com, jiyong@google.com, jooyung@google.com
diff --git a/scripts/build-aml-prebuilts.sh b/scripts/build-aml-prebuilts.sh
new file mode 100755
index 0000000..4b08ac3
--- /dev/null
+++ b/scripts/build-aml-prebuilts.sh
@@ -0,0 +1,131 @@
+#!/bin/bash -e
+
+# This is a wrapper around "m" that builds the given modules in multi-arch mode
+# for all architectures supported by Mainline modules. The make (kati) stage is
+# skipped, so the build targets in the arguments can only be Soong modules or
+# intermediate output files - make targets and normal installed paths are not
+# supported.
+#
+# This script is typically used with "sdk" or "module_export" modules, which
+# Soong will install in $OUT_DIR/soong/mainline-sdks (cf
+# PathForMainlineSdksInstall in android/paths.go).
+
+export OUT_DIR=${OUT_DIR:-out}
+
+if [ -e ${OUT_DIR}/soong/.soong.kati_enabled ]; then
+  # If ${OUT_DIR} has been created without --skip-make, Soong will create an
+  # ${OUT_DIR}/soong/build.ninja that leaves out many targets which are
+  # expected to be supplied by the .mk files, and that might cause errors in
+  # "m --skip-make" below. We therefore default to a different out dir
+  # location in that case.
+  AML_OUT_DIR=out/aml
+  echo "Avoiding in-make OUT_DIR '${OUT_DIR}' - building in '${AML_OUT_DIR}' instead"
+  OUT_DIR=${AML_OUT_DIR}
+fi
+
+if [ ! -e "build/envsetup.sh" ]; then
+  echo "$0 must be run from the top of the tree"
+  exit 1
+fi
+
+source build/envsetup.sh
+
+my_get_build_var() {
+  # get_build_var will run Soong in normal in-make mode where it creates
+  # .soong.kati_enabled. That would clobber our real out directory, so we need
+  # to run it in a different one.
+  OUT_DIR=${OUT_DIR}/get_build_var get_build_var "$@"
+}
+
+readonly SOONG_OUT=${OUT_DIR}/soong
+mkdir -p ${SOONG_OUT}
+
+# Some Soong build rules may require this, and the failure mode if it's missing
+# is confusing (b/172548608).
+readonly BUILD_NUMBER="$(my_get_build_var BUILD_NUMBER)"
+echo -n ${BUILD_NUMBER} > ${SOONG_OUT}/build_number.txt
+
+readonly PLATFORM_SDK_VERSION="$(my_get_build_var PLATFORM_SDK_VERSION)"
+readonly PLATFORM_VERSION="$(my_get_build_var PLATFORM_VERSION)"
+PLATFORM_VERSION_ALL_CODENAMES="$(my_get_build_var PLATFORM_VERSION_ALL_CODENAMES)"
+
+# PLATFORM_VERSION_ALL_CODENAMES is a comma separated list like O,P. We need to
+# turn this into ["O","P"].
+PLATFORM_VERSION_ALL_CODENAMES="${PLATFORM_VERSION_ALL_CODENAMES/,/'","'}"
+PLATFORM_VERSION_ALL_CODENAMES="[\"${PLATFORM_VERSION_ALL_CODENAMES}\"]"
+
+# Get the list of missing <uses-library> modules and convert it to a JSON array
+# (quote module names, add comma separator and wrap in brackets).
+MISSING_USES_LIBRARIES="$(my_get_build_var INTERNAL_PLATFORM_MISSING_USES_LIBRARIES)"
+MISSING_USES_LIBRARIES="[$(echo $MISSING_USES_LIBRARIES | sed -e 's/\([^ ]\+\)/\"\1\"/g' -e 's/[ ]\+/, /g')]"
+
+# Logic from build/make/core/goma.mk
+if [ "${USE_GOMA}" = true ]; then
+  if [ -n "${GOMA_DIR}" ]; then
+    goma_dir="${GOMA_DIR}"
+  else
+    goma_dir="${HOME}/goma"
+  fi
+  GOMA_CC="${goma_dir}/gomacc"
+  export CC_WRAPPER="${CC_WRAPPER}${CC_WRAPPER:+ }${GOMA_CC}"
+  export CXX_WRAPPER="${CXX_WRAPPER}${CXX_WRAPPER:+ }${GOMA_CC}"
+  export JAVAC_WRAPPER="${JAVAC_WRAPPER}${JAVAC_WRAPPER:+ }${GOMA_CC}"
+else
+  USE_GOMA=false
+fi
+
+readonly SOONG_VARS=${SOONG_OUT}/soong.variables
+
+# Aml_abis: true
+#   -  This flag configures Soong to compile for all architectures required for
+#      Mainline modules.
+# CrossHost: linux_bionic
+# CrossHostArch: x86_64
+#   -  Enable Bionic on host as ART needs prebuilts for it.
+# VendorVars.art_mdoule.source_build
+#   -  TODO(b/172480615): Change default to false when platform uses ART Module
+#      prebuilts by default.
+cat > ${SOONG_VARS}.new << EOF
+{
+    "BuildNumberFile": "build_number.txt",
+
+    "Platform_version_name": "${PLATFORM_VERSION}",
+    "Platform_sdk_version": ${PLATFORM_SDK_VERSION},
+    "Platform_sdk_codename": "${PLATFORM_VERSION}",
+    "Platform_version_active_codenames": ${PLATFORM_VERSION_ALL_CODENAMES},
+
+    "DeviceName": "generic_arm64",
+    "HostArch": "x86_64",
+    "HostSecondaryArch": "x86",
+    "CrossHost": "linux_bionic",
+    "CrossHostArch": "x86_64",
+    "Aml_abis": true,
+
+    "Allow_missing_dependencies": ${SOONG_ALLOW_MISSING_DEPENDENCIES:-false},
+    "Unbundled_build": ${TARGET_BUILD_UNBUNDLED:-false},
+    "UseGoma": ${USE_GOMA},
+
+    "VendorVars": {
+        "art_module": {
+            "source_build": "${ENABLE_ART_SOURCE_BUILD:-true}"
+        }
+    },
+
+    "MissingUsesLibraries": ${MISSING_USES_LIBRARIES}
+}
+EOF
+
+if [ -f ${SOONG_VARS} ] && cmp -s ${SOONG_VARS} ${SOONG_VARS}.new; then
+  # Don't touch soong.variables if we don't have to, to avoid Soong rebuilding
+  # the ninja file when it isn't necessary.
+  rm ${SOONG_VARS}.new
+else
+  mv ${SOONG_VARS}.new ${SOONG_VARS}
+fi
+
+# We use force building LLVM components flag (even though we actually don't
+# compile them) because we don't have bionic host prebuilts
+# for them.
+export FORCE_BUILD_LLVM_COMPONENTS=true
+
+m --skip-make "$@"
diff --git a/scripts/build-mainline-modules.sh b/scripts/build-mainline-modules.sh
index d6c6f94..ea62af4 100755
--- a/scripts/build-mainline-modules.sh
+++ b/scripts/build-mainline-modules.sh
@@ -84,16 +84,10 @@
 done
 
 # Create multi-archs SDKs in a different out directory. The multi-arch script
-# uses Soong in --skip-kati mode which cannot use the same directory as normal
+# uses Soong in --skip-make mode which cannot use the same directory as normal
 # mode with make.
 export OUT_DIR=${OUT_DIR}/aml
-# We use force building LLVM components flag (even though we actually don't
-# compile them) because we don't have bionic host prebuilts
-# for them.
-export FORCE_BUILD_LLVM_COMPONENTS=true
-
-echo_and_run build/soong/soong_ui.bash --make-mode --skip-kati \
-  TARGET_PRODUCT=mainline_sdk "$@" ${MODULES_SDK_AND_EXPORTS[@]}
+echo_and_run build/soong/scripts/build-aml-prebuilts.sh ${MODULES_SDK_AND_EXPORTS[@]}
 
 rm -rf ${DIST_DIR}/mainline-sdks
 echo_and_run cp -R ${OUT_DIR}/soong/mainline-sdks ${DIST_DIR}
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)