Merge "Add test for missing certificate with AllowMissingDependencies"
diff --git a/android/allowlists/allowlists.go b/android/allowlists/allowlists.go
index e7bd920..07db3c2 100644
--- a/android/allowlists/allowlists.go
+++ b/android/allowlists/allowlists.go
@@ -164,6 +164,7 @@
 		"external/lzma/C":                        Bp2BuildDefaultTrueRecursively,
 		"external/mdnsresponder":                 Bp2BuildDefaultTrueRecursively,
 		"external/minijail":                      Bp2BuildDefaultTrueRecursively,
+		"external/musl":                          Bp2BuildDefaultTrueRecursively,
 		"external/objenesis":                     Bp2BuildDefaultTrueRecursively,
 		"external/openscreen":                    Bp2BuildDefaultTrueRecursively,
 		"external/ow2-asm":                       Bp2BuildDefaultTrueRecursively,
@@ -364,6 +365,7 @@
 		"external/guava":/* recursive = */ true,
 		"external/jsr305":/* recursive = */ true,
 		"external/protobuf":/* recursive = */ false,
+		"external/python/absl-py":/* recursive = */ true,
 
 		// this BUILD file is globbed by //external/icu/icu4c/source:icu4c_test_data's "data/**/*".
 		"external/icu/icu4c/source/data/unidata/norm2":/* recursive = */ false,
@@ -731,8 +733,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",
@@ -793,6 +794,7 @@
 		"libstatslog",               // depends on unconverted modules: libstatspull, statsd-aidl-ndk
 		"libstatslog_art",           // depends on unconverted modules: statslog_art.cpp, statslog_art.h
 		"linker_reloc_bench_main",   // depends on unconverted modules: liblinker_reloc_bench_*
+		"malloc-rss-benchmark",      // depends on unconverted modules: libmeminfo
 		"pbtombstone", "crash_dump", // depends on libdebuggerd, libunwindstack
 		"robolectric-sqlite4java-0.282", // depends on unconverted modules: robolectric-sqlite4java-import, robolectric-sqlite4java-native
 		"static_crasher",                // depends on unconverted modules: libdebuggerd_handler
@@ -1305,6 +1307,9 @@
 		// TODO(b/254476335): disable the following due to this bug
 		"libapexinfo",
 		"libapexinfo_tests",
+
+		// uses glob in $(locations)
+		"libc_musl_sysroot",
 	}
 
 	Bp2buildCcLibraryStaticOnlyList = []string{}
@@ -1350,7 +1355,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",
 	}
 
@@ -1361,5 +1365,9 @@
 	// 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"}
+	// It is implicit that all modules in ProdMixedBuildsEnabledList will
+	// also be built - do not add them to this list.
+	StagingMixedBuildsEnabledList = []string{
+		"com.android.adbd",
+	}
 )
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/arch_list.go b/android/arch_list.go
index cbf8e7a..578904c 100644
--- a/android/arch_list.go
+++ b/android/arch_list.go
@@ -31,6 +31,8 @@
 		"amberlake",
 		"atom",
 		"broadwell",
+		"goldmont",
+		"goldmont-plus",
 		"haswell",
 		"icelake",
 		"ivybridge",
@@ -40,12 +42,15 @@
 		"skylake",
 		"stoneyridge",
 		"tigerlake",
+		"tremont",
 		"whiskeylake",
 		"x86_64",
 	},
 	X86_64: {
 		"amberlake",
 		"broadwell",
+		"goldmont",
+		"goldmont-plus",
 		"haswell",
 		"icelake",
 		"ivybridge",
@@ -55,6 +60,7 @@
 		"skylake",
 		"stoneyridge",
 		"tigerlake",
+		"tremont",
 		"whiskeylake",
 	},
 }
@@ -168,6 +174,24 @@
 			"aes_ni",
 			"popcnt",
 		},
+		"goldmont": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"aes_ni",
+			"popcnt",
+			"movbe",
+		},
+		"goldmont-plus": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"aes_ni",
+			"popcnt",
+			"movbe",
+		},
 		"haswell": {
 			"ssse3",
 			"sse4",
@@ -257,6 +281,15 @@
 			"aes_ni",
 			"popcnt",
 		},
+		"tremont": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"aes_ni",
+			"popcnt",
+			"movbe",
+		},
 		"whiskeylake": {
 			"ssse3",
 			"sse4",
@@ -304,6 +337,22 @@
 			"aes_ni",
 			"popcnt",
 		},
+		"goldmont": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"aes_ni",
+			"popcnt",
+		},
+		"goldmont-plus": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"aes_ni",
+			"popcnt",
+		},
 		"haswell": {
 			"ssse3",
 			"sse4",
@@ -390,6 +439,14 @@
 			"aes_ni",
 			"popcnt",
 		},
+		"tremont": {
+			"ssse3",
+			"sse4",
+			"sse4_1",
+			"sse4_2",
+			"aes_ni",
+			"popcnt",
+		},
 		"whiskeylake": {
 			"ssse3",
 			"sse4",
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index 122495f..0b768a7 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -16,13 +16,13 @@
 
 import (
 	"bytes"
-	"errors"
 	"fmt"
 	"os"
 	"os/exec"
 	"path"
 	"path/filepath"
 	"runtime"
+	"sort"
 	"strings"
 	"sync"
 
@@ -375,108 +375,96 @@
 	return []bazel.AqueryDepset{}
 }
 
-func NewBazelContext(c *config) (BazelContext, error) {
-	var modulesDefaultToBazel bool
+func GetBazelEnabledAndDisabledModules(buildMode SoongBuildMode, forceEnabled map[string]struct{}) (map[string]bool, map[string]bool) {
 	disabledModules := map[string]bool{}
 	enabledModules := map[string]bool{}
-
-	switch c.BuildMode {
-	case BazelProdMode:
-		modulesDefaultToBazel = false
-
-		for _, enabledProdModule := range allowlists.ProdMixedBuildsEnabledList {
-			enabledModules[enabledProdModule] = true
+	addToStringSet := func(set map[string]bool, items []string) {
+		for _, item := range items {
+			set[item] = true
 		}
+	}
 
-		for enabledAdHocModule := range c.BazelModulesForceEnabledByFlag() {
+	switch buildMode {
+	case BazelProdMode:
+		addToStringSet(enabledModules, allowlists.ProdMixedBuildsEnabledList)
+		for enabledAdHocModule := range forceEnabled {
 			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
-		}
-
-		for enabledAdHocModule := range c.BazelModulesForceEnabledByFlag() {
+		addToStringSet(enabledModules, allowlists.ProdMixedBuildsEnabledList)
+		addToStringSet(enabledModules, allowlists.StagingMixedBuildsEnabledList)
+		for enabledAdHocModule := range forceEnabled {
 			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:
+		panic("Expected BazelProdMode, BazelStagingMode, or BazelDevMode")
+	}
+	return enabledModules, disabledModules
+}
+
+func GetBazelEnabledModules(buildMode SoongBuildMode) []string {
+	enabledModules, disabledModules := GetBazelEnabledAndDisabledModules(buildMode, nil)
+	enabledList := make([]string, 0, len(enabledModules))
+	for module := range enabledModules {
+		if !disabledModules[module] {
+			enabledList = append(enabledList, module)
+		}
+	}
+	sort.Strings(enabledList)
+	return enabledList
+}
+
+func NewBazelContext(c *config) (BazelContext, error) {
+	if c.BuildMode != BazelProdMode && c.BuildMode != BazelStagingMode && c.BuildMode != BazelDevMode {
 		return noopBazelContext{}, nil
 	}
 
-	p, err := bazelPathsFromConfig(c)
-	if err != nil {
-		return nil, err
-	}
+	enabledModules, disabledModules := GetBazelEnabledAndDisabledModules(c.BuildMode, c.BazelModulesForceEnabledByFlag())
 
+	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
 }
@@ -1137,7 +1125,7 @@
 }
 
 // Register bazel-owned build statements (obtained from the aquery invocation).
-func createCommand(cmd *RuleBuilderCommand, buildStatement bazel.BuildStatement, executionRoot string, bazelOutDir string, ctx PathContext) {
+func createCommand(cmd *RuleBuilderCommand, buildStatement bazel.BuildStatement, executionRoot string, bazelOutDir string, ctx BuilderContext) {
 	// executionRoot is the action cwd.
 	cmd.Text(fmt.Sprintf("cd '%s' &&", executionRoot))
 
@@ -1156,7 +1144,14 @@
 	}
 
 	// The actual Bazel action.
-	cmd.Text(buildStatement.Command)
+	if len(buildStatement.Command) > 16*1024 {
+		commandFile := PathForBazelOut(ctx, buildStatement.OutputPaths[0]+".sh")
+		WriteFileRule(ctx, commandFile, buildStatement.Command)
+
+		cmd.Text("bash").Text(buildStatement.OutputPaths[0] + ".sh").Implicit(commandFile)
+	} else {
+		cmd.Text(buildStatement.Command)
+	}
 
 	for _, outputPath := range buildStatement.OutputPaths {
 		cmd.ImplicitOutput(PathForBazelOut(ctx, outputPath))
diff --git a/android/bazel_handler_test.go b/android/bazel_handler_test.go
index bc16cb5..10bbf31 100644
--- a/android/bazel_handler_test.go
+++ b/android/bazel_handler_test.go
@@ -129,7 +129,8 @@
 		}
 
 		cmd := RuleBuilderCommand{}
-		createCommand(&cmd, got[0], "test/exec_root", "test/bazel_out", PathContextForTesting(TestConfig("out", nil, "", nil)))
+		ctx := builderContextForTests{PathContextForTesting(TestConfig("out", nil, "", nil))}
+		createCommand(&cmd, got[0], "test/exec_root", "test/bazel_out", ctx)
 		if actual, expected := cmd.buf.String(), testCase.command; expected != actual {
 			t.Errorf("expected: [%s], actual: [%s]", expected, actual)
 		}
diff --git a/android/bazel_paths.go b/android/bazel_paths.go
index b2ea22f..3b159d3 100644
--- a/android/bazel_paths.go
+++ b/android/bazel_paths.go
@@ -458,6 +458,11 @@
 	return fmt.Sprintf("//%s:%s", moduleDir, moduleName)
 }
 
+// ModuleFromBazelLabel reverses the logic in bp2buildModuleLabel
+func ModuleFromBazelLabel(label string) string {
+	return strings.Split(label, ":")[1]
+}
+
 // BazelOutPath is a Bazel output path compatible to be used for mixed builds within Soong/Ninja.
 type BazelOutPath struct {
 	OutputPath
diff --git a/android/config.go b/android/config.go
index 9d9ab30..9c1a484 100644
--- a/android/config.go
+++ b/android/config.go
@@ -21,7 +21,6 @@
 	"bytes"
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"reflect"
@@ -70,6 +69,26 @@
 
 type SoongBuildMode int
 
+type CmdArgs struct {
+	bootstrap.Args
+	RunGoTests  bool
+	OutDir      string
+	SoongOutDir string
+
+	SymlinkForestMarker string
+	Bp2buildMarker      string
+	BazelQueryViewDir   string
+	BazelApiBp2buildDir string
+	ModuleGraphFile     string
+	ModuleActionsFile   string
+	DocFile             string
+
+	BazelMode                bool
+	BazelModeDev             bool
+	BazelModeStaging         bool
+	BazelForceEnabledModules string
+}
+
 // Build modes that soong_build can run as.
 const (
 	// Don't use bazel at all during module analysis.
@@ -307,7 +326,7 @@
 		return fmt.Errorf("cannot marshal config data: %s", err.Error())
 	}
 
-	f, err := ioutil.TempFile(filepath.Dir(filename), "config")
+	f, err := os.CreateTemp(filepath.Dir(filename), "config")
 	if err != nil {
 		return fmt.Errorf("cannot create empty config file %s: %s", filename, err.Error())
 	}
@@ -404,20 +423,19 @@
 
 // NewConfig creates a new Config object. The srcDir argument specifies the path
 // to the root source directory. It also loads the config file, if found.
-func NewConfig(moduleListFile string, buildMode SoongBuildMode, runGoTests bool, outDir, soongOutDir string, availableEnv map[string]string,
-	bazelForceEnabledModules []string) (Config, error) {
+func NewConfig(cmdArgs CmdArgs, availableEnv map[string]string) (Config, error) {
 	// Make a config with default options.
 	config := &config{
-		ProductVariablesFileName: filepath.Join(soongOutDir, productVariablesFileName),
+		ProductVariablesFileName: filepath.Join(cmdArgs.SoongOutDir, productVariablesFileName),
 
 		env: availableEnv,
 
-		outDir:            outDir,
-		soongOutDir:       soongOutDir,
-		runGoTests:        runGoTests,
+		outDir:            cmdArgs.OutDir,
+		soongOutDir:       cmdArgs.SoongOutDir,
+		runGoTests:        cmdArgs.RunGoTests,
 		multilibConflicts: make(map[ArchType]bool),
 
-		moduleListFile:            moduleListFile,
+		moduleListFile:            cmdArgs.ModuleListFile,
 		fs:                        pathtools.NewOsFs(absSrcDir),
 		mixedBuildDisabledModules: make(map[string]struct{}),
 		mixedBuildEnabledModules:  make(map[string]struct{}),
@@ -430,7 +448,7 @@
 
 	// Soundness check of the build and source directories. This won't catch strange
 	// configurations with symlinks, but at least checks the obvious case.
-	absBuildDir, err := filepath.Abs(soongOutDir)
+	absBuildDir, err := filepath.Abs(cmdArgs.SoongOutDir)
 	if err != nil {
 		return Config{}, err
 	}
@@ -450,7 +468,7 @@
 		return Config{}, err
 	}
 
-	KatiEnabledMarkerFile := filepath.Join(soongOutDir, ".soong.kati_enabled")
+	KatiEnabledMarkerFile := filepath.Join(cmdArgs.SoongOutDir, ".soong.kati_enabled")
 	if _, err := os.Stat(absolutePath(KatiEnabledMarkerFile)); err == nil {
 		config.katiEnabled = true
 	}
@@ -503,11 +521,32 @@
 		config.AndroidFirstDeviceTarget = FirstTarget(config.Targets[Android], "lib64", "lib32")[0]
 	}
 
-	config.BuildMode = buildMode
+	if cmdArgs.SymlinkForestMarker != "" {
+		config.BuildMode = SymlinkForest
+	} else if cmdArgs.Bp2buildMarker != "" {
+		config.BuildMode = Bp2build
+	} else if cmdArgs.BazelQueryViewDir != "" {
+		config.BuildMode = GenerateQueryView
+	} else if cmdArgs.BazelApiBp2buildDir != "" {
+		config.BuildMode = ApiBp2build
+	} else if cmdArgs.ModuleGraphFile != "" {
+		config.BuildMode = GenerateModuleGraph
+	} else if cmdArgs.DocFile != "" {
+		config.BuildMode = GenerateDocFile
+	} else if cmdArgs.BazelModeDev {
+		config.BuildMode = BazelDevMode
+	} else if cmdArgs.BazelMode {
+		config.BuildMode = BazelProdMode
+	} else if cmdArgs.BazelModeStaging {
+		config.BuildMode = BazelStagingMode
+	} else {
+		config.BuildMode = AnalysisNoBazel
+	}
+
 	config.BazelContext, err = NewBazelContext(config)
 	config.Bp2buildPackageConfig = GetBp2BuildAllowList()
 
-	for _, module := range bazelForceEnabledModules {
+	for _, module := range strings.Split(cmdArgs.BazelForceEnabledModules, ",") {
 		config.bazelForceEnabledModules[module] = struct{}{}
 	}
 
@@ -1150,7 +1189,7 @@
 		return nil, nil
 	}
 	ctx.AddNinjaFileDeps(path.String())
-	return ioutil.ReadFile(absolutePath(path.String()))
+	return os.ReadFile(absolutePath(path.String()))
 }
 
 func (c *deviceConfig) WithDexpreopt() bool {
@@ -1169,7 +1208,7 @@
 	return c.multilibConflicts[arch]
 }
 
-func (c *config) PrebuiltHiddenApiDir(ctx PathContext) string {
+func (c *config) PrebuiltHiddenApiDir(_ PathContext) string {
 	return String(c.productVariables.PrebuiltHiddenApiDir)
 }
 
diff --git a/android/gen_notice.go b/android/gen_notice.go
index 28fddbc..091345b 100644
--- a/android/gen_notice.go
+++ b/android/gen_notice.go
@@ -16,6 +16,7 @@
 
 import (
 	"fmt"
+	"path/filepath"
 	"strings"
 
 	"github.com/google/blueprint/proptools"
@@ -73,6 +74,7 @@
 		out(ctx, gm.output, ctx.ModuleName(gm),
 			proptools.StringDefault(gm.properties.ArtifactName, defaultName),
 			[]string{
+				filepath.Join(ctx.Config().OutDir(), "target", "product", ctx.Config().DeviceName()) + "/",
 				ctx.Config().OutDir() + "/",
 				ctx.Config().SoongOutDir() + "/",
 			}, modules...)
diff --git a/android/module.go b/android/module.go
index 681f724..bf9737a 100644
--- a/android/module.go
+++ b/android/module.go
@@ -27,7 +27,6 @@
 	"text/scanner"
 
 	"android/soong/bazel"
-
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 )
diff --git a/android/proto.go b/android/proto.go
index 8204f77..09e50c8 100644
--- a/android/proto.go
+++ b/android/proto.go
@@ -234,10 +234,13 @@
 			}
 		}
 
+		tags := ApexAvailableTags(ctx.Module())
+
 		ctx.CreateBazelTargetModule(
 			bazel.BazelTargetModuleProperties{Rule_class: "proto_library"},
-			CommonAttributes{Name: info.Name},
-			&attrs)
+			CommonAttributes{Name: info.Name, Tags: tags},
+			&attrs,
+		)
 
 		protoLibraries.Add(&bazel.Label{
 			Label: ":" + info.Name,
diff --git a/android/register.go b/android/register.go
index 6cfc205..9a3d3aa 100644
--- a/android/register.go
+++ b/android/register.go
@@ -35,7 +35,7 @@
 	// tests.
 	componentName() string
 
-	// register registers this component in the supplied context.
+	// registers this component in the supplied context.
 	register(ctx *Context)
 }
 
@@ -124,7 +124,7 @@
 	return func() blueprint.Singleton {
 		singleton := factory()
 		if makevars, ok := singleton.(SingletonMakeVarsProvider); ok {
-			registerSingletonMakeVarsProvider(ctx.config, makevars)
+			ctx.registerSingletonMakeVarsProvider(makevars)
 		}
 		return &singletonAdaptor{Singleton: singleton}
 	}
@@ -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)
 }
 
@@ -207,6 +208,14 @@
 	singletons.registerAll(ctx)
 }
 
+func (ctx *Context) Config() Config {
+	return ctx.config
+}
+
+func (ctx *Context) registerSingletonMakeVarsProvider(makevars SingletonMakeVarsProvider) {
+	registerSingletonMakeVarsProvider(ctx.config, makevars)
+}
+
 func collateGloballyRegisteredSingletons() sortableComponents {
 	allSingletons := append(sortableComponents(nil), singletons...)
 	allSingletons = append(allSingletons,
@@ -334,7 +343,7 @@
 	PreArchMutators(f)
 }
 
-func (ctx *initRegistrationContext) HardCodedPreArchMutators(f RegisterMutatorFunc) {
+func (ctx *initRegistrationContext) HardCodedPreArchMutators(_ RegisterMutatorFunc) {
 	// Nothing to do as the mutators are hard code in preArch in mutator.go
 }
 
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..36ce658 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) {
@@ -1897,6 +1906,12 @@
 	a.containerCertificateFile = android.PathForBazelOut(ctx, outputs.ContainerKeyInfo[0])
 	a.containerPrivateKeyFile = android.PathForBazelOut(ctx, outputs.ContainerKeyInfo[1])
 
+	// Ensure ApexInfo.RequiresLibs are installed as part of a bundle build
+	for _, bazelLabel := range outputs.RequiresLibs {
+		// convert Bazel label back to Soong module name
+		a.requiredDeps = append(a.requiredDeps, android.ModuleFromBazelLabel(bazelLabel))
+	}
+
 	apexType := a.properties.ApexType
 	switch apexType {
 	case imageApex:
@@ -3078,31 +3093,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..c0bdfc4 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) {
@@ -9800,11 +9835,11 @@
 						JavaSymbolsUsedByApex: "foo_using.xml",
 						BundleFile:            "apex_bundle.zip",
 						InstalledFiles:        "installed-files.txt",
+						RequiresLibs:          []string{"//path/c:c", "//path/d:d"},
 
 						// unused
 						PackageName:  "pkg_name",
 						ProvidesLibs: []string{"a", "b"},
-						RequiresLibs: []string{"c", "d"},
 					},
 				},
 			}
@@ -9860,4 +9895,7 @@
 	if w := "$(call dist-for-goals,checkbuild,out/bazel/execroot/__main__/installed-files.txt:foo-installed-files.txt)"; !strings.Contains(data, w) {
 		t.Errorf("Expected %q in androidmk data, but did not find %q", w, data)
 	}
+	if w := "LOCAL_REQUIRED_MODULES := c d"; !strings.Contains(data, w) {
+		t.Errorf("Expected %q in androidmk data, but did not find it in %q", w, data)
+	}
 }
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/cquery/request_type.go b/bazel/cquery/request_type.go
index 118a3a9..9feb82b 100644
--- a/bazel/cquery/request_type.go
+++ b/bazel/cquery/request_type.go
@@ -227,7 +227,7 @@
 //   - The function body should not be indented outside of its own scope.
 func (g getApexInfoType) StarlarkFunctionBody() string {
 	return `
-info = providers(target).get("//build/bazel/rules/apex:apex.bzl%ApexInfo")
+info = providers(target).get("//build/bazel/rules/apex:apex_info.bzl%ApexInfo")
 if not info:
   fail("%s did not provide ApexInfo" % id_string)
 bundle_key_info = info.bundle_key_info
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/Android.bp b/bp2build/Android.bp
index 7c9af1a..2e01789 100644
--- a/bp2build/Android.bp
+++ b/bp2build/Android.bp
@@ -51,6 +51,7 @@
         "cc_prebuilt_library_conversion_test.go",
         "cc_prebuilt_library_shared_test.go",
         "cc_prebuilt_library_static_test.go",
+        "cc_prebuilt_object_conversion_test.go",
         "cc_test_conversion_test.go",
         "cc_yasm_conversion_test.go",
         "conversion_test.go",
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/cc_object_conversion_test.go b/bp2build/cc_object_conversion_test.go
index b8dc690..1377c6b 100644
--- a/bp2build/cc_object_conversion_test.go
+++ b/bp2build/cc_object_conversion_test.go
@@ -24,6 +24,7 @@
 func registerCcObjectModuleTypes(ctx android.RegistrationContext) {
 	// Always register cc_defaults module factory
 	ctx.RegisterModuleType("cc_defaults", func() android.Module { return cc.DefaultsFactory() })
+	ctx.RegisterModuleType("cc_library_headers", cc.LibraryHeaderFactory)
 }
 
 func runCcObjectTestCase(t *testing.T, tc Bp2buildTestCase) {
@@ -147,7 +148,7 @@
 				"system_dynamic_deps": `[]`,
 			}), MakeBazelTarget("cc_object", "foo", AttrNameToString{
 				"copts":               `["-fno-addrsig"]`,
-				"deps":                `[":bar"]`,
+				"objs":                `[":bar"]`,
 				"srcs":                `["a/b/c.c"]`,
 				"system_dynamic_deps": `[]`,
 			}),
@@ -362,7 +363,7 @@
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("cc_object", "foo", AttrNameToString{
 				"copts": `["-fno-addrsig"]`,
-				"deps": `select({
+				"objs": `select({
         "//build/bazel/platforms/arch:arm": [":arm_obj"],
         "//build/bazel/platforms/arch:x86": [":x86_obj"],
         "//build/bazel/platforms/arch:x86_64": [":x86_64_obj"],
@@ -422,3 +423,56 @@
 		},
 	})
 }
+
+func TestCcObjectHeaderLib(t *testing.T) {
+	runCcObjectTestCase(t, Bp2buildTestCase{
+		Description: "simple cc_object generates cc_object with include header dep",
+		Filesystem: map[string]string{
+			"a/b/foo.h":     "",
+			"a/b/bar.h":     "",
+			"a/b/exclude.c": "",
+			"a/b/c.c":       "",
+		},
+		Blueprint: `cc_object {
+    name: "foo",
+	header_libs: ["libheaders"],
+    system_shared_libs: [],
+    cflags: [
+        "-Wno-gcc-compat",
+        "-Wall",
+        "-Werror",
+    ],
+    srcs: [
+        "a/b/*.c"
+    ],
+    exclude_srcs: ["a/b/exclude.c"],
+    sdk_version: "current",
+    min_sdk_version: "29",
+}
+
+cc_library_headers {
+    name: "libheaders",
+	export_include_dirs: ["include"],
+}
+`,
+		ExpectedBazelTargets: []string{
+			MakeBazelTarget("cc_object", "foo", AttrNameToString{
+				"copts": `[
+        "-fno-addrsig",
+        "-Wno-gcc-compat",
+        "-Wall",
+        "-Werror",
+    ]`,
+				"deps":                `[":libheaders"]`,
+				"local_includes":      `["."]`,
+				"srcs":                `["a/b/c.c"]`,
+				"system_dynamic_deps": `[]`,
+				"sdk_version":         `"current"`,
+				"min_sdk_version":     `"29"`,
+			}),
+			MakeBazelTarget("cc_library_headers", "libheaders", AttrNameToString{
+				"export_includes": `["include"]`,
+			}),
+		},
+	})
+}
diff --git a/bp2build/cc_prebuilt_object_conversion_test.go b/bp2build/cc_prebuilt_object_conversion_test.go
new file mode 100644
index 0000000..903c816
--- /dev/null
+++ b/bp2build/cc_prebuilt_object_conversion_test.go
@@ -0,0 +1,101 @@
+// Copyright 2022 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package bp2build
+
+import (
+	"fmt"
+	"testing"
+
+	"android/soong/cc"
+)
+
+func runCcPrebuiltObjectTestCase(t *testing.T, testCase Bp2buildTestCase) {
+	t.Helper()
+	description := fmt.Sprintf("cc_prebuilt_object: %s", testCase.Description)
+	testCase.ModuleTypeUnderTest = "cc_prebuilt_object"
+	testCase.ModuleTypeUnderTestFactory = cc.PrebuiltObjectFactory
+	testCase.Description = description
+	t.Run(description, func(t *testing.T) {
+		t.Helper()
+		RunBp2BuildTestCaseSimple(t, testCase)
+	})
+}
+
+func TestPrebuiltObject(t *testing.T) {
+	runCcPrebuiltObjectTestCase(t,
+		Bp2buildTestCase{
+			Description: "simple",
+			Filesystem: map[string]string{
+				"obj.o": "",
+			},
+			Blueprint: `
+cc_prebuilt_object {
+	name: "objtest",
+	srcs: ["obj.o"],
+	bazel_module: { bp2build_available: true },
+}`,
+			ExpectedBazelTargets: []string{
+				MakeBazelTarget("cc_prebuilt_object", "objtest", AttrNameToString{
+					"src": `"obj.o"`,
+				})},
+		})
+}
+
+func TestPrebuiltObjectWithArchVariance(t *testing.T) {
+	runCcPrebuiltObjectTestCase(t,
+		Bp2buildTestCase{
+			Description: "with arch variance",
+			Filesystem: map[string]string{
+				"obja.o": "",
+				"objb.o": "",
+			},
+			Blueprint: `
+cc_prebuilt_object {
+	name: "objtest",
+	arch: {
+		arm64: { srcs: ["obja.o"], },
+		arm: { srcs: ["objb.o"], },
+	},
+	bazel_module: { bp2build_available: true },
+}`, ExpectedBazelTargets: []string{
+				MakeBazelTarget("cc_prebuilt_object", "objtest", AttrNameToString{
+					"src": `select({
+        "//build/bazel/platforms/arch:arm": "objb.o",
+        "//build/bazel/platforms/arch:arm64": "obja.o",
+        "//conditions:default": None,
+    })`,
+				}),
+			},
+		})
+}
+
+func TestPrebuiltObjectMultipleSrcsFails(t *testing.T) {
+	runCcPrebuiltObjectTestCase(t,
+		Bp2buildTestCase{
+			Description: "fails because multiple sources",
+			Filesystem: map[string]string{
+				"obja": "",
+				"objb": "",
+			},
+			Blueprint: `
+cc_prebuilt_object {
+	name: "objtest",
+	srcs: ["obja.o", "objb.o"],
+	bazel_module: { bp2build_available: true },
+}`,
+			ExpectedErr: fmt.Errorf("Expected at most one source file"),
+		})
+}
+
+// TODO: nosrcs test
diff --git a/bp2build/cc_test_conversion_test.go b/bp2build/cc_test_conversion_test.go
index 8c2d30d..b20f6ff 100644
--- a/bp2build/cc_test_conversion_test.go
+++ b/bp2build/cc_test_conversion_test.go
@@ -26,6 +26,7 @@
 type ccTestBp2buildTestCase struct {
 	description string
 	blueprint   string
+	filesystem  map[string]string
 	targets     []testBazelTarget
 }
 
@@ -41,12 +42,12 @@
 func runCcTestTestCase(t *testing.T, testCase ccTestBp2buildTestCase) {
 	t.Helper()
 	moduleTypeUnderTest := "cc_test"
-
 	description := fmt.Sprintf("%s %s", moduleTypeUnderTest, testCase.description)
 	t.Run(description, func(t *testing.T) {
 		t.Helper()
 		RunBp2BuildTestCase(t, registerCcTestModuleTypes, Bp2buildTestCase{
 			ExpectedBazelTargets:       generateBazelTargetsForTest(testCase.targets, android.HostAndDeviceSupported),
+			Filesystem:                 testCase.filesystem,
 			ModuleTypeUnderTest:        moduleTypeUnderTest,
 			ModuleTypeUnderTestFactory: cc.TestFactory,
 			Description:                description,
@@ -172,3 +173,90 @@
 		},
 	})
 }
+
+func TestCcTest_TestConfig(t *testing.T) {
+	runCcTestTestCase(t, ccTestBp2buildTestCase{
+		description: "cc test that sets a test_config",
+		filesystem: map[string]string{
+			"test_config.xml": "",
+		},
+		blueprint: `
+cc_test {
+	name: "mytest",
+	srcs: ["test.cpp"],
+	test_config: "test_config.xml",
+}
+`,
+		targets: []testBazelTarget{
+			{"cc_test", "mytest", AttrNameToString{
+				"gtest":                  "True",
+				"isolated":               "True",
+				"local_includes":         `["."]`,
+				"srcs":                   `["test.cpp"]`,
+				"target_compatible_with": `["//build/bazel/platforms/os:android"]`,
+				"test_config":            `"test_config.xml"`,
+			},
+			},
+		},
+	})
+}
+
+func TestCcTest_TestConfigAndroidTestXML(t *testing.T) {
+	runCcTestTestCase(t, ccTestBp2buildTestCase{
+		description: "cc test that defaults to test config AndroidTest.xml",
+		filesystem: map[string]string{
+			"AndroidTest.xml": "",
+		},
+		blueprint: `
+cc_test {
+	name: "mytest",
+	srcs: ["test.cpp"],
+}
+`,
+		targets: []testBazelTarget{
+			{"cc_test", "mytest", AttrNameToString{
+				"gtest":                  "True",
+				"isolated":               "True",
+				"local_includes":         `["."]`,
+				"srcs":                   `["test.cpp"]`,
+				"target_compatible_with": `["//build/bazel/platforms/os:android"]`,
+				"test_config":            `"AndroidTest.xml"`,
+			},
+			},
+		},
+	})
+}
+
+func TestCcTest_TestConfigTemplateOptions(t *testing.T) {
+	runCcTestTestCase(t, ccTestBp2buildTestCase{
+		description: "cc test that sets test config template attributes",
+		filesystem: map[string]string{
+			"test_config_template.xml": "",
+		},
+		blueprint: `
+cc_test {
+	name: "mytest",
+	srcs: ["test.cpp"],
+	test_config_template: "test_config_template.xml",
+	auto_gen_config: true,
+}
+`,
+		targets: []testBazelTarget{
+			{"cc_test", "mytest", AttrNameToString{
+				"auto_generate_test_config": "True",
+				"gtest":                     "True",
+				"isolated":                  "True",
+				"local_includes":            `["."]`,
+				"srcs":                      `["test.cpp"]`,
+				"target_compatible_with":    `["//build/bazel/platforms/os:android"]`,
+				"template_configs": `[
+        "'<target_preparer class=\"com.android.tradefed.targetprep.RootTargetPreparer\">\\n        <option name=\"force-root\" value=\"false\" />\\n    </target_preparer>'",
+        "'<option name=\"not-shardable\" value=\"true\" />'",
+    ]`,
+				"template_install_base": `"/data/local/tmp"`,
+				"template_test_config":  `"test_config_template.xml"`,
+			},
+			},
+		},
+	})
+}
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/bp2build/conversion.go b/bp2build/conversion.go
index 6eb93bc..e15dd59 100644
--- a/bp2build/conversion.go
+++ b/bp2build/conversion.go
@@ -55,6 +55,10 @@
 	files = append(files, newFile("api_levels", "api_levels.json", string(apiLevelsContent)))
 	files = append(files, newFile("api_levels", "api_levels.bzl", android.StarlarkApiLevelConfigs(cfg)))
 
+	// TODO(b/262781701): Create an alternate soong_build entrypoint for writing out these files only when requested
+	files = append(files, newFile("allowlists", "mixed_build_prod_allowlist.txt", strings.Join(android.GetBazelEnabledModules(android.BazelProdMode), "\n")+"\n"))
+	files = append(files, newFile("allowlists", "mixed_build_staging_allowlist.txt", strings.Join(android.GetBazelEnabledModules(android.BazelStagingMode), "\n")+"\n"))
+
 	return files
 }
 
diff --git a/bp2build/conversion_test.go b/bp2build/conversion_test.go
index 8de2f83..dfc7f0b 100644
--- a/bp2build/conversion_test.go
+++ b/bp2build/conversion_test.go
@@ -147,6 +147,14 @@
 			dir:      "api_levels",
 			basename: "api_levels.bzl",
 		},
+		{
+			dir:      "allowlists",
+			basename: "mixed_build_prod_allowlist.txt",
+		},
+		{
+			dir:      "allowlists",
+			basename: "mixed_build_staging_allowlist.txt",
+		},
 	}
 
 	if len(files) != len(expectedFilePaths) {
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/afdo_test.go b/cc/afdo_test.go
index fe3392a..f5d27ff 100644
--- a/cc/afdo_test.go
+++ b/cc/afdo_test.go
@@ -150,3 +150,31 @@
 	}
 
 }
+
+func TestAfdoEnabledWithRuntimeDepNoAfdo(t *testing.T) {
+	bp := `
+	cc_library {
+		name: "libTest",
+		srcs: ["foo.c"],
+		runtime_libs: ["libFoo"],
+		afdo: true,
+	}
+
+	cc_library {
+		name: "libFoo",
+	}
+	`
+	prepareForAfdoTest := android.FixtureAddTextFile("toolchain/pgo-profiles/sampling/libTest.afdo", "TEST")
+
+	result := android.GroupFixturePreparers(
+		prepareForCcTest,
+		prepareForAfdoTest,
+	).RunTestWithBp(t, bp)
+
+	libFooVariants := result.ModuleVariantsForTests("libFoo")
+	for _, v := range libFooVariants {
+		if strings.Contains(v, "afdo-") {
+			t.Errorf("Expected no afdo variant of 'foo', got %q", v)
+		}
+	}
+}
diff --git a/cc/bp2build.go b/cc/bp2build.go
index 6caa854..510ecee 100644
--- a/cc/bp2build.go
+++ b/cc/bp2build.go
@@ -337,6 +337,19 @@
 	}
 }
 
+func bp2BuildParsePrebuiltObjectProps(ctx android.BazelConversionPathContext, module *Module) prebuiltAttributes {
+	var srcLabelAttribute bazel.LabelAttribute
+	bp2BuildPropParseHelper(ctx, module, &prebuiltObjectProperties{}, func(axis bazel.ConfigurationAxis, config string, props interface{}) {
+		if props, ok := props.(*prebuiltObjectProperties); ok {
+			parseSrc(ctx, &srcLabelAttribute, axis, config, props.Srcs)
+		}
+	})
+
+	return prebuiltAttributes{
+		Src: srcLabelAttribute,
+	}
+}
+
 type baseAttributes struct {
 	compilerAttributes
 	linkerAttributes
@@ -393,6 +406,8 @@
 	features bazel.StringListAttribute
 
 	suffix bazel.StringAttribute
+
+	fdoProfile bazel.LabelAttribute
 }
 
 type filterOutFn func(string) bool
@@ -777,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))
 	}
@@ -793,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,
@@ -1257,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/cc.go b/cc/cc.go
index 2ff5bba..632bdca 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -1440,7 +1440,7 @@
 
 func isBionic(name string) bool {
 	switch name {
-	case "libc", "libm", "libdl", "libdl_android", "linker", "linkerconfig":
+	case "libc", "libm", "libdl", "libdl_android", "linker":
 		return true
 	}
 	return false
@@ -3775,7 +3775,9 @@
 			testBinaryBp2build(ctx, c)
 		}
 	case object:
-		if !prebuilt {
+		if prebuilt {
+			prebuiltObjectBp2Build(ctx, c)
+		} else {
 			objectBp2Build(ctx, c)
 		}
 	case fullLibrary:
diff --git a/cc/config/global.go b/cc/config/global.go
index 61151d1..a4e2975 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -278,8 +278,6 @@
 		// http://b/145211477
 		"-Wno-pointer-compare",
 		// http://b/145211022
-		"-Wno-xor-used-as-pow",
-		// http://b/145211022
 		"-Wno-final-dtor-non-final-class",
 
 		// http://b/165945989
@@ -311,8 +309,8 @@
 
 	// prebuilts/clang default settings.
 	ClangDefaultBase         = "prebuilts/clang/host"
-	ClangDefaultVersion      = "clang-r475365"
-	ClangDefaultShortVersion = "16.0.1"
+	ClangDefaultVersion      = "clang-r475365b"
+	ClangDefaultShortVersion = "16.0.2"
 
 	// Directories with warnings from Android.bp files.
 	WarningAllowedProjects = []string{
diff --git a/cc/config/x86_64_device.go b/cc/config/x86_64_device.go
index e2b0f06..9f093bb 100644
--- a/cc/config/x86_64_device.go
+++ b/cc/config/x86_64_device.go
@@ -41,6 +41,12 @@
 		"broadwell": []string{
 			"-march=broadwell",
 		},
+		"goldmont": []string{
+			"-march=goldmont",
+		},
+		"goldmont-plus": []string{
+			"-march=goldmont-plus",
+		},
 		"haswell": []string{
 			"-march=core-avx2",
 		},
@@ -59,6 +65,9 @@
 		"stoneyridge": []string{
 			"-march=bdver4",
 		},
+		"tremont": []string{
+			"-march=tremont",
+		},
 	}
 
 	x86_64ArchFeatureCflags = map[string][]string{
diff --git a/cc/config/x86_device.go b/cc/config/x86_device.go
index 3001ab4..c826d3c 100644
--- a/cc/config/x86_device.go
+++ b/cc/config/x86_device.go
@@ -50,6 +50,12 @@
 		"broadwell": []string{
 			"-march=broadwell",
 		},
+		"goldmont": []string{
+			"-march=goldmont",
+		},
+		"goldmont-plus": []string{
+			"-march=goldmont-plus",
+		},
 		"haswell": []string{
 			"-march=core-avx2",
 		},
@@ -68,6 +74,9 @@
 		"stoneyridge": []string{
 			"-march=bdver4",
 		},
+		"tremont": []string{
+			"-march=tremont",
+		},
 	}
 
 	x86ArchFeatureCflags = map[string][]string{
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_headers.go b/cc/library_headers.go
index 1c4f354..4d38068 100644
--- a/cc/library_headers.go
+++ b/cc/library_headers.go
@@ -145,7 +145,12 @@
 		Bzl_load_location: "//build/bazel/rules/cc:cc_library_headers.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: module.Name()}, attrs)
+	tags := android.ApexAvailableTags(module)
+
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{
+		Name: module.Name(),
+		Tags: tags,
+	}, attrs)
 }
 
 // Append .contribution suffix to input labels
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/linker.go b/cc/linker.go
index 76a60ca..625d89c 100644
--- a/cc/linker.go
+++ b/cc/linker.go
@@ -221,7 +221,7 @@
 	// Generate compact dynamic relocation table, default true.
 	Pack_relocations *bool `android:"arch_variant"`
 
-	// local file name to pass to the linker as --version_script
+	// local file name to pass to the linker as --version-script
 	Version_script *string `android:"path,arch_variant"`
 
 	// local file name to pass to the linker as --dynamic-list
diff --git a/cc/object.go b/cc/object.go
index 1a96b72..c3a198d 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -133,6 +133,7 @@
 	Srcs                bazel.LabelListAttribute
 	Srcs_as             bazel.LabelListAttribute
 	Hdrs                bazel.LabelListAttribute
+	Objs                bazel.LabelListAttribute
 	Deps                bazel.LabelListAttribute
 	System_dynamic_deps bazel.LabelListAttribute
 	Copts               bazel.StringListAttribute
@@ -155,6 +156,7 @@
 	// Set arch-specific configurable attributes
 	baseAttributes := bp2BuildParseBaseProps(ctx, m)
 	compilerAttrs := baseAttributes.compilerAttributes
+	var objs bazel.LabelListAttribute
 	var deps bazel.LabelListAttribute
 	systemDynamicDeps := bazel.LabelListAttribute{ForceSpecifyEmptyList: true}
 
@@ -167,16 +169,19 @@
 					label := android.BazelLabelForModuleSrcSingle(ctx, *objectLinkerProps.Linker_script)
 					linkerScript.SetSelectValue(axis, config, label)
 				}
-				deps.SetSelectValue(axis, config, android.BazelLabelForModuleDeps(ctx, objectLinkerProps.Objs))
+				objs.SetSelectValue(axis, config, android.BazelLabelForModuleDeps(ctx, objectLinkerProps.Objs))
 				systemSharedLibs := objectLinkerProps.System_shared_libs
 				if len(systemSharedLibs) > 0 {
 					systemSharedLibs = android.FirstUniqueStrings(systemSharedLibs)
 				}
 				systemDynamicDeps.SetSelectValue(axis, config, bazelLabelForSharedDeps(ctx, systemSharedLibs))
+				deps.SetSelectValue(axis, config, android.BazelLabelForModuleDeps(ctx, objectLinkerProps.Static_libs))
+				deps.SetSelectValue(axis, config, android.BazelLabelForModuleDeps(ctx, objectLinkerProps.Shared_libs))
+				deps.SetSelectValue(axis, config, android.BazelLabelForModuleDeps(ctx, objectLinkerProps.Header_libs))
 			}
 		}
 	}
-	deps.ResolveExcludes()
+	objs.ResolveExcludes()
 
 	// Don't split cc_object srcs across languages. Doing so would add complexity,
 	// and this isn't typically done for cc_object.
@@ -192,6 +197,7 @@
 	attrs := &bazelObjectAttributes{
 		Srcs:                srcs,
 		Srcs_as:             compilerAttrs.asSrcs,
+		Objs:                objs,
 		Deps:                deps,
 		System_dynamic_deps: systemDynamicDeps,
 		Copts:               compilerAttrs.copts,
@@ -208,7 +214,12 @@
 		Bzl_load_location: "//build/bazel/rules/cc:cc_object.bzl",
 	}
 
-	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs)
+	tags := android.ApexAvailableTags(m)
+
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{
+		Name: m.Name(),
+		Tags: tags,
+	}, attrs)
 }
 
 func (object *objectLinker) appendLdflags(flags []string) {
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index 9fbf879..45af303 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -32,7 +32,7 @@
 	ctx.RegisterModuleType("cc_prebuilt_library_shared", PrebuiltSharedLibraryFactory)
 	ctx.RegisterModuleType("cc_prebuilt_library_static", PrebuiltStaticLibraryFactory)
 	ctx.RegisterModuleType("cc_prebuilt_test_library_shared", PrebuiltSharedTestLibraryFactory)
-	ctx.RegisterModuleType("cc_prebuilt_object", prebuiltObjectFactory)
+	ctx.RegisterModuleType("cc_prebuilt_object", PrebuiltObjectFactory)
 	ctx.RegisterModuleType("cc_prebuilt_binary", PrebuiltBinaryFactory)
 }
 
@@ -572,6 +572,8 @@
 
 func NewPrebuiltObject(hod android.HostOrDeviceSupported) *Module {
 	module := newObject(hod)
+	module.bazelHandler = &prebuiltObjectBazelHandler{module: module}
+	module.bazelable = true
 	prebuilt := &prebuiltObjectLinker{
 		objectLinker: objectLinker{
 			baseLinker: NewBaseLinker(nil),
@@ -584,7 +586,55 @@
 	return module
 }
 
-func prebuiltObjectFactory() android.Module {
+type prebuiltObjectBazelHandler struct {
+	module *Module
+}
+
+var _ BazelHandler = (*prebuiltObjectBazelHandler)(nil)
+
+func (h *prebuiltObjectBazelHandler) QueueBazelCall(ctx android.BaseModuleContext, label string) {
+	bazelCtx := ctx.Config().BazelContext
+	bazelCtx.QueueBazelRequest(label, cquery.GetOutputFiles, android.GetConfigKey(ctx))
+}
+
+func (h *prebuiltObjectBazelHandler) ProcessBazelQueryResponse(ctx android.ModuleContext, label string) {
+	bazelCtx := ctx.Config().BazelContext
+	outputs, err := bazelCtx.GetOutputFiles(label, android.GetConfigKey(ctx))
+	if err != nil {
+		ctx.ModuleErrorf(err.Error())
+		return
+	}
+	if len(outputs) != 1 {
+		ctx.ModuleErrorf("Expected a single output for `%s`, but got:\n%v", label, outputs)
+		return
+	}
+	out := android.PathForBazelOut(ctx, outputs[0])
+	h.module.outputFile = android.OptionalPathForPath(out)
+	h.module.maybeUnhideFromMake()
+}
+
+type bazelPrebuiltObjectAttributes struct {
+	Src bazel.LabelAttribute
+}
+
+func prebuiltObjectBp2Build(ctx android.TopDownMutatorContext, module *Module) {
+	prebuiltAttrs := bp2BuildParsePrebuiltObjectProps(ctx, module)
+
+	attrs := &bazelPrebuiltObjectAttributes{
+		Src: prebuiltAttrs.Src,
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		Rule_class:        "cc_prebuilt_object",
+		Bzl_load_location: "//build/bazel/rules/cc:cc_prebuilt_object.bzl",
+	}
+
+	name := android.RemoveOptionalPrebuiltPrefix(module.Name())
+	tags := android.ApexAvailableTags(module)
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: name, Tags: tags}, attrs)
+}
+
+func PrebuiltObjectFactory() android.Module {
 	module := NewPrebuiltObject(android.HostAndDeviceSupported)
 	return module.Init()
 }
diff --git a/cc/proto.go b/cc/proto.go
index 27f37cb..97470e5 100644
--- a/cc/proto.go
+++ b/cc/proto.go
@@ -165,7 +165,8 @@
 }
 
 type protoAttributes struct {
-	Deps bazel.LabelListAttribute
+	Deps            bazel.LabelListAttribute
+	Min_sdk_version *string
 }
 
 type bp2buildProtoDeps struct {
@@ -203,6 +204,7 @@
 
 	var protoAttrs protoAttributes
 	protoAttrs.Deps.SetValue(protoInfo.Proto_libs)
+	protoAttrs.Min_sdk_version = m.Properties.Min_sdk_version
 
 	name := m.Name() + suffix
 	tags := android.ApexAvailableTags(m)
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/cc/test.go b/cc/test.go
index 536210b..dee6ed6 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -378,12 +378,6 @@
 }
 
 func (test *testBinary) install(ctx ModuleContext, file android.Path) {
-	// TODO: (b/167308193) Switch to /data/local/tests/unrestricted as the default install base.
-	testInstallBase := "/data/local/tmp"
-	if ctx.inVendor() || ctx.useVndk() {
-		testInstallBase = "/data/local/tests/vendor"
-	}
-
 	dataSrcPaths := android.PathsForModuleSrc(ctx, test.Properties.Data)
 
 	for _, dataSrcPath := range dataSrcPaths {
@@ -415,52 +409,20 @@
 		}
 	})
 
-	var configs []tradefed.Config
-	for _, module := range test.Properties.Test_mainline_modules {
-		configs = append(configs, tradefed.Option{Name: "config-descriptor:metadata", Key: "mainline-param", Value: module})
-	}
-	if Bool(test.Properties.Require_root) {
-		configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.RootTargetPreparer", nil})
-	} else {
-		var options []tradefed.Option
-		options = append(options, tradefed.Option{Name: "force-root", Value: "false"})
-		configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.RootTargetPreparer", options})
-	}
-	if Bool(test.Properties.Disable_framework) {
-		var options []tradefed.Option
-		configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.StopServicesSetup", options})
-	}
-	if test.isolated(ctx) {
-		configs = append(configs, tradefed.Option{Name: "not-shardable", Value: "true"})
-	}
-	if test.Properties.Test_options.Run_test_as != nil {
-		configs = append(configs, tradefed.Option{Name: "run-test-as", Value: String(test.Properties.Test_options.Run_test_as)})
-	}
-	for _, tag := range test.Properties.Test_options.Test_suite_tag {
-		configs = append(configs, tradefed.Option{Name: "test-suite-tag", Value: tag})
-	}
-	if test.Properties.Test_options.Min_shipping_api_level != nil {
-		if test.Properties.Test_options.Vsr_min_shipping_api_level != nil {
-			ctx.PropertyErrorf("test_options.min_shipping_api_level", "must not be set at the same time as 'vsr_min_shipping_api_level'.")
-		}
-		var options []tradefed.Option
-		options = append(options, tradefed.Option{Name: "min-api-level", Value: strconv.FormatInt(int64(*test.Properties.Test_options.Min_shipping_api_level), 10)})
-		configs = append(configs, tradefed.Object{"module_controller", "com.android.tradefed.testtype.suite.module.ShippingApiLevelModuleController", options})
-	}
-	if test.Properties.Test_options.Vsr_min_shipping_api_level != nil {
-		var options []tradefed.Option
-		options = append(options, tradefed.Option{Name: "vsr-min-api-level", Value: strconv.FormatInt(int64(*test.Properties.Test_options.Vsr_min_shipping_api_level), 10)})
-		configs = append(configs, tradefed.Object{"module_controller", "com.android.tradefed.testtype.suite.module.ShippingApiLevelModuleController", options})
-	}
-	if test.Properties.Test_options.Min_vndk_version != nil {
-		var options []tradefed.Option
-		options = append(options, tradefed.Option{Name: "min-api-level", Value: strconv.FormatInt(int64(*test.Properties.Test_options.Min_vndk_version), 10)})
-		options = append(options, tradefed.Option{Name: "api-level-prop", Value: "ro.vndk.version"})
-		configs = append(configs, tradefed.Object{"module_controller", "com.android.tradefed.testtype.suite.module.MinApiLevelModuleController", options})
-	}
+	useVendor := ctx.inVendor() || ctx.useVndk()
+	testInstallBase := getTestInstallBase(useVendor)
+	configs := getTradefedConfigOptions(ctx, &test.Properties, test.isolated(ctx))
 
-	test.testConfig = tradefed.AutoGenNativeTestConfig(ctx, test.Properties.Test_config,
-		test.Properties.Test_config_template, test.testDecorator.InstallerProperties.Test_suites, configs, test.Properties.Auto_gen_config, testInstallBase)
+	test.testConfig = tradefed.NewMaybeAutoGenTestConfigBuilder(ctx).
+		SetTestConfigProp(test.Properties.Test_config).
+		SetTestTemplateConfigProp(test.Properties.Test_config_template).
+		SetTestSuites(test.testDecorator.InstallerProperties.Test_suites).
+		SetConfig(configs).
+		SetAutoGenConfig(test.Properties.Auto_gen_config).
+		SetTestInstallBase(testInstallBase).
+		SetDeviceTemplate("${NativeTestConfigTemplate}").
+		SetHostTemplate("${NativeHostTestConfigTemplate}").
+		Build()
 
 	test.extraTestConfigs = android.PathsForModuleSrc(ctx, test.Properties.Test_options.Extra_test_configs)
 
@@ -479,6 +441,63 @@
 	test.binaryDecorator.baseInstaller.install(ctx, file)
 }
 
+func getTestInstallBase(useVendor bool) string {
+	// TODO: (b/167308193) Switch to /data/local/tests/unrestricted as the default install base.
+	testInstallBase := "/data/local/tmp"
+	if useVendor {
+		testInstallBase = "/data/local/tests/vendor"
+	}
+	return testInstallBase
+}
+
+func getTradefedConfigOptions(ctx android.EarlyModuleContext, properties *TestBinaryProperties, isolated bool) []tradefed.Config {
+	var configs []tradefed.Config
+
+	for _, module := range properties.Test_mainline_modules {
+		configs = append(configs, tradefed.Option{Name: "config-descriptor:metadata", Key: "mainline-param", Value: module})
+	}
+	if Bool(properties.Require_root) {
+		configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.RootTargetPreparer", nil})
+	} else {
+		var options []tradefed.Option
+		options = append(options, tradefed.Option{Name: "force-root", Value: "false"})
+		configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.RootTargetPreparer", options})
+	}
+	if Bool(properties.Disable_framework) {
+		var options []tradefed.Option
+		configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.StopServicesSetup", options})
+	}
+	if isolated {
+		configs = append(configs, tradefed.Option{Name: "not-shardable", Value: "true"})
+	}
+	if properties.Test_options.Run_test_as != nil {
+		configs = append(configs, tradefed.Option{Name: "run-test-as", Value: String(properties.Test_options.Run_test_as)})
+	}
+	for _, tag := range properties.Test_options.Test_suite_tag {
+		configs = append(configs, tradefed.Option{Name: "test-suite-tag", Value: tag})
+	}
+	if properties.Test_options.Min_shipping_api_level != nil {
+		if properties.Test_options.Vsr_min_shipping_api_level != nil {
+			ctx.PropertyErrorf("test_options.min_shipping_api_level", "must not be set at the same time as 'vsr_min_shipping_api_level'.")
+		}
+		var options []tradefed.Option
+		options = append(options, tradefed.Option{Name: "min-api-level", Value: strconv.FormatInt(int64(*properties.Test_options.Min_shipping_api_level), 10)})
+		configs = append(configs, tradefed.Object{"module_controller", "com.android.tradefed.testtype.suite.module.ShippingApiLevelModuleController", options})
+	}
+	if properties.Test_options.Vsr_min_shipping_api_level != nil {
+		var options []tradefed.Option
+		options = append(options, tradefed.Option{Name: "vsr-min-api-level", Value: strconv.FormatInt(int64(*properties.Test_options.Vsr_min_shipping_api_level), 10)})
+		configs = append(configs, tradefed.Object{"module_controller", "com.android.tradefed.testtype.suite.module.ShippingApiLevelModuleController", options})
+	}
+	if properties.Test_options.Min_vndk_version != nil {
+		var options []tradefed.Option
+		options = append(options, tradefed.Option{Name: "min-api-level", Value: strconv.FormatInt(int64(*properties.Test_options.Min_vndk_version), 10)})
+		options = append(options, tradefed.Option{Name: "api-level-prop", Value: "ro.vndk.version"})
+		configs = append(configs, tradefed.Object{"module_controller", "com.android.tradefed.testtype.suite.module.MinApiLevelModuleController", options})
+	}
+	return configs
+}
+
 func NewTest(hod android.HostOrDeviceSupported, bazelable bool) *Module {
 	module, binary := newBinary(hod, bazelable)
 	module.multilib = android.MultilibBoth
@@ -616,8 +635,15 @@
 	if Bool(benchmark.Properties.Require_root) {
 		configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.RootTargetPreparer", nil})
 	}
-	benchmark.testConfig = tradefed.AutoGenNativeBenchmarkTestConfig(ctx, benchmark.Properties.Test_config,
-		benchmark.Properties.Test_config_template, benchmark.Properties.Test_suites, configs, benchmark.Properties.Auto_gen_config)
+	benchmark.testConfig = tradefed.NewMaybeAutoGenTestConfigBuilder(ctx).
+		SetTestConfigProp(benchmark.Properties.Test_config).
+		SetTestTemplateConfigProp(benchmark.Properties.Test_config_template).
+		SetTestSuites(benchmark.Properties.Test_suites).
+		SetConfig(configs).
+		SetAutoGenConfig(benchmark.Properties.Auto_gen_config).
+		SetDeviceTemplate("${NativeBenchmarkTestConfigTemplate}").
+		SetHostTemplate("${NativeBenchmarkTestConfigTemplate}").
+		Build()
 
 	benchmark.binaryDecorator.baseInstaller.dir = filepath.Join("benchmarktest", ctx.ModuleName())
 	benchmark.binaryDecorator.baseInstaller.dir64 = filepath.Join("benchmarktest64", ctx.ModuleName())
@@ -645,6 +671,7 @@
 	Isolated bool
 
 	tidyAttributes
+	tradefed.TestConfigAttributes
 }
 
 // testBinaryBp2build is the bp2build converter for cc_test modules. A cc_test's
@@ -655,7 +682,6 @@
 // TODO(b/244432609): handle `isolated` property.
 // TODO(b/244432134): handle custom runpaths for tests that assume runfile layouts not
 // default to bazel. (see linkerInit function)
-// TODO(b/244432500): handle test.testConfig generation (see install function)
 func testBinaryBp2build(ctx android.TopDownMutatorContext, m *Module) {
 	var testBinaryAttrs testBinaryAttributes
 	testBinaryAttrs.binaryAttributes = binaryBp2buildAttrs(ctx, m)
@@ -688,6 +714,25 @@
 		}
 	}
 
+	for _, testProps := range m.GetProperties() {
+		if p, ok := testProps.(*TestBinaryProperties); ok {
+			useVendor := false // TODO Bug: 262914724
+			testInstallBase := getTestInstallBase(useVendor)
+			testConfigAttributes := tradefed.GetTestConfigAttributes(
+				ctx,
+				p.Test_config,
+				p.Test_options.Extra_test_configs,
+				p.Auto_gen_config,
+				p.Test_options.Test_suite_tag,
+				p.Test_config_template,
+				getTradefedConfigOptions(ctx, p, testBinaryAttrs.Isolated),
+				&testInstallBase,
+			)
+			testBinaryAttrs.TestConfigAttributes = testConfigAttributes
+		}
+	}
+
+	// TODO (b/262914724): convert to tradefed_cc_test and tradefed_cc_test_host
 	ctx.CreateBazelTargetModule(
 		bazel.BazelTargetModuleProperties{
 			Rule_class:        "cc_test",
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index f4a63a4..744a10c 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -18,7 +18,6 @@
 	"bytes"
 	"flag"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
@@ -38,38 +37,26 @@
 
 var (
 	topDir           string
-	outDir           string
-	soongOutDir      string
 	availableEnvFile string
 	usedEnvFile      string
 
-	runGoTests bool
-
 	globFile    string
 	globListDir string
 	delveListen string
 	delvePath   string
 
-	moduleGraphFile     string
-	moduleActionsFile   string
-	docFile             string
-	bazelQueryViewDir   string
-	bazelApiBp2buildDir string
-	bp2buildMarker      string
-	symlinkForestMarker string
-
-	cmdlineArgs bootstrap.Args
+	cmdlineArgs android.CmdArgs
 )
 
 func init() {
 	// Flags that make sense in every mode
 	flag.StringVar(&topDir, "top", "", "Top directory of the Android source tree")
-	flag.StringVar(&soongOutDir, "soong_out", "", "Soong output directory (usually $TOP/out/soong)")
+	flag.StringVar(&cmdlineArgs.SoongOutDir, "soong_out", "", "Soong output directory (usually $TOP/out/soong)")
 	flag.StringVar(&availableEnvFile, "available_env", "", "File containing available environment variables")
 	flag.StringVar(&usedEnvFile, "used_env", "", "File containing used environment variables")
 	flag.StringVar(&globFile, "globFile", "build-globs.ninja", "the Ninja file of globs to output")
 	flag.StringVar(&globListDir, "globListDir", "", "the directory containing the glob list files")
-	flag.StringVar(&outDir, "out", "", "the ninja builddir directory")
+	flag.StringVar(&cmdlineArgs.OutDir, "out", "", "the ninja builddir directory")
 	flag.StringVar(&cmdlineArgs.ModuleListFile, "l", "", "file that lists filepaths to parse")
 
 	// Debug flags
@@ -81,13 +68,13 @@
 	flag.BoolVar(&cmdlineArgs.NoGC, "nogc", false, "turn off GC for debugging")
 
 	// Flags representing various modes soong_build can run in
-	flag.StringVar(&moduleGraphFile, "module_graph_file", "", "JSON module graph file to output")
-	flag.StringVar(&moduleActionsFile, "module_actions_file", "", "JSON file to output inputs/outputs of actions of modules")
-	flag.StringVar(&docFile, "soong_docs", "", "build documentation file to output")
-	flag.StringVar(&bazelQueryViewDir, "bazel_queryview_dir", "", "path to the bazel queryview directory relative to --top")
-	flag.StringVar(&bazelApiBp2buildDir, "bazel_api_bp2build_dir", "", "path to the bazel api_bp2build directory relative to --top")
-	flag.StringVar(&bp2buildMarker, "bp2build_marker", "", "If set, run bp2build, touch the specified marker file then exit")
-	flag.StringVar(&symlinkForestMarker, "symlink_forest_marker", "", "If set, create the bp2build symlink forest, touch the specified marker file, then exit")
+	flag.StringVar(&cmdlineArgs.ModuleGraphFile, "module_graph_file", "", "JSON module graph file to output")
+	flag.StringVar(&cmdlineArgs.ModuleActionsFile, "module_actions_file", "", "JSON file to output inputs/outputs of actions of modules")
+	flag.StringVar(&cmdlineArgs.DocFile, "soong_docs", "", "build documentation file to output")
+	flag.StringVar(&cmdlineArgs.BazelQueryViewDir, "bazel_queryview_dir", "", "path to the bazel queryview directory relative to --top")
+	flag.StringVar(&cmdlineArgs.BazelApiBp2buildDir, "bazel_api_bp2build_dir", "", "path to the bazel api_bp2build directory relative to --top")
+	flag.StringVar(&cmdlineArgs.Bp2buildMarker, "bp2build_marker", "", "If set, run bp2build, touch the specified marker file then exit")
+	flag.StringVar(&cmdlineArgs.SymlinkForestMarker, "symlink_forest_marker", "", "If set, create the bp2build symlink forest, touch the specified marker file, then exit")
 	flag.StringVar(&cmdlineArgs.OutFile, "o", "build.ninja", "the Ninja file to output")
 	flag.StringVar(&cmdlineArgs.BazelForceEnabledModules, "bazel-force-enabled-modules", "", "additional modules to build with Bazel. Comma-delimited")
 	flag.BoolVar(&cmdlineArgs.EmptyNinjaFile, "empty-ninja-file", false, "write out a 0-byte ninja file")
@@ -95,9 +82,9 @@
 	flag.BoolVar(&cmdlineArgs.BazelModeStaging, "bazel-mode-staging", false, "use bazel for analysis of certain near-ready modules")
 	flag.BoolVar(&cmdlineArgs.BazelModeDev, "bazel-mode-dev", false, "use bazel for analysis of a large number of modules (less stable)")
 
-	// Flags that probably shouldn't be flags of soong_build but we haven't found
+	// Flags that probably shouldn't be flags of soong_build, but we haven't found
 	// the time to remove them yet
-	flag.BoolVar(&runGoTests, "t", false, "build and run go tests during bootstrap")
+	flag.BoolVar(&cmdlineArgs.RunGoTests, "t", false, "build and run go tests during bootstrap")
 
 	// Disable deterministic randomization in the protobuf package, so incremental
 	// builds with unrelated Soong changes don't trigger large rebuilds (since we
@@ -118,87 +105,44 @@
 	return ctx
 }
 
-func newConfig(availableEnv map[string]string) android.Config {
-	var buildMode android.SoongBuildMode
-	var bazelForceEnabledModules []string
-	if len(cmdlineArgs.BazelForceEnabledModules) > 0 {
-		bazelForceEnabledModules = strings.Split(cmdlineArgs.BazelForceEnabledModules, ",")
-	}
-
-	if symlinkForestMarker != "" {
-		buildMode = android.SymlinkForest
-	} else if bp2buildMarker != "" {
-		buildMode = android.Bp2build
-	} else if bazelQueryViewDir != "" {
-		buildMode = android.GenerateQueryView
-	} else if bazelApiBp2buildDir != "" {
-		buildMode = android.ApiBp2build
-	} else if moduleGraphFile != "" {
-		buildMode = android.GenerateModuleGraph
-	} else if docFile != "" {
-		buildMode = android.GenerateDocFile
-	} else if cmdlineArgs.BazelModeDev {
-		buildMode = android.BazelDevMode
-	} else if cmdlineArgs.BazelMode {
-		buildMode = android.BazelProdMode
-	} else if cmdlineArgs.BazelModeStaging {
-		buildMode = android.BazelStagingMode
-	} else {
-		buildMode = android.AnalysisNoBazel
-	}
-
-	configuration, err := android.NewConfig(cmdlineArgs.ModuleListFile, buildMode, runGoTests, outDir, soongOutDir, availableEnv, bazelForceEnabledModules)
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "%s", err)
-		os.Exit(1)
-	}
-	return configuration
-}
-
 // Bazel-enabled mode. Attaches a mutator to queue Bazel requests, adds a
 // BeforePrepareBuildActionsHook to invoke Bazel, and then uses Bazel metadata
 // for modules that should be handled by Bazel.
-func runMixedModeBuild(configuration android.Config, ctx *android.Context, extraNinjaDeps []string) string {
+func runMixedModeBuild(ctx *android.Context, extraNinjaDeps []string) string {
 	ctx.EventHandler.Begin("mixed_build")
 	defer ctx.EventHandler.End("mixed_build")
 
 	bazelHook := func() error {
-		return configuration.BazelContext.InvokeBazel(configuration, ctx)
+		return ctx.Config().BazelContext.InvokeBazel(ctx.Config(), ctx)
 	}
 	ctx.SetBeforePrepareBuildActionsHook(bazelHook)
-	ninjaDeps := bootstrap.RunBlueprint(cmdlineArgs, bootstrap.DoEverything, ctx.Context, configuration)
+	ninjaDeps := bootstrap.RunBlueprint(cmdlineArgs.Args, bootstrap.DoEverything, ctx.Context, ctx.Config())
 	ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
 
-	bazelPaths, err := readBazelPaths(configuration)
+	bazelPaths, err := readFileLines(ctx.Config().Getenv("BAZEL_DEPS_FILE"))
 	if err != nil {
 		panic("Bazel deps file not found: " + err.Error())
 	}
 	ninjaDeps = append(ninjaDeps, bazelPaths...)
-
-	globListFiles := writeBuildGlobsNinjaFile(ctx, configuration.SoongOutDir(), configuration)
-	ninjaDeps = append(ninjaDeps, globListFiles...)
+	ninjaDeps = append(ninjaDeps, writeBuildGlobsNinjaFile(ctx)...)
 
 	writeDepFile(cmdlineArgs.OutFile, ctx.EventHandler, ninjaDeps)
 	return cmdlineArgs.OutFile
 }
 
 // Run the code-generation phase to convert BazelTargetModules to BUILD files.
-func runQueryView(queryviewDir, queryviewMarker string, configuration android.Config, ctx *android.Context) {
+func runQueryView(queryviewDir, queryviewMarker string, ctx *android.Context) {
 	ctx.EventHandler.Begin("queryview")
 	defer ctx.EventHandler.End("queryview")
-	codegenContext := bp2build.NewCodegenContext(configuration, ctx, bp2build.QueryView)
-	absoluteQueryViewDir := shared.JoinPath(topDir, queryviewDir)
-	if err := createBazelWorkspace(codegenContext, absoluteQueryViewDir); err != nil {
-		fmt.Fprintf(os.Stderr, "%s", err)
-		os.Exit(1)
-	}
-
+	codegenContext := bp2build.NewCodegenContext(ctx.Config(), ctx, bp2build.QueryView)
+	err := createBazelWorkspace(codegenContext, shared.JoinPath(topDir, queryviewDir))
+	maybeQuit(err, "")
 	touch(shared.JoinPath(topDir, queryviewMarker))
 }
 
 // Run the code-generation phase to convert API contributions to BUILD files.
 // Return marker file for the new synthetic workspace
-func runApiBp2build(configuration android.Config, ctx *android.Context, extraNinjaDeps []string) string {
+func runApiBp2build(ctx *android.Context, extraNinjaDeps []string) string {
 	ctx.EventHandler.Begin("api_bp2build")
 	defer ctx.EventHandler.End("api_bp2build")
 	// Do not allow missing dependencies.
@@ -215,50 +159,42 @@
 	}
 
 	// Run the loading and analysis phase
-	ninjaDeps := bootstrap.RunBlueprint(cmdlineArgs,
+	ninjaDeps := bootstrap.RunBlueprint(cmdlineArgs.Args,
 		bootstrap.StopBeforePrepareBuildActions,
 		ctx.Context,
-		configuration)
+		ctx.Config())
 	ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
 
 	// Add the globbed dependencies
-	globs := writeBuildGlobsNinjaFile(ctx, configuration.SoongOutDir(), configuration)
-	ninjaDeps = append(ninjaDeps, globs...)
+	ninjaDeps = append(ninjaDeps, writeBuildGlobsNinjaFile(ctx)...)
 
 	// Run codegen to generate BUILD files
-	codegenContext := bp2build.NewCodegenContext(configuration, ctx, bp2build.ApiBp2build)
-	absoluteApiBp2buildDir := shared.JoinPath(topDir, bazelApiBp2buildDir)
-	if err := createBazelWorkspace(codegenContext, absoluteApiBp2buildDir); err != nil {
-		fmt.Fprintf(os.Stderr, "%s", err)
-		os.Exit(1)
-	}
+	codegenContext := bp2build.NewCodegenContext(ctx.Config(), ctx, bp2build.ApiBp2build)
+	absoluteApiBp2buildDir := shared.JoinPath(topDir, cmdlineArgs.BazelApiBp2buildDir)
+	err := createBazelWorkspace(codegenContext, absoluteApiBp2buildDir)
+	maybeQuit(err, "")
 	ninjaDeps = append(ninjaDeps, codegenContext.AdditionalNinjaDeps()...)
 
 	// Create soong_injection repository
-	soongInjectionFiles := bp2build.CreateSoongInjectionFiles(configuration, bp2build.CreateCodegenMetrics())
-	absoluteSoongInjectionDir := shared.JoinPath(topDir, configuration.SoongOutDir(), bazel.SoongInjectionDirName)
+	soongInjectionFiles := bp2build.CreateSoongInjectionFiles(ctx.Config(), bp2build.CreateCodegenMetrics())
+	absoluteSoongInjectionDir := shared.JoinPath(topDir, ctx.Config().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")
-
-	excludes := bazelArtifacts()
-	// Exclude all src BUILD files
-	excludes = append(excludes, apiBuildFileExcludes()...)
-
-	// Android.bp files for api surfaces are mounted to out/, but out/ should not be a
-	// dep for api_bp2build.
-	// Otherwise api_bp2build will be run every single time
-	excludes = append(excludes, configuration.OutDir())
-
+	workspace := shared.JoinPath(ctx.Config().SoongOutDir(), "api_bp2build")
 	// Create the symlink forest
 	symlinkDeps := bp2build.PlantSymlinkForest(
-		configuration.IsEnvTrue("BP2BUILD_VERBOSE"),
+		ctx.Config().IsEnvTrue("BP2BUILD_VERBOSE"),
 		topDir,
 		workspace,
-		bazelApiBp2buildDir,
-		excludes)
+		cmdlineArgs.BazelApiBp2buildDir,
+		apiBuildFileExcludes(ctx))
 	ninjaDeps = append(ninjaDeps, symlinkDeps...)
 
 	workspaceMarkerFile := workspace + ".marker"
@@ -269,15 +205,12 @@
 
 // With some exceptions, api_bp2build does not have any dependencies on the checked-in BUILD files
 // Exclude them from the generated workspace to prevent unrelated errors during the loading phase
-func apiBuildFileExcludes() []string {
-	ret := make([]string, 0)
-
+func apiBuildFileExcludes(ctx *android.Context) []string {
+	ret := bazelArtifacts()
 	srcs, err := getExistingBazelRelatedFiles(topDir)
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "Error determining existing Bazel-related files: %s\n", err)
-		os.Exit(1)
-	}
+	maybeQuit(err, "Error determining existing Bazel-related files")
 	for _, src := range srcs {
+		// Exclude all src BUILD files
 		if src != "WORKSPACE" &&
 			src != "BUILD" &&
 			src != "BUILD.bazel" &&
@@ -287,6 +220,9 @@
 			ret = append(ret, src)
 		}
 	}
+	// Android.bp files for api surfaces are mounted to out/, but out/ should not be a
+	// dep for api_bp2build. Otherwise, api_bp2build will be run every single time
+	ret = append(ret, ctx.Config().OutDir())
 	return ret
 }
 
@@ -297,36 +233,30 @@
 	}
 	metricsFile := filepath.Join(metricsDir, "soong_build_metrics.pb")
 	err := android.WriteMetrics(configuration, eventHandler, metricsFile)
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "error writing soong_build metrics %s: %s", metricsFile, err)
-		os.Exit(1)
-	}
+	maybeQuit(err, "error writing soong_build metrics %s", metricsFile)
 }
 
-func writeJsonModuleGraphAndActions(ctx *android.Context, graphPath string, actionsPath string) {
-	graphFile, graphErr := os.Create(shared.JoinPath(topDir, graphPath))
-	actionsFile, actionsErr := os.Create(shared.JoinPath(topDir, actionsPath))
-	if graphErr != nil || actionsErr != nil {
-		fmt.Fprintf(os.Stderr, "Graph err: %s, actions err: %s", graphErr, actionsErr)
-		os.Exit(1)
-	}
-
+func writeJsonModuleGraphAndActions(ctx *android.Context, cmdArgs android.CmdArgs) {
+	graphFile, graphErr := os.Create(shared.JoinPath(topDir, cmdArgs.ModuleGraphFile))
+	maybeQuit(graphErr, "graph err")
 	defer graphFile.Close()
+	actionsFile, actionsErr := os.Create(shared.JoinPath(topDir, cmdArgs.ModuleActionsFile))
+	maybeQuit(actionsErr, "actions err")
 	defer actionsFile.Close()
 	ctx.Context.PrintJSONGraphAndActions(graphFile, actionsFile)
 }
 
-func writeBuildGlobsNinjaFile(ctx *android.Context, buildDir string, config interface{}) []string {
+func writeBuildGlobsNinjaFile(ctx *android.Context) []string {
 	ctx.EventHandler.Begin("globs_ninja_file")
 	defer ctx.EventHandler.End("globs_ninja_file")
 
-	globDir := bootstrap.GlobDirectory(buildDir, globListDir)
+	globDir := bootstrap.GlobDirectory(ctx.Config().SoongOutDir(), globListDir)
 	bootstrap.WriteBuildGlobsNinjaFile(&bootstrap.GlobSingleton{
 		GlobLister: ctx.Globs,
 		GlobFile:   globFile,
 		GlobDir:    globDir,
 		SrcDir:     ctx.SrcDir(),
-	}, config)
+	}, ctx.Config())
 	return bootstrap.GlobFileListFiles(globDir)
 }
 
@@ -335,83 +265,50 @@
 	defer eventHandler.End("ninja_deps")
 	depFile := shared.JoinPath(topDir, outputFile+".d")
 	err := deptools.WriteDepFile(depFile, outputFile, ninjaDeps)
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "Error writing depfile '%s': %s\n", depFile, err)
-		os.Exit(1)
-	}
-}
-
-// doChosenActivity runs Soong for a specific activity, like bp2build, queryview
-// or the actual Soong build for the build.ninja file. Returns the top level
-// output file of the specific activity.
-func doChosenActivity(ctx *android.Context, configuration android.Config, extraNinjaDeps []string, metricsDir string) string {
-	if configuration.BuildMode == android.SymlinkForest {
-		return runSymlinkForestCreation(configuration, ctx, extraNinjaDeps, metricsDir)
-	} else if configuration.BuildMode == android.Bp2build {
-		// Run the alternate pipeline of bp2build mutators and singleton to convert
-		// Blueprint to BUILD files before everything else.
-		return runBp2Build(configuration, ctx, extraNinjaDeps, metricsDir)
-	} else if configuration.BuildMode == android.ApiBp2build {
-		outputFile := runApiBp2build(configuration, ctx, extraNinjaDeps)
-		writeMetrics(configuration, ctx.EventHandler, metricsDir)
-		return outputFile
-	} else {
-		ctx.Register()
-
-		var outputFile string
-		if configuration.IsMixedBuildsEnabled() {
-			outputFile = runMixedModeBuild(configuration, ctx, extraNinjaDeps)
-		} else {
-			outputFile = runSoongOnlyBuild(configuration, ctx, extraNinjaDeps)
-		}
-
-		writeMetrics(configuration, ctx.EventHandler, metricsDir)
-
-		return outputFile
-	}
+	maybeQuit(err, "error writing depfile '%s'", depFile)
 }
 
 // runSoongOnlyBuild runs the standard Soong build in a number of different modes.
-func runSoongOnlyBuild(configuration android.Config, ctx *android.Context, extraNinjaDeps []string) string {
+func runSoongOnlyBuild(ctx *android.Context, extraNinjaDeps []string) string {
 	ctx.EventHandler.Begin("soong_build")
 	defer ctx.EventHandler.End("soong_build")
 
 	var stopBefore bootstrap.StopBefore
-	if configuration.BuildMode == android.GenerateModuleGraph {
+	switch ctx.Config().BuildMode {
+	case android.GenerateModuleGraph:
 		stopBefore = bootstrap.StopBeforeWriteNinja
-	} else if configuration.BuildMode == android.GenerateQueryView || configuration.BuildMode == android.GenerateDocFile {
+	case android.GenerateQueryView | android.GenerateDocFile:
 		stopBefore = bootstrap.StopBeforePrepareBuildActions
-	} else {
+	default:
 		stopBefore = bootstrap.DoEverything
 	}
 
-	ninjaDeps := bootstrap.RunBlueprint(cmdlineArgs, stopBefore, ctx.Context, configuration)
+	ninjaDeps := bootstrap.RunBlueprint(cmdlineArgs.Args, stopBefore, ctx.Context, ctx.Config())
 	ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
 
-	globListFiles := writeBuildGlobsNinjaFile(ctx, configuration.SoongOutDir(), configuration)
+	globListFiles := writeBuildGlobsNinjaFile(ctx)
 	ninjaDeps = append(ninjaDeps, globListFiles...)
 
 	// Convert the Soong module graph into Bazel BUILD files.
-	if configuration.BuildMode == android.GenerateQueryView {
-		queryviewMarkerFile := bazelQueryViewDir + ".marker"
-		runQueryView(bazelQueryViewDir, queryviewMarkerFile, configuration, ctx)
+	switch ctx.Config().BuildMode {
+	case android.GenerateQueryView:
+		queryviewMarkerFile := cmdlineArgs.BazelQueryViewDir + ".marker"
+		runQueryView(cmdlineArgs.BazelQueryViewDir, queryviewMarkerFile, ctx)
 		writeDepFile(queryviewMarkerFile, ctx.EventHandler, ninjaDeps)
 		return queryviewMarkerFile
-	} else if configuration.BuildMode == android.GenerateModuleGraph {
-		writeJsonModuleGraphAndActions(ctx, moduleGraphFile, moduleActionsFile)
-		writeDepFile(moduleGraphFile, ctx.EventHandler, ninjaDeps)
-		return moduleGraphFile
-	} else if configuration.BuildMode == android.GenerateDocFile {
+	case android.GenerateModuleGraph:
+		writeJsonModuleGraphAndActions(ctx, cmdlineArgs)
+		writeDepFile(cmdlineArgs.ModuleGraphFile, ctx.EventHandler, ninjaDeps)
+		return cmdlineArgs.ModuleGraphFile
+	case android.GenerateDocFile:
 		// TODO: we could make writeDocs() return the list of documentation files
 		// written and add them to the .d file. Then soong_docs would be re-run
 		// whenever one is deleted.
-		if err := writeDocs(ctx, shared.JoinPath(topDir, docFile)); err != nil {
-			fmt.Fprintf(os.Stderr, "error building Soong documentation: %s\n", err)
-			os.Exit(1)
-		}
-		writeDepFile(docFile, ctx.EventHandler, ninjaDeps)
-		return docFile
-	} else {
+		err := writeDocs(ctx, shared.JoinPath(topDir, cmdlineArgs.DocFile))
+		maybeQuit(err, "error building Soong documentation")
+		writeDepFile(cmdlineArgs.DocFile, ctx.EventHandler, ninjaDeps)
+		return cmdlineArgs.DocFile
+	default:
 		// The actual output (build.ninja) was written in the RunBlueprint() call
 		// above
 		writeDepFile(cmdlineArgs.OutFile, ctx.EventHandler, ninjaDeps)
@@ -437,13 +334,8 @@
 		fmt.Fprintf(os.Stderr, "--available_env not set\n")
 		os.Exit(1)
 	}
-
 	result, err := shared.EnvFromFile(shared.JoinPath(topDir, availableEnvFile))
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "error reading available environment file '%s': %s\n", availableEnvFile, err)
-		os.Exit(1)
-	}
-
+	maybeQuit(err, "error reading available environment file '%s'", availableEnvFile)
 	return result
 }
 
@@ -454,17 +346,13 @@
 	android.InitSandbox(topDir)
 
 	availableEnv := parseAvailableEnv()
-
-	configuration := newConfig(availableEnv)
-	extraNinjaDeps := []string{
-		configuration.ProductVariablesFileName,
-		usedEnvFile,
-	}
-
+	configuration, err := android.NewConfig(cmdlineArgs, availableEnv)
+	maybeQuit(err, "")
 	if configuration.Getenv("ALLOW_MISSING_DEPENDENCIES") == "true" {
 		configuration.SetAllowMissingDependencies()
 	}
 
+	extraNinjaDeps := []string{configuration.ProductVariablesFileName, usedEnvFile}
 	if shared.IsDebugging() {
 		// Add a non-existent file to the dependencies so that soong_build will rerun when the debugger is
 		// enabled even if it completed successfully.
@@ -473,12 +361,33 @@
 
 	// Bypass configuration.Getenv, as LOG_DIR does not need to be dependency tracked. By definition, it will
 	// change between every CI build, so tracking it would require re-running Soong for every build.
-	logDir := availableEnv["LOG_DIR"]
+	metricsDir := availableEnv["LOG_DIR"]
 
 	ctx := newContext(configuration)
 
-	finalOutputFile := doChosenActivity(ctx, configuration, extraNinjaDeps, logDir)
+	var finalOutputFile string
 
+	// Run Soong for a specific activity, like bp2build, queryview
+	// or the actual Soong build for the build.ninja file.
+	switch configuration.BuildMode {
+	case android.SymlinkForest:
+		finalOutputFile = runSymlinkForestCreation(ctx, extraNinjaDeps, metricsDir)
+	case android.Bp2build:
+		// Run the alternate pipeline of bp2build mutators and singleton to convert
+		// Blueprint to BUILD files before everything else.
+		finalOutputFile = runBp2Build(ctx, extraNinjaDeps, metricsDir)
+	case android.ApiBp2build:
+		finalOutputFile = runApiBp2build(ctx, extraNinjaDeps)
+		writeMetrics(configuration, ctx.EventHandler, metricsDir)
+	default:
+		ctx.Register()
+		if configuration.IsMixedBuildsEnabled() {
+			finalOutputFile = runMixedModeBuild(ctx, extraNinjaDeps)
+		} else {
+			finalOutputFile = runSoongOnlyBuild(ctx, extraNinjaDeps)
+		}
+		writeMetrics(configuration, ctx.EventHandler, metricsDir)
+	}
 	writeUsedEnvironmentFile(configuration, finalOutputFile)
 }
 
@@ -489,24 +398,19 @@
 
 	path := shared.JoinPath(topDir, usedEnvFile)
 	data, err := shared.EnvFileContents(configuration.EnvDeps())
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "error writing used environment file '%s': %s\n", usedEnvFile, err)
-		os.Exit(1)
-	}
+	maybeQuit(err, "error writing used environment file '%s'\n", usedEnvFile)
 
 	if preexistingData, err := os.ReadFile(path); err != nil {
 		if !os.IsNotExist(err) {
-			fmt.Fprintf(os.Stderr, "error reading used environment file '%s': %s\n", usedEnvFile, err)
-			os.Exit(1)
+			maybeQuit(err, "error reading used environment file '%s'", usedEnvFile)
 		}
 	} else if bytes.Equal(preexistingData, data) {
 		// used environment file is unchanged
 		return
 	}
-	if err = os.WriteFile(path, data, 0666); err != nil {
-		fmt.Fprintf(os.Stderr, "error writing used environment file '%s': %s\n", usedEnvFile, err)
-		os.Exit(1)
-	}
+	err = os.WriteFile(path, data, 0666)
+	maybeQuit(err, "error writing used environment file '%s'", usedEnvFile)
+
 	// Touch the output file so that it's not older than the file we just
 	// wrote. We can't write the environment file earlier because one an access
 	// new environment variables while writing it.
@@ -515,88 +419,13 @@
 
 func touch(path string) {
 	f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "Error touching '%s': %s\n", path, err)
-		os.Exit(1)
-	}
-
+	maybeQuit(err, "Error touching '%s'", path)
 	err = f.Close()
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "Error touching '%s': %s\n", path, err)
-		os.Exit(1)
-	}
+	maybeQuit(err, "Error touching '%s'", path)
 
 	currentTime := time.Now().Local()
 	err = os.Chtimes(path, currentTime, currentTime)
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "error touching '%s': %s\n", path, err)
-		os.Exit(1)
-	}
-}
-
-// Find BUILD files in the srcDir which are not in the allowlist
-// (android.Bp2BuildConversionAllowlist#ShouldKeepExistingBuildFileForDir)
-// and return their paths so they can be left out of the Bazel workspace dir (i.e. ignored)
-func getPathsToIgnoredBuildFiles(config android.Bp2BuildConversionAllowlist, topDir string, srcDirBazelFiles []string, verbose bool) []string {
-	paths := make([]string, 0)
-
-	for _, srcDirBazelFileRelativePath := range srcDirBazelFiles {
-		srcDirBazelFileFullPath := shared.JoinPath(topDir, srcDirBazelFileRelativePath)
-		fileInfo, err := os.Stat(srcDirBazelFileFullPath)
-		if err != nil {
-			// Warn about error, but continue trying to check files
-			fmt.Fprintf(os.Stderr, "WARNING: Error accessing path '%s', err: %s\n", srcDirBazelFileFullPath, err)
-			continue
-		}
-		if fileInfo.IsDir() {
-			// Don't ignore entire directories
-			continue
-		}
-		if fileInfo.Name() != "BUILD" && fileInfo.Name() != "BUILD.bazel" {
-			// Don't ignore this file - it is not a build file
-			continue
-		}
-		if config.ShouldKeepExistingBuildFileForDir(filepath.Dir(srcDirBazelFileRelativePath)) {
-			// Don't ignore this existing build file
-			continue
-		}
-		if verbose {
-			fmt.Fprintf(os.Stderr, "Ignoring existing BUILD file: %s\n", srcDirBazelFileRelativePath)
-		}
-		paths = append(paths, srcDirBazelFileRelativePath)
-	}
-
-	return paths
-}
-
-// Returns temporary symlink forest excludes necessary for bazel build //external/... (and bazel build //frameworks/...) to work
-func getTemporaryExcludes() []string {
-	excludes := make([]string, 0)
-
-	// FIXME: 'autotest_lib' is a symlink back to external/autotest, and this causes an infinite symlink expansion error for Bazel
-	excludes = append(excludes, "external/autotest/venv/autotest_lib")
-	excludes = append(excludes, "external/autotest/autotest_lib")
-	excludes = append(excludes, "external/autotest/client/autotest_lib/client")
-
-	// FIXME: The external/google-fruit/extras/bazel_root/third_party/fruit dir is poison
-	// It contains several symlinks back to real source dirs, and those source dirs contain BUILD files we want to ignore
-	excludes = append(excludes, "external/google-fruit/extras/bazel_root/third_party/fruit")
-
-	// FIXME: 'frameworks/compile/slang' has a filegroup error due to an escaping issue
-	excludes = append(excludes, "frameworks/compile/slang")
-
-	// FIXME(b/260809113): 'prebuilts/clang/host/linux-x86/clang-dev' is a tool-generated symlink directory that contains a BUILD file.
-	// The bazel files finder code doesn't traverse into symlink dirs, and hence is not aware of this BUILD file and exclude it accordingly
-	// during symlink forest generation when checking against keepExistingBuildFiles allowlist.
-	//
-	// This is necessary because globs in //prebuilts/clang/host/linux-x86/BUILD
-	// currently assume no subpackages (keepExistingBuildFile is not recursive for that directory).
-	//
-	// This is a bandaid until we the symlink forest logic can intelligently exclude BUILD files found in source symlink dirs according
-	// to the keepExistingBuildFile allowlist.
-	excludes = append(excludes, "prebuilts/clang/host/linux-x86/clang-dev")
-
-	return excludes
+	maybeQuit(err, "error touching '%s'", path)
 }
 
 // Read the bazel.list file that the Soong Finder already dumped earlier (hopefully)
@@ -607,12 +436,7 @@
 		// Assume this was a relative path under topDir
 		bazelFinderFile = filepath.Join(topDir, bazelFinderFile)
 	}
-	data, err := ioutil.ReadFile(bazelFinderFile)
-	if err != nil {
-		return nil, err
-	}
-	files := strings.Split(strings.TrimSpace(string(data)), "\n")
-	return files, nil
+	return readFileLines(bazelFinderFile)
 }
 
 func bazelArtifacts() []string {
@@ -634,41 +458,25 @@
 // Ideally, bp2build would write a file that contains instructions to the
 // symlink tree creation binary. Then the latter would not need to depend on
 // the very heavy-weight machinery of soong_build .
-func runSymlinkForestCreation(configuration android.Config, ctx *android.Context, extraNinjaDeps []string, metricsDir string) string {
+func runSymlinkForestCreation(ctx *android.Context, extraNinjaDeps []string, metricsDir string) string {
 	ctx.EventHandler.Do("symlink_forest", func() {
 		var ninjaDeps []string
 		ninjaDeps = append(ninjaDeps, extraNinjaDeps...)
-
-		generatedRoot := shared.JoinPath(configuration.SoongOutDir(), "bp2build")
-		workspaceRoot := shared.JoinPath(configuration.SoongOutDir(), "workspace")
-
-		excludes := bazelArtifacts()
-
-		if outDir[0] != '/' {
-			excludes = append(excludes, outDir)
-		}
-
-		existingBazelRelatedFiles, err := getExistingBazelRelatedFiles(topDir)
-		if err != nil {
-			fmt.Fprintf(os.Stderr, "Error determining existing Bazel-related files: %s\n", err)
-			os.Exit(1)
-		}
-
-		pathsToIgnoredBuildFiles := getPathsToIgnoredBuildFiles(configuration.Bp2buildPackageConfig, topDir, existingBazelRelatedFiles, configuration.IsEnvTrue("BP2BUILD_VERBOSE"))
-		excludes = append(excludes, pathsToIgnoredBuildFiles...)
-		excludes = append(excludes, getTemporaryExcludes()...)
+		verbose := ctx.Config().IsEnvTrue("BP2BUILD_VERBOSE")
 
 		// PlantSymlinkForest() returns all the directories that were readdir()'ed.
 		// Such a directory SHOULD be added to `ninjaDeps` so that a child directory
 		// or file created/deleted under it would trigger an update of the symlink forest.
+		generatedRoot := shared.JoinPath(ctx.Config().SoongOutDir(), "bp2build")
+		workspaceRoot := shared.JoinPath(ctx.Config().SoongOutDir(), "workspace")
 		ctx.EventHandler.Do("plant", func() {
 			symlinkForestDeps := bp2build.PlantSymlinkForest(
-				configuration.IsEnvTrue("BP2BUILD_VERBOSE"), topDir, workspaceRoot, generatedRoot, excludes)
+				verbose, topDir, workspaceRoot, generatedRoot, excludedFromSymlinkForest(ctx, verbose))
 			ninjaDeps = append(ninjaDeps, symlinkForestDeps...)
 		})
 
-		writeDepFile(symlinkForestMarker, ctx.EventHandler, ninjaDeps)
-		touch(shared.JoinPath(topDir, symlinkForestMarker))
+		writeDepFile(cmdlineArgs.SymlinkForestMarker, ctx.EventHandler, ninjaDeps)
+		touch(shared.JoinPath(topDir, cmdlineArgs.SymlinkForestMarker))
 	})
 	codegenMetrics := bp2build.ReadCodegenMetrics(metricsDir)
 	if codegenMetrics == nil {
@@ -679,21 +487,84 @@
 		//invocation of codegen. We should simply use a separate .pb file
 	}
 	writeBp2BuildMetrics(codegenMetrics, ctx.EventHandler, metricsDir)
+	return cmdlineArgs.SymlinkForestMarker
+}
 
-	return symlinkForestMarker
+func excludedFromSymlinkForest(ctx *android.Context, verbose bool) []string {
+	excluded := bazelArtifacts()
+	if cmdlineArgs.OutDir[0] != '/' {
+		excluded = append(excluded, cmdlineArgs.OutDir)
+	}
+
+	// Find BUILD files in the srcDir which are not in the allowlist
+	// (android.Bp2BuildConversionAllowlist#ShouldKeepExistingBuildFileForDir)
+	// and return their paths so they can be left out of the Bazel workspace dir (i.e. ignored)
+	existingBazelFiles, err := getExistingBazelRelatedFiles(topDir)
+	maybeQuit(err, "Error determining existing Bazel-related files")
+
+	for _, path := range existingBazelFiles {
+		fullPath := shared.JoinPath(topDir, path)
+		fileInfo, err2 := os.Stat(fullPath)
+		if err2 != nil {
+			// Warn about error, but continue trying to check files
+			fmt.Fprintf(os.Stderr, "WARNING: Error accessing path '%s', err: %s\n", fullPath, err2)
+			continue
+		}
+		// Exclude only files named 'BUILD' or 'BUILD.bazel' and unless forcibly kept
+		if fileInfo.IsDir() ||
+			(fileInfo.Name() != "BUILD" && fileInfo.Name() != "BUILD.bazel") ||
+			ctx.Config().Bp2buildPackageConfig.ShouldKeepExistingBuildFileForDir(filepath.Dir(path)) {
+			// Don't ignore this existing build file
+			continue
+		}
+		if verbose {
+			fmt.Fprintf(os.Stderr, "Ignoring existing BUILD file: %s\n", path)
+		}
+		excluded = append(excluded, path)
+	}
+
+	// Temporarily exclude stuff to make `bazel build //external/...` (and `bazel build //frameworks/...`)  work
+	excluded = append(excluded,
+		// FIXME: 'autotest_lib' is a symlink back to external/autotest, and this causes an infinite
+		// symlink expansion error for Bazel
+		"external/autotest/venv/autotest_lib",
+		"external/autotest/autotest_lib",
+		"external/autotest/client/autotest_lib/client",
+
+		// FIXME: The external/google-fruit/extras/bazel_root/third_party/fruit dir is poison
+		// It contains several symlinks back to real source dirs, and those source dirs contain
+		// BUILD files we want to ignore
+		"external/google-fruit/extras/bazel_root/third_party/fruit",
+
+		// FIXME: 'frameworks/compile/slang' has a filegroup error due to an escaping issue
+		"frameworks/compile/slang",
+
+		// FIXME(b/260809113): 'prebuilts/clang/host/linux-x86/clang-dev' is a tool-generated symlink
+		// directory that contains a BUILD file. The bazel files finder code doesn't traverse into symlink dirs,
+		// and hence is not aware of this BUILD file and exclude it accordingly during symlink forest generation
+		// when checking against keepExistingBuildFiles allowlist.
+		//
+		// This is necessary because globs in //prebuilts/clang/host/linux-x86/BUILD
+		// currently assume no subpackages (keepExistingBuildFile is not recursive for that directory).
+		//
+		// This is a bandaid until we the symlink forest logic can intelligently exclude BUILD files found in
+		// source symlink dirs according to the keepExistingBuildFile allowlist.
+		"prebuilts/clang/host/linux-x86/clang-dev",
+	)
+	return excluded
 }
 
 // Run Soong in the bp2build mode. This creates a standalone context that registers
 // an alternate pipeline of mutators and singletons specifically for generating
 // Bazel BUILD files instead of Ninja files.
-func runBp2Build(configuration android.Config, ctx *android.Context, extraNinjaDeps []string, metricsDir string) string {
+func runBp2Build(ctx *android.Context, extraNinjaDeps []string, metricsDir string) string {
 	var codegenMetrics *bp2build.CodegenMetrics
 	ctx.EventHandler.Do("bp2build", func() {
 
 		// Propagate "allow misssing dependencies" bit. This is normally set in
 		// newContext(), but we create ctx without calling that method.
-		ctx.SetAllowMissingDependencies(configuration.AllowMissingDependencies())
-		ctx.SetNameInterface(newNameResolver(configuration))
+		ctx.SetAllowMissingDependencies(ctx.Config().AllowMissingDependencies())
+		ctx.SetNameInterface(newNameResolver(ctx.Config()))
 		ctx.RegisterForBazelConversion()
 		ctx.SetModuleListFile(cmdlineArgs.ModuleListFile)
 
@@ -705,34 +576,35 @@
 		// from the regular Modules.
 		ctx.EventHandler.Do("bootstrap", func() {
 			blueprintArgs := cmdlineArgs
-			bootstrapDeps := bootstrap.RunBlueprint(blueprintArgs, bootstrap.StopBeforePrepareBuildActions, ctx.Context, configuration)
+			bootstrapDeps := bootstrap.RunBlueprint(blueprintArgs.Args,
+				bootstrap.StopBeforePrepareBuildActions, ctx.Context, ctx.Config())
 			ninjaDeps = append(ninjaDeps, bootstrapDeps...)
 		})
 
-		globListFiles := writeBuildGlobsNinjaFile(ctx, configuration.SoongOutDir(), configuration)
+		globListFiles := writeBuildGlobsNinjaFile(ctx)
 		ninjaDeps = append(ninjaDeps, globListFiles...)
 
 		// Run the code-generation phase to convert BazelTargetModules to BUILD files
 		// and print conversion codegenMetrics to the user.
-		codegenContext := bp2build.NewCodegenContext(configuration, ctx, bp2build.Bp2Build)
+		codegenContext := bp2build.NewCodegenContext(ctx.Config(), ctx, bp2build.Bp2Build)
 		ctx.EventHandler.Do("codegen", func() {
 			codegenMetrics = bp2build.Codegen(codegenContext)
 		})
 
 		ninjaDeps = append(ninjaDeps, codegenContext.AdditionalNinjaDeps()...)
 
-		writeDepFile(bp2buildMarker, ctx.EventHandler, ninjaDeps)
-		touch(shared.JoinPath(topDir, bp2buildMarker))
+		writeDepFile(cmdlineArgs.Bp2buildMarker, ctx.EventHandler, ninjaDeps)
+		touch(shared.JoinPath(topDir, cmdlineArgs.Bp2buildMarker))
 	})
 
 	// Only report metrics when in bp2build mode. The metrics aren't relevant
 	// for queryview, since that's a total repo-wide conversion and there's a
 	// 1:1 mapping for each module.
-	if configuration.IsEnvTrue("BP2BUILD_VERBOSE") {
+	if ctx.Config().IsEnvTrue("BP2BUILD_VERBOSE") {
 		codegenMetrics.Print()
 	}
 	writeBp2BuildMetrics(codegenMetrics, ctx.EventHandler, metricsDir)
-	return bp2buildMarker
+	return cmdlineArgs.Bp2buildMarker
 }
 
 // Write Bp2Build metrics into $LOG_DIR
@@ -751,13 +623,22 @@
 	codegenMetrics.Write(metricsDir)
 }
 
-func readBazelPaths(configuration android.Config) ([]string, error) {
-	depsPath := configuration.Getenv("BAZEL_DEPS_FILE")
-
-	data, err := os.ReadFile(depsPath)
-	if err != nil {
-		return nil, err
+func readFileLines(path string) ([]string, error) {
+	data, err := os.ReadFile(path)
+	if err == nil {
+		return strings.Split(strings.TrimSpace(string(data)), "\n"), nil
 	}
-	paths := strings.Split(strings.TrimSpace(string(data)), "\n")
-	return paths, nil
+	return nil, err
+
+}
+func maybeQuit(err error, format string, args ...interface{}) {
+	if err == nil {
+		return
+	}
+	if format != "" {
+		fmt.Fprintln(os.Stderr, fmt.Sprintf(format, args...)+": "+err.Error())
+	} else {
+		fmt.Fprintln(os.Stderr, err)
+	}
+	os.Exit(1)
 }
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/genrule/genrule.go b/genrule/genrule.go
index 14895c9..f5da50e 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -940,6 +940,8 @@
 		}
 	}
 
+	tags := android.ApexAvailableTags(m)
+
 	if ctx.ModuleType() == "gensrcs" {
 		// The Output_extension prop is not in an immediately accessible field
 		// in the Module struct, so use GetProperties and cast it
@@ -961,7 +963,10 @@
 			Cmd:              cmd,
 			Tools:            tools,
 		}
-		ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs)
+		ctx.CreateBazelTargetModule(props, android.CommonAttributes{
+			Name: m.Name(),
+			Tags: tags,
+		}, attrs)
 	} else {
 		// The Out prop is not in an immediately accessible field
 		// in the Module struct, so use GetProperties and cast it
@@ -982,7 +987,10 @@
 		props := bazel.BazelTargetModuleProperties{
 			Rule_class: "genrule",
 		}
-		ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: m.Name()}, attrs)
+		ctx.CreateBazelTargetModule(props, android.CommonAttributes{
+			Name: m.Name(),
+			Tags: tags,
+		}, attrs)
 	}
 }
 
diff --git a/java/builder.go b/java/builder.go
index b1b9a4a..6f8eec9 100644
--- a/java/builder.go
+++ b/java/builder.go
@@ -231,7 +231,7 @@
 
 	jetifier = pctx.AndroidStaticRule("jetifier",
 		blueprint.RuleParams{
-			Command:     "${config.JavaCmd}  ${config.JavaVmFlags} -jar ${config.JetifierJar} -l error -o $out -i $in",
+			Command:     "${config.JavaCmd}  ${config.JavaVmFlags} -jar ${config.JetifierJar} -l error -o $out -i $in -t epoch",
 			CommandDeps: []string{"${config.JavaCmd}", "${config.JetifierJar}"},
 		},
 	)
diff --git a/java/java.go b/java/java.go
index e37a77e..dd24376 100644
--- a/java/java.go
+++ b/java/java.go
@@ -888,6 +888,10 @@
 
 	// a list of extra test configuration files that should be installed with the module.
 	Extra_test_configs []string `android:"path,arch_variant"`
+
+	// Extra <option> tags to add to the auto generated test xml file. The "key"
+	// is optional in each of these.
+	Tradefed_options []tradefed.Option
 }
 
 type testProperties struct {
@@ -1166,8 +1170,18 @@
 		j.testProperties.Test_options.Unit_test = proptools.BoolPtr(defaultUnitTest)
 	}
 
-	j.testConfig = tradefed.AutoGenJavaTestConfig(ctx, j.testProperties.Test_config, j.testProperties.Test_config_template,
-		j.testProperties.Test_suites, configs, j.testProperties.Auto_gen_config, j.testProperties.Test_options.Unit_test)
+	j.testConfig = tradefed.NewMaybeAutoGenTestConfigBuilder(ctx).
+		SetTestConfigProp(j.testProperties.Test_config).
+		SetTestTemplateConfigProp(j.testProperties.Test_config_template).
+		SetTestSuites(j.testProperties.Test_suites).
+		SetConfig(configs).
+		SetOptionsForAutogenerated(j.testProperties.Test_options.Tradefed_options).
+		SetAutoGenConfig(j.testProperties.Auto_gen_config).
+		SetUnitTest(j.testProperties.Test_options.Unit_test).
+		SetDeviceTemplate("${JavaTestConfigTemplate}").
+		SetHostTemplate("${JavaHostTestConfigTemplate}").
+		SetHostUnitTestTemplate("${JavaHostUnitTestConfigTemplate}").
+		Build()
 
 	j.data = android.PathsForModuleSrc(ctx, j.testProperties.Data)
 
@@ -1212,8 +1226,13 @@
 }
 
 func (j *JavaTestImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	j.testConfig = tradefed.AutoGenJavaTestConfig(ctx, j.prebuiltTestProperties.Test_config, nil,
-		j.prebuiltTestProperties.Test_suites, nil, nil, nil)
+	j.testConfig = tradefed.NewMaybeAutoGenTestConfigBuilder(ctx).
+		SetTestConfigProp(j.prebuiltTestProperties.Test_config).
+		SetTestSuites(j.prebuiltTestProperties.Test_suites).
+		SetDeviceTemplate("${JavaTestConfigTemplate}").
+		SetHostTemplate("${JavaHostTestConfigTemplate}").
+		SetHostUnitTestTemplate("${JavaHostUnitTestConfigTemplate}").
+		Build()
 
 	j.Import.GenerateAndroidBuildActions(ctx)
 }
@@ -1567,8 +1586,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 +1690,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..62a372c 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 {
@@ -1944,3 +1945,25 @@
 		}
 	}
 }
+
+func TestTradefedOptions(t *testing.T) {
+	result := PrepareForTestWithJavaBuildComponents.RunTestWithBp(t, `
+java_test_host {
+	name: "foo",
+	test_options: {
+		tradefed_options: [
+			{
+				name: "exclude-path",
+				value: "org/apache"
+			}
+		]
+	}
+}
+`)
+	args := result.ModuleForTests("foo", "linux_glibc_common").
+		Output("out/soong/.intermediates/foo/linux_glibc_common/foo.config").Args
+	expected := proptools.NinjaAndShellEscape("<option name=\"exclude-path\" value=\"org/apache\" />")
+	if args["extraConfigs"] != expected {
+		t.Errorf("Expected args[\"extraConfigs\"] to equal %q, was %q", expected, args["extraConfigs"])
+	}
+}
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..63e05d8 100644
--- a/java/robolectric.go
+++ b/java/robolectric.go
@@ -131,9 +131,14 @@
 	r.forceOSType = ctx.Config().BuildOS
 	r.forceArchType = ctx.Config().BuildArch
 
-	r.testConfig = tradefed.AutoGenRobolectricTestConfig(ctx, r.testProperties.Test_config,
-		r.testProperties.Test_config_template, r.testProperties.Test_suites,
-		r.testProperties.Auto_gen_config)
+	r.testConfig = tradefed.NewMaybeAutoGenTestConfigBuilder(ctx).
+		SetTestConfigProp(r.testProperties.Test_config).
+		SetTestTemplateConfigProp(r.testProperties.Test_config_template).
+		SetTestSuites(r.testProperties.Test_suites).
+		SetAutoGenConfig(r.testProperties.Auto_gen_config).
+		SetDeviceTemplate("${RobolectricTestConfigTemplate}").
+		SetHostTemplate("${RobolectricTestConfigTemplate}").
+		Build()
 	r.data = android.PathsForModuleSrc(ctx, r.testProperties.Data)
 
 	roboTestConfig := android.PathForModuleGen(ctx, "robolectric").
@@ -296,6 +301,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 +335,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..705eac3 100644
--- a/mk2rbc/mk2rbc.go
+++ b/mk2rbc/mk2rbc.go
@@ -77,6 +77,8 @@
 	"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},
+	"clear-var-list":                       &simpleCallParser{name: baseName + ".clear_var_list", returnType: starlarkTypeVoid, addGlobals: true, addHandle: 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 +107,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 +117,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 +1435,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 +1898,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/python/test.go b/python/test.go
index b9b3465..5781df7 100644
--- a/python/test.go
+++ b/python/test.go
@@ -67,9 +67,14 @@
 }
 
 func (test *testDecorator) install(ctx android.ModuleContext, file android.Path) {
-	test.testConfig = tradefed.AutoGenPythonBinaryHostTestConfig(ctx, test.testProperties.Test_config,
-		test.testProperties.Test_config_template, test.binaryDecorator.binaryProperties.Test_suites,
-		test.binaryDecorator.binaryProperties.Auto_gen_config)
+	test.testConfig = tradefed.NewMaybeAutoGenTestConfigBuilder(ctx).
+		SetTestConfigProp(test.testProperties.Test_config).
+		SetTestTemplateConfigProp(test.testProperties.Test_config_template).
+		SetTestSuites(test.binaryDecorator.binaryProperties.Test_suites).
+		SetAutoGenConfig(test.binaryDecorator.binaryProperties.Auto_gen_config).
+		SetDeviceTemplate("${PythonBinaryHostTestConfigTemplate}").
+		SetHostTemplate("${PythonBinaryHostTestConfigTemplate}").
+		Build()
 
 	test.binaryDecorator.pythonInstaller.dir = "nativetest"
 	test.binaryDecorator.pythonInstaller.dir64 = "nativetest64"
diff --git a/rust/benchmark.go b/rust/benchmark.go
index 0e84243..b417a2d 100644
--- a/rust/benchmark.go
+++ b/rust/benchmark.go
@@ -112,12 +112,14 @@
 }
 
 func (benchmark *benchmarkDecorator) install(ctx ModuleContext) {
-	benchmark.testConfig = tradefed.AutoGenRustBenchmarkConfig(ctx,
-		benchmark.Properties.Test_config,
-		benchmark.Properties.Test_config_template,
-		benchmark.Properties.Test_suites,
-		nil,
-		benchmark.Properties.Auto_gen_config)
+	benchmark.testConfig = tradefed.NewMaybeAutoGenTestConfigBuilder(ctx).
+		SetTestConfigProp(benchmark.Properties.Test_config).
+		SetTestTemplateConfigProp(benchmark.Properties.Test_config_template).
+		SetTestSuites(benchmark.Properties.Test_suites).
+		SetAutoGenConfig(benchmark.Properties.Auto_gen_config).
+		SetDeviceTemplate("${RustDeviceBenchmarkConfigTemplate}").
+		SetHostTemplate("${RustHostBenchmarkConfigTemplate}").
+		Build()
 
 	// default relative install path is module name
 	if !Bool(benchmark.Properties.No_named_install_directory) {
diff --git a/rust/config/x86_64_device.go b/rust/config/x86_64_device.go
index 94b719f..3458ec9 100644
--- a/rust/config/x86_64_device.go
+++ b/rust/config/x86_64_device.go
@@ -26,15 +26,18 @@
 	x86_64LinkFlags            = []string{}
 
 	x86_64ArchVariantRustFlags = map[string][]string{
-		"":            []string{},
-		"broadwell":   []string{"-C target-cpu=broadwell"},
-		"haswell":     []string{"-C target-cpu=haswell"},
-		"ivybridge":   []string{"-C target-cpu=ivybridge"},
-		"sandybridge": []string{"-C target-cpu=sandybridge"},
-		"silvermont":  []string{"-C target-cpu=silvermont"},
-		"skylake":     []string{"-C target-cpu=skylake"},
+		"":              []string{},
+		"broadwell":     []string{"-C target-cpu=broadwell"},
+		"goldmont":      []string{"-C target-cpu=goldmont"},
+		"goldmont-plus": []string{"-C target-cpu=goldmont-plus"},
+		"haswell":       []string{"-C target-cpu=haswell"},
+		"ivybridge":     []string{"-C target-cpu=ivybridge"},
+		"sandybridge":   []string{"-C target-cpu=sandybridge"},
+		"silvermont":    []string{"-C target-cpu=silvermont"},
+		"skylake":       []string{"-C target-cpu=skylake"},
 		//TODO: Add target-cpu=stoneyridge when rustc supports it.
 		"stoneyridge": []string{""},
+		"tremont":     []string{"-C target-cpu=tremont"},
 	}
 )
 
diff --git a/rust/config/x86_device.go b/rust/config/x86_device.go
index 5ae30e7..43f7340 100644
--- a/rust/config/x86_device.go
+++ b/rust/config/x86_device.go
@@ -26,16 +26,19 @@
 	x86LinkFlags            = []string{}
 
 	x86ArchVariantRustFlags = map[string][]string{
-		"":            []string{},
-		"atom":        []string{"-C target-cpu=atom"},
-		"broadwell":   []string{"-C target-cpu=broadwell"},
-		"haswell":     []string{"-C target-cpu=haswell"},
-		"ivybridge":   []string{"-C target-cpu=ivybridge"},
-		"sandybridge": []string{"-C target-cpu=sandybridge"},
-		"silvermont":  []string{"-C target-cpu=silvermont"},
-		"skylake":     []string{"-C target-cpu=skylake"},
+		"":              []string{},
+		"atom":          []string{"-C target-cpu=atom"},
+		"broadwell":     []string{"-C target-cpu=broadwell"},
+		"goldmont":      []string{"-C target-cpu=goldmont"},
+		"goldmont-plus": []string{"-C target-cpu=goldmont-plus"},
+		"haswell":       []string{"-C target-cpu=haswell"},
+		"ivybridge":     []string{"-C target-cpu=ivybridge"},
+		"sandybridge":   []string{"-C target-cpu=sandybridge"},
+		"silvermont":    []string{"-C target-cpu=silvermont"},
+		"skylake":       []string{"-C target-cpu=skylake"},
 		//TODO: Add target-cpu=stoneyridge when rustc supports it.
 		"stoneyridge": []string{""},
+		"tremont":     []string{"-C target-cpu=tremont"},
 		// use prescott for x86_64, like cc/config/x86_device.go
 		"x86_64": []string{"-C target-cpu=prescott"},
 	}
diff --git a/rust/test.go b/rust/test.go
index 0cc3bca..ecc7d5d 100644
--- a/rust/test.go
+++ b/rust/test.go
@@ -130,13 +130,16 @@
 		configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.RootTargetPreparer", options})
 	}
 
-	test.testConfig = tradefed.AutoGenRustTestConfig(ctx,
-		test.Properties.Test_config,
-		test.Properties.Test_config_template,
-		test.Properties.Test_suites,
-		configs,
-		test.Properties.Auto_gen_config,
-		testInstallBase)
+	test.testConfig = tradefed.NewMaybeAutoGenTestConfigBuilder(ctx).
+		SetTestConfigProp(test.Properties.Test_config).
+		SetTestTemplateConfigProp(test.Properties.Test_config_template).
+		SetTestSuites(test.Properties.Test_suites).
+		SetConfig(configs).
+		SetAutoGenConfig(test.Properties.Auto_gen_config).
+		SetTestInstallBase(testInstallBase).
+		SetDeviceTemplate("${RustDeviceTestConfigTemplate}").
+		SetHostTemplate("${RustHostTestConfigTemplate}").
+		Build()
 
 	dataSrcPaths := android.PathsForModuleSrc(ctx, test.Properties.Data)
 
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/sh/sh_binary.go b/sh/sh_binary.go
index 9627329..4eae397 100644
--- a/sh/sh_binary.go
+++ b/sh/sh_binary.go
@@ -379,8 +379,16 @@
 		}
 		configs = append(configs, tradefed.Object{"target_preparer", "com.android.tradefed.targetprep.PushFilePreparer", options})
 	}
-	s.testConfig = tradefed.AutoGenShellTestConfig(ctx, s.testProperties.Test_config,
-		s.testProperties.Test_config_template, s.testProperties.Test_suites, configs, s.testProperties.Auto_gen_config, s.outputFilePath.Base())
+	s.testConfig = tradefed.NewMaybeAutoGenTestConfigBuilder(ctx).
+		SetTestConfigProp(s.testProperties.Test_config).
+		SetTestTemplateConfigProp(s.testProperties.Test_config_template).
+		SetTestSuites(s.testProperties.Test_suites).
+		SetConfig(configs).
+		SetAutoGenConfig(s.testProperties.Auto_gen_config).
+		SetOutputFileName(s.outputFilePath.Base()).
+		SetDeviceTemplate("${ShellTestConfigTemplate}").
+		SetHostTemplate("${ShellTestConfigTemplate}").
+		Build()
 
 	s.dataModules = make(map[string]android.Path)
 	ctx.VisitDirectDeps(func(dep android.Module) {
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..6a47e9f 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
 
@@ -65,11 +61,9 @@
   outdir=out2
   trap "rm -rf $outdir" EXIT
   # Modify OUT_DIR in a subshell so it doesn't affect the top level one.
-  (export OUT_DIR=$outdir; run_soong bp2build && run_bazel build --config=bp2build //a:g)
+  (export OUT_DIR=$outdir; run_soong bp2build && run_bazel build --config=bp2build --config=ci //a:g)
 }
 
-test_different_relative_outdir
-
 function test_different_absolute_outdir {
   setup
 
@@ -87,12 +81,10 @@
   outdir=$(mktemp -t -d st.XXXXX)
   trap 'rm -rf $outdir' EXIT
   # Modify OUT_DIR in a subshell so it doesn't affect the top level one.
-  (export OUT_DIR=$outdir; run_soong bp2build && run_bazel build --config=bp2build //a:g)
+  (export OUT_DIR=$outdir; run_soong bp2build && run_bazel build --config=bp2build --config=ci //a:g)
 }
 
-test_different_absolute_outdir
-
-function test_bp2build_generates_all_buildfiles {
+function _bp2build_generates_all_buildfiles {
   setup
 
   mkdir -p foo/convertible_soong_module
@@ -146,9 +138,9 @@
   fi
 
   # NOTE: We don't actually use the extra BUILD file for anything here
-  run_bazel build --config=android --package_path=out/soong/workspace //foo/...
+  run_bazel build --config=android --config=bp2build --config=ci //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
@@ -191,10 +185,10 @@
 
   run_soong bp2build
 
-  run_bazel build --config=android --package_path=out/soong/workspace //a:qq
+  run_bazel build --config=android --config=bp2build --config=ci //a:qq
   local -r output_mtime1=$(stat -c "%y" bazel-bin/a/_objs/qq/qq.o)
 
-  run_bazel build --config=android --package_path=out/soong/workspace //a:qq
+  run_bazel build --config=android --config=bp2build --config=ci //a:qq
   local -r output_mtime2=$(stat -c "%y" bazel-bin/a/_objs/qq/qq.o)
 
   if [[ "$output_mtime1" != "$output_mtime2" ]]; then
@@ -205,7 +199,7 @@
 #define QQ 2
 EOF
 
-  run_bazel build --config=android --package_path=out/soong/workspace //a:qq
+  run_bazel build --config=android --config=bp2build --config=ci //a:qq
   local -r output_mtime3=$(stat -c "%y" bazel-bin/a/_objs/qq/qq.o)
 
   if [[ "$output_mtime1" == "$output_mtime3" ]]; then
@@ -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..ae8875a 100644
--- a/tests/lib.sh
+++ b/tests/lib.sh
@@ -119,6 +119,7 @@
   copy_directory build/bazel
   copy_directory build/bazel_common_rules
 
+  symlink_directory packages/modules/common/build
   symlink_directory prebuilts/bazel
   symlink_directory prebuilts/clang
   symlink_directory prebuilts/jdk
@@ -150,3 +151,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/tradefed/Android.bp b/tradefed/Android.bp
index f0336a3..a161108 100644
--- a/tradefed/Android.bp
+++ b/tradefed/Android.bp
@@ -11,6 +11,7 @@
     ],
     srcs: [
         "autogen.go",
+        "autogen_bazel.go",
         "config.go",
         "makevars.go",
     ],
diff --git a/tradefed/autogen.go b/tradefed/autogen.go
index c2429ab..236e559 100644
--- a/tradefed/autogen.go
+++ b/tradefed/autogen.go
@@ -107,15 +107,134 @@
 
 }
 
-func autogenTemplate(ctx android.ModuleContext, output android.WritablePath, template string, configs []Config, testInstallBase string) {
-	autogenTemplateWithNameAndOutputFile(ctx, ctx.ModuleName(), output, template, configs, "", testInstallBase)
+// MaybeAutoGenTestConfigBuilder provides a Build() method that will either
+// generate a AndroidTest.xml file, or use an existing user-supplied one.
+// It used to be a bunch of separate functions for each language, but was
+// converted to this builder pattern to have one function that accepts many
+// optional arguments.
+type MaybeAutoGenTestConfigBuilder struct {
+	ctx                     android.ModuleContext
+	name                    string
+	outputFileName          string
+	testConfigProp          *string
+	testConfigTemplateProp  *string
+	testSuites              []string
+	config                  []Config
+	configsForAutogenerated []Config
+	autoGenConfig           *bool
+	unitTest                *bool
+	testInstallBase         string
+	deviceTemplate          string
+	hostTemplate            string
+	hostUnitTestTemplate    string
 }
 
-func autogenTemplateWithName(ctx android.ModuleContext, name string, output android.WritablePath, template string, configs []Config, testInstallBase string) {
-	autogenTemplateWithNameAndOutputFile(ctx, name, output, template, configs, "", testInstallBase)
+func NewMaybeAutoGenTestConfigBuilder(ctx android.ModuleContext) *MaybeAutoGenTestConfigBuilder {
+	return &MaybeAutoGenTestConfigBuilder{
+		ctx:  ctx,
+		name: ctx.ModuleName(),
+	}
 }
 
-func autogenTemplateWithNameAndOutputFile(ctx android.ModuleContext, name string, output android.WritablePath, template string, configs []Config, outputFileName string, testInstallBase string) {
+func (b *MaybeAutoGenTestConfigBuilder) SetName(name string) *MaybeAutoGenTestConfigBuilder {
+	b.name = name
+	return b
+}
+
+func (b *MaybeAutoGenTestConfigBuilder) SetOutputFileName(outputFileName string) *MaybeAutoGenTestConfigBuilder {
+	b.outputFileName = outputFileName
+	return b
+}
+
+func (b *MaybeAutoGenTestConfigBuilder) SetTestConfigProp(testConfigProp *string) *MaybeAutoGenTestConfigBuilder {
+	b.testConfigProp = testConfigProp
+	return b
+}
+
+func (b *MaybeAutoGenTestConfigBuilder) SetTestTemplateConfigProp(testConfigTemplateProp *string) *MaybeAutoGenTestConfigBuilder {
+	b.testConfigTemplateProp = testConfigTemplateProp
+	return b
+}
+
+func (b *MaybeAutoGenTestConfigBuilder) SetTestSuites(testSuites []string) *MaybeAutoGenTestConfigBuilder {
+	b.testSuites = testSuites
+	return b
+}
+
+func (b *MaybeAutoGenTestConfigBuilder) SetConfig(config []Config) *MaybeAutoGenTestConfigBuilder {
+	b.config = config
+	return b
+}
+
+func (b *MaybeAutoGenTestConfigBuilder) SetOptionsForAutogenerated(configsForAutogenerated []Option) *MaybeAutoGenTestConfigBuilder {
+	configs := make([]Config, 0, len(configsForAutogenerated))
+	for _, c := range configsForAutogenerated {
+		configs = append(configs, c)
+	}
+	b.configsForAutogenerated = configs
+	return b
+}
+
+func (b *MaybeAutoGenTestConfigBuilder) SetUnitTest(unitTest *bool) *MaybeAutoGenTestConfigBuilder {
+	b.unitTest = unitTest
+	return b
+}
+
+func (b *MaybeAutoGenTestConfigBuilder) SetAutoGenConfig(autoGenConfig *bool) *MaybeAutoGenTestConfigBuilder {
+	b.autoGenConfig = autoGenConfig
+	return b
+}
+
+func (b *MaybeAutoGenTestConfigBuilder) SetTestInstallBase(testInstallBase string) *MaybeAutoGenTestConfigBuilder {
+	b.testInstallBase = testInstallBase
+	return b
+}
+
+func (b *MaybeAutoGenTestConfigBuilder) SetDeviceTemplate(deviceTemplate string) *MaybeAutoGenTestConfigBuilder {
+	b.deviceTemplate = deviceTemplate
+	return b
+}
+
+func (b *MaybeAutoGenTestConfigBuilder) SetHostTemplate(hostTemplate string) *MaybeAutoGenTestConfigBuilder {
+	b.hostTemplate = hostTemplate
+	return b
+}
+
+func (b *MaybeAutoGenTestConfigBuilder) SetHostUnitTestTemplate(hostUnitTestTemplate string) *MaybeAutoGenTestConfigBuilder {
+	b.hostUnitTestTemplate = hostUnitTestTemplate
+	return b
+}
+
+func (b *MaybeAutoGenTestConfigBuilder) Build() android.Path {
+	config := append(b.config, b.configsForAutogenerated...)
+	path, autogenPath := testConfigPath(b.ctx, b.testConfigProp, b.testSuites, b.autoGenConfig, b.testConfigTemplateProp)
+	if autogenPath != nil {
+		templatePath := getTestConfigTemplate(b.ctx, b.testConfigTemplateProp)
+		if templatePath.Valid() {
+			autogenTemplate(b.ctx, b.name, autogenPath, templatePath.String(), config, b.outputFileName, b.testInstallBase)
+		} else {
+			if b.ctx.Device() {
+				autogenTemplate(b.ctx, b.name, autogenPath, b.deviceTemplate, config, b.outputFileName, b.testInstallBase)
+			} else {
+				if Bool(b.unitTest) {
+					autogenTemplate(b.ctx, b.name, autogenPath, b.hostUnitTestTemplate, config, b.outputFileName, b.testInstallBase)
+				} else {
+					autogenTemplate(b.ctx, b.name, autogenPath, b.hostTemplate, config, b.outputFileName, b.testInstallBase)
+				}
+			}
+		}
+		return autogenPath
+	}
+	if len(b.configsForAutogenerated) > 0 {
+		b.ctx.ModuleErrorf("Extra tradefed configurations were provided for an autogenerated xml file, but the autogenerated xml file was not used.")
+	}
+	return path
+}
+
+func autogenTemplate(ctx android.ModuleContext, name string, output android.WritablePath, template string, configs []Config, outputFileName string, testInstallBase string) {
+	if template == "" {
+		ctx.ModuleErrorf("Empty template")
+	}
 	var configStrings []string
 	for _, config := range configs {
 		configStrings = append(configStrings, config.Config())
@@ -137,148 +256,6 @@
 	})
 }
 
-func AutoGenNativeTestConfig(ctx android.ModuleContext, testConfigProp *string,
-	testConfigTemplateProp *string, testSuites []string, config []Config, autoGenConfig *bool, testInstallBase string) android.Path {
-
-	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig, testConfigTemplateProp)
-	if autogenPath != nil {
-		templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp)
-		if templatePath.Valid() {
-			autogenTemplate(ctx, autogenPath, templatePath.String(), config, testInstallBase)
-		} else {
-			if ctx.Device() {
-				autogenTemplate(ctx, autogenPath, "${NativeTestConfigTemplate}", config, testInstallBase)
-			} else {
-				autogenTemplate(ctx, autogenPath, "${NativeHostTestConfigTemplate}", config, testInstallBase)
-			}
-		}
-		return autogenPath
-	}
-	return path
-}
-
-func AutoGenShellTestConfig(ctx android.ModuleContext, testConfigProp *string,
-	testConfigTemplateProp *string, testSuites []string, config []Config, autoGenConfig *bool, outputFileName string) android.Path {
-	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig, testConfigTemplateProp)
-	if autogenPath != nil {
-		templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp)
-		if templatePath.Valid() {
-			autogenTemplateWithNameAndOutputFile(ctx, ctx.ModuleName(), autogenPath, templatePath.String(), config, outputFileName, "")
-		} else {
-			autogenTemplateWithNameAndOutputFile(ctx, ctx.ModuleName(), autogenPath, "${ShellTestConfigTemplate}", config, outputFileName, "")
-		}
-		return autogenPath
-	}
-	return path
-}
-
-func AutoGenNativeBenchmarkTestConfig(ctx android.ModuleContext, testConfigProp *string,
-	testConfigTemplateProp *string, testSuites []string, configs []Config, autoGenConfig *bool) android.Path {
-	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig, testConfigTemplateProp)
-	if autogenPath != nil {
-		templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp)
-		if templatePath.Valid() {
-			autogenTemplate(ctx, autogenPath, templatePath.String(), configs, "")
-		} else {
-			autogenTemplate(ctx, autogenPath, "${NativeBenchmarkTestConfigTemplate}", configs, "")
-		}
-		return autogenPath
-	}
-	return path
-}
-
-func AutoGenJavaTestConfig(ctx android.ModuleContext, testConfigProp *string, testConfigTemplateProp *string,
-	testSuites []string, config []Config, autoGenConfig *bool, unitTest *bool) android.Path {
-	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig, testConfigTemplateProp)
-	if autogenPath != nil {
-		templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp)
-		if templatePath.Valid() {
-			autogenTemplate(ctx, autogenPath, templatePath.String(), config, "")
-		} else {
-			if ctx.Device() {
-				autogenTemplate(ctx, autogenPath, "${JavaTestConfigTemplate}", config, "")
-			} else {
-				if Bool(unitTest) {
-					autogenTemplate(ctx, autogenPath, "${JavaHostUnitTestConfigTemplate}", config, "")
-				} else {
-					autogenTemplate(ctx, autogenPath, "${JavaHostTestConfigTemplate}", config, "")
-				}
-			}
-		}
-		return autogenPath
-	}
-	return path
-}
-
-func AutoGenPythonBinaryHostTestConfig(ctx android.ModuleContext, testConfigProp *string,
-	testConfigTemplateProp *string, testSuites []string, autoGenConfig *bool) android.Path {
-
-	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig, testConfigTemplateProp)
-	if autogenPath != nil {
-		templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp)
-		if templatePath.Valid() {
-			autogenTemplate(ctx, autogenPath, templatePath.String(), nil, "")
-		} else {
-			autogenTemplate(ctx, autogenPath, "${PythonBinaryHostTestConfigTemplate}", nil, "")
-		}
-		return autogenPath
-	}
-	return path
-}
-
-func AutoGenRustTestConfig(ctx android.ModuleContext, testConfigProp *string,
-	testConfigTemplateProp *string, testSuites []string, config []Config, autoGenConfig *bool, testInstallBase string) android.Path {
-	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig, testConfigTemplateProp)
-	if autogenPath != nil {
-		templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp)
-		if templatePath.Valid() {
-			autogenTemplate(ctx, autogenPath, templatePath.String(), config, testInstallBase)
-		} else {
-			if ctx.Device() {
-				autogenTemplate(ctx, autogenPath, "${RustDeviceTestConfigTemplate}", config, testInstallBase)
-			} else {
-				autogenTemplate(ctx, autogenPath, "${RustHostTestConfigTemplate}", config, testInstallBase)
-			}
-		}
-		return autogenPath
-	}
-	return path
-}
-
-func AutoGenRustBenchmarkConfig(ctx android.ModuleContext, testConfigProp *string,
-	testConfigTemplateProp *string, testSuites []string, config []Config, autoGenConfig *bool) android.Path {
-	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig, testConfigTemplateProp)
-	if autogenPath != nil {
-		templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp)
-		if templatePath.Valid() {
-			autogenTemplate(ctx, autogenPath, templatePath.String(), config, "")
-		} else {
-			if ctx.Device() {
-				autogenTemplate(ctx, autogenPath, "${RustDeviceBenchmarkConfigTemplate}", config, "")
-			} else {
-				autogenTemplate(ctx, autogenPath, "${RustHostBenchmarkConfigTemplate}", config, "")
-			}
-		}
-		return autogenPath
-	}
-	return path
-}
-
-func AutoGenRobolectricTestConfig(ctx android.ModuleContext, testConfigProp *string, testConfigTemplateProp *string,
-	testSuites []string, autoGenConfig *bool) android.Path {
-	path, autogenPath := testConfigPath(ctx, testConfigProp, testSuites, autoGenConfig, testConfigTemplateProp)
-	if autogenPath != nil {
-		templatePath := getTestConfigTemplate(ctx, testConfigTemplateProp)
-		if templatePath.Valid() {
-			autogenTemplate(ctx, autogenPath, templatePath.String(), nil, "")
-		} else {
-			autogenTemplate(ctx, autogenPath, "${RobolectricTestConfigTemplate}", nil, "")
-		}
-		return autogenPath
-	}
-	return path
-}
-
 var autogenInstrumentationTest = pctx.StaticRule("autogenInstrumentationTest", blueprint.RuleParams{
 	Command: "${AutoGenTestConfigScript} $out $in ${EmptyTestConfig} $template ${extraConfigs}",
 	CommandDeps: []string{
diff --git a/tradefed/autogen_bazel.go b/tradefed/autogen_bazel.go
new file mode 100644
index 0000000..d3109d9
--- /dev/null
+++ b/tradefed/autogen_bazel.go
@@ -0,0 +1,105 @@
+// Copyright 2022 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tradefed
+
+import (
+	"android/soong/android"
+	"android/soong/bazel"
+
+	"github.com/google/blueprint/proptools"
+)
+
+const (
+	InstrumentationTestConfigTemplate  = "build/make/core/instrumentation_test_config_template.xml"
+	JavaTestConfigTemplate             = "build/make/core/java_test_config_template.xml"
+	JavaHostTestConfigTemplate         = "build/make/core/java_host_test_config_template.xml"
+	JavaHostUnitTestConfigTemplate     = "build/make/core/java_host_unit_test_config_template.xml"
+	NativeBenchmarkTestConfigTemplate  = "build/make/core/native_benchmark_test_config_template.xml"
+	NativeHostTestConfigTemplate       = "build/make/core/native_host_test_config_template.xml"
+	NativeTestConfigTemplate           = "build/make/core/native_test_config_template.xml"
+	PythonBinaryHostTestConfigTemplate = "build/make/core/python_binary_host_test_config_template.xml"
+	RustDeviceTestConfigTemplate       = "build/make/core/rust_device_test_config_template.xml"
+	RustHostTestConfigTemplate         = "build/make/core/rust_host_test_config_template.xml"
+	RustDeviceBenchmarkConfigTemplate  = "build/make/core/rust_device_benchmark_config_template.xml"
+	RustHostBenchmarkConfigTemplate    = "build/make/core/rust_host_benchmark_config_template.xml"
+	RobolectricTestConfigTemplate      = "build/make/core/robolectric_test_config_template.xml"
+	ShellTestConfigTemplate            = "build/make/core/shell_test_config_template.xml"
+)
+
+type TestConfigAttributes struct {
+	Test_config *bazel.Label
+
+	Auto_generate_test_config *bool
+	Template_test_config      *bazel.Label
+	Template_configs          []string
+	Template_install_base     *string
+}
+
+func GetTestConfigAttributes(
+	ctx android.TopDownMutatorContext,
+	testConfig *string,
+	extraTestConfigs []string,
+	autoGenConfig *bool,
+	testSuites []string,
+	template *string,
+	templateConfigs []Config,
+	templateInstallBase *string) TestConfigAttributes {
+
+	attrs := TestConfigAttributes{}
+	attrs.Test_config = GetTestConfig(ctx, testConfig)
+	// do not generate a test config if
+	// 1) test config already found
+	// 2) autoGenConfig == false
+	// 3) CTS tests and no template specified.
+	// CTS Modules can be used for test data, so test config files must be explicitly specified.
+	if (attrs.Template_test_config != nil) ||
+		proptools.Bool(autoGenConfig) == false ||
+		(template == nil && !android.InList("cts", testSuites)) {
+
+		return attrs
+	}
+
+	// Add properties for the bazel rule to generate a test config
+	// since a test config was not specified.
+	templateLabel := android.BazelLabelForModuleSrcSingle(ctx, *template)
+	attrs.Template_test_config = &templateLabel
+	attrs.Auto_generate_test_config = autoGenConfig
+	var configStrings []string
+	for _, c := range templateConfigs {
+		configString := proptools.NinjaAndShellEscape(c.Config())
+		configStrings = append(configStrings, configString)
+	}
+	attrs.Template_configs = configStrings
+	attrs.Template_install_base = templateInstallBase
+	return attrs
+}
+
+func GetTestConfig(
+	ctx android.TopDownMutatorContext,
+	testConfig *string,
+) *bazel.Label {
+
+	if testConfig != nil {
+		c, _ := android.BazelStringOrLabelFromProp(ctx, testConfig)
+		if c.Value != nil {
+			return c.Value
+		}
+	}
+
+	// check for default AndroidTest.xml
+	defaultTestConfigPath := ctx.ModuleDir() + "/AndroidTest.xml"
+	c, _ := android.BazelStringOrLabelFromProp(ctx, &defaultTestConfigPath)
+	return c.Value
+}
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/rbe.go b/ui/build/rbe.go
index c3e52c6..3c844c1 100644
--- a/ui/build/rbe.go
+++ b/ui/build/rbe.go
@@ -148,7 +148,7 @@
 	}
 	if !config.StubbyExists() && prodCredsAuthType(config) {
 		fmt.Fprintln(ctx.Writer, "")
-		fmt.Fprintln(ctx.Writer, fmt.Sprintf("\033[33mWARNING: %q binary not found in $PATH, follow go/build-fast#opting-out-of-loas-credentials instead for authenticating with RBE.\033[0m", "stubby"))
+		fmt.Fprintln(ctx.Writer, fmt.Sprintf("\033[33mWARNING: %q binary not found in $PATH, follow go/build-fast-without-stubby instead for authenticating with RBE.\033[0m", "stubby"))
 		fmt.Fprintln(ctx.Writer, "")
 		return
 	}
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 {