Merge "Add external/musl to the bp2build allowlists"
diff --git a/android/allowlists/allowlists.go b/android/allowlists/allowlists.go
index 957d3ea..73021d7 100644
--- a/android/allowlists/allowlists.go
+++ b/android/allowlists/allowlists.go
@@ -732,8 +732,7 @@
 		"platform_tools_properties", "build_tools_source_properties", // TODO(b/203369847): multiple genrules in the same package creating the same file
 
 		// aar support
-		"prebuilt_car-ui-androidx-core-common",         // TODO(b/224773339), genrule dependency creates an .aar, not a .jar
-		"prebuilt_platform-robolectric-4.5.1-prebuilt", // aosp/1999250, needs .aar support in Jars
+		"prebuilt_car-ui-androidx-core-common", // TODO(b/224773339), genrule dependency creates an .aar, not a .jar
 		// ERROR: The dependencies for the following 1 jar(s) are not complete.
 		// 1.bazel-out/android_target-fastbuild/bin/prebuilts/tools/common/m2/_aar/robolectric-monitor-1.0.2-alpha1/classes_and_libs_merged.jar
 		"prebuilt_robolectric-monitor-1.0.2-alpha1",
@@ -1354,7 +1353,6 @@
 		"prebuilt_kotlin-stdlib-jdk8",
 		"prebuilt_kotlin-test",
 		// TODO(b/217750501) exclude_files property not supported
-		"prebuilt_platform-robolectric-4.5.1-prebuilt",
 		"prebuilt_currysrc_org.eclipse",
 	}
 
@@ -1365,5 +1363,8 @@
 	// Staging-mode allowlist. Modules in this list are only built
 	// by Bazel with --bazel-mode-staging. This list should contain modules
 	// which will soon be added to the prod allowlist.
-	StagingMixedBuildsEnabledList = []string{"com.android.tzdata"}
+	StagingMixedBuildsEnabledList = []string{
+		"com.android.adbd",
+		"com.android.tzdata",
+	}
 )
diff --git a/android/androidmk.go b/android/androidmk.go
index 846d506..6346401 100644
--- a/android/androidmk.go
+++ b/android/androidmk.go
@@ -489,11 +489,11 @@
 // Write the license variables to Make for AndroidMkData.Custom(..) methods that do not call WriteAndroidMkData(..)
 // It's required to propagate the license metadata even for module types that have non-standard interfaces to Make.
 func (a *AndroidMkEntries) WriteLicenseVariables(w io.Writer) {
-	fmt.Fprintln(w, "LOCAL_LICENSE_KINDS :=", strings.Join(a.EntryMap["LOCAL_LICENSE_KINDS"], " "))
-	fmt.Fprintln(w, "LOCAL_LICENSE_CONDITIONS :=", strings.Join(a.EntryMap["LOCAL_LICENSE_CONDITIONS"], " "))
-	fmt.Fprintln(w, "LOCAL_NOTICE_FILE :=", strings.Join(a.EntryMap["LOCAL_NOTICE_FILE"], " "))
+	AndroidMkEmitAssignList(w, "LOCAL_LICENSE_KINDS", a.EntryMap["LOCAL_LICENSE_KINDS"])
+	AndroidMkEmitAssignList(w, "LOCAL_LICENSE_CONDITIONS", a.EntryMap["LOCAL_LICENSE_CONDITIONS"])
+	AndroidMkEmitAssignList(w, "LOCAL_NOTICE_FILE", a.EntryMap["LOCAL_NOTICE_FILE"])
 	if pn, ok := a.EntryMap["LOCAL_LICENSE_PACKAGE_NAME"]; ok {
-		fmt.Fprintln(w, "LOCAL_LICENSE_PACKAGE_NAME :=", strings.Join(pn, " "))
+		AndroidMkEmitAssignList(w, "LOCAL_LICENSE_PACKAGE_NAME", pn)
 	}
 }
 
@@ -672,7 +672,7 @@
 
 	w.Write(a.header.Bytes())
 	for _, name := range a.entryOrder {
-		fmt.Fprintln(w, name+" := "+strings.Join(a.EntryMap[name], " "))
+		AndroidMkEmitAssignList(w, name, a.EntryMap[name])
 	}
 	w.Write(a.footer.Bytes())
 }
@@ -972,3 +972,28 @@
 	}
 	return testFiles
 }
+
+// AndroidMkEmitAssignList emits the line
+//
+//	VAR := ITEM ...
+//
+// Items are the elements to the given set of lists
+// If all the passed lists are empty, no line will be emitted
+func AndroidMkEmitAssignList(w io.Writer, varName string, lists ...[]string) {
+	doPrint := false
+	for _, l := range lists {
+		if doPrint = len(l) > 0; doPrint {
+			break
+		}
+	}
+	if !doPrint {
+		return
+	}
+	fmt.Fprint(w, varName, " :=")
+	for _, l := range lists {
+		for _, item := range l {
+			fmt.Fprint(w, " ", item)
+		}
+	}
+	fmt.Fprintln(w)
+}
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index 122495f..908b2d6 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -16,7 +16,6 @@
 
 import (
 	"bytes"
-	"errors"
 	"fmt"
 	"os"
 	"os/exec"
@@ -376,107 +375,74 @@
 }
 
 func NewBazelContext(c *config) (BazelContext, error) {
-	var modulesDefaultToBazel bool
 	disabledModules := map[string]bool{}
 	enabledModules := map[string]bool{}
+	addToStringSet := func(set map[string]bool, items []string) {
+		for _, item := range items {
+			set[item] = true
+		}
+	}
 
 	switch c.BuildMode {
 	case BazelProdMode:
-		modulesDefaultToBazel = false
-
-		for _, enabledProdModule := range allowlists.ProdMixedBuildsEnabledList {
-			enabledModules[enabledProdModule] = true
-		}
-
+		addToStringSet(enabledModules, allowlists.ProdMixedBuildsEnabledList)
 		for enabledAdHocModule := range c.BazelModulesForceEnabledByFlag() {
 			enabledModules[enabledAdHocModule] = true
 		}
 	case BazelStagingMode:
-		modulesDefaultToBazel = false
 		// Staging mode includes all prod modules plus all staging modules.
-		for _, enabledProdModule := range allowlists.ProdMixedBuildsEnabledList {
-			enabledModules[enabledProdModule] = true
-		}
-		for _, enabledStagingMode := range allowlists.StagingMixedBuildsEnabledList {
-			enabledModules[enabledStagingMode] = true
-		}
-
+		addToStringSet(enabledModules, allowlists.ProdMixedBuildsEnabledList)
+		addToStringSet(enabledModules, allowlists.StagingMixedBuildsEnabledList)
 		for enabledAdHocModule := range c.BazelModulesForceEnabledByFlag() {
 			enabledModules[enabledAdHocModule] = true
 		}
 	case BazelDevMode:
-		modulesDefaultToBazel = true
-
 		// Don't use partially-converted cc_library targets in mixed builds,
 		// since mixed builds would generally rely on both static and shared
 		// variants of a cc_library.
 		for staticOnlyModule := range GetBp2BuildAllowList().ccLibraryStaticOnly {
 			disabledModules[staticOnlyModule] = true
 		}
-		for _, disabledDevModule := range allowlists.MixedBuildsDisabledList {
-			disabledModules[disabledDevModule] = true
-		}
+		addToStringSet(disabledModules, allowlists.MixedBuildsDisabledList)
 	default:
 		return noopBazelContext{}, nil
 	}
 
-	p, err := bazelPathsFromConfig(c)
-	if err != nil {
-		return nil, err
+	paths := bazelPaths{
+		soongOutDir: c.soongOutDir,
 	}
-
+	var missing []string
+	vars := []struct {
+		name string
+		ptr  *string
+	}{
+		{"BAZEL_HOME", &paths.homeDir},
+		{"BAZEL_PATH", &paths.bazelPath},
+		{"BAZEL_OUTPUT_BASE", &paths.outputBase},
+		{"BAZEL_WORKSPACE", &paths.workspaceDir},
+		{"BAZEL_METRICS_DIR", &paths.metricsDir},
+		{"BAZEL_DEPS_FILE", &paths.bazelDepsFile},
+	}
+	for _, v := range vars {
+		if s := c.Getenv(v.name); len(s) > 1 {
+			*v.ptr = s
+		} else {
+			missing = append(missing, v.name)
+		}
+	}
+	if len(missing) > 0 {
+		return nil, fmt.Errorf("missing required env vars to use bazel: %s", missing)
+	}
 	return &bazelContext{
 		bazelRunner:           &builtinBazelRunner{},
-		paths:                 p,
+		paths:                 &paths,
 		requests:              make(map[cqueryKey]bool),
-		modulesDefaultToBazel: modulesDefaultToBazel,
+		modulesDefaultToBazel: c.BuildMode == BazelDevMode,
 		bazelEnabledModules:   enabledModules,
 		bazelDisabledModules:  disabledModules,
 	}, nil
 }
 
-func bazelPathsFromConfig(c *config) (*bazelPaths, error) {
-	p := bazelPaths{
-		soongOutDir: c.soongOutDir,
-	}
-	var missingEnvVars []string
-	if len(c.Getenv("BAZEL_HOME")) > 1 {
-		p.homeDir = c.Getenv("BAZEL_HOME")
-	} else {
-		missingEnvVars = append(missingEnvVars, "BAZEL_HOME")
-	}
-	if len(c.Getenv("BAZEL_PATH")) > 1 {
-		p.bazelPath = c.Getenv("BAZEL_PATH")
-	} else {
-		missingEnvVars = append(missingEnvVars, "BAZEL_PATH")
-	}
-	if len(c.Getenv("BAZEL_OUTPUT_BASE")) > 1 {
-		p.outputBase = c.Getenv("BAZEL_OUTPUT_BASE")
-	} else {
-		missingEnvVars = append(missingEnvVars, "BAZEL_OUTPUT_BASE")
-	}
-	if len(c.Getenv("BAZEL_WORKSPACE")) > 1 {
-		p.workspaceDir = c.Getenv("BAZEL_WORKSPACE")
-	} else {
-		missingEnvVars = append(missingEnvVars, "BAZEL_WORKSPACE")
-	}
-	if len(c.Getenv("BAZEL_METRICS_DIR")) > 1 {
-		p.metricsDir = c.Getenv("BAZEL_METRICS_DIR")
-	} else {
-		missingEnvVars = append(missingEnvVars, "BAZEL_METRICS_DIR")
-	}
-	if len(c.Getenv("BAZEL_DEPS_FILE")) > 1 {
-		p.bazelDepsFile = c.Getenv("BAZEL_DEPS_FILE")
-	} else {
-		missingEnvVars = append(missingEnvVars, "BAZEL_DEPS_FILE")
-	}
-	if len(missingEnvVars) > 0 {
-		return nil, errors.New(fmt.Sprintf("missing required env vars to use bazel: %s", missingEnvVars))
-	} else {
-		return &p, nil
-	}
-}
-
 func (p *bazelPaths) BazelMetricsDir() string {
 	return p.metricsDir
 }
diff --git a/android/register.go b/android/register.go
index 6cfc205..33e9ea3 100644
--- a/android/register.go
+++ b/android/register.go
@@ -165,29 +165,30 @@
 	return ctx
 }
 
-// RegisterForBazelConversion registers an alternate shadow pipeline of
-// singletons, module types and mutators to register for converting Blueprint
-// files to semantically equivalent BUILD files.
-func (ctx *Context) RegisterForBazelConversion() {
+// Helper function to register the module types used in bp2build and
+// api_bp2build.
+func registerModuleTypes(ctx *Context) {
 	for _, t := range moduleTypes {
 		t.register(ctx)
 	}
-
 	// Required for SingletonModule types, even though we are not using them.
 	for _, t := range singletons {
 		t.register(ctx)
 	}
+}
 
+// RegisterForBazelConversion registers an alternate shadow pipeline of
+// singletons, module types and mutators to register for converting Blueprint
+// files to semantically equivalent BUILD files.
+func (ctx *Context) RegisterForBazelConversion() {
+	registerModuleTypes(ctx)
 	RegisterMutatorsForBazelConversion(ctx, bp2buildPreArchMutators)
 }
 
 // RegisterForApiBazelConversion is similar to RegisterForBazelConversion except that
 // it only generates API targets in the generated  workspace
 func (ctx *Context) RegisterForApiBazelConversion() {
-	for _, t := range moduleTypes {
-		t.register(ctx)
-	}
-
+	registerModuleTypes(ctx)
 	RegisterMutatorsForApiBazelConversion(ctx, bp2buildPreArchMutators)
 }
 
diff --git a/apex/androidmk.go b/apex/androidmk.go
index 0fc971b..b76f6bd 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -154,9 +154,7 @@
 			if a.primaryApexType && !symbolFilesNotNeeded {
 				fmt.Fprintln(w, "LOCAL_SOONG_SYMBOL_PATH :=", pathWhenActivated)
 			}
-			if len(fi.symlinks) > 0 {
-				fmt.Fprintln(w, "LOCAL_MODULE_SYMLINKS :=", strings.Join(fi.symlinks, " "))
-			}
+			android.AndroidMkEmitAssignList(w, "LOCAL_MODULE_SYMLINKS", fi.symlinks)
 			newDataPaths := []android.DataPath{}
 			for _, path := range fi.dataPaths {
 				dataOutPath := modulePath + ":" + path.SrcPath.Rel()
@@ -165,9 +163,7 @@
 					seenDataOutPaths[dataOutPath] = true
 				}
 			}
-			if len(newDataPaths) > 0 {
-				fmt.Fprintln(w, "LOCAL_TEST_DATA :=", strings.Join(android.AndroidMkDataPaths(newDataPaths), " "))
-			}
+			android.AndroidMkEmitAssignList(w, "LOCAL_TEST_DATA", android.AndroidMkDataPaths(newDataPaths))
 		} else {
 			modulePath = pathWhenActivated
 			fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", pathWhenActivated)
@@ -236,9 +232,7 @@
 			// we will have foo.apk.apk
 			fmt.Fprintln(w, "LOCAL_MODULE_STEM :=", strings.TrimSuffix(fi.stem(), ".apk"))
 			if app, ok := fi.module.(*java.AndroidApp); ok {
-				if jniCoverageOutputs := app.JniCoverageOutputs(); len(jniCoverageOutputs) > 0 {
-					fmt.Fprintln(w, "LOCAL_PREBUILT_COVERAGE_ARCHIVE :=", strings.Join(jniCoverageOutputs.Strings(), " "))
-				}
+				android.AndroidMkEmitAssignList(w, "LOCAL_PREBUILT_COVERAGE_ARCHIVE", app.JniCoverageOutputs().Strings())
 				if jniLibSymbols := app.JNISymbolsInstalls(modulePath); len(jniLibSymbols) > 0 {
 					fmt.Fprintln(w, "LOCAL_SOONG_JNI_LIBS_SYMBOLS :=", jniLibSymbols.String())
 				}
@@ -275,7 +269,7 @@
 					}
 					for _, name := range commonProperties {
 						if value, ok := apexAndroidMkData.Entries.EntryMap[name]; ok {
-							fmt.Fprintln(w, name+" := "+strings.Join(value, " "))
+							android.AndroidMkEmitAssignList(w, name, value)
 						}
 					}
 
@@ -285,9 +279,7 @@
 					for _, o := range a.overridableProperties.Overrides {
 						patterns = append(patterns, "%."+o+a.suffix)
 					}
-					if len(patterns) > 0 {
-						fmt.Fprintln(w, "LOCAL_OVERRIDES_MODULES :=", strings.Join(patterns, " "))
-					}
+					android.AndroidMkEmitAssignList(w, "LOCAL_OVERRIDES_MODULES", patterns)
 				}
 
 				// File_contexts of flattened APEXes should be merged into file_contexts.bin
@@ -306,13 +298,6 @@
 }
 
 func (a *apexBundle) writeRequiredModules(w io.Writer, moduleNames []string) {
-	if len(moduleNames) > 0 {
-		fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES +=", strings.Join(moduleNames, " "))
-	}
-	if len(a.requiredDeps) > 0 {
-		fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES +=", strings.Join(a.requiredDeps, " "))
-	}
-
 	var required []string
 	var targetRequired []string
 	var hostRequired []string
@@ -324,16 +309,9 @@
 		targetRequired = append(targetRequired, fi.targetRequiredModuleNames...)
 		hostRequired = append(hostRequired, fi.hostRequiredModuleNames...)
 	}
-
-	if len(required) > 0 {
-		fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES +=", strings.Join(required, " "))
-	}
-	if len(targetRequired) > 0 {
-		fmt.Fprintln(w, "LOCAL_TARGET_REQUIRED_MODULES +=", strings.Join(targetRequired, " "))
-	}
-	if len(hostRequired) > 0 {
-		fmt.Fprintln(w, "LOCAL_HOST_REQUIRED_MODULES +=", strings.Join(hostRequired, " "))
-	}
+	android.AndroidMkEmitAssignList(w, "LOCAL_REQUIRED_MODULES", moduleNames, a.requiredDeps, required)
+	android.AndroidMkEmitAssignList(w, "LOCAL_TARGET_REQUIRED_MODULES", targetRequired)
+	android.AndroidMkEmitAssignList(w, "LOCAL_HOST_REQUIRED_MODULES", hostRequired)
 }
 
 func (a *apexBundle) androidMkForType() android.AndroidMkData {
@@ -383,13 +361,11 @@
 				}
 				for _, name := range commonProperties {
 					if value, ok := data.Entries.EntryMap[name]; ok {
-						fmt.Fprintln(w, name+" := "+strings.Join(value, " "))
+						android.AndroidMkEmitAssignList(w, name, value)
 					}
 				}
 
-				if len(a.overridableProperties.Overrides) > 0 {
-					fmt.Fprintln(w, "LOCAL_OVERRIDES_MODULES :=", strings.Join(a.overridableProperties.Overrides, " "))
-				}
+				android.AndroidMkEmitAssignList(w, "LOCAL_OVERRIDES_MODULES", a.overridableProperties.Overrides)
 				a.writeRequiredModules(w, moduleNames)
 
 				fmt.Fprintln(w, "include $(BUILD_PREBUILT)")
@@ -397,10 +373,7 @@
 				if apexType == imageApex {
 					fmt.Fprintln(w, "ALL_MODULES.$(my_register_name).BUNDLE :=", a.bundleModuleFile.String())
 				}
-				if len(a.lintReports) > 0 {
-					fmt.Fprintln(w, "ALL_MODULES.$(my_register_name).LINT_REPORTS :=",
-						strings.Join(a.lintReports.Strings(), " "))
-				}
+				android.AndroidMkEmitAssignList(w, "ALL_MODULES.$(my_register_name).LINT_REPORTS", a.lintReports.Strings())
 
 				if a.installedFilesFile != nil {
 					goal := "checkbuild"
diff --git a/apex/apex.go b/apex/apex.go
index 8e1783e..01e4f12 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -734,12 +734,14 @@
 	}
 }
 
-// getImageVariation returns the image variant name for this apexBundle. In most cases, it's simply
-// android.CoreVariation, but gets complicated for the vendor APEXes and the VNDK APEX.
-func (a *apexBundle) getImageVariation(ctx android.BottomUpMutatorContext) string {
-	deviceConfig := ctx.DeviceConfig()
+// getImageVariationPair returns a pair for the image variation name as its
+// prefix and suffix. The prefix indicates whether it's core/vendor/product and the
+// suffix indicates the vndk version when it's vendor or product.
+// getImageVariation can simply join the result of this function to get the
+// image variation name.
+func (a *apexBundle) getImageVariationPair(deviceConfig android.DeviceConfig) (string, string) {
 	if a.vndkApex {
-		return cc.VendorVariationPrefix + a.vndkVersion(deviceConfig)
+		return cc.VendorVariationPrefix, a.vndkVersion(deviceConfig)
 	}
 
 	var prefix string
@@ -757,10 +759,17 @@
 		vndkVersion = deviceConfig.PlatformVndkVersion()
 	}
 	if vndkVersion != "" {
-		return prefix + vndkVersion
+		return prefix, vndkVersion
 	}
 
-	return android.CoreVariation // The usual case
+	return android.CoreVariation, "" // The usual case
+}
+
+// getImageVariation returns the image variant name for this apexBundle. In most cases, it's simply
+// android.CoreVariation, but gets complicated for the vendor APEXes and the VNDK APEX.
+func (a *apexBundle) getImageVariation(ctx android.BottomUpMutatorContext) string {
+	prefix, vndkVersion := a.getImageVariationPair(ctx.DeviceConfig())
+	return prefix + vndkVersion
 }
 
 func (a *apexBundle) DepsMutator(ctx android.BottomUpMutatorContext) {
@@ -3078,31 +3087,7 @@
 	// Module separator
 	//
 	m["com.android.btservices"] = []string{
-		"bluetooth-protos-lite",
-		"internal_include_headers",
-		"libaudio-a2dp-hw-utils",
-		"libaudio-hearing-aid-hw-utils",
-		"libbluetooth",
-		"libbluetooth-types",
-		"libbluetooth-types-header",
-		"libbluetooth_gd",
-		"libbluetooth_headers",
-		"libbluetooth_jni",
-		"libbt-audio-hal-interface",
-		"libbt-bta",
-		"libbt-common",
-		"libbt-hci",
-		"libbt-platform-protos-lite",
-		"libbt-protos-lite",
-		"libbt-sbc-decoder",
-		"libbt-sbc-encoder",
-		"libbt-stack",
-		"libbt-utils",
-		"libbtcore",
-		"libbtdevice",
-		"libbte",
-		"libbtif",
-		"libchrome",
+		// empty
 	}
 	//
 	// Module separator
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 883c3c8..1f2be35 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -2997,7 +2997,7 @@
 	var builder strings.Builder
 	data.Custom(&builder, name, prefix, "", data)
 	androidMk := builder.String()
-	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES += libc.vendor libm.vendor libdl.vendor\n")
+	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES := libc++.vendor.myapex:64 mylib.vendor.myapex:64 apex_manifest.pb.myapex apex_pubkey.myapex libc.vendor libm.vendor libdl.vendor\n")
 }
 
 func TestAndroidMkWritesCommonProperties(t *testing.T) {
@@ -4130,6 +4130,41 @@
 	ensureNotContains(t, androidMk, "LOCAL_MODULE := mylib.com.android.myapex\n")
 }
 
+func TestOverrideApexManifestDefaultVersion(t *testing.T) {
+	ctx := testApex(t, `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			apex_name: "com.android.myapex",
+			native_shared_libs: ["mylib"],
+			updatable: false,
+		}
+
+		apex_key {
+			name: "myapex.key",
+			public_key: "testkey.avbpubkey",
+			private_key: "testkey.pem",
+		}
+
+		cc_library {
+			name: "mylib",
+			srcs: ["mylib.cpp"],
+			system_shared_libs: [],
+			stl: "none",
+			apex_available: [
+				"//apex_available:platform",
+				"myapex",
+			],
+		}
+	`, android.FixtureMergeEnv(map[string]string{
+		"OVERRIDE_APEX_MANIFEST_DEFAULT_VERSION": "1234",
+	}))
+
+	module := ctx.ModuleForTests("myapex", "android_common_com.android.myapex_image")
+	apexManifestRule := module.Rule("apexManifestRule")
+	ensureContains(t, apexManifestRule.Args["default_version"], "1234")
+}
+
 func TestCompileMultilibProp(t *testing.T) {
 	testCases := []struct {
 		compileMultiLibProp string
@@ -5664,7 +5699,7 @@
 	var builder strings.Builder
 	mk.Custom(&builder, ab.Name(), "TARGET_", "", mk)
 	androidMk := builder.String()
-	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES += myapex.flattened")
+	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES := apex_manifest.pb.myapex apex_pubkey.myapex myapex.flattened\n")
 }
 
 func TestErrorsIfDepsAreNotEnabled(t *testing.T) {
@@ -7018,9 +7053,9 @@
 	var builder strings.Builder
 	data.Custom(&builder, name, prefix, "", data)
 	androidMk := builder.String()
-	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES += a b\n")
-	ensureContains(t, androidMk, "LOCAL_HOST_REQUIRED_MODULES += c d\n")
-	ensureContains(t, androidMk, "LOCAL_TARGET_REQUIRED_MODULES += e f\n")
+	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES := mylib.myapex:64 apex_manifest.pb.myapex apex_pubkey.myapex a b\n")
+	ensureContains(t, androidMk, "LOCAL_HOST_REQUIRED_MODULES := c d\n")
+	ensureContains(t, androidMk, "LOCAL_TARGET_REQUIRED_MODULES := e f\n")
 }
 
 func TestSymlinksFromApexToSystem(t *testing.T) {
@@ -7202,7 +7237,7 @@
 	ensureNotContains(t, androidMk, "LOCAL_MODULE := prebuilt_myotherlib.myapex\n")
 	ensureNotContains(t, androidMk, "LOCAL_MODULE := myotherlib.myapex\n")
 	// `myapex` should have `myotherlib` in its required line, not `prebuilt_myotherlib`
-	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES += mylib.myapex:64 myotherlib:64 apex_manifest.pb.myapex apex_pubkey.myapex\n")
+	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES := mylib.myapex:64 myotherlib:64 apex_manifest.pb.myapex apex_pubkey.myapex\n")
 }
 
 func TestApexWithJniLibs(t *testing.T) {
@@ -8715,7 +8750,7 @@
 
 	// The make level dependency needs to be on otherlib - prebuilt_otherlib isn't
 	// a thing there.
-	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES += otherlib\n")
+	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES := libc++:64 mylib.myapex:64 apex_manifest.pb.myapex apex_pubkey.myapex otherlib\n")
 }
 
 func TestExcludeDependency(t *testing.T) {
@@ -9109,7 +9144,7 @@
 	var builder strings.Builder
 	data.Custom(&builder, apexBundle.BaseModuleName(), "TARGET_", "", data)
 	androidMk := builder.String()
-	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES += foo-dexpreopt-arm64-apex@myapex@javalib@foo.jar@classes.odex foo-dexpreopt-arm64-apex@myapex@javalib@foo.jar@classes.vdex")
+	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES := foo.myapex apex_manifest.pb.myapex apex_pubkey.myapex foo-dexpreopt-arm64-apex@myapex@javalib@foo.jar@classes.odex foo-dexpreopt-arm64-apex@myapex@javalib@foo.jar@classes.vdex\n")
 }
 
 func TestAndroidMk_DexpreoptBuiltInstalledForApex_Prebuilt(t *testing.T) {
@@ -9185,7 +9220,7 @@
 	var builder strings.Builder
 	data.Custom(&builder, apexBundle.BaseModuleName(), "TARGET_", "", data)
 	androidMk := builder.String()
-	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES += otherapex")
+	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES := foo.myapex apex_manifest.pb.myapex apex_pubkey.myapex otherapex")
 }
 
 func TestAndroidMk_RequiredDeps(t *testing.T) {
@@ -9209,7 +9244,7 @@
 	var builder strings.Builder
 	data.Custom(&builder, bundle.BaseModuleName(), "TARGET_", "", data)
 	androidMk := builder.String()
-	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES += foo")
+	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES := apex_manifest.pb.myapex apex_pubkey.myapex foo\n")
 
 	flattenedBundle := ctx.ModuleForTests("myapex", "android_common_myapex_flattened").Module().(*apexBundle)
 	flattenedBundle.requiredDeps = append(flattenedBundle.requiredDeps, "foo")
@@ -9217,7 +9252,7 @@
 	var flattenedBuilder strings.Builder
 	flattenedData.Custom(&flattenedBuilder, flattenedBundle.BaseModuleName(), "TARGET_", "", flattenedData)
 	flattenedAndroidMk := flattenedBuilder.String()
-	ensureContains(t, flattenedAndroidMk, "LOCAL_REQUIRED_MODULES += foo")
+	ensureContains(t, flattenedAndroidMk, "LOCAL_REQUIRED_MODULES := apex_manifest.pb.myapex.flattened apex_pubkey.myapex.flattened foo\n")
 }
 
 func TestApexOutputFileProducer(t *testing.T) {
diff --git a/apex/builder.go b/apex/builder.go
index 9e368b6..82a523c 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -235,7 +235,17 @@
 		optCommands = append(optCommands, "-a jniLibs "+strings.Join(jniLibs, " "))
 	}
 
+	if android.InList(":vndk", requireNativeLibs) {
+		if _, vndkVersion := a.getImageVariationPair(ctx.DeviceConfig()); vndkVersion != "" {
+			optCommands = append(optCommands, "-v vndkVersion "+vndkVersion)
+		}
+	}
+
 	manifestJsonFullOut := android.PathForModuleOut(ctx, "apex_manifest_full.json")
+	defaultVersion := android.DefaultUpdatableModuleVersion
+	if override := ctx.Config().Getenv("OVERRIDE_APEX_MANIFEST_DEFAULT_VERSION"); override != "" {
+		defaultVersion = override
+	}
 	ctx.Build(pctx, android.BuildParams{
 		Rule:   apexManifestRule,
 		Input:  src,
@@ -243,7 +253,7 @@
 		Args: map[string]string{
 			"provideNativeLibs": strings.Join(provideNativeLibs, " "),
 			"requireNativeLibs": strings.Join(requireNativeLibs, " "),
-			"default_version":   android.DefaultUpdatableModuleVersion,
+			"default_version":   defaultVersion,
 			"opt":               strings.Join(optCommands, " "),
 		},
 	})
diff --git a/apex/deapexer.go b/apex/deapexer.go
index 8c9030a..fed9cd1 100644
--- a/apex/deapexer.go
+++ b/apex/deapexer.go
@@ -140,6 +140,8 @@
 			Tool(android.PathForSource(ctx, "build/soong/scripts/unpack-prebuilt-apex.sh")).
 			BuiltTool("deapexer").
 			BuiltTool("debugfs").
+			BuiltTool("blkid").
+			BuiltTool("fsck.erofs").
 			Input(p.inputApex).
 			Text(deapexerOutput.String())
 		for _, p := range exportedPaths {
diff --git a/apex/prebuilt.go b/apex/prebuilt.go
index 6fdd50a..0997a68 100644
--- a/apex/prebuilt.go
+++ b/apex/prebuilt.go
@@ -830,6 +830,7 @@
 	srcsSupplier := func(ctx android.BaseModuleContext, prebuilt android.Module) []string {
 		return p.properties.prebuiltSrcs(ctx)
 	}
+	defaultAllowPrerelease := ctx.Config().IsEnvTrue("SOONG_ALLOW_PRERELEASE_APEXES")
 	apexSet := android.SingleSourcePathFromSupplier(ctx, srcsSupplier, "set")
 	p.extractedApex = android.PathForModuleOut(ctx, "extracted", apexSet.Base())
 	// Filter out NativeBridge archs (b/260115309)
@@ -842,7 +843,7 @@
 			Output:      p.extractedApex,
 			Args: map[string]string{
 				"abis":              strings.Join(abis, ","),
-				"allow-prereleased": strconv.FormatBool(proptools.Bool(p.properties.Prerelease)),
+				"allow-prereleased": strconv.FormatBool(proptools.BoolDefault(p.properties.Prerelease, defaultAllowPrerelease)),
 				"sdk-version":       ctx.Config().PlatformSdkVersion().String(),
 			},
 		})
@@ -915,6 +916,15 @@
 	return false
 }
 
+func (a *ApexSet) OutputFiles(tag string) (android.Paths, error) {
+	switch tag {
+	case "":
+		return android.Paths{a.outputApex}, nil
+	default:
+		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+	}
+}
+
 // prebuilt_apex imports an `.apex` file into the build graph as if it was built with apex.
 func apexSetFactory() android.Module {
 	module := &ApexSet{}
diff --git a/bazel/properties.go b/bazel/properties.go
index ee9609a..6921984 100644
--- a/bazel/properties.go
+++ b/bazel/properties.go
@@ -1207,6 +1207,11 @@
 	// The configured attribute label list Values. Optional
 	// a map of independent configurability axes
 	ConfigurableValues configurableStringLists
+
+	// If a property has struct tag "variant_prepend", this value should
+	// be set to True, so that when bp2build generates BUILD.bazel, variant
+	// properties(select ...) come before general properties.
+	Prepend bool
 }
 
 // IsEmpty returns true if the attribute has no values under any configuration.
@@ -1273,6 +1278,9 @@
 // Append appends all values, including os and arch specific ones, from another
 // StringListAttribute to this StringListAttribute
 func (sla *StringListAttribute) Append(other StringListAttribute) *StringListAttribute {
+	if sla.Prepend != other.Prepend {
+		panic(fmt.Errorf("StringListAttribute could not be appended because it has different prepend value"))
+	}
 	sla.Value = append(sla.Value, other.Value...)
 	if sla.ConfigurableValues == nil {
 		sla.ConfigurableValues = make(configurableStringLists)
diff --git a/bp2build/cc_library_conversion_test.go b/bp2build/cc_library_conversion_test.go
index a1e83d8..ee6e5b8 100644
--- a/bp2build/cc_library_conversion_test.go
+++ b/bp2build/cc_library_conversion_test.go
@@ -3603,6 +3603,89 @@
 	})
 }
 
+func TestCcLibraryWithAfdoEnabled(t *testing.T) {
+	bp := `
+cc_library {
+	name: "foo",
+	afdo: true,
+	include_build_directory: false,
+}`
+
+	// TODO(b/260714900): Add test case for arch-specific afdo profile
+	testCases := []struct {
+		description          string
+		filesystem           map[string]string
+		expectedBazelTargets []string
+	}{
+		{
+			description: "cc_library with afdo enabled and existing profile",
+			filesystem: map[string]string{
+				"vendor/google_data/pgo_profile/sampling/BUILD":    "",
+				"vendor/google_data/pgo_profile/sampling/foo.afdo": "",
+			},
+			expectedBazelTargets: []string{
+				MakeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", AttrNameToString{}),
+				MakeBazelTarget("cc_library_shared", "foo", AttrNameToString{
+					"fdo_profile": `"//vendor/google_data/pgo_profile/sampling:foo"`,
+				}),
+			},
+		},
+		{
+			description: "cc_library with afdo enabled and existing profile in AOSP",
+			filesystem: map[string]string{
+				"toolchain/pgo-profiles/sampling/BUILD":    "",
+				"toolchain/pgo-profiles/sampling/foo.afdo": "",
+			},
+			expectedBazelTargets: []string{
+				MakeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", AttrNameToString{}),
+				MakeBazelTarget("cc_library_shared", "foo", AttrNameToString{
+					"fdo_profile": `"//toolchain/pgo-profiles/sampling:foo"`,
+				}),
+			},
+		},
+		{
+			description: "cc_library with afdo enabled but profile filename doesn't match with module name",
+			filesystem: map[string]string{
+				"toolchain/pgo-profiles/sampling/BUILD":    "",
+				"toolchain/pgo-profiles/sampling/bar.afdo": "",
+			},
+			expectedBazelTargets: []string{
+				MakeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", AttrNameToString{}),
+				MakeBazelTarget("cc_library_shared", "foo", AttrNameToString{}),
+			},
+		},
+		{
+			description: "cc_library with afdo enabled but profile doesn't exist",
+			expectedBazelTargets: []string{
+				MakeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", AttrNameToString{}),
+				MakeBazelTarget("cc_library_shared", "foo", AttrNameToString{}),
+			},
+		},
+		{
+			description: "cc_library with afdo enabled and existing profile but BUILD file doesn't exist",
+			filesystem: map[string]string{
+				"vendor/google_data/pgo_profile/sampling/foo.afdo": "",
+			},
+			expectedBazelTargets: []string{
+				MakeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", AttrNameToString{}),
+				MakeBazelTarget("cc_library_shared", "foo", AttrNameToString{}),
+			},
+		},
+	}
+	for _, testCase := range testCases {
+		t.Run(testCase.description, func(t *testing.T) {
+			runCcLibraryTestCase(t, Bp2buildTestCase{
+				ExpectedBazelTargets:       testCase.expectedBazelTargets,
+				ModuleTypeUnderTest:        "cc_library",
+				ModuleTypeUnderTestFactory: cc.LibraryFactory,
+				Description:                testCase.description,
+				Blueprint:                  binaryReplacer.Replace(bp),
+				Filesystem:                 testCase.filesystem,
+			})
+		})
+	}
+}
+
 func TestCcLibraryHeaderAbiChecker(t *testing.T) {
 	runCcLibraryTestCase(t, Bp2buildTestCase{
 		Description:                "cc_library with header abi checker",
diff --git a/bp2build/cc_library_headers_conversion_test.go b/bp2build/cc_library_headers_conversion_test.go
index 7d9db6f..686c9d5 100644
--- a/bp2build/cc_library_headers_conversion_test.go
+++ b/bp2build/cc_library_headers_conversion_test.go
@@ -107,15 +107,15 @@
 }`,
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("cc_library_headers", "foo_headers", AttrNameToString{
-				"export_includes": `[
-        "dir-1",
-        "dir-2",
-    ] + select({
+				"export_includes": `select({
         "//build/bazel/platforms/arch:arm64": ["arch_arm64_exported_include_dir"],
         "//build/bazel/platforms/arch:x86": ["arch_x86_exported_include_dir"],
         "//build/bazel/platforms/arch:x86_64": ["arch_x86_64_exported_include_dir"],
         "//conditions:default": [],
-    })`,
+    }) + [
+        "dir-1",
+        "dir-2",
+    ]`,
 				"sdk_version":     `"current"`,
 				"min_sdk_version": `"29"`,
 			}),
@@ -340,16 +340,16 @@
 }`,
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("cc_library_headers", "foo_headers", AttrNameToString{
-				"export_system_includes": `["shared_include_dir"] + select({
-        "//build/bazel/platforms/arch:arm": ["arm_include_dir"],
-        "//build/bazel/platforms/arch:x86_64": ["x86_64_include_dir"],
-        "//conditions:default": [],
-    }) + select({
+				"export_system_includes": `select({
         "//build/bazel/platforms/os:android": ["android_include_dir"],
         "//build/bazel/platforms/os:darwin": ["darwin_include_dir"],
         "//build/bazel/platforms/os:linux": ["linux_include_dir"],
         "//conditions:default": [],
-    })`,
+    }) + select({
+        "//build/bazel/platforms/arch:arm": ["arm_include_dir"],
+        "//build/bazel/platforms/arch:x86_64": ["x86_64_include_dir"],
+        "//conditions:default": [],
+    }) + ["shared_include_dir"]`,
 			}),
 		},
 	})
diff --git a/bp2build/configurability.go b/bp2build/configurability.go
index 9398d12..112755b 100644
--- a/bp2build/configurability.go
+++ b/bp2build/configurability.go
@@ -37,10 +37,11 @@
 	return value, []selects{ret}
 }
 
-func getStringListValues(list bazel.StringListAttribute) (reflect.Value, []selects) {
+func getStringListValues(list bazel.StringListAttribute) (reflect.Value, []selects, bool) {
 	value := reflect.ValueOf(list.Value)
+	prepend := reflect.ValueOf(list.Prepend).Bool()
 	if !list.HasConfigurableValues() {
-		return value, []selects{}
+		return value, []selects{}, prepend
 	}
 
 	var ret []selects
@@ -56,7 +57,7 @@
 		}
 	}
 
-	return value, ret
+	return value, ret, prepend
 }
 
 func getLabelValue(label bazel.LabelAttribute) (reflect.Value, []selects) {
@@ -156,6 +157,7 @@
 func prettyPrintAttribute(v bazel.Attribute, indent int) (string, error) {
 	var value reflect.Value
 	var configurableAttrs []selects
+	var prepend bool
 	var defaultSelectValue *string
 	var emitZeroValues bool
 	// If true, print the default attribute value, even if the attribute is zero.
@@ -168,7 +170,7 @@
 		value, configurableAttrs = getStringValue(list)
 		defaultSelectValue = &bazelNone
 	case bazel.StringListAttribute:
-		value, configurableAttrs = getStringListValues(list)
+		value, configurableAttrs, prepend = getStringListValues(list)
 		defaultSelectValue = &emptyBazelList
 	case bazel.LabelListAttribute:
 		value, configurableAttrs = getLabelListValues(list)
@@ -203,22 +205,28 @@
 
 		ret += s
 	}
-	// Convenience function to append selects components to an attribute value.
-	appendSelects := func(selectsData selects, defaultValue *string, s string) (string, error) {
+	// Convenience function to prepend/append selects components to an attribute value.
+	concatenateSelects := func(selectsData selects, defaultValue *string, s string, prepend bool) (string, error) {
 		selectMap, err := prettyPrintSelectMap(selectsData, defaultValue, indent, emitZeroValues)
 		if err != nil {
 			return "", err
 		}
-		if s != "" && selectMap != "" {
-			s += " + "
+		var left, right string
+		if prepend {
+			left, right = selectMap, s
+		} else {
+			left, right = s, selectMap
 		}
-		s += selectMap
+		if left != "" && right != "" {
+			left += " + "
+		}
+		left += right
 
-		return s, nil
+		return left, nil
 	}
 
 	for _, configurableAttr := range configurableAttrs {
-		ret, err = appendSelects(configurableAttr, defaultSelectValue, ret)
+		ret, err = concatenateSelects(configurableAttr, defaultSelectValue, ret, prepend)
 		if err != nil {
 			return "", err
 		}
diff --git a/bpf/bpf.go b/bpf/bpf.go
index d91180b..45009c1 100644
--- a/bpf/bpf.go
+++ b/bpf/bpf.go
@@ -240,7 +240,7 @@
 			fmt.Fprintln(w, "include $(CLEAR_VARS)", " # bpf.bpf")
 			fmt.Fprintln(w, "LOCAL_MODULE := ", name)
 			data.Entries.WriteLicenseVariables(w)
-			fmt.Fprintln(w, "LOCAL_REQUIRED_MODULES :=", strings.Join(names, " "))
+			android.AndroidMkEmitAssignList(w, "LOCAL_REQUIRED_MODULES", names)
 			fmt.Fprintln(w, "include $(BUILD_PHONY_PACKAGE)")
 		},
 	}
diff --git a/cc/bp2build.go b/cc/bp2build.go
index 6ed62fc..510ecee 100644
--- a/cc/bp2build.go
+++ b/cc/bp2build.go
@@ -406,6 +406,8 @@
 	features bazel.StringListAttribute
 
 	suffix bazel.StringAttribute
+
+	fdoProfile bazel.LabelAttribute
 }
 
 type filterOutFn func(string) bool
@@ -790,6 +792,13 @@
 	(&compilerAttrs).srcs.Add(&convertedLSrcs.srcName)
 	(&compilerAttrs).cSrcs.Add(&convertedLSrcs.cSrcName)
 
+	if module.afdo != nil && module.afdo.Properties.Afdo {
+		fdoProfileDep := bp2buildFdoProfile(ctx, module)
+		if fdoProfileDep != nil {
+			(&compilerAttrs).fdoProfile.SetValue(*fdoProfileDep)
+		}
+	}
+
 	if !compilerAttrs.syspropSrcs.IsEmpty() {
 		(&linkerAttrs).wholeArchiveDeps.Add(bp2buildCcSysprop(ctx, module.Name(), module.Properties.Min_sdk_version, compilerAttrs.syspropSrcs))
 	}
@@ -806,6 +815,41 @@
 	}
 }
 
+type fdoProfileAttributes struct {
+	Absolute_path_profile string
+}
+
+func bp2buildFdoProfile(
+	ctx android.Bp2buildMutatorContext,
+	m *Module,
+) *bazel.Label {
+	for _, project := range globalAfdoProfileProjects {
+		// Ensure handcrafted BUILD file exists in the project
+		BUILDPath := android.ExistentPathForSource(ctx, project, "BUILD")
+		if BUILDPath.Valid() {
+			// We handcraft a BUILD file with fdo_profile targets that use the existing profiles in the project
+			// This implementation is assuming that every afdo profile in globalAfdoProfileProjects already has
+			// an associated fdo_profile target declared in the same package.
+			// TODO(b/260714900): Handle arch-specific afdo profiles (e.g. `<module-name>-arm<64>.afdo`)
+			path := android.ExistentPathForSource(ctx, project, m.Name()+".afdo")
+			if path.Valid() {
+				// FIXME: Some profiles only exist internally and are not released to AOSP.
+				// When generated BUILD files are checked in, we'll run into merge conflict.
+				// The cc_library_shared target in AOSP won't have reference to an fdo_profile target because
+				// the profile doesn't exist. Internally, the same cc_library_shared target will
+				// have reference to the fdo_profile.
+				// For more context, see b/258682955#comment2
+				fdoProfileLabel := "//" + strings.TrimSuffix(project, "/") + ":" + m.Name()
+				return &bazel.Label{
+					Label: fdoProfileLabel,
+				}
+			}
+		}
+	}
+
+	return nil
+}
+
 func bp2buildCcAidlLibrary(
 	ctx android.Bp2buildMutatorContext,
 	m *Module,
@@ -1270,6 +1314,13 @@
 	} else {
 		exported = BazelIncludes{}
 	}
+
+	// cc library Export_include_dirs and Export_system_include_dirs are marked
+	// "variant_prepend" in struct tag, set their prepend property to true to make
+	// sure bp2build generates correct result.
+	exported.Includes.Prepend = true
+	exported.SystemIncludes.Prepend = true
+
 	bp2BuildPropParseHelper(ctx, module, &FlagExporterProperties{}, func(axis bazel.ConfigurationAxis, config string, props interface{}) {
 		if flagExporterProperties, ok := props.(*FlagExporterProperties); ok {
 			if len(flagExporterProperties.Export_include_dirs) > 0 {
diff --git a/cc/library.go b/cc/library.go
index d1d1945..ed0ed01 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -120,6 +120,9 @@
 
 		// Extra flags passed to header-abi-diff
 		Diff_flags []string
+
+		// Opt-in reference dump directories
+		Ref_dump_dirs []string
 	}
 
 	// Inject boringssl hash into the shared library.  This is only intended for use by external/boringssl.
@@ -414,6 +417,8 @@
 		Strip:                             stripAttrsFromLinkerAttrs(&linkerAttrs),
 		Features:                          baseAttributes.features,
 		bazelCcHeaderAbiCheckerAttributes: bp2buildParseAbiCheckerProps(ctx, m),
+
+		Fdo_profile: compilerAttrs.fdoProfile,
 	}
 
 	if compilerAttrs.stubsSymbolFile != nil && len(compilerAttrs.stubsVersions.Value) > 0 {
@@ -1911,6 +1916,16 @@
 		isLlndkOrNdk, allowExtensions, "current", errorMessage)
 }
 
+func (library *libraryDecorator) optInAbiDiff(ctx android.ModuleContext, referenceDump android.Path,
+	baseName, nameExt string, isLlndkOrNdk bool, refDumpDir string) {
+
+	libName := strings.TrimSuffix(baseName, filepath.Ext(baseName))
+	errorMessage := "error: Please update ABI references with: $$ANDROID_BUILD_TOP/development/vndk/tools/header-checker/utils/create_reference_dumps.py -l " + libName + " -ref-dump-dir $$ANDROID_BUILD_TOP/" + refDumpDir
+
+	library.sourceAbiDiff(ctx, referenceDump, baseName, nameExt,
+		isLlndkOrNdk, /* allowExtensions */ false, "current", errorMessage)
+}
+
 func (library *libraryDecorator) linkSAbiDumpFiles(ctx ModuleContext, objs Objects, fileName string, soFile android.Path) {
 	if library.sabi.shouldCreateSourceAbiDump() {
 		exportIncludeDirs := library.flagExporter.exportedIncludes(ctx)
@@ -1955,6 +1970,19 @@
 			library.sameVersionAbiDiff(ctx, currDumpFile.Path(),
 				fileName, isLlndk || isNdk, ctx.IsVndkExt())
 		}
+		// Check against the opt-in reference dumps.
+		for i, optInDumpDir := range library.Properties.Header_abi_checker.Ref_dump_dirs {
+			optInDumpDirPath := android.PathForModuleSrc(ctx, optInDumpDir)
+			// Ref_dump_dirs are not versioned.
+			// They do not contain subdir for binder bitness because 64-bit binder has been mandatory.
+			optInDumpFile := getRefAbiDumpFile(ctx, optInDumpDirPath.String(), fileName)
+			if !optInDumpFile.Valid() {
+				continue
+			}
+			library.optInAbiDiff(ctx, optInDumpFile.Path(),
+				fileName, "opt"+strconv.Itoa(i), isLlndk || isNdk,
+				optInDumpDirPath.String())
+		}
 	}
 }
 
@@ -2904,6 +2932,8 @@
 			Suffix: compilerAttrs.suffix,
 
 			bazelCcHeaderAbiCheckerAttributes: bp2buildParseAbiCheckerProps(ctx, module),
+
+			Fdo_profile: compilerAttrs.fdoProfile,
 		}
 		if compilerAttrs.stubsSymbolFile != nil && len(compilerAttrs.stubsVersions.Value) > 0 {
 			hasStubs := true
@@ -2994,6 +3024,8 @@
 	Suffix bazel.StringAttribute
 
 	bazelCcHeaderAbiCheckerAttributes
+
+	Fdo_profile bazel.LabelAttribute
 }
 
 type bazelCcStubSuiteAttributes struct {
diff --git a/cc/library_stub.go b/cc/library_stub.go
index c61e2d1..d21df51 100644
--- a/cc/library_stub.go
+++ b/cc/library_stub.go
@@ -148,8 +148,9 @@
 
 	var in android.Path
 
+	// src might not exist during the beginning of soong analysis in Multi-tree
 	if src := String(d.properties.Src); src != "" {
-		in = android.PathForModuleSrc(ctx, src)
+		in = android.MaybeExistentPathForSource(ctx, ctx.ModuleDir(), src)
 	}
 
 	// LLNDK variant
diff --git a/cc/sanitize_test.go b/cc/sanitize_test.go
index b102d33..143602a 100644
--- a/cc/sanitize_test.go
+++ b/cc/sanitize_test.go
@@ -572,7 +572,7 @@
 		}
 
 		cc_binary {
-			name: "bin_depends_ubsan",
+			name: "bin_depends_ubsan_static",
 			host_supported: true,
 			shared_libs: [
 				"libshared",
@@ -585,6 +585,14 @@
 		}
 
 		cc_binary {
+			name: "bin_depends_ubsan_shared",
+			host_supported: true,
+			shared_libs: [
+				"libsharedubsan",
+			],
+		}
+
+		cc_binary {
 			name: "bin_no_ubsan",
 			host_supported: true,
 			shared_libs: [
@@ -607,6 +615,14 @@
 			host_supported: true,
 		}
 
+		cc_library_shared {
+			name: "libsharedubsan",
+			host_supported: true,
+			sanitize: {
+				undefined: true,
+			}
+		}
+
 		cc_library_static {
 			name: "libubsan",
 			host_supported: true,
@@ -632,22 +648,33 @@
 
 	check := func(t *testing.T, result *android.TestResult, variant string) {
 		staticVariant := variant + "_static"
+		sharedVariant := variant + "_shared"
 
 		minimalRuntime := result.ModuleForTests("libclang_rt.ubsan_minimal", staticVariant)
 
 		// The binaries, one with ubsan and one without
 		binWithUbsan := result.ModuleForTests("bin_with_ubsan", variant)
-		binDependsUbsan := result.ModuleForTests("bin_depends_ubsan", variant)
+		binDependsUbsan := result.ModuleForTests("bin_depends_ubsan_static", variant)
+		libSharedUbsan := result.ModuleForTests("libsharedubsan", sharedVariant)
+		binDependsUbsanShared := result.ModuleForTests("bin_depends_ubsan_shared", variant)
 		binNoUbsan := result.ModuleForTests("bin_no_ubsan", variant)
 
 		android.AssertStringListContains(t, "missing libclang_rt.ubsan_minimal in bin_with_ubsan static libs",
 			strings.Split(binWithUbsan.Rule("ld").Args["libFlags"], " "),
 			minimalRuntime.OutputFiles(t, "")[0].String())
 
-		android.AssertStringListContains(t, "missing libclang_rt.ubsan_minimal in bin_depends_ubsan static libs",
+		android.AssertStringListContains(t, "missing libclang_rt.ubsan_minimal in bin_depends_ubsan_static static libs",
 			strings.Split(binDependsUbsan.Rule("ld").Args["libFlags"], " "),
 			minimalRuntime.OutputFiles(t, "")[0].String())
 
+		android.AssertStringListContains(t, "missing libclang_rt.ubsan_minimal in libsharedubsan static libs",
+			strings.Split(libSharedUbsan.Rule("ld").Args["libFlags"], " "),
+			minimalRuntime.OutputFiles(t, "")[0].String())
+
+		android.AssertStringListDoesNotContain(t, "unexpected libclang_rt.ubsan_minimal in bin_depends_ubsan_shared static libs",
+			strings.Split(binDependsUbsanShared.Rule("ld").Args["libFlags"], " "),
+			minimalRuntime.OutputFiles(t, "")[0].String())
+
 		android.AssertStringListDoesNotContain(t, "unexpected libclang_rt.ubsan_minimal in bin_no_ubsan static libs",
 			strings.Split(binNoUbsan.Rule("ld").Args["libFlags"], " "),
 			minimalRuntime.OutputFiles(t, "")[0].String())
@@ -656,10 +683,18 @@
 			strings.Split(binWithUbsan.Rule("ld").Args["ldFlags"], " "),
 			"-Wl,--exclude-libs="+minimalRuntime.OutputFiles(t, "")[0].Base())
 
-		android.AssertStringListContains(t, "missing -Wl,--exclude-libs for minimal runtime in bin_depends_ubsan static libs",
+		android.AssertStringListContains(t, "missing -Wl,--exclude-libs for minimal runtime in bin_depends_ubsan_static static libs",
 			strings.Split(binDependsUbsan.Rule("ld").Args["ldFlags"], " "),
 			"-Wl,--exclude-libs="+minimalRuntime.OutputFiles(t, "")[0].Base())
 
+		android.AssertStringListContains(t, "missing -Wl,--exclude-libs for minimal runtime in libsharedubsan static libs",
+			strings.Split(libSharedUbsan.Rule("ld").Args["ldFlags"], " "),
+			"-Wl,--exclude-libs="+minimalRuntime.OutputFiles(t, "")[0].Base())
+
+		android.AssertStringListDoesNotContain(t, "unexpected -Wl,--exclude-libs for minimal runtime in bin_depends_ubsan_shared static libs",
+			strings.Split(binDependsUbsanShared.Rule("ld").Args["ldFlags"], " "),
+			"-Wl,--exclude-libs="+minimalRuntime.OutputFiles(t, "")[0].Base())
+
 		android.AssertStringListDoesNotContain(t, "unexpected -Wl,--exclude-libs for minimal runtime in bin_no_ubsan static libs",
 			strings.Split(binNoUbsan.Rule("ld").Args["ldFlags"], " "),
 			"-Wl,--exclude-libs="+minimalRuntime.OutputFiles(t, "")[0].Base())
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index f4a63a4..bea8df0 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -238,7 +238,12 @@
 	soongInjectionFiles := bp2build.CreateSoongInjectionFiles(configuration, bp2build.CreateCodegenMetrics())
 	absoluteSoongInjectionDir := shared.JoinPath(topDir, configuration.SoongOutDir(), bazel.SoongInjectionDirName)
 	for _, file := range soongInjectionFiles {
-		writeReadOnlyFile(absoluteSoongInjectionDir, file)
+		// The API targets in api_bp2build workspace do not have any dependency on api_bp2build.
+		// But we need to create these files to prevent errors during Bazel analysis.
+		// These need to be created in Read-Write mode.
+		// This is because the subsequent step (bp2build in api domain analysis) creates them in Read-Write mode
+		// to allow users to edit/experiment in the synthetic workspace.
+		writeReadWriteFile(absoluteSoongInjectionDir, file)
 	}
 
 	workspace := shared.JoinPath(configuration.SoongOutDir(), "api_bp2build")
diff --git a/cmd/soong_build/queryview.go b/cmd/soong_build/queryview.go
index a876522..45b451c 100644
--- a/cmd/soong_build/queryview.go
+++ b/cmd/soong_build/queryview.go
@@ -91,6 +91,19 @@
 	return err
 }
 
+func writeReadWriteFile(dir string, f bp2build.BazelFile) error {
+	dir = filepath.Join(dir, f.Dir)
+	if err := createDirectoryIfNonexistent(dir); err != nil {
+		return err
+	}
+	pathToFile := filepath.Join(dir, f.Basename)
+
+	// 0644 is read-write
+	err := ioutil.WriteFile(pathToFile, []byte(f.Contents), 0644)
+
+	return err
+}
+
 func createDirectoryIfNonexistent(dir string) error {
 	if _, err := os.Stat(dir); os.IsNotExist(err) {
 		return os.MkdirAll(dir, os.ModePerm)
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index f7689b9..928ae17 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -117,6 +117,7 @@
 // Command is the type of soong_ui execution. Only one type of
 // execution is specified. The args are specific to the command.
 func main() {
+	//TODO(juu): Add logic to soong_ui to delete a hardcoded list of metrics files
 	shared.ReexecWithDelveMaybe(os.Getenv("SOONG_UI_DELVE"), shared.ResolveDelveBinary())
 
 	buildStarted := time.Now()
@@ -184,6 +185,7 @@
 	rbeMetricsFile := filepath.Join(logsDir, c.logsPrefix+"rbe_metrics.pb")
 	soongMetricsFile := filepath.Join(logsDir, c.logsPrefix+"soong_metrics")
 	bp2buildMetricsFile := filepath.Join(logsDir, c.logsPrefix+"bp2build_metrics.pb")
+	soongBuildMetricsFile := filepath.Join(logsDir, c.logsPrefix+"soong_build_metrics.pb")
 
 	build.PrintOutDirWarning(buildCtx, config)
 
@@ -211,11 +213,15 @@
 		files := []string{
 			buildErrorFile,           // build error strings
 			rbeMetricsFile,           // high level metrics related to remote build execution.
+			soongBuildMetricsFile,    // high level metrics related to soong build(except bp2build).
 			bp2buildMetricsFile,      // high level metrics related to bp2build.
 			soongMetricsFile,         // high level metrics related to this build system.
 			config.BazelMetricsDir(), // directory that contains a set of bazel metrics.
 		}
-		defer build.UploadMetrics(buildCtx, config, c.simpleOutput, buildStarted, files...)
+
+		if !config.SkipMetricsUpload() {
+			defer build.UploadMetrics(buildCtx, config, c.simpleOutput, buildStarted, files...)
+		}
 		defer met.Dump(soongMetricsFile)
 		defer build.CheckProdCreds(buildCtx, config)
 	}
diff --git a/filesystem/avb_add_hash_footer.go b/filesystem/avb_add_hash_footer.go
index 1ee0edc..2ee420c 100644
--- a/filesystem/avb_add_hash_footer.go
+++ b/filesystem/avb_add_hash_footer.go
@@ -149,7 +149,7 @@
 		cmd.FlagWithArg("--prop ", proptools.ShellEscape(fmt.Sprintf("%s:%s", name, value)))
 	} else {
 		p := android.PathForModuleSrc(ctx, file)
-		cmd.Input(p)
+		cmd.Implicit(p)
 		cmd.FlagWithArg("--prop_from_file ", proptools.ShellEscape(fmt.Sprintf("%s:%s", name, cmd.PathForInput(p))))
 	}
 }
diff --git a/java/java.go b/java/java.go
index e37a77e..9dd5850 100644
--- a/java/java.go
+++ b/java/java.go
@@ -1567,8 +1567,14 @@
 	Api_surface *string
 
 	// list of Java API contribution modules that consists this API surface
+	// This is a list of Soong modules
 	Api_contributions []string
 
+	// list of api.txt files relative to this directory that contribute to the
+	// API surface.
+	// This is a list of relative paths
+	Api_files []string
+
 	// List of flags to be passed to the javac compiler to generate jar file
 	Javacflags []string
 }
@@ -1665,6 +1671,13 @@
 		srcFiles = append(srcFiles, android.PathForSource(ctx, provider.ApiFile.String()))
 	})
 
+	// Add the api_files inputs
+	for _, api := range al.properties.Api_files {
+		// Use MaybeExistentPathForSource since the api file might not exist during analysis.
+		// This will be provided by the orchestrator in the combined execution.
+		srcFiles = append(srcFiles, android.MaybeExistentPathForSource(ctx, ctx.ModuleDir(), api))
+	}
+
 	cmd := metalavaStubCmd(ctx, rule, srcFiles, homeDir)
 
 	al.stubsFlags(ctx, cmd, stubsDir)
diff --git a/java/java_test.go b/java/java_test.go
index ff15783..dff1fd0 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -1863,6 +1863,7 @@
 			name: "bar2",
 			api_surface: "system",
 			api_contributions: ["foo1", "foo2"],
+			api_files: ["api1/current.txt", "api2/current.txt"]
 		}
 		`,
 		map[string][]byte{
@@ -1880,7 +1881,7 @@
 		},
 		{
 			moduleName:         "bar2",
-			sourceTextFileDirs: []string{"a/foo1.txt", "b/foo2.txt"},
+			sourceTextFileDirs: []string{"a/foo1.txt", "b/foo2.txt", "api1/current.txt", "api2/current.txt"},
 		},
 	}
 	for _, c := range testcases {
diff --git a/java/lint.go b/java/lint.go
index 7a6e5d9..07b9629 100644
--- a/java/lint.go
+++ b/java/lint.go
@@ -366,6 +366,9 @@
 		}
 	}
 
+	l.extraLintCheckJars = append(l.extraLintCheckJars, android.PathForSource(ctx,
+		"prebuilts/cmdline-tools/AndroidGlobalLintChecker.jar"))
+
 	rule := android.NewRuleBuilder(pctx, ctx).
 		Sbox(android.PathForModuleOut(ctx, "lint"),
 			android.PathForModuleOut(ctx, "lint.sbox.textproto")).
diff --git a/java/robolectric.go b/java/robolectric.go
index 1d56708..6e8d591 100644
--- a/java/robolectric.go
+++ b/java/robolectric.go
@@ -296,6 +296,7 @@
 	entries.ExtraEntries = append(entries.ExtraEntries,
 		func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
 			entries.SetBool("LOCAL_UNINSTALLABLE_MODULE", true)
+			entries.AddStrings("LOCAL_COMPATIBILITY_SUITE", "robolectric-tests")
 		})
 
 	entries.ExtraFooters = []android.AndroidMkExtraFootersFunc{
@@ -329,11 +330,10 @@
 	fmt.Fprintln(w, "")
 	fmt.Fprintln(w, "include $(CLEAR_VARS)", " # java.robolectricTest")
 	fmt.Fprintln(w, "LOCAL_MODULE :=", name)
-	fmt.Fprintln(w, "LOCAL_JAVA_LIBRARIES :=", module)
-	fmt.Fprintln(w, "LOCAL_JAVA_LIBRARIES += ", strings.Join(r.libs, " "))
+	android.AndroidMkEmitAssignList(w, "LOCAL_JAVA_LIBRARIES", []string{module}, r.libs)
 	fmt.Fprintln(w, "LOCAL_TEST_PACKAGE :=", String(r.robolectricProperties.Instrumentation_for))
 	fmt.Fprintln(w, "LOCAL_INSTRUMENT_SRCJARS :=", r.roboSrcJar.String())
-	fmt.Fprintln(w, "LOCAL_ROBOTEST_FILES :=", strings.Join(tests, " "))
+	android.AndroidMkEmitAssignList(w, "LOCAL_ROBOTEST_FILES", tests)
 	if t := r.robolectricProperties.Test_options.Timeout; t != nil {
 		fmt.Fprintln(w, "LOCAL_ROBOTEST_TIMEOUT :=", *t)
 	}
diff --git a/java/testing.go b/java/testing.go
index ccbb638..e6f76e1 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -56,6 +56,8 @@
 		"build/make/target/product/security": nil,
 		// Required to generate Java used-by API coverage
 		"build/soong/scripts/gen_java_usedby_apex.sh": nil,
+		// Needed for the global lint checks provided from frameworks/base
+		"prebuilts/cmdline-tools/AndroidGlobalLintChecker.jar": nil,
 	}.AddToFixture(),
 )
 
diff --git a/licenses/Android.bp b/licenses/Android.bp
index eabc303..7267cf3 100644
--- a/licenses/Android.bp
+++ b/licenses/Android.bp
@@ -921,6 +921,14 @@
 }
 
 license_kind {
+    name: "SPDX-license-identifier-Linux-syscall-note",
+    // expanding visibility requires approval from an OSPO lawyer or pcounsel
+    visibility: ["//external/libbpf:__subpackages__"],
+    conditions: ["permissive"],
+    url: "https://spdx.org/licenses/Linux-syscall-note.html",
+}
+
+license_kind {
     name: "SPDX-license-identifier-LPL-1.02",
     conditions: ["notice"],
     url: "https://spdx.org/licenses/LPL-1.02.html",
diff --git a/mk2rbc/mk2rbc.go b/mk2rbc/mk2rbc.go
index aa48e63..c3b192d 100644
--- a/mk2rbc/mk2rbc.go
+++ b/mk2rbc/mk2rbc.go
@@ -77,6 +77,7 @@
 	"add-to-product-copy-files-if-exists":  &simpleCallParser{name: baseName + ".copy_if_exists", returnType: starlarkTypeList},
 	"addprefix":                            &simpleCallParser{name: baseName + ".addprefix", returnType: starlarkTypeList},
 	"addsuffix":                            &simpleCallParser{name: baseName + ".addsuffix", returnType: starlarkTypeList},
+	"and":                                  &andOrParser{isAnd: true},
 	"copy-files":                           &simpleCallParser{name: baseName + ".copy_files", returnType: starlarkTypeList},
 	"dir":                                  &simpleCallParser{name: baseName + ".dir", returnType: starlarkTypeString},
 	"dist-for-goals":                       &simpleCallParser{name: baseName + ".mkdist_for_goals", returnType: starlarkTypeVoid, addGlobals: true},
@@ -105,6 +106,7 @@
 	"math_gt":                              &mathComparisonCallParser{op: ">"},
 	"math_lt":                              &mathComparisonCallParser{op: "<"},
 	"my-dir":                               &myDirCallParser{},
+	"or":                                   &andOrParser{isAnd: false},
 	"patsubst":                             &substCallParser{fname: "patsubst"},
 	"product-copy-files-by-pattern":        &simpleCallParser{name: baseName + ".product_copy_files_by_pattern", returnType: starlarkTypeList},
 	"require-artifacts-in-path":            &simpleCallParser{name: baseName + ".require_artifacts_in_path", returnType: starlarkTypeVoid, addHandle: true},
@@ -114,6 +116,8 @@
 	"sort":     &simpleCallParser{name: baseName + ".mksort", returnType: starlarkTypeList},
 	"strip":    &simpleCallParser{name: baseName + ".mkstrip", returnType: starlarkTypeString},
 	"subst":    &substCallParser{fname: "subst"},
+	"to-lower": &lowerUpperParser{isUpper: false},
+	"to-upper": &lowerUpperParser{isUpper: true},
 	"warning":  &makeControlFuncParser{name: baseName + ".mkwarning"},
 	"word":     &wordCallParser{},
 	"words":    &wordsCallParser{},
@@ -1430,6 +1434,51 @@
 	return &stringLiteralExpr{literal: filepath.Dir(ctx.script.mkFile)}
 }
 
+type andOrParser struct {
+	isAnd bool
+}
+
+func (p *andOrParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
+	if args.Empty() {
+		return ctx.newBadExpr(node, "and/or function must have at least 1 argument")
+	}
+	op := "or"
+	if p.isAnd {
+		op = "and"
+	}
+
+	argsParsed := make([]starlarkExpr, 0)
+
+	for _, arg := range args.Split(",") {
+		arg.TrimLeftSpaces()
+		arg.TrimRightSpaces()
+		x := ctx.parseMakeString(node, arg)
+		if xBad, ok := x.(*badExpr); ok {
+			return xBad
+		}
+		argsParsed = append(argsParsed, x)
+	}
+	typ := starlarkTypeUnknown
+	for _, arg := range argsParsed {
+		if typ != arg.typ() && arg.typ() != starlarkTypeUnknown && typ != starlarkTypeUnknown {
+			return ctx.newBadExpr(node, "Expected all arguments to $(or) or $(and) to have the same type, found %q and %q", typ.String(), arg.typ().String())
+		}
+		if arg.typ() != starlarkTypeUnknown {
+			typ = arg.typ()
+		}
+	}
+	result := argsParsed[0]
+	for _, arg := range argsParsed[1:] {
+		result = &binaryOpExpr{
+			left:       result,
+			right:      arg,
+			op:         op,
+			returnType: typ,
+		}
+	}
+	return result
+}
+
 type isProductInListCallParser struct{}
 
 func (p *isProductInListCallParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
@@ -1848,6 +1897,24 @@
 	return []starlarkNode{ctx.newBadNode(node, "Eval expression too complex; only assignments, comments, includes, and inherit-products are supported")}
 }
 
+type lowerUpperParser struct {
+	isUpper bool
+}
+
+func (p *lowerUpperParser) parse(ctx *parseContext, node mkparser.Node, args *mkparser.MakeString) starlarkExpr {
+	fn := "lower"
+	if p.isUpper {
+		fn = "upper"
+	}
+	arg := ctx.parseMakeString(node, args)
+
+	return &callExpr{
+		object:     arg,
+		name:       fn,
+		returnType: starlarkTypeString,
+	}
+}
+
 func (ctx *parseContext) parseMakeString(node mkparser.Node, mk *mkparser.MakeString) starlarkExpr {
 	if mk.Const() {
 		return &stringLiteralExpr{mk.Dump()}
diff --git a/mk2rbc/mk2rbc_test.go b/mk2rbc/mk2rbc_test.go
index 31555d3..65a3be7 100644
--- a/mk2rbc/mk2rbc_test.go
+++ b/mk2rbc/mk2rbc_test.go
@@ -1629,6 +1629,58 @@
   g["MY_VAR_5"] = rblf.mk2rbc_error("product.mk:6", "reference is too complex: $(MY_VAR_2) bar")
 `,
 	},
+	{
+		desc:   "Conditional functions",
+		mkname: "product.mk",
+		in: `
+B := foo
+X := $(or $(A))
+X := $(or $(A),$(B))
+X := $(or $(A),$(B),$(C))
+X := $(and $(A))
+X := $(and $(A),$(B))
+X := $(and $(A),$(B),$(C))
+X := $(or $(A),$(B)) Y
+
+D := $(wildcard *.mk)
+X := $(or $(B),$(D))
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["B"] = "foo"
+  g["X"] = g.get("A", "")
+  g["X"] = g.get("A", "") or g["B"]
+  g["X"] = g.get("A", "") or g["B"] or g.get("C", "")
+  g["X"] = g.get("A", "")
+  g["X"] = g.get("A", "") and g["B"]
+  g["X"] = g.get("A", "") and g["B"] and g.get("C", "")
+  g["X"] = "%s Y" % g.get("A", "") or g["B"]
+  g["D"] = rblf.expand_wildcard("*.mk")
+  g["X"] = rblf.mk2rbc_error("product.mk:12", "Expected all arguments to $(or) or $(and) to have the same type, found \"string\" and \"list\"")
+`,
+	},
+	{
+
+		desc:   "is-lower/is-upper",
+		mkname: "product.mk",
+		in: `
+X := $(call to-lower,aBc)
+X := $(call to-upper,aBc)
+X := $(call to-lower,$(VAR))
+X := $(call to-upper,$(VAR))
+`,
+		expected: `load("//build/make/core:product_config.rbc", "rblf")
+
+def init(g, handle):
+  cfg = rblf.cfg(handle)
+  g["X"] = ("aBc").lower()
+  g["X"] = ("aBc").upper()
+  g["X"] = (g.get("VAR", "")).lower()
+  g["X"] = (g.get("VAR", "")).upper()
+`,
+	},
 }
 
 var known_variables = []struct {
diff --git a/mk2rbc/types.go b/mk2rbc/types.go
index 46c6aa9..ac32507 100644
--- a/mk2rbc/types.go
+++ b/mk2rbc/types.go
@@ -14,6 +14,8 @@
 
 package mk2rbc
 
+import "fmt"
+
 // Starlark expression types we use
 type starlarkType int
 
@@ -31,6 +33,25 @@
 	starlarkTypeVoid    starlarkType = iota
 )
 
+func (t starlarkType) String() string {
+	switch t {
+	case starlarkTypeList:
+		return "list"
+	case starlarkTypeString:
+		return "string"
+	case starlarkTypeInt:
+		return "int"
+	case starlarkTypeBool:
+		return "bool"
+	case starlarkTypeVoid:
+		return "void"
+	case starlarkTypeUnknown:
+		return "unknown"
+	default:
+		panic(fmt.Sprintf("Unknown starlark type %d", t))
+	}
+}
+
 type hiddenArgType int
 
 const (
diff --git a/scripts/unpack-prebuilt-apex.sh b/scripts/unpack-prebuilt-apex.sh
index f34a480..b244f79 100755
--- a/scripts/unpack-prebuilt-apex.sh
+++ b/scripts/unpack-prebuilt-apex.sh
@@ -17,23 +17,28 @@
 # limitations under the License.
 
 # Tool to unpack an apex file and verify that the required files were extracted.
-if [ $# -lt 5 ]; then
-  echo "usage: $0 <deapaxer_path> <debugfs_path> <apex file> <output_dir> <required_files>+" >&2
+if [ $# -lt 7 ]; then
+  echo "usage: $0 <deapaxer_path> <debugfs_path> <blkid_path> <fsck.erofs_path> <apex file> <output_dir> <required_files>+" >&2
   exit 1
 fi
 
 DEAPEXER_PATH=$1
 DEBUGFS_PATH=$2
-APEX_FILE=$3
-OUTPUT_DIR=$4
-shift 4
+BLKID_PATH=$3
+FSCK_EROFS_PATH=$4
+APEX_FILE=$5
+OUTPUT_DIR=$6
+shift 6
 REQUIRED_PATHS=$@
 
 rm -fr $OUTPUT_DIR
 mkdir -p $OUTPUT_DIR
 
 # Unpack the apex file contents.
-$DEAPEXER_PATH --debugfs_path $DEBUGFS_PATH extract $APEX_FILE $OUTPUT_DIR
+$DEAPEXER_PATH --debugfs_path $DEBUGFS_PATH \
+               --blkid_path $BLKID_PATH \
+               --fsckerofs_path $FSCK_EROFS_PATH \
+               extract $APEX_FILE $OUTPUT_DIR
 
 # Verify that the files that the build expects to be in the .apex file actually
 # exist, and make sure they have a fresh mtime to not confuse ninja.
diff --git a/tests/apex_comparison_tests.sh b/tests/apex_comparison_tests.sh
index 5fbbd0f..d579429 100755
--- a/tests/apex_comparison_tests.sh
+++ b/tests/apex_comparison_tests.sh
@@ -29,10 +29,13 @@
 # Test Setup
 ############
 
-OUTPUT_DIR="$(mktemp -d)"
+OUTPUT_DIR="$(mktemp -d tmp.XXXXXX)"
 SOONG_OUTPUT_DIR="$OUTPUT_DIR/soong"
 BAZEL_OUTPUT_DIR="$OUTPUT_DIR/bazel"
 
+export TARGET_PRODUCT="module_arm"
+[ "$#" -eq 1 ] && export TARGET_PRODUCT="$1"
+
 function call_bazel() {
   build/bazel/bin/bazel --output_base="$BAZEL_OUTPUT_DIR" $@
 }
@@ -50,7 +53,7 @@
 export UNBUNDLED_BUILD_SDKS_FROM_SOURCE=true # don't rely on prebuilts
 export TARGET_BUILD_APPS="com.android.adbd com.android.tzdata build.bazel.examples.apex.minimal"
 packages/modules/common/build/build_unbundled_mainline_module.sh \
-  --product module_arm \
+  --product "$TARGET_PRODUCT" \
   --dist_dir "$SOONG_OUTPUT_DIR"
 
 ######################
@@ -60,22 +63,15 @@
 
 BAZEL_OUT="$(call_bazel info --config=bp2build output_path)"
 
-export TARGET_PRODUCT="module_arm"
 call_bazel build --config=bp2build --config=ci --config=android \
   //packages/modules/adb/apex:com.android.adbd \
   //system/timezone/apex:com.android.tzdata \
   //build/bazel/examples/apex/minimal:build.bazel.examples.apex.minimal.apex
 
-# Build debugfs separately, as it's not a dep of apexer, but needs to be an explicit arg.
-call_bazel build --config=bp2build --config=linux_x86_64 //external/e2fsprogs/debugfs
-DEBUGFS_PATH="$BAZEL_OUT/linux_x86_64-fastbuild/bin/external/e2fsprogs/debugfs/debugfs"
-
-function run_deapexer() {
-  call_bazel run --config=bp2build --config=linux_x86_64 //system/apex/tools:deapexer \
-    -- \
-    --debugfs_path="$DEBUGFS_PATH" \
-    $@
-}
+# # Build debugfs separately, as it's not a dep of apexer, but needs to be an explicit arg.
+call_bazel build --config=bp2build --config=linux_x86_64 //external/e2fsprogs/debugfs //system/apex/tools:deapexer
+DEBUGFS_PATH="$BAZEL_OUT/linux_x86_64-opt/bin/external/e2fsprogs/debugfs/debugfs"
+DEAPEXER="$BAZEL_OUT/linux_x86_64-opt/bin/system/apex/tools/deapexer --debugfs_path=$DEBUGFS_PATH"
 
 #######
 # Tests
@@ -87,13 +83,13 @@
 
   # Compare the outputs of `deapexer list`, which lists the contents of the apex filesystem image.
   local SOONG_APEX="$SOONG_OUTPUT_DIR/$APEX"
-  local BAZEL_APEX="$BAZEL_OUT/android_target-fastbuild/bin/$APEX_DIR/$APEX"
+  local BAZEL_APEX="$BAZEL_OUT/android_target-opt/bin/$APEX_DIR/$APEX"
 
   local SOONG_LIST="$OUTPUT_DIR/soong.list"
   local BAZEL_LIST="$OUTPUT_DIR/bazel.list"
 
-  run_deapexer list "$SOONG_APEX" > "$SOONG_LIST"
-  run_deapexer list "$BAZEL_APEX" > "$BAZEL_LIST"
+  $DEAPEXER list "$SOONG_APEX" > "$SOONG_LIST"
+  $DEAPEXER list "$BAZEL_APEX" > "$BAZEL_LIST"
 
   if cmp -s "$SOONG_LIST" "$BAZEL_LIST"
   then
diff --git a/tests/bootstrap_test.sh b/tests/bootstrap_test.sh
index d89e6b7..17b4419 100755
--- a/tests/bootstrap_test.sh
+++ b/tests/bootstrap_test.sh
@@ -17,11 +17,11 @@
 function test_null_build() {
   setup
   run_soong
-  local bootstrap_mtime1=$(stat -c "%y" out/soong/bootstrap.ninja)
-  local output_mtime1=$(stat -c "%y" out/soong/build.ninja)
+  local -r bootstrap_mtime1=$(stat -c "%y" out/soong/bootstrap.ninja)
+  local -r output_mtime1=$(stat -c "%y" out/soong/build.ninja)
   run_soong
-  local bootstrap_mtime2=$(stat -c "%y" out/soong/bootstrap.ninja)
-  local output_mtime2=$(stat -c "%y" out/soong/build.ninja)
+  local -r bootstrap_mtime2=$(stat -c "%y" out/soong/bootstrap.ninja)
+  local -r output_mtime2=$(stat -c "%y" out/soong/build.ninja)
 
   if [[ "$bootstrap_mtime1" == "$bootstrap_mtime2" ]]; then
     # Bootstrapping is always done. It doesn't take a measurable amount of time.
@@ -36,12 +36,12 @@
 function test_soong_build_rebuilt_if_blueprint_changes() {
   setup
   run_soong
-  local mtime1=$(stat -c "%y" out/soong/bootstrap.ninja)
+  local -r mtime1=$(stat -c "%y" out/soong/bootstrap.ninja)
 
   sed -i 's/pluginGenSrcCmd/pluginGenSrcCmd2/g' build/blueprint/bootstrap/bootstrap.go
 
   run_soong
-  local mtime2=$(stat -c "%y" out/soong/bootstrap.ninja)
+  local -r mtime2=$(stat -c "%y" out/soong/bootstrap.ninja)
 
   if [[ "$mtime1" == "$mtime2" ]]; then
     fail "Bootstrap Ninja file did not change"
@@ -79,7 +79,7 @@
 function test_add_android_bp() {
   setup
   run_soong
-  local mtime1=$(stat -c "%y" out/soong/build.ninja)
+  local -r mtime1=$(stat -c "%y" out/soong/build.ninja)
 
   mkdir -p a
   cat > a/Android.bp <<'EOF'
@@ -91,7 +91,7 @@
   touch a/my_little_binary_host.py
   run_soong
 
-  local mtime2=$(stat -c "%y" out/soong/build.ninja)
+  local -r mtime2=$(stat -c "%y" out/soong/build.ninja)
   if [[ "$mtime1" == "$mtime2" ]]; then
     fail "Output Ninja file did not change"
   fi
@@ -142,7 +142,7 @@
 EOF
   touch a/my_little_binary_host.py
   run_soong
-  local ninja_mtime1=$(stat -c "%y" out/soong/build.ninja)
+  local -r ninja_mtime1=$(stat -c "%y" out/soong/build.ninja)
 
   local glob_deps_file=out/soong/globs/build/0.d
 
@@ -151,7 +151,7 @@
   fi
 
   run_soong
-  local ninja_mtime2=$(stat -c "%y" out/soong/build.ninja)
+  local -r ninja_mtime2=$(stat -c "%y" out/soong/build.ninja)
 
   # There is an ineffiencency in glob that requires bpglob to rerun once for each glob to update
   # the entry in the .ninja_log.  It doesn't update the output file, but we can detect the rerun
@@ -160,15 +160,15 @@
     fail "Glob deps file missing after second build"
   fi
 
-  local glob_deps_mtime2=$(stat -c "%y" "$glob_deps_file")
+  local -r glob_deps_mtime2=$(stat -c "%y" "$glob_deps_file")
 
   if [[ "$ninja_mtime1" != "$ninja_mtime2" ]]; then
     fail "Ninja file rewritten on null incremental build"
   fi
 
   run_soong
-  local ninja_mtime3=$(stat -c "%y" out/soong/build.ninja)
-  local glob_deps_mtime3=$(stat -c "%y" "$glob_deps_file")
+  local -r ninja_mtime3=$(stat -c "%y" out/soong/build.ninja)
+  local -r glob_deps_mtime3=$(stat -c "%y" "$glob_deps_file")
 
   if [[ "$ninja_mtime2" != "$ninja_mtime3" ]]; then
     fail "Ninja file rewritten on null incremental build"
@@ -192,12 +192,12 @@
 EOF
   touch a/my_little_binary_host.py
   run_soong
-  local mtime1=$(stat -c "%y" out/soong/build.ninja)
+  local -r mtime1=$(stat -c "%y" out/soong/build.ninja)
 
   touch a/my_little_library.py
   run_soong
 
-  local mtime2=$(stat -c "%y" out/soong/build.ninja)
+  local -r mtime2=$(stat -c "%y" out/soong/build.ninja)
   if [[ "$mtime1" == "$mtime2" ]]; then
     fail "Output Ninja file did not change"
   fi
@@ -275,10 +275,10 @@
   run_soong
   grep -q "CHERRY IS RED" out/soong/build.ninja \
     || fail "second value of environment variable not used"
-  local mtime1=$(stat -c "%y" out/soong/build.ninja)
+  local -r mtime1=$(stat -c "%y" out/soong/build.ninja)
 
   run_soong
-  local mtime2=$(stat -c "%y" out/soong/build.ninja)
+  local -r mtime2=$(stat -c "%y" out/soong/build.ninja)
   if [[ "$mtime1" != "$mtime2" ]]; then
     fail "Output Ninja file changed when environment variable did not"
   fi
@@ -288,7 +288,7 @@
 function test_create_global_include_directory() {
   setup
   run_soong
-  local mtime1=$(stat -c "%y" out/soong/build.ninja)
+  local -r mtime1=$(stat -c "%y" out/soong/build.ninja)
 
   # Soong needs to know if top level directories like hardware/ exist for use
   # as global include directories.  Make sure that doesn't cause regens for
@@ -296,7 +296,7 @@
   mkdir -p system/core
 
   run_soong
-  local mtime2=$(stat -c "%y" out/soong/build.ninja)
+  local -r mtime2=$(stat -c "%y" out/soong/build.ninja)
   if [[ "$mtime1" != "$mtime2" ]]; then
     fail "Output Ninja file changed when top level directory changed"
   fi
@@ -306,7 +306,7 @@
   mkdir -p system/core/include
 
   run_soong
-  local mtime3=$(stat -c "%y" out/soong/build.ninja)
+  local -r mtime3=$(stat -c "%y" out/soong/build.ninja)
   if [[ "$mtime2" = "$mtime3" ]]; then
     fail "Output Ninja file did not change when global include directory created"
   fi
@@ -317,7 +317,7 @@
 function test_add_file_to_soong_build() {
   setup
   run_soong
-  local mtime1=$(stat -c "%y" out/soong/build.ninja)
+  local -r mtime1=$(stat -c "%y" out/soong/build.ninja)
 
   mkdir -p a
   cat > a/Android.bp <<'EOF'
@@ -379,7 +379,7 @@
 EOF
 
   run_soong
-  local mtime2=$(stat -c "%y" out/soong/build.ninja)
+  local -r mtime2=$(stat -c "%y" out/soong/build.ninja)
   if [[ "$mtime1" == "$mtime2" ]]; then
     fail "Output Ninja file did not change"
   fi
@@ -457,7 +457,7 @@
 EOF
 
   run_soong
-  local mtime1=$(stat -c "%y" out/soong/build.ninja)
+  local -r mtime1=$(stat -c "%y" out/soong/build.ninja)
 
   grep -q "Make it so" out/soong/build.ninja || fail "Original action not present"
 
@@ -489,7 +489,7 @@
 EOF
 
   run_soong
-  local mtime2=$(stat -c "%y" out/soong/build.ninja)
+  local -r mtime2=$(stat -c "%y" out/soong/build.ninja)
   if [[ "$mtime1" == "$mtime2" ]]; then
     fail "Output Ninja file did not change"
   fi
@@ -514,20 +514,20 @@
   setup
 
   run_soong
-  local ninja_mtime1=$(stat -c "%y" out/soong/build.ninja)
+  local -r ninja_mtime1=$(stat -c "%y" out/soong/build.ninja)
 
   run_soong soong_docs
-  local docs_mtime1=$(stat -c "%y" out/soong/docs/soong_build.html)
+  local -r docs_mtime1=$(stat -c "%y" out/soong/docs/soong_build.html)
 
   run_soong soong_docs
-  local docs_mtime2=$(stat -c "%y" out/soong/docs/soong_build.html)
+  local -r docs_mtime2=$(stat -c "%y" out/soong/docs/soong_build.html)
 
   if [[ "$docs_mtime1" != "$docs_mtime2" ]]; then
     fail "Output Ninja file changed on null build"
   fi
 
   run_soong
-  local ninja_mtime2=$(stat -c "%y" out/soong/build.ninja)
+  local -r ninja_mtime2=$(stat -c "%y" out/soong/build.ninja)
 
   if [[ "$ninja_mtime1" != "$ninja_mtime2" ]]; then
     fail "Output Ninja file changed on null build"
@@ -552,7 +552,7 @@
   run_ninja BUILD_BROKEN_SRC_DIR_IS_WRITABLE=false ${EXPECTED_OUT} &> /dev/null && \
     fail "Write to source tree should not work in a ReadOnly source tree"
 
-  if grep -q "${ERROR_MSG}" ${ERROR_LOG} && grep -q "${ERROR_HINT_PATTERN}" ${ERROR_LOG} ; then
+  if grep -q "${ERROR_MSG}" "${ERROR_LOG}" && grep -q "${ERROR_HINT_PATTERN}" "${ERROR_LOG}" ; then
     echo Error message and error hint found in logs >/dev/null
   else
     fail "Did not find Read-only error AND error hint in error.log"
@@ -562,7 +562,7 @@
   run_ninja BUILD_BROKEN_SRC_DIR_IS_WRITABLE=true ${EXPECTED_OUT} &> /dev/null || \
     fail "Write to source tree did not succeed in a ReadWrite source tree"
 
-  if  grep -q "${ERROR_MSG}\|${ERROR_HINT_PATTERN}" ${ERROR_LOG} ; then
+  if  grep -q "${ERROR_MSG}\|${ERROR_HINT_PATTERN}" "${ERROR_LOG}" ; then
     fail "Found read-only error OR error hint in error.log"
   fi
 }
@@ -606,11 +606,11 @@
     fail "BUILD file in symlink forest was not created";
   fi
 
-  local mtime1=$(stat -c "%y" out/soong/bp2build/a/b/BUILD.bazel)
+  local -r mtime1=$(stat -c "%y" out/soong/bp2build/a/b/BUILD.bazel)
 
   touch a/irrelevant.txt
   run_soong bp2build
-  local mtime2=$(stat -c "%y" out/soong/bp2build/a/b/BUILD.bazel)
+  local -r mtime2=$(stat -c "%y" out/soong/bp2build/a/b/BUILD.bazel)
 
   if [[ "$mtime1" != "$mtime2" ]]; then
     fail "BUILD.bazel file was regenerated"
@@ -657,10 +657,10 @@
   setup
 
   run_soong bp2build
-  local mtime1=$(stat -c "%y" out/soong/bp2build_workspace_marker)
+  local -r mtime1=$(stat -c "%y" out/soong/bp2build_workspace_marker)
 
   run_soong bp2build
-  local mtime2=$(stat -c "%y" out/soong/bp2build_workspace_marker)
+  local -r mtime2=$(stat -c "%y" out/soong/bp2build_workspace_marker)
 
   if [[ "$mtime1" != "$mtime2" ]]; then
     fail "Output Ninja file changed on null build"
@@ -717,19 +717,19 @@
   setup
 
   run_soong
-  local ninja_mtime1=$(stat -c "%y" out/soong/build.ninja)
+  local -r ninja_mtime1=$(stat -c "%y" out/soong/build.ninja)
 
   run_soong json-module-graph
-  local json_mtime1=$(stat -c "%y" out/soong/module-graph.json)
+  local -r json_mtime1=$(stat -c "%y" out/soong/module-graph.json)
 
   run_soong
-  local ninja_mtime2=$(stat -c "%y" out/soong/build.ninja)
+  local -r ninja_mtime2=$(stat -c "%y" out/soong/build.ninja)
   if [[ "$ninja_mtime1" != "$ninja_mtime2" ]]; then
     fail "Output Ninja file changed after writing JSON module graph"
   fi
 
   run_soong json-module-graph
-  local json_mtime2=$(stat -c "%y" out/soong/module-graph.json)
+  local -r json_mtime2=$(stat -c "%y" out/soong/module-graph.json)
   if [[ "$json_mtime1" != "$json_mtime2" ]]; then
     fail "JSON module graph file changed after writing Ninja file"
   fi
@@ -837,19 +837,19 @@
   setup
 
   run_soong
-  local output_mtime1=$(stat -c "%y" out/soong/build.ninja)
+  local -r output_mtime1=$(stat -c "%y" out/soong/build.ninja)
 
   run_soong bp2build
-  local output_mtime2=$(stat -c "%y" out/soong/build.ninja)
+  local -r output_mtime2=$(stat -c "%y" out/soong/build.ninja)
   if [[ "$output_mtime1" != "$output_mtime2" ]]; then
     fail "Output Ninja file changed when switching to bp2build"
   fi
 
-  local marker_mtime1=$(stat -c "%y" out/soong/bp2build_workspace_marker)
+  local -r marker_mtime1=$(stat -c "%y" out/soong/bp2build_workspace_marker)
 
   run_soong
-  local output_mtime3=$(stat -c "%y" out/soong/build.ninja)
-  local marker_mtime2=$(stat -c "%y" out/soong/bp2build_workspace_marker)
+  local -r output_mtime3=$(stat -c "%y" out/soong/build.ninja)
+  local -r marker_mtime2=$(stat -c "%y" out/soong/bp2build_workspace_marker)
   if [[ "$output_mtime1" != "$output_mtime3" ]]; then
     fail "Output Ninja file changed when switching to regular build from bp2build"
   fi
@@ -858,8 +858,8 @@
   fi
 
   run_soong bp2build
-  local output_mtime4=$(stat -c "%y" out/soong/build.ninja)
-  local marker_mtime3=$(stat -c "%y" out/soong/bp2build_workspace_marker)
+  local -r output_mtime4=$(stat -c "%y" out/soong/build.ninja)
+  local -r marker_mtime3=$(stat -c "%y" out/soong/bp2build_workspace_marker)
   if [[ "$output_mtime1" != "$output_mtime4" ]]; then
     fail "Output Ninja file changed when switching back to bp2build"
   fi
@@ -880,44 +880,14 @@
   setup
 
   run_soong queryview
-  local output_mtime1=$(stat -c "%y" out/soong/queryview.marker)
+  local -r output_mtime1=$(stat -c "%y" out/soong/queryview.marker)
 
   run_soong queryview
-  local output_mtime2=$(stat -c "%y" out/soong/queryview.marker)
+  local -r output_mtime2=$(stat -c "%y" out/soong/queryview.marker)
 
   if [[ "$output_mtime1" != "$output_mtime2" ]]; then
     fail "Queryview marker file changed on null build"
   fi
 }
 
-test_smoke
-test_null_build
-test_soong_docs_smoke
-test_null_build_after_soong_docs
-test_soong_build_rebuilt_if_blueprint_changes
-test_glob_noop_incremental
-test_add_file_to_glob
-test_add_android_bp
-test_change_android_bp
-test_delete_android_bp
-test_add_file_to_soong_build
-test_glob_during_bootstrapping
-test_soong_build_rerun_iff_environment_changes
-test_create_global_include_directory
-test_multiple_soong_build_modes
-test_dump_json_module_graph
-test_json_module_graph_back_and_forth_null_build
-test_write_to_source_tree
-test_queryview_smoke
-test_queryview_null_build
-test_bp2build_smoke
-test_bp2build_generates_marker_file
-test_bp2build_null_build
-test_bp2build_back_and_forth_null_build
-test_bp2build_add_android_bp
-test_bp2build_add_irrelevant_file
-test_bp2build_add_to_glob
-test_bp2build_bazel_workspace_structure
-test_bp2build_bazel_workspace_add_file
-test_bp2build_build_file_precedence
-test_bp2build_reports_multiple_errors
+scan_and_run_tests
diff --git a/tests/bp2build_bazel_test.sh b/tests/bp2build_bazel_test.sh
index 679ac55..07738b7 100755
--- a/tests/bp2build_bazel_test.sh
+++ b/tests/bp2build_bazel_test.sh
@@ -8,7 +8,7 @@
 
 readonly GENERATED_BUILD_FILE_NAME="BUILD.bazel"
 
-function test_bp2build_null_build() {
+function test_bp2build_null_build {
   setup
   run_soong bp2build
   local -r output_mtime1=$(stat -c "%y" out/soong/bp2build_workspace_marker)
@@ -21,9 +21,7 @@
   fi
 }
 
-test_bp2build_null_build
-
-function test_bp2build_null_build_with_globs() {
+function test_bp2build_null_build_with_globs {
   setup
 
   mkdir -p foo/bar
@@ -46,8 +44,6 @@
   fi
 }
 
-test_bp2build_null_build_with_globs
-
 function test_different_relative_outdir {
   setup
 
@@ -68,8 +64,6 @@
   (export OUT_DIR=$outdir; run_soong bp2build && run_bazel build --config=bp2build //a:g)
 }
 
-test_different_relative_outdir
-
 function test_different_absolute_outdir {
   setup
 
@@ -90,9 +84,7 @@
   (export OUT_DIR=$outdir; run_soong bp2build && run_bazel build --config=bp2build //a:g)
 }
 
-test_different_absolute_outdir
-
-function test_bp2build_generates_all_buildfiles {
+function _bp2build_generates_all_buildfiles {
   setup
 
   mkdir -p foo/convertible_soong_module
@@ -148,7 +140,7 @@
   # NOTE: We don't actually use the extra BUILD file for anything here
   run_bazel build --config=android --package_path=out/soong/workspace //foo/...
 
-  local the_answer_file="bazel-out/android_target-fastbuild/bin/foo/convertible_soong_module/the_answer.txt"
+  local the_answer_file="bazel-out/android_target-opt/bin/foo/convertible_soong_module/the_answer.txt"
   if [[ ! -f "${the_answer_file}" ]]; then
     fail "Expected '${the_answer_file}' to be generated, but was missing"
   fi
@@ -157,10 +149,12 @@
   fi
 }
 
-_save_trap=$(trap -p EXIT)
-trap '[[ $? -ne 0 ]] && echo Are you running this locally? Try changing --sandbox_tmpfs_path to something other than /tmp/ in build/bazel/linux.bazelrc.' EXIT
-test_bp2build_generates_all_buildfiles
-eval ${_save_trap}
+function test_bp2build_generates_all_buildfiles {
+  _save_trap=$(trap -p EXIT)
+  trap '[[ $? -ne 0 ]] && echo Are you running this locally? Try changing --sandbox_tmpfs_path to something other than /tmp/ in build/bazel/linux.bazelrc.' EXIT
+  _bp2build_generates_all_buildfiles
+  eval "${_save_trap}"
+}
 
 function test_cc_correctness {
   setup
@@ -213,8 +207,6 @@
   fi
 }
 
-test_cc_correctness
-
 # Regression test for the following failure during symlink forest creation:
 #
 #   Cannot stat '/tmp/st.rr054/foo/bar/unresolved_symlink': stat /tmp/st.rr054/foo/bar/unresolved_symlink: no such file or directory
@@ -239,4 +231,4 @@
   fi
 }
 
-test_bp2build_null_build_with_unresolved_symlink_in_source
+scan_and_run_tests
diff --git a/tests/lib.sh b/tests/lib.sh
index e40f0ad..f68199a 100644
--- a/tests/lib.sh
+++ b/tests/lib.sh
@@ -150,3 +150,16 @@
 export ALLOW_MISSING_DEPENDENCIES=true
 export ALLOW_BP_UNDER_SYMLINKS=true
 warmup_mock_top
+
+function scan_and_run_tests {
+  # find all test_ functions
+  # NB "declare -F" output is sorted, hence test order is deterministic
+  readarray -t test_fns < <(declare -F | sed -n -e 's/^declare -f \(test_.*\)$/\1/p')
+  info "Found ${#test_fns[*]} tests"
+  if [[ ${#test_fns[*]} -eq 0 ]]; then
+    fail "No tests found"
+  fi
+  for f in ${test_fns[*]}; do
+    $f
+  done
+}
\ No newline at end of file
diff --git a/tests/run_integration_tests.sh b/tests/run_integration_tests.sh
index 1e07727..7a71b27 100755
--- a/tests/run_integration_tests.sh
+++ b/tests/run_integration_tests.sh
@@ -13,3 +13,4 @@
 # The following tests build against the full source tree and don't rely on the
 # mock client.
 "$TOP/build/soong/tests/apex_comparison_tests.sh"
+"$TOP/build/soong/tests/apex_comparison_tests.sh" "module_arm64only"
diff --git a/ui/build/config.go b/ui/build/config.go
index ef2e87e..a6bba15 100644
--- a/ui/build/config.go
+++ b/ui/build/config.go
@@ -65,24 +65,25 @@
 	buildDateTime string
 
 	// From the arguments
-	parallel        int
-	keepGoing       int
-	verbose         bool
-	checkbuild      bool
-	dist            bool
-	jsonModuleGraph bool
-	apiBp2build     bool // Generate BUILD files for Soong modules that contribute APIs
-	bp2build        bool
-	queryview       bool
-	reportMkMetrics bool // Collect and report mk2bp migration progress metrics.
-	soongDocs       bool
-	skipConfig      bool
-	skipKati        bool
-	skipKatiNinja   bool
-	skipSoong       bool
-	skipNinja       bool
-	skipSoongTests  bool
-	searchApiDir    bool // Scan the Android.bp files generated in out/api_surfaces
+	parallel          int
+	keepGoing         int
+	verbose           bool
+	checkbuild        bool
+	dist              bool
+	jsonModuleGraph   bool
+	apiBp2build       bool // Generate BUILD files for Soong modules that contribute APIs
+	bp2build          bool
+	queryview         bool
+	reportMkMetrics   bool // Collect and report mk2bp migration progress metrics.
+	soongDocs         bool
+	skipConfig        bool
+	skipKati          bool
+	skipKatiNinja     bool
+	skipSoong         bool
+	skipNinja         bool
+	skipSoongTests    bool
+	searchApiDir      bool // Scan the Android.bp files generated in out/api_surfaces
+	skipMetricsUpload bool
 
 	// From the product config
 	katiArgs        []string
@@ -735,6 +736,8 @@
 			c.skipConfig = true
 		} else if arg == "--skip-soong-tests" {
 			c.skipSoongTests = true
+		} else if arg == "--skip-metrics-upload" {
+			c.skipMetricsUpload = true
 		} else if arg == "--mk-metrics" {
 			c.reportMkMetrics = true
 		} else if arg == "--bazel-mode" {
@@ -1512,6 +1515,10 @@
 	return c.bazelForceEnabledModules
 }
 
+func (c *configImpl) SkipMetricsUpload() bool {
+	return c.skipMetricsUpload
+}
+
 func GetMetricsUploader(topDir string, env *Environment) string {
 	if p, ok := env.Get("METRICS_UPLOADER"); ok {
 		metricsUploader := filepath.Join(topDir, p)
diff --git a/ui/build/soong.go b/ui/build/soong.go
index b89ca20..370b1bc 100644
--- a/ui/build/soong.go
+++ b/ui/build/soong.go
@@ -566,11 +566,12 @@
 		targets = append(targets, config.SoongNinjaFile())
 	}
 
+	// TODO(juu): Stop embedding soong_build_metrics in soong_metrics.
+	soongBuildMetricsFile := filepath.Join(config.LogsDir(), "soong_build_metrics.pb")
+	if err := os.Remove(soongBuildMetricsFile); err != nil && !os.IsNotExist(err) {
+		ctx.Verbosef("Failed to remove %s", soongBuildMetricsFile)
+	}
 	if shouldCollectBuildSoongMetrics(config) {
-		soongBuildMetricsFile := filepath.Join(config.LogsDir(), "soong_build_metrics.pb")
-		if err := os.Remove(soongBuildMetricsFile); err != nil && !os.IsNotExist(err) {
-			ctx.Verbosef("Failed to remove %s", soongBuildMetricsFile)
-		}
 		defer func() {
 			soongBuildMetrics := loadSoongBuildMetrics(ctx, soongBuildMetricsFile)
 			if soongBuildMetrics != nil {