Merge "Write product config files only if changed."
diff --git a/android/allowlists/allowlists.go b/android/allowlists/allowlists.go
index fb56ee1..4fc6ae3 100644
--- a/android/allowlists/allowlists.go
+++ b/android/allowlists/allowlists.go
@@ -400,6 +400,9 @@
 		// not recursive due to conflicting workspace paths in tools/atest/bazel/rules
 		"tools/asuite/atest":/* recursive = */ false,
 		"tools/asuite/atest/bazel/reporter":/* recursive = */ true,
+
+		// TODO(b/266459895): remove this and the placeholder BUILD file after re-enabling libunwindstack
+		"external/rust/crates/rustc-demangle-capi":/* recursive = */ false,
 	}
 
 	Bp2buildModuleAlwaysConvertList = []string{
@@ -673,6 +676,9 @@
 		// kotlin srcs in java binary
 		"AnalyzerKt",
 		"trebuchet-core",
+
+		// kotlin srcs in android_library
+		"renderscript_toolkit",
 	}
 
 	Bp2buildModuleTypeAlwaysConvertList = []string{
@@ -1320,6 +1326,9 @@
 
 		// uses glob in $(locations)
 		"libc_musl_sysroot",
+
+		// TODO(b/266459895): depends on libunwindstack
+		"libutils_test",
 	}
 
 	MixedBuildsDisabledList = []string{
@@ -1364,6 +1373,30 @@
 		"prebuilt_kotlin-test",
 		// TODO(b/217750501) exclude_files property not supported
 		"prebuilt_currysrc_org.eclipse",
+
+		// TODO(b/266459895): re-enable libunwindstack
+		"libunwindstack",
+		"libunwindstack_stdout_log",
+		"libunwindstack_no_dex",
+		"libunwindstack_utils",
+		"unwind_reg_info",
+		"libunwindstack_local",
+		"unwind_for_offline",
+		"unwind",
+		"unwind_info",
+		"unwind_symbols",
+		"libc_malloc_debug",
+		"libfdtrack",
+		"mediaswcodec",
+		"libcodec2_hidl@1.0",
+		"libEGL",
+		"libstagefright_bufferqueue_helper_novndk",
+		"libGLESv2",
+		"libcodec2_hidl@1.1",
+		"libmedia_codecserviceregistrant",
+		"libcodec2_hidl@1.2",
+		"libutils_test",
+		"libutilscallstack",
 	}
 
 	// Bazel prod-mode allowlist. Modules in this list are built by Bazel
@@ -1381,5 +1414,9 @@
 	StagingMixedBuildsEnabledList = []string{
 		"com.android.adbd",
 		"adbd_test",
+		"adb_crypto_test",
+		"adb_pairing_auth_test",
+		"adb_pairing_connection_test",
+		"adb_tls_connection_test",
 	}
 )
diff --git a/android/api_domain.go b/android/api_domain.go
index bdd4e6f..587ceae 100644
--- a/android/api_domain.go
+++ b/android/api_domain.go
@@ -32,17 +32,18 @@
 
 // TODO(b/246656800): Reconcile with android.SdkKind
 const (
-	PublicApi ApiSurface = iota
-	SystemApi
-	VendorApi
+	// API surface provided by platform and mainline modules to other mainline modules
+	ModuleLibApi ApiSurface = iota
+	PublicApi               // Aka NDK
+	VendorApi               // Aka LLNDK
 )
 
 func (a ApiSurface) String() string {
 	switch a {
+	case ModuleLibApi:
+		return "module-libapi"
 	case PublicApi:
 		return "publicapi"
-	case SystemApi:
-		return "systemapi"
 	case VendorApi:
 		return "vendorapi"
 	default:
diff --git a/android/bazel.go b/android/bazel.go
index 10e9251..52f50c5 100644
--- a/android/bazel.go
+++ b/android/bazel.go
@@ -526,3 +526,15 @@
 
 	return "", errors.New("Main-Class is not found.")
 }
+
+func AttachValidationActions(ctx ModuleContext, outputFilePath Path, validations Paths) ModuleOutPath {
+	validatedOutputFilePath := PathForModuleOut(ctx, "validated", outputFilePath.Base())
+	ctx.Build(pctx, BuildParams{
+		Rule:        CpNoPreserveSymlink,
+		Description: "run validations " + outputFilePath.Base(),
+		Output:      validatedOutputFilePath,
+		Input:       outputFilePath,
+		Validations: validations,
+	})
+	return validatedOutputFilePath
+}
diff --git a/android/bazel_handler.go b/android/bazel_handler.go
index 8d45041..9ff6b52 100644
--- a/android/bazel_handler.go
+++ b/android/bazel_handler.go
@@ -29,6 +29,7 @@
 	"android/soong/android/allowlists"
 	"android/soong/bazel/cquery"
 	"android/soong/shared"
+	"android/soong/starlark_fmt"
 
 	"github.com/google/blueprint"
 
@@ -43,6 +44,27 @@
 		Description: "",
 		CommandDeps: []string{"${bazelBuildRunfilesTool}"},
 	}, "outDir")
+	allowedBazelEnvironmentVars = []string{
+		"ALLOW_LOCAL_TIDY_TRUE",
+		"DEFAULT_TIDY_HEADER_DIRS",
+		"TIDY_TIMEOUT",
+		"WITH_TIDY",
+		"WITH_TIDY_FLAGS",
+		"SKIP_ABI_CHECKS",
+		"UNSAFE_DISABLE_APEX_ALLOWED_DEPS_CHECK",
+		"AUTO_ZERO_INITIALIZE",
+		"AUTO_PATTERN_INITIALIZE",
+		"AUTO_UNINITIALIZE",
+		"USE_CCACHE",
+		"LLVM_NEXT",
+		"ALLOW_UNKNOWN_WARNING_OPTION",
+
+		// Overrides the version in the apex_manifest.json. The version is unique for
+		// each branch (internal, aosp, mainline releases, dessert releases).  This
+		// enables modules built on an older branch to be installed against a newer
+		// device for development purposes.
+		"OVERRIDE_APEX_MANIFEST_DEFAULT_VERSION",
+	}
 )
 
 func init() {
@@ -165,7 +187,7 @@
 }
 
 type bazelRunner interface {
-	createBazelCommand(paths *bazelPaths, runName bazel.RunName, command bazelCommand, extraFlags ...string) *exec.Cmd
+	createBazelCommand(config Config, paths *bazelPaths, runName bazel.RunName, command bazelCommand, extraFlags ...string) *exec.Cmd
 	issueBazelCommand(bazelCmd *exec.Cmd) (output string, errorMessage string, error error)
 }
 
@@ -183,9 +205,11 @@
 // and their results after the requests have been made.
 type mixedBuildBazelContext struct {
 	bazelRunner
-	paths        *bazelPaths
-	requests     map[cqueryKey]bool // cquery requests that have not yet been issued to Bazel
-	requestMutex sync.Mutex         // requests can be written in parallel
+	paths *bazelPaths
+	// cquery requests that have not yet been issued to Bazel. This list is maintained
+	// in a sorted state, and is guaranteed to have no duplicates.
+	requests     []cqueryKey
+	requestMutex sync.Mutex // requests can be written in parallel
 
 	results map[cqueryKey]string // Results of cquery requests after Bazel invocations
 
@@ -233,27 +257,42 @@
 }
 
 func (m MockBazelContext) GetOutputFiles(label string, _ configKey) ([]string, error) {
-	result, _ := m.LabelToOutputFiles[label]
+	result, ok := m.LabelToOutputFiles[label]
+	if !ok {
+		return []string{}, fmt.Errorf("no target with label %q in LabelToOutputFiles", label)
+	}
 	return result, nil
 }
 
 func (m MockBazelContext) GetCcInfo(label string, _ configKey) (cquery.CcInfo, error) {
-	result, _ := m.LabelToCcInfo[label]
+	result, ok := m.LabelToCcInfo[label]
+	if !ok {
+		return cquery.CcInfo{}, fmt.Errorf("no target with label %q in LabelToCcInfo", label)
+	}
 	return result, nil
 }
 
 func (m MockBazelContext) GetPythonBinary(label string, _ configKey) (string, error) {
-	result, _ := m.LabelToPythonBinary[label]
+	result, ok := m.LabelToPythonBinary[label]
+	if !ok {
+		return "", fmt.Errorf("no target with label %q in LabelToPythonBinary", label)
+	}
 	return result, nil
 }
 
 func (m MockBazelContext) GetApexInfo(label string, _ configKey) (cquery.ApexInfo, error) {
-	result, _ := m.LabelToApexInfo[label]
+	result, ok := m.LabelToApexInfo[label]
+	if !ok {
+		return cquery.ApexInfo{}, fmt.Errorf("no target with label %q in LabelToApexInfo", label)
+	}
 	return result, nil
 }
 
 func (m MockBazelContext) GetCcUnstrippedInfo(label string, _ configKey) (cquery.CcUnstrippedInfo, error) {
-	result, _ := m.LabelToCcBinary[label]
+	result, ok := m.LabelToCcBinary[label]
+	if !ok {
+		return cquery.CcUnstrippedInfo{}, fmt.Errorf("no target with label %q in LabelToCcBinary", label)
+	}
 	return result, nil
 }
 
@@ -281,7 +320,29 @@
 	key := makeCqueryKey(label, requestType, cfgKey)
 	bazelCtx.requestMutex.Lock()
 	defer bazelCtx.requestMutex.Unlock()
-	bazelCtx.requests[key] = true
+
+	// Insert key into requests, maintaining the sort, and only if it's not duplicate.
+	keyString := key.String()
+	foundEqual := false
+	notLessThanKeyString := func(i int) bool {
+		s := bazelCtx.requests[i].String()
+		v := strings.Compare(s, keyString)
+		if v == 0 {
+			foundEqual = true
+		}
+		return v >= 0
+	}
+	targetIndex := sort.Search(len(bazelCtx.requests), notLessThanKeyString)
+	if foundEqual {
+		return
+	}
+
+	if targetIndex == len(bazelCtx.requests) {
+		bazelCtx.requests = append(bazelCtx.requests, key)
+	} else {
+		bazelCtx.requests = append(bazelCtx.requests[:targetIndex+1], bazelCtx.requests[targetIndex:]...)
+		bazelCtx.requests[targetIndex] = key
+	}
 }
 
 func (bazelCtx *mixedBuildBazelContext) GetOutputFiles(label string, cfgKey configKey) ([]string, error) {
@@ -472,7 +533,6 @@
 	return &mixedBuildBazelContext{
 		bazelRunner:           &builtinBazelRunner{},
 		paths:                 &paths,
-		requests:              make(map[cqueryKey]bool),
 		modulesDefaultToBazel: c.BuildMode == BazelDevMode,
 		bazelEnabledModules:   enabledModules,
 		bazelDisabledModules:  disabledModules,
@@ -520,7 +580,7 @@
 	extraFlags []string
 }
 
-func (r *mockBazelRunner) createBazelCommand(_ *bazelPaths, _ bazel.RunName,
+func (r *mockBazelRunner) createBazelCommand(_ Config, _ *bazelPaths, _ bazel.RunName,
 	command bazelCommand, extraFlags ...string) *exec.Cmd {
 	r.commands = append(r.commands, command)
 	r.extraFlags = append(r.extraFlags, strings.Join(extraFlags, " "))
@@ -557,7 +617,7 @@
 	}
 }
 
-func (r *builtinBazelRunner) createBazelCommand(paths *bazelPaths, runName bazel.RunName, command bazelCommand,
+func (r *builtinBazelRunner) createBazelCommand(config Config, paths *bazelPaths, runName bazel.RunName, command bazelCommand,
 	extraFlags ...string) *exec.Cmd {
 	cmdFlags := []string{
 		"--output_base=" + absolutePath(paths.outputBase),
@@ -601,6 +661,13 @@
 		// explicitly in BUILD files.
 		"BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1",
 	}
+	for _, envvar := range allowedBazelEnvironmentVars {
+		val := config.Getenv(envvar)
+		if val == "" {
+			continue
+		}
+		extraEnv = append(extraEnv, fmt.Sprintf("%s=%s", envvar, val))
+	}
 	bazelCmd.Env = append(os.Environ(), extraEnv...)
 
 	return bazelCmd
@@ -712,14 +779,23 @@
 	configNodesSection := ""
 
 	labelsByConfig := map[string][]string{}
-	for val := range context.requests {
+
+	for _, val := range context.requests {
 		labelString := fmt.Sprintf("\"@%s\"", val.label)
 		configString := getConfigString(val)
 		labelsByConfig[configString] = append(labelsByConfig[configString], labelString)
 	}
 
+	// Configs need to be sorted to maintain determinism of the BUILD file.
+	sortedConfigs := make([]string, 0, len(labelsByConfig))
+	for val := range labelsByConfig {
+		sortedConfigs = append(sortedConfigs, val)
+	}
+	sort.Slice(sortedConfigs, func(i, j int) bool { return sortedConfigs[i] < sortedConfigs[j] })
+
 	allLabels := []string{}
-	for configString, labels := range labelsByConfig {
+	for _, configString := range sortedConfigs {
+		labels := labelsByConfig[configString]
 		configTokens := strings.Split(configString, "|")
 		if len(configTokens) != 2 {
 			panic(fmt.Errorf("Unexpected config string format: %s", configString))
@@ -750,7 +826,7 @@
 // request type.
 func (context *mixedBuildBazelContext) cqueryStarlarkFileContents() []byte {
 	requestTypeToCqueryIdEntries := map[cqueryRequest][]string{}
-	for val := range context.requests {
+	for _, val := range context.requests {
 		cqueryId := getCqueryId(val)
 		mapEntryString := fmt.Sprintf("%q : True", cqueryId)
 		requestTypeToCqueryIdEntries[val.requestType] =
@@ -918,22 +994,22 @@
 		}
 	}
 	context.results = make(map[cqueryKey]string)
-	if err := context.runCquery(ctx); err != nil {
+	if err := context.runCquery(config, ctx); err != nil {
 		return err
 	}
 	if err := context.runAquery(config, ctx); err != nil {
 		return err
 	}
-	if err := context.generateBazelSymlinks(ctx); err != nil {
+	if err := context.generateBazelSymlinks(config, ctx); err != nil {
 		return err
 	}
 
 	// Clear requests.
-	context.requests = map[cqueryKey]bool{}
+	context.requests = []cqueryKey{}
 	return nil
 }
 
-func (context *mixedBuildBazelContext) runCquery(ctx *Context) error {
+func (context *mixedBuildBazelContext) runCquery(config Config, ctx *Context) error {
 	if ctx != nil {
 		ctx.EventHandler.Begin("cquery")
 		defer ctx.EventHandler.End("cquery")
@@ -946,21 +1022,21 @@
 			return err
 		}
 	}
-	if err := os.WriteFile(filepath.Join(soongInjectionPath, "WORKSPACE.bazel"), []byte{}, 0666); err != nil {
+	if err := writeFileBytesIfChanged(filepath.Join(soongInjectionPath, "WORKSPACE.bazel"), []byte{}, 0666); err != nil {
 		return err
 	}
-	if err := os.WriteFile(filepath.Join(mixedBuildsPath, "main.bzl"), context.mainBzlFileContents(), 0666); err != nil {
+	if err := writeFileBytesIfChanged(filepath.Join(mixedBuildsPath, "main.bzl"), context.mainBzlFileContents(), 0666); err != nil {
 		return err
 	}
-	if err := os.WriteFile(filepath.Join(mixedBuildsPath, "BUILD.bazel"), context.mainBuildFileContents(), 0666); err != nil {
+	if err := writeFileBytesIfChanged(filepath.Join(mixedBuildsPath, "BUILD.bazel"), context.mainBuildFileContents(), 0666); err != nil {
 		return err
 	}
 	cqueryFileRelpath := filepath.Join(context.paths.injectedFilesDir(), "buildroot.cquery")
-	if err := os.WriteFile(absolutePath(cqueryFileRelpath), context.cqueryStarlarkFileContents(), 0666); err != nil {
+	if err := writeFileBytesIfChanged(absolutePath(cqueryFileRelpath), context.cqueryStarlarkFileContents(), 0666); err != nil {
 		return err
 	}
 
-	cqueryCommandWithFlag := context.createBazelCommand(context.paths, bazel.CqueryBuildRootRunName, cqueryCmd,
+	cqueryCommandWithFlag := context.createBazelCommand(config, context.paths, bazel.CqueryBuildRootRunName, cqueryCmd,
 		"--output=starlark", "--starlark:file="+absolutePath(cqueryFileRelpath))
 	cqueryOutput, cqueryErrorMessage, cqueryErr := context.issueBazelCommand(cqueryCommandWithFlag)
 	if cqueryErr != nil {
@@ -977,7 +1053,7 @@
 			cqueryResults[splitLine[0]] = splitLine[1]
 		}
 	}
-	for val := range context.requests {
+	for _, val := range context.requests {
 		if cqueryResult, ok := cqueryResults[getCqueryId(val)]; ok {
 			context.results[val] = cqueryResult
 		} else {
@@ -988,6 +1064,14 @@
 	return nil
 }
 
+func writeFileBytesIfChanged(path string, contents []byte, perm os.FileMode) error {
+	oldContents, err := os.ReadFile(path)
+	if err != nil || !bytes.Equal(contents, oldContents) {
+		err = os.WriteFile(path, contents, perm)
+	}
+	return nil
+}
+
 func (context *mixedBuildBazelContext) runAquery(config Config, ctx *Context) error {
 	if ctx != nil {
 		ctx.EventHandler.Begin("aquery")
@@ -1017,7 +1101,7 @@
 			extraFlags = append(extraFlags, "--instrumentation_filter="+strings.Join(paths, ","))
 		}
 	}
-	aqueryOutput, _, err := context.issueBazelCommand(context.createBazelCommand(context.paths, bazel.AqueryBuildRootRunName, aqueryCmd,
+	aqueryOutput, _, err := context.issueBazelCommand(context.createBazelCommand(config, context.paths, bazel.AqueryBuildRootRunName, aqueryCmd,
 		extraFlags...))
 	if err != nil {
 		return err
@@ -1026,7 +1110,7 @@
 	return err
 }
 
-func (context *mixedBuildBazelContext) generateBazelSymlinks(ctx *Context) error {
+func (context *mixedBuildBazelContext) generateBazelSymlinks(config Config, ctx *Context) error {
 	if ctx != nil {
 		ctx.EventHandler.Begin("symlinks")
 		defer ctx.EventHandler.End("symlinks")
@@ -1034,7 +1118,7 @@
 	// Issue a build command of the phony root to generate symlink forests for dependencies of the
 	// Bazel build. This is necessary because aquery invocations do not generate this symlink forest,
 	// but some of symlinks may be required to resolve source dependencies of the build.
-	_, _, err := context.issueBazelCommand(context.createBazelCommand(context.paths, bazel.BazelBuildPhonyRootRunName, buildCmd))
+	_, _, err := context.issueBazelCommand(context.createBazelCommand(config, context.paths, bazel.BazelBuildPhonyRootRunName, buildCmd))
 	return err
 }
 
@@ -1120,10 +1204,11 @@
 		// the 'bazel' package, which cannot depend on 'android' package where ctx is defined,
 		// because this would cause circular dependency. So, until we move aquery processing
 		// to the 'android' package, we need to handle special cases here.
-		if buildStatement.Mnemonic == "FileWrite" || buildStatement.Mnemonic == "SourceSymlinkManifest" {
+		switch buildStatement.Mnemonic {
+		case "FileWrite", "SourceSymlinkManifest":
 			out := PathForBazelOut(ctx, buildStatement.OutputPaths[0])
 			WriteFileRuleVerbatim(ctx, out, buildStatement.FileContents)
-		} else if buildStatement.Mnemonic == "SymlinkTree" {
+		case "SymlinkTree":
 			// build-runfiles arguments are the manifest file and the target directory
 			// where it creates the symlink tree according to this manifest (and then
 			// writes the MANIFEST file to it).
@@ -1142,7 +1227,7 @@
 					"outDir": outDir,
 				},
 			})
-		} else {
+		default:
 			panic(fmt.Sprintf("unhandled build statement: %v", buildStatement))
 		}
 	}
@@ -1243,3 +1328,13 @@
 func bazelDepsetName(contentHash string) string {
 	return fmt.Sprintf("bazel_depset_%s", contentHash)
 }
+
+func EnvironmentVarsFile(config Config) string {
+	return fmt.Sprintf(bazel.GeneratedBazelFileWarning+`
+_env = %s
+
+env = _env
+`,
+		starlark_fmt.PrintStringList(allowedBazelEnvironmentVars, 0),
+	)
+}
diff --git a/android/bazel_handler_test.go b/android/bazel_handler_test.go
index 013e19c..d971802 100644
--- a/android/bazel_handler_test.go
+++ b/android/bazel_handler_test.go
@@ -168,6 +168,32 @@
 	}
 }
 
+func TestBazelRequestsSorted(t *testing.T) {
+	bazelContext, _ := testBazelContext(t, map[bazelCommand]string{})
+
+	bazelContext.QueueBazelRequest("zzz", cquery.GetOutputFiles, configKey{"arm64_armv8-a", Android})
+	bazelContext.QueueBazelRequest("ccc", cquery.GetApexInfo, configKey{"arm64_armv8-a", Android})
+	bazelContext.QueueBazelRequest("duplicate", cquery.GetOutputFiles, configKey{"arm64_armv8-a", Android})
+	bazelContext.QueueBazelRequest("duplicate", cquery.GetOutputFiles, configKey{"arm64_armv8-a", Android})
+	bazelContext.QueueBazelRequest("xxx", cquery.GetOutputFiles, configKey{"arm64_armv8-a", Linux})
+	bazelContext.QueueBazelRequest("aaa", cquery.GetOutputFiles, configKey{"arm64_armv8-a", Android})
+	bazelContext.QueueBazelRequest("aaa", cquery.GetOutputFiles, configKey{"otherarch", Android})
+	bazelContext.QueueBazelRequest("bbb", cquery.GetOutputFiles, configKey{"otherarch", Android})
+
+	if len(bazelContext.requests) != 7 {
+		t.Error("Expected 7 request elements, but got", len(bazelContext.requests))
+	}
+
+	lastString := ""
+	for _, val := range bazelContext.requests {
+		thisString := val.String()
+		if thisString <= lastString {
+			t.Errorf("Requests are not ordered correctly. '%s' came before '%s'", lastString, thisString)
+		}
+		lastString = thisString
+	}
+}
+
 func verifyExtraFlags(t *testing.T, config Config, expected string) string {
 	bazelContext, _ := testBazelContext(t, map[bazelCommand]string{})
 
@@ -204,7 +230,6 @@
 	return &mixedBuildBazelContext{
 		bazelRunner: runner,
 		paths:       &p,
-		requests:    map[cqueryKey]bool{},
 	}, p.soongOutDir
 }
 
diff --git a/android/bazel_paths.go b/android/bazel_paths.go
index e151521..bad7baf 100644
--- a/android/bazel_paths.go
+++ b/android/bazel_paths.go
@@ -461,11 +461,6 @@
 	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 f30c154..bb3cc97 100644
--- a/android/config.go
+++ b/android/config.go
@@ -523,27 +523,33 @@
 		config.AndroidFirstDeviceTarget = FirstTarget(config.Targets[Android], "lib64", "lib32")[0]
 	}
 
-	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
+	setBuildMode := func(arg string, mode SoongBuildMode) {
+		if arg != "" {
+			if config.BuildMode != AnalysisNoBazel {
+				fmt.Fprintf(os.Stderr, "buildMode is already set, illegal argument: %s", arg)
+				os.Exit(1)
+			}
+			config.BuildMode = mode
+		}
 	}
+	setBazelMode := func(arg bool, argName string, mode SoongBuildMode) {
+		if arg {
+			if config.BuildMode != AnalysisNoBazel {
+				fmt.Fprintf(os.Stderr, "buildMode is already set, illegal argument: %s", argName)
+				os.Exit(1)
+			}
+			config.BuildMode = mode
+		}
+	}
+	setBuildMode(cmdArgs.SymlinkForestMarker, SymlinkForest)
+	setBuildMode(cmdArgs.Bp2buildMarker, Bp2build)
+	setBuildMode(cmdArgs.BazelQueryViewDir, GenerateQueryView)
+	setBuildMode(cmdArgs.BazelApiBp2buildDir, ApiBp2build)
+	setBuildMode(cmdArgs.ModuleGraphFile, GenerateModuleGraph)
+	setBuildMode(cmdArgs.DocFile, GenerateDocFile)
+	setBazelMode(cmdArgs.BazelModeDev, "--bazel-mode-dev", BazelDevMode)
+	setBazelMode(cmdArgs.BazelMode, "--bazel-mode", BazelProdMode)
+	setBazelMode(cmdArgs.BazelModeStaging, "--bazel-mode-staging", BazelStagingMode)
 
 	config.BazelContext, err = NewBazelContext(config)
 	config.Bp2buildPackageConfig = GetBp2BuildAllowList()
@@ -721,10 +727,6 @@
 	return value == "0" || value == "n" || value == "no" || value == "off" || value == "false"
 }
 
-func (c *config) TargetsJava17() bool {
-	return c.IsEnvTrue("EXPERIMENTAL_TARGET_JAVA_VERSION_17")
-}
-
 // EnvDeps returns the environment variables this build depends on. The first
 // call to this function blocks future reads from the environment.
 func (c *config) EnvDeps() map[string]string {
@@ -1496,6 +1498,10 @@
 	return Bool(c.productVariables.CompressedApex) && !c.UnbundledBuildApps()
 }
 
+func (c *config) ApexTrimEnabled() bool {
+	return Bool(c.productVariables.TrimmedApex)
+}
+
 func (c *config) EnforceSystemCertificate() bool {
 	return Bool(c.productVariables.EnforceSystemCertificate)
 }
diff --git a/android/defaults.go b/android/defaults.go
index 7906e94..925eafc 100644
--- a/android/defaults.go
+++ b/android/defaults.go
@@ -55,7 +55,7 @@
 	d.hook = hook
 }
 
-func (d *DefaultableModuleBase) callHookIfAvailable(ctx DefaultableHookContext) {
+func (d *DefaultableModuleBase) CallHookIfAvailable(ctx DefaultableHookContext) {
 	if d.hook != nil {
 		d.hook(ctx)
 	}
@@ -82,7 +82,7 @@
 	SetDefaultableHook(hook DefaultableHook)
 
 	// Call the hook if specified.
-	callHookIfAvailable(context DefaultableHookContext)
+	CallHookIfAvailable(context DefaultableHookContext)
 }
 
 type DefaultableModule interface {
@@ -630,6 +630,6 @@
 			defaultable.applyDefaults(ctx, defaultsList)
 		}
 
-		defaultable.callHookIfAvailable(ctx)
+		defaultable.CallHookIfAvailable(ctx)
 	}
 }
diff --git a/android/defs.go b/android/defs.go
index 6e5bb05..18eed2d 100644
--- a/android/defs.go
+++ b/android/defs.go
@@ -58,6 +58,14 @@
 		},
 		"cpFlags", "extraCmds")
 
+	// A copy rule that doesn't preserve symlinks.
+	CpNoPreserveSymlink = pctx.AndroidStaticRule("CpNoPreserveSymlink",
+		blueprint.RuleParams{
+			Command:     "rm -f $out && cp $cpFlags $in $out$extraCmds",
+			Description: "cp $out",
+		},
+		"cpFlags", "extraCmds")
+
 	// A copy rule that only updates the output if it changed.
 	CpIfChanged = pctx.AndroidStaticRule("CpIfChanged",
 		blueprint.RuleParams{
diff --git a/android/paths.go b/android/paths.go
index 0fc39df..6c3009f 100644
--- a/android/paths.go
+++ b/android/paths.go
@@ -16,7 +16,6 @@
 
 import (
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"reflect"
@@ -1868,10 +1867,14 @@
 	return ret
 }
 
-// validateSafePath validates a path that we trust (may contain ninja variables).
-// Ensures that each path component does not attempt to leave its component.
-func validateSafePath(pathComponents ...string) (string, error) {
+// validatePathInternal ensures that a path does not leave its component, and
+// optionally doesn't contain Ninja variables.
+func validatePathInternal(allowNinjaVariables bool, pathComponents ...string) (string, error) {
 	for _, path := range pathComponents {
+		if !allowNinjaVariables && strings.Contains(path, "$") {
+			return "", fmt.Errorf("Path contains invalid character($): %s", path)
+		}
+
 		path := filepath.Clean(path)
 		if path == ".." || strings.HasPrefix(path, "../") || strings.HasPrefix(path, "/") {
 			return "", fmt.Errorf("Path is outside directory: %s", path)
@@ -1883,16 +1886,18 @@
 	return filepath.Join(pathComponents...), nil
 }
 
+// validateSafePath validates a path that we trust (may contain ninja
+// variables).  Ensures that each path component does not attempt to leave its
+// component. Returns a joined version of each path component.
+func validateSafePath(pathComponents ...string) (string, error) {
+	return validatePathInternal(true, pathComponents...)
+}
+
 // validatePath validates that a path does not include ninja variables, and that
 // each path component does not attempt to leave its component. Returns a joined
 // version of each path component.
 func validatePath(pathComponents ...string) (string, error) {
-	for _, path := range pathComponents {
-		if strings.Contains(path, "$") {
-			return "", fmt.Errorf("Path contains invalid character($): %s", path)
-		}
-	}
-	return validateSafePath(pathComponents...)
+	return validatePathInternal(false, pathComponents...)
 }
 
 func PathForPhony(ctx PathContext, phony string) WritablePath {
@@ -2093,13 +2098,16 @@
 
 // Writes a file to the output directory.  Attempting to write directly to the output directory
 // will fail due to the sandbox of the soong_build process.
+// Only writes the file if the file doesn't exist or if it has different contents, to prevent
+// updating the timestamp if no changes would be made. (This is better for incremental
+// performance.)
 func WriteFileToOutputDir(path WritablePath, data []byte, perm os.FileMode) error {
 	absPath := absolutePath(path.String())
 	err := os.MkdirAll(filepath.Dir(absPath), 0777)
 	if err != nil {
 		return err
 	}
-	return ioutil.WriteFile(absPath, data, perm)
+	return pathtools.WriteFileIfChanged(absPath, data, perm)
 }
 
 func RemoveAllOutputDir(path WritablePath) error {
diff --git a/android/testing.go b/android/testing.go
index 29af71f..fc39a9c 100644
--- a/android/testing.go
+++ b/android/testing.go
@@ -1125,6 +1125,11 @@
 	config.katiEnabled = true
 }
 
+func SetTrimmedApexEnabledForTests(config Config) {
+	config.productVariables.TrimmedApex = new(bool)
+	*config.productVariables.TrimmedApex = true
+}
+
 func AndroidMkEntriesForTest(t *testing.T, ctx *TestContext, mod blueprint.Module) []AndroidMkEntries {
 	t.Helper()
 	var p AndroidMkEntriesProvider
@@ -1145,7 +1150,7 @@
 	var p AndroidMkDataProvider
 	var ok bool
 	if p, ok = mod.(AndroidMkDataProvider); !ok {
-		t.Errorf("module does not implement AndroidMkDataProvider: " + mod.Name())
+		t.Fatalf("module does not implement AndroidMkDataProvider: " + mod.Name())
 	}
 	data := p.AndroidMk()
 	data.fillInData(ctx, mod)
diff --git a/android/util.go b/android/util.go
index a0f7160..947af69 100644
--- a/android/util.go
+++ b/android/util.go
@@ -136,6 +136,38 @@
 	return IndexList(s, list) != -1
 }
 
+func setFromList[T comparable](l []T) map[T]bool {
+	m := make(map[T]bool, len(l))
+	for _, t := range l {
+		m[t] = true
+	}
+	return m
+}
+
+// ListSetDifference checks if the two lists contain the same elements. It returns
+// a boolean which is true if there is a difference, and then returns lists of elements
+// that are in l1 but not l2, and l2 but not l1.
+func ListSetDifference[T comparable](l1, l2 []T) (bool, []T, []T) {
+	listsDiffer := false
+	diff1 := []T{}
+	diff2 := []T{}
+	m1 := setFromList(l1)
+	m2 := setFromList(l2)
+	for t := range m1 {
+		if _, ok := m2[t]; !ok {
+			diff1 = append(diff1, t)
+			listsDiffer = true
+		}
+	}
+	for t := range m2 {
+		if _, ok := m1[t]; !ok {
+			diff2 = append(diff2, t)
+			listsDiffer = true
+		}
+	}
+	return listsDiffer, diff1, diff2
+}
+
 // Returns true if the given string s is prefixed with any string in the given prefix list.
 func HasAnyPrefix(s string, prefixList []string) bool {
 	for _, prefix := range prefixList {
diff --git a/android/variable.go b/android/variable.go
index e838b7c..e714fc4 100644
--- a/android/variable.go
+++ b/android/variable.go
@@ -380,6 +380,7 @@
 
 	Ndk_abis *bool `json:",omitempty"`
 
+	TrimmedApex                  *bool `json:",omitempty"`
 	Flatten_apex                 *bool `json:",omitempty"`
 	ForceApexSymlinkOptimization *bool `json:",omitempty"`
 	CompressedApex               *bool `json:",omitempty"`
@@ -502,6 +503,7 @@
 		Malloc_zero_contents:         boolPtr(true),
 		Malloc_pattern_fill_contents: boolPtr(false),
 		Safestack:                    boolPtr(false),
+		TrimmedApex:                  boolPtr(false),
 
 		BootJars:     ConfiguredJarList{apexes: []string{}, jars: []string{}},
 		ApexBootJars: ConfiguredJarList{apexes: []string{}, jars: []string{}},
diff --git a/apex/androidmk.go b/apex/androidmk.go
index aadccb7..7f03621 100644
--- a/apex/androidmk.go
+++ b/apex/androidmk.go
@@ -23,8 +23,7 @@
 	"android/soong/android"
 	"android/soong/cc"
 	"android/soong/java"
-
-	"github.com/google/blueprint/proptools"
+	"android/soong/rust"
 )
 
 func (a *apexBundle) AndroidMk() android.AndroidMkData {
@@ -73,12 +72,15 @@
 	return fi.androidMkModuleName + "." + apexBundleName + a.suffix
 }
 
-func (a *apexBundle) androidMkForFiles(w io.Writer, apexBundleName, apexName, moduleDir string,
+func (a *apexBundle) androidMkForFiles(w io.Writer, apexBundleName, moduleDir string,
 	apexAndroidMkData android.AndroidMkData) []string {
 
-	// apexBundleName comes from the 'name' property; apexName comes from 'apex_name' property.
+	// apexBundleName comes from the 'name' property or soong module.
+	// apexName comes from 'name' property of apex_manifest.
 	// An apex is installed to /system/apex/<apexBundleName> and is activated at /apex/<apexName>
 	// In many cases, the two names are the same, but could be different in general.
+	// However, symbol files for apex files are installed under /apex/<apexBundleName> to avoid
+	// conflicts between two apexes with the same apexName.
 
 	moduleNames := []string{}
 	apexType := a.properties.ApexType
@@ -89,25 +91,6 @@
 		return moduleNames
 	}
 
-	// b/162366062. Prevent GKI APEXes to emit make rules to avoid conflicts.
-	if strings.HasPrefix(apexName, "com.android.gki.") && apexType != flattenedApex {
-		return moduleNames
-	}
-
-	// b/140136207. When there are overriding APEXes for a VNDK APEX, the symbols file for the overridden
-	// APEX and the overriding APEX will have the same installation paths at /apex/com.android.vndk.v<ver>
-	// as their apexName will be the same. To avoid the path conflicts, skip installing the symbol files
-	// for the overriding VNDK APEXes.
-	symbolFilesNotNeeded := a.vndkApex && len(a.overridableProperties.Overrides) > 0
-	if symbolFilesNotNeeded && apexType != flattenedApex {
-		return moduleNames
-	}
-
-	// Avoid creating duplicate build rules for multi-installed APEXes.
-	if proptools.BoolDefault(a.properties.Multi_install_skip_symbol_files, false) {
-		return moduleNames
-	}
-
 	seenDataOutPaths := make(map[string]bool)
 
 	for _, fi := range a.filesInfo {
@@ -144,15 +127,15 @@
 		if fi.module != nil && fi.module.Owner() != "" {
 			fmt.Fprintln(w, "LOCAL_MODULE_OWNER :=", fi.module.Owner())
 		}
-		// /apex/<apex_name>/{lib|framework|...}
-		pathWhenActivated := filepath.Join("$(PRODUCT_OUT)", "apex", apexName, fi.installDir)
+		// /apex/<apexBundleName>/{lib|framework|...}
+		pathForSymbol := filepath.Join("$(PRODUCT_OUT)", "apex", apexBundleName, fi.installDir)
 		var modulePath string
 		if apexType == flattenedApex {
-			// /system/apex/<name>/{lib|framework|...}
+			// /system/apex/<apexBundleName>/{lib|framework|...}
 			modulePath = filepath.Join(a.installDir.String(), apexBundleName, fi.installDir)
 			fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", modulePath)
-			if a.primaryApexType && !symbolFilesNotNeeded {
-				fmt.Fprintln(w, "LOCAL_SOONG_SYMBOL_PATH :=", pathWhenActivated)
+			if a.primaryApexType {
+				fmt.Fprintln(w, "LOCAL_SOONG_SYMBOL_PATH :=", pathForSymbol)
 			}
 			android.AndroidMkEmitAssignList(w, "LOCAL_MODULE_SYMLINKS", fi.symlinks)
 			newDataPaths := []android.DataPath{}
@@ -165,8 +148,8 @@
 			}
 			android.AndroidMkEmitAssignList(w, "LOCAL_TEST_DATA", android.AndroidMkDataPaths(newDataPaths))
 		} else {
-			modulePath = pathWhenActivated
-			fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", pathWhenActivated)
+			modulePath = pathForSymbol
+			fmt.Fprintln(w, "LOCAL_MODULE_PATH :=", modulePath)
 
 			// For non-flattend APEXes, the merged notice file is attached to the APEX itself.
 			// We don't need to have notice file for the individual modules in it. Otherwise,
@@ -256,6 +239,10 @@
 				if ccMod.CoverageOutputFile().Valid() {
 					fmt.Fprintln(w, "LOCAL_PREBUILT_COVERAGE_ARCHIVE :=", ccMod.CoverageOutputFile().String())
 				}
+			} else if rustMod, ok := fi.module.(*rust.Module); ok {
+				if rustMod.UnstrippedOutputFile() != nil {
+					fmt.Fprintln(w, "LOCAL_SOONG_UNSTRIPPED_BINARY :=", rustMod.UnstrippedOutputFile().String())
+				}
 			}
 			fmt.Fprintln(w, "include $(BUILD_SYSTEM)/soong_cc_rust_prebuilt.mk")
 		default:
@@ -309,7 +296,7 @@
 		targetRequired = append(targetRequired, fi.targetRequiredModuleNames...)
 		hostRequired = append(hostRequired, fi.hostRequiredModuleNames...)
 	}
-	android.AndroidMkEmitAssignList(w, "LOCAL_REQUIRED_MODULES", moduleNames, a.requiredDeps, required)
+	android.AndroidMkEmitAssignList(w, "LOCAL_REQUIRED_MODULES", moduleNames, a.makeModulesToInstall, required)
 	android.AndroidMkEmitAssignList(w, "LOCAL_TARGET_REQUIRED_MODULES", targetRequired)
 	android.AndroidMkEmitAssignList(w, "LOCAL_HOST_REQUIRED_MODULES", hostRequired)
 }
@@ -317,14 +304,13 @@
 func (a *apexBundle) androidMkForType() android.AndroidMkData {
 	return android.AndroidMkData{
 		Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
+			moduleNames := []string{}
 			apexType := a.properties.ApexType
+			if a.installable() {
+				moduleNames = a.androidMkForFiles(w, name, moduleDir, data)
+			}
 
 			if apexType == flattenedApex {
-				var moduleNames []string = nil
-				if a.installable() {
-					apexName := proptools.StringDefault(a.properties.Apex_name, name)
-					moduleNames = a.androidMkForFiles(w, name, apexName, moduleDir, data)
-				}
 				// Only image APEXes can be flattened.
 				fmt.Fprintln(w, "\ninclude $(CLEAR_VARS)  # apex.apexBundle.flat")
 				fmt.Fprintln(w, "LOCAL_PATH :=", moduleDir)
@@ -366,7 +352,7 @@
 				}
 
 				android.AndroidMkEmitAssignList(w, "LOCAL_OVERRIDES_MODULES", a.overridableProperties.Overrides)
-				a.writeRequiredModules(w, nil)
+				a.writeRequiredModules(w, moduleNames)
 
 				fmt.Fprintln(w, "include $(BUILD_PREBUILT)")
 
diff --git a/apex/apex.go b/apex/apex.go
index 9485a4b..ff38773 100644
--- a/apex/apex.go
+++ b/apex/apex.go
@@ -80,6 +80,7 @@
 	ctx.BottomUp("apex", apexMutator).Parallel()
 	ctx.BottomUp("apex_directly_in_any", apexDirectlyInAnyMutator).Parallel()
 	ctx.BottomUp("apex_flattened", apexFlattenedMutator).Parallel()
+	ctx.BottomUp("apex_dcla_deps", apexDCLADepsMutator).Parallel()
 	// Register after apex_info mutator so that it can use ApexVariationName
 	ctx.TopDown("apex_strict_updatability_lint", apexStrictUpdatibilityLintMutator).Parallel()
 }
@@ -93,10 +94,6 @@
 	// a default one is automatically generated.
 	AndroidManifest *string `android:"path"`
 
-	// Canonical name of this APEX bundle. Used to determine the path to the activated APEX on
-	// device (/apex/<apex_name>). If unspecified, follows the name property.
-	Apex_name *string
-
 	// Determines the file contexts file for setting the security contexts to files in this APEX
 	// bundle. For platform APEXes, this should points to a file under /system/sepolicy Default:
 	// /system/sepolicy/apex/<module_name>_file_contexts.
@@ -123,10 +120,6 @@
 	// List of filesystem images that are embedded inside this APEX bundle.
 	Filesystems []string
 
-	// The minimum SDK version that this APEX must support at minimum. This is usually set to
-	// the SDK version that the APEX was first introduced.
-	Min_sdk_version *string
-
 	// Whether this APEX is considered updatable or not. When set to true, this will enforce
 	// additional rules for making sure that the APEX is truly updatable. To be updatable,
 	// min_sdk_version should be set as well. This will also disable the size optimizations like
@@ -152,16 +145,6 @@
 	// Should be only used in non-system apexes (e.g. vendor: true). Default is false.
 	Use_vndk_as_stable *bool
 
-	// Whether this is multi-installed APEX should skip installing symbol files.
-	// Multi-installed APEXes share the same apex_name and are installed at the same time.
-	// Default is false.
-	//
-	// Should be set to true for all multi-installed APEXes except the singular
-	// default version within the multi-installed group.
-	// Only the default version can install symbol files in $(PRODUCT_OUT}/apex,
-	// or else conflicting build rules may be created.
-	Multi_install_skip_symbol_files *bool
-
 	// The type of APEX to build. Controls what the APEX payload is. Either 'image', 'zip' or
 	// 'both'. When set to image, contents are stored in a filesystem image inside a zip
 	// container. When set to zip, contents are stored in a zip container directly. This type is
@@ -389,6 +372,13 @@
 	// conditions, e.g., target device needs to support APEX compression, are also fulfilled.
 	// Default: false.
 	Compressible *bool
+
+	// Trim against a specific Dynamic Common Lib APEX
+	Trim_against *string
+
+	// The minimum SDK version that this APEX must support at minimum. This is usually set to
+	// the SDK version that the APEX was first introduced.
+	Min_sdk_version *string
 }
 
 type apexBundle struct {
@@ -439,8 +429,8 @@
 	// GenerateAndroidBuildActions.
 	filesInfo []apexFile
 
-	// List of other module names that should be installed when this APEX gets installed.
-	requiredDeps []string
+	// List of other module names that should be installed when this APEX gets installed (LOCAL_REQUIRED_MODULES).
+	makeModulesToInstall []string
 
 	///////////////////////////////////////////////////////////////////////////////////////////
 	// Outputs (final and intermediates)
@@ -488,8 +478,6 @@
 	// Optional list of lint report zip files for apexes that contain java or app modules
 	lintReports android.Paths
 
-	prebuiltFileToDelete string
-
 	isCompressed bool
 
 	// Path of API coverage generate file
@@ -675,6 +663,7 @@
 	androidAppTag   = &dependencyTag{name: "androidApp", payload: true}
 	bpfTag          = &dependencyTag{name: "bpf", payload: true}
 	certificateTag  = &dependencyTag{name: "certificate"}
+	dclaTag         = &dependencyTag{name: "dcla"}
 	executableTag   = &dependencyTag{name: "executable", payload: true}
 	fsTag           = &dependencyTag{name: "filesystem", payload: true}
 	bcpfTag         = &dependencyTag{name: "bootclasspathFragment", payload: true, sourceOnly: true, memberType: java.BootclasspathFragmentSdkMemberType}
@@ -908,6 +897,33 @@
 	}
 }
 
+func apexDCLADepsMutator(mctx android.BottomUpMutatorContext) {
+	if !mctx.Config().ApexTrimEnabled() {
+		return
+	}
+	if a, ok := mctx.Module().(*apexBundle); ok && a.overridableProperties.Trim_against != nil {
+		commonVariation := mctx.Config().AndroidCommonTarget.Variations()
+		mctx.AddFarVariationDependencies(commonVariation, dclaTag, String(a.overridableProperties.Trim_against))
+	} else if o, ok := mctx.Module().(*OverrideApex); ok {
+		for _, p := range o.GetProperties() {
+			properties, ok := p.(*overridableProperties)
+			if !ok {
+				continue
+			}
+			if properties.Trim_against != nil {
+				commonVariation := mctx.Config().AndroidCommonTarget.Variations()
+				mctx.AddFarVariationDependencies(commonVariation, dclaTag, String(properties.Trim_against))
+			}
+		}
+	}
+}
+
+type DCLAInfo struct {
+	ProvidedLibs []string
+}
+
+var DCLAInfoProvider = blueprint.NewMutatorProvider(DCLAInfo{}, "apex_info")
+
 type ApexBundleInfo struct {
 	Contents *android.ApexContents
 }
@@ -1017,7 +1033,7 @@
 	// This is the main part of this mutator. Mark the collected dependencies that they need to
 	// be built for this apexBundle.
 
-	apexVariationName := proptools.StringDefault(a.properties.Apex_name, mctx.ModuleName()) // could be com.android.foo
+	apexVariationName := mctx.ModuleName() // could be com.android.foo
 	a.properties.ApexVariationName = apexVariationName
 	apexInfo := android.ApexInfo{
 		ApexVariationName: apexVariationName,
@@ -1035,6 +1051,12 @@
 		child.(android.ApexModule).BuildForApex(apexInfo) // leave a mark!
 		return true
 	})
+
+	if a.dynamic_common_lib_apex() {
+		mctx.SetProvider(DCLAInfoProvider, DCLAInfo{
+			ProvidedLibs: a.properties.Native_shared_libs,
+		})
+	}
 }
 
 type ApexInfoMutator interface {
@@ -1531,6 +1553,19 @@
 	return proptools.BoolDefault(a.properties.Dynamic_common_lib_apex, false)
 }
 
+// See the list of libs to trim
+func (a *apexBundle) libs_to_trim(ctx android.ModuleContext) []string {
+	dclaModules := ctx.GetDirectDepsWithTag(dclaTag)
+	if len(dclaModules) > 1 {
+		panic(fmt.Errorf("expected exactly at most one dcla dependency, got %d", len(dclaModules)))
+	}
+	if len(dclaModules) > 0 {
+		DCLAInfo := ctx.OtherModuleProvider(dclaModules[0], DCLAInfoProvider).(DCLAInfo)
+		return DCLAInfo.ProvidedLibs
+	}
+	return []string{}
+}
+
 // These functions are interfacing with cc/sanitizer.go. The entire APEX (along with all of its
 // members) can be sanitized, either forcibly, or by the global configuration. For some of the
 // sanitizers, extra dependencies can be forcibly added as well.
@@ -1657,7 +1692,7 @@
 	return newApexFile(ctx, fileToCopy, androidMkModuleName, dirInApex, nativeSharedLib, rustm)
 }
 
-func apexFileForPyBinary(ctx android.BaseModuleContext, py *python.Module) apexFile {
+func apexFileForPyBinary(ctx android.BaseModuleContext, py *python.PythonBinaryModule) apexFile {
 	dirInApex := "bin"
 	fileToCopy := py.HostToolPath().Path()
 	return newApexFile(ctx, fileToCopy, py.BaseModuleName(), dirInApex, pyBinary, py)
@@ -1732,6 +1767,18 @@
 	return af
 }
 
+func apexFileForJavaModuleProfile(ctx android.BaseModuleContext, module javaModule) *apexFile {
+	if dexpreopter, ok := module.(java.DexpreopterInterface); ok {
+		if profilePathOnHost := dexpreopter.ProfilePathOnHost(); profilePathOnHost != nil {
+			dirInApex := "javalib"
+			af := newApexFile(ctx, profilePathOnHost, module.BaseModuleName()+"-profile", dirInApex, etc, nil)
+			af.customStem = module.Stem() + ".jar.prof"
+			return &af
+		}
+	}
+	return nil
+}
+
 // androidApp is an interface to handle all app modules (android_app, android_app_import, etc.) in
 // the same way.
 type androidApp interface {
@@ -1915,6 +1962,11 @@
 	}
 	a.outputFile = a.outputApexFile
 
+	if len(outputs.TidyFiles) > 0 {
+		tidyFiles := android.PathsForBazelOut(ctx, outputs.TidyFiles)
+		a.outputFile = android.AttachValidationActions(ctx, a.outputFile, tidyFiles)
+	}
+
 	// TODO(b/257829940): These are used by the apex_keys_text singleton; would probably be a clearer
 	// interface if these were set in a provider rather than the module itself
 	a.publicKeyFile = android.PathForBazelOut(ctx, outputs.BundleKeyInfo[0])
@@ -1922,11 +1974,9 @@
 	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))
-	}
+	// Ensure ApexMkInfo.install_to_system make module names are installed as
+	// part of a bundled build.
+	a.makeModulesToInstall = append(a.makeModulesToInstall, outputs.MakeModulesToInstall...)
 
 	apexType := a.properties.ApexType
 	switch apexType {
@@ -1944,21 +1994,17 @@
 		a.installedFile = ctx.InstallFile(a.installDir, a.Name()+installSuffix, a.outputFile,
 			a.compatSymlinks.Paths()...)
 	default:
-		panic(fmt.Errorf("unexpected apex_type for the ProcessBazelQuery: %v", a.properties.ApexType))
+		panic(fmt.Errorf("internal error: unexpected apex_type for the ProcessBazelQueryResponse: %v", a.properties.ApexType))
 	}
 
-	/*
-			TODO(asmundak): compared to building an APEX with Soong, building it with Bazel does not
-			return filesInfo and requiredDeps fields (in the Soong build the latter is updated).
-			Fix this, as these fields are subsequently used in apex/androidmk.go and in apex/builder/go
-			To find out what Soong build puts there, run:
-			vctx := visitorContext{handleSpecialLibs: !android.Bool(a.properties.Ignore_system_library_special_case)}
-			ctx.WalkDepsBlueprint(func(child, parent blueprint.Module) bool {
-		      return a.depVisitor(&vctx, ctx, child, parent)
-		    })
-			vctx.normalizeFileInfo()
-	*/
-
+	// filesInfo is not set in mixed mode, because all information about the
+	// apex's contents should completely come from the Starlark providers.
+	//
+	// Prevent accidental writes to filesInfo in the earlier parts Soong by
+	// asserting it to be nil.
+	if a.filesInfo != nil {
+		panic(fmt.Errorf("internal error: filesInfo must be nil for an apex handled by Bazel."))
+	}
 }
 
 func (a *apexBundle) setCompression(ctx android.ModuleContext) {
@@ -2025,7 +2071,7 @@
 			a.primaryApexType = true
 
 			if ctx.Config().InstallExtraFlattenedApexes() {
-				a.requiredDeps = append(a.requiredDeps, a.Name()+flattenedSuffix)
+				a.makeModulesToInstall = append(a.makeModulesToInstall, a.Name()+flattenedSuffix)
 			}
 		}
 	case zipApex:
@@ -2147,7 +2193,7 @@
 			case *cc.Module:
 				vctx.filesInfo = append(vctx.filesInfo, apexFileForExecutable(ctx, ch))
 				return true // track transitive dependencies
-			case *python.Module:
+			case *python.PythonBinaryModule:
 				if ch.HostToolPath().Valid() {
 					vctx.filesInfo = append(vctx.filesInfo, apexFileForPyBinary(ctx, ch))
 				}
@@ -2177,7 +2223,7 @@
 
 			vctx.filesInfo = append(vctx.filesInfo, apexBootclasspathFragmentFiles(ctx, child)...)
 			for _, makeModuleName := range bcpfModule.BootImageDeviceInstallMakeModules() {
-				a.requiredDeps = append(a.requiredDeps, makeModuleName)
+				a.makeModulesToInstall = append(a.makeModulesToInstall, makeModuleName)
 			}
 			return true
 		case sscpfTag:
@@ -2295,12 +2341,6 @@
 			} else {
 				ctx.ModuleErrorf("certificate dependency %q must be an android_app_certificate module", depName)
 			}
-		case android.PrebuiltDepTag:
-			// If the prebuilt is force disabled, remember to delete the prebuilt file
-			// that might have been installed in the previous builds
-			if prebuilt, ok := child.(prebuilt); ok && prebuilt.isForceDisabled() {
-				a.prebuiltFileToDelete = prebuilt.InstallFilename()
-			}
 		}
 		return false
 	}
@@ -2340,11 +2380,14 @@
 				//
 				// Always include if we are a host-apex however since those won't have any
 				// system libraries.
-				if ch.IsStubsImplementationRequired() && !am.DirectlyInAnyApex() {
+				//
+				// Skip the dependency in unbundled builds where the device image is not
+				// being built.
+				if ch.IsStubsImplementationRequired() && !am.DirectlyInAnyApex() && !ctx.Config().UnbundledBuild() {
 					// we need a module name for Make
 					name := ch.ImplementationModuleNameForMake(ctx) + ch.Properties.SubName
-					if !android.InList(name, a.requiredDeps) {
-						a.requiredDeps = append(a.requiredDeps, name)
+					if !android.InList(name, a.makeModulesToInstall) {
+						a.makeModulesToInstall = append(a.makeModulesToInstall, name)
 					}
 				}
 				vctx.requireNativeLibs = append(vctx.requireNativeLibs, af.stem())
@@ -2431,6 +2474,9 @@
 		case *java.Library, *java.SdkLibrary:
 			af := apexFileForJavaModule(ctx, child.(javaModule))
 			vctx.filesInfo = append(vctx.filesInfo, af)
+			if profileAf := apexFileForJavaModuleProfile(ctx, child.(javaModule)); profileAf != nil {
+				vctx.filesInfo = append(vctx.filesInfo, *profileAf)
+			}
 			return true // track transitive dependencies
 		default:
 			ctx.PropertyErrorf("systemserverclasspath_fragments",
@@ -2479,7 +2525,6 @@
 	}
 	////////////////////////////////////////////////////////////////////////////////////////////
 	// 2) traverse the dependency tree to collect apexFile structs from them.
-
 	// Collect the module directory for IDE info in java/jdeps.go.
 	a.modulePaths = append(a.modulePaths, ctx.ModuleDir())
 
@@ -2837,7 +2882,7 @@
 	// Only override the minSdkVersion value on Apexes which already specify
 	// a min_sdk_version (it's optional for non-updatable apexes), and that its
 	// min_sdk_version value is lower than the one to override with.
-	minApiLevel := minSdkVersionFromValue(ctx, proptools.String(a.properties.Min_sdk_version))
+	minApiLevel := minSdkVersionFromValue(ctx, proptools.String(a.overridableProperties.Min_sdk_version))
 	if minApiLevel.IsNone() {
 		return ""
 	}
@@ -3491,8 +3536,8 @@
 	// TODO(b/219503907) this would need to be set to a.MinSdkVersionValue(ctx) but
 	// given it's coming via config, we probably don't want to put it in here.
 	var minSdkVersion bazel.StringAttribute
-	if a.properties.Min_sdk_version != nil {
-		minSdkVersion.SetValue(*a.properties.Min_sdk_version)
+	if a.overridableProperties.Min_sdk_version != nil {
+		minSdkVersion.SetValue(*a.overridableProperties.Min_sdk_version)
 	}
 	if props, ok := productVariableProps[minSdkVersionPropName]; ok {
 		for c, p := range props {
diff --git a/apex/apex_test.go b/apex/apex_test.go
index 28097aa..eec24b0 100644
--- a/apex/apex_test.go
+++ b/apex/apex_test.go
@@ -526,6 +526,7 @@
 	data.Custom(&builder, ab.BaseModuleName(), "TARGET_", "", data)
 
 	androidMk := builder.String()
+	ensureContains(t, androidMk, "LOCAL_MODULE := mylib.myapex\n")
 	ensureNotContains(t, androidMk, "LOCAL_MODULE := mylib.com.android.myapex\n")
 
 	optFlags := apexRule.Args["opt_flags"]
@@ -2995,7 +2996,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) {
@@ -3328,17 +3329,14 @@
 	// non-APEX variant does not have __ANDROID_APEX__ defined
 	mylibCFlags := ctx.ModuleForTests("mylib", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX__")
-	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_MIN_SDK_VERSION__")
 
-	// APEX variant has __ANDROID_APEX__ and __ANDROID_APEX_SDK__ defined
+	// APEX variant has __ANDROID_APEX__ and __ANDROID_APEX__ defined
 	mylibCFlags = ctx.ModuleForTests("mylib", "android_arm64_armv8-a_static_apex10000").Rule("cc").Args["cFlags"]
 	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX__")
-	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX_MIN_SDK_VERSION__=10000")
 
-	// APEX variant has __ANDROID_APEX__ and __ANDROID_APEX_SDK__ defined
+	// APEX variant has __ANDROID_APEX__ and __ANDROID_APEX__ defined
 	mylibCFlags = ctx.ModuleForTests("mylib", "android_arm64_armv8-a_static_apex29").Rule("cc").Args["cFlags"]
 	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX__")
-	ensureContains(t, mylibCFlags, "-D__ANDROID_APEX_MIN_SDK_VERSION__=29")
 
 	// When a cc_library sets use_apex_name_macro: true each apex gets a unique variant and
 	// each variant defines additional macros to distinguish which apex variant it is built for
@@ -3347,19 +3345,17 @@
 	mylibCFlags = ctx.ModuleForTests("mylib3", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX__")
 
-	// recovery variant does not set __ANDROID_APEX_MIN_SDK_VERSION__
+	// recovery variant does not set __ANDROID_APEX__
 	mylibCFlags = ctx.ModuleForTests("mylib3", "android_recovery_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX__")
-	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_MIN_SDK_VERSION__")
 
 	// non-APEX variant does not have __ANDROID_APEX__ defined
 	mylibCFlags = ctx.ModuleForTests("mylib2", "android_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX__")
 
-	// recovery variant does not set __ANDROID_APEX_MIN_SDK_VERSION__
+	// recovery variant does not set __ANDROID_APEX__
 	mylibCFlags = ctx.ModuleForTests("mylib2", "android_recovery_arm64_armv8-a_static").Rule("cc").Args["cFlags"]
 	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX__")
-	ensureNotContains(t, mylibCFlags, "-D__ANDROID_APEX_MIN_SDK_VERSION__")
 }
 
 func TestHeaderLibsDependency(t *testing.T) {
@@ -3488,14 +3484,14 @@
 	return ret
 }
 
-func ensureExactContents(t *testing.T, ctx *android.TestContext, moduleName, variant string, files []string) {
+func assertFileListEquals(t *testing.T, expectedFiles []string, actualFiles []fileInApex) {
 	t.Helper()
 	var failed bool
 	var surplus []string
 	filesMatched := make(map[string]bool)
-	for _, file := range getFiles(t, ctx, moduleName, variant) {
+	for _, file := range actualFiles {
 		matchFound := false
-		for _, expected := range files {
+		for _, expected := range expectedFiles {
 			if file.match(expected) {
 				matchFound = true
 				filesMatched[expected] = true
@@ -3513,9 +3509,9 @@
 		failed = true
 	}
 
-	if len(files) > len(filesMatched) {
+	if len(expectedFiles) > len(filesMatched) {
 		var missing []string
-		for _, expected := range files {
+		for _, expected := range expectedFiles {
 			if !filesMatched[expected] {
 				missing = append(missing, expected)
 			}
@@ -3529,6 +3525,32 @@
 	}
 }
 
+func ensureExactContents(t *testing.T, ctx *android.TestContext, moduleName, variant string, files []string) {
+	assertFileListEquals(t, files, getFiles(t, ctx, moduleName, variant))
+}
+
+func ensureExactDeapexedContents(t *testing.T, ctx *android.TestContext, moduleName string, variant string, files []string) {
+	deapexer := ctx.ModuleForTests(moduleName+".deapexer", variant).Rule("deapexer")
+	outputs := make([]string, 0, len(deapexer.ImplicitOutputs)+1)
+	if deapexer.Output != nil {
+		outputs = append(outputs, deapexer.Output.String())
+	}
+	for _, output := range deapexer.ImplicitOutputs {
+		outputs = append(outputs, output.String())
+	}
+	actualFiles := make([]fileInApex, 0, len(outputs))
+	for _, output := range outputs {
+		dir := "/deapexer/"
+		pos := strings.LastIndex(output, dir)
+		if pos == -1 {
+			t.Fatal("Unknown deapexer output ", output)
+		}
+		path := output[pos+len(dir):]
+		actualFiles = append(actualFiles, fileInApex{path: path, src: "", isLink: false})
+	}
+	assertFileListEquals(t, files, actualFiles)
+}
+
 func TestVndkApexCurrent(t *testing.T) {
 	commonFiles := []string{
 		"lib/libc++.so",
@@ -3810,11 +3832,9 @@
 		}`+vndkLibrariesTxtFiles("28", "current"))
 
 	assertApexName := func(expected, moduleName string) {
-		bundle := ctx.ModuleForTests(moduleName, "android_common_image").Module().(*apexBundle)
-		actual := proptools.String(bundle.properties.Apex_name)
-		if !reflect.DeepEqual(actual, expected) {
-			t.Errorf("Got '%v', expected '%v'", actual, expected)
-		}
+		module := ctx.ModuleForTests(moduleName, "android_common_image")
+		apexManifestRule := module.Rule("apexManifestRule")
+		ensureContains(t, apexManifestRule.Args["opt"], "-v name "+expected)
 	}
 
 	assertApexName("com.android.vndk.v29", "com.android.vndk.current")
@@ -4111,56 +4131,11 @@
 	ensureListEmpty(t, requireNativeLibs)
 }
 
-func TestApexName(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",
-			],
-		}
-	`)
-
-	module := ctx.ModuleForTests("myapex", "android_common_com.android.myapex_image")
-	apexManifestRule := module.Rule("apexManifestRule")
-	ensureContains(t, apexManifestRule.Args["opt"], "-v name com.android.myapex")
-	apexRule := module.Rule("apexRule")
-	ensureContains(t, apexRule.Args["opt_flags"], "--do_not_check_keyname")
-
-	apexBundle := module.Module().(*apexBundle)
-	data := android.AndroidMkDataForTest(t, ctx, apexBundle)
-	name := apexBundle.BaseModuleName()
-	prefix := "TARGET_"
-	var builder strings.Builder
-	data.Custom(&builder, name, prefix, "", data)
-	androidMk := builder.String()
-	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,
 		}
@@ -4185,7 +4160,7 @@
 		"OVERRIDE_APEX_MANIFEST_DEFAULT_VERSION": "1234",
 	}))
 
-	module := ctx.ModuleForTests("myapex", "android_common_com.android.myapex_image")
+	module := ctx.ModuleForTests("myapex", "android_common_myapex_image")
 	apexManifestRule := module.Rule("apexManifestRule")
 	ensureContains(t, apexManifestRule.Args["default_version"], "1234")
 }
@@ -5685,6 +5660,12 @@
 	var builder strings.Builder
 	data.Custom(&builder, name, prefix, "", data)
 	androidMk := builder.String()
+	ensureContains(t, androidMk, "LOCAL_MODULE := mytest.myapex\n")
+	ensureContains(t, androidMk, "LOCAL_MODULE := mytest1.myapex\n")
+	ensureContains(t, androidMk, "LOCAL_MODULE := mytest2.myapex\n")
+	ensureContains(t, androidMk, "LOCAL_MODULE := mytest3.myapex\n")
+	ensureContains(t, androidMk, "LOCAL_MODULE := apex_manifest.pb.myapex\n")
+	ensureContains(t, androidMk, "LOCAL_MODULE := apex_pubkey.myapex\n")
 	ensureContains(t, androidMk, "LOCAL_MODULE := myapex\n")
 
 	flatBundle := ctx.ModuleForTests("myapex", "android_common_myapex_flattened").Module().(*apexBundle)
@@ -5713,12 +5694,12 @@
 		}),
 	)
 	ab := ctx.ModuleForTests("myapex", "android_common_myapex_image").Module().(*apexBundle)
-	ensureListContains(t, ab.requiredDeps, "myapex.flattened")
+	ensureListContains(t, ab.makeModulesToInstall, "myapex.flattened")
 	mk := android.AndroidMkDataForTest(t, ctx, ab)
 	var builder strings.Builder
 	mk.Custom(&builder, ab.Name(), "TARGET_", "", mk)
 	androidMk := builder.String()
-	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES := myapex.flattened\n")
+	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES := apex_manifest.pb.myapex apex_pubkey.myapex myapex.flattened\n")
 }
 
 func TestErrorsIfDepsAreNotEnabled(t *testing.T) {
@@ -6511,6 +6492,12 @@
 	var builder strings.Builder
 	data.Custom(&builder, name, "TARGET_", "", data)
 	androidMk := builder.String()
+	ensureContains(t, androidMk, "LOCAL_MODULE := override_app.override_myapex")
+	ensureContains(t, androidMk, "LOCAL_MODULE := overrideBpf.o.override_myapex")
+	ensureContains(t, androidMk, "LOCAL_MODULE := apex_manifest.pb.override_myapex")
+	ensureContains(t, androidMk, "LOCAL_MODULE := override_bcplib.override_myapex")
+	ensureContains(t, androidMk, "LOCAL_MODULE := override_systemserverlib.override_myapex")
+	ensureContains(t, androidMk, "LOCAL_MODULE := override_java_library.override_myapex")
 	ensureContains(t, androidMk, "LOCAL_MODULE_STEM := override_myapex.apex")
 	ensureContains(t, androidMk, "LOCAL_OVERRIDES_MODULES := unknownapex myapex")
 	ensureNotContains(t, androidMk, "LOCAL_MODULE := app.myapex")
@@ -7102,7 +7089,7 @@
 	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_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")
 }
@@ -7273,9 +7260,6 @@
 				"myapex",
 				"//apex_available:platform",
 			],
-			stubs: {
-				versions: ["current"],
-			},
 		}
 	`)
 
@@ -7285,10 +7269,11 @@
 	data.Custom(&builder, apexBundle.BaseModuleName(), "TARGET_", "", data)
 	androidMk := builder.String()
 	// `myotherlib` is added to `myapex` as symlink
+	ensureContains(t, androidMk, "LOCAL_MODULE := mylib.myapex\n")
 	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 := myotherlib\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) {
@@ -8801,7 +8786,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) {
@@ -9195,7 +9180,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\n")
+	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) {
@@ -9271,7 +9256,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) {
@@ -9290,15 +9275,15 @@
 	`)
 
 	bundle := ctx.ModuleForTests("myapex", "android_common_myapex_image").Module().(*apexBundle)
-	bundle.requiredDeps = append(bundle.requiredDeps, "foo")
+	bundle.makeModulesToInstall = append(bundle.makeModulesToInstall, "foo")
 	data := android.AndroidMkDataForTest(t, ctx, bundle)
 	var builder strings.Builder
 	data.Custom(&builder, bundle.BaseModuleName(), "TARGET_", "", data)
 	androidMk := builder.String()
-	ensureContains(t, androidMk, "LOCAL_REQUIRED_MODULES := foo\n")
+	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")
+	flattenedBundle.makeModulesToInstall = append(flattenedBundle.makeModulesToInstall, "foo")
 	flattenedData := android.AndroidMkDataForTest(t, ctx, flattenedBundle)
 	var flattenedBuilder strings.Builder
 	flattenedData.Custom(&flattenedBuilder, flattenedBundle.BaseModuleName(), "TARGET_", "", flattenedData)
@@ -9542,7 +9527,7 @@
 func ensureContainsRequiredDeps(t *testing.T, ctx *android.TestContext, moduleName, variant string, deps []string) {
 	a := ctx.ModuleForTests(moduleName, variant).Module().(*apexBundle)
 	for _, dep := range deps {
-		android.AssertStringListContains(t, "", a.requiredDeps, dep)
+		android.AssertStringListContains(t, "", a.makeModulesToInstall, dep)
 	}
 }
 
@@ -9550,7 +9535,7 @@
 func ensureDoesNotContainRequiredDeps(t *testing.T, ctx *android.TestContext, moduleName, variant string, deps []string) {
 	a := ctx.ModuleForTests(moduleName, variant).Module().(*apexBundle)
 	for _, dep := range deps {
-		android.AssertStringListDoesNotContain(t, "", a.requiredDeps, dep)
+		android.AssertStringListDoesNotContain(t, "", a.makeModulesToInstall, dep)
 	}
 }
 
@@ -9855,3 +9840,70 @@
 	libcCoreVariant := result.ModuleForTests("libc.apiimport", "android_arm64_armv8-a_shared").Module()
 	android.AssertBoolEquals(t, "core variant should link against source libc", true, hasDep(libfooCoreVariant, libcCoreVariant))
 }
+
+func TestTrimmedApex(t *testing.T) {
+	bp := `
+		apex {
+			name: "myapex",
+			key: "myapex.key",
+			native_shared_libs: ["libfoo","libbaz"],
+			min_sdk_version: "29",
+			trim_against: "mydcla",
+    }
+		apex {
+			name: "mydcla",
+			key: "myapex.key",
+			native_shared_libs: ["libfoo","libbar"],
+			min_sdk_version: "29",
+			file_contexts: ":myapex-file_contexts",
+			dynamic_common_lib_apex: true,
+		}
+		apex_key {
+			name: "myapex.key",
+		}
+		cc_library {
+			name: "libfoo",
+			shared_libs: ["libc"],
+			apex_available: ["myapex","mydcla"],
+			min_sdk_version: "29",
+		}
+		cc_library {
+			name: "libbar",
+			shared_libs: ["libc"],
+			apex_available: ["myapex","mydcla"],
+			min_sdk_version: "29",
+		}
+		cc_library {
+			name: "libbaz",
+			shared_libs: ["libc"],
+			apex_available: ["myapex","mydcla"],
+			min_sdk_version: "29",
+		}
+		cc_api_library {
+			name: "libc",
+			src: "libc.so",
+			min_sdk_version: "29",
+			recovery_available: true,
+		}
+		api_imports {
+			name: "api_imports",
+			shared_libs: [
+				"libc",
+			],
+			header_libs: [],
+		}
+		`
+	ctx := testApex(t, bp)
+	module := ctx.ModuleForTests("myapex", "android_common_myapex_image")
+	apexRule := module.MaybeRule("apexRule")
+	if apexRule.Rule == nil {
+		t.Errorf("Expecting regular apex rule but a non regular apex rule found")
+	}
+
+	ctx = testApex(t, bp, android.FixtureModifyConfig(android.SetTrimmedApexEnabledForTests))
+	trimmedApexRule := ctx.ModuleForTests("myapex", "android_common_myapex_image").Rule("TrimmedApexRule")
+	libs_to_trim := trimmedApexRule.Args["libs_to_trim"]
+	android.AssertStringDoesContain(t, "missing lib to trim", libs_to_trim, "libfoo")
+	android.AssertStringDoesContain(t, "missing lib to trim", libs_to_trim, "libbar")
+	android.AssertStringDoesNotContain(t, "unexpected libs in the libs to trim", libs_to_trim, "libbaz")
+}
diff --git a/apex/bootclasspath_fragment_test.go b/apex/bootclasspath_fragment_test.go
index af4fd9f..2ddfd03 100644
--- a/apex/bootclasspath_fragment_test.go
+++ b/apex/bootclasspath_fragment_test.go
@@ -530,9 +530,8 @@
 			java.FixtureSetBootImageInstallDirOnDevice("art", "apex/com.android.art/javalib"),
 		).RunTest(t)
 
-		ensureExactContents(t, result.TestContext, "com.android.art", "android_common_com.android.art_image", []string{
+		ensureExactDeapexedContents(t, result.TestContext, "com.android.art", "android_common", []string{
 			"etc/boot-image.prof",
-			"etc/classpaths/bootclasspath.pb",
 			"javalib/arm/boot.art",
 			"javalib/arm/boot.oat",
 			"javalib/arm/boot.vdex",
@@ -592,9 +591,8 @@
 			java.FixtureSetBootImageInstallDirOnDevice("art", "system/framework"),
 		).RunTest(t)
 
-		ensureExactContents(t, result.TestContext, "com.android.art", "android_common_com.android.art_image", []string{
+		ensureExactDeapexedContents(t, result.TestContext, "com.android.art", "android_common", []string{
 			"etc/boot-image.prof",
-			"etc/classpaths/bootclasspath.pb",
 			"javalib/bar.jar",
 			"javalib/foo.jar",
 		})
diff --git a/apex/bp2build.go b/apex/bp2build.go
index d28f512..a3dda83 100644
--- a/apex/bp2build.go
+++ b/apex/bp2build.go
@@ -15,16 +15,22 @@
 
 import (
 	"android/soong/android"
+	"encoding/json"
 	"strings"
 )
 
 // This file contains the bp2build integration for the apex package.
 
 // Export constants as Starlark using bp2build to Bazel.
-func BazelApexToolchainVars() string {
+func BazelApexToolchainVars() (string, error) {
+	marshalled, err := json.Marshal(apexAvailBaseline)
+	if err != nil {
+		return "", err
+	}
 	content := []string{
 		"# GENERATED BY SOONG. DO NOT EDIT.",
 		"default_manifest_version = " + android.DefaultUpdatableModuleVersion, // constants.go is different in every branch.
+		"apex_available_baseline = json.decode('''" + string(marshalled) + "''')",
 	}
-	return strings.Join(content, "\n")
+	return strings.Join(content, "\n"), nil
 }
diff --git a/apex/bp2build_test.go b/apex/bp2build_test.go
index 01afa52..2f2b61e 100644
--- a/apex/bp2build_test.go
+++ b/apex/bp2build_test.go
@@ -42,6 +42,7 @@
 				OutputBaseDir: outputBaseDir,
 				LabelToApexInfo: map[string]cquery.ApexInfo{
 					"//:foo": cquery.ApexInfo{
+						// ApexInfo Starlark provider.
 						SignedOutput:           "signed_out.apex",
 						SignedCompressedOutput: "signed_out.capex",
 						UnsignedOutput:         "unsigned_out.apex",
@@ -56,6 +57,9 @@
 						// unused
 						PackageName:  "pkg_name",
 						ProvidesLibs: []string{"a", "b"},
+
+						// ApexMkInfo Starlark provider
+						MakeModulesToInstall: []string{"c"}, // d deliberately omitted
 					},
 				},
 			}
@@ -111,7 +115,12 @@
 	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) {
+
+	// make modules to be installed to system
+	if len(ab.makeModulesToInstall) != 1 && ab.makeModulesToInstall[0] != "c" {
+		t.Errorf("Expected makeModulesToInstall slice to only contain 'c', got %q", ab.makeModulesToInstall)
+	}
+	if w := "LOCAL_REQUIRED_MODULES := c"; !strings.Contains(data, w) {
 		t.Errorf("Expected %q in androidmk data, but did not find it in %q", w, data)
 	}
 }
@@ -212,6 +221,7 @@
 				OutputBaseDir: outputBaseDir,
 				LabelToApexInfo: map[string]cquery.ApexInfo{
 					"//:foo": cquery.ApexInfo{
+						// ApexInfo Starlark provider
 						SignedOutput:          "signed_out.apex",
 						UnsignedOutput:        "unsigned_out.apex",
 						BundleKeyInfo:         []string{"public_key", "private_key"},
@@ -225,8 +235,12 @@
 						// unused
 						PackageName:  "pkg_name",
 						ProvidesLibs: []string{"a", "b"},
+
+						// ApexMkInfo Starlark provider
+						MakeModulesToInstall: []string{"c"}, // d deliberately omitted
 					},
 					"//:override_foo": cquery.ApexInfo{
+						// ApexInfo Starlark provider
 						SignedOutput:          "override_signed_out.apex",
 						UnsignedOutput:        "override_unsigned_out.apex",
 						BundleKeyInfo:         []string{"override_public_key", "override_private_key"},
@@ -240,6 +254,9 @@
 						// unused
 						PackageName:  "override_pkg_name",
 						ProvidesLibs: []string{"a", "b"},
+
+						// ApexMkInfo Starlark provider
+						MakeModulesToInstall: []string{"c"}, // d deliberately omitted
 					},
 				},
 			}
@@ -295,7 +312,12 @@
 	if w := "$(call dist-for-goals,checkbuild,out/bazel/execroot/__main__/override_installed-files.txt:override_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) {
+
+	// make modules to be installed to system
+	if len(ab.makeModulesToInstall) != 1 && ab.makeModulesToInstall[0] != "c" {
+		t.Errorf("Expected makeModulestoInstall slice to only contain 'c', got %q", ab.makeModulesToInstall)
+	}
+	if w := "LOCAL_REQUIRED_MODULES := c"; !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 18d0836..93ff80d 100644
--- a/apex/builder.go
+++ b/apex/builder.go
@@ -40,6 +40,8 @@
 	pctx.Import("android/soong/java")
 	pctx.HostBinToolVariable("apexer", "apexer")
 	pctx.HostBinToolVariable("apexer_with_DCLA_preprocessing", "apexer_with_DCLA_preprocessing")
+	pctx.HostBinToolVariable("apexer_with_trim_preprocessing", "apexer_with_trim_preprocessing")
+
 	// ART minimal builds (using the master-art manifest) do not have the "frameworks/base"
 	// projects, and hence cannot build 'aapt2'. Use the SDK prebuilt instead.
 	hostBinToolVariableWithPrebuilt := func(name, prebuiltDir, tool string) {
@@ -146,6 +148,34 @@
 	}, "tool_path", "image_dir", "copy_commands", "file_contexts", "canned_fs_config", "key",
 		"opt_flags", "manifest", "is_DCLA")
 
+	TrimmedApexRule = pctx.StaticRule("TrimmedApexRule", blueprint.RuleParams{
+		Command: `rm -rf ${image_dir} && mkdir -p ${image_dir} && ` +
+			`(. ${out}.copy_commands) && ` +
+			`APEXER_TOOL_PATH=${tool_path} ` +
+			`${apexer_with_trim_preprocessing} ` +
+			`--apexer ${apexer} ` +
+			`--canned_fs_config ${canned_fs_config} ` +
+			`--manifest ${manifest} ` +
+			`--libs_to_trim ${libs_to_trim} ` +
+			`${image_dir} ` +
+			`${out} ` +
+			`-- ` +
+			`--include_build_info ` +
+			`--force ` +
+			`--payload_type image ` +
+			`--key ${key} ` +
+			`--file_contexts ${file_contexts} ` +
+			`${opt_flags} `,
+		CommandDeps: []string{"${apexer_with_trim_preprocessing}", "${apexer}", "${avbtool}", "${e2fsdroid}",
+			"${merge_zips}", "${mke2fs}", "${resize2fs}", "${sefcontext_compile}", "${make_f2fs}",
+			"${sload_f2fs}", "${make_erofs}", "${soong_zip}", "${zipalign}", "${aapt2}",
+			"prebuilts/sdk/current/public/android.jar"},
+		Rspfile:        "${out}.copy_commands",
+		RspfileContent: "${copy_commands}",
+		Description:    "APEX ${image_dir} => ${out}",
+	}, "tool_path", "image_dir", "copy_commands", "file_contexts", "canned_fs_config", "key",
+		"opt_flags", "manifest", "libs_to_trim")
+
 	zipApexRule = pctx.StaticRule("zipApexRule", blueprint.RuleParams{
 		Command: `rm -rf ${image_dir} && mkdir -p ${image_dir} && ` +
 			`(. ${out}.copy_commands) && ` +
@@ -211,10 +241,11 @@
 	provideNativeLibs = android.SortedUniqueStrings(provideNativeLibs)
 	requireNativeLibs = android.SortedUniqueStrings(android.RemoveListFromList(requireNativeLibs, provideNativeLibs))
 
-	// APEX name can be overridden
+	// VNDK APEX name is determined at runtime, so update "name" in apex_manifest
 	optCommands := []string{}
-	if a.properties.Apex_name != nil {
-		optCommands = append(optCommands, "-v name "+*a.properties.Apex_name)
+	if a.vndkApex {
+		apexName := vndkApexNamePrefix + a.vndkVersion(ctx.DeviceConfig())
+		optCommands = append(optCommands, "-v name "+apexName)
 	}
 
 	// Collect jniLibs. Notice that a.filesInfo is already sorted
@@ -415,7 +446,7 @@
 func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) {
 	apexType := a.properties.ApexType
 	suffix := apexType.suffix()
-	apexName := proptools.StringDefault(a.properties.Apex_name, a.BaseModuleName())
+	apexName := a.BaseModuleName()
 
 	////////////////////////////////////////////////////////////////////////////////////////////
 	// Step 1: copy built files to appropriate directories under the image directory
@@ -424,26 +455,13 @@
 
 	installSymbolFiles := (!ctx.Config().KatiEnabled() || a.ExportedToMake()) && a.installable()
 
-	// b/140136207. When there are overriding APEXes for a VNDK APEX, the symbols file for the overridden
-	// APEX and the overriding APEX will have the same installation paths at /apex/com.android.vndk.v<ver>
-	// as their apexName will be the same. To avoid the path conflicts, skip installing the symbol files
-	// for the overriding VNDK APEXes.
-	if a.vndkApex && len(a.overridableProperties.Overrides) > 0 {
-		installSymbolFiles = false
-	}
-
-	// Avoid creating duplicate build rules for multi-installed APEXes.
-	if proptools.BoolDefault(a.properties.Multi_install_skip_symbol_files, false) {
-		installSymbolFiles = false
-
-	}
 	// set of dependency module:location mappings
 	installMapSet := make(map[string]bool)
 
 	// TODO(jiyong): use the RuleBuilder
 	var copyCommands []string
 	var implicitInputs []android.Path
-	pathWhenActivated := android.PathForModuleInPartitionInstall(ctx, "apex", apexName)
+	apexDir := android.PathForModuleInPartitionInstall(ctx, "apex", apexName)
 	for _, fi := range a.filesInfo {
 		destPath := imageDir.Join(ctx, fi.path()).String()
 		// Prepare the destination path
@@ -473,12 +491,12 @@
 					fmt.Sprintf("unzip -qDD -d %s %s", destPathDir,
 						fi.module.(*java.AndroidAppSet).PackedAdditionalOutputs().String()))
 				if installSymbolFiles {
-					installedPath = ctx.InstallFileWithExtraFilesZip(pathWhenActivated.Join(ctx, fi.installDir),
+					installedPath = ctx.InstallFileWithExtraFilesZip(apexDir.Join(ctx, fi.installDir),
 						fi.stem(), fi.builtFile, fi.module.(*java.AndroidAppSet).PackedAdditionalOutputs())
 				}
 			} else {
 				if installSymbolFiles {
-					installedPath = ctx.InstallFile(pathWhenActivated.Join(ctx, fi.installDir), fi.stem(), fi.builtFile)
+					installedPath = ctx.InstallFile(apexDir.Join(ctx, fi.installDir), fi.stem(), fi.builtFile)
 				}
 			}
 			implicitInputs = append(implicitInputs, fi.builtFile)
@@ -492,7 +510,7 @@
 				symlinkDest := imageDir.Join(ctx, symlinkPath).String()
 				copyCommands = append(copyCommands, "ln -sfn "+filepath.Base(destPath)+" "+symlinkDest)
 				if installSymbolFiles {
-					installedSymlink := ctx.InstallSymlink(pathWhenActivated.Join(ctx, filepath.Dir(symlinkPath)), filepath.Base(symlinkPath), installedPath)
+					installedSymlink := ctx.InstallSymlink(apexDir.Join(ctx, filepath.Dir(symlinkPath)), filepath.Base(symlinkPath), installedPath)
 					implicitInputs = append(implicitInputs, installedSymlink)
 				}
 			}
@@ -519,8 +537,8 @@
 	}
 	implicitInputs = append(implicitInputs, a.manifestPbOut)
 	if installSymbolFiles {
-		installedManifest := ctx.InstallFile(pathWhenActivated, "apex_manifest.pb", a.manifestPbOut)
-		installedKey := ctx.InstallFile(pathWhenActivated, "apex_pubkey", a.publicKeyFile)
+		installedManifest := ctx.InstallFile(apexDir, "apex_manifest.pb", a.manifestPbOut)
+		installedKey := ctx.InstallFile(apexDir, "apex_pubkey", a.publicKeyFile)
 		implicitInputs = append(implicitInputs, installedManifest, installedKey)
 	}
 
@@ -676,12 +694,6 @@
 			optFlags = append(optFlags, "--unsigned_payload")
 		}
 
-		if a.properties.Apex_name != nil {
-			// If apex_name is set, apexer can skip checking if key name matches with
-			// apex name.  Note that apex_manifest is also mended.
-			optFlags = append(optFlags, "--do_not_check_keyname")
-		}
-
 		if moduleMinSdkVersion == android.SdkVersion_Android10 {
 			implicitInputs = append(implicitInputs, a.manifestJsonOut)
 			optFlags = append(optFlags, "--manifest_json "+a.manifestJsonOut.String())
@@ -706,6 +718,24 @@
 					"opt_flags":        strings.Join(optFlags, " "),
 				},
 			})
+		} else if ctx.Config().ApexTrimEnabled() && len(a.libs_to_trim(ctx)) > 0 {
+			ctx.Build(pctx, android.BuildParams{
+				Rule:        TrimmedApexRule,
+				Implicits:   implicitInputs,
+				Output:      unsignedOutputFile,
+				Description: "apex (" + apexType.name() + ")",
+				Args: map[string]string{
+					"tool_path":        outHostBinDir + ":" + prebuiltSdkToolsBinDir,
+					"image_dir":        imageDir.String(),
+					"copy_commands":    strings.Join(copyCommands, " && "),
+					"manifest":         a.manifestPbOut.String(),
+					"file_contexts":    fileContexts.String(),
+					"canned_fs_config": cannedFsConfig.String(),
+					"key":              a.privateKeyFile.String(),
+					"opt_flags":        strings.Join(optFlags, " "),
+					"libs_to_trim":     strings.Join(a.libs_to_trim(ctx), ","),
+				},
+			})
 		} else {
 			ctx.Build(pctx, android.BuildParams{
 				Rule:        apexRule,
@@ -970,7 +1000,7 @@
 	if a.vndkApex {
 		overrideName, overridden := ctx.DeviceConfig().OverrideManifestPackageNameFor(vndkApexName)
 		if overridden {
-			return strings.Replace(*a.properties.Apex_name, vndkApexName, overrideName, 1)
+			return overrideName + ".v" + a.vndkVersion(ctx.DeviceConfig())
 		}
 		return ""
 	}
diff --git a/apex/systemserver_classpath_fragment_test.go b/apex/systemserver_classpath_fragment_test.go
index d037664..c404a2e 100644
--- a/apex/systemserver_classpath_fragment_test.go
+++ b/apex/systemserver_classpath_fragment_test.go
@@ -31,7 +31,7 @@
 	result := android.GroupFixturePreparers(
 		prepareForTestWithSystemserverclasspathFragment,
 		prepareForTestWithMyapex,
-		dexpreopt.FixtureSetApexSystemServerJars("myapex:foo"),
+		dexpreopt.FixtureSetApexSystemServerJars("myapex:foo", "myapex:bar"),
 	).RunTestWithBp(t, `
 		apex {
 			name: "myapex",
@@ -57,10 +57,23 @@
 			],
 		}
 
+		java_library {
+			name: "bar",
+			srcs: ["c.java"],
+			installable: true,
+			dex_preopt: {
+				profile: "bar-art-profile",
+			},
+			apex_available: [
+				"myapex",
+			],
+		}
+
 		systemserverclasspath_fragment {
 			name: "mysystemserverclasspathfragment",
 			contents: [
 				"foo",
+				"bar",
 			],
 			apex_available: [
 				"myapex",
@@ -71,6 +84,8 @@
 	ensureExactContents(t, result.TestContext, "myapex", "android_common_myapex_image", []string{
 		"etc/classpaths/systemserverclasspath.pb",
 		"javalib/foo.jar",
+		"javalib/bar.jar",
+		"javalib/bar.jar.prof",
 	})
 
 	java.CheckModuleDependencies(t, result.TestContext, "myapex", "android_common_myapex_image", []string{
@@ -236,7 +251,7 @@
 	result := android.GroupFixturePreparers(
 		prepareForTestWithSystemserverclasspathFragment,
 		prepareForTestWithMyapex,
-		dexpreopt.FixtureSetApexStandaloneSystemServerJars("myapex:foo"),
+		dexpreopt.FixtureSetApexStandaloneSystemServerJars("myapex:foo", "myapex:bar"),
 	).RunTestWithBp(t, `
 		apex {
 			name: "myapex",
@@ -262,10 +277,23 @@
 			],
 		}
 
+		java_library {
+			name: "bar",
+			srcs: ["c.java"],
+			dex_preopt: {
+				profile: "bar-art-profile",
+			},
+			installable: true,
+			apex_available: [
+				"myapex",
+			],
+		}
+
 		systemserverclasspath_fragment {
 			name: "mysystemserverclasspathfragment",
 			standalone_contents: [
 				"foo",
+				"bar",
 			],
 			apex_available: [
 				"myapex",
@@ -276,6 +304,8 @@
 	ensureExactContents(t, result.TestContext, "myapex", "android_common_myapex_image", []string{
 		"etc/classpaths/systemserverclasspath.pb",
 		"javalib/foo.jar",
+		"javalib/bar.jar",
+		"javalib/bar.jar.prof",
 	})
 }
 
diff --git a/apex/vndk.go b/apex/vndk.go
index ef3e5e1..c0be753 100644
--- a/apex/vndk.go
+++ b/apex/vndk.go
@@ -65,8 +65,19 @@
 		}
 
 		vndkVersion := ab.vndkVersion(mctx.DeviceConfig())
-		// Ensure VNDK APEX mount point is formatted as com.android.vndk.v###
-		ab.properties.Apex_name = proptools.StringPtr(vndkApexNamePrefix + vndkVersion)
+		apiLevel, err := android.ApiLevelFromUser(mctx, vndkVersion)
+		if err != nil {
+			mctx.PropertyErrorf("vndk_version", "%s", err.Error())
+			return
+		}
+
+		targets := mctx.MultiTargets()
+		if len(targets) > 0 && apiLevel.LessThan(cc.MinApiForArch(mctx, targets[0].Arch.ArchType)) {
+			// Disable VNDK apexes for VNDK versions less than the minimum supported API level for the primary
+			// architecture.
+			ab.Disable()
+		}
+
 	}
 }
 
diff --git a/bazel/aquery.go b/bazel/aquery.go
index bc823b3..80cf70a 100644
--- a/bazel/aquery.go
+++ b/bazel/aquery.go
@@ -118,12 +118,11 @@
 // A helper type for aquery processing which facilitates retrieval of path IDs from their
 // less readable Bazel structures (depset and path fragment).
 type aqueryArtifactHandler struct {
-	// Switches to true if any depset contains only `bazelToolsDependencySentinel`
-	bazelToolsDependencySentinelNeeded bool
 	// Maps depset id to AqueryDepset, a representation of depset which is
 	// post-processed for middleman artifact handling, unhandled artifact
 	// dropping, content hashing, etc.
 	depsetIdToAqueryDepset map[depsetId]AqueryDepset
+	emptyDepsetIds         map[depsetId]struct{}
 	// Maps content hash to AqueryDepset.
 	depsetHashToAqueryDepset map[string]AqueryDepset
 
@@ -145,9 +144,6 @@
 // The file name of py3wrapper.sh, which is used by py_binary targets.
 const py3wrapperFileName = "/py3wrapper.sh"
 
-// A file to be put into depsets that are otherwise empty
-const bazelToolsDependencySentinel = "BAZEL_TOOLS_DEPENDENCY_SENTINEL"
-
 func indexBy[K comparable, V any](values []V, keyFn func(v V) K) map[K]V {
 	m := map[K]V{}
 	for _, v := range values {
@@ -192,6 +188,7 @@
 		depsetIdToAqueryDepset:         map[depsetId]AqueryDepset{},
 		depsetHashToAqueryDepset:       map[string]AqueryDepset{},
 		depsetHashToArtifactPathsCache: map[string][]string{},
+		emptyDepsetIds:                 make(map[depsetId]struct{}, 0),
 		artifactIdToPath:               artifactIdToPath,
 	}
 
@@ -208,16 +205,16 @@
 
 // Ensures that the handler's depsetIdToAqueryDepset map contains an entry for the given
 // depset.
-func (a *aqueryArtifactHandler) populateDepsetMaps(depset depSetOfFiles, middlemanIdToDepsetIds map[artifactId][]depsetId, depsetIdToDepset map[depsetId]depSetOfFiles) (AqueryDepset, error) {
+func (a *aqueryArtifactHandler) populateDepsetMaps(depset depSetOfFiles, middlemanIdToDepsetIds map[artifactId][]depsetId, depsetIdToDepset map[depsetId]depSetOfFiles) (*AqueryDepset, error) {
 	if aqueryDepset, containsDepset := a.depsetIdToAqueryDepset[depset.Id]; containsDepset {
-		return aqueryDepset, nil
+		return &aqueryDepset, nil
 	}
 	transitiveDepsetIds := depset.TransitiveDepSetIds
 	var directArtifactPaths []string
 	for _, artifactId := range depset.DirectArtifactIds {
 		path, pathExists := a.artifactIdToPath[artifactId]
 		if !pathExists {
-			return AqueryDepset{}, fmt.Errorf("undefined input artifactId %d", artifactId)
+			return nil, fmt.Errorf("undefined input artifactId %d", artifactId)
 		}
 		// Filter out any inputs which are universally dropped, and swap middleman
 		// artifacts with their corresponding depsets.
@@ -226,6 +223,7 @@
 			transitiveDepsetIds = append(transitiveDepsetIds, depsetsToUse...)
 		} else if strings.HasSuffix(path, py3wrapperFileName) ||
 			strings.HasPrefix(path, "../bazel_tools") {
+			continue
 			// Drop these artifacts.
 			// See go/python-binary-host-mixed-build for more details.
 			// 1) Drop py3wrapper.sh, just use python binary, the launcher script generated by the
@@ -241,20 +239,23 @@
 	for _, childDepsetId := range transitiveDepsetIds {
 		childDepset, exists := depsetIdToDepset[childDepsetId]
 		if !exists {
-			return AqueryDepset{}, fmt.Errorf("undefined input depsetId %d (referenced by depsetId %d)", childDepsetId, depset.Id)
+			if _, empty := a.emptyDepsetIds[childDepsetId]; empty {
+				continue
+			} else {
+				return nil, fmt.Errorf("undefined input depsetId %d (referenced by depsetId %d)", childDepsetId, depset.Id)
+			}
 		}
-		childAqueryDepset, err := a.populateDepsetMaps(childDepset, middlemanIdToDepsetIds, depsetIdToDepset)
-		if err != nil {
-			return AqueryDepset{}, err
+		if childAqueryDepset, err := a.populateDepsetMaps(childDepset, middlemanIdToDepsetIds, depsetIdToDepset); err != nil {
+			return nil, err
+		} else if childAqueryDepset == nil {
+			continue
+		} else {
+			childDepsetHashes = append(childDepsetHashes, childAqueryDepset.ContentHash)
 		}
-		childDepsetHashes = append(childDepsetHashes, childAqueryDepset.ContentHash)
 	}
 	if len(directArtifactPaths) == 0 && len(childDepsetHashes) == 0 {
-		// We could omit this depset altogether but that requires cleanup on
-		// transitive dependents.
-		// As a simpler alternative, we use this sentinel file as a dependency.
-		directArtifactPaths = append(directArtifactPaths, bazelToolsDependencySentinel)
-		a.bazelToolsDependencySentinelNeeded = true
+		a.emptyDepsetIds[depset.Id] = struct{}{}
+		return nil, nil
 	}
 	aqueryDepset := AqueryDepset{
 		ContentHash:            depsetContentHash(directArtifactPaths, childDepsetHashes),
@@ -263,7 +264,7 @@
 	}
 	a.depsetIdToAqueryDepset[depset.Id] = aqueryDepset
 	a.depsetHashToAqueryDepset[aqueryDepset.ContentHash] = aqueryDepset
-	return aqueryDepset, nil
+	return &aqueryDepset, nil
 }
 
 // getInputPaths flattens the depsets of the given IDs and returns all transitive
@@ -392,14 +393,6 @@
 	}
 
 	var buildStatements []BuildStatement
-	if aqueryHandler.bazelToolsDependencySentinelNeeded {
-		buildStatements = append(buildStatements, BuildStatement{
-			Command:     fmt.Sprintf("touch '%s'", bazelToolsDependencySentinel),
-			OutputPaths: []string{bazelToolsDependencySentinel},
-			Mnemonic:    bazelToolsDependencySentinel,
-		})
-	}
-
 	for _, actionEntry := range aqueryResult.Actions {
 		if shouldSkipAction(actionEntry) {
 			continue
@@ -484,7 +477,9 @@
 	var hashes []string
 	for _, depsetId := range inputDepsetIds {
 		if aqueryDepset, exists := a.depsetIdToAqueryDepset[depsetId]; !exists {
-			return nil, fmt.Errorf("undefined input depsetId %d", depsetId)
+			if _, empty := a.emptyDepsetIds[depsetId]; !empty {
+				return nil, fmt.Errorf("undefined (not even empty) input depsetId %d", depsetId)
+			}
 		} else {
 			hashes = append(hashes, aqueryDepset.ContentHash)
 		}
diff --git a/bazel/aquery_test.go b/bazel/aquery_test.go
index 2eacafa..4d1503e 100644
--- a/bazel/aquery_test.go
+++ b/bazel/aquery_test.go
@@ -227,7 +227,7 @@
 		return
 	}
 	_, _, err = AqueryBuildStatements(data)
-	assertError(t, err, "undefined input depsetId 2")
+	assertError(t, err, "undefined (not even empty) input depsetId 2")
 }
 
 func TestInvalidInputDepsetIdFromDepset(t *testing.T) {
@@ -584,13 +584,18 @@
    { "id": 60, "label": ".."}
  ]
 }`
+	/* depsets
+	       1111  2222
+	       /  \   |
+	../dep2    ../bazel_tools/dep1
+	*/
 	data, err := JsonToActionGraphContainer(inputString)
 	if err != nil {
 		t.Error(err)
 		return
 	}
 	actualBuildStatements, actualDepsets, _ := AqueryBuildStatements(data)
-	if len(actualDepsets) != 2 {
+	if len(actualDepsets) != 1 {
 		t.Errorf("expected 1 depset but found %#v", actualDepsets)
 		return
 	}
@@ -624,6 +629,82 @@
 	}
 }
 
+func TestBazelOutRemovalFromTransitiveInputDepsets(t *testing.T) {
+	const inputString = `{
+ "artifacts": [
+   { "id": 1, "path_fragment_id": 10 },
+   { "id": 2, "path_fragment_id": 20 },
+   { "id": 3, "path_fragment_id": 30 }],
+ "dep_set_of_files": [{
+   "id": 1111,
+   "transitive_dep_set_ids": [2222]
+ }, {
+   "id": 2222,
+   "direct_artifact_ids": [3]
+ }, {
+   "id": 3333,
+   "direct_artifact_ids": [3]
+ }, {
+   "id": 4444,
+   "transitive_dep_set_ids": [3333]
+ }],
+ "actions": [{
+   "target_id": 100,
+   "action_key": "x",
+   "input_dep_set_ids": [1111, 4444],
+   "mnemonic": "x",
+   "arguments": ["bogus", "command"],
+   "output_ids": [2],
+   "primary_output_id": 1
+ }],
+ "path_fragments": [
+   { "id": 10, "label": "input" },
+   { "id": 20, "label": "output" },
+   { "id": 30, "label": "dep", "parent_id": 50 },
+   { "id": 50, "label": "bazel_tools", "parent_id": 60 },
+   { "id": 60, "label": ".."}
+ ]
+}`
+	/* depsets
+	    1111    4444
+	     ||      ||
+	    2222    3333
+	      |      |
+	../bazel_tools/dep
+	Note: in dep_set_of_files:
+	  1111 appears BEFORE its dependency,2222 while
+	  4444 appears AFTER its dependency 3333
+	and this test shows that that order doesn't affect empty depset pruning
+	*/
+	data, err := JsonToActionGraphContainer(inputString)
+	if err != nil {
+		t.Error(err)
+		return
+	}
+	actualBuildStatements, actualDepsets, _ := AqueryBuildStatements(data)
+	if len(actualDepsets) != 0 {
+		t.Errorf("expected 0 depsets but found %#v", actualDepsets)
+		return
+	}
+
+	expectedBuildStatement := BuildStatement{
+		Command:     "bogus command",
+		OutputPaths: []string{"output"},
+		Mnemonic:    "x",
+	}
+	buildStatementFound := false
+	for _, actualBuildStatement := range actualBuildStatements {
+		if buildStatementEquals(actualBuildStatement, expectedBuildStatement) == "" {
+			buildStatementFound = true
+			break
+		}
+	}
+	if !buildStatementFound {
+		t.Errorf("expected but missing %#v in %#v", expectedBuildStatement, actualBuildStatements)
+		return
+	}
+}
+
 func TestMiddlemenAction(t *testing.T) {
 	const inputString = `
 {
diff --git a/bazel/configurability.go b/bazel/configurability.go
index 2b8753b..4680256 100644
--- a/bazel/configurability.go
+++ b/bazel/configurability.go
@@ -343,8 +343,8 @@
 }
 
 func (ca *ConfigurationAxis) less(other ConfigurationAxis) bool {
-	if ca.configurationType < other.configurationType {
-		return true
+	if ca.configurationType == other.configurationType {
+		return ca.subType < other.subType
 	}
-	return ca.subType < other.subType
+	return ca.configurationType < other.configurationType
 }
diff --git a/bazel/cquery/request_type.go b/bazel/cquery/request_type.go
index 81c60d9..0c8247a 100644
--- a/bazel/cquery/request_type.go
+++ b/bazel/cquery/request_type.go
@@ -14,7 +14,14 @@
 	GetCcUnstrippedInfo = &getCcUnstrippedInfoType{}
 )
 
+type CcAndroidMkInfo struct {
+	LocalStaticLibs      []string
+	LocalWholeStaticLibs []string
+	LocalSharedLibs      []string
+}
+
 type CcInfo struct {
+	CcAndroidMkInfo
 	OutputFiles          []string
 	CcObjectFiles        []string
 	CcSharedLibraryFiles []string
@@ -173,27 +180,40 @@
 tidy_files = []
 clang_tidy_info = p.get("//build/bazel/rules/cc:clang_tidy.bzl%ClangTidyInfo")
 if clang_tidy_info:
-  tidy_files = [v.path for v in clang_tidy_info.tidy_files.to_list()]
+  tidy_files = [v.path for v in clang_tidy_info.transitive_tidy_files.to_list()]
 
 abi_diff_files = []
 abi_diff_info = p.get("//build/bazel/rules/abi:abi_dump.bzl%AbiDiffInfo")
 if abi_diff_info:
   abi_diff_files = [f.path for f in abi_diff_info.diff_files.to_list()]
 
+local_static_libs = []
+local_whole_static_libs = []
+local_shared_libs = []
+androidmk_tag = "//build/bazel/rules/cc:cc_library_common.bzl%CcAndroidMkInfo"
+if androidmk_tag in p:
+    androidmk_info = p[androidmk_tag]
+    local_static_libs = androidmk_info.local_static_libs
+    local_whole_static_libs = androidmk_info.local_whole_static_libs
+    local_shared_libs = androidmk_info.local_shared_libs
+
 return json_encode({
-	"OutputFiles": outputFiles,
-	"CcObjectFiles": ccObjectFiles,
-	"CcSharedLibraryFiles": sharedLibraries,
-	"CcStaticLibraryFiles": staticLibraries,
-	"Includes": includes,
-	"SystemIncludes": system_includes,
-	"Headers": headers,
-	"RootStaticArchives": rootStaticArchives,
-	"RootDynamicLibraries": rootSharedLibraries,
-	"TidyFiles": tidy_files,
-	"TocFile": toc_file,
-	"UnstrippedOutput": unstripped,
-	"AbiDiffFiles": abi_diff_files,
+    "OutputFiles": outputFiles,
+    "CcObjectFiles": ccObjectFiles,
+    "CcSharedLibraryFiles": sharedLibraries,
+    "CcStaticLibraryFiles": staticLibraries,
+    "Includes": includes,
+    "SystemIncludes": system_includes,
+    "Headers": headers,
+    "RootStaticArchives": rootStaticArchives,
+    "RootDynamicLibraries": rootSharedLibraries,
+    "TidyFiles": [t for t in tidy_files],
+    "TocFile": toc_file,
+    "UnstrippedOutput": unstripped,
+    "AbiDiffFiles": abi_diff_files,
+    "LocalStaticLibs": [l for l in local_static_libs],
+    "LocalWholeStaticLibs": [l for l in local_whole_static_libs],
+    "LocalSharedLibs": [l for l in local_shared_libs],
 })`
 
 }
@@ -237,6 +257,15 @@
 if info.signed_compressed_output:
     signed_compressed_output = info.signed_compressed_output.path
 
+mk_info = providers(target).get("//build/bazel/rules/apex:apex_info.bzl%ApexMkInfo")
+if not mk_info:
+  fail("%s did not provide ApexMkInfo" % id_string)
+
+tidy_files = []
+clang_tidy_info = providers(target).get("//build/bazel/rules/cc:clang_tidy.bzl%ClangTidyInfo")
+if clang_tidy_info:
+    tidy_files = [v.path for v in clang_tidy_info.transitive_tidy_files.to_list()]
+
 return json_encode({
     "signed_output": info.signed_output.path,
     "signed_compressed_output": signed_compressed_output,
@@ -251,10 +280,13 @@
     "backing_libs": info.backing_libs.path,
     "bundle_file": info.base_with_config_zip.path,
     "installed_files": info.installed_files.path,
+    "make_modules_to_install": mk_info.make_modules_to_install,
+    "tidy_files": [t for t in tidy_files],
 })`
 }
 
 type ApexInfo struct {
+	// From the ApexInfo provider
 	SignedOutput           string   `json:"signed_output"`
 	SignedCompressedOutput string   `json:"signed_compressed_output"`
 	UnsignedOutput         string   `json:"unsigned_output"`
@@ -268,6 +300,10 @@
 	BackingLibs            string   `json:"backing_libs"`
 	BundleFile             string   `json:"bundle_file"`
 	InstalledFiles         string   `json:"installed_files"`
+	TidyFiles              []string `json:"tidy_files"`
+
+	// From the ApexMkInfo provider
+	MakeModulesToInstall []string `json:"make_modules_to_install"`
 }
 
 // ParseResult returns a value obtained by parsing the result of the request's Starlark function.
@@ -289,15 +325,38 @@
 }
 
 func (g getCcUnstrippedInfoType) StarlarkFunctionBody() string {
-	return `unstripped_tag = "//build/bazel/rules/cc:stripped_cc_common.bzl%CcUnstrippedInfo"
+	return `
 p = providers(target)
 output_path = target.files.to_list()[0].path
+
 unstripped = output_path
+unstripped_tag = "//build/bazel/rules/cc:stripped_cc_common.bzl%CcUnstrippedInfo"
 if unstripped_tag in p:
-    unstripped = p[unstripped_tag].unstripped.files.to_list()[0].path
+    unstripped_info = p[unstripped_tag]
+    unstripped = unstripped_info.unstripped.files.to_list()[0].path
+
+local_static_libs = []
+local_whole_static_libs = []
+local_shared_libs = []
+androidmk_tag = "//build/bazel/rules/cc:cc_library_common.bzl%CcAndroidMkInfo"
+if androidmk_tag in p:
+    androidmk_info = p[androidmk_tag]
+    local_static_libs = androidmk_info.local_static_libs
+    local_whole_static_libs = androidmk_info.local_whole_static_libs
+    local_shared_libs = androidmk_info.local_shared_libs
+
+tidy_files = []
+clang_tidy_info = p.get("//build/bazel/rules/cc:clang_tidy.bzl%ClangTidyInfo")
+if clang_tidy_info:
+    tidy_files = [v.path for v in clang_tidy_info.transitive_tidy_files.to_list()]
+
 return json_encode({
     "OutputFile":  output_path,
     "UnstrippedOutput": unstripped,
+    "LocalStaticLibs": [l for l in local_static_libs],
+    "LocalWholeStaticLibs": [l for l in local_whole_static_libs],
+    "LocalSharedLibs": [l for l in local_shared_libs],
+    "TidyFiles": [t for t in tidy_files],
 })
 `
 }
@@ -312,8 +371,10 @@
 }
 
 type CcUnstrippedInfo struct {
+	CcAndroidMkInfo
 	OutputFile       string
 	UnstrippedOutput string
+	TidyFiles        []string
 }
 
 // splitOrEmpty is a modification of strings.Split() that returns an empty list
diff --git a/bazel/cquery/request_type_test.go b/bazel/cquery/request_type_test.go
index 1d30535..7003ce1 100644
--- a/bazel/cquery/request_type_test.go
+++ b/bazel/cquery/request_type_test.go
@@ -177,9 +177,11 @@
 	"backing_libs":"path/to/backing.txt",
 	"bundle_file": "dir/bundlefile.zip",
 	"installed_files":"path/to/installed-files.txt",
-	"provides_native_libs":[]
+	"provides_native_libs":[],
+	"make_modules_to_install": ["foo","bar"]
 }`,
 			expectedOutput: ApexInfo{
+				// ApexInfo
 				SignedOutput:      "my.apex",
 				UnsignedOutput:    "my.apex.unsigned",
 				RequiresLibs:      []string{"//bionic/libc:libc", "//bionic/libdl:libdl"},
@@ -191,6 +193,9 @@
 				BackingLibs:       "path/to/backing.txt",
 				BundleFile:        "dir/bundlefile.zip",
 				InstalledFiles:    "path/to/installed-files.txt",
+
+				// ApexMkInfo
+				MakeModulesToInstall: []string{"foo", "bar"},
 			},
 		},
 	}
diff --git a/bazel/properties.go b/bazel/properties.go
index 0fca60b..f4acd26 100644
--- a/bazel/properties.go
+++ b/bazel/properties.go
@@ -73,6 +73,16 @@
 	}
 }
 
+func SortedConfigurationAxes[T any](m map[ConfigurationAxis]T) []ConfigurationAxis {
+	keys := make([]ConfigurationAxis, 0, len(m))
+	for k := range m {
+		keys = append(keys, k)
+	}
+
+	sort.Slice(keys, func(i, j int) bool { return keys[i].less(keys[j]) })
+	return keys
+}
+
 // MakeLabelListFromTargetNames creates a LabelList from unqualified target names
 // This is a utiltity function for bp2build converters of Soong modules that have 1:many generated targets
 func MakeLabelListFromTargetNames(targetNames []string) LabelList {
@@ -152,7 +162,7 @@
 		ll.Includes = append(ll.Includes, other.Includes...)
 	}
 	if len(ll.Excludes) > 0 || len(other.Excludes) > 0 {
-		ll.Excludes = append(other.Excludes, other.Excludes...)
+		ll.Excludes = append(ll.Excludes, other.Excludes...)
 	}
 }
 
@@ -412,13 +422,7 @@
 
 // SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
 func (la *LabelAttribute) SortedConfigurationAxes() []ConfigurationAxis {
-	keys := make([]ConfigurationAxis, 0, len(la.ConfigurableValues))
-	for k := range la.ConfigurableValues {
-		keys = append(keys, k)
-	}
-
-	sort.Slice(keys, func(i, j int) bool { return keys[i].less(keys[j]) })
-	return keys
+	return SortedConfigurationAxes(la.ConfigurableValues)
 }
 
 // MakeLabelAttribute turns a string into a LabelAttribute
@@ -608,13 +612,7 @@
 
 // SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
 func (ba *BoolAttribute) SortedConfigurationAxes() []ConfigurationAxis {
-	keys := make([]ConfigurationAxis, 0, len(ba.ConfigurableValues))
-	for k := range ba.ConfigurableValues {
-		keys = append(keys, k)
-	}
-
-	sort.Slice(keys, func(i, j int) bool { return keys[i].less(keys[j]) })
-	return keys
+	return SortedConfigurationAxes(ba.ConfigurableValues)
 }
 
 // labelListSelectValues supports config-specific label_list typed Bazel attribute values.
@@ -761,13 +759,7 @@
 
 // SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
 func (lla *LabelListAttribute) SortedConfigurationAxes() []ConfigurationAxis {
-	keys := make([]ConfigurationAxis, 0, len(lla.ConfigurableValues))
-	for k := range lla.ConfigurableValues {
-		keys = append(keys, k)
-	}
-
-	sort.Slice(keys, func(i, j int) bool { return keys[i].less(keys[j]) })
-	return keys
+	return SortedConfigurationAxes(lla.ConfigurableValues)
 }
 
 // Append all values, including os and arch specific ones, from another
@@ -888,7 +880,7 @@
 			// then remove all config-specific excludes
 			allLabels := baseLabels.deepCopy()
 			allLabels.Append(val)
-			lla.ConfigurableValues[axis][config] = SubtractBazelLabelList(allLabels, LabelList{Includes: val.Excludes})
+			lla.ConfigurableValues[axis][config] = SubtractBazelLabelList(allLabels, LabelList{Includes: allLabels.Excludes})
 		}
 
 		// After going through all configs, delete the duplicates in the config
@@ -1145,13 +1137,7 @@
 
 // SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
 func (sa *StringAttribute) SortedConfigurationAxes() []ConfigurationAxis {
-	keys := make([]ConfigurationAxis, 0, len(sa.ConfigurableValues))
-	for k := range sa.ConfigurableValues {
-		keys = append(keys, k)
-	}
-
-	sort.Slice(keys, func(i, j int) bool { return keys[i].less(keys[j]) })
-	return keys
+	return SortedConfigurationAxes(sa.ConfigurableValues)
 }
 
 // Collapse reduces the configurable axes of the string attribute to a single axis.
@@ -1353,13 +1339,7 @@
 
 // SortedConfigurationAxes returns all the used ConfigurationAxis in sorted order.
 func (sla *StringListAttribute) SortedConfigurationAxes() []ConfigurationAxis {
-	keys := make([]ConfigurationAxis, 0, len(sla.ConfigurableValues))
-	for k := range sla.ConfigurableValues {
-		keys = append(keys, k)
-	}
-
-	sort.Slice(keys, func(i, j int) bool { return keys[i].less(keys[j]) })
-	return keys
+	return SortedConfigurationAxes(sla.ConfigurableValues)
 }
 
 // DeduplicateAxesFromBase ensures no duplication of items between the no-configuration value and
diff --git a/bazel/properties_test.go b/bazel/properties_test.go
index 8729381..cf03eb5 100644
--- a/bazel/properties_test.go
+++ b/bazel/properties_test.go
@@ -231,6 +231,7 @@
 				"all_include",
 				"arm_exclude",
 				"android_exclude",
+				"product_config_exclude",
 			},
 			[]string{"all_exclude"},
 		),
@@ -251,10 +252,10 @@
 				"a":                        makeLabelList([]string{}, []string{"not_in_value"}),
 				"b":                        makeLabelList([]string{"b_val"}, []string{}),
 				"c":                        makeLabelList([]string{"c_val"}, []string{}),
-				ConditionsDefaultConfigKey: makeLabelList([]string{"c_val", "default", "default2"}, []string{}),
+				ConditionsDefaultConfigKey: makeLabelList([]string{"c_val", "default", "default2", "all_exclude"}, []string{}),
 			},
 			ProductVariableConfigurationAxis("product_only_with_excludes", NoConfigAxis): labelListSelectValues{
-				"a": makeLabelList([]string{}, []string{"not_in_value"}),
+				"a": makeLabelList([]string{}, []string{"product_config_exclude"}),
 			},
 		},
 	}
@@ -287,6 +288,10 @@
 			"c":                        makeLabels("c_val"),
 			ConditionsDefaultConfigKey: makeLabels("c_val", "default", "default2"),
 		},
+		ProductVariableConfigurationAxis("product_only_with_excludes", NoConfigAxis): {
+			"a":                        nilLabels,
+			ConditionsDefaultConfigKey: makeLabels("product_config_exclude"),
+		},
 	}
 	for _, axis := range attr.SortedConfigurationAxes() {
 		if _, ok := expectedConfiguredIncludes[axis]; !ok {
diff --git a/bp2build/aar_conversion_test.go b/bp2build/aar_conversion_test.go
index 5f93eb7..0cda5dd 100644
--- a/bp2build/aar_conversion_test.go
+++ b/bp2build/aar_conversion_test.go
@@ -138,3 +138,36 @@
 		},
 	)
 }
+
+func TestConvertAndroidLibraryKotlin(t *testing.T) {
+	t.Helper()
+	RunBp2BuildTestCase(t, func(ctx android.RegistrationContext) {}, Bp2buildTestCase{
+		Description:                "Android Library with .kt srcs and common_srcs attribute",
+		ModuleTypeUnderTest:        "android_library",
+		ModuleTypeUnderTestFactory: java.AndroidLibraryFactory,
+		Filesystem: map[string]string{
+			"AndroidManifest.xml": "",
+		},
+		Blueprint: `
+android_library {
+        name: "TestLib",
+        srcs: ["a.java", "b.kt"],
+        common_srcs: ["c.kt"],
+}
+`,
+		ExpectedBazelTargets: []string{
+			MakeBazelTarget(
+				"android_library",
+				"TestLib",
+				AttrNameToString{
+					"srcs": `[
+        "a.java",
+        "b.kt",
+    ]`,
+					"common_srcs":    `["c.kt"]`,
+					"manifest":       `"AndroidManifest.xml"`,
+					"resource_files": `[]`,
+				}),
+			MakeNeverlinkDuplicateTarget("android_library", "TestLib"),
+		}})
+}
diff --git a/bp2build/android_app_conversion_test.go b/bp2build/android_app_conversion_test.go
index 067e34f..4d18f83 100644
--- a/bp2build/android_app_conversion_test.go
+++ b/bp2build/android_app_conversion_test.go
@@ -227,3 +227,82 @@
 			}),
 		}})
 }
+
+func TestAndroidAppKotlinSrcs(t *testing.T) {
+	runAndroidAppTestCase(t, Bp2buildTestCase{
+		Description:                "Android app with kotlin sources and common_srcs",
+		ModuleTypeUnderTest:        "android_app",
+		ModuleTypeUnderTestFactory: java.AndroidAppFactory,
+		Filesystem: map[string]string{
+			"res/res.png": "",
+		},
+		Blueprint: simpleModuleDoNotConvertBp2build("filegroup", "foocert") + `
+android_app {
+        name: "foo",
+        srcs: ["a.java", "b.kt"],
+        certificate: ":foocert",
+        manifest: "fooManifest.xml",
+        libs: ["barLib"]
+}
+java_library{
+      name:   "barLib",
+}
+`,
+		ExpectedBazelTargets: []string{
+			MakeBazelTarget("java_library", "barLib", AttrNameToString{}),
+			MakeNeverlinkDuplicateTarget("java_library", "barLib"),
+			MakeBazelTarget("android_library", "foo_kt", AttrNameToString{
+				"srcs": `[
+        "a.java",
+        "b.kt",
+    ]`,
+				"manifest":       `"fooManifest.xml"`,
+				"resource_files": `["res/res.png"]`,
+				"deps":           `[":barLib-neverlink"]`,
+			}),
+			MakeBazelTarget("android_binary", "foo", AttrNameToString{
+				"deps":        `[":foo_kt"]`,
+				"certificate": `":foocert"`,
+				"manifest":    `"fooManifest.xml"`,
+			}),
+		}})
+}
+
+func TestAndroidAppCommonSrcs(t *testing.T) {
+	runAndroidAppTestCase(t, Bp2buildTestCase{
+		Description:                "Android app with common_srcs",
+		ModuleTypeUnderTest:        "android_app",
+		ModuleTypeUnderTestFactory: java.AndroidAppFactory,
+		Filesystem: map[string]string{
+			"res/res.png": "",
+		},
+		Blueprint: simpleModuleDoNotConvertBp2build("filegroup", "foocert") + `
+android_app {
+        name: "foo",
+        srcs: ["a.java"],
+        common_srcs: ["b.kt"],
+        certificate: "foocert",
+        manifest: "fooManifest.xml",
+        libs:        ["barLib"],
+}
+java_library{
+      name:   "barLib",
+}
+`,
+		ExpectedBazelTargets: []string{
+			MakeBazelTarget("java_library", "barLib", AttrNameToString{}),
+			MakeNeverlinkDuplicateTarget("java_library", "barLib"),
+			MakeBazelTarget("android_library", "foo_kt", AttrNameToString{
+				"srcs":           `["a.java"]`,
+				"common_srcs":    `["b.kt"]`,
+				"manifest":       `"fooManifest.xml"`,
+				"resource_files": `["res/res.png"]`,
+				"deps":           `[":barLib-neverlink"]`,
+			}),
+			MakeBazelTarget("android_binary", "foo", AttrNameToString{
+				"deps":             `[":foo_kt"]`,
+				"certificate_name": `"foocert"`,
+				"manifest":         `"fooManifest.xml"`,
+			}),
+		}})
+}
diff --git a/bp2build/bp2build.go b/bp2build/bp2build.go
index 86b9b27..062eba8 100644
--- a/bp2build/bp2build.go
+++ b/bp2build/bp2build.go
@@ -45,8 +45,12 @@
 	bp2buildFiles := CreateBazelFiles(ctx.Config(), nil, res.buildFileToTargets, ctx.mode)
 	writeFiles(ctx, bp2buildDir, bp2buildFiles)
 
-	soongInjectionDir := android.PathForOutput(ctx, bazel.SoongInjectionDirName)
-	writeFiles(ctx, soongInjectionDir, CreateSoongInjectionDirFiles(ctx, res.metrics))
+	injectionFiles, err := CreateSoongInjectionDirFiles(ctx, res.metrics)
+	if err != nil {
+		fmt.Printf("%s\n", err.Error())
+		os.Exit(1)
+	}
+	writeFiles(ctx, android.PathForOutput(ctx, bazel.SoongInjectionDirName), injectionFiles)
 
 	return &res.metrics
 }
@@ -55,17 +59,20 @@
 // This includes
 // 1. config value(s) that are hardcoded in Soong
 // 2. product_config variables
-func CreateSoongInjectionDirFiles(ctx *CodegenContext, metrics CodegenMetrics) []BazelFile {
+func CreateSoongInjectionDirFiles(ctx *CodegenContext, metrics CodegenMetrics) ([]BazelFile, error) {
 	var ret []BazelFile
 
 	productConfigFiles, err := CreateProductConfigFiles(ctx)
 	if err != nil {
-		fmt.Printf("ERROR: %s", err.Error())
-		os.Exit(1)
+		return nil, err
 	}
 	ret = append(ret, productConfigFiles...)
-	ret = append(ret, soongInjectionFiles(ctx.Config(), metrics)...)
-	return ret
+	injectionFiles, err := soongInjectionFiles(ctx.Config(), metrics)
+	if err != nil {
+		return nil, err
+	}
+	ret = append(ret, injectionFiles...)
+	return ret, nil
 }
 
 // Get the output directory and create it if it doesn't exist.
diff --git a/bp2build/cc_library_conversion_test.go b/bp2build/cc_library_conversion_test.go
index 052bc32..c11a50d 100644
--- a/bp2build/cc_library_conversion_test.go
+++ b/bp2build/cc_library_conversion_test.go
@@ -2825,7 +2825,7 @@
 	expectedBazelTargets := []string{
 		MakeBazelTarget(
 			"cc_api_library_headers",
-			"libfoo.systemapi.headers",
+			"libfoo.module-libapi.headers",
 			AttrNameToString{
 				"export_includes": `["dir1"]`,
 			}),
@@ -2842,18 +2842,18 @@
 				"api":          `"libfoo.map.txt"`,
 				"library_name": `"libfoo"`,
 				"api_surfaces": `[
-        "systemapi",
+        "module-libapi",
         "vendorapi",
     ]`,
 				"hdrs": `[
-        ":libfoo.systemapi.headers",
+        ":libfoo.module-libapi.headers",
         ":libfoo.vendorapi.headers",
     ]`,
 			}),
 	}
 	RunApiBp2BuildTestCase(t, cc.RegisterLibraryBuildComponents, Bp2buildTestCase{
 		Blueprint:            bp,
-		Description:          "cc API contributions to systemapi and vendorapi",
+		Description:          "cc API contributions to module-libapi and vendorapi",
 		ExpectedBazelTargets: expectedBazelTargets,
 	})
 }
@@ -2872,8 +2872,8 @@
 				stubs: {symbol_file: "a.map.txt"},
 			}`,
 			expectedApi:         `"a.map.txt"`,
-			expectedApiSurfaces: `["systemapi"]`,
-			description:         "Library that contributes to systemapi",
+			expectedApiSurfaces: `["module-libapi"]`,
+			description:         "Library that contributes to module-libapi",
 		},
 		{
 			bp: `
@@ -2894,10 +2894,10 @@
 			}`,
 			expectedApi: `"a.map.txt"`,
 			expectedApiSurfaces: `[
-        "systemapi",
+        "module-libapi",
         "vendorapi",
     ]`,
-			description: "Library that contributes to systemapi and vendorapi",
+			description: "Library that contributes to module-libapi and vendorapi",
 		},
 	}
 	for _, testCase := range testCases {
@@ -3437,23 +3437,23 @@
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("cc_library_static", "foo_bp2build_cc_library_static", AttrNameToString{
 				"implementation_deps": `[":baz__BP2BUILD__MISSING__DEP"] + select({
-        "//build/bazel/rules/apex:non_apex": [":buh__BP2BUILD__MISSING__DEP"],
-        "//conditions:default": [],
+        "//build/bazel/rules/apex:in_apex": [],
+        "//conditions:default": [":buh__BP2BUILD__MISSING__DEP"],
     })`,
 				"implementation_dynamic_deps": `[":baz__BP2BUILD__MISSING__DEP"] + select({
-        "//build/bazel/rules/apex:non_apex": [":bar__BP2BUILD__MISSING__DEP"],
-        "//conditions:default": [],
+        "//build/bazel/rules/apex:in_apex": [],
+        "//conditions:default": [":bar__BP2BUILD__MISSING__DEP"],
     })`,
 				"local_includes": `["."]`,
 			}),
 			MakeBazelTarget("cc_library_shared", "foo", AttrNameToString{
 				"implementation_deps": `[":baz__BP2BUILD__MISSING__DEP"] + select({
-        "//build/bazel/rules/apex:non_apex": [":buh__BP2BUILD__MISSING__DEP"],
-        "//conditions:default": [],
+        "//build/bazel/rules/apex:in_apex": [],
+        "//conditions:default": [":buh__BP2BUILD__MISSING__DEP"],
     })`,
 				"implementation_dynamic_deps": `[":baz__BP2BUILD__MISSING__DEP"] + select({
-        "//build/bazel/rules/apex:non_apex": [":bar__BP2BUILD__MISSING__DEP"],
-        "//conditions:default": [],
+        "//build/bazel/rules/apex:in_apex": [],
+        "//conditions:default": [":bar__BP2BUILD__MISSING__DEP"],
     })`,
 				"local_includes": `["."]`,
 			}),
@@ -3483,16 +3483,16 @@
 		ExpectedBazelTargets: []string{
 			MakeBazelTarget("cc_library_static", "foo", AttrNameToString{
 				"implementation_dynamic_deps": `select({
-        "//build/bazel/rules/apex:non_apex": [":bar__BP2BUILD__MISSING__DEP"],
-        "//conditions:default": [],
+        "//build/bazel/rules/apex:in_apex": [],
+        "//conditions:default": [":bar__BP2BUILD__MISSING__DEP"],
     })`,
 				"dynamic_deps": `select({
-        "//build/bazel/rules/apex:non_apex": [":baz__BP2BUILD__MISSING__DEP"],
-        "//conditions:default": [],
+        "//build/bazel/rules/apex:in_apex": [],
+        "//conditions:default": [":baz__BP2BUILD__MISSING__DEP"],
     })`,
 				"deps": `select({
-        "//build/bazel/rules/apex:non_apex": [":abc__BP2BUILD__MISSING__DEP"],
-        "//conditions:default": [],
+        "//build/bazel/rules/apex:in_apex": [],
+        "//conditions:default": [":abc__BP2BUILD__MISSING__DEP"],
     })`,
 				"local_includes": `["."]`,
 			}),
diff --git a/bp2build/cc_object_conversion_test.go b/bp2build/cc_object_conversion_test.go
index 1377c6b..eab84e1 100644
--- a/bp2build/cc_object_conversion_test.go
+++ b/bp2build/cc_object_conversion_test.go
@@ -58,6 +58,7 @@
     exclude_srcs: ["a/b/exclude.c"],
     sdk_version: "current",
     min_sdk_version: "29",
+	crt: true,
 }
 `,
 		ExpectedBazelTargets: []string{
@@ -76,6 +77,7 @@
 				"system_dynamic_deps": `[]`,
 				"sdk_version":         `"current"`,
 				"min_sdk_version":     `"29"`,
+				"crt":                 "True",
 			}),
 		},
 	})
diff --git a/bp2build/configurability.go b/bp2build/configurability.go
index 4244956..2a0a78e 100644
--- a/bp2build/configurability.go
+++ b/bp2build/configurability.go
@@ -160,6 +160,12 @@
 // select statements.
 func prettyPrintAttribute(v bazel.Attribute, indent int) (string, error) {
 	var value reflect.Value
+	// configurableAttrs is the list of individual select statements to be
+	// concatenated together. These select statements should be along different
+	// axes. For example, one element may be
+	// `select({"//color:red": "one", "//color:green": "two"})`, and the second
+	// element may be `select({"//animal:cat": "three", "//animal:dog": "four"}).
+	// These selects should be sorted by axis identifier.
 	var configurableAttrs []selects
 	var prepend bool
 	var defaultSelectValue *string
diff --git a/bp2build/conversion.go b/bp2build/conversion.go
index e53f29e..73df675 100644
--- a/bp2build/conversion.go
+++ b/bp2build/conversion.go
@@ -6,6 +6,7 @@
 	"strings"
 
 	"android/soong/android"
+	"android/soong/cc"
 	cc_config "android/soong/cc/config"
 	java_config "android/soong/java/config"
 
@@ -21,20 +22,25 @@
 }
 
 // PRIVATE: Use CreateSoongInjectionDirFiles instead
-func soongInjectionFiles(cfg android.Config, metrics CodegenMetrics) []BazelFile {
+func soongInjectionFiles(cfg android.Config, metrics CodegenMetrics) ([]BazelFile, error) {
 	var files []BazelFile
 
 	files = append(files, newFile("android", GeneratedBuildFileName, "")) // Creates a //cc_toolchain package.
 	files = append(files, newFile("android", "constants.bzl", android.BazelCcToolchainVars(cfg)))
 
 	files = append(files, newFile("cc_toolchain", GeneratedBuildFileName, "")) // Creates a //cc_toolchain package.
-	files = append(files, newFile("cc_toolchain", "constants.bzl", cc_config.BazelCcToolchainVars(cfg)))
+	files = append(files, newFile("cc_toolchain", "config_constants.bzl", cc_config.BazelCcToolchainVars(cfg)))
+	files = append(files, newFile("cc_toolchain", "sanitizer_constants.bzl", cc.BazelCcSanitizerToolchainVars(cfg)))
 
 	files = append(files, newFile("java_toolchain", GeneratedBuildFileName, "")) // Creates a //java_toolchain package.
 	files = append(files, newFile("java_toolchain", "constants.bzl", java_config.BazelJavaToolchainVars(cfg)))
 
 	files = append(files, newFile("apex_toolchain", GeneratedBuildFileName, "")) // Creates a //apex_toolchain package.
-	files = append(files, newFile("apex_toolchain", "constants.bzl", apex.BazelApexToolchainVars()))
+	apexToolchainVars, err := apex.BazelApexToolchainVars()
+	if err != nil {
+		return nil, err
+	}
+	files = append(files, newFile("apex_toolchain", "constants.bzl", apexToolchainVars))
 
 	files = append(files, newFile("metrics", "converted_modules.txt", strings.Join(metrics.Serialize().ConvertedModules, "\n")))
 
@@ -50,17 +56,19 @@
 
 	apiLevelsContent, err := json.Marshal(android.GetApiLevelsMap(cfg))
 	if err != nil {
-		panic(err)
+		return nil, err
 	}
 	files = append(files, newFile("api_levels", GeneratedBuildFileName, `exports_files(["api_levels.json"])`))
 	files = append(files, newFile("api_levels", "api_levels.json", string(apiLevelsContent)))
 	files = append(files, newFile("api_levels", "api_levels.bzl", android.StarlarkApiLevelConfigs(cfg)))
 
+	files = append(files, newFile("allowlists", GeneratedBuildFileName, ""))
+	files = append(files, newFile("allowlists", "env.bzl", android.EnvironmentVarsFile(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
+	return files, nil
 }
 
 func CreateBazelFiles(
diff --git a/bp2build/conversion_test.go b/bp2build/conversion_test.go
index 43daddc..8c1d2ae 100644
--- a/bp2build/conversion_test.go
+++ b/bp2build/conversion_test.go
@@ -84,8 +84,10 @@
 
 func TestCreateBazelFiles_Bp2Build_CreatesDefaultFiles(t *testing.T) {
 	testConfig := android.TestConfig("", make(map[string]string), "", make(map[string][]byte))
-	files := soongInjectionFiles(testConfig, CreateCodegenMetrics())
-
+	files, err := soongInjectionFiles(testConfig, CreateCodegenMetrics())
+	if err != nil {
+		t.Error(err)
+	}
 	expectedFilePaths := []bazelFilepath{
 		{
 			dir:      "android",
@@ -101,7 +103,11 @@
 		},
 		{
 			dir:      "cc_toolchain",
-			basename: "constants.bzl",
+			basename: "config_constants.bzl",
+		},
+		{
+			dir:      "cc_toolchain",
+			basename: "sanitizer_constants.bzl",
 		},
 		{
 			dir:      "java_toolchain",
@@ -149,6 +155,14 @@
 		},
 		{
 			dir:      "allowlists",
+			basename: GeneratedBuildFileName,
+		},
+		{
+			dir:      "allowlists",
+			basename: "env.bzl",
+		},
+		{
+			dir:      "allowlists",
 			basename: "mixed_build_prod_allowlist.txt",
 		},
 		{
diff --git a/bp2build/java_library_conversion_test.go b/bp2build/java_library_conversion_test.go
index 93a6174..0784f4b 100644
--- a/bp2build/java_library_conversion_test.go
+++ b/bp2build/java_library_conversion_test.go
@@ -715,3 +715,43 @@
 		},
 	})
 }
+
+func TestJavaLibraryArchVariantLibs(t *testing.T) {
+	runJavaLibraryTestCase(t, Bp2buildTestCase{
+		Description: "java_library with arch variant libs",
+		Blueprint: `java_library {
+    name: "java-lib-1",
+    srcs: ["a.java"],
+    libs: ["java-lib-2"],
+    target: {
+        android: {
+            libs: ["java-lib-3"],
+        },
+    },
+    bazel_module: { bp2build_available: true },
+}
+
+	java_library{
+		name: "java-lib-2",
+}
+
+	java_library{
+		name: "java-lib-3",
+}
+`,
+		ExpectedBazelTargets: []string{
+			MakeBazelTarget("java_library", "java-lib-1", AttrNameToString{
+				"srcs": `["a.java"]`,
+				"deps": `[":java-lib-2-neverlink"] + select({
+        "//build/bazel/platforms/os:android": [":java-lib-3-neverlink"],
+        "//conditions:default": [],
+    })`,
+			}),
+			MakeNeverlinkDuplicateTarget("java_library", "java-lib-1"),
+			MakeBazelTarget("java_library", "java-lib-2", AttrNameToString{}),
+			MakeNeverlinkDuplicateTarget("java_library", "java-lib-2"),
+			MakeBazelTarget("java_library", "java-lib-3", AttrNameToString{}),
+			MakeNeverlinkDuplicateTarget("java_library", "java-lib-3"),
+		},
+	})
+}
diff --git a/bp2build/metrics.go b/bp2build/metrics.go
index d6e5cf3..7e29fac 100644
--- a/bp2build/metrics.go
+++ b/bp2build/metrics.go
@@ -188,6 +188,10 @@
 )
 
 func (metrics *CodegenMetrics) AddConvertedModule(m blueprint.Module, moduleType string, dir string, conversionType ConversionType) {
+	//a package module has empty name
+	if moduleType == "package" {
+		return
+	}
 	// Undo prebuilt_ module name prefix modifications
 	moduleName := android.RemoveOptionalPrebuiltPrefix(m.Name())
 	metrics.serialized.ConvertedModules = append(metrics.serialized.ConvertedModules, moduleName)
diff --git a/bp2build/soong_config_module_type_conversion_test.go b/bp2build/soong_config_module_type_conversion_test.go
index 89be767..ba42f34 100644
--- a/bp2build/soong_config_module_type_conversion_test.go
+++ b/bp2build/soong_config_module_type_conversion_test.go
@@ -742,6 +742,101 @@
 )`}})
 }
 
+func TestSoongConfigModuleType_UnsetConditionsExcludeLibs(t *testing.T) {
+	bp := `
+soong_config_string_variable {
+    name: "library_linking_strategy",
+    values: [
+        "prefer_static",
+    ],
+}
+
+soong_config_module_type {
+    name: "library_linking_strategy_cc_defaults",
+    module_type: "cc_defaults",
+    config_namespace: "ANDROID",
+    variables: ["library_linking_strategy"],
+    properties: ["shared_libs"],
+}
+
+library_linking_strategy_cc_defaults {
+    name: "library_linking_strategy_lib_a_defaults",
+    soong_config_variables: {
+        library_linking_strategy: {
+            prefer_static: {},
+            conditions_default: {
+                shared_libs: [
+                    "lib_a",
+                ],
+            },
+        },
+    },
+}
+
+library_linking_strategy_cc_defaults {
+    name: "library_linking_strategy_merged_defaults",
+    defaults: ["library_linking_strategy_lib_a_defaults"],
+    host_supported: true,
+    soong_config_variables: {
+        library_linking_strategy: {
+            prefer_static: {},
+            conditions_default: {
+                shared_libs: [
+                    "lib_b",
+                    "lib_c",
+                ],
+            },
+        },
+    },
+    exclude_shared_libs: ["lib_a"],
+}
+
+cc_binary {
+    name: "library_linking_strategy_sample_binary",
+    defaults: ["library_linking_strategy_merged_defaults"],
+    include_build_directory: false,
+}
+
+cc_binary {
+    name: "library_linking_strategy_sample_binary_with_excludes",
+    defaults: ["library_linking_strategy_merged_defaults"],
+    exclude_shared_libs: ["lib_c"],
+    include_build_directory: false,
+}`
+
+	otherDeps := `
+cc_library { name: "lib_a", bazel_module: { bp2build_available: false } }
+cc_library { name: "lib_b", bazel_module: { bp2build_available: false } }
+cc_library { name: "lib_c", bazel_module: { bp2build_available: false } }
+`
+
+	runSoongConfigModuleTypeTest(t, Bp2buildTestCase{
+		Description:                "soong config variables - generates selects for library_linking_strategy",
+		ModuleTypeUnderTest:        "cc_binary",
+		ModuleTypeUnderTestFactory: cc.BinaryFactory,
+		Blueprint:                  bp,
+		Filesystem: map[string]string{
+			"foo/bar/Android.bp": otherDeps,
+		},
+		ExpectedBazelTargets: []string{
+			MakeBazelTargetNoRestrictions("cc_binary", "library_linking_strategy_sample_binary", AttrNameToString{
+				"dynamic_deps": `select({
+        "//build/bazel/product_variables:android__library_linking_strategy__prefer_static": [],
+        "//conditions:default": [
+            "//foo/bar:lib_b",
+            "//foo/bar:lib_c",
+        ],
+    })`,
+			}),
+			MakeBazelTargetNoRestrictions("cc_binary", "library_linking_strategy_sample_binary_with_excludes", AttrNameToString{
+				"dynamic_deps": `select({
+        "//build/bazel/product_variables:android__library_linking_strategy__prefer_static": [],
+        "//conditions:default": ["//foo/bar:lib_b"],
+    })`,
+			}),
+		}})
+}
+
 func TestSoongConfigModuleType_Defaults(t *testing.T) {
 	bp := `
 soong_config_string_variable {
diff --git a/bp2build/symlink_forest.go b/bp2build/symlink_forest.go
index 37188f1..aac5e7d 100644
--- a/bp2build/symlink_forest.go
+++ b/bp2build/symlink_forest.go
@@ -15,9 +15,7 @@
 package bp2build
 
 import (
-	"errors"
 	"fmt"
-	"io/fs"
 	"io/ioutil"
 	"os"
 	"path/filepath"
@@ -27,6 +25,7 @@
 	"sync/atomic"
 
 	"android/soong/shared"
+	"github.com/google/blueprint/pathtools"
 )
 
 // A tree structure that describes what to do at each directory in the created
@@ -53,59 +52,6 @@
 	symlinkCount atomic.Uint64
 }
 
-// A simple thread pool to limit concurrency on system calls.
-// Necessary because Go spawns a new OS-level thread for each blocking system
-// call. This means that if syscalls are too slow and there are too many of
-// them, the hard limit on OS-level threads can be exhausted.
-type syscallPool struct {
-	shutdownCh []chan<- struct{}
-	workCh     chan syscall
-}
-
-type syscall struct {
-	work func()
-	done chan<- struct{}
-}
-
-func createSyscallPool(count int) *syscallPool {
-	result := &syscallPool{
-		shutdownCh: make([]chan<- struct{}, count),
-		workCh:     make(chan syscall),
-	}
-
-	for i := 0; i < count; i++ {
-		shutdownCh := make(chan struct{})
-		result.shutdownCh[i] = shutdownCh
-		go result.worker(shutdownCh)
-	}
-
-	return result
-}
-
-func (p *syscallPool) do(work func()) {
-	doneCh := make(chan struct{})
-	p.workCh <- syscall{work, doneCh}
-	<-doneCh
-}
-
-func (p *syscallPool) shutdown() {
-	for _, ch := range p.shutdownCh {
-		ch <- struct{}{} // Blocks until the value is received
-	}
-}
-
-func (p *syscallPool) worker(shutdownCh <-chan struct{}) {
-	for {
-		select {
-		case <-shutdownCh:
-			return
-		case work := <-p.workCh:
-			work.work()
-			work.done <- struct{}{}
-		}
-	}
-}
-
 // Ensures that the node for the given path exists in the tree and returns it.
 func ensureNodeExists(root *instructionsNode, path string) *instructionsNode {
 	if path == "" {
@@ -171,25 +117,13 @@
 		generatedBuildFileContent = packageDefaultVisibilityRegex.ReplaceAll(generatedBuildFileContent, []byte{})
 	}
 
-	outFile, err := os.Create(output)
-	if err != nil {
-		return err
+	newContents := generatedBuildFileContent
+	if newContents[len(newContents)-1] != '\n' {
+		newContents = append(newContents, '\n')
 	}
+	newContents = append(newContents, srcBuildFileContent...)
 
-	_, err = outFile.Write(generatedBuildFileContent)
-	if err != nil {
-		return err
-	}
-
-	if generatedBuildFileContent[len(generatedBuildFileContent)-1] != '\n' {
-		_, err = outFile.WriteString("\n")
-		if err != nil {
-			return err
-		}
-	}
-
-	_, err = outFile.Write(srcBuildFileContent)
-	return err
+	return pathtools.WriteFileIfChanged(output, newContents, 0666)
 }
 
 // Calls readdir() and returns it as a map from the basename of the files in dir
@@ -217,12 +151,35 @@
 }
 
 // Creates a symbolic link at dst pointing to src
-func symlinkIntoForest(topdir, dst, src string) {
-	err := os.Symlink(shared.JoinPath(topdir, src), shared.JoinPath(topdir, dst))
-	if err != nil {
+func symlinkIntoForest(topdir, dst, src string) uint64 {
+	srcPath := shared.JoinPath(topdir, src)
+	dstPath := shared.JoinPath(topdir, dst)
+
+	// Check if a symlink already exists.
+	if dstInfo, err := os.Lstat(dstPath); err != nil {
+		if !os.IsNotExist(err) {
+			fmt.Fprintf(os.Stderr, "Failed to lstat '%s': %s", dst, err)
+			os.Exit(1)
+		}
+	} else {
+		if dstInfo.Mode()&os.ModeSymlink != 0 {
+			// Assume that the link's target is correct, i.e. no manual tampering.
+			// E.g. OUT_DIR could have been previously used with a different source tree check-out!
+			return 0
+		} else {
+			if err := os.RemoveAll(dstPath); err != nil {
+				fmt.Fprintf(os.Stderr, "Failed to remove '%s': %s", dst, err)
+				os.Exit(1)
+			}
+		}
+	}
+
+	// Create symlink.
+	if err := os.Symlink(srcPath, dstPath); err != nil {
 		fmt.Fprintf(os.Stderr, "Cannot create symlink at '%s' pointing to '%s': %s", dst, src, err)
 		os.Exit(1)
 	}
+	return 1
 }
 
 func isDir(path string, fi os.FileInfo) bool {
@@ -253,8 +210,9 @@
 	defer context.wg.Done()
 
 	if instructions != nil && instructions.excluded {
-		// This directory is not needed, bail out
-		return
+		// Excluded paths are skipped at the level of the non-excluded parent.
+		fmt.Fprintf(os.Stderr, "may not specify a root-level exclude directory '%s'", srcDir)
+		os.Exit(1)
 	}
 
 	// We don't add buildFilesDir here because the bp2build files marker files is
@@ -272,6 +230,12 @@
 				renamingBuildFile = true
 				srcDirMap["BUILD.bazel"] = srcDirMap["BUILD"]
 				delete(srcDirMap, "BUILD")
+				if instructions != nil {
+					if _, ok := instructions.children["BUILD"]; ok {
+						instructions.children["BUILD.bazel"] = instructions.children["BUILD"]
+						delete(instructions.children, "BUILD")
+					}
+				}
 			}
 		}
 	}
@@ -288,17 +252,41 @@
 	// Tests read the error messages generated, so ensure their order is deterministic
 	sort.Strings(allEntries)
 
-	err := os.MkdirAll(shared.JoinPath(context.topdir, forestDir), 0777)
-	if err != nil {
-		fmt.Fprintf(os.Stderr, "Cannot mkdir '%s': %s\n", forestDir, err)
-		os.Exit(1)
+	fullForestPath := shared.JoinPath(context.topdir, forestDir)
+	createForestDir := false
+	if fi, err := os.Lstat(fullForestPath); err != nil {
+		if os.IsNotExist(err) {
+			createForestDir = true
+		} else {
+			fmt.Fprintf(os.Stderr, "Could not read info for '%s': %s\n", forestDir, err)
+		}
+	} else if fi.Mode()&os.ModeDir == 0 {
+		if err := os.RemoveAll(fullForestPath); err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to remove '%s': %s", forestDir, err)
+			os.Exit(1)
+		}
+		createForestDir = true
 	}
-	context.mkdirCount.Add(1)
+	if createForestDir {
+		if err := os.MkdirAll(fullForestPath, 0777); err != nil {
+			fmt.Fprintf(os.Stderr, "Could not mkdir '%s': %s\n", forestDir, err)
+			os.Exit(1)
+		}
+		context.mkdirCount.Add(1)
+	}
+
+	// Start with a list of items that already exist in the forest, and remove
+	// each element as it is processed in allEntries. Any remaining items in
+	// forestMapForDeletion must be removed. (This handles files which were
+	// removed since the previous forest generation).
+	forestMapForDeletion := readdirToMap(shared.JoinPath(context.topdir, forestDir))
 
 	for _, f := range allEntries {
 		if f[0] == '.' {
 			continue // Ignore dotfiles
 		}
+		delete(forestMapForDeletion, f)
+		// todo add deletionCount metric
 
 		// The full paths of children in the input trees and in the output tree
 		forestChild := shared.JoinPath(forestDir, f)
@@ -309,13 +297,9 @@
 		buildFilesChild := shared.JoinPath(buildFilesDir, f)
 
 		// Descend in the instruction tree if it exists
-		var instructionsChild *instructionsNode = nil
+		var instructionsChild *instructionsNode
 		if instructions != nil {
-			if f == "BUILD.bazel" && renamingBuildFile {
-				instructionsChild = instructions.children["BUILD"]
-			} else {
-				instructionsChild = instructions.children[f]
-			}
+			instructionsChild = instructions.children[f]
 		}
 
 		srcChildEntry, sExists := srcDirMap[f]
@@ -323,8 +307,7 @@
 
 		if instructionsChild != nil && instructionsChild.excluded {
 			if bExists {
-				symlinkIntoForest(context.topdir, forestChild, buildFilesChild)
-				context.symlinkCount.Add(1)
+				context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, buildFilesChild))
 			}
 			continue
 		}
@@ -340,8 +323,7 @@
 				go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
 			} else {
 				// Not in the source tree, symlink BUILD file
-				symlinkIntoForest(context.topdir, forestChild, buildFilesChild)
-				context.symlinkCount.Add(1)
+				context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, buildFilesChild))
 			}
 		} else if !bExists {
 			if sDir && instructionsChild != nil {
@@ -351,8 +333,7 @@
 				go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
 			} else {
 				// Not in the build file tree, symlink source tree, carry on
-				symlinkIntoForest(context.topdir, forestChild, srcChild)
-				context.symlinkCount.Add(1)
+				context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, srcChild))
 			}
 		} else if sDir && bDir {
 			// Both are directories. Descend.
@@ -365,8 +346,7 @@
 			// The Android.bp file that codegen used to produce `buildFilesChild` is
 			// already a dependency, we can ignore `buildFilesChild`.
 			context.depCh <- srcChild
-			err = mergeBuildFiles(shared.JoinPath(context.topdir, forestChild), srcBuildFile, generatedBuildFile, context.verbose)
-			if err != nil {
+			if err := mergeBuildFiles(shared.JoinPath(context.topdir, forestChild), srcBuildFile, generatedBuildFile, context.verbose); err != nil {
 				fmt.Fprintf(os.Stderr, "Error merging %s and %s: %s",
 					srcBuildFile, generatedBuildFile, err)
 				os.Exit(1)
@@ -379,51 +359,27 @@
 			os.Exit(1)
 		}
 	}
-}
 
-func removeParallelRecursive(pool *syscallPool, path string, fi os.FileInfo, wg *sync.WaitGroup) {
-	defer wg.Done()
-
-	if fi.IsDir() {
-		children := readdirToMap(path)
-		childrenWg := &sync.WaitGroup{}
-		childrenWg.Add(len(children))
-
-		for child, childFi := range children {
-			go removeParallelRecursive(pool, shared.JoinPath(path, child), childFi, childrenWg)
+	// Remove all files in the forest that exist in neither the source
+	// tree nor the build files tree. (This handles files which were removed
+	// since the previous forest generation).
+	for f := range forestMapForDeletion {
+		var instructionsChild *instructionsNode
+		if instructions != nil {
+			instructionsChild = instructions.children[f]
 		}
 
-		childrenWg.Wait()
-	}
-
-	pool.do(func() {
-		if err := os.Remove(path); err != nil {
-			fmt.Fprintf(os.Stderr, "Cannot unlink '%s': %s\n", path, err)
+		if instructionsChild != nil && instructionsChild.excluded {
+			// This directory may be excluded because bazel writes to it under the
+			// forest root. Thus this path is intentionally left alone.
+			continue
+		}
+		forestChild := shared.JoinPath(context.topdir, forestDir, f)
+		if err := os.RemoveAll(forestChild); err != nil {
+			fmt.Fprintf(os.Stderr, "Failed to remove '%s/%s': %s", forestDir, f, err)
 			os.Exit(1)
 		}
-	})
-}
-
-func removeParallel(path string) {
-	fi, err := os.Lstat(path)
-	if err != nil {
-		if errors.Is(err, fs.ErrNotExist) {
-			return
-		}
-
-		fmt.Fprintf(os.Stderr, "Cannot lstat '%s': %s\n", path, err)
-		os.Exit(1)
 	}
-
-	wg := &sync.WaitGroup{}
-	wg.Add(1)
-
-	// Random guess as to the best number of syscalls to run in parallel
-	pool := createSyscallPool(100)
-	removeParallelRecursive(pool, path, fi, wg)
-	pool.shutdown()
-
-	wg.Wait()
 }
 
 // PlantSymlinkForest Creates a symlink forest by merging the directory tree at "buildFiles" and
@@ -439,8 +395,6 @@
 		symlinkCount: atomic.Uint64{},
 	}
 
-	removeParallel(shared.JoinPath(topdir, forest))
-
 	instructions := instructionsFromExcludePathList(exclude)
 	go func() {
 		context.wg.Add(1)
diff --git a/bp2build/testing.go b/bp2build/testing.go
index c340a8f..92a9bf1 100644
--- a/bp2build/testing.go
+++ b/bp2build/testing.go
@@ -258,6 +258,7 @@
 }
 
 func (b BazelTestResult) CompareBazelTargets(t *testing.T, description string, expectedContents []string, actualTargets BazelTargets) {
+	t.Helper()
 	if actualCount, expectedCount := len(actualTargets), len(expectedContents); actualCount != expectedCount {
 		t.Errorf("%s: Expected %d bazel target (%s), got %d (%s)",
 			description, expectedCount, expectedContents, actualCount, actualTargets)
diff --git a/cc/api_level.go b/cc/api_level.go
index fdff5cb..a5571f3 100644
--- a/cc/api_level.go
+++ b/cc/api_level.go
@@ -20,7 +20,9 @@
 	"android/soong/android"
 )
 
-func minApiForArch(ctx android.EarlyModuleContext,
+// MinApiLevelForArch returns the ApiLevel for the Android version that
+// first supported the architecture.
+func MinApiForArch(ctx android.EarlyModuleContext,
 	arch android.ArchType) android.ApiLevel {
 
 	switch arch {
@@ -38,7 +40,7 @@
 func nativeApiLevelFromUser(ctx android.BaseModuleContext,
 	raw string) (android.ApiLevel, error) {
 
-	min := minApiForArch(ctx, ctx.Arch().ArchType)
+	min := MinApiForArch(ctx, ctx.Arch().ArchType)
 	if raw == "minimum" {
 		return min, nil
 	}
diff --git a/cc/binary.go b/cc/binary.go
index 54c1abc..a04b174 100644
--- a/cc/binary.go
+++ b/cc/binary.go
@@ -588,9 +588,15 @@
 		return
 	}
 
-	outputFilePath := android.PathForBazelOut(ctx, info.OutputFile)
+	var outputFilePath android.Path = android.PathForBazelOut(ctx, info.OutputFile)
+	if len(info.TidyFiles) > 0 {
+		handler.module.tidyFiles = android.PathsForBazelOut(ctx, info.TidyFiles)
+		outputFilePath = android.AttachValidationActions(ctx, outputFilePath, handler.module.tidyFiles)
+	}
 	handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
 	handler.module.linker.(*binaryDecorator).unstrippedOutputFile = android.PathForBazelOut(ctx, info.UnstrippedOutput)
+
+	handler.module.setAndroidMkVariablesFromCquery(info.CcAndroidMkInfo)
 }
 
 func binaryBp2buildAttrs(ctx android.TopDownMutatorContext, m *Module) binaryAttributes {
diff --git a/cc/binary_test.go b/cc/binary_test.go
index 43aff5c..e0b5b5d 100644
--- a/cc/binary_test.go
+++ b/cc/binary_test.go
@@ -55,6 +55,47 @@
 	android.AssertStringEquals(t, "Unstripped output file", expectedUnStrippedFile, unStrippedFilePath.String())
 }
 
+func TestCcBinaryWithBazelValidations(t *testing.T) {
+	t.Parallel()
+	bp := `
+cc_binary {
+	name: "foo",
+	srcs: ["foo.cc"],
+	bazel_module: { label: "//foo/bar:bar" },
+	tidy: true,
+}`
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
+	config.BazelContext = android.MockBazelContext{
+		OutputBaseDir: "outputbase",
+		LabelToCcBinary: map[string]cquery.CcUnstrippedInfo{
+			"//foo/bar:bar": cquery.CcUnstrippedInfo{
+				OutputFile:       "foo",
+				UnstrippedOutput: "foo.unstripped",
+				TidyFiles:        []string{"foo.c.tidy"},
+			},
+		},
+	}
+	ctx := android.GroupFixturePreparers(
+		prepareForCcTest,
+		android.FixtureMergeEnv(map[string]string{
+			"ALLOW_LOCAL_TIDY_TRUE": "1",
+		}),
+	).RunTestWithConfig(t, config).TestContext
+
+	binMod := ctx.ModuleForTests("foo", "android_arm64_armv8-a").Module()
+	producer := binMod.(android.OutputFileProducer)
+	outputFiles, err := producer.OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error getting cc_binary outputfiles %s", err)
+	}
+	expectedOutputFiles := []string{"out/soong/.intermediates/foo/android_arm64_armv8-a/validated/foo"}
+	android.AssertPathsRelativeToTopEquals(t, "output files", expectedOutputFiles, outputFiles)
+
+	unStrippedFilePath := binMod.(*Module).UnstrippedOutputFile()
+	expectedUnStrippedFile := "outputbase/execroot/__main__/foo.unstripped"
+	android.AssertStringEquals(t, "Unstripped output file", expectedUnStrippedFile, unStrippedFilePath.String())
+}
+
 func TestBinaryLinkerScripts(t *testing.T) {
 	t.Parallel()
 	result := PrepareForIntegrationTestWithCc.RunTestWithBp(t, `
diff --git a/cc/bp2build.go b/cc/bp2build.go
index aea1fa1..6c5505a 100644
--- a/cc/bp2build.go
+++ b/cc/bp2build.go
@@ -702,7 +702,13 @@
 	compilerAttrs := compilerAttributes{}
 	linkerAttrs := linkerAttributes{}
 
-	for axis, configs := range axisToConfigs {
+	// Iterate through these axes in a deterministic order. This is required
+	// because processing certain dependencies may result in concatenating
+	// elements along other axes. (For example, processing NoConfig may result
+	// in elements being added to InApex). This is thus the only way to ensure
+	// that the order of entries in each list is in a predictable order.
+	for _, axis := range bazel.SortedConfigurationAxes(axisToConfigs) {
+		configs := axisToConfigs[axis]
 		for cfg := range configs {
 			var allHdrs []string
 			if baseCompilerProps, ok := archVariantCompilerProps[axis][cfg].(*BaseCompilerProperties); ok {
@@ -979,34 +985,22 @@
 
 // resolveTargetApex re-adds the shared and static libs in target.apex.exclude_shared|static_libs props to non-apex variant
 // since all libs are already excluded by default
-func (la *linkerAttributes) resolveTargetApexProp(ctx android.BazelConversionPathContext, isBinary bool, props *BaseLinkerProperties) {
-	sharedLibsForNonApex := maybePartitionExportedAndImplementationsDeps(
-		ctx,
-		true,
-		props.Target.Apex.Exclude_shared_libs,
-		props.Export_shared_lib_headers,
-		bazelLabelForSharedDeps,
-	)
-	dynamicDeps := la.dynamicDeps.SelectValue(bazel.InApexAxis, bazel.NonApex)
-	implDynamicDeps := la.implementationDynamicDeps.SelectValue(bazel.InApexAxis, bazel.NonApex)
-	(&dynamicDeps).Append(sharedLibsForNonApex.export)
-	(&implDynamicDeps).Append(sharedLibsForNonApex.implementation)
-	la.dynamicDeps.SetSelectValue(bazel.InApexAxis, bazel.NonApex, dynamicDeps)
-	la.implementationDynamicDeps.SetSelectValue(bazel.InApexAxis, bazel.NonApex, implDynamicDeps)
+func (la *linkerAttributes) resolveTargetApexProp(ctx android.BazelConversionPathContext, props *BaseLinkerProperties) {
+	excludeSharedLibs := bazelLabelForSharedDeps(ctx, props.Target.Apex.Exclude_shared_libs)
+	sharedExcludes := bazel.LabelList{Excludes: excludeSharedLibs.Includes}
+	sharedExcludesLabelList := bazel.LabelListAttribute{}
+	sharedExcludesLabelList.SetSelectValue(bazel.InApexAxis, bazel.InApex, sharedExcludes)
 
-	staticLibsForNonApex := maybePartitionExportedAndImplementationsDeps(
-		ctx,
-		!isBinary,
-		props.Target.Apex.Exclude_static_libs,
-		props.Export_static_lib_headers,
-		bazelLabelForSharedDeps,
-	)
-	deps := la.deps.SelectValue(bazel.InApexAxis, bazel.NonApex)
-	implDeps := la.implementationDeps.SelectValue(bazel.InApexAxis, bazel.NonApex)
-	(&deps).Append(staticLibsForNonApex.export)
-	(&implDeps).Append(staticLibsForNonApex.implementation)
-	la.deps.SetSelectValue(bazel.InApexAxis, bazel.NonApex, deps)
-	la.implementationDeps.SetSelectValue(bazel.InApexAxis, bazel.NonApex, implDeps)
+	la.dynamicDeps.Append(sharedExcludesLabelList)
+	la.implementationDynamicDeps.Append(sharedExcludesLabelList)
+
+	excludeStaticLibs := bazelLabelForStaticDeps(ctx, props.Target.Apex.Exclude_static_libs)
+	staticExcludes := bazel.LabelList{Excludes: excludeStaticLibs.Includes}
+	staticExcludesLabelList := bazel.LabelListAttribute{}
+	staticExcludesLabelList.SetSelectValue(bazel.InApexAxis, bazel.InApex, staticExcludes)
+
+	la.deps.Append(staticExcludesLabelList)
+	la.implementationDeps.Append(staticExcludesLabelList)
 }
 
 func (la *linkerAttributes) bp2buildForAxisAndConfig(ctx android.BazelConversionPathContext, isBinary bool, axis bazel.ConfigurationAxis, config string, props *BaseLinkerProperties) {
@@ -1040,8 +1034,7 @@
 		ctx,
 		!isBinary,
 		staticLibs,
-		// Exclude static libs in Exclude_static_libs and Target.Apex.Exclude_static_libs props
-		append(props.Exclude_static_libs, props.Target.Apex.Exclude_static_libs...),
+		props.Exclude_static_libs,
 		props.Export_static_lib_headers,
 		bazelLabelForStaticDepsExcludes,
 	)
@@ -1080,14 +1073,13 @@
 		ctx,
 		!isBinary,
 		sharedLibs,
-		// Exclude shared libs in Exclude_shared_libs and Target.Apex.Exclude_shared_libs props
-		append(props.Exclude_shared_libs, props.Target.Apex.Exclude_shared_libs...),
+		props.Exclude_shared_libs,
 		props.Export_shared_lib_headers,
 		bazelLabelForSharedDepsExcludes,
 	)
 	la.dynamicDeps.SetSelectValue(axis, config, sharedDeps.export)
 	la.implementationDynamicDeps.SetSelectValue(axis, config, sharedDeps.implementation)
-	la.resolveTargetApexProp(ctx, isBinary, props)
+	la.resolveTargetApexProp(ctx, props)
 
 	if axis == bazel.NoConfigAxis || (axis == bazel.OsConfigurationAxis && config == bazel.OsAndroid) {
 		// If a dependency in la.implementationDynamicDeps has stubs, its stub variant should be
diff --git a/cc/builder.go b/cc/builder.go
index 0629406..fef00d4 100644
--- a/cc/builder.go
+++ b/cc/builder.go
@@ -519,6 +519,13 @@
 	cppflags += " ${config.NoOverrideGlobalCflags}"
 	toolingCppflags += " ${config.NoOverrideGlobalCflags}"
 
+	if flags.toolchain.Is64Bit() {
+		cflags += " ${config.NoOverride64GlobalCflags}"
+		toolingCflags += " ${config.NoOverride64GlobalCflags}"
+		cppflags += " ${config.NoOverride64GlobalCflags}"
+		toolingCppflags += " ${config.NoOverride64GlobalCflags}"
+	}
+
 	modulePath := android.PathForModuleSrc(ctx).String()
 	if android.IsThirdPartyPath(modulePath) {
 		cflags += " ${config.NoOverrideExternalGlobalCflags}"
diff --git a/cc/cc.go b/cc/cc.go
index cb425c3..c81160d 100644
--- a/cc/cc.go
+++ b/cc/cc.go
@@ -28,6 +28,7 @@
 	"github.com/google/blueprint/proptools"
 
 	"android/soong/android"
+	"android/soong/bazel/cquery"
 	"android/soong/cc/config"
 	"android/soong/fuzz"
 	"android/soong/genrule"
@@ -1065,6 +1066,31 @@
 	return false
 }
 
+func (c *Module) IsFuzzModule() bool {
+	if _, ok := c.compiler.(*fuzzBinary); ok {
+		return true
+	}
+	return false
+}
+
+func (c *Module) FuzzModuleStruct() fuzz.FuzzModule {
+	return c.FuzzModule
+}
+
+func (c *Module) FuzzPackagedModule() fuzz.FuzzPackagedModule {
+	if fuzzer, ok := c.compiler.(*fuzzBinary); ok {
+		return fuzzer.fuzzPackagedModule
+	}
+	panic(fmt.Errorf("FuzzPackagedModule called on non-fuzz module: %q", c.BaseModuleName()))
+}
+
+func (c *Module) FuzzSharedLibraries() android.Paths {
+	if fuzzer, ok := c.compiler.(*fuzzBinary); ok {
+		return fuzzer.sharedLibraries
+	}
+	panic(fmt.Errorf("FuzzSharedLibraries called on non-fuzz module: %q", c.BaseModuleName()))
+}
+
 func (c *Module) NonCcVariants() bool {
 	return false
 }
@@ -1445,6 +1471,8 @@
 }
 
 func InstallToBootstrap(name string, config android.Config) bool {
+	// NOTE: also update //build/bazel/rules/apex/cc.bzl#_installed_to_bootstrap
+	// if this list is updated.
 	if name == "libclang_rt.hwasan" {
 		return true
 	}
@@ -1859,6 +1887,10 @@
 var (
 	mixedBuildSupportedCcTest = []string{
 		"adbd_test",
+		"adb_crypto_test",
+		"adb_pairing_auth_test",
+		"adb_pairing_connection_test",
+		"adb_tls_connection_test",
 	}
 )
 
@@ -1866,7 +1898,8 @@
 // in any of the --bazel-mode(s). This filters at the module level and takes
 // precedence over the allowlists in allowlists/allowlists.go.
 func (c *Module) IsMixedBuildSupported(ctx android.BaseModuleContext) bool {
-	if c.testBinary() && !android.InList(c.Name(), mixedBuildSupportedCcTest) {
+	_, isForTesting := ctx.Config().BazelContext.(android.MockBazelContext)
+	if c.testBinary() && !android.InList(c.Name(), mixedBuildSupportedCcTest) && !isForTesting {
 		// Per-module rollout of mixed-builds for cc_test modules.
 		return false
 	}
@@ -1880,12 +1913,6 @@
 
 func (c *Module) ProcessBazelQueryResponse(ctx android.ModuleContext) {
 	bazelModuleLabel := c.getBazelModuleLabel(ctx)
-
-	bazelCtx := ctx.Config().BazelContext
-	if ccInfo, err := bazelCtx.GetCcInfo(bazelModuleLabel, android.GetConfigKey(ctx)); err == nil {
-		c.tidyFiles = android.PathsForBazelOut(ctx, ccInfo.TidyFiles)
-	}
-
 	c.bazelHandler.ProcessBazelQueryResponse(ctx, bazelModuleLabel)
 
 	c.Properties.SubName = GetSubnameProperty(ctx, c)
@@ -2081,6 +2108,12 @@
 	}
 }
 
+func (c *Module) setAndroidMkVariablesFromCquery(info cquery.CcAndroidMkInfo) {
+	c.Properties.AndroidMkSharedLibs = info.LocalSharedLibs
+	c.Properties.AndroidMkStaticLibs = info.LocalStaticLibs
+	c.Properties.AndroidMkWholeStaticLibs = info.LocalWholeStaticLibs
+}
+
 func (c *Module) toolchain(ctx android.BaseModuleContext) config.Toolchain {
 	if c.cachedToolchain == nil {
 		c.cachedToolchain = config.FindToolchainWithContext(ctx)
@@ -2212,6 +2245,13 @@
 		if err != nil {
 			ctx.PropertyErrorf("min_sdk_version", err.Error())
 		}
+
+		// Raise the minSdkVersion to the minimum supported for the architecture.
+		minApiForArch := MinApiForArch(ctx, m.Target().Arch.ArchType)
+		if apiLevel.LessThan(minApiForArch) {
+			apiLevel = minApiForArch
+		}
+
 		return []blueprint.Variation{
 			{Mutator: "sdk", Variation: "sdk"},
 			{Mutator: "version", Variation: apiLevel.String()},
@@ -3693,7 +3733,7 @@
 	// This allows introducing new architectures in the platform that
 	// need to be included in apexes that normally require an older
 	// min_sdk_version.
-	minApiForArch := minApiForArch(ctx, c.Target().Arch.ArchType)
+	minApiForArch := MinApiForArch(ctx, c.Target().Arch.ArchType)
 	if sdkVersion.LessThan(minApiForArch) {
 		sdkVersion = minApiForArch
 	}
diff --git a/cc/cc_test.go b/cc/cc_test.go
index 6dfd395..62adfd3 100644
--- a/cc/cc_test.go
+++ b/cc/cc_test.go
@@ -25,6 +25,7 @@
 	"testing"
 
 	"android/soong/android"
+	"android/soong/bazel/cquery"
 )
 
 func TestMain(m *testing.M) {
@@ -3054,6 +3055,253 @@
 	checkStaticLibs(t, []string{"lib1", "libc++_static", "libc++demangle", "libclang_rt.builtins"}, module)
 }
 
+func TestLibDepAndroidMkExportInMixedBuilds(t *testing.T) {
+	bp := `
+		cc_library {
+			name: "static_dep",
+		}
+		cc_library {
+			name: "whole_static_dep",
+		}
+		cc_library {
+			name: "shared_dep",
+		}
+		cc_library {
+			name: "lib",
+			bazel_module: { label: "//:lib" },
+			static_libs: ["static_dep"],
+			whole_static_libs: ["whole_static_dep"],
+			shared_libs: ["shared_dep"],
+		}
+		cc_test {
+			name: "test",
+			bazel_module: { label: "//:test" },
+			static_libs: ["static_dep"],
+			whole_static_libs: ["whole_static_dep"],
+			shared_libs: ["shared_dep"],
+			gtest: false,
+		}
+		cc_binary {
+			name: "binary",
+			bazel_module: { label: "//:binary" },
+			static_libs: ["static_dep"],
+			whole_static_libs: ["whole_static_dep"],
+			shared_libs: ["shared_dep"],
+		}
+		cc_library_headers {
+			name: "lib_headers",
+			bazel_module: { label: "//:lib_headers" },
+			static_libs: ["static_dep"],
+			whole_static_libs: ["whole_static_dep"],
+			shared_libs: ["shared_dep"],
+		}
+		cc_prebuilt_library {
+			name: "lib_prebuilt",
+			bazel_module: { label: "//:lib_prebuilt" },
+			static_libs: ["static_dep"],
+			whole_static_libs: ["whole_static_dep"],
+			shared_libs: ["shared_dep"],
+		}
+	`
+
+	testCases := []struct {
+		name          string
+		moduleName    string
+		variant       string
+		androidMkInfo cquery.CcAndroidMkInfo
+	}{
+		{
+			name:       "shared lib",
+			moduleName: "lib",
+			variant:    "android_arm64_armv8-a_shared",
+			androidMkInfo: cquery.CcAndroidMkInfo{
+				LocalStaticLibs:      []string{"static_dep"},
+				LocalWholeStaticLibs: []string{"whole_static_dep"},
+				LocalSharedLibs:      []string{"shared_dep"},
+			},
+		},
+		{
+			name:       "static lib",
+			moduleName: "lib",
+			variant:    "android_arm64_armv8-a_static",
+			androidMkInfo: cquery.CcAndroidMkInfo{
+				LocalStaticLibs:      []string{"static_dep"},
+				LocalWholeStaticLibs: []string{"whole_static_dep"},
+				LocalSharedLibs:      []string{"shared_dep"},
+			},
+		},
+		{
+			name:       "cc_test arm64",
+			moduleName: "test",
+			variant:    "android_arm64_armv8-a",
+			androidMkInfo: cquery.CcAndroidMkInfo{
+				LocalStaticLibs:      []string{"static_dep"},
+				LocalWholeStaticLibs: []string{"whole_static_dep"},
+				LocalSharedLibs:      []string{"shared_dep"},
+			},
+		},
+		{
+			name:       "cc_test arm",
+			moduleName: "test",
+			variant:    "android_arm_armv7-a-neon",
+			androidMkInfo: cquery.CcAndroidMkInfo{
+				LocalStaticLibs:      []string{"static_dep"},
+				LocalWholeStaticLibs: []string{"whole_static_dep"},
+				LocalSharedLibs:      []string{"shared_dep"},
+			},
+		},
+		{
+			name:       "cc_binary",
+			moduleName: "binary",
+			variant:    "android_arm64_armv8-a",
+			androidMkInfo: cquery.CcAndroidMkInfo{
+				LocalStaticLibs:      []string{"static_dep"},
+				LocalWholeStaticLibs: []string{"whole_static_dep"},
+				LocalSharedLibs:      []string{"shared_dep"},
+			},
+		},
+		{
+			name:       "cc_library_headers",
+			moduleName: "lib_headers",
+			variant:    "android_arm64_armv8-a",
+			androidMkInfo: cquery.CcAndroidMkInfo{
+				LocalStaticLibs:      []string{"static_dep"},
+				LocalWholeStaticLibs: []string{"whole_static_dep"},
+				LocalSharedLibs:      []string{"shared_dep"},
+			},
+		},
+		{
+			name:       "prebuilt lib static",
+			moduleName: "lib_prebuilt",
+			variant:    "android_arm64_armv8-a_static",
+			androidMkInfo: cquery.CcAndroidMkInfo{
+				LocalStaticLibs:      []string{"static_dep"},
+				LocalWholeStaticLibs: []string{"whole_static_dep"},
+				LocalSharedLibs:      []string{"shared_dep"},
+			},
+		},
+		{
+			name:       "prebuilt lib shared",
+			moduleName: "lib_prebuilt",
+			variant:    "android_arm64_armv8-a_shared",
+			androidMkInfo: cquery.CcAndroidMkInfo{
+				LocalStaticLibs:      []string{"static_dep"},
+				LocalWholeStaticLibs: []string{"whole_static_dep"},
+				LocalSharedLibs:      []string{"shared_dep"},
+			},
+		},
+	}
+
+	outputBaseDir := "out/bazel"
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			result := android.GroupFixturePreparers(
+				prepareForCcTest,
+				android.FixtureModifyConfig(func(config android.Config) {
+					config.BazelContext = android.MockBazelContext{
+						OutputBaseDir: outputBaseDir,
+						LabelToCcInfo: map[string]cquery.CcInfo{
+							"//:lib": cquery.CcInfo{
+								CcAndroidMkInfo:      tc.androidMkInfo,
+								RootDynamicLibraries: []string{""},
+							},
+							"//:lib_bp2build_cc_library_static": cquery.CcInfo{
+								CcAndroidMkInfo:    tc.androidMkInfo,
+								RootStaticArchives: []string{""},
+							},
+							"//:lib_headers": cquery.CcInfo{
+								CcAndroidMkInfo: tc.androidMkInfo,
+								OutputFiles:     []string{""},
+							},
+							"//:lib_prebuilt": cquery.CcInfo{
+								CcAndroidMkInfo: tc.androidMkInfo,
+							},
+							"//:lib_prebuilt_bp2build_cc_library_static": cquery.CcInfo{
+								CcAndroidMkInfo: tc.androidMkInfo,
+							},
+						},
+						LabelToCcBinary: map[string]cquery.CcUnstrippedInfo{
+							"//:test": cquery.CcUnstrippedInfo{
+								CcAndroidMkInfo: tc.androidMkInfo,
+							},
+							"//:binary": cquery.CcUnstrippedInfo{
+								CcAndroidMkInfo: tc.androidMkInfo,
+							},
+						},
+					}
+				}),
+			).RunTestWithBp(t, bp)
+			ctx := result.TestContext
+
+			module := ctx.ModuleForTests(tc.moduleName, tc.variant).Module().(*Module)
+			entries := android.AndroidMkEntriesForTest(t, ctx, module)[0]
+			if !reflect.DeepEqual(module.Properties.AndroidMkStaticLibs, tc.androidMkInfo.LocalStaticLibs) {
+				t.Errorf("incorrect static_libs"+
+					"\nactual:   %v"+
+					"\nexpected: %v",
+					module.Properties.AndroidMkStaticLibs,
+					tc.androidMkInfo.LocalStaticLibs,
+				)
+			}
+			staticDepsDiffer, missingStaticDeps, additionalStaticDeps := android.ListSetDifference(
+				entries.EntryMap["LOCAL_STATIC_LIBRARIES"],
+				tc.androidMkInfo.LocalStaticLibs,
+			)
+			if staticDepsDiffer {
+				t.Errorf(
+					"expected LOCAL_STATIC_LIBRARIES to be %q but was %q; missing: %q; extra %q",
+					tc.androidMkInfo.LocalStaticLibs,
+					entries.EntryMap["LOCAL_STATIC_LIBRARIES"],
+					missingStaticDeps,
+					additionalStaticDeps,
+				)
+			}
+
+			if !reflect.DeepEqual(module.Properties.AndroidMkWholeStaticLibs, tc.androidMkInfo.LocalWholeStaticLibs) {
+				t.Errorf("expected module.Properties.AndroidMkWholeStaticLibs to be %q, but was %q",
+					tc.androidMkInfo.LocalWholeStaticLibs,
+					module.Properties.AndroidMkWholeStaticLibs,
+				)
+			}
+			wholeStaticDepsDiffer, missingWholeStaticDeps, additionalWholeStaticDeps := android.ListSetDifference(
+				entries.EntryMap["LOCAL_WHOLE_STATIC_LIBRARIES"],
+				tc.androidMkInfo.LocalWholeStaticLibs,
+			)
+			if wholeStaticDepsDiffer {
+				t.Errorf(
+					"expected LOCAL_WHOLE_STATIC_LIBRARIES to be %q but was %q; missing: %q; extra %q",
+					tc.androidMkInfo.LocalWholeStaticLibs,
+					entries.EntryMap["LOCAL_WHOLE_STATIC_LIBRARIES"],
+					missingWholeStaticDeps,
+					additionalWholeStaticDeps,
+				)
+			}
+
+			if !reflect.DeepEqual(module.Properties.AndroidMkSharedLibs, tc.androidMkInfo.LocalSharedLibs) {
+				t.Errorf("incorrect shared_libs"+
+					"\nactual:   %v"+
+					"\nexpected: %v",
+					module.Properties.AndroidMkSharedLibs,
+					tc.androidMkInfo.LocalSharedLibs,
+				)
+			}
+			sharedDepsDiffer, missingSharedDeps, additionalSharedDeps := android.ListSetDifference(
+				entries.EntryMap["LOCAL_SHARED_LIBRARIES"],
+				tc.androidMkInfo.LocalSharedLibs,
+			)
+			if sharedDepsDiffer {
+				t.Errorf(
+					"expected LOCAL_SHARED_LIBRARIES to be %q but was %q; missing %q; extra %q",
+					tc.androidMkInfo.LocalSharedLibs,
+					entries.EntryMap["LOCAL_SHARED_LIBRARIES"],
+					missingSharedDeps,
+					additionalSharedDeps,
+				)
+			}
+		})
+	}
+}
+
 var compilerFlagsTestCases = []struct {
 	in  string
 	out bool
@@ -4483,6 +4731,39 @@
 
 }
 
+func TestAddnoOverride64GlobalCflags(t *testing.T) {
+	t.Parallel()
+	ctx := testCc(t, `
+		cc_library_shared {
+			name: "libclient",
+			srcs: ["foo.c"],
+			shared_libs: ["libfoo#1"],
+		}
+
+		cc_library_shared {
+			name: "libfoo",
+			srcs: ["foo.c"],
+			shared_libs: ["libbar"],
+			export_shared_lib_headers: ["libbar"],
+			stubs: {
+				symbol_file: "foo.map.txt",
+				versions: ["1", "2", "3"],
+			},
+		}
+
+		cc_library_shared {
+			name: "libbar",
+			export_include_dirs: ["include/libbar"],
+			srcs: ["foo.c"],
+		}`)
+
+	cFlags := ctx.ModuleForTests("libclient", "android_arm64_armv8-a_shared").Rule("cc").Args["cFlags"]
+
+	if !strings.Contains(cFlags, "${config.NoOverride64GlobalCflags}") {
+		t.Errorf("expected %q in cflags, got %q", "${config.NoOverride64GlobalCflags}", cFlags)
+	}
+}
+
 func TestCcBuildBrokenClangProperty(t *testing.T) {
 	t.Parallel()
 	tests := []struct {
diff --git a/cc/compiler.go b/cc/compiler.go
index a751754..88985b6 100644
--- a/cc/compiler.go
+++ b/cc/compiler.go
@@ -416,11 +416,6 @@
 
 	if ctx.apexVariationName() != "" {
 		flags.Global.CommonFlags = append(flags.Global.CommonFlags, "-D__ANDROID_APEX__")
-		if ctx.Device() {
-			flags.Global.CommonFlags = append(flags.Global.CommonFlags,
-				fmt.Sprintf("-D__ANDROID_APEX_MIN_SDK_VERSION__=%d",
-					ctx.apexSdkVersion().FinalOrFutureInt()))
-		}
 	}
 
 	if ctx.Target().NativeBridge == android.NativeBridgeEnabled {
diff --git a/cc/config/global.go b/cc/config/global.go
index 811e86e..454a4db 100644
--- a/cc/config/global.go
+++ b/cc/config/global.go
@@ -207,10 +207,7 @@
 		"-Werror=fortify-source",
 
 		"-Werror=address-of-temporary",
-		// Bug: http://b/29823425 Disable -Wnull-dereference until the
-		// new cases detected by this warning in Clang r271374 are
-		// fixed.
-		//"-Werror=null-dereference",
+		"-Werror=null-dereference",
 		"-Werror=return-type",
 
 		// http://b/72331526 Disable -Wtautological-* until the instances detected by these
@@ -247,6 +244,8 @@
 		"-Wno-error=enum-constexpr-conversion",               // http://b/243964282
 	}
 
+	noOverride64GlobalCflags = []string{}
+
 	noOverrideExternalGlobalCflags = []string{
 		// http://b/148815709
 		"-Wno-sizeof-array-div",
@@ -277,7 +276,6 @@
 
 		// http://b/145211477
 		"-Wno-pointer-compare",
-		// http://b/145211022
 		"-Wno-final-dtor-non-final-class",
 
 		// http://b/165945989
@@ -291,6 +289,9 @@
 
 		// http://b/239661264
 		"-Wno-deprecated-non-prototype",
+
+		// http://b/191699019
+		"-Wno-format-insufficient-args",
 	}
 
 	llvmNextExtraCommonGlobalCflags = []string{
@@ -394,6 +395,7 @@
 		return strings.Join(flags, " ")
 	})
 
+	exportedVars.ExportStringListStaticVariable("NoOverride64GlobalCflags", noOverride64GlobalCflags)
 	exportedVars.ExportStringListStaticVariable("HostGlobalCflags", hostGlobalCflags)
 	exportedVars.ExportStringListStaticVariable("NoOverrideExternalGlobalCflags", noOverrideExternalGlobalCflags)
 	exportedVars.ExportStringListStaticVariable("CommonGlobalCppflags", commonGlobalCppflags)
@@ -429,7 +431,7 @@
 	pctx.StaticVariable("ClangBin", "${ClangPath}/bin")
 
 	pctx.StaticVariableWithEnvOverride("ClangShortVersion", "LLVM_RELEASE_VERSION", ClangDefaultShortVersion)
-	pctx.StaticVariable("ClangAsanLibDir", "${ClangBase}/linux-x86/${ClangVersion}/lib64/clang/${ClangShortVersion}/lib/linux")
+	pctx.StaticVariable("ClangAsanLibDir", "${ClangBase}/linux-x86/${ClangVersion}/lib/clang/${ClangShortVersion}/lib/linux")
 
 	// These are tied to the version of LLVM directly in external/llvm, so they might trail the host prebuilts
 	// being used for the rest of the build process.
diff --git a/cc/fuzz.go b/cc/fuzz.go
index 7113d87..7aa8b91 100644
--- a/cc/fuzz.go
+++ b/cc/fuzz.go
@@ -212,7 +212,7 @@
 	return true
 }
 
-func sharedLibraryInstallLocation(
+func SharedLibraryInstallLocation(
 	libraryPath android.Path, isHost bool, fuzzDir string, archString string) string {
 	installLocation := "$(PRODUCT_OUT)/data"
 	if isHost {
@@ -224,7 +224,7 @@
 }
 
 // Get the device-only shared library symbols install directory.
-func sharedLibrarySymbolsInstallLocation(libraryPath android.Path, fuzzDir string, archString string) string {
+func SharedLibrarySymbolsInstallLocation(libraryPath android.Path, fuzzDir string, archString string) string {
 	return filepath.Join("$(PRODUCT_OUT)/symbols/data/", fuzzDir, archString, "/lib/", libraryPath.Base())
 }
 
@@ -237,59 +237,64 @@
 		installBase, ctx.Target().Arch.ArchType.String(), ctx.ModuleName())
 	fuzzBin.binaryDecorator.baseInstaller.install(ctx, file)
 
-	fuzzBin.fuzzPackagedModule.Corpus = android.PathsForModuleSrc(ctx, fuzzBin.fuzzPackagedModule.FuzzProperties.Corpus)
-	builder := android.NewRuleBuilder(pctx, ctx)
-	intermediateDir := android.PathForModuleOut(ctx, "corpus")
-	for _, entry := range fuzzBin.fuzzPackagedModule.Corpus {
-		builder.Command().Text("cp").
-			Input(entry).
-			Output(intermediateDir.Join(ctx, entry.Base()))
-	}
-	builder.Build("copy_corpus", "copy corpus")
-	fuzzBin.fuzzPackagedModule.CorpusIntermediateDir = intermediateDir
-
-	fuzzBin.fuzzPackagedModule.Data = android.PathsForModuleSrc(ctx, fuzzBin.fuzzPackagedModule.FuzzProperties.Data)
-	builder = android.NewRuleBuilder(pctx, ctx)
-	intermediateDir = android.PathForModuleOut(ctx, "data")
-	for _, entry := range fuzzBin.fuzzPackagedModule.Data {
-		builder.Command().Text("cp").
-			Input(entry).
-			Output(intermediateDir.Join(ctx, entry.Rel()))
-	}
-	builder.Build("copy_data", "copy data")
-	fuzzBin.fuzzPackagedModule.DataIntermediateDir = intermediateDir
-
-	if fuzzBin.fuzzPackagedModule.FuzzProperties.Dictionary != nil {
-		fuzzBin.fuzzPackagedModule.Dictionary = android.PathForModuleSrc(ctx, *fuzzBin.fuzzPackagedModule.FuzzProperties.Dictionary)
-		if fuzzBin.fuzzPackagedModule.Dictionary.Ext() != ".dict" {
-			ctx.PropertyErrorf("dictionary",
-				"Fuzzer dictionary %q does not have '.dict' extension",
-				fuzzBin.fuzzPackagedModule.Dictionary.String())
-		}
-	}
-
-	if fuzzBin.fuzzPackagedModule.FuzzProperties.Fuzz_config != nil {
-		configPath := android.PathForModuleOut(ctx, "config").Join(ctx, "config.json")
-		android.WriteFileRule(ctx, configPath, fuzzBin.fuzzPackagedModule.FuzzProperties.Fuzz_config.String())
-		fuzzBin.fuzzPackagedModule.Config = configPath
-	}
+	fuzzBin.fuzzPackagedModule = PackageFuzzModule(ctx, fuzzBin.fuzzPackagedModule, pctx)
 
 	// Grab the list of required shared libraries.
 	fuzzBin.sharedLibraries, _ = CollectAllSharedDependencies(ctx)
 
 	for _, lib := range fuzzBin.sharedLibraries {
 		fuzzBin.installedSharedDeps = append(fuzzBin.installedSharedDeps,
-			sharedLibraryInstallLocation(
+			SharedLibraryInstallLocation(
 				lib, ctx.Host(), installBase, ctx.Arch().ArchType.String()))
 
 		// Also add the dependency on the shared library symbols dir.
 		if !ctx.Host() {
 			fuzzBin.installedSharedDeps = append(fuzzBin.installedSharedDeps,
-				sharedLibrarySymbolsInstallLocation(lib, installBase, ctx.Arch().ArchType.String()))
+				SharedLibrarySymbolsInstallLocation(lib, installBase, ctx.Arch().ArchType.String()))
 		}
 	}
 }
 
+func PackageFuzzModule(ctx android.ModuleContext, fuzzPackagedModule fuzz.FuzzPackagedModule, pctx android.PackageContext) fuzz.FuzzPackagedModule {
+	fuzzPackagedModule.Corpus = android.PathsForModuleSrc(ctx, fuzzPackagedModule.FuzzProperties.Corpus)
+	builder := android.NewRuleBuilder(pctx, ctx)
+	intermediateDir := android.PathForModuleOut(ctx, "corpus")
+	for _, entry := range fuzzPackagedModule.Corpus {
+		builder.Command().Text("cp").
+			Input(entry).
+			Output(intermediateDir.Join(ctx, entry.Base()))
+	}
+	builder.Build("copy_corpus", "copy corpus")
+	fuzzPackagedModule.CorpusIntermediateDir = intermediateDir
+
+	fuzzPackagedModule.Data = android.PathsForModuleSrc(ctx, fuzzPackagedModule.FuzzProperties.Data)
+	builder = android.NewRuleBuilder(pctx, ctx)
+	intermediateDir = android.PathForModuleOut(ctx, "data")
+	for _, entry := range fuzzPackagedModule.Data {
+		builder.Command().Text("cp").
+			Input(entry).
+			Output(intermediateDir.Join(ctx, entry.Rel()))
+	}
+	builder.Build("copy_data", "copy data")
+	fuzzPackagedModule.DataIntermediateDir = intermediateDir
+
+	if fuzzPackagedModule.FuzzProperties.Dictionary != nil {
+		fuzzPackagedModule.Dictionary = android.PathForModuleSrc(ctx, *fuzzPackagedModule.FuzzProperties.Dictionary)
+		if fuzzPackagedModule.Dictionary.Ext() != ".dict" {
+			ctx.PropertyErrorf("dictionary",
+				"Fuzzer dictionary %q does not have '.dict' extension",
+				fuzzPackagedModule.Dictionary.String())
+		}
+	}
+
+	if fuzzPackagedModule.FuzzProperties.Fuzz_config != nil {
+		configPath := android.PathForModuleOut(ctx, "config").Join(ctx, "config.json")
+		android.WriteFileRule(ctx, configPath, fuzzPackagedModule.FuzzProperties.Fuzz_config.String())
+		fuzzPackagedModule.Config = configPath
+	}
+	return fuzzPackagedModule
+}
+
 func NewFuzzer(hod android.HostOrDeviceSupported) *Module {
 	module, binary := newBinary(hod, false)
 	baseInstallerPath := "fuzz"
@@ -344,7 +349,7 @@
 
 // Responsible for generating GNU Make rules that package fuzz targets into
 // their architecture & target/host specific zip file.
-type ccFuzzPackager struct {
+type ccRustFuzzPackager struct {
 	fuzz.FuzzPackager
 	fuzzPackagingArchModules         string
 	fuzzTargetSharedDepsInstallPairs string
@@ -353,7 +358,7 @@
 
 func fuzzPackagingFactory() android.Singleton {
 
-	fuzzPackager := &ccFuzzPackager{
+	fuzzPackager := &ccRustFuzzPackager{
 		fuzzPackagingArchModules:         "SOONG_FUZZ_PACKAGING_ARCH_MODULES",
 		fuzzTargetSharedDepsInstallPairs: "FUZZ_TARGET_SHARED_DEPS_INSTALL_PAIRS",
 		allFuzzTargetsName:               "ALL_FUZZ_TARGETS",
@@ -361,7 +366,7 @@
 	return fuzzPackager
 }
 
-func (s *ccFuzzPackager) GenerateBuildActions(ctx android.SingletonContext) {
+func (s *ccRustFuzzPackager) GenerateBuildActions(ctx android.SingletonContext) {
 	// Map between each architecture + host/device combination, and the files that
 	// need to be packaged (in the tuple of {source file, destination folder in
 	// archive}).
@@ -376,19 +381,18 @@
 	sharedLibraryInstalled := make(map[string]bool)
 
 	ctx.VisitAllModules(func(module android.Module) {
-		ccModule, ok := module.(*Module)
-		if !ok || ccModule.Properties.PreventInstall {
+		ccModule, ok := module.(LinkableInterface)
+		if !ok || ccModule.PreventInstall() {
 			return
 		}
 
 		// Discard non-fuzz targets.
-		if ok := fuzz.IsValid(ccModule.FuzzModule); !ok {
+		if ok := fuzz.IsValid(ccModule.FuzzModuleStruct()); !ok {
 			return
 		}
 
 		sharedLibsInstallDirPrefix := "lib"
-		fuzzModule, ok := ccModule.compiler.(*fuzzBinary)
-		if !ok {
+		if !ccModule.IsFuzzModule() {
 			return
 		}
 
@@ -399,12 +403,12 @@
 
 		fpm := fuzz.FuzzPackagedModule{}
 		if ok {
-			fpm = fuzzModule.fuzzPackagedModule
+			fpm = ccModule.FuzzPackagedModule()
 		}
 
 		intermediatePath := "fuzz"
 
-		archString := ccModule.Arch().ArchType.String()
+		archString := ccModule.Target().Arch.ArchType.String()
 		archDir := android.PathForIntermediates(ctx, intermediatePath, hostOrTargetString, archString)
 		archOs := fuzz.ArchOs{HostOrTarget: hostOrTargetString, Arch: archString, Dir: archDir.String()}
 
@@ -415,7 +419,7 @@
 		files = s.PackageArtifacts(ctx, module, fpm, archDir, builder)
 
 		// Package shared libraries
-		files = append(files, GetSharedLibsToZip(fuzzModule.sharedLibraries, ccModule, &s.FuzzPackager, archString, sharedLibsInstallDirPrefix, &sharedLibraryInstalled)...)
+		files = append(files, GetSharedLibsToZip(ccModule.FuzzSharedLibraries(), ccModule, &s.FuzzPackager, archString, sharedLibsInstallDirPrefix, &sharedLibraryInstalled)...)
 
 		// The executable.
 		files = append(files, fuzz.FileToZip{android.OutputFileForModule(ctx, ccModule, "unstripped"), ""})
@@ -429,7 +433,7 @@
 	s.CreateFuzzPackage(ctx, archDirs, fuzz.Cc, pctx)
 }
 
-func (s *ccFuzzPackager) MakeVars(ctx android.MakeVarsContext) {
+func (s *ccRustFuzzPackager) MakeVars(ctx android.MakeVarsContext) {
 	packages := s.Packages.Strings()
 	sort.Strings(packages)
 	sort.Strings(s.FuzzPackager.SharedLibInstallStrings)
@@ -460,7 +464,7 @@
 		// For each architecture-specific shared library dependency, we need to
 		// install it to the output directory. Setup the install destination here,
 		// which will be used by $(copy-many-files) in the Make backend.
-		installDestination := sharedLibraryInstallLocation(
+		installDestination := SharedLibraryInstallLocation(
 			library, module.Host(), fuzzDir, archString)
 		if (*sharedLibraryInstalled)[installDestination] {
 			continue
@@ -479,7 +483,7 @@
 		// we want symbolization tools (like `stack`) to be able to find the symbols
 		// in $ANDROID_PRODUCT_OUT/symbols automagically.
 		if !module.Host() {
-			symbolsInstallDestination := sharedLibrarySymbolsInstallLocation(library, fuzzDir, archString)
+			symbolsInstallDestination := SharedLibrarySymbolsInstallLocation(library, fuzzDir, archString)
 			symbolsInstallDestination = strings.ReplaceAll(symbolsInstallDestination, "$", "$$")
 			s.SharedLibInstallStrings = append(s.SharedLibInstallStrings,
 				library.String()+":"+symbolsInstallDestination)
diff --git a/cc/library.go b/cc/library.go
index 1291f5c..9421007 100644
--- a/cc/library.go
+++ b/cc/library.go
@@ -475,10 +475,10 @@
 func apiContributionBp2Build(ctx android.TopDownMutatorContext, module *Module) {
 	apiSurfaces := make([]string, 0)
 	apiHeaders := make([]string, 0)
-	// systemapi (non-null `stubs` property)
+	// module-libapi for apexes (non-null `stubs` property)
 	if module.HasStubsVariants() {
-		apiSurfaces = append(apiSurfaces, android.SystemApi.String())
-		apiIncludes := getSystemApiIncludes(ctx, module)
+		apiSurfaces = append(apiSurfaces, android.ModuleLibApi.String())
+		apiIncludes := getModuleLibApiIncludes(ctx, module)
 		if !apiIncludes.isEmpty() {
 			createApiHeaderTarget(ctx, apiIncludes)
 			apiHeaders = append(apiHeaders, apiIncludes.name)
@@ -494,8 +494,8 @@
 		}
 	}
 	// create a target only if this module contributes to an api surface
-	// TODO: Currently this does not distinguish systemapi-only headers and vendrorapi-only headers
-	// TODO: Update so that systemapi-only headers do not get exported to vendorapi (and vice-versa)
+	// TODO: Currently this does not distinguish modulelibapi-only headers and vendrorapi-only headers
+	// TODO: Update so that modulelibapi-only headers do not get exported to vendorapi (and vice-versa)
 	if len(apiSurfaces) > 0 {
 		props := bazel.BazelTargetModuleProperties{
 			Rule_class:        "cc_api_contribution",
@@ -527,8 +527,8 @@
 	linker := module.linker.(*libraryDecorator)
 	if llndkApi := linker.Properties.Llndk.Symbol_file; llndkApi != nil {
 		apiFile = llndkApi
-	} else if systemApi := linker.Properties.Stubs.Symbol_file; systemApi != nil {
-		apiFile = systemApi
+	} else if moduleLibApi := linker.Properties.Stubs.Symbol_file; moduleLibApi != nil {
+		apiFile = moduleLibApi
 	} else {
 		ctx.ModuleErrorf("API surface library does not have any API file")
 	}
@@ -566,7 +566,8 @@
 	includes.attrs.Deps.Append(lla)
 }
 
-func getSystemApiIncludes(ctx android.TopDownMutatorContext, c *Module) apiIncludes {
+// includes provided to the module-lib API surface. This API surface is used by apexes.
+func getModuleLibApiIncludes(ctx android.TopDownMutatorContext, c *Module) apiIncludes {
 	flagProps := c.library.(*libraryDecorator).flagExporter.Properties
 	linkProps := c.library.(*libraryDecorator).baseLinker.Properties
 	includes := android.FirstUniqueStrings(flagProps.Export_include_dirs)
@@ -579,7 +580,7 @@
 	}
 
 	return apiIncludes{
-		name: c.Name() + ".systemapi.headers",
+		name: c.Name() + ".module-libapi.headers",
 		attrs: bazelCcApiLibraryHeadersAttributes{
 			bazelCcLibraryHeadersAttributes: attrs,
 		},
@@ -844,7 +845,11 @@
 		ctx.ModuleErrorf("expected exactly one root archive file for '%s', but got %s", label, rootStaticArchives)
 		return
 	}
-	outputFilePath := android.PathForBazelOut(ctx, rootStaticArchives[0])
+	var outputFilePath android.Path = android.PathForBazelOut(ctx, rootStaticArchives[0])
+	if len(ccInfo.TidyFiles) > 0 {
+		handler.module.tidyFiles = android.PathsForBazelOut(ctx, ccInfo.TidyFiles)
+		outputFilePath = android.AttachValidationActions(ctx, outputFilePath, handler.module.tidyFiles)
+	}
 	handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
 
 	objPaths := ccInfo.CcObjectFiles
@@ -880,9 +885,13 @@
 		ctx.ModuleErrorf("expected exactly one root dynamic library file for '%s', but got %s", label, rootDynamicLibraries)
 		return
 	}
-	outputFilePath := android.PathForBazelOut(ctx, rootDynamicLibraries[0])
-	handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
+	var outputFilePath android.Path = android.PathForBazelOut(ctx, rootDynamicLibraries[0])
+	if len(ccInfo.TidyFiles) > 0 {
+		handler.module.tidyFiles = android.PathsForBazelOut(ctx, ccInfo.TidyFiles)
+		outputFilePath = android.AttachValidationActions(ctx, outputFilePath, handler.module.tidyFiles)
+	}
 
+	handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
 	handler.module.linker.(*libraryDecorator).unstrippedOutputFile = android.PathForBazelOut(ctx, ccInfo.UnstrippedOutput)
 
 	var tocFile android.OptionalPath
@@ -936,6 +945,8 @@
 		// implementation.
 		i.(*libraryDecorator).collectedSnapshotHeaders = android.Paths{}
 	}
+
+	handler.module.setAndroidMkVariablesFromCquery(ccInfo.CcAndroidMkInfo)
 }
 
 func (library *libraryDecorator) setFlagExporterInfoFromCcInfo(ctx android.ModuleContext, ccInfo cquery.CcInfo) {
diff --git a/cc/library_headers.go b/cc/library_headers.go
index 4d38068..32ea1d4 100644
--- a/cc/library_headers.go
+++ b/cc/library_headers.go
@@ -76,7 +76,11 @@
 		return
 	}
 
-	outputPath := android.PathForBazelOut(ctx, outputPaths[0])
+	var outputPath android.Path = android.PathForBazelOut(ctx, outputPaths[0])
+	if len(ccInfo.TidyFiles) > 0 {
+		h.module.tidyFiles = android.PathsForBazelOut(ctx, ccInfo.TidyFiles)
+		outputPath = android.AttachValidationActions(ctx, outputPath, h.module.tidyFiles)
+	}
 	h.module.outputFile = android.OptionalPathForPath(outputPath)
 
 	// HeaderLibraryInfo is an empty struct to indicate to dependencies that this is a header library
@@ -88,6 +92,8 @@
 	// validation will fail. For now, set this to an empty list.
 	// TODO(cparsons): More closely mirror the collectHeadersForSnapshot implementation.
 	h.library.collectedSnapshotHeaders = android.Paths{}
+
+	h.module.setAndroidMkVariablesFromCquery(ccInfo.CcAndroidMkInfo)
 }
 
 // cc_library_headers contains a set of c/c++ headers which are imported by
@@ -169,7 +175,7 @@
 	// For API export, create a top-level arch-agnostic target and list the arch-specific targets as its deps
 
 	// arch-agnostic includes
-	apiIncludes := getSystemApiIncludes(ctx, module)
+	apiIncludes := getModuleLibApiIncludes(ctx, module)
 	// arch and os specific includes
 	archApiIncludes, androidOsIncludes := archOsSpecificApiIncludes(ctx, module)
 	for _, arch := range allArches { // sorted iteration
@@ -186,7 +192,7 @@
 	}
 
 	if !apiIncludes.isEmpty() {
-		// override the name from <mod>.systemapi.headers --> <mod>.contribution
+		// override the name from <mod>.module-libapi.headers --> <mod>.contribution
 		apiIncludes.name = android.ApiContributionTargetName(module.Name())
 		createApiHeaderTarget(ctx, apiIncludes)
 	}
diff --git a/cc/library_test.go b/cc/library_test.go
index dab5bb8..de3db99 100644
--- a/cc/library_test.go
+++ b/cc/library_test.go
@@ -308,6 +308,75 @@
 	android.AssertPathsRelativeToTopEquals(t, "deps", []string{"outputbase/execroot/__main__/foo.h"}, flagExporter.Deps)
 }
 
+func TestCcLibraryWithBazelValidations(t *testing.T) {
+	t.Parallel()
+	bp := `
+cc_library {
+	name: "foo",
+	srcs: ["foo.cc"],
+	bazel_module: { label: "//foo/bar:bar" },
+	tidy: true,
+}`
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
+	config.BazelContext = android.MockBazelContext{
+		OutputBaseDir: "outputbase",
+		LabelToCcInfo: map[string]cquery.CcInfo{
+			"//foo/bar:bar": cquery.CcInfo{
+				CcObjectFiles:        []string{"foo.o"},
+				Includes:             []string{"include"},
+				SystemIncludes:       []string{"system_include"},
+				Headers:              []string{"foo.h"},
+				RootDynamicLibraries: []string{"foo.so"},
+				UnstrippedOutput:     "foo_unstripped.so",
+			},
+			"//foo/bar:bar_bp2build_cc_library_static": cquery.CcInfo{
+				CcObjectFiles:      []string{"foo.o"},
+				Includes:           []string{"include"},
+				SystemIncludes:     []string{"system_include"},
+				Headers:            []string{"foo.h"},
+				RootStaticArchives: []string{"foo.a"},
+				TidyFiles:          []string{"foo.c.tidy"},
+			},
+		},
+	}
+	ctx := android.GroupFixturePreparers(
+		prepareForCcTest,
+		android.FixtureMergeEnv(map[string]string{
+			"ALLOW_LOCAL_TIDY_TRUE": "1",
+		}),
+	).RunTestWithConfig(t, config).TestContext
+
+	staticFoo := ctx.ModuleForTests("foo", "android_arm_armv7-a-neon_static").Module()
+	outputFiles, err := staticFoo.(android.OutputFileProducer).OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error getting cc_object outputfiles %s", err)
+	}
+
+	expectedOutputFiles := []string{"out/soong/.intermediates/foo/android_arm_armv7-a-neon_static/validated/foo.a"}
+	android.AssertPathsRelativeToTopEquals(t, "output files", expectedOutputFiles, outputFiles)
+
+	flagExporter := ctx.ModuleProvider(staticFoo, FlagExporterInfoProvider).(FlagExporterInfo)
+	android.AssertPathsRelativeToTopEquals(t, "exported include dirs", []string{"outputbase/execroot/__main__/include"}, flagExporter.IncludeDirs)
+	android.AssertPathsRelativeToTopEquals(t, "exported system include dirs", []string{"outputbase/execroot/__main__/system_include"}, flagExporter.SystemIncludeDirs)
+	android.AssertPathsRelativeToTopEquals(t, "exported headers", []string{"outputbase/execroot/__main__/foo.h"}, flagExporter.GeneratedHeaders)
+	android.AssertPathsRelativeToTopEquals(t, "deps", []string{"outputbase/execroot/__main__/foo.h"}, flagExporter.Deps)
+
+	sharedFoo := ctx.ModuleForTests("foo", "android_arm_armv7-a-neon_shared").Module()
+	outputFiles, err = sharedFoo.(android.OutputFileProducer).OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error getting cc_library outputfiles %s", err)
+	}
+	expectedOutputFiles = []string{"outputbase/execroot/__main__/foo.so"}
+	android.AssertDeepEquals(t, "output files", expectedOutputFiles, outputFiles.Strings())
+
+	android.AssertStringEquals(t, "unstripped shared library", "outputbase/execroot/__main__/foo_unstripped.so", sharedFoo.(*Module).linker.unstrippedOutputFilePath().String())
+	flagExporter = ctx.ModuleProvider(sharedFoo, FlagExporterInfoProvider).(FlagExporterInfo)
+	android.AssertPathsRelativeToTopEquals(t, "exported include dirs", []string{"outputbase/execroot/__main__/include"}, flagExporter.IncludeDirs)
+	android.AssertPathsRelativeToTopEquals(t, "exported system include dirs", []string{"outputbase/execroot/__main__/system_include"}, flagExporter.SystemIncludeDirs)
+	android.AssertPathsRelativeToTopEquals(t, "exported headers", []string{"outputbase/execroot/__main__/foo.h"}, flagExporter.GeneratedHeaders)
+	android.AssertPathsRelativeToTopEquals(t, "deps", []string{"outputbase/execroot/__main__/foo.h"}, flagExporter.Deps)
+}
+
 func TestLibraryVersionScript(t *testing.T) {
 	t.Parallel()
 	result := PrepareForIntegrationTestWithCc.RunTestWithBp(t, `
@@ -344,6 +413,59 @@
 
 }
 
+func TestCcLibrarySharedWithBazelValidations(t *testing.T) {
+	t.Parallel()
+	bp := `
+cc_library_shared {
+	name: "foo",
+	srcs: ["foo.cc"],
+	bazel_module: { label: "//foo/bar:bar" },
+	tidy: true,
+}`
+	config := TestConfig(t.TempDir(), android.Android, nil, bp, nil)
+	config.BazelContext = android.MockBazelContext{
+		OutputBaseDir: "outputbase",
+		LabelToCcInfo: map[string]cquery.CcInfo{
+			"//foo/bar:bar": cquery.CcInfo{
+				CcObjectFiles:        []string{"foo.o"},
+				Includes:             []string{"include"},
+				SystemIncludes:       []string{"system_include"},
+				RootDynamicLibraries: []string{"foo.so"},
+				TocFile:              "foo.so.toc",
+				TidyFiles:            []string{"foo.c.tidy"},
+			},
+		},
+	}
+	ctx := android.GroupFixturePreparers(
+		prepareForCcTest,
+		android.FixtureMergeEnv(map[string]string{
+			"ALLOW_LOCAL_TIDY_TRUE": "1",
+		}),
+	).RunTestWithConfig(t, config).TestContext
+
+	sharedFoo := ctx.ModuleForTests("foo", "android_arm_armv7-a-neon_shared").Module()
+	producer := sharedFoo.(android.OutputFileProducer)
+	outputFiles, err := producer.OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error getting cc_object outputfiles %s", err)
+	}
+	expectedOutputFiles := []string{"out/soong/.intermediates/foo/android_arm_armv7-a-neon_shared/validated/foo.so"}
+	android.AssertPathsRelativeToTopEquals(t, "output files", expectedOutputFiles, outputFiles)
+
+	tocFilePath := sharedFoo.(*Module).Toc()
+	if !tocFilePath.Valid() {
+		t.Errorf("Invalid tocFilePath: %s", tocFilePath)
+	}
+	tocFile := tocFilePath.Path()
+	expectedToc := "outputbase/execroot/__main__/foo.so.toc"
+	android.AssertStringEquals(t, "toc file", expectedToc, tocFile.String())
+
+	entries := android.AndroidMkEntriesForTest(t, ctx, sharedFoo)[0]
+	expectedFlags := []string{"-Ioutputbase/execroot/__main__/include", "-isystem outputbase/execroot/__main__/system_include"}
+	gotFlags := entries.EntryMap["LOCAL_EXPORT_CFLAGS"]
+	android.AssertDeepEquals(t, "androidmk exported cflags", expectedFlags, gotFlags)
+}
+
 func TestCcLibrarySharedWithBazel(t *testing.T) {
 	t.Parallel()
 	bp := `
diff --git a/cc/linkable.go b/cc/linkable.go
index 0522fc6..9578807 100644
--- a/cc/linkable.go
+++ b/cc/linkable.go
@@ -3,6 +3,7 @@
 import (
 	"android/soong/android"
 	"android/soong/bazel/cquery"
+	"android/soong/fuzz"
 	"android/soong/snapshot"
 
 	"github.com/google/blueprint"
@@ -120,6 +121,17 @@
 	IsPrebuilt() bool
 	Toc() android.OptionalPath
 
+	// IsFuzzModule returns true if this a *_fuzz module.
+	IsFuzzModule() bool
+
+	// FuzzPackagedModule returns the fuzz.FuzzPackagedModule for this module.
+	// Expects that IsFuzzModule returns true.
+	FuzzPackagedModule() fuzz.FuzzPackagedModule
+
+	// FuzzSharedLibraries returns the shared library dependencies for this module.
+	// Expects that IsFuzzModule returns true.
+	FuzzSharedLibraries() android.Paths
+
 	Device() bool
 	Host() bool
 
@@ -256,6 +268,9 @@
 
 	// Partition returns the partition string for this module.
 	Partition() string
+
+	// FuzzModule returns the fuzz.FuzzModule associated with the module.
+	FuzzModuleStruct() fuzz.FuzzModule
 }
 
 var (
diff --git a/cc/lto_test.go b/cc/lto_test.go
index fbd91be..cee5aa3 100644
--- a/cc/lto_test.go
+++ b/cc/lto_test.go
@@ -15,10 +15,11 @@
 package cc
 
 import (
-	"android/soong/android"
 	"strings"
 	"testing"
 
+	"android/soong/android"
+
 	"github.com/google/blueprint"
 )
 
@@ -177,3 +178,34 @@
 		t.Errorf("'baz' expected to have flags %q, but got %q", w, libFooCFlags)
 	}
 }
+
+func TestLtoDisabledButEnabledForArch(t *testing.T) {
+	t.Parallel()
+	bp := `
+	cc_library {
+		name: "libfoo",
+		srcs: ["foo.c"],
+		lto: {
+			never: true,
+		},
+		target: {
+			android_arm: {
+				lto: {
+					never: false,
+					thin: true,
+				},
+			},
+		},
+	}`
+	result := android.GroupFixturePreparers(
+		prepareForCcTest,
+	).RunTestWithBp(t, bp)
+
+	libFooWithLto := result.ModuleForTests("libfoo", "android_arm_armv7-a-neon_shared").Rule("ld")
+	libFooWithoutLto := result.ModuleForTests("libfoo", "android_arm64_armv8-a_shared").Rule("ld")
+
+	android.AssertStringDoesContain(t, "missing flag for LTO in variant that expects it",
+		libFooWithLto.Args["ldFlags"], "-flto=thin")
+	android.AssertStringDoesNotContain(t, "got flag for LTO in variant that doesn't expect it",
+		libFooWithoutLto.Args["ldFlags"], "-flto=thin")
+}
diff --git a/cc/makevars.go b/cc/makevars.go
index c70d4a6..6c3f551 100644
--- a/cc/makevars.go
+++ b/cc/makevars.go
@@ -93,6 +93,7 @@
 
 	ctx.Strict("CLANG_EXTERNAL_CFLAGS", "${config.ExternalCflags}")
 	ctx.Strict("GLOBAL_CLANG_CFLAGS_NO_OVERRIDE", "${config.NoOverrideGlobalCflags}")
+	ctx.Strict("GLOBAL_CLANG_CFLAGS_64_NO_OVERRIDE", "${config.NoOverride64GlobalCflags}")
 	ctx.Strict("GLOBAL_CLANG_CPPFLAGS_NO_OVERRIDE", "")
 	ctx.Strict("GLOBAL_CLANG_EXTERNAL_CFLAGS_NO_OVERRIDE", "${config.NoOverrideExternalGlobalCflags}")
 
diff --git a/cc/object.go b/cc/object.go
index 6cb1a30..11ce793 100644
--- a/cc/object.go
+++ b/cc/object.go
@@ -142,6 +142,7 @@
 	Absolute_includes   bazel.StringListAttribute
 	Stl                 *string
 	Linker_script       bazel.LabelAttribute
+	Crt                 *bool
 	sdkAttributes
 }
 
@@ -208,6 +209,7 @@
 		Absolute_includes:   compilerAttrs.absoluteIncludes,
 		Stl:                 compilerAttrs.stl,
 		Linker_script:       linkerScript,
+		Crt:                 m.linker.(*objectLinker).Properties.Crt,
 		sdkAttributes:       bp2BuildParseSdkAttributes(m),
 	}
 
diff --git a/cc/prebuilt.go b/cc/prebuilt.go
index af83278..03a600a 100644
--- a/cc/prebuilt.go
+++ b/cc/prebuilt.go
@@ -462,6 +462,8 @@
 	}
 
 	h.module.maybeUnhideFromMake()
+
+	h.module.setAndroidMkVariablesFromCquery(ccInfo.CcAndroidMkInfo)
 }
 
 func (h *prebuiltLibraryBazelHandler) processStaticBazelQueryResponse(ctx android.ModuleContext, label string, ccInfo cquery.CcInfo) bool {
@@ -486,13 +488,17 @@
 		return true
 	}
 
-	out := android.PathForBazelOut(ctx, staticLibs[0])
-	h.module.outputFile = android.OptionalPathForPath(out)
+	var outputPath android.Path = android.PathForBazelOut(ctx, staticLibs[0])
+	if len(ccInfo.TidyFiles) > 0 {
+		h.module.tidyFiles = android.PathsForBazelOut(ctx, ccInfo.TidyFiles)
+		outputPath = android.AttachValidationActions(ctx, outputPath, h.module.tidyFiles)
+	}
 
-	depSet := android.NewDepSetBuilder(android.TOPOLOGICAL).Direct(out).Build()
+	h.module.outputFile = android.OptionalPathForPath(outputPath)
+
+	depSet := android.NewDepSetBuilder(android.TOPOLOGICAL).Direct(outputPath).Build()
 	ctx.SetProvider(StaticLibraryInfoProvider, StaticLibraryInfo{
-		StaticLibrary: out,
-
+		StaticLibrary:                        outputPath,
 		TransitiveStaticLibrariesForOrdering: depSet,
 	})
 
@@ -516,21 +522,26 @@
 		return true
 	}
 
-	out := android.PathForBazelOut(ctx, sharedLibs[0])
-	h.module.outputFile = android.OptionalPathForPath(out)
+	var outputPath android.Path = android.PathForBazelOut(ctx, sharedLibs[0])
+	if len(ccInfo.TidyFiles) > 0 {
+		h.module.tidyFiles = android.PathsForBazelOut(ctx, ccInfo.TidyFiles)
+		outputPath = android.AttachValidationActions(ctx, outputPath, h.module.tidyFiles)
+	}
+
+	h.module.outputFile = android.OptionalPathForPath(outputPath)
 
 	// FIXME(b/214600441): We don't yet strip prebuilt shared libraries
-	h.library.unstrippedOutputFile = out
+	h.library.unstrippedOutputFile = outputPath
 
 	var toc android.Path
 	if len(ccInfo.TocFile) > 0 {
 		toc = android.PathForBazelOut(ctx, ccInfo.TocFile)
 	} else {
-		toc = out // Just reuse `out` so ninja still gets an input but won't matter
+		toc = outputPath // Just reuse `out` so ninja still gets an input but won't matter
 	}
 
 	info := SharedLibraryInfo{
-		SharedLibrary:   out,
+		SharedLibrary:   outputPath,
 		TableOfContents: android.OptionalPathForPath(toc),
 		Target:          ctx.Target(),
 	}
diff --git a/cc/prebuilt_test.go b/cc/prebuilt_test.go
index 95fa99e..405680c 100644
--- a/cc/prebuilt_test.go
+++ b/cc/prebuilt_test.go
@@ -443,6 +443,75 @@
 		expectedStaticOutputFiles, staticOutputFiles.Strings())
 }
 
+func TestPrebuiltLibraryWithBazelValidations(t *testing.T) {
+	const bp = `
+cc_prebuilt_library {
+	name: "foo",
+	shared: {
+		srcs: ["foo.so"],
+	},
+	static: {
+		srcs: ["foo.a"],
+	},
+	bazel_module: { label: "//foo/bar:bar" },
+	tidy: true,
+}`
+	outBaseDir := "outputbase"
+	result := android.GroupFixturePreparers(
+		prepareForPrebuiltTest,
+		android.FixtureMergeEnv(map[string]string{
+			"ALLOW_LOCAL_TIDY_TRUE": "1",
+		}),
+		android.FixtureModifyConfig(func(config android.Config) {
+			config.BazelContext = android.MockBazelContext{
+				OutputBaseDir: outBaseDir,
+				LabelToCcInfo: map[string]cquery.CcInfo{
+					"//foo/bar:bar": cquery.CcInfo{
+						CcSharedLibraryFiles: []string{"foo.so"},
+						TidyFiles:            []string{"foo.c.tidy"},
+					},
+					"//foo/bar:bar_bp2build_cc_library_static": cquery.CcInfo{
+						CcStaticLibraryFiles: []string{"foo.a"},
+						TidyFiles:            []string{"foo.c.tidy"},
+					},
+				},
+			}
+		}),
+	).RunTestWithBp(t, bp)
+	sharedFoo := result.ModuleForTests("foo", "android_arm_armv7-a-neon_shared").Module()
+
+	expectedOutputFile := "out/soong/.intermediates/foo/android_arm_armv7-a-neon_shared/validated/foo.so"
+	sharedInfo := result.ModuleProvider(sharedFoo, SharedLibraryInfoProvider).(SharedLibraryInfo)
+	android.AssertPathRelativeToTopEquals(t,
+		"prebuilt library shared target path did not exist or did not match expected. If the base path is what does not match, it is likely that Soong built this module instead of Bazel.",
+		expectedOutputFile, sharedInfo.SharedLibrary)
+
+	outputFiles, err := sharedFoo.(android.OutputFileProducer).OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error getting cc_object outputfiles %s", err)
+	}
+	expectedOutputFiles := []string{expectedOutputFile}
+	android.AssertPathsRelativeToTopEquals(t,
+		"prebuilt library shared target output files did not match expected.",
+		expectedOutputFiles, outputFiles)
+
+	staticFoo := result.ModuleForTests("foo", "android_arm_armv7-a-neon_static").Module()
+	staticInfo := result.ModuleProvider(staticFoo, StaticLibraryInfoProvider).(StaticLibraryInfo)
+	expectedStaticOutputFile := "out/soong/.intermediates/foo/android_arm_armv7-a-neon_static/validated/foo.a"
+	android.AssertPathRelativeToTopEquals(t,
+		"prebuilt library static target path did not exist or did not match expected. If the base path is what does not match, it is likely that Soong built this module instead of Bazel.",
+		expectedStaticOutputFile, staticInfo.StaticLibrary)
+
+	staticOutputFiles, err := staticFoo.(android.OutputFileProducer).OutputFiles("")
+	if err != nil {
+		t.Errorf("Unexpected error getting cc_object staticOutputFiles %s", err)
+	}
+	expectedStaticOutputFiles := []string{expectedStaticOutputFile}
+	android.AssertPathsRelativeToTopEquals(t,
+		"prebuilt library static target output files did not match expected.",
+		expectedStaticOutputFiles, staticOutputFiles)
+}
+
 func TestPrebuiltLibraryWithBazelStaticDisabled(t *testing.T) {
 	const bp = `
 cc_prebuilt_library {
diff --git a/cc/sanitize.go b/cc/sanitize.go
index 8e2d161..66f459a 100644
--- a/cc/sanitize.go
+++ b/cc/sanitize.go
@@ -76,8 +76,11 @@
 	minimalRuntimeFlags = []string{"-fsanitize-minimal-runtime", "-fno-sanitize-trap=integer,undefined",
 		"-fno-sanitize-recover=integer,undefined"}
 	hwasanGlobalOptions = []string{"heap_history_size=1023", "stack_history_size=512",
-		"export_memory_stats=0", "max_malloc_fill_size=4096", "malloc_fill_byte=0"}
+		"export_memory_stats=0", "max_malloc_fill_size=131072", "malloc_fill_byte=0"}
 	memtagStackCommonFlags = []string{"-march=armv8-a+memtag"}
+
+	hostOnlySanitizeFlags   = []string{"-fno-sanitize-recover=all"}
+	deviceOnlySanitizeFlags = []string{"-fsanitize-trap=all", "-ftrap-function=abort"}
 )
 
 type SanitizerType int
@@ -379,7 +382,12 @@
 
 var _ android.SkipApexAllowedDependenciesCheck = (*libraryDependencyTag)(nil)
 
+var exportedVars = android.NewExportedVariables(pctx)
+
 func init() {
+	exportedVars.ExportStringListStaticVariable("HostOnlySanitizeFlags", hostOnlySanitizeFlags)
+	exportedVars.ExportStringList("DeviceOnlySanitizeFlags", deviceOnlySanitizeFlags)
+
 	android.RegisterMakeVarsProvider(pctx, cfiMakeVarsProvider)
 	android.RegisterMakeVarsProvider(pctx, hwasanMakeVarsProvider)
 }
@@ -869,9 +877,9 @@
 			// When fuzzing, we wish to crash with diagnostics on any bug.
 			flags.Local.CFlags = append(flags.Local.CFlags, "-fno-sanitize-trap=all", "-fno-sanitize-recover=all")
 		} else if ctx.Host() {
-			flags.Local.CFlags = append(flags.Local.CFlags, "-fno-sanitize-recover=all")
+			flags.Local.CFlags = append(flags.Local.CFlags, hostOnlySanitizeFlags...)
 		} else {
-			flags.Local.CFlags = append(flags.Local.CFlags, "-fsanitize-trap=all", "-ftrap-function=abort")
+			flags.Local.CFlags = append(flags.Local.CFlags, deviceOnlySanitizeFlags...)
 		}
 
 		if enableMinimalRuntime(s) {
@@ -1787,3 +1795,7 @@
 func hwasanMakeVarsProvider(ctx android.MakeVarsContext) {
 	hwasanStaticLibs(ctx.Config()).exportToMake(ctx)
 }
+
+func BazelCcSanitizerToolchainVars(config android.Config) string {
+	return android.BazelToolchainVars(config, exportedVars)
+}
diff --git a/cc/stl.go b/cc/stl.go
index 6353a4a..f1433ef 100644
--- a/cc/stl.go
+++ b/cc/stl.go
@@ -38,9 +38,9 @@
 func getNdkStlFamilyAndLinkType(m LinkableInterface) (string, string) {
 	stl := m.SelectedStl()
 	switch stl {
-	case "ndk_libc++_shared":
+	case "ndk_libc++_shared", "libc++":
 		return "libc++", "shared"
-	case "ndk_libc++_static":
+	case "ndk_libc++_static", "libc++_static":
 		return "libc++", "static"
 	case "ndk_system":
 		return "system", "shared"
@@ -80,7 +80,8 @@
 			return ""
 		}
 		s = deduplicateStlInput(s)
-		if ctx.useSdk() && ctx.Device() {
+		archHasNDKStl := ctx.Arch().ArchType != android.Riscv64
+		if ctx.useSdk() && ctx.Device() && archHasNDKStl {
 			switch s {
 			case "", "system":
 				return "ndk_system"
diff --git a/cc/test.go b/cc/test.go
index 4b968dc..27de06b 100644
--- a/cc/test.go
+++ b/cc/test.go
@@ -649,9 +649,15 @@
 		return
 	}
 
-	outputFilePath := android.PathForBazelOut(ctx, info.OutputFile)
+	var outputFilePath android.Path = android.PathForBazelOut(ctx, info.OutputFile)
+	if len(info.TidyFiles) > 0 {
+		handler.module.tidyFiles = android.PathsForBazelOut(ctx, info.TidyFiles)
+		outputFilePath = android.AttachValidationActions(ctx, outputFilePath, handler.module.tidyFiles)
+	}
 	handler.module.outputFile = android.OptionalPathForPath(outputFilePath)
 	handler.module.linker.(*testBinary).unstrippedOutputFile = android.PathForBazelOut(ctx, info.UnstrippedOutput)
+
+	handler.module.setAndroidMkVariablesFromCquery(info.CcAndroidMkInfo)
 }
 
 // binaryAttributes contains Bazel attributes corresponding to a cc test
diff --git a/cmd/merge_zips/merge_zips.go b/cmd/merge_zips/merge_zips.go
index 712c7fc..e3d1179 100644
--- a/cmd/merge_zips/merge_zips.go
+++ b/cmd/merge_zips/merge_zips.go
@@ -359,7 +359,8 @@
 		}
 		for _, file := range inputZip.Entries() {
 			pyPkg := getPackage(file.Name)
-			if filepath.Base(file.Name) == "__init__.py" {
+			baseName := filepath.Base(file.Name)
+			if baseName == "__init__.py" || baseName == "__init__.pyc" {
 				if _, found := initedPackages[pyPkg]; found {
 					panic(fmt.Errorf("found __init__.py path duplicates during pars merging: %q", file.Name))
 				}
diff --git a/cmd/soong_build/main.go b/cmd/soong_build/main.go
index 29a6f95..5f27fa7 100644
--- a/cmd/soong_build/main.go
+++ b/cmd/soong_build/main.go
@@ -178,7 +178,8 @@
 	ninjaDeps = append(ninjaDeps, codegenContext.AdditionalNinjaDeps()...)
 
 	// Create soong_injection repository
-	soongInjectionFiles := bp2build.CreateSoongInjectionDirFiles(codegenContext, bp2build.CreateCodegenMetrics())
+	soongInjectionFiles, err := bp2build.CreateSoongInjectionDirFiles(codegenContext, bp2build.CreateCodegenMetrics())
+	maybeQuit(err, "")
 	absoluteSoongInjectionDir := shared.JoinPath(topDir, ctx.Config().SoongOutDir(), bazel.SoongInjectionDirName)
 	for _, file := range soongInjectionFiles {
 		// The API targets in api_bp2build workspace do not have any dependency on api_bp2build.
@@ -447,6 +448,7 @@
 		"bazel-genfiles",
 		"bazel-out",
 		"bazel-testlogs",
+		"bazel-workspace",
 		"bazel-" + filepath.Base(topDir),
 	}
 }
diff --git a/cmd/soong_ui/main.go b/cmd/soong_ui/main.go
index 8c99988..fd718c2 100644
--- a/cmd/soong_ui/main.go
+++ b/cmd/soong_ui/main.go
@@ -199,11 +199,16 @@
 	soongMetricsFile := filepath.Join(logsDir, c.logsPrefix+"soong_metrics")
 	rbeMetricsFile := filepath.Join(logsDir, c.logsPrefix+"rbe_metrics.pb")
 	bp2buildMetricsFile := filepath.Join(logsDir, c.logsPrefix+"bp2build_metrics.pb")
+	bazelMetricsFile := filepath.Join(logsDir, c.logsPrefix+"bazel_metrics.pb")
+
+	//the profile file generated by Bazel"
+	bazelProfileFile := filepath.Join(logsDir, c.logsPrefix+"analyzed_bazel_profile.txt")
 	metricsFiles := []string{
 		buildErrorFile,           // build error strings
 		rbeMetricsFile,           // high level metrics related to remote build execution.
 		bp2buildMetricsFile,      // high level metrics related to bp2build.
 		soongMetricsFile,         // high level metrics related to this build system.
+		bazelMetricsFile,         // high level metrics related to bazel execution
 		config.BazelMetricsDir(), // directory that contains a set of bazel metrics.
 	}
 
@@ -213,12 +218,12 @@
 
 	trace.SetOutput(filepath.Join(logsDir, c.logsPrefix+"build.trace"))
 
-	c.run(buildCtx, config, args)
-
-	defer met.Dump(soongMetricsFile)
 	if !config.SkipMetricsUpload() {
-		defer build.UploadMetrics(buildCtx, config, c.simpleOutput, buildStarted, metricsFiles...)
+		defer build.UploadMetrics(buildCtx, config, c.simpleOutput, buildStarted, bazelProfileFile, bazelMetricsFile, metricsFiles...)
 	}
+	defer met.Dump(soongMetricsFile)
+
+	c.run(buildCtx, config, args)
 
 }
 
diff --git a/compliance/OWNERS b/compliance/OWNERS
new file mode 100644
index 0000000..f52e201
--- /dev/null
+++ b/compliance/OWNERS
@@ -0,0 +1,8 @@
+# OSEP Build
+bbadour@google.com
+kanouche@google.com
+napier@google.com
+
+# Open Source Compliance Tools
+rtp@google.com
+austinyuan@google.com
diff --git a/dexpreopt/dexpreopt.go b/dexpreopt/dexpreopt.go
index e3404a5..a590c72 100644
--- a/dexpreopt/dexpreopt.go
+++ b/dexpreopt/dexpreopt.go
@@ -101,6 +101,10 @@
 }
 
 func dexpreoptDisabled(ctx android.PathContext, global *GlobalConfig, module *ModuleConfig) bool {
+	if ctx.Config().UnbundledBuild() {
+		return true
+	}
+
 	if contains(global.DisablePreoptModules, module.Name) {
 		return true
 	}
diff --git a/docs/map_files.md b/docs/map_files.md
index 1388059..35e8cbb 100644
--- a/docs/map_files.md
+++ b/docs/map_files.md
@@ -148,9 +148,24 @@
 
 ### systemapi
 
-This is a synonym of the `apex` tag. It should be used to clarify that the API
-is an API exposed by the system for an APEX, whereas `apex` should be used for
-APIs exposed by an APEX to the platform or another APEX.
+Indicates that the symbol is exposed by the platform for an apex. Whereas `apex`
+should be used for APIs exposed by an APEX to the platform or another APEX.
+
+May be used in combination with `llndk` if the symbol is exposed to both APEX
+and the LL-NDK.
+
+Since a single library can be installed ether in platform or an apex, but not
+both, a single map.txt file should not contain _both_ # apex and # systemapi symbols.
+
+The granularity between # apex and # systemapi exists to help the API review
+process (b/191371676). These two symbols have very similar lifetime "in
+practice". A #systemapi symbol can be dropped from the next release if we are
+confident that no one is using it. Similarily, #apex can be dropped if we are
+sure that the old platform which used the symbol has reached EOL and thus is no
+longer accepting new APEX updates. Unlike the APIs for apps where we have zero
+control over how APIs are used, we are in a much more controllable environment
+when talking about #systemapi and #apex symbols. So, we have some flexibility
+here when determining the lifetime of a symbol.
 
 ### var
 
diff --git a/java/aar.go b/java/aar.go
index 58b72ab..a483e13 100644
--- a/java/aar.go
+++ b/java/aar.go
@@ -651,6 +651,8 @@
 	// Functionality common to Module and Import.
 	embeddableInModuleAndImport
 
+	providesTransitiveHeaderJars
+
 	properties AARImportProperties
 
 	classpathFile         android.WritablePath
@@ -897,8 +899,11 @@
 		a.assetsPackage = mergedAssets
 	}
 
+	a.collectTransitiveHeaderJars(ctx)
 	ctx.SetProvider(JavaInfoProvider, JavaInfo{
 		HeaderJars:                     android.PathsIfNonNil(a.classpathFile),
+		TransitiveLibsHeaderJars:       a.transitiveLibsHeaderJars,
+		TransitiveStaticLibsHeaderJars: a.transitiveStaticLibsHeaderJars,
 		ImplementationAndResourcesJars: android.PathsIfNonNil(a.classpathFile),
 		ImplementationJars:             android.PathsIfNonNil(a.classpathFile),
 	})
@@ -1069,6 +1074,10 @@
 		ctx.ModuleErrorf("Module has direct dependencies but no sources. Bazel will not allow this.")
 	}
 
+	if len(a.properties.Common_srcs) != 0 {
+		commonAttrs.Common_srcs = bazel.MakeLabelListAttribute(android.BazelLabelForModuleSrc(ctx, a.properties.Common_srcs))
+	}
+
 	name := a.Name()
 	props := bazel.BazelTargetModuleProperties{
 		Rule_class:        "android_library",
diff --git a/java/android_manifest.go b/java/android_manifest.go
index c785310..f6457a0 100644
--- a/java/android_manifest.go
+++ b/java/android_manifest.go
@@ -149,13 +149,14 @@
 
 	if params.SdkContext != nil {
 		targetSdkVersion := targetSdkVersionForManifestFixer(ctx, params)
-		args = append(args, "--targetSdkVersion ", targetSdkVersion)
 
 		if UseApiFingerprint(ctx) && ctx.ModuleName() != "framework-res" {
 			targetSdkVersion = ctx.Config().PlatformSdkCodename() + fmt.Sprintf(".$$(cat %s)", ApiFingerprintPath(ctx).String())
 			deps = append(deps, ApiFingerprintPath(ctx))
 		}
 
+		args = append(args, "--targetSdkVersion ", targetSdkVersion)
+
 		minSdkVersion, err := params.SdkContext.MinSdkVersion(ctx).EffectiveVersionString(ctx)
 		if err != nil {
 			ctx.ModuleErrorf("invalid minSdkVersion: %s", err)
diff --git a/java/app.go b/java/app.go
index 98c31bc..1731970 100755
--- a/java/app.go
+++ b/java/app.go
@@ -984,8 +984,11 @@
 	// The name of the android_app module that the tests will run against.
 	Instrumentation_for *string
 
-	// if specified, the instrumentation target package name in the manifest is overwritten by it.
+	// If specified, the instrumentation target package name in the manifest is overwritten by it.
 	Instrumentation_target_package *string
+
+	// If specified, the mainline module package name in the test config is overwritten by it.
+	Mainline_package_name *string
 }
 
 type AndroidTest struct {
@@ -1063,6 +1066,11 @@
 			FlagWithArg("--package-name ", *a.overridableAppProperties.Package_name)
 	}
 
+	if a.appTestProperties.Mainline_package_name != nil {
+		fixNeeded = true
+		command.FlagWithArg("--mainline-package-name ", *a.appTestProperties.Mainline_package_name)
+	}
+
 	if fixNeeded {
 		rule.Build("fix_test_config", "fix test config")
 		return fixedConfig
@@ -1312,6 +1320,9 @@
 			ctx.AddVariationDependencies(nil, usesLibCompat28OptTag, dexpreopt.OptionalCompatUsesLibs28...)
 			ctx.AddVariationDependencies(nil, usesLibCompat30OptTag, dexpreopt.OptionalCompatUsesLibs30...)
 		}
+	} else {
+		ctx.AddVariationDependencies(nil, r8LibraryJarTag, u.usesLibraryProperties.Uses_libs...)
+		ctx.AddVariationDependencies(nil, r8LibraryJarTag, u.presentOptionalUsesLibs(ctx)...)
 	}
 }
 
@@ -1505,20 +1516,48 @@
 
 	certificate, certificateName := android.BazelStringOrLabelFromProp(ctx, a.overridableAppProperties.Certificate)
 
-	attrs := &bazelAndroidAppAttributes{
-		commonAttrs,
-		aapt,
-		deps,
+	appAttrs := &bazelAndroidAppAttributes{
 		// TODO(b/209576404): handle package name override by product variable PRODUCT_MANIFEST_PACKAGE_NAME_OVERRIDES
-		a.overridableAppProperties.Package_name,
-		certificate,
-		certificateName,
+		Custom_package:   a.overridableAppProperties.Package_name,
+		Certificate:      certificate,
+		Certificate_name: certificateName,
 	}
 
 	props := bazel.BazelTargetModuleProperties{
 		Rule_class:        "android_binary",
 		Bzl_load_location: "//build/bazel/rules/android:rules.bzl",
 	}
-	ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: a.Name()}, attrs)
+
+	if !bp2BuildInfo.hasKotlinSrcs && len(a.properties.Common_srcs) == 0 {
+		appAttrs.javaCommonAttributes = commonAttrs
+		appAttrs.bazelAapt = aapt
+		appAttrs.Deps = deps
+	} else {
+		ktName := a.Name() + "_kt"
+		commonAttrs.Common_srcs = bazel.MakeLabelListAttribute(android.BazelLabelForModuleSrc(ctx, a.properties.Common_srcs))
+		ctx.CreateBazelTargetModule(
+			bazel.BazelTargetModuleProperties{
+				Rule_class:        "android_library",
+				Bzl_load_location: "//build/bazel/rules/android:rules.bzl",
+			},
+			android.CommonAttributes{Name: ktName},
+			&bazelAndroidLibrary{
+				javaLibraryAttributes: &javaLibraryAttributes{
+					javaCommonAttributes: commonAttrs,
+					Deps:                 deps,
+				},
+				bazelAapt: aapt,
+			},
+		)
+
+		appAttrs.bazelAapt = &bazelAapt{Manifest: aapt.Manifest}
+		appAttrs.Deps = bazel.MakeSingleLabelListAttribute(bazel.Label{Label: ":" + ktName})
+	}
+
+	ctx.CreateBazelTargetModule(
+		props,
+		android.CommonAttributes{Name: a.Name()},
+		appAttrs,
+	)
 
 }
diff --git a/java/app_test.go b/java/app_test.go
index 3fb67c1..c77f29d 100644
--- a/java/app_test.go
+++ b/java/app_test.go
@@ -2330,12 +2330,14 @@
 			srcs: ["b.java"],
 			package_name: "com.android.bar.test",
 			instrumentation_for: "foo",
+			mainline_package_name: "com.android.bar",
 		}
 
 		override_android_test {
 			name: "baz_test",
 			base: "foo_test",
 			package_name: "com.android.baz.test",
+			mainline_package_name: "com.android.baz",
 		}
 		`)
 
@@ -2354,6 +2356,7 @@
 			expectedFlags: []string{
 				"--manifest out/soong/.intermediates/bar_test/android_common/manifest_fixer/AndroidManifest.xml",
 				"--package-name com.android.bar.test",
+				"--mainline-package-name com.android.bar",
 			},
 		},
 		{
@@ -2363,6 +2366,8 @@
 				"--manifest out/soong/.intermediates/foo_test/android_common_baz_test/manifest_fixer/AndroidManifest.xml",
 				"--package-name com.android.baz.test",
 				"--test-file-name baz_test.apk",
+				"out/soong/.intermediates/foo_test/android_common_baz_test/test_config_fixer/AndroidTest.xml",
+				"--mainline-package-name com.android.baz",
 			},
 		},
 	}
diff --git a/java/base.go b/java/base.go
index 84fda37..cce06a4 100644
--- a/java/base.go
+++ b/java/base.go
@@ -1583,6 +1583,8 @@
 
 	ctx.SetProvider(JavaInfoProvider, JavaInfo{
 		HeaderJars:                     android.PathsIfNonNil(j.headerJarFile),
+		TransitiveLibsHeaderJars:       j.transitiveLibsHeaderJars,
+		TransitiveStaticLibsHeaderJars: j.transitiveStaticLibsHeaderJars,
 		ImplementationAndResourcesJars: android.PathsIfNonNil(j.implementationAndResourcesJar),
 		ImplementationJars:             android.PathsIfNonNil(j.implementationJarFile),
 		ResourceJars:                   android.PathsIfNonNil(j.resourceJar),
@@ -1719,6 +1721,52 @@
 	return instrumentedJar
 }
 
+type providesTransitiveHeaderJars struct {
+	// set of header jars for all transitive libs deps
+	transitiveLibsHeaderJars *android.DepSet
+	// set of header jars for all transitive static libs deps
+	transitiveStaticLibsHeaderJars *android.DepSet
+}
+
+func (j *providesTransitiveHeaderJars) TransitiveLibsHeaderJars() *android.DepSet {
+	return j.transitiveLibsHeaderJars
+}
+
+func (j *providesTransitiveHeaderJars) TransitiveStaticLibsHeaderJars() *android.DepSet {
+	return j.transitiveStaticLibsHeaderJars
+}
+
+func (j *providesTransitiveHeaderJars) collectTransitiveHeaderJars(ctx android.ModuleContext) {
+	directLibs := android.Paths{}
+	directStaticLibs := android.Paths{}
+	transitiveLibs := []*android.DepSet{}
+	transitiveStaticLibs := []*android.DepSet{}
+	ctx.VisitDirectDeps(func(module android.Module) {
+		// don't add deps of the prebuilt version of the same library
+		if ctx.ModuleName() == android.RemoveOptionalPrebuiltPrefix(module.Name()) {
+			return
+		}
+
+		dep := ctx.OtherModuleProvider(module, JavaInfoProvider).(JavaInfo)
+		if dep.TransitiveLibsHeaderJars != nil {
+			transitiveLibs = append(transitiveLibs, dep.TransitiveLibsHeaderJars)
+		}
+		if dep.TransitiveStaticLibsHeaderJars != nil {
+			transitiveStaticLibs = append(transitiveStaticLibs, dep.TransitiveStaticLibsHeaderJars)
+		}
+
+		tag := ctx.OtherModuleDependencyTag(module)
+		_, isUsesLibDep := tag.(usesLibraryDependencyTag)
+		if tag == libTag || tag == r8LibraryJarTag || isUsesLibDep {
+			directLibs = append(directLibs, dep.HeaderJars...)
+		} else if tag == staticLibTag {
+			directStaticLibs = append(directStaticLibs, dep.HeaderJars...)
+		}
+	})
+	j.transitiveLibsHeaderJars = android.NewDepSet(android.POSTORDER, directLibs, transitiveLibs)
+	j.transitiveStaticLibsHeaderJars = android.NewDepSet(android.POSTORDER, directStaticLibs, transitiveStaticLibs)
+}
+
 func (j *Module) HeaderJars() android.Paths {
 	if j.headerJarFile == nil {
 		return nil
@@ -1947,6 +1995,7 @@
 
 	sdkLinkType, _ := j.getSdkLinkType(ctx, ctx.ModuleName())
 
+	j.collectTransitiveHeaderJars(ctx)
 	ctx.VisitDirectDeps(func(module android.Module) {
 		otherName := ctx.OtherModuleName(module)
 		tag := ctx.OtherModuleDependencyTag(module)
diff --git a/java/config/config.go b/java/config/config.go
index 49d88c4..7c22076 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -96,8 +96,6 @@
 	}, dexerJavaVmFlagsList...))
 	exportedVars.ExportStringListStaticVariable("R8Flags", append([]string{
 		"-JXmx2048M",
-		// Disable this optimization as it can impact weak reference semantics. See b/233432839.
-		"-JDcom.android.tools.r8.disableEnqueuerDeferredTracing=true",
 	}, dexerJavaVmFlagsList...))
 
 	exportedVars.ExportStringListStaticVariable("CommonJdkFlags", []string{
diff --git a/java/dex.go b/java/dex.go
index 40ee99d..a8dd375 100644
--- a/java/dex.go
+++ b/java/dex.go
@@ -89,7 +89,10 @@
 	// list of extra proguard flag files
 	extraProguardFlagFiles android.Paths
 	proguardDictionary     android.OptionalPath
+	proguardConfiguration  android.OptionalPath
 	proguardUsageZip       android.OptionalPath
+
+	providesTransitiveHeaderJars
 }
 
 func (d *dexer) effectiveOptimizeEnabled() bool {
@@ -130,17 +133,18 @@
 var r8, r8RE = pctx.MultiCommandRemoteStaticRules("r8",
 	blueprint.RuleParams{
 		Command: `rm -rf "$outDir" && mkdir -p "$outDir" && ` +
-			`rm -f "$outDict" && rm -rf "${outUsageDir}" && ` +
+			`rm -f "$outDict" && rm -f "$outConfig" && rm -rf "${outUsageDir}" && ` +
 			`mkdir -p $$(dirname ${outUsage}) && ` +
 			`mkdir -p $$(dirname $tmpJar) && ` +
 			`${config.Zip2ZipCmd} -i $in -o $tmpJar -x '**/*.dex' && ` +
 			`$r8Template${config.R8Cmd} ${config.R8Flags} -injars $tmpJar --output $outDir ` +
 			`--no-data-resources ` +
 			`-printmapping ${outDict} ` +
+			`--pg-conf-output ${outConfig} ` +
 			`-printusage ${outUsage} ` +
 			`--deps-file ${out}.d ` +
 			`$r8Flags && ` +
-			`touch "${outDict}" "${outUsage}" && ` +
+			`touch "${outDict}" "${outConfig}" "${outUsage}" && ` +
 			`${config.SoongZipCmd} -o ${outUsageZip} -C ${outUsageDir} -f ${outUsage} && ` +
 			`rm -rf ${outUsageDir} && ` +
 			`$zipTemplate${config.SoongZipCmd} $zipFlags -o $outDir/classes.dex.jar -C $outDir -f "$outDir/classes*.dex" && ` +
@@ -176,7 +180,7 @@
 			ExecStrategy: "${config.RER8ExecStrategy}",
 			Platform:     map[string]string{remoteexec.PoolKey: "${config.REJavaPool}"},
 		},
-	}, []string{"outDir", "outDict", "outUsage", "outUsageZip", "outUsageDir",
+	}, []string{"outDir", "outDict", "outConfig", "outUsage", "outUsageZip", "outUsageDir",
 		"r8Flags", "zipFlags", "tmpJar", "mergeZipsFlags"}, []string{"implicits"})
 
 func (d *dexer) dexCommonFlags(ctx android.ModuleContext,
@@ -249,13 +253,32 @@
 	})
 
 	r8Flags = append(r8Flags, proguardRaiseDeps.FormJavaClassPath("-libraryjars"))
-	r8Flags = append(r8Flags, flags.bootClasspath.FormJavaClassPath("-libraryjars"))
-	r8Flags = append(r8Flags, flags.dexClasspath.FormJavaClassPath("-libraryjars"))
-
 	r8Deps = append(r8Deps, proguardRaiseDeps...)
+	r8Flags = append(r8Flags, flags.bootClasspath.FormJavaClassPath("-libraryjars"))
 	r8Deps = append(r8Deps, flags.bootClasspath...)
+	r8Flags = append(r8Flags, flags.dexClasspath.FormJavaClassPath("-libraryjars"))
 	r8Deps = append(r8Deps, flags.dexClasspath...)
 
+	transitiveStaticLibsLookupMap := map[android.Path]bool{}
+	if d.transitiveStaticLibsHeaderJars != nil {
+		for _, jar := range d.transitiveStaticLibsHeaderJars.ToList() {
+			transitiveStaticLibsLookupMap[jar] = true
+		}
+	}
+	transitiveHeaderJars := android.Paths{}
+	if d.transitiveLibsHeaderJars != nil {
+		for _, jar := range d.transitiveLibsHeaderJars.ToList() {
+			if _, ok := transitiveStaticLibsLookupMap[jar]; ok {
+				// don't include a lib if it is already packaged in the current JAR as a static lib
+				continue
+			}
+			transitiveHeaderJars = append(transitiveHeaderJars, jar)
+		}
+	}
+	transitiveClasspath := classpath(transitiveHeaderJars)
+	r8Flags = append(r8Flags, transitiveClasspath.FormJavaClassPath("-libraryjars"))
+	r8Deps = append(r8Deps, transitiveClasspath...)
+
 	flagFiles := android.Paths{
 		android.PathForSource(ctx, "build/make/core/proguard.flags"),
 	}
@@ -342,6 +365,8 @@
 	if useR8 {
 		proguardDictionary := android.PathForModuleOut(ctx, "proguard_dictionary")
 		d.proguardDictionary = android.OptionalPathForPath(proguardDictionary)
+		proguardConfiguration := android.PathForModuleOut(ctx, "proguard_configuration")
+		d.proguardConfiguration = android.OptionalPathForPath(proguardConfiguration)
 		proguardUsageDir := android.PathForModuleOut(ctx, "proguard_usage")
 		proguardUsage := proguardUsageDir.Join(ctx, ctx.Namespace().Path,
 			android.ModuleNameWithPossibleOverride(ctx), "unused.txt")
@@ -354,6 +379,7 @@
 			"r8Flags":        strings.Join(append(commonFlags, r8Flags...), " "),
 			"zipFlags":       zipFlags,
 			"outDict":        proguardDictionary.String(),
+			"outConfig":      proguardConfiguration.String(),
 			"outUsageDir":    proguardUsageDir.String(),
 			"outUsage":       proguardUsage.String(),
 			"outUsageZip":    proguardUsageZip.String(),
diff --git a/java/dex_test.go b/java/dex_test.go
index fc6cd0f..dc85f9e 100644
--- a/java/dex_test.go
+++ b/java/dex_test.go
@@ -18,6 +18,8 @@
 	"testing"
 
 	"android/soong/android"
+
+	"github.com/google/blueprint/proptools"
 )
 
 func TestR8(t *testing.T) {
@@ -74,7 +76,7 @@
 
 	android.AssertStringDoesContain(t, "expected lib header jar in app r8 classpath",
 		appR8.Args["r8Flags"], libHeader.String())
-	android.AssertStringDoesNotContain(t, "expected no  static_lib header jar in app javac classpath",
+	android.AssertStringDoesNotContain(t, "expected no static_lib header jar in app r8 classpath",
 		appR8.Args["r8Flags"], staticLibHeader.String())
 	android.AssertStringDoesContain(t, "expected -ignorewarnings in app r8 flags",
 		appR8.Args["r8Flags"], "-ignorewarnings")
@@ -86,6 +88,174 @@
 		corePlatformAppR8.Args["r8Flags"], "--android-platform-build")
 }
 
+func TestR8TransitiveDeps(t *testing.T) {
+	bp := `
+		override_android_app {
+			name: "override_app",
+			base: "app",
+		}
+
+		android_app {
+			name: "app",
+			srcs: ["foo.java"],
+			libs: [
+				"lib",
+				"uses_libs_dep_import",
+			],
+			static_libs: [
+				"static_lib",
+				"repeated_dep",
+			],
+			platform_apis: true,
+		}
+
+		java_library {
+			name: "static_lib",
+			srcs: ["foo.java"],
+		}
+
+		java_library {
+			name: "lib",
+			libs: [
+				"transitive_lib",
+				"repeated_dep",
+				"prebuilt_lib",
+			],
+			static_libs: ["transitive_static_lib"],
+			srcs: ["foo.java"],
+		}
+
+		java_library {
+			name: "repeated_dep",
+			srcs: ["foo.java"],
+		}
+
+		java_library {
+			name: "transitive_static_lib",
+			srcs: ["foo.java"],
+		}
+
+		java_library {
+			name: "transitive_lib",
+			srcs: ["foo.java"],
+			libs: ["transitive_lib_2"],
+		}
+
+		java_library {
+			name: "transitive_lib_2",
+			srcs: ["foo.java"],
+		}
+
+		java_import {
+			name: "lib",
+			jars: ["lib.jar"],
+		}
+
+		java_library {
+			name: "uses_lib",
+			srcs: ["foo.java"],
+		}
+
+		java_library {
+			name: "optional_uses_lib",
+			srcs: ["foo.java"],
+		}
+
+		android_library {
+			name: "uses_libs_dep",
+			uses_libs: ["uses_lib"],
+			optional_uses_libs: ["optional_uses_lib"],
+		}
+
+		android_library_import {
+			name: "uses_libs_dep_import",
+			aars: ["aar.aar"],
+			static_libs: ["uses_libs_dep"],
+		}
+	`
+
+	testcases := []struct {
+		name      string
+		unbundled bool
+	}{
+		{
+			name:      "non-unbundled build",
+			unbundled: false,
+		},
+		{
+			name:      "unbundled build",
+			unbundled: true,
+		},
+	}
+
+	for _, tc := range testcases {
+		t.Run(tc.name, func(t *testing.T) {
+			fixturePreparer := PrepareForTestWithJavaDefaultModulesWithoutFakeDex2oatd
+			if tc.unbundled {
+				fixturePreparer = android.GroupFixturePreparers(
+					fixturePreparer,
+					android.FixtureModifyProductVariables(
+						func(variables android.FixtureProductVariables) {
+							variables.Unbundled_build = proptools.BoolPtr(true)
+						},
+					),
+				)
+			}
+			result := fixturePreparer.RunTestWithBp(t, bp)
+
+			getHeaderJar := func(name string) android.Path {
+				mod := result.ModuleForTests(name, "android_common")
+				return mod.Output("turbine-combined/" + name + ".jar").Output
+			}
+
+			appR8 := result.ModuleForTests("app", "android_common").Rule("r8")
+			overrideAppR8 := result.ModuleForTests("app", "android_common_override_app").Rule("r8")
+			appHeader := getHeaderJar("app")
+			overrideAppHeader := result.ModuleForTests("app", "android_common_override_app").Output("turbine-combined/app.jar").Output
+			libHeader := getHeaderJar("lib")
+			transitiveLibHeader := getHeaderJar("transitive_lib")
+			transitiveLib2Header := getHeaderJar("transitive_lib_2")
+			staticLibHeader := getHeaderJar("static_lib")
+			transitiveStaticLibHeader := getHeaderJar("transitive_static_lib")
+			repeatedDepHeader := getHeaderJar("repeated_dep")
+			usesLibHeader := getHeaderJar("uses_lib")
+			optionalUsesLibHeader := getHeaderJar("optional_uses_lib")
+			prebuiltLibHeader := result.ModuleForTests("prebuilt_lib", "android_common").Output("combined/lib.jar").Output
+
+			for _, rule := range []android.TestingBuildParams{appR8, overrideAppR8} {
+				android.AssertStringDoesNotContain(t, "expected no app header jar in app r8 classpath",
+					rule.Args["r8Flags"], appHeader.String())
+				android.AssertStringDoesNotContain(t, "expected no override_app header jar in app r8 classpath",
+					rule.Args["r8Flags"], overrideAppHeader.String())
+				android.AssertStringDoesContain(t, "expected transitive lib header jar in app r8 classpath",
+					rule.Args["r8Flags"], transitiveLibHeader.String())
+				android.AssertStringDoesContain(t, "expected transitive lib ^2 header jar in app r8 classpath",
+					rule.Args["r8Flags"], transitiveLib2Header.String())
+				android.AssertStringDoesContain(t, "expected lib header jar in app r8 classpath",
+					rule.Args["r8Flags"], libHeader.String())
+				android.AssertStringDoesContain(t, "expected uses_lib header jar in app r8 classpath",
+					rule.Args["r8Flags"], usesLibHeader.String())
+				android.AssertStringDoesContain(t, "expected optional_uses_lib header jar in app r8 classpath",
+					rule.Args["r8Flags"], optionalUsesLibHeader.String())
+				android.AssertStringDoesNotContain(t, "expected no static_lib header jar in app r8 classpath",
+					rule.Args["r8Flags"], staticLibHeader.String())
+				android.AssertStringDoesNotContain(t, "expected no transitive static_lib header jar in app r8 classpath",
+					rule.Args["r8Flags"], transitiveStaticLibHeader.String())
+				// we shouldn't list this dep because it is already included as static_libs in the app
+				android.AssertStringDoesNotContain(t, "expected no repeated_dep header jar in app r8 classpath",
+					rule.Args["r8Flags"], repeatedDepHeader.String())
+				// skip a prebuilt transitive dep if the source is also a transitive dep
+				android.AssertStringDoesNotContain(t, "expected no prebuilt header jar in app r8 classpath",
+					rule.Args["r8Flags"], prebuiltLibHeader.String())
+				android.AssertStringDoesContain(t, "expected -ignorewarnings in app r8 flags",
+					rule.Args["r8Flags"], "-ignorewarnings")
+				android.AssertStringDoesContain(t, "expected --android-platform-build in app r8 flags",
+					rule.Args["r8Flags"], "--android-platform-build")
+			}
+		})
+	}
+}
+
 func TestR8Flags(t *testing.T) {
 	result := PrepareForTestWithJavaDefaultModulesWithoutFakeDex2oatd.RunTestWithBp(t, `
 		android_app {
diff --git a/java/dexpreopt.go b/java/dexpreopt.go
index 77cbe9c..c4b0af4 100644
--- a/java/dexpreopt.go
+++ b/java/dexpreopt.go
@@ -27,6 +27,7 @@
 	dexpreoptDisabled(ctx android.BaseModuleContext) bool
 	DexpreoptBuiltInstalledForApex() []dexpreopterInstall
 	AndroidMkEntriesForApex() []android.AndroidMkEntries
+	ProfilePathOnHost() android.Path
 }
 
 type dexpreopterInstall struct {
@@ -103,6 +104,9 @@
 	// - Dexpreopt post-processing (using dexpreopt artifacts from a prebuilt system image to incrementally
 	//   dexpreopt another partition).
 	configPath android.WritablePath
+
+	// The path to the profile on host.
+	profilePathOnHost android.Path
 }
 
 type DexpreoptProperties struct {
@@ -180,9 +184,8 @@
 
 	isApexSystemServerJar := global.AllApexSystemServerJars(ctx).ContainsJar(moduleName(ctx))
 	if isApexVariant(ctx) {
-		// Don't preopt APEX variant module unless the module is an APEX system server jar and we are
-		// building the entire system image.
-		if !isApexSystemServerJar || ctx.Config().UnbundledBuild() {
+		// Don't preopt APEX variant module unless the module is an APEX system server jar.
+		if !isApexSystemServerJar {
 			return true
 		}
 	} else {
@@ -368,21 +371,29 @@
 		installBase := filepath.Base(install.To)
 		arch := filepath.Base(installDir)
 		installPath := android.PathForModuleInPartitionInstall(ctx, "", installDir)
+		isProfile := strings.HasSuffix(installBase, ".prof")
+
+		if isProfile {
+			d.profilePathOnHost = install.From
+		}
 
 		if isApexSystemServerJar {
-			// APEX variants of java libraries are hidden from Make, so their dexpreopt
-			// outputs need special handling. Currently, for APEX variants of java
-			// libraries, only those in the system server classpath are handled here.
-			// Preopting of boot classpath jars in the ART APEX are handled in
-			// java/dexpreopt_bootjars.go, and other APEX jars are not preopted.
-			// The installs will be handled by Make as sub-modules of the java library.
-			d.builtInstalledForApex = append(d.builtInstalledForApex, dexpreopterInstall{
-				name:                arch + "-" + installBase,
-				moduleName:          moduleName(ctx),
-				outputPathOnHost:    install.From,
-				installDirOnDevice:  installPath,
-				installFileOnDevice: installBase,
-			})
+			// Profiles are handled separately because they are installed into the APEX.
+			if !isProfile {
+				// APEX variants of java libraries are hidden from Make, so their dexpreopt
+				// outputs need special handling. Currently, for APEX variants of java
+				// libraries, only those in the system server classpath are handled here.
+				// Preopting of boot classpath jars in the ART APEX are handled in
+				// java/dexpreopt_bootjars.go, and other APEX jars are not preopted.
+				// The installs will be handled by Make as sub-modules of the java library.
+				d.builtInstalledForApex = append(d.builtInstalledForApex, dexpreopterInstall{
+					name:                arch + "-" + installBase,
+					moduleName:          moduleName(ctx),
+					outputPathOnHost:    install.From,
+					installDirOnDevice:  installPath,
+					installFileOnDevice: installBase,
+				})
+			}
 		} else if !d.preventInstall {
 			ctx.InstallFile(installPath, installBase, install.From)
 		}
@@ -404,3 +415,7 @@
 	}
 	return entries
 }
+
+func (d *dexpreopter) ProfilePathOnHost() android.Path {
+	return d.profilePathOnHost
+}
diff --git a/java/droiddoc.go b/java/droiddoc.go
index aa55f37..01a2c14 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -602,8 +602,15 @@
 		Flag("-J-Xmx1600m").
 		Flag("-J-XX:-OmitStackTraceInFastThrow").
 		Flag("-XDignore.symbol.file").
-		FlagWithArg("-doclet ", "com.google.doclava.Doclava").
+		Flag("--ignore-source-errors").
+		// b/240421555: use a stub doclet until Doclava works with JDK 17
+		//FlagWithArg("-doclet ", "com.google.doclava.Doclava").
+		FlagWithArg("-doclet ", "com.google.stubdoclet.StubDoclet").
 		FlagWithInputList("-docletpath ", docletPath.Paths(), ":").
+		FlagWithArg("-Xmaxerrs ", "1").
+		FlagWithArg("-Xmaxwarns ", "1").
+		Flag("-J--add-exports=jdk.javadoc/jdk.javadoc.internal.doclets.formats.html=ALL-UNNAMED").
+		Flag("-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED").
 		FlagWithArg("-hdf page.build ", ctx.Config().BuildId()+"-$(cat "+buildNumberFile.String()+")").OrderOnly(buildNumberFile).
 		FlagWithArg("-hdf page.now ", `"$(date -d @$(cat `+ctx.Config().Getenv("BUILD_DATETIME_FILE")+`) "+%d %b %Y %k:%M")" `)
 
@@ -687,7 +694,7 @@
 	outDir, srcJarDir, srcJarList android.Path, sourcepaths android.Paths) *android.RuleBuilderCommand {
 
 	cmd := rule.Command().
-		BuiltTool("soong_javac_wrapper").Tool(android.PathForSource(ctx, "prebuilts/jdk/jdk11/linux-x86/bin/javadoc")).
+		BuiltTool("soong_javac_wrapper").Tool(config.JavadocCmd(ctx)).
 		Flag(config.JavacVmFlags).
 		FlagWithArg("-encoding ", "UTF-8").
 		FlagWithRspFileInputList("@", android.PathForModuleOut(ctx, "javadoc.rsp"), srcs).
@@ -773,6 +780,8 @@
 
 	jsilver := ctx.Config().HostJavaToolPath(ctx, "jsilver.jar")
 	doclava := ctx.Config().HostJavaToolPath(ctx, "doclava.jar")
+	// b/240421555: use a stub doclet until Doclava works with JDK 17
+	stubdoclet := ctx.Config().HostJavaToolPath(ctx, "stubdoclet.jar")
 
 	outDir := android.PathForModuleOut(ctx, "out")
 	srcJarDir := android.PathForModuleOut(ctx, "srcjars")
@@ -800,7 +809,8 @@
 	if Bool(d.properties.Dokka_enabled) {
 		desc = "dokka"
 	} else {
-		d.doclavaDocsFlags(ctx, cmd, classpath{jsilver, doclava})
+		// b/240421555: use a stub doclet until Doclava works with JDK 17
+		d.doclavaDocsFlags(ctx, cmd, classpath{jsilver, doclava, stubdoclet})
 
 		for _, o := range d.Javadoc.properties.Out {
 			cmd.ImplicitOutput(android.PathForModuleGen(ctx, o))
@@ -818,9 +828,9 @@
 		FlagWithArg("-C ", outDir.String()).
 		FlagWithArg("-D ", outDir.String())
 
-	rule.Restat()
+	// rule.Restat()
 
-	zipSyncCleanupCmd(rule, srcJarDir)
+	// zipSyncCleanupCmd(rule, srcJarDir)
 
 	rule.Build("javadoc", desc)
 }
diff --git a/java/droidstubs.go b/java/droidstubs.go
index 4bbe70a..8a521aa 100644
--- a/java/droidstubs.go
+++ b/java/droidstubs.go
@@ -148,6 +148,10 @@
 	// path or filegroup to file defining extension an SDK name <-> numerical ID mapping and
 	// what APIs exist in which SDKs; passed to metalava via --sdk-extensions-info
 	Extensions_info_file *string `android:"path"`
+
+	// API surface of this module. If set, the module contributes to an API surface.
+	// For the full list of available API surfaces, refer to soong/android/sdk_version.go
+	Api_surface *string
 }
 
 // Used by xsd_config
@@ -178,6 +182,10 @@
 		&module.Javadoc.properties)
 
 	InitDroiddocModule(module, android.HostAndDeviceSupported)
+
+	module.SetDefaultableHook(func(ctx android.DefaultableHookContext) {
+		module.createApiContribution(ctx)
+	})
 	return module
 }
 
@@ -862,6 +870,25 @@
 	}, attrs)
 }
 
+func (d *Droidstubs) createApiContribution(ctx android.DefaultableHookContext) {
+	api_file := d.properties.Check_api.Current.Api_file
+	api_surface := d.properties.Api_surface
+
+	props := struct {
+		Name        *string
+		Api_surface *string
+		Api_file    *string
+		Visibility  []string
+	}{}
+
+	props.Name = proptools.StringPtr(d.Name() + ".api.contribution")
+	props.Api_surface = api_surface
+	props.Api_file = api_file
+	props.Visibility = []string{"//visibility:override", "//visibility:public"}
+
+	ctx.CreateModule(ApiContributionFactory, &props)
+}
+
 // TODO (b/262014796): Export the API contributions of CorePlatformApi
 // A map to populate the api surface of a droidstub from a substring appearing in its name
 // This map assumes that droidstubs (either checked-in or created by java_sdk_library)
@@ -876,6 +903,7 @@
 		"system":        android.SdkSystem,
 		"module_lib":    android.SdkModule,
 		"module-lib":    android.SdkModule,
+		"platform.api":  android.SdkCorePlatform,
 		"test":          android.SdkTest,
 		"toolchain":     android.SdkToolchain,
 	}
diff --git a/java/droidstubs_test.go b/java/droidstubs_test.go
index ef2e6dc..7a04d73 100644
--- a/java/droidstubs_test.go
+++ b/java/droidstubs_test.go
@@ -346,3 +346,60 @@
 		android.AssertStringEquals(t, tc.desc, tc.expectedApiSurface, bazelApiSurfaceName(tc.name))
 	}
 }
+
+func TestDroidStubsApiContributionGeneration(t *testing.T) {
+	ctx, _ := testJavaWithFS(t, `
+		droidstubs {
+			name: "foo",
+			srcs: ["A/a.java"],
+			api_surface: "public",
+			check_api: {
+				current: {
+					api_file: "A/current.txt",
+					removed_api_file: "A/removed.txt",
+				}
+			}
+		}
+		`,
+		map[string][]byte{
+			"A/a.java":      nil,
+			"A/current.txt": nil,
+			"A/removed.txt": nil,
+		},
+	)
+
+	ctx.ModuleForTests("foo.api.contribution", "")
+}
+
+func TestGeneratedApiContributionVisibilityTest(t *testing.T) {
+	library_bp := `
+		java_api_library {
+			name: "bar",
+			api_surface: "public",
+			api_contributions: ["foo.api.contribution"],
+		}
+	`
+	ctx, _ := testJavaWithFS(t, `
+			droidstubs {
+				name: "foo",
+				srcs: ["A/a.java"],
+				api_surface: "public",
+				check_api: {
+					current: {
+						api_file: "A/current.txt",
+						removed_api_file: "A/removed.txt",
+					}
+				},
+				visibility: ["//a"],
+			}
+		`,
+		map[string][]byte{
+			"a/a.java":      nil,
+			"a/current.txt": nil,
+			"a/removed.txt": nil,
+			"b/Android.bp":  []byte(library_bp),
+		},
+	)
+
+	ctx.ModuleForTests("bar", "android_common")
+}
diff --git a/java/java.go b/java/java.go
index 0479f10..874f935 100644
--- a/java/java.go
+++ b/java/java.go
@@ -230,6 +230,12 @@
 	// against this module.  If empty, ImplementationJars should be used instead.
 	HeaderJars android.Paths
 
+	// set of header jars for all transitive libs deps
+	TransitiveLibsHeaderJars *android.DepSet
+
+	// set of header jars for all transitive static libs deps
+	TransitiveStaticLibsHeaderJars *android.DepSet
+
 	// ImplementationAndResourceJars is a list of jars that contain the implementations of classes
 	// in the module as well as any resources included in the module.
 	ImplementationAndResourcesJars android.Paths
@@ -380,6 +386,7 @@
 	instrumentationForTag   = dependencyTag{name: "instrumentation_for"}
 	extraLintCheckTag       = dependencyTag{name: "extra-lint-check", toolchain: true}
 	jniLibTag               = dependencyTag{name: "jnilib", runtimeLinked: true}
+	r8LibraryJarTag         = dependencyTag{name: "r8-libraryjar", runtimeLinked: true}
 	syspropPublicStubDepTag = dependencyTag{name: "sysprop public stub"}
 	jniInstallTag           = installDependencyTag{name: "jni install"}
 	binaryInstallTag        = installDependencyTag{name: "binary install"}
@@ -510,14 +517,8 @@
 		return normalizeJavaVersion(ctx, javaVersion)
 	} else if ctx.Device() {
 		return defaultJavaLanguageVersion(ctx, sdkContext.SdkVersion(ctx))
-	} else if ctx.Config().TargetsJava17() {
-		// Temporary experimental flag to be able to try and build with
-		// java version 17 options.  The flag, if used, just sets Java
-		// 17 as the default version, leaving any components that
-		// target an older version intact.
-		return JAVA_VERSION_17
 	} else {
-		return JAVA_VERSION_11
+		return JAVA_VERSION_17
 	}
 }
 
@@ -1587,7 +1588,11 @@
 var JavaApiImportProvider = blueprint.NewProvider(JavaApiImportInfo{})
 
 func (ap *JavaApiContribution) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	apiFile := android.PathForModuleSrc(ctx, String(ap.properties.Api_file))
+	var apiFile android.Path = nil
+	if apiFileString := ap.properties.Api_file; apiFileString != nil {
+		apiFile = android.PathForModuleSrc(ctx, String(apiFileString))
+	}
+
 	ctx.SetProvider(JavaApiImportProvider, JavaApiImportInfo{
 		ApiFile: apiFile,
 	})
@@ -1618,6 +1623,10 @@
 
 	// List of flags to be passed to the javac compiler to generate jar file
 	Javacflags []string
+
+	// List of shared java libs that this module has dependencies to and
+	// should be passed as classpath in javac invocation
+	Libs []string
 }
 
 func ApiLibraryFactory() android.Module {
@@ -1689,6 +1698,7 @@
 	for _, apiContributionName := range apiContributions {
 		ctx.AddDependency(ctx.Module(), javaApiContributionTag, apiContributionName)
 	}
+	ctx.AddVariationDependencies(nil, libTag, al.properties.Libs...)
 }
 
 func (al *ApiLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
@@ -1706,10 +1716,22 @@
 
 	homeDir := android.PathForModuleOut(ctx, "metalava", "home")
 
-	var srcFiles []android.Path
-	ctx.VisitDirectDepsWithTag(javaApiContributionTag, func(dep android.Module) {
-		provider := ctx.OtherModuleProvider(dep, JavaApiImportProvider).(JavaApiImportInfo)
-		srcFiles = append(srcFiles, android.PathForSource(ctx, provider.ApiFile.String()))
+	var srcFiles android.Paths
+	var classPaths android.Paths
+	ctx.VisitDirectDeps(func(dep android.Module) {
+		tag := ctx.OtherModuleDependencyTag(dep)
+		switch tag {
+		case javaApiContributionTag:
+			provider := ctx.OtherModuleProvider(dep, JavaApiImportProvider).(JavaApiImportInfo)
+			providerApiFile := provider.ApiFile
+			if providerApiFile == nil {
+				ctx.ModuleErrorf("Error: %s has an empty api file.", dep.Name())
+			}
+			srcFiles = append(srcFiles, android.PathForSource(ctx, providerApiFile.String()))
+		case libTag:
+			provider := ctx.OtherModuleProvider(dep, JavaInfoProvider).(JavaInfo)
+			classPaths = append(classPaths, provider.HeaderJars...)
+		}
 	})
 
 	// Add the api_files inputs
@@ -1739,11 +1761,16 @@
 	var flags javaBuilderFlags
 	flags.javaVersion = getStubsJavaVersion()
 	flags.javacFlags = strings.Join(al.properties.Javacflags, " ")
+	flags.classpath = classpath(classPaths)
 
 	TransformJavaToClasses(ctx, al.stubsJar, 0, android.Paths{},
 		android.Paths{al.stubsSrcJar}, flags, android.Paths{})
 
 	ctx.Phony(ctx.ModuleName(), al.stubsJar)
+
+	ctx.SetProvider(JavaInfoProvider, JavaInfo{
+		HeaderJars: android.PathsIfNonNil(al.stubsJar),
+	})
 }
 
 //
@@ -1926,9 +1953,9 @@
 
 	var flags javaBuilderFlags
 
+	j.collectTransitiveHeaderJars(ctx)
 	ctx.VisitDirectDeps(func(module android.Module) {
 		tag := ctx.OtherModuleDependencyTag(module)
-
 		if ctx.OtherModuleHasProvider(module, JavaInfoProvider) {
 			dep := ctx.OtherModuleProvider(module, JavaInfoProvider).(JavaInfo)
 			switch tag {
@@ -2018,6 +2045,8 @@
 
 	ctx.SetProvider(JavaInfoProvider, JavaInfo{
 		HeaderJars:                     android.PathsIfNonNil(j.combinedClasspathFile),
+		TransitiveLibsHeaderJars:       j.transitiveLibsHeaderJars,
+		TransitiveStaticLibsHeaderJars: j.transitiveStaticLibsHeaderJars,
 		ImplementationAndResourcesJars: android.PathsIfNonNil(j.combinedClasspathFile),
 		ImplementationJars:             android.PathsIfNonNil(j.combinedClasspathFile),
 		AidlIncludeDirs:                j.exportAidlIncludeDirs,
@@ -2568,7 +2597,7 @@
 // to be returned to the calling function.
 func (m *Library) convertLibraryAttrsBp2Build(ctx android.TopDownMutatorContext) (*javaCommonAttributes, *bp2BuildJavaInfo) {
 	var srcs bazel.LabelListAttribute
-	var deps bazel.LabelList
+	var deps bazel.LabelListAttribute
 	var staticDeps bazel.LabelList
 
 	archVariantProps := m.GetArchVariantProperties(ctx, &CommonProperties{})
@@ -2674,11 +2703,17 @@
 		Javacopts: bazel.MakeStringListAttribute(javacopts),
 	}
 
-	if m.properties.Libs != nil {
-		for _, d := range m.properties.Libs {
-			neverlinkLabel := android.BazelLabelForModuleDepSingle(ctx, d)
-			neverlinkLabel.Label = neverlinkLabel.Label + "-neverlink"
-			deps.Add(&neverlinkLabel)
+	for axis, configToProps := range archVariantProps {
+		for config, _props := range configToProps {
+			if archProps, ok := _props.(*CommonProperties); ok {
+				var libLabels []bazel.Label
+				for _, d := range archProps.Libs {
+					neverlinkLabel := android.BazelLabelForModuleDepSingle(ctx, d)
+					neverlinkLabel.Label = neverlinkLabel.Label + "-neverlink"
+					libLabels = append(libLabels, neverlinkLabel)
+				}
+				deps.SetSelectValue(axis, config, bazel.MakeLabelList(libLabels))
+			}
 		}
 	}
 
@@ -2696,7 +2731,7 @@
 	staticDeps.Add(protoDepLabel)
 
 	depLabels := &javaDependencyLabels{}
-	depLabels.Deps = bazel.MakeLabelListAttribute(deps)
+	depLabels.Deps = deps
 	depLabels.StaticDeps = bazel.MakeLabelListAttribute(staticDeps)
 
 	bp2BuildInfo := &bp2BuildJavaInfo{
diff --git a/java/java_test.go b/java/java_test.go
index 49adefb..21993ec 100644
--- a/java/java_test.go
+++ b/java/java_test.go
@@ -30,7 +30,6 @@
 	"android/soong/cc"
 	"android/soong/dexpreopt"
 	"android/soong/genrule"
-	"android/soong/python"
 )
 
 // Legacy preparer used for running tests within the java package.
@@ -49,7 +48,6 @@
 	// Include all the default java modules.
 	PrepareForTestWithJavaDefaultModules,
 	PrepareForTestWithOverlayBuildComponents,
-	python.PrepareForTestWithPythonBuildComponents,
 	android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) {
 		ctx.RegisterPreSingletonType("sdk_versions", sdkPreSingletonFactory)
 	}),
@@ -1440,24 +1438,26 @@
 }
 
 func TestDataNativeBinaries(t *testing.T) {
-	ctx, _ := testJava(t, `
+	ctx := android.GroupFixturePreparers(
+		prepareForJavaTest,
+		android.PrepareForTestWithAllowMissingDependencies).RunTestWithBp(t, `
 		java_test_host {
 			name: "foo",
 			srcs: ["a.java"],
 			data_native_bins: ["bin"]
 		}
 
-		python_binary_host {
+		cc_binary_host {
 			name: "bin",
-			srcs: ["bin.py"],
+			srcs: ["bin.cpp"],
 		}
-	`)
+	`).TestContext
 
 	buildOS := ctx.Config().BuildOS.String()
 
 	test := ctx.ModuleForTests("foo", buildOS+"_common").Module().(*TestHost)
 	entries := android.AndroidMkEntriesForTest(t, ctx, test)[0]
-	expected := []string{"out/soong/.intermediates/bin/" + buildOS + "_x86_64_PY3/bin:bin"}
+	expected := []string{"out/soong/.intermediates/bin/" + buildOS + "_x86_64/bin:bin"}
 	actual := entries.EntryMap["LOCAL_COMPATIBILITY_SUPPORT_FILES"]
 	android.AssertStringPathsRelativeToTopEquals(t, "LOCAL_COMPATIBILITY_SUPPORT_FILES", ctx.Config(), expected, actual)
 }
@@ -1840,6 +1840,20 @@
 		}`)
 }
 
+func TestJavaApiContributionEmptyApiFile(t *testing.T) {
+	testJavaError(t,
+		"Error: foo has an empty api file.",
+		`java_api_contribution {
+			name: "foo",
+		}
+		java_api_library {
+			name: "bar",
+			api_surface: "public",
+			api_contributions: ["foo"],
+		}
+	`)
+}
+
 func TestJavaApiLibraryAndProviderLink(t *testing.T) {
 	provider_bp_a := `
 	java_api_contribution {
@@ -1895,22 +1909,26 @@
 }
 
 func TestJavaApiLibraryAndDefaultsLink(t *testing.T) {
-	provider_bp_a := `java_api_contribution {
+	provider_bp_a := `
+	java_api_contribution {
 		name: "foo1",
 		api_file: "foo1.txt",
 	}
 	`
-	provider_bp_b := `java_api_contribution {
+	provider_bp_b := `
+	java_api_contribution {
 		name: "foo2",
 		api_file: "foo2.txt",
 	}
 	`
-	provider_bp_c := `java_api_contribution {
+	provider_bp_c := `
+	java_api_contribution {
 		name: "foo3",
 		api_file: "foo3.txt",
 	}
 	`
-	provider_bp_d := `java_api_contribution {
+	provider_bp_d := `
+	java_api_contribution {
 		name: "foo4",
 		api_file: "foo4.txt",
 	}
@@ -1989,7 +2007,8 @@
 		api_file: "foo1.txt",
 	}
 	`
-	provider_bp_b := `java_api_contribution {
+	provider_bp_b := `
+	java_api_contribution {
 		name: "foo2",
 		api_file: "foo2.txt",
 	}
@@ -2034,6 +2053,81 @@
 	}
 }
 
+func TestJavaApiLibraryLibsLink(t *testing.T) {
+	provider_bp_a := `
+	java_api_contribution {
+		name: "foo1",
+		api_file: "foo1.txt",
+	}
+	`
+	provider_bp_b := `
+	java_api_contribution {
+		name: "foo2",
+		api_file: "foo2.txt",
+	}
+	`
+	lib_bp_a := `
+	java_library {
+		name: "lib1",
+		srcs: ["Lib.java"],
+	}
+	`
+	lib_bp_b := `
+	java_library {
+		name: "lib2",
+		srcs: ["Lib.java"],
+	}
+	`
+
+	ctx, _ := testJavaWithFS(t, `
+		java_api_library {
+			name: "bar1",
+			api_surface: "public",
+			api_contributions: ["foo1"],
+			libs: ["lib1"],
+		}
+
+		java_api_library {
+			name: "bar2",
+			api_surface: "system",
+			api_contributions: ["foo1", "foo2"],
+			libs: ["lib1", "lib2", "bar1"],
+		}
+		`,
+		map[string][]byte{
+			"a/Android.bp": []byte(provider_bp_a),
+			"b/Android.bp": []byte(provider_bp_b),
+			"c/Android.bp": []byte(lib_bp_a),
+			"c/Lib.java":   {},
+			"d/Android.bp": []byte(lib_bp_b),
+			"d/Lib.java":   {},
+		})
+
+	testcases := []struct {
+		moduleName        string
+		classPathJarNames []string
+	}{
+		{
+			moduleName:        "bar1",
+			classPathJarNames: []string{"lib1.jar"},
+		},
+		{
+			moduleName:        "bar2",
+			classPathJarNames: []string{"lib1.jar", "lib2.jar", "bar1/android.jar"},
+		},
+	}
+	for _, c := range testcases {
+		m := ctx.ModuleForTests(c.moduleName, "android_common")
+		javacRules := m.Rule("javac")
+		classPathArgs := javacRules.Args["classpath"]
+		for _, jarName := range c.classPathJarNames {
+			if !strings.Contains(classPathArgs, jarName) {
+				t.Errorf("Module output does not contain expected jar %s", jarName)
+			}
+		}
+	}
+}
+
 func TestTradefedOptions(t *testing.T) {
 	result := PrepareForTestWithJavaBuildComponents.RunTestWithBp(t, `
 java_test_host {
diff --git a/java/kotlin_test.go b/java/kotlin_test.go
index 491ce29..933fc51 100644
--- a/java/kotlin_test.go
+++ b/java/kotlin_test.go
@@ -44,6 +44,10 @@
 
 	kotlinStdlib := ctx.ModuleForTests("kotlin-stdlib", "android_common").
 		Output("turbine-combined/kotlin-stdlib.jar").Output
+	kotlinStdlibJdk7 := ctx.ModuleForTests("kotlin-stdlib-jdk7", "android_common").
+		Output("turbine-combined/kotlin-stdlib-jdk7.jar").Output
+	kotlinStdlibJdk8 := ctx.ModuleForTests("kotlin-stdlib-jdk8", "android_common").
+		Output("turbine-combined/kotlin-stdlib-jdk8.jar").Output
 	kotlinAnnotations := ctx.ModuleForTests("kotlin-annotations", "android_common").
 		Output("turbine-combined/kotlin-annotations.jar").Output
 
@@ -79,6 +83,16 @@
 			fooJar.Inputs.Strings(), kotlinStdlib.String())
 	}
 
+	if !inList(kotlinStdlibJdk7.String(), fooJar.Inputs.Strings()) {
+		t.Errorf("foo jar inputs %v does not contain %v",
+			fooJar.Inputs.Strings(), kotlinStdlibJdk7.String())
+	}
+
+	if !inList(kotlinStdlibJdk8.String(), fooJar.Inputs.Strings()) {
+		t.Errorf("foo jar inputs %v does not contain %v",
+			fooJar.Inputs.Strings(), kotlinStdlibJdk8.String())
+	}
+
 	if !inList(kotlinAnnotations.String(), fooJar.Inputs.Strings()) {
 		t.Errorf("foo jar inputs %v does not contain %v",
 			fooJar.Inputs.Strings(), kotlinAnnotations.String())
diff --git a/java/sdk.go b/java/sdk.go
index b0da5af..10ae3f6 100644
--- a/java/sdk.go
+++ b/java/sdk.go
@@ -57,14 +57,10 @@
 		return JAVA_VERSION_8
 	} else if sdk.FinalOrFutureInt() <= 31 {
 		return JAVA_VERSION_9
-	} else if ctx.Config().TargetsJava17() {
-		// Temporary experimental flag to be able to try and build with
-		// java version 17 options.  The flag, if used, just sets Java
-		// 17 as the default version, leaving any components that
-		// target an older version intact.
-		return JAVA_VERSION_17
-	} else {
+	} else if sdk.FinalOrFutureInt() <= 32 {
 		return JAVA_VERSION_11
+	} else {
+		return JAVA_VERSION_17
 	}
 }
 
diff --git a/java/sdk_library.go b/java/sdk_library.go
index 3b64bf7..a2295f4 100644
--- a/java/sdk_library.go
+++ b/java/sdk_library.go
@@ -1599,6 +1599,7 @@
 		Srcs                             []string
 		Installable                      *bool
 		Sdk_version                      *string
+		Api_surface                      *string
 		System_modules                   *string
 		Libs                             []string
 		Output_javadoc_comments          *bool
@@ -1638,6 +1639,7 @@
 	props.Srcs = append(props.Srcs, module.properties.Srcs...)
 	props.Srcs = append(props.Srcs, module.sdkLibraryProperties.Api_srcs...)
 	props.Sdk_version = module.deviceProperties.Sdk_version
+	props.Api_surface = &apiScope.name
 	props.System_modules = module.deviceProperties.System_modules
 	props.Installable = proptools.BoolPtr(false)
 	// A droiddoc module has only one Libs property and doesn't distinguish between
@@ -1747,7 +1749,7 @@
 		}
 	}
 
-	mctx.CreateModule(DroidstubsFactory, &props)
+	mctx.CreateModule(DroidstubsFactory, &props).(*Droidstubs).CallHookIfAvailable(mctx)
 }
 
 func (module *SdkLibrary) compareAgainstLatestApi(apiScope *apiScope) bool {
diff --git a/java/sdk_library_test.go b/java/sdk_library_test.go
index 210bfc3..1d0c13d 100644
--- a/java/sdk_library_test.go
+++ b/java/sdk_library_test.go
@@ -120,6 +120,7 @@
 	result.ModuleForTests(apiScopePublic.stubsSourceModuleName("foo"), "android_common")
 	result.ModuleForTests(apiScopeSystem.stubsSourceModuleName("foo"), "android_common")
 	result.ModuleForTests(apiScopeTest.stubsSourceModuleName("foo"), "android_common")
+	result.ModuleForTests(apiScopePublic.stubsSourceModuleName("foo")+".api.contribution", "")
 	result.ModuleForTests("foo"+sdkXmlFileSuffix, "android_common")
 	result.ModuleForTests("foo.api.public.28", "")
 	result.ModuleForTests("foo.api.system.28", "")
diff --git a/python/Android.bp b/python/Android.bp
index e49fa6a..7578673 100644
--- a/python/Android.bp
+++ b/python/Android.bp
@@ -9,13 +9,13 @@
         "blueprint",
         "soong-android",
         "soong-tradefed",
+        "soong-cc",
     ],
     srcs: [
-        "androidmk.go",
         "binary.go",
+        "bp2build.go",
         "builder.go",
         "defaults.go",
-        "installer.go",
         "library.go",
         "proto.go",
         "python.go",
diff --git a/python/androidmk.go b/python/androidmk.go
deleted file mode 100644
index 7dc4713..0000000
--- a/python/androidmk.go
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright 2017 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 python
-
-import (
-	"path/filepath"
-	"strings"
-
-	"android/soong/android"
-)
-
-type subAndroidMkProvider interface {
-	AndroidMk(*Module, *android.AndroidMkEntries)
-}
-
-func (p *Module) subAndroidMk(entries *android.AndroidMkEntries, obj interface{}) {
-	if p.subAndroidMkOnce == nil {
-		p.subAndroidMkOnce = make(map[subAndroidMkProvider]bool)
-	}
-	if androidmk, ok := obj.(subAndroidMkProvider); ok {
-		if !p.subAndroidMkOnce[androidmk] {
-			p.subAndroidMkOnce[androidmk] = true
-			androidmk.AndroidMk(p, entries)
-		}
-	}
-}
-
-func (p *Module) AndroidMkEntries() []android.AndroidMkEntries {
-	entries := android.AndroidMkEntries{OutputFile: p.installSource}
-
-	p.subAndroidMk(&entries, p.installer)
-
-	return []android.AndroidMkEntries{entries}
-}
-
-func (p *binaryDecorator) AndroidMk(base *Module, entries *android.AndroidMkEntries) {
-	entries.Class = "EXECUTABLES"
-
-	entries.ExtraEntries = append(entries.ExtraEntries,
-		func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-			entries.AddCompatibilityTestSuites(p.binaryProperties.Test_suites...)
-		})
-	base.subAndroidMk(entries, p.pythonInstaller)
-}
-
-func (p *testDecorator) AndroidMk(base *Module, entries *android.AndroidMkEntries) {
-	entries.Class = "NATIVE_TESTS"
-
-	entries.ExtraEntries = append(entries.ExtraEntries,
-		func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-			entries.AddCompatibilityTestSuites(p.binaryDecorator.binaryProperties.Test_suites...)
-			if p.testConfig != nil {
-				entries.SetString("LOCAL_FULL_TEST_CONFIG", p.testConfig.String())
-			}
-
-			entries.SetBoolIfTrue("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", !BoolDefault(p.binaryProperties.Auto_gen_config, true))
-
-			entries.AddStrings("LOCAL_TEST_DATA", android.AndroidMkDataPaths(p.data)...)
-
-			p.testProperties.Test_options.SetAndroidMkEntries(entries)
-		})
-	base.subAndroidMk(entries, p.binaryDecorator.pythonInstaller)
-}
-
-func (installer *pythonInstaller) AndroidMk(base *Module, entries *android.AndroidMkEntries) {
-	entries.Required = append(entries.Required, "libc++")
-	entries.ExtraEntries = append(entries.ExtraEntries,
-		func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
-			path, file := filepath.Split(installer.path.String())
-			stem := strings.TrimSuffix(file, filepath.Ext(file))
-
-			entries.SetString("LOCAL_MODULE_SUFFIX", filepath.Ext(file))
-			entries.SetString("LOCAL_MODULE_PATH", path)
-			entries.SetString("LOCAL_MODULE_STEM", stem)
-			entries.AddStrings("LOCAL_SHARED_LIBRARIES", installer.androidMkSharedLibs...)
-			entries.SetBool("LOCAL_CHECK_ELF_FILES", false)
-		})
-}
diff --git a/python/binary.go b/python/binary.go
index 670e0d3..75135f3 100644
--- a/python/binary.go
+++ b/python/binary.go
@@ -18,11 +18,10 @@
 
 import (
 	"fmt"
+	"path/filepath"
+	"strings"
 
 	"android/soong/android"
-	"android/soong/bazel"
-
-	"github.com/google/blueprint/proptools"
 )
 
 func init() {
@@ -33,63 +32,6 @@
 	ctx.RegisterModuleType("python_binary_host", PythonBinaryHostFactory)
 }
 
-type bazelPythonBinaryAttributes struct {
-	Main           *bazel.Label
-	Srcs           bazel.LabelListAttribute
-	Deps           bazel.LabelListAttribute
-	Python_version *string
-	Imports        bazel.StringListAttribute
-}
-
-func pythonBinaryBp2Build(ctx android.TopDownMutatorContext, m *Module) {
-	// TODO(b/182306917): this doesn't fully handle all nested props versioned
-	// by the python version, which would have been handled by the version split
-	// mutator. This is sufficient for very simple python_binary_host modules
-	// under Bionic.
-	py3Enabled := proptools.BoolDefault(m.properties.Version.Py3.Enabled, false)
-	py2Enabled := proptools.BoolDefault(m.properties.Version.Py2.Enabled, false)
-	var python_version *string
-	if py3Enabled && py2Enabled {
-		panic(fmt.Errorf(
-			"error for '%s' module: bp2build's python_binary_host converter does not support "+
-				"converting a module that is enabled for both Python 2 and 3 at the same time.", m.Name()))
-	} else if py2Enabled {
-		python_version = &pyVersion2
-	} else {
-		// do nothing, since python_version defaults to PY3.
-	}
-
-	baseAttrs := m.makeArchVariantBaseAttributes(ctx)
-	attrs := &bazelPythonBinaryAttributes{
-		Main:           nil,
-		Srcs:           baseAttrs.Srcs,
-		Deps:           baseAttrs.Deps,
-		Python_version: python_version,
-		Imports:        baseAttrs.Imports,
-	}
-
-	for _, propIntf := range m.GetProperties() {
-		if props, ok := propIntf.(*BinaryProperties); ok {
-			// main is optional.
-			if props.Main != nil {
-				main := android.BazelLabelForModuleSrcSingle(ctx, *props.Main)
-				attrs.Main = &main
-				break
-			}
-		}
-	}
-
-	props := bazel.BazelTargetModuleProperties{
-		// Use the native py_binary rule.
-		Rule_class: "py_binary",
-	}
-
-	ctx.CreateBazelTargetModule(props, android.CommonAttributes{
-		Name: m.Name(),
-		Data: baseAttrs.Data,
-	}, attrs)
-}
-
 type BinaryProperties struct {
 	// the name of the source file that is the main entry point of the program.
 	// this file must also be listed in srcs.
@@ -118,49 +60,58 @@
 	Auto_gen_config *bool
 }
 
-type binaryDecorator struct {
+type PythonBinaryModule struct {
+	PythonLibraryModule
 	binaryProperties BinaryProperties
 
-	*pythonInstaller
+	// (.intermediate) module output path as installation source.
+	installSource android.Path
+
+	// Final installation path.
+	installedDest android.Path
+
+	androidMkSharedLibs []string
 }
 
+var _ android.AndroidMkEntriesProvider = (*PythonBinaryModule)(nil)
+var _ android.Module = (*PythonBinaryModule)(nil)
+
 type IntermPathProvider interface {
 	IntermPathForModuleOut() android.OptionalPath
 }
 
-func NewBinary(hod android.HostOrDeviceSupported) (*Module, *binaryDecorator) {
-	module := newModule(hod, android.MultilibFirst)
-	decorator := &binaryDecorator{pythonInstaller: NewPythonInstaller("bin", "")}
-
-	module.bootstrapper = decorator
-	module.installer = decorator
-
-	return module, decorator
+func NewBinary(hod android.HostOrDeviceSupported) *PythonBinaryModule {
+	return &PythonBinaryModule{
+		PythonLibraryModule: *newModule(hod, android.MultilibFirst),
+	}
 }
 
 func PythonBinaryHostFactory() android.Module {
-	module, _ := NewBinary(android.HostSupported)
-
-	android.InitBazelModule(module)
-
-	return module.init()
+	return NewBinary(android.HostSupported).init()
 }
 
-func (binary *binaryDecorator) autorun() bool {
-	return BoolDefault(binary.binaryProperties.Autorun, true)
+func (p *PythonBinaryModule) init() android.Module {
+	p.AddProperties(&p.properties, &p.protoProperties)
+	p.AddProperties(&p.binaryProperties)
+	android.InitAndroidArchModule(p, p.hod, p.multilib)
+	android.InitDefaultableModule(p)
+	android.InitBazelModule(p)
+	return p
 }
 
-func (binary *binaryDecorator) bootstrapperProps() []interface{} {
-	return []interface{}{&binary.binaryProperties}
+func (p *PythonBinaryModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	p.PythonLibraryModule.GenerateAndroidBuildActions(ctx)
+	p.buildBinary(ctx)
+	p.installedDest = ctx.InstallFile(installDir(ctx, "bin", "", ""),
+		p.installSource.Base(), p.installSource)
 }
 
-func (binary *binaryDecorator) bootstrap(ctx android.ModuleContext, actualVersion string,
-	embeddedLauncher bool, srcsPathMappings []pathMapping, srcsZip android.Path,
-	depsSrcsZips android.Paths) android.OptionalPath {
-
+func (p *PythonBinaryModule) buildBinary(ctx android.ModuleContext) {
+	embeddedLauncher := p.isEmbeddedLauncherEnabled()
+	depsSrcsZips := p.collectPathsFromTransitiveDeps(ctx, embeddedLauncher)
 	main := ""
-	if binary.autorun() {
-		main = binary.getPyMainFile(ctx, srcsPathMappings)
+	if p.autorun() {
+		main = p.getPyMainFile(ctx, p.srcsPathMappings)
 	}
 
 	var launcherPath android.OptionalPath
@@ -175,15 +126,88 @@
 			}
 		})
 	}
-	binFile := registerBuildActionForParFile(ctx, embeddedLauncher, launcherPath,
-		binary.getHostInterpreterName(ctx, actualVersion),
-		main, binary.getStem(ctx), append(android.Paths{srcsZip}, depsSrcsZips...))
+	srcsZips := make(android.Paths, 0, len(depsSrcsZips)+1)
+	if embeddedLauncher {
+		srcsZips = append(srcsZips, p.precompiledSrcsZip)
+	} else {
+		srcsZips = append(srcsZips, p.srcsZip)
+	}
+	srcsZips = append(srcsZips, depsSrcsZips...)
+	p.installSource = registerBuildActionForParFile(ctx, embeddedLauncher, launcherPath,
+		p.getHostInterpreterName(ctx, p.properties.Actual_version),
+		main, p.getStem(ctx), srcsZips)
 
-	return android.OptionalPathForPath(binFile)
+	var sharedLibs []string
+	// if embedded launcher is enabled, we need to collect the shared library dependencies of the
+	// launcher
+	for _, dep := range ctx.GetDirectDepsWithTag(launcherSharedLibTag) {
+		sharedLibs = append(sharedLibs, ctx.OtherModuleName(dep))
+	}
+	p.androidMkSharedLibs = sharedLibs
+}
+
+func (p *PythonBinaryModule) AndroidMkEntries() []android.AndroidMkEntries {
+	entries := android.AndroidMkEntries{OutputFile: android.OptionalPathForPath(p.installSource)}
+
+	entries.Class = "EXECUTABLES"
+
+	entries.ExtraEntries = append(entries.ExtraEntries,
+		func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+			entries.AddCompatibilityTestSuites(p.binaryProperties.Test_suites...)
+		})
+
+	entries.Required = append(entries.Required, "libc++")
+	entries.ExtraEntries = append(entries.ExtraEntries,
+		func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+			path, file := filepath.Split(p.installedDest.String())
+			stem := strings.TrimSuffix(file, filepath.Ext(file))
+
+			entries.SetString("LOCAL_MODULE_SUFFIX", filepath.Ext(file))
+			entries.SetString("LOCAL_MODULE_PATH", path)
+			entries.SetString("LOCAL_MODULE_STEM", stem)
+			entries.AddStrings("LOCAL_SHARED_LIBRARIES", p.androidMkSharedLibs...)
+			entries.SetBool("LOCAL_CHECK_ELF_FILES", false)
+		})
+
+	return []android.AndroidMkEntries{entries}
+}
+
+func (p *PythonBinaryModule) DepsMutator(ctx android.BottomUpMutatorContext) {
+	p.PythonLibraryModule.DepsMutator(ctx)
+
+	if p.isEmbeddedLauncherEnabled() {
+		p.AddDepsOnPythonLauncherAndStdlib(ctx, pythonLibTag, launcherTag, launcherSharedLibTag, p.autorun(), ctx.Target())
+	}
+}
+
+// HostToolPath returns a path if appropriate such that this module can be used as a host tool,
+// fulfilling the android.HostToolProvider interface.
+func (p *PythonBinaryModule) HostToolPath() android.OptionalPath {
+	// TODO: This should only be set when building host binaries -- tests built for device would be
+	// setting this incorrectly.
+	return android.OptionalPathForPath(p.installedDest)
+}
+
+// OutputFiles returns output files based on given tag, returns an error if tag is unsupported.
+func (p *PythonBinaryModule) OutputFiles(tag string) (android.Paths, error) {
+	switch tag {
+	case "":
+		return android.Paths{p.installSource}, nil
+	default:
+		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
+	}
+}
+
+func (p *PythonBinaryModule) isEmbeddedLauncherEnabled() bool {
+	return Bool(p.properties.Embedded_launcher)
+}
+
+func (b *PythonBinaryModule) autorun() bool {
+	return BoolDefault(b.binaryProperties.Autorun, true)
 }
 
 // get host interpreter name.
-func (binary *binaryDecorator) getHostInterpreterName(ctx android.ModuleContext,
+func (p *PythonBinaryModule) getHostInterpreterName(ctx android.ModuleContext,
 	actualVersion string) string {
 	var interp string
 	switch actualVersion {
@@ -200,13 +224,13 @@
 }
 
 // find main program path within runfiles tree.
-func (binary *binaryDecorator) getPyMainFile(ctx android.ModuleContext,
+func (p *PythonBinaryModule) getPyMainFile(ctx android.ModuleContext,
 	srcsPathMappings []pathMapping) string {
 	var main string
-	if String(binary.binaryProperties.Main) == "" {
+	if String(p.binaryProperties.Main) == "" {
 		main = ctx.ModuleName() + pyExt
 	} else {
-		main = String(binary.binaryProperties.Main)
+		main = String(p.binaryProperties.Main)
 	}
 
 	for _, path := range srcsPathMappings {
@@ -219,11 +243,21 @@
 	return ""
 }
 
-func (binary *binaryDecorator) getStem(ctx android.ModuleContext) string {
+func (p *PythonBinaryModule) getStem(ctx android.ModuleContext) string {
 	stem := ctx.ModuleName()
-	if String(binary.binaryProperties.Stem) != "" {
-		stem = String(binary.binaryProperties.Stem)
+	if String(p.binaryProperties.Stem) != "" {
+		stem = String(p.binaryProperties.Stem)
 	}
 
-	return stem + String(binary.binaryProperties.Suffix)
+	return stem + String(p.binaryProperties.Suffix)
+}
+
+func installDir(ctx android.ModuleContext, dir, dir64, relative string) android.InstallPath {
+	if ctx.Arch().ArchType.Multilib == "lib64" && dir64 != "" {
+		dir = dir64
+	}
+	if !ctx.Host() && ctx.Config().HasMultilibConflict(ctx.Arch().ArchType) {
+		dir = filepath.Join(dir, ctx.Arch().ArchType.String())
+	}
+	return android.PathForModuleInstall(ctx, dir, relative)
 }
diff --git a/python/bp2build.go b/python/bp2build.go
new file mode 100644
index 0000000..bdac2dc
--- /dev/null
+++ b/python/bp2build.go
@@ -0,0 +1,226 @@
+// Copyright 2023 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 python
+
+import (
+	"fmt"
+	"path/filepath"
+	"strings"
+
+	"github.com/google/blueprint/proptools"
+
+	"android/soong/android"
+	"android/soong/bazel"
+)
+
+type bazelPythonLibraryAttributes struct {
+	Srcs         bazel.LabelListAttribute
+	Deps         bazel.LabelListAttribute
+	Imports      bazel.StringListAttribute
+	Srcs_version *string
+}
+
+type bazelPythonProtoLibraryAttributes struct {
+	Deps bazel.LabelListAttribute
+}
+
+type baseAttributes struct {
+	// TODO(b/200311466): Probably not translate b/c Bazel has no good equiv
+	//Pkg_path    bazel.StringAttribute
+	// TODO: Related to Pkg_bath and similarLy gated
+	//Is_internal bazel.BoolAttribute
+	// Combines Srcs and Exclude_srcs
+	Srcs bazel.LabelListAttribute
+	Deps bazel.LabelListAttribute
+	// Combines Data and Java_data (invariant)
+	Data    bazel.LabelListAttribute
+	Imports bazel.StringListAttribute
+}
+
+func (m *PythonLibraryModule) makeArchVariantBaseAttributes(ctx android.TopDownMutatorContext) baseAttributes {
+	var attrs baseAttributes
+	archVariantBaseProps := m.GetArchVariantProperties(ctx, &BaseProperties{})
+	for axis, configToProps := range archVariantBaseProps {
+		for config, props := range configToProps {
+			if baseProps, ok := props.(*BaseProperties); ok {
+				attrs.Srcs.SetSelectValue(axis, config,
+					android.BazelLabelForModuleSrcExcludes(ctx, baseProps.Srcs, baseProps.Exclude_srcs))
+				attrs.Deps.SetSelectValue(axis, config,
+					android.BazelLabelForModuleDeps(ctx, baseProps.Libs))
+				data := android.BazelLabelForModuleSrc(ctx, baseProps.Data)
+				data.Append(android.BazelLabelForModuleSrc(ctx, baseProps.Java_data))
+				attrs.Data.SetSelectValue(axis, config, data)
+			}
+		}
+	}
+
+	partitionedSrcs := bazel.PartitionLabelListAttribute(ctx, &attrs.Srcs, bazel.LabelPartitions{
+		"proto": android.ProtoSrcLabelPartition,
+		"py":    bazel.LabelPartition{Keep_remainder: true},
+	})
+	attrs.Srcs = partitionedSrcs["py"]
+
+	if !partitionedSrcs["proto"].IsEmpty() {
+		protoInfo, _ := android.Bp2buildProtoProperties(ctx, &m.ModuleBase, partitionedSrcs["proto"])
+		protoLabel := bazel.Label{Label: ":" + protoInfo.Name}
+
+		pyProtoLibraryName := m.Name() + "_py_proto"
+		ctx.CreateBazelTargetModule(bazel.BazelTargetModuleProperties{
+			Rule_class:        "py_proto_library",
+			Bzl_load_location: "//build/bazel/rules/python:py_proto.bzl",
+		}, android.CommonAttributes{
+			Name: pyProtoLibraryName,
+		}, &bazelPythonProtoLibraryAttributes{
+			Deps: bazel.MakeSingleLabelListAttribute(protoLabel),
+		})
+
+		attrs.Deps.Add(bazel.MakeLabelAttribute(":" + pyProtoLibraryName))
+	}
+
+	// Bazel normally requires `import path.from.top.of.tree` statements in
+	// python code, but with soong you can directly import modules from libraries.
+	// Add "imports" attributes to the bazel library so it matches soong's behavior.
+	imports := "."
+	if m.properties.Pkg_path != nil {
+		// TODO(b/215119317) This is a hack to handle the fact that we don't convert
+		// pkg_path properly right now. If the folder structure that contains this
+		// Android.bp file matches pkg_path, we can set imports to an appropriate
+		// number of ../..s to emulate moving the files under a pkg_path folder.
+		pkg_path := filepath.Clean(*m.properties.Pkg_path)
+		if strings.HasPrefix(pkg_path, "/") {
+			ctx.ModuleErrorf("pkg_path cannot start with a /: %s", pkg_path)
+		}
+
+		if !strings.HasSuffix(ctx.ModuleDir(), "/"+pkg_path) && ctx.ModuleDir() != pkg_path {
+			ctx.ModuleErrorf("Currently, bp2build only supports pkg_paths that are the same as the folders the Android.bp file is in. pkg_path: %s, module directory: %s", pkg_path, ctx.ModuleDir())
+		}
+		numFolders := strings.Count(pkg_path, "/") + 1
+		dots := make([]string, numFolders)
+		for i := 0; i < numFolders; i++ {
+			dots[i] = ".."
+		}
+		imports = strings.Join(dots, "/")
+	}
+	attrs.Imports = bazel.MakeStringListAttribute([]string{imports})
+
+	return attrs
+}
+
+func pythonLibBp2Build(ctx android.TopDownMutatorContext, m *PythonLibraryModule) {
+	// TODO(b/182306917): this doesn't fully handle all nested props versioned
+	// by the python version, which would have been handled by the version split
+	// mutator. This is sufficient for very simple python_library modules under
+	// Bionic.
+	py3Enabled := proptools.BoolDefault(m.properties.Version.Py3.Enabled, true)
+	py2Enabled := proptools.BoolDefault(m.properties.Version.Py2.Enabled, false)
+	var python_version *string
+	if py2Enabled && !py3Enabled {
+		python_version = &pyVersion2
+	} else if !py2Enabled && py3Enabled {
+		python_version = &pyVersion3
+	} else if !py2Enabled && !py3Enabled {
+		ctx.ModuleErrorf("bp2build converter doesn't understand having neither py2 nor py3 enabled")
+	} else {
+		// do nothing, since python_version defaults to PY2ANDPY3
+	}
+
+	baseAttrs := m.makeArchVariantBaseAttributes(ctx)
+
+	attrs := &bazelPythonLibraryAttributes{
+		Srcs:         baseAttrs.Srcs,
+		Deps:         baseAttrs.Deps,
+		Srcs_version: python_version,
+		Imports:      baseAttrs.Imports,
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		// Use the native py_library rule.
+		Rule_class: "py_library",
+	}
+
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{
+		Name: m.Name(),
+		Data: baseAttrs.Data,
+	}, attrs)
+}
+
+type bazelPythonBinaryAttributes struct {
+	Main           *bazel.Label
+	Srcs           bazel.LabelListAttribute
+	Deps           bazel.LabelListAttribute
+	Python_version *string
+	Imports        bazel.StringListAttribute
+}
+
+func pythonBinaryBp2Build(ctx android.TopDownMutatorContext, m *PythonBinaryModule) {
+	// TODO(b/182306917): this doesn't fully handle all nested props versioned
+	// by the python version, which would have been handled by the version split
+	// mutator. This is sufficient for very simple python_binary_host modules
+	// under Bionic.
+	py3Enabled := proptools.BoolDefault(m.properties.Version.Py3.Enabled, false)
+	py2Enabled := proptools.BoolDefault(m.properties.Version.Py2.Enabled, false)
+	var python_version *string
+	if py3Enabled && py2Enabled {
+		panic(fmt.Errorf(
+			"error for '%s' module: bp2build's python_binary_host converter does not support "+
+				"converting a module that is enabled for both Python 2 and 3 at the same time.", m.Name()))
+	} else if py2Enabled {
+		python_version = &pyVersion2
+	} else {
+		// do nothing, since python_version defaults to PY3.
+	}
+
+	baseAttrs := m.makeArchVariantBaseAttributes(ctx)
+	attrs := &bazelPythonBinaryAttributes{
+		Main:           nil,
+		Srcs:           baseAttrs.Srcs,
+		Deps:           baseAttrs.Deps,
+		Python_version: python_version,
+		Imports:        baseAttrs.Imports,
+	}
+
+	for _, propIntf := range m.GetProperties() {
+		if props, ok := propIntf.(*BinaryProperties); ok {
+			// main is optional.
+			if props.Main != nil {
+				main := android.BazelLabelForModuleSrcSingle(ctx, *props.Main)
+				attrs.Main = &main
+				break
+			}
+		}
+	}
+
+	props := bazel.BazelTargetModuleProperties{
+		// Use the native py_binary rule.
+		Rule_class: "py_binary",
+	}
+
+	ctx.CreateBazelTargetModule(props, android.CommonAttributes{
+		Name: m.Name(),
+		Data: baseAttrs.Data,
+	}, attrs)
+}
+
+func (p *PythonLibraryModule) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	pythonLibBp2Build(ctx, p)
+}
+
+func (p *PythonBinaryModule) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
+	pythonBinaryBp2Build(ctx, p)
+}
+
+func (p *PythonTestModule) ConvertWithBp2build(_ android.TopDownMutatorContext) {
+	// Tests are currently unsupported
+}
diff --git a/python/builder.go b/python/builder.go
index b4ab206..1066493 100644
--- a/python/builder.go
+++ b/python/builder.go
@@ -70,6 +70,17 @@
 			CommandDeps: []string{"$mergeParCmd"},
 		},
 		"srcsZips", "launcher")
+
+	precompile = pctx.AndroidStaticRule("precompilePython", blueprint.RuleParams{
+		Command: `LD_LIBRARY_PATH="$ldLibraryPath" ` +
+			`PYTHONPATH=$stdlibZip/internal/stdlib ` +
+			`$launcher build/soong/python/scripts/precompile_python.py $in $out`,
+		CommandDeps: []string{
+			"$stdlibZip",
+			"$launcher",
+			"build/soong/python/scripts/precompile_python.py",
+		},
+	}, "stdlibZip", "launcher", "ldLibraryPath")
 )
 
 func init() {
diff --git a/python/installer.go b/python/installer.go
deleted file mode 100644
index 396f036..0000000
--- a/python/installer.go
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright 2017 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 python
-
-import (
-	"path/filepath"
-
-	"android/soong/android"
-)
-
-// This file handles installing python executables into their final location
-
-type installLocation int
-
-const (
-	InstallInData installLocation = iota
-)
-
-type pythonInstaller struct {
-	dir      string
-	dir64    string
-	relative string
-
-	path android.InstallPath
-
-	androidMkSharedLibs []string
-}
-
-func NewPythonInstaller(dir, dir64 string) *pythonInstaller {
-	return &pythonInstaller{
-		dir:   dir,
-		dir64: dir64,
-	}
-}
-
-var _ installer = (*pythonInstaller)(nil)
-
-func (installer *pythonInstaller) installDir(ctx android.ModuleContext) android.InstallPath {
-	dir := installer.dir
-	if ctx.Arch().ArchType.Multilib == "lib64" && installer.dir64 != "" {
-		dir = installer.dir64
-	}
-	if !ctx.Host() && ctx.Config().HasMultilibConflict(ctx.Arch().ArchType) {
-		dir = filepath.Join(dir, ctx.Arch().ArchType.String())
-	}
-	return android.PathForModuleInstall(ctx, dir, installer.relative)
-}
-
-func (installer *pythonInstaller) install(ctx android.ModuleContext, file android.Path) {
-	installer.path = ctx.InstallFile(installer.installDir(ctx), file.Base(), file)
-}
-
-func (installer *pythonInstaller) setAndroidMkSharedLibs(sharedLibs []string) {
-	installer.androidMkSharedLibs = sharedLibs
-}
diff --git a/python/library.go b/python/library.go
index df92df4..7cdb80b 100644
--- a/python/library.go
+++ b/python/library.go
@@ -18,9 +18,6 @@
 
 import (
 	"android/soong/android"
-	"android/soong/bazel"
-
-	"github.com/google/blueprint/proptools"
 )
 
 func init() {
@@ -33,66 +30,9 @@
 }
 
 func PythonLibraryHostFactory() android.Module {
-	module := newModule(android.HostSupported, android.MultilibFirst)
-
-	android.InitBazelModule(module)
-
-	return module.init()
-}
-
-type bazelPythonLibraryAttributes struct {
-	Srcs         bazel.LabelListAttribute
-	Deps         bazel.LabelListAttribute
-	Imports      bazel.StringListAttribute
-	Srcs_version *string
-}
-
-type bazelPythonProtoLibraryAttributes struct {
-	Deps bazel.LabelListAttribute
-}
-
-func pythonLibBp2Build(ctx android.TopDownMutatorContext, m *Module) {
-	// TODO(b/182306917): this doesn't fully handle all nested props versioned
-	// by the python version, which would have been handled by the version split
-	// mutator. This is sufficient for very simple python_library modules under
-	// Bionic.
-	py3Enabled := proptools.BoolDefault(m.properties.Version.Py3.Enabled, true)
-	py2Enabled := proptools.BoolDefault(m.properties.Version.Py2.Enabled, false)
-	var python_version *string
-	if py2Enabled && !py3Enabled {
-		python_version = &pyVersion2
-	} else if !py2Enabled && py3Enabled {
-		python_version = &pyVersion3
-	} else if !py2Enabled && !py3Enabled {
-		ctx.ModuleErrorf("bp2build converter doesn't understand having neither py2 nor py3 enabled")
-	} else {
-		// do nothing, since python_version defaults to PY2ANDPY3
-	}
-
-	baseAttrs := m.makeArchVariantBaseAttributes(ctx)
-
-	attrs := &bazelPythonLibraryAttributes{
-		Srcs:         baseAttrs.Srcs,
-		Deps:         baseAttrs.Deps,
-		Srcs_version: python_version,
-		Imports:      baseAttrs.Imports,
-	}
-
-	props := bazel.BazelTargetModuleProperties{
-		// Use the native py_library rule.
-		Rule_class: "py_library",
-	}
-
-	ctx.CreateBazelTargetModule(props, android.CommonAttributes{
-		Name: m.Name(),
-		Data: baseAttrs.Data,
-	}, attrs)
+	return newModule(android.HostSupported, android.MultilibFirst).init()
 }
 
 func PythonLibraryFactory() android.Module {
-	module := newModule(android.HostAndDeviceSupported, android.MultilibBoth)
-
-	android.InitBazelModule(module)
-
-	return module.init()
+	return newModule(android.HostAndDeviceSupported, android.MultilibBoth).init()
 }
diff --git a/python/python.go b/python/python.go
index 24e1bb2..18e5b68 100644
--- a/python/python.go
+++ b/python/python.go
@@ -22,8 +22,6 @@
 	"regexp"
 	"strings"
 
-	"android/soong/bazel"
-
 	"github.com/google/blueprint"
 	"github.com/google/blueprint/proptools"
 
@@ -122,26 +120,13 @@
 	Embedded_launcher *bool `blueprint:"mutated"`
 }
 
-type baseAttributes struct {
-	// TODO(b/200311466): Probably not translate b/c Bazel has no good equiv
-	//Pkg_path    bazel.StringAttribute
-	// TODO: Related to Pkg_bath and similarLy gated
-	//Is_internal bazel.BoolAttribute
-	// Combines Srcs and Exclude_srcs
-	Srcs bazel.LabelListAttribute
-	Deps bazel.LabelListAttribute
-	// Combines Data and Java_data (invariant)
-	Data    bazel.LabelListAttribute
-	Imports bazel.StringListAttribute
-}
-
 // Used to store files of current module after expanding dependencies
 type pathMapping struct {
 	dest string
 	src  android.Path
 }
 
-type Module struct {
+type PythonLibraryModule struct {
 	android.ModuleBase
 	android.DefaultableModuleBase
 	android.BazelModuleBase
@@ -153,16 +138,6 @@
 	hod      android.HostOrDeviceSupported
 	multilib android.Multilib
 
-	// interface used to bootstrap .par executable when embedded_launcher is true
-	// this should be set by Python modules which are runnable, e.g. binaries and tests
-	// bootstrapper might be nil (e.g. Python library module).
-	bootstrapper bootstrapper
-
-	// interface that implements functions required for installation
-	// this should be set by Python modules which are runnable, e.g. binaries and tests
-	// installer might be nil (e.g. Python library module).
-	installer installer
-
 	// the Python files of current module after expanding source dependencies.
 	// pathMapping: <dest: runfile_path, src: source_path>
 	srcsPathMappings []pathMapping
@@ -171,152 +146,62 @@
 	// pathMapping: <dest: runfile_path, src: source_path>
 	dataPathMappings []pathMapping
 
-	// the zip filepath for zipping current module source/data files.
+	// The zip file containing the current module's source/data files.
 	srcsZip android.Path
 
-	// dependency modules' zip filepath for zipping current module source/data files.
-	depsSrcsZips android.Paths
-
-	// (.intermediate) module output path as installation source.
-	installSource android.OptionalPath
-
-	// Map to ensure sub-part of the AndroidMk for this module is only added once
-	subAndroidMkOnce map[subAndroidMkProvider]bool
+	// The zip file containing the current module's source/data files, with the
+	// source files precompiled.
+	precompiledSrcsZip android.Path
 }
 
 // newModule generates new Python base module
-func newModule(hod android.HostOrDeviceSupported, multilib android.Multilib) *Module {
-	return &Module{
+func newModule(hod android.HostOrDeviceSupported, multilib android.Multilib) *PythonLibraryModule {
+	return &PythonLibraryModule{
 		hod:      hod,
 		multilib: multilib,
 	}
 }
 
-func (m *Module) makeArchVariantBaseAttributes(ctx android.TopDownMutatorContext) baseAttributes {
-	var attrs baseAttributes
-	archVariantBaseProps := m.GetArchVariantProperties(ctx, &BaseProperties{})
-	for axis, configToProps := range archVariantBaseProps {
-		for config, props := range configToProps {
-			if baseProps, ok := props.(*BaseProperties); ok {
-				attrs.Srcs.SetSelectValue(axis, config,
-					android.BazelLabelForModuleSrcExcludes(ctx, baseProps.Srcs, baseProps.Exclude_srcs))
-				attrs.Deps.SetSelectValue(axis, config,
-					android.BazelLabelForModuleDeps(ctx, baseProps.Libs))
-				data := android.BazelLabelForModuleSrc(ctx, baseProps.Data)
-				data.Append(android.BazelLabelForModuleSrc(ctx, baseProps.Java_data))
-				attrs.Data.SetSelectValue(axis, config, data)
-			}
-		}
-	}
-
-	partitionedSrcs := bazel.PartitionLabelListAttribute(ctx, &attrs.Srcs, bazel.LabelPartitions{
-		"proto": android.ProtoSrcLabelPartition,
-		"py":    bazel.LabelPartition{Keep_remainder: true},
-	})
-	attrs.Srcs = partitionedSrcs["py"]
-
-	if !partitionedSrcs["proto"].IsEmpty() {
-		protoInfo, _ := android.Bp2buildProtoProperties(ctx, &m.ModuleBase, partitionedSrcs["proto"])
-		protoLabel := bazel.Label{Label: ":" + protoInfo.Name}
-
-		pyProtoLibraryName := m.Name() + "_py_proto"
-		ctx.CreateBazelTargetModule(bazel.BazelTargetModuleProperties{
-			Rule_class:        "py_proto_library",
-			Bzl_load_location: "//build/bazel/rules/python:py_proto.bzl",
-		}, android.CommonAttributes{
-			Name: pyProtoLibraryName,
-		}, &bazelPythonProtoLibraryAttributes{
-			Deps: bazel.MakeSingleLabelListAttribute(protoLabel),
-		})
-
-		attrs.Deps.Add(bazel.MakeLabelAttribute(":" + pyProtoLibraryName))
-	}
-
-	// Bazel normally requires `import path.from.top.of.tree` statements in
-	// python code, but with soong you can directly import modules from libraries.
-	// Add "imports" attributes to the bazel library so it matches soong's behavior.
-	imports := "."
-	if m.properties.Pkg_path != nil {
-		// TODO(b/215119317) This is a hack to handle the fact that we don't convert
-		// pkg_path properly right now. If the folder structure that contains this
-		// Android.bp file matches pkg_path, we can set imports to an appropriate
-		// number of ../..s to emulate moving the files under a pkg_path folder.
-		pkg_path := filepath.Clean(*m.properties.Pkg_path)
-		if strings.HasPrefix(pkg_path, "/") {
-			ctx.ModuleErrorf("pkg_path cannot start with a /: %s", pkg_path)
-		}
-
-		if !strings.HasSuffix(ctx.ModuleDir(), "/"+pkg_path) && ctx.ModuleDir() != pkg_path {
-			ctx.ModuleErrorf("Currently, bp2build only supports pkg_paths that are the same as the folders the Android.bp file is in. pkg_path: %s, module directory: %s", pkg_path, ctx.ModuleDir())
-		}
-		numFolders := strings.Count(pkg_path, "/") + 1
-		dots := make([]string, numFolders)
-		for i := 0; i < numFolders; i++ {
-			dots[i] = ".."
-		}
-		imports = strings.Join(dots, "/")
-	}
-	attrs.Imports = bazel.MakeStringListAttribute([]string{imports})
-
-	return attrs
-}
-
-// bootstrapper interface should be implemented for runnable modules, e.g. binary and test
-type bootstrapper interface {
-	bootstrapperProps() []interface{}
-	bootstrap(ctx android.ModuleContext, ActualVersion string, embeddedLauncher bool,
-		srcsPathMappings []pathMapping, srcsZip android.Path,
-		depsSrcsZips android.Paths) android.OptionalPath
-
-	autorun() bool
-}
-
-// installer interface should be implemented for installable modules, e.g. binary and test
-type installer interface {
-	install(ctx android.ModuleContext, path android.Path)
-	setAndroidMkSharedLibs(sharedLibs []string)
-}
-
 // interface implemented by Python modules to provide source and data mappings and zip to python
 // modules that depend on it
 type pythonDependency interface {
 	getSrcsPathMappings() []pathMapping
 	getDataPathMappings() []pathMapping
 	getSrcsZip() android.Path
+	getPrecompiledSrcsZip() android.Path
 }
 
 // getSrcsPathMappings gets this module's path mapping of src source path : runfiles destination
-func (p *Module) getSrcsPathMappings() []pathMapping {
+func (p *PythonLibraryModule) getSrcsPathMappings() []pathMapping {
 	return p.srcsPathMappings
 }
 
 // getSrcsPathMappings gets this module's path mapping of data source path : runfiles destination
-func (p *Module) getDataPathMappings() []pathMapping {
+func (p *PythonLibraryModule) getDataPathMappings() []pathMapping {
 	return p.dataPathMappings
 }
 
 // getSrcsZip returns the filepath where the current module's source/data files are zipped.
-func (p *Module) getSrcsZip() android.Path {
+func (p *PythonLibraryModule) getSrcsZip() android.Path {
 	return p.srcsZip
 }
 
-var _ pythonDependency = (*Module)(nil)
+// getSrcsZip returns the filepath where the current module's source/data files are zipped.
+func (p *PythonLibraryModule) getPrecompiledSrcsZip() android.Path {
+	return p.precompiledSrcsZip
+}
 
-var _ android.AndroidMkEntriesProvider = (*Module)(nil)
+func (p *PythonLibraryModule) getBaseProperties() *BaseProperties {
+	return &p.properties
+}
 
-func (p *Module) init(additionalProps ...interface{}) android.Module {
+var _ pythonDependency = (*PythonLibraryModule)(nil)
+
+func (p *PythonLibraryModule) init() android.Module {
 	p.AddProperties(&p.properties, &p.protoProperties)
-
-	// Add additional properties for bootstrapping/installation
-	// This is currently tied to the bootstrapper interface;
-	// however, these are a combination of properties for the installation and bootstrapping of a module
-	if p.bootstrapper != nil {
-		p.AddProperties(p.bootstrapper.bootstrapperProps()...)
-	}
-
 	android.InitAndroidArchModule(p, p.hod, p.multilib)
 	android.InitDefaultableModule(p)
-
+	android.InitBazelModule(p)
 	return p
 }
 
@@ -338,36 +223,48 @@
 }
 
 var (
-	pythonLibTag         = dependencyTag{name: "pythonLib"}
-	javaDataTag          = dependencyTag{name: "javaData"}
+	pythonLibTag = dependencyTag{name: "pythonLib"}
+	javaDataTag  = dependencyTag{name: "javaData"}
+	// The python interpreter, with soong module name "py3-launcher" or "py3-launcher-autorun".
 	launcherTag          = dependencyTag{name: "launcher"}
 	launcherSharedLibTag = installDependencyTag{name: "launcherSharedLib"}
-	pathComponentRegexp  = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_-]*$`)
-	pyExt                = ".py"
-	protoExt             = ".proto"
-	pyVersion2           = "PY2"
-	pyVersion3           = "PY3"
-	internalPath         = "internal"
+	// The python interpreter built for host so that we can precompile python sources.
+	// This only works because the precompiled sources don't vary by architecture.
+	// The soong module name is "py3-launcher".
+	hostLauncherTag          = dependencyTag{name: "hostLauncher"}
+	hostlauncherSharedLibTag = dependencyTag{name: "hostlauncherSharedLib"}
+	hostStdLibTag            = dependencyTag{name: "hostStdLib"}
+	pathComponentRegexp      = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_-]*$`)
+	pyExt                    = ".py"
+	protoExt                 = ".proto"
+	pyVersion2               = "PY2"
+	pyVersion3               = "PY3"
+	internalPath             = "internal"
 )
 
+type basePropertiesProvider interface {
+	getBaseProperties() *BaseProperties
+}
+
 // versionSplitMutator creates version variants for modules and appends the version-specific
 // properties for a given variant to the properties in the variant module
 func versionSplitMutator() func(android.BottomUpMutatorContext) {
 	return func(mctx android.BottomUpMutatorContext) {
-		if base, ok := mctx.Module().(*Module); ok {
-			versionNames := []string{}
+		if base, ok := mctx.Module().(basePropertiesProvider); ok {
+			props := base.getBaseProperties()
+			var versionNames []string
 			// collect version specific properties, so that we can merge version-specific properties
 			// into the module's overall properties
-			versionProps := []VersionProperties{}
+			var versionProps []VersionProperties
 			// PY3 is first so that we alias the PY3 variant rather than PY2 if both
 			// are available
-			if proptools.BoolDefault(base.properties.Version.Py3.Enabled, true) {
+			if proptools.BoolDefault(props.Version.Py3.Enabled, true) {
 				versionNames = append(versionNames, pyVersion3)
-				versionProps = append(versionProps, base.properties.Version.Py3)
+				versionProps = append(versionProps, props.Version.Py3)
 			}
-			if proptools.BoolDefault(base.properties.Version.Py2.Enabled, false) {
+			if proptools.BoolDefault(props.Version.Py2.Enabled, false) {
 				versionNames = append(versionNames, pyVersion2)
-				versionProps = append(versionProps, base.properties.Version.Py2)
+				versionProps = append(versionProps, props.Version.Py2)
 			}
 			modules := mctx.CreateLocalVariations(versionNames...)
 			// Alias module to the first variant
@@ -376,9 +273,10 @@
 			}
 			for i, v := range versionNames {
 				// set the actual version for Python module.
-				modules[i].(*Module).properties.Actual_version = v
+				newProps := modules[i].(basePropertiesProvider).getBaseProperties()
+				newProps.Actual_version = v
 				// append versioned properties for the Python module to the overall properties
-				err := proptools.AppendMatchingProperties([]interface{}{&modules[i].(*Module).properties}, &versionProps[i], nil)
+				err := proptools.AppendMatchingProperties([]interface{}{newProps}, &versionProps[i], nil)
 				if err != nil {
 					panic(err)
 				}
@@ -387,38 +285,6 @@
 	}
 }
 
-// HostToolPath returns a path if appropriate such that this module can be used as a host tool,
-// fulfilling HostToolProvider interface.
-func (p *Module) HostToolPath() android.OptionalPath {
-	if p.installer != nil {
-		if bin, ok := p.installer.(*binaryDecorator); ok {
-			// TODO: This should only be set when building host binaries -- tests built for device would be
-			// setting this incorrectly.
-			return android.OptionalPathForPath(bin.path)
-		}
-	}
-
-	return android.OptionalPath{}
-
-}
-
-// OutputFiles returns output files based on given tag, returns an error if tag is unsupported.
-func (p *Module) OutputFiles(tag string) (android.Paths, error) {
-	switch tag {
-	case "":
-		if outputFile := p.installSource; outputFile.Valid() {
-			return android.Paths{outputFile.Path()}, nil
-		}
-		return android.Paths{}, nil
-	default:
-		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
-	}
-}
-
-func (p *Module) isEmbeddedLauncherEnabled() bool {
-	return p.installer != nil && Bool(p.properties.Embedded_launcher)
-}
-
 func anyHasExt(paths []string, ext string) bool {
 	for _, p := range paths {
 		if filepath.Ext(p) == ext {
@@ -429,7 +295,7 @@
 	return false
 }
 
-func (p *Module) anySrcHasExt(ctx android.BottomUpMutatorContext, ext string) bool {
+func (p *PythonLibraryModule) anySrcHasExt(ctx android.BottomUpMutatorContext, ext string) bool {
 	return anyHasExt(p.properties.Srcs, ext)
 }
 
@@ -437,7 +303,7 @@
 //   - handles proto dependencies,
 //   - if required, specifies launcher and adds launcher dependencies,
 //   - applies python version mutations to Python dependencies
-func (p *Module) DepsMutator(ctx android.BottomUpMutatorContext) {
+func (p *PythonLibraryModule) DepsMutator(ctx android.BottomUpMutatorContext) {
 	android.ProtoDeps(ctx, &p.protoProperties)
 
 	versionVariation := []blueprint.Variation{
@@ -452,111 +318,85 @@
 	// Add python library dependencies for this python version variation
 	ctx.AddVariationDependencies(versionVariation, pythonLibTag, android.LastUniqueStrings(p.properties.Libs)...)
 
-	// If this module will be installed and has an embedded launcher, we need to add dependencies for:
-	//   * standard library
-	//   * launcher
-	//   * shared dependencies of the launcher
-	if p.installer != nil && p.isEmbeddedLauncherEnabled() {
-		var stdLib string
-		var launcherModule string
-		// Add launcher shared lib dependencies. Ideally, these should be
-		// derived from the `shared_libs` property of the launcher. However, we
-		// cannot read the property at this stage and it will be too late to add
-		// dependencies later.
-		launcherSharedLibDeps := []string{
-			"libsqlite",
-		}
-		// Add launcher-specific dependencies for bionic
-		if ctx.Target().Os.Bionic() {
-			launcherSharedLibDeps = append(launcherSharedLibDeps, "libc", "libdl", "libm")
-		}
-		if ctx.Target().Os == android.LinuxMusl && !ctx.Config().HostStaticBinaries() {
-			launcherSharedLibDeps = append(launcherSharedLibDeps, "libc_musl")
-		}
-
-		switch p.properties.Actual_version {
-		case pyVersion2:
-			stdLib = "py2-stdlib"
-
-			launcherModule = "py2-launcher"
-			if p.bootstrapper.autorun() {
-				launcherModule = "py2-launcher-autorun"
-			}
-
-			launcherSharedLibDeps = append(launcherSharedLibDeps, "libc++")
-
-		case pyVersion3:
-			stdLib = "py3-stdlib"
-
-			launcherModule = "py3-launcher"
-			if p.bootstrapper.autorun() {
-				launcherModule = "py3-launcher-autorun"
-			}
-			if ctx.Config().HostStaticBinaries() && ctx.Target().Os == android.LinuxMusl {
-				launcherModule += "-static"
-			}
-
-			if ctx.Device() {
-				launcherSharedLibDeps = append(launcherSharedLibDeps, "liblog")
-			}
-		default:
-			panic(fmt.Errorf("unknown Python Actual_version: %q for module: %q.",
-				p.properties.Actual_version, ctx.ModuleName()))
-		}
-		ctx.AddVariationDependencies(versionVariation, pythonLibTag, stdLib)
-		ctx.AddFarVariationDependencies(ctx.Target().Variations(), launcherTag, launcherModule)
-		ctx.AddFarVariationDependencies(ctx.Target().Variations(), launcherSharedLibTag, launcherSharedLibDeps...)
-	}
-
 	// Emulate the data property for java_data but with the arch variation overridden to "common"
 	// so that it can point to java modules.
 	javaDataVariation := []blueprint.Variation{{"arch", android.Common.String()}}
 	ctx.AddVariationDependencies(javaDataVariation, javaDataTag, p.properties.Java_data...)
+
+	p.AddDepsOnPythonLauncherAndStdlib(ctx, hostStdLibTag, hostLauncherTag, hostlauncherSharedLibTag, false, ctx.Config().BuildOSTarget)
 }
 
-func (p *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
-	p.generatePythonBuildActions(ctx)
-
-	// Only Python binary and test modules have non-empty bootstrapper.
-	if p.bootstrapper != nil {
-		// if the module is being installed, we need to collect all transitive dependencies to embed in
-		// the final par
-		p.collectPathsFromTransitiveDeps(ctx)
-		// bootstrap the module, including resolving main file, getting launcher path, and
-		// registering actions to build the par file
-		// bootstrap returns the binary output path
-		p.installSource = p.bootstrapper.bootstrap(ctx, p.properties.Actual_version,
-			p.isEmbeddedLauncherEnabled(), p.srcsPathMappings, p.srcsZip, p.depsSrcsZips)
+// AddDepsOnPythonLauncherAndStdlib will make the current module depend on the python stdlib,
+// launcher (interpreter), and the launcher's shared libraries. If autorun is true, it will use
+// the autorun launcher instead of the regular one. This function acceps a targetForDeps argument
+// as the target to use for these dependencies. For embedded launcher python binaries, the launcher
+// that will be embedded will be under the same target as the python module itself. But when
+// precompiling python code, we need to get the python launcher built for host, even if we're
+// compiling the python module for device, so we pass a different target to this function.
+func (p *PythonLibraryModule) AddDepsOnPythonLauncherAndStdlib(ctx android.BottomUpMutatorContext,
+	stdLibTag, launcherTag, launcherSharedLibTag blueprint.DependencyTag,
+	autorun bool, targetForDeps android.Target) {
+	var stdLib string
+	var launcherModule string
+	// Add launcher shared lib dependencies. Ideally, these should be
+	// derived from the `shared_libs` property of the launcher. TODO: read these from
+	// the python launcher itself using ctx.OtherModuleProvider() or similar on the result
+	// of ctx.AddFarVariationDependencies()
+	launcherSharedLibDeps := []string{
+		"libsqlite",
+	}
+	// Add launcher-specific dependencies for bionic
+	if targetForDeps.Os.Bionic() {
+		launcherSharedLibDeps = append(launcherSharedLibDeps, "libc", "libdl", "libm")
+	}
+	if targetForDeps.Os == android.LinuxMusl && !ctx.Config().HostStaticBinaries() {
+		launcherSharedLibDeps = append(launcherSharedLibDeps, "libc_musl")
 	}
 
-	// Only Python binary and test modules have non-empty installer.
-	if p.installer != nil {
-		var sharedLibs []string
-		// if embedded launcher is enabled, we need to collect the shared library depenendencies of the
-		// launcher
-		for _, dep := range ctx.GetDirectDepsWithTag(launcherSharedLibTag) {
-			sharedLibs = append(sharedLibs, ctx.OtherModuleName(dep))
+	switch p.properties.Actual_version {
+	case pyVersion2:
+		stdLib = "py2-stdlib"
+
+		launcherModule = "py2-launcher"
+		if autorun {
+			launcherModule = "py2-launcher-autorun"
 		}
 
-		p.installer.setAndroidMkSharedLibs(sharedLibs)
+		launcherSharedLibDeps = append(launcherSharedLibDeps, "libc++")
+	case pyVersion3:
+		stdLib = "py3-stdlib"
 
-		// Install the par file from installSource
-		if p.installSource.Valid() {
-			p.installer.install(ctx, p.installSource.Path())
+		launcherModule = "py3-launcher"
+		if autorun {
+			launcherModule = "py3-launcher-autorun"
 		}
+		if ctx.Config().HostStaticBinaries() && targetForDeps.Os == android.LinuxMusl {
+			launcherModule += "-static"
+		}
+		if ctx.Device() {
+			launcherSharedLibDeps = append(launcherSharedLibDeps, "liblog")
+		}
+	default:
+		panic(fmt.Errorf("unknown Python Actual_version: %q for module: %q.",
+			p.properties.Actual_version, ctx.ModuleName()))
 	}
+	targetVariations := targetForDeps.Variations()
+	if ctx.ModuleName() != stdLib {
+		stdLibVariations := make([]blueprint.Variation, 0, len(targetVariations)+1)
+		stdLibVariations = append(stdLibVariations, blueprint.Variation{Mutator: "python_version", Variation: p.properties.Actual_version})
+		stdLibVariations = append(stdLibVariations, targetVariations...)
+		// Using AddFarVariationDependencies for all of these because they can be for a different
+		// platform, like if the python module itself was being compiled for device, we may want
+		// the python interpreter built for host so that we can precompile python sources.
+		ctx.AddFarVariationDependencies(stdLibVariations, stdLibTag, stdLib)
+	}
+	ctx.AddFarVariationDependencies(targetVariations, launcherTag, launcherModule)
+	ctx.AddFarVariationDependencies(targetVariations, launcherSharedLibTag, launcherSharedLibDeps...)
 }
 
-// generatePythonBuildActions performs build actions common to all Python modules
-func (p *Module) generatePythonBuildActions(ctx android.ModuleContext) {
+// GenerateAndroidBuildActions performs build actions common to all Python modules
+func (p *PythonLibraryModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
 	expandedSrcs := android.PathsForModuleSrcExcludes(ctx, p.properties.Srcs, p.properties.Exclude_srcs)
-	requiresSrcs := true
-	if p.bootstrapper != nil && !p.bootstrapper.autorun() {
-		requiresSrcs = false
-	}
-	if len(expandedSrcs) == 0 && requiresSrcs {
-		ctx.ModuleErrorf("doesn't have any source files!")
-	}
 
 	// expand data files from "data" property.
 	expandedData := android.PathsForModuleSrc(ctx, p.properties.Data)
@@ -589,6 +429,7 @@
 
 	// generate the zipfile of all source and data files
 	p.srcsZip = p.createSrcsZip(ctx, pkgPath)
+	p.precompiledSrcsZip = p.precompileSrcs(ctx)
 }
 
 func isValidPythonPath(path string) error {
@@ -607,7 +448,7 @@
 
 // For this module, generate unique pathMappings: <dest: runfiles_path, src: source_path>
 // for python/data files expanded from properties.
-func (p *Module) genModulePathMappings(ctx android.ModuleContext, pkgPath string,
+func (p *PythonLibraryModule) genModulePathMappings(ctx android.ModuleContext, pkgPath string,
 	expandedSrcs, expandedData android.Paths) {
 	// fetch <runfiles_path, source_path> pairs from "src" and "data" properties to
 	// check current module duplicates.
@@ -642,27 +483,28 @@
 }
 
 // createSrcsZip registers build actions to zip current module's sources and data.
-func (p *Module) createSrcsZip(ctx android.ModuleContext, pkgPath string) android.Path {
+func (p *PythonLibraryModule) createSrcsZip(ctx android.ModuleContext, pkgPath string) android.Path {
 	relativeRootMap := make(map[string]android.Paths)
-	pathMappings := append(p.srcsPathMappings, p.dataPathMappings...)
-
 	var protoSrcs android.Paths
-	// "srcs" or "data" properties may contain filegroup so it might happen that
-	// the root directory for each source path is different.
-	for _, path := range pathMappings {
+	addPathMapping := func(path pathMapping) {
 		// handle proto sources separately
 		if path.src.Ext() == protoExt {
 			protoSrcs = append(protoSrcs, path.src)
 		} else {
-			var relativeRoot string
-			relativeRoot = strings.TrimSuffix(path.src.String(), path.src.Rel())
-			if v, found := relativeRootMap[relativeRoot]; found {
-				relativeRootMap[relativeRoot] = append(v, path.src)
-			} else {
-				relativeRootMap[relativeRoot] = android.Paths{path.src}
-			}
+			relativeRoot := strings.TrimSuffix(path.src.String(), path.src.Rel())
+			relativeRootMap[relativeRoot] = append(relativeRootMap[relativeRoot], path.src)
 		}
 	}
+
+	// "srcs" or "data" properties may contain filegroups so it might happen that
+	// the root directory for each source path is different.
+	for _, path := range p.srcsPathMappings {
+		addPathMapping(path)
+	}
+	for _, path := range p.dataPathMappings {
+		addPathMapping(path)
+	}
+
 	var zips android.Paths
 	if len(protoSrcs) > 0 {
 		protoFlags := android.GetProtoFlags(ctx, &p.protoProperties)
@@ -736,30 +578,79 @@
 	}
 }
 
-// isPythonLibModule returns whether the given module is a Python library Module or not
+func (p *PythonLibraryModule) precompileSrcs(ctx android.ModuleContext) android.Path {
+	// To precompile the python sources, we need a python interpreter and stdlib built
+	// for host. We then use those to compile the python sources, which may be used on either
+	// host of device. Python bytecode is architecture agnostic, so we're essentially
+	// "cross compiling" for device here purely by virtue of host and device python bytecode
+	// being the same.
+	var stdLib android.Path
+	var launcher android.Path
+	if ctx.ModuleName() == "py3-stdlib" || ctx.ModuleName() == "py2-stdlib" {
+		stdLib = p.srcsZip
+	} else {
+		ctx.VisitDirectDepsWithTag(hostStdLibTag, func(module android.Module) {
+			if dep, ok := module.(pythonDependency); ok {
+				stdLib = dep.getPrecompiledSrcsZip()
+			}
+		})
+	}
+	ctx.VisitDirectDepsWithTag(hostLauncherTag, func(module android.Module) {
+		if dep, ok := module.(IntermPathProvider); ok {
+			optionalLauncher := dep.IntermPathForModuleOut()
+			if optionalLauncher.Valid() {
+				launcher = optionalLauncher.Path()
+			}
+		}
+	})
+	var launcherSharedLibs android.Paths
+	var ldLibraryPath []string
+	ctx.VisitDirectDepsWithTag(hostlauncherSharedLibTag, func(module android.Module) {
+		if dep, ok := module.(IntermPathProvider); ok {
+			optionalPath := dep.IntermPathForModuleOut()
+			if optionalPath.Valid() {
+				launcherSharedLibs = append(launcherSharedLibs, optionalPath.Path())
+				ldLibraryPath = append(ldLibraryPath, filepath.Dir(optionalPath.Path().String()))
+			}
+		}
+	})
+
+	out := android.PathForModuleOut(ctx, ctx.ModuleName()+".srcszipprecompiled")
+	if stdLib == nil || launcher == nil {
+		// This shouldn't happen in a real build because we'll error out when adding dependencies
+		// on the stdlib and launcher if they don't exist. But some tests set
+		// AllowMissingDependencies.
+		return out
+	}
+	ctx.Build(pctx, android.BuildParams{
+		Rule:        precompile,
+		Input:       p.srcsZip,
+		Output:      out,
+		Implicits:   launcherSharedLibs,
+		Description: "Precompile the python sources of " + ctx.ModuleName(),
+		Args: map[string]string{
+			"stdlibZip":     stdLib.String(),
+			"launcher":      launcher.String(),
+			"ldLibraryPath": strings.Join(ldLibraryPath, ":"),
+		},
+	})
+	return out
+}
+
+// isPythonLibModule returns whether the given module is a Python library PythonLibraryModule or not
 func isPythonLibModule(module blueprint.Module) bool {
-	if m, ok := module.(*Module); ok {
-		return m.isLibrary()
+	if _, ok := module.(*PythonLibraryModule); ok {
+		if _, ok := module.(*PythonBinaryModule); !ok {
+			return true
+		}
 	}
 	return false
 }
 
-// This is distinguished by the fact that Python libraries are not installable, while other Python
-// modules are.
-func (p *Module) isLibrary() bool {
-	// Python library has no bootstrapper or installer
-	return p.bootstrapper == nil && p.installer == nil
-}
-
-func (p *Module) isBinary() bool {
-	_, ok := p.bootstrapper.(*binaryDecorator)
-	return ok
-}
-
 // collectPathsFromTransitiveDeps checks for source/data files for duplicate paths
 // for module and its transitive dependencies and collects list of data/source file
 // zips for transitive dependencies.
-func (p *Module) collectPathsFromTransitiveDeps(ctx android.ModuleContext) {
+func (p *PythonLibraryModule) collectPathsFromTransitiveDeps(ctx android.ModuleContext, precompiled bool) android.Paths {
 	// fetch <runfiles_path, source_path> pairs from "src" and "data" properties to
 	// check duplicates.
 	destToPySrcs := make(map[string]string)
@@ -773,6 +664,8 @@
 
 	seen := make(map[android.Module]bool)
 
+	var result android.Paths
+
 	// visit all its dependencies in depth first.
 	ctx.WalkDeps(func(child, parent android.Module) bool {
 		// we only collect dependencies tagged as python library deps
@@ -801,10 +694,15 @@
 				checkForDuplicateOutputPath(ctx, destToPyData,
 					path.dest, path.src.String(), ctx.ModuleName(), ctx.OtherModuleName(child))
 			}
-			p.depsSrcsZips = append(p.depsSrcsZips, dep.getSrcsZip())
+			if precompiled {
+				result = append(result, dep.getPrecompiledSrcsZip())
+			} else {
+				result = append(result, dep.getSrcsZip())
+			}
 		}
 		return true
 	})
+	return result
 }
 
 // chckForDuplicateOutputPath checks whether outputPath has already been included in map m, which
@@ -825,18 +723,10 @@
 }
 
 // InstallInData returns true as Python is not supported in the system partition
-func (p *Module) InstallInData() bool {
+func (p *PythonLibraryModule) InstallInData() bool {
 	return true
 }
 
-func (p *Module) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
-	if p.isLibrary() {
-		pythonLibBp2Build(ctx, p)
-	} else if p.isBinary() {
-		pythonBinaryBp2Build(ctx, p)
-	}
-}
-
 var Bool = proptools.Bool
 var BoolDefault = proptools.BoolDefault
 var String = proptools.String
diff --git a/python/python_test.go b/python/python_test.go
index 42a1ffb..75a6a89 100644
--- a/python/python_test.go
+++ b/python/python_test.go
@@ -18,10 +18,10 @@
 	"fmt"
 	"os"
 	"path/filepath"
-	"regexp"
 	"testing"
 
 	"android/soong/android"
+	"android/soong/cc"
 )
 
 type pyModule struct {
@@ -33,8 +33,10 @@
 }
 
 var (
-	buildNamePrefix          = "soong_python_test"
-	moduleVariantErrTemplate = "%s: module %q variant %q: "
+	buildNamePrefix = "soong_python_test"
+	// We allow maching almost anything before the actual variant so that the os/arch variant
+	// is matched.
+	moduleVariantErrTemplate = `%s: module %q variant "[a-zA-Z0-9_]*%s": `
 	pkgPathErrTemplate       = moduleVariantErrTemplate +
 		"pkg_path: %q must be a relative path contained in par file."
 	badIdentifierErrTemplate = moduleVariantErrTemplate +
@@ -312,10 +314,6 @@
 						"e/file4.py",
 					},
 					srcsZip: "out/soong/.intermediates/dir/bin/PY3/bin.py.srcszip",
-					depsSrcsZips: []string{
-						"out/soong/.intermediates/dir/lib5/PY3/lib5.py.srcszip",
-						"out/soong/.intermediates/dir/lib6/PY3/lib6.py.srcszip",
-					},
 				},
 			},
 		},
@@ -327,17 +325,26 @@
 		if d.desc != "module with duplicate runfile path" {
 			continue
 		}
-		errorPatterns := make([]string, len(d.errors))
-		for i, s := range d.errors {
-			errorPatterns[i] = regexp.QuoteMeta(s)
-		}
+		d.mockFiles[filepath.Join("common", bpFile)] = []byte(`
+python_library {
+  name: "py3-stdlib",
+  host_supported: true,
+}
+cc_binary {
+  name: "py3-launcher",
+  host_supported: true,
+}
+`)
 
 		t.Run(d.desc, func(t *testing.T) {
 			result := android.GroupFixturePreparers(
 				android.PrepareForTestWithDefaults,
+				android.PrepareForTestWithArchMutator,
+				android.PrepareForTestWithAllowMissingDependencies,
+				cc.PrepareForTestWithCcDefaultModules,
 				PrepareForTestWithPythonBuildComponents,
 				d.mockFiles.AddToFixture(),
-			).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(errorPatterns)).
+			).ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(d.errors)).
 				RunTest(t)
 
 			if len(result.Errs) > 0 {
@@ -346,17 +353,17 @@
 
 			for _, e := range d.expectedBinaries {
 				t.Run(e.name, func(t *testing.T) {
-					expectModule(t, result.TestContext, e.name, e.actualVersion, e.srcsZip, e.pyRunfiles, e.depsSrcsZips)
+					expectModule(t, result.TestContext, e.name, e.actualVersion, e.srcsZip, e.pyRunfiles)
 				})
 			}
 		})
 	}
 }
 
-func expectModule(t *testing.T, ctx *android.TestContext, name, variant, expectedSrcsZip string, expectedPyRunfiles, expectedDepsSrcsZips []string) {
+func expectModule(t *testing.T, ctx *android.TestContext, name, variant, expectedSrcsZip string, expectedPyRunfiles []string) {
 	module := ctx.ModuleForTests(name, variant)
 
-	base, baseOk := module.Module().(*Module)
+	base, baseOk := module.Module().(*PythonLibraryModule)
 	if !baseOk {
 		t.Fatalf("%s is not Python module!", name)
 	}
@@ -369,8 +376,6 @@
 	android.AssertDeepEquals(t, "pyRunfiles", expectedPyRunfiles, actualPyRunfiles)
 
 	android.AssertPathRelativeToTopEquals(t, "srcsZip", expectedSrcsZip, base.srcsZip)
-
-	android.AssertPathsRelativeToTopEquals(t, "depsSrcsZips", expectedDepsSrcsZips, base.depsSrcsZips)
 }
 
 func TestMain(m *testing.M) {
diff --git a/python/scripts/precompile_python.py b/python/scripts/precompile_python.py
new file mode 100644
index 0000000..e12e7d2
--- /dev/null
+++ b/python/scripts/precompile_python.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python3
+# Copyright 2023 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.
+
+import argparse
+import py_compile
+import os
+import shutil
+import tempfile
+import zipfile
+
+# This file needs to support both python 2 and 3.
+
+
+def process_one_file(name, inf, outzip):
+    if not name.endswith('.py'):
+        outzip.writestr(name, inf.read())
+        return
+
+    # Unfortunately py_compile requires the input/output files to be written
+    # out to disk.
+    with tempfile.NamedTemporaryFile(prefix="Soong_precompile_", delete=False) as tmp:
+        shutil.copyfileobj(inf, tmp)
+        in_name = tmp.name
+    with tempfile.NamedTemporaryFile(prefix="Soong_precompile_", delete=False) as tmp:
+        out_name = tmp.name
+    try:
+        py_compile.compile(in_name, out_name, name, doraise=True)
+        with open(out_name, 'rb') as f:
+            outzip.writestr(name + 'c', f.read())
+    finally:
+        os.remove(in_name)
+        os.remove(out_name)
+
+
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('src_zip')
+    parser.add_argument('dst_zip')
+    args = parser.parse_args()
+
+    with open(args.dst_zip, 'wb') as outf, open(args.src_zip, 'rb') as inf:
+        with zipfile.ZipFile(outf, mode='w') as outzip, zipfile.ZipFile(inf, mode='r') as inzip:
+            for name in inzip.namelist():
+                with inzip.open(name, mode='r') as inzipf:
+                    process_one_file(name, inzipf, outzip)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/python/test.go b/python/test.go
index fc5c211..fb8e918 100644
--- a/python/test.go
+++ b/python/test.go
@@ -32,6 +32,20 @@
 	ctx.RegisterModuleType("python_test", PythonTestFactory)
 }
 
+func NewTest(hod android.HostOrDeviceSupported) *PythonTestModule {
+	return &PythonTestModule{PythonBinaryModule: *NewBinary(hod)}
+}
+
+func PythonTestHostFactory() android.Module {
+	return NewTest(android.HostSupportedNoCross).init()
+}
+
+func PythonTestFactory() android.Module {
+	module := NewTest(android.HostAndDeviceSupported)
+	module.multilib = android.MultilibBoth
+	return module.init()
+}
+
 type TestProperties struct {
 	// the name of the test configuration (for example "AndroidTest.xml") that should be
 	// installed with the module.
@@ -52,76 +66,79 @@
 	Test_options android.CommonTestOptions
 }
 
-type testDecorator struct {
-	*binaryDecorator
+type PythonTestModule struct {
+	PythonBinaryModule
 
 	testProperties TestProperties
-
-	testConfig android.Path
-
-	data []android.DataPath
+	testConfig     android.Path
+	data           []android.DataPath
 }
 
-func (test *testDecorator) bootstrapperProps() []interface{} {
-	return append(test.binaryDecorator.bootstrapperProps(), &test.testProperties)
+func (p *PythonTestModule) init() android.Module {
+	p.AddProperties(&p.properties, &p.protoProperties)
+	p.AddProperties(&p.binaryProperties)
+	p.AddProperties(&p.testProperties)
+	android.InitAndroidArchModule(p, p.hod, p.multilib)
+	android.InitDefaultableModule(p)
+	android.InitBazelModule(p)
+	if p.hod == android.HostSupportedNoCross && p.testProperties.Test_options.Unit_test == nil {
+		p.testProperties.Test_options.Unit_test = proptools.BoolPtr(true)
+	}
+	return p
 }
 
-func (test *testDecorator) install(ctx android.ModuleContext, file android.Path) {
-	test.testConfig = tradefed.AutoGenTestConfig(ctx, tradefed.AutoGenTestConfigOptions{
-		TestConfigProp:         test.testProperties.Test_config,
-		TestConfigTemplateProp: test.testProperties.Test_config_template,
-		TestSuites:             test.binaryDecorator.binaryProperties.Test_suites,
-		AutoGenConfig:          test.binaryDecorator.binaryProperties.Auto_gen_config,
+func (p *PythonTestModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
+	// We inherit from only the library's GenerateAndroidBuildActions, and then
+	// just use buildBinary() so that the binary is not installed into the location
+	// it would be for regular binaries.
+	p.PythonLibraryModule.GenerateAndroidBuildActions(ctx)
+	p.buildBinary(ctx)
+
+	p.testConfig = tradefed.AutoGenTestConfig(ctx, tradefed.AutoGenTestConfigOptions{
+		TestConfigProp:         p.testProperties.Test_config,
+		TestConfigTemplateProp: p.testProperties.Test_config_template,
+		TestSuites:             p.binaryProperties.Test_suites,
+		AutoGenConfig:          p.binaryProperties.Auto_gen_config,
 		DeviceTemplate:         "${PythonBinaryHostTestConfigTemplate}",
 		HostTemplate:           "${PythonBinaryHostTestConfigTemplate}",
 	})
 
-	test.binaryDecorator.pythonInstaller.dir = "nativetest"
-	test.binaryDecorator.pythonInstaller.dir64 = "nativetest64"
+	p.installedDest = ctx.InstallFile(installDir(ctx, "nativetest", "nativetest64", ctx.ModuleName()), p.installSource.Base(), p.installSource)
 
-	test.binaryDecorator.pythonInstaller.relative = ctx.ModuleName()
-
-	test.binaryDecorator.pythonInstaller.install(ctx, file)
-
-	dataSrcPaths := android.PathsForModuleSrc(ctx, test.testProperties.Data)
-
-	for _, dataSrcPath := range dataSrcPaths {
-		test.data = append(test.data, android.DataPath{SrcPath: dataSrcPath})
+	for _, dataSrcPath := range android.PathsForModuleSrc(ctx, p.testProperties.Data) {
+		p.data = append(p.data, android.DataPath{SrcPath: dataSrcPath})
 	}
 
 	// Emulate the data property for java_data dependencies.
 	for _, javaData := range ctx.GetDirectDepsWithTag(javaDataTag) {
 		for _, javaDataSrcPath := range android.OutputFilesForModule(ctx, javaData, "") {
-			test.data = append(test.data, android.DataPath{SrcPath: javaDataSrcPath})
+			p.data = append(p.data, android.DataPath{SrcPath: javaDataSrcPath})
 		}
 	}
 }
 
-func NewTest(hod android.HostOrDeviceSupported) *Module {
-	module, binary := NewBinary(hod)
-
-	binary.pythonInstaller = NewPythonInstaller("nativetest", "nativetest64")
-
-	test := &testDecorator{binaryDecorator: binary}
-	if hod == android.HostSupportedNoCross && test.testProperties.Test_options.Unit_test == nil {
-		test.testProperties.Test_options.Unit_test = proptools.BoolPtr(true)
+func (p *PythonTestModule) AndroidMkEntries() []android.AndroidMkEntries {
+	entriesList := p.PythonBinaryModule.AndroidMkEntries()
+	if len(entriesList) != 1 {
+		panic("Expected 1 entry")
 	}
+	entries := &entriesList[0]
 
-	module.bootstrapper = test
-	module.installer = test
+	entries.Class = "NATIVE_TESTS"
 
-	return module
-}
+	entries.ExtraEntries = append(entries.ExtraEntries,
+		func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
+			//entries.AddCompatibilityTestSuites(p.binaryProperties.Test_suites...)
+			if p.testConfig != nil {
+				entries.SetString("LOCAL_FULL_TEST_CONFIG", p.testConfig.String())
+			}
 
-func PythonTestHostFactory() android.Module {
-	module := NewTest(android.HostSupportedNoCross)
+			entries.SetBoolIfTrue("LOCAL_DISABLE_AUTO_GENERATE_TEST_CONFIG", !BoolDefault(p.binaryProperties.Auto_gen_config, true))
 
-	return module.init()
-}
+			entries.AddStrings("LOCAL_TEST_DATA", android.AndroidMkDataPaths(p.data)...)
 
-func PythonTestFactory() android.Module {
-	module := NewTest(android.HostAndDeviceSupported)
-	module.multilib = android.MultilibBoth
+			p.testProperties.Test_options.SetAndroidMkEntries(entries)
+		})
 
-	return module.init()
+	return entriesList
 }
diff --git a/python/tests/par_test.py b/python/tests/par_test.py
index 56a5063..1e03f16 100644
--- a/python/tests/par_test.py
+++ b/python/tests/par_test.py
@@ -27,7 +27,10 @@
         failed = True
 
 assert_equal("__name__", __name__, "__main__")
-assert_equal("os.path.basename(__file__)", os.path.basename(__file__), "par_test.py")
+fileName = os.path.basename(__file__)
+if fileName.endswith('.pyc'):
+    fileName = fileName[:-1]
+assert_equal("os.path.basename(__file__)", fileName, "par_test.py")
 
 archive = os.path.dirname(__file__)
 
diff --git a/python/tests/testpkg/par_test.py b/python/tests/testpkg/par_test.py
index ffad430..b513409 100644
--- a/python/tests/testpkg/par_test.py
+++ b/python/tests/testpkg/par_test.py
@@ -28,7 +28,10 @@
 archive = sys.modules["__main__"].__loader__.archive
 
 assert_equal("__name__", __name__, "testpkg.par_test")
-assert_equal("__file__", __file__, os.path.join(archive, "testpkg/par_test.py"))
+fileName = __file__
+if fileName.endswith('.pyc'):
+    fileName = fileName[:-1]
+assert_equal("__file__", fileName, os.path.join(archive, "testpkg/par_test.py"))
 
 # Python3 is returning None here for me, and I haven't found any problems caused by this.
 if sys.version_info[0] == 2:
diff --git a/rust/androidmk.go b/rust/androidmk.go
index 32c746e..20e9919 100644
--- a/rust/androidmk.go
+++ b/rust/androidmk.go
@@ -205,8 +205,8 @@
 		})
 }
 
-func (fuzz *fuzzDecorator) AndroidMkEntries(ctx AndroidMkContext, entries *android.AndroidMkEntries) {
-	ctx.SubAndroidMk(entries, fuzz.binaryDecorator)
+func (fuzz *fuzzDecorator) AndroidMk(ctx AndroidMkContext, ret *android.AndroidMkEntries) {
+	ctx.SubAndroidMk(ret, fuzz.binaryDecorator)
 
 	var fuzzFiles []string
 	for _, d := range fuzz.fuzzPackagedModule.Corpus {
@@ -229,11 +229,14 @@
 			filepath.Dir(fuzz.fuzzPackagedModule.Config.String())+":config.json")
 	}
 
-	entries.ExtraEntries = append(entries.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext,
+	ret.ExtraEntries = append(ret.ExtraEntries, func(ctx android.AndroidMkExtraEntriesContext,
 		entries *android.AndroidMkEntries) {
 		entries.SetBool("LOCAL_IS_FUZZ_TARGET", true)
 		if len(fuzzFiles) > 0 {
 			entries.AddStrings("LOCAL_TEST_DATA", fuzzFiles...)
 		}
+		if fuzz.installedSharedDeps != nil {
+			entries.AddStrings("LOCAL_FUZZ_INSTALLED_SHARED_DEPS", fuzz.installedSharedDeps...)
+		}
 	})
 }
diff --git a/rust/bindgen.go b/rust/bindgen.go
index 17d80dd..878f896 100644
--- a/rust/bindgen.go
+++ b/rust/bindgen.go
@@ -15,7 +15,6 @@
 package rust
 
 import (
-	"fmt"
 	"strings"
 
 	"github.com/google/blueprint"
@@ -30,7 +29,7 @@
 	defaultBindgenFlags = []string{""}
 
 	// bindgen should specify its own Clang revision so updating Clang isn't potentially blocked on bindgen failures.
-	bindgenClangVersion = "clang-r468909b"
+	bindgenClangVersion = "clang-r475365b"
 
 	_ = pctx.VariableFunc("bindgenClangVersion", func(ctx android.PackageVarContext) string {
 		if override := ctx.Config().Getenv("LLVM_BINDGEN_PREBUILTS_VERSION"); override != "" {
@@ -53,7 +52,7 @@
 		if ctx.Config().UseHostMusl() {
 			return "musl/lib/"
 		} else {
-			return "lib64/"
+			return "lib/"
 		}
 	})
 	_ = pctx.SourcePathVariable("bindgenClang",
@@ -178,10 +177,6 @@
 
 	if mctx, ok := ctx.(*moduleContext); ok && mctx.apexVariationName() != "" {
 		cflags = append(cflags, "-D__ANDROID_APEX__")
-		if ctx.Device() {
-			cflags = append(cflags, fmt.Sprintf("-D__ANDROID_APEX_MIN_SDK_VERSION__=%d",
-				ctx.RustModule().apexSdkVersion.FinalOrFutureInt()))
-		}
 	}
 
 	if ctx.Target().NativeBridge == android.NativeBridgeEnabled {
diff --git a/rust/config/global.go b/rust/config/global.go
index 26e2d06..50ac1f7 100644
--- a/rust/config/global.go
+++ b/rust/config/global.go
@@ -24,7 +24,7 @@
 var pctx = android.NewPackageContext("android/soong/rust/config")
 
 var (
-	RustDefaultVersion = "1.65.0"
+	RustDefaultVersion = "1.65.0.p1"
 	RustDefaultBase    = "prebuilts/rust/"
 	DefaultEdition     = "2021"
 	Stdlibs            = []string{
@@ -51,6 +51,9 @@
 		// Use v0 mangling to distinguish from C++ symbols
 		"-C symbol-mangling-version=v0",
 		"--color always",
+		// TODO (b/267698452): Temporary workaround until the "no unstable
+		// features" policy is enforced.
+		"-A stable-features",
 	}
 
 	deviceGlobalRustFlags = []string{
diff --git a/rust/config/x86_linux_bionic_host.go b/rust/config/x86_linux_bionic_host.go
index b1a2c17..79c40ce 100644
--- a/rust/config/x86_linux_bionic_host.go
+++ b/rust/config/x86_linux_bionic_host.go
@@ -21,7 +21,9 @@
 )
 
 var (
-	LinuxBionicRustFlags     = []string{}
+	LinuxBionicRustFlags = []string{
+		"-C panic=abort",
+	}
 	LinuxBionicRustLinkFlags = []string{
 		"-B${cc_config.ClangBin}",
 		"-fuse-ld=lld",
diff --git a/rust/fuzz.go b/rust/fuzz.go
index 6faf55c..d7e7ddf 100644
--- a/rust/fuzz.go
+++ b/rust/fuzz.go
@@ -16,8 +16,6 @@
 
 import (
 	"path/filepath"
-	"sort"
-	"strings"
 
 	"android/soong/android"
 	"android/soong/cc"
@@ -27,14 +25,14 @@
 
 func init() {
 	android.RegisterModuleType("rust_fuzz", RustFuzzFactory)
-	android.RegisterSingletonType("rust_fuzz_packaging", rustFuzzPackagingFactory)
 }
 
 type fuzzDecorator struct {
 	*binaryDecorator
 
-	fuzzPackagedModule fuzz.FuzzPackagedModule
-	sharedLibraries    android.Paths
+	fuzzPackagedModule  fuzz.FuzzPackagedModule
+	sharedLibraries     android.Paths
+	installedSharedDeps []string
 }
 
 var _ compiler = (*fuzzDecorator)(nil)
@@ -64,9 +62,14 @@
 	flags = fuzzer.binaryDecorator.compilerFlags(ctx, flags)
 
 	// `../lib` for installed fuzz targets (both host and device), and `./lib` for fuzz target packages.
-	flags.LinkFlags = append(flags.LinkFlags, `-Wl,-rpath,\$$ORIGIN/../lib`)
 	flags.LinkFlags = append(flags.LinkFlags, `-Wl,-rpath,\$$ORIGIN/lib`)
 
+	if ctx.InstallInVendor() {
+		flags.LinkFlags = append(flags.LinkFlags, `-Wl,-rpath,\$$ORIGIN/../../lib`)
+	} else {
+		flags.LinkFlags = append(flags.LinkFlags, `-Wl,-rpath,\$$ORIGIN/../lib`)
+
+	}
 	return flags
 }
 
@@ -88,10 +91,8 @@
 }
 
 func (fuzzer *fuzzDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) buildOutput {
-	out := fuzzer.binaryDecorator.compile(ctx, flags, deps)
 
-	// Grab the list of required shared libraries.
-	fuzzer.sharedLibraries, _ = cc.CollectAllSharedDependencies(ctx)
+	out := fuzzer.binaryDecorator.compile(ctx, flags, deps)
 
 	return out
 }
@@ -104,83 +105,6 @@
 	return rlibAutoDep
 }
 
-// Responsible for generating GNU Make rules that package fuzz targets into
-// their architecture & target/host specific zip file.
-type rustFuzzPackager struct {
-	fuzz.FuzzPackager
-}
-
-func rustFuzzPackagingFactory() android.Singleton {
-	return &rustFuzzPackager{}
-}
-
-func (s *rustFuzzPackager) GenerateBuildActions(ctx android.SingletonContext) {
-	// Map between each architecture + host/device combination.
-	archDirs := make(map[fuzz.ArchOs][]fuzz.FileToZip)
-
-	// List of individual fuzz targets.
-	s.FuzzTargets = make(map[string]bool)
-
-	// Map tracking whether each shared library has an install rule to avoid duplicate install rules from
-	// multiple fuzzers that depend on the same shared library.
-	sharedLibraryInstalled := make(map[string]bool)
-
-	ctx.VisitAllModules(func(module android.Module) {
-		// Discard non-fuzz targets.
-		rustModule, ok := module.(*Module)
-		if !ok {
-			return
-		}
-
-		if ok := fuzz.IsValid(rustModule.FuzzModule); !ok || rustModule.Properties.PreventInstall {
-			return
-		}
-
-		fuzzModule, ok := rustModule.compiler.(*fuzzDecorator)
-		if !ok {
-			return
-		}
-
-		hostOrTargetString := "target"
-		if rustModule.Host() {
-			hostOrTargetString = "host"
-		}
-
-		archString := rustModule.Arch().ArchType.String()
-		archDir := android.PathForIntermediates(ctx, "fuzz", hostOrTargetString, archString)
-		archOs := fuzz.ArchOs{HostOrTarget: hostOrTargetString, Arch: archString, Dir: archDir.String()}
-
-		var files []fuzz.FileToZip
-		builder := android.NewRuleBuilder(pctx, ctx)
-
-		// Package the artifacts (data, corpus, config and dictionary into a zipfile.
-		files = s.PackageArtifacts(ctx, module, fuzzModule.fuzzPackagedModule, archDir, builder)
-
-		// The executable.
-		files = append(files, fuzz.FileToZip{rustModule.UnstrippedOutputFile(), ""})
-
-		// Package shared libraries
-		files = append(files, cc.GetSharedLibsToZip(fuzzModule.sharedLibraries, rustModule, &s.FuzzPackager, archString, "lib", &sharedLibraryInstalled)...)
-
-		archDirs[archOs], ok = s.BuildZipFile(ctx, module, fuzzModule.fuzzPackagedModule, files, builder, archDir, archString, hostOrTargetString, archOs, archDirs)
-		if !ok {
-			return
-		}
-
-	})
-	s.CreateFuzzPackage(ctx, archDirs, fuzz.Rust, pctx)
-}
-
-func (s *rustFuzzPackager) MakeVars(ctx android.MakeVarsContext) {
-	packages := s.Packages.Strings()
-	sort.Strings(packages)
-
-	ctx.Strict("SOONG_RUST_FUZZ_PACKAGING_ARCH_MODULES", strings.Join(packages, " "))
-
-	// Preallocate the slice of fuzz targets to minimize memory allocations.
-	s.PreallocateSlice(ctx, "ALL_RUST_FUZZ_TARGETS")
-}
-
 func (fuzz *fuzzDecorator) install(ctx ModuleContext) {
 	fuzz.binaryDecorator.baseCompiler.dir = filepath.Join(
 		"fuzz", ctx.Target().Arch.ArchType.String(), ctx.ModuleName())
@@ -188,13 +112,22 @@
 		"fuzz", ctx.Target().Arch.ArchType.String(), ctx.ModuleName())
 	fuzz.binaryDecorator.baseCompiler.install(ctx)
 
-	if fuzz.fuzzPackagedModule.FuzzProperties.Corpus != nil {
-		fuzz.fuzzPackagedModule.Corpus = android.PathsForModuleSrc(ctx, fuzz.fuzzPackagedModule.FuzzProperties.Corpus)
-	}
-	if fuzz.fuzzPackagedModule.FuzzProperties.Data != nil {
-		fuzz.fuzzPackagedModule.Data = android.PathsForModuleSrc(ctx, fuzz.fuzzPackagedModule.FuzzProperties.Data)
-	}
-	if fuzz.fuzzPackagedModule.FuzzProperties.Dictionary != nil {
-		fuzz.fuzzPackagedModule.Dictionary = android.PathForModuleSrc(ctx, *fuzz.fuzzPackagedModule.FuzzProperties.Dictionary)
+	fuzz.fuzzPackagedModule = cc.PackageFuzzModule(ctx, fuzz.fuzzPackagedModule, pctx)
+
+	installBase := "fuzz"
+
+	// Grab the list of required shared libraries.
+	fuzz.sharedLibraries, _ = cc.CollectAllSharedDependencies(ctx)
+
+	for _, lib := range fuzz.sharedLibraries {
+		fuzz.installedSharedDeps = append(fuzz.installedSharedDeps,
+			cc.SharedLibraryInstallLocation(
+				lib, ctx.Host(), installBase, ctx.Arch().ArchType.String()))
+
+		// Also add the dependency on the shared library symbols dir.
+		if !ctx.Host() {
+			fuzz.installedSharedDeps = append(fuzz.installedSharedDeps,
+				cc.SharedLibrarySymbolsInstallLocation(lib, installBase, ctx.Arch().ArchType.String()))
+		}
 	}
 }
diff --git a/rust/image.go b/rust/image.go
index dfc7f74..50bf02a 100644
--- a/rust/image.go
+++ b/rust/image.go
@@ -129,6 +129,10 @@
 	return ctx.ModuleContext.DeviceSpecific() || (ctx.RustModule().InVendor() && ctx.RustModule().VendorVariantToOdm())
 }
 
+func (ctx *moduleContext) SystemExtSpecific() bool {
+	return ctx.ModuleContext.SystemExtSpecific()
+}
+
 // Returns true when this module creates a vendor variant and wants to install the vendor variant
 // to the odm partition.
 func (c *Module) VendorVariantToOdm() bool {
@@ -158,22 +162,15 @@
 }
 
 func (mod *Module) OnlyInRamdisk() bool {
-	// TODO(b/165791368)
-	return false
+	return mod.ModuleBase.InstallInRamdisk()
 }
 
 func (mod *Module) OnlyInRecovery() bool {
-	// TODO(b/165791368)
-	return false
+	return mod.ModuleBase.InstallInRecovery()
 }
 
 func (mod *Module) OnlyInVendorRamdisk() bool {
-	return false
-}
-
-func (mod *Module) OnlyInProduct() bool {
-	//TODO(b/165791368)
-	return false
+	return mod.ModuleBase.InstallInVendorRamdisk()
 }
 
 // Returns true when this module is configured to have core and vendor variants.
@@ -226,10 +223,7 @@
 	// Rust does not support installing to the product image yet.
 	vendorSpecific := mctx.SocSpecific() || mctx.DeviceSpecific()
 
-	if mctx.ProductSpecific() {
-		mctx.PropertyErrorf("product_specific",
-			"Rust modules do not yet support installing to the product image.")
-	} else if Bool(mod.VendorProperties.Double_loadable) {
+	if Bool(mod.VendorProperties.Double_loadable) {
 		mctx.PropertyErrorf("double_loadable",
 			"Rust modules do not yet support double loading")
 	}
@@ -243,6 +237,11 @@
 			mctx.PropertyErrorf("vendor", "Vendor-only dylibs are not yet supported, use rust_library_rlib.")
 		}
 	}
+	if mctx.ProductSpecific() {
+		if lib, ok := mod.compiler.(libraryInterface); ok && lib.buildDylib() {
+			mctx.PropertyErrorf("product", "Product-only dylibs are not yet supported, use rust_library_rlib.")
+		}
+	}
 
 	cc.MutateImage(mctx, mod)
 
diff --git a/rust/image_test.go b/rust/image_test.go
index 95e788f..8185872 100644
--- a/rust/image_test.go
+++ b/rust/image_test.go
@@ -103,3 +103,93 @@
 		}
        `)
 }
+
+func checkInstallPartition(t *testing.T, ctx *android.TestContext, name, variant, expected string) {
+	mod := ctx.ModuleForTests(name, variant).Module().(*Module)
+	partitionDefined := false
+	checkPartition := func(specific bool, partition string) {
+		if specific {
+			if expected != partition && !partitionDefined {
+				// The variant is installed to the 'partition'
+				t.Errorf("%s variant of %q must not be installed to %s partition", variant, name, partition)
+			}
+			partitionDefined = true
+		} else {
+			// The variant is not installed to the 'partition'
+			if expected == partition {
+				t.Errorf("%s variant of %q must be installed to %s partition", variant, name, partition)
+			}
+		}
+	}
+	socSpecific := func(m *Module) bool {
+		return m.SocSpecific()
+	}
+	deviceSpecific := func(m *Module) bool {
+		return m.DeviceSpecific()
+	}
+	productSpecific := func(m *Module) bool {
+		return m.ProductSpecific() || m.productSpecificModuleContext()
+	}
+	systemExtSpecific := func(m *Module) bool {
+		return m.SystemExtSpecific()
+	}
+	checkPartition(socSpecific(mod), "vendor")
+	checkPartition(deviceSpecific(mod), "odm")
+	checkPartition(productSpecific(mod), "product")
+	checkPartition(systemExtSpecific(mod), "system_ext")
+	if !partitionDefined && expected != "system" {
+		t.Errorf("%s variant of %q is expected to be installed to %s partition,"+
+			" but installed to system partition", variant, name, expected)
+	}
+}
+
+func TestInstallPartition(t *testing.T) {
+	t.Parallel()
+	t.Helper()
+	ctx := testRust(t, `
+		rust_binary {
+			name: "sample_system",
+			crate_name: "sample",
+			srcs: ["foo.rs"],
+		}
+		rust_binary {
+			name: "sample_system_ext",
+			crate_name: "sample",
+			srcs: ["foo.rs"],
+			system_ext_specific: true,
+		}
+		rust_binary {
+			name: "sample_product",
+			crate_name: "sample",
+			srcs: ["foo.rs"],
+			product_specific: true,
+		}
+		rust_binary {
+			name: "sample_vendor",
+			crate_name: "sample",
+			srcs: ["foo.rs"],
+			vendor: true,
+		}
+		rust_binary {
+			name: "sample_odm",
+			crate_name: "sample",
+			srcs: ["foo.rs"],
+			device_specific: true,
+		}
+		rust_binary {
+			name: "sample_all_available",
+			crate_name: "sample",
+			srcs: ["foo.rs"],
+			vendor_available: true,
+			product_available: true,
+		}
+	`)
+
+	checkInstallPartition(t, ctx, "sample_system", binaryCoreVariant, "system")
+	checkInstallPartition(t, ctx, "sample_system_ext", binaryCoreVariant, "system_ext")
+	checkInstallPartition(t, ctx, "sample_product", binaryProductVariant, "product")
+	checkInstallPartition(t, ctx, "sample_vendor", binaryVendorVariant, "vendor")
+	checkInstallPartition(t, ctx, "sample_odm", binaryVendorVariant, "odm")
+
+	checkInstallPartition(t, ctx, "sample_all_available", binaryCoreVariant, "system")
+}
diff --git a/rust/rust.go b/rust/rust.go
index 28a300b..67e0d7c 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -208,6 +208,11 @@
 			}
 			return android.Paths{}, nil
 		}
+	case "unstripped":
+		if mod.compiler != nil {
+			return android.PathsIfNonNil(mod.compiler.unstrippedOutputFilePath()), nil
+		}
+		return nil, nil
 	default:
 		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
 	}
@@ -619,6 +624,31 @@
 	return false
 }
 
+func (mod *Module) IsFuzzModule() bool {
+	if _, ok := mod.compiler.(*fuzzDecorator); ok {
+		return true
+	}
+	return false
+}
+
+func (mod *Module) FuzzModuleStruct() fuzz.FuzzModule {
+	return mod.FuzzModule
+}
+
+func (mod *Module) FuzzPackagedModule() fuzz.FuzzPackagedModule {
+	if fuzzer, ok := mod.compiler.(*fuzzDecorator); ok {
+		return fuzzer.fuzzPackagedModule
+	}
+	panic(fmt.Errorf("FuzzPackagedModule called on non-fuzz module: %q", mod.BaseModuleName()))
+}
+
+func (mod *Module) FuzzSharedLibraries() android.Paths {
+	if fuzzer, ok := mod.compiler.(*fuzzDecorator); ok {
+		return fuzzer.sharedLibraries
+	}
+	panic(fmt.Errorf("FuzzSharedLibraries called on non-fuzz module: %q", mod.BaseModuleName()))
+}
+
 func (mod *Module) UnstrippedOutputFile() android.Path {
 	if mod.compiler != nil {
 		return mod.compiler.unstrippedOutputFilePath()
diff --git a/rust/rust_test.go b/rust/rust_test.go
index 97bd541..3bcd58c 100644
--- a/rust/rust_test.go
+++ b/rust/rust_test.go
@@ -83,6 +83,10 @@
 	rlibVendorVariant     = "android_vendor.29_arm64_armv8-a_rlib_rlib-std"
 	sharedRecoveryVariant = "android_recovery_arm64_armv8-a_shared"
 	rlibRecoveryVariant   = "android_recovery_arm64_armv8-a_rlib_rlib-std"
+	binaryCoreVariant     = "android_arm64_armv8-a"
+	binaryVendorVariant   = "android_vendor.29_arm64_armv8-a"
+	binaryProductVariant  = "android_product.29_arm64_armv8-a"
+	binaryRecoveryVariant = "android_recovery_arm64_armv8-a"
 )
 
 func testRustVndkFs(t *testing.T, bp string, fs android.MockFS) *android.TestContext {
diff --git a/rust/source_provider.go b/rust/source_provider.go
index 4f8d22b..3236bce 100644
--- a/rust/source_provider.go
+++ b/rust/source_provider.go
@@ -31,9 +31,8 @@
 	Properties SourceProviderProperties
 
 	// The first file in OutputFiles must be the library entry point.
-	OutputFiles      android.Paths
-	subAndroidMkOnce map[SubAndroidMkProvider]bool
-	subName          string
+	OutputFiles android.Paths
+	subName     string
 }
 
 var _ SourceProvider = (*BaseSourceProvider)(nil)
diff --git a/rust/testing.go b/rust/testing.go
index 4796f69..24ca3d6 100644
--- a/rust/testing.go
+++ b/rust/testing.go
@@ -104,6 +104,7 @@
 			crate_name: "std",
 			srcs: ["foo.rs"],
 			no_stdlibs: true,
+			product_available: true,
 			host_supported: true,
 			vendor_available: true,
 			vendor_ramdisk_available: true,
diff --git a/scripts/check_boot_jars/package_allowed_list.txt b/scripts/check_boot_jars/package_allowed_list.txt
index a02c195..08bd80c 100644
--- a/scripts/check_boot_jars/package_allowed_list.txt
+++ b/scripts/check_boot_jars/package_allowed_list.txt
@@ -75,6 +75,7 @@
 jdk\.internal\.ref
 jdk\.internal\.reflect
 jdk\.internal\.util
+jdk\.internal\.util\.jar
 jdk\.internal\.vm\.annotation
 jdk\.net
 org\.w3c\.dom
diff --git a/scripts/test_config_fixer.py b/scripts/test_config_fixer.py
index 3dbc22e..07e01a1 100644
--- a/scripts/test_config_fixer.py
+++ b/scripts/test_config_fixer.py
@@ -31,6 +31,8 @@
 KNOWN_PREPARERS = ['com.android.tradefed.targetprep.TestAppInstallSetup',
                    'com.android.tradefed.targetprep.suite.SuiteApkInstaller']
 
+MAINLINE_CONTROLLER = 'com.android.tradefed.testtype.suite.module.MainlineTestModuleController'
+
 def parse_args():
   """Parse commandline arguments."""
 
@@ -41,6 +43,8 @@
                       help=('overwrite package fields in the test config'))
   parser.add_argument('--test-file-name', default='', dest='test_file_name',
                       help=('overwrite test file name in the test config'))
+  parser.add_argument('--mainline-package-name', default='', dest='mainline_package_name',
+                      help=('overwrite mainline module package name in the test config'))
   parser.add_argument('input', help='input test config file')
   parser.add_argument('output', help='output test config file')
   return parser.parse_args()
@@ -72,6 +76,16 @@
         if option.getAttribute('name') == "test-file-name":
           option.setAttribute('value', test_file_name)
 
+def overwrite_mainline_module_package_name(test_config_doc, mainline_package_name):
+
+  test_config = parse_test_config(test_config_doc)
+
+  for obj in get_children_with_tag(test_config, 'object'):
+    if obj.getAttribute('class') == MAINLINE_CONTROLLER:
+      for option in get_children_with_tag(obj, 'option'):
+        if option.getAttribute('name') == "mainline-module-package-name":
+          option.setAttribute('value', mainline_package_name)
+
 def main():
   """Program entry point."""
   try:
@@ -88,6 +102,9 @@
     if args.test_file_name:
       overwrite_test_file_name(doc, args.test_file_name)
 
+    if args.mainline_package_name:
+      overwrite_mainline_module_package_name(doc, args.mainline_package_name)
+
     with open(args.output, 'w') as f:
       write_xml(f, doc)
 
diff --git a/scripts/test_config_fixer_test.py b/scripts/test_config_fixer_test.py
index 39ce5b3..699f91e 100644
--- a/scripts/test_config_fixer_test.py
+++ b/scripts/test_config_fixer_test.py
@@ -23,6 +23,8 @@
 
 import test_config_fixer
 
+from manifest import write_xml
+
 sys.dont_write_bytecode = True
 
 
@@ -117,5 +119,39 @@
     self.assertEqual(expected, output.getvalue())
 
 
+class OverwriteMainlineModulePackageNameTest(unittest.TestCase):
+  """ Unit tests for overwrite_mainline_module_package_name function """
+
+  test_config = (
+      '<?xml version="1.0" encoding="utf-8"?>\n'
+      '<configuration description="Runs some tests.">\n'
+      '    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">\n'
+      '        <option name="test-file-name" value="foo.apk"/>\n'
+      '    </target_preparer>\n'
+      '    <test class="com.android.tradefed.testtype.AndroidJUnitTest">\n'
+      '        <option name="package" value="com.android.foo"/>\n'
+      '        <option name="runtime-hint" value="20s"/>\n'
+      '    </test>\n'
+      '    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">\n'
+      '        <option name="enable" value="true"/>\n'
+      '        <option name="mainline-module-package-name" value="%s"/>\n'
+      '    </object>\n'
+      '</configuration>\n')
+
+  def test_testappinstallsetup(self):
+    doc = minidom.parseString(self.test_config % ("com.android.old.package.name"))
+
+    test_config_fixer.overwrite_mainline_module_package_name(doc, "com.android.new.package.name")
+    output = io.StringIO()
+    test_config_fixer.write_xml(output, doc)
+
+    # Only the mainline module package name should be updated. Format the xml
+    # with minidom first to avoid mismatches due to trivial reformatting.
+    expected = io.StringIO()
+    write_xml(expected, minidom.parseString(self.test_config % ("com.android.new.package.name")))
+    self.maxDiff = None
+    self.assertEqual(expected.getvalue(), output.getvalue())
+
+
 if __name__ == '__main__':
   unittest.main(verbosity=2)
diff --git a/tests/apex_cc_module_arch_variant_tests.sh b/tests/apex_cc_module_arch_variant_tests.sh
index 97e6576..b0cade1 100755
--- a/tests/apex_cc_module_arch_variant_tests.sh
+++ b/tests/apex_cc_module_arch_variant_tests.sh
@@ -56,7 +56,7 @@
 
 # Number of CppCompile actions with arch variant flag
 actions_with_arch_variant_num=$(call_bazel aquery --config=bp2build --config=ci --config=android \
-  'mnemonic("CppCompile", deps(//build/bazel/examples/apex/minimal:build.bazel.examples.apex.minimal.apex))' | grep -c "\-march=$ARCH_VARIANT_CFLAG")
+  'mnemonic("CppCompile", deps(//build/bazel/examples/apex/minimal:build.bazel.examples.apex.minimal.apex))' | grep -c \'-march=$ARCH_VARIANT_CFLAG\')
 
 # Number of all CppCompile actions
 all_cppcompile_actions_num=0
diff --git a/tests/bp2build_bazel_test.sh b/tests/bp2build_bazel_test.sh
index 6477dac..878b4a1 100755
--- a/tests/bp2build_bazel_test.sh
+++ b/tests/bp2build_bazel_test.sh
@@ -140,7 +140,7 @@
   # NOTE: We don't actually use the extra BUILD file for anything here
   run_bazel build --config=android --config=bp2build --config=ci //foo/...
 
-  local the_answer_file="$(find -L bazel-out -name the_answer.txt)"
+  local -r the_answer_file="$(find -L bazel-out -name the_answer.txt)"
   if [[ ! -f "${the_answer_file}" ]]; then
     fail "Expected the_answer.txt to be generated, but was missing"
   fi
@@ -156,6 +156,49 @@
   eval "${_save_trap}"
 }
 
+function test_bp2build_symlinks_files {
+  setup
+  mkdir -p foo
+  touch foo/BLANK1
+  touch foo/BLANK2
+  touch foo/F2D
+  touch foo/BUILD
+
+  run_soong bp2build
+
+  if [[ -e "./out/soong/workspace/foo/BUILD" ]]; then
+    fail "./out/soong/workspace/foo/BUILD should be omitted"
+  fi
+  for file in BLANK1 BLANK2 F2D
+  do
+    if [[ ! -L "./out/soong/workspace/foo/$file" ]]; then
+      fail "./out/soong/workspace/foo/$file should exist"
+    fi
+  done
+  local -r BLANK1_BEFORE=$(stat -c %y "./out/soong/workspace/foo/BLANK1")
+
+  rm foo/BLANK2
+  rm foo/F2D
+  mkdir foo/F2D
+  touch foo/F2D/BUILD
+
+  run_soong bp2build
+
+  if [[ -e "./out/soong/workspace/foo/BUILD" ]]; then
+    fail "./out/soong/workspace/foo/BUILD should be omitted"
+  fi
+  local -r BLANK1_AFTER=$(stat -c %y "./out/soong/workspace/foo/BLANK1")
+  if [[ "$BLANK1_AFTER" != "$BLANK1_BEFORE" ]]; then
+    fail "./out/soong/workspace/foo/BLANK1 should be untouched"
+  fi
+  if [[  -e "./out/soong/workspace/foo/BLANK2" ]]; then
+    fail "./out/soong/workspace/foo/BLANK2 should be removed"
+  fi
+  if [[ -L "./out/soong/workspace/foo/F2D" ]] || [[ ! -d "./out/soong/workspace/foo/F2D" ]]; then
+    fail "./out/soong/workspace/foo/F2D should be a dir"
+  fi
+}
+
 function test_cc_correctness {
   setup
 
diff --git a/tests/lib.sh b/tests/lib.sh
index 26bdc97..0973beb 100644
--- a/tests/lib.sh
+++ b/tests/lib.sh
@@ -94,6 +94,8 @@
   symlink_directory external/go-cmp
   symlink_directory external/golang-protobuf
   symlink_directory external/starlark-go
+  symlink_directory external/python
+  symlink_directory external/sqlite
 
   touch "$MOCK_TOP/Android.bp"
 }
diff --git a/ui/build/upload.go b/ui/build/upload.go
index 9f14bdd..9959e6f 100644
--- a/ui/build/upload.go
+++ b/ui/build/upload.go
@@ -18,16 +18,21 @@
 // another.
 
 import (
+	"bufio"
 	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
+	"strconv"
+	"strings"
 	"time"
 
+	"android/soong/shared"
 	"android/soong/ui/metrics"
 
 	"google.golang.org/protobuf/proto"
 
+	bazel_metrics_proto "android/soong/ui/metrics/bazel_metrics_proto"
 	upload_proto "android/soong/ui/metrics/upload_proto"
 )
 
@@ -73,12 +78,113 @@
 	return metricsFiles
 }
 
+func parseTimingToNanos(str string) int64 {
+	millisString := removeDecimalPoint(str)
+	timingMillis, _ := strconv.ParseInt(millisString, 10, 64)
+	return timingMillis * 1000000
+}
+
+func parsePercentageToTenThousandths(str string) int32 {
+	percentageString := removeDecimalPoint(str)
+	//remove the % at the end of the string
+	percentage := strings.ReplaceAll(percentageString, "%", "")
+	percentagePortion, _ := strconv.ParseInt(percentage, 10, 32)
+	return int32(percentagePortion)
+}
+
+func removeDecimalPoint(numString string) string {
+	// The format is always 0.425 or 10.425
+	return strings.ReplaceAll(numString, ".", "")
+}
+
+func parseTotal(line string) int64 {
+	words := strings.Fields(line)
+	timing := words[3]
+	return parseTimingToNanos(timing)
+}
+
+func parsePhaseTiming(line string) bazel_metrics_proto.PhaseTiming {
+	words := strings.Fields(line)
+	getPhaseNameAndTimingAndPercentage := func([]string) (string, int64, int32) {
+		// Sample lines include:
+		// Total launch phase time   0.011 s    2.59%
+		// Total target pattern evaluation phase time  0.011 s    2.59%
+		var beginning int
+		var end int
+		for ind, word := range words {
+			if word == "Total" {
+				beginning = ind + 1
+			} else if beginning > 0 && word == "phase" {
+				end = ind
+				break
+			}
+		}
+		phaseName := strings.Join(words[beginning:end], " ")
+
+		// end is now "phase" - advance by 2 for timing and 4 for percentage
+		percentageString := words[end+4]
+		timingString := words[end+2]
+		timing := parseTimingToNanos(timingString)
+		percentagePortion := parsePercentageToTenThousandths(percentageString)
+		return phaseName, timing, percentagePortion
+	}
+
+	phaseName, timing, portion := getPhaseNameAndTimingAndPercentage(words)
+	phaseTiming := bazel_metrics_proto.PhaseTiming{}
+	phaseTiming.DurationNanos = &timing
+	phaseTiming.PortionOfBuildTime = &portion
+
+	phaseTiming.PhaseName = &phaseName
+	return phaseTiming
+}
+
+func processBazelMetrics(bazelProfileFile string, bazelMetricsFile string, ctx Context) {
+	if bazelProfileFile == "" {
+		return
+	}
+
+	readBazelProto := func(filepath string) bazel_metrics_proto.BazelMetrics {
+		//serialize the proto, write it
+		bazelMetrics := bazel_metrics_proto.BazelMetrics{}
+
+		file, err := os.ReadFile(filepath)
+		if err != nil {
+			ctx.Fatalln("Error reading metrics file\n", err)
+		}
+
+		scanner := bufio.NewScanner(strings.NewReader(string(file)))
+		scanner.Split(bufio.ScanLines)
+
+		var phaseTimings []*bazel_metrics_proto.PhaseTiming
+		for scanner.Scan() {
+			line := scanner.Text()
+			if strings.HasPrefix(line, "Total run time") {
+				total := parseTotal(line)
+				bazelMetrics.Total = &total
+			} else if strings.HasPrefix(line, "Total") {
+				phaseTiming := parsePhaseTiming(line)
+				phaseTimings = append(phaseTimings, &phaseTiming)
+			}
+		}
+		bazelMetrics.PhaseTimings = phaseTimings
+
+		return bazelMetrics
+	}
+
+	if _, err := os.Stat(bazelProfileFile); err != nil {
+		// We can assume bazel didn't run if the profile doesn't exist
+		return
+	}
+	bazelProto := readBazelProto(bazelProfileFile)
+	shared.Save(&bazelProto, bazelMetricsFile)
+}
+
 // UploadMetrics uploads a set of metrics files to a server for analysis.
 // The metrics files are first copied to a temporary directory
 // and the uploader is then executed in the background to allow the user/system
 // to continue working. Soong communicates to the uploader through the
 // upload_proto raw protobuf file.
-func UploadMetrics(ctx Context, config Config, simpleOutput bool, buildStarted time.Time, paths ...string) {
+func UploadMetrics(ctx Context, config Config, simpleOutput bool, buildStarted time.Time, bazelProfileFile string, bazelMetricsFile string, paths ...string) {
 	ctx.BeginTrace(metrics.RunSetupTool, "upload_metrics")
 	defer ctx.EndTrace()
 
@@ -88,6 +194,7 @@
 		return
 	}
 
+	processBazelMetrics(bazelProfileFile, bazelMetricsFile, ctx)
 	// Several of the files might be directories.
 	metricsFiles := pruneMetricsFiles(paths)
 	if len(metricsFiles) == 0 {
diff --git a/ui/build/upload_test.go b/ui/build/upload_test.go
index 764a1e1..58d9237 100644
--- a/ui/build/upload_test.go
+++ b/ui/build/upload_test.go
@@ -29,6 +29,30 @@
 	"android/soong/ui/logger"
 )
 
+func writeBazelProfileFile(dir string) error {
+	contents := `
+
+=== PHASE SUMMARY INFORMATION ===
+
+Total launch phase time                              1.193 s   15.77%
+Total init phase time                                1.092 s   14.44%
+Total target pattern evaluation phase time           0.580 s    7.67%
+Total interleaved loading-and-analysis phase time    3.646 s   48.21%
+Total preparation phase time                         0.022 s    0.30%
+Total execution phase time                           0.993 s   13.13%
+Total finish phase time                              0.036 s    0.48%
+---------------------------------------------------------------------
+Total run time                                       7.563 s  100.00%
+
+Critical path (178 ms):
+       Time Percentage   Description
+     178 ms  100.00%   action 'BazelWorkspaceStatusAction stable-status.txt'
+
+`
+	file := filepath.Join(dir, "bazel_metrics.txt")
+	return os.WriteFile(file, []byte(contents), 0666)
+}
+
 func TestPruneMetricsFiles(t *testing.T) {
 	rootDir := t.TempDir()
 
@@ -84,12 +108,12 @@
 	}, {
 		description: "non-existent metrics files no upload",
 		uploader:    "echo",
-		files:       []string{"metrics_file_1", "metrics_file_2", "metrics_file_3"},
+		files:       []string{"metrics_file_1", "metrics_file_2", "metrics_file_3, bazel_metrics.pb"},
 	}, {
 		description: "trigger upload",
 		uploader:    "echo",
 		createFiles: true,
-		files:       []string{"metrics_file_1", "metrics_file_2"},
+		files:       []string{"metrics_file_1", "metrics_file_2, bazel_metrics.pb"},
 	}}
 
 	for _, tt := range tests {
@@ -130,6 +154,9 @@
 					}
 				}
 			}
+			if err := writeBazelProfileFile(outDir); err != nil {
+				t.Fatalf("failed to create bazel profile file in dir: %v", outDir)
+			}
 
 			config := Config{&configImpl{
 				environ: &Environment{
@@ -139,7 +166,7 @@
 				metricsUploader: tt.uploader,
 			}}
 
-			UploadMetrics(ctx, config, false, time.Now(), metricsFiles...)
+			UploadMetrics(ctx, config, false, time.Now(), "out/bazel_metrics.txt", "out/bazel_metrics.pb", metricsFiles...)
 		})
 	}
 }
@@ -194,8 +221,79 @@
 				metricsUploader: "echo",
 			}}
 
-			UploadMetrics(ctx, config, true, time.Now(), metricsFile)
+			UploadMetrics(ctx, config, true, time.Now(), "", "", metricsFile)
 			t.Errorf("got nil, expecting %q as a failure", tt.expectedErr)
 		})
 	}
 }
+
+func TestParsePercentageToTenThousandths(t *testing.T) {
+	// 2.59% should be returned as 259 - representing 259/10000 of the build
+	percentage := parsePercentageToTenThousandths("2.59%")
+	if percentage != 259 {
+		t.Errorf("Expected percentage to be returned as ten-thousandths. Expected 259, have %d\n", percentage)
+	}
+
+	// Test without a leading digit
+	percentage = parsePercentageToTenThousandths(".52%")
+	if percentage != 52 {
+		t.Errorf("Expected percentage to be returned as ten-thousandths. Expected 52, have %d\n", percentage)
+	}
+}
+
+func TestParseTimingToNanos(t *testing.T) {
+	// This parses from seconds (with millis precision) and returns nanos
+	timingNanos := parseTimingToNanos("0.111")
+	if timingNanos != 111000000 {
+		t.Errorf("Error parsing timing. Expected 111000, have %d\n", timingNanos)
+	}
+
+	// Test without a leading digit
+	timingNanos = parseTimingToNanos(".112")
+	if timingNanos != 112000000 {
+		t.Errorf("Error parsing timing. Expected 112000, have %d\n", timingNanos)
+	}
+}
+
+func TestParsePhaseTiming(t *testing.T) {
+	// Sample lines include:
+	// Total launch phase time   0.011 s    2.59%
+	// Total target pattern evaluation phase time  0.012 s    4.59%
+
+	line1 := "Total launch phase time   0.011 s    2.59%"
+	timing := parsePhaseTiming(line1)
+
+	if timing.GetPhaseName() != "launch" {
+		t.Errorf("Failed to parse phase name. Expected launch, have %s\n", timing.GetPhaseName())
+	} else if timing.GetDurationNanos() != 11000000 {
+		t.Errorf("Failed to parse duration nanos. Expected 11000000, have %d\n", timing.GetDurationNanos())
+	} else if timing.GetPortionOfBuildTime() != 259 {
+		t.Errorf("Failed to parse portion of build time. Expected 259, have %d\n", timing.GetPortionOfBuildTime())
+	}
+
+	// Test with a multiword phase name
+	line2 := "Total target pattern evaluation phase  time  0.012 s    4.59%"
+
+	timing = parsePhaseTiming(line2)
+	if timing.GetPhaseName() != "target pattern evaluation" {
+		t.Errorf("Failed to parse phase name. Expected target pattern evaluation, have %s\n", timing.GetPhaseName())
+	} else if timing.GetDurationNanos() != 12000000 {
+		t.Errorf("Failed to parse duration nanos. Expected 12000000, have %d\n", timing.GetDurationNanos())
+	} else if timing.GetPortionOfBuildTime() != 459 {
+		t.Errorf("Failed to parse portion of build time. Expected 459, have %d\n", timing.GetPortionOfBuildTime())
+	}
+}
+
+func TestParseTotal(t *testing.T) {
+	// Total line is in the form of:
+	// Total run time                                       7.563 s  100.00%
+
+	line := "Total run time                                       7.563 s  100.00%"
+
+	total := parseTotal(line)
+
+	// Only the seconds field is parsed, as nanos
+	if total != 7563000000 {
+		t.Errorf("Failed to parse total build time. Expected 7563000000, have %d\n", total)
+	}
+}
diff --git a/ui/metrics/Android.bp b/ui/metrics/Android.bp
index 2301c56..bd1517c 100644
--- a/ui/metrics/Android.bp
+++ b/ui/metrics/Android.bp
@@ -22,6 +22,7 @@
     deps: [
         "golang-protobuf-proto",
         "soong-ui-bp2build_metrics_proto",
+        "soong-ui-bazel_metrics_proto",
         "soong-ui-metrics_upload_proto",
         "soong-ui-metrics_proto",
         "soong-ui-mk_metrics_proto",
@@ -73,6 +74,18 @@
 }
 
 bootstrap_go_package {
+    name: "soong-ui-bazel_metrics_proto",
+    pkgPath: "android/soong/ui/metrics/bazel_metrics_proto",
+    deps: [
+        "golang-protobuf-reflect-protoreflect",
+        "golang-protobuf-runtime-protoimpl",
+    ],
+    srcs: [
+        "bazel_metrics_proto/bazel_metrics.pb.go",
+    ],
+}
+
+bootstrap_go_package {
     name: "soong-ui-mk_metrics_proto",
     pkgPath: "android/soong/ui/metrics/mk_metrics_proto",
     deps: [
diff --git a/ui/metrics/bp2build_progress_metrics_proto/BUILD.bazel b/ui/metrics/bp2build_progress_metrics_proto/BUILD.bazel
index 356b188..f6c6df8 100644
--- a/ui/metrics/bp2build_progress_metrics_proto/BUILD.bazel
+++ b/ui/metrics/bp2build_progress_metrics_proto/BUILD.bazel
@@ -22,6 +22,6 @@
 
 py_proto_library(
     name = "bp2build_py_proto",
+    visibility = ["//build/bazel/scripts/bp2build_progress:__pkg__"],
     deps = [":bp2build_proto"],
-    visibility = ["//build/bazel/scripts/bp2build-progress:__pkg__"],
 )
diff --git a/zip/cmd/main.go b/zip/cmd/main.go
index cbc73ed..def76aa 100644
--- a/zip/cmd/main.go
+++ b/zip/cmd/main.go
@@ -163,6 +163,7 @@
 	parallelJobs := flags.Int("parallel", runtime.NumCPU(), "number of parallel threads to use")
 	cpuProfile := flags.String("cpuprofile", "", "write cpu profile to file")
 	traceFile := flags.String("trace", "", "write trace to file")
+	sha256Checksum := flags.Bool("sha256", false, "add a zip header to each file containing its SHA256 digest")
 
 	flags.Var(&rootPrefix{}, "P", "path prefix within the zip at which to place files")
 	flags.Var(&listFiles{}, "l", "file containing list of files to zip")
@@ -224,6 +225,7 @@
 		WriteIfChanged:           *writeIfChanged,
 		StoreSymlinks:            *symlinks,
 		IgnoreMissingFiles:       *ignoreMissingFiles,
+		Sha256Checksum:           *sha256Checksum,
 	})
 	if err != nil {
 		fmt.Fprintln(os.Stderr, "error:", err.Error())
diff --git a/zip/zip.go b/zip/zip.go
index 955fe68..6f1a8ad 100644
--- a/zip/zip.go
+++ b/zip/zip.go
@@ -17,8 +17,11 @@
 import (
 	"bytes"
 	"compress/flate"
+	"crypto/sha256"
+	"encoding/binary"
 	"errors"
 	"fmt"
+	"hash"
 	"hash/crc32"
 	"io"
 	"io/ioutil"
@@ -38,6 +41,14 @@
 	"android/soong/third_party/zip"
 )
 
+// Sha256HeaderID is a custom Header ID for the `extra` field in
+// the file header to store the SHA checksum.
+const Sha256HeaderID = 0x4967
+
+// Sha256HeaderSignature is the signature to verify that the extra
+// data block is used to store the SHA checksum.
+const Sha256HeaderSignature = 0x9514
+
 // Block size used during parallel compression of a single file.
 const parallelBlockSize = 1 * 1024 * 1024 // 1MB
 
@@ -231,6 +242,8 @@
 
 	stderr io.Writer
 	fs     pathtools.FileSystem
+
+	sha256Checksum bool
 }
 
 type zipEntry struct {
@@ -257,6 +270,7 @@
 	WriteIfChanged           bool
 	StoreSymlinks            bool
 	IgnoreMissingFiles       bool
+	Sha256Checksum           bool
 
 	Stderr     io.Writer
 	Filesystem pathtools.FileSystem
@@ -280,6 +294,7 @@
 		ignoreMissingFiles: args.IgnoreMissingFiles,
 		stderr:             args.Stderr,
 		fs:                 args.Filesystem,
+		sha256Checksum:     args.Sha256Checksum,
 	}
 
 	if z.fs == nil {
@@ -782,15 +797,17 @@
 		// this based on actual buffer sizes in RateLimit.
 		ze.futureReaders = make(chan chan io.Reader, (fileSize/parallelBlockSize)+1)
 
-		// Calculate the CRC in the background, since reading the entire
-		// file could take a while.
+		// Calculate the CRC and SHA256 in the background, since reading
+		// the entire file could take a while.
 		//
 		// We could split this up into chunks as well, but it's faster
 		// than the compression. Due to the Go Zip API, we also need to
 		// know the result before we can begin writing the compressed
 		// data out to the zipfile.
+		//
+		// We calculate SHA256 only if `-sha256` is set.
 		wg.Add(1)
-		go z.crcFile(r, ze, compressChan, wg)
+		go z.checksumFileAsync(r, ze, compressChan, wg)
 
 		for start := int64(0); start < fileSize; start += parallelBlockSize {
 			sr := io.NewSectionReader(r, start, parallelBlockSize)
@@ -829,20 +846,53 @@
 	return nil
 }
 
-func (z *ZipWriter) crcFile(r io.Reader, ze *zipEntry, resultChan chan *zipEntry, wg *sync.WaitGroup) {
+func (z *ZipWriter) checksumFileAsync(r io.ReadSeeker, ze *zipEntry, resultChan chan *zipEntry, wg *sync.WaitGroup) {
 	defer wg.Done()
 	defer z.cpuRateLimiter.Finish()
 
+	z.checksumFile(r, ze)
+
+	resultChan <- ze
+	close(resultChan)
+}
+
+func (z *ZipWriter) checksumFile(r io.ReadSeeker, ze *zipEntry) {
 	crc := crc32.NewIEEE()
-	_, err := io.Copy(crc, r)
+	writers := []io.Writer{crc}
+
+	var shaHasher hash.Hash
+	if z.sha256Checksum && !ze.fh.Mode().IsDir() {
+		shaHasher = sha256.New()
+		writers = append(writers, shaHasher)
+	}
+
+	w := io.MultiWriter(writers...)
+
+	_, err := io.Copy(w, r)
 	if err != nil {
 		z.errors <- err
 		return
 	}
 
 	ze.fh.CRC32 = crc.Sum32()
-	resultChan <- ze
-	close(resultChan)
+	if shaHasher != nil {
+		z.appendSHAToExtra(ze, shaHasher.Sum(nil))
+	}
+}
+
+func (z *ZipWriter) appendSHAToExtra(ze *zipEntry, checksum []byte) {
+	// The block of SHA256 checksum consist of:
+	// - Header ID, equals to Sha256HeaderID (2 bytes)
+	// - Data size (2 bytes)
+	// - Data block:
+	//   - Signature, equals to Sha256HeaderSignature (2 bytes)
+	//   - Data, SHA checksum value
+	var buf []byte
+	buf = binary.LittleEndian.AppendUint16(buf, Sha256HeaderID)
+	buf = binary.LittleEndian.AppendUint16(buf, uint16(len(checksum)+2))
+	buf = binary.LittleEndian.AppendUint16(buf, Sha256HeaderSignature)
+	buf = append(buf, checksum...)
+	ze.fh.Extra = append(ze.fh.Extra, buf...)
 }
 
 func (z *ZipWriter) compressPartialFile(r io.Reader, dict []byte, last bool, resultChan chan io.Reader, wg *sync.WaitGroup) {
@@ -894,17 +944,9 @@
 }
 
 func (z *ZipWriter) compressWholeFile(ze *zipEntry, r io.ReadSeeker, compressChan chan *zipEntry) {
+	z.checksumFile(r, ze)
 
-	crc := crc32.NewIEEE()
-	_, err := io.Copy(crc, r)
-	if err != nil {
-		z.errors <- err
-		return
-	}
-
-	ze.fh.CRC32 = crc.Sum32()
-
-	_, err = r.Seek(0, 0)
+	_, err := r.Seek(0, 0)
 	if err != nil {
 		z.errors <- err
 		return
diff --git a/zip/zip_test.go b/zip/zip_test.go
index c4832dc..e7fdea8 100644
--- a/zip/zip_test.go
+++ b/zip/zip_test.go
@@ -16,6 +16,7 @@
 
 import (
 	"bytes"
+	"encoding/hex"
 	"hash/crc32"
 	"io"
 	"os"
@@ -35,6 +36,10 @@
 	fileEmpty    = []byte("")
 	fileManifest = []byte("Manifest-Version: 1.0\nCreated-By: soong_zip\n\n")
 
+	sha256FileA = "d53eda7a637c99cc7fb566d96e9fa109bf15c478410a3f5eb4d4c4e26cd081f6"
+	sha256FileB = "430c56c5818e62bcb6d478901ef86284e97714c138f3c86aa14fd6a84b7ce5d3"
+	sha256FileC = "31c5ab6111f1d6aa13c2c4e92bb3c0f7c76b61b42d141af1e846eb7f6586a51c"
+
 	fileCustomManifest  = []byte("Custom manifest: true\n")
 	customManifestAfter = []byte("Manifest-Version: 1.0\nCreated-By: soong_zip\nCustom manifest: true\n\n")
 )
@@ -67,6 +72,20 @@
 	}
 }
 
+func fhWithSHA256(name string, contents []byte, method uint16, sha256 string) zip.FileHeader {
+	h := fh(name, contents, method)
+	// The extra field contains 38 bytes, including 2 bytes of header ID, 2 bytes
+	// of size, 2 bytes of signature, and 32 bytes of checksum data block.
+	var extra [38]byte
+	// The first 6 bytes contains Sha256HeaderID (0x4967), size (unit(34)) and
+	// Sha256HeaderSignature (0x9514)
+	copy(extra[0:], []byte{103, 73, 34, 0, 20, 149})
+	sha256Bytes, _ := hex.DecodeString(sha256)
+	copy(extra[6:], sha256Bytes)
+	h.Extra = append(h.Extra, extra[:]...)
+	return h
+}
+
 func fhManifest(contents []byte) zip.FileHeader {
 	return zip.FileHeader{
 		Name:               "META-INF/MANIFEST.MF",
@@ -87,13 +106,18 @@
 	}
 }
 
-func fhDir(name string) zip.FileHeader {
+type fhDirOptions struct {
+	extra []byte
+}
+
+func fhDir(name string, opts fhDirOptions) zip.FileHeader {
 	return zip.FileHeader{
 		Name:               name,
 		Method:             zip.Store,
 		CRC32:              crc32.ChecksumIEEE(nil),
 		UncompressedSize64: 0,
 		ExternalAttrs:      (syscall.S_IFDIR|0755)<<16 | 0x10,
+		Extra:              opts.extra,
 	}
 }
 
@@ -114,6 +138,7 @@
 		manifest           string
 		storeSymlinks      bool
 		ignoreMissingFiles bool
+		sha256Checksum     bool
 
 		files []zip.FileHeader
 		err   error
@@ -320,10 +345,10 @@
 			emulateJar:       true,
 
 			files: []zip.FileHeader{
-				fhDir("META-INF/"),
+				fhDir("META-INF/", fhDirOptions{extra: []byte{254, 202, 0, 0}}),
 				fhManifest(fileManifest),
-				fhDir("a/"),
-				fhDir("a/a/"),
+				fhDir("a/", fhDirOptions{}),
+				fhDir("a/a/", fhDirOptions{}),
 				fh("a/a/a", fileA, zip.Deflate),
 				fh("a/a/b", fileB, zip.Deflate),
 			},
@@ -338,10 +363,10 @@
 			manifest:         "manifest.txt",
 
 			files: []zip.FileHeader{
-				fhDir("META-INF/"),
+				fhDir("META-INF/", fhDirOptions{extra: []byte{254, 202, 0, 0}}),
 				fhManifest(customManifestAfter),
-				fhDir("a/"),
-				fhDir("a/a/"),
+				fhDir("a/", fhDirOptions{}),
+				fhDir("a/a/", fhDirOptions{}),
 				fh("a/a/a", fileA, zip.Deflate),
 				fh("a/a/b", fileB, zip.Deflate),
 			},
@@ -355,8 +380,8 @@
 			dirEntries:       true,
 
 			files: []zip.FileHeader{
-				fhDir("a/"),
-				fhDir("a/a/"),
+				fhDir("a/", fhDirOptions{}),
+				fhDir("a/a/", fhDirOptions{}),
 				fh("a/a/a", fileA, zip.Deflate),
 				fh("a/a/b", fileB, zip.Deflate),
 			},
@@ -412,6 +437,23 @@
 				fh("a/a/a", fileA, zip.Deflate),
 			},
 		},
+		{
+			name: "generate SHA256 checksum",
+			args: fileArgsBuilder().
+				File("a/a/a").
+				File("a/a/b").
+				File("a/a/c").
+				File("c"),
+			compressionLevel: 9,
+			sha256Checksum:   true,
+
+			files: []zip.FileHeader{
+				fhWithSHA256("a/a/a", fileA, zip.Deflate, sha256FileA),
+				fhWithSHA256("a/a/b", fileB, zip.Deflate, sha256FileB),
+				fhWithSHA256("a/a/c", fileC, zip.Deflate, sha256FileC),
+				fhWithSHA256("c", fileC, zip.Deflate, sha256FileC),
+			},
+		},
 
 		// errors
 		{
@@ -465,6 +507,7 @@
 			args.ManifestSourcePath = test.manifest
 			args.StoreSymlinks = test.storeSymlinks
 			args.IgnoreMissingFiles = test.ignoreMissingFiles
+			args.Sha256Checksum = test.sha256Checksum
 			args.Filesystem = mockFs
 			args.Stderr = &bytes.Buffer{}
 
@@ -555,6 +598,11 @@
 					t.Errorf("incorrect file %s method want %v got %v", want.Name,
 						want.Method, got.Method)
 				}
+
+				if !bytes.Equal(want.Extra, got.Extra) {
+					t.Errorf("incorrect file %s extra want %v got %v", want.Name,
+						want.Extra, got.Extra)
+				}
 			}
 		})
 	}